All Downloads are FREE. Search and download functionalities are using the official Maven repository.

jvmTest.okhttp3.internal.tls.HostnameVerifierTest.kt Maven / Gradle / Ivy

There is a newer version: 5.0.0-alpha.14
Show newest version
/*
 * 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.
 */
package okhttp3.internal.tls

import java.io.ByteArrayInputStream
import java.security.cert.CertificateFactory
import java.security.cert.X509Certificate
import java.util.stream.Stream
import javax.net.ssl.SSLSession
import javax.security.auth.x500.X500Principal
import okhttp3.FakeSSLSession
import okhttp3.OkHttpClient
import okhttp3.internal.canParseAsIpAddress
import okhttp3.internal.platform.Platform.Companion.isAndroid
import okhttp3.testing.PlatformRule
import okhttp3.tls.HeldCertificate
import okhttp3.tls.internal.TlsUtil.localhost
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.RegisterExtension

/**
 * Tests for our hostname verifier. Most of these tests are from AOSP, which itself includes tests
 * from the Apache HTTP Client test suite.
 */
class HostnameVerifierTest {
  private val verifier = OkHostnameVerifier

  @RegisterExtension
  var platform = PlatformRule()

  @Test fun verify() {
    val session = FakeSSLSession()
    assertThat(verifier.verify("localhost", session)).isFalse
  }

  @Test fun verifyCn() {
    // CN=foo.com
    val session = session(
      """
      -----BEGIN CERTIFICATE-----
      MIIERjCCAy6gAwIBAgIJAIz+EYMBU6aQMA0GCSqGSIb3DQEBBQUAMIGiMQswCQYD
      VQQGEwJDQTELMAkGA1UECBMCQkMxEjAQBgNVBAcTCVZhbmNvdXZlcjEWMBQGA1UE
      ChMNd3d3LmN1Y2JjLmNvbTEUMBIGA1UECxQLY29tbW9uc19zc2wxHTAbBgNVBAMU
      FGRlbW9faW50ZXJtZWRpYXRlX2NhMSUwIwYJKoZIhvcNAQkBFhZqdWxpdXNkYXZp
      ZXNAZ21haWwuY29tMB4XDTA2MTIxMTE1MzE0MVoXDTI4MTEwNTE1MzE0MVowgaQx
      CzAJBgNVBAYTAlVTMREwDwYDVQQIEwhNYXJ5bGFuZDEUMBIGA1UEBxMLRm9yZXN0
      IEhpbGwxFzAVBgNVBAoTDmh0dHBjb21wb25lbnRzMRowGAYDVQQLExF0ZXN0IGNl
      cnRpZmljYXRlczEQMA4GA1UEAxMHZm9vLmNvbTElMCMGCSqGSIb3DQEJARYWanVs
      aXVzZGF2aWVzQGdtYWlsLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
      ggEBAMhjr5aCPoyp0R1iroWAfnEyBMGYWoCidH96yGPFjYLowez5aYKY1IOKTY2B
      lYho4O84X244QrZTRl8kQbYtxnGh4gSCD+Z8gjZ/gMvLUlhqOb+WXPAUHMB39GRy
      zerA/ZtrlUqf+lKo0uWcocxeRc771KN8cPH3nHZ0rV0Hx4ZAZy6U4xxObe4rtSVY
      07hNKXAb2odnVqgzcYiDkLV8ilvEmoNWMWrp8UBqkTcpEhYhCYp3cTkgJwMSuqv8
      BqnGd87xQU3FVZI4tbtkB+KzjD9zz8QCDJAfDjZHR03KNQ5mxOgXwxwKw6lGMaiV
      JTxpTKqym93whYk93l3ocEe55c0CAwEAAaN7MHkwCQYDVR0TBAIwADAsBglghkgB
      hvhCAQ0EHxYdT3BlblNTTCBHZW5lcmF0ZWQgQ2VydGlmaWNhdGUwHQYDVR0OBBYE
      FJ8Ud78/OrbKOIJCSBYs2tDLXofYMB8GA1UdIwQYMBaAFHua2o+QmU5S0qzbswNS
      yoemDT4NMA0GCSqGSIb3DQEBBQUAA4IBAQC3jRmEya6sQCkmieULcvx8zz1euCk9
      fSez7BEtki8+dmfMXe3K7sH0lI8f4jJR0rbSCjpmCQLYmzC3NxBKeJOW0RcjNBpO
      c2JlGO9auXv2GDP4IYiXElLJ6VSqc8WvDikv0JmCCWm0Zga+bZbR/EWN5DeEtFdF
      815CLpJZNcYwiYwGy/CVQ7w2TnXlG+mraZOz+owr+cL6J/ZesbdEWfjoS1+cUEhE
      HwlNrAu8jlZ2UqSgskSWlhYdMTAP9CPHiUv9N7FcT58Itv/I4fKREINQYjDpvQcx
      SaTYb9dr5sB4WLNglk7zxDtM80H518VvihTcP7FHL+Gn6g4j5fkI98+S
      -----END CERTIFICATE-----
      """.trimIndent()
    )
    assertThat(verifier.verify("foo.com", session)).isFalse
    assertThat(verifier.verify("a.foo.com", session)).isFalse
    assertThat(verifier.verify("bar.com", session)).isFalse
  }

  @Test fun verifyNonAsciiCn() {
    // CN=花子.co.jp
    val session = session(
      """
      -----BEGIN CERTIFICATE-----
      MIIESzCCAzOgAwIBAgIJAIz+EYMBU6aTMA0GCSqGSIb3DQEBBQUAMIGiMQswCQYD
      VQQGEwJDQTELMAkGA1UECBMCQkMxEjAQBgNVBAcTCVZhbmNvdXZlcjEWMBQGA1UE
      ChMNd3d3LmN1Y2JjLmNvbTEUMBIGA1UECxQLY29tbW9uc19zc2wxHTAbBgNVBAMU
      FGRlbW9faW50ZXJtZWRpYXRlX2NhMSUwIwYJKoZIhvcNAQkBFhZqdWxpdXNkYXZp
      ZXNAZ21haWwuY29tMB4XDTA2MTIxMTE1NDIxNVoXDTI4MTEwNTE1NDIxNVowgakx
      CzAJBgNVBAYTAlVTMREwDwYDVQQIDAhNYXJ5bGFuZDEUMBIGA1UEBwwLRm9yZXN0
      IEhpbGwxFzAVBgNVBAoMDmh0dHBjb21wb25lbnRzMRowGAYDVQQLDBF0ZXN0IGNl
      cnRpZmljYXRlczEVMBMGA1UEAwwM6Iqx5a2QLmNvLmpwMSUwIwYJKoZIhvcNAQkB
      FhZqdWxpdXNkYXZpZXNAZ21haWwuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
      MIIBCgKCAQEAyGOvloI+jKnRHWKuhYB+cTIEwZhagKJ0f3rIY8WNgujB7PlpgpjU
      g4pNjYGViGjg7zhfbjhCtlNGXyRBti3GcaHiBIIP5nyCNn+Ay8tSWGo5v5Zc8BQc
      wHf0ZHLN6sD9m2uVSp/6UqjS5ZyhzF5FzvvUo3xw8fecdnStXQfHhkBnLpTjHE5t
      7iu1JVjTuE0pcBvah2dWqDNxiIOQtXyKW8Sag1YxaunxQGqRNykSFiEJindxOSAn
      AxK6q/wGqcZ3zvFBTcVVkji1u2QH4rOMP3PPxAIMkB8ONkdHTco1DmbE6BfDHArD
      qUYxqJUlPGlMqrKb3fCFiT3eXehwR7nlzQIDAQABo3sweTAJBgNVHRMEAjAAMCwG
      CWCGSAGG+EIBDQQfFh1PcGVuU1NMIEdlbmVyYXRlZCBDZXJ0aWZpY2F0ZTAdBgNV
      HQ4EFgQUnxR3vz86tso4gkJIFiza0Mteh9gwHwYDVR0jBBgwFoAUe5raj5CZTlLS
      rNuzA1LKh6YNPg0wDQYJKoZIhvcNAQEFBQADggEBALJ27i3okV/KvlDp6KMID3gd
      ITl68PyItzzx+SquF8gahMh016NX73z/oVZoVUNdftla8wPUB1GwIkAnGkhQ9LHK
      spBdbRiCj0gMmLCsX8SrjFvr7cYb2cK6J/fJe92l1tg/7Y4o7V/s4JBe/cy9U9w8
      a0ctuDmEBCgC784JMDtT67klRfr/2LlqWhlOEq7pUFxRLbhpquaAHSOjmIcWnVpw
      9BsO7qe46hidgn39hKh1WjKK2VcL/3YRsC4wUi0PBtFW6ScMCuMhgIRXSPU55Rae
      UIlOdPjjr1SUNWGId1rD7W16Scpwnknn310FNxFMHVI0GTGFkNdkilNCFJcIoRA=
      -----END CERTIFICATE-----
      """.trimIndent()
    )
    assertThat(verifier.verify("\u82b1\u5b50.co.jp", session)).isFalse
    assertThat(verifier.verify("a.\u82b1\u5b50.co.jp", session)).isFalse
  }

