/* * Copyright (C) 2016 Square, Inc. * * Licensed 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; import java.security.GeneralSecurityException; import java.security.cert.Certificate; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.List; import javax.net.ssl.SSLPeerUnverifiedException; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.X509TrustManager; import okhttp3.internal.platform.Platform; import okhttp3.internal.tls.CertificateChainCleaner; import okhttp3.internal.tls.HeldCertificate; import okhttp3.internal.tls.SslClient; import org.junit.Test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; public final class CertificateChainCleanerTest { @Test public void equalsFromCertificate() throws Exception { HeldCertificate rootA = new HeldCertificate.Builder() .serialNumber("1") .build(); HeldCertificate rootB = new HeldCertificate.Builder() .serialNumber("2") .build(); assertEquals( CertificateChainCleaner.get(rootA.certificate, rootB.certificate), CertificateChainCleaner.get(rootB.certificate, rootA.certificate)); } @Test public void equalsFromTrustManager() throws Exception { SslClient client = new SslClient.Builder().build(); X509TrustManager x509TrustManager = client.trustManager; assertEquals( CertificateChainCleaner.get(x509TrustManager), CertificateChainCleaner.get(x509TrustManager)); } @Test public void normalizeSingleSelfSignedCertificate() throws Exception { HeldCertificate root = new HeldCertificate.Builder() .serialNumber("1") .build(); CertificateChainCleaner cleaner = CertificateChainCleaner.get(root.certificate); assertEquals(list(root), cleaner.clean(list(root), "hostname")); } @Test public void normalizeUnknownSelfSignedCertificate() throws Exception { HeldCertificate root = new HeldCertificate.Builder() .serialNumber("1") .build(); CertificateChainCleaner cleaner = CertificateChainCleaner.get(); try { cleaner.clean(list(root), "hostname"); fail(); } catch (SSLPeerUnverifiedException expected) { } } @Test public void orderedChainOfCertificatesWithRoot() throws Exception { HeldCertificate root = new HeldCertificate.Builder() .serialNumber("1") .build(); HeldCertificate certA = new HeldCertificate.Builder() .serialNumber("2") .issuedBy(root) .build(); HeldCertificate certB = new HeldCertificate.Builder() .serialNumber("3") .issuedBy(certA) .build(); CertificateChainCleaner cleaner = CertificateChainCleaner.get(root.certificate); assertEquals(list(certB, certA, root), cleaner.clean(list(certB, certA, root), "hostname")); } @Test public void orderedChainOfCertificatesWithoutRoot() throws Exception { HeldCertificate root = new HeldCertificate.Builder() .serialNumber("1") .build(); HeldCertificate certA = new HeldCertificate.Builder() .serialNumber("2") .issuedBy(root) .build(); HeldCertificate certB = new HeldCertificate.Builder() .serialNumber("3") .issuedBy(certA) .build(); CertificateChainCleaner cleaner = CertificateChainCleaner.get(root.certificate); assertEquals(list(certB, certA, root), cleaner.clean(list(certB, certA), "hostname")); // Root is added! } @Test public void unorderedChainOfCertificatesWithRoot() throws Exception { HeldCertificate root = new HeldCertificate.Builder() .serialNumber("1") .build(); HeldCertificate certA = new HeldCertificate.Builder() .serialNumber("2") .issuedBy(root) .build(); HeldCertificate certB = new HeldCertificate.Builder() .serialNumber("3") .issuedBy(certA) .build(); HeldCertificate certC = new HeldCertificate.Builder() .serialNumber("4") .issuedBy(certB) .build(); CertificateChainCleaner cleaner = CertificateChainCleaner.get(root.certificate); assertEquals(list(certC, certB, certA, root), cleaner.clean(list(certC, certA, root, certB), "hostname")); } @Test public void unorderedChainOfCertificatesWithoutRoot() throws Exception { HeldCertificate root = new HeldCertificate.Builder() .serialNumber("1") .build(); HeldCertificate certA = new HeldCertificate.Builder() .serialNumber("2") .issuedBy(root) .build(); HeldCertificate certB = new HeldCertificate.Builder() .serialNumber("3") .issuedBy(certA) .build(); HeldCertificate certC = new HeldCertificate.Builder() .serialNumber("4") .issuedBy(certB) .build(); CertificateChainCleaner cleaner = CertificateChainCleaner.get(root.certificate); assertEquals(list(certC, certB, certA, root), cleaner.clean(list(certC, certA, certB), "hostname")); } @Test public void unrelatedCertificatesAreOmitted() throws Exception { HeldCertificate root = new HeldCertificate.Builder() .serialNumber("1") .build(); HeldCertificate certA = new HeldCertificate.Builder() .serialNumber("2") .issuedBy(root) .build(); HeldCertificate certB = new HeldCertificate.Builder() .serialNumber("3") .issuedBy(certA) .build(); HeldCertificate certUnnecessary = new HeldCertificate.Builder() .serialNumber("4") .build(); CertificateChainCleaner cleaner = CertificateChainCleaner.get(root.certificate); assertEquals(list(certB, certA, root), cleaner.clean(list(certB, certUnnecessary, certA, root), "hostname")); } @Test public void chainGoesAllTheWayToSelfSignedRoot() throws Exception { HeldCertificate selfSigned = new HeldCertificate.Builder() .serialNumber("1") .build(); HeldCertificate trusted = new HeldCertificate.Builder() .serialNumber("2") .issuedBy(selfSigned) .build(); HeldCertificate certA = new HeldCertificate.Builder() .serialNumber("3") .issuedBy(trusted) .build(); HeldCertificate certB = new HeldCertificate.Builder() .serialNumber("4") .issuedBy(certA) .build(); CertificateChainCleaner cleaner = CertificateChainCleaner.get( selfSigned.certificate, trusted.certificate); assertEquals(list(certB, certA, trusted, selfSigned), cleaner.clean(list(certB, certA), "hostname")); assertEquals(list(certB, certA, trusted, selfSigned), cleaner.clean(list(certB, certA, trusted), "hostname")); assertEquals(list(certB, certA, trusted, selfSigned), cleaner.clean(list(certB, certA, trusted, selfSigned), "hostname")); } @Test public void trustedRootNotSelfSigned() throws Exception { HeldCertificate unknownSigner = new HeldCertificate.Builder() .serialNumber("1") .build(); HeldCertificate trusted = new HeldCertificate.Builder() .issuedBy(unknownSigner) .serialNumber("2") .build(); HeldCertificate intermediateCa = new HeldCertificate.Builder() .issuedBy(trusted) .serialNumber("3") .build(); HeldCertificate certificate = new HeldCertificate.Builder() .issuedBy(intermediateCa) .serialNumber("4") .build(); CertificateChainCleaner cleaner = CertificateChainCleaner.get(trusted.certificate); assertEquals(list(certificate, intermediateCa, trusted), cleaner.clean(list(certificate, intermediateCa), "hostname")); assertEquals(list(certificate, intermediateCa, trusted), cleaner.clean(list(certificate, intermediateCa, trusted), "hostname")); } @Test public void chainMaxLength() throws Exception { List<HeldCertificate> heldCertificates = chainOfLength(10); List<Certificate> certificates = new ArrayList<>(); for (HeldCertificate heldCertificate : heldCertificates) { certificates.add(heldCertificate.certificate); } X509Certificate root = heldCertificates.get(heldCertificates.size() - 1).certificate; CertificateChainCleaner cleaner = CertificateChainCleaner.get(root); assertEquals(certificates, cleaner.clean(certificates, "hostname")); assertEquals(certificates, cleaner.clean(certificates.subList(0, 9), "hostname")); } @Test public void chainTooLong() throws Exception { List<HeldCertificate> heldCertificates = chainOfLength(11); List<Certificate> certificates = new ArrayList<>(); for (HeldCertificate heldCertificate : heldCertificates) { certificates.add(heldCertificate.certificate); } X509Certificate root = heldCertificates.get(heldCertificates.size() - 1).certificate; CertificateChainCleaner cleaner = CertificateChainCleaner.get(root); try { cleaner.clean(certificates, "hostname"); fail(); } catch (SSLPeerUnverifiedException expected) { } } /** Returns a chain starting at the leaf certificate and progressing to the root. */ private List<HeldCertificate> chainOfLength(int length) throws GeneralSecurityException { List<HeldCertificate> result = new ArrayList<>(); for (int i = 1; i <= length; i++) { result.add(0, new HeldCertificate.Builder() .issuedBy(!result.isEmpty() ? result.get(0) : null) .serialNumber(Integer.toString(i)) .build()); } return result; } private List<Certificate> list(HeldCertificate... heldCertificates) { List<Certificate> result = new ArrayList<>(); for (HeldCertificate heldCertificate : heldCertificates) { result.add(heldCertificate.certificate); } return result; } }