Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

advancedtls: adding AdvancedTlsX509TrustManager and AdvancedTlsX509KeyManager #8175

Merged
merged 12 commits into from Aug 17, 2021
14 changes: 11 additions & 3 deletions core/src/main/java/io/grpc/util/AdvancedTlsX509KeyManager.java
Expand Up @@ -16,6 +16,8 @@

package io.grpc.util;

import static com.google.common.base.Preconditions.checkNotNull;

import io.grpc.ExperimentalApi;
import java.io.File;
import java.io.FileInputStream;
Expand Down Expand Up @@ -54,12 +56,18 @@ public AdvancedTlsX509KeyManager() throws CertificateException { }

@Override
public PrivateKey getPrivateKey(String alias) {
ejona86 marked this conversation as resolved.
Show resolved Hide resolved
return this.keyInfo.key;
if (alias.equals("default")) {
return this.keyInfo.key;
}
return null;
}

@Override
public X509Certificate[] getCertificateChain(String alias) {
return Arrays.copyOf(this.keyInfo.certs, this.keyInfo.certs.length);
if (alias.equals("default")) {
return Arrays.copyOf(this.keyInfo.certs, this.keyInfo.certs.length);
}
return null;
}

@Override
Expand Down Expand Up @@ -102,7 +110,7 @@ public String chooseEngineServerAlias(String keyType, Principal[] issuers,
public void updateIdentityCredentials(PrivateKey key, X509Certificate[] certs)
ejona86 marked this conversation as resolved.
Show resolved Hide resolved
throws CertificateException {
// TODO(ZhenLian): explore possibilities to do a crypto check here.
this.keyInfo = new KeyInfo(key, certs);
this.keyInfo = new KeyInfo(checkNotNull(key), checkNotNull(certs));
ejona86 marked this conversation as resolved.
Show resolved Hide resolved
}

/**
Expand Down
123 changes: 53 additions & 70 deletions core/src/main/java/io/grpc/util/AdvancedTlsX509TrustManager.java
Expand Up @@ -33,6 +33,7 @@
import java.util.logging.Logger;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLParameters;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509ExtendedTrustManager;
Expand All @@ -49,23 +50,22 @@ public final class AdvancedTlsX509TrustManager extends X509ExtendedTrustManager
private static final Logger log = Logger.getLogger(AdvancedTlsX509TrustManager.class.getName());

private final Verification verification;
private final PeerVerifier peerVerifier;
private final SslSocketAndEnginePeerVerifier socketAndEnginePeerVerifier;

// The delegated trust manager used to perform traditional certificate verification.
private volatile X509ExtendedTrustManager delegateManager = null;

private AdvancedTlsX509TrustManager(Verification verification, PeerVerifier peerVerifier,
private AdvancedTlsX509TrustManager(Verification verification,
SslSocketAndEnginePeerVerifier socketAndEnginePeerVerifier) throws CertificateException {
this.verification = verification;
this.peerVerifier = peerVerifier;
this.socketAndEnginePeerVerifier = socketAndEnginePeerVerifier;
}

@Override
public void checkClientTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
checkTrusted(chain, authType, null, null, false);
throw new CertificateException(
"Either SSLEngine or Socket should be available. Consider upgrading your java version");
ejona86 marked this conversation as resolved.
Show resolved Hide resolved
}

@Override
Expand All @@ -89,7 +89,8 @@ public void checkServerTrusted(X509Certificate[] chain, String authType, SSLEng
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
checkTrusted(chain, authType, null, null, true);
throw new CertificateException(
"Either SSLEngine or Socket should be available. Consider upgrading your java version");
}

@Override
Expand All @@ -101,7 +102,6 @@ public void checkServerTrusted(X509Certificate[] chain, String authType, Socket
@Override
public X509Certificate[] getAcceptedIssuers() {
if (this.delegateManager == null) {
log.log(Level.SEVERE, "Credential hasn't been provided yet");
return new X509Certificate[0];
}
return this.delegateManager.getAcceptedIssuers();
Expand All @@ -116,8 +116,7 @@ public void useSystemDefaultTrustCerts() throws CertificateException, KeyStoreEx
NoSuchAlgorithmException {
// Passing a null value of KeyStore would make {@code TrustManagerFactory} attempt to use
// system-default trust CA certs.
X509ExtendedTrustManager newDelegateManager = createDelegateTrustManager(null);
this.delegateManager = newDelegateManager;
this.delegateManager = createDelegateTrustManager(null);
}

/**
Expand Down Expand Up @@ -156,46 +155,55 @@ private static X509ExtendedTrustManager createDelegateTrustManager(KeyStore keyS
}
if (delegateManager == null) {
throw new CertificateException(
"Instance delegateX509TrustManager is null. Failed to initialize");
"Failed to find X509ExtendedTrustManager with default TrustManager algorithm "
+ TrustManagerFactory.getDefaultAlgorithm());
}
return delegateManager;
}

private void checkTrusted(X509Certificate[] chain, String authType, SSLEngine sslEngine,
Socket socket, boolean checkingServer) throws CertificateException {
if (chain == null || chain.length == 0) {
throw new CertificateException(
ejona86 marked this conversation as resolved.
Show resolved Hide resolved
"Want certificate verification but got null or empty certificates");
}
if (sslEngine == null && socket == null) {
throw new CertificateException("Either SSLEngine or socket should be available");
}
if (this.verification != Verification.InsecurelySkipAllVerification) {
if (chain == null || chain.length == 0) {
throw new CertificateException(
"Want certificate verification but got null or empty certificates");
}
if (this.delegateManager == null) {
throw new CertificateException("Credential hasn't been provided yet");
X509ExtendedTrustManager currentDelegateManager = this.delegateManager;
if (currentDelegateManager == null) {
throw new CertificateException("No trust roots configured");
}
if (checkingServer) {
if (this.verification == Verification.CertificateAndHostNameVerification
&& sslEngine == null) {
throw new CertificateException(
"SSLEngine is null. Couldn't check host name");
}
String algorithm = this.verification == Verification.CertificateAndHostNameVerification
? "HTTPS" : "";
SSLParameters sslParams = sslEngine.getSSLParameters();
sslParams.setEndpointIdentificationAlgorithm(algorithm);
sslEngine.setSSLParameters(sslParams);
delegateManager.checkServerTrusted(chain, authType, sslEngine);
if (sslEngine != null) {
SSLParameters sslParams = sslEngine.getSSLParameters();
sslParams.setEndpointIdentificationAlgorithm(algorithm);
sslEngine.setSSLParameters(sslParams);
currentDelegateManager.checkServerTrusted(chain, authType, sslEngine);
} else {
if (!(socket instanceof SSLSocket)) {
throw new CertificateException("socket is not a type of SSLSocket");
}
SSLSocket sslSocket = (SSLSocket)socket;
SSLParameters sslParams = sslSocket.getSSLParameters();
sslParams.setEndpointIdentificationAlgorithm(algorithm);
sslSocket.setSSLParameters(sslParams);
currentDelegateManager.checkServerTrusted(chain, authType, sslSocket);
}
} else {
delegateManager.checkClientTrusted(chain, authType, sslEngine);
currentDelegateManager.checkClientTrusted(chain, authType, sslEngine);
}
}
// Perform the additional peer cert check.
if (sslEngine != null && socketAndEnginePeerVerifier != null) {
socketAndEnginePeerVerifier.verifyPeerCertificate(chain, authType, sslEngine);
} else if (socket != null && socketAndEnginePeerVerifier != null) {
socketAndEnginePeerVerifier.verifyPeerCertificate(chain, authType, socket);
} else if (peerVerifier != null) {
peerVerifier.verifyPeerCertificate(chain, authType);
} else {
log.log(Level.INFO, "No peer verifier is specified");
if (socketAndEnginePeerVerifier != null) {
if (sslEngine != null) {
socketAndEnginePeerVerifier.verifyPeerCertificate(chain, authType, sslEngine);
} else {
socketAndEnginePeerVerifier.verifyPeerCertificate(chain, authType, socket);
}
}
}

Expand Down Expand Up @@ -233,10 +241,7 @@ public LoadFilePathExecution(File file) {
@Override
public void run() {
try {
long newUpdateTime = readAndUpdate(this.file, this.currentTime);
if (this.currentTime != newUpdateTime) {
this.currentTime = newUpdateTime;
}
this.currentTime = readAndUpdate(this.file, this.currentTime);
} catch (CertificateException | IOException | KeyStoreException
| NoSuchAlgorithmException e) {
log.log(Level.SEVERE, "Failed refreshing trust CAs from file. Using previous CAs", e);
Expand All @@ -255,17 +260,17 @@ public void run() {
private long readAndUpdate(File trustCertFile, long oldTime)
throws CertificateException, IOException, KeyStoreException, NoSuchAlgorithmException {
long newTime = trustCertFile.lastModified();
if (newTime != oldTime) {
FileInputStream inputStream = new FileInputStream(trustCertFile);
try {
X509Certificate[] certificates = CertificateUtils.getX509Certificates(inputStream);
updateTrustCredentials(certificates);
return newTime;
} finally {
inputStream.close();
}
if (newTime == oldTime) {
return oldTime;
}
FileInputStream inputStream = new FileInputStream(trustCertFile);
try {
X509Certificate[] certificates = CertificateUtils.getX509Certificates(inputStream);
updateTrustCredentials(certificates);
return newTime;
} finally {
inputStream.close();
}
return oldTime;
}

// Mainly used to avoid throwing IO Exceptions in java.io.Closeable.
Expand Down Expand Up @@ -301,21 +306,6 @@ public enum Verification {
InsecurelySkipAllVerification,
}

// Additional custom peer verification check.
// It will be used when checkClientTrusted/checkServerTrusted is called without
// {@code Socket}/{@code SSLEngine} parameter.
public interface PeerVerifier {
/**
* Verifies the peer certificate chain. For more information, please refer to
* {@code X509ExtendedTrustManager}.
*
* @param peerCertChain the certificate chain sent from the peer
* @param authType the key exchange algorithm used, e.g. "RSA", "DHE_DSS", etc
*/
void verifyPeerCertificate(X509Certificate[] peerCertChain, String authType)
throws CertificateException;
}