  @Test fun verifySubjectAlt() {
    // CN=foo.com, subjectAlt=bar.com
    val session = session(
      """
      -----BEGIN CERTIFICATE-----
      MIIEXDCCA0SgAwIBAgIJAIz+EYMBU6aRMA0GCSqGSIb3DQEBBQUAMIGiMQswCQYD
      VQQGEwJDQTELMAkGA1UECBMCQkMxEjAQBgNVBAcTCVZhbmNvdXZlcjEWMBQGA1UE
      ChMNd3d3LmN1Y2JjLmNvbTEUMBIGA1UECxQLY29tbW9uc19zc2wxHTAbBgNVBAMU
      FGRlbW9faW50ZXJtZWRpYXRlX2NhMSUwIwYJKoZIhvcNAQkBFhZqdWxpdXNkYXZp
      ZXNAZ21haWwuY29tMB4XDTA2MTIxMTE1MzYyOVoXDTI4MTEwNTE1MzYyOVowgaQx
      CzAJBgNVBAYTAlVTMREwDwYDVQQIEwhNYXJ5bGFuZDEUMBIGA1UEBxMLRm9yZXN0
      IEhpbGwxFzAVBgNVBAoTDmh0dHBjb21wb25lbnRzMRowGAYDVQQLExF0ZXN0IGNl
      cnRpZmljYXRlczEQMA4GA1UEAxMHZm9vLmNvbTElMCMGCSqGSIb3DQEJARYWanVs
      aXVzZGF2aWVzQGdtYWlsLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
      ggEBAMhjr5aCPoyp0R1iroWAfnEyBMGYWoCidH96yGPFjYLowez5aYKY1IOKTY2B
      lYho4O84X244QrZTRl8kQbYtxnGh4gSCD+Z8gjZ/gMvLUlhqOb+WXPAUHMB39GRy
      zerA/ZtrlUqf+lKo0uWcocxeRc771KN8cPH3nHZ0rV0Hx4ZAZy6U4xxObe4rtSVY
      07hNKXAb2odnVqgzcYiDkLV8ilvEmoNWMWrp8UBqkTcpEhYhCYp3cTkgJwMSuqv8
      BqnGd87xQU3FVZI4tbtkB+KzjD9zz8QCDJAfDjZHR03KNQ5mxOgXwxwKw6lGMaiV
      JTxpTKqym93whYk93l3ocEe55c0CAwEAAaOBkDCBjTAJBgNVHRMEAjAAMCwGCWCG
      SAGG+EIBDQQfFh1PcGVuU1NMIEdlbmVyYXRlZCBDZXJ0aWZpY2F0ZTAdBgNVHQ4E
      FgQUnxR3vz86tso4gkJIFiza0Mteh9gwHwYDVR0jBBgwFoAUe5raj5CZTlLSrNuz
      A1LKh6YNPg0wEgYDVR0RBAswCYIHYmFyLmNvbTANBgkqhkiG9w0BAQUFAAOCAQEA
      dQyprNZBmVnvuVWjV42sey/PTfkYShJwy1j0/jcFZR/ypZUovpiHGDO1DgL3Y3IP
      zVQ26uhUsSw6G0gGRiaBDe/0LUclXZoJzXX1qpS55OadxW73brziS0sxRgGrZE/d
      3g5kkio6IED47OP6wYnlmZ7EKP9cqjWwlnvHnnUcZ2SscoLNYs9rN9ccp8tuq2by
      88OyhKwGjJfhOudqfTNZcDzRHx4Fzm7UsVaycVw4uDmhEHJrAsmMPpj/+XRK9/42
      2xq+8bc6HojdtbCyug/fvBZvZqQXSmU8m8IVcMmWMz0ZQO8ee3QkBHMZfCy7P/kr
      VbWx/uETImUu+NZg22ewEw==
      -----END CERTIFICATE-----
      """.trimIndent()
    )
    assertThat(verifier.verify("foo.com", session)).isFalse
    assertThat(verifier.verify("a.foo.com", session)).isFalse
    assertThat(verifier.verify("bar.com", session)).isTrue
    assertThat(verifier.verify("a.bar.com", session)).isFalse
  }

