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

okhttp: properly verify IPv6 address hosts #4292

Merged
merged 5 commits into from
Apr 3, 2018
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion okhttp/src/main/java/io/grpc/okhttp/OkHttpTlsUpgrader.java
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,24 @@ public static SSLSocket upgrade(SSLSocketFactory sslSocketFactory,
if (hostnameVerifier == null) {
hostnameVerifier = OkHostnameVerifier.INSTANCE;
}
if (!hostnameVerifier.verify(host, sslSocket.getSession())) {
if (!hostnameVerifier.verify(canonicalizeHost(host), sslSocket.getSession())) {
throw new SSLPeerUnverifiedException("Cannot verify hostname: " + host);
}
return sslSocket;
}

/**
* Converts a host from URI to X509 format.
*
* <p>IPv6 host addresses derived from URIs are enclosed in square brackets per RFC2732, but
* omit these brackets in X509 certificate subjectAltName extensions per RFC5280.
*
* @see <a href="https://www.ietf.org/rfc/rfc2732.txt">RFC2732</a>
* @see <a href="https://tools.ietf.org/html/rfc5280#section-4.2.1.6">RFC5280</a>
*
* @return {@param host} in a form consistent with X509 certificates
*/
static String canonicalizeHost(String host) {
Copy link
Contributor

Choose a reason for hiding this comment

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

This can be private

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Is package-private with @VisibleForTesting (ala TLS_PROTOCOLS) acceptable?

Copy link
Contributor

Choose a reason for hiding this comment

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

Oops, my mistake - yes, the visibility is fine, but the @VisibleForTesting annotation would be helpful.

return host.replaceAll("[\\[\\]]", "");
Copy link
Contributor

Choose a reason for hiding this comment

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

This may be safe as-is, but we should be more conservative here: if the first and last characters are [ and ], strip only those two characters out. Otherwise, just return the string.

This would mean that we can add a test like assertEquals("[someIp]", canonicalizeHost("[[someIp]]"));. This address would, as in #4278, result in OkHostnameVerifier failing to match the host - but that's good, because having two brackets is malformed anyways, and probably indicates a bug somewhere else that we don't want to mask here.

}
}
42 changes: 42 additions & 0 deletions okhttp/src/test/java/io/grpc/okhttp/OkHttpTlsUpgraderTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

package io.grpc.okhttp;

import static io.grpc.okhttp.OkHttpTlsUpgrader.canonicalizeHost;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

import io.grpc.okhttp.internal.Protocol;
Expand All @@ -32,4 +34,44 @@ public class OkHttpTlsUpgraderTest {
|| OkHttpTlsUpgrader.TLS_PROTOCOLS.indexOf(Protocol.GRPC_EXP)
< OkHttpTlsUpgrader.TLS_PROTOCOLS.indexOf(Protocol.HTTP_2));
}

