From ce573fb1374e2becd2884b1f928c8733d21cc960 Mon Sep 17 00:00:00 2001 From: Uarbekov Date: Mon, 31 Jul 2023 15:42:08 +0600 Subject: [PATCH 1/3] fix(uat): add PrivateKeyInfo for tls --- .../aws/greengrass/testing/util/SslUtil.java | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/uat/custom-components/client-java-paho/src/main/java/com/aws/greengrass/testing/util/SslUtil.java b/uat/custom-components/client-java-paho/src/main/java/com/aws/greengrass/testing/util/SslUtil.java index 9924dab61..8aec6c863 100644 --- a/uat/custom-components/client-java-paho/src/main/java/com/aws/greengrass/testing/util/SslUtil.java +++ b/uat/custom-components/client-java-paho/src/main/java/com/aws/greengrass/testing/util/SslUtil.java @@ -6,6 +6,8 @@ package com.aws.greengrass.testing.util; import com.aws.greengrass.testing.mqtt5.client.MqttLib; +import org.bouncycastle.asn1.pkcs.EncryptedPrivateKeyInfo; +import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.openssl.PEMKeyPair; import org.bouncycastle.openssl.PEMParser; @@ -90,11 +92,20 @@ public static SSLSocketFactory getSocketFactory(final String caCrtFile, final St try (PEMParser pemParser = new PEMParser(new InputStreamReader(new ByteArrayInputStream(keyFile.getBytes())))) { object = pemParser.readObject(); JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider("BC"); - key = converter.getKeyPair((PEMKeyPair) object); + + if (object instanceof PrivateKeyInfo) { + PrivateKeyInfo keyInfo = (PrivateKeyInfo) object; + EncryptedPrivateKeyInfo privateKeyInfo = + new EncryptedPrivateKeyInfo(keyInfo.getPrivateKeyAlgorithm(), keyInfo.getEncoded()); + ks.setKeyEntry("private-key", privateKeyInfo.getEncoded(), + new java.security.cert.Certificate[]{cert}); + } else { + key = converter.getKeyPair((PEMKeyPair) object); + ks.setKeyEntry("private-key", key.getPrivate(), "".toCharArray(), + new java.security.cert.Certificate[]{cert}); + } } - ks.setKeyEntry("private-key", key.getPrivate(), "".toCharArray(), - new java.security.cert.Certificate[]{cert}); KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory .getDefaultAlgorithm()); kmf.init(ks, "".toCharArray()); From 27f08ab5225e34fb2215ed24491adcdc353946e6 Mon Sep 17 00:00:00 2001 From: Vitaly Khalmansky Date: Mon, 31 Jul 2023 19:59:37 +0200 Subject: [PATCH 2/3] fix(uat): rework SslUtil --- .../client/paho/Mqtt311ConnectionImpl.java | 5 +- .../testing/mqtt5/client/MqttLib.java | 2 +- .../mqtt5/client/paho/MqttConnectionImpl.java | 5 +- .../aws/greengrass/testing/util/SslUtil.java | 163 ++++++++++-------- .../testing/mqtt5/client/MqttLib.java | 2 +- 5 files changed, 102 insertions(+), 75 deletions(-) diff --git a/uat/custom-components/client-java-paho/src/main/java/com/aws/greengrass/testing/mqtt311/client/paho/Mqtt311ConnectionImpl.java b/uat/custom-components/client-java-paho/src/main/java/com/aws/greengrass/testing/mqtt311/client/paho/Mqtt311ConnectionImpl.java index f53fbae61..965703736 100644 --- a/uat/custom-components/client-java-paho/src/main/java/com/aws/greengrass/testing/mqtt311/client/paho/Mqtt311ConnectionImpl.java +++ b/uat/custom-components/client-java-paho/src/main/java/com/aws/greengrass/testing/mqtt311/client/paho/Mqtt311ConnectionImpl.java @@ -206,8 +206,9 @@ private MqttConnectOptions convertParams(MqttLib.ConnectionParams connectionPara connectionParams.getCert() != null); connectionOptions.setServerURIs(new String[]{uri}); - if (connectionParams.getKey() != null) { - SSLSocketFactory sslSocketFactory = SslUtil.getSocketFactory(connectionParams); + if (connectionParams.getCert() != null) { + SSLSocketFactory sslSocketFactory = SslUtil.getSocketFactory( + connectionParams.getCa(), connectionParams.getCert(), connectionParams.getKey()); connectionOptions.setSocketFactory(sslSocketFactory); } diff --git a/uat/custom-components/client-java-paho/src/main/java/com/aws/greengrass/testing/mqtt5/client/MqttLib.java b/uat/custom-components/client-java-paho/src/main/java/com/aws/greengrass/testing/mqtt5/client/MqttLib.java index 16c37314d..790dbe768 100644 --- a/uat/custom-components/client-java-paho/src/main/java/com/aws/greengrass/testing/mqtt5/client/MqttLib.java +++ b/uat/custom-components/client-java-paho/src/main/java/com/aws/greengrass/testing/mqtt5/client/MqttLib.java @@ -39,7 +39,7 @@ class ConnectionParams { /** Clean session (clean start) flag of CONNECT packet. */ private boolean cleanSession; - /** Content of CA, optional. */ + /** Content of CA list joined by \n, optional. */ private String ca; /** Content of MQTT client's certificate, optional. */ diff --git a/uat/custom-components/client-java-paho/src/main/java/com/aws/greengrass/testing/mqtt5/client/paho/MqttConnectionImpl.java b/uat/custom-components/client-java-paho/src/main/java/com/aws/greengrass/testing/mqtt5/client/paho/MqttConnectionImpl.java index be31f1153..250230206 100644 --- a/uat/custom-components/client-java-paho/src/main/java/com/aws/greengrass/testing/mqtt5/client/paho/MqttConnectionImpl.java +++ b/uat/custom-components/client-java-paho/src/main/java/com/aws/greengrass/testing/mqtt5/client/paho/MqttConnectionImpl.java @@ -317,8 +317,9 @@ private MqttConnectionOptions convertConnectParams(MqttLib.ConnectionParams conn connectionOptions.setServerURIs(new String[]{uri}); connectionOptions.setConnectionTimeout(connectionParams.getConnectionTimeout()); - if (connectionParams.getKey() != null) { - SSLSocketFactory sslSocketFactory = SslUtil.getSocketFactory(connectionParams); + if (connectionParams.getCert() != null) { + SSLSocketFactory sslSocketFactory = SslUtil.getSocketFactory( + connectionParams.getCa(), connectionParams.getCert(), connectionParams.getKey()); connectionOptions.setSocketFactory(sslSocketFactory); } diff --git a/uat/custom-components/client-java-paho/src/main/java/com/aws/greengrass/testing/util/SslUtil.java b/uat/custom-components/client-java-paho/src/main/java/com/aws/greengrass/testing/util/SslUtil.java index 8aec6c863..3d123e36b 100644 --- a/uat/custom-components/client-java-paho/src/main/java/com/aws/greengrass/testing/util/SslUtil.java +++ b/uat/custom-components/client-java-paho/src/main/java/com/aws/greengrass/testing/util/SslUtil.java @@ -5,9 +5,7 @@ package com.aws.greengrass.testing.util; -import com.aws.greengrass.testing.mqtt5.client.MqttLib; -import org.bouncycastle.asn1.pkcs.EncryptedPrivateKeyInfo; -import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; +import lombok.experimental.UtilityClass; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.openssl.PEMKeyPair; import org.bouncycastle.openssl.PEMParser; @@ -17,104 +15,131 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import java.math.BigInteger; import java.security.GeneralSecurityException; -import java.security.KeyPair; +import java.security.KeyFactory; import java.security.KeyStore; +import java.security.PrivateKey; import java.security.Security; +import java.security.cert.Certificate; import java.security.cert.CertificateFactory; -import java.security.cert.X509Certificate; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.RSAPrivateCrtKeySpec; +import java.util.ArrayList; +import java.util.Base64; +import java.util.List; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManagerFactory; +@UtilityClass public final class SslUtil { - private SslUtil() { - } + private static final char[] PASSWORD = "".toCharArray(); - /** - * generate SSL socket factory. - * - * @param connectionParams MQTT connection parameters - * @throws IOException on errors - * @throws GeneralSecurityException on errors - */ - public static SSLSocketFactory getSocketFactory(MqttLib.ConnectionParams connectionParams) - throws IOException, GeneralSecurityException { - return getSocketFactory(connectionParams.getCa(), connectionParams.getCert(), connectionParams.getKey()); + // PKCS#8 format + private static final String PEM_PRIVATE_START = "-----BEGIN PRIVATE KEY-----"; + private static final String PEM_PRIVATE_END = "-----END PRIVATE KEY-----"; + + // PKCS#1 format + private static final String PEM_RSA_PRIVATE_START = "-----BEGIN RSA PRIVATE KEY-----"; + private static final String PEM_RSA_PRIVATE_END = "-----END RSA PRIVATE KEY-----"; + + + static { + Security.addProvider(new BouncyCastleProvider()); } /** - * generate SSL socket factory. + * Generates SSL socket factory. * - * @param caCrtFile certificate authority - * @param crtFile certification - * @param keyFile private key + * @param caList the list of certificate authority + * @param crt the certificate + * @param key the private key + * @return instance of ssl socket factory with attached client credential and CA list * @throws IOException on errors * @throws GeneralSecurityException on errors */ - public static SSLSocketFactory getSocketFactory(final String caCrtFile, final String crtFile, final String keyFile) + public static SSLSocketFactory getSocketFactory(final String caList, final String crt, final String key) throws IOException, GeneralSecurityException { - Security.addProvider(new BouncyCastleProvider()); - // load CA certificate - X509Certificate caCert = null; - InputStream bis = new ByteArrayInputStream(caCrtFile.getBytes()); - CertificateFactory cf = CertificateFactory.getInstance("X.509"); + TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("X509"); - while (bis.available() > 0) { - caCert = (X509Certificate) cf.generateCertificate(bis); - } + // CA certificates are used to authenticate server + KeyStore caKeyStore = KeyStore.getInstance(KeyStore.getDefaultType()); + caKeyStore.load(null, null); - // load client certificate - bis = new ByteArrayInputStream(crtFile.getBytes()); - X509Certificate cert = null; - while (bis.available() > 0) { - cert = (X509Certificate) cf.generateCertificate(bis); + // load CA certificates + List caCertificates = getCerificates(caList); + int certNo = 0; + for (Certificate cert : caCertificates) { + caKeyStore.setCertificateEntry(String.format("ca-certificate-%d", ++certNo), cert); } - - // CA certificate is used to authenticate server - KeyStore caKs = KeyStore.getInstance(KeyStore.getDefaultType()); - caKs.load(null, null); - caKs.setCertificateEntry("ca-certificate", caCert); - TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509"); - tmf.init(caKs); + trustManagerFactory.init(caKeyStore); // client key and certificates are sent to server so it can authenticate - KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType()); - ks.load(null, null); - ks.setCertificateEntry("certificate", cert); + KeyStore credKeyStore = KeyStore.getInstance(KeyStore.getDefaultType()); + credKeyStore.load(null, null); + certNo = 0; + List certificates = getCerificates(crt); + for (Certificate cert : caCertificates) { + credKeyStore.setCertificateEntry(String.format("certificate-%d", ++certNo), cert); + } // load client private key - Object object; - KeyPair key; - try (PEMParser pemParser = new PEMParser(new InputStreamReader(new ByteArrayInputStream(keyFile.getBytes())))) { - object = pemParser.readObject(); - JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider("BC"); - - if (object instanceof PrivateKeyInfo) { - PrivateKeyInfo keyInfo = (PrivateKeyInfo) object; - EncryptedPrivateKeyInfo privateKeyInfo = - new EncryptedPrivateKeyInfo(keyInfo.getPrivateKeyAlgorithm(), keyInfo.getEncoded()); - ks.setKeyEntry("private-key", privateKeyInfo.getEncoded(), - new java.security.cert.Certificate[]{cert}); - } else { - key = converter.getKeyPair((PEMKeyPair) object); - ks.setKeyEntry("private-key", key.getPrivate(), "".toCharArray(), - new java.security.cert.Certificate[]{cert}); - } + PrivateKey privateKey = loadPrivateKey(key); + credKeyStore.setKeyEntry("private-key", privateKey, PASSWORD, certificates.toArray(new Certificate[0])); - } - KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory - .getDefaultAlgorithm()); - kmf.init(ks, "".toCharArray()); + KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + keyManagerFactory.init(credKeyStore, PASSWORD); // finally, create SSL socket factory - SSLContext context = SSLContext.getInstance("TLSv1.2"); - context.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null); + // FIXME: probably that force to use only TLS 1.2 + SSLContext sslContext = SSLContext.getInstance("TLSv1.2"); + sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), null); + + return sslContext.getSocketFactory(); + } + + private static List getCerificates(final String certificatesPem) + throws IOException, GeneralSecurityException { + + List certificates = new ArrayList<>(); + CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); - return context.getSocketFactory(); + try (InputStream bis = new ByteArrayInputStream(certificatesPem.getBytes())) { + while (bis.available() > 0) { + Certificate cert = certificateFactory.generateCertificate(bis); + certificates.add(cert); + } + } + + return certificates; } + private static PrivateKey loadPrivateKey(String privateKeyPem) throws GeneralSecurityException, IOException { + if (privateKeyPem.contains(PEM_PRIVATE_START)) { + // PKCS#8 format + privateKeyPem = privateKeyPem.replace(PEM_PRIVATE_START, "").replace(PEM_PRIVATE_END, ""); + privateKeyPem = privateKeyPem.replaceAll("\\s", ""); + + byte[] pkcs8EncodedKey = Base64.getDecoder().decode(privateKeyPem); + + KeyFactory factory = KeyFactory.getInstance("RSA"); + return factory.generatePrivate(new PKCS8EncodedKeySpec(pkcs8EncodedKey)); + + } else if (privateKeyPem.contains(PEM_RSA_PRIVATE_START)) { + // PKCS#1 format + try (PEMParser pemParser = new PEMParser( + new InputStreamReader( + new ByteArrayInputStream(privateKeyPem.getBytes())))) { + Object object = pemParser.readObject(); + JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider("BC"); + return converter.getKeyPair((PEMKeyPair) object).getPrivate(); + } + } + + throw new GeneralSecurityException("Not supported format of a private key"); + } } diff --git a/uat/custom-components/client-java-sdk/src/main/java/com/aws/greengrass/testing/mqtt5/client/MqttLib.java b/uat/custom-components/client-java-sdk/src/main/java/com/aws/greengrass/testing/mqtt5/client/MqttLib.java index 114424a7b..a010aedef 100644 --- a/uat/custom-components/client-java-sdk/src/main/java/com/aws/greengrass/testing/mqtt5/client/MqttLib.java +++ b/uat/custom-components/client-java-sdk/src/main/java/com/aws/greengrass/testing/mqtt5/client/MqttLib.java @@ -39,7 +39,7 @@ class ConnectionParams { /** Clean session (clean start) flag of CONNECT packet. */ private boolean cleanSession; - /** Content of CA, optional. */ + /** Content of CA list joined by \n, optional. */ private String ca; /** Content of MQTT client's certificate, optional. */ From 75eb1c36be5051a3985d41235f7eb22f893b7162 Mon Sep 17 00:00:00 2001 From: Vitaly Khalmansky Date: Mon, 31 Jul 2023 23:49:13 +0200 Subject: [PATCH 3/3] fix(uat): use list of CA instead of concatenated string --- .../testing/mqtt5/client/MqttLib.java | 4 +- .../mqtt5/client/grpc/GRPCControlServer.java | 3 +- .../aws/greengrass/testing/util/SslUtil.java | 45 ++++++++----------- 3 files changed, 21 insertions(+), 31 deletions(-) diff --git a/uat/custom-components/client-java-paho/src/main/java/com/aws/greengrass/testing/mqtt5/client/MqttLib.java b/uat/custom-components/client-java-paho/src/main/java/com/aws/greengrass/testing/mqtt5/client/MqttLib.java index 790dbe768..16a6e14d2 100644 --- a/uat/custom-components/client-java-paho/src/main/java/com/aws/greengrass/testing/mqtt5/client/MqttLib.java +++ b/uat/custom-components/client-java-paho/src/main/java/com/aws/greengrass/testing/mqtt5/client/MqttLib.java @@ -39,8 +39,8 @@ class ConnectionParams { /** Clean session (clean start) flag of CONNECT packet. */ private boolean cleanSession; - /** Content of CA list joined by \n, optional. */ - private String ca; + /** List of CA, optional. */ + private List ca; /** Content of MQTT client's certificate, optional. */ private String cert; diff --git a/uat/custom-components/client-java-paho/src/main/java/com/aws/greengrass/testing/mqtt5/client/grpc/GRPCControlServer.java b/uat/custom-components/client-java-paho/src/main/java/com/aws/greengrass/testing/mqtt5/client/grpc/GRPCControlServer.java index 3cfad7ca2..a8bda94ac 100644 --- a/uat/custom-components/client-java-paho/src/main/java/com/aws/greengrass/testing/mqtt5/client/grpc/GRPCControlServer.java +++ b/uat/custom-components/client-java-paho/src/main/java/com/aws/greengrass/testing/mqtt5/client/grpc/GRPCControlServer.java @@ -214,8 +214,7 @@ public void createMqttConnection(MqttConnectRequest request, return; } - final String ca = String.join("\n", caList); - connectionParamsBuilder.ca(ca).cert(cert).key(key); + connectionParamsBuilder.ca(caList).cert(cert).key(key); } logger.atInfo().log("createMqttConnection: clientId {} broker {}:{}", clientId, host, port); diff --git a/uat/custom-components/client-java-paho/src/main/java/com/aws/greengrass/testing/util/SslUtil.java b/uat/custom-components/client-java-paho/src/main/java/com/aws/greengrass/testing/util/SslUtil.java index 3d123e36b..b3a2aae57 100644 --- a/uat/custom-components/client-java-paho/src/main/java/com/aws/greengrass/testing/util/SslUtil.java +++ b/uat/custom-components/client-java-paho/src/main/java/com/aws/greengrass/testing/util/SslUtil.java @@ -6,6 +6,8 @@ package com.aws.greengrass.testing.util; import lombok.experimental.UtilityClass; +import org.bouncycastle.cert.X509CertificateHolder; +import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.openssl.PEMKeyPair; import org.bouncycastle.openssl.PEMParser; @@ -13,19 +15,14 @@ import java.io.ByteArrayInputStream; import java.io.IOException; -import java.io.InputStream; import java.io.InputStreamReader; -import java.math.BigInteger; import java.security.GeneralSecurityException; import java.security.KeyFactory; import java.security.KeyStore; import java.security.PrivateKey; import java.security.Security; import java.security.cert.Certificate; -import java.security.cert.CertificateFactory; import java.security.spec.PKCS8EncodedKeySpec; -import java.security.spec.RSAPrivateCrtKeySpec; -import java.util.ArrayList; import java.util.Base64; import java.util.List; import javax.net.ssl.KeyManagerFactory; @@ -60,7 +57,7 @@ public final class SslUtil { * @throws IOException on errors * @throws GeneralSecurityException on errors */ - public static SSLSocketFactory getSocketFactory(final String caList, final String crt, final String key) + public static SSLSocketFactory getSocketFactory(final List caList, final String crt, final String key) throws IOException, GeneralSecurityException { @@ -71,51 +68,45 @@ public static SSLSocketFactory getSocketFactory(final String caList, final Strin caKeyStore.load(null, null); // load CA certificates - List caCertificates = getCerificates(caList); int certNo = 0; - for (Certificate cert : caCertificates) { - caKeyStore.setCertificateEntry(String.format("ca-certificate-%d", ++certNo), cert); + for (String ca : caList) { + Certificate caCertificate = getCerificate(ca); + caKeyStore.setCertificateEntry(String.format("ca-certificate-%d", ++certNo), caCertificate); } trustManagerFactory.init(caKeyStore); // client key and certificates are sent to server so it can authenticate KeyStore credKeyStore = KeyStore.getInstance(KeyStore.getDefaultType()); credKeyStore.load(null, null); - certNo = 0; - List certificates = getCerificates(crt); - for (Certificate cert : caCertificates) { - credKeyStore.setCertificateEntry(String.format("certificate-%d", ++certNo), cert); - } + Certificate certificate = getCerificate(crt); + credKeyStore.setCertificateEntry("certificate", certificate); // load client private key PrivateKey privateKey = loadPrivateKey(key); - credKeyStore.setKeyEntry("private-key", privateKey, PASSWORD, certificates.toArray(new Certificate[0])); + credKeyStore.setKeyEntry("private-key", privateKey, PASSWORD, new Certificate[]{certificate}); KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); keyManagerFactory.init(credKeyStore, PASSWORD); // finally, create SSL socket factory // FIXME: probably that force to use only TLS 1.2 - SSLContext sslContext = SSLContext.getInstance("TLSv1.2"); + // SSLContext sslContext = SSLContext.getInstance("TLSv1.2"); + SSLContext sslContext = SSLContext.getInstance("TLS"); sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), null); return sslContext.getSocketFactory(); } - private static List getCerificates(final String certificatesPem) + private static Certificate getCerificate(final String certificatePem) throws IOException, GeneralSecurityException { - List certificates = new ArrayList<>(); - CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); - - try (InputStream bis = new ByteArrayInputStream(certificatesPem.getBytes())) { - while (bis.available() > 0) { - Certificate cert = certificateFactory.generateCertificate(bis); - certificates.add(cert); - } + try (PEMParser pemParser = new PEMParser( + new InputStreamReader( + new ByteArrayInputStream(certificatePem.getBytes())))) { + X509CertificateHolder certHolder = (X509CertificateHolder) pemParser.readObject(); + JcaX509CertificateConverter certificateConverter = new JcaX509CertificateConverter().setProvider("BC"); + return certificateConverter.getCertificate(certHolder); } - - return certificates; } private static PrivateKey loadPrivateKey(String privateKeyPem) throws GeneralSecurityException, IOException {