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