  /**
   * Ignored due to incompatibilities between Android and Java on how non-ASCII subject alt names
   * are parsed. Android fails to parse these, which means we fall back to the CN. The RI does parse
   * them, so the CN is unused.
   */
  @Test fun verifyNonAsciiSubjectAlt() {
    // CN=foo.com, subjectAlt=bar.com, subjectAlt=花子.co.jp
    // (hanako.co.jp in kanji)
    val session = session(
      """
      -----BEGIN CERTIFICATE-----
      MIIEajCCA1KgAwIBAgIJAIz+EYMBU6aSMA0GCSqGSIb3DQEBBQUAMIGiMQswCQYD
      VQQGEwJDQTELMAkGA1UECBMCQkMxEjAQBgNVBAcTCVZhbmNvdXZlcjEWMBQGA1UE
      ChMNd3d3LmN1Y2JjLmNvbTEUMBIGA1UECxQLY29tbW9uc19zc2wxHTAbBgNVBAMU
      FGRlbW9faW50ZXJtZWRpYXRlX2NhMSUwIwYJKoZIhvcNAQkBFhZqdWxpdXNkYXZp
      ZXNAZ21haWwuY29tMB4XDTA2MTIxMTE1MzgxM1oXDTI4MTEwNTE1MzgxM1owgaQx
      CzAJBgNVBAYTAlVTMREwDwYDVQQIEwhNYXJ5bGFuZDEUMBIGA1UEBxMLRm9yZXN0
      IEhpbGwxFzAVBgNVBAoTDmh0dHBjb21wb25lbnRzMRowGAYDVQQLExF0ZXN0IGNl
      cnRpZmljYXRlczEQMA4GA1UEAxMHZm9vLmNvbTElMCMGCSqGSIb3DQEJARYWanVs
      aXVzZGF2aWVzQGdtYWlsLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
      ggEBAMhjr5aCPoyp0R1iroWAfnEyBMGYWoCidH96yGPFjYLowez5aYKY1IOKTY2B
      lYho4O84X244QrZTRl8kQbYtxnGh4gSCD+Z8gjZ/gMvLUlhqOb+WXPAUHMB39GRy
      zerA/ZtrlUqf+lKo0uWcocxeRc771KN8cPH3nHZ0rV0Hx4ZAZy6U4xxObe4rtSVY
      07hNKXAb2odnVqgzcYiDkLV8ilvEmoNWMWrp8UBqkTcpEhYhCYp3cTkgJwMSuqv8
      BqnGd87xQU3FVZI4tbtkB+KzjD9zz8QCDJAfDjZHR03KNQ5mxOgXwxwKw6lGMaiV
      JTxpTKqym93whYk93l3ocEe55c0CAwEAAaOBnjCBmzAJBgNVHRMEAjAAMCwGCWCG
      SAGG+EIBDQQfFh1PcGVuU1NMIEdlbmVyYXRlZCBDZXJ0aWZpY2F0ZTAdBgNVHQ4E
      FgQUnxR3vz86tso4gkJIFiza0Mteh9gwHwYDVR0jBBgwFoAUe5raj5CZTlLSrNuz
      A1LKh6YNPg0wIAYDVR0RBBkwF4IHYmFyLmNvbYIM6Iqx5a2QLmNvLmpwMA0GCSqG
      SIb3DQEBBQUAA4IBAQBeZs7ZIYyKtdnVxVvdLgwySEPOE4pBSXii7XYv0Q9QUvG/
      ++gFGQh89HhABzA1mVUjH5dJTQqSLFvRfqTHqLpxSxSWqMHnvRM4cPBkIRp/XlMK
      PlXadYtJLPTgpbgvulA1ickC9EwlNYWnowZ4uxnfsMghW4HskBqaV+PnQ8Zvy3L0
      12c7Cg4mKKS5pb1HdRuiD2opZ+Hc77gRQLvtWNS8jQvd/iTbh6fuvTKfAOFoXw22
      sWIKHYrmhCIRshUNohGXv50m2o+1w9oWmQ6Dkq7lCjfXfUB4wIbggJjpyEtbNqBt
      j4MC2x5rfsLKKqToKmNE7pFEgqwe8//Aar1b+Qj+
      -----END CERTIFICATE-----
      """.trimIndent()
    )
    val peerCertificate = session.peerCertificates[0] as X509Certificate

    if (isAndroid || platform.isConscrypt()) {
      assertThat(certificateSANs(peerCertificate)).containsExactly("bar.com")
    } else {
      assertThat(certificateSANs(peerCertificate)).containsExactly("bar.com", "������.co.jp")
    }

    assertThat(verifier.verify("foo.com", session)).isFalse
    assertThat(verifier.verify("a.foo.com", session)).isFalse
    // these checks test alternative subjects. The test data contains an
    // alternative subject starting with a japanese kanji character. This is
    // not supported by Android because the underlying implementation from
    // harmony follows the definition from rfc 1034 page 10 for alternative
    // subject names. This causes the code to drop all alternative subjects.
    assertThat(verifier.verify("bar.com", session)).isTrue
    assertThat(verifier.verify("a.bar.com", session)).isFalse
    assertThat(verifier.verify("a.\u82b1\u5b50.co.jp", session)).isFalse
  }

  @Test fun verifySubjectAltOnly() {
    // subjectAlt=foo.com
    val session = session(
      """
      -----BEGIN CERTIFICATE-----
      MIIESjCCAzKgAwIBAgIJAIz+EYMBU6aYMA0GCSqGSIb3DQEBBQUAMIGiMQswCQYD
      VQQGEwJDQTELMAkGA1UECBMCQkMxEjAQBgNVBAcTCVZhbmNvdXZlcjEWMBQGA1UE
      ChMNd3d3LmN1Y2JjLmNvbTEUMBIGA1UECxQLY29tbW9uc19zc2wxHTAbBgNVBAMU
      FGRlbW9faW50ZXJtZWRpYXRlX2NhMSUwIwYJKoZIhvcNAQkBFhZqdWxpdXNkYXZp
      ZXNAZ21haWwuY29tMB4XDTA2MTIxMTE2MjYxMFoXDTI4MTEwNTE2MjYxMFowgZIx
      CzAJBgNVBAYTAlVTMREwDwYDVQQIDAhNYXJ5bGFuZDEUMBIGA1UEBwwLRm9yZXN0
      IEhpbGwxFzAVBgNVBAoMDmh0dHBjb21wb25lbnRzMRowGAYDVQQLDBF0ZXN0IGNl
      cnRpZmljYXRlczElMCMGCSqGSIb3DQEJARYWanVsaXVzZGF2aWVzQGdtYWlsLmNv
      bTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMhjr5aCPoyp0R1iroWA
      fnEyBMGYWoCidH96yGPFjYLowez5aYKY1IOKTY2BlYho4O84X244QrZTRl8kQbYt
      xnGh4gSCD+Z8gjZ/gMvLUlhqOb+WXPAUHMB39GRyzerA/ZtrlUqf+lKo0uWcocxe
      Rc771KN8cPH3nHZ0rV0Hx4ZAZy6U4xxObe4rtSVY07hNKXAb2odnVqgzcYiDkLV8
      ilvEmoNWMWrp8UBqkTcpEhYhCYp3cTkgJwMSuqv8BqnGd87xQU3FVZI4tbtkB+Kz
      jD9zz8QCDJAfDjZHR03KNQ5mxOgXwxwKw6lGMaiVJTxpTKqym93whYk93l3ocEe5
      5c0CAwEAAaOBkDCBjTAJBgNVHRMEAjAAMCwGCWCGSAGG+EIBDQQfFh1PcGVuU1NM
      IEdlbmVyYXRlZCBDZXJ0aWZpY2F0ZTAdBgNVHQ4EFgQUnxR3vz86tso4gkJIFiza
      0Mteh9gwHwYDVR0jBBgwFoAUe5raj5CZTlLSrNuzA1LKh6YNPg0wEgYDVR0RBAsw
      CYIHZm9vLmNvbTANBgkqhkiG9w0BAQUFAAOCAQEAjl78oMjzFdsMy6F1sGg/IkO8
      tF5yUgPgFYrs41yzAca7IQu6G9qtFDJz/7ehh/9HoG+oqCCIHPuIOmS7Sd0wnkyJ
      Y7Y04jVXIb3a6f6AgBkEFP1nOT0z6kjT7vkA5LJ2y3MiDcXuRNMSta5PYVnrX8aZ
      yiqVUNi40peuZ2R8mAUSBvWgD7z2qWhF8YgDb7wWaFjg53I36vWKn90ZEti3wNCw
      qAVqixM+J0qJmQStgAc53i2aTMvAQu3A3snvH/PHTBo+5UL72n9S1kZyNCsVf1Qo
      n8jKTiRriEM+fMFlcgQP284EBFzYHyCXFb9O/hMjK2+6mY9euMB1U1aFFzM/Bg==
      -----END CERTIFICATE-----
      """.trimIndent()
    )
    assertThat(verifier.verify("foo.com", session)).isTrue
    assertThat(verifier.verify("a.foo.com", session)).isFalse
    assertThat(verifier.verify("foo.com", session)).isTrue
    assertThat(verifier.verify("a.foo.com", session)).isFalse
  }

