Skip to content

Commit

Permalink
In-progress changes to move authority verification to the ChannelHand…
Browse files Browse the repository at this point in the history
…ler instead of the Protocol negotiator.
  • Loading branch information
kannanjgithub committed Jan 24, 2025
1 parent b8b70bf commit 15c8161
Show file tree
Hide file tree
Showing 8 changed files with 105 additions and 70 deletions.
7 changes: 7 additions & 0 deletions core/src/main/java/io/grpc/internal/AuthorityVerifier.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package io.grpc.internal;

import io.grpc.Status;

public interface AuthorityVerifier {
Status verifyAuthority(String authority);
}
2 changes: 2 additions & 0 deletions core/src/main/java/io/grpc/internal/GrpcAttributes.java
Original file line number Diff line number Diff line change
Expand Up @@ -42,5 +42,7 @@ public final class GrpcAttributes {
public static final Attributes.Key<Attributes> ATTR_CLIENT_EAG_ATTRS =
Attributes.Key.create("io.grpc.internal.GrpcAttributes.clientEagAttrs");

public static final Attributes.Key<AuthorityVerifier> ATTR_AUTHORITY_VERIFIER =
Attributes.Key.create("io.grpc.internal.GrpcAttributes.authorityVerifier");
private GrpcAttributes() {}
}
2 changes: 1 addition & 1 deletion examples/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ dependencies {
protobuf {
protoc { artifact = "com.google.protobuf:protoc:${protocVersion}" }
plugins {
grpc { artifact = "io.grpc:protoc-gen-grpc-java:${grpcVersion}" }
grpc { artifact = "io.grpc:protoc-gen-grpc-java:1.70.0" }
}
generateProtoTasks {
all()*.plugins { grpc {} }
Expand Down
2 changes: 1 addition & 1 deletion examples/example-tls/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ dependencies {
protobuf {
protoc { artifact = "com.google.protobuf:protoc:${protocVersion}" }
plugins {
grpc { artifact = "io.grpc:protoc-gen-grpc-java:${grpcVersion}" }
grpc { artifact = "io.grpc:protoc-gen-grpc-java:1.70.0" }
}
generateProtoTasks {
all()*.plugins { grpc {} }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ public static ChannelHandler clientTlsHandler(
ChannelHandler next, SslContext sslContext, String authority,
ChannelLogger negotiationLogger) {
return new ClientTlsHandler(next, sslContext, authority, null, negotiationLogger,
Optional.absent(), null);
Optional.absent(), null, null);
}

public static class ProtocolNegotiationHandler
Expand Down
75 changes: 12 additions & 63 deletions netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java
Original file line number Diff line number Diff line change
Expand Up @@ -585,25 +585,6 @@ protected void userEventTriggered0(ChannelHandlerContext ctx, Object evt) throws
}

static final class ClientTlsProtocolNegotiator implements ProtocolNegotiator {
private static final Method checkServerTrustedMethod;

static {
Method method = null;
try {
Class<?> x509ExtendedTrustManagerClass =
Class.forName("javax.net.ssl.X509ExtendedTrustManager");
method = x509ExtendedTrustManagerClass.getMethod("checkServerTrusted",
X509Certificate[].class, String.class, SSLEngine.class);
} catch (ClassNotFoundException e) {
// Per-rpc authority overriding via call options will be disallowed.
} catch (NoSuchMethodException e) {
// Should never happen since X509ExtendedTrustManager was introduced in Android API level 24
// along with checkServerTrusted.
}
checkServerTrustedMethod = method;
}

private SSLEngine sslEngine;

public ClientTlsProtocolNegotiator(SslContext sslContext,
ObjectPool<? extends Executor> executorPool, Optional<Runnable> handshakeCompleteRunnable,
Expand Down Expand Up @@ -633,7 +614,8 @@ public ChannelHandler newHandler(GrpcHttp2ConnectionHandler grpcHandler) {
ChannelHandler gnh = new GrpcNegotiationHandler(grpcHandler);
ChannelLogger negotiationLogger = grpcHandler.getNegotiationLogger();
ChannelHandler cth = new ClientTlsHandler(gnh, sslContext, grpcHandler.getAuthority(),
this.executor, negotiationLogger, handshakeCompleteRunnable, this);
this.executor, negotiationLogger, handshakeCompleteRunnable, this,
x509ExtendedTrustManager);
return new WaitUntilActiveHandler(cth, negotiationLogger);
}

Expand All @@ -644,47 +626,6 @@ public void close() {
}
}

@Override
public Status verifyAuthority(@Nonnull String authority) {
// sslEngine won't be set when creating ClientTlsHandler from InternalProtocolNegotiators
// for example.
if (sslEngine == null || x509ExtendedTrustManager == null) {
return Status.FAILED_PRECONDITION.withDescription(
"Can't allow authority override in rpc when SslEngine or X509ExtendedTrustManager"
+ " is not available");
}
Status peerVerificationStatus;
try {
verifyAuthorityAllowedForPeerCert(authority);
peerVerificationStatus = Status.OK;
} catch (SSLPeerUnverifiedException | CertificateException | InvocationTargetException
| IllegalAccessException | IllegalStateException e) {
peerVerificationStatus = Status.UNAVAILABLE.withDescription(
String.format("Peer hostname verification during rpc failed for authority '%s'",
authority)).withCause(e);
}
return peerVerificationStatus;
}

public void setSslEngine(SSLEngine sslEngine) {
this.sslEngine = sslEngine;
}

private void verifyAuthorityAllowedForPeerCert(String authority)
throws SSLPeerUnverifiedException, CertificateException, InvocationTargetException,
IllegalAccessException {
SSLEngine sslEngineWrapper = new SslEngineWrapper(sslEngine, authority);
// The typecasting of Certificate to X509Certificate should work because this method will only
// be called when using TLS and thus X509.
Certificate[] peerCertificates = sslEngine.getSession().getPeerCertificates();
X509Certificate[] x509PeerCertificates = new X509Certificate[peerCertificates.length];
for (int i = 0; i < peerCertificates.length; i++) {
x509PeerCertificates[i] = (X509Certificate) peerCertificates[i];
}
checkServerTrustedMethod.invoke(
x509ExtendedTrustManager, x509PeerCertificates, "RSA", sslEngineWrapper);
}

@VisibleForTesting
boolean hasX509ExtendedTrustManager() {
return x509ExtendedTrustManager != null;
Expand All @@ -699,11 +640,13 @@ static final class ClientTlsHandler extends ProtocolNegotiationHandler {
private final ClientTlsProtocolNegotiator clientTlsProtocolNegotiator;
private Executor executor;
private final Optional<Runnable> handshakeCompleteRunnable;
private final X509TrustManager x509ExtendedTrustManager;

ClientTlsHandler(ChannelHandler next, SslContext sslContext, String authority,
Executor executor, ChannelLogger negotiationLogger,
Optional<Runnable> handshakeCompleteRunnable,
ClientTlsProtocolNegotiator clientTlsProtocolNegotiator) {
ClientTlsProtocolNegotiator clientTlsProtocolNegotiator,
X509TrustManager x509ExtendedTrustManager) {
super(next, negotiationLogger);
this.sslContext = checkNotNull(sslContext, "sslContext");
HostPort hostPort = parseAuthority(authority);
Expand All @@ -712,6 +655,7 @@ static final class ClientTlsHandler extends ProtocolNegotiationHandler {
this.executor = executor;
this.handshakeCompleteRunnable = handshakeCompleteRunnable;
this.clientTlsProtocolNegotiator = clientTlsProtocolNegotiator;
this.x509ExtendedTrustManager = x509ExtendedTrustManager;
}

@Override
Expand All @@ -724,7 +668,12 @@ protected void handlerAdded0(ChannelHandlerContext ctx) {
ctx.pipeline().addBefore(ctx.name(), /* name= */ null, this.executor != null
? new SslHandler(sslEngine, false, this.executor)
: new SslHandler(sslEngine, false));
clientTlsProtocolNegotiator.setSslEngine(sslEngine);
ProtocolNegotiationEvent existingPne = getProtocolNegotiationEvent();
Attributes attrs = existingPne.getAttributes().toBuilder()
.set(GrpcAttributes.ATTR_AUTHORITY_VERIFIER, new X509AuthorityVerifier(
sslEngine, x509ExtendedTrustManager))
.build();
replaceProtocolNegotiationEvent(existingPne.withAttributes(attrs));
}

@Override
Expand Down
77 changes: 77 additions & 0 deletions netty/src/main/java/io/grpc/netty/X509AuthorityVerifier.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package io.grpc.netty;

import io.grpc.Status;
import io.grpc.internal.AuthorityVerifier;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import javax.annotation.Nonnull;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLPeerUnverifiedException;
import javax.net.ssl.X509TrustManager;

public class X509AuthorityVerifier implements AuthorityVerifier {
private final SSLEngine sslEngine;
private final X509TrustManager x509ExtendedTrustManager;

private static final Method checkServerTrustedMethod;

static {
Method method = null;
try {
Class<?> x509ExtendedTrustManagerClass =
Class.forName("javax.net.ssl.X509ExtendedTrustManager");
method = x509ExtendedTrustManagerClass.getMethod("checkServerTrusted",
X509Certificate[].class, String.class, SSLEngine.class);
} catch (ClassNotFoundException e) {
// Per-rpc authority overriding via call options will be disallowed.
} catch (NoSuchMethodException e) {
// Should never happen since X509ExtendedTrustManager was introduced in Android API level 24
// along with checkServerTrusted.
}
checkServerTrustedMethod = method;
}

public X509AuthorityVerifier(SSLEngine sslEngine, X509TrustManager x509ExtendedTrustManager) {
this.sslEngine = sslEngine;
this.x509ExtendedTrustManager = x509ExtendedTrustManager;
}

public Status verifyAuthority(@Nonnull String authority) {
// sslEngine won't be set when creating ClientTlsHandler from InternalProtocolNegotiators
// for example.
if (sslEngine == null || x509ExtendedTrustManager == null) {
return Status.FAILED_PRECONDITION.withDescription(
"Can't allow authority override in rpc when SslEngine or X509ExtendedTrustManager"
+ " is not available");
}
Status peerVerificationStatus;
try {
verifyAuthorityAllowedForPeerCert(authority);
peerVerificationStatus = Status.OK;
} catch (SSLPeerUnverifiedException | CertificateException | InvocationTargetException
| IllegalAccessException | IllegalStateException e) {
peerVerificationStatus = Status.UNAVAILABLE.withDescription(
String.format("Peer hostname verification during rpc failed for authority '%s'",
authority)).withCause(e);
}
return peerVerificationStatus;
}

private void verifyAuthorityAllowedForPeerCert(String authority)
throws SSLPeerUnverifiedException, CertificateException, InvocationTargetException,
IllegalAccessException {
SSLEngine sslEngineWrapper = new ProtocolNegotiators.SslEngineWrapper(sslEngine, authority);
// The typecasting of Certificate to X509Certificate should work because this method will only
// be called when using TLS and thus X509.
Certificate[] peerCertificates = sslEngine.getSession().getPeerCertificates();
X509Certificate[] x509PeerCertificates = new X509Certificate[peerCertificates.length];
for (int i = 0; i < peerCertificates.length; i++) {
x509PeerCertificates[i] = (X509Certificate) peerCertificates[i];
}
checkServerTrustedMethod.invoke(
x509ExtendedTrustManager, x509PeerCertificates, "RSA", sslEngineWrapper);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -921,7 +921,7 @@ public String applicationProtocol() {

ClientTlsHandler handler = new ClientTlsHandler(grpcHandler, sslContext,
"authority", elg, noopLogger, Optional.absent(),
getClientTlsProtocolNegotiator());
getClientTlsProtocolNegotiator(), null);
pipeline.addLast(handler);
pipeline.replace(SslHandler.class, null, goodSslHandler);
pipeline.fireUserEventTriggered(ProtocolNegotiationEvent.DEFAULT);
Expand Down Expand Up @@ -960,7 +960,7 @@ public String applicationProtocol() {

ClientTlsHandler handler = new ClientTlsHandler(grpcHandler, sslContext,
"authority", elg, noopLogger, Optional.absent(),
getClientTlsProtocolNegotiator());
getClientTlsProtocolNegotiator(), null);
pipeline.addLast(handler);
pipeline.replace(SslHandler.class, null, goodSslHandler);
pipeline.fireUserEventTriggered(ProtocolNegotiationEvent.DEFAULT);
Expand All @@ -985,7 +985,7 @@ public String applicationProtocol() {

ClientTlsHandler handler = new ClientTlsHandler(grpcHandler, sslContext,
"authority", elg, noopLogger, Optional.absent(),
getClientTlsProtocolNegotiator());
getClientTlsProtocolNegotiator(), null);
pipeline.addLast(handler);

final AtomicReference<Throwable> error = new AtomicReference<>();
Expand Down Expand Up @@ -1014,7 +1014,7 @@ public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
public void clientTlsHandler_closeDuringNegotiation() throws Exception {
ClientTlsHandler handler = new ClientTlsHandler(grpcHandler, sslContext,
"authority", null, noopLogger, Optional.absent(),
getClientTlsProtocolNegotiator());
getClientTlsProtocolNegotiator(), null);
pipeline.addLast(new WriteBufferingAndExceptionHandler(handler));
ChannelFuture pendingWrite = channel.writeAndFlush(NettyClientHandler.NOOP_MESSAGE);

Expand Down

0 comments on commit 15c8161

Please sign in to comment.