From 19ca55a3bb4e308ef1122a1cdd8c911b811e18be Mon Sep 17 00:00:00 2001 From: Bjørn Christian Seime Date: Wed, 17 Oct 2018 16:21:59 +0200 Subject: Move classes in com.yahoo.security to security-utils --- container-disc/pom.xml | 1 + jdisc_http_service/pom.xml | 2 +- jrt/pom.xml | 11 +- pom.xml | 1 + security-utils/OWNERS | 1 + security-utils/pom.xml | 56 +++++++ .../yahoo/security/BasicConstraintsExtension.java | 14 ++ .../yahoo/security/BouncyCastleProviderHolder.java | 14 ++ .../main/java/com/yahoo/security/Extension.java | 22 +++ .../main/java/com/yahoo/security/KeyAlgorithm.java | 20 +++ .../java/com/yahoo/security/KeyStoreBuilder.java | 121 +++++++++++++++ .../main/java/com/yahoo/security/KeyStoreType.java | 23 +++ .../java/com/yahoo/security/KeyStoreUtils.java | 34 +++++ .../src/main/java/com/yahoo/security/KeyUtils.java | 129 ++++++++++++++++ .../main/java/com/yahoo/security/Pkcs10Csr.java | 71 +++++++++ .../java/com/yahoo/security/Pkcs10CsrBuilder.java | 106 +++++++++++++ .../java/com/yahoo/security/Pkcs10CsrUtils.java | 38 +++++ .../com/yahoo/security/SignatureAlgorithm.java | 22 +++ .../java/com/yahoo/security/SslContextBuilder.java | 137 +++++++++++++++++ .../com/yahoo/security/SubjectAlternativeName.java | 114 ++++++++++++++ .../com/yahoo/security/X509CertificateBuilder.java | 167 +++++++++++++++++++++ .../com/yahoo/security/X509CertificateUtils.java | 136 +++++++++++++++++ .../main/java/com/yahoo/security/package-info.java | 9 ++ .../security/tls/TransportSecurityOptions.java | 90 +++++++++++ .../yahoo/security/tls/TransportSecurityUtils.java | 66 ++++++++ .../java/com/yahoo/security/tls/package-info.java | 8 + .../com/yahoo/security/KeyStoreBuilderTest.java | 55 +++++++ .../test/java/com/yahoo/security/KeyUtilsTest.java | 54 +++++++ .../com/yahoo/security/Pkcs10CsrBuilderTest.java | 27 ++++ .../java/com/yahoo/security/Pkcs10CsrTest.java | 57 +++++++ .../com/yahoo/security/Pkcs10CsrUtilsTest.java | 30 ++++ .../com/yahoo/security/SslContextBuilderTest.java | 77 ++++++++++ .../test/java/com/yahoo/security/TestUtils.java | 42 ++++++ .../yahoo/security/X509CertificateBuilderTest.java | 83 ++++++++++ .../yahoo/security/X509CertificateUtilsTest.java | 74 +++++++++ .../security/tls/TransportSecurityOptionsTest.java | 25 +++ .../test/resources/transport-security-options.json | 7 + vespajlib/pom.xml | 26 +--- .../yahoo/security/BasicConstraintsExtension.java | 14 -- .../yahoo/security/BouncyCastleProviderHolder.java | 14 -- .../main/java/com/yahoo/security/Extension.java | 22 --- .../main/java/com/yahoo/security/KeyAlgorithm.java | 20 --- .../java/com/yahoo/security/KeyStoreBuilder.java | 121 --------------- .../main/java/com/yahoo/security/KeyStoreType.java | 23 --- .../java/com/yahoo/security/KeyStoreUtils.java | 34 ----- .../src/main/java/com/yahoo/security/KeyUtils.java | 129 ---------------- .../main/java/com/yahoo/security/Pkcs10Csr.java | 71 --------- .../java/com/yahoo/security/Pkcs10CsrBuilder.java | 106 ------------- .../java/com/yahoo/security/Pkcs10CsrUtils.java | 38 ----- .../com/yahoo/security/SignatureAlgorithm.java | 22 --- .../java/com/yahoo/security/SslContextBuilder.java | 137 ----------------- .../com/yahoo/security/SubjectAlternativeName.java | 114 -------------- .../com/yahoo/security/X509CertificateBuilder.java | 167 --------------------- .../com/yahoo/security/X509CertificateUtils.java | 136 ----------------- .../main/java/com/yahoo/security/package-info.java | 9 -- .../security/tls/TransportSecurityOptions.java | 90 ----------- .../yahoo/security/tls/TransportSecurityUtils.java | 66 -------- .../java/com/yahoo/security/tls/package-info.java | 8 - .../com/yahoo/security/KeyStoreBuilderTest.java | 55 ------- .../test/java/com/yahoo/security/KeyUtilsTest.java | 54 ------- .../com/yahoo/security/Pkcs10CsrBuilderTest.java | 27 ---- .../java/com/yahoo/security/Pkcs10CsrTest.java | 57 ------- .../com/yahoo/security/Pkcs10CsrUtilsTest.java | 30 ---- .../com/yahoo/security/SslContextBuilderTest.java | 77 ---------- .../test/java/com/yahoo/security/TestUtils.java | 42 ------ .../yahoo/security/X509CertificateBuilderTest.java | 83 ---------- .../yahoo/security/X509CertificateUtilsTest.java | 74 --------- .../security/tls/TransportSecurityOptionsTest.java | 25 --- .../test/resources/transport-security-options.json | 7 - 69 files changed, 1943 insertions(+), 1899 deletions(-) create mode 100644 security-utils/OWNERS create mode 100644 security-utils/pom.xml create mode 100644 security-utils/src/main/java/com/yahoo/security/BasicConstraintsExtension.java create mode 100644 security-utils/src/main/java/com/yahoo/security/BouncyCastleProviderHolder.java create mode 100644 security-utils/src/main/java/com/yahoo/security/Extension.java create mode 100644 security-utils/src/main/java/com/yahoo/security/KeyAlgorithm.java create mode 100644 security-utils/src/main/java/com/yahoo/security/KeyStoreBuilder.java create mode 100644 security-utils/src/main/java/com/yahoo/security/KeyStoreType.java create mode 100644 security-utils/src/main/java/com/yahoo/security/KeyStoreUtils.java create mode 100644 security-utils/src/main/java/com/yahoo/security/KeyUtils.java create mode 100644 security-utils/src/main/java/com/yahoo/security/Pkcs10Csr.java create mode 100644 security-utils/src/main/java/com/yahoo/security/Pkcs10CsrBuilder.java create mode 100644 security-utils/src/main/java/com/yahoo/security/Pkcs10CsrUtils.java create mode 100644 security-utils/src/main/java/com/yahoo/security/SignatureAlgorithm.java create mode 100644 security-utils/src/main/java/com/yahoo/security/SslContextBuilder.java create mode 100644 security-utils/src/main/java/com/yahoo/security/SubjectAlternativeName.java create mode 100644 security-utils/src/main/java/com/yahoo/security/X509CertificateBuilder.java create mode 100644 security-utils/src/main/java/com/yahoo/security/X509CertificateUtils.java create mode 100644 security-utils/src/main/java/com/yahoo/security/package-info.java create mode 100644 security-utils/src/main/java/com/yahoo/security/tls/TransportSecurityOptions.java create mode 100644 security-utils/src/main/java/com/yahoo/security/tls/TransportSecurityUtils.java create mode 100644 security-utils/src/main/java/com/yahoo/security/tls/package-info.java create mode 100644 security-utils/src/test/java/com/yahoo/security/KeyStoreBuilderTest.java create mode 100644 security-utils/src/test/java/com/yahoo/security/KeyUtilsTest.java create mode 100644 security-utils/src/test/java/com/yahoo/security/Pkcs10CsrBuilderTest.java create mode 100644 security-utils/src/test/java/com/yahoo/security/Pkcs10CsrTest.java create mode 100644 security-utils/src/test/java/com/yahoo/security/Pkcs10CsrUtilsTest.java create mode 100644 security-utils/src/test/java/com/yahoo/security/SslContextBuilderTest.java create mode 100644 security-utils/src/test/java/com/yahoo/security/TestUtils.java create mode 100644 security-utils/src/test/java/com/yahoo/security/X509CertificateBuilderTest.java create mode 100644 security-utils/src/test/java/com/yahoo/security/X509CertificateUtilsTest.java create mode 100644 security-utils/src/test/java/com/yahoo/security/tls/TransportSecurityOptionsTest.java create mode 100644 security-utils/src/test/resources/transport-security-options.json delete mode 100644 vespajlib/src/main/java/com/yahoo/security/BasicConstraintsExtension.java delete mode 100644 vespajlib/src/main/java/com/yahoo/security/BouncyCastleProviderHolder.java delete mode 100644 vespajlib/src/main/java/com/yahoo/security/Extension.java delete mode 100644 vespajlib/src/main/java/com/yahoo/security/KeyAlgorithm.java delete mode 100644 vespajlib/src/main/java/com/yahoo/security/KeyStoreBuilder.java delete mode 100644 vespajlib/src/main/java/com/yahoo/security/KeyStoreType.java delete mode 100644 vespajlib/src/main/java/com/yahoo/security/KeyStoreUtils.java delete mode 100644 vespajlib/src/main/java/com/yahoo/security/KeyUtils.java delete mode 100644 vespajlib/src/main/java/com/yahoo/security/Pkcs10Csr.java delete mode 100644 vespajlib/src/main/java/com/yahoo/security/Pkcs10CsrBuilder.java delete mode 100644 vespajlib/src/main/java/com/yahoo/security/Pkcs10CsrUtils.java delete mode 100644 vespajlib/src/main/java/com/yahoo/security/SignatureAlgorithm.java delete mode 100644 vespajlib/src/main/java/com/yahoo/security/SslContextBuilder.java delete mode 100644 vespajlib/src/main/java/com/yahoo/security/SubjectAlternativeName.java delete mode 100644 vespajlib/src/main/java/com/yahoo/security/X509CertificateBuilder.java delete mode 100644 vespajlib/src/main/java/com/yahoo/security/X509CertificateUtils.java delete mode 100644 vespajlib/src/main/java/com/yahoo/security/package-info.java delete mode 100644 vespajlib/src/main/java/com/yahoo/security/tls/TransportSecurityOptions.java delete mode 100644 vespajlib/src/main/java/com/yahoo/security/tls/TransportSecurityUtils.java delete mode 100644 vespajlib/src/main/java/com/yahoo/security/tls/package-info.java delete mode 100644 vespajlib/src/test/java/com/yahoo/security/KeyStoreBuilderTest.java delete mode 100644 vespajlib/src/test/java/com/yahoo/security/KeyUtilsTest.java delete mode 100644 vespajlib/src/test/java/com/yahoo/security/Pkcs10CsrBuilderTest.java delete mode 100644 vespajlib/src/test/java/com/yahoo/security/Pkcs10CsrTest.java delete mode 100644 vespajlib/src/test/java/com/yahoo/security/Pkcs10CsrUtilsTest.java delete mode 100644 vespajlib/src/test/java/com/yahoo/security/SslContextBuilderTest.java delete mode 100644 vespajlib/src/test/java/com/yahoo/security/TestUtils.java delete mode 100644 vespajlib/src/test/java/com/yahoo/security/X509CertificateBuilderTest.java delete mode 100644 vespajlib/src/test/java/com/yahoo/security/X509CertificateUtilsTest.java delete mode 100644 vespajlib/src/test/java/com/yahoo/security/tls/TransportSecurityOptionsTest.java delete mode 100644 vespajlib/src/test/resources/transport-security-options.json diff --git a/container-disc/pom.xml b/container-disc/pom.xml index d3319480ab3..62985192ad3 100644 --- a/container-disc/pom.xml +++ b/container-disc/pom.xml @@ -176,6 +176,7 @@ model-evaluation-jar-with-dependencies.jar, vespaclient-container-plugin-jar-with-dependencies.jar, vespa-athenz-jar-with-dependencies.jar, + security-utils-jar-with-dependencies.jar, simplemetrics-jar-with-dependencies.jar, defaults-jar-with-dependencies.jar, component-jar-with-dependencies.jar, diff --git a/jdisc_http_service/pom.xml b/jdisc_http_service/pom.xml index 879036db355..20d34b2489e 100644 --- a/jdisc_http_service/pom.xml +++ b/jdisc_http_service/pom.xml @@ -77,7 +77,7 @@ com.yahoo.vespa - vespajlib + security-utils ${project.version} provided diff --git a/jrt/pom.xml b/jrt/pom.xml index 84578f9e04d..6852c6deee6 100644 --- a/jrt/pom.xml +++ b/jrt/pom.xml @@ -18,11 +18,6 @@ junit test - - org.bouncycastle - bcpkix-jdk15on - test - com.yahoo.vespa annotations @@ -34,6 +29,12 @@ ${project.version} provided + + com.yahoo.vespa + security-utils + ${project.version} + compile + src diff --git a/pom.xml b/pom.xml index caa875a1918..f749adbdabf 100644 --- a/pom.xml +++ b/pom.xml @@ -107,6 +107,7 @@ searchcore searchlib searchsummary + security-utils serviceview service-monitor simplemetrics diff --git a/security-utils/OWNERS b/security-utils/OWNERS new file mode 100644 index 00000000000..569bf1cc3a1 --- /dev/null +++ b/security-utils/OWNERS @@ -0,0 +1 @@ +bjorncs diff --git a/security-utils/pom.xml b/security-utils/pom.xml new file mode 100644 index 00000000000..7006a0f5f86 --- /dev/null +++ b/security-utils/pom.xml @@ -0,0 +1,56 @@ + + + + 4.0.0 + + com.yahoo.vespa + parent + 6-SNAPSHOT + ../parent/pom.xml + + security-utils + container-plugin + 6-SNAPSHOT + + + + com.yahoo.vespa + annotations + ${project.version} + provided + + + + + org.bouncycastle + bcpkix-jdk15on + compile + + + com.fasterxml.jackson.core + jackson-databind + compile + + + + + junit + junit + test + + + org.hamcrest + hamcrest-library + test + + + + + + com.yahoo.vespa + bundle-plugin + true + + + + diff --git a/security-utils/src/main/java/com/yahoo/security/BasicConstraintsExtension.java b/security-utils/src/main/java/com/yahoo/security/BasicConstraintsExtension.java new file mode 100644 index 00000000000..d3c08ba27d0 --- /dev/null +++ b/security-utils/src/main/java/com/yahoo/security/BasicConstraintsExtension.java @@ -0,0 +1,14 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.security; + +/** + * @author bjorncs + */ +class BasicConstraintsExtension { + final boolean isCritical, isCertAuthorityCertificate; + + BasicConstraintsExtension(boolean isCritical, boolean isCertAuthorityCertificate) { + this.isCritical = isCritical; + this.isCertAuthorityCertificate = isCertAuthorityCertificate; + } +} diff --git a/security-utils/src/main/java/com/yahoo/security/BouncyCastleProviderHolder.java b/security-utils/src/main/java/com/yahoo/security/BouncyCastleProviderHolder.java new file mode 100644 index 00000000000..48a23a1fe7e --- /dev/null +++ b/security-utils/src/main/java/com/yahoo/security/BouncyCastleProviderHolder.java @@ -0,0 +1,14 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.security; + +import org.bouncycastle.jce.provider.BouncyCastleProvider; + +/** + * @author bjorncs + */ +class BouncyCastleProviderHolder { + + private static final BouncyCastleProvider bcProvider = new BouncyCastleProvider(); + + static BouncyCastleProvider getInstance() { return bcProvider; } +} diff --git a/security-utils/src/main/java/com/yahoo/security/Extension.java b/security-utils/src/main/java/com/yahoo/security/Extension.java new file mode 100644 index 00000000000..46b781c9c86 --- /dev/null +++ b/security-utils/src/main/java/com/yahoo/security/Extension.java @@ -0,0 +1,22 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.security; + +import org.bouncycastle.asn1.ASN1ObjectIdentifier; + +/** + * @author bjorncs + */ +public enum Extension { + BASIC_CONSTRAINTS(org.bouncycastle.asn1.x509.Extension.basicConstraints), + SUBJECT_ALTERNATIVE_NAMES(org.bouncycastle.asn1.x509.Extension.subjectAlternativeName); + + final ASN1ObjectIdentifier extensionOId; + + Extension(ASN1ObjectIdentifier extensionOId) { + this.extensionOId = extensionOId; + } + + public String getOId() { + return extensionOId.getId(); + } +} diff --git a/security-utils/src/main/java/com/yahoo/security/KeyAlgorithm.java b/security-utils/src/main/java/com/yahoo/security/KeyAlgorithm.java new file mode 100644 index 00000000000..3218f81f0d6 --- /dev/null +++ b/security-utils/src/main/java/com/yahoo/security/KeyAlgorithm.java @@ -0,0 +1,20 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.security; + +/** + * @author bjorncs + */ +public enum KeyAlgorithm { + RSA("RSA"), + EC("EC"); + + final String algorithmName; + + KeyAlgorithm(String algorithmName) { + this.algorithmName = algorithmName; + } + + String getAlgorithmName() { + return algorithmName; + } +} diff --git a/security-utils/src/main/java/com/yahoo/security/KeyStoreBuilder.java b/security-utils/src/main/java/com/yahoo/security/KeyStoreBuilder.java new file mode 100644 index 00000000000..2160fbf6455 --- /dev/null +++ b/security-utils/src/main/java/com/yahoo/security/KeyStoreBuilder.java @@ -0,0 +1,121 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.security; + +import java.io.BufferedInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.GeneralSecurityException; +import java.security.KeyStore; +import java.security.PrivateKey; +import java.security.cert.Certificate; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.List; + +import static java.util.Collections.singletonList; + +/** + * @author bjorncs + */ +public class KeyStoreBuilder { + + private final List keyEntries = new ArrayList<>(); + private final List certificateEntries = new ArrayList<>(); + + private final KeyStoreType keyStoreType; + private Path inputFile; + private char[] inputFilePassword; + + private KeyStoreBuilder(KeyStoreType keyStoreType) { + this.keyStoreType = keyStoreType; + } + + public static KeyStoreBuilder withType(KeyStoreType type) { + return new KeyStoreBuilder(type); + } + + public KeyStoreBuilder fromFile(Path file, char[] password) { + this.inputFile = file; + this.inputFilePassword = password; + return this; + } + + public KeyStoreBuilder fromFile(Path file) { + return fromFile(file, null); + } + + public KeyStoreBuilder withKeyEntry(String alias, PrivateKey privateKey, char[] password, List certificateChain) { + keyEntries.add(new KeyEntry(alias, privateKey, certificateChain, password)); + return this; + } + + public KeyStoreBuilder withKeyEntry(String alias, PrivateKey privateKey, char[] password, X509Certificate certificate) { + return withKeyEntry(alias, privateKey, password, singletonList(certificate)); + } + + public KeyStoreBuilder withKeyEntry(String alias, PrivateKey privateKey, X509Certificate certificate) { + return withKeyEntry(alias, privateKey, null, certificate); + } + + public KeyStoreBuilder withKeyEntry(String alias, PrivateKey privateKey, List certificateChain) { + return withKeyEntry(alias, privateKey, null, certificateChain); + } + + public KeyStoreBuilder withCertificateEntry(String alias, X509Certificate certificate) { + certificateEntries.add(new CertificateEntry(alias, certificate)); + return this; + } + + public KeyStore build() { + try { + KeyStore keystore = this.keyStoreType.createKeystore(); + if (this.inputFile != null) { + try (InputStream in = new BufferedInputStream(Files.newInputStream(this.inputFile))) { + keystore.load(in, this.inputFilePassword); + } + } else { + keystore.load(null); + } + for (KeyEntry entry : keyEntries) { + char[] password = entry.password != null ? entry.password : new char[0]; + Certificate[] certificateChain = entry.certificateChain.toArray(new Certificate[entry.certificateChain.size()]); + keystore.setKeyEntry(entry.alias, entry.privateKey, password, certificateChain); + } + for (CertificateEntry entry : certificateEntries) { + keystore.setCertificateEntry(entry.alias, entry.certificate); + } + return keystore; + } catch (GeneralSecurityException e) { + throw new RuntimeException(e); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + private static class KeyEntry { + final String alias; + final PrivateKey privateKey; + final List certificateChain; + final char[] password; + + KeyEntry(String alias, PrivateKey privateKey, List certificateChain, char[] password) { + this.alias = alias; + this.privateKey = privateKey; + this.certificateChain = certificateChain; + this.password = password; + } + } + + private static class CertificateEntry { + final String alias; + final X509Certificate certificate; + + CertificateEntry(String alias, X509Certificate certificate) { + this.alias = alias; + this.certificate = certificate; + } + } +} diff --git a/security-utils/src/main/java/com/yahoo/security/KeyStoreType.java b/security-utils/src/main/java/com/yahoo/security/KeyStoreType.java new file mode 100644 index 00000000000..7fb8df35286 --- /dev/null +++ b/security-utils/src/main/java/com/yahoo/security/KeyStoreType.java @@ -0,0 +1,23 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.security; + +import java.security.GeneralSecurityException; +import java.security.KeyStore; +import java.security.KeyStoreException; + +/** + * @author bjorncs + */ +public enum KeyStoreType { + JKS { + KeyStore createKeystore() throws KeyStoreException { + return KeyStore.getInstance("JKS"); + } + }, + PKCS12 { + KeyStore createKeystore() throws KeyStoreException { + return KeyStore.getInstance("PKCS12", BouncyCastleProviderHolder.getInstance()); + } + }; + abstract KeyStore createKeystore() throws GeneralSecurityException; +} diff --git a/security-utils/src/main/java/com/yahoo/security/KeyStoreUtils.java b/security-utils/src/main/java/com/yahoo/security/KeyStoreUtils.java new file mode 100644 index 00000000000..f0c4d99bf69 --- /dev/null +++ b/security-utils/src/main/java/com/yahoo/security/KeyStoreUtils.java @@ -0,0 +1,34 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.security; + +import java.io.BufferedOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.GeneralSecurityException; +import java.security.KeyStore; + +/** + * @author bjorncs + */ +public class KeyStoreUtils { + private KeyStoreUtils() {} + + public static void writeKeyStoreToFile(KeyStore keyStore, Path file, char[] password) { + try (OutputStream out = new BufferedOutputStream(Files.newOutputStream(file))) { + keyStore.store(out, password); + } catch (IOException e) { + throw new UncheckedIOException(e); + } catch (GeneralSecurityException e) { + throw new RuntimeException(e); + } + + } + + public static void writeKeyStoreToFile(KeyStore keyStore, Path file) { + writeKeyStoreToFile(keyStore, file, new char[0]); + } + +} diff --git a/security-utils/src/main/java/com/yahoo/security/KeyUtils.java b/security-utils/src/main/java/com/yahoo/security/KeyUtils.java new file mode 100644 index 00000000000..11fb0f432e4 --- /dev/null +++ b/security-utils/src/main/java/com/yahoo/security/KeyUtils.java @@ -0,0 +1,129 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.security; + +import org.bouncycastle.asn1.ASN1Encodable; +import org.bouncycastle.asn1.ASN1Primitive; +import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; +import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey; +import org.bouncycastle.jce.spec.ECParameterSpec; +import org.bouncycastle.jce.spec.ECPublicKeySpec; +import org.bouncycastle.math.ec.ECPoint; +import org.bouncycastle.math.ec.FixedPointCombMultiplier; +import org.bouncycastle.openssl.PEMKeyPair; +import org.bouncycastle.openssl.PEMParser; +import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter; +import org.bouncycastle.openssl.jcajce.JcaPEMWriter; +import org.bouncycastle.util.io.pem.PemObject; + +import java.io.IOException; +import java.io.StringReader; +import java.io.StringWriter; +import java.io.UncheckedIOException; +import java.security.GeneralSecurityException; +import java.security.KeyFactory; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.interfaces.RSAPrivateCrtKey; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.RSAPublicKeySpec; + +import static com.yahoo.security.KeyAlgorithm.EC; +import static com.yahoo.security.KeyAlgorithm.RSA; + +/** + * @author bjorncs + */ +public class KeyUtils { + private KeyUtils() {} + + public static KeyPair generateKeypair(KeyAlgorithm algorithm, int keySize) { + try { + KeyPairGenerator keyGen = KeyPairGenerator.getInstance(algorithm.getAlgorithmName(), BouncyCastleProviderHolder.getInstance()); + if (keySize != -1) { + keyGen.initialize(keySize); + } + return keyGen.genKeyPair(); + } catch (GeneralSecurityException e) { + throw new RuntimeException(e); + } + } + + public static KeyPair generateKeypair(KeyAlgorithm algorithm) { + return generateKeypair(algorithm, -1); + } + + public static PublicKey extractPublicKey(PrivateKey privateKey) { + String algorithm = privateKey.getAlgorithm(); + try { + if (algorithm.equals(RSA.getAlgorithmName())) { + KeyFactory keyFactory = KeyFactory.getInstance(RSA.getAlgorithmName(), BouncyCastleProviderHolder.getInstance()); + RSAPrivateCrtKey rsaPrivateCrtKey = (RSAPrivateCrtKey) privateKey; + RSAPublicKeySpec keySpec = new RSAPublicKeySpec(rsaPrivateCrtKey.getModulus(), rsaPrivateCrtKey.getPublicExponent()); + return keyFactory.generatePublic(keySpec); + } else if (algorithm.equals(EC.getAlgorithmName())) { + KeyFactory keyFactory = KeyFactory.getInstance(EC.getAlgorithmName(), BouncyCastleProviderHolder.getInstance()); + BCECPrivateKey ecPrivateKey = (BCECPrivateKey) privateKey; + ECParameterSpec ecParameterSpec = ecPrivateKey.getParameters(); + ECPoint ecPoint = new FixedPointCombMultiplier().multiply(ecParameterSpec.getG(), ecPrivateKey.getD()); + ECPublicKeySpec keySpec = new ECPublicKeySpec(ecPoint, ecParameterSpec); + return keyFactory.generatePublic(keySpec); + } else { + throw new IllegalArgumentException("Unexpected key algorithm: " + algorithm); + } + } catch (GeneralSecurityException e) { + throw new RuntimeException(e); + } + } + + public static PrivateKey fromPemEncodedPrivateKey(String pem) { + try (PEMParser parser = new PEMParser(new StringReader(pem))) { + Object pemObject = parser.readObject(); + if (pemObject instanceof PrivateKeyInfo) { + PrivateKeyInfo keyInfo = (PrivateKeyInfo) pemObject; + PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyInfo.getEncoded()); + return KeyFactory.getInstance(RSA.getAlgorithmName()).generatePrivate(keySpec); + } else if (pemObject instanceof PEMKeyPair) { + PEMKeyPair pemKeypair = (PEMKeyPair) pemObject; + PrivateKeyInfo keyInfo = pemKeypair.getPrivateKeyInfo(); + JcaPEMKeyConverter pemConverter = new JcaPEMKeyConverter().setProvider(BouncyCastleProviderHolder.getInstance()); + return pemConverter.getPrivateKey(keyInfo); + } + throw new IllegalArgumentException("Unexpected type of PEM type: " + pemObject); + } catch (IOException e) { + throw new UncheckedIOException(e); + } catch (GeneralSecurityException e) { + throw new RuntimeException(e); + } + } + + public static String toPem(PrivateKey privateKey) { + try (StringWriter stringWriter = new StringWriter(); JcaPEMWriter pemWriter = new JcaPEMWriter(stringWriter)) { + String algorithm = privateKey.getAlgorithm(); + // Note: Encoding using PKCS#1 as this is to be read by tools only supporting PKCS#1 + String type; + if (algorithm.equals(RSA.getAlgorithmName())) { + type = "RSA PRIVATE KEY"; + } else if (algorithm.equals(EC.getAlgorithmName())) { + type = "EC PRIVATE KEY"; + } else { + throw new IllegalArgumentException("Unexpected key algorithm: " + algorithm); + } + pemWriter.writeObject(new PemObject(type, getPkcs1Bytes(privateKey))); + pemWriter.flush(); + return stringWriter.toString(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + private static byte[] getPkcs1Bytes(PrivateKey privateKey) throws IOException{ + + byte[] privBytes = privateKey.getEncoded(); + PrivateKeyInfo pkInfo = PrivateKeyInfo.getInstance(privBytes); + ASN1Encodable encodable = pkInfo.parsePrivateKey(); + ASN1Primitive primitive = encodable.toASN1Primitive(); + return primitive.getEncoded(); + } +} diff --git a/security-utils/src/main/java/com/yahoo/security/Pkcs10Csr.java b/security-utils/src/main/java/com/yahoo/security/Pkcs10Csr.java new file mode 100644 index 00000000000..e08ee117fcd --- /dev/null +++ b/security-utils/src/main/java/com/yahoo/security/Pkcs10Csr.java @@ -0,0 +1,71 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.security; + +import org.bouncycastle.asn1.ASN1ObjectIdentifier; +import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers; +import org.bouncycastle.asn1.x509.BasicConstraints; +import org.bouncycastle.asn1.x509.Extension; +import org.bouncycastle.asn1.x509.Extensions; +import org.bouncycastle.asn1.x509.GeneralNames; +import org.bouncycastle.pkcs.PKCS10CertificationRequest; + +import javax.security.auth.x500.X500Principal; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; + +import static java.util.Collections.emptyList; +import static java.util.stream.Collectors.toList; + +/** + * @author bjorncs + */ +public class Pkcs10Csr { + + private final PKCS10CertificationRequest csr; + + Pkcs10Csr(PKCS10CertificationRequest csr) { + this.csr = csr; + } + + PKCS10CertificationRequest getBcCsr() { + return csr; + } + + public X500Principal getSubject() { + return new X500Principal(csr.getSubject().toString()); + } + + public List getSubjectAlternativeNames() { + return getExtensions() + .map(extensions -> GeneralNames.fromExtensions(extensions, Extension.subjectAlternativeName)) + .map(SubjectAlternativeName::fromGeneralNames) + .orElse(emptyList()); + } + + /** + * @return If basic constraints extension is present: returns true if CA cert, false otherwise. Returns empty if the extension is not present. + */ + public Optional getBasicConstraints() { + return getExtensions() + .map(BasicConstraints::fromExtensions) + .map(BasicConstraints::isCA); + } + + public List getExtensionOIds() { + return getExtensions() + .map(extensions -> Arrays.stream(extensions.getExtensionOIDs()) + .map(ASN1ObjectIdentifier::getId) + .collect(toList())) + .orElse(emptyList()); + + } + + private Optional getExtensions() { + return Optional.of(csr.getAttributes(PKCSObjectIdentifiers.pkcs_9_at_extensionRequest)) + .filter(attributes -> attributes.length > 0) + .map(attributes -> attributes[0]) + .map(attribute -> Extensions.getInstance(attribute.getAttrValues().getObjectAt(0))); + } + +} diff --git a/security-utils/src/main/java/com/yahoo/security/Pkcs10CsrBuilder.java b/security-utils/src/main/java/com/yahoo/security/Pkcs10CsrBuilder.java new file mode 100644 index 00000000000..0fe6117ef2f --- /dev/null +++ b/security-utils/src/main/java/com/yahoo/security/Pkcs10CsrBuilder.java @@ -0,0 +1,106 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.security; + +import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers; +import org.bouncycastle.asn1.x500.X500Name; +import org.bouncycastle.asn1.x509.BasicConstraints; +import org.bouncycastle.asn1.x509.Extension; +import org.bouncycastle.asn1.x509.ExtensionsGenerator; +import org.bouncycastle.asn1.x509.GeneralName; +import org.bouncycastle.asn1.x509.GeneralNames; +import org.bouncycastle.operator.ContentSigner; +import org.bouncycastle.operator.OperatorCreationException; +import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; +import org.bouncycastle.pkcs.PKCS10CertificationRequestBuilder; +import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequestBuilder; + +import javax.security.auth.x500.X500Principal; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.security.KeyPair; +import java.util.ArrayList; +import java.util.List; + +import static com.yahoo.security.SubjectAlternativeName.Type.DNS_NAME; + +/** + * @author bjorncs + */ +public class Pkcs10CsrBuilder { + + private final X500Principal subject; + private final KeyPair keyPair; + private final List subjectAlternativeNames = new ArrayList<>(); + private final SignatureAlgorithm signatureAlgorithm; + private BasicConstraintsExtension basicConstraintsExtension; + + private Pkcs10CsrBuilder(X500Principal subject, + KeyPair keyPair, + SignatureAlgorithm signatureAlgorithm) { + this.subject = subject; + this.keyPair = keyPair; + this.signatureAlgorithm = signatureAlgorithm; + } + + public static Pkcs10CsrBuilder fromKeypair(X500Principal subject, + KeyPair keyPair, + SignatureAlgorithm signatureAlgorithm) { + return new Pkcs10CsrBuilder(subject, keyPair, signatureAlgorithm); + } + + public Pkcs10CsrBuilder addSubjectAlternativeName(String dns) { + this.subjectAlternativeNames.add(new SubjectAlternativeName(DNS_NAME, dns)); + return this; + } + + public Pkcs10CsrBuilder addSubjectAlternativeName(SubjectAlternativeName san) { + this.subjectAlternativeNames.add(san); + return this; + } + + public Pkcs10CsrBuilder addSubjectAlternativeName(SubjectAlternativeName.Type type, String value) { + this.subjectAlternativeNames.add(new SubjectAlternativeName(type, value)); + return this; + } + + public Pkcs10CsrBuilder setBasicConstraints(boolean isCritical, boolean isCertAuthorityCertificate) { + this.basicConstraintsExtension = new BasicConstraintsExtension(isCritical, isCertAuthorityCertificate); + return this; + } + + public Pkcs10CsrBuilder setIsCertAuthority(boolean isCertAuthority) { + return setBasicConstraints(true, isCertAuthority); + } + + public Pkcs10Csr build() { + try { + PKCS10CertificationRequestBuilder requestBuilder = + new JcaPKCS10CertificationRequestBuilder(new X500Name(subject.getName()), keyPair.getPublic()); + ExtensionsGenerator extGen = new ExtensionsGenerator(); + if (basicConstraintsExtension != null) { + extGen.addExtension( + Extension.basicConstraints, + basicConstraintsExtension.isCritical, + new BasicConstraints(basicConstraintsExtension.isCertAuthorityCertificate)); + } + if (!subjectAlternativeNames.isEmpty()) { + GeneralNames generalNames = new GeneralNames( + subjectAlternativeNames.stream() + .map(SubjectAlternativeName::toGeneralName) + .toArray(GeneralName[]::new)); + extGen.addExtension(Extension.subjectAlternativeName, false, generalNames); + } + requestBuilder.addAttribute(PKCSObjectIdentifiers.pkcs_9_at_extensionRequest, extGen.generate()); + ContentSigner contentSigner = new JcaContentSignerBuilder(signatureAlgorithm.getAlgorithmName()) + .setProvider(BouncyCastleProviderHolder.getInstance()) + .build(keyPair.getPrivate()); + return new Pkcs10Csr(requestBuilder.build(contentSigner)); + } catch (OperatorCreationException e) { + throw new RuntimeException(e); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + + } + +} diff --git a/security-utils/src/main/java/com/yahoo/security/Pkcs10CsrUtils.java b/security-utils/src/main/java/com/yahoo/security/Pkcs10CsrUtils.java new file mode 100644 index 00000000000..6f12450528d --- /dev/null +++ b/security-utils/src/main/java/com/yahoo/security/Pkcs10CsrUtils.java @@ -0,0 +1,38 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.security; + +import org.bouncycastle.openssl.PEMParser; +import org.bouncycastle.openssl.jcajce.JcaPEMWriter; +import org.bouncycastle.pkcs.PKCS10CertificationRequest; +import org.bouncycastle.util.io.pem.PemObject; + +import java.io.IOException; +import java.io.StringReader; +import java.io.StringWriter; +import java.io.UncheckedIOException; + +/** + * @author bjorncs + */ +public class Pkcs10CsrUtils { + + private Pkcs10CsrUtils() {} + + public static Pkcs10Csr fromPem(String pem) { + try (PEMParser pemParser = new PEMParser(new StringReader(pem))) { + return new Pkcs10Csr((PKCS10CertificationRequest) pemParser.readObject()); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + public static String toPem(Pkcs10Csr csr) { + try (StringWriter stringWriter = new StringWriter(); JcaPEMWriter pemWriter = new JcaPEMWriter(stringWriter)) { + pemWriter.writeObject(new PemObject("CERTIFICATE REQUEST", csr.getBcCsr().getEncoded())); + pemWriter.flush(); + return stringWriter.toString(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } +} diff --git a/security-utils/src/main/java/com/yahoo/security/SignatureAlgorithm.java b/security-utils/src/main/java/com/yahoo/security/SignatureAlgorithm.java new file mode 100644 index 00000000000..fbff18f5c12 --- /dev/null +++ b/security-utils/src/main/java/com/yahoo/security/SignatureAlgorithm.java @@ -0,0 +1,22 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.security; + +/** + * @author bjorncs + */ +public enum SignatureAlgorithm { + SHA256_WITH_RSA("SHA256withRSA"), + SHA512_WITH_RSA("SHA512withRSA"), + SHA256_WITH_ECDSA("SHA256withECDSA"), + SHA512_WITH_ECDSA("SHA512withECDSA"); + + private final String algorithmName; + + SignatureAlgorithm(String algorithmName) { + this.algorithmName = algorithmName; + } + + public String getAlgorithmName() { + return algorithmName; + } +} diff --git a/security-utils/src/main/java/com/yahoo/security/SslContextBuilder.java b/security-utils/src/main/java/com/yahoo/security/SslContextBuilder.java new file mode 100644 index 00000000000..75ab2417edf --- /dev/null +++ b/security-utils/src/main/java/com/yahoo/security/SslContextBuilder.java @@ -0,0 +1,137 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.security; + +import javax.net.ssl.KeyManager; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.GeneralSecurityException; +import java.security.KeyStore; +import java.security.PrivateKey; +import java.security.cert.X509Certificate; +import java.util.Collections; +import java.util.List; + +import static java.util.Collections.singletonList; + +/** + * @author bjorncs + */ +public class SslContextBuilder { + + private KeyStoreSupplier trustStoreSupplier; + private KeyStoreSupplier keyStoreSupplier; + private char[] keyStorePassword; + + public SslContextBuilder() {} + + public SslContextBuilder withTrustStore(Path file, KeyStoreType trustStoreType) { + this.trustStoreSupplier = () -> KeyStoreBuilder.withType(trustStoreType).fromFile(file).build(); + return this; + } + + public SslContextBuilder withTrustStore(KeyStore trustStore) { + this.trustStoreSupplier = () -> trustStore; + return this; + } + + public SslContextBuilder withTrustStore(X509Certificate caCertificate) { + return withTrustStore(singletonList(caCertificate)); + } + + public SslContextBuilder withTrustStore(List caCertificates) { + this.trustStoreSupplier = () -> createTrustStore(caCertificates); + return this; + } + + public SslContextBuilder withTrustStore(Path pemEncodedCaCertificates) { + this.trustStoreSupplier = () -> { + List caCertificates = + X509CertificateUtils.certificateListFromPem(new String(Files.readAllBytes(pemEncodedCaCertificates))); + return createTrustStore(caCertificates); + }; + return this; + } + + public SslContextBuilder withKeyStore(PrivateKey privateKey, X509Certificate certificate) { + char[] pwd = new char[0]; + this.keyStoreSupplier = () -> KeyStoreBuilder.withType(KeyStoreType.JKS).withKeyEntry("default", privateKey, certificate).build(); + this.keyStorePassword = pwd; + return this; + } + + public SslContextBuilder withKeyStore(KeyStore keyStore, char[] password) { + this.keyStoreSupplier = () -> keyStore; + this.keyStorePassword = password; + return this; + } + + public SslContextBuilder withKeyStore(Path file, char[] password, KeyStoreType keyStoreType) { + this.keyStoreSupplier = () -> KeyStoreBuilder.withType(keyStoreType).fromFile(file, password).build(); + this.keyStorePassword = password; + return this; + } + + public SslContextBuilder withKeyStore(Path privateKeyPemFile, Path certificatesPemFile) { + this.keyStoreSupplier = + () -> { + PrivateKey privateKey = KeyUtils.fromPemEncodedPrivateKey(new String(Files.readAllBytes(privateKeyPemFile))); + List certificates = X509CertificateUtils.certificateListFromPem(new String(Files.readAllBytes(certificatesPemFile))); + return KeyStoreBuilder.withType(KeyStoreType.JKS) + .withKeyEntry("default", privateKey, certificates) + .build(); + }; + this.keyStorePassword = new char[0]; + return this; + } + + public SSLContext build() { + try { + SSLContext sslContext = SSLContext.getInstance("TLSv1.2"); + TrustManager[] trustManagers = + trustStoreSupplier != null ? createTrustManagers(trustStoreSupplier) : null; + KeyManager[] keyManagers = + keyStoreSupplier != null ? createKeyManagers(keyStoreSupplier, keyStorePassword) : null; + sslContext.init(keyManagers, trustManagers, null); + return sslContext; + } catch (GeneralSecurityException e) { + throw new RuntimeException(e); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + private static TrustManager[] createTrustManagers(KeyStoreSupplier trustStoreSupplier) + throws GeneralSecurityException, IOException { + TrustManagerFactory trustManagerFactory = + TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + trustManagerFactory.init(trustStoreSupplier.get()); + return trustManagerFactory.getTrustManagers(); + } + + private static KeyManager[] createKeyManagers(KeyStoreSupplier keyStoreSupplier, char[] password) + throws GeneralSecurityException, IOException { + KeyManagerFactory keyManagerFactory = + KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + keyManagerFactory.init(keyStoreSupplier.get(), password); + return keyManagerFactory.getKeyManagers(); + } + + private static KeyStore createTrustStore(List caCertificates) { + KeyStoreBuilder trustStoreBuilder = KeyStoreBuilder.withType(KeyStoreType.JKS); + for (int i = 0; i < caCertificates.size(); i++) { + trustStoreBuilder.withCertificateEntry("cert-" + i, caCertificates.get(i)); + } + return trustStoreBuilder.build(); + } + + private interface KeyStoreSupplier { + KeyStore get() throws IOException, GeneralSecurityException; + } + +} diff --git a/security-utils/src/main/java/com/yahoo/security/SubjectAlternativeName.java b/security-utils/src/main/java/com/yahoo/security/SubjectAlternativeName.java new file mode 100644 index 00000000000..29395c75e70 --- /dev/null +++ b/security-utils/src/main/java/com/yahoo/security/SubjectAlternativeName.java @@ -0,0 +1,114 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.security; + +import org.bouncycastle.asn1.ASN1Encodable; +import org.bouncycastle.asn1.DERIA5String; +import org.bouncycastle.asn1.x500.X500Name; +import org.bouncycastle.asn1.x509.GeneralName; +import org.bouncycastle.asn1.x509.GeneralNames; + +import java.util.Arrays; +import java.util.List; +import java.util.Objects; + +import static java.util.stream.Collectors.toList; + +/** + * @author bjorncs + */ +public class SubjectAlternativeName { + + private final Type type; + private final String value; + + public SubjectAlternativeName(Type type, String value) { + this.type = type; + this.value = value; + } + + SubjectAlternativeName(GeneralName bcGeneralName) { + this.type = Type.fromTag(bcGeneralName.getTagNo()); + this.value = getValue(bcGeneralName); + } + + public Type getType() { + return type; + } + + public String getValue() { + return value; + } + + GeneralName toGeneralName() { + return new GeneralName(type.tag, value); + } + + static List fromGeneralNames(GeneralNames generalNames) { + return Arrays.stream(generalNames.getNames()).map(SubjectAlternativeName::new).collect(toList()); + } + + private String getValue(GeneralName bcGeneralName) { + ASN1Encodable name = bcGeneralName.getName(); + switch (bcGeneralName.getTagNo()) { + case GeneralName.rfc822Name: + case GeneralName.dNSName: + case GeneralName.uniformResourceIdentifier: + return DERIA5String.getInstance(name).getString(); + case GeneralName.directoryName: + return X500Name.getInstance(name).toString(); + default: + return name.toString(); + } + } + + @Override + public String toString() { + return "SubjectAlternativeName{" + + "type=" + type + + ", value='" + value + '\'' + + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + SubjectAlternativeName that = (SubjectAlternativeName) o; + return type == that.type && + Objects.equals(value, that.value); + } + + @Override + public int hashCode() { + return Objects.hash(type, value); + } + + public enum Type { + OTHER_NAME(0), + RFC822_NAME(1), + DNS_NAME(2), + X400_ADDRESS(3), + DIRECTORY_NAME(4), + EDI_PARITY_NAME(5), + UNIFORM_RESOURCE_IDENTIFIER(6), + IP_ADDRESS(7), + REGISTERED_ID(8); + + final int tag; + + Type(int tag) { + this.tag = tag; + } + + public static Type fromTag(int tag) { + return Arrays.stream(Type.values()) + .filter(type -> type.tag == tag) + .findAny() + .orElseThrow(() -> new IllegalArgumentException("Invalid tag: " + tag)); + } + + public int getTag() { + return tag; + } + } +} diff --git a/security-utils/src/main/java/com/yahoo/security/X509CertificateBuilder.java b/security-utils/src/main/java/com/yahoo/security/X509CertificateBuilder.java new file mode 100644 index 00000000000..54d7d39253e --- /dev/null +++ b/security-utils/src/main/java/com/yahoo/security/X509CertificateBuilder.java @@ -0,0 +1,167 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.security; + +import org.bouncycastle.asn1.x509.BasicConstraints; +import org.bouncycastle.asn1.x509.Extension; +import org.bouncycastle.asn1.x509.GeneralName; +import org.bouncycastle.asn1.x509.GeneralNames; +import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; +import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder; +import org.bouncycastle.operator.ContentSigner; +import org.bouncycastle.operator.OperatorException; +import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; +import org.bouncycastle.pkcs.PKCS10CertificationRequest; +import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest; + +import javax.security.auth.x500.X500Principal; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.math.BigInteger; +import java.security.GeneralSecurityException; +import java.security.KeyPair; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.SecureRandom; +import java.security.cert.X509Certificate; +import java.sql.Date; +import java.time.Instant; +import java.util.ArrayList; +import java.util.List; + +import static com.yahoo.security.SubjectAlternativeName.Type.DNS_NAME; + + +/** + * @author bjorncs + */ +public class X509CertificateBuilder { + + private final BigInteger serialNumber; + private final SignatureAlgorithm signingAlgorithm; + private final PrivateKey caPrivateKey; + private final Instant notBefore; + private final Instant notAfter; + private final List subjectAlternativeNames = new ArrayList<>(); + private final X500Principal issuer; + private final X500Principal subject; + private final PublicKey certPublicKey; + private BasicConstraintsExtension basicConstraintsExtension; + + private X509CertificateBuilder(X500Principal issuer, + X500Principal subject, + Instant notBefore, + Instant notAfter, + PublicKey certPublicKey, + PrivateKey caPrivateKey, + SignatureAlgorithm signingAlgorithm, + BigInteger serialNumber) { + this.issuer = issuer; + this.subject = subject; + this.notBefore = notBefore; + this.notAfter = notAfter; + this.certPublicKey = certPublicKey; + this.caPrivateKey = caPrivateKey; + this.signingAlgorithm = signingAlgorithm; + this.serialNumber = serialNumber; + } + + public static X509CertificateBuilder fromCsr(Pkcs10Csr csr, + X500Principal caIssuer, + Instant notBefore, + Instant notAfter, + PrivateKey caPrivateKey, + SignatureAlgorithm signingAlgorithm, + BigInteger serialNumber) { + try { + PKCS10CertificationRequest bcCsr = csr.getBcCsr(); + PublicKey publicKey = new JcaPKCS10CertificationRequest(bcCsr) + .setProvider(BouncyCastleProviderHolder.getInstance()) + .getPublicKey(); + return new X509CertificateBuilder(caIssuer, + new X500Principal(bcCsr.getSubject().getEncoded()), + notBefore, + notAfter, + publicKey, + caPrivateKey, + signingAlgorithm, + serialNumber); + } catch (GeneralSecurityException e) { + throw new RuntimeException(e); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + public static X509CertificateBuilder fromKeypair(KeyPair keyPair, + X500Principal subject, + Instant notBefore, + Instant notAfter, + SignatureAlgorithm signingAlgorithm, + BigInteger serialNumber) { + return new X509CertificateBuilder(subject, + subject, + notBefore, + notAfter, + keyPair.getPublic(), + keyPair.getPrivate(), + signingAlgorithm, + serialNumber); + } + + /** + * @return generates a cryptographically secure positive serial number up to 128 bits + */ + public static BigInteger generateRandomSerialNumber() { + return new BigInteger(128, new SecureRandom()); + } + + public X509CertificateBuilder addSubjectAlternativeName(String dnsName) { + this.subjectAlternativeNames.add(new SubjectAlternativeName(DNS_NAME, dnsName)); + return this; + } + + public X509CertificateBuilder addSubjectAlternativeName(SubjectAlternativeName san) { + this.subjectAlternativeNames.add(san); + return this; + } + + public X509CertificateBuilder setBasicConstraints(boolean isCritical, boolean isCertAuthorityCertificate) { + this.basicConstraintsExtension = new BasicConstraintsExtension(isCritical, isCertAuthorityCertificate); + return this; + } + + public X509CertificateBuilder setIsCertAuthority(boolean isCertAuthority) { + return setBasicConstraints(true, isCertAuthority); + } + + public X509Certificate build() { + try { + JcaX509v3CertificateBuilder jcaCertBuilder = new JcaX509v3CertificateBuilder( + issuer, serialNumber, Date.from(notBefore), Date.from(notAfter), subject, certPublicKey); + if (basicConstraintsExtension != null) { + jcaCertBuilder.addExtension( + Extension.basicConstraints, + basicConstraintsExtension.isCritical, + new BasicConstraints(basicConstraintsExtension.isCertAuthorityCertificate)); + } + if (!subjectAlternativeNames.isEmpty()) { + GeneralNames generalNames = new GeneralNames( + subjectAlternativeNames.stream() + .map(SubjectAlternativeName::toGeneralName) + .toArray(GeneralName[]::new)); + jcaCertBuilder.addExtension(Extension.subjectAlternativeName, false, generalNames); + } + ContentSigner contentSigner = new JcaContentSignerBuilder(signingAlgorithm.getAlgorithmName()) + .setProvider(BouncyCastleProviderHolder.getInstance()) + .build(caPrivateKey); + return new JcaX509CertificateConverter() + .setProvider(BouncyCastleProviderHolder.getInstance()) + .getCertificate(jcaCertBuilder.build(contentSigner)); + } catch (OperatorException | GeneralSecurityException e) { + throw new RuntimeException(e); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + +} diff --git a/security-utils/src/main/java/com/yahoo/security/X509CertificateUtils.java b/security-utils/src/main/java/com/yahoo/security/X509CertificateUtils.java new file mode 100644 index 00000000000..33bd750bac5 --- /dev/null +++ b/security-utils/src/main/java/com/yahoo/security/X509CertificateUtils.java @@ -0,0 +1,136 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.security; + +import org.bouncycastle.asn1.ASN1Encodable; +import org.bouncycastle.asn1.ASN1OctetString; +import org.bouncycastle.asn1.ASN1Primitive; +import org.bouncycastle.asn1.x509.GeneralNames; +import org.bouncycastle.cert.X509CertificateHolder; +import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; +import org.bouncycastle.openssl.PEMParser; +import org.bouncycastle.openssl.jcajce.JcaPEMWriter; +import org.bouncycastle.util.io.pem.PemObject; + +import javax.naming.NamingException; +import javax.naming.ldap.LdapName; +import javax.security.auth.x500.X500Principal; +import java.io.IOException; +import java.io.StringReader; +import java.io.StringWriter; +import java.io.UncheckedIOException; +import java.security.GeneralSecurityException; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import static com.yahoo.security.Extension.SUBJECT_ALTERNATIVE_NAMES; +import static java.util.stream.Collectors.toList; + +/** + * @author bjorncs + */ +public class X509CertificateUtils { + + private X509CertificateUtils() {} + + public static X509Certificate fromPem(String pem) { + try (PEMParser parser = new PEMParser(new StringReader(pem))) { + return toX509Certificate(parser.readObject()); + } catch (IOException e) { + throw new UncheckedIOException(e); + } catch (CertificateException e) { + throw new RuntimeException(e); + } + } + + public static List certificateListFromPem(String pem) { + try (PEMParser parser = new PEMParser(new StringReader(pem))) { + List list = new ArrayList<>(); + Object pemObject; + while ((pemObject = parser.readObject()) != null) { + list.add(toX509Certificate(pemObject)); + } + return list; + } catch (IOException e) { + throw new UncheckedIOException(e); + } catch (CertificateException e) { + throw new RuntimeException(e); + } + } + + private static X509Certificate toX509Certificate(Object pemObject) throws CertificateException { + if (pemObject instanceof X509Certificate) { + return (X509Certificate) pemObject; + } + if (pemObject instanceof X509CertificateHolder) { + return new JcaX509CertificateConverter() + .setProvider(BouncyCastleProviderHolder.getInstance()) + .getCertificate((X509CertificateHolder) pemObject); + } + throw new IllegalArgumentException("Invalid type of PEM object: " + pemObject); + } + + public static String toPem(X509Certificate certificate) { + try (StringWriter stringWriter = new StringWriter(); JcaPEMWriter pemWriter = new JcaPEMWriter(stringWriter)) { + pemWriter.writeObject(new PemObject("CERTIFICATE", certificate.getEncoded())); + pemWriter.flush(); + return stringWriter.toString(); + } catch (GeneralSecurityException e) { + throw new RuntimeException(e); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + public static String toPem(List certificates) { + try (StringWriter stringWriter = new StringWriter(); JcaPEMWriter pemWriter = new JcaPEMWriter(stringWriter)) { + for (X509Certificate certificate : certificates) { + pemWriter.writeObject(new PemObject("CERTIFICATE", certificate.getEncoded())); + } + pemWriter.flush(); + return stringWriter.toString(); + } catch (GeneralSecurityException e) { + throw new RuntimeException(e); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + public static List getSubjectCommonNames(X509Certificate certificate) { + return getCommonNames(certificate.getSubjectX500Principal()); + } + + public static List getIssuerCommonNames(X509Certificate certificate) { + return getCommonNames(certificate.getIssuerX500Principal()); + } + + public static List getCommonNames(X500Principal subject) { + try { + String subjectPrincipal = subject.getName(); + return new LdapName(subjectPrincipal).getRdns().stream() + .filter(rdn -> rdn.getType().equalsIgnoreCase("cn")) + .map(rdn -> rdn.getValue().toString()) + .collect(toList()); + } catch (NamingException e) { + throw new IllegalArgumentException("Invalid CN: " + e, e); + } + + } + + public static List getSubjectAlternativeNames(X509Certificate certificate) { + try { + byte[] extensionValue = certificate.getExtensionValue(SUBJECT_ALTERNATIVE_NAMES.getOId()); + if (extensionValue == null) return Collections.emptyList(); + ASN1Encodable asn1Encodable = ASN1Primitive.fromByteArray(extensionValue); + if (asn1Encodable instanceof ASN1OctetString) { + asn1Encodable = ASN1Primitive.fromByteArray(((ASN1OctetString) asn1Encodable).getOctets()); + } + GeneralNames names = GeneralNames.getInstance(asn1Encodable); + return SubjectAlternativeName.fromGeneralNames(names); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } +} diff --git a/security-utils/src/main/java/com/yahoo/security/package-info.java b/security-utils/src/main/java/com/yahoo/security/package-info.java new file mode 100644 index 00000000000..10a4c9c0e0d --- /dev/null +++ b/security-utils/src/main/java/com/yahoo/security/package-info.java @@ -0,0 +1,9 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +/** + * @author bjorncs + */ + +@ExportPackage +package com.yahoo.security; + +import com.yahoo.osgi.annotation.ExportPackage; \ No newline at end of file diff --git a/security-utils/src/main/java/com/yahoo/security/tls/TransportSecurityOptions.java b/security-utils/src/main/java/com/yahoo/security/tls/TransportSecurityOptions.java new file mode 100644 index 00000000000..f0d1edd6889 --- /dev/null +++ b/security-utils/src/main/java/com/yahoo/security/tls/TransportSecurityOptions.java @@ -0,0 +1,90 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.security.tls; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Objects; +import java.util.Optional; + +/** + * Generic TLS configuration for Vespa + * + * @author bjorncs + */ +public class TransportSecurityOptions { + + private static final ObjectMapper mapper = new ObjectMapper(); + + private final Path privateKeyFile; + private final Path certificatesFile; + private final Path caCertificatesFile; + + public TransportSecurityOptions(String privateKeyFile, String certificatesFile, String caCertificatesFile) { + this(Paths.get(privateKeyFile), Paths.get(certificatesFile), Paths.get(caCertificatesFile)); + } + + public TransportSecurityOptions(Path privateKeyFile, Path certificatesFile, Path caCertificatesFile) { + this.privateKeyFile = privateKeyFile; + this.certificatesFile = certificatesFile; + this.caCertificatesFile = caCertificatesFile; + } + + public Path getPrivateKeyFile() { + return privateKeyFile; + } + + public Path getCertificatesFile() { + return certificatesFile; + } + + public Path getCaCertificatesFile() { + return caCertificatesFile; + } + + public static TransportSecurityOptions fromJsonFile(Path file) { + try { + JsonNode root = mapper.readTree(file.toFile()); + JsonNode filesNode = getField(root, "files"); + String privateKeyFile = getField(filesNode, "private-key").asText(); + String certificatesFile = getField(filesNode, "certificates").asText(); + String caCertificatesFile = getField(filesNode, "ca-certificates").asText(); + return new TransportSecurityOptions(privateKeyFile, certificatesFile, caCertificatesFile); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + private static JsonNode getField(JsonNode root, String fieldName) { + return Optional.ofNullable(root.get(fieldName)) + .orElseThrow(() -> new IllegalArgumentException(String.format("'%s' field missing", fieldName))); + } + + @Override + public String toString() { + return "TransportSecurityOptions{" + + "privateKeyFile=" + privateKeyFile + + ", certificatesFile=" + certificatesFile + + ", caCertificatesFile=" + caCertificatesFile + + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + TransportSecurityOptions that = (TransportSecurityOptions) o; + return Objects.equals(privateKeyFile, that.privateKeyFile) && + Objects.equals(certificatesFile, that.certificatesFile) && + Objects.equals(caCertificatesFile, that.caCertificatesFile); + } + + @Override + public int hashCode() { + return Objects.hash(privateKeyFile, certificatesFile, caCertificatesFile); + } +} \ No newline at end of file diff --git a/security-utils/src/main/java/com/yahoo/security/tls/TransportSecurityUtils.java b/security-utils/src/main/java/com/yahoo/security/tls/TransportSecurityUtils.java new file mode 100644 index 00000000000..5595d33a9b5 --- /dev/null +++ b/security-utils/src/main/java/com/yahoo/security/tls/TransportSecurityUtils.java @@ -0,0 +1,66 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.security.tls; + +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.Optional; + +/** + * Utility class for retrieving {@link TransportSecurityOptions} from the system. + * + * @author bjorncs + */ +public class TransportSecurityUtils { + + public static final String CONFIG_FILE_ENVIRONMENT_VARIABLE = "VESPA_TLS_CONFIG_FILE"; + public static final String INSECURE_MIXED_MODE_ENVIRONMENT_VARIABLE = "VESPA_TLS_INSECURE_MIXED_MODE"; + + public enum MixedMode { + PLAINTEXT_CLIENT_MIXED_SERVER("plaintext_client_mixed_server"), + TLS_CLIENT_MIXED_SERVER("tls_client_mixed_server"); + + final String configValue; + + MixedMode(String configValue) { + this.configValue = configValue; + } + + static MixedMode fromConfigValue(String configValue) { + return Arrays.stream(values()) + .filter(v -> v.configValue.equals(configValue)) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("Unknown value: " + configValue)); + } + } + + private TransportSecurityUtils() {} + + public static boolean isTransportSecurityEnabled() { + return getConfigFile().isPresent(); + } + + public static boolean isInsecureMixedModeEnabled() { + return getInsecureMixedMode().isPresent(); + } + + public static Optional getInsecureMixedMode() { + if (!isTransportSecurityEnabled()) return Optional.empty(); + return getEnvironmentVariable(INSECURE_MIXED_MODE_ENVIRONMENT_VARIABLE) + .map(MixedMode::fromConfigValue); + } + + public static Optional getConfigFile() { + return getEnvironmentVariable(CONFIG_FILE_ENVIRONMENT_VARIABLE).map(Paths::get); + } + + public static Optional getOptions() { + return getConfigFile() + .map(TransportSecurityOptions::fromJsonFile); + } + + private static Optional getEnvironmentVariable(String environmentVariable) { + return Optional.ofNullable(System.getenv(environmentVariable)) + .filter(var -> !var.isEmpty()); + } +} diff --git a/security-utils/src/main/java/com/yahoo/security/tls/package-info.java b/security-utils/src/main/java/com/yahoo/security/tls/package-info.java new file mode 100644 index 00000000000..b5668182f14 --- /dev/null +++ b/security-utils/src/main/java/com/yahoo/security/tls/package-info.java @@ -0,0 +1,8 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +/** + * @author bjorncs + */ +@ExportPackage +package com.yahoo.security.tls; + +import com.yahoo.osgi.annotation.ExportPackage; \ No newline at end of file diff --git a/security-utils/src/test/java/com/yahoo/security/KeyStoreBuilderTest.java b/security-utils/src/test/java/com/yahoo/security/KeyStoreBuilderTest.java new file mode 100644 index 00000000000..06ea5d963a3 --- /dev/null +++ b/security-utils/src/test/java/com/yahoo/security/KeyStoreBuilderTest.java @@ -0,0 +1,55 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.security; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import java.nio.file.Path; +import java.security.KeyPair; +import java.security.cert.X509Certificate; + +import static com.yahoo.security.TestUtils.createCertificate; +import static com.yahoo.security.TestUtils.createKeystoreFile; + + +/** + * @author bjorncs + */ +public class KeyStoreBuilderTest { + + private static final char[] PASSWORD = new char[0]; + + @Rule + public TemporaryFolder tempDirectory = new TemporaryFolder(); + + @Test + public void can_create_jks_keystore_from_privatekey_and_certificate() throws Exception { + KeyPair keyPair = KeyUtils.generateKeypair(KeyAlgorithm.EC, 256); + X509Certificate certificate = createCertificate(keyPair); + KeyStoreBuilder.withType(KeyStoreType.JKS) + .withKeyEntry("key", keyPair.getPrivate(), certificate) + .build(); + } + + @Test + public void can_build_jks_keystore_from_file() throws Exception { + Path keystoreFile = tempDirectory.newFile().toPath(); + createKeystoreFile(keystoreFile, KeyStoreType.JKS, PASSWORD); + + KeyStoreBuilder.withType(KeyStoreType.JKS) + .fromFile(keystoreFile, PASSWORD) + .build(); + } + + @Test + public void can_build_pcks12_keystore_from_file() throws Exception { + Path keystoreFile = tempDirectory.newFile().toPath(); + createKeystoreFile(keystoreFile, KeyStoreType.PKCS12, PASSWORD); + + KeyStoreBuilder.withType(KeyStoreType.PKCS12) + .fromFile(keystoreFile, PASSWORD) + .build(); + } + +} \ No newline at end of file diff --git a/security-utils/src/test/java/com/yahoo/security/KeyUtilsTest.java b/security-utils/src/test/java/com/yahoo/security/KeyUtilsTest.java new file mode 100644 index 00000000000..5e786654d7c --- /dev/null +++ b/security-utils/src/test/java/com/yahoo/security/KeyUtilsTest.java @@ -0,0 +1,54 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.security; + +import org.junit.Test; + +import java.security.KeyPair; +import java.security.PrivateKey; +import java.security.PublicKey; + +import static org.hamcrest.CoreMatchers.containsString; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThat; + +/** + * @author bjorncs + */ +public class KeyUtilsTest { + + @Test + public void can_extract_public_key_from_rsa_private() { + KeyPair keyPair = KeyUtils.generateKeypair(KeyAlgorithm.RSA); + PublicKey publicKey = KeyUtils.extractPublicKey(keyPair.getPrivate()); + assertNotNull(publicKey); + } + + @Test + public void can_extract_public_key_from_ecdsa_private() { + KeyPair keyPair = KeyUtils.generateKeypair(KeyAlgorithm.EC); + PublicKey publicKey = KeyUtils.extractPublicKey(keyPair.getPrivate()); + assertNotNull(publicKey); + } + + @Test + public void can_serialize_and_deserialize_rsa_privatekey_using_pem_format() { + KeyPair keyPair = KeyUtils.generateKeypair(KeyAlgorithm.RSA); + String pem = KeyUtils.toPem(keyPair.getPrivate()); + assertThat(pem, containsString("BEGIN RSA PRIVATE KEY")); + assertThat(pem, containsString("END RSA PRIVATE KEY")); + PrivateKey deserializedKey = KeyUtils.fromPemEncodedPrivateKey(pem); + assertEquals(keyPair.getPrivate(), deserializedKey); + } + + @Test + public void can_serialize_and_deserialize_ec_privatekey_using_pem_format() { + KeyPair keyPair = KeyUtils.generateKeypair(KeyAlgorithm.EC); + String pem = KeyUtils.toPem(keyPair.getPrivate()); + assertThat(pem, containsString("BEGIN EC PRIVATE KEY")); + assertThat(pem, containsString("END EC PRIVATE KEY")); + PrivateKey deserializedKey = KeyUtils.fromPemEncodedPrivateKey(pem); + assertEquals(keyPair.getPrivate(), deserializedKey); + } + +} \ No newline at end of file diff --git a/security-utils/src/test/java/com/yahoo/security/Pkcs10CsrBuilderTest.java b/security-utils/src/test/java/com/yahoo/security/Pkcs10CsrBuilderTest.java new file mode 100644 index 00000000000..d51203a5cb2 --- /dev/null +++ b/security-utils/src/test/java/com/yahoo/security/Pkcs10CsrBuilderTest.java @@ -0,0 +1,27 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.security; + +import org.junit.Test; + +import javax.security.auth.x500.X500Principal; +import java.security.KeyPair; + +import static org.junit.Assert.assertEquals; + +/** + * @author bjorncs + */ +public class Pkcs10CsrBuilderTest { + + @Test + public void can_build_csr_with_sans() { + X500Principal subject = new X500Principal("CN=subject"); + KeyPair keypair = KeyUtils.generateKeypair(KeyAlgorithm.EC, 256); + Pkcs10Csr csr = Pkcs10CsrBuilder.fromKeypair(subject, keypair, SignatureAlgorithm.SHA512_WITH_ECDSA) + .addSubjectAlternativeName("san1.com") + .addSubjectAlternativeName("san2.com") + .build(); + assertEquals(subject, csr.getSubject()); + } + +} \ No newline at end of file diff --git a/security-utils/src/test/java/com/yahoo/security/Pkcs10CsrTest.java b/security-utils/src/test/java/com/yahoo/security/Pkcs10CsrTest.java new file mode 100644 index 00000000000..cc1f6cc6a14 --- /dev/null +++ b/security-utils/src/test/java/com/yahoo/security/Pkcs10CsrTest.java @@ -0,0 +1,57 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.security; + +import org.junit.Test; + +import javax.security.auth.x500.X500Principal; +import java.security.KeyPair; +import java.util.Arrays; +import java.util.List; + +import static com.yahoo.security.SubjectAlternativeName.Type.DNS_NAME; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +/** + * @author bjorncs + */ +public class Pkcs10CsrTest { + + @Test + public void can_read_subject_alternative_names() { + X500Principal subject = new X500Principal("CN=subject"); + KeyPair keypair = KeyUtils.generateKeypair(KeyAlgorithm.EC, 256); + SubjectAlternativeName san1 = new SubjectAlternativeName(DNS_NAME, "san1.com"); + SubjectAlternativeName san2 = new SubjectAlternativeName(DNS_NAME, "san2.com"); + Pkcs10Csr csr = Pkcs10CsrBuilder.fromKeypair(subject, keypair, SignatureAlgorithm.SHA512_WITH_ECDSA) + .addSubjectAlternativeName(san1) + .addSubjectAlternativeName(san2) + .build(); + assertEquals(Arrays.asList(san1, san2), csr.getSubjectAlternativeNames()); + } + + @Test + public void can_read_basic_constraints() { + X500Principal subject = new X500Principal("CN=subject"); + KeyPair keypair = KeyUtils.generateKeypair(KeyAlgorithm.EC, 256); + Pkcs10Csr csr = Pkcs10CsrBuilder.fromKeypair(subject, keypair, SignatureAlgorithm.SHA512_WITH_ECDSA) + .setBasicConstraints(true, true) + .build(); + assertTrue(csr.getBasicConstraints().isPresent()); + assertTrue(csr.getBasicConstraints().get()); + } + + @Test + public void can_read_extensions() { + X500Principal subject = new X500Principal("CN=subject"); + KeyPair keypair = KeyUtils.generateKeypair(KeyAlgorithm.EC, 256); + Pkcs10Csr csr = Pkcs10CsrBuilder.fromKeypair(subject, keypair, SignatureAlgorithm.SHA512_WITH_ECDSA) + .addSubjectAlternativeName("san") + .setBasicConstraints(true, true) + .build(); + List expected = Arrays.asList(Extension.BASIC_CONSTRAINTS.getOId(), Extension.SUBJECT_ALTERNATIVE_NAMES.getOId()); + List actual = csr.getExtensionOIds(); + assertEquals(expected, actual); + } + +} \ No newline at end of file diff --git a/security-utils/src/test/java/com/yahoo/security/Pkcs10CsrUtilsTest.java b/security-utils/src/test/java/com/yahoo/security/Pkcs10CsrUtilsTest.java new file mode 100644 index 00000000000..04d35a537bb --- /dev/null +++ b/security-utils/src/test/java/com/yahoo/security/Pkcs10CsrUtilsTest.java @@ -0,0 +1,30 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.security; + +import org.junit.Test; + +import javax.security.auth.x500.X500Principal; +import java.security.KeyPair; + +import static org.hamcrest.CoreMatchers.containsString; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; + +/** + * @author bjorncs + */ +public class Pkcs10CsrUtilsTest { + + @Test + public void can_deserialize_serialized_pem_csr() { + X500Principal subject = new X500Principal("CN=subject"); + KeyPair keypair = KeyUtils.generateKeypair(KeyAlgorithm.EC, 256); + Pkcs10Csr csr = Pkcs10CsrBuilder.fromKeypair(subject, keypair, SignatureAlgorithm.SHA512_WITH_ECDSA).build(); + String pem = Pkcs10CsrUtils.toPem(csr); + Pkcs10Csr deserializedCsr = Pkcs10CsrUtils.fromPem(pem); + assertThat(pem, containsString("BEGIN CERTIFICATE REQUEST")); + assertThat(pem, containsString("END CERTIFICATE REQUEST")); + assertEquals(subject, deserializedCsr.getSubject()); + } + +} \ No newline at end of file diff --git a/security-utils/src/test/java/com/yahoo/security/SslContextBuilderTest.java b/security-utils/src/test/java/com/yahoo/security/SslContextBuilderTest.java new file mode 100644 index 00000000000..cc269a4ef43 --- /dev/null +++ b/security-utils/src/test/java/com/yahoo/security/SslContextBuilderTest.java @@ -0,0 +1,77 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.security; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import java.nio.file.Path; +import java.security.KeyPair; +import java.security.cert.X509Certificate; + +import static com.yahoo.security.TestUtils.createCertificate; +import static com.yahoo.security.TestUtils.createKeystore; +import static com.yahoo.security.TestUtils.createKeystoreFile; + +/** + * @author bjorncs + */ +public class SslContextBuilderTest { + + private static final char[] PASSWORD = new char[0]; + + @Rule + public TemporaryFolder tempDirectory = new TemporaryFolder(); + + @Test + public void can_build_sslcontext_with_truststore_only() throws Exception { + new SslContextBuilder() + .withTrustStore(createKeystore(KeyStoreType.JKS, PASSWORD)) + .build(); + } + + @Test + public void can_build_sslcontext_with_keystore_only() throws Exception { + new SslContextBuilder() + .withKeyStore(createKeystore(KeyStoreType.JKS, PASSWORD), PASSWORD) + .build(); + } + + @Test + public void can_build_sslcontext_with_truststore_and_keystore() throws Exception { + new SslContextBuilder() + .withKeyStore(createKeystore(KeyStoreType.JKS, PASSWORD), PASSWORD) + .withTrustStore(createKeystore(KeyStoreType.JKS, PASSWORD)) + .build(); + } + + @Test + public void can_build_sslcontext_with_keystore_from_private_key_and_certificate() throws Exception { + KeyPair keyPair = KeyUtils.generateKeypair(KeyAlgorithm.EC, 256); + X509Certificate certificate = createCertificate(keyPair); + new SslContextBuilder() + .withKeyStore(keyPair.getPrivate(), certificate) + .build(); + } + + @Test + public void can_build_sslcontext_with_jks_keystore_from_file() throws Exception { + Path keystoreFile = tempDirectory.newFile().toPath(); + createKeystoreFile(keystoreFile, KeyStoreType.JKS, PASSWORD); + + new SslContextBuilder() + .withKeyStore(keystoreFile, PASSWORD, KeyStoreType.JKS) + .build(); + } + + @Test + public void can_build_sslcontext_with_pcks12_keystore_from_file() throws Exception { + Path keystoreFile = tempDirectory.newFile().toPath(); + createKeystoreFile(keystoreFile, KeyStoreType.PKCS12, PASSWORD); + + new SslContextBuilder() + .withKeyStore(keystoreFile, PASSWORD, KeyStoreType.PKCS12) + .build(); + } + +} diff --git a/security-utils/src/test/java/com/yahoo/security/TestUtils.java b/security-utils/src/test/java/com/yahoo/security/TestUtils.java new file mode 100644 index 00000000000..fcfcfb2b761 --- /dev/null +++ b/security-utils/src/test/java/com/yahoo/security/TestUtils.java @@ -0,0 +1,42 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.security; + +import javax.security.auth.x500.X500Principal; +import java.math.BigInteger; +import java.nio.file.Path; +import java.security.KeyPair; +import java.security.KeyStore; +import java.security.cert.X509Certificate; +import java.time.Instant; +import java.time.temporal.ChronoUnit; + +import static com.yahoo.security.KeyStoreUtils.writeKeyStoreToFile; + + +/** + * @author bjorncs + */ +class TestUtils { + + static KeyStore createKeystore(KeyStoreType type, char[] password) { + KeyPair keyPair = KeyUtils.generateKeypair(KeyAlgorithm.EC, 256); + return KeyStoreBuilder.withType(type) + .withKeyEntry("entry-name", keyPair.getPrivate(), password, createCertificate(keyPair)) + .build(); + } + + static X509Certificate createCertificate(KeyPair keyPair) { + return createCertificate(keyPair, new X500Principal("CN=mysubject")); + } + + static X509Certificate createCertificate(KeyPair keyPair, X500Principal subject) { + return X509CertificateBuilder + .fromKeypair( + keyPair, subject, Instant.now(), Instant.now().plus(1, ChronoUnit.DAYS), SignatureAlgorithm.SHA512_WITH_ECDSA, BigInteger.valueOf(1)) + .build(); + } + + static void createKeystoreFile(Path file, KeyStoreType type, char[] password) { + writeKeyStoreToFile(createKeystore(type, password), file, password); + } +} diff --git a/security-utils/src/test/java/com/yahoo/security/X509CertificateBuilderTest.java b/security-utils/src/test/java/com/yahoo/security/X509CertificateBuilderTest.java new file mode 100644 index 00000000000..7e6d343b570 --- /dev/null +++ b/security-utils/src/test/java/com/yahoo/security/X509CertificateBuilderTest.java @@ -0,0 +1,83 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.security; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import javax.security.auth.x500.X500Principal; +import java.math.BigInteger; +import java.security.KeyPair; +import java.security.cert.X509Certificate; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.Arrays; +import java.util.Collection; + +import static org.junit.Assert.assertEquals; + +/** + * @author bjorncs + */ +@RunWith(Parameterized.class) +public class X509CertificateBuilderTest { + + @Parameterized.Parameters(name = "{0}") + public static Collection data() { + return Arrays.asList(new Object[][] { + {KeyAlgorithm.RSA, 2048, SignatureAlgorithm.SHA512_WITH_RSA}, + {KeyAlgorithm.EC, 256, SignatureAlgorithm.SHA512_WITH_ECDSA}}); + } + + private final KeyAlgorithm keyAlgorithm; + private final int keySize; + private final SignatureAlgorithm signatureAlgorithm; + + public X509CertificateBuilderTest(KeyAlgorithm keyAlgorithm, + int keySize, + SignatureAlgorithm signatureAlgorithm) { + this.keyAlgorithm = keyAlgorithm; + this.keySize = keySize; + this.signatureAlgorithm = signatureAlgorithm; + } + + @Test + public void can_build_self_signed_certificate() { + KeyPair keyPair = KeyUtils.generateKeypair(keyAlgorithm, keySize); + X500Principal subject = new X500Principal("CN=myservice"); + X509Certificate cert = + X509CertificateBuilder.fromKeypair( + keyPair, + subject, + Instant.now(), + Instant.now().plus(1, ChronoUnit.DAYS), + signatureAlgorithm, + BigInteger.valueOf(1)) + .setBasicConstraints(true, true) + .build(); + assertEquals(subject, cert.getSubjectX500Principal()); + } + + @Test + public void can_build_certificate_from_csr() { + X500Principal subject = new X500Principal("CN=subject"); + X500Principal issuer = new X500Principal("CN=issuer"); + KeyPair csrKeypair = KeyUtils.generateKeypair(keyAlgorithm, keySize); + Pkcs10Csr csr = Pkcs10CsrBuilder.fromKeypair(subject, csrKeypair, signatureAlgorithm).build(); + KeyPair caKeypair = KeyUtils.generateKeypair(keyAlgorithm, keySize); + X509Certificate cert = X509CertificateBuilder + .fromCsr( + csr, + issuer, + Instant.now(), + Instant.now().plus(1, ChronoUnit.DAYS), + caKeypair.getPrivate(), + signatureAlgorithm, + BigInteger.valueOf(1)) + .addSubjectAlternativeName("subject1.alt") + .addSubjectAlternativeName("subject2.alt") + .build(); + assertEquals(subject, cert.getSubjectX500Principal()); + } + +} \ No newline at end of file diff --git a/security-utils/src/test/java/com/yahoo/security/X509CertificateUtilsTest.java b/security-utils/src/test/java/com/yahoo/security/X509CertificateUtilsTest.java new file mode 100644 index 00000000000..76a93028efe --- /dev/null +++ b/security-utils/src/test/java/com/yahoo/security/X509CertificateUtilsTest.java @@ -0,0 +1,74 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.security; + +import org.junit.Test; + +import javax.security.auth.x500.X500Principal; +import java.math.BigInteger; +import java.security.KeyPair; +import java.security.cert.X509Certificate; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.Arrays; +import java.util.List; + +import static com.yahoo.security.SubjectAlternativeName.Type.DNS_NAME; +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; + +/** + * @author bjorncs + */ +public class X509CertificateUtilsTest { + @Test + public void can_deserialize_serialized_pem_certificate() { + KeyPair keypair = KeyUtils.generateKeypair(KeyAlgorithm.EC, 256); + X500Principal subject = new X500Principal("CN=myservice"); + X509Certificate cert = TestUtils.createCertificate(keypair, subject); + assertEquals(subject, cert.getSubjectX500Principal()); + String pem = X509CertificateUtils.toPem(cert); + assertThat(pem, containsString("BEGIN CERTIFICATE")); + assertThat(pem, containsString("END CERTIFICATE")); + X509Certificate deserializedCert = X509CertificateUtils.fromPem(pem); + assertEquals(subject, deserializedCert.getSubjectX500Principal()); + } + + @Test + public void can_deserialize_serialized_pem_certificate_list() { + KeyPair keypair = KeyUtils.generateKeypair(KeyAlgorithm.EC, 256); + X500Principal subject1 = new X500Principal("CN=myservice1"); + X509Certificate cert1 = TestUtils.createCertificate(keypair, subject1); + X500Principal subject2 = new X500Principal("CN=myservice2"); + X509Certificate cert2 = TestUtils.createCertificate(keypair, subject2); + List certificateList = Arrays.asList(cert1, cert2); + String pem = X509CertificateUtils.toPem(certificateList); + List deserializedCertificateList = X509CertificateUtils.certificateListFromPem(pem); + assertEquals(2, certificateList.size()); + assertEquals(subject1, deserializedCertificateList.get(0).getSubjectX500Principal()); + assertEquals(subject2, deserializedCertificateList.get(1).getSubjectX500Principal()); + } + + @Test + public void can_list_subject_alternative_names() { + KeyPair keypair = KeyUtils.generateKeypair(KeyAlgorithm.EC, 256); + X500Principal subject = new X500Principal("CN=myservice"); + SubjectAlternativeName san = new SubjectAlternativeName(DNS_NAME, "dns-san"); + X509Certificate cert = X509CertificateBuilder + .fromKeypair( + keypair, + subject, + Instant.now(), + Instant.now().plus(1, ChronoUnit.DAYS), + SignatureAlgorithm.SHA512_WITH_ECDSA, + BigInteger.valueOf(1)) + .addSubjectAlternativeName(san) + .build(); + + List sans = X509CertificateUtils.getSubjectAlternativeNames(cert); + assertThat(sans.size(), is(1)); + assertThat(sans.get(0), equalTo(san)); + } +} \ No newline at end of file diff --git a/security-utils/src/test/java/com/yahoo/security/tls/TransportSecurityOptionsTest.java b/security-utils/src/test/java/com/yahoo/security/tls/TransportSecurityOptionsTest.java new file mode 100644 index 00000000000..f311651cab0 --- /dev/null +++ b/security-utils/src/test/java/com/yahoo/security/tls/TransportSecurityOptionsTest.java @@ -0,0 +1,25 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.security.tls; + +import org.junit.Test; + +import java.nio.file.Path; +import java.nio.file.Paths; + +import static org.junit.Assert.*; + +/** + * @author bjorncs + */ +public class TransportSecurityOptionsTest { + + private static final Path TEST_CONFIG_FILE = Paths.get("src/test/resources/transport-security-options.json"); + + @Test + public void can_read_options_from_json_file() { + TransportSecurityOptions expectedOptions = new TransportSecurityOptions("myhost.key", "certs.pem", "my_cas.pem"); + TransportSecurityOptions actualOptions = TransportSecurityOptions.fromJsonFile(TEST_CONFIG_FILE); + assertEquals(expectedOptions, actualOptions); + } + +} diff --git a/security-utils/src/test/resources/transport-security-options.json b/security-utils/src/test/resources/transport-security-options.json new file mode 100644 index 00000000000..0506c130722 --- /dev/null +++ b/security-utils/src/test/resources/transport-security-options.json @@ -0,0 +1,7 @@ +{ + "files": { + "private-key": "myhost.key", + "ca-certificates": "my_cas.pem", + "certificates": "certs.pem" + } +} \ No newline at end of file diff --git a/vespajlib/pom.xml b/vespajlib/pom.xml index 741b4b58cc9..68a959b2e30 100644 --- a/vespajlib/pom.xml +++ b/vespajlib/pom.xml @@ -36,7 +36,6 @@ jna - com.google.guava @@ -55,26 +54,6 @@ ${project.version} provided - - org.bouncycastle - bcprov-jdk15on - provided - - - org.bouncycastle - bcpkix-jdk15on - provided - - - com.fasterxml.jackson.core - jackson-core - provided - - - com.fasterxml.jackson.core - jackson-databind - provided - @@ -98,6 +77,11 @@ ${project.version} test + + com.fasterxml.jackson.core + jackson-databind + test + diff --git a/vespajlib/src/main/java/com/yahoo/security/BasicConstraintsExtension.java b/vespajlib/src/main/java/com/yahoo/security/BasicConstraintsExtension.java deleted file mode 100644 index d3c08ba27d0..00000000000 --- a/vespajlib/src/main/java/com/yahoo/security/BasicConstraintsExtension.java +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.security; - -/** - * @author bjorncs - */ -class BasicConstraintsExtension { - final boolean isCritical, isCertAuthorityCertificate; - - BasicConstraintsExtension(boolean isCritical, boolean isCertAuthorityCertificate) { - this.isCritical = isCritical; - this.isCertAuthorityCertificate = isCertAuthorityCertificate; - } -} diff --git a/vespajlib/src/main/java/com/yahoo/security/BouncyCastleProviderHolder.java b/vespajlib/src/main/java/com/yahoo/security/BouncyCastleProviderHolder.java deleted file mode 100644 index 48a23a1fe7e..00000000000 --- a/vespajlib/src/main/java/com/yahoo/security/BouncyCastleProviderHolder.java +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.security; - -import org.bouncycastle.jce.provider.BouncyCastleProvider; - -/** - * @author bjorncs - */ -class BouncyCastleProviderHolder { - - private static final BouncyCastleProvider bcProvider = new BouncyCastleProvider(); - - static BouncyCastleProvider getInstance() { return bcProvider; } -} diff --git a/vespajlib/src/main/java/com/yahoo/security/Extension.java b/vespajlib/src/main/java/com/yahoo/security/Extension.java deleted file mode 100644 index 46b781c9c86..00000000000 --- a/vespajlib/src/main/java/com/yahoo/security/Extension.java +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.security; - -import org.bouncycastle.asn1.ASN1ObjectIdentifier; - -/** - * @author bjorncs - */ -public enum Extension { - BASIC_CONSTRAINTS(org.bouncycastle.asn1.x509.Extension.basicConstraints), - SUBJECT_ALTERNATIVE_NAMES(org.bouncycastle.asn1.x509.Extension.subjectAlternativeName); - - final ASN1ObjectIdentifier extensionOId; - - Extension(ASN1ObjectIdentifier extensionOId) { - this.extensionOId = extensionOId; - } - - public String getOId() { - return extensionOId.getId(); - } -} diff --git a/vespajlib/src/main/java/com/yahoo/security/KeyAlgorithm.java b/vespajlib/src/main/java/com/yahoo/security/KeyAlgorithm.java deleted file mode 100644 index 3218f81f0d6..00000000000 --- a/vespajlib/src/main/java/com/yahoo/security/KeyAlgorithm.java +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.security; - -/** - * @author bjorncs - */ -public enum KeyAlgorithm { - RSA("RSA"), - EC("EC"); - - final String algorithmName; - - KeyAlgorithm(String algorithmName) { - this.algorithmName = algorithmName; - } - - String getAlgorithmName() { - return algorithmName; - } -} diff --git a/vespajlib/src/main/java/com/yahoo/security/KeyStoreBuilder.java b/vespajlib/src/main/java/com/yahoo/security/KeyStoreBuilder.java deleted file mode 100644 index 2160fbf6455..00000000000 --- a/vespajlib/src/main/java/com/yahoo/security/KeyStoreBuilder.java +++ /dev/null @@ -1,121 +0,0 @@ -// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.security; - -import java.io.BufferedInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.UncheckedIOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.security.GeneralSecurityException; -import java.security.KeyStore; -import java.security.PrivateKey; -import java.security.cert.Certificate; -import java.security.cert.X509Certificate; -import java.util.ArrayList; -import java.util.List; - -import static java.util.Collections.singletonList; - -/** - * @author bjorncs - */ -public class KeyStoreBuilder { - - private final List keyEntries = new ArrayList<>(); - private final List certificateEntries = new ArrayList<>(); - - private final KeyStoreType keyStoreType; - private Path inputFile; - private char[] inputFilePassword; - - private KeyStoreBuilder(KeyStoreType keyStoreType) { - this.keyStoreType = keyStoreType; - } - - public static KeyStoreBuilder withType(KeyStoreType type) { - return new KeyStoreBuilder(type); - } - - public KeyStoreBuilder fromFile(Path file, char[] password) { - this.inputFile = file; - this.inputFilePassword = password; - return this; - } - - public KeyStoreBuilder fromFile(Path file) { - return fromFile(file, null); - } - - public KeyStoreBuilder withKeyEntry(String alias, PrivateKey privateKey, char[] password, List certificateChain) { - keyEntries.add(new KeyEntry(alias, privateKey, certificateChain, password)); - return this; - } - - public KeyStoreBuilder withKeyEntry(String alias, PrivateKey privateKey, char[] password, X509Certificate certificate) { - return withKeyEntry(alias, privateKey, password, singletonList(certificate)); - } - - public KeyStoreBuilder withKeyEntry(String alias, PrivateKey privateKey, X509Certificate certificate) { - return withKeyEntry(alias, privateKey, null, certificate); - } - - public KeyStoreBuilder withKeyEntry(String alias, PrivateKey privateKey, List certificateChain) { - return withKeyEntry(alias, privateKey, null, certificateChain); - } - - public KeyStoreBuilder withCertificateEntry(String alias, X509Certificate certificate) { - certificateEntries.add(new CertificateEntry(alias, certificate)); - return this; - } - - public KeyStore build() { - try { - KeyStore keystore = this.keyStoreType.createKeystore(); - if (this.inputFile != null) { - try (InputStream in = new BufferedInputStream(Files.newInputStream(this.inputFile))) { - keystore.load(in, this.inputFilePassword); - } - } else { - keystore.load(null); - } - for (KeyEntry entry : keyEntries) { - char[] password = entry.password != null ? entry.password : new char[0]; - Certificate[] certificateChain = entry.certificateChain.toArray(new Certificate[entry.certificateChain.size()]); - keystore.setKeyEntry(entry.alias, entry.privateKey, password, certificateChain); - } - for (CertificateEntry entry : certificateEntries) { - keystore.setCertificateEntry(entry.alias, entry.certificate); - } - return keystore; - } catch (GeneralSecurityException e) { - throw new RuntimeException(e); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - private static class KeyEntry { - final String alias; - final PrivateKey privateKey; - final List certificateChain; - final char[] password; - - KeyEntry(String alias, PrivateKey privateKey, List certificateChain, char[] password) { - this.alias = alias; - this.privateKey = privateKey; - this.certificateChain = certificateChain; - this.password = password; - } - } - - private static class CertificateEntry { - final String alias; - final X509Certificate certificate; - - CertificateEntry(String alias, X509Certificate certificate) { - this.alias = alias; - this.certificate = certificate; - } - } -} diff --git a/vespajlib/src/main/java/com/yahoo/security/KeyStoreType.java b/vespajlib/src/main/java/com/yahoo/security/KeyStoreType.java deleted file mode 100644 index 7fb8df35286..00000000000 --- a/vespajlib/src/main/java/com/yahoo/security/KeyStoreType.java +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.security; - -import java.security.GeneralSecurityException; -import java.security.KeyStore; -import java.security.KeyStoreException; - -/** - * @author bjorncs - */ -public enum KeyStoreType { - JKS { - KeyStore createKeystore() throws KeyStoreException { - return KeyStore.getInstance("JKS"); - } - }, - PKCS12 { - KeyStore createKeystore() throws KeyStoreException { - return KeyStore.getInstance("PKCS12", BouncyCastleProviderHolder.getInstance()); - } - }; - abstract KeyStore createKeystore() throws GeneralSecurityException; -} diff --git a/vespajlib/src/main/java/com/yahoo/security/KeyStoreUtils.java b/vespajlib/src/main/java/com/yahoo/security/KeyStoreUtils.java deleted file mode 100644 index f0c4d99bf69..00000000000 --- a/vespajlib/src/main/java/com/yahoo/security/KeyStoreUtils.java +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.security; - -import java.io.BufferedOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.io.UncheckedIOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.security.GeneralSecurityException; -import java.security.KeyStore; - -/** - * @author bjorncs - */ -public class KeyStoreUtils { - private KeyStoreUtils() {} - - public static void writeKeyStoreToFile(KeyStore keyStore, Path file, char[] password) { - try (OutputStream out = new BufferedOutputStream(Files.newOutputStream(file))) { - keyStore.store(out, password); - } catch (IOException e) { - throw new UncheckedIOException(e); - } catch (GeneralSecurityException e) { - throw new RuntimeException(e); - } - - } - - public static void writeKeyStoreToFile(KeyStore keyStore, Path file) { - writeKeyStoreToFile(keyStore, file, new char[0]); - } - -} diff --git a/vespajlib/src/main/java/com/yahoo/security/KeyUtils.java b/vespajlib/src/main/java/com/yahoo/security/KeyUtils.java deleted file mode 100644 index 11fb0f432e4..00000000000 --- a/vespajlib/src/main/java/com/yahoo/security/KeyUtils.java +++ /dev/null @@ -1,129 +0,0 @@ -// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.security; - -import org.bouncycastle.asn1.ASN1Encodable; -import org.bouncycastle.asn1.ASN1Primitive; -import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; -import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey; -import org.bouncycastle.jce.spec.ECParameterSpec; -import org.bouncycastle.jce.spec.ECPublicKeySpec; -import org.bouncycastle.math.ec.ECPoint; -import org.bouncycastle.math.ec.FixedPointCombMultiplier; -import org.bouncycastle.openssl.PEMKeyPair; -import org.bouncycastle.openssl.PEMParser; -import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter; -import org.bouncycastle.openssl.jcajce.JcaPEMWriter; -import org.bouncycastle.util.io.pem.PemObject; - -import java.io.IOException; -import java.io.StringReader; -import java.io.StringWriter; -import java.io.UncheckedIOException; -import java.security.GeneralSecurityException; -import java.security.KeyFactory; -import java.security.KeyPair; -import java.security.KeyPairGenerator; -import java.security.PrivateKey; -import java.security.PublicKey; -import java.security.interfaces.RSAPrivateCrtKey; -import java.security.spec.PKCS8EncodedKeySpec; -import java.security.spec.RSAPublicKeySpec; - -import static com.yahoo.security.KeyAlgorithm.EC; -import static com.yahoo.security.KeyAlgorithm.RSA; - -/** - * @author bjorncs - */ -public class KeyUtils { - private KeyUtils() {} - - public static KeyPair generateKeypair(KeyAlgorithm algorithm, int keySize) { - try { - KeyPairGenerator keyGen = KeyPairGenerator.getInstance(algorithm.getAlgorithmName(), BouncyCastleProviderHolder.getInstance()); - if (keySize != -1) { - keyGen.initialize(keySize); - } - return keyGen.genKeyPair(); - } catch (GeneralSecurityException e) { - throw new RuntimeException(e); - } - } - - public static KeyPair generateKeypair(KeyAlgorithm algorithm) { - return generateKeypair(algorithm, -1); - } - - public static PublicKey extractPublicKey(PrivateKey privateKey) { - String algorithm = privateKey.getAlgorithm(); - try { - if (algorithm.equals(RSA.getAlgorithmName())) { - KeyFactory keyFactory = KeyFactory.getInstance(RSA.getAlgorithmName(), BouncyCastleProviderHolder.getInstance()); - RSAPrivateCrtKey rsaPrivateCrtKey = (RSAPrivateCrtKey) privateKey; - RSAPublicKeySpec keySpec = new RSAPublicKeySpec(rsaPrivateCrtKey.getModulus(), rsaPrivateCrtKey.getPublicExponent()); - return keyFactory.generatePublic(keySpec); - } else if (algorithm.equals(EC.getAlgorithmName())) { - KeyFactory keyFactory = KeyFactory.getInstance(EC.getAlgorithmName(), BouncyCastleProviderHolder.getInstance()); - BCECPrivateKey ecPrivateKey = (BCECPrivateKey) privateKey; - ECParameterSpec ecParameterSpec = ecPrivateKey.getParameters(); - ECPoint ecPoint = new FixedPointCombMultiplier().multiply(ecParameterSpec.getG(), ecPrivateKey.getD()); - ECPublicKeySpec keySpec = new ECPublicKeySpec(ecPoint, ecParameterSpec); - return keyFactory.generatePublic(keySpec); - } else { - throw new IllegalArgumentException("Unexpected key algorithm: " + algorithm); - } - } catch (GeneralSecurityException e) { - throw new RuntimeException(e); - } - } - - public static PrivateKey fromPemEncodedPrivateKey(String pem) { - try (PEMParser parser = new PEMParser(new StringReader(pem))) { - Object pemObject = parser.readObject(); - if (pemObject instanceof PrivateKeyInfo) { - PrivateKeyInfo keyInfo = (PrivateKeyInfo) pemObject; - PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyInfo.getEncoded()); - return KeyFactory.getInstance(RSA.getAlgorithmName()).generatePrivate(keySpec); - } else if (pemObject instanceof PEMKeyPair) { - PEMKeyPair pemKeypair = (PEMKeyPair) pemObject; - PrivateKeyInfo keyInfo = pemKeypair.getPrivateKeyInfo(); - JcaPEMKeyConverter pemConverter = new JcaPEMKeyConverter().setProvider(BouncyCastleProviderHolder.getInstance()); - return pemConverter.getPrivateKey(keyInfo); - } - throw new IllegalArgumentException("Unexpected type of PEM type: " + pemObject); - } catch (IOException e) { - throw new UncheckedIOException(e); - } catch (GeneralSecurityException e) { - throw new RuntimeException(e); - } - } - - public static String toPem(PrivateKey privateKey) { - try (StringWriter stringWriter = new StringWriter(); JcaPEMWriter pemWriter = new JcaPEMWriter(stringWriter)) { - String algorithm = privateKey.getAlgorithm(); - // Note: Encoding using PKCS#1 as this is to be read by tools only supporting PKCS#1 - String type; - if (algorithm.equals(RSA.getAlgorithmName())) { - type = "RSA PRIVATE KEY"; - } else if (algorithm.equals(EC.getAlgorithmName())) { - type = "EC PRIVATE KEY"; - } else { - throw new IllegalArgumentException("Unexpected key algorithm: " + algorithm); - } - pemWriter.writeObject(new PemObject(type, getPkcs1Bytes(privateKey))); - pemWriter.flush(); - return stringWriter.toString(); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - private static byte[] getPkcs1Bytes(PrivateKey privateKey) throws IOException{ - - byte[] privBytes = privateKey.getEncoded(); - PrivateKeyInfo pkInfo = PrivateKeyInfo.getInstance(privBytes); - ASN1Encodable encodable = pkInfo.parsePrivateKey(); - ASN1Primitive primitive = encodable.toASN1Primitive(); - return primitive.getEncoded(); - } -} diff --git a/vespajlib/src/main/java/com/yahoo/security/Pkcs10Csr.java b/vespajlib/src/main/java/com/yahoo/security/Pkcs10Csr.java deleted file mode 100644 index e08ee117fcd..00000000000 --- a/vespajlib/src/main/java/com/yahoo/security/Pkcs10Csr.java +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.security; - -import org.bouncycastle.asn1.ASN1ObjectIdentifier; -import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers; -import org.bouncycastle.asn1.x509.BasicConstraints; -import org.bouncycastle.asn1.x509.Extension; -import org.bouncycastle.asn1.x509.Extensions; -import org.bouncycastle.asn1.x509.GeneralNames; -import org.bouncycastle.pkcs.PKCS10CertificationRequest; - -import javax.security.auth.x500.X500Principal; -import java.util.Arrays; -import java.util.List; -import java.util.Optional; - -import static java.util.Collections.emptyList; -import static java.util.stream.Collectors.toList; - -/** - * @author bjorncs - */ -public class Pkcs10Csr { - - private final PKCS10CertificationRequest csr; - - Pkcs10Csr(PKCS10CertificationRequest csr) { - this.csr = csr; - } - - PKCS10CertificationRequest getBcCsr() { - return csr; - } - - public X500Principal getSubject() { - return new X500Principal(csr.getSubject().toString()); - } - - public List getSubjectAlternativeNames() { - return getExtensions() - .map(extensions -> GeneralNames.fromExtensions(extensions, Extension.subjectAlternativeName)) - .map(SubjectAlternativeName::fromGeneralNames) - .orElse(emptyList()); - } - - /** - * @return If basic constraints extension is present: returns true if CA cert, false otherwise. Returns empty if the extension is not present. - */ - public Optional getBasicConstraints() { - return getExtensions() - .map(BasicConstraints::fromExtensions) - .map(BasicConstraints::isCA); - } - - public List getExtensionOIds() { - return getExtensions() - .map(extensions -> Arrays.stream(extensions.getExtensionOIDs()) - .map(ASN1ObjectIdentifier::getId) - .collect(toList())) - .orElse(emptyList()); - - } - - private Optional getExtensions() { - return Optional.of(csr.getAttributes(PKCSObjectIdentifiers.pkcs_9_at_extensionRequest)) - .filter(attributes -> attributes.length > 0) - .map(attributes -> attributes[0]) - .map(attribute -> Extensions.getInstance(attribute.getAttrValues().getObjectAt(0))); - } - -} diff --git a/vespajlib/src/main/java/com/yahoo/security/Pkcs10CsrBuilder.java b/vespajlib/src/main/java/com/yahoo/security/Pkcs10CsrBuilder.java deleted file mode 100644 index 0fe6117ef2f..00000000000 --- a/vespajlib/src/main/java/com/yahoo/security/Pkcs10CsrBuilder.java +++ /dev/null @@ -1,106 +0,0 @@ -// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.security; - -import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers; -import org.bouncycastle.asn1.x500.X500Name; -import org.bouncycastle.asn1.x509.BasicConstraints; -import org.bouncycastle.asn1.x509.Extension; -import org.bouncycastle.asn1.x509.ExtensionsGenerator; -import org.bouncycastle.asn1.x509.GeneralName; -import org.bouncycastle.asn1.x509.GeneralNames; -import org.bouncycastle.operator.ContentSigner; -import org.bouncycastle.operator.OperatorCreationException; -import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; -import org.bouncycastle.pkcs.PKCS10CertificationRequestBuilder; -import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequestBuilder; - -import javax.security.auth.x500.X500Principal; -import java.io.IOException; -import java.io.UncheckedIOException; -import java.security.KeyPair; -import java.util.ArrayList; -import java.util.List; - -import static com.yahoo.security.SubjectAlternativeName.Type.DNS_NAME; - -/** - * @author bjorncs - */ -public class Pkcs10CsrBuilder { - - private final X500Principal subject; - private final KeyPair keyPair; - private final List subjectAlternativeNames = new ArrayList<>(); - private final SignatureAlgorithm signatureAlgorithm; - private BasicConstraintsExtension basicConstraintsExtension; - - private Pkcs10CsrBuilder(X500Principal subject, - KeyPair keyPair, - SignatureAlgorithm signatureAlgorithm) { - this.subject = subject; - this.keyPair = keyPair; - this.signatureAlgorithm = signatureAlgorithm; - } - - public static Pkcs10CsrBuilder fromKeypair(X500Principal subject, - KeyPair keyPair, - SignatureAlgorithm signatureAlgorithm) { - return new Pkcs10CsrBuilder(subject, keyPair, signatureAlgorithm); - } - - public Pkcs10CsrBuilder addSubjectAlternativeName(String dns) { - this.subjectAlternativeNames.add(new SubjectAlternativeName(DNS_NAME, dns)); - return this; - } - - public Pkcs10CsrBuilder addSubjectAlternativeName(SubjectAlternativeName san) { - this.subjectAlternativeNames.add(san); - return this; - } - - public Pkcs10CsrBuilder addSubjectAlternativeName(SubjectAlternativeName.Type type, String value) { - this.subjectAlternativeNames.add(new SubjectAlternativeName(type, value)); - return this; - } - - public Pkcs10CsrBuilder setBasicConstraints(boolean isCritical, boolean isCertAuthorityCertificate) { - this.basicConstraintsExtension = new BasicConstraintsExtension(isCritical, isCertAuthorityCertificate); - return this; - } - - public Pkcs10CsrBuilder setIsCertAuthority(boolean isCertAuthority) { - return setBasicConstraints(true, isCertAuthority); - } - - public Pkcs10Csr build() { - try { - PKCS10CertificationRequestBuilder requestBuilder = - new JcaPKCS10CertificationRequestBuilder(new X500Name(subject.getName()), keyPair.getPublic()); - ExtensionsGenerator extGen = new ExtensionsGenerator(); - if (basicConstraintsExtension != null) { - extGen.addExtension( - Extension.basicConstraints, - basicConstraintsExtension.isCritical, - new BasicConstraints(basicConstraintsExtension.isCertAuthorityCertificate)); - } - if (!subjectAlternativeNames.isEmpty()) { - GeneralNames generalNames = new GeneralNames( - subjectAlternativeNames.stream() - .map(SubjectAlternativeName::toGeneralName) - .toArray(GeneralName[]::new)); - extGen.addExtension(Extension.subjectAlternativeName, false, generalNames); - } - requestBuilder.addAttribute(PKCSObjectIdentifiers.pkcs_9_at_extensionRequest, extGen.generate()); - ContentSigner contentSigner = new JcaContentSignerBuilder(signatureAlgorithm.getAlgorithmName()) - .setProvider(BouncyCastleProviderHolder.getInstance()) - .build(keyPair.getPrivate()); - return new Pkcs10Csr(requestBuilder.build(contentSigner)); - } catch (OperatorCreationException e) { - throw new RuntimeException(e); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - - } - -} diff --git a/vespajlib/src/main/java/com/yahoo/security/Pkcs10CsrUtils.java b/vespajlib/src/main/java/com/yahoo/security/Pkcs10CsrUtils.java deleted file mode 100644 index 6f12450528d..00000000000 --- a/vespajlib/src/main/java/com/yahoo/security/Pkcs10CsrUtils.java +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.security; - -import org.bouncycastle.openssl.PEMParser; -import org.bouncycastle.openssl.jcajce.JcaPEMWriter; -import org.bouncycastle.pkcs.PKCS10CertificationRequest; -import org.bouncycastle.util.io.pem.PemObject; - -import java.io.IOException; -import java.io.StringReader; -import java.io.StringWriter; -import java.io.UncheckedIOException; - -/** - * @author bjorncs - */ -public class Pkcs10CsrUtils { - - private Pkcs10CsrUtils() {} - - public static Pkcs10Csr fromPem(String pem) { - try (PEMParser pemParser = new PEMParser(new StringReader(pem))) { - return new Pkcs10Csr((PKCS10CertificationRequest) pemParser.readObject()); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - public static String toPem(Pkcs10Csr csr) { - try (StringWriter stringWriter = new StringWriter(); JcaPEMWriter pemWriter = new JcaPEMWriter(stringWriter)) { - pemWriter.writeObject(new PemObject("CERTIFICATE REQUEST", csr.getBcCsr().getEncoded())); - pemWriter.flush(); - return stringWriter.toString(); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } -} diff --git a/vespajlib/src/main/java/com/yahoo/security/SignatureAlgorithm.java b/vespajlib/src/main/java/com/yahoo/security/SignatureAlgorithm.java deleted file mode 100644 index fbff18f5c12..00000000000 --- a/vespajlib/src/main/java/com/yahoo/security/SignatureAlgorithm.java +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.security; - -/** - * @author bjorncs - */ -public enum SignatureAlgorithm { - SHA256_WITH_RSA("SHA256withRSA"), - SHA512_WITH_RSA("SHA512withRSA"), - SHA256_WITH_ECDSA("SHA256withECDSA"), - SHA512_WITH_ECDSA("SHA512withECDSA"); - - private final String algorithmName; - - SignatureAlgorithm(String algorithmName) { - this.algorithmName = algorithmName; - } - - public String getAlgorithmName() { - return algorithmName; - } -} diff --git a/vespajlib/src/main/java/com/yahoo/security/SslContextBuilder.java b/vespajlib/src/main/java/com/yahoo/security/SslContextBuilder.java deleted file mode 100644 index 75ab2417edf..00000000000 --- a/vespajlib/src/main/java/com/yahoo/security/SslContextBuilder.java +++ /dev/null @@ -1,137 +0,0 @@ -// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.security; - -import javax.net.ssl.KeyManager; -import javax.net.ssl.KeyManagerFactory; -import javax.net.ssl.SSLContext; -import javax.net.ssl.TrustManager; -import javax.net.ssl.TrustManagerFactory; -import java.io.IOException; -import java.io.UncheckedIOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.security.GeneralSecurityException; -import java.security.KeyStore; -import java.security.PrivateKey; -import java.security.cert.X509Certificate; -import java.util.Collections; -import java.util.List; - -import static java.util.Collections.singletonList; - -/** - * @author bjorncs - */ -public class SslContextBuilder { - - private KeyStoreSupplier trustStoreSupplier; - private KeyStoreSupplier keyStoreSupplier; - private char[] keyStorePassword; - - public SslContextBuilder() {} - - public SslContextBuilder withTrustStore(Path file, KeyStoreType trustStoreType) { - this.trustStoreSupplier = () -> KeyStoreBuilder.withType(trustStoreType).fromFile(file).build(); - return this; - } - - public SslContextBuilder withTrustStore(KeyStore trustStore) { - this.trustStoreSupplier = () -> trustStore; - return this; - } - - public SslContextBuilder withTrustStore(X509Certificate caCertificate) { - return withTrustStore(singletonList(caCertificate)); - } - - public SslContextBuilder withTrustStore(List caCertificates) { - this.trustStoreSupplier = () -> createTrustStore(caCertificates); - return this; - } - - public SslContextBuilder withTrustStore(Path pemEncodedCaCertificates) { - this.trustStoreSupplier = () -> { - List caCertificates = - X509CertificateUtils.certificateListFromPem(new String(Files.readAllBytes(pemEncodedCaCertificates))); - return createTrustStore(caCertificates); - }; - return this; - } - - public SslContextBuilder withKeyStore(PrivateKey privateKey, X509Certificate certificate) { - char[] pwd = new char[0]; - this.keyStoreSupplier = () -> KeyStoreBuilder.withType(KeyStoreType.JKS).withKeyEntry("default", privateKey, certificate).build(); - this.keyStorePassword = pwd; - return this; - } - - public SslContextBuilder withKeyStore(KeyStore keyStore, char[] password) { - this.keyStoreSupplier = () -> keyStore; - this.keyStorePassword = password; - return this; - } - - public SslContextBuilder withKeyStore(Path file, char[] password, KeyStoreType keyStoreType) { - this.keyStoreSupplier = () -> KeyStoreBuilder.withType(keyStoreType).fromFile(file, password).build(); - this.keyStorePassword = password; - return this; - } - - public SslContextBuilder withKeyStore(Path privateKeyPemFile, Path certificatesPemFile) { - this.keyStoreSupplier = - () -> { - PrivateKey privateKey = KeyUtils.fromPemEncodedPrivateKey(new String(Files.readAllBytes(privateKeyPemFile))); - List certificates = X509CertificateUtils.certificateListFromPem(new String(Files.readAllBytes(certificatesPemFile))); - return KeyStoreBuilder.withType(KeyStoreType.JKS) - .withKeyEntry("default", privateKey, certificates) - .build(); - }; - this.keyStorePassword = new char[0]; - return this; - } - - public SSLContext build() { - try { - SSLContext sslContext = SSLContext.getInstance("TLSv1.2"); - TrustManager[] trustManagers = - trustStoreSupplier != null ? createTrustManagers(trustStoreSupplier) : null; - KeyManager[] keyManagers = - keyStoreSupplier != null ? createKeyManagers(keyStoreSupplier, keyStorePassword) : null; - sslContext.init(keyManagers, trustManagers, null); - return sslContext; - } catch (GeneralSecurityException e) { - throw new RuntimeException(e); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - private static TrustManager[] createTrustManagers(KeyStoreSupplier trustStoreSupplier) - throws GeneralSecurityException, IOException { - TrustManagerFactory trustManagerFactory = - TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); - trustManagerFactory.init(trustStoreSupplier.get()); - return trustManagerFactory.getTrustManagers(); - } - - private static KeyManager[] createKeyManagers(KeyStoreSupplier keyStoreSupplier, char[] password) - throws GeneralSecurityException, IOException { - KeyManagerFactory keyManagerFactory = - KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); - keyManagerFactory.init(keyStoreSupplier.get(), password); - return keyManagerFactory.getKeyManagers(); - } - - private static KeyStore createTrustStore(List caCertificates) { - KeyStoreBuilder trustStoreBuilder = KeyStoreBuilder.withType(KeyStoreType.JKS); - for (int i = 0; i < caCertificates.size(); i++) { - trustStoreBuilder.withCertificateEntry("cert-" + i, caCertificates.get(i)); - } - return trustStoreBuilder.build(); - } - - private interface KeyStoreSupplier { - KeyStore get() throws IOException, GeneralSecurityException; - } - -} diff --git a/vespajlib/src/main/java/com/yahoo/security/SubjectAlternativeName.java b/vespajlib/src/main/java/com/yahoo/security/SubjectAlternativeName.java deleted file mode 100644 index 29395c75e70..00000000000 --- a/vespajlib/src/main/java/com/yahoo/security/SubjectAlternativeName.java +++ /dev/null @@ -1,114 +0,0 @@ -// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.security; - -import org.bouncycastle.asn1.ASN1Encodable; -import org.bouncycastle.asn1.DERIA5String; -import org.bouncycastle.asn1.x500.X500Name; -import org.bouncycastle.asn1.x509.GeneralName; -import org.bouncycastle.asn1.x509.GeneralNames; - -import java.util.Arrays; -import java.util.List; -import java.util.Objects; - -import static java.util.stream.Collectors.toList; - -/** - * @author bjorncs - */ -public class SubjectAlternativeName { - - private final Type type; - private final String value; - - public SubjectAlternativeName(Type type, String value) { - this.type = type; - this.value = value; - } - - SubjectAlternativeName(GeneralName bcGeneralName) { - this.type = Type.fromTag(bcGeneralName.getTagNo()); - this.value = getValue(bcGeneralName); - } - - public Type getType() { - return type; - } - - public String getValue() { - return value; - } - - GeneralName toGeneralName() { - return new GeneralName(type.tag, value); - } - - static List fromGeneralNames(GeneralNames generalNames) { - return Arrays.stream(generalNames.getNames()).map(SubjectAlternativeName::new).collect(toList()); - } - - private String getValue(GeneralName bcGeneralName) { - ASN1Encodable name = bcGeneralName.getName(); - switch (bcGeneralName.getTagNo()) { - case GeneralName.rfc822Name: - case GeneralName.dNSName: - case GeneralName.uniformResourceIdentifier: - return DERIA5String.getInstance(name).getString(); - case GeneralName.directoryName: - return X500Name.getInstance(name).toString(); - default: - return name.toString(); - } - } - - @Override - public String toString() { - return "SubjectAlternativeName{" + - "type=" + type + - ", value='" + value + '\'' + - '}'; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - SubjectAlternativeName that = (SubjectAlternativeName) o; - return type == that.type && - Objects.equals(value, that.value); - } - - @Override - public int hashCode() { - return Objects.hash(type, value); - } - - public enum Type { - OTHER_NAME(0), - RFC822_NAME(1), - DNS_NAME(2), - X400_ADDRESS(3), - DIRECTORY_NAME(4), - EDI_PARITY_NAME(5), - UNIFORM_RESOURCE_IDENTIFIER(6), - IP_ADDRESS(7), - REGISTERED_ID(8); - - final int tag; - - Type(int tag) { - this.tag = tag; - } - - public static Type fromTag(int tag) { - return Arrays.stream(Type.values()) - .filter(type -> type.tag == tag) - .findAny() - .orElseThrow(() -> new IllegalArgumentException("Invalid tag: " + tag)); - } - - public int getTag() { - return tag; - } - } -} diff --git a/vespajlib/src/main/java/com/yahoo/security/X509CertificateBuilder.java b/vespajlib/src/main/java/com/yahoo/security/X509CertificateBuilder.java deleted file mode 100644 index 54d7d39253e..00000000000 --- a/vespajlib/src/main/java/com/yahoo/security/X509CertificateBuilder.java +++ /dev/null @@ -1,167 +0,0 @@ -// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.security; - -import org.bouncycastle.asn1.x509.BasicConstraints; -import org.bouncycastle.asn1.x509.Extension; -import org.bouncycastle.asn1.x509.GeneralName; -import org.bouncycastle.asn1.x509.GeneralNames; -import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; -import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder; -import org.bouncycastle.operator.ContentSigner; -import org.bouncycastle.operator.OperatorException; -import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; -import org.bouncycastle.pkcs.PKCS10CertificationRequest; -import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest; - -import javax.security.auth.x500.X500Principal; -import java.io.IOException; -import java.io.UncheckedIOException; -import java.math.BigInteger; -import java.security.GeneralSecurityException; -import java.security.KeyPair; -import java.security.PrivateKey; -import java.security.PublicKey; -import java.security.SecureRandom; -import java.security.cert.X509Certificate; -import java.sql.Date; -import java.time.Instant; -import java.util.ArrayList; -import java.util.List; - -import static com.yahoo.security.SubjectAlternativeName.Type.DNS_NAME; - - -/** - * @author bjorncs - */ -public class X509CertificateBuilder { - - private final BigInteger serialNumber; - private final SignatureAlgorithm signingAlgorithm; - private final PrivateKey caPrivateKey; - private final Instant notBefore; - private final Instant notAfter; - private final List subjectAlternativeNames = new ArrayList<>(); - private final X500Principal issuer; - private final X500Principal subject; - private final PublicKey certPublicKey; - private BasicConstraintsExtension basicConstraintsExtension; - - private X509CertificateBuilder(X500Principal issuer, - X500Principal subject, - Instant notBefore, - Instant notAfter, - PublicKey certPublicKey, - PrivateKey caPrivateKey, - SignatureAlgorithm signingAlgorithm, - BigInteger serialNumber) { - this.issuer = issuer; - this.subject = subject; - this.notBefore = notBefore; - this.notAfter = notAfter; - this.certPublicKey = certPublicKey; - this.caPrivateKey = caPrivateKey; - this.signingAlgorithm = signingAlgorithm; - this.serialNumber = serialNumber; - } - - public static X509CertificateBuilder fromCsr(Pkcs10Csr csr, - X500Principal caIssuer, - Instant notBefore, - Instant notAfter, - PrivateKey caPrivateKey, - SignatureAlgorithm signingAlgorithm, - BigInteger serialNumber) { - try { - PKCS10CertificationRequest bcCsr = csr.getBcCsr(); - PublicKey publicKey = new JcaPKCS10CertificationRequest(bcCsr) - .setProvider(BouncyCastleProviderHolder.getInstance()) - .getPublicKey(); - return new X509CertificateBuilder(caIssuer, - new X500Principal(bcCsr.getSubject().getEncoded()), - notBefore, - notAfter, - publicKey, - caPrivateKey, - signingAlgorithm, - serialNumber); - } catch (GeneralSecurityException e) { - throw new RuntimeException(e); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - public static X509CertificateBuilder fromKeypair(KeyPair keyPair, - X500Principal subject, - Instant notBefore, - Instant notAfter, - SignatureAlgorithm signingAlgorithm, - BigInteger serialNumber) { - return new X509CertificateBuilder(subject, - subject, - notBefore, - notAfter, - keyPair.getPublic(), - keyPair.getPrivate(), - signingAlgorithm, - serialNumber); - } - - /** - * @return generates a cryptographically secure positive serial number up to 128 bits - */ - public static BigInteger generateRandomSerialNumber() { - return new BigInteger(128, new SecureRandom()); - } - - public X509CertificateBuilder addSubjectAlternativeName(String dnsName) { - this.subjectAlternativeNames.add(new SubjectAlternativeName(DNS_NAME, dnsName)); - return this; - } - - public X509CertificateBuilder addSubjectAlternativeName(SubjectAlternativeName san) { - this.subjectAlternativeNames.add(san); - return this; - } - - public X509CertificateBuilder setBasicConstraints(boolean isCritical, boolean isCertAuthorityCertificate) { - this.basicConstraintsExtension = new BasicConstraintsExtension(isCritical, isCertAuthorityCertificate); - return this; - } - - public X509CertificateBuilder setIsCertAuthority(boolean isCertAuthority) { - return setBasicConstraints(true, isCertAuthority); - } - - public X509Certificate build() { - try { - JcaX509v3CertificateBuilder jcaCertBuilder = new JcaX509v3CertificateBuilder( - issuer, serialNumber, Date.from(notBefore), Date.from(notAfter), subject, certPublicKey); - if (basicConstraintsExtension != null) { - jcaCertBuilder.addExtension( - Extension.basicConstraints, - basicConstraintsExtension.isCritical, - new BasicConstraints(basicConstraintsExtension.isCertAuthorityCertificate)); - } - if (!subjectAlternativeNames.isEmpty()) { - GeneralNames generalNames = new GeneralNames( - subjectAlternativeNames.stream() - .map(SubjectAlternativeName::toGeneralName) - .toArray(GeneralName[]::new)); - jcaCertBuilder.addExtension(Extension.subjectAlternativeName, false, generalNames); - } - ContentSigner contentSigner = new JcaContentSignerBuilder(signingAlgorithm.getAlgorithmName()) - .setProvider(BouncyCastleProviderHolder.getInstance()) - .build(caPrivateKey); - return new JcaX509CertificateConverter() - .setProvider(BouncyCastleProviderHolder.getInstance()) - .getCertificate(jcaCertBuilder.build(contentSigner)); - } catch (OperatorException | GeneralSecurityException e) { - throw new RuntimeException(e); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - -} diff --git a/vespajlib/src/main/java/com/yahoo/security/X509CertificateUtils.java b/vespajlib/src/main/java/com/yahoo/security/X509CertificateUtils.java deleted file mode 100644 index 33bd750bac5..00000000000 --- a/vespajlib/src/main/java/com/yahoo/security/X509CertificateUtils.java +++ /dev/null @@ -1,136 +0,0 @@ -// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.security; - -import org.bouncycastle.asn1.ASN1Encodable; -import org.bouncycastle.asn1.ASN1OctetString; -import org.bouncycastle.asn1.ASN1Primitive; -import org.bouncycastle.asn1.x509.GeneralNames; -import org.bouncycastle.cert.X509CertificateHolder; -import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; -import org.bouncycastle.openssl.PEMParser; -import org.bouncycastle.openssl.jcajce.JcaPEMWriter; -import org.bouncycastle.util.io.pem.PemObject; - -import javax.naming.NamingException; -import javax.naming.ldap.LdapName; -import javax.security.auth.x500.X500Principal; -import java.io.IOException; -import java.io.StringReader; -import java.io.StringWriter; -import java.io.UncheckedIOException; -import java.security.GeneralSecurityException; -import java.security.cert.CertificateException; -import java.security.cert.X509Certificate; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import static com.yahoo.security.Extension.SUBJECT_ALTERNATIVE_NAMES; -import static java.util.stream.Collectors.toList; - -/** - * @author bjorncs - */ -public class X509CertificateUtils { - - private X509CertificateUtils() {} - - public static X509Certificate fromPem(String pem) { - try (PEMParser parser = new PEMParser(new StringReader(pem))) { - return toX509Certificate(parser.readObject()); - } catch (IOException e) { - throw new UncheckedIOException(e); - } catch (CertificateException e) { - throw new RuntimeException(e); - } - } - - public static List certificateListFromPem(String pem) { - try (PEMParser parser = new PEMParser(new StringReader(pem))) { - List list = new ArrayList<>(); - Object pemObject; - while ((pemObject = parser.readObject()) != null) { - list.add(toX509Certificate(pemObject)); - } - return list; - } catch (IOException e) { - throw new UncheckedIOException(e); - } catch (CertificateException e) { - throw new RuntimeException(e); - } - } - - private static X509Certificate toX509Certificate(Object pemObject) throws CertificateException { - if (pemObject instanceof X509Certificate) { - return (X509Certificate) pemObject; - } - if (pemObject instanceof X509CertificateHolder) { - return new JcaX509CertificateConverter() - .setProvider(BouncyCastleProviderHolder.getInstance()) - .getCertificate((X509CertificateHolder) pemObject); - } - throw new IllegalArgumentException("Invalid type of PEM object: " + pemObject); - } - - public static String toPem(X509Certificate certificate) { - try (StringWriter stringWriter = new StringWriter(); JcaPEMWriter pemWriter = new JcaPEMWriter(stringWriter)) { - pemWriter.writeObject(new PemObject("CERTIFICATE", certificate.getEncoded())); - pemWriter.flush(); - return stringWriter.toString(); - } catch (GeneralSecurityException e) { - throw new RuntimeException(e); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - public static String toPem(List certificates) { - try (StringWriter stringWriter = new StringWriter(); JcaPEMWriter pemWriter = new JcaPEMWriter(stringWriter)) { - for (X509Certificate certificate : certificates) { - pemWriter.writeObject(new PemObject("CERTIFICATE", certificate.getEncoded())); - } - pemWriter.flush(); - return stringWriter.toString(); - } catch (GeneralSecurityException e) { - throw new RuntimeException(e); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - public static List getSubjectCommonNames(X509Certificate certificate) { - return getCommonNames(certificate.getSubjectX500Principal()); - } - - public static List getIssuerCommonNames(X509Certificate certificate) { - return getCommonNames(certificate.getIssuerX500Principal()); - } - - public static List getCommonNames(X500Principal subject) { - try { - String subjectPrincipal = subject.getName(); - return new LdapName(subjectPrincipal).getRdns().stream() - .filter(rdn -> rdn.getType().equalsIgnoreCase("cn")) - .map(rdn -> rdn.getValue().toString()) - .collect(toList()); - } catch (NamingException e) { - throw new IllegalArgumentException("Invalid CN: " + e, e); - } - - } - - public static List getSubjectAlternativeNames(X509Certificate certificate) { - try { - byte[] extensionValue = certificate.getExtensionValue(SUBJECT_ALTERNATIVE_NAMES.getOId()); - if (extensionValue == null) return Collections.emptyList(); - ASN1Encodable asn1Encodable = ASN1Primitive.fromByteArray(extensionValue); - if (asn1Encodable instanceof ASN1OctetString) { - asn1Encodable = ASN1Primitive.fromByteArray(((ASN1OctetString) asn1Encodable).getOctets()); - } - GeneralNames names = GeneralNames.getInstance(asn1Encodable); - return SubjectAlternativeName.fromGeneralNames(names); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } -} diff --git a/vespajlib/src/main/java/com/yahoo/security/package-info.java b/vespajlib/src/main/java/com/yahoo/security/package-info.java deleted file mode 100644 index 10a4c9c0e0d..00000000000 --- a/vespajlib/src/main/java/com/yahoo/security/package-info.java +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -/** - * @author bjorncs - */ - -@ExportPackage -package com.yahoo.security; - -import com.yahoo.osgi.annotation.ExportPackage; \ No newline at end of file diff --git a/vespajlib/src/main/java/com/yahoo/security/tls/TransportSecurityOptions.java b/vespajlib/src/main/java/com/yahoo/security/tls/TransportSecurityOptions.java deleted file mode 100644 index f0d1edd6889..00000000000 --- a/vespajlib/src/main/java/com/yahoo/security/tls/TransportSecurityOptions.java +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.security.tls; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; - -import java.io.IOException; -import java.io.UncheckedIOException; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.Objects; -import java.util.Optional; - -/** - * Generic TLS configuration for Vespa - * - * @author bjorncs - */ -public class TransportSecurityOptions { - - private static final ObjectMapper mapper = new ObjectMapper(); - - private final Path privateKeyFile; - private final Path certificatesFile; - private final Path caCertificatesFile; - - public TransportSecurityOptions(String privateKeyFile, String certificatesFile, String caCertificatesFile) { - this(Paths.get(privateKeyFile), Paths.get(certificatesFile), Paths.get(caCertificatesFile)); - } - - public TransportSecurityOptions(Path privateKeyFile, Path certificatesFile, Path caCertificatesFile) { - this.privateKeyFile = privateKeyFile; - this.certificatesFile = certificatesFile; - this.caCertificatesFile = caCertificatesFile; - } - - public Path getPrivateKeyFile() { - return privateKeyFile; - } - - public Path getCertificatesFile() { - return certificatesFile; - } - - public Path getCaCertificatesFile() { - return caCertificatesFile; - } - - public static TransportSecurityOptions fromJsonFile(Path file) { - try { - JsonNode root = mapper.readTree(file.toFile()); - JsonNode filesNode = getField(root, "files"); - String privateKeyFile = getField(filesNode, "private-key").asText(); - String certificatesFile = getField(filesNode, "certificates").asText(); - String caCertificatesFile = getField(filesNode, "ca-certificates").asText(); - return new TransportSecurityOptions(privateKeyFile, certificatesFile, caCertificatesFile); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - private static JsonNode getField(JsonNode root, String fieldName) { - return Optional.ofNullable(root.get(fieldName)) - .orElseThrow(() -> new IllegalArgumentException(String.format("'%s' field missing", fieldName))); - } - - @Override - public String toString() { - return "TransportSecurityOptions{" + - "privateKeyFile=" + privateKeyFile + - ", certificatesFile=" + certificatesFile + - ", caCertificatesFile=" + caCertificatesFile + - '}'; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - TransportSecurityOptions that = (TransportSecurityOptions) o; - return Objects.equals(privateKeyFile, that.privateKeyFile) && - Objects.equals(certificatesFile, that.certificatesFile) && - Objects.equals(caCertificatesFile, that.caCertificatesFile); - } - - @Override - public int hashCode() { - return Objects.hash(privateKeyFile, certificatesFile, caCertificatesFile); - } -} \ No newline at end of file diff --git a/vespajlib/src/main/java/com/yahoo/security/tls/TransportSecurityUtils.java b/vespajlib/src/main/java/com/yahoo/security/tls/TransportSecurityUtils.java deleted file mode 100644 index 5595d33a9b5..00000000000 --- a/vespajlib/src/main/java/com/yahoo/security/tls/TransportSecurityUtils.java +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.security.tls; - -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.Arrays; -import java.util.Optional; - -/** - * Utility class for retrieving {@link TransportSecurityOptions} from the system. - * - * @author bjorncs - */ -public class TransportSecurityUtils { - - public static final String CONFIG_FILE_ENVIRONMENT_VARIABLE = "VESPA_TLS_CONFIG_FILE"; - public static final String INSECURE_MIXED_MODE_ENVIRONMENT_VARIABLE = "VESPA_TLS_INSECURE_MIXED_MODE"; - - public enum MixedMode { - PLAINTEXT_CLIENT_MIXED_SERVER("plaintext_client_mixed_server"), - TLS_CLIENT_MIXED_SERVER("tls_client_mixed_server"); - - final String configValue; - - MixedMode(String configValue) { - this.configValue = configValue; - } - - static MixedMode fromConfigValue(String configValue) { - return Arrays.stream(values()) - .filter(v -> v.configValue.equals(configValue)) - .findFirst() - .orElseThrow(() -> new IllegalArgumentException("Unknown value: " + configValue)); - } - } - - private TransportSecurityUtils() {} - - public static boolean isTransportSecurityEnabled() { - return getConfigFile().isPresent(); - } - - public static boolean isInsecureMixedModeEnabled() { - return getInsecureMixedMode().isPresent(); - } - - public static Optional getInsecureMixedMode() { - if (!isTransportSecurityEnabled()) return Optional.empty(); - return getEnvironmentVariable(INSECURE_MIXED_MODE_ENVIRONMENT_VARIABLE) - .map(MixedMode::fromConfigValue); - } - - public static Optional getConfigFile() { - return getEnvironmentVariable(CONFIG_FILE_ENVIRONMENT_VARIABLE).map(Paths::get); - } - - public static Optional getOptions() { - return getConfigFile() - .map(TransportSecurityOptions::fromJsonFile); - } - - private static Optional getEnvironmentVariable(String environmentVariable) { - return Optional.ofNullable(System.getenv(environmentVariable)) - .filter(var -> !var.isEmpty()); - } -} diff --git a/vespajlib/src/main/java/com/yahoo/security/tls/package-info.java b/vespajlib/src/main/java/com/yahoo/security/tls/package-info.java deleted file mode 100644 index b5668182f14..00000000000 --- a/vespajlib/src/main/java/com/yahoo/security/tls/package-info.java +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -/** - * @author bjorncs - */ -@ExportPackage -package com.yahoo.security.tls; - -import com.yahoo.osgi.annotation.ExportPackage; \ No newline at end of file diff --git a/vespajlib/src/test/java/com/yahoo/security/KeyStoreBuilderTest.java b/vespajlib/src/test/java/com/yahoo/security/KeyStoreBuilderTest.java deleted file mode 100644 index 06ea5d963a3..00000000000 --- a/vespajlib/src/test/java/com/yahoo/security/KeyStoreBuilderTest.java +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.security; - -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; - -import java.nio.file.Path; -import java.security.KeyPair; -import java.security.cert.X509Certificate; - -import static com.yahoo.security.TestUtils.createCertificate; -import static com.yahoo.security.TestUtils.createKeystoreFile; - - -/** - * @author bjorncs - */ -public class KeyStoreBuilderTest { - - private static final char[] PASSWORD = new char[0]; - - @Rule - public TemporaryFolder tempDirectory = new TemporaryFolder(); - - @Test - public void can_create_jks_keystore_from_privatekey_and_certificate() throws Exception { - KeyPair keyPair = KeyUtils.generateKeypair(KeyAlgorithm.EC, 256); - X509Certificate certificate = createCertificate(keyPair); - KeyStoreBuilder.withType(KeyStoreType.JKS) - .withKeyEntry("key", keyPair.getPrivate(), certificate) - .build(); - } - - @Test - public void can_build_jks_keystore_from_file() throws Exception { - Path keystoreFile = tempDirectory.newFile().toPath(); - createKeystoreFile(keystoreFile, KeyStoreType.JKS, PASSWORD); - - KeyStoreBuilder.withType(KeyStoreType.JKS) - .fromFile(keystoreFile, PASSWORD) - .build(); - } - - @Test - public void can_build_pcks12_keystore_from_file() throws Exception { - Path keystoreFile = tempDirectory.newFile().toPath(); - createKeystoreFile(keystoreFile, KeyStoreType.PKCS12, PASSWORD); - - KeyStoreBuilder.withType(KeyStoreType.PKCS12) - .fromFile(keystoreFile, PASSWORD) - .build(); - } - -} \ No newline at end of file diff --git a/vespajlib/src/test/java/com/yahoo/security/KeyUtilsTest.java b/vespajlib/src/test/java/com/yahoo/security/KeyUtilsTest.java deleted file mode 100644 index 5e786654d7c..00000000000 --- a/vespajlib/src/test/java/com/yahoo/security/KeyUtilsTest.java +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.security; - -import org.junit.Test; - -import java.security.KeyPair; -import java.security.PrivateKey; -import java.security.PublicKey; - -import static org.hamcrest.CoreMatchers.containsString; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertThat; - -/** - * @author bjorncs - */ -public class KeyUtilsTest { - - @Test - public void can_extract_public_key_from_rsa_private() { - KeyPair keyPair = KeyUtils.generateKeypair(KeyAlgorithm.RSA); - PublicKey publicKey = KeyUtils.extractPublicKey(keyPair.getPrivate()); - assertNotNull(publicKey); - } - - @Test - public void can_extract_public_key_from_ecdsa_private() { - KeyPair keyPair = KeyUtils.generateKeypair(KeyAlgorithm.EC); - PublicKey publicKey = KeyUtils.extractPublicKey(keyPair.getPrivate()); - assertNotNull(publicKey); - } - - @Test - public void can_serialize_and_deserialize_rsa_privatekey_using_pem_format() { - KeyPair keyPair = KeyUtils.generateKeypair(KeyAlgorithm.RSA); - String pem = KeyUtils.toPem(keyPair.getPrivate()); - assertThat(pem, containsString("BEGIN RSA PRIVATE KEY")); - assertThat(pem, containsString("END RSA PRIVATE KEY")); - PrivateKey deserializedKey = KeyUtils.fromPemEncodedPrivateKey(pem); - assertEquals(keyPair.getPrivate(), deserializedKey); - } - - @Test - public void can_serialize_and_deserialize_ec_privatekey_using_pem_format() { - KeyPair keyPair = KeyUtils.generateKeypair(KeyAlgorithm.EC); - String pem = KeyUtils.toPem(keyPair.getPrivate()); - assertThat(pem, containsString("BEGIN EC PRIVATE KEY")); - assertThat(pem, containsString("END EC PRIVATE KEY")); - PrivateKey deserializedKey = KeyUtils.fromPemEncodedPrivateKey(pem); - assertEquals(keyPair.getPrivate(), deserializedKey); - } - -} \ No newline at end of file diff --git a/vespajlib/src/test/java/com/yahoo/security/Pkcs10CsrBuilderTest.java b/vespajlib/src/test/java/com/yahoo/security/Pkcs10CsrBuilderTest.java deleted file mode 100644 index d51203a5cb2..00000000000 --- a/vespajlib/src/test/java/com/yahoo/security/Pkcs10CsrBuilderTest.java +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.security; - -import org.junit.Test; - -import javax.security.auth.x500.X500Principal; -import java.security.KeyPair; - -import static org.junit.Assert.assertEquals; - -/** - * @author bjorncs - */ -public class Pkcs10CsrBuilderTest { - - @Test - public void can_build_csr_with_sans() { - X500Principal subject = new X500Principal("CN=subject"); - KeyPair keypair = KeyUtils.generateKeypair(KeyAlgorithm.EC, 256); - Pkcs10Csr csr = Pkcs10CsrBuilder.fromKeypair(subject, keypair, SignatureAlgorithm.SHA512_WITH_ECDSA) - .addSubjectAlternativeName("san1.com") - .addSubjectAlternativeName("san2.com") - .build(); - assertEquals(subject, csr.getSubject()); - } - -} \ No newline at end of file diff --git a/vespajlib/src/test/java/com/yahoo/security/Pkcs10CsrTest.java b/vespajlib/src/test/java/com/yahoo/security/Pkcs10CsrTest.java deleted file mode 100644 index cc1f6cc6a14..00000000000 --- a/vespajlib/src/test/java/com/yahoo/security/Pkcs10CsrTest.java +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.security; - -import org.junit.Test; - -import javax.security.auth.x500.X500Principal; -import java.security.KeyPair; -import java.util.Arrays; -import java.util.List; - -import static com.yahoo.security.SubjectAlternativeName.Type.DNS_NAME; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - -/** - * @author bjorncs - */ -public class Pkcs10CsrTest { - - @Test - public void can_read_subject_alternative_names() { - X500Principal subject = new X500Principal("CN=subject"); - KeyPair keypair = KeyUtils.generateKeypair(KeyAlgorithm.EC, 256); - SubjectAlternativeName san1 = new SubjectAlternativeName(DNS_NAME, "san1.com"); - SubjectAlternativeName san2 = new SubjectAlternativeName(DNS_NAME, "san2.com"); - Pkcs10Csr csr = Pkcs10CsrBuilder.fromKeypair(subject, keypair, SignatureAlgorithm.SHA512_WITH_ECDSA) - .addSubjectAlternativeName(san1) - .addSubjectAlternativeName(san2) - .build(); - assertEquals(Arrays.asList(san1, san2), csr.getSubjectAlternativeNames()); - } - - @Test - public void can_read_basic_constraints() { - X500Principal subject = new X500Principal("CN=subject"); - KeyPair keypair = KeyUtils.generateKeypair(KeyAlgorithm.EC, 256); - Pkcs10Csr csr = Pkcs10CsrBuilder.fromKeypair(subject, keypair, SignatureAlgorithm.SHA512_WITH_ECDSA) - .setBasicConstraints(true, true) - .build(); - assertTrue(csr.getBasicConstraints().isPresent()); - assertTrue(csr.getBasicConstraints().get()); - } - - @Test - public void can_read_extensions() { - X500Principal subject = new X500Principal("CN=subject"); - KeyPair keypair = KeyUtils.generateKeypair(KeyAlgorithm.EC, 256); - Pkcs10Csr csr = Pkcs10CsrBuilder.fromKeypair(subject, keypair, SignatureAlgorithm.SHA512_WITH_ECDSA) - .addSubjectAlternativeName("san") - .setBasicConstraints(true, true) - .build(); - List expected = Arrays.asList(Extension.BASIC_CONSTRAINTS.getOId(), Extension.SUBJECT_ALTERNATIVE_NAMES.getOId()); - List actual = csr.getExtensionOIds(); - assertEquals(expected, actual); - } - -} \ No newline at end of file diff --git a/vespajlib/src/test/java/com/yahoo/security/Pkcs10CsrUtilsTest.java b/vespajlib/src/test/java/com/yahoo/security/Pkcs10CsrUtilsTest.java deleted file mode 100644 index 04d35a537bb..00000000000 --- a/vespajlib/src/test/java/com/yahoo/security/Pkcs10CsrUtilsTest.java +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.security; - -import org.junit.Test; - -import javax.security.auth.x500.X500Principal; -import java.security.KeyPair; - -import static org.hamcrest.CoreMatchers.containsString; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertThat; - -/** - * @author bjorncs - */ -public class Pkcs10CsrUtilsTest { - - @Test - public void can_deserialize_serialized_pem_csr() { - X500Principal subject = new X500Principal("CN=subject"); - KeyPair keypair = KeyUtils.generateKeypair(KeyAlgorithm.EC, 256); - Pkcs10Csr csr = Pkcs10CsrBuilder.fromKeypair(subject, keypair, SignatureAlgorithm.SHA512_WITH_ECDSA).build(); - String pem = Pkcs10CsrUtils.toPem(csr); - Pkcs10Csr deserializedCsr = Pkcs10CsrUtils.fromPem(pem); - assertThat(pem, containsString("BEGIN CERTIFICATE REQUEST")); - assertThat(pem, containsString("END CERTIFICATE REQUEST")); - assertEquals(subject, deserializedCsr.getSubject()); - } - -} \ No newline at end of file diff --git a/vespajlib/src/test/java/com/yahoo/security/SslContextBuilderTest.java b/vespajlib/src/test/java/com/yahoo/security/SslContextBuilderTest.java deleted file mode 100644 index cc269a4ef43..00000000000 --- a/vespajlib/src/test/java/com/yahoo/security/SslContextBuilderTest.java +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.security; - -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; - -import java.nio.file.Path; -import java.security.KeyPair; -import java.security.cert.X509Certificate; - -import static com.yahoo.security.TestUtils.createCertificate; -import static com.yahoo.security.TestUtils.createKeystore; -import static com.yahoo.security.TestUtils.createKeystoreFile; - -/** - * @author bjorncs - */ -public class SslContextBuilderTest { - - private static final char[] PASSWORD = new char[0]; - - @Rule - public TemporaryFolder tempDirectory = new TemporaryFolder(); - - @Test - public void can_build_sslcontext_with_truststore_only() throws Exception { - new SslContextBuilder() - .withTrustStore(createKeystore(KeyStoreType.JKS, PASSWORD)) - .build(); - } - - @Test - public void can_build_sslcontext_with_keystore_only() throws Exception { - new SslContextBuilder() - .withKeyStore(createKeystore(KeyStoreType.JKS, PASSWORD), PASSWORD) - .build(); - } - - @Test - public void can_build_sslcontext_with_truststore_and_keystore() throws Exception { - new SslContextBuilder() - .withKeyStore(createKeystore(KeyStoreType.JKS, PASSWORD), PASSWORD) - .withTrustStore(createKeystore(KeyStoreType.JKS, PASSWORD)) - .build(); - } - - @Test - public void can_build_sslcontext_with_keystore_from_private_key_and_certificate() throws Exception { - KeyPair keyPair = KeyUtils.generateKeypair(KeyAlgorithm.EC, 256); - X509Certificate certificate = createCertificate(keyPair); - new SslContextBuilder() - .withKeyStore(keyPair.getPrivate(), certificate) - .build(); - } - - @Test - public void can_build_sslcontext_with_jks_keystore_from_file() throws Exception { - Path keystoreFile = tempDirectory.newFile().toPath(); - createKeystoreFile(keystoreFile, KeyStoreType.JKS, PASSWORD); - - new SslContextBuilder() - .withKeyStore(keystoreFile, PASSWORD, KeyStoreType.JKS) - .build(); - } - - @Test - public void can_build_sslcontext_with_pcks12_keystore_from_file() throws Exception { - Path keystoreFile = tempDirectory.newFile().toPath(); - createKeystoreFile(keystoreFile, KeyStoreType.PKCS12, PASSWORD); - - new SslContextBuilder() - .withKeyStore(keystoreFile, PASSWORD, KeyStoreType.PKCS12) - .build(); - } - -} diff --git a/vespajlib/src/test/java/com/yahoo/security/TestUtils.java b/vespajlib/src/test/java/com/yahoo/security/TestUtils.java deleted file mode 100644 index fcfcfb2b761..00000000000 --- a/vespajlib/src/test/java/com/yahoo/security/TestUtils.java +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.security; - -import javax.security.auth.x500.X500Principal; -import java.math.BigInteger; -import java.nio.file.Path; -import java.security.KeyPair; -import java.security.KeyStore; -import java.security.cert.X509Certificate; -import java.time.Instant; -import java.time.temporal.ChronoUnit; - -import static com.yahoo.security.KeyStoreUtils.writeKeyStoreToFile; - - -/** - * @author bjorncs - */ -class TestUtils { - - static KeyStore createKeystore(KeyStoreType type, char[] password) { - KeyPair keyPair = KeyUtils.generateKeypair(KeyAlgorithm.EC, 256); - return KeyStoreBuilder.withType(type) - .withKeyEntry("entry-name", keyPair.getPrivate(), password, createCertificate(keyPair)) - .build(); - } - - static X509Certificate createCertificate(KeyPair keyPair) { - return createCertificate(keyPair, new X500Principal("CN=mysubject")); - } - - static X509Certificate createCertificate(KeyPair keyPair, X500Principal subject) { - return X509CertificateBuilder - .fromKeypair( - keyPair, subject, Instant.now(), Instant.now().plus(1, ChronoUnit.DAYS), SignatureAlgorithm.SHA512_WITH_ECDSA, BigInteger.valueOf(1)) - .build(); - } - - static void createKeystoreFile(Path file, KeyStoreType type, char[] password) { - writeKeyStoreToFile(createKeystore(type, password), file, password); - } -} diff --git a/vespajlib/src/test/java/com/yahoo/security/X509CertificateBuilderTest.java b/vespajlib/src/test/java/com/yahoo/security/X509CertificateBuilderTest.java deleted file mode 100644 index 7e6d343b570..00000000000 --- a/vespajlib/src/test/java/com/yahoo/security/X509CertificateBuilderTest.java +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.security; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; - -import javax.security.auth.x500.X500Principal; -import java.math.BigInteger; -import java.security.KeyPair; -import java.security.cert.X509Certificate; -import java.time.Instant; -import java.time.temporal.ChronoUnit; -import java.util.Arrays; -import java.util.Collection; - -import static org.junit.Assert.assertEquals; - -/** - * @author bjorncs - */ -@RunWith(Parameterized.class) -public class X509CertificateBuilderTest { - - @Parameterized.Parameters(name = "{0}") - public static Collection data() { - return Arrays.asList(new Object[][] { - {KeyAlgorithm.RSA, 2048, SignatureAlgorithm.SHA512_WITH_RSA}, - {KeyAlgorithm.EC, 256, SignatureAlgorithm.SHA512_WITH_ECDSA}}); - } - - private final KeyAlgorithm keyAlgorithm; - private final int keySize; - private final SignatureAlgorithm signatureAlgorithm; - - public X509CertificateBuilderTest(KeyAlgorithm keyAlgorithm, - int keySize, - SignatureAlgorithm signatureAlgorithm) { - this.keyAlgorithm = keyAlgorithm; - this.keySize = keySize; - this.signatureAlgorithm = signatureAlgorithm; - } - - @Test - public void can_build_self_signed_certificate() { - KeyPair keyPair = KeyUtils.generateKeypair(keyAlgorithm, keySize); - X500Principal subject = new X500Principal("CN=myservice"); - X509Certificate cert = - X509CertificateBuilder.fromKeypair( - keyPair, - subject, - Instant.now(), - Instant.now().plus(1, ChronoUnit.DAYS), - signatureAlgorithm, - BigInteger.valueOf(1)) - .setBasicConstraints(true, true) - .build(); - assertEquals(subject, cert.getSubjectX500Principal()); - } - - @Test - public void can_build_certificate_from_csr() { - X500Principal subject = new X500Principal("CN=subject"); - X500Principal issuer = new X500Principal("CN=issuer"); - KeyPair csrKeypair = KeyUtils.generateKeypair(keyAlgorithm, keySize); - Pkcs10Csr csr = Pkcs10CsrBuilder.fromKeypair(subject, csrKeypair, signatureAlgorithm).build(); - KeyPair caKeypair = KeyUtils.generateKeypair(keyAlgorithm, keySize); - X509Certificate cert = X509CertificateBuilder - .fromCsr( - csr, - issuer, - Instant.now(), - Instant.now().plus(1, ChronoUnit.DAYS), - caKeypair.getPrivate(), - signatureAlgorithm, - BigInteger.valueOf(1)) - .addSubjectAlternativeName("subject1.alt") - .addSubjectAlternativeName("subject2.alt") - .build(); - assertEquals(subject, cert.getSubjectX500Principal()); - } - -} \ No newline at end of file diff --git a/vespajlib/src/test/java/com/yahoo/security/X509CertificateUtilsTest.java b/vespajlib/src/test/java/com/yahoo/security/X509CertificateUtilsTest.java deleted file mode 100644 index 76a93028efe..00000000000 --- a/vespajlib/src/test/java/com/yahoo/security/X509CertificateUtilsTest.java +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.security; - -import org.junit.Test; - -import javax.security.auth.x500.X500Principal; -import java.math.BigInteger; -import java.security.KeyPair; -import java.security.cert.X509Certificate; -import java.time.Instant; -import java.time.temporal.ChronoUnit; -import java.util.Arrays; -import java.util.List; - -import static com.yahoo.security.SubjectAlternativeName.Type.DNS_NAME; -import static org.hamcrest.CoreMatchers.containsString; -import static org.hamcrest.CoreMatchers.equalTo; -import static org.hamcrest.Matchers.is; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertThat; - -/** - * @author bjorncs - */ -public class X509CertificateUtilsTest { - @Test - public void can_deserialize_serialized_pem_certificate() { - KeyPair keypair = KeyUtils.generateKeypair(KeyAlgorithm.EC, 256); - X500Principal subject = new X500Principal("CN=myservice"); - X509Certificate cert = TestUtils.createCertificate(keypair, subject); - assertEquals(subject, cert.getSubjectX500Principal()); - String pem = X509CertificateUtils.toPem(cert); - assertThat(pem, containsString("BEGIN CERTIFICATE")); - assertThat(pem, containsString("END CERTIFICATE")); - X509Certificate deserializedCert = X509CertificateUtils.fromPem(pem); - assertEquals(subject, deserializedCert.getSubjectX500Principal()); - } - - @Test - public void can_deserialize_serialized_pem_certificate_list() { - KeyPair keypair = KeyUtils.generateKeypair(KeyAlgorithm.EC, 256); - X500Principal subject1 = new X500Principal("CN=myservice1"); - X509Certificate cert1 = TestUtils.createCertificate(keypair, subject1); - X500Principal subject2 = new X500Principal("CN=myservice2"); - X509Certificate cert2 = TestUtils.createCertificate(keypair, subject2); - List certificateList = Arrays.asList(cert1, cert2); - String pem = X509CertificateUtils.toPem(certificateList); - List deserializedCertificateList = X509CertificateUtils.certificateListFromPem(pem); - assertEquals(2, certificateList.size()); - assertEquals(subject1, deserializedCertificateList.get(0).getSubjectX500Principal()); - assertEquals(subject2, deserializedCertificateList.get(1).getSubjectX500Principal()); - } - - @Test - public void can_list_subject_alternative_names() { - KeyPair keypair = KeyUtils.generateKeypair(KeyAlgorithm.EC, 256); - X500Principal subject = new X500Principal("CN=myservice"); - SubjectAlternativeName san = new SubjectAlternativeName(DNS_NAME, "dns-san"); - X509Certificate cert = X509CertificateBuilder - .fromKeypair( - keypair, - subject, - Instant.now(), - Instant.now().plus(1, ChronoUnit.DAYS), - SignatureAlgorithm.SHA512_WITH_ECDSA, - BigInteger.valueOf(1)) - .addSubjectAlternativeName(san) - .build(); - - List sans = X509CertificateUtils.getSubjectAlternativeNames(cert); - assertThat(sans.size(), is(1)); - assertThat(sans.get(0), equalTo(san)); - } -} \ No newline at end of file diff --git a/vespajlib/src/test/java/com/yahoo/security/tls/TransportSecurityOptionsTest.java b/vespajlib/src/test/java/com/yahoo/security/tls/TransportSecurityOptionsTest.java deleted file mode 100644 index f311651cab0..00000000000 --- a/vespajlib/src/test/java/com/yahoo/security/tls/TransportSecurityOptionsTest.java +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.security.tls; - -import org.junit.Test; - -import java.nio.file.Path; -import java.nio.file.Paths; - -import static org.junit.Assert.*; - -/** - * @author bjorncs - */ -public class TransportSecurityOptionsTest { - - private static final Path TEST_CONFIG_FILE = Paths.get("src/test/resources/transport-security-options.json"); - - @Test - public void can_read_options_from_json_file() { - TransportSecurityOptions expectedOptions = new TransportSecurityOptions("myhost.key", "certs.pem", "my_cas.pem"); - TransportSecurityOptions actualOptions = TransportSecurityOptions.fromJsonFile(TEST_CONFIG_FILE); - assertEquals(expectedOptions, actualOptions); - } - -} diff --git a/vespajlib/src/test/resources/transport-security-options.json b/vespajlib/src/test/resources/transport-security-options.json deleted file mode 100644 index 0506c130722..00000000000 --- a/vespajlib/src/test/resources/transport-security-options.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "files": { - "private-key": "myhost.key", - "ca-certificates": "my_cas.pem", - "certificates": "certs.pem" - } -} \ No newline at end of file -- cgit v1.2.3