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

ok-http: Per-rpc call option authority verification #11754

Open
wants to merge 38 commits into
base: master
Choose a base branch
from

Conversation

kannanjgithub
Copy link
Contributor

No description provided.

Copy link
Member

@ejona86 ejona86 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still need to look at the core of it, but the shape looks right. These comments are mostly surface-level.

@kannanjgithub
Copy link
Contributor Author

Yet to make the changes to use X509ExtendedTrustManager only via reflection if it is available in the class loader.

@kannanjgithub
Copy link
Contributor Author

kannanjgithub commented Jan 13, 2025

Yet to make the changes to use X509ExtendedTrustManager only via reflection if it is available in the class loader.

Done.

@kannanjgithub
Copy link
Contributor Author

Moved the authority verification from OkHttpClientTransport.newStream down to the point just before sending the headers over the network so any overwrites to the authority gets seen.

@ejona86 ejona86 self-requested a review February 26, 2025 00:39
Copy link
Member

@ejona86 ejona86 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still need to look at it more, but sending what I have.

# Conflicts:
#	core/src/main/java/io/grpc/internal/CertificateUtils.java
#	okhttp/src/main/java/io/grpc/okhttp/OkHttpChannelBuilder.java
@kannanjgithub kannanjgithub requested a review from ejona86 March 11, 2025 04:07
if (peerVerificationResults.containsKey(authority)) {
peerVerificationStatus = peerVerificationResults.get(authority);
} else {
if (x509ExtendedTrustManager == null) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If x509ExtendedTrustManagerClass == null, we want to let this authority through as we wouldn't use it during the connection handshake either.

... And digging further to see what conditions we use, it looks like... we never use the trust manager for hostname verification. Looking at the SSLParameters, I don't see us ever calling setEndpointIdentificationAlgorithm(). And I commented out the hostnameVerifier.verify() call in OkHttpTlsUpgrader and TlsTest failed because hostname verification was not being performed. I added io.grpc.internal.testing.TestUtils.installConscryptIfAvailable(); to the @Before method, and the error stderr logs changed but still the same lack of verification. And that shouldn't be a surprise since those APIs were added in API level 24 which came out in August 2016, four days after grpc-java 1.0.0. It would be years before enough devices supported it to be interesting. I thought we had checked some of this before, but it was too long ago for me to figure out where we went wrong.

(Note that there still may be cases where it is used, but that'd have to be with a custom SSLSocketFactory.)

Copy link
Contributor Author

@kannanjgithub kannanjgithub Mar 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

<<< If x509ExtendedTrustManagerClass == null, we want to let this authority through as we wouldn't use it during the connection handshake either.

Now I'm only doing the authority check during RPC if (!authority.equals(defaultAuthority)). If x509ExtendedTrustManager is null, can we check the value of sslParameters endpoint identification algorithm, and only if it is set to HTTPS then fail the RPC because the checkServerTrusted won't check the hostname anyway if the endpoint identification algorithm is not HTTPS.

Also should we then do the checkServerTrusted check here only if sslParameters had endpoint identification algorithm set to 'https' in the protocol negotiator?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#11754 (comment) has a discussion about still running the x509ExtendedTrustManager even though the endpoint algorithm isn't set. I'm thinking the idea was that while the default verifier won't be checking the hostname a user-provided TrustManager could be.

If x509ExtendedTrustManagerClass == null (that's the class), then the trust manager can't verify the host name, because the API doesn't exist. x509ExtendedTrustManagerClass == null && x509ExtendedTrustManager == null is common enough, as it just means you're on an old Android version. The endpoint identification algorithm has nothing to do with it as that API doesn't exist either on those old versions. x509ExtendedTrustManagerClass != null && x509ExtendedTrustManager == null is a case I thought I didn't care too much about, because the GeneralSecurityException when creating the trust manager is highly unlikely. So I was fine with it failing. But I see now it is also the case where there is a trust manager, it just isn't an X509ExtendedTrustManager.

I don't think we should search for a an extended manager in getX509ExtendedTrustManager(). That's not how it is done during handshaking. Instead, look for an X509TrustManager. Then have the isInstance() check when we are doing the actual verification for an individual RPC. That means we can also run the code from getX509ExtendedTrustManager() even if x509ExtendedTrustManagerClass == null. Then x509ExtendedTrustManager == null will only be true when we encountered the GeneralSecurityException

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

ManagedChannel channel = grpcCleanupRule.register(
OkHttpChannelBuilder.forAddress("localhost", server.getPort(), channelCreds)
.overrideAuthority(TestUtils.TEST_SERVER_HOST)
.hostnameVerifier((hostname, session) -> true)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm unable to set hostname verifier after setting channel creds. I need to override hostname verifier behavior to simply return true, and TLS credentials so that authority verification happens. I'm facing this exception

java.lang.IllegalStateException: Cannot change security when using ChannelCredentials
	at com.google.common.base.Preconditions.checkState(Preconditions.java:513)
	at io.grpc.okhttp.OkHttpChannelBuilder.hostnameVerifier(OkHttpChannelBuilder.java:395)
	at io.grpc.okhttp.TlsTest.perRpcAuthorityOverride_peerVerificationFails_rpcFails(TlsTest.java:302)

How can I do what I want to do?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To change the hostname verifier, you can't pass channel creds to the channel builder.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

if (goAwayStatus != null) {
clientStream.transportState().transportReportStatus(
goAwayStatus, RpcProgress.MISCARRIED, true, new Metadata());
} else if (streams.size() >= maxConcurrentStreams) {
pendingStreams.add(clientStream);
setInUse(clientStream);
} else {
if (!authority.equals(defaultAuthority)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a lot of code is being added to a block that used to be one line in an eight line method. Move most of it to a helper method. You can have the caching logic here. We should only have a single cache that has the result from the combined hostnameVerifier and trustManager checks.

tmf.init((KeyStore) null);
tm = tmf.getTrustManagers();
}
return tm[0];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You still need the loop to find the first _X509_TrustManager.

if (goAwayStatus != null) {
clientStream.transportState().transportReportStatus(
goAwayStatus, RpcProgress.MISCARRIED, true, new Metadata());
} else if (streams.size() >= maxConcurrentStreams) {
pendingStreams.add(clientStream);
setInUse(clientStream);
} else {
if (!authority.equals(defaultAuthority)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You deleted the socket instanceof SSLSocket check. Add that to this authority check? if (socket instanceof SSLSocket && !authority.equals(defaultAuthority))

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants