aboutsummaryrefslogtreecommitdiffstats
path: root/vespa-feed-client/src/main/java/ai/vespa/feed/client/impl/SslContextBuilder.java
diff options
context:
space:
mode:
Diffstat (limited to 'vespa-feed-client/src/main/java/ai/vespa/feed/client/impl/SslContextBuilder.java')
-rw-r--r--vespa-feed-client/src/main/java/ai/vespa/feed/client/impl/SslContextBuilder.java176
1 files changed, 176 insertions, 0 deletions
diff --git a/vespa-feed-client/src/main/java/ai/vespa/feed/client/impl/SslContextBuilder.java b/vespa-feed-client/src/main/java/ai/vespa/feed/client/impl/SslContextBuilder.java
new file mode 100644
index 00000000000..2ca4577abe6
--- /dev/null
+++ b/vespa-feed-client/src/main/java/ai/vespa/feed/client/impl/SslContextBuilder.java
@@ -0,0 +1,176 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package ai.vespa.feed.client.impl;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
+import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.openssl.PEMKeyPair;
+import org.bouncycastle.openssl.PEMParser;
+
+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.nio.file.Files;
+import java.nio.file.Path;
+import java.security.GeneralSecurityException;
+import java.security.KeyFactory;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.PrivateKey;
+import java.security.cert.Certificate;
+import java.security.cert.X509Certificate;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * BouncyCastle integration for creating a {@link SSLContext} instance from PEM encoded material
+ *
+ * @author bjorncs
+ */
+class SslContextBuilder {
+
+ static final BouncyCastleProvider bcProvider = new BouncyCastleProvider();
+
+ private Path certificateFile;
+ private Path privateKeyFile;
+ private Path caCertificatesFile;
+ private Collection<X509Certificate> certificate;
+ private PrivateKey privateKey;
+ private Collection<X509Certificate> caCertificates;
+
+ SslContextBuilder withCertificateAndKey(Path certificate, Path privateKey) {
+ this.certificateFile = certificate;
+ this.privateKeyFile = privateKey;
+ return this;
+ }
+
+ SslContextBuilder withCertificateAndKey(Collection<X509Certificate> certificate, PrivateKey privateKey) {
+ this.certificate = certificate;
+ this.privateKey = privateKey;
+ return this;
+ }
+
+ SslContextBuilder withCaCertificates(Path caCertificates) {
+ this.caCertificatesFile = caCertificates;
+ return this;
+ }
+
+ SslContextBuilder withCaCertificates(Collection<X509Certificate> caCertificates) {
+ this.caCertificates = caCertificates;
+ return this;
+ }
+
+ SSLContext build() throws IOException {
+ try {
+ KeyStore keystore = KeyStore.getInstance("PKCS12");
+ keystore.load(null);
+ if (hasCertificateFile()) {
+ keystore.setKeyEntry("cert", privateKey(privateKeyFile), new char[0], certificates(certificateFile));
+ } else if (hasCertificateInstance()) {
+ keystore.setKeyEntry("cert", privateKey, new char[0], certificate.toArray(new Certificate[0]));
+ }
+ if (hasCaCertificateFile()) {
+ addCaCertificates(keystore, Arrays.asList(certificates(caCertificatesFile)));
+ } else if (hasCaCertificateInstance()) {
+ addCaCertificates(keystore, caCertificates);
+ }
+ SSLContext sslContext = SSLContext.getInstance("TLS");
+ sslContext.init(
+ createKeyManagers(keystore).orElse(null),
+ createTrustManagers(keystore).orElse(null),
+ /*Default secure random algorithm*/null);
+ return sslContext;
+ } catch (GeneralSecurityException e) {
+ throw new IOException(e);
+ }
+ }
+
+ private boolean hasCertificateFile() { return certificateFile != null && privateKeyFile != null; }
+ private boolean hasCertificateInstance() { return certificate != null && privateKey != null; }
+ private boolean hasCaCertificateFile() { return caCertificatesFile != null; }
+ private boolean hasCaCertificateInstance() { return caCertificates != null; }
+
+ private Optional<KeyManager[]> createKeyManagers(KeyStore keystore) throws GeneralSecurityException {
+ if (!hasCertificateInstance() && !hasCertificateFile()) return Optional.empty();
+ KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
+ kmf.init(keystore, new char[0]);
+ return Optional.of(kmf.getKeyManagers());
+ }
+
+ private Optional<TrustManager[]> createTrustManagers(KeyStore keystore) throws GeneralSecurityException {
+ if (!hasCaCertificateInstance() && !hasCaCertificateFile()) return Optional.empty();
+ TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
+ tmf.init(keystore);
+ return Optional.of(tmf.getTrustManagers());
+ }
+
+ private static void addCaCertificates(KeyStore keystore, Collection<? extends Certificate> certificates) throws KeyStoreException {
+ int i = 0;
+ for (Certificate cert : certificates) {
+ keystore.setCertificateEntry("ca-cert-" + ++i, cert);
+ }
+ }
+
+ private static Certificate[] certificates(Path file) throws IOException, GeneralSecurityException {
+ try (PEMParser parser = new PEMParser(Files.newBufferedReader(file))) {
+ List<X509Certificate> result = new ArrayList<>();
+ Object pemObject;
+ while ((pemObject = parser.readObject()) != null) {
+ result.add(toX509Certificate(pemObject));
+ }
+ if (result.isEmpty()) throw new IOException("File contains no PEM encoded certificates: " + file);
+ return result.toArray(new Certificate[0]);
+ }
+ }
+
+ private static PrivateKey privateKey(Path file) throws IOException, GeneralSecurityException {
+ try (PEMParser parser = new PEMParser(Files.newBufferedReader(file))) {
+ Object pemObject;
+ while ((pemObject = parser.readObject()) != null) {
+ if (pemObject instanceof PrivateKeyInfo) {
+ PrivateKeyInfo keyInfo = (PrivateKeyInfo) pemObject;
+ PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyInfo.getEncoded());
+ return createKeyFactory(keyInfo).generatePrivate(keySpec);
+ } else if (pemObject instanceof PEMKeyPair) {
+ PEMKeyPair pemKeypair = (PEMKeyPair) pemObject;
+ PrivateKeyInfo keyInfo = pemKeypair.getPrivateKeyInfo();
+ return createKeyFactory(keyInfo).generatePrivate(new PKCS8EncodedKeySpec(keyInfo.getEncoded()));
+ }
+ }
+ throw new IOException("Could not find private key in PEM file");
+ }
+ }
+
+ private static X509Certificate toX509Certificate(Object pemObject) throws IOException, GeneralSecurityException {
+ if (pemObject instanceof X509Certificate) return (X509Certificate) pemObject;
+ if (pemObject instanceof X509CertificateHolder) {
+ return new JcaX509CertificateConverter()
+ .setProvider(bcProvider)
+ .getCertificate((X509CertificateHolder) pemObject);
+ }
+ throw new IOException("Invalid type of PEM object: " + pemObject);
+ }
+
+ private static KeyFactory createKeyFactory(PrivateKeyInfo info) throws IOException, GeneralSecurityException {
+ ASN1ObjectIdentifier algorithm = info.getPrivateKeyAlgorithm().getAlgorithm();
+ if (X9ObjectIdentifiers.id_ecPublicKey.equals(algorithm)) {
+ return KeyFactory.getInstance("EC", bcProvider);
+ } else if (PKCSObjectIdentifiers.rsaEncryption.equals(algorithm)) {
+ return KeyFactory.getInstance("RSA", bcProvider);
+ } else {
+ throw new IOException("Unknown key algorithm: " + algorithm);
+ }
+ }
+
+}