Skip to content

Commit 703a98a

Browse files
authored
0.62.0
- add ability to manually import certs to truststore - use java's x509trustmanagers for verifying cert chains
2 parents 831d0f3 + 0776ba8 commit 703a98a

File tree

5 files changed

+246
-27
lines changed

5 files changed

+246
-27
lines changed

build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ subprojects {
55
apply plugin: 'maven'
66

77
group 'org.iot-dsa'
8-
version '0.61.0'
8+
version '0.62.0'
99

1010
sourceCompatibility = 1.8
1111
targetCompatibility = 1.8

dslink-v2/src/main/java/com/acuity/iot/dsa/dslink/sys/cert/AnonymousTrustFactory.java

Lines changed: 41 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.acuity.iot.dsa.dslink.sys.cert;
22

33
import java.security.KeyStore;
4+
import java.security.KeyStoreException;
45
import java.security.NoSuchAlgorithmException;
56
import java.security.NoSuchProviderException;
67
import java.security.Provider;
@@ -19,6 +20,7 @@
1920
import javax.net.ssl.TrustManagerFactory;
2021
import javax.net.ssl.TrustManagerFactorySpi;
2122
import javax.net.ssl.X509TrustManager;
23+
import org.iot.dsa.logging.DSLogger;
2224

2325
/**
2426
* Adds support for self signed SSL. If anonymous is not allowed
@@ -33,9 +35,12 @@ public class AnonymousTrustFactory extends TrustManagerFactorySpi {
3335
// Fields
3436
/////////////////////////////////////////////////////////////////
3537

38+
private static String defaultAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
3639
private static SysCertService certManager;
3740
private static X509TrustManager defaultX509Mgr;
41+
private static X509TrustManager localX509Mgr;
3842
private static TrustManager[] trustManagers;
43+
private static DSLogger log = new DSLogger();
3944

4045
/////////////////////////////////////////////////////////////////
4146
// Methods - Public and in alphabetical order by method TrustAnon.
@@ -53,15 +58,26 @@ public void engineInit(KeyStore ks) {
5358
@Override
5459
public void engineInit(ManagerFactoryParameters spec) {
5560
}
61+
62+
// This gets called once on startup, and again every time a new certificate is added to the local truststore.
63+
public static void initLocalTrustManager() throws NoSuchAlgorithmException, KeyStoreException {
64+
TrustManagerFactory fac = TrustManagerFactory.getInstance(defaultAlgorithm);
65+
fac.init(certManager.getLocalTruststore());
66+
for (TrustManager locTm: fac.getTrustManagers()) {
67+
if (locTm instanceof X509TrustManager) {
68+
localX509Mgr = (X509TrustManager) locTm;
69+
break;
70+
}
71+
}
72+
}
5673

5774
/**
5875
* Captures the default trust factory and installs this one.
5976
*/
6077
static void init(SysCertService mgr) {
6178
certManager = mgr;
6279
try {
63-
TrustManagerFactory fac = TrustManagerFactory.getInstance(
64-
TrustManagerFactory.getDefaultAlgorithm());
80+
TrustManagerFactory fac = TrustManagerFactory.getInstance(defaultAlgorithm);
6581
fac.init((KeyStore) null);
6682
trustManagers = fac.getTrustManagers();
6783
if (trustManagers == null) {
@@ -82,6 +98,8 @@ static void init(SysCertService mgr) {
8298
list.add(new MyTrustManager());
8399
trustManagers = list.toArray(new TrustManager[list.size()]);
84100
}
101+
102+
initLocalTrustManager();
85103
} catch (Exception x) {
86104
certManager.error(certManager.getPath(), x);
87105
}
@@ -129,9 +147,15 @@ public void checkClientTrusted(X509Certificate[] chain, String authType)
129147
defaultX509Mgr.checkClientTrusted(chain, authType);
130148
return;
131149
} catch (CertificateException e) {
150+
try {
151+
localX509Mgr.checkClientTrusted(chain, authType);
152+
return;
153+
} catch (CertificateException e1) {
154+
tryAddingRootCertToQuarantine(chain, authType);
155+
throw e1;
156+
}
132157
}
133158
}
134-
checkLocally(chain, authType);
135159
}
136160

137161
@Override
@@ -145,9 +169,15 @@ public void checkServerTrusted(X509Certificate[] chain, String authType)
145169
defaultX509Mgr.checkServerTrusted(chain, authType);
146170
return;
147171
} catch (CertificateException e) {
172+
try {
173+
localX509Mgr.checkServerTrusted(chain, authType);
174+
return;
175+
} catch (CertificateException e1) {
176+
tryAddingRootCertToQuarantine(chain, authType);
177+
throw e1;
178+
}
148179
}
149180
}
150-
checkLocally(chain, authType);
151181
}
152182