// Additional custom peer verification check.
// It will be used when checkClientTrusted/checkServerTrusted is called with the {@code Socket} or
// the {@code SSLEngine} parameter.
Expand Down Expand Up @@ -348,7 +338,6 @@ void verifyPeerCertificate(X509Certificate[] peerCertChain, String authType, SSL
public static final class Builder {
ZhenLian marked this conversation as resolved.
Show resolved Hide resolved

private Verification verification = Verification.CertificateAndHostNameVerification;
private PeerVerifier peerVerifier;
private SslSocketAndEnginePeerVerifier socketAndEnginePeerVerifier;

private Builder() {}
Expand All @@ -358,19 +347,13 @@ public Builder setVerification(Verification verification) {
return this;
}

public Builder setPeerVerifier(PeerVerifier verifier) {
this.peerVerifier = verifier;
return this;
}

public Builder setSslSocketAndEnginePeerVerifier(SslSocketAndEnginePeerVerifier verifier) {
this.socketAndEnginePeerVerifier = verifier;
return this;
}

public AdvancedTlsX509TrustManager build() throws CertificateException {
return new AdvancedTlsX509TrustManager(this.verification, this.peerVerifier,
this.socketAndEnginePeerVerifier);
return new AdvancedTlsX509TrustManager(this.verification, this.socketAndEnginePeerVerifier);
}
}
}
Expand Down