  @Test fun verifyMultipleCn() {
    // CN=foo.com, CN=bar.com, CN=花子.co.jp
    val session = session(
      """
      -----BEGIN CERTIFICATE-----
      MIIEbzCCA1egAwIBAgIJAIz+EYMBU6aXMA0GCSqGSIb3DQEBBQUAMIGiMQswCQYD
      VQQGEwJDQTELMAkGA1UECBMCQkMxEjAQBgNVBAcTCVZhbmNvdXZlcjEWMBQGA1UE
      ChMNd3d3LmN1Y2JjLmNvbTEUMBIGA1UECxQLY29tbW9uc19zc2wxHTAbBgNVBAMU
      FGRlbW9faW50ZXJtZWRpYXRlX2NhMSUwIwYJKoZIhvcNAQkBFhZqdWxpdXNkYXZp
      ZXNAZ21haWwuY29tMB4XDTA2MTIxMTE2MTk0NVoXDTI4MTEwNTE2MTk0NVowgc0x
      CzAJBgNVBAYTAlVTMREwDwYDVQQIDAhNYXJ5bGFuZDEUMBIGA1UEBwwLRm9yZXN0
      IEhpbGwxFzAVBgNVBAoMDmh0dHBjb21wb25lbnRzMRowGAYDVQQLDBF0ZXN0IGNl
      cnRpZmljYXRlczEQMA4GA1UEAwwHZm9vLmNvbTEQMA4GA1UEAwwHYmFyLmNvbTEV
      MBMGA1UEAwwM6Iqx5a2QLmNvLmpwMSUwIwYJKoZIhvcNAQkBFhZqdWxpdXNkYXZp
      ZXNAZ21haWwuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyGOv
      loI+jKnRHWKuhYB+cTIEwZhagKJ0f3rIY8WNgujB7PlpgpjUg4pNjYGViGjg7zhf
      bjhCtlNGXyRBti3GcaHiBIIP5nyCNn+Ay8tSWGo5v5Zc8BQcwHf0ZHLN6sD9m2uV
      Sp/6UqjS5ZyhzF5FzvvUo3xw8fecdnStXQfHhkBnLpTjHE5t7iu1JVjTuE0pcBva
      h2dWqDNxiIOQtXyKW8Sag1YxaunxQGqRNykSFiEJindxOSAnAxK6q/wGqcZ3zvFB
      TcVVkji1u2QH4rOMP3PPxAIMkB8ONkdHTco1DmbE6BfDHArDqUYxqJUlPGlMqrKb
      3fCFiT3eXehwR7nlzQIDAQABo3sweTAJBgNVHRMEAjAAMCwGCWCGSAGG+EIBDQQf
      Fh1PcGVuU1NMIEdlbmVyYXRlZCBDZXJ0aWZpY2F0ZTAdBgNVHQ4EFgQUnxR3vz86
      tso4gkJIFiza0Mteh9gwHwYDVR0jBBgwFoAUe5raj5CZTlLSrNuzA1LKh6YNPg0w
      DQYJKoZIhvcNAQEFBQADggEBAGuZb8ai1NO2j4v3y9TLZvd5s0vh5/TE7n7RX+8U
      y37OL5k7x9nt0mM1TyAKxlCcY+9h6frue8MemZIILSIvMrtzccqNz0V1WKgA+Orf
      uUrabmn+CxHF5gpy6g1Qs2IjVYWA5f7FROn/J+Ad8gJYc1azOWCLQqSyfpNRLSvY
      EriQFEV63XvkJ8JrG62b+2OT2lqT4OO07gSPetppdlSa8NBSKP6Aro9RIX1ZjUZQ
      SpQFCfo02NO0uNRDPUdJx2huycdNb+AXHaO7eXevDLJ+QnqImIzxWiY6zLOdzjjI
      VBMkLHmnP7SjGSQ3XA4ByrQOxfOUTyLyE7NuemhHppuQPxE=
      -----END CERTIFICATE-----
      """.trimIndent()
    )
    assertThat(verifier.verify("foo.com", session)).isFalse
    assertThat(verifier.verify("a.foo.com", session)).isFalse
    assertThat(verifier.verify("bar.com", session)).isFalse
    assertThat(verifier.verify("a.bar.com", session)).isFalse
    assertThat(verifier.verify("\u82b1\u5b50.co.jp", session)).isFalse
    assertThat(verifier.verify("a.\u82b1\u5b50.co.jp", session)).isFalse
  }

  @Test fun verifyWilcardCn() {
    // CN=*.foo.com
    val session = session(
      """
      -----BEGIN CERTIFICATE-----
      MIIESDCCAzCgAwIBAgIJAIz+EYMBU6aUMA0GCSqGSIb3DQEBBQUAMIGiMQswCQYD
      VQQGEwJDQTELMAkGA1UECBMCQkMxEjAQBgNVBAcTCVZhbmNvdXZlcjEWMBQGA1UE
      ChMNd3d3LmN1Y2JjLmNvbTEUMBIGA1UECxQLY29tbW9uc19zc2wxHTAbBgNVBAMU
      FGRlbW9faW50ZXJtZWRpYXRlX2NhMSUwIwYJKoZIhvcNAQkBFhZqdWxpdXNkYXZp
      ZXNAZ21haWwuY29tMB4XDTA2MTIxMTE2MTU1NVoXDTI4MTEwNTE2MTU1NVowgaYx
      CzAJBgNVBAYTAlVTMREwDwYDVQQIEwhNYXJ5bGFuZDEUMBIGA1UEBxMLRm9yZXN0
      IEhpbGwxFzAVBgNVBAoTDmh0dHBjb21wb25lbnRzMRowGAYDVQQLExF0ZXN0IGNl
      cnRpZmljYXRlczESMBAGA1UEAxQJKi5mb28uY29tMSUwIwYJKoZIhvcNAQkBFhZq
      dWxpdXNkYXZpZXNAZ21haWwuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
      CgKCAQEAyGOvloI+jKnRHWKuhYB+cTIEwZhagKJ0f3rIY8WNgujB7PlpgpjUg4pN
      jYGViGjg7zhfbjhCtlNGXyRBti3GcaHiBIIP5nyCNn+Ay8tSWGo5v5Zc8BQcwHf0
      ZHLN6sD9m2uVSp/6UqjS5ZyhzF5FzvvUo3xw8fecdnStXQfHhkBnLpTjHE5t7iu1
      JVjTuE0pcBvah2dWqDNxiIOQtXyKW8Sag1YxaunxQGqRNykSFiEJindxOSAnAxK6
      q/wGqcZ3zvFBTcVVkji1u2QH4rOMP3PPxAIMkB8ONkdHTco1DmbE6BfDHArDqUYx
      qJUlPGlMqrKb3fCFiT3eXehwR7nlzQIDAQABo3sweTAJBgNVHRMEAjAAMCwGCWCG
      SAGG+EIBDQQfFh1PcGVuU1NMIEdlbmVyYXRlZCBDZXJ0aWZpY2F0ZTAdBgNVHQ4E
      FgQUnxR3vz86tso4gkJIFiza0Mteh9gwHwYDVR0jBBgwFoAUe5raj5CZTlLSrNuz
      A1LKh6YNPg0wDQYJKoZIhvcNAQEFBQADggEBAH0ipG6J561UKUfgkeW7GvYwW98B
      N1ZooWX+JEEZK7+Pf/96d3Ij0rw9ACfN4bpfnCq0VUNZVSYB+GthQ2zYuz7tf/UY
      A6nxVgR/IjG69BmsBl92uFO7JTNtHztuiPqBn59pt+vNx4yPvno7zmxsfI7jv0ww
      yfs+0FNm7FwdsC1k47GBSOaGw38kuIVWqXSAbL4EX9GkryGGOKGNh0qvAENCdRSB
      G9Z6tyMbmfRY+dLSh3a9JwoEcBUso6EWYBakLbq4nG/nvYdYvG9ehrnLVwZFL82e
      l3Q/RK95bnA6cuRClGusLad0e6bjkBzx/VQ3VarDEpAkTLUGVAa0CLXtnyc=
      -----END CERTIFICATE-----
      """.trimIndent()
    )
    assertThat(verifier.verify("foo.com", session)).isFalse
    assertThat(verifier.verify("www.foo.com", session)).isFalse
    assertThat(verifier.verify("\u82b1\u5b50.foo.com", session)).isFalse
    assertThat(verifier.verify("a.b.foo.com", session)).isFalse
  }

