Skip to content

Commit c24b397

Browse files
committed
progress on the certificate bootstrapping #2
1 parent 5610819 commit c24b397

File tree

9 files changed

+108
-69
lines changed

9 files changed

+108
-69
lines changed

client-common/src/main/java/eu/arrowhead/client/common/ArrowheadClientMain.java

Lines changed: 12 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,7 @@
1717
import java.io.BufferedReader;
1818
import java.io.IOException;
1919
import java.io.InputStreamReader;
20-
import java.net.MalformedURLException;
2120
import java.net.URI;
22-
import java.net.URL;
2321
import java.security.KeyStore;
2422
import java.security.cert.X509Certificate;
2523
import java.util.ArrayList;
@@ -50,7 +48,7 @@ public abstract class ArrowheadClientMain {
5048
protected String baseUri;
5149
protected String base64PublicKey;
5250
protected HttpServer server;
53-
protected final TypeSafeProperties props = Utility.getProp();
51+
protected TypeSafeProperties props = Utility.getProp();
5452

5553
private boolean daemon;
5654
private ClientType clientType;
@@ -137,31 +135,27 @@ protected void startSecureServer(Set<Class<?>> classes, String[] packages) {
137135
config.registerClasses(classes);
138136
config.packages(packages);
139137

140-
String keystorePath = props.getProperty("keystore");
141-
String keystorePass = props.getProperty("keystorepass");
142-
String keyPass = props.getProperty("keypass");
143-
String truststorePath = props.getProperty("truststore");
144-
String truststorePass = props.getProperty("truststorepass");
145-
146138
SSLContextConfigurator sslCon = new SSLContextConfigurator();
147-
sslCon.setKeyStoreFile(keystorePath);
148-
sslCon.setKeyStorePass(keystorePass);
149-
sslCon.setKeyPass(keyPass);
150-
sslCon.setTrustStoreFile(truststorePath);
151-
sslCon.setTrustStorePass(truststorePass);
139+
sslCon.setKeyStoreFile(props.getProperty("keystore"));
140+
sslCon.setKeyStorePass(props.getProperty("keystorepass"));
141+
sslCon.setKeyPass(props.getProperty("keypass"));
142+
sslCon.setTrustStoreFile(props.getProperty("truststore"));
143+
sslCon.setTrustStorePass(props.getProperty("truststorepass"));
152144
if (!sslCon.validateConfiguration(true)) {
153145
try {
154-
sslCon = doCertificateBootstrapping();
155-
} catch (ArrowheadException | MalformedURLException e) {
146+
sslCon = CertificateBootstrapper.bootstrap(clientType);
147+
props = Utility.getProp();
148+
} catch (ArrowheadException e) {
156149
throw new AuthException(
157-
"Provided SSL context is not valid, check the certificate or the config files! Certificate bootstrapping failed with: " + e.getMessage());
150+
"Provided SSL context is not valid, check the certificate or the config files! Certificate bootstrapping failed with: " + e.getMessage(),
151+
e);
158152
}
159153
}
160154

161155
SSLContext sslContext = sslCon.createSSLContext();
162156
Utility.setSSLContext(sslContext);
163157

164-
KeyStore keyStore = SecurityUtils.loadKeyStore(keystorePath, keystorePass);
158+
KeyStore keyStore = SecurityUtils.loadKeyStore(props.getProperty("keystore"), props.getProperty("keystorepass"));
165159
X509Certificate serverCert = SecurityUtils.getFirstCertFromKeyStore(keyStore);
166160
base64PublicKey = Base64.getEncoder().encodeToString(serverCert.getPublicKey().getEncoded());
167161
System.out.println("Server PublicKey Base64: " + base64PublicKey);
@@ -193,22 +187,4 @@ protected void shutdown() {
193187
System.out.println(clientType + " Server stopped");
194188
System.exit(0);
195189
}
196-
197-
protected SSLContextConfigurator doCertificateBootstrapping() throws MalformedURLException {
198-
URL url = new URL(props.getProperty("cert_authority_url"));
199-
if (!Utility.isHostAvailable(url.getHost(), url.getPort(), 3000)) {
200-
throw new ArrowheadException("CA Core System is unavailable at " + props.getProperty("cert_authority_url"));
201-
}
202-
203-
String cloudCN = CertificateBootstrapper.getCloudCommonNameFromCA();
204-
String systemName = clientType.name().replaceAll("_", "").toLowerCase() + System.currentTimeMillis();
205-
String keyStorePassword = props.getProperty("keystorepass") != null ? props.getProperty("keystorepass") : Utility.getRandomPassword();
206-
String trustStorePassword = props.getProperty("truststorepass") != null ? props.getProperty("truststorepass") : Utility.getRandomPassword();
207-
208-
KeyStore[] keyStores = CertificateBootstrapper
209-
.obtainSystemAndCloudKeyStore(systemName, cloudCN, keyStorePassword.toCharArray(), trustStorePassword.toCharArray());
210-
211-
//updating app.conf is missing in the end yet
212-
return null;
213-
}
214190
}

