/*
* Copyright (C) 2013 Intel Corporation
* All rights reserved.
*/
package com.intel.mtwilson.tag.model;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.intel.dcsg.cpg.crypto.Sha256Digest;
import com.intel.dcsg.cpg.io.UUID;
import com.intel.mtwilson.tag.model.x509.UTF8NameValueMicroformat;
import com.intel.mtwilson.tag.model.x509.UTF8NameValueSequence;
import java.io.IOException;
import java.math.BigInteger;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;
import org.bouncycastle.asn1.ASN1Encodable;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.DEROctetString;
import org.bouncycastle.asn1.DERUTF8String;
import org.bouncycastle.asn1.x500.AttributeTypeAndValue;
import org.bouncycastle.asn1.x500.RDN;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x509.Attribute;
import org.bouncycastle.cert.X509AttributeCertificateHolder;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.operator.ContentVerifierProvider;
import org.bouncycastle.operator.DefaultDigestAlgorithmIdentifierFinder;
import org.bouncycastle.operator.bc.BcRSAContentVerifierProviderBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Convenience class that wraps the Bouncy Castle API to walk the ASN1 tree and extract the information that we are
* interested in.
*
* @author jbuhacoff
*/
public class X509AttributeCertificate {
private static Logger log = LoggerFactory.getLogger(X509AttributeCertificate.class);
private final byte[] encoded;
private String issuer;
private BigInteger serialNumber = null;
private String subject = null;
// private UUID subjectUuid = null;
private Date notBefore;
private Date notAfter;
private ArrayList<Attribute> attributes = new ArrayList<>();
private ArrayList<UTF8NameValueMicroformat> tags1 = new ArrayList<>();
private ArrayList<UTF8NameValueSequence> tags2 = new ArrayList<>();
private ArrayList<ASN1Encodable> tagsOther = new ArrayList<>();
private X509AttributeCertificate(byte[] encoded) {
this.encoded = encoded;
}
public byte[] getEncoded() {
return encoded;
}
public byte[] getFingerprintSha256() {
return Sha256Digest.digestOf(encoded).toByteArray();
}
public String getIssuer() {
return issuer;
}
public BigInteger getSerialNumber() {
return serialNumber;
}
public String getSubject() {
return subject;
}
public Date getNotBefore() {
return notBefore;
}
public Date getNotAfter() {
return notAfter;
}
public List<Attribute> getAttribute() {
return attributes;
}
public <T extends ASN1Encodable> List<T> getAttributes(Class<T> clazz) {
if( clazz.equals(UTF8NameValueMicroformat.class) ) {
return (List<T>)tags1;
}
if( clazz.equals(UTF8NameValueSequence.class)) {
return (List<T>)tags2;
}
return (List<T>)tagsOther;
}
@Override
public String toString() {
return Base64.encodeBase64String(encoded);
}
/**
*
* @param encodedCertificate
* @return
*/
@JsonCreator
public static X509AttributeCertificate valueOf(@JsonProperty("encoded") byte[] encodedCertificate) {
X509AttributeCertificate result = new X509AttributeCertificate(encodedCertificate);
X509AttributeCertificateHolder cert;
try {
cert = new X509AttributeCertificateHolder(encodedCertificate);
}
catch(IOException e) {
throw new IllegalArgumentException(e);
}
log.debug("issuer: {}", StringUtils.join(cert.getIssuer().getNames(), "; ")); // calls toString() on each X500Name so we get the default representation; we can do it ourselves for custom display; output example: CN=Attr CA,OU=CPG,OU=DCSG,O=Intel,ST=CA,C=US
result.issuer = StringUtils.join(cert.getIssuer().getNames(), "; "); // but expected to be only one
log.debug("serial number: {}", cert.getSerialNumber().toString()); // output example: 1
result.serialNumber = cert.getSerialNumber();
log.debug("holder: {}", StringUtils.join(cert.getHolder().getEntityNames(), ", ")); // output example: 2.25=#041092a71a228c174522a18bfd3ed3d00b39
// now let's get the UUID specifically out of this
log.debug("holder has {} entity names", cert.getHolder().getEntityNames().length);
for (X500Name entityName : cert.getHolder().getEntityNames()) {
log.debug("holder entity name has {} rdns", entityName.getRDNs().length);
for (RDN rdn : entityName.getRDNs()) {
log.debug("entity rdn is multivalued? {}", rdn.isMultiValued());
AttributeTypeAndValue attr = rdn.getFirst();
if (attr.getType().toString().equals(OID.HOST_UUID)) {
UUID uuid = UUID.valueOf(DEROctetString.getInstance(attr.getValue()).getOctets());
log.debug("holder uuid: {}", uuid);
result.subject = uuid.toString();// example: 33766a63-5c55-4461-8a84-5936577df450
}
}
}
// if we ddin't identify the UUID, just display the subject same way we did the issuer... concat all the entity names. example: 2.25=#041033766a635c5544618a845936577df450 (notice that in the value, there's a #0410 prepended to the uuid 33766a635c5544618a845936577df450)
if (result.subject == null) {
result.subject = StringUtils.join(cert.getHolder().getEntityNames(), "; ");
}
log.debug("not before: {}", cert.getNotBefore()); // output example: Thu Aug 08 15:21:13 PDT 2013
log.debug("not after: {}", cert.getNotAfter()); // output example: Sun Sep 08 15:21:13 PDT 2013
result.notBefore = cert.getNotBefore();
result.notAfter = cert.getNotAfter();
Attribute[] attributes = cert.getAttributes();
result.tags1 = new ArrayList<>();
result.tags2 = new ArrayList<>();
result.tagsOther = new ArrayList<>();
for (Attribute attr : attributes) {
log.debug("attr {} is {}", attr.hashCode(), attr.toString());
result.attributes.add(attr);
for (ASN1Encodable value : attr.getAttributeValues()) {
// log.trace("encoded value: {}", Base64.encodeBase64String(value.getEncoded())); // throws IOException
// log.debug("attribute: {} is {}", attr.getAttrType().toString(), DERUTF8String.getInstance(value).getString()); // our values are just UTF-8 strings but if you use new String(value.getEncoded()) you will get two extra spaces at the beginning of the string
// result.tags.add(new AttributeOidAndValue(attr.getAttrType().toString(), DERUTF8String.getInstance(value).getString()));
if( attr.getAttrType().toString().equals(UTF8NameValueMicroformat.OID)) {
log.debug("name-value microformat attribute: {}", DERUTF8String.getInstance(value).getString()); // our values are just UTF-8 strings but if you use new String(value.getEncoded()) you will get two extra spaces at the beginning of the string
UTF8NameValueMicroformat microformat = new UTF8NameValueMicroformat(DERUTF8String.getInstance(value));
log.debug("name-value microformat attribute (2) name {} value {}", microformat.getName(), microformat.getValue());
result.tags1.add(microformat);
}
else if( attr.getAttrType().toString().equals(UTF8NameValueSequence.OID)) {
UTF8NameValueSequence sequence = new UTF8NameValueSequence(ASN1Sequence.getInstance(value));
String name = sequence.getName();
List<String> values = sequence.getValues();
log.debug("name-values asn.1 attribute {} values {}", name, values);
result.tags2.add(sequence);
}
else {
log.debug("unrecognzied attribute type {}", attr.getAttrType().toString());
result.tagsOther.add(value);
}
/*
* output examples:
* attribute: 1.3.6.1.4.1.99999.1.1.1.1 is US
* attribute: 1.3.6.1.4.1.99999.2.2.2.2 is CA
* attribute: 1.3.6.1.4.1.99999.3.3.3.3 is Folsom
*/
}
}
log.debug("valueOf ok");
return result;
}
/**
* This checks the certificate's notBefore and notAfter dates against the current time.
* This does NOT check the signature. Do that separately with isTrusted().
* @return true if the certificate is valid now
*/
public boolean isValid(X509Certificate issuer) {
return isValid(issuer, new Date());
}
/**
* This checks the certificate's notBefore and notAfter dates against the current time.
* This does NOT check the signature. Do that separately with isTrusted().
*
* @param date to check against the certificate's validity period
* @return true if the certificate is valid on the given date
*/
public boolean isValid(X509Certificate issuer, Date date) {
try {
X509AttributeCertificateHolder holder = new X509AttributeCertificateHolder(encoded);
ContentVerifierProvider verifierProvider = new BcRSAContentVerifierProviderBuilder(new DefaultDigestAlgorithmIdentifierFinder()).build(new X509CertificateHolder(issuer.getEncoded()));
if( !holder.isSignatureValid(verifierProvider) ) {
log.debug("Certificate signature cannot be validated with certificate: {}", issuer.getIssuerX500Principal().getName());
return false;
}
return date.compareTo(notBefore) > -1 && date.compareTo(notAfter) < 1;
}
catch(Exception e) {
log.error("Cannot initialize certificate verifier", e);
return false;
}
}
}