aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBjørn Christian Seime <bjorn.christian@seime.no>2018-11-28 13:40:34 +0100
committerGitHub <noreply@github.com>2018-11-28 13:40:34 +0100
commitf78715cad1b7a8124ad95720b1a74403f68443ab (patch)
treeb7b0bba7f75a55e71b67914fd8a3c4691c2227cb
parent125dc30240e2ac6a00d2a4d3f503d2511ea592e2 (diff)
parent20317de277a40a6c7711ada685f7564707dc0573 (diff)
Merge pull request #7783 from vespa-engine/bjorncs/tls-certificate-validation
Bjorncs/tls certificate validation
-rw-r--r--jrt/pom.xml5
-rw-r--r--jrt/src/com/yahoo/jrt/TlsCryptoEngine.java5
-rw-r--r--jrt/tests/com/yahoo/jrt/CryptoUtils.java24
-rw-r--r--security-utils/src/main/java/com/yahoo/security/SslContextBuilder.java26
-rw-r--r--security-utils/src/main/java/com/yahoo/security/tls/authz/PeerAuthorizer.java5
-rw-r--r--security-utils/src/main/java/com/yahoo/security/tls/authz/PeerAuthorizerTrustManager.java128
-rw-r--r--security-utils/src/main/java/com/yahoo/security/tls/authz/PeerAuthorizerTrustManagersFactory.java27
7 files changed, 216 insertions, 4 deletions
diff --git a/jrt/pom.xml b/jrt/pom.xml
index 6852c6deee6..641ed7e69ac 100644
--- a/jrt/pom.xml
+++ b/jrt/pom.xml
@@ -35,6 +35,11 @@
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
+ <dependency> <!-- required due to bug in maven dependency resolving - bouncycastle is compile scope in security-utils, yet it is not part of test scope here -->
+ <groupId>org.bouncycastle</groupId>
+ <artifactId>bcpkix-jdk15on</artifactId>
+ <scope>test</scope>
+ </dependency>
</dependencies>
<build>
<sourceDirectory>src</sourceDirectory>
diff --git a/jrt/src/com/yahoo/jrt/TlsCryptoEngine.java b/jrt/src/com/yahoo/jrt/TlsCryptoEngine.java
index 4c483072f5f..db18ddf8c9d 100644
--- a/jrt/src/com/yahoo/jrt/TlsCryptoEngine.java
+++ b/jrt/src/com/yahoo/jrt/TlsCryptoEngine.java
@@ -3,6 +3,8 @@ package com.yahoo.jrt;
import com.yahoo.security.SslContextBuilder;
import com.yahoo.security.tls.TransportSecurityOptions;
+import com.yahoo.security.tls.authz.PeerAuthorizerTrustManager.Mode;
+import com.yahoo.security.tls.authz.PeerAuthorizerTrustManagersFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
@@ -33,11 +35,14 @@ public class TlsCryptoEngine implements CryptoEngine {
return new TlsCryptoSocket(channel, sslEngine);
}
+ // TODO Move to dedicated factory type controlling certificate hot-reloading in security-utils
private static SSLContext createSslContext(TransportSecurityOptions options) {
SslContextBuilder builder = new SslContextBuilder();
options.getCertificatesFile()
.ifPresent(certificates -> builder.withKeyStore(options.getPrivateKeyFile().get(), certificates));
options.getCaCertificatesFile().ifPresent(builder::withTrustStore);
+ options.getAuthorizedPeers().ifPresent(
+ authorizedPeers -> builder.withTrustManagerFactory(new PeerAuthorizerTrustManagersFactory(authorizedPeers, Mode.DRY_RUN)));
return builder.build();
}
}
diff --git a/jrt/tests/com/yahoo/jrt/CryptoUtils.java b/jrt/tests/com/yahoo/jrt/CryptoUtils.java
index c3128e09bd3..6c843000779 100644
--- a/jrt/tests/com/yahoo/jrt/CryptoUtils.java
+++ b/jrt/tests/com/yahoo/jrt/CryptoUtils.java
@@ -5,6 +5,14 @@ import com.yahoo.security.KeyStoreBuilder;
import com.yahoo.security.KeyUtils;
import com.yahoo.security.SslContextBuilder;
import com.yahoo.security.X509CertificateBuilder;
+import com.yahoo.security.tls.authz.PeerAuthorizerTrustManager.Mode;
+import com.yahoo.security.tls.authz.PeerAuthorizerTrustManagersFactory;
+import com.yahoo.security.tls.policy.AuthorizedPeers;
+import com.yahoo.security.tls.policy.HostGlobPattern;
+import com.yahoo.security.tls.policy.PeerPolicy;
+import com.yahoo.security.tls.policy.RequiredPeerCredential;
+import com.yahoo.security.tls.policy.RequiredPeerCredential.Field;
+import com.yahoo.security.tls.policy.Role;
import javax.net.ssl.SSLContext;
import javax.security.auth.x500.X500Principal;
@@ -19,6 +27,8 @@ import static com.yahoo.security.SignatureAlgorithm.SHA256_WITH_RSA;
import static com.yahoo.security.X509CertificateBuilder.generateRandomSerialNumber;
import static java.time.Instant.EPOCH;
import static java.time.temporal.ChronoUnit.DAYS;
+import static java.util.Collections.singleton;
+import static java.util.Collections.singletonList;
/**
* @author bjorncs
@@ -35,9 +45,23 @@ class CryptoUtils {
.withCertificateEntry("self-signed", certificate)
.build();
+
return new SslContextBuilder()
.withTrustStore(trustStore)
.withKeyStore(keyPair.getPrivate(), certificate)
+ .withTrustManagerFactory(new PeerAuthorizerTrustManagersFactory(createAuthorizedPeers(), Mode.ENFORCE))
.build();
}
+
+ private static AuthorizedPeers createAuthorizedPeers() {
+ return new AuthorizedPeers(
+ singleton(
+ new PeerPolicy(
+ "dummy-policy",
+ singleton(
+ new Role("dummy-role")),
+ singletonList(
+ new RequiredPeerCredential(
+ Field.CN, new HostGlobPattern("dummy"))))));
+ }
}
diff --git a/security-utils/src/main/java/com/yahoo/security/SslContextBuilder.java b/security-utils/src/main/java/com/yahoo/security/SslContextBuilder.java
index 75ab2417edf..17d425578ee 100644
--- a/security-utils/src/main/java/com/yahoo/security/SslContextBuilder.java
+++ b/security-utils/src/main/java/com/yahoo/security/SslContextBuilder.java
@@ -14,7 +14,6 @@ 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;
@@ -27,6 +26,7 @@ public class SslContextBuilder {
private KeyStoreSupplier trustStoreSupplier;
private KeyStoreSupplier keyStoreSupplier;
private char[] keyStorePassword;
+ private TrustManagersFactory trustManagersFactory = SslContextBuilder::createDefaultTrustManagers;
public SslContextBuilder() {}
@@ -90,11 +90,16 @@ public class SslContextBuilder {
return this;
}
+ public SslContextBuilder withTrustManagerFactory(TrustManagersFactory trustManagersFactory) {
+ this.trustManagersFactory = trustManagersFactory;
+ return this;
+ }
+
public SSLContext build() {
try {
SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
TrustManager[] trustManagers =
- trustStoreSupplier != null ? createTrustManagers(trustStoreSupplier) : null;
+ trustStoreSupplier != null ? createTrustManagers(trustManagersFactory, trustStoreSupplier) : null;
KeyManager[] keyManagers =
keyStoreSupplier != null ? createKeyManagers(keyStoreSupplier, keyStorePassword) : null;
sslContext.init(keyManagers, trustManagers, null);
@@ -106,11 +111,16 @@ public class SslContextBuilder {
}
}
- private static TrustManager[] createTrustManagers(KeyStoreSupplier trustStoreSupplier)
+ private static TrustManager[] createTrustManagers(TrustManagersFactory trustManagersFactory, KeyStoreSupplier trustStoreSupplier)
throws GeneralSecurityException, IOException {
+ KeyStore truststore = trustStoreSupplier.get();
+ return trustManagersFactory.createTrustManagers(truststore);
+ }
+
+ private static TrustManager[] createDefaultTrustManagers(KeyStore truststore) throws GeneralSecurityException {
TrustManagerFactory trustManagerFactory =
TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
- trustManagerFactory.init(trustStoreSupplier.get());
+ trustManagerFactory.init(truststore);
return trustManagerFactory.getTrustManagers();
}
@@ -134,4 +144,12 @@ public class SslContextBuilder {
KeyStore get() throws IOException, GeneralSecurityException;
}
+ /**
+ * A factory interface that is similar to {@link TrustManagerFactory}, but is an interface instead of a class.
+ */
+ @FunctionalInterface
+ public interface TrustManagersFactory {
+ TrustManager[] createTrustManagers(KeyStore truststore) throws GeneralSecurityException;
+ }
+
}
diff --git a/security-utils/src/main/java/com/yahoo/security/tls/authz/PeerAuthorizer.java b/security-utils/src/main/java/com/yahoo/security/tls/authz/PeerAuthorizer.java
index bead32fe309..a40813be96f 100644
--- a/security-utils/src/main/java/com/yahoo/security/tls/authz/PeerAuthorizer.java
+++ b/security-utils/src/main/java/com/yahoo/security/tls/authz/PeerAuthorizer.java
@@ -13,6 +13,7 @@ import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
+import java.util.logging.Logger;
import static com.yahoo.security.SubjectAlternativeName.Type.DNS_NAME;
import static com.yahoo.security.SubjectAlternativeName.Type.IP_ADDRESS;
@@ -24,6 +25,9 @@ import static java.util.stream.Collectors.toList;
* @author bjorncs
*/
public class PeerAuthorizer {
+
+ private static final Logger log = Logger.getLogger(PeerAuthorizer.class.getName());
+
private final AuthorizedPeers authorizedPeers;
public PeerAuthorizer(AuthorizedPeers authorizedPeers) {
@@ -35,6 +39,7 @@ public class PeerAuthorizer {
Set<String> matchedPolicies = new HashSet<>();
String cn = getCommonName(peerCertificate).orElse(null);
List<String> sans = getSubjectAlternativeNames(peerCertificate);
+ log.fine(() -> String.format("Subject info from x509 certificate: CN=[%s], 'SAN=%s", cn, sans));
for (PeerPolicy peerPolicy : authorizedPeers.peerPolicies()) {
if (matchesPolicy(peerPolicy, cn, sans)) {
assumedRoles.addAll(peerPolicy.assumedRoles());
diff --git a/security-utils/src/main/java/com/yahoo/security/tls/authz/PeerAuthorizerTrustManager.java b/security-utils/src/main/java/com/yahoo/security/tls/authz/PeerAuthorizerTrustManager.java
new file mode 100644
index 00000000000..aca4f86b639
--- /dev/null
+++ b/security-utils/src/main/java/com/yahoo/security/tls/authz/PeerAuthorizerTrustManager.java
@@ -0,0 +1,128 @@
+// 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.authz;
+
+import com.yahoo.security.X509CertificateUtils;
+import com.yahoo.security.tls.policy.AuthorizedPeers;
+
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+import javax.net.ssl.X509ExtendedTrustManager;
+import java.net.Socket;
+import java.security.GeneralSecurityException;
+import java.security.KeyStore;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.util.logging.Logger;
+
+/**
+ * A {@link X509ExtendedTrustManager} that performs additional certificate verification through {@link PeerAuthorizer}.
+ *
+ * @author bjorncs
+ */
+// Note: Implementation assumes that provided X509ExtendedTrustManager will throw IllegalArgumentException when chain is empty or null
+public class PeerAuthorizerTrustManager extends X509ExtendedTrustManager {
+
+ public static final String HANDSHAKE_SESSION_AUTHZ_RESULT_PROPERTY = "vespa.tls.authorization.result";
+
+ private static final Logger log = Logger.getLogger(PeerAuthorizerTrustManager.class.getName());
+
+ public enum Mode { DRY_RUN, ENFORCE }
+
+ private final PeerAuthorizer authorizer;
+ private final X509ExtendedTrustManager defaultTrustManager;
+ private final Mode mode;
+
+ public PeerAuthorizerTrustManager(AuthorizedPeers authorizedPeers, Mode mode, X509ExtendedTrustManager defaultTrustManager) {
+ this.authorizer = new PeerAuthorizer(authorizedPeers);
+ this.mode = mode;
+ this.defaultTrustManager = defaultTrustManager;
+ }
+
+ public static TrustManager[] wrapTrustManagersFromKeystore(AuthorizedPeers authorizedPeers, Mode mode, KeyStore keystore) throws GeneralSecurityException {
+ TrustManagerFactory factory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
+ factory.init(keystore);
+ return wrapTrustManagers(authorizedPeers, mode, factory.getTrustManagers());
+ }
+
+ public static TrustManager[] wrapTrustManagers(AuthorizedPeers authorizedPeers, Mode mode, TrustManager[] managers) {
+ TrustManager[] wrappedManagers = new TrustManager[managers.length];
+ for (int i = 0; i < managers.length; i++) {
+ if (managers[i] instanceof X509ExtendedTrustManager) {
+ wrappedManagers[i] = new PeerAuthorizerTrustManager(authorizedPeers, mode, (X509ExtendedTrustManager) managers[i]);
+ } else {
+ wrappedManagers[i] = managers[i];
+ }
+ }
+ return wrappedManagers;
+ }
+
+ @Override
+ public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
+ defaultTrustManager.checkClientTrusted(chain, authType);
+ authorizePeer(chain[0], authType, true, null);
+ }
+
+ @Override
+ public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
+ defaultTrustManager.checkServerTrusted(chain, authType);
+ authorizePeer(chain[0], authType, false, null);
+ }
+
+ @Override
+ public void checkClientTrusted(X509Certificate[] chain, String authType, Socket socket) throws CertificateException {
+ defaultTrustManager.checkClientTrusted(chain, authType, socket);
+ authorizePeer(chain[0], authType, true, null);
+ }
+
+ @Override
+ public void checkServerTrusted(X509Certificate[] chain, String authType, Socket socket) throws CertificateException {
+ defaultTrustManager.checkServerTrusted(chain, authType, socket);
+ authorizePeer(chain[0], authType, false, null);
+ }
+
+ @Override
+ public void checkClientTrusted(X509Certificate[] chain, String authType, SSLEngine sslEngine) throws CertificateException {
+ defaultTrustManager.checkClientTrusted(chain, authType, sslEngine);
+ authorizePeer(chain[0], authType, true, sslEngine);
+ }
+
+ @Override
+ public void checkServerTrusted(X509Certificate[] chain, String authType, SSLEngine sslEngine) throws CertificateException {
+ defaultTrustManager.checkServerTrusted(chain, authType, sslEngine);
+ authorizePeer(chain[0], authType, false, sslEngine);
+ }
+
+ @Override
+ public X509Certificate[] getAcceptedIssuers() {
+ return defaultTrustManager.getAcceptedIssuers();
+ }
+
+ private void authorizePeer(X509Certificate certificate, String authType, boolean isVerifyingClient, SSLEngine sslEngine) throws CertificateException {
+ log.fine(() -> "Verifying certificate: " + createInfoString(certificate, authType, isVerifyingClient));
+ AuthorizationResult result = authorizer.authorizePeer(certificate);
+ if (sslEngine != null) { // getHandshakeSession() will never return null in this context
+ sslEngine.getHandshakeSession().putValue(HANDSHAKE_SESSION_AUTHZ_RESULT_PROPERTY, result);
+ }
+ if (result.succeeded()) {
+ log.fine(() -> String.format("Verification result: %s", result));
+ } else {
+ String errorMessage = "Authorization failed: " + createInfoString(certificate, authType, isVerifyingClient);
+ log.warning(errorMessage);
+ switch (mode) {
+ case ENFORCE:
+ throw new CertificateException(errorMessage);
+ case DRY_RUN:
+ break;
+ default:
+ throw new UnsupportedOperationException();
+ }
+ }
+ }
+
+ private static String createInfoString(X509Certificate certificate, String authType, boolean isVerifyingClient) {
+ return String.format("DN='%s', SANs=%s, authType='%s', isVerifyingClient='%b'",
+ certificate.getSubjectX500Principal(), X509CertificateUtils.getSubjectAlternativeNames(certificate), authType, isVerifyingClient);
+ }
+
+}
diff --git a/security-utils/src/main/java/com/yahoo/security/tls/authz/PeerAuthorizerTrustManagersFactory.java b/security-utils/src/main/java/com/yahoo/security/tls/authz/PeerAuthorizerTrustManagersFactory.java
new file mode 100644
index 00000000000..0bb99aea886
--- /dev/null
+++ b/security-utils/src/main/java/com/yahoo/security/tls/authz/PeerAuthorizerTrustManagersFactory.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.tls.authz;
+
+import com.yahoo.security.SslContextBuilder;
+import com.yahoo.security.tls.policy.AuthorizedPeers;
+
+import javax.net.ssl.TrustManager;
+import java.security.GeneralSecurityException;
+import java.security.KeyStore;
+
+/**
+ * @author bjorncs
+ */
+public class PeerAuthorizerTrustManagersFactory implements SslContextBuilder.TrustManagersFactory {
+ private final AuthorizedPeers authorizedPeers;
+ private PeerAuthorizerTrustManager.Mode mode;
+
+ public PeerAuthorizerTrustManagersFactory(AuthorizedPeers authorizedPeers, PeerAuthorizerTrustManager.Mode mode) {
+ this.authorizedPeers = authorizedPeers;
+ this.mode = mode;
+ }
+
+ @Override
+ public TrustManager[] createTrustManagers(KeyStore truststore) throws GeneralSecurityException {
+ return PeerAuthorizerTrustManager.wrapTrustManagersFromKeystore(authorizedPeers, mode, truststore);
+ }
+}