/*
* This file is part of rockframework.
*
* rockframework is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* rockframework is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>;.
*/
package br.net.woodstock.rockframework.security.cert.impl;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.PrivateKey;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.Map.Entry;
import java.util.Vector;
import org.bouncycastle.asn1.ASN1Encodable;
import org.bouncycastle.asn1.ASN1EncodableVector;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.ASN1Primitive;
import org.bouncycastle.asn1.BERTags;
import org.bouncycastle.asn1.DERIA5String;
import org.bouncycastle.asn1.DEROctetString;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.asn1.DERTaggedObject;
import org.bouncycastle.asn1.misc.MiscObjectIdentifiers;
import org.bouncycastle.asn1.x509.AuthorityInformationAccess;
import org.bouncycastle.asn1.x509.AuthorityKeyIdentifier;
import org.bouncycastle.asn1.x509.BasicConstraints;
import org.bouncycastle.asn1.x509.CRLDistPoint;
import org.bouncycastle.asn1.x509.DistributionPoint;
import org.bouncycastle.asn1.x509.DistributionPointName;
import org.bouncycastle.asn1.x509.GeneralName;
import org.bouncycastle.asn1.x509.GeneralNames;
import org.bouncycastle.asn1.x509.KeyPurposeId;
import org.bouncycastle.asn1.x509.PolicyInformation;
import org.bouncycastle.asn1.x509.PolicyQualifierInfo;
import org.bouncycastle.asn1.x509.SubjectKeyIdentifier;
import org.bouncycastle.asn1.x509.X509Extension;
import org.bouncycastle.asn1.x509.X509ObjectIdentifiers;
import org.bouncycastle.cert.CertIOException;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils;
import org.bouncycastle.cert.jcajce.JcaX509v1CertificateBuilder;
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import br.net.woodstock.rockframework.security.cert.CertificateBuilder;
import br.net.woodstock.rockframework.security.cert.CertificateException;
import br.net.woodstock.rockframework.security.cert.CertificateRequest;
import br.net.woodstock.rockframework.security.cert.CertificateType;
import br.net.woodstock.rockframework.security.cert.CertificateVersionType;
import br.net.woodstock.rockframework.security.cert.ExtendedKeyUsageType;
import br.net.woodstock.rockframework.security.cert.KeyUsageType;
import br.net.woodstock.rockframework.security.cert.PrivateKeyHolder;
import br.net.woodstock.rockframework.security.util.BouncyCastleProviderHelper;
import br.net.woodstock.rockframework.security.util.SecurityUtils;
import br.net.woodstock.rockframework.utils.ConditionUtils;
public class BouncyCastleCertificateBuilder implements CertificateBuilder {
private static BouncyCastleCertificateBuilder instance = new BouncyCastleCertificateBuilder();
protected BouncyCastleCertificateBuilder() {
super();
}
@Override
public PrivateKeyHolder build(final CertificateRequest request) {
try {
BouncyCastleCertificateRequest bcRequest = new BouncyCastleCertificateRequest(request);
PrivateKeyHolder privateKeyHolder = null;
if (CertificateVersionType.V3.equals(bcRequest.getVersion())) {
privateKeyHolder = this.buildV3Certificate(bcRequest);
} else {
privateKeyHolder = this.buildV1Certificate(bcRequest);
}
return privateKeyHolder;
} catch (Exception e) {
throw new CertificateException(e);
}
}
protected PrivateKeyHolder buildV1Certificate(final BouncyCastleCertificateRequest request) throws OperatorCreationException, GeneralSecurityException, IOException {
JcaX509v1CertificateBuilder builder = new JcaX509v1CertificateBuilder(request.getIssuerAsX500Name(), request.getSerialNumber(), request.getNotBefore(), request.getNotAfter(), request.getSubjectAsX500Name(), request.getPublicKey());
JcaContentSignerBuilder contentSignerBuilder = new JcaContentSignerBuilder(request.getSignAlgorithm());
contentSignerBuilder.setProvider(BouncyCastleProviderHelper.PROVIDER_NAME);
ContentSigner contentSigner = contentSignerBuilder.build(request.getPrivateKey());
X509CertificateHolder holder = builder.build(contentSigner);
X509Certificate certificate = (X509Certificate) SecurityUtils.getCertificateFromFile(holder.getEncoded(), CertificateType.X509);
PrivateKey privateKey = request.getPrivateKey();
PrivateKeyHolder privateKeyHolder = new PrivateKeyHolder(privateKey, new Certificate[] { certificate });
return privateKeyHolder;
}
protected PrivateKeyHolder buildV3Certificate(final BouncyCastleCertificateRequest request) throws OperatorCreationException, GeneralSecurityException, IOException {
JcaX509v3CertificateBuilder builder = null;
ContentSigner contentSigner = null;
JcaContentSignerBuilder contentSignerBuilder = new JcaContentSignerBuilder(request.getSignAlgorithm());
contentSignerBuilder.setProvider(BouncyCastleProviderHelper.PROVIDER_NAME);
if ((request.getIssuerPrivateKey() != null) && (request.getIssuerCertificate() != null)) {
builder = new JcaX509v3CertificateBuilder(request.getIssuerCertificate(), request.getSerialNumber(), request.getNotBefore(), request.getNotAfter(), request.getSubjectAsX500Principal(), request.getPublicKey());
AuthorityKeyIdentifier authorityKeyIdentifier = new JcaX509ExtensionUtils().createAuthorityKeyIdentifier(request.getIssuerCertificate());
builder.addExtension(X509Extension.authorityKeyIdentifier, false, authorityKeyIdentifier);
contentSigner = contentSignerBuilder.build(request.getIssuerPrivateKey());
} else {
builder = new JcaX509v3CertificateBuilder(request.getIssuerAsX500Name(), request.getSerialNumber(), request.getNotBefore(), request.getNotAfter(), request.getSubjectAsX500Name(), request.getPublicKey());
contentSigner = contentSignerBuilder.build(request.getPrivateKey());
}
SubjectKeyIdentifier subjectKeyIdentifier = new JcaX509ExtensionUtils().createSubjectKeyIdentifier(request.getPublicKey());
builder.addExtension(X509Extension.subjectKeyIdentifier, false, subjectKeyIdentifier);
this.addV3KeyUsage(builder, request);
this.addV3ExtendedKeyUsage(builder, request);
this.addV3CertificatePolicies(builder, request);
this.addV3OtherNames(builder, request);
this.addV3Comment(builder, request);
this.addV3CRLDistPoint(builder, request);
this.addV3OcspUrl(builder, request);
this.addV3PolicyUrl(builder, request);
this.addV3CAExtensions(builder, request);
// builder.addExtension(MiscObjectIdentifiers.netscapeCertType, false, new
// NetscapeCertType(NetscapeCertType.objectSigning | NetscapeCertType.smime));
X509CertificateHolder holder = builder.build(contentSigner);
X509Certificate certificate = (X509Certificate) SecurityUtils.getCertificateFromFile(holder.getEncoded(), CertificateType.X509);
if ((request.getIssuerPrivateKey() != null) && (request.getIssuerCertificate() != null)) {
certificate.verify(request.getIssuerCertificate().getPublicKey());
}
PrivateKey privateKey = request.getPrivateKey();
PrivateKeyHolder privateKeyHolder = new PrivateKeyHolder(privateKey, new Certificate[] { certificate });
return privateKeyHolder;
}
protected void addV3KeyUsage(final JcaX509v3CertificateBuilder builder, final BouncyCastleCertificateRequest request) throws CertIOException {
if (request.isCa()) {
int usage = 0;
usage |= BouncyCastleCertificateHelper.toKeyUsage(KeyUsageType.CRL_SIGN);
usage |= BouncyCastleCertificateHelper.toKeyUsage(KeyUsageType.DATA_ENCIPHERMENT);
usage |= BouncyCastleCertificateHelper.toKeyUsage(KeyUsageType.DIGITAL_SIGNATURE);
usage |= BouncyCastleCertificateHelper.toKeyUsage(KeyUsageType.KEY_AGREEMENT);
usage |= BouncyCastleCertificateHelper.toKeyUsage(KeyUsageType.KEY_CERT_SIGN);
usage |= BouncyCastleCertificateHelper.toKeyUsage(KeyUsageType.KEY_ENCIPHERMENT);
usage |= BouncyCastleCertificateHelper.toKeyUsage(KeyUsageType.NON_REPUDIATION);
org.bouncycastle.asn1.x509.KeyUsage ku = new org.bouncycastle.asn1.x509.KeyUsage(usage);
builder.addExtension(X509Extension.keyUsage, true, ku);
} else {
if (!request.getKeyUsage().isEmpty()) {
int usage = 0;
for (KeyUsageType keyUsage : request.getKeyUsage()) {
usage = usage | BouncyCastleCertificateHelper.toKeyUsage(keyUsage);
}
org.bouncycastle.asn1.x509.KeyUsage ku = new org.bouncycastle.asn1.x509.KeyUsage(usage);
builder.addExtension(X509Extension.keyUsage, true, ku);
}
}
}
protected void addV3ExtendedKeyUsage(final JcaX509v3CertificateBuilder builder, final BouncyCastleCertificateRequest request) throws CertIOException {
if (!request.getExtendedKeyUsage().isEmpty()) {
Vector<ASN1Primitive> vector = new Vector<ASN1Primitive>();
for (ExtendedKeyUsageType keyUsageType : request.getExtendedKeyUsage()) {
KeyPurposeId keyPurposeId = BouncyCastleCertificateHelper.toExtendedKeyUsage(keyUsageType);
if (keyPurposeId != null) {
vector.add(keyPurposeId);
}
}
if (vector.size() > 0) {
org.bouncycastle.asn1.x509.ExtendedKeyUsage extendedKeyUsage = new org.bouncycastle.asn1.x509.ExtendedKeyUsage(vector);
builder.addExtension(X509Extension.extendedKeyUsage, true, extendedKeyUsage);
}
}
}
protected void addV3CertificatePolicies(final JcaX509v3CertificateBuilder builder, final BouncyCastleCertificateRequest request) throws CertIOException {
if (ConditionUtils.isNotEmpty(request.getCertificatePolicies())) {
ASN1EncodableVector vector = new ASN1EncodableVector();
for (Entry<String, String> entry : request.getCertificatePolicies().entrySet()) {
String oid = entry.getKey();
String value = entry.getValue();
ASN1ObjectIdentifier policyIdentifier = new ASN1ObjectIdentifier(oid);
PolicyQualifierInfo policyQualifierInfo = new PolicyQualifierInfo(X509Extension.cRLDistributionPoints, new DERIA5String(value));
DERSequence policyQualifiers = new DERSequence(new ASN1Encodable[] { policyQualifierInfo });
PolicyInformation policyInformation = new PolicyInformation(policyIdentifier, policyQualifiers);
vector.add(policyInformation);
}
DERSequence sequence = new DERSequence(vector);
builder.addExtension(X509Extension.certificatePolicies, false, sequence);
}
}
protected void addV3OtherNames(final JcaX509v3CertificateBuilder builder, final BouncyCastleCertificateRequest request) throws CertIOException {
if ((ConditionUtils.isNotEmpty(request.getEmail())) || (!request.getOtherNames().isEmpty())) {
ASN1EncodableVector vector = new ASN1EncodableVector();
if (ConditionUtils.isNotEmpty(request.getEmail())) {
GeneralName rfc822Name = new GeneralName(GeneralName.rfc822Name, request.getEmail());
vector.add(rfc822Name);
}
if (!request.getOtherNames().isEmpty()) {
for (Entry<String, String> entry : request.getOtherNames().entrySet()) {
String oid = entry.getKey();
String value = entry.getValue();
ASN1ObjectIdentifier identifier = new ASN1ObjectIdentifier(oid);
DEROctetString octetString = new DEROctetString(value.getBytes());
DERTaggedObject taggedObject = new DERTaggedObject(BERTags.OCTET_STRING, octetString);
DERSequence sequence = new DERSequence(new ASN1Encodable[] { identifier, taggedObject });
GeneralName name = new GeneralName(GeneralName.otherName, sequence);
vector.add(name);
}
}
if (vector.size() > 0) {
GeneralNames subjectAltName = GeneralNames.getInstance(new DERSequence(vector));
builder.addExtension(X509Extension.subjectAlternativeName, false, subjectAltName.toASN1Primitive());
}
}
}
protected void addV3CRLDistPoint(final JcaX509v3CertificateBuilder builder, final BouncyCastleCertificateRequest request) throws CertIOException {
if (ConditionUtils.isNotEmpty(request.getCrlDistPoint())) {
GeneralName gn = new GeneralName(6, new DERIA5String(request.getCrlDistPoint()));
ASN1EncodableVector vec = new ASN1EncodableVector();
vec.add(gn);
GeneralNames generalNames = GeneralNames.getInstance(new DERSequence(vec));
DistributionPointName distributionPointName = new DistributionPointName(0, generalNames);
CRLDistPoint crlDistPoint = new CRLDistPoint(new DistributionPoint[] { new DistributionPoint(distributionPointName, null, null) });
builder.addExtension(X509Extension.cRLDistributionPoints, false, crlDistPoint);
builder.addExtension(MiscObjectIdentifiers.netscapeCApolicyURL, false, new DERIA5String(request.getCrlDistPoint()));
}
}
protected void addV3OcspUrl(final JcaX509v3CertificateBuilder builder, final BouncyCastleCertificateRequest request) throws CertIOException {
if (ConditionUtils.isNotEmpty(request.getOcspURL())) {
GeneralName ocspLocation = new GeneralName(6, new DERIA5String(request.getOcspURL()));
builder.addExtension(X509Extension.authorityInfoAccess, false, new AuthorityInformationAccess(X509ObjectIdentifiers.ocspAccessMethod, ocspLocation));
}
}
protected void addV3PolicyUrl(final JcaX509v3CertificateBuilder builder, final BouncyCastleCertificateRequest request) throws CertIOException {
if (ConditionUtils.isNotEmpty(request.getPolicyURL())) {
builder.addExtension(MiscObjectIdentifiers.netscapeCApolicyURL, false, new DERIA5String(request.getPolicyURL()));
}
}
protected void addV3Comment(final JcaX509v3CertificateBuilder builder, final BouncyCastleCertificateRequest request) throws CertIOException {
if (ConditionUtils.isNotEmpty(request.getComment())) {
builder.addExtension(MiscObjectIdentifiers.netscapeCertComment, false, new DERIA5String(request.getComment()));
}
}
protected void addV3CAExtensions(final JcaX509v3CertificateBuilder builder, final BouncyCastleCertificateRequest request) throws CertIOException {
if (request.isCa()) {
builder.addExtension(X509Extension.basicConstraints, true, new BasicConstraints(true));
}
}
public static BouncyCastleCertificateBuilder getInstance() {
return BouncyCastleCertificateBuilder.instance;
}
}