Search code examples
javasslbouncycastlex509

Certificate generated through CSR signing with BouncyCastle considered untrusted


I am struggling with the following issue:

I have a CSR which I am signing with this code:

@Override
public X509Certificate signCSR( Reader pemcsr, int validityDays ) throws APIException
{
try ( PEMParser reader = new PEMParser( pemcsr ) )
{

  KeyStore keystore = getKeyStore();

  Properties cryptoProps = getCryptoProperties();

  String caKeyAlias = cryptoProps.getProperty( PROPERTY_KEYSTORE_CA_CERT_ALIAS );
  String caKeyPassword = cryptoProps.getProperty( PROPERTY_KEYSTORE_CA_CERT_PASSWORD );

  PrivateKey cakey = (PrivateKey) keystore.getKey( caKeyAlias, caKeyPassword.toCharArray() );
  X509Certificate cacert = (X509Certificate) keystore.getCertificate( caKeyAlias );

  PKCS10CertificationRequest csr = (PKCS10CertificationRequest) reader.readObject();

  AlgorithmIdentifier sigAlgId = new DefaultSignatureAlgorithmIdentifierFinder().find( "SHA1withRSA" );
  AlgorithmIdentifier digAlgId = new DefaultDigestAlgorithmIdentifierFinder().find( sigAlgId );
  X500Name issuer = new X500Name( cacert.getSubjectX500Principal().getName() );
  BigInteger serial = new BigInteger( 32, new SecureRandom() );
  Date from = new Date();
  Date to = new Date( System.currentTimeMillis() + ( validityDays * 86400000L ) );

  DigestCalculator digCalc = new BcDigestCalculatorProvider().get( new AlgorithmIdentifier( OIWObjectIdentifiers.idSHA1 ) );
  X509ExtensionUtils x509ExtensionUtils = new X509ExtensionUtils( digCalc );

  X509v3CertificateBuilder certgen = new X509v3CertificateBuilder( issuer, serial, from, to, csr.getSubject(), csr.getSubjectPublicKeyInfo() );

  // Basic Constraints
  // certgen.addExtension( Extension.basicConstraints, true, new
  // BasicConstraints( 0 ) );

  // Subject Key Identifier
  // certgen.addExtension( Extension.subjectKeyIdentifier, false,
  // x509ExtensionUtils.createSubjectKeyIdentifier(
  // csr.getSubjectPublicKeyInfo() ) );

  // Authority Key Identifier
  // byte[] caKeyEncoded = cacert.getPublicKey().getEncoded();
  // SubjectPublicKeyInfo caSubjectPublicKeyInfo =
  // SubjectPublicKeyInfo.getInstance( caKeyEncoded );
  // certgen.addExtension( Extension.authorityKeyIdentifier, false,
  // x509ExtensionUtils.createAuthorityKeyIdentifier( caSubjectPublicKeyInfo
  // ) );

  // Key Usage
  // certgen.addExtension( Extension.keyUsage, false, new KeyUsage(
  // KeyUsage.digitalSignature | KeyUsage.keyCertSign | KeyUsage.cRLSign )
  // );

  ContentSigner signer = new BcRSAContentSignerBuilder( sigAlgId, digAlgId ).build( PrivateKeyFactory.createKey( cakey.getEncoded() ) );

  // ContentSigner signer = new JcaContentSignerBuilder(
  // "SHA1WithRSAEncryption" ).setProvider( "BC" ).build( cakey );

  X509CertificateHolder holder = certgen.build( signer );

  return new JcaX509CertificateConverter().setProvider( "BC" ).getCertificate( holder );
}
catch ( NoSuchAlgorithmException | KeyStoreException | CertificateException | OperatorCreationException | UnrecoverableKeyException | CertIOException e )
{
  throw new APIException( API_ERROR_CODE.CRYPTOGRAPHY_ERROR, e );
}
catch ( IOException e )
{
  throw new APIException( API_ERROR_CODE.IO_ERROR, e );
}

}

This runs through successfully. However, when I try to check the key using:

KeyStore ks = getKeyStore();

  TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance( TrustManagerFactory.getDefaultAlgorithm() );
  trustManagerFactory.init( ks );

  for ( TrustManager trustManager : trustManagerFactory.getTrustManagers() )
  {
    if ( trustManager instanceof X509TrustManager )
    {
      X509TrustManager x509TrustManager = (X509TrustManager) trustManager;
      x509TrustManager.checkClientTrusted( new X509Certificate[] { certificate }, "RSA" );
    }
  }

