package net.deterlab.abac;
import java.io.*;
import java.math.*;
import java.util.*;
import java.security.*;
import java.security.cert.*;
import javax.security.auth.x500.*;
import org.bouncycastle.asn1.*;
import org.bouncycastle.asn1.x509.*;
import org.bouncycastle.x509.*;
import org.bouncycastle.x509.util.*;
import org.bouncycastle.openssl.*;
/**
* An ABAC credential, with or without an underlying certificate that
* represents it. These are edges in proof garphs and can be constructed from
* their constituent Roles.
* @author ISI ABAC team
* @version 1.5
*/
public class X509Credential extends Credential implements Comparable {
/** The ASN1 OID for an IETF attribute. */
protected static String attrOID = "1.3.6.1.5.5.7.10.4";
/** The ASN1 OID for AuthorityKeyIdentifier. */
protected static String authKeyOID = "2.5.29.35";
/** The certificate representing this credential */
protected X509V2AttributeCertificate ac;
/** The X.509 credential suffix */
private static final String fileSuffix = ".der";
/** Make sure BouncyCastle is loaded */
static { Context.loadBouncyCastle(); }
/**
* Create an empty Credential.
*/
public X509Credential() {
super();
ac = null;
setSuffix(fileSuffix);
}
/**
* Create a credential from a head and tail role. This credential has no
* underlying certificate, and cannot be exported or used in real proofs.
* make_cert can create a certificate for a credential initialized this
* way.
* @param head the Role at the head of the credential
* @param tail the Role at the tail of the credential
*/
public X509Credential(Role head, Role tail) {
super(head, tail);
ac = null;
setSuffix(fileSuffix);
}
/**
* Do the credential extraction from an input stream. This parses the
* certificate from the input stream and saves it. The contents must be
* validated and parsed later.
* @param stream the InputStream to read the certificate from.
* @throws IOException if the stream is unparsable
*/
protected void read_certificate(InputStream stream)
throws IOException {
try {
ac = new X509V2AttributeCertificate(stream);
}
catch (Exception e) {
throw new IOException(e.getMessage(), e);
}
}
/**
* Initialize a credential from parsed certificate. Validiate it against
* the given identities and parse out the roles. Note that catching
* java.security.GeneralSecurityException catches most of the exceptions
* this throws.
* @param ids a Collection of Identities to use in validating the cert
* @throws CertInvalidException if the stream is unparsable
* @throws MissingIssuerException if none of the Identities can validate the
* certificate
* @throws BadSignatureException if the signature check fails
*/
protected void init(Collection ids)
throws ABACException {
for (Identity i: ids) {
try {
ac.verify(i.getCertificate().getPublicKey(), "BC");
id = i;
break;
}
catch (GeneralSecurityException e) { }
}
if (id == null) throw new MissingIssuerException("Unknown identity");
m_expiration = ac.getNotAfter();
load_roles();
if (!id.getKeyID().equals(m_head.principal()))
throw new MissingIssuerException("Unknown identity");
}
/**
* Parse a credential from an InputStream and initialize the role from it.
* Combine read_credential(stream) and init(ids). Note that catching
* java.security.GeneralSecurityException catches most of the exceptions
* this throws.
* @param stream the InputStream to read the certificate from.
* @param ids a Collection of Identities to use in validating the cert
* @throws CertInvalidException if the stream is unparsable
* @throws MissingIssuerException if none of the Identities can validate the
* certificate
* @throws BadSignatureException if the signature check fails
*/
protected void init(InputStream stream, Collection ids)
throws ABACException {
try {
read_certificate(stream);
}
catch (IOException e) {
throw new CertInvalidException(e.getMessage(), e);
}
if (ac == null) throw new CertInvalidException("Unknown Format");
init(ids);
}
/**
* Create a credential from an attribute cert in a file. Throws an
* exception if the cert file can't be opened or if there's a format
* problem with the cert. Note that catching
* java.security.GeneralSecurityException catches most of the exceptions
* this throws.
* @param filename a String containing the filename to read
* @param ids a Collection of Identities to use in validating the cert
* @throws CertInvalidException if the stream is unparsable
* @throws MissingIssuerException if none of the Identities can validate the
* certificate
* @throws BadSignatureException if the signature check fails
*/
X509Credential(String filename, Collection ids)
throws ABACException {
super();
setSuffix(fileSuffix);
try {
init(new FileInputStream(filename), ids);
}
catch (FileNotFoundException e) {
throw new CertInvalidException("Bad filename", e);
}
}
/**
* Create a credential from an attribute cert in a file. Throws an
* exception if the cert file can't be opened or if there's a format
* problem with the cert. Note that catching
* java.security.GeneralSecurityException catches most of the exceptions
* this throws.
* @param file the File to read
* @param ids a Collection of Identities to use in validating the cert
* @throws CertInvalidException if the stream is unparsable
* @throws MissingIssuerException if none of the Identities can validate the
* certificate
* @throws BadSignatureException if the signature check fails
*/
X509Credential(File file, Collection ids)
throws ABACException {
super();
setSuffix(fileSuffix);
try {
init(new FileInputStream(file), ids);
}
catch (FileNotFoundException e) {
throw new CertInvalidException("Bad filename", e);
}
}
/**
* Create a credential from an InputStream. Throws an exception if the
* stream can't be parsed or if there's a format problem with the cert.
* Note that catching java.security.GeneralSecurityException catches most
* of the exceptions this throws.
* @param s the InputStream to read
* @param ids a Collection of Identities to use in validating the cert
* @throws CertInvalidException if the stream is unparsable
* @throws MissingIssuerException if none of the Identities can validate the
* certificate
* @throws BadSignatureException if the signature check fails
*/
X509Credential(InputStream s, Collection ids)
throws ABACException {
super();
setSuffix(fileSuffix);
init(s, ids);
}
/**
* Create a credential from an X509V2AttributeCertificate object. Throws
* an exception if the certificate doesn't parse into an ABAC credential,
* or cannot be validated. Note that catching
* java.security.GeneralSecurityException catches most of the exceptions
* this throws.
* @param c the X509V2AttributeCertificate to create from
* @param ids a Collection of Identities to use in validating the cert
* @throws CertInvalidException if the stream is unparsable
* @throws MissingIssuerException if none of the Identities can validate the
* certificate
* @throws BadSignatureException if the signature check fails
*/
X509Credential(X509V2AttributeCertificate c,
Collection ids)
throws ABACException {
super();
setSuffix(fileSuffix);
ac = c;
init(ids);
}
/**
* Create a certificate from this credential issued by the given identity
* valid for the given time.
* @param i the Identity that will issue the certificate
* @param validity a long holding the number of seconds that the credential
* is valid for.
* @throws ABACException if xml creation fails
* @throws MissingIssuerException if the issuer is bad
* @throws BadSignatureException if the signature creation fails
*/
public void make_cert(Identity i, long validity)
throws ABACException {
PrivateKey key = i.getKeyPair().getPrivate();
SubjectPublicKeyInfo pki = Context.extractSubjectPublicKeyInfo(
i.getKeyPair().getPublic());
X509V2AttributeCertificateGenerator gen =
new X509V2AttributeCertificateGenerator();
try {
gen.setIssuer(new AttributeCertificateIssuer(
new X500Principal("CN="+m_head.principal())));
gen.setHolder(new AttributeCertificateHolder(
new X500Principal("CN="+m_head.principal())));
m_expiration = new Date(System.currentTimeMillis()
+ (1000L * validity));
gen.setNotAfter(m_expiration);
gen.setNotBefore(new Date(System.currentTimeMillis()));
gen.setSerialNumber(BigInteger.valueOf(System.currentTimeMillis()));
gen.addAttribute(new X509Attribute(attrOID,
new DERSequence(
new DERSequence(
new DERUTF8String(toString())))));
gen.setSignatureAlgorithm("SHA256WithRSAEncryption");
// Creddy expects an authority key identifier.
gen.addExtension(authKeyOID, false,
new AuthorityKeyIdentifier(pki));
ac = (X509V2AttributeCertificate) gen.generate(key, "BC");
}
catch (Exception e) {
throw new ABACException("Cannot encode cert", e);
}
// Create the cert.
id = i;
}
/**
* Create a certificate from this credential issued by the given identity
* valid for the default validity.
* @param i the Identity that will issue the certificate
* @throws ABACException if xml creation fails
* @throws MissingIssuerException if the issuer is bad
* @throws BadSignatureException if the signature creation fails
*/
public void make_cert(Identity i) throws ABACException {
make_cert(i, defaultValidity);
}
/**
* Load the roles off the attribute cert.
* @throws CertInvalidException if the certificate is badly formatted
*/
private void load_roles() throws CertInvalidException {
String roles = null;
try {
X509Attribute attr = ac.getAttributes()[0];
DERSequence t1 = (DERSequence)attr.getValues()[0];
DERSequence t2 = (DERSequence)t1.getObjectAt(0);
DERUTF8String t3 = (DERUTF8String)t2.getObjectAt(0);
roles = t3.getString();
}
catch (Exception e) {
throw new CertInvalidException("Badly formatted certificate");
}
String[] parts = roles.split("\\s*<--?\\s*");
if (parts.length != 2)
throw new CertInvalidException("Invalid attribute: " + roles);
m_head = new Role(parts[0]);
m_tail = new Role(parts[1]);
}
/**
* Output the DER formatted attribute certificate associated with this
* Credential to the OutputStream.
* @param s the OutputStream on which to write
* @throws IOException if there is an error writing.
*/
public void write(OutputStream s) throws IOException {
if (ac != null )
s.write(ac.getEncoded());
s.flush();
}
/**
* Output the DER formatted attribute certificate associated with this
* Credential to the filename given.
* @param fn a String containing the output filename
* @throws IOException if there is an error writing.
*/
public void write(String fn) throws IOException, FileNotFoundException {
write(new FileOutputStream(fn));
}
/**
* Return true if this Credential has a certificate associated. A jabac
* extension.
* @return true if this Credential has a certificate associated.
*/
public boolean hasCertificate() { return ac != null; }
/**
* Return a CredentialFactorySpecialization for X509Credentials. Used by
* the CredentialFactory to parse and generate these kind of credentials.
* It is basically a wrapper around constuctor calls.
* @return a CredentialParser for this kind of credential.
*/
static public CredentialFactorySpecialization
getCredentialFactorySpecialization() {
return new CredentialFactorySpecialization() {
public Credential[] parseCredential(InputStream is,
Collection ids) throws ABACException {
return new Credential[] { new X509Credential(is, ids) };
}
public Credential generateCredential(Role head, Role tail,
KeyIDMap aliases) {
return new X509Credential(head, tail);
}
};
}
}