I am using jscep in a mobile device management project. Jscep uses bouncy castle as the security provider and I have done the same in my project. I have created a few simple static methods to create certificates using BC. These have been tested and work as expected. My problem is related to the Java security provider. In the example below I create two certificates, a CA and end point.
On a successful enrollment the jscep client returns a CertStore but the provider is set to "SUN". The store contains the certificate chain for the two certificates mentioned above. If I verify certificates in the CertStore and against the originals it passes, however if I verify against the certificates in the CertStore it fails. More strange is the fact it does not always fail - it sometimes works.
However, if I set the provide to "BC" it always works. The original certificates always verify correctly as shown in the code below. This code does not use jscep but reproduces the issue. I set the provider in two locations and have added some comments in the code to illustrate the behavior with different provider settings.
package com.mdm.utils.test;
import java.io.ByteArrayInputStream;
import java.math.BigInteger;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Security;
import java.security.cert.CertStore;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.cert.CollectionCertStoreParameters;
import java.security.cert.X509Certificate;
import java.security.spec.RSAPrivateCrtKeySpec;
import java.security.spec.RSAPublicKeySpec;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.Iterator;
import javax.security.auth.x500.X500Principal;
import org.bouncycastle.asn1.x509.BasicConstraints;
import org.bouncycastle.asn1.x509.KeyUsage;
import org.bouncycastle.asn1.x509.X509Extension;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils;
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import static org.junit.Assert.fail;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ProviderIssueTest {
private static final Logger LOG = LoggerFactory.getLogger(X509CertificateGeneratorTest.class);
private static final String BC = BouncyCastleProvider.PROVIDER_NAME;
private static long serialNum = 1;
@Before
public void setUp() {
Security.addProvider(new BouncyCastleProvider());
}
@After
public void tearDown() throws Exception {
Security.removeProvider(BC);
}
/**
* Create a v3 self signed root certificate.
*/
public static X509Certificate createV3RootCA(PublicKey pubKey, PrivateKey privKey,
int durationInDays,
String subject, String issuer) throws Exception {
if (issuer == null)
issuer = subject;
// Mandatory
Calendar calendar = Calendar.getInstance();
Date notBefore = calendar.getTime();
calendar.add(Calendar.DATE, durationInDays);
Date notAfter = calendar.getTime();
BigInteger issuerSerialNumber = BigInteger.valueOf(serialNum++);
JcaX509v3CertificateBuilder certBuilder = new JcaX509v3CertificateBuilder(
new X500Principal(issuer),
issuerSerialNumber,
notBefore, notAfter,
new X500Principal(subject),
pubKey);
// Optional extensions
certBuilder.addExtension(X509Extension.basicConstraints, true, new BasicConstraints(true));
certBuilder.addExtension(X509Extension.keyUsage, true, new KeyUsage(KeyUsage.keyCertSign|KeyUsage.cRLSign|KeyUsage.digitalSignature));
// Signing
ContentSigner certSigner = new JcaContentSignerBuilder("SHA1WithRSA")
.setProvider(BC)
.build(privKey);
X509CertificateHolder certHolder = certBuilder.build(certSigner);
// Extract a JCA-compatible certificate
X509Certificate cert = new JcaX509CertificateConverter()
.setProvider(BC).getCertificate(certHolder);
cert.checkValidity(new Date());
cert.verify(pubKey);
return cert;
}
/**
* Generate a leaf certificate signed by a CA
*/
public static X509Certificate createCert(PublicKey pubKey, X509Certificate caCert, PrivateKey caPrivKey,
int durationInDays,
String subject) throws Exception {
// Mandatory
Calendar calendar = Calendar.getInstance();
Date notBefore = calendar.getTime();
calendar.add(Calendar.DATE, durationInDays);
Date notAfter = calendar.getTime();
JcaX509v3CertificateBuilder certBuilder = new JcaX509v3CertificateBuilder(
caCert.getSubjectX500Principal(),
BigInteger.valueOf(serialNum++),
notBefore, notAfter,
new X500Principal(subject),
pubKey);
// Optional extensions
JcaX509ExtensionUtils extUtils = new JcaX509ExtensionUtils();
certBuilder.addExtension(X509Extension.basicConstraints, false, new BasicConstraints(false));
certBuilder.addExtension(X509Extension.keyUsage, true, new KeyUsage(KeyUsage.digitalSignature|KeyUsage.keyEncipherment));
certBuilder.addExtension(X509Extension.subjectKeyIdentifier, false, extUtils.createSubjectKeyIdentifier(pubKey));
certBuilder.addExtension(X509Extension.authorityKeyIdentifier, false, extUtils.createAuthorityKeyIdentifier(caCert));
// Signing
ContentSigner certSigner = new JcaContentSignerBuilder("SHA1WithRSA")
.setProvider(BC).build(caPrivKey);
X509CertificateHolder certHolder = certBuilder.build(certSigner);
// Extract a JCA-compatible certificate
X509Certificate cert = new JcaX509CertificateConverter()
.setProvider(BC).getCertificate(certHolder);
cert.checkValidity(new Date());
cert.verify(caCert.getPublicKey());
return cert;
}
@Test
public void test() throws Exception {
KeyFactory fact = KeyFactory.getInstance("RSA", BC);
PrivateKey caPriv = fact.generatePrivate(
new RSAPrivateCrtKeySpec(
new BigInteger("a5226e241a19f5b796ef2326f4f580b1e5cbc05360a7fd94fd8d59013115e077a422beb4904c5e57f0d9827a0da98b337ab8d47a2b24f77d83f9689e9b43af6b23bf39a1e4e87d8ce9f7d68b8dd50ffec1d34b25833848325ed035d3a1ddeaf62fe5a184dec918d7c2e8b89b17b057a9af359280956dc2a393be6e9a04517b25", 16),
new BigInteger("10001", 16),
new BigInteger("6ff223507e11532e1e380750858758b340e11b846a65f7d664fcc975b15cef4aac0e91d1be70c7143ec6755960a1ab283eedc5bcfc3a973c9397248141286565d479dd57d9bc01d4dec645dd1ae01590671315ec6f9bcde606707255382fcb363744a8bcda3c7a3c2e4015d450ed4aafb675ae277ddcf0e779165125a84f6681", 16),
new BigInteger("f8e745cf5388418a0f038b425095aa8ce3cae42764c15d6f91021a0b6fe0746653428ac95c88ce127deae745521805b6a53da780b56c3f4d15f0c88a85a19609", 16),
new BigInteger("a9d7bc0903893d8116ad8df22e425df382f895d47c0a47d7ea182e9a6221f3d1b27cdfd278960d8cc65699a5c1e5e17197805c9954ff6c37c19a0d9e2241a33d", 16),
new BigInteger("88181ca9a228ec7d0a7c8b9674ed80d58c701194209941f790b82f797570aaf4902de028fdb9a7c3a0a9e24e9af69b99247cb3abc2872f8d7ca3ad636071dbd1", 16),
new BigInteger("5f024cb0aa26ba9e1cc68772238882aff6e30245b401b840c33635d3acf39b4601d7b30934e593bcdd32928ed411b97466b0aa9c279d1eb76df8b48772584f6d", 16),
new BigInteger("e9774efb165c4309e7c7f32603d882d2e8b728887ddb50ee2c2e89591d192b64058699d3251e01348ee24dd23669aec43f1b4e16266950f6268e632242b7d500", 16)));
PublicKey caPub = fact.generatePublic(
new RSAPublicKeySpec(
new BigInteger("a5226e241a19f5b796ef2326f4f580b1e5cbc05360a7fd94fd8d59013115e077a422beb4904c5e57f0d9827a0da98b337ab8d47a2b24f77d83f9689e9b43af6b23bf39a1e4e87d8ce9f7d68b8dd50ffec1d34b25833848325ed035d3a1ddeaf62fe5a184dec918d7c2e8b89b17b057a9af359280956dc2a393be6e9a04517b25", 16),
new BigInteger("10001", 16)));
PublicKey usrPub = fact.generatePublic(
new RSAPublicKeySpec(
new BigInteger("84d4269505c38ba8c5fee8619cf0442eb55c31ae76ec430c1bbe3c82e48a1b56c6f2a3449edf044bcb7151b5df289182b685456f60f819ff7307478fe24f322c6afd4beae7bb4ad50c8bb26c9d0bd505cd91afb144003bea1d2c7fd743178d0141789aca69a5a97918dfccf7d82b25b1bf952cf06f9f432b338ddb773f79583dbbbeaf9fc4cf0878154fdcdfff160b3b5c1ed713990264ab97a3c0a5c617fe123395c03bf94ab24e3f7120ab7d95d06aa83ec9481566b1b6c2dcc9047a46abbf8ee43b32b5589edca36b3342073eb6bf8838a397363bf567640c1d0536961c125b81c0d31d09bd08171b1b6ca9343e09cfa7e3a6010e98d46da7cb6adccf52d5", 16),
new BigInteger("10001", 16)));
// Create self signed CA
X509Certificate caCert = createV3RootCA(
caPub,
caPriv,
365,
"CN=Root Test, C=US, ST=California, L=Woodside ,O=Acme Inc,OU=Root Certificate",
null); // set issuer=subject
X509Certificate usrCert = createCert(
usrPub,
caCert,
caPriv,
365,
"CN=Pablobill, C=US, ST=California, L=Woodside,O=Acme Inc.,OU=EndEntity Certificate");
// Always passes
caCert.verify(caCert.getPublicKey());
usrCert.verify(caCert.getPublicKey());
try {
// PROVIDER 1
CertificateFactory cf = CertificateFactory.getInstance("X.509", "SUN");
Certificate CA = cf.generateCertificate(new ByteArrayInputStream(caCert.getEncoded()));
Certificate UA = cf.generateCertificate(new ByteArrayInputStream(usrCert.getEncoded()));
CA.verify(CA.getPublicKey()); // This always works irrespective of the provider
UA.verify(CA.getPublicKey()); // This always works irrespective of the provider
ArrayList<Certificate> alist = new ArrayList<Certificate>(2);
alist.add(UA);
alist.add(CA);
// PROVIDER 2
CertStore certStore = CertStore.getInstance("Collection",
new CollectionCertStoreParameters(alist), "SUN");
Collection<?> certs = certStore.getCertificates(null);
String provider = certStore.getProvider().getName();
LOG.debug("Provider is {}", provider);
// Get chain from cert store
Iterator<?> iter = certs.iterator();
Certificate UB = (Certificate)iter.next();
Certificate CB = (Certificate)iter.next();
LOG.debug("UB.length={}, UA.length={}, UB ={}", UB.getEncoded().length, UA.getEncoded().length, UB);
LOG.debug("CB.length={}, CA.length={}, CB ={}", CB.getEncoded().length, CA.getEncoded().length, CB);
// This always works if provider 2 is "BC", provider 1 can be either "SUN" or "BC".
// Fails if provider 2 is "SUN" and provider 1 is "SUN"
UA.verify(CB.getPublicKey());
CA.verify(CB.getPublicKey());
// Works sometimes if provider 2 is "SUN", Always works if provider 2 is "BC"
UB.verify(CB.getPublicKey());
CB.verify(CB.getPublicKey());
LOG.debug("SUCCESS");
} catch (Exception e) {
e.printStackTrace();
fail();
}
}
}
I would have thought calling Certificate.getEncoded() would remove any provider dependence. The problem I have is Jscep returns a CertStore with "SUN" as the provider and the certificate chain cannot be validated with store entries.
Please read the comments below; I now understand the exact nature of this issue. When SUN is the provider the order in the CertStore is not consistent across runs of the test.
Is there a way to guarantee ordering?
Any help much appreciated.
DEBUG OUTPUT RUN 1 (SUCCESS)
0 [main] DEBUG com.mdm.utils.test.X509CertificateGeneratorTest - Provider is SUN
8 [main] DEBUG com.mdm.utils.test.X509CertificateGeneratorTest - UB.length=980, UA.length=980, UB =[
[
Version: V3
Subject: CN=Pablobill, C=US, ST=California, L=Woodside, O=Acme Inc., OU=EndEntity Certificate
Signature Algorithm: SHA1withRSA, OID = 1.2.840.113549.1.1.5
Key: Sun RSA public key, 2048 bits
modulus: 16768071670382525923108417071558186448345300080234882539261215442293489118744766516458998304300255531972593921338621155617570519295701287200918722384776808663209526068422690015419314730171909240597223060319385279562945357640946436608123561191766398078873400927250173773856327184499822450130773761828557700657093608566092380977746721299494533830480993371754648531497509681985981605034166444610796945239938191965934961812439864940235377352655354266302700246535173514307468185584585631338844720965267698182627932436044699321382289418637996460855280178953787469375899336175332362249696925445675694303599937295996964262613
public exponent: 65537
Validity: [From: Tue Nov 12 01:34:27 PST 2013,
To: Wed Nov 12 01:34:27 PST 2014]
Issuer: CN=Root Test, C=US, ST=California, L=Woodside, O=Acme Inc, OU=Root Certificate
SerialNumber: [ 02]
Certificate Extensions: 4
[1]: ObjectId: 2.5.29.35 Criticality=false
AuthorityKeyIdentifier [
KeyIdentifier [
0000: 12 05 9F B8 84 CA BC B6 1F 55 25 37 F4 4E 13 AC .........U%7.N..
0010: E1 DA AC C8 ....
]
[CN=Root Test, C=US, ST=California, L=Woodside, O=Acme Inc, OU=Root Certificate]
SerialNumber: [ 01]
]
[2]: ObjectId: 2.5.29.19 Criticality=false
BasicConstraints:[
CA:false
PathLen: undefined
]
[3]: ObjectId: 2.5.29.15 Criticality=true
KeyUsage [
DigitalSignature
Key_Encipherment
]
[4]: ObjectId: 2.5.29.14 Criticality=false
SubjectKeyIdentifier [
KeyIdentifier [
0000: F3 3D 00 4E D4 1D 72 F7 19 49 61 0A 91 7C AB 18 .=.N..r..Ia.....
0010: 54 56 F9 6F TV.o
]
]
]
Algorithm: [SHA1withRSA]
Signature:
0000: 2F 07 E3 59 5A E3 B6 9E 51 2C 1F 66 BA C1 A2 DE /..YZ...Q,.f....
0010: 11 D9 91 93 CD E1 E5 CC B1 CE 0C D2 42 93 E7 08 ............B...
0020: C1 AB 3C 50 43 D5 2D AB 4D C4 87 23 00 FB 92 7E ..<PC.-.M..#....
0030: EC DC B3 88 CB C3 9E 56 E2 DE 38 B9 01 E7 40 71 .......V..8...@q
0040: 4D 1F D6 F9 49 B6 09 4E D5 37 31 3D 33 70 B1 0D M...I..N.71=3p..
0050: F7 95 57 69 22 4A F1 71 1C 32 4C 11 8F C6 86 0C ..Wi"J.q.2L.....
0060: 3B B6 36 9A EA 86 35 1B 30 3A F5 9D C8 0C 17 81 ;.6...5.0:......
0070: 16 AE 9E 71 25 EC FB 29 28 14 68 23 CB 32 E9 BE ...q%..)(.h#.2..
]
11 [main] DEBUG com.mdm.utils.test.X509CertificateGeneratorTest - CB.length=651, CA.length=651, CB =[
[
Version: V3
Subject: CN=Root Test, C=US, ST=California, L=Woodside, O=Acme Inc, OU=Root Certificate
Signature Algorithm: SHA1withRSA, OID = 1.2.840.113549.1.1.5
Key: Sun RSA public key, 1024 bits
modulus: 115961384612636641377515620217869320729793441040343476575562226776614089017051695237661046678469318513262011978862355272647361696676995692401162556817667242798889708372407550431983869833459964470284125551037400515858953182420212692027848881609761511002714010033447993093218955676653913874882198503919201647397
public exponent: 65537
Validity: [From: Tue Nov 12 01:34:27 PST 2013,
To: Wed Nov 12 01:34:27 PST 2014]
Issuer: CN=Root Test, C=US, ST=California, L=Woodside, O=Acme Inc, OU=Root Certificate
SerialNumber: [ 01]
Certificate Extensions: 2
[1]: ObjectId: 2.5.29.19 Criticality=true
BasicConstraints:[
CA:true
PathLen:2147483647
]
[2]: ObjectId: 2.5.29.15 Criticality=true
KeyUsage [
DigitalSignature
Key_CertSign
Crl_Sign
]
]
Algorithm: [SHA1withRSA]
Signature:
0000: 93 0B BF B6 72 89 00 A8 03 A2 B1 2A 88 F9 BB 6B ....r......*...k
0010: F5 69 F5 2D 80 C2 16 40 08 ED 7D 7F B8 AD 69 E7 .i.-...@......i.
0020: 93 1B EC B8 F4 6A 18 99 31 55 46 3D 2F E6 20 D3 .....j..1UF=/. .
0030: A1 69 FC 58 FA 9B 97 63 4B 74 C9 24 36 F8 32 E1 .i.X...cKt.$6.2.
0040: BA E2 5B 75 44 8E 11 74 BF 87 79 9D 5A 91 CB 8E ..[uD..t..y.Z...
0050: B4 2E 02 FF D4 C0 F5 8E 79 37 21 B2 28 86 CD 29 ........y7!.(..)
0060: E2 C7 43 85 52 69 6C F6 1D B7 EE C4 91 87 6A 7B ..C.Ril.......j.
0070: 0D 60 1C EB F6 E2 7D 31 43 21 43 34 7B FC BF 4E .`.....1C!C4...N
]
11 [main] DEBUG com.mdm.utils.test.X509CertificateGeneratorTest - SUCCESS
DEBUG OUTPUT RUN 2 (FAILED)
0 [main] DEBUG com.mdm.utils.test.X509CertificateGeneratorTest - Provider is SUN
6 [main] DEBUG com.mdm.utils.test.X509CertificateGeneratorTest - UB.length=651, UA.length=980, UB =[
[
Version: V3
Subject: CN=Root Test, C=US, ST=California, L=Woodside, O=Acme Inc, OU=Root Certificate
Signature Algorithm: SHA1withRSA, OID = 1.2.840.113549.1.1.5
Key: Sun RSA public key, 1024 bits
modulus: 115961384612636641377515620217869320729793441040343476575562226776614089017051695237661046678469318513262011978862355272647361696676995692401162556817667242798889708372407550431983869833459964470284125551037400515858953182420212692027848881609761511002714010033447993093218955676653913874882198503919201647397
public exponent: 65537
Validity: [From: Tue Nov 12 01:55:14 PST 2013,
To: Wed Nov 12 01:55:14 PST 2014]
Issuer: CN=Root Test, C=US, ST=California, L=Woodside, O=Acme Inc, OU=Root Certificate
SerialNumber: [ 01]
Certificate Extensions: 2
[1]: ObjectId: 2.5.29.19 Criticality=true
BasicConstraints:[
CA:true
PathLen:2147483647
]
[2]: ObjectId: 2.5.29.15 Criticality=true
KeyUsage [
DigitalSignature
Key_CertSign
Crl_Sign
]
]
Algorithm: [SHA1withRSA]
Signature:
0000: 84 9C FA 11 12 90 61 D6 E6 71 5E 5B D8 72 30 1B ......a..q^[.r0.
0010: D5 21 E9 7E 2D 25 59 13 98 A7 00 A5 5A F8 DD 46 .!..-%Y.....Z..F
0020: 2A 0F A0 7B 98 2A E2 4C D8 36 46 52 F4 B3 9E A2 *....*.L.6FR....
0030: 0B C3 C1 79 B7 01 CC 3B AC E1 B5 17 9A AC 95 F3 ...y...;........
0040: DA 2C 08 8D 77 F3 91 DD 2F E9 3C A4 D2 94 24 08 .,..w.../.<...$.
0050: 5A 59 54 0F AA 14 6C 0E 22 37 D3 80 78 03 1E D5 ZYT...l."7..x...
0060: C6 7F 3F 42 5E A9 28 49 31 07 6F 0B C3 A6 E2 0F ..?B^.(I1.o.....
0070: D2 48 5D 6C 50 27 30 E7 4A B3 31 9A 83 E7 88 C9 .H]lP'0.J.1.....
]
10 [main] DEBUG com.mdm.utils.test.X509CertificateGeneratorTest - CB.length=980, CA.length=651, CB =[
[
Version: V3
Subject: CN=Pablobill, C=US, ST=California, L=Woodside, O=Acme Inc., OU=EndEntity Certificate
Signature Algorithm: SHA1withRSA, OID = 1.2.840.113549.1.1.5
Key: Sun RSA public key, 2048 bits
modulus: 16768071670382525923108417071558186448345300080234882539261215442293489118744766516458998304300255531972593921338621155617570519295701287200918722384776808663209526068422690015419314730171909240597223060319385279562945357640946436608123561191766398078873400927250173773856327184499822450130773761828557700657093608566092380977746721299494533830480993371754648531497509681985981605034166444610796945239938191965934961812439864940235377352655354266302700246535173514307468185584585631338844720965267698182627932436044699321382289418637996460855280178953787469375899336175332362249696925445675694303599937295996964262613
public exponent: 65537
Validity: [From: Tue Nov 12 01:55:14 PST 2013,
To: Wed Nov 12 01:55:14 PST 2014]
Issuer: CN=Root Test, C=US, ST=California, L=Woodside, O=Acme Inc, OU=Root Certificate
SerialNumber: [ 02]
Certificate Extensions: 4
[1]: ObjectId: 2.5.29.35 Criticality=false
AuthorityKeyIdentifier [
KeyIdentifier [
0000: 12 05 9F B8 84 CA BC B6 1F 55 25 37 F4 4E 13 AC .........U%7.N..
0010: E1 DA AC C8 ....
]
[CN=Root Test, C=US, ST=California, L=Woodside, O=Acme Inc, OU=Root Certificate]
SerialNumber: [ 01]
]
[2]: ObjectId: 2.5.29.19 Criticality=false
BasicConstraints:[
CA:false
PathLen: undefined
]
[3]: ObjectId: 2.5.29.15 Criticality=true
KeyUsage [
DigitalSignature
Key_Encipherment
]
[4]: ObjectId: 2.5.29.14 Criticality=false
SubjectKeyIdentifier [
KeyIdentifier [
0000: F3 3D 00 4E D4 1D 72 F7 19 49 61 0A 91 7C AB 18 .=.N..r..Ia.....
0010: 54 56 F9 6F TV.o
]
]
]
Algorithm: [SHA1withRSA]
Signature:
0000: 75 22 44 D4 AD 00 2D 32 70 EA EF 68 2B E5 3D 18 u"D...-2p..h+.=.
0010: 62 94 8F 90 C6 FD 0F E9 3B A3 1E 18 02 FA 2F A7 b.......;...../.
0020: 68 5F 1E 97 AF AF FB 2E 10 30 44 BB 79 28 F8 E3 h_.......0D.y(..
0030: 59 25 64 1C 59 51 C5 F3 E6 0F E2 92 66 1B 4A 28 Y%d.YQ......f.J(
0040: 18 68 10 65 31 C5 B4 67 87 90 DD 79 47 EB 00 91 .h.e1..g...yG...
0050: 4E 73 5B F3 6B CB 6B 20 E6 9A DC 4F 57 CD ED 30 Ns[.k.k ...OW..0
0060: D0 A0 BB DA 73 BE 78 E2 08 BD 66 D2 F0 08 B7 D3 ....s.x...f.....
0070: ED 6E 93 29 36 C1 60 2E E0 08 51 2B 4C C8 57 85 .n.)6.`...Q+L.W.
]
java.security.SignatureException: Signature length not correct: got 128 but was expecting 256
at sun.security.rsa.RSASignature.engineVerify(RSASignature.java:189)
at java.security.Signature$Delegate.engineVerify(Signature.java:1172)
at java.security.Signature.verify(Signature.java:623)
at sun.security.x509.X509CertImpl.verify(X509CertImpl.java:446)
at sun.security.x509.X509CertImpl.verify(X509CertImpl.java:394)
at com.mdm.utils.test.ProviderIssueTest.test(ProviderIssueTest.java:207)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:27)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:271)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:70)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)
at org.junit.runners.ParentRunner.run(ParentRunner.java:309)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)
The problem with your code is that SUN's provider implementation of CertStore.getCertificates()
returns HashSet
. And HashSet
makes no guarantees as to the iteration order of the set; in particular, it does not guarantee that the order will remain constant over time.
import java.security.cert.CertStore;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.cert.CollectionCertStoreParameters;
import java.security.cert.X509Certificate;
import java.security.spec.RSAPrivateCrtKeySpec;
import java.security.spec.RSAPublicKeySpec;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.Iterator;
import javax.security.auth.x500.X500Principal;
import org.bouncycastle.asn1.x509.BasicConstraints;
import org.bouncycastle.asn1.x509.KeyUsage;
import org.bouncycastle.asn1.x509.X509Extension;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils;
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
public class Test {
private static long serialNum = 1;
/**
* Create a v3 self signed root certificate.
*/
public static X509Certificate createV3RootCA(PublicKey pubKey, PrivateKey privKey,
int durationInDays,
String subject, String issuer) throws Exception {
if (issuer == null)
issuer = subject;
// Mandatory
Calendar calendar = Calendar.getInstance();
Date notBefore = calendar.getTime();
calendar.add(Calendar.DATE, durationInDays);
Date notAfter = calendar.getTime();
BigInteger issuerSerialNumber = BigInteger.valueOf(serialNum++);
JcaX509v3CertificateBuilder certBuilder = new JcaX509v3CertificateBuilder(
new X500Principal(issuer),
issuerSerialNumber,
notBefore, notAfter,
new X500Principal(subject),
pubKey);
// Optional extensions
certBuilder.addExtension(X509Extension.basicConstraints, true, new BasicConstraints(true));
certBuilder.addExtension(X509Extension.keyUsage, true, new KeyUsage(KeyUsage.keyCertSign|KeyUsage.cRLSign|KeyUsage.digitalSignature));
// Signing
ContentSigner certSigner = new JcaContentSignerBuilder("SHA1WithRSA")
.setProvider(BouncyCastleProvider.PROVIDER_NAME)
.build(privKey);
X509CertificateHolder certHolder = certBuilder.build(certSigner);
// Extract a JCA-compatible certificate
X509Certificate cert = new JcaX509CertificateConverter()
.setProvider(BouncyCastleProvider.PROVIDER_NAME).getCertificate(certHolder);
cert.checkValidity(new Date());
cert.verify(pubKey);
return cert;
}
/**
* Generate a leaf certificate signed by a CA
*/
public static X509Certificate createCert(PublicKey pubKey, X509Certificate caCert, PrivateKey caPrivKey,
int durationInDays,
String subject) throws Exception {
// Mandatory
Calendar calendar = Calendar.getInstance();
Date notBefore = calendar.getTime();
calendar.add(Calendar.DATE, durationInDays);
Date notAfter = calendar.getTime();
JcaX509v3CertificateBuilder certBuilder = new JcaX509v3CertificateBuilder(
caCert.getSubjectX500Principal(),
BigInteger.valueOf(serialNum++),
notBefore, notAfter,
new X500Principal(subject),
pubKey);
// Optional extensions
JcaX509ExtensionUtils extUtils = new JcaX509ExtensionUtils();
certBuilder.addExtension(X509Extension.basicConstraints, false, new BasicConstraints(false));
certBuilder.addExtension(X509Extension.keyUsage, true, new KeyUsage(KeyUsage.digitalSignature|KeyUsage.keyEncipherment));
certBuilder.addExtension(X509Extension.subjectKeyIdentifier, false, extUtils.createSubjectKeyIdentifier(pubKey));
certBuilder.addExtension(X509Extension.authorityKeyIdentifier, false, extUtils.createAuthorityKeyIdentifier(caCert));
// Signing
ContentSigner certSigner = new JcaContentSignerBuilder("SHA1WithRSA")
.setProvider(BouncyCastleProvider.PROVIDER_NAME).build(caPrivKey);
X509CertificateHolder certHolder = certBuilder.build(certSigner);
// Extract a JCA-compatible certificate
X509Certificate cert = new JcaX509CertificateConverter()
.setProvider(BouncyCastleProvider.PROVIDER_NAME).getCertificate(certHolder);
cert.checkValidity(new Date());
cert.verify(caCert.getPublicKey());
return cert;
}
private static String toHexStr(byte[] bytes) {
return new BigInteger(1, bytes).toString(16);
}
public static void main(String [] args) {
Security.addProvider(new BouncyCastleProvider());
try {
KeyFactory fact = KeyFactory.getInstance("RSA", BouncyCastleProvider.PROVIDER_NAME);
PrivateKey caPriv = fact.generatePrivate(
new RSAPrivateCrtKeySpec(
new BigInteger("a5226e241a19f5b796ef2326f4f580b1e5cbc05360a7fd94fd8d59013115e077a422beb4904c5e57f0d9827a0da98b337ab8d47a2b24f77d83f9689e9b43af6b23bf39a1e4e87d8ce9f7d68b8dd50ffec1d34b25833848325ed035d3a1ddeaf62fe5a184dec918d7c2e8b89b17b057a9af359280956dc2a393be6e9a04517b25", 16),
new BigInteger("10001", 16),
new BigInteger("6ff223507e11532e1e380750858758b340e11b846a65f7d664fcc975b15cef4aac0e91d1be70c7143ec6755960a1ab283eedc5bcfc3a973c9397248141286565d479dd57d9bc01d4dec645dd1ae01590671315ec6f9bcde606707255382fcb363744a8bcda3c7a3c2e4015d450ed4aafb675ae277ddcf0e779165125a84f6681", 16),
new BigInteger("f8e745cf5388418a0f038b425095aa8ce3cae42764c15d6f91021a0b6fe0746653428ac95c88ce127deae745521805b6a53da780b56c3f4d15f0c88a85a19609", 16),
new BigInteger("a9d7bc0903893d8116ad8df22e425df382f895d47c0a47d7ea182e9a6221f3d1b27cdfd278960d8cc65699a5c1e5e17197805c9954ff6c37c19a0d9e2241a33d", 16),
new BigInteger("88181ca9a228ec7d0a7c8b9674ed80d58c701194209941f790b82f797570aaf4902de028fdb9a7c3a0a9e24e9af69b99247cb3abc2872f8d7ca3ad636071dbd1", 16),
new BigInteger("5f024cb0aa26ba9e1cc68772238882aff6e30245b401b840c33635d3acf39b4601d7b30934e593bcdd32928ed411b97466b0aa9c279d1eb76df8b48772584f6d", 16),
new BigInteger("e9774efb165c4309e7c7f32603d882d2e8b728887ddb50ee2c2e89591d192b64058699d3251e01348ee24dd23669aec43f1b4e16266950f6268e632242b7d500", 16)));
PublicKey caPub = fact.generatePublic(
new RSAPublicKeySpec(
new BigInteger("a5226e241a19f5b796ef2326f4f580b1e5cbc05360a7fd94fd8d59013115e077a422beb4904c5e57f0d9827a0da98b337ab8d47a2b24f77d83f9689e9b43af6b23bf39a1e4e87d8ce9f7d68b8dd50ffec1d34b25833848325ed035d3a1ddeaf62fe5a184dec918d7c2e8b89b17b057a9af359280956dc2a393be6e9a04517b25", 16),
new BigInteger("10001", 16)));
PublicKey usrPub = fact.generatePublic(
new RSAPublicKeySpec(
new BigInteger("84d4269505c38ba8c5fee8619cf0442eb55c31ae76ec430c1bbe3c82e48a1b56c6f2a3449edf044bcb7151b5df289182b685456f60f819ff7307478fe24f322c6afd4beae7bb4ad50c8bb26c9d0bd505cd91afb144003bea1d2c7fd743178d0141789aca69a5a97918dfccf7d82b25b1bf952cf06f9f432b338ddb773f79583dbbbeaf9fc4cf0878154fdcdfff160b3b5c1ed713990264ab97a3c0a5c617fe123395c03bf94ab24e3f7120ab7d95d06aa83ec9481566b1b6c2dcc9047a46abbf8ee43b32b5589edca36b3342073eb6bf8838a397363bf567640c1d0536961c125b81c0d31d09bd08171b1b6ca9343e09cfa7e3a6010e98d46da7cb6adccf52d5", 16),
new BigInteger("10001", 16)));
// Create self signed CA
X509Certificate caCert = createV3RootCA(
caPub,
caPriv,
365,
"CN=Root Test, C=US, ST=California, L=Woodside ,O=Acme Inc,OU=Root Certificate",
null); // set issuer=subject
X509Certificate usrCert = createCert(
usrPub,
caCert,
caPriv,
365,
"CN=Pablobill, C=US, ST=California, L=Woodside,O=Acme Inc.,OU=EndEntity Certificate");
System.out.println("CA key:\n" + toHexStr(caCert.getPublicKey().getEncoded()));
System.out.println("USR key:\n" + toHexStr(usrCert.getPublicKey().getEncoded()));
// Always passes
caCert.verify(caCert.getPublicKey());
usrCert.verify(caCert.getPublicKey());
// PROVIDER 1
CertificateFactory cf = CertificateFactory.getInstance("X.509", "SUN");
Certificate CA = cf.generateCertificate(new ByteArrayInputStream(caCert.getEncoded()));
Certificate UA = cf.generateCertificate(new ByteArrayInputStream(usrCert.getEncoded()));
CA.verify(CA.getPublicKey()); // This always works irrespective of the provider
UA.verify(CA.getPublicKey()); // This always works irrespective of the provider
ArrayList<Certificate> alist = new ArrayList<Certificate>(2);
alist.add(UA);
alist.add(CA);
// PROVIDER 2
CertStore certStore = CertStore.getInstance("Collection",
new CollectionCertStoreParameters(alist), "SUN");
Collection<?> certs = certStore.getCertificates(null);
System.out.println(String.format("Provider is %s and Collection is %s",
certStore.getProvider().getName(),
certs.getClass().getCanonicalName()));
// Get chain from cert store
Iterator<?> iter = certs.iterator();
Certificate UB = (Certificate)iter.next();
Certificate CB = (Certificate)iter.next();
System.out.println("CA key:\n" + toHexStr(CB.getPublicKey().getEncoded()));
System.out.println("USR key:\n" + toHexStr(UB.getPublicKey().getEncoded()));
if (CB.getPublicKey().getEncoded().length != caCert.getPublicKey().getEncoded().length) {
System.out.println("Certificates were swapped in CertStore!");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
Sample output:
[java] CA key:
[java] 30819f300d06092a864886f70d010101050003818d0030818902818100a5226e241a19f5b796ef2326f4f580b1e5cbc05360a7fd94fd8d59013115e077a422beb4904c5e57f0d9827a0da98b337ab8d47a2b24f77d83f9689e9b43af6b23bf39a1e4e87d8ce9f7d68b8dd50ffec1d34b25833848325ed035d3a1ddeaf62fe5a184dec918d7c2e8b89b17b057a9af359280956dc2a393be6e9a04517b250203010001
[java] USR key:
[java] 30820122300d06092a864886f70d01010105000382010f003082010a028201010084d4269505c38ba8c5fee8619cf0442eb55c31ae76ec430c1bbe3c82e48a1b56c6f2a3449edf044bcb7151b5df289182b685456f60f819ff7307478fe24f322c6afd4beae7bb4ad50c8bb26c9d0bd505cd91afb144003bea1d2c7fd743178d0141789aca69a5a97918dfccf7d82b25b1bf952cf06f9f432b338ddb773f79583dbbbeaf9fc4cf0878154fdcdfff160b3b5c1ed713990264ab97a3c0a5c617fe123395c03bf94ab24e3f7120ab7d95d06aa83ec9481566b1b6c2dcc9047a46abbf8ee43b32b5589edca36b3342073eb6bf8838a397363bf567640c1d0536961c125b81c0d31d09bd08171b1b6ca9343e09cfa7e3a6010e98d46da7cb6adccf52d50203010001
[java] Provider is SUN and Collection is java.util.HashSet
[java] CA key:
[java] 30820122300d06092a864886f70d01010105000382010f003082010a028201010084d4269505c38ba8c5fee8619cf0442eb55c31ae76ec430c1bbe3c82e48a1b56c6f2a3449edf044bcb7151b5df289182b685456f60f819ff7307478fe24f322c6afd4beae7bb4ad50c8bb26c9d0bd505cd91afb144003bea1d2c7fd743178d0141789aca69a5a97918dfccf7d82b25b1bf952cf06f9f432b338ddb773f79583dbbbeaf9fc4cf0878154fdcdfff160b3b5c1ed713990264ab97a3c0a5c617fe123395c03bf94ab24e3f7120ab7d95d06aa83ec9481566b1b6c2dcc9047a46abbf8ee43b32b5589edca36b3342073eb6bf8838a397363bf567640c1d0536961c125b81c0d31d09bd08171b1b6ca9343e09cfa7e3a6010e98d46da7cb6adccf52d50203010001
[java] USR key:
[java] 30819f300d06092a864886f70d010101050003818d0030818902818100a5226e241a19f5b796ef2326f4f580b1e5cbc05360a7fd94fd8d59013115e077a422beb4904c5e57f0d9827a0da98b337ab8d47a2b24f77d83f9689e9b43af6b23bf39a1e4e87d8ce9f7d68b8dd50ffec1d34b25833848325ed035d3a1ddeaf62fe5a184dec918d7c2e8b89b17b057a9af359280956dc2a393be6e9a04517b250203010001
[java] Certificates were swapped in CertStore!