  @Test fun verifyWilcardCnOnTld() {
    // It's the CA's responsibility to not issue broad-matching certificates!
    // CN=*.co.jp
    val session = session(
      """
      -----BEGIN CERTIFICATE-----
      MIIERjCCAy6gAwIBAgIJAIz+EYMBU6aVMA0GCSqGSIb3DQEBBQUAMIGiMQswCQYD
      VQQGEwJDQTELMAkGA1UECBMCQkMxEjAQBgNVBAcTCVZhbmNvdXZlcjEWMBQGA1UE
      ChMNd3d3LmN1Y2JjLmNvbTEUMBIGA1UECxQLY29tbW9uc19zc2wxHTAbBgNVBAMU
      FGRlbW9faW50ZXJtZWRpYXRlX2NhMSUwIwYJKoZIhvcNAQkBFhZqdWxpdXNkYXZp
      ZXNAZ21haWwuY29tMB4XDTA2MTIxMTE2MTYzMFoXDTI4MTEwNTE2MTYzMFowgaQx
      CzAJBgNVBAYTAlVTMREwDwYDVQQIEwhNYXJ5bGFuZDEUMBIGA1UEBxMLRm9yZXN0
      IEhpbGwxFzAVBgNVBAoTDmh0dHBjb21wb25lbnRzMRowGAYDVQQLExF0ZXN0IGNl
      cnRpZmljYXRlczEQMA4GA1UEAxQHKi5jby5qcDElMCMGCSqGSIb3DQEJARYWanVs
      aXVzZGF2aWVzQGdtYWlsLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
      ggEBAMhjr5aCPoyp0R1iroWAfnEyBMGYWoCidH96yGPFjYLowez5aYKY1IOKTY2B
      lYho4O84X244QrZTRl8kQbYtxnGh4gSCD+Z8gjZ/gMvLUlhqOb+WXPAUHMB39GRy
      zerA/ZtrlUqf+lKo0uWcocxeRc771KN8cPH3nHZ0rV0Hx4ZAZy6U4xxObe4rtSVY
      07hNKXAb2odnVqgzcYiDkLV8ilvEmoNWMWrp8UBqkTcpEhYhCYp3cTkgJwMSuqv8
      BqnGd87xQU3FVZI4tbtkB+KzjD9zz8QCDJAfDjZHR03KNQ5mxOgXwxwKw6lGMaiV
      JTxpTKqym93whYk93l3ocEe55c0CAwEAAaN7MHkwCQYDVR0TBAIwADAsBglghkgB
      hvhCAQ0EHxYdT3BlblNTTCBHZW5lcmF0ZWQgQ2VydGlmaWNhdGUwHQYDVR0OBBYE
      FJ8Ud78/OrbKOIJCSBYs2tDLXofYMB8GA1UdIwQYMBaAFHua2o+QmU5S0qzbswNS
      yoemDT4NMA0GCSqGSIb3DQEBBQUAA4IBAQA0sWglVlMx2zNGvUqFC73XtREwii53
      CfMM6mtf2+f3k/d8KXhLNySrg8RRlN11zgmpPaLtbdTLrmG4UdAHHYr8O4y2BBmE
      1cxNfGxxechgF8HX10QV4dkyzp6Z1cfwvCeMrT5G/V1pejago0ayXx+GPLbWlNeZ
      S+Kl0m3p+QplXujtwG5fYcIpaGpiYraBLx3Tadih39QN65CnAh/zRDhLCUzKyt9l
      UGPLEUDzRHMPHLnSqT1n5UU5UDRytbjJPXzF+l/+WZIsanefWLsxnkgAuZe/oMMF
      EJMryEzOjg4Tfuc5qM0EXoPcQ/JlheaxZ40p2IyHqbsWV4MRYuFH4bkM
      -----END CERTIFICATE-----
      """.trimIndent()
    )
    assertThat(verifier.verify("foo.co.jp", session)).isFalse
    assertThat(verifier.verify("\u82b1\u5b50.co.jp", session)).isFalse
  }

  /**
   * Previously ignored due to incompatibilities between Android and Java on how non-ASCII subject
   * alt names are parsed. Android fails to parse these, which means we fall back to the CN.
   * The RI does parse them, so the CN is unused.
   */
  @Test fun testWilcardNonAsciiSubjectAlt() {
    // CN=*.foo.com, subjectAlt=*.bar.com, subjectAlt=*.花子.co.jp
    // (*.hanako.co.jp in kanji)
    val session = session(
      """
      -----BEGIN CERTIFICATE-----
      MIIEcDCCA1igAwIBAgIJAIz+EYMBU6aWMA0GCSqGSIb3DQEBBQUAMIGiMQswCQYD
      VQQGEwJDQTELMAkGA1UECBMCQkMxEjAQBgNVBAcTCVZhbmNvdXZlcjEWMBQGA1UE
      ChMNd3d3LmN1Y2JjLmNvbTEUMBIGA1UECxQLY29tbW9uc19zc2wxHTAbBgNVBAMU
      FGRlbW9faW50ZXJtZWRpYXRlX2NhMSUwIwYJKoZIhvcNAQkBFhZqdWxpdXNkYXZp
      ZXNAZ21haWwuY29tMB4XDTA2MTIxMTE2MTczMVoXDTI4MTEwNTE2MTczMVowgaYx
      CzAJBgNVBAYTAlVTMREwDwYDVQQIEwhNYXJ5bGFuZDEUMBIGA1UEBxMLRm9yZXN0
      IEhpbGwxFzAVBgNVBAoTDmh0dHBjb21wb25lbnRzMRowGAYDVQQLExF0ZXN0IGNl
      cnRpZmljYXRlczESMBAGA1UEAxQJKi5mb28uY29tMSUwIwYJKoZIhvcNAQkBFhZq
      dWxpdXNkYXZpZXNAZ21haWwuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
      CgKCAQEAyGOvloI+jKnRHWKuhYB+cTIEwZhagKJ0f3rIY8WNgujB7PlpgpjUg4pN
      jYGViGjg7zhfbjhCtlNGXyRBti3GcaHiBIIP5nyCNn+Ay8tSWGo5v5Zc8BQcwHf0
      ZHLN6sD9m2uVSp/6UqjS5ZyhzF5FzvvUo3xw8fecdnStXQfHhkBnLpTjHE5t7iu1
      JVjTuE0pcBvah2dWqDNxiIOQtXyKW8Sag1YxaunxQGqRNykSFiEJindxOSAnAxK6
      q/wGqcZ3zvFBTcVVkji1u2QH4rOMP3PPxAIMkB8ONkdHTco1DmbE6BfDHArDqUYx
      qJUlPGlMqrKb3fCFiT3eXehwR7nlzQIDAQABo4GiMIGfMAkGA1UdEwQCMAAwLAYJ
      YIZIAYb4QgENBB8WHU9wZW5TU0wgR2VuZXJhdGVkIENlcnRpZmljYXRlMB0GA1Ud
      DgQWBBSfFHe/Pzq2yjiCQkgWLNrQy16H2DAfBgNVHSMEGDAWgBR7mtqPkJlOUtKs
      27MDUsqHpg0+DTAkBgNVHREEHTAbggkqLmJhci5jb22CDiou6Iqx5a2QLmNvLmpw
      MA0GCSqGSIb3DQEBBQUAA4IBAQBobWC+D5/lx6YhX64CwZ26XLjxaE0S415ajbBq
      DK7lz+Rg7zOE3GsTAMi+ldUYnhyz0wDiXB8UwKXl0SDToB2Z4GOgqQjAqoMmrP0u
      WB6Y6dpkfd1qDRUzI120zPYgSdsXjHW9q2H77iV238hqIU7qCvEz+lfqqWEY504z
      hYNlknbUnR525ItosEVwXFBJTkZ3Yw8gg02c19yi8TAh5Li3Ad8XQmmSJMWBV4XK
      qFr0AIZKBlg6NZZFf/0dP9zcKhzSriW27bY0XfzA6GSiRDXrDjgXq6baRT6YwgIg
      pgJsDbJtZfHnV1nd3M6zOtQPm1TIQpNmMMMd/DPrGcUQerD3
      -----END CERTIFICATE-----
      """.trimIndent()
    )
    val peerCertificate = session.peerCertificates[0] as X509Certificate
    if (isAndroid || platform.isConscrypt()) {
      assertThat(certificateSANs(peerCertificate)).containsExactly("*.bar.com")
    } else {
      assertThat(certificateSANs(peerCertificate))
        .containsExactly("*.bar.com", "*.������.co.jp")
    }

    // try the foo.com variations
    assertThat(verifier.verify("foo.com", session)).isFalse
    assertThat(verifier.verify("www.foo.com", session)).isFalse
    assertThat(verifier.verify("\u82b1\u5b50.foo.com", session)).isFalse
    assertThat(verifier.verify("a.b.foo.com", session)).isFalse
    // these checks test alternative subjects. The test data contains an
    // alternative subject starting with a japanese kanji character. This is
    // not supported by Android because the underlying implementation from
    // harmony follows the definition from rfc 1034 page 10 for alternative
    // subject names. This causes the code to drop all alternative subjects.
    assertThat(verifier.verify("bar.com", session)).isFalse
    assertThat(verifier.verify("www.bar.com", session)).isTrue
    assertThat(verifier.verify("\u82b1\u5b50.bar.com", session)).isFalse
    assertThat(verifier.verify("a.b.bar.com", session)).isFalse
  }