...it fails with a CertificateException. Please note that I am using the VERY SAME keystore here, which means the CA key I am signing with is included therein. Why does this happen?

By the way, strangely enough, when I open the generated signed certificate using Windows's certificate viewer, it DOES show the issuing CA name but its entry does not show up in the certificate chain. It seems as if the CA root certificate were not present in the Windows trusted authirities list, but in fact it is also there.

Even stranger: if I sign the CSR using OpenSSL, the certificate chain looks OK. I also had the idea that the process of importing the CA key pair from OpenSSL to the Java keystore via PKCS12 as an intermediate format wasn't successful, but actually if I export the CA certificate from the Java keystore and open it with the Windows certificate viewer, it is shown as trusted...

UPDATE: For those familiar with ASN.1, here are two encoded certificates. One is made with BouncyCastle and is NOT trusted, the other one is signed by the same CA key with OpenSSL and that IS trusted. They can be decoded with a tool like this: ASN.1 decoder I would be very grateful is somebody could view this decoded data and tell me what could cause the difference between them.

This one is NOT trusted:

-----BEGIN CERTIFICATE-----
MIIC6TCCAlKgAwIBAgIESdsI/TANBgkqhkiG9w0BAQUFADCBgzEgMB4GCSqGSIb3
DQEJARYRdGVzdGNhQHRlc3RjYS5jb20xEDAOBgNVBAMMB1Rlc3QgQ0ExEDAOBgNV
BAsMB1Rlc3QgQ0ExEDAOBgNVBAoMB1Rlc3QgQ0ExDTALBgNVBAcMBFdpZW4xDTAL
BgNVBAgMBFdpZW4xCzAJBgNVBAYTAkFUMB4XDTE0MDUxOTExNTYwM1oXDTE1MDUx
OTExNTYwM1owajELMAkGA1UEBhMCVUsxCzAJBgNVBAgTAlBiMQswCQYDVQQHEwJC
cDETMBEGA1UEChMKZmdmZ2ZnZGZnZDEPMA0GA1UECxMGYWJjZGVmMRswGQYDVQQD
DBJwZXRlcnZlbG9zeV90aWdyaXMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
AoIBAQCdL7taENsONBazc2iMDV5nw9ACP5mevmnzPwOJRUcd5GlGgry/iSa3tTwL
l6Um3zNc4X0m5nVVskKeJE4dTvYFV3+vJlEKCra86yQfa6XkGllU4EG6SdG8lRhE
Btk1QbOQZKrUz77IdOWWOUvIsNxtDDQcUhnrSjSxHohdoe/yoCl+60RBdjrgUrRo
uctSHFPvVt2uZaVM2rAVovx56vvJHOag2++rcvXaOh9WHvdwRAIZt/4aOv2O4jdI
jKdRrmF8dOudjR89wIeVjX9fvyvx+hw+ZolUio9GOVKLlBcYno6lEupHLUDK9ECs
W8F6y65nYGlm9/0G0+gB7K1yy1dBAgMBAAEwDQYJKoZIhvcNAQEFBQADgYEAKJpM
7AbkWBH3ho1YV0d1glJvefQ1xaXGpDfd+Tzf3+cR1o3+YxxEyuYvBbiQ/MBxKD9/
hsFCqEWzOfu2lAZ+/6uHvt7BCEGhaLdWKXehoaIw/kEMeISIUDFbKORCsKJNbYRB
xgqBXGglTQ4gVXMDRBxzOmButN31j1VDt55gvn4=
-----END CERTIFICATE-----

This one is TRUSTED, this has been generated using theoretically the same CA certificate but through OpenSSL:

