Skip to content

Commit

Permalink
Changed CertificatePinner to match against canonicalized hostnames.
Browse files Browse the repository at this point in the history
We leverage HttpUrl to canonicalize the hostname. This has a few
advantages:
 - When matching pins against a hostname, case is ignored.
 - Punycode works for free.

Closes: square#2609
  • Loading branch information
dave-r12 committed Jun 15, 2016
1 parent a464a63 commit 3e24b4b
Show file tree
Hide file tree
Showing 2 changed files with 34 additions and 4 deletions.
22 changes: 22 additions & 0 deletions okhttp-tests/src/test/java/okhttp3/CertificatePinnerTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -253,4 +253,26 @@ public final class CertificatePinnerTest {
assertEquals(Collections.emptyList(), certificatePinner.findMatchingPins("example.com"));
assertEquals(Collections.emptyList(), certificatePinner.findMatchingPins("a.b.example.com"));
}

@Test public void successfulFindMatchingPinsIgnoresCase() {
CertificatePinner certificatePinner = new CertificatePinner.Builder()
.add("EXAMPLE.com", certA1Sha256Pin)
.add("*.MyExample.Com", certB1Sha256Pin)
.build();

List<Pin> expectedPin1 = Arrays.asList(new Pin("EXAMPLE.com", certA1Sha256Pin));
assertEquals(expectedPin1, certificatePinner.findMatchingPins("example.com"));

List<Pin> expectedPin2 = Arrays.asList(new Pin("*.MyExample.Com", certB1Sha256Pin));
assertEquals(expectedPin2, certificatePinner.findMatchingPins("a.myexample.com"));
}

@Test public void successfulFindMatchingPinPunycode() {
CertificatePinner certificatePinner = new CertificatePinner.Builder()
.add("σkhttp.com", certA1Sha256Pin)
.build();

List<Pin> expectedPin = Arrays.asList(new Pin("σkhttp.com", certA1Sha256Pin));
assertEquals(expectedPin, certificatePinner.findMatchingPins("xn--khttp-fde.com"));
}
}
16 changes: 12 additions & 4 deletions okhttp/src/main/java/okhttp3/CertificatePinner.java
Original file line number Diff line number Diff line change
Expand Up @@ -237,15 +237,21 @@ static ByteString sha256(X509Certificate x509Certificate) {
}

static final class Pin {
private static final String WILDCARD = "*.";
/** A hostname like {@code example.com} or a pattern like {@code *.example.com}. */
final String pattern;
/** The canonical hostname, i.e. {@code EXAMPLE.com} becomes {@code example.com}. */
final String canonicalHostname;
/** Either {@code sha1/} or {@code sha256/}. */
final String hashAlgorithm;
/** The hash of the pinned certificate using {@link #hashAlgorithm}. */
final ByteString hash;

Pin(String pattern, String pin) {
this.pattern = pattern;
this.canonicalHostname = pattern.startsWith(WILDCARD)
? HttpUrl.parse("http://" + pattern.substring(WILDCARD.length())).host()
: HttpUrl.parse("http://" + pattern).host();
if (pin.startsWith("sha1/")) {
this.hashAlgorithm = "sha1/";
this.hash = ByteString.decodeBase64(pin.substring("sha1/".length()));
Expand All @@ -262,11 +268,13 @@ static final class Pin {
}

boolean matches(String hostname) {
if (pattern.equals(hostname)) return true;
if (pattern.startsWith(WILDCARD)) {
int firstDot = hostname.indexOf('.');
return hostname.regionMatches(false, firstDot + 1, canonicalHostname, 0,
canonicalHostname.length());
}

int firstDot = hostname.indexOf('.');
return pattern.startsWith("*.")
&& hostname.regionMatches(false, firstDot + 1, pattern, 2, pattern.length() - 2);
return hostname.equals(canonicalHostname);
}

@Override public boolean equals(Object other) {
Expand Down

0 comments on commit 3e24b4b

Please sign in to comment.