/** * Copyright © 2013 enioka. All rights reserved * * 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 com.enioka.jqm.pki; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.FileWriter; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.StringWriter; import java.io.Writer; import java.math.BigInteger; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.KeyStore; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.PrivateKey; import java.security.PublicKey; import java.security.SecureRandom; import java.security.Security; import java.security.cert.Certificate; import java.util.Calendar; import java.util.Date; import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers; import org.bouncycastle.asn1.x500.X500Name; import org.bouncycastle.asn1.x509.AlgorithmIdentifier; import org.bouncycastle.asn1.x509.AuthorityKeyIdentifier; import org.bouncycastle.asn1.x509.BasicConstraints; import org.bouncycastle.asn1.x509.ExtendedKeyUsage; import org.bouncycastle.asn1.x509.Extension; import org.bouncycastle.asn1.x509.KeyPurposeId; import org.bouncycastle.asn1.x509.KeyUsage; import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; import org.bouncycastle.cert.X509CertificateHolder; import org.bouncycastle.cert.X509ExtensionUtils; import org.bouncycastle.cert.X509v3CertificateBuilder; import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.openssl.PEMWriter; import org.bouncycastle.operator.ContentSigner; import org.bouncycastle.operator.DigestCalculator; import org.bouncycastle.operator.bc.BcDigestCalculatorProvider; import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; import org.bouncycastle.util.BigIntegers; public class CertificateRequest { // Parameter fields private String prettyName; private Integer size = 2048; private X509CertificateHolder authorityCertificate = null; private PrivateKey authorityKey = null; private String Subject; private KeyPurposeId[] EKU; private int keyUsage = 0; private int validityYear = 10; // Result fields OutputStream pemPublicFile; OutputStream pemPrivateFile; X509CertificateHolder holder; PublicKey publicKey; PrivateKey privateKey; // Public API public void generateCA(String prettyName) { this.prettyName = prettyName; Subject = "CN=JQM-CA,OU=ServerProducts,O=Oxymores,C=FR"; size = 4096; EKU = new KeyPurposeId[4]; EKU[0] = KeyPurposeId.id_kp_codeSigning; EKU[1] = KeyPurposeId.id_kp_serverAuth; EKU[2] = KeyPurposeId.id_kp_clientAuth; EKU[3] = KeyPurposeId.id_kp_emailProtection; keyUsage = KeyUsage.cRLSign | KeyUsage.keyCertSign; generateAll(); } public void generateClientCert(String prettyName, X509CertificateHolder authority, PrivateKey issuerPrivateKey, String subject) { this.prettyName = prettyName; authorityCertificate = authority; authorityKey = issuerPrivateKey; this.Subject = subject; size = 2048; EKU = new KeyPurposeId[1]; EKU[0] = KeyPurposeId.id_kp_clientAuth; keyUsage = KeyUsage.digitalSignature | KeyUsage.keyEncipherment; generateAll(); } public void generateServerCert(String prettyName, X509CertificateHolder authority, PrivateKey issuerPrivateKey, String subject) { this.prettyName = prettyName; authorityCertificate = authority; authorityKey = issuerPrivateKey; this.Subject = subject; size = 2048; EKU = new KeyPurposeId[1]; EKU[0] = KeyPurposeId.id_kp_serverAuth; keyUsage = KeyUsage.digitalSignature | KeyUsage.keyEncipherment; generateAll(); } public void writePemPublicToFile(String path) { try { File f = new File(path); if (!f.getParentFile().isDirectory() && !f.getParentFile().mkdir()) { throw new PkiException("couldn't create directory " + f.getParentFile().getAbsolutePath() + " for storing the SSL keystore"); } FileWriter fw = new FileWriter(path); PEMWriter wr = new PEMWriter(fw); wr.writeObject(holder); wr.close(); fw.close(); } catch (Exception e) { throw new PkiException(e); } } public String writePemPublicToString() { try { StringWriter sw = new StringWriter(); PEMWriter wr = new PEMWriter(sw); wr.writeObject(holder); wr.close(); sw.close(); return sw.toString(); } catch (Exception e) { throw new PkiException(e); } } public void writePemPrivateToFile(String path) { try { FileWriter fw = new FileWriter(path); PEMWriter wr = new PEMWriter(fw); wr.writeObject(privateKey); wr.close(); fw.close(); } catch (Exception e) { throw new PkiException(e); } } public String writePemPrivateToString() { try { StringWriter sw = new StringWriter(); PEMWriter wr = new PEMWriter(sw); wr.writeObject(privateKey); wr.close(); sw.close(); return sw.toString(); } catch (Exception e) { throw new PkiException(e); } } // Internal methods private void generateAll() { try { generateKeyPair(); generateX509(); generatePem(); } catch (Exception e) { throw new PkiException(e); } } private void generatePem() { try { // PEM public key pemPublicFile = new ByteArrayOutputStream(); Writer osw = new OutputStreamWriter(pemPublicFile); PEMWriter wr = new PEMWriter(osw); wr.writeObject(holder); wr.close(); // PEM private key pemPrivateFile = new ByteArrayOutputStream(); osw = new OutputStreamWriter(pemPrivateFile); wr = new PEMWriter(osw); wr.writeObject(privateKey); wr.close(); } catch (Exception e) { throw new PkiException(e); } } void writePfxToFile(OutputStream out, String password) { try { KeyStore ks = KeyStore.getInstance("PKCS12"); ks.load(null, null); Certificate[] chain = null; if (authorityCertificate != null) { chain = new Certificate[2]; chain[0] = new JcaX509CertificateConverter().getCertificate(this.holder); chain[1] = new JcaX509CertificateConverter().getCertificate(this.authorityCertificate); } else { chain = new Certificate[1]; chain[0] = new JcaX509CertificateConverter().setProvider("BC").getCertificate(this.holder); } ks.setKeyEntry("private key for " + this.prettyName, privateKey, password.toCharArray(), chain); ks.store(out, password.toCharArray()); } catch (Exception e) { throw new PkiException(e); } } public void writeTrustPfxToFile(String path, String password) { try { KeyStore ks = KeyStore.getInstance("JKS"); ks.load(null, null); Certificate ca = new JcaX509CertificateConverter().getCertificate(this.authorityCertificate); ks.setCertificateEntry("JQM-CA", ca); FileOutputStream fos = new FileOutputStream(path); ks.store(fos, password.toCharArray()); fos.close(); } catch (Exception e) { throw new PkiException(e); } } private void generateKeyPair() throws NoSuchAlgorithmException, NoSuchProviderException { Security.addProvider(new BouncyCastleProvider()); SecureRandom random = new SecureRandom(); KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(Constants.KEY_ALGORITHM, Constants.JCA_PROVIDER); keyPairGenerator.initialize(size, random); KeyPair keyPair = keyPairGenerator.generateKeyPair(); publicKey = keyPair.getPublic(); privateKey = keyPair.getPrivate(); } private void generateX509() throws Exception { SecureRandom random = new SecureRandom(); X500Name dnName = new X500Name(Subject); Calendar endValidity = Calendar.getInstance(); endValidity.add(Calendar.YEAR, validityYear); SubjectPublicKeyInfo publicKeyInfo = SubjectPublicKeyInfo.getInstance(publicKey.getEncoded()); X509v3CertificateBuilder gen = new X509v3CertificateBuilder(authorityCertificate == null ? dnName : authorityCertificate.getSubject(), BigIntegers.createRandomInRange(BigInteger.ZERO, BigInteger.valueOf(Long.MAX_VALUE), random), new Date(), endValidity.getTime(), dnName, publicKeyInfo); // Public key ID DigestCalculator digCalc = new BcDigestCalculatorProvider().get(new AlgorithmIdentifier(OIWObjectIdentifiers.idSHA1)); X509ExtensionUtils x509ExtensionUtils = new X509ExtensionUtils(digCalc); gen.addExtension(Extension.subjectKeyIdentifier, false, x509ExtensionUtils.createSubjectKeyIdentifier(publicKeyInfo)); // EKU gen.addExtension(Extension.extendedKeyUsage, false, new ExtendedKeyUsage(EKU)); // Basic constraints (is CA?) if (authorityCertificate == null) { gen.addExtension(Extension.basicConstraints, true, new BasicConstraints(true)); } // Key usage gen.addExtension(Extension.keyUsage, true, new KeyUsage(keyUsage)); // Subject Alt names ? // Authority if (authorityCertificate != null) { gen.addExtension(Extension.authorityKeyIdentifier, false, new AuthorityKeyIdentifier(authorityCertificate.getSubjectPublicKeyInfo())); } // Signer ContentSigner signer = new JcaContentSignerBuilder("SHA512WithRSAEncryption").setProvider(Constants.JCA_PROVIDER).build( authorityKey == null ? privateKey : authorityKey); // Go holder = gen.build(signer); } // Data that can be accessed public OutputStream getPemPublicFile() { if (pemPublicFile == null) { generatePem(); } return pemPublicFile; } public OutputStream getPemPrivateFile() { if (pemPrivateFile == null) { generatePem(); } return pemPrivateFile; } public X509CertificateHolder getHolder() { return holder; } public PublicKey getPublicKey() { return publicKey; } public PrivateKey getPrivateKey() { return privateKey; } }