Tuesday, May 14, 2013

Bouncy Castle, Self-Signed Certificates, XMPP, and Keystores, oh my...

I had the opportunity recently to implement a method to dynamically generate a self-signed certificate to use with the Apache Vypser XMPP server. For our needs, we needed to have the ability to configure a domain from a web console, generate the keystore for use with Vysper, and then download the certificate from the keystore to use with federated servers.

Starting out, I thought this was going to be a simple task. As it turned out, there were a few complications, but in the end, it works pretty well. I used bouncy-castle v 1.48 to generate the certificate.

The problem I kept running in to was that all of the documentation I could find used the deprecated bouncy-castle APIs. I wanted to make sure that while generating new code, I wasn't using APIs that were already known to be obsolete. Unfortunately, there is not a lot of documentation available yet for the new APIs.

Adding to the challenge is that certificates used with XMPP server federation have to include the subject alternative name extension in the certificate. Finding the right sequence of magic included reverse engineering a certificate generated from OpenSSL.

The following code is what I ended up with.

     
     final ASN1ObjectIdentifier XMPP_ADDR_OID = new ASN1ObjectIdentifier("1.3.6.1.5.5.7.8.5");
     RSAKeyPairGenerator kpg = new RSAKeyPairGenerator();  
     kpg.init(new RSAKeyGenerationParameters(  
         BigInteger.valueOf(0x1001), new SecureRandom(), 1024, 25));  
     AsymmetricCipherKeyPair keyPair = kpg.generateKeyPair();  
   
     X500Name subject = new X500Name("CN=" + domainName);  
     DefaultSignatureAlgorithmIdentifierFinder sigAlgFinder = new DefaultSignatureAlgorithmIdentifierFinder();  
     DefaultDigestAlgorithmIdentifierFinder digAlgFinder = new DefaultDigestAlgorithmIdentifierFinder();  
   
     AlgorithmIdentifier sigAlgId = sigAlgFinder.find("SHA1withRSA");  
     AlgorithmIdentifier digAlgId = digAlgFinder.find(sigAlgId);  
   
     ContentSigner sigGen = new BcRSAContentSignerBuilder(sigAlgId, digAlgId).build(keyPair.getPrivate());  
   
     // Create the extension for subjectAltName  
     DERUTF8String derDomainName = new DERUTF8String(domainName);  
     DERTaggedObject derTaggedDomainName = new DERTaggedObject(0, derDomainName);  
     DLSequence otherName = new DLSequence(new ASN1Encodable[]{XMPP_ADDR_OID, derTaggedDomainName});  
     GeneralNames generalNames = new GeneralNames(new GeneralName(GeneralName.otherName, otherName));  
   
     // Create the certificate  
     BcX509v3CertificateBuilder certBuilder = new BcX509v3CertificateBuilder(subject, BigInteger.valueOf(1),  
         new Date(  
         System.currentTimeMillis() - 50000),  
         new Date(System.currentTimeMillis() + (1000L * 60 * 60 * 24 * 3650)),  
         subject, keyPair.getPublic());  
     certBuilder.addExtension(Extension.subjectAlternativeName, false, generalNames);  
     X509CertificateHolder cert = certBuilder.build(sigGen);  
   
     // Convert to standard Java objects to be able to create the keystore  
     RSAKeyParameters privateKey = (RSAKeyParameters) keyPair.getPrivate();  
     RSAPrivateKeySpec privateKeySpec = new RSAPrivateKeySpec(privateKey.getModulus(), privateKey.getExponent());  
     PrivateKey jPrivateKey = KeyFactory.getInstance("RSA").generatePrivate(privateKeySpec);  
   
     final X509CertificateObject x509CertificateObject = new X509CertificateObject(cert.toASN1Structure());  
   
     // Create a keystore with a randomly generated password to secure it  
     final String password = RandomStringUtils.randomAlphanumeric(24);  
     KeyStore keystore = KeyStore.getInstance("JKS");  
     keystore.load(null, null);  
     keystore.setKeyEntry("xmpp", jPrivateKey, password.toCharArray(), new Certificate[]{x509CertificateObject});  
   
     OutputStream fos = new FileOutputStream(keystoreFile);  
     keystore.store(fos, password.toCharArray());  
     fos.close();  
   

No comments:

Post a Comment