From ca3e4c7751d0cf1a878c5af536a15c62deb382d3 Mon Sep 17 00:00:00 2001 From: Antonis Kouzoupis Date: Thu, 19 Oct 2023 16:04:12 +0200 Subject: [PATCH] [HWORKS-795] Create Java keystore and send it back as part of signing request response (#1417) * [HWORKS-795] Create Java keystore and send it back as part of signing request response [HWORKS-795] Test create keystores * [HWORKS-795] Remove forgotten imports --- .../ca/api/certificates/CSRView.java | 41 +++++ .../api/certificates/HostCertsResource.java | 9 ++ .../io/hops/hopsworks/ca/controllers/PKI.java | 37 +---- .../hopsworks/ca/controllers/PKIUtils.java | 144 ++++++++++++++++++ .../hopsworks/ca/controllers/PKIMocking.java | 2 +- .../ca/controllers/TestCreateKeystores.java | 123 +++++++++++++++ .../ca/controllers/TestPKIUtils.java | 47 +++++- 7 files changed, 365 insertions(+), 38 deletions(-) create mode 100644 hopsworks-ca/src/test/java/io/hops/hopsworks/ca/controllers/TestCreateKeystores.java diff --git a/hopsworks-ca/src/main/java/io/hops/hopsworks/ca/api/certificates/CSRView.java b/hopsworks-ca/src/main/java/io/hops/hopsworks/ca/api/certificates/CSRView.java index 0e8105b367..2aa8669da8 100644 --- a/hopsworks-ca/src/main/java/io/hops/hopsworks/ca/api/certificates/CSRView.java +++ b/hopsworks-ca/src/main/java/io/hops/hopsworks/ca/api/certificates/CSRView.java @@ -47,10 +47,14 @@ @XmlRootElement @ApiModel(value = "Represents a certificate signing request") public class CSRView { + private String privateKey; private String csr; private String signedCert; private String intermediateCaCert; private String rootCaCert; + private String keyStore; + private String trustStore; + private String password; public CSRView() { @@ -74,6 +78,15 @@ public CSRView(String rootCaCert, String intermediateCaCert) { this.rootCaCert = rootCaCert; } + @ApiModelProperty(value = "PKCS8 encoded private key used to create Java keystore") + public String getPrivateKey() { + return privateKey; + } + + public void setPrivateKey(String privateKey) { + this.privateKey = privateKey; + } + @ApiModelProperty(value = "String containing the certificate signing request", required = true) public String getCsr() { return csr; @@ -109,4 +122,32 @@ public String getRootCaCert() { public void setRootCaCert(String rootCaCert) { this.rootCaCert = rootCaCert; } + + @ApiModelProperty(value = "Base64 encoded keystore containing signed certificate and intermediate CA locked by " + + "password") + public String getKeyStore() { + return keyStore; + } + + public void setKeyStore(String keyStore) { + this.keyStore = keyStore; + } + + @ApiModelProperty(value = "Base64 encoded trustore containing Hopsworks root CA certificate locked by password") + public String getTrustStore() { + return trustStore; + } + + public void setTrustStore(String trustStore) { + this.trustStore = trustStore; + } + + @ApiModelProperty(value = "Randomly generated password to protect keystore and truststore") + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } } diff --git a/hopsworks-ca/src/main/java/io/hops/hopsworks/ca/api/certificates/HostCertsResource.java b/hopsworks-ca/src/main/java/io/hops/hopsworks/ca/api/certificates/HostCertsResource.java index 7ce8d376d2..1b08b1f7dc 100644 --- a/hopsworks-ca/src/main/java/io/hops/hopsworks/ca/api/certificates/HostCertsResource.java +++ b/hopsworks-ca/src/main/java/io/hops/hopsworks/ca/api/certificates/HostCertsResource.java @@ -72,6 +72,7 @@ import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.List; +import java.util.logging.Logger; import static io.hops.hopsworks.ca.controllers.CertificateType.HOST; @@ -80,6 +81,7 @@ public class HostCertsResource { private static final String REVOKE_CERTIFICATES_PATTERN = "^%s__.*__[0-9]+.*"; + private static final Logger LOGGER = Logger.getLogger(HostCertsResource.class.getName()); @EJB private NoCacheResponse noCacheResponse; @@ -105,6 +107,13 @@ public Response signCSR(CSRView csrView) throws CAException { String stringifiedCert = pkiUtils.convertToPEM(signedCert); Pair chainOfTrust = pki.getChainOfTrust(pkiUtils.getResponsibleCA(HOST)); CSRView signedCsr = new CSRView(stringifiedCert, chainOfTrust.getLeft(), chainOfTrust.getRight()); + if (!Strings.isNullOrEmpty(csrView.getPrivateKey())) { + PKIUtils.KeyStores keyStores = pkiUtils.createB64Keystores(csrView.getPrivateKey(), signedCert, + pkiUtils.loadCertificate(chainOfTrust.getRight()), pkiUtils.loadCertificate(chainOfTrust.getLeft())); + signedCsr.setKeyStore(keyStores.getKeyStore()); + signedCsr.setTrustStore(keyStores.getTrustStore()); + signedCsr.setPassword(new String(keyStores.getPassword())); + } GenericEntity csrViewGenericEntity = new GenericEntity(signedCsr) { }; return noCacheResponse.getNoCacheResponseBuilder(Response.Status.OK).entity(csrViewGenericEntity).build(); } catch (IOException | GeneralSecurityException | OperatorCreationException | CAInitializationException ex) { diff --git a/hopsworks-ca/src/main/java/io/hops/hopsworks/ca/controllers/PKI.java b/hopsworks-ca/src/main/java/io/hops/hopsworks/ca/controllers/PKI.java index d3f66a565c..0c0d22bfb8 100644 --- a/hopsworks-ca/src/main/java/io/hops/hopsworks/ca/controllers/PKI.java +++ b/hopsworks-ca/src/main/java/io/hops/hopsworks/ca/controllers/PKI.java @@ -92,12 +92,7 @@ import org.bouncycastle.cert.jcajce.JcaX509v2CRLBuilder; import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder; import org.bouncycastle.jce.provider.BouncyCastleProvider; -import org.bouncycastle.openssl.PEMDecryptorProvider; -import org.bouncycastle.openssl.PEMEncryptedKeyPair; -import org.bouncycastle.openssl.PEMKeyPair; import org.bouncycastle.openssl.PEMParser; -import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter; -import org.bouncycastle.openssl.jcajce.JcePEMDecryptorProviderBuilder; import org.bouncycastle.operator.ContentSigner; import org.bouncycastle.operator.OperatorCreationException; import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; @@ -197,7 +192,6 @@ public class PKI { private KeyFactory keyFactory; private JcaX509CertificateConverter converter; private JcaX509CRLConverter crlConverter; - private JcaPEMKeyConverter pemKeyConverter; private CAsConfiguration conf; private static final Map CA_SUBJECT_NAME = new HashMap<>(3); private static final String CA_INIT_LOCK = "caInitLock"; @@ -241,7 +235,6 @@ public void init() { keyFactory = KeyFactory.getInstance("RSA"); converter = new JcaX509CertificateConverter().setProvider(new BouncyCastleProvider()); crlConverter = new JcaX509CRLConverter().setProvider(new BouncyCastleProvider()); - pemKeyConverter = new JcaPEMKeyConverter().setProvider(new BouncyCastleProvider()); configure(); } catch (GeneralSecurityException ex) { throw new RuntimeException("Failed to initialize PKI", ex); @@ -521,7 +514,7 @@ protected Pair loadOrGenerateKeypair(String owner) throws Inva protected KeyPair loadKeyPairFromFile(String owner) throws IOException { Path keyPath = getPathToPrivateKey(owner); - return loadKeyPair(keyPath, getPrivateFileKeyPassword(owner)); + return pkiUtils.loadKeyPair(keyPath, getPrivateFileKeyPassword(owner)); } private String getPrivateFileKeyPassword(String owner) { @@ -552,22 +545,6 @@ private Path getPathToPrivateKey(String owner) throws FileNotFoundException { return path; } - private KeyPair loadKeyPair(Path path, String password) throws IOException { - try (PEMParser pemParser = new PEMParser(new FileReader(path.toFile()))) { - Object object = pemParser.readObject(); - KeyPair kp; - if (object instanceof PEMEncryptedKeyPair) { - PEMEncryptedKeyPair ekp = (PEMEncryptedKeyPair) object; - PEMDecryptorProvider decryptorProvider = new JcePEMDecryptorProviderBuilder().build(password.toCharArray()); - kp = pemKeyConverter.getKeyPair(ekp.decryptKeyPair(decryptorProvider)); - } else { - PEMKeyPair ukp = (PEMKeyPair) object; - kp = pemKeyConverter.getKeyPair(ukp); - } - return kp; - } - } - @TransactionAttribute(TransactionAttributeType.NEVER) protected KeyPair generateKeyPair() { return keyPairGenerator.generateKeyPair(); @@ -633,7 +610,7 @@ protected Pair loadOrGenerateCACertificate(CAType type @VisibleForTesting protected X509Certificate loadCACertificate(CAType type) throws IOException, CertificateException { Path certPath = getCACertificatePath(type); - return loadCertificate(certPath); + return pkiUtils.loadCertificate(certPath); } private Path getCACertificatePath(CAType type) throws FileNotFoundException { @@ -657,16 +634,6 @@ private Path getCACertificatePath(CAType type) throws FileNotFoundException { return path; } - private X509Certificate loadCertificate(Path path) throws IOException, CertificateException { - try (PEMParser pemParser = new PEMParser(new FileReader(path.toFile()))) { - Object object = pemParser.readObject(); - if (object instanceof X509CertificateHolder) { - return converter.getCertificate((X509CertificateHolder) object); - } - return null; - } - } - protected Optional loadCertificate(String subject) throws IOException, CertificateException { LOGGER.log(Level.INFO, "Loading certificate with subject " + subject); Optional pkiCert = pkiCertificateFacade.findBySubjectAndStatus(subject, diff --git a/hopsworks-ca/src/main/java/io/hops/hopsworks/ca/controllers/PKIUtils.java b/hopsworks-ca/src/main/java/io/hops/hopsworks/ca/controllers/PKIUtils.java index 641b0cadb5..2e15897a4d 100644 --- a/hopsworks-ca/src/main/java/io/hops/hopsworks/ca/controllers/PKIUtils.java +++ b/hopsworks-ca/src/main/java/io/hops/hopsworks/ca/controllers/PKIUtils.java @@ -21,28 +21,54 @@ import io.hops.hopsworks.persistence.entity.pki.CAType; import io.hops.hopsworks.persistence.entity.pki.PKICertificate; import io.hops.hopsworks.restutils.RESTCodes; +import org.apache.commons.codec.binary.Base64; +import org.apache.commons.lang3.RandomStringUtils; +import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; import org.bouncycastle.asn1.x500.X500Name; import org.bouncycastle.asn1.x500.X500NameBuilder; import org.bouncycastle.asn1.x500.style.BCStrictStyle; import org.bouncycastle.asn1.x500.style.BCStyle; import org.bouncycastle.cert.CertIOException; +import org.bouncycastle.cert.X509CertificateHolder; +import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.openssl.PEMDecryptorProvider; +import org.bouncycastle.openssl.PEMEncryptedKeyPair; +import org.bouncycastle.openssl.PEMKeyPair; +import org.bouncycastle.openssl.PEMParser; import org.bouncycastle.openssl.jcajce.JcaMiscPEMGenerator; +import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter; import org.bouncycastle.openssl.jcajce.JcaPEMWriter; +import org.bouncycastle.openssl.jcajce.JcePEMDecryptorProviderBuilder; import org.bouncycastle.operator.OperatorCreationException; import org.bouncycastle.util.io.pem.PemObjectGenerator; import org.bouncycastle.util.io.pem.PemWriter; +import javax.annotation.PostConstruct; import javax.ejb.EJB; import javax.ejb.Stateless; import javax.naming.InvalidNameException; +import java.io.ByteArrayOutputStream; +import java.io.FileReader; import java.io.IOException; +import java.io.OutputStream; +import java.io.Reader; +import java.io.StringReader; import java.io.StringWriter; +import java.io.UnsupportedEncodingException; +import java.nio.file.Path; +import java.security.GeneralSecurityException; import java.security.KeyException; +import java.security.KeyPair; +import java.security.KeyStore; import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.Security; import java.security.SignatureException; import java.security.cert.CRLException; import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; import java.security.cert.X509Extension; import java.time.Duration; import java.time.temporal.ChronoUnit; @@ -78,8 +104,19 @@ public class PKIUtils { TIME_SUFFIXES.put("d", ChronoUnit.DAYS); } private static final Pattern TIME_CONF_PATTERN = Pattern.compile("([0-9]+)([a-z]+)?"); + private static final Base64 B64 = new Base64(); + private JcaPEMKeyConverter pemKeyConverter; + private JcaX509CertificateConverter x509Converter; + + @PostConstruct + public void init() { + Security.addProvider(new BouncyCastleProvider()); + pemKeyConverter = new JcaPEMKeyConverter().setProvider(new BouncyCastleProvider()); + x509Converter = new JcaX509CertificateConverter().setProvider(new BouncyCastleProvider()); + } + public X500Name parseCertificateSubjectName(String subject, CertificateType certificateType) throws InvalidNameException { switch (certificateType) { @@ -290,4 +327,111 @@ private ChronoUnit getConfTimeTimeUnit(String configurationTime) { } return timeUnitStr == null ? ChronoUnit.MINUTES : TIME_SUFFIXES.get(timeUnitStr.toLowerCase()); } + + public KeyPair loadKeyPair(String privateKey, String password) throws IOException { + return loadKeyPair(new StringReader(privateKey), password); + } + + public KeyPair loadKeyPair(Path path, String password) throws IOException { + return loadKeyPair(new FileReader(path.toFile()), password); + } + + private KeyPair loadKeyPair(Reader reader, String password) throws IOException { + try (PEMParser pemParser = new PEMParser(reader)) { + Object object = pemParser.readObject(); + KeyPair kp; + if (object instanceof PEMEncryptedKeyPair) { + PEMEncryptedKeyPair ekp = (PEMEncryptedKeyPair) object; + PEMDecryptorProvider decryptorProvider = new JcePEMDecryptorProviderBuilder().build(password.toCharArray()); + kp = pemKeyConverter.getKeyPair(ekp.decryptKeyPair(decryptorProvider)); + } else if (object instanceof PEMKeyPair) { + // PKCS1 + PEMKeyPair ukp = (PEMKeyPair) object; + kp = pemKeyConverter.getKeyPair(ukp); + } else if (object instanceof PrivateKeyInfo) { + // PKCS8 + PrivateKey privateKey = pemKeyConverter.getPrivateKey((PrivateKeyInfo) object); + kp = new KeyPair(null, privateKey); + } else { + throw new UnsupportedEncodingException("Unsupported keypair encoding"); + } + return kp; + } + } + + public X509Certificate loadCertificate(Path path) throws IOException, CertificateException { + return loadCertificate(new FileReader(path.toFile())); + } + + public X509Certificate loadCertificate(String certificate) throws IOException, CertificateException { + return loadCertificate(new StringReader(certificate)); + } + + private X509Certificate loadCertificate(Reader reader) throws IOException, CertificateException { + try (PEMParser pemParser = new PEMParser(reader)) { + Object object = pemParser.readObject(); + if (object instanceof X509CertificateHolder) { + return x509Converter.getCertificate((X509CertificateHolder) object); + } + return null; + } + } + + public KeyStores createB64Keystores(String privateKey, X509Certificate certificate, + X509Certificate issuer, X509Certificate root) throws GeneralSecurityException, IOException { + + try (ByteArrayOutputStream keyStore = new ByteArrayOutputStream(); + ByteArrayOutputStream trustStore = new ByteArrayOutputStream()) { + char[] password = createKeystores(privateKey, certificate, issuer, root, keyStore, trustStore); + return new KeyStores<>(B64.encodeToString(keyStore.toByteArray()), B64.encodeToString(trustStore.toByteArray()), + password); + } + } + + public char[] createKeystores(String privateKey, X509Certificate certificate, X509Certificate issuer, + X509Certificate root, OutputStream keyStore, OutputStream trustStore) + throws GeneralSecurityException, IOException { + PrivateKey key = loadKeyPair(new StringReader(privateKey), "").getPrivate(); + char[] password = RandomStringUtils.randomAlphanumeric(15, 20).toCharArray(); + KeyStore ks = KeyStore.getInstance("JKS"); + ks.load(null, null); + X509Certificate[] chain = new X509Certificate[2]; + chain[0] = certificate; + chain[1] = issuer; + ks.setKeyEntry("own", key, password, chain); + ks.store(keyStore, password); + + + KeyStore ts = KeyStore.getInstance("JKS"); + ts.load(null, null); + ts.setCertificateEntry("hw_root_ca", root); + ts.store(trustStore, password); + keyStore.flush(); + trustStore.flush(); + return password; + } + + public static class KeyStores { + private final T keyStore; + private final T trustStore; + private final char[] password; + + public KeyStores(T keyStore, T trustStore, char[] password) { + this.keyStore = keyStore; + this.trustStore = trustStore; + this.password = password; + } + + public T getKeyStore() { + return keyStore; + } + + public T getTrustStore() { + return trustStore; + } + + public char[] getPassword() { + return password; + } + } } diff --git a/hopsworks-ca/src/test/java/io/hops/hopsworks/ca/controllers/PKIMocking.java b/hopsworks-ca/src/test/java/io/hops/hopsworks/ca/controllers/PKIMocking.java index 37fabff9ea..1d24cf4df2 100644 --- a/hopsworks-ca/src/test/java/io/hops/hopsworks/ca/controllers/PKIMocking.java +++ b/hopsworks-ca/src/test/java/io/hops/hopsworks/ca/controllers/PKIMocking.java @@ -96,7 +96,7 @@ public Long answer(InvocationOnMock invocationOnMock) throws Throwable { pki.setCaConf(conf); } - protected String stringifyCSR(PKCS10CertificationRequest csr) throws IOException { + protected static String stringifyCSR(PKCS10CertificationRequest csr) throws IOException { try (StringWriter sw = new StringWriter()) { PemWriter pw = new JcaPEMWriter(sw); PemObjectGenerator pog = new JcaMiscPEMGenerator(csr); diff --git a/hopsworks-ca/src/test/java/io/hops/hopsworks/ca/controllers/TestCreateKeystores.java b/hopsworks-ca/src/test/java/io/hops/hopsworks/ca/controllers/TestCreateKeystores.java new file mode 100644 index 0000000000..378b98dce4 --- /dev/null +++ b/hopsworks-ca/src/test/java/io/hops/hopsworks/ca/controllers/TestCreateKeystores.java @@ -0,0 +1,123 @@ +/* + * This file is part of Hopsworks + * Copyright (C) 2023, Hopsworks AB. All rights reserved + * + * Hopsworks is free software: you can redistribute it and/or modify it under the terms of + * the GNU Affero General Public License as published by the Free Software Foundation, + * either version 3 of the License, or (at your option) any later version. + * + * Hopsworks is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along with this program. + * If not, see . + */ +package io.hops.hopsworks.ca.controllers; + +import io.hops.hopsworks.persistence.entity.pki.CAType; +import org.apache.commons.codec.binary.Base64; +import org.bouncycastle.asn1.x500.X500Name; +import org.bouncycastle.openssl.jcajce.JcaMiscPEMGenerator; +import org.bouncycastle.openssl.jcajce.JcaPEMWriter; +import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; +import org.bouncycastle.pkcs.PKCS10CertificationRequest; +import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequestBuilder; +import org.bouncycastle.util.io.pem.PemObjectGenerator; +import org.bouncycastle.util.io.pem.PemWriter; +import org.junit.Assert; +import org.junit.Test; +import org.mockito.Mockito; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.StringWriter; +import java.security.KeyPair; +import java.security.KeyStore; +import java.security.PrivateKey; +import java.security.cert.Certificate; +import java.security.cert.X509Certificate; +import java.time.Duration; + +import static io.hops.hopsworks.ca.controllers.PKI.EMPTY_CONFIGURATION; + +public class TestCreateKeystores extends PKIMocking { + + @Test + public void testCreateKeystores() throws Exception { + setupBasicPKI(); + pkiUtils.init(); + Mockito.doReturn(EMPTY_CONFIGURATION).when(pki).loadConfiguration(); + pki.init(); + pki.initializeCertificateAuthorities(); + Mockito.doNothing().when(pki).maybeInitializeCA(); + Mockito.doNothing().when(pkiCertificateFacade).saveCertificate(Mockito.any()); + PKIUtils mockPKIUtils = Mockito.mock(PKIUtils.class); + Mockito.when(mockPKIUtils.getValidityPeriod(Mockito.eq(CertificateType.APP))) + .thenReturn(Duration.ofMinutes(10)); + Mockito.when(mockPKIUtils.getResponsibleCA(Mockito.any())).thenCallRealMethod(); + pki.setPKIUtils(mockPKIUtils); + + KeyPair requesterKeypair = pki.generateKeyPair(); + + X500Name requesterName = new X500Name("CN=owner_1,L=hdfs"); + JcaPKCS10CertificationRequestBuilder csrBuilder = new JcaPKCS10CertificationRequestBuilder(requesterName, + requesterKeypair.getPublic()); + PKCS10CertificationRequest csr = csrBuilder.build( + new JcaContentSignerBuilder("SHA256withRSA").build(requesterKeypair.getPrivate())); + String stringifiedCSR = stringifyCSR(csr); + X509Certificate certificate = pki.signCertificateSigningRequest(stringifiedCSR, CertificateType.APP); + Assert.assertNotNull(certificate); + + X509Certificate rootCA = pki.getCaCertificates().get(CAType.ROOT); + Assert.assertNotNull(rootCA); + X509Certificate intermediateCA = pki.getCaCertificates().get(CAType.INTERMEDIATE); + Assert.assertNotNull(intermediateCA); + + String privateKeyStr = stringifyPrivateKey(requesterKeypair.getPrivate()); + PKIUtils.KeyStores keyStores = pkiUtils.createB64Keystores(privateKeyStr, certificate, intermediateCA, + rootCA); + + // Make sure we have indeed gotten something + Assert.assertNotNull(keyStores.getKeyStore()); + Assert.assertNotNull(keyStores.getTrustStore()); + Assert.assertNotNull(keyStores.getPassword()); + + Base64 b64 = new Base64(); + ByteArrayInputStream kbis = new ByteArrayInputStream(b64.decode(keyStores.getKeyStore())); + KeyStore ks = KeyStore.getInstance("JKS"); + ks.load(kbis, keyStores.getPassword()); + + ByteArrayInputStream tbis = new ByteArrayInputStream(b64.decode(keyStores.getTrustStore())); + KeyStore ts = KeyStore.getInstance("JKS"); + ts.load(tbis, keyStores.getPassword()); + + // Inspect keystore + Assert.assertTrue(ks.containsAlias("own")); + // Signature of my certificate - first in the bundle + X509Certificate myCert = (X509Certificate) ks.getCertificate("own"); + Assert.assertArrayEquals(certificate.getSignature(), myCert.getSignature()); + // Signature of the intermediate - second in the bundle + Certificate[] bundle = ks.getCertificateChain("own"); + Assert.assertEquals(2, bundle.length); + X509Certificate kInterCA = (X509Certificate) bundle[1]; + Assert.assertArrayEquals(intermediateCA.getSignature(), kInterCA.getSignature()); + + // Inspect truststore + // Signature of Root + Assert.assertTrue(ts.containsAlias("hw_root_ca")); + X509Certificate tRootCA = (X509Certificate) ts.getCertificate("hw_root_ca"); + Assert.assertArrayEquals(rootCA.getSignature(), tRootCA.getSignature()); + } + + private String stringifyPrivateKey(PrivateKey key) throws IOException { + try (StringWriter sw = new StringWriter()) { + PemWriter pw = new JcaPEMWriter(sw); + PemObjectGenerator pog = new JcaMiscPEMGenerator(key); + pw.writeObject(pog.generate()); + pw.flush(); + pw.close(); + return sw.toString(); + } + } +} diff --git a/hopsworks-ca/src/test/java/io/hops/hopsworks/ca/controllers/TestPKIUtils.java b/hopsworks-ca/src/test/java/io/hops/hopsworks/ca/controllers/TestPKIUtils.java index faa4fb4484..72088caa1c 100644 --- a/hopsworks-ca/src/test/java/io/hops/hopsworks/ca/controllers/TestPKIUtils.java +++ b/hopsworks-ca/src/test/java/io/hops/hopsworks/ca/controllers/TestPKIUtils.java @@ -16,14 +16,16 @@ package io.hops.hopsworks.ca.controllers; import org.bouncycastle.asn1.x500.X500Name; +import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.junit.Assert; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import javax.naming.InvalidNameException; -import java.time.Duration; -import java.time.temporal.ChronoUnit; +import java.io.IOException; +import java.security.KeyPair; +import java.security.Security; public class TestPKIUtils { @@ -71,4 +73,45 @@ public void testGetCertificateValidityInS() { Assert.assertEquals(60*60*30, pkiUtils.getCertificateValidityInS("30h")); Assert.assertEquals(60*60*24*2, pkiUtils.getCertificateValidityInS("2d")); } + + @Test + public void testLoadPrivateKey() throws IOException, Exception { + Security.addProvider(new BouncyCastleProvider()); + String privateKey = "-----BEGIN PRIVATE KEY-----\n" + + "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC0b91ohTVHecKA\n" + + "o2U1e3DFsS6RyR8oRiVLHBRFv2SMI+/GNxB1mAUAEE4V7T0vNIv76fV54NRh3MBY\n" + + "4cUDiWYtFCcxV1acHtCP8ppix+74dO3ZeoQ6ZklwgVLGUHcsicRpSegiZzEn840H\n" + + "KCNSE7+Kyql7JgIZD4G4vWN3v0taIMhsTEK/iKs5gGVRNPQgu9j2CA07BqoplHde\n" + + "412D8gJCEXhs0ZXq3L4OrV1HfmGiqqU/7fauTGnKmVp2AHpwHrzW8Qx+jc8d33Bl\n" + + "OxwbmptjoG42uTQsrX/HKBddQ4RI3MbHZIVmUkVtQsXsAYD3/4IE8UI50TY3qEC2\n" + + "2bLBpuTxAgMBAAECggEAWThvTL2BiORGLwGcjAOL0dU459GBXJLC4g7yX0KyXzFt\n" + + "4J9dvif7YPqvAdybQnpDNb+MKEXp/rH/UnPgzUzlfyjV8Gn1Y6FBE8ysVmfyXFzF\n" + + "N6KDO7VUXxMzcOhc5WMCAeYPqONJxS2C8KUCQhWNwv1PLJuwsd+fD1BmnNG0Ws3C\n" + + "NRmEkBp9RfoHTmSAV3+c4DmllAfoXVNA/KkAfPGNTZGcVm0FikWb2uC91sBD7aHG\n" + + "w8FUi4VvniQeKaksfhCucJoA/cl8h7imEl3sVvbImhhkH7EtEET/4HNXsAacBRdL\n" + + "juFjTapvI5G/zaxqdKlzw5+2ycEuFtM5cMZztzoYAQKBgQDL5kl3x0XqSCnLRclx\n" + + "W7wU1J7ZVzbHoohujL7X62q7CakJ45byOQKtjRJyYRYE5zgjdzL8ZgO/ieog2n5C\n" + + "tpaMP/0jY8r33HfdHV/KhA6VSiNmqu/xj0Puq14xEvJ7jVImvlhH8gSa7/EXXnNg\n" + + "udvc5GMEEHrGKlPzMGB0Kl8wwQKBgQDiitDuoeBX30DZ91snCZHIVZ/SwcCWeX53\n" + + "1EKIK+wLtd1pjxR3zyagyKqyFsDht8Cl/AvqljEJJegFoPLuLxJCDccTHf8I20Rc\n" + + "GcT9Bpvfy2YyiTNZIjUAJu764dDmQqYYbS2HOtevNqwbivJPUR1+4j8Ra96poCTi\n" + + "5OiIWIKQMQKBgHhnaILenZ6XNnbeovHZpdr3I0ZchfClPcNqQVfnoIMKVVONnZkz\n" + + "qS0q3PXF9ua2UyQ+Q1FgPF5i5mq4G07x2zy+nJDFYRm0iuN7cRF5odLukLETx9Tx\n" + + "MMBDWb/I3H+xGA3g4Oi7NZT4k3mlQKShm/94ri+8O4PBgwlcS9jNHKEBAoGBAI8N\n" + + "aXHG9ouGhsUc1YqJGG2Q5COKBbr/bUTt3DVwxtV+Ohp2J06gmJvfGyrqA1KFXjly\n" + + "N3Qi80P7k9A6Gi0dvEHJwXPo9Sr6iug9vY6ppbRkFFzFFo+qch1ueGokPm2omInE\n" + + "J4PFPH1/4J5j1y8O4blF1N2DaE9kuOYt9khi28+BAoGAdhUtA0YQnENzjQKF7Hyf\n" + + "tLvvm/em/eb974KzT/IC0kVHGbgJEOdhJ0nXI/oRk3+9ojmLjqAVMLMjK05j4gLp\n" + + "jBbcHA/zPwv5C+Pe9u5iiFy1lV6Oridlm4hFcJnAM67y4/9aKVFBlJdTcAa0lGHU\n" + + "MO3BxsWG2mgkPn63/mSfbAk=\n" + + "-----END PRIVATE KEY-----"; + + PKIUtils pkiUtils = new PKIUtils(); + pkiUtils.init(); + KeyPair keyPair = pkiUtils.loadKeyPair(privateKey, ""); + // The key above contains only the private key + Assert.assertNull(keyPair.getPublic()); + Assert.assertNotNull(keyPair.getPrivate()); + Assert.assertEquals("RSA", keyPair.getPrivate().getAlgorithm()); + } }