@Test public void upgrade_canonicalizeHost() {
Copy link
Contributor

Choose a reason for hiding this comment

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

Tests coverage look good to me (with the addition of a case to ensure we don't strip out additional brackets for malformed URIs). Out of curiosity, what methodology went into choosing the test hosts?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Will update to ensure we don't strip out additional brackets.

I lifted these from okhttp's HostnameVerifierTest, so I'll add some attribution.

Copy link
Contributor

Choose a reason for hiding this comment

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

Let's simplify the tests somewhat here, which will also remove the need for attribution - we actually don't care about all of the edge cases that the linked test case covers, because we don't need to distinguish between IP addresses and hostnames. Probably just the following are enough:

assertEquals("::1", canonicalizeHost("::1"));
assertEquals("::1", canonicalizeHost("[::1]"));
assertEquals("127.0.0.1", canonicalizeHost("127.0.0.1"));
assertEquals("some.long.url.com", canonicalizeHost("some.long.url.com"));

// Extra square brackets in a malformed URI are retained
assertEquals("[::1]", canonicalizeHost("[[::1]]"));

Also, does it make sense to rename the test method to just canonicalizeHost, as this isn't testing any other part of the upgrade logic?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

👍 Is canonicalizeHosts ok to avoid clash with the static import of canonicalizeHost?

Copy link
Contributor

Choose a reason for hiding this comment

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

sounds good to me

// IPv4
assertEquals(canonicalizeHost("127.0.0.1"), "127.0.0.1");
Copy link
Contributor

Choose a reason for hiding this comment

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

The argument order for assertEquals is reversed. These should be in the form: assertEquals("ip", canonicalizeHost("ip")). (this results in better output when a test fails, as error message will label the first argument as the expected value and the second argument as the actual value)

assertEquals(canonicalizeHost("1.2.3.4"), "1.2.3.4");

// IPv6
assertEquals(canonicalizeHost("::1"), "::1");
assertEquals(canonicalizeHost("[::1]"), "::1");
assertEquals(canonicalizeHost("2001:db8::1"), "2001:db8::1");
assertEquals(canonicalizeHost("[2001:db8::1]"), "2001:db8::1");
assertEquals(canonicalizeHost("::192.168.0.1"), "::192.168.0.1");
assertEquals(canonicalizeHost("[::192.168.0.1]"), "::192.168.0.1");
assertEquals(canonicalizeHost("::ffff:192.168.0.1"), "::ffff:192.168.0.1");
assertEquals(canonicalizeHost("[::ffff:192.168.0.1]"), "::ffff:192.168.0.1");
assertEquals(canonicalizeHost("FEDC:BA98:7654:3210:FEDC:BA98:7654:3210"),
"FEDC:BA98:7654:3210:FEDC:BA98:7654:3210");
assertEquals(canonicalizeHost("[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]"),
"FEDC:BA98:7654:3210:FEDC:BA98:7654:3210");
assertEquals(canonicalizeHost("1080:0:0:0:8:800:200C:417A"), "1080:0:0:0:8:800:200C:417A");
assertEquals(canonicalizeHost("[1080:0:0:0:8:800:200C:417A]"), "1080:0:0:0:8:800:200C:417A");
assertEquals(canonicalizeHost("1080::8:800:200C:417A"), "1080::8:800:200C:417A");
assertEquals(canonicalizeHost("[1080::8:800:200C:417A]"), "1080::8:800:200C:417A");
assertEquals(canonicalizeHost("FF01::101"), "FF01::101");
assertEquals(canonicalizeHost("[FF01::101]"), "FF01::101");
assertEquals(canonicalizeHost("0:0:0:0:0:0:13.1.68.3"), "0:0:0:0:0:0:13.1.68.3");
assertEquals(canonicalizeHost("[0:0:0:0:0:0:13.1.68.3]"), "0:0:0:0:0:0:13.1.68.3");
assertEquals(canonicalizeHost("0:0:0:0:0:FFFF:129.144.52.38"), "0:0:0:0:0:FFFF:129.144.52.38");
assertEquals(canonicalizeHost("[0:0:0:0:0:FFFF:129.144.52.38]"), "0:0:0:0:0:FFFF:129.144.52.38");
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this fails checkstyle (100 char limit per line)

assertEquals(canonicalizeHost("::13.1.68.3"), "::13.1.68.3");
assertEquals(canonicalizeHost("[::13.1.68.3]"), "::13.1.68.3");
assertEquals(canonicalizeHost("::FFFF:129.144.52.38"), "::FFFF:129.144.52.38");
assertEquals(canonicalizeHost("[::FFFF:129.144.52.38]"), "::FFFF:129.144.52.38");

// Hostnames
assertEquals(canonicalizeHost("go"), "go");
assertEquals(canonicalizeHost("localhost"), "localhost");
assertEquals(canonicalizeHost("squareup.com"), "squareup.com");
assertEquals(canonicalizeHost("www.nintendo.co.jp"), "www.nintendo.co.jp");
}
}