  @Test fun subjectAltUsesLocalDomainAndIp() {
    // cat cert.cnf
    // [req]
    // distinguished_name=distinguished_name
    // req_extensions=req_extensions
    // x509_extensions=x509_extensions
    // [distinguished_name]
    // [req_extensions]
    // [x509_extensions]
    // subjectAltName=DNS:localhost.localdomain,DNS:localhost,IP:127.0.0.1
    //
    // $ openssl req -x509 -nodes -days 36500 -subj '/CN=localhost' -config ./cert.cnf \
    //     -newkey rsa:512 -out cert.pem
    val certificate = certificate(
      """
      -----BEGIN CERTIFICATE-----
      MIIBWDCCAQKgAwIBAgIJANS1EtICX2AZMA0GCSqGSIb3DQEBBQUAMBQxEjAQBgNV
      BAMTCWxvY2FsaG9zdDAgFw0xMjAxMDIxOTA4NThaGA8yMTExMTIwOTE5MDg1OFow
      FDESMBAGA1UEAxMJbG9jYWxob3N0MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAPpt
      atK8r4/hf4hSIs0os/BSlQLbRBaK9AfBReM4QdAklcQqe6CHsStKfI8pp0zs7Ptg
      PmMdpbttL0O7mUboBC8CAwEAAaM1MDMwMQYDVR0RBCowKIIVbG9jYWxob3N0Lmxv
      Y2FsZG9tYWlugglsb2NhbGhvc3SHBH8AAAEwDQYJKoZIhvcNAQEFBQADQQD0ntfL
      DCzOCv9Ma6Lv5o5jcYWVxvBSTsnt22hsJpWD1K7iY9lbkLwl0ivn73pG2evsAn9G
      X8YKH52fnHsCrhSD
      -----END CERTIFICATE-----
      """.trimIndent()
    )
    assertThat(certificate.subjectX500Principal).isEqualTo(
      X500Principal("CN=localhost")
    )
    val session = FakeSSLSession(certificate)
    assertThat(verifier.verify("localhost", session)).isTrue
    assertThat(verifier.verify("localhost.localdomain", session)).isTrue
    assertThat(verifier.verify("local.host", session)).isFalse
    assertThat(verifier.verify("127.0.0.1", session)).isTrue
    assertThat(verifier.verify("127.0.0.2", session)).isFalse
  }

  @Test fun wildcardsCannotMatchIpAddresses() {
    // openssl req -x509 -nodes -days 36500 -subj '/CN=*.0.0.1' -newkey rsa:512 -out cert.pem
    val session = session(
      """
      -----BEGIN CERTIFICATE-----
      MIIBkjCCATygAwIBAgIJAMdemqOwd/BEMA0GCSqGSIb3DQEBBQUAMBIxEDAOBgNV
      BAMUByouMC4wLjEwIBcNMTAxMjIwMTY0NDI1WhgPMjExMDExMjYxNjQ0MjVaMBIx
      EDAOBgNVBAMUByouMC4wLjEwXDANBgkqhkiG9w0BAQEFAANLADBIAkEAqY8c9Qrt
      YPWCvb7lclI+aDHM6fgbJcHsS9Zg8nUOh5dWrS7AgeA25wyaokFl4plBbbHQe2j+
      cCjsRiJIcQo9HwIDAQABo3MwcTAdBgNVHQ4EFgQUJ436TZPJvwCBKklZZqIvt1Yt
      JjEwQgYDVR0jBDswOYAUJ436TZPJvwCBKklZZqIvt1YtJjGhFqQUMBIxEDAOBgNV
      BAMUByouMC4wLjGCCQDHXpqjsHfwRDAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEB
      BQUAA0EAk9i88xdjWoewqvE+iMC9tD2obMchgFDaHH0ogxxiRaIKeEly3g0uGxIt
      fl2WRY8hb4x+zRrwsFaLEpdEvqcjOQ==
      -----END CERTIFICATE-----
      """.trimIndent()
    )
    assertThat(verifier.verify("127.0.0.1", session)).isFalse
  }

  /**
   * Earlier implementations of Android's hostname verifier required that wildcard names wouldn't
   * match "*.com" or similar. This was a nonstandard check that we've since dropped. It is the CA's
   * responsibility to not hand out certificates that match so broadly.
   */
  @Test fun wildcardsDoesNotNeedTwoDots() {
    // openssl req -x509 -nodes -days 36500 -subj '/CN=*.com' -newkey rsa:512 -out cert.pem
    val session = session(
      """
      -----BEGIN CERTIFICATE-----
      MIIBjDCCATagAwIBAgIJAOVulXCSu6HuMA0GCSqGSIb3DQEBBQUAMBAxDjAMBgNV
      BAMUBSouY29tMCAXDTEwMTIyMDE2NDkzOFoYDzIxMTAxMTI2MTY0OTM4WjAQMQ4w
      DAYDVQQDFAUqLmNvbTBcMA0GCSqGSIb3DQEBAQUAA0sAMEgCQQDJd8xqni+h7Iaz
      ypItivs9kPuiJUqVz+SuJ1C05SFc3PmlRCvwSIfhyD67fHcbMdl+A/LrIjhhKZJe
      1joO0+pFAgMBAAGjcTBvMB0GA1UdDgQWBBS4Iuzf5w8JdCp+EtBfdFNudf6+YzBA
      BgNVHSMEOTA3gBS4Iuzf5w8JdCp+EtBfdFNudf6+Y6EUpBIwEDEOMAwGA1UEAxQF
      Ki5jb22CCQDlbpVwkruh7jAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA0EA
      U6LFxmZr31lFyis2/T68PpjAppc0DpNQuA2m/Y7oTHBDi55Fw6HVHCw3lucuWZ5d
      qUYo4ES548JdpQtcLrW2sA==
      -----END CERTIFICATE-----
      """.trimIndent()
    )
    assertThat(verifier.verify("google.com", session)).isFalse
  }

