package won.cryptography.service; import org.bouncycastle.asn1.ASN1ObjectIdentifier; import org.bouncycastle.asn1.ASN1Sequence; import org.bouncycastle.asn1.x500.X500Name; import org.bouncycastle.asn1.x500.X500NameBuilder; import org.bouncycastle.asn1.x500.style.BCStyle; import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; import org.bouncycastle.cert.CertIOException; import org.bouncycastle.cert.X509v3CertificateBuilder; import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; import org.bouncycastle.operator.ContentSigner; import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; import org.joda.time.DateTime; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.math.BigInteger; import java.net.URI; import java.net.URISyntaxException; import java.security.KeyPair; import java.security.cert.CertificateException; import java.security.cert.CertificateParsingException; import java.security.cert.X509Certificate; import java.util.*; /** * User: fsalcher * Date: 13.06.2014 */ public class CertificateService { private static final String PROVIDER_BC = org.bouncycastle.jce.provider.BouncyCastleProvider.PROVIDER_NAME; private final Logger logger = LoggerFactory.getLogger(getClass()); public CertificateService() { } /** * * @param serialNumber unique number within the certificate issuer * @param key key pair for which the certificate is to be created * @param commonName subject common name. For servers, this should be the host name. E.g. if a server supports * https at https://www.example.com/secure, the common name should be specified as www.example.com. * For clients, it can be anything. * @param webId webID that represents the identity of the subject or null if the subject has no webID * @return * @throws IOException */ public X509Certificate createSelfSignedCertificate(BigInteger serialNumber, KeyPair key, String commonName, String webId) { Map<ASN1ObjectIdentifier,String> subjectData = new HashMap<ASN1ObjectIdentifier,String>(); subjectData.put(BCStyle.CN, commonName); // ToDo: which attributes to use? make them configurable? // subjectData.put(BCStyle.C, "your county"); // subjectData.put(BCStyle.O, "your organization name"); // subjectData.put(BCStyle.OU, "your organization unit name"); // subjectData.put(BCStyle.E, "your e-mail"); subjectData.put(BCStyle.OU, "Web of Needs"); return createSelfSignedCertificate(serialNumber, key, subjectData, webId); } /** * * @param serialNumber unique number within the certificate issuer. * @param key key pair for which the certificate is to be created. * @param subjectData subject data to be put in distinguished name field of the certificate. Such as CN - common name, * O - organization, OU - organization unit, L - locality, C - county, etc. * @param webId webID that represents the identity of the subject or null if the subject has no webID. * @return * @throws IOException */ public X509Certificate createSelfSignedCertificate(BigInteger serialNumber, KeyPair key, Map<ASN1ObjectIdentifier,String> subjectData, String webId) { X509Certificate cert = null; try { X509v3CertificateBuilder certBuilder = createBuilderWithBasicInfo(serialNumber, key, subjectData); if (webId != null) { addToCertBuilderWebIdInfo(certBuilder, webId); } ContentSigner certSigner = createContentSigner(key); cert = new JcaX509CertificateConverter().setProvider(PROVIDER_BC) .getCertificate(certBuilder.build(certSigner)); cert.checkValidity(new Date()); cert.verify(cert.getPublicKey()); } catch (Exception e) { throw new IllegalArgumentException("Could not create certificate for key pair with algorithm " + key.getPublic() .getAlgorithm(), e); } return cert; } private void addToCertBuilderWebIdInfo(final X509v3CertificateBuilder certBuilder, final String webID) throws CertificateException { if (webID != null) { org.bouncycastle.asn1.x509.GeneralName[] genNames = new org.bouncycastle.asn1.x509.GeneralName[1]; genNames[0] = new org.bouncycastle.asn1.x509.GeneralName(org.bouncycastle.asn1.x509.GeneralName .uniformResourceIdentifier, webID); try { certBuilder.addExtension(org.bouncycastle.asn1.x509.Extension.subjectAlternativeName, false, new org.bouncycastle .asn1.x509.GeneralNames(genNames)); } catch (CertIOException e) { throw new CertificateException("Could not add webID to the certificate" + webID, e); } } } private ContentSigner createContentSigner(final KeyPair key) throws Exception { String signatureAlgorithm; if (key.getPublic().getAlgorithm().contains("ECDSA")) { signatureAlgorithm = "SHA256WithECDSA"; } else if (key.getPublic().getAlgorithm().contains("RSA")) { signatureAlgorithm = "SHA256WithRSA"; } else if (key.getPublic().getAlgorithm().contains("DSA")) { signatureAlgorithm = "SHA256WithDSA"; } else { throw new IllegalArgumentException(key.getPublic().getAlgorithm() + " is not supported"); } JcaContentSignerBuilder csBuilder = new JcaContentSignerBuilder(signatureAlgorithm); ContentSigner sigGen = csBuilder.setProvider(PROVIDER_BC).build(key.getPrivate()); return sigGen; } private X509v3CertificateBuilder createBuilderWithBasicInfo(BigInteger serialNumber, KeyPair key, Map<ASN1ObjectIdentifier,String> subjectData) { DateTime today = new DateTime(); Date notBefore = today.minusDays(1).withTimeAtStartOfDay().toDate(); Date notAfter = today.plusYears(2).withTimeAtStartOfDay().toDate(); X500NameBuilder nameBuilder = new X500NameBuilder(BCStyle.INSTANCE); for (ASN1ObjectIdentifier objectIdentifier : subjectData.keySet()) { nameBuilder.addRDN(objectIdentifier, subjectData.get(objectIdentifier)); } X500Name subject = nameBuilder.build(); SubjectPublicKeyInfo subjPubKeyInfo = new SubjectPublicKeyInfo(ASN1Sequence.getInstance( key.getPublic().getEncoded())); X509v3CertificateBuilder certGen = new X509v3CertificateBuilder( subject, serialNumber, notBefore, notAfter, subject, subjPubKeyInfo); return certGen; } public static List<URI> getWebIdFromSubjectAlternativeNames(final X509Certificate cert) throws CertificateParsingException { List<URI> webIDs = new ArrayList<URI>(); Collection<List<?>> alternativeNames = cert.getSubjectAlternativeNames(); if (alternativeNames != null) { for (List<?> alternativeName : alternativeNames) { Integer id = (Integer) alternativeName.get(0); // according to https://tools.ietf.org/html/rfc3280#page-33 // index 6 is used to provide URI in subject alternative names, represented as IA5String. // This is how subject's webID is represented in the certificate. if (id == 6) { try { URI webID = new URI((String) alternativeName.get(1)); webIDs.add(webID); } catch (URISyntaxException e) { throw new CertificateParsingException("Could not retrieve webID from SAN", e); } } } } return webIDs; } }