From f65519cfc06693ed1775e9c00c642ab65f32ed05 Mon Sep 17 00:00:00 2001 From: Jim Hahn Date: Mon, 20 Sep 2021 23:23:38 -0400 Subject: Don't use keytool in test code Modified code to generate a self-signed certificate file programmatically instead of using keytool. Issue-ID: POLICY-3587 Change-Id: I53b6ffe65f33e5710eba633973e5d23b148f049f Signed-off-by: Jim Hahn --- utils-test/pom.xml | 5 + .../common/utils/security/SelfSignedKeyStore.java | 101 ++++++++++++++------- .../utils/security/SelfSignedKeyStoreTest.java | 9 +- 3 files changed, 78 insertions(+), 37 deletions(-) diff --git a/utils-test/pom.xml b/utils-test/pom.xml index 863467f1..ca2e83c7 100644 --- a/utils-test/pom.xml +++ b/utils-test/pom.xml @@ -39,6 +39,11 @@ + + org.bouncycastle + bcpkix-fips + 1.0.5 + com.google.re2j re2j diff --git a/utils-test/src/main/java/org/onap/policy/common/utils/security/SelfSignedKeyStore.java b/utils-test/src/main/java/org/onap/policy/common/utils/security/SelfSignedKeyStore.java index 11dff4b4..647ff1ba 100644 --- a/utils-test/src/main/java/org/onap/policy/common/utils/security/SelfSignedKeyStore.java +++ b/utils-test/src/main/java/org/onap/policy/common/utils/security/SelfSignedKeyStore.java @@ -22,11 +22,34 @@ package org.onap.policy.common.utils.security; import java.io.File; import java.io.FileNotFoundException; +import java.io.FileOutputStream; import java.io.IOException; -import java.lang.ProcessBuilder.Redirect; +import java.math.BigInteger; import java.nio.file.Files; +import java.security.KeyPairGenerator; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.util.Arrays; +import java.util.Date; import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; import lombok.Getter; +import org.bouncycastle.asn1.x500.X500Name; +import org.bouncycastle.asn1.x509.Extension; +import org.bouncycastle.asn1.x509.GeneralName; +import org.bouncycastle.asn1.x509.GeneralNames; +import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; +import org.bouncycastle.cert.X509CertificateHolder; +import org.bouncycastle.cert.X509v3CertificateBuilder; +import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; +import org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider; +import org.bouncycastle.operator.ContentSigner; +import org.bouncycastle.operator.OperatorCreationException; +import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; import org.onap.policy.common.utils.resources.ResourceUtils; /** @@ -70,49 +93,63 @@ public class SelfSignedKeyStore { keystoreName = System.getProperty("user.dir") + "/" + relativePath; // use existing file if it isn't too old - var keystore = new File(keystoreName); - if (keystore.exists()) { - if (System.currentTimeMillis() < keystore.lastModified() + var keystoreFile = new File(keystoreName); + if (keystoreFile.exists()) { + if (System.currentTimeMillis() < keystoreFile.lastModified() + TimeUnit.MILLISECONDS.convert(5, TimeUnit.HOURS)) { return; } - Files.delete(keystore.toPath()); + Files.delete(keystoreFile.toPath()); } /* * Read the list of subject-alternative names, joining the lines with commas, and * dropping the trailing comma. */ - String sanName = getKeystoreSanName(); - var subAltNames = ResourceUtils.getResourceAsString(sanName); - if (subAltNames == null) { - throw new FileNotFoundException(sanName); + String sanFileName = getKeystoreSanName(); + var sanString = ResourceUtils.getResourceAsString(sanFileName); + if (sanString == null) { + throw new FileNotFoundException(sanFileName); } - subAltNames = subAltNames.replace("\r", "").replace("\n", ","); - subAltNames = "SAN=" + subAltNames.substring(0, subAltNames.length() - 1); - - // build up the "keytool" command - - // @formatter:off - var builder = new ProcessBuilder("keytool", "-genkeypair", - "-alias", "policy@policy.onap.org", - "-validity", "1", - "-keyalg", "RSA", - "-dname", "C=US, O=ONAP, OU=OSAAF, OU=policy@policy.onap.org:DEV, CN=policy", - "-keystore", keystoreName, - "-keypass", PRIVATE_KEY_PASSWORD, - "-storepass", KEYSTORE_PASSWORD, - "-ext", subAltNames); - // @formatter:on - - Process proc = builder.redirectOutput(Redirect.INHERIT).redirectError(Redirect.INHERIT).start(); - proc.waitFor(); - - int exitCode = proc.exitValue(); - if (exitCode != 0) { - throw new IOException("keytool exited with " + exitCode); + var sanArray = sanString.replace("DNS:", "").replace("\r", "").split("\n"); + GeneralName[] nameArray = Arrays.stream(sanArray).map(name -> new GeneralName(GeneralName.dNSName, name)) + .collect(Collectors.toList()).toArray(new GeneralName[0]); + final var names = new GeneralNames(nameArray); + + try (var ostr = new FileOutputStream(keystoreFile)) { + var keyPairGenerator = KeyPairGenerator.getInstance("RSA"); + keyPairGenerator.initialize(2048); + final var keyPair = keyPairGenerator.generateKeyPair(); + + final long tcur = System.currentTimeMillis(); + + final var dn = new X500Name("C=US, O=ONAP, OU=OSAAF, OU=policy@policy.onap.org:DEV, CN=policy"); + final var serial = BigInteger.valueOf(new SecureRandom().nextInt()); + final var notBefore = new Date(tcur); + final var notAfter = new Date(tcur + TimeUnit.MILLISECONDS.convert(365, TimeUnit.DAYS)); + final var pubKeyInfo = SubjectPublicKeyInfo.getInstance(keyPair.getPublic().getEncoded()); + + ContentSigner signer = new JcaContentSignerBuilder("SHA256WithRSA") + .setProvider(new BouncyCastleFipsProvider()).build(keyPair.getPrivate()); + + X509CertificateHolder holder = new X509v3CertificateBuilder(dn, serial, notBefore, notAfter, dn, pubKeyInfo) + .addExtension(Extension.subjectAlternativeName, false, names).build(signer); + + var cert = new JcaX509CertificateConverter().setProvider(new BouncyCastleFipsProvider()) + .getCertificate(holder); + final Certificate[] chain = {cert}; + + var keystore = KeyStore.getInstance("PKCS12"); + keystore.load(null, null); + keystore.setKeyEntry("policy@policy.onap.org", keyPair.getPrivate(), PRIVATE_KEY_PASSWORD.toCharArray(), + chain); + + keystore.store(ostr, KEYSTORE_PASSWORD.toCharArray()); + + } catch (NoSuchAlgorithmException | OperatorCreationException | CertificateException | KeyStoreException e) { + throw new IOException("cannot create certificate", e); } } diff --git a/utils-test/src/test/java/org/onap/policy/common/utils/security/SelfSignedKeyStoreTest.java b/utils-test/src/test/java/org/onap/policy/common/utils/security/SelfSignedKeyStoreTest.java index 62565bde..27bceacf 100644 --- a/utils-test/src/test/java/org/onap/policy/common/utils/security/SelfSignedKeyStoreTest.java +++ b/utils-test/src/test/java/org/onap/policy/common/utils/security/SelfSignedKeyStoreTest.java @@ -118,12 +118,12 @@ public class SelfSignedKeyStoreTest { } /** - * Tests the constructor, when keytool fails. + * Tests the constructor, when write fails. */ @Test - public void testSelfSignedKeyStoreStringKeytoolFailure() throws Exception { + public void testSelfSignedKeyStoreStringWriteFailure() throws Exception { assertThatThrownBy(() -> new SelfSignedKeyStore("target/unknown/path/to/keystore")) - .isInstanceOf(IOException.class).hasMessageContaining("keytool exited with"); + .isInstanceOf(IOException.class); } @Test @@ -140,8 +140,7 @@ public class SelfSignedKeyStoreTest { assertThat(defaultKeystore).exists(); // try again using the original relative path - should fail, as it's now deeper - assertThatThrownBy(() -> new SelfSignedKeyStore(relpath)).isInstanceOf(IOException.class) - .hasMessageContaining("keytool exited with"); + assertThatThrownBy(() -> new SelfSignedKeyStore(relpath)).isInstanceOf(IOException.class); } private static void delete(File file) { -- cgit 1.2.3-korg