/* * Copyright (C) 2010 The Android Open Source Project * * 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 libcore.java.security.cert; import com.android.org.bouncycastle.asn1.x509.BasicConstraints; import com.android.org.bouncycastle.asn1.x509.X509Extensions; import com.android.org.bouncycastle.x509.X509V3CertificateGenerator; import com.android.org.bouncycastle.x509.extension.AuthorityKeyIdentifierStructure; import com.android.org.bouncycastle.x509.extension.SubjectKeyIdentifierStructure; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.OptionalDataException; import java.io.StreamCorruptedException; import java.math.BigInteger; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.PrivateKey; import java.security.Provider; import java.security.Security; import java.security.cert.CertPath; import java.security.cert.Certificate; import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; import java.util.GregorianCalendar; import java.util.Iterator; import java.util.List; import java.util.TimeZone; import javax.security.auth.x500.X500Principal; import junit.framework.TestCase; import libcore.java.security.StandardNames; public class CertificateFactoryTest extends TestCase { private static final String VALID_CERTIFICATE_PEM = "-----BEGIN CERTIFICATE-----\n" + "MIIDITCCAoqgAwIBAgIQL9+89q6RUm0PmqPfQDQ+mjANBgkqhkiG9w0BAQUFADBM\n" + "MQswCQYDVQQGEwJaQTElMCMGA1UEChMcVGhhd3RlIENvbnN1bHRpbmcgKFB0eSkg\n" + "THRkLjEWMBQGA1UEAxMNVGhhd3RlIFNHQyBDQTAeFw0wOTEyMTgwMDAwMDBaFw0x\n" + "MTEyMTgyMzU5NTlaMGgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlh\n" + "MRYwFAYDVQQHFA1Nb3VudGFpbiBWaWV3MRMwEQYDVQQKFApHb29nbGUgSW5jMRcw\n" + "FQYDVQQDFA53d3cuZ29vZ2xlLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkC\n" + "gYEA6PmGD5D6htffvXImttdEAoN4c9kCKO+IRTn7EOh8rqk41XXGOOsKFQebg+jN\n" + "gtXj9xVoRaELGYW84u+E593y17iYwqG7tcFR39SDAqc9BkJb4SLD3muFXxzW2k6L\n" + "05vuuWciKh0R73mkszeK9P4Y/bz5RiNQl/Os/CRGK1w7t0UCAwEAAaOB5zCB5DAM\n" + "BgNVHRMBAf8EAjAAMDYGA1UdHwQvMC0wK6ApoCeGJWh0dHA6Ly9jcmwudGhhd3Rl\n" + "LmNvbS9UaGF3dGVTR0NDQS5jcmwwKAYDVR0lBCEwHwYIKwYBBQUHAwEGCCsGAQUF\n" + "BwMCBglghkgBhvhCBAEwcgYIKwYBBQUHAQEEZjBkMCIGCCsGAQUFBzABhhZodHRw\n" + "Oi8vb2NzcC50aGF3dGUuY29tMD4GCCsGAQUFBzAChjJodHRwOi8vd3d3LnRoYXd0\n" + "ZS5jb20vcmVwb3NpdG9yeS9UaGF3dGVfU0dDX0NBLmNydDANBgkqhkiG9w0BAQUF\n" + "AAOBgQCfQ89bxFApsb/isJr/aiEdLRLDLE5a+RLizrmCUi3nHX4adpaQedEkUjh5\n" + "u2ONgJd8IyAPkU0Wueru9G2Jysa9zCRo1kNbzipYvzwY4OA8Ys+WAi0oR1A04Se6\n" + "z5nRUP8pJcA2NhUzUnC+MY+f6H/nEQyNv4SgQhqAibAxWEEHXw==\n" + "-----END CERTIFICATE-----\n"; private static final String INVALID_CERTIFICATE_PEM = "-----BEGIN CERTIFICATE-----\n" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n" + "AAAAAAAA\n" + "-----END CERTIFICATE-----"; public void test_generateCertificate() throws Exception { Provider[] providers = Security.getProviders("CertificateFactory.X509"); for (Provider p : providers) { CertificateFactory cf = CertificateFactory.getInstance("X509", p); try { test_generateCertificate(cf); test_generateCertificate_InputStream_Offset_Correct(cf); test_generateCertificate_InputStream_Empty(cf); } catch (Exception e) { throw new Exception("Problem testing " + p.getName(), e); } } } private void test_generateCertificate(CertificateFactory cf) throws Exception { byte[] valid = VALID_CERTIFICATE_PEM.getBytes(); Certificate c = cf.generateCertificate(new ByteArrayInputStream(valid)); assertNotNull(c); try { byte[] invalid = INVALID_CERTIFICATE_PEM.getBytes(); cf.generateCertificate(new ByteArrayInputStream(invalid)); fail(); } catch (CertificateException expected) { } } private void test_generateCertificate_InputStream_Empty(CertificateFactory cf) throws Exception { try { Certificate c = cf.generateCertificate(new ByteArrayInputStream(new byte[0])); if (!"BC".equals(cf.getProvider().getName())) { fail("should throw CertificateException: " + cf.getProvider().getName()); } assertNull(c); } catch (CertificateException e) { if ("BC".equals(cf.getProvider().getName())) { fail("should return null: " + cf.getProvider().getName()); } } } private void test_generateCertificate_InputStream_Offset_Correct(CertificateFactory cf) throws Exception { byte[] valid = VALID_CERTIFICATE_PEM.getBytes(); byte[] doubleCertificateData = new byte[valid.length * 2]; System.arraycopy(valid, 0, doubleCertificateData, 0, valid.length); System.arraycopy(valid, 0, doubleCertificateData, valid.length, valid.length); MeasuredInputStream certStream = new MeasuredInputStream(new ByteArrayInputStream( doubleCertificateData)); Certificate certificate = cf.generateCertificate(certStream); assertNotNull(certificate); assertEquals(valid.length, certStream.getCount()); } /** * Proxy that counts the number of bytes read from an InputStream. */ private static class MeasuredInputStream extends InputStream { private long mCount = 0; private long mMarked = 0; private InputStream mStream; public MeasuredInputStream(InputStream is) { mStream = is; } public long getCount() { return mCount; } @Override public int read() throws IOException { int nextByte = mStream.read(); mCount++; return nextByte; } @Override public int read(byte[] buffer) throws IOException { int count = mStream.read(buffer); mCount += count; return count; } @Override public int read(byte[] buffer, int offset, int length) throws IOException { int count = mStream.read(buffer, offset, length); mCount += count; return count; } @Override public long skip(long byteCount) throws IOException { long count = mStream.skip(byteCount); mCount += count; return count; } @Override public int available() throws IOException { return mStream.available(); } @Override public void close() throws IOException { mStream.close(); } @Override public void mark(int readlimit) { mMarked = mCount; mStream.mark(readlimit); } @Override public boolean markSupported() { return mStream.markSupported(); } @Override public synchronized void reset() throws IOException { mCount = mMarked; mStream.reset(); } } /* CertPath tests */ public void testGenerateCertPath() throws Exception { KeyHolder ca = generateCertificate(true, null); KeyHolder cert1 = generateCertificate(true, ca); KeyHolder cert2 = generateCertificate(false, cert1); KeyHolder cert3 = generateCertificate(false, cert2); List<X509Certificate> certs = new ArrayList<X509Certificate>(); certs.add(cert3.certificate); certs.add(cert2.certificate); certs.add(cert1.certificate); List<X509Certificate> duplicatedCerts = new ArrayList<X509Certificate>(certs); duplicatedCerts.add(cert2.certificate); Provider[] providers = Security.getProviders("CertificateFactory.X509"); for (Provider p : providers) { final CertificateFactory cf = CertificateFactory.getInstance("X.509", p); // Duplicate certificates can cause an exception. { final CertPath duplicatedPath = cf.generateCertPath(duplicatedCerts); try { duplicatedPath.getEncoded(); if (StandardNames.IS_RI) { fail("duplicate certificates should cause failure: " + p.getName()); } } catch (CertificateEncodingException expected) { if (!StandardNames.IS_RI) { fail("duplicate certificates should pass: " + p.getName()); } } } testCertPathEncoding(cf, certs, null); /* Make sure all encoding entries are the same. */ final Iterator<String> it1 = cf.getCertPathEncodings(); final Iterator<String> it2 = cf.generateCertPath(certs).getEncodings(); for (;;) { assertEquals(p.getName(), it1.hasNext(), it2.hasNext()); if (!it1.hasNext()) { break; } String encoding = it1.next(); assertEquals(p.getName(), encoding, it2.next()); try { it1.remove(); fail("Should not be able to remove from iterator"); } catch (UnsupportedOperationException expected) { } try { it2.remove(); fail("Should not be able to remove from iterator"); } catch (UnsupportedOperationException expected) { } /* Now test using this encoding. */ testCertPathEncoding(cf, certs, encoding); } } } private void testCertPathEncoding(CertificateFactory cf, List<X509Certificate> expectedCerts, String encoding) throws Exception { final String providerName = cf.getProvider().getName() + "[" + encoding + "]"; final CertPath pathFromList = cf.generateCertPath(expectedCerts); // Create a copy we can modify and discard. final byte[] encodedCopy; if (encoding == null) { encodedCopy = pathFromList.getEncoded(); assertNotNull(providerName, encodedCopy); // check idempotence assertEquals(providerName, Arrays.toString(pathFromList.getEncoded()), Arrays.toString(encodedCopy)); } else { encodedCopy = pathFromList.getEncoded(encoding); assertNotNull(providerName, encodedCopy); // check idempotence assertEquals(providerName, Arrays.toString(pathFromList.getEncoded(encoding)), Arrays.toString(encodedCopy)); } // Try to modify byte array. encodedCopy[0] ^= (byte) 0xFF; // Get a real copy we will use if the test proceeds. final byte[] encoded; if (encoding == null) { encoded = pathFromList.getEncoded(); assertNotNull(providerName, encodedCopy); // check idempotence assertEquals(providerName, Arrays.toString(pathFromList.getEncoded()), Arrays.toString(encoded)); } else { encoded = pathFromList.getEncoded(encoding); assertNotNull(providerName, encodedCopy); // check idempotence assertEquals(providerName, Arrays.toString(pathFromList.getEncoded(encoding)), Arrays.toString(encoded)); } assertFalse(providerName, Arrays.toString(encoded).equals(Arrays.toString(encodedCopy))); encodedCopy[0] ^= (byte) 0xFF; assertEquals(providerName, Arrays.toString(encoded), Arrays.toString(encodedCopy)); final CertPath actualPath; if (encoding == null) { actualPath = cf.generateCertPath(new ByteArrayInputStream(encoded)); } else { actualPath = cf.generateCertPath(new ByteArrayInputStream(encoded), encoding); } // PKCS7 certificate bags are not guaranteed to be in order. final List<? extends Certificate> actualCerts; if (!"PKCS7".equals(encoding)) { actualCerts = actualPath.getCertificates(); assertEquals(providerName, expectedCerts, actualCerts); } else { actualCerts = pathFromList.getCertificates(); } try { actualCerts.remove(0); fail("List of certificate should be immutable"); } catch (UnsupportedOperationException expected) { } ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos); oos.writeObject(actualPath); oos.close(); byte[] serialized = baos.toByteArray(); ByteArrayInputStream bais = new ByteArrayInputStream(serialized); ObjectInputStream ois = new ObjectInputStream(bais); Object output = ois.readObject(); assertTrue(providerName, output instanceof CertPath); assertEquals(providerName, actualPath, (CertPath) output); } public static class KeyHolder { public X509Certificate certificate; public PrivateKey privateKey; } @SuppressWarnings("deprecation") private static KeyHolder generateCertificate(boolean isCa, KeyHolder issuer) throws Exception { Date startDate = new Date(); GregorianCalendar cal = new GregorianCalendar(); cal.setTimeZone(TimeZone.getTimeZone("UTC")); cal.set(2100, 0, 1, 0, 0, 0); // Jan 1, 2100 UTC Date expiryDate = cal.getTime(); KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA"); KeyPair keyPair = kpg.generateKeyPair(); BigInteger serial; X500Principal issuerPrincipal; X500Principal subjectPrincipal; PrivateKey caKey; if (issuer != null) { serial = issuer.certificate.getSerialNumber().add(BigInteger.ONE); subjectPrincipal = new X500Principal("CN=Test Certificate Serial #" + serial.toString()); issuerPrincipal = issuer.certificate.getSubjectX500Principal(); caKey = issuer.privateKey; } else { serial = BigInteger.ONE; subjectPrincipal = new X500Principal("CN=Test CA, O=Tests, C=US"); issuerPrincipal = subjectPrincipal; caKey = keyPair.getPrivate(); } BasicConstraints basicConstraints; if (isCa) { basicConstraints = new BasicConstraints(10 - serial.intValue()); } else { basicConstraints = new BasicConstraints(false); } X509V3CertificateGenerator certGen = new X509V3CertificateGenerator(); certGen.setSerialNumber(serial); certGen.setIssuerDN(issuerPrincipal); certGen.setNotBefore(startDate); certGen.setNotAfter(expiryDate); certGen.setSubjectDN(subjectPrincipal); certGen.setPublicKey(keyPair.getPublic()); certGen.setSignatureAlgorithm("SHA1withRSA"); if (issuer != null) { certGen.addExtension(X509Extensions.AuthorityKeyIdentifier, false, new AuthorityKeyIdentifierStructure(issuer.certificate)); } else { certGen.addExtension(X509Extensions.AuthorityKeyIdentifier, false, new AuthorityKeyIdentifierStructure(keyPair.getPublic())); } certGen.addExtension(X509Extensions.SubjectKeyIdentifier, false, new SubjectKeyIdentifierStructure(keyPair.getPublic())); certGen.addExtension(X509Extensions.BasicConstraints, true, basicConstraints); X509Certificate cert = certGen.generate(caKey); KeyHolder holder = new KeyHolder(); holder.certificate = cert; holder.privateKey = keyPair.getPrivate(); return holder; } }