summaryrefslogtreecommitdiffstats
path: root/security-utils/src/main/java/com/yahoo/security/tls/PeerAuthorizerTrustManager.java
diff options
context:
space:
mode:
Diffstat (limited to 'security-utils/src/main/java/com/yahoo/security/tls/PeerAuthorizerTrustManager.java')
-rw-r--r--security-utils/src/main/java/com/yahoo/security/tls/PeerAuthorizerTrustManager.java164
1 files changed, 164 insertions, 0 deletions
diff --git a/security-utils/src/main/java/com/yahoo/security/tls/PeerAuthorizerTrustManager.java b/security-utils/src/main/java/com/yahoo/security/tls/PeerAuthorizerTrustManager.java
new file mode 100644
index 00000000000..c3dcdf1dc9e
--- /dev/null
+++ b/security-utils/src/main/java/com/yahoo/security/tls/PeerAuthorizerTrustManager.java
@@ -0,0 +1,164 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.security.tls;
+
+import com.yahoo.security.TrustManagerUtils;
+import com.yahoo.security.X509CertificateUtils;
+
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLParameters;
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.SSLSocket;
+import javax.net.ssl.X509ExtendedTrustManager;
+import java.net.Socket;
+import java.security.KeyStore;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * A {@link X509ExtendedTrustManager} that performs additional certificate verification through {@link PeerAuthorizer}.
+ *
+ * Implementation assumes that provided {@link X509ExtendedTrustManager} will throw {@link IllegalArgumentException}
+ * when chain is empty or null.
+ *
+ * @author bjorncs
+ */
+class PeerAuthorizerTrustManager extends X509ExtendedTrustManager {
+
+ static final String AUTH_CONTEXT_PROPERTY = "vespa.tls.auth.ctx";
+
+ private static final Logger log = Logger.getLogger(PeerAuthorizerTrustManager.class.getName());
+
+ private final PeerAuthorizer authorizer;
+ private final X509ExtendedTrustManager defaultTrustManager;
+ private final AuthorizationMode mode;
+ private final HostnameVerification hostnameVerification;
+
+ PeerAuthorizerTrustManager(AuthorizedPeers authorizedPeers, AuthorizationMode mode,
+ HostnameVerification hostnameVerification, X509ExtendedTrustManager defaultTrustManager) {
+ this.authorizer = new PeerAuthorizer(authorizedPeers);
+ this.mode = mode;
+ this.hostnameVerification = hostnameVerification;
+ this.defaultTrustManager = defaultTrustManager;
+ }
+
+ PeerAuthorizerTrustManager(AuthorizedPeers authorizedPeers, AuthorizationMode mode,
+ HostnameVerification hostnameVerification, KeyStore truststore) {
+ this(authorizedPeers, mode, hostnameVerification, TrustManagerUtils.createDefaultX509TrustManager(truststore));
+ }
+
+ @Override
+ public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
+ defaultTrustManager.checkClientTrusted(chain, authType);
+ authorizePeer(chain, authType, true, null);
+ }
+
+ @Override
+ public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
+ defaultTrustManager.checkServerTrusted(chain, authType);
+ authorizePeer(chain, authType, false, null);
+ }
+
+ @Override
+ public void checkClientTrusted(X509Certificate[] chain, String authType, Socket socket) throws CertificateException {
+ defaultTrustManager.checkClientTrusted(chain, authType, socket);
+ authorizePeer(chain, authType, true, ((SSLSocket)socket).getHandshakeSession());
+ }
+
+ @Override
+ public void checkServerTrusted(X509Certificate[] chain, String authType, Socket socket) throws CertificateException {
+ overrideHostnameVerificationForClient(socket);
+ defaultTrustManager.checkServerTrusted(chain, authType, socket);
+ authorizePeer(chain, authType, false, ((SSLSocket)socket).getHandshakeSession());
+ }
+
+ @Override
+ public void checkClientTrusted(X509Certificate[] chain, String authType, SSLEngine sslEngine) throws CertificateException {
+ defaultTrustManager.checkClientTrusted(chain, authType, sslEngine);
+ authorizePeer(chain, authType, true, sslEngine.getHandshakeSession());
+ }
+
+ @Override
+ public void checkServerTrusted(X509Certificate[] chain, String authType, SSLEngine sslEngine) throws CertificateException {
+ overrideHostnameVerificationForClient(sslEngine);
+ defaultTrustManager.checkServerTrusted(chain, authType, sslEngine);
+ authorizePeer(chain, authType, false, sslEngine.getHandshakeSession());
+ }
+
+ @Override
+ public X509Certificate[] getAcceptedIssuers() {
+ return defaultTrustManager.getAcceptedIssuers();
+ }
+
+ private void authorizePeer(X509Certificate[] certChain, String authType, boolean isVerifyingClient,
+ SSLSession handshakeSessionOrNull) throws PeerAuthorizationFailedException {
+ log.fine(() -> "Verifying certificate: " + createInfoString(certChain[0], authType, isVerifyingClient));
+ ConnectionAuthContext result = mode != AuthorizationMode.DISABLE
+ ? authorizer.authorizePeer(List.of(certChain))
+ : ConnectionAuthContext.defaultAllCapabilities(List.of(certChain));
+ if (handshakeSessionOrNull != null) {
+ handshakeSessionOrNull.putValue(AUTH_CONTEXT_PROPERTY, result);
+ } else {
+ log.log(Level.FINE,
+ () -> "Warning: unable to provide ConnectionAuthContext as no SSLSession is available");
+ }
+ if (result.authorized()) {
+ log.fine(() -> String.format("Verification result: %s", result));
+ } else {
+ String errorMessage = "Authorization failed: " + createInfoString(certChain[0], authType, isVerifyingClient);
+ log.warning(errorMessage);
+ if (mode == AuthorizationMode.ENFORCE) {
+ throw new PeerAuthorizationFailedException(errorMessage, List.of(certChain));
+ }
+ }
+ }
+
+ private String createInfoString(X509Certificate certificate, String authType, boolean isVerifyingClient) {
+ return String.format("DN='%s', SANs=%s, authType='%s', isVerifyingClient='%b', mode=%s",
+ certificate.getSubjectX500Principal(), X509CertificateUtils.getSubjectAlternativeNames(certificate),
+ authType, isVerifyingClient, mode);
+ }
+
+ private void overrideHostnameVerificationForClient(SSLEngine engine) {
+ SSLParameters params = engine.getSSLParameters();
+ if (overrideHostnameVerificationForClient(params)) {
+ engine.setSSLParameters(params);
+ }
+ }
+
+ private void overrideHostnameVerificationForClient(Socket socket) {
+ if (socket instanceof SSLSocket) {
+ SSLSocket sslSocket = (SSLSocket) socket;
+ SSLParameters params = sslSocket.getSSLParameters();
+ if (overrideHostnameVerificationForClient(params)) {
+ sslSocket.setSSLParameters(params);
+ }
+ }
+ }
+
+ // Overrides the endpoint identification algorithm specified in the ssl parameters of the ssl engine/socket.
+ // The underlying trust manager will perform hostname verification if endpoint identification algorithm is set to 'HTTPS'.
+ // Returns true if the parameter instance was modified
+ private boolean overrideHostnameVerificationForClient(SSLParameters params) {
+ String configuredAlgorithm = params.getEndpointIdentificationAlgorithm();
+ switch (hostnameVerification) {
+ case ENABLED:
+ if (!"HTTPS".equals(configuredAlgorithm)) {
+ params.setEndpointIdentificationAlgorithm("HTTPS");
+ return true;
+ }
+ return false;
+ case DISABLED:
+ if (configuredAlgorithm != null && !configuredAlgorithm.isEmpty()) {
+ params.setEndpointIdentificationAlgorithm(""); // disable any configured endpoint identification algorithm
+ return true;
+ }
+ return false;
+ default:
+ throw new IllegalStateException("Unknown host verification type: " + hostnameVerification);
+ }
+ }
+
+}