153183
@Override
@@ -158,7 +188,7 @@ public X509Certificate[] getAcceptedIssuers() {
158188
return new X509Certificate[0];
159189
}
160190

161-
private void checkLocally(X509Certificate[] chain, String authType)
191+
private void tryAddingRootCertToQuarantine(X509Certificate[] chain, String authType)
162192
throws CertificateException {
163193
Set<X509Certificate> chainAsSet = new HashSet<X509Certificate>();
164194
Collections.addAll(chainAsSet, chain);
@@ -174,20 +204,16 @@ private void checkLocally(X509Certificate[] chain, String authType)
174204
}
175205

176206
if (anchorCert == null) {
177-
throw new CertificateException();
178-
}
179-
180-
if (!certManager.isInTrustStore(anchorCert)) {
181-
certManager.addToQuarantine(anchorCert);
182-
throw new CertificateException();
207+
return;
183208
}
184209

185-
} catch (CertificateVerificationException e1) {
186-
throw new CertificateException();
210+
certManager.addToQuarantine(anchorCert);
211+
} catch (CertificateVerificationException e) {
212+
log.debug("", e);
187213
} catch (NoSuchAlgorithmException e) {
188-
throw new CertificateException();
214+
log.debug("", e);
189215
} catch (NoSuchProviderException e) {
190-
throw new CertificateException();
216+
log.debug("", e);
191217
}
192218
}
193219

dslink-v2/src/main/java/com/acuity/iot/dsa/dslink/sys/cert/CertCollection.java

Lines changed: 110 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,65 @@
11
package com.acuity.iot.dsa.dslink.sys.cert;
22

3+
import java.io.ByteArrayInputStream;
34
import java.security.cert.CertificateEncodingException;
5+
import java.security.cert.CertificateException;
6+
import java.security.cert.CertificateFactory;
47
import java.security.cert.X509Certificate;
58
import java.util.Base64;
69
import java.util.Base64.Encoder;
10+
import java.util.HashMap;
11+
import java.util.Map;
712
import org.iot.dsa.node.DSIObject;
13+
import org.iot.dsa.node.DSInfo;
14+
import org.iot.dsa.node.DSMap;
15+
import org.iot.dsa.node.DSMetadata;
816
import org.iot.dsa.node.DSNode;
17+
import org.iot.dsa.node.DSValueType;
18+
import org.iot.dsa.node.action.ActionInvocation;
19+
import org.iot.dsa.node.action.ActionResult;
20+
import org.iot.dsa.node.action.DSAction;
921
import org.iot.dsa.time.DSTime;
22+
import org.iot.dsa.util.DSException;
1023