-----BEGIN CERTIFICATE-----
MIIC+TCCAmICAhI4MA0GCSqGSIb3DQEBBQUAMIGDMQswCQYDVQQGEwJBVDENMAsG
A1UECAwEV2llbjENMAsGA1UEBwwEV2llbjEQMA4GA1UECgwHVGVzdCBDQTEQMA4G
A1UECwwHVGVzdCBDQTEQMA4GA1UEAwwHVGVzdCBDQTEgMB4GCSqGSIb3DQEJARYR
dGVzdGNhQHRlc3RjYS5jb20wHhcNMTQwNTE0MTkzMTAzWhcNMTUwNTA5MTkzMTAz
WjCBgDELMAkGA1UEBhMCSFUxETAPBgNVBAgTCEJ1ZGFwZXN0MREwDwYDVQQHEwhC
dWRhcGVzdDEWMBQGA1UEChMNTWVyY2hhbnQgVGVzdDEWMBQGA1UECxMNTWVyY2hh
bnQgVGVzdDEbMBkGA1UEAwwScGV0ZXJ2ZWxvc3lfdGlncmlzMIIBIjANBgkqhkiG
9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1vuY4MQ5b9Jb0MiyEuCrR4E+7VgmrvEwlswO
aMIF4H6i538PwPml5dbqx/3whxR/BcQJuJYWI/Hh7xxGS7FvSQ+DNhzxv9TpECKS
/5OZNm+JikPZwTiwrS/Cf4NP+ZcXOjtVZp6ngVtTarn3NC/J7gJVYaHVVO4NbUkt
kCYhdfCXg71QiJ42RWMjMC9tJFrrlfem+SVzh8yMtUCBKm7nbMjQ6LngawjTzDK8
2Zcdqwdzvt2pcYcsYSViO5j5t/r7rIDGjRkjJqRSEiJMOvn0W+sdTdmFoZbyj7Qe
pgyCyf28uFyCO9QZro337D8klPLXaWJOwPDXXiuYOTDYAjBVbwIDAQABMA0GCSqG
SIb3DQEBBQUAA4GBAGU60GVjR+2oEiJMSe1CKU7gf+bGuxaCxXQTzVQLU652i1sp
Fv56o6jnLtw46/rQydNKX4GBH022B/BDEPAQQiQv31YKQAoWtBZod0SRonogcx7p
AULacoma9QEgHSX0l+2yEn42/qo7o0pAmmewJlsCnHVIqI0eU8x1XbCEAf53
-----END CERTIFICATE-----

UPDATE 2:

Thanks to Bruno's answer, the certificate chain now looks OK and the following certificate is generated:

-----BEGIN CERTIFICATE-----
MIIC6TCCAlKgAwIBAgIEI2vbpTANBgkqhkiG9w0BAQUFADCBgzELMAkGA1UEBhMC
QVQxDTALBgNVBAgMBFdpZW4xDTALBgNVBAcMBFdpZW4xEDAOBgNVBAoMB1Rlc3Qg
Q0ExEDAOBgNVBAsMB1Rlc3QgQ0ExEDAOBgNVBAMMB1Rlc3QgQ0ExIDAeBgkqhkiG
9w0BCQEWEXRlc3RjYUB0ZXN0Y2EuY29tMB4XDTE0MDUyMDA3MzkyMFoXDTE1MDUy
MDA3MzkyMFowajELMAkGA1UEBhMCVUsxCzAJBgNVBAgTAlBiMQswCQYDVQQHEwJC
cDETMBEGA1UEChMKZmdmZ2ZnZGZnZDEPMA0GA1UECxMGYWJjZGVmMRswGQYDVQQD
DBJwZXRlcnZlbG9zeV90aWdyaXMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
AoIBAQCdL7taENsONBazc2iMDV5nw9ACP5mevmnzPwOJRUcd5GlGgry/iSa3tTwL
l6Um3zNc4X0m5nVVskKeJE4dTvYFV3+vJlEKCra86yQfa6XkGllU4EG6SdG8lRhE
Btk1QbOQZKrUz77IdOWWOUvIsNxtDDQcUhnrSjSxHohdoe/yoCl+60RBdjrgUrRo
uctSHFPvVt2uZaVM2rAVovx56vvJHOag2++rcvXaOh9WHvdwRAIZt/4aOv2O4jdI
jKdRrmF8dOudjR89wIeVjX9fvyvx+hw+ZolUio9GOVKLlBcYno6lEupHLUDK9ECs
W8F6y65nYGlm9/0G0+gB7K1yy1dBAgMBAAEwDQYJKoZIhvcNAQEFBQADgYEAIdFF
h6uLY7ioKQ3O0c4cZHHjRA0HTlWjih8P2xvXY/V9jF914BT7OW52UJ16tQaJlOf+
mAeeBDq9srKnkmOQp3mCejVnkyVZF8pOOzNbqSVzylt0Csg2twnxZ0NcM63Oda5b
YSQI8+arryxykLWkHWH8i/6rPCDCtbAHBo7fSeQ=
-----END CERTIFICATE-----