  @Test fun subjectAltName() {
    // $ cat ./cert.cnf
    // [req]
    // distinguished_name=distinguished_name
    // req_extensions=req_extensions
    // x509_extensions=x509_extensions
    // [distinguished_name]
    // [req_extensions]
    // [x509_extensions]
    // subjectAltName=DNS:bar.com,DNS:baz.com
    //
    // $ openssl req -x509 -nodes -days 36500 -subj '/CN=foo.com' -config ./cert.cnf \
    //     -newkey rsa:512 -out cert.pem
    val session = session(
      """
      -----BEGIN CERTIFICATE-----
      MIIBPTCB6KADAgECAgkA7zoHaaqNGHQwDQYJKoZIhvcNAQEFBQAwEjEQMA4GA1UE
      AxMHZm9vLmNvbTAgFw0xMDEyMjAxODM5MzZaGA8yMTEwMTEyNjE4MzkzNlowEjEQ
      MA4GA1UEAxMHZm9vLmNvbTBcMA0GCSqGSIb3DQEBAQUAA0sAMEgCQQC+gmoSxF+8
      hbV+rgRQqHIJd50216OWQJbU3BvdlPbca779NYO4+UZWTFdBM8BdQqs3H4B5Agvp
      y7HeSff1F7XRAgMBAAGjHzAdMBsGA1UdEQQUMBKCB2Jhci5jb22CB2Jhei5jb20w
      DQYJKoZIhvcNAQEFBQADQQBXpZZPOY2Dy1lGG81JTr8L4or9jpKacD7n51eS8iqI
      oTznPNuXHU5bFN0AAGX2ij47f/EahqTpo5RdS95P4sVm
      -----END CERTIFICATE-----
      """.trimIndent()
    )
    assertThat(verifier.verify("foo.com", session)).isFalse
    assertThat(verifier.verify("bar.com", session)).isTrue
    assertThat(verifier.verify("baz.com", session)).isTrue
    assertThat(verifier.verify("a.foo.com", session)).isFalse
    assertThat(verifier.verify("quux.com", session)).isFalse
  }

  @Test fun subjectAltNameWithWildcard() {
    // $ cat ./cert.cnf
    // [req]
    // distinguished_name=distinguished_name
    // req_extensions=req_extensions
    // x509_extensions=x509_extensions
    // [distinguished_name]
    // [req_extensions]
    // [x509_extensions]
    // subjectAltName=DNS:bar.com,DNS:*.baz.com
    //
    // $ openssl req -x509 -nodes -days 36500 -subj '/CN=foo.com' -config ./cert.cnf \
    //     -newkey rsa:512 -out cert.pem
    val session = session(
      """
      -----BEGIN CERTIFICATE-----
      MIIBPzCB6qADAgECAgkAnv/7Jv5r7pMwDQYJKoZIhvcNAQEFBQAwEjEQMA4GA1UE
      AxMHZm9vLmNvbTAgFw0xMDEyMjAxODQ2MDFaGA8yMTEwMTEyNjE4NDYwMVowEjEQ
      MA4GA1UEAxMHZm9vLmNvbTBcMA0GCSqGSIb3DQEBAQUAA0sAMEgCQQDAz2YXnyog
      YdYLSFr/OEgSumtwqtZKJTB4wqTW/eKbBCEzxnyUMxWZIqUGu353PzwfOuWp2re3
      nvVV+QDYQlh9AgMBAAGjITAfMB0GA1UdEQQWMBSCB2Jhci5jb22CCSouYmF6LmNv
      bTANBgkqhkiG9w0BAQUFAANBAB8yrSl8zqy07i0SNYx2B/FnvQY734pxioaqFWfO
      Bqo1ZZl/9aPHEWIwBrxYNVB0SGu/kkbt/vxqOjzzrkXukmI=
      -----END CERTIFICATE-----
      """.trimIndent()
    )
    assertThat(verifier.verify("foo.com", session)).isFalse
    assertThat(verifier.verify("bar.com", session)).isTrue
    assertThat(verifier.verify("a.baz.com", session)).isTrue
    assertThat(verifier.verify("baz.com", session)).isFalse
    assertThat(verifier.verify("a.foo.com", session)).isFalse
    assertThat(verifier.verify("a.bar.com", session)).isFalse
    assertThat(verifier.verify("quux.com", session)).isFalse
  }

  @Test fun subjectAltNameWithIPAddresses() {
    // $ cat ./cert.cnf
    // [req]
    // distinguished_name=distinguished_name
    // req_extensions=req_extensions
    // x509_extensions=x509_extensions
    // [distinguished_name]
    // [req_extensions]
    // [x509_extensions]
    // subjectAltName=IP:0:0:0:0:0:0:0:1,IP:2a03:2880:f003:c07:face:b00c::2,IP:0::5,IP:192.168.1.1
    //
    // $ openssl req -x509 -nodes -days 36500 -subj '/CN=foo.com' -config ./cert.cnf \
    //     -newkey rsa:512 -out cert.pem
    val session = session(
      """
      -----BEGIN CERTIFICATE-----
      MIIBaDCCARKgAwIBAgIJALxN+AOBVGwQMA0GCSqGSIb3DQEBCwUAMBIxEDAOBgNV
      BAMMB2Zvby5jb20wIBcNMjAwMzIyMTEwNDI4WhgPMjEyMDAyMjcxMTA0MjhaMBIx
      EDAOBgNVBAMMB2Zvby5jb20wXDANBgkqhkiG9w0BAQEFAANLADBIAkEAlnVbVfQ9
      4aYjrPCcFuxOpjXuvyOc9Hcha4K7TfXyfsrjhAvCjCBIT/TiLOUVF3sx4yoCAtX8
      wmt404tTbKD6UwIDAQABo0kwRzBFBgNVHREEPjA8hxAAAAAAAAAAAAAAAAAAAAAB
      hxAqAyiA8AMMB/rOsAwAAAAChxAAAAAAAAAAAAAAAAAAAAAFhwTAqAEBMA0GCSqG
      SIb3DQEBCwUAA0EAPSOYHJh7hB4ElBqTCAFW+T5Y7mXsv9nQjBJ7w0YIw83V2PEI
      3KbBIyGTrqHD6lG8QGZy+yNkIcRlodG8OfQRUg==
      -----END CERTIFICATE-----
      """.trimIndent()
    )
    assertThat(verifier.verify("foo.com", session)).isFalse
    assertThat(verifier.verify("::1", session)).isTrue
    assertThat(verifier.verify("::2", session)).isFalse
    assertThat(verifier.verify("::5", session)).isTrue
    assertThat(verifier.verify("2a03:2880:f003:c07:face:b00c::2", session)).isTrue
    assertThat(verifier.verify("2a03:2880:f003:c07:face:b00c:0:2", session)).isTrue
    assertThat(verifier.verify("2a03:2880:f003:c07:FACE:B00C:0:2", session)).isTrue
    assertThat(verifier.verify("2a03:2880:f003:c07:face:b00c:0:3", session)).isFalse
    assertThat(verifier.verify("127.0.0.1", session)).isFalse
    assertThat(verifier.verify("192.168.1.1", session)).isTrue
    assertThat(verifier.verify("::ffff:192.168.1.1", session)).isTrue
    assertThat(verifier.verify("0:0:0:0:0:FFFF:C0A8:0101", session)).isTrue
  }

