From 72bccfc4b727d9ef34c878da6a4b0c376b2ffd23 Mon Sep 17 00:00:00 2001 From: Yuri Schimke Date: Wed, 28 Oct 2020 06:49:25 +0000 Subject: [PATCH 1/5] Use generated certificates in unit tests --- .../internal/tls/HostnameVerifierTest.java | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/okhttp/src/test/java/okhttp3/internal/tls/HostnameVerifierTest.java b/okhttp/src/test/java/okhttp3/internal/tls/HostnameVerifierTest.java index ff3ed62b291e..b858d4e3d9eb 100644 --- a/okhttp/src/test/java/okhttp3/internal/tls/HostnameVerifierTest.java +++ b/okhttp/src/test/java/okhttp3/internal/tls/HostnameVerifierTest.java @@ -18,17 +18,21 @@ package okhttp3.internal.tls; import java.io.ByteArrayInputStream; +import java.net.IDN; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.SSLSession; import javax.security.auth.x500.X500Principal; import okhttp3.FakeSSLSession; +import okhttp3.internal.Internal; import okhttp3.internal.Util; +import okhttp3.tls.HeldCertificate; import org.junit.Ignore; import org.junit.Test; import static java.nio.charset.StandardCharsets.UTF_8; +import static okhttp3.internal.HostnamesKt.toCanonicalHost; import static org.assertj.core.api.Assertions.assertThat; /** @@ -554,6 +558,32 @@ public final class HostnameVerifierTest { assertThat(verifier.verify("0:0:0:0:0:FFFF:C0A8:0101", session)).isTrue(); } + @Test public void generatedCertificate() throws Exception { + HeldCertificate heldCertificate = new HeldCertificate.Builder() + .commonName("Foo Corp") + .addSubjectAlternativeName("foo.com") + .build(); + + SSLSession session = session(heldCertificate.certificatePem()); + assertThat(verifier.verify("foo.com", session)).isTrue(); + assertThat(verifier.verify("bar.com", session)).isFalse(); + } + + @Test public void specialK() throws Exception { + HeldCertificate heldCertificate = new HeldCertificate.Builder() + .commonName("Foo Corp") + .addSubjectAlternativeName("k.com") + .build(); + + SSLSession session = session(heldCertificate.certificatePem()); + assertThat(verifier.verify("foo.com", session)).isFalse(); + assertThat(verifier.verify("bar.com", session)).isFalse(); + assertThat(verifier.verify("k.com", session)).isTrue(); + assertThat(verifier.verify("K.com", session)).isTrue(); + + assertThat(verifier.verify("\u212A.com", session)).isFalse(); + } + @Test public void verifyAsIpAddress() { // IPv4 assertThat(Util.canParseAsIpAddress("127.0.0.1")).isTrue(); From 6e69ffb830d5c768499a081ab38b0b560617e986 Mon Sep 17 00:00:00 2001 From: Yuri Schimke Date: Wed, 28 Oct 2020 20:45:06 +0000 Subject: [PATCH 2/5] Fix lowercase logic --- .../kotlin/okhttp3/internal/tls/DnsUtils.kt | 76 +++++++++++++++++++ .../internal/tls/OkHostnameVerifier.kt | 4 +- okhttp/src/test/java/okhttp3/HttpUrlTest.java | 11 +++ .../internal/tls/HostnameVerifierTest.java | 30 +++++++- 4 files changed, 116 insertions(+), 5 deletions(-) create mode 100644 okhttp/src/main/kotlin/okhttp3/internal/tls/DnsUtils.kt diff --git a/okhttp/src/main/kotlin/okhttp3/internal/tls/DnsUtils.kt b/okhttp/src/main/kotlin/okhttp3/internal/tls/DnsUtils.kt new file mode 100644 index 000000000000..cb5e548c3f10 --- /dev/null +++ b/okhttp/src/main/kotlin/okhttp3/internal/tls/DnsUtils.kt @@ -0,0 +1,76 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ +package okhttp3.internal.tls + +/** + * A collection of utilities relating to Domain Name System. + * + * https://raw.githubusercontent.com/apache/httpcomponents-client/master/httpclient5/src/main/java/org/apache/hc/client5/http/utils/DnsUtils.java + */ +internal object DnsUtils { + private fun isIA5(c: Char): Boolean { + return c < 128.toChar() + } + + private fun isUpper(c: Char): Boolean { + return c in 'A'..'Z' + } + + // https://tools.ietf.org/html/rfc2459#section-4.2.1.11 + fun normalizeIA5String(s: String): String { + var pos = 0 + var remaining = s.length + while (remaining > 0) { + // TODO enable if we agree to enforce here +// check (isIA5(s[pos])) { +// "Invalid char ${s[pos]} in hostname $s" +// } + if (isUpper(s[pos])) { + break + } + pos++ + remaining-- + } + return if (remaining > 0) { + buildString(s.length) { + append(s, 0, pos) + while (remaining > 0) { + val c = s[pos] + if (isUpper(c)) { + append((c.toInt() + ('a' - 'A')).toChar()) + } else { + append(c) + } + pos++ + remaining-- + } + } + } else { + s + } + } +} \ No newline at end of file diff --git a/okhttp/src/main/kotlin/okhttp3/internal/tls/OkHostnameVerifier.kt b/okhttp/src/main/kotlin/okhttp3/internal/tls/OkHostnameVerifier.kt index 79a8359163a5..717b5a6fe75f 100644 --- a/okhttp/src/main/kotlin/okhttp3/internal/tls/OkHostnameVerifier.kt +++ b/okhttp/src/main/kotlin/okhttp3/internal/tls/OkHostnameVerifier.kt @@ -61,7 +61,7 @@ object OkHostnameVerifier : HostnameVerifier { /** Returns true if [certificate] matches [hostname]. */ private fun verifyHostname(hostname: String, certificate: X509Certificate): Boolean { - val hostname = hostname.toLowerCase(Locale.US) + val hostname = DnsUtils.normalizeIA5String(hostname) return getSubjectAltNames(certificate, ALT_DNS_NAME).any { verifyHostname(hostname, it) } @@ -108,7 +108,7 @@ object OkHostnameVerifier : HostnameVerifier { } // Hostname and pattern are now absolute domain names. - pattern = pattern.toLowerCase(Locale.US) + pattern = DnsUtils.normalizeIA5String(pattern) // Hostname and pattern are now in lower case -- domain names are case-insensitive. if ("*" !in pattern) { diff --git a/okhttp/src/test/java/okhttp3/HttpUrlTest.java b/okhttp/src/test/java/okhttp3/HttpUrlTest.java index ff10f31d975a..14984a973934 100644 --- a/okhttp/src/test/java/okhttp3/HttpUrlTest.java +++ b/okhttp/src/test/java/okhttp3/HttpUrlTest.java @@ -1773,6 +1773,17 @@ public void unparseableTopPrivateDomain() { assertInvalid("http://../", "Invalid URL host: \"..\""); } + @Test + public void hostnameTelephone() throws Exception { + // https://www.gosecure.net/blog/2020/10/27/weakness-in-java-tls-host-verification/ + + // Map the single character telephone symbol (℡) to the string "tel". + assertThat(parse("http://\u2121").host()).isEqualTo("tel"); + + // Map the Kelvin symbol (K) to the string "k". + assertThat(parse("http://\u212A").host()).isEqualTo("k"); + } + private void assertInvalid(String string, String exceptionMessage) { if (useGet) { try { diff --git a/okhttp/src/test/java/okhttp3/internal/tls/HostnameVerifierTest.java b/okhttp/src/test/java/okhttp3/internal/tls/HostnameVerifierTest.java index b858d4e3d9eb..b8d713f638a6 100644 --- a/okhttp/src/test/java/okhttp3/internal/tls/HostnameVerifierTest.java +++ b/okhttp/src/test/java/okhttp3/internal/tls/HostnameVerifierTest.java @@ -18,21 +18,18 @@ package okhttp3.internal.tls; import java.io.ByteArrayInputStream; -import java.net.IDN; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.SSLSession; import javax.security.auth.x500.X500Principal; import okhttp3.FakeSSLSession; -import okhttp3.internal.Internal; import okhttp3.internal.Util; import okhttp3.tls.HeldCertificate; import org.junit.Ignore; import org.junit.Test; import static java.nio.charset.StandardCharsets.UTF_8; -import static okhttp3.internal.HostnamesKt.toCanonicalHost; import static org.assertj.core.api.Assertions.assertThat; /** @@ -570,9 +567,13 @@ public final class HostnameVerifierTest { } @Test public void specialK() throws Exception { + // https://github.com/apache/httpcomponents-client/commit/303e435d7949652ea77a6c50df1c548682476b6e + // https://www.gosecure.net/blog/2020/10/27/weakness-in-java-tls-host-verification/ + HeldCertificate heldCertificate = new HeldCertificate.Builder() .commonName("Foo Corp") .addSubjectAlternativeName("k.com") + .addSubjectAlternativeName("tel.com") .build(); SSLSession session = session(heldCertificate.certificatePem()); @@ -581,7 +582,30 @@ public final class HostnameVerifierTest { assertThat(verifier.verify("k.com", session)).isTrue(); assertThat(verifier.verify("K.com", session)).isTrue(); + assertThat(verifier.verify("\u2121.com", session)).isFalse(); + assertThat(verifier.verify("℡.com", session)).isFalse(); assertThat(verifier.verify("\u212A.com", session)).isFalse(); + assertThat(verifier.verify("K.com", session)).isFalse(); + } + + @Test public void specialKReversed() throws Exception { + // https://github.com/apache/httpcomponents-client/commit/303e435d7949652ea77a6c50df1c548682476b6e + // https://www.gosecure.net/blog/2020/10/27/weakness-in-java-tls-host-verification/ + + HeldCertificate heldCertificate = new HeldCertificate.Builder() + .commonName("Foo Corp") + .addSubjectAlternativeName("\u2121.com") + .addSubjectAlternativeName("\u212A.com") + .build(); + + SSLSession session = session(heldCertificate.certificatePem()); + assertThat(verifier.verify("foo.com", session)).isFalse(); + assertThat(verifier.verify("bar.com", session)).isFalse(); + assertThat(verifier.verify("k.com", session)).isFalse(); + assertThat(verifier.verify("K.com", session)).isFalse(); + + assertThat(verifier.verify("tel.com", session)).isFalse(); + assertThat(verifier.verify("k.com", session)).isFalse(); } @Test public void verifyAsIpAddress() { From b1fd6950e8b5d80f4e2e74e25d79e531fa26b1db Mon Sep 17 00:00:00 2001 From: Yuri Schimke Date: Thu, 29 Oct 2020 07:32:58 +0000 Subject: [PATCH 3/5] Strict to lowercase implementation --- .../kotlin/okhttp3/internal/tls/DnsUtils.kt | 15 +++--- .../internal/tls/OkHostnameVerifier.kt | 5 +- .../internal/tls/HostnameVerifierTest.java | 49 ++++++++++++++++++- 3 files changed, 56 insertions(+), 13 deletions(-) diff --git a/okhttp/src/main/kotlin/okhttp3/internal/tls/DnsUtils.kt b/okhttp/src/main/kotlin/okhttp3/internal/tls/DnsUtils.kt index cb5e548c3f10..c2203739a505 100644 --- a/okhttp/src/main/kotlin/okhttp3/internal/tls/DnsUtils.kt +++ b/okhttp/src/main/kotlin/okhttp3/internal/tls/DnsUtils.kt @@ -32,10 +32,13 @@ package okhttp3.internal.tls * https://raw.githubusercontent.com/apache/httpcomponents-client/master/httpclient5/src/main/java/org/apache/hc/client5/http/utils/DnsUtils.java */ internal object DnsUtils { - private fun isIA5(c: Char): Boolean { - return c < 128.toChar() - } - + /** + * This strict interpretation of isUpper for IA5String achieves two things. + * Firstly it avoids converting Kelvin (k) to lowercase k. + * Secondly it avoids the simple and efficient lowercase ASCII conversion method below + * from being applied to other unicode uppercase characters and just moving them to another + * undesired unicode character. + */ private fun isUpper(c: Char): Boolean { return c in 'A'..'Z' } @@ -45,10 +48,6 @@ internal object DnsUtils { var pos = 0 var remaining = s.length while (remaining > 0) { - // TODO enable if we agree to enforce here -// check (isIA5(s[pos])) { -// "Invalid char ${s[pos]} in hostname $s" -// } if (isUpper(s[pos])) { break } diff --git a/okhttp/src/main/kotlin/okhttp3/internal/tls/OkHostnameVerifier.kt b/okhttp/src/main/kotlin/okhttp3/internal/tls/OkHostnameVerifier.kt index 717b5a6fe75f..f0bb34de735e 100644 --- a/okhttp/src/main/kotlin/okhttp3/internal/tls/OkHostnameVerifier.kt +++ b/okhttp/src/main/kotlin/okhttp3/internal/tls/OkHostnameVerifier.kt @@ -16,14 +16,13 @@ */ package okhttp3.internal.tls +import okhttp3.internal.canParseAsIpAddress +import okhttp3.internal.toCanonicalHost import java.security.cert.CertificateParsingException import java.security.cert.X509Certificate -import java.util.Locale import javax.net.ssl.HostnameVerifier import javax.net.ssl.SSLException import javax.net.ssl.SSLSession -import okhttp3.internal.canParseAsIpAddress -import okhttp3.internal.toCanonicalHost /** * A HostnameVerifier consistent with [RFC 2818][rfc_2818]. diff --git a/okhttp/src/test/java/okhttp3/internal/tls/HostnameVerifierTest.java b/okhttp/src/test/java/okhttp3/internal/tls/HostnameVerifierTest.java index b8d713f638a6..2ba7b9bdb174 100644 --- a/okhttp/src/test/java/okhttp3/internal/tls/HostnameVerifierTest.java +++ b/okhttp/src/test/java/okhttp3/internal/tls/HostnameVerifierTest.java @@ -24,6 +24,7 @@ import javax.net.ssl.SSLSession; import javax.security.auth.x500.X500Principal; import okhttp3.FakeSSLSession; +import okhttp3.OkHttpClient; import okhttp3.internal.Util; import okhttp3.tls.HeldCertificate; import org.junit.Ignore; @@ -566,7 +567,7 @@ public final class HostnameVerifierTest { assertThat(verifier.verify("bar.com", session)).isFalse(); } - @Test public void specialK() throws Exception { + @Test public void specialKInHostname() throws Exception { // https://github.com/apache/httpcomponents-client/commit/303e435d7949652ea77a6c50df1c548682476b6e // https://www.gosecure.net/blog/2020/10/27/weakness-in-java-tls-host-verification/ @@ -584,11 +585,14 @@ public final class HostnameVerifierTest { assertThat(verifier.verify("\u2121.com", session)).isFalse(); assertThat(verifier.verify("℡.com", session)).isFalse(); + + // These should ideally be false, but we know that hostname is usually already checked by us assertThat(verifier.verify("\u212A.com", session)).isFalse(); + // Kelvin character below assertThat(verifier.verify("K.com", session)).isFalse(); } - @Test public void specialKReversed() throws Exception { + @Test public void specialKInCert() throws Exception { // https://github.com/apache/httpcomponents-client/commit/303e435d7949652ea77a6c50df1c548682476b6e // https://www.gosecure.net/blog/2020/10/27/weakness-in-java-tls-host-verification/ @@ -608,6 +612,47 @@ public final class HostnameVerifierTest { assertThat(verifier.verify("k.com", session)).isFalse(); } + @Test public void specialKInExternalCert() throws Exception { + // $ cat ./cert.cnf + // [req] + // distinguished_name=distinguished_name + // req_extensions=req_extensions + // x509_extensions=x509_extensions + // [distinguished_name] + // [req_extensions] + // [x509_extensions] + // subjectAltName=DNS:℡.com,DNS:K.com + // + // $ openssl req -x509 -nodes -days 36500 -subj '/CN=foo.com' -config ./cert.cnf \ + // -newkey rsa:512 -out cert.pem + SSLSession session = session("" + + "-----BEGIN CERTIFICATE-----\n" + + "MIIBSDCB86ADAgECAhRLR4TGgXBegg0np90FZ1KPeWpDtjANBgkqhkiG9w0BAQsF\n" + + "ADASMRAwDgYDVQQDDAdmb28uY29tMCAXDTIwMTAyOTA2NTkwNVoYDzIxMjAxMDA1\n" + + "MDY1OTA1WjASMRAwDgYDVQQDDAdmb28uY29tMFwwDQYJKoZIhvcNAQEBBQADSwAw\n" + + "SAJBALQcTVW9aW++ClIV9/9iSzijsPvQGEu/FQOjIycSrSIheZyZmR8bluSNBq0C\n" + + "9fpalRKZb0S2tlCTi5WoX8d3K30CAwEAAaMfMB0wGwYDVR0RBBQwEoIH4oShLmNv\n" + + "bYIH4oSqLmNvbTANBgkqhkiG9w0BAQsFAANBAA1+/eDvSUGv78iEjNW+1w3OPAwt\n" + + "Ij1qLQ/YI8OogZPMk7YY46/ydWWp7UpD47zy/vKmm4pOc8Glc8MoDD6UADs=\n" + + "-----END CERTIFICATE-----\n"); + + assertThat(verifier.verify("tel.com", session)).isFalse(); + assertThat(verifier.verify("k.com", session)).isFalse(); + + assertThat(verifier.verify("foo.com", session)).isFalse(); + assertThat(verifier.verify("bar.com", session)).isFalse(); + assertThat(verifier.verify("k.com", session)).isFalse(); + assertThat(verifier.verify("K.com", session)).isFalse(); + } + + @Test + public void thatOkHttpVerifierIsNotTrulyInternal() { + verifier = new OkHttpClient().hostnameVerifier(); + + // Since this is public API, okhttp3.internal.tls.OkHostnameVerifier.verify is also + assertThat(verifier).isInstanceOf(OkHostnameVerifier.class); + } + @Test public void verifyAsIpAddress() { // IPv4 assertThat(Util.canParseAsIpAddress("127.0.0.1")).isTrue(); From 40af50161a91fea80725c23f778b11cf536d0228 Mon Sep 17 00:00:00 2001 From: Yuri Schimke Date: Fri, 30 Oct 2020 07:14:14 +0000 Subject: [PATCH 4/5] Add public API test --- .../java/okhttp3/internal/tls/HostnameVerifierTest.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/okhttp/src/test/java/okhttp3/internal/tls/HostnameVerifierTest.java b/okhttp/src/test/java/okhttp3/internal/tls/HostnameVerifierTest.java index 2ba7b9bdb174..611b58318ba2 100644 --- a/okhttp/src/test/java/okhttp3/internal/tls/HostnameVerifierTest.java +++ b/okhttp/src/test/java/okhttp3/internal/tls/HostnameVerifierTest.java @@ -27,6 +27,7 @@ import okhttp3.OkHttpClient; import okhttp3.internal.Util; import okhttp3.tls.HeldCertificate; +import okhttp3.tls.internal.TlsUtil; import org.junit.Ignore; import org.junit.Test; @@ -646,11 +647,14 @@ public final class HostnameVerifierTest { } @Test - public void thatOkHttpVerifierIsNotTrulyInternal() { - verifier = new OkHttpClient().hostnameVerifier(); + public void thatCatchesErrorsWithBadSession() { + HostnameVerifier localVerifier = new OkHttpClient().hostnameVerifier(); // Since this is public API, okhttp3.internal.tls.OkHostnameVerifier.verify is also assertThat(verifier).isInstanceOf(OkHostnameVerifier.class); + + SSLSession session = TlsUtil.localhost().sslContext().createSSLEngine().getSession(); + assertThat(localVerifier.verify("\uD83D\uDCA9.com", session)).isFalse(); } @Test public void verifyAsIpAddress() { From de32e8ba59d1bb5ada16fd127510ad30524d9c20 Mon Sep 17 00:00:00 2001 From: Jesse Wilson Date: Thu, 29 Oct 2020 22:13:57 -0400 Subject: [PATCH 5/5] Use utf8Size to check for non-ASCII chars --- .../kotlin/okhttp3/internal/tls/DnsUtils.kt | 75 ------------------- .../internal/tls/OkHostnameVerifier.kt | 18 ++++- 2 files changed, 16 insertions(+), 77 deletions(-) delete mode 100644 okhttp/src/main/kotlin/okhttp3/internal/tls/DnsUtils.kt diff --git a/okhttp/src/main/kotlin/okhttp3/internal/tls/DnsUtils.kt b/okhttp/src/main/kotlin/okhttp3/internal/tls/DnsUtils.kt deleted file mode 100644 index c2203739a505..000000000000 --- a/okhttp/src/main/kotlin/okhttp3/internal/tls/DnsUtils.kt +++ /dev/null @@ -1,75 +0,0 @@ -/* - * ==================================================================== - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * ==================================================================== - * - * This software consists of voluntary contributions made by many - * individuals on behalf of the Apache Software Foundation. For more - * information on the Apache Software Foundation, please see - * . - * - */ -package okhttp3.internal.tls - -/** - * A collection of utilities relating to Domain Name System. - * - * https://raw.githubusercontent.com/apache/httpcomponents-client/master/httpclient5/src/main/java/org/apache/hc/client5/http/utils/DnsUtils.java - */ -internal object DnsUtils { - /** - * This strict interpretation of isUpper for IA5String achieves two things. - * Firstly it avoids converting Kelvin (k) to lowercase k. - * Secondly it avoids the simple and efficient lowercase ASCII conversion method below - * from being applied to other unicode uppercase characters and just moving them to another - * undesired unicode character. - */ - private fun isUpper(c: Char): Boolean { - return c in 'A'..'Z' - } - - // https://tools.ietf.org/html/rfc2459#section-4.2.1.11 - fun normalizeIA5String(s: String): String { - var pos = 0 - var remaining = s.length - while (remaining > 0) { - if (isUpper(s[pos])) { - break - } - pos++ - remaining-- - } - return if (remaining > 0) { - buildString(s.length) { - append(s, 0, pos) - while (remaining > 0) { - val c = s[pos] - if (isUpper(c)) { - append((c.toInt() + ('a' - 'A')).toChar()) - } else { - append(c) - } - pos++ - remaining-- - } - } - } else { - s - } - } -} \ No newline at end of file diff --git a/okhttp/src/main/kotlin/okhttp3/internal/tls/OkHostnameVerifier.kt b/okhttp/src/main/kotlin/okhttp3/internal/tls/OkHostnameVerifier.kt index f0bb34de735e..389aac118819 100644 --- a/okhttp/src/main/kotlin/okhttp3/internal/tls/OkHostnameVerifier.kt +++ b/okhttp/src/main/kotlin/okhttp3/internal/tls/OkHostnameVerifier.kt @@ -18,8 +18,10 @@ package okhttp3.internal.tls import okhttp3.internal.canParseAsIpAddress import okhttp3.internal.toCanonicalHost +import okio.utf8Size import java.security.cert.CertificateParsingException import java.security.cert.X509Certificate +import java.util.Locale import javax.net.ssl.HostnameVerifier import javax.net.ssl.SSLException import javax.net.ssl.SSLSession @@ -60,12 +62,24 @@ object OkHostnameVerifier : HostnameVerifier { /** Returns true if [certificate] matches [hostname]. */ private fun verifyHostname(hostname: String, certificate: X509Certificate): Boolean { - val hostname = DnsUtils.normalizeIA5String(hostname) + val hostname = hostname.asciiToLowercase() return getSubjectAltNames(certificate, ALT_DNS_NAME).any { verifyHostname(hostname, it) } } + /** + * This is like [toLowerCase] except that it does nothing if this contains any non-ASCII + * characters. We want to avoid lower casing special chars like U+212A (Kelvin symbol) because + * they can return ASCII characters that match real hostnames. + */ + private fun String.asciiToLowercase(): String { + return when { + length == utf8Size().toInt() -> toLowerCase(Locale.US) // This is an ASCII string. + else -> this + } + } + /** * Returns true if [hostname] matches the domain name [pattern]. * @@ -107,7 +121,7 @@ object OkHostnameVerifier : HostnameVerifier { } // Hostname and pattern are now absolute domain names. - pattern = DnsUtils.normalizeIA5String(pattern) + pattern = pattern.asciiToLowercase() // Hostname and pattern are now in lower case -- domain names are case-insensitive. if ("*" !in pattern) {