1124
/**
1225
* @author Daniel Shapiro
1326
*/
1427
public class CertCollection extends DSNode {
28+
29+
private static final String ADD_CERT = "Add Certificate";
30+
private static final String CERT = "Certificate";
31+
32+
private CertificateFactory certFactory;
33+
34+
private CertificateFactory getCertFactory() {
35+
if (certFactory == null) {
36+
try {
37+
certFactory = CertificateFactory.getInstance("X.509");
38+
} catch (CertificateException e) {
39+
warn("", e);
40+
}
41+
}
42+
return certFactory;
43+
}
1544

1645
public void addCertificate(X509Certificate cert) throws CertificateEncodingException {
1746
String name = certToName(cert);
1847
addCertificate(name, encodeCertificate(cert));
1948
}
2049

2150
public void addCertificate(String name, String cert) {
22-
put(name, new CertNode().updateValue(cert));
51+
CertNode certNode = new CertNode().updateValue(cert);
52+
put(name, certNode);
53+
try {
54+
certNode.getCertManager().onCertAddedToCollection(this, certFromString(cert));
55+
} catch (CertificateException e) {
56+
warn("", e);
57+
}
2358
}
2459

2560
public static String certToName(X509Certificate cert) {
26-
return DSTime.encodeForFiles(DSTime.getCalendar(System.currentTimeMillis()),
27-
new StringBuilder(cert.getIssuerX500Principal().getName()))
28-
.toString();
61+
return DSTime.encodeForFiles(DSTime.getCalendar(System.currentTimeMillis()),
62+
new StringBuilder(cert.getIssuerX500Principal().getName())).toString();
2963
}
3064

3165
public boolean containsCertificate(X509Certificate cert) {
@@ -40,11 +74,83 @@ public boolean containsCertificate(X509Certificate cert) {
4074
return obj != null && obj instanceof CertNode && certStr
4175
.equals(((CertNode) obj).toElement().toString());
4276
}
77+
78+
public Map<String, X509Certificate> getCertificates() {
79+
Map<String, X509Certificate> certs = new HashMap<String, X509Certificate>();
80+
for (DSInfo info: this) {
81+
DSIObject obj = info.get();
82+
if (obj instanceof CertNode) {
83+
String certStr = ((CertNode) obj).toElement().toString();
84+
X509Certificate cert;
85+
try {
86+
cert = certFromString(certStr);
87+
certs.put(info.getName(), cert);
88+
} catch (CertificateException e) {
89+
warn("", e);
90+
}
91+
}
92+
}
93+
return certs;
94+
}
4395

4496
public static String encodeCertificate(X509Certificate cert)
4597
throws CertificateEncodingException {
4698
Encoder encoder = Base64.getEncoder();
4799
return encoder.encodeToString(cert.getEncoded());
48100
}
101+
102+
@Override
103+
protected void declareDefaults() {
104+
super.declareDefaults();
105+
declareDefault(ADD_CERT, makeAddCertAction());
106+
}
107+
108+
@Override
109+
public DSNode remove(DSInfo info) {
110+
DSIObject child = info.get();
111+
if (child instanceof CertNode) {
112+
CertNode certNode = (CertNode) child;
113+
try {
114+
certNode.getCertManager().onCertRemovedFromCollection(this, certFromString(certNode.toElement().toString()));
115+
} catch (CertificateException e) {
116+
warn("", e);
117+
}
118+
}
119+
return super.remove(info);
120+
}
121+
122+
private DSAction makeAddCertAction() {
123+
DSAction act = new DSAction.Parameterless() {
124+
@Override
125+
public ActionResult invoke(DSInfo target, ActionInvocation request) {
126+
((CertCollection) target.get()).addCert(request.getParameters());
127+
return null;
128+
}
129+
};
130+
act.addParameter(CERT, DSValueType.STRING, null).setEditor(DSMetadata.STR_EDITOR_TEXT_AREA);
131+
return act;
132+
}
133+
134+
private void addCert(DSMap parameters) {
135+
String certStr = parameters.getString(CERT);
136+
try {
137+
X509Certificate cert = certFromString(certStr);
138+
addCertificate(cert);
139+
} catch (CertificateException e) {
140+
warn("", e);
141+
DSException.throwRuntime(e);
142+
}
143+
}
144+
145+
private X509Certificate certFromString(String certStr) throws CertificateException {
146+
certStr = certStr.trim();
147+
if (!certStr.startsWith("-----BEGIN CERTIFICATE-----")) {
148+
certStr = "-----BEGIN CERTIFICATE-----\n" + certStr;
149+
}
150+
if (!certStr.endsWith("-----END CERTIFICATE-----")) {
151+
certStr = certStr + "\n-----END CERTIFICATE-----";
152+
}
153+
return (X509Certificate) getCertFactory().generateCertificate(new ByteArrayInputStream(certStr.getBytes()));
154+
}
49155

50156
}

dslink-v2/src/main/java/com/acuity/iot/dsa/dslink/sys/cert/CertNode.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
package com.acuity.iot.dsa.dslink.sys.cert;
22

3+
import java.util.Collection;
34
import org.iot.dsa.node.DSInfo;
45
import org.iot.dsa.node.DSString;
56
import org.iot.dsa.node.DSValueNode;
67
import org.iot.dsa.node.action.ActionInvocation;
78
import org.iot.dsa.node.action.ActionResult;
89
import org.iot.dsa.node.action.DSAction;
10+
import org.iot.dsa.node.action.DeleteAction;
11+
import org.iot.dsa.node.action.DuplicateAction;
12+
import org.iot.dsa.node.action.RenameAction;
913

1014
/**
1115
* @author Daniel Shapiro
@@ -34,6 +38,16 @@ public CertNode updateValue(String newVal) {
3438
put(VALUE, newVal);
3539
return this;
3640
}
41+
42+
@Override
43+
public void getVirtualActions(DSInfo target, Collection<String> bucket) {
44+
super.getVirtualActions(target, bucket);
45+
if (target.isNode() && target.getNode() == this) {
46+
bucket.remove(DeleteAction.DELETE);
47+
bucket.remove(RenameAction.RENAME);
48+
bucket.remove(DuplicateAction.DUPLICATE);
49+
}
50+
}
3751

3852
@Override
3953
protected void declareDefaults() {

0 commit comments

Comments
 (0)