/*
* Copyright (C) 2013 Intel Corporation
* All rights reserved.
*/
package com.intel.mtwilson.tag.common;
import com.intel.mtwilson.tag.model.OID;
import com.intel.mtwilson.tag.model.x509.*;
import com.intel.dcsg.cpg.crypto.RsaCredentialX509;
import com.intel.dcsg.cpg.io.UUID;
import com.intel.dcsg.cpg.validation.BuilderModel;
import java.io.IOException;
import org.bouncycastle.asn1.ASN1Encodable;
import java.math.BigInteger;
import java.security.PrivateKey;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.concurrent.TimeUnit;
import javax.security.auth.x500.X500Principal;
import org.apache.commons.codec.binary.Base64;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.DEROctetString;
import org.bouncycastle.asn1.x500.AttributeTypeAndValue;
import org.bouncycastle.asn1.x500.RDN;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.cert.AttributeCertificateHolder;
import org.bouncycastle.cert.AttributeCertificateIssuer;
import org.bouncycastle.cert.X509AttributeCertificateHolder;
import org.bouncycastle.cert.X509v2AttributeCertificateBuilder;
import org.bouncycastle.crypto.util.PrivateKeyFactory;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.DefaultDigestAlgorithmIdentifierFinder;
import org.bouncycastle.operator.DefaultSignatureAlgorithmIdentifierFinder;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.bc.BcRSAContentSignerBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This convenience class implements the Builder pattern in order to simplify creation of X509 Attribute Certificates.
* Certificates created by this builder do NOT have a public key. They only have attributes about the subject/holder.
*
* NOTE: this is different than X509 Public Key Certificates which can be built using cpg-crypto X509Builder.
*
* You should create a new instance of this class for every certificate.
*
* This class requires Bouncy Castle
*
* @since 0.1
* @author jbuhacoff
*/
public class X509AttrBuilder extends BuilderModel {
private final Logger log = LoggerFactory.getLogger(getClass());
private X500Name issuerName = null;
private PrivateKey issuerPrivateKey = null;
private BigInteger serialNumber = null;
private X500Name subjectName = null;
private Date notBefore = null;
private Date notAfter = null;
private ArrayList<Attribute> attributes = new ArrayList<>();
public static class Attribute {
public ASN1ObjectIdentifier oid;
public ASN1Encodable value;
public Attribute(ASN1ObjectIdentifier oid, ASN1Encodable value) {
this.oid = oid;
this.value = value;
}
}
public X509AttrBuilder() {
}
/**
* Supports fluent writing: X509AttrBuilder x509 = X509AttrBuilder.factory().subjectName(...).alternativeName(...);
* if( x509.isValid() ) { X509Certificate cert = x509.build(); } // check for isValid() is optional, but you will
* get null result from build if it's not valid
*
* @return
*/
public static X509AttrBuilder factory() {
return new X509AttrBuilder();
}
public X509AttrBuilder expires(long expiration, TimeUnit units) {
notBefore = new Date();
notAfter = new Date(notBefore.getTime() + TimeUnit.MILLISECONDS.convert(expiration, units));
return this;
}
public X509AttrBuilder valid(Date from, Date to) {
notBefore = from;
notAfter = to;
return this;
}
public X509AttrBuilder serialNumber(BigInteger number) {
serialNumber = number;
return this;
}
public X509AttrBuilder randomSerial() {
serialNumber = new BigInteger(64, new SecureRandom());
return this;
}
public X509AttrBuilder dateSerial() {
serialNumber = new BigInteger( String.valueOf(Calendar.getInstance().getTimeInMillis()) );
return this;
}
/*
public X509AttrBuilder subjectName(sun.security.x509.X500Name subjectName) {
return subjectName(subjectName.getRFC2253Name());
}
*/
public X509AttrBuilder subjectUuid(UUID uuid) {
DEROctetString uuidText = new DEROctetString(uuid.toByteArray().getBytes());
ASN1ObjectIdentifier oid = new ASN1ObjectIdentifier(OID.HOST_UUID);
AttributeTypeAndValue attr = new AttributeTypeAndValue(oid, uuidText);
RDN rdn = new RDN(attr);
subjectName = new X500Name(new RDN[]{rdn});
return this;
}
/**
*
* @param dn like "CN=Dave, OU=JavaSoft, O=Sun Microsystems, C=US"
* @return
*/
/*
public X509AttrBuilder subjectName(String dn) {
try {
}
catch(Exception e) {
fault(e, "subjectName(%s)", dn);
}
return this;
}
*/
public X509AttrBuilder issuerName(X500Principal principal) {
issuerName = new X500Name(principal.getName()); // principal.getName() produces RFC 2253 output which we hope is compatible wtih X500Name directory name input
return this;
}
public X509AttrBuilder issuerName(X500Name issuerName) {
this.issuerName = issuerName;
return this;
}
public X509AttrBuilder issuerName(X509Certificate issuerCertificate) {
return issuerName(issuerCertificate.getSubjectX500Principal());
}
/**
* Sets the issuerPrivateKey and issuerName using the provided credential.
*
* @param issuerCredential
* @return
*/
public X509AttrBuilder issuer(RsaCredentialX509 issuerCredential) {
try {
issuerPrivateKey(issuerCredential.getPrivateKey());
issuerName(issuerCredential.getCertificate());
} catch (Exception e) {
fault(e, "issuer(%s)", issuerCredential == null ? "null" : issuerCredential.getCertificate().getIssuerX500Principal().getName());
}
return this;
}
public X509AttrBuilder issuerPrivateKey(PrivateKey privateKey) {
this.issuerPrivateKey = privateKey;
return this;
}
public X509AttrBuilder attribute(String name, String textValue) {
attributes.add(new Attribute(new ASN1ObjectIdentifier(UTF8NameValueSequence.OID), new UTF8NameValueSequence(name, textValue)));
return this;
}
public X509AttrBuilder attribute(String name, String... textValues) {
attributes.add(new Attribute(new ASN1ObjectIdentifier(UTF8NameValueSequence.OID), new UTF8NameValueSequence(name, textValues)));
return this;
}
public X509AttrBuilder attribute(ASN1ObjectIdentifier oid, ASN1Encodable value) {
attributes.add(new Attribute(oid, value));
return this;
}
public X509AttrBuilder attribute(Attribute attribute) {
attributes.add(attribute);
return this;
}
public byte[] build() {
if (notBefore == null || notAfter == null) {
expires(1, TimeUnit.DAYS); // 1 day default
}
if (serialNumber == null) {
dateSerial();
}
if (subjectName == null) {
fault("Subject name is missing");
}
if (issuerName == null) {
fault("Issuer name is missing");
}
if (issuerPrivateKey == null) {
fault("Issuer private key is missing");
}
if (attributes.isEmpty()) {
fault("No attributes selected");
}
try {
if (getFaults().isEmpty()) {
AlgorithmIdentifier sigAlgId = new DefaultSignatureAlgorithmIdentifierFinder().find("SHA256withRSA");
AlgorithmIdentifier digAlgId = new DefaultDigestAlgorithmIdentifierFinder().find(sigAlgId);
ContentSigner authority = new BcRSAContentSignerBuilder(sigAlgId, digAlgId).build(PrivateKeyFactory.createKey(issuerPrivateKey.getEncoded())); // create a bouncy castle content signer convert using our existing private key
// second, prepare the attribute certificate
AttributeCertificateHolder holder = new AttributeCertificateHolder(subjectName); // which is expected to be a UUID like this: 33766a63-5c55-4461-8a84-5936577df450
AttributeCertificateIssuer issuer = new AttributeCertificateIssuer(issuerName);
X509v2AttributeCertificateBuilder builder = new X509v2AttributeCertificateBuilder(holder, issuer, serialNumber, notBefore, notAfter);
for (Attribute attribute : attributes) {
builder.addAttribute(attribute.oid, attribute.value);
}
// third, add extensions - information regarding the certificate itself which is not an attribute of the subject
// builder.addExtension(oid, /*critical*/true, /*asn1encodable*/)
// fourth, sign the attribute certificate
X509AttributeCertificateHolder cert = builder.build(authority);
log.debug("cert: {}", Base64.encodeBase64String(cert.getEncoded())); // MIICGDCCAQACAQEwH6EdpBswGTEXMBUGAWkEEJKnGiKMF0UioYv9PtPQCzmgXzBdpFswWTEQMA4GA1UEAwwHQXR0ciBDQTEMMAoGA1UECwwDQ1BHMQ0wCwYDVQQLDAREQ1NHMQ4wDAYDVQQKDAVJbnRlbDELMAkGA1UECAwCQ0ExCzAJBgNVBAYTAlVTMA0GCSqGSIb3DQEBBQUAAgEBMCIYDzIwMTMwODA4MjIyMTEzWhgPMjAxMzA5MDgyMjIxMTNaMEMwEwYLKwYBBAG9hDcBAQExBAwCVVMwEwYLKwYBBAG9hDgCAgIxBAwCQ0EwFwYLKwYBBAG9hDkDAwMxCAwGRm9sc29tMA0GCSqGSIb3DQEBBQUAA4IBAQCcN8KjjmR2H3LT5aL1SCFS4joy/7vAd3/xdJtkqrb3UAQHMdUUJQHf3frJsMJs22m0So0xs/f1sB15frC1LsQGF5+RYVXsClv0glStWbPYiqEfdM7dc/RDMRtrXKEH3sBlxMT7YS/g5E6qwmKZX9shQ3BYmeZi5A3DTzgHCbA3Cm4/MQbgWGjoamfWZ9EDk4Bww2y0ueRi60PfoLg43rcijr8Wf+JEzCRw040vIaH3DtFdmzvvGRdqE3YlEkrUL3gEIZNY3Po1NL4cb238vT5CHZTt9NyD7xSv0XkwOY4RbSUdYBsxfH3mEcdQ6LtJdfF1BUXfMThKN3TctFcY/dLF
return cert.getEncoded(); //X509AttributeCertificate.valueOf(cert.getEncoded());
}
return null;
} catch (IOException | OperatorCreationException e) {
fault(e, "cannot sign certificate");
return null;
} finally {
done();
}
}
}