1/*
2 * Copyright 2016 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package libcore.java.security.cert;
18
19import java.security.KeyStore.PrivateKeyEntry;
20import java.security.PrivateKey;
21import java.security.cert.CertPath;
22import java.security.cert.CertPathValidator;
23import java.security.cert.CertPathValidatorException;
24import java.security.cert.CertificateFactory;
25import java.security.cert.PKIXCertPathChecker;
26import java.security.cert.PKIXParameters;
27import java.security.cert.PKIXRevocationChecker;
28import java.security.cert.TrustAnchor;
29import java.security.cert.X509Certificate;
30import java.security.cert.PKIXRevocationChecker.Option;
31import java.util.ArrayList;
32import java.util.Collections;
33import java.util.Date;
34import java.util.List;
35import com.android.org.bouncycastle.asn1.x509.CRLReason;
36import com.android.org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
37import com.android.org.bouncycastle.cert.X509CertificateHolder;
38import com.android.org.bouncycastle.cert.jcajce.JcaX509CertificateHolder;
39import com.android.org.bouncycastle.cert.ocsp.BasicOCSPResp;
40import com.android.org.bouncycastle.cert.ocsp.BasicOCSPRespBuilder;
41import com.android.org.bouncycastle.cert.ocsp.CertificateID;
42import com.android.org.bouncycastle.cert.ocsp.CertificateStatus;
43import com.android.org.bouncycastle.cert.ocsp.OCSPResp;
44import com.android.org.bouncycastle.cert.ocsp.OCSPRespBuilder;
45import com.android.org.bouncycastle.cert.ocsp.RevokedStatus;
46import com.android.org.bouncycastle.operator.DigestCalculatorProvider;
47import com.android.org.bouncycastle.operator.bc.BcDigestCalculatorProvider;
48import com.android.org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
49import junit.framework.TestCase;
50import libcore.java.security.TestKeyStore;
51
52import dalvik.system.VMRuntime;
53import sun.security.jca.Providers;
54
55public class CertPathValidatorTest extends TestCase {
56
57    // Allow access to deprecated BC algorithms in this test, so we can ensure they
58    // continue to work
59    @Override
60    public void setUp() throws Exception {
61        super.setUp();
62        Providers.setMaximumAllowableApiLevelForBcDeprecation(
63                VMRuntime.getRuntime().getTargetSdkVersion());
64    }
65
66    @Override
67    public void tearDown() throws Exception {
68        Providers.setMaximumAllowableApiLevelForBcDeprecation(
69                Providers.DEFAULT_MAXIMUM_ALLOWABLE_TARGET_API_LEVEL_FOR_BC_DEPRECATION);
70        super.tearDown();
71    }
72
73    private OCSPResp generateOCSPResponse(X509Certificate serverCertJca, X509Certificate caCertJca,
74            PrivateKey caKey, CertificateStatus status) throws Exception {
75        X509CertificateHolder caCert = new JcaX509CertificateHolder(caCertJca);
76
77        DigestCalculatorProvider digCalcProv = new BcDigestCalculatorProvider();
78        BasicOCSPRespBuilder basicBuilder = new BasicOCSPRespBuilder(
79                SubjectPublicKeyInfo.getInstance(caCertJca.getPublicKey().getEncoded()),
80                digCalcProv.get(CertificateID.HASH_SHA1));
81
82        CertificateID certId = new CertificateID(digCalcProv.get(CertificateID.HASH_SHA1),
83                caCert, serverCertJca.getSerialNumber());
84
85        basicBuilder.addResponse(certId, status);
86
87        BasicOCSPResp resp = basicBuilder.build(
88                new JcaContentSignerBuilder("SHA1withRSA").build(caKey), null, new Date());
89
90        OCSPRespBuilder builder = new OCSPRespBuilder();
91        return builder.build(OCSPRespBuilder.SUCCESSFUL, resp);
92    }
93
94    private void runOCSPStapledTest(CertificateStatus certStatus, final boolean goodStatus)
95            throws Exception {
96        PrivateKeyEntry serverEntry = TestKeyStore.getServer().getPrivateKey("RSA", "RSA");
97        PrivateKeyEntry caEntry = TestKeyStore.getIntermediateCa().getPrivateKey("RSA", "RSA");
98        PrivateKeyEntry rootCaEntry = TestKeyStore.getRootCa().getPrivateKey("RSA", "RSA");
99
100        X509Certificate serverCert = (X509Certificate) serverEntry.getCertificate();
101        OCSPResp ocspResponse = generateOCSPResponse(serverCert,
102                (X509Certificate) caEntry.getCertificate(), caEntry.getPrivateKey(), certStatus);
103
104        PKIXParameters params = new PKIXParameters(Collections
105                .singleton(new TrustAnchor((X509Certificate) rootCaEntry.getCertificate(), null)));
106
107        // By default we shouldn't have a PKIXRevocationChecker already.
108        for (PKIXCertPathChecker checker : params.getCertPathCheckers()) {
109            assertFalse(checker instanceof PKIXRevocationChecker);
110        }
111
112        CertPathValidator cpv = CertPathValidator.getInstance("PKIX");
113
114        PKIXRevocationChecker revChecker = (PKIXRevocationChecker) cpv.getRevocationChecker();
115        revChecker.setOptions(Collections.singleton(Option.ONLY_END_ENTITY));
116        revChecker.setOcspResponses(
117                Collections.singletonMap(serverCert, ocspResponse.getEncoded()));
118
119        List<PKIXCertPathChecker> checkers = new ArrayList<>(params.getCertPathCheckers());
120        checkers.add(revChecker);
121        params.setCertPathCheckers(checkers);
122
123        ArrayList<X509Certificate> chain = new ArrayList<>();
124        chain.add(serverCert);
125        chain.add((X509Certificate) caEntry.getCertificate());
126
127        CertificateFactory cf = CertificateFactory.getInstance("X.509");
128        CertPath certPath = cf.generateCertPath(chain);
129
130        try {
131            cpv.validate(certPath, params);
132            assertTrue("should fail with failure OCSP status", goodStatus);
133        } catch (CertPathValidatorException maybeExpected) {
134            assertFalse("should not fail with good OCSP status", goodStatus);
135        }
136    }
137
138    public void test_OCSP_EndEntity_KeyCompromise_Failure() throws Exception {
139        runOCSPStapledTest(new RevokedStatus(new Date(), CRLReason.keyCompromise), false);
140    }
141
142    public void test_OCSP_EndEntity_Good_Success() throws Exception {
143        runOCSPStapledTest(CertificateStatus.GOOD, true);
144    }
145}
146