  @Test fun generatedCertificate() {
    val heldCertificate = HeldCertificate.Builder()
      .commonName("Foo Corp")
      .addSubjectAlternativeName("foo.com")
      .build()
    val session = session(heldCertificate.certificatePem())
    assertThat(verifier.verify("foo.com", session)).isTrue
    assertThat(verifier.verify("bar.com", session)).isFalse
  }

  @Test fun specialKInHostname() {
    // https://github.com/apache/httpcomponents-client/commit/303e435d7949652ea77a6c50df1c548682476b6e
    // https://www.gosecure.net/blog/2020/10/27/weakness-in-java-tls-host-verification/
    val heldCertificate = HeldCertificate.Builder()
      .commonName("Foo Corp")
      .addSubjectAlternativeName("k.com")
      .addSubjectAlternativeName("tel.com")
      .build()
    val 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("\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 fun specialKInCert() {
    // https://github.com/apache/httpcomponents-client/commit/303e435d7949652ea77a6c50df1c548682476b6e
    // https://www.gosecure.net/blog/2020/10/27/weakness-in-java-tls-host-verification/
    val heldCertificate = HeldCertificate.Builder()
      .commonName("Foo Corp")
      .addSubjectAlternativeName("\u2121.com")
      .addSubjectAlternativeName("\u212A.com")
      .build()
    val 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 fun specialKInExternalCert() {
    // OpenJDK related test.
    platform.assumeNotConscrypt()

    // $ 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
    val session = session(
      """
      -----BEGIN CERTIFICATE-----
      MIIBSDCB86ADAgECAhRLR4TGgXBegg0np90FZ1KPeWpDtjANBgkqhkiG9w0BAQsF
      ADASMRAwDgYDVQQDDAdmb28uY29tMCAXDTIwMTAyOTA2NTkwNVoYDzIxMjAxMDA1
      MDY1OTA1WjASMRAwDgYDVQQDDAdmb28uY29tMFwwDQYJKoZIhvcNAQEBBQADSwAw
      SAJBALQcTVW9aW++ClIV9/9iSzijsPvQGEu/FQOjIycSrSIheZyZmR8bluSNBq0C
      9fpalRKZb0S2tlCTi5WoX8d3K30CAwEAAaMfMB0wGwYDVR0RBBQwEoIH4oShLmNv
      bYIH4oSqLmNvbTANBgkqhkiG9w0BAQsFAANBAA1+/eDvSUGv78iEjNW+1w3OPAwt
      Ij1qLQ/YI8OogZPMk7YY46/ydWWp7UpD47zy/vKmm4pOc8Glc8MoDD6UADs=
      -----END CERTIFICATE-----
      """.trimIndent()
    )
    val peerCertificate = session.peerCertificates[0] as X509Certificate
    if (isAndroid) {
      assertThat(certificateSANs(peerCertificate)).containsExactly()
    } else {
      assertThat(certificateSANs(peerCertificate)).containsExactly("���.com", "���.com")
    }
    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
  }

  private fun certificateSANs(peerCertificate: X509Certificate): Stream {
    val subjectAlternativeNames = peerCertificate.subjectAlternativeNames
    return if (subjectAlternativeNames == null) {
      Stream.empty()
    } else {
      subjectAlternativeNames.stream().map { c: List<*> -> c[1] as String }
    }
  }

  @Test fun replacementCharacter() {
    // $ 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
    val session = session(
      """
      -----BEGIN CERTIFICATE-----
      MIIBSDCB86ADAgECAhRLR4TGgXBegg0np90FZ1KPeWpDtjANBgkqhkiG9w0BAQsF
      ADASMRAwDgYDVQQDDAdmb28uY29tMCAXDTIwMTAyOTA2NTkwNVoYDzIxMjAxMDA1
      MDY1OTA1WjASMRAwDgYDVQQDDAdmb28uY29tMFwwDQYJKoZIhvcNAQEBBQADSwAw
      SAJBALQcTVW9aW++ClIV9/9iSzijsPvQGEu/FQOjIycSrSIheZyZmR8bluSNBq0C
      9fpalRKZb0S2tlCTi5WoX8d3K30CAwEAAaMfMB0wGwYDVR0RBBQwEoIH4oShLmNv
      bYIH4oSqLmNvbTANBgkqhkiG9w0BAQsFAANBAA1+/eDvSUGv78iEjNW+1w3OPAwt
      Ij1qLQ/YI8OogZPMk7YY46/ydWWp7UpD47zy/vKmm4pOc8Glc8MoDD6UADs=
      -----END CERTIFICATE-----
      """.trimIndent()
    )

    // Replacement characters are deliberate, from certificate loading.
    assertThat(verifier.verify("���.com", session)).isFalse
    assertThat(verifier.verify("℡.com", session)).isFalse
  }

  @Test fun thatCatchesErrorsWithBadSession() {
    val localVerifier = OkHttpClient().hostnameVerifier

    // Since this is public API, okhttp3.internal.tls.OkHostnameVerifier.verify is also
    assertThat(verifier).isInstanceOf(OkHostnameVerifier::class.java)
    val session = localhost().sslContext().createSSLEngine().session
    assertThat(localVerifier.verify("\uD83D\uDCA9.com", session)).isFalse
  }

  @Test fun verifyAsIpAddress() {
    // IPv4
    assertThat("127.0.0.1".canParseAsIpAddress()).isTrue
    assertThat("1.2.3.4".canParseAsIpAddress()).isTrue

    // IPv6
    assertThat("::1".canParseAsIpAddress()).isTrue
    assertThat("2001:db8::1".canParseAsIpAddress()).isTrue
    assertThat("::192.168.0.1".canParseAsIpAddress()).isTrue
    assertThat("::ffff:192.168.0.1".canParseAsIpAddress()).isTrue
    assertThat("FEDC:BA98:7654:3210:FEDC:BA98:7654:3210".canParseAsIpAddress()).isTrue
    assertThat("1080:0:0:0:8:800:200C:417A".canParseAsIpAddress()).isTrue
    assertThat("1080::8:800:200C:417A".canParseAsIpAddress()).isTrue
    assertThat("FF01::101".canParseAsIpAddress()).isTrue
    assertThat("0:0:0:0:0:0:13.1.68.3".canParseAsIpAddress()).isTrue
    assertThat("0:0:0:0:0:FFFF:129.144.52.38".canParseAsIpAddress()).isTrue
    assertThat("::13.1.68.3".canParseAsIpAddress()).isTrue
    assertThat("::FFFF:129.144.52.38".canParseAsIpAddress()).isTrue

    // Hostnames
    assertThat("go".canParseAsIpAddress()).isFalse
    assertThat("localhost".canParseAsIpAddress()).isFalse
    assertThat("squareup.com".canParseAsIpAddress()).isFalse
    assertThat("www.nintendo.co.jp".canParseAsIpAddress()).isFalse
  }

  private fun certificate(certificate: String): X509Certificate {
    return CertificateFactory.getInstance("X.509")
      .generateCertificate(ByteArrayInputStream(certificate.toByteArray()))
      as X509Certificate
  }

  private fun session(certificate: String): SSLSession = FakeSSLSession(certificate(certificate))
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy