package gridpp.portal.voms;
// Gidon Moont
// Imperial College London
// Copyright (C) 2006
import java.io.ByteArrayOutputStream;
import java.math.BigInteger;
import java.security.PrivateKey;
import java.security.Signature;
import java.util.ArrayList;
import java.util.Date;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;
import org.bouncycastle.asn1.ASN1OctetString;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.ASN1Set;
import org.bouncycastle.asn1.ASN1TaggedObject;
import org.bouncycastle.asn1.DERBitString;
import org.bouncycastle.asn1.DERBoolean;
import org.bouncycastle.asn1.DEREncodable;
import org.bouncycastle.asn1.DEREncodableVector;
import org.bouncycastle.asn1.DERGeneralString;
import org.bouncycastle.asn1.DERGeneralizedTime;
import org.bouncycastle.asn1.DERIA5String;
import org.bouncycastle.asn1.DERInteger;
import org.bouncycastle.asn1.DERNull;
import org.bouncycastle.asn1.DERObjectIdentifier;
import org.bouncycastle.asn1.DEROctetString;
import org.bouncycastle.asn1.DEROutputStream;
import org.bouncycastle.asn1.DERPrintableString;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.asn1.DERSet;
import org.bouncycastle.asn1.DERTaggedObject;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.asn1.x509.AttCertIssuer;
import org.bouncycastle.asn1.x509.AttCertValidityPeriod;
import org.bouncycastle.asn1.x509.AttributeCertificate;
import org.bouncycastle.asn1.x509.AttributeCertificateInfo;
import org.bouncycastle.asn1.x509.GeneralName;
import org.bouncycastle.asn1.x509.GeneralNames;
import org.bouncycastle.asn1.x509.Holder;
import org.bouncycastle.asn1.x509.IssuerSerial;
import org.bouncycastle.asn1.x509.V2Form;
import org.bouncycastle.asn1.x509.X509Extension;
import org.bouncycastle.asn1.x509.X509Extensions;
import org.globus.gsi.OpenSSLKey;
import org.globus.gsi.bc.BouncyCastleOpenSSLKey;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
//--------------------------------------------------------------------------------
public class VOMSAttributeCertificate {
static final Logger myLogger = LoggerFactory
.getLogger(VOMSAttributeCertificate.class);
AttributeCertificate ac = null;
// ------------------------------------------------------------------------------
// first level contains three parts
AttributeCertificateInfo acinfo = null;
AlgorithmIdentifier signatureAlgorithm = null;
DERBitString signatureValue = null;
// ------------------------------------------------------------------------------
// second level is the acinfo - this has 9 subparts
DERInteger version = null;
Holder holder = null;
AttCertIssuer issuer = null;
AlgorithmIdentifier signature = null;
DERInteger serialNumber = null;
AttCertValidityPeriod attrCertValidityPeriod = null;
ASN1Sequence attributes = null;
DERBitString issuerUniqueID = null;
X509Extensions extensions = null;
// ------------------------------------------------------------------------------
public VOMSAttributeCertificate(AttributeCertificate ac) {
this.ac = ac;
// ----------------------------------------------------------------------------
// first level
acinfo = ac.getAcinfo();
signatureAlgorithm = ac.getSignatureAlgorithm();
signatureValue = ac.getSignatureValue();
// ----------------------------------------------------------------------------
// second level therefore is the acinfo - this has 9 subparts
version = acinfo.getVersion();
holder = acinfo.getHolder();
issuer = acinfo.getIssuer();
signature = acinfo.getSignature();
serialNumber = acinfo.getSerialNumber();
attrCertValidityPeriod = acinfo.getAttrCertValidityPeriod();
attributes = acinfo.getAttributes();
issuerUniqueID = acinfo.getIssuerUniqueID(); // OPTIONAL
extensions = acinfo.getExtensions(); // OPTIONAL
}
// ------------------------------------------------------------------------------
public VOMSAttributeCertificate(String holderString,
int holderSerialNumber, String issuerString, int productionSerial,
long fromEpoch, long toEpoch, String[] fqans) throws Exception {
try {
DEREncodableVector infoVector = new DEREncodableVector();
this.setVersion();
this.setHolder(holderString, holderSerialNumber);
this.setIssuer(issuerString);
this.setAlgorithmIdentifier();
this.setSerialNumber(productionSerial);
this.setTimes(new Date(fromEpoch), new Date(toEpoch));
this.setVOMSFQANs(fqans);
this.setExtensions();
infoVector.add(version);
infoVector.add(holder);
infoVector.add(issuer);
infoVector.add(signature);
infoVector.add(serialNumber);
infoVector.add(attrCertValidityPeriod);
infoVector.add(attributes);
infoVector.add(extensions);
ASN1Sequence infoSequence = ASN1Sequence
.getInstance(new DERSequence(infoVector));
this.acinfo = new AttributeCertificateInfo(infoSequence);
// Do it this way to match Vincenzo as much as possible
// - rather than this way... this.signatureAlgorithm = new
// AlgorithmIdentifier( "1.2.840.113549.1.1.4" ) ;
this.signatureAlgorithm = new AlgorithmIdentifier(
new DERObjectIdentifier("1.2.840.113549.1.1.4"),
(DEREncodable) null);
this.signatureValue = new DERBitString(this.sign());
this.ac = new AttributeCertificate(acinfo, signatureAlgorithm,
signatureValue);
} catch (Exception e) {
throw e;
}
}
// ------------------------------------------------------------------------------
private String DERSequencetoDN(DERSequence this_sequence) throws Exception {
String thisDN = "";
try {
for (Enumeration n = this_sequence.getObjects(); n
.hasMoreElements();) {
DERSet this_set = (DERSet) n.nextElement();
DERSequence this_seq = (DERSequence) this_set.getObjectAt(0);
try {
DERPrintableString this_string = (DERPrintableString) this_seq
.getObjectAt(1);
thisDN = thisDN.concat("/"
+ Translate_OID.getString(""
+ this_seq.getObjectAt(0)) + "="
+ this_string.getString());
} catch (Exception notPS) {
// email is encoded differently?
DERIA5String this_string = (DERIA5String) this_seq
.getObjectAt(1);
thisDN = thisDN.concat("/"
+ Translate_OID.getString(""
+ this_seq.getObjectAt(0)) + "="
+ this_string.getString());
}
}
} catch (Exception e) {
throw e;
}
return thisDN;
}
// ------------------------------------------------------------------------------
private DERSequence DNtoDERSequence(String thisDN) throws Exception {
DERSequence this_sequence = null;
try {
DEREncodableVector this_overall_vector = new DEREncodableVector();
String[] parts = thisDN.split("/");
for (int p = 1; p < parts.length; p++) {
int equals_position = parts[p].indexOf("=");
String oid_string = parts[p].substring(0, equals_position);
String value_string = parts[p].substring(equals_position + 1);
String oid = Translate_OID.getOID(oid_string);
if (oid.equals(oid_string)) {
throw new Exception("unrecognised OID string :: " + oid);
}
DEREncodableVector this_vector = new DEREncodableVector();
DERObjectIdentifier this_oid = new DERObjectIdentifier(oid);
this_vector.add(this_oid);
if (oid_string.equals("E")) {
DERIA5String this_string = new DERIA5String(value_string);
this_vector.add(this_string);
} else {
DERPrintableString this_string = new DERPrintableString(
value_string);
this_vector.add(this_string);
}
DERSet this_single_object_set = new DERSet(new DERSequence(
this_vector));
this_overall_vector.add(this_single_object_set);
}
this_sequence = new DERSequence(this_overall_vector);
} catch (Exception e) {
throw e;
}
return this_sequence;
}
public String getAlgorithmIdentifier() {
return Translate_OID.getString(this.signature.getObjectId().getId());
}
// ------------------------------------------------------------------------------
public AttributeCertificate getAttributeCertificate() {
return this.ac;
}
public String getHolder() throws Exception {
// ----------------------------------------------------------------------------
// return the holder's DN as a String
String holderDN = "";
try {
IssuerSerial baseCertificateID = this.holder.getBaseCertificateID();
if (baseCertificateID != null) {
GeneralName[] holder_name_array = baseCertificateID.getIssuer()
.getNames();
DERSequence holder_name_sequence = (DERSequence) holder_name_array[0]
.getName();
holderDN = this.DERSequencetoDN(holder_name_sequence);
}
}
catch (Exception e) {
throw e;
}
return holderDN;
}
// ------------------------------------------------------------------------------
public String getIssuer() throws Exception {
// ----------------------------------------------------------------------------
// return the issuer's DN as a String
String issuerDN = "";
try {
V2Form v2form = (V2Form) this.issuer.getIssuer();
if (v2form != null) {
GeneralName[] issuer_name_array = v2form.getIssuerName()
.getNames();
DERSequence issuer_name_sequence = (DERSequence) issuer_name_array[0]
.getName();
issuerDN = this.DERSequencetoDN(issuer_name_sequence);
}
}
catch (Exception e) {
throw e;
}
return issuerDN;
}
public DERInteger getSerialNumber() {
return this.serialNumber;
}
// ------------------------------------------------------------------------------
public int getSerialNumberIntValue() {
// the getValue() function of DERInteger returns a BigInteger - for
// which we use intValue to get the int
return this.serialNumber.getValue().intValue();
}
public long getTime() throws Exception {
try {
Date from = this.attrCertValidityPeriod.getNotBeforeTime()
.getDate();
Date to = this.attrCertValidityPeriod.getNotAfterTime().getDate();
Date now = new Date();
// TODO check this now.after( from ) thing. I always get a now which
// is before the from date... ???
// if( now.after( from ) )
// {
if (now.before(to)) {
long milliseconds_left = to.getTime() - now.getTime();
return milliseconds_left;
} else {
return -1;
}
// } else {
// return -2 ;
// }
} catch (Exception e) {
throw e;
}
}
// ------------------------------------------------------------------------------
public BigInteger getVersion() {
return this.version.getValue();
}
public ArrayList<String> getVOMSFQANs() {
ArrayList<String> theseFQANs = new ArrayList<String>();
// could have more than one AC in here...
for (Enumeration a = this.attributes.getObjects(); a
.hasMoreElements();) {
ASN1Sequence sequence = (ASN1Sequence) a.nextElement();
// sequence contains the OID [voms 4] (as a DERObjectIdentifier)
// at address 0 , and an SET at address 1
ASN1Set set = (ASN1Set) sequence.getObjectAt(1);
// set contains only a SEQUENCE at address 0
ASN1Sequence sequence2 = (ASN1Sequence) set.getObjectAt(0);
// sequence2 contains a TAGGED OBJECT ad address 0 and another
// SEQUENCE at address 1
ASN1TaggedObject taggedObject = (ASN1TaggedObject) sequence2
.getObjectAt(0);
// dig down the tagged object... (undocumented?) - TagNumber
// value is 0
ASN1TaggedObject taggedObject2 = (ASN1TaggedObject) taggedObject
.getObject();
// this tagged object has TagNumber value of 6 (?)
ASN1OctetString originOctetString = (ASN1OctetString) taggedObject2
.getObject();
String origin = (new DERGeneralString(
originOctetString.getOctets())).getString();
ASN1Sequence fqanSequence = (ASN1Sequence) sequence2
.getObjectAt(1);
// this is the actual sequence of FQANs
for (int fqan = 0; fqan < fqanSequence.size(); fqan++) {
ASN1OctetString fqanOctetString = (ASN1OctetString) fqanSequence
.getObjectAt(fqan);
String FQAN_Value = (new DERGeneralString(
fqanOctetString.getOctets())).getString();
theseFQANs.add(FQAN_Value);
}
}
return theseFQANs;
}
// ------------------------------------------------------------------------------
// should always be "MD5 with RSA encryption"
public void setAlgorithmIdentifier() {
// Do it this way to match Vincenzo as much as possible
// - rather than this way... this.signature = new AlgorithmIdentifier(
// "1.2.840.113549.1.1.4" ) ;
this.signature = new AlgorithmIdentifier(new DERObjectIdentifier(
"1.2.840.113549.1.1.4"), (DEREncodable) null);
}
private void setExtensions() throws Exception {
try {
Vector<DERObjectIdentifier> myOIDs = new Vector<DERObjectIdentifier>();
Hashtable<DERObjectIdentifier, X509Extension> myExtensions = new Hashtable<DERObjectIdentifier, X509Extension>();
// --------------------------------------------------------------------------
// id-ce-noRevAvail
ByteArrayOutputStream a = new ByteArrayOutputStream();
new DEROutputStream(a).writeObject((new DERNull()).toASN1Object());
ASN1OctetString nraOctetString = ASN1OctetString
.getInstance(new DEROctetString(a.toByteArray()));
X509Extension nraExtension = new X509Extension(
new DERBoolean(false), nraOctetString);
DERObjectIdentifier nraOID = new DERObjectIdentifier("2.5.29.56");
myOIDs.add(nraOID);
myExtensions.put(nraOID, nraExtension);
// --------------------------------------------------------------------------
// AuthorityKeyIdentifier
myLogger.warn("VOMSAttributeCertificate verification not implemented yet.");
// String issuerDN = this.getIssuer() ;
//
// String serverName = "unknown" ;
// //serverName = VirtualOrganisation.getServer( issuerDN ) ;
//
// if( ! serverName.equals( "unknown" ) )
// {
// String vomsServerCredentialLocation = new String(
// System.getProperty( "user.home" ) +
// "/gridsecurity/certificates/voms-server-certificates/" +
// serverName ) ;
//
// X509Certificate vomsServerCredential = CertUtil.loadCertificate(
// vomsServerCredentialLocation ) ;
//
// PublicKey pk = vomsServerCredential.getPublicKey() ;
//
// SubjectPublicKeyInfo spki = new SubjectPublicKeyInfo(
// (ASN1Sequence) new ASN1InputStream( new ByteArrayInputStream(
// pk.getEncoded() ) ).readObject() ) ;
// AuthorityKeyIdentifier aki = new AuthorityKeyIdentifier( spki ) ;
//
// // not clear why this does not work...
// // DEROctetString akiOctetString = (DEROctetString)
// DEROctetString.getInstance( akiSequence.getDERObject() ) ;
//
// // These three lines get to the desired result...
// ByteArrayOutputStream b = new ByteArrayOutputStream() ;
// new DEROutputStream( b ).writeObject( aki.toASN1Object() ) ;
// ASN1OctetString akiOctetString = ASN1OctetString.getInstance( new
// DEROctetString( b.toByteArray() ) ) ;
//
// X509Extension akiExtension = new X509Extension( new DERBoolean(
// false ) , akiOctetString ) ;
// DERObjectIdentifier akiOID = new DERObjectIdentifier( "2.5.29.35"
// ) ;
//
// myOIDs.add( akiOID ) ;
// myExtensions.put( akiOID , akiExtension ) ;
//
// this.extensions = new X509Extensions( myOIDs , myExtensions ) ;
//
// }
} catch (Exception e) {
myLogger.error("Error setting extension.", e);
// e.printStackTrace() ;
}
}
public void setHolder(String holderDN, int holderSerialNumber)
throws Exception {
try {
DERSequence holder_name_sequence = DNtoDERSequence(holderDN);
IssuerSerial baseCertificateID = new IssuerSerial(new GeneralNames(
new GeneralName(4, holder_name_sequence)), new DERInteger(
holderSerialNumber));
this.holder = new Holder(baseCertificateID);
}
catch (Exception e) {
throw e;
}
}
// ------------------------------------------------------------------------------
public void setIssuer(String issuerDN) throws Exception {
try {
DERSequence issuer_name_sequence = DNtoDERSequence(issuerDN);
V2Form v2form = new V2Form(new GeneralNames(new GeneralName(4,
issuer_name_sequence)));
this.issuer = new AttCertIssuer(v2form);
}
catch (Exception e) {
throw e;
}
}
public void setSerialNumber(int serial) {
serialNumber = new DERInteger(serial);
}
// ------------------------------------------------------------------------------
public void setTimes(Date from, Date to) throws Exception {
try {
this.attrCertValidityPeriod = new AttCertValidityPeriod(
new DERGeneralizedTime(from), new DERGeneralizedTime(to));
} catch (Exception e) {
throw e;
}
}
// always value of 1 so do not provide an option...
public void setVersion() {
this.version = new DERInteger(BigInteger.valueOf(1));
}
// ------------------------------------------------------------------------------
// Extensions
// DOCUMENTATION REQUIRED!!
/*
* Current (October 2006) VOMS ACs always have id-ce-noRevAvail and
* Authority Key Identifier Extensions
*
* The id-ce-noRevAvail is set to NULL
*
* The AuthorityKeyIdentifier object. id-ce-authorityKeyIdentifier OBJECT
* IDENTIFIER ::= { id-ce 35 }
*
* AuthorityKeyIdentifier ::= SEQUENCE { keyIdentifier [0] IMPLICIT
* KeyIdentifier OPTIONAL, authorityCertIssuer [1] IMPLICIT GeneralNames
* OPTIONAL, authorityCertSerialNumber [2] IMPLICIT CertificateSerialNumber
* OPTIONAL }
*
* KeyIdentifier ::= OCTET STRING
*
* Vincenzo is using a 160-bit SHA-1 hash of the value of the BIT STRING
* subjectPublicKey (as described in 4.2.1.2 of RFC 2459)
*/
public void setVOMSFQANs(String[] fqans) throws Exception {
try {
// --------------------------------------------------------------------------
// put the FQANs into the SEQUENCE
DEREncodableVector fqanVector = new DEREncodableVector();
for (String fqan2 : fqans) {
DERGeneralString fqan = new DERGeneralString(fqan2);
ASN1OctetString fqanOctetString = ASN1OctetString
.getInstance(new DEROctetString(fqan.getOctets()));
fqanVector.add(fqanOctetString);
}
ASN1Sequence fqanSequence = ASN1Sequence
.getInstance(new DERSequence(fqanVector));
// --------------------------------------------------------------------------
// put something into the undocumented TaggedObject
DERGeneralString origin = new DERGeneralString(
"gridportal://newvoms:15000");
ASN1OctetString originOctetString = ASN1OctetString
.getInstance(new DEROctetString(origin.getOctets()));
/*
* ASN1TaggedObject taggedObject2 = ASN1TaggedObject.getInstance(
* new DERTaggedObject( 6 , originOctetString ) , true ) ;
*
* ASN1TaggedObject taggedObject = ASN1TaggedObject.getInstance( new
* DERTaggedObject( 0 , taggedObject2 ) , true ) ;
*
* DEROctetString originOctetString = new DEROctetString(
* origin.getOctets() ) ;
*/
DERTaggedObject taggedObject2 = new DERTaggedObject(6,
originOctetString);
DERTaggedObject taggedObject = new DERTaggedObject(0, taggedObject2);
// --------------------------------------------------------------------------
// put the taggedObject and then the fqanSequence into sequence2
DEREncodableVector sequence2Vector = new DEREncodableVector();
sequence2Vector.add(taggedObject);
sequence2Vector.add(fqanSequence);
ASN1Sequence sequence2 = ASN1Sequence.getInstance(new DERSequence(
sequence2Vector));
// --------------------------------------------------------------------------
// the SET has one member - sequence2
ASN1Set set = ASN1Set.getInstance(new DERSet(sequence2));
// --------------------------------------------------------------------------
// SEQUENCE sequence has an OID and the set
DERObjectIdentifier voms4oid = new DERObjectIdentifier(
"1.3.6.1.4.1.8005.100.100.4");
DEREncodableVector sequenceVector = new DEREncodableVector();
sequenceVector.add(voms4oid);
sequenceVector.add(set);
ASN1Sequence sequence = ASN1Sequence.getInstance(new DERSequence(
sequenceVector));
// --------------------------------------------------------------------------
this.attributes = ASN1Sequence
.getInstance(new DERSequence(sequence));
} catch (Exception e) {
throw e;
}
}
// ------------------------------------------------------------------------------
public byte[] sign() throws Exception {
try {
ByteArrayOutputStream b = new ByteArrayOutputStream();
new DEROutputStream(b).writeObject(acinfo);
Signature sig = Signature.getInstance(signatureAlgorithm
.getObjectId().getId());
String hostPrivateKeyLocation = new String(
System.getProperty("user.home")
+ "/gridsecurity/hostkey.pem");
OpenSSLKey key = new BouncyCastleOpenSSLKey(hostPrivateKeyLocation);
PrivateKey pk = key.getPrivateKey();
if (pk != null) {
sig.initSign(pk);
sig.update(b.toByteArray());
byte[] sigBytes = sig.sign();
return sigBytes;
}
}
catch (Exception e) {
throw e;
}
return new byte[0];
}
// ------------------------------------------------------------------------------
public boolean verify() throws Exception {
boolean checked = false;
// ----------------------------------------------------------------------------
// verify signature
// String issuerDN = this.getIssuer() ;
//
// String serverName = "unknown" ;
// serverName = VO.getServer( issuerDN ) ;
//
// if( ! serverName.equals( "unknown" ) )
// {
// try
// {
//
// ByteArrayOutputStream b = new ByteArrayOutputStream() ;
// new DEROutputStream( b ).writeObject( acinfo ) ;
//
// Signature sig = Signature.getInstance(
// signatureAlgorithm.getObjectId().getId() ) ;
//
// String vomsServerCredentialLocation = new String( System.getProperty(
// "user.home" ) +
// "/gridsecurity/certificates/voms-server-certificates/" + serverName )
// ;
//
// X509Certificate vomsServerCredential = CertUtil.loadCertificate(
// vomsServerCredentialLocation ) ;
//
// PublicKey pk = vomsServerCredential.getPublicKey() ;
//
// if( pk != null )
// {
// sig.initVerify( pk ) ;
// sig.update( b.toByteArray() ) ;
// if( sig.verify( signatureValue.getBytes() ) )
// {
// checked = true ;
// }
// }
//
// }
// catch( Exception e )
// {
// throw e ;
// }
//
// }
myLogger.warn("VOMESAttributeCertificate verification not implemented yet.");
return checked;
}
// ------------------------------------------------------------------------------
}