client-common/src/main/java/eu/arrowhead/client/common/CertificateBootstrapper.java

Lines changed: 68 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,63 +2,107 @@
22

33
import eu.arrowhead.client.common.exception.ArrowheadException;
44
import eu.arrowhead.client.common.exception.AuthException;
5+
import eu.arrowhead.client.common.misc.ClientType;
56
import eu.arrowhead.client.common.misc.SecurityUtils;
7+
import eu.arrowhead.client.common.misc.TypeSafeProperties;
68
import eu.arrowhead.client.common.model.CertificateSigningRequest;
79
import eu.arrowhead.client.common.model.CertificateSigningResponse;
810
import java.io.ByteArrayInputStream;
911
import java.io.File;
1012
import java.io.FileInputStream;
1113
import java.io.FileOutputStream;
1214
import java.io.IOException;
15+
import java.net.MalformedURLException;
16+
import java.net.URL;
1317
import java.security.KeyPair;
1418
import java.security.KeyPairGenerator;
1519
import java.security.KeyStore;
1620
import java.security.KeyStoreException;
1721
import java.security.NoSuchAlgorithmException;
1822
import java.security.PublicKey;
19-
import java.security.Security;
2023
import java.security.cert.Certificate;
2124
import java.security.cert.CertificateException;
2225
import java.security.cert.CertificateFactory;
2326
import java.security.cert.X509Certificate;
2427
import java.security.spec.InvalidKeySpecException;
2528
import java.util.Base64;
29+
import java.util.HashMap;
2630
import java.util.Map;
2731
import java.util.Map.Entry;
2832
import java.util.Properties;
2933
import java.util.ServiceConfigurationError;
3034
import javax.ws.rs.core.Response;
3135
import javax.ws.rs.core.Response.Status;
3236
import org.bouncycastle.asn1.x500.X500Name;
33-
import org.bouncycastle.jce.provider.BouncyCastleProvider;
3437
import org.bouncycastle.operator.ContentSigner;
3538
import org.bouncycastle.operator.OperatorCreationException;
3639
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
3740
import org.bouncycastle.pkcs.PKCS10CertificationRequest;
3841
import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequestBuilder;
42+
import org.glassfish.grizzly.ssl.SSLContextConfigurator;
3943

