Java tutorial
/* Java Libraries https://github.com/foilen/java-libraries Copyright (c) 2015-2018 Foilen (http://foilen.com) The MIT License http://opensource.org/licenses/MIT */ package com.foilen.smalltools.crypt.bouncycastle.cert; import java.io.ByteArrayOutputStream; import java.io.FileWriter; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.StringReader; import java.io.Writer; import java.math.BigInteger; import java.security.PrivateKey; import java.security.PublicKey; import java.security.Security; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.security.interfaces.RSAPublicKey; import java.util.Collection; import java.util.Date; import java.util.HashSet; import java.util.List; import java.util.Set; import org.bouncycastle.asn1.ASN1Primitive; import org.bouncycastle.asn1.ASN1Set; import org.bouncycastle.asn1.x500.AttributeTypeAndValue; import org.bouncycastle.asn1.x500.RDN; import org.bouncycastle.asn1.x500.X500Name; import org.bouncycastle.asn1.x509.Extension; import org.bouncycastle.asn1.x509.GeneralName; import org.bouncycastle.asn1.x509.GeneralNames; import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; import org.bouncycastle.cert.X509CertificateHolder; import org.bouncycastle.cert.X509v3CertificateBuilder; import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; import org.bouncycastle.crypto.params.AsymmetricKeyParameter; import org.bouncycastle.crypto.params.RSAKeyParameters; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.openssl.MiscPEMGenerator; import org.bouncycastle.operator.ContentSigner; import org.bouncycastle.operator.ContentVerifierProvider; import org.bouncycastle.operator.DefaultDigestAlgorithmIdentifierFinder; import org.bouncycastle.operator.bc.BcRSAContentVerifierProviderBuilder; import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; import org.bouncycastle.util.io.pem.PemObject; import org.bouncycastle.util.io.pem.PemObjectGenerator; import org.bouncycastle.util.io.pem.PemReader; import org.bouncycastle.util.io.pem.PemWriter; import com.foilen.smalltools.crypt.bouncycastle.asymmetric.AsymmetricKeys; import com.foilen.smalltools.crypt.bouncycastle.asymmetric.RSACrypt; import com.foilen.smalltools.crypt.bouncycastle.asymmetric.RSAKeyDetails; import com.foilen.smalltools.exception.SmallToolsException; import com.foilen.smalltools.hash.HashSha1; import com.foilen.smalltools.tools.AssertTools; import com.foilen.smalltools.tools.CloseableTools; import com.foilen.smalltools.tools.CollectionsTools; import com.foilen.smalltools.tools.DateTools; import com.foilen.smalltools.tools.FileTools; /** * To create self-signed certificates and to sign other certificates. * * <pre> * Usage: * * // Root * AsymmetricKeys rootKeys = rsaCrypt.generateKeyPair(2048); * RSACertificate rootCertificate = new RSACertificate(rootKeys); * rootCertificate.selfSign(new CertificateDetails().setCommonName("CA root")); * * // Node * AsymmetricKeys nodeKeys = rsaCrypt.generateKeyPair(2048); * RSACertificate nodeCertificate = rootCertificate.signPublicKey(nodeKeys, new CertificateDetails().setCommonName("p001.node.foilen.org")); * * // Fake Root * AsymmetricKeys fakeRootKeys = rsaCrypt.generateKeyPair(2048); * RSACertificate fakeRootCertificate = new RSACertificate(fakeRootKeys); * fakeRootCertificate.selfSign(new CertificateDetails().setCommonName("CA root")); * * // Assert certificates * Assert.assertTrue(rootCertificate.isValidSignature(rootCertificate)); * Assert.assertTrue(nodeCertificate.isValidSignature(rootCertificate)); * Assert.assertTrue(fakeRootCertificate.isValidSignature(fakeRootCertificate)); * * Assert.assertFalse(rootCertificate.isValidSignature(nodeCertificate)); * Assert.assertFalse(rootCertificate.isValidSignature(fakeRootCertificate)); * Assert.assertFalse(nodeCertificate.isValidSignature(nodeCertificate)); * Assert.assertFalse(nodeCertificate.isValidSignature(fakeRootCertificate)); * Assert.assertFalse(fakeRootCertificate.isValidSignature(rootCertificate)); * Assert.assertFalse(fakeRootCertificate.isValidSignature(nodeCertificate)); * </pre> * * <pre> * Dependencies: * compile 'org.bouncycastle:bcpkix-jdk15on:1.58' * compile 'org.bouncycastle:bcpg-jdk15on:1.58' * compile 'org.bouncycastle:bcprov-jdk15on:1.58' * </pre> */ public class RSACertificate { static { BouncyCastleProvider provider = new BouncyCastleProvider(); if (Security.getProvider(provider.getName()) == null) { Security.addProvider(provider); } } private static String OID_COMMON_NAME = "2.5.4.3"; private static RSACrypt rsaCrypt = new RSACrypt(); /** * Load the certificate and keys (if present in the file). * * @param fileName * the full path of the file * @return the certificate */ public static RSACertificate loadPemFromFile(String fileName) { String pem = FileTools.getFileAsString(fileName); return loadPemFromString(pem); } /** * Load the certificate and keys (if present in the strings). * * @param pems * the pems (some can be null) * @return the certificate */ public static RSACertificate loadPemFromString(String... pems) { RSACertificate certificate = new RSACertificate(); PemReader pemReader = null; try { // Keys if present certificate.keysForSigning = rsaCrypt.loadKeysPemFromString(pems); // Certificate for (String pem : pems) { if (pem == null) { continue; } pemReader = new PemReader(new StringReader(pem)); PemObject pemObject; while ((pemObject = pemReader.readPemObject()) != null) { if ("CERTIFICATE".equals(pemObject.getType())) { certificate.certificateHolder = new X509CertificateHolder(pemObject.getContent()); } } } return certificate; } catch (Exception e) { throw new SmallToolsException("Problem loading the certificate", e); } finally { CloseableTools.close(pemReader); } } private X509CertificateHolder certificateHolder; private AsymmetricKeys keysForSigning; public RSACertificate() { } public RSACertificate(AsymmetricKeys keysForSigning) { this.keysForSigning = keysForSigning; } public RSACertificate(javax.security.cert.X509Certificate certificate) { try { this.certificateHolder = new X509CertificateHolder(certificate.getEncoded()); } catch (Exception e) { throw new SmallToolsException("Problem setting the certificate", e); } } public RSACertificate(X509Certificate certificate) { try { this.certificateHolder = new X509CertificateHolder(certificate.getEncoded()); } catch (Exception e) { throw new SmallToolsException("Problem setting the certificate", e); } } public RSACertificate(X509CertificateHolder certificateHolder) { this.certificateHolder = certificateHolder; } public RSACertificate(X509CertificateHolder certificateHolder, AsymmetricKeys keysForSigning) { this.certificateHolder = certificateHolder; this.keysForSigning = keysForSigning; } /** * Get the Java certificate. * * @return the Java certificate */ public X509Certificate getCertificate() { AssertTools.assertNotNull(certificateHolder, "The certificate is not set"); try { return new JcaX509CertificateConverter().getCertificate(certificateHolder); } catch (CertificateException e) { throw new SmallToolsException("Could not convert the certificate", e); } } public X509CertificateHolder getCertificateHolder() { return certificateHolder; } /** * Get the first certificate's common name. * * @return the common name */ public String getCommonName() { AssertTools.assertNotNull(certificateHolder, "The certificate is not set"); X500Name subject = certificateHolder.getSubject(); for (RDN rdn : subject.getRDNs()) { AttributeTypeAndValue first = rdn.getFirst(); if (OID_COMMON_NAME.equals(first.getType().toString())) { return first.getValue().toString(); } } return null; } /** * Get the certificate's common names. * * @return the common names */ public Set<String> getCommonNames() { AssertTools.assertNotNull(certificateHolder, "The certificate is not set"); X500Name subject = certificateHolder.getSubject(); Set<String> commonNames = new HashSet<>(); for (RDN rdn : subject.getRDNs()) { ASN1Primitive primitive = rdn.toASN1Primitive(); if (primitive instanceof ASN1Set) { ASN1Set asn1Set = (ASN1Set) primitive; for (int i = 0; i < asn1Set.size(); ++i) { AttributeTypeAndValue next = AttributeTypeAndValue.getInstance(asn1Set.getObjectAt(i)); if (OID_COMMON_NAME.equals(next.getType().toString())) { commonNames.add(next.getValue().toString()); } } } } return commonNames; } /** * Get the ending date of this certificate. * * @return the ending date */ public Date getEndDate() { AssertTools.assertNotNull(certificateHolder, "The certificate is not set"); return certificateHolder.getNotAfter(); } public AsymmetricKeys getKeysForSigning() { // Fill the public key if missing if (certificateHolder != null) { // Create missing keys if (keysForSigning == null) { keysForSigning = new AsymmetricKeys(); } // Create missing public key if (keysForSigning.getPublicKey() == null) { X509Certificate cert = getCertificate(); RSAPublicKey publicKey = (RSAPublicKey) cert.getPublicKey(); keysForSigning.setPublicKey( new RSAKeyParameters(false, publicKey.getModulus(), publicKey.getPublicExponent())); } } return keysForSigning; } /** * Get the starting date of this certificate. * * @return the starting date */ public Date getStartDate() { AssertTools.assertNotNull(certificateHolder, "The certificate is not set"); return certificateHolder.getNotBefore(); } /** * Get all the Subject Alternative Names. * * @return the SANs */ public Set<String> getSubjectAltNames() { AssertTools.assertNotNull(certificateHolder, "The certificate is not set"); Set<String> results = new HashSet<>(); X509Certificate x509Certificate = getCertificate(); try { Collection<List<?>> subjectAlternativeNames = x509Certificate.getSubjectAlternativeNames(); if (subjectAlternativeNames == null) { return results; } for (Object next : subjectAlternativeNames) { results.add(((List<?>) next).get(1).toString()); } } catch (Exception e) { throw new SmallToolsException("Problem parsing the certificate", e); } return results; } /** * Compute the SHA1 thumbprint. * * @return the SHA1 thumbprint */ public String getThumbprint() { AssertTools.assertNotNull(certificateHolder, "The certificate is not set"); try { return HashSha1.hashBytes(certificateHolder.getEncoded()); } catch (IOException e) { throw new SmallToolsException("Problem getting the thumbprint", e); } } /** * Check if the current time is in the certificate dates range. * * @return true if valid */ public boolean isValidDate() { return isValidDate(new Date()); } /** * Check if the specified time is in the certificate dates range. * * @param date * the time to check * @return true if valid */ public boolean isValidDate(Date date) { AssertTools.assertNotNull(certificateHolder, "The certificate is not set"); return DateTools.isAfter(date, certificateHolder.getNotBefore()) && DateTools.isBefore(date, certificateHolder.getNotAfter()); } /** * Check if the certificate was signed by the specified public key. * * @param signerPublicKey * the signer's public key * @return true if signed by it */ public boolean isValidSignature(AsymmetricKeyParameter signerPublicKey) { try { ContentVerifierProvider verifierProvider = new BcRSAContentVerifierProviderBuilder( new DefaultDigestAlgorithmIdentifierFinder()).build(signerPublicKey); return certificateHolder.isSignatureValid(verifierProvider); } catch (Exception e) { throw new SmallToolsException("Problem validating the certificate", e); } } /** * Check if the certificate was signed by the specified public key. * * @param signerPublicKey * the signer's pair of keys that contains the public key * @return true if signed by it */ public boolean isValidSignature(AsymmetricKeys signerPublicKey) { try { ContentVerifierProvider verifierProvider = new BcRSAContentVerifierProviderBuilder( new DefaultDigestAlgorithmIdentifierFinder()).build(signerPublicKey.getPublicKey()); return certificateHolder.isSignatureValid(verifierProvider); } catch (Exception e) { throw new SmallToolsException("Problem validating the certificate", e); } } /** * Check if the certificate was signed by the specified certificate. * * @param signerCertificate * the signer's certificate * @return true if signed by it */ public boolean isValidSignature(RSACertificate signerCertificate) { try { ContentVerifierProvider verifierProvider = new BcRSAContentVerifierProviderBuilder( new DefaultDigestAlgorithmIdentifierFinder()).build(signerCertificate.certificateHolder); return certificateHolder.isSignatureValid(verifierProvider); } catch (Exception e) { throw new SmallToolsException("Problem validating the certificate", e); } } /** * Save the certificate in a PEM file. * * @param fileName * the full path to the file */ public void saveCertificatePem(String fileName) { try { saveCertificatePem(new FileWriter(fileName)); } catch (IOException e) { throw new SmallToolsException("Could not save cert", e); } } /** * Save the certificate in a PEM writer. * * @param writer * the writer. Will be closed at the end */ public void saveCertificatePem(Writer writer) { AssertTools.assertNotNull(certificateHolder, "The certificate is not set"); PemWriter pemWriter = null; try { pemWriter = new PemWriter(writer); PemObjectGenerator pemObjectGenerator = new MiscPEMGenerator(certificateHolder); pemWriter.writeObject(pemObjectGenerator); } catch (Exception e) { throw new SmallToolsException("Could not save cert", e); } finally { CloseableTools.close(pemWriter); } } /** * Save the certificate in a PEM String. * * @return the pem */ public String saveCertificatePemAsString() { ByteArrayOutputStream result = new ByteArrayOutputStream(); saveCertificatePem(new OutputStreamWriter(result)); return result.toString(); } /** * Sign the {@link #setKeysForSigning(AsymmetricKeys)} with itself and put it in certificateHolder. * * @param certificateDetails * some information to store in the certificate * @return this */ public RSACertificate selfSign(CertificateDetails certificateDetails) { AssertTools.assertNotNull(keysForSigning, "The keysForSigning is not set"); AssertTools.assertNull(certificateHolder, "The certificate already exists"); try { RSAKeyDetails keyDetails = rsaCrypt.retrieveKeyDetails(keysForSigning); PrivateKey privKey = keyDetails.getJcaPrivateKey(); PublicKey publicKey = keyDetails.getJcaPublicKey(); ContentSigner sigGen = new JcaContentSignerBuilder("SHA256withRSA").setProvider("BC").build(privKey); SubjectPublicKeyInfo subPubKeyInfo = SubjectPublicKeyInfo.getInstance(publicKey.getEncoded()); Date startDate = certificateDetails.getStartDate(); Date endDate = certificateDetails.getEndDate(); BigInteger serial = certificateDetails.getSerial(); // Common Name X500Name issuer = new X500Name("CN=" + certificateDetails.getCommonName()); X509v3CertificateBuilder certificateBuilder = new X509v3CertificateBuilder(issuer, serial, startDate, endDate, issuer, subPubKeyInfo); // Subject Alternative Names (DNS) if (!CollectionsTools.isNullOrEmpty(certificateDetails.getSanDns())) { GeneralName[] altNames = new GeneralName[certificateDetails.getSanDns().size()]; int i = 0; for (String sanDns : certificateDetails.getSanDns()) { altNames[i++] = new GeneralName(GeneralName.dNSName, sanDns); } GeneralNames subjectAltNames = new GeneralNames(altNames); certificateBuilder.addExtension(Extension.subjectAlternativeName, false, subjectAltNames); } certificateHolder = certificateBuilder.build(sigGen); return this; } catch (Exception e) { throw new SmallToolsException("Problem signing the key", e); } } public RSACertificate setCertificateHolder(X509CertificateHolder certificateHolder) { this.certificateHolder = certificateHolder; return this; } public RSACertificate setKeysForSigning(AsymmetricKeys keysForSigning) { this.keysForSigning = keysForSigning; return this; } /** * Sign another public key. * * @param publicKeyToSign * the public key to sign * @param certificateDetails * some information to store in the certificate * @return the new certificate */ public RSACertificate signPublicKey(AsymmetricKeys publicKeyToSign, CertificateDetails certificateDetails) { try { PrivateKey privKey = rsaCrypt.retrieveKeyDetails(keysForSigning).getJcaPrivateKey(); PublicKey publicKey = rsaCrypt.retrieveKeyDetails(publicKeyToSign).getJcaPublicKey(); ContentSigner sigGen = new JcaContentSignerBuilder("SHA256withRSA").setProvider("BC").build(privKey); SubjectPublicKeyInfo subPubKeyInfo = SubjectPublicKeyInfo.getInstance(publicKey.getEncoded()); Date startDate = certificateDetails.getStartDate(); Date endDate = certificateDetails.getEndDate(); BigInteger serial = certificateDetails.getSerial(); X500Name issuer = new X500Name("CN=" + getCommonName()); X500Name subject = new X500Name("CN=" + certificateDetails.getCommonName()); X509v3CertificateBuilder certificateBuilder = new X509v3CertificateBuilder(issuer, serial, startDate, endDate, subject, subPubKeyInfo); // Subject Alternative Names (DNS) if (!CollectionsTools.isNullOrEmpty(certificateDetails.getSanDns())) { GeneralName[] altNames = new GeneralName[certificateDetails.getSanDns().size()]; int i = 0; for (String sanDns : certificateDetails.getSanDns()) { altNames[i++] = new GeneralName(GeneralName.dNSName, sanDns); } GeneralNames subjectAltNames = new GeneralNames(altNames); certificateBuilder.addExtension(Extension.subjectAlternativeName, false, subjectAltNames); } X509CertificateHolder newCert = certificateBuilder.build(sigGen); return new RSACertificate(newCert, publicKeyToSign); } catch (Exception e) { throw new SmallToolsException("Problem signing the key", e); } } }