However, the TrustManager code above rejects it. If I circumvent the TrustManager and do something like this:

 KeyStore ks = getKeyStore();

  Enumeration<String> aliases = ks.aliases();

  while ( aliases.hasMoreElements() )
  {
    String alias = aliases.nextElement();
    Certificate currentCert = ks.getCertificate( alias );
    try
    {
      certificate.verify( currentCert.getPublicKey() );
      return true;
    }
    catch ( Exception e )
    {
      // the certificate cannot be verified with this key.
    }
  }

  return false;

...it passes. Does anybody know why it fails on the TrustManager check?

P.s. the CA certificate looks like this:

-----BEGIN CERTIFICATE-----
MIICfzCCAegCCQCU+Ah6M5qQGTANBgkqhkiG9w0BAQUFADCBgzELMAkGA1UEBhMC
QVQxDTALBgNVBAgMBFdpZW4xDTALBgNVBAcMBFdpZW4xEDAOBgNVBAoMB1Rlc3Qg
Q0ExEDAOBgNVBAsMB1Rlc3QgQ0ExEDAOBgNVBAMMB1Rlc3QgQ0ExIDAeBgkqhkiG
9w0BCQEWEXRlc3RjYUB0ZXN0Y2EuY29tMB4XDTE0MDQyMzA3MjYzNFoXDTI0MDQy
MDA3MjYzNFowgYMxCzAJBgNVBAYTAkFUMQ0wCwYDVQQIDARXaWVuMQ0wCwYDVQQH
DARXaWVuMRAwDgYDVQQKDAdUZXN0IENBMRAwDgYDVQQLDAdUZXN0IENBMRAwDgYD
VQQDDAdUZXN0IENBMSAwHgYJKoZIhvcNAQkBFhF0ZXN0Y2FAdGVzdGNhLmNvbTCB
nzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAldKTo8iqF52dsOwln0Oppu+ODiaG
R4T7Znrca4Cs5FBQOmuMwqUP6ilW115p/WvkBHhm8dZyVACPKdshEfhh4VFAW5r2
mJnosYgjafQpTEv83sc938DwtK6iikZ0uvdBJKG/IuYblNq9TPMLFeTYjD8mgf9j
m6JOvA/Q9J4nRW0CAwEAATANBgkqhkiG9w0BAQUFAAOBgQB8ACYeC+zjV/KqxPr1
cyzfJP9xfUnxDTEKUJS2YVuxJqpfbHeUtvKoN89BfY07XWdnj8cgMDfJp10Kdc2A
clwP2lVDtOgHZS07UUW98q9FKQ33mLHIn0nDKNwTo5VH8t/NJVeMFuZPAbFiI2gj
KH2sTU2GNNvKC4jHh0PS+OZFtg==
-----END CERTIFICATE-----

Solution

  • If you look at the Issuer DN in your two certificates, they don't match (output from openssl x509 -text):

    Issuer: C=AT, ST=Wien, L=Wien, O=Test CA, OU=Test CA, CN=Test CA/[email protected]
    

    and

    Issuer: [email protected], CN=Test CA, OU=Test CA, O=Test CA, L=Wien, ST=Wien, C=AT
    

    As a result, it's not going to be able to match the wrong issuer to the CA's Subject DN.

    Unfortunately, X500Name issuer = new X500Name(cacert.getSubjectX500Principal().getName()) doesn't do what you'd expect. The order of the RDNs is reversed. Generally, re-building the DN from a string representation can fail, since there are different ways of serialising the ASN.1 representation into a string. Java's X500Principal has multiple formats available for getName(...) and it even provides a way to provide your own OID to string maps (for more obscure OIDs). The way emailAddress is separated can also cause problems (notice the way it's separated with a comma or with a slash).

    Instead, build the X500Name from the encoded form, this should always work:

    X500Name x500Name = X500Name.getInstance(cert
                            .getSubjectX500Principal().getEncoded());