4044
public final class CertificateBootstrapper {
4145

42-
private static String CA_URL;
43-
44-
//Static initializer: get the Certificate Authority URL when the class first gets used
45-
static {
46-
Security.addProvider(new BouncyCastleProvider());
47-
CA_URL = Utility.getProp().getProperty("cert_authority_url");
48-
if (CA_URL == null) {
49-
throw new ArrowheadException("cert_authority_url property is not provided in config file, but certificate bootstrapping is requested!",
50-
Status.BAD_REQUEST.getStatusCode());
51-
}
52-
}
46+
private static TypeSafeProperties props = Utility.getProp();
47+
private static String CA_URL = props.getProperty("cert_authority_url");
5348

5449
private CertificateBootstrapper() {
5550
throw new AssertionError("CertificateBootstrapper is a non-instantiable class");
5651
}
5752

53+
public static SSLContextConfigurator bootstrap(ClientType clientType) {
54+
//Check if the CA is available at the provided URL (with socket opening)
55+
URL url;
56+
try {
57+
url = new URL(CA_URL);
58+
} catch (MalformedURLException e) {
59+
throw new ArrowheadException(
60+
"cert_authority_url property is not (properly) provided in config file, but certificate bootstrapping is requested!",
61+
Status.BAD_REQUEST.getStatusCode(), e);
62+
}
63+
if (!Utility.isHostAvailable(url.getHost(), url.getPort(), 3000)) {
64+
throw new ArrowheadException("CA Core System is unavailable at " + props.getProperty("cert_authority_url"));
65+
}
66+
67+
//Prepare the data needed to generate the certificate(s)
68+
String cloudCN = CertificateBootstrapper.getCloudCommonNameFromCA();
69+
String systemName = clientType.name().replaceAll("_", "").toLowerCase() + System.currentTimeMillis();
70+
String keyStorePassword = !Utility.isBlank(props.getProperty("keystorepass")) ? props.getProperty("keystorepass") : Utility.getRandomPassword();
71+
String trustStorePassword =
72+
!Utility.isBlank(props.getProperty("truststorepass")) ? props.getProperty("truststorepass") : Utility.getRandomPassword();
73+
74+
//Obtain the keystore and truststore
75+
KeyStore[] keyStores = CertificateBootstrapper
76+
.obtainSystemAndCloudKeyStore(systemName, cloudCN, keyStorePassword.toCharArray(), trustStorePassword.toCharArray());
77+
78+
//Save the keystores to file
79+
String certPathPrefix = "config" + File.separator + "certificates";
80+
CertificateBootstrapper.saveKeyStoreToFile(keyStores[0], keyStorePassword.toCharArray(), systemName + ".p12", certPathPrefix);
81+
CertificateBootstrapper.saveKeyStoreToFile(keyStores[1], trustStorePassword.toCharArray(), "truststore.p12", certPathPrefix);
82+
83+
//Update app.conf with the new values
84+
Map<String, String> secureParameters = new HashMap<>();
85+
secureParameters.put("keystore", certPathPrefix + File.separator + systemName + ".p12");
86+
secureParameters.put("keystorepass", keyStorePassword);
87+
secureParameters.put("keypass", keyStorePassword);
88+
secureParameters.put("truststore", certPathPrefix + File.separator + "truststore.p12");
89+
secureParameters.put("truststorepass", trustStorePassword);
90+
CertificateBootstrapper.updateConfigurationFiles("config" + File.separator + "app.conf", secureParameters);
91+
92+
//Return a new, valid SSLContextConfigurator
93+
SSLContextConfigurator sslCon = new SSLContextConfigurator();
94+
sslCon.setKeyStoreFile(certPathPrefix + File.separator + systemName + ".p12");
95+
sslCon.setKeyStorePass(keyStorePassword);
96+
sslCon.setKeyPass(keyStorePassword);
97+
sslCon.setTrustStoreFile(certPathPrefix + File.separator + "truststore.p12");
98+
sslCon.setTrustStorePass(trustStorePassword);
99+
return sslCon;
100+
}
101+
58102
/*
59103
Gets the Cloud Common Name from the Certificate Authority Core System, proper URL is read from the config file
60104
*/
61-
public static String getCloudCommonNameFromCA() {
105+
private static String getCloudCommonNameFromCA() {
62106
Response caResponse = Utility.sendRequest(CA_URL, "GET", null);
63107
return caResponse.readEntity(String.class);
64108
}
@@ -74,7 +118,7 @@ public static String getCloudCommonNameFromCA() {
74118
*
75119
* @see <a href="https://tools.ietf.org/html/rfc5280.html#section-7.1">X.509 certificate specification: distinguished names</a>
76120
*/
77-
public static KeyStore obtainSystemKeyStore(String commonName, char[] keyStorePassword) {
121+
private static KeyStore obtainSystemKeyStore(String commonName, char[] keyStorePassword) {
78122
CertificateSigningResponse signingResponse = getSignedCertFromCA(commonName);
79123

80124
//Get the reconstructed certs from the CA response
@@ -110,7 +154,7 @@ public static KeyStore obtainSystemKeyStore(String commonName, char[] keyStorePa
110154
*
111155
* @see <a href="https://tools.ietf.org/html/rfc5280.html#section-7.1">X.509 certificate specification: distinguished names</a>
112156
*/
113-
public static KeyStore[] obtainSystemAndCloudKeyStore(String systemName, String cloudCN, char[] systemKsPassword, char[] cloudKsPassword) {
157+
private static KeyStore[] obtainSystemAndCloudKeyStore(String systemName, String cloudCN, char[] systemKsPassword, char[] cloudKsPassword) {
114158
String commonName = systemName + "." + cloudCN;
115159
CertificateSigningResponse signingResponse = getSignedCertFromCA(commonName);
116160

@@ -139,7 +183,7 @@ Create the Cloud KeyStore (with a different KeyStore Entry type,
139183
KeyStore ks = KeyStore.getInstance("pkcs12");
140184
ks.load(null, cloudKsPassword);
141185
KeyStore.Entry certEntry = new KeyStore.TrustedCertificateEntry(cloudCert);
142-
ks.setEntry(cloudCN, certEntry, new KeyStore.PasswordProtection(cloudKsPassword));
186+
ks.setEntry(cloudCN, certEntry, null);
143187
keyStores[1] = ks;
144188
} catch (KeyStoreException | IOException | NoSuchAlgorithmException | CertificateException e) {
145189
throw new ArrowheadException("System key store creation failed!", e);
@@ -157,11 +201,11 @@ Create the Cloud KeyStore (with a different KeyStore Entry type,
157201
* @param saveLocation optional relative or absolute path where the keystore should be saved (must point to directory). If not provided, the file
158202
* will be placed into the working directory.
159203
*/
160-
public static void saveKeyStoreToFile(KeyStore keyStore, char[] keyStorePassword, String fileName, String saveLocation) {
204+
private static void saveKeyStoreToFile(KeyStore keyStore, char[] keyStorePassword, String fileName, String saveLocation) {
161205
if (keyStore == null || fileName == null) {
162206
throw new NullPointerException("Saving the key store to file is not possible, key store or file name is null!");
163207
}
164-
if (!fileName.endsWith(".p12") || !fileName.endsWith(".jks")) {
208+
if (!(fileName.endsWith(".p12") || fileName.endsWith(".jks"))) {
165209
throw new ServiceConfigurationError("File name should end with its extension! (p12 or jks)");
166210
}
167211
if (saveLocation != null) {
@@ -179,7 +223,7 @@ public static void saveKeyStoreToFile(KeyStore keyStore, char[] keyStorePassword
179223
/*
180224
Updates the given properties file with the given key-value pairs.
181225
*/
182-
public static void updateConfigurationFiles(String configLocation, Map<String, String> configValues) {
226+
private static void updateConfigurationFiles(String configLocation, Map<String, String> configValues) {
183227
try {
184228
FileInputStream in = new FileInputStream(configLocation);
185229
Properties props = new Properties();
@@ -195,12 +239,15 @@ public static void updateConfigurationFiles(String configLocation, Map<String, S
195239
} catch (IOException e) {
196240
throw new ArrowheadException("Cert bootstrapping: IOException during configuration file update", e);
197241
}
242+
props = Utility.getProp();
243+
CA_URL = props.getProperty("cert_authority_url");
198244
}
199245

200246
/*
201247
Authorization Public Key is used by ArrowheadProviders to verify the signatures by the Authorization Core System in secure mode
202248
*/
203-
public static PublicKey getAuthorizationPublicKey() {
249+
//TODO also save it to file and update props
250+
private static PublicKey getAuthorizationPublicKey() {
204251
Response caResponse = Utility.sendRequest(CA_URL + "/auth", "GET", null);
205252
try {
206253
return SecurityUtils.getPublicKey(caResponse.readEntity(String.class));

client-common/src/main/java/eu/arrowhead/client/common/Utility.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -379,4 +379,8 @@ public static String getRandomPassword() {
379379
PasswordGenerator generator = new PasswordGenerator.Builder().useDigits(true).useLower(true).useUpper(true).usePunctuation(false).build();
380380
return generator.generate(12);
381381
}
382+
383+
public static boolean isBlank(final String str) {
384+
return (str == null || "".equals(str.trim()));
385+
}
382386
}

consumer/src/main/java/eu/arrowhead/client/consumer/ConsumerMain.java

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
package eu.arrowhead.client.consumer;
1111

12+
import eu.arrowhead.client.common.CertificateBootstrapper;
1213
import eu.arrowhead.client.common.Utility;
1314
import eu.arrowhead.client.common.exception.ArrowheadException;
1415
import eu.arrowhead.client.common.misc.ClientType;
@@ -27,15 +28,15 @@
2728
import javax.swing.JOptionPane;
2829
import javax.ws.rs.core.Response;
2930
import javax.ws.rs.core.UriBuilder;
30-
import org.glassfish.jersey.SslConfigurator;
31+
import org.glassfish.grizzly.ssl.SSLContextConfigurator;
3132

3233
public class ConsumerMain {
3334

3435
private static boolean isSecure;
3536
private static String orchestratorUrl;
36-
private static final TypeSafeProperties props = Utility.getProp();
37+
private static TypeSafeProperties props = Utility.getProp();
3738

38-
public static void main(String[] args) {
39+
private ConsumerMain(String[] args) {
3940
//Prints the working directory for extra information. Working directory should always contain a config folder with the app.conf file!
4041
System.out.println("Working directory: " + System.getProperty("user.dir"));
4142

@@ -63,8 +64,12 @@ public static void main(String[] args) {
6364
JOptionPane.showMessageDialog(null, label, "Provider Response", JOptionPane.INFORMATION_MESSAGE);
6465
}
6566

67+
public static void main(String[] args) {
68+
new ConsumerMain(args);
69+
}
70+
6671
//Compiles the payload for the orchestration request
67-
private static ServiceRequestForm compileSRF() {
72+
private ServiceRequestForm compileSRF() {
6873
/*
6974
ArrowheadSystem: systemName, (address, port, authenticationInfo)
7075
Since this Consumer skeleton will not receive HTTP requests (does not provide any services on its own),
@@ -104,7 +109,7 @@ Since this Consumer skeleton will not receive HTTP requests (does not provide an
104109
return srf;
105110
}
106111

107-
private static double consumeService(String providerUrl) {
112+
private double consumeService(String providerUrl) {
108113
/*
109114
Sending request to the provider, to the acquired URL. The method type and payload should be known beforehand.
110115
If needed, compile the request payload here, before sending the request.
@@ -141,19 +146,25 @@ private static double consumeService(String providerUrl) {
141146
*/
142147

143148
//DO NOT MODIFY - Gets the correct URL where the orchestration requests needs to be sent (from app.conf config file + command line argument)
144-
private static void getOrchestratorUrl(String[] args) {
149+
private void getOrchestratorUrl(String[] args) {
145150
String orchAddress = props.getProperty("orch_address", "0.0.0.0");
146151
int orchInsecurePort = props.getIntProperty("orch_insecure_port", 8440);
147152
int orchSecurePort = props.getIntProperty("orch_secure_port", 8441);
148153

149154
for (String arg : args) {
150155
if (arg.equals("-tls")) {
151156
isSecure = true;
152-
SslConfigurator sslConfig = SslConfigurator.newInstance().trustStoreFile(props.getProperty("truststore"))
153-
.trustStorePassword(props.getProperty("truststorepass"))
154-
.keyStoreFile(props.getProperty("keystore")).keyStorePassword(props.getProperty("keystorepass"))
155-
.keyPassword(props.getProperty("keypass"));
156-
SSLContext sslContext = sslConfig.createSSLContext();
157+
SSLContextConfigurator sslCon = new SSLContextConfigurator();
158+
sslCon.setKeyStoreFile(props.getProperty("keystore"));
159+
sslCon.setKeyStorePass(props.getProperty("keystorepass"));
160+
sslCon.setKeyPass(props.getProperty("keypass"));
161+
sslCon.setTrustStoreFile(props.getProperty("truststore"));
162+
sslCon.setTrustStorePass(props.getProperty("truststorepass"));
163+
if (!sslCon.validateConfiguration(true)) {
164+
sslCon = CertificateBootstrapper.bootstrap(ClientType.CONSUMER);
165+
props = Utility.getProp();
166+
}
167+
SSLContext sslContext = sslCon.createSSLContext();
157168
Utility.setSSLContext(sslContext);
158169
break;
159170
}
@@ -169,7 +180,7 @@ private static void getOrchestratorUrl(String[] args) {
169180

170181
/* NO NEED TO MODIFY (for basic functionality)
171182
Sends the orchestration request to the Orchestrator, and compiles the URL for the first provider received from the OrchestrationResponse */
172-
private static String sendOrchestrationRequest(ServiceRequestForm srf) {
183+
private String sendOrchestrationRequest(ServiceRequestForm srf) {
173184
//Sending a POST request to the orchestrator (URL, method, payload)
174185
Response postResponse = Utility.sendRequest(orchestratorUrl, "POST", srf);
175186
//Parsing the orchestrator response

provider/src/main/java/eu/arrowhead/client/provider/FullProviderMain.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ protected void startSecureServer(Set<Class<?>> classes, String[] packages) {
112112
privateKey = SecurityUtils.getPrivateKey(keyStore, keystorePass);
113113

114114
//Load the Authorization Core System public key
115+
//TODO expect and extract a public key only from crt
115116
String authCertPath = props.getProperty("authorization_cert");
116117
KeyStore authKeyStore = SecurityUtils.createKeyStoreFromCert(authCertPath);
117118
X509Certificate authCert = SecurityUtils.getFirstCertFromKeyStore(authKeyStore);

publisher/config/certificate/tempsensor.testcloud1.jks renamed to publisher/config/certificates/tempsensor.testcloud1.jks

File renamed without changes.
File renamed without changes.

subscriber/config/certificate/tempsensor.testcloud1.jks renamed to subscriber/config/certificates/tempsensor.testcloud1.jks

File renamed without changes.
File renamed without changes.

0 commit comments

Comments
 (0)