/*************************************************************************
* *
* EJBCA: The OpenSource Certificate Authority *
* *
* This software is free software; you can redistribute it and/or *
* modify it under the terms of the GNU Lesser General Public *
* License as published by the Free Software Foundation; either *
* version 2.1 of the License, or any later version. *
* *
* See terms of license at gnu.org. *
* *
*************************************************************************/
package org.ejbca.extra.ra;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.KeyPair;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;
import java.security.SignatureException;
import java.security.cert.CertStore;
import java.security.cert.CertStoreException;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CollectionCertStoreParameters;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.Vector;
import org.apache.log4j.Logger;
import org.bouncycastle.asn1.ASN1EncodableVector;
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.DERUTF8String;
import org.bouncycastle.asn1.cms.Attribute;
import org.bouncycastle.asn1.cms.AttributeTable;
import org.bouncycastle.asn1.cms.IssuerAndSerialNumber;
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
import org.bouncycastle.asn1.smime.SMIMECapability;
import org.bouncycastle.asn1.x509.GeneralNames;
import org.bouncycastle.asn1.x509.X509Extension;
import org.bouncycastle.asn1.x509.X509Extensions;
import org.bouncycastle.asn1.x509.X509Name;
import org.bouncycastle.cms.CMSEnvelopedData;
import org.bouncycastle.cms.CMSEnvelopedDataGenerator;
import org.bouncycastle.cms.CMSException;
import org.bouncycastle.cms.CMSProcessable;
import org.bouncycastle.cms.CMSProcessableByteArray;
import org.bouncycastle.cms.CMSSignedData;
import org.bouncycastle.cms.CMSSignedDataGenerator;
import org.bouncycastle.cms.CMSSignedGenerator;
import org.bouncycastle.jce.PKCS10CertificationRequest;
import org.bouncycastle.util.encoders.Base64;
import org.ejbca.core.model.AlgorithmConstants;
import org.ejbca.core.protocol.scep.ScepRequestMessage;
import org.ejbca.util.CertTools;
public class ScepRequestGenerator {
private static Logger log = Logger.getLogger(ScepRequestGenerator.class);
private X509Certificate cert = null;
private X509Certificate cacert = null;
private String reqdn = null;
private KeyPair keys = null;
private String digestOid = CMSSignedGenerator.DIGEST_SHA1;
private PKCS10CertificationRequest p10request;
private String senderNonce = null;
private String transactionId = null;
public void setKeys(KeyPair myKeys) {
this.keys = myKeys;
}
public void setDigestOid(String oid) {
digestOid = oid;
}
/** Base 64 encode senderNonce
*/
public String getSenderNonce() {
return senderNonce;
}
public String getTransactionId() {
return transactionId;
}
/** Generates a SCEP CrlReq. Keys must have been set in the generator for this to succeed
*
*/
public byte[] generateCrlReq(String dn, X509Certificate ca) throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException, SignatureException, IOException, CMSException, InvalidAlgorithmParameterException, CertStoreException, CertificateEncodingException, IllegalStateException {
this.cacert = ca;
this.reqdn = dn;
X509Name name = CertTools.stringToBcX509Name(cacert.getIssuerDN().getName());
IssuerAndSerialNumber ias = new IssuerAndSerialNumber(name, cacert.getSerialNumber());
// Create self signed cert, validity 1 day
cert = CertTools.genSelfCert(reqdn,24*60*60*1000,null,keys.getPrivate(),keys.getPublic(),AlgorithmConstants.SIGALG_SHA1_WITH_RSA,false);
// wrap message in pkcs#7
byte[] msg = wrap(ias.getEncoded(), "22");
return msg;
}
/** Generates a SCEP CertReq. Keys must have been set in the generator for this to succeed
*
*/
public byte[] generateCertReq(String dn, String password, X509Certificate ca) throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException, SignatureException, IOException, CMSException, InvalidAlgorithmParameterException, CertStoreException, CertificateEncodingException, IllegalStateException {
this.cacert = ca;
this.reqdn = dn;
// Create challenge password attribute for PKCS10
// Attributes { ATTRIBUTE:IOSet } ::= SET OF Attribute{{ IOSet }}
//
// Attribute { ATTRIBUTE:IOSet } ::= SEQUENCE {
// type ATTRIBUTE.&id({IOSet}),
// values SET SIZE(1..MAX) OF ATTRIBUTE.&Type({IOSet}{\@type})
// }
ASN1EncodableVector challpwdattr = new ASN1EncodableVector();
// Challenge password attribute
challpwdattr.add(PKCSObjectIdentifiers.pkcs_9_at_challengePassword);
ASN1EncodableVector pwdvalues = new ASN1EncodableVector();
pwdvalues.add(new DERUTF8String(password));
challpwdattr.add(new DERSet(pwdvalues));
// Requested extensions attribute
ASN1EncodableVector extensionattr = new ASN1EncodableVector();
extensionattr.add(PKCSObjectIdentifiers.pkcs_9_at_extensionRequest);
// AltNames
GeneralNames san = CertTools.getGeneralNamesFromAltName("dNSName=foo.bar.com,iPAddress=10.0.0.1");
ByteArrayOutputStream bOut = new ByteArrayOutputStream();
DEROutputStream dOut = new DEROutputStream(bOut);
try {
dOut.writeObject(san);
} catch (IOException e) {
throw new IllegalArgumentException("error encoding value: " + e);
}
Vector oidvec = new Vector();
oidvec.add(X509Extensions.SubjectAlternativeName);
Vector valuevec = new Vector();
valuevec.add(new X509Extension(false, new DEROctetString(bOut.toByteArray())));
X509Extensions exts = new X509Extensions(oidvec,valuevec);
extensionattr.add(new DERSet(exts));
// Complete the Attribute section of the request, the set (Attributes) contains two sequences (Attribute)
ASN1EncodableVector v = new ASN1EncodableVector();
v.add(new DERSequence(challpwdattr));
v.add(new DERSequence(extensionattr));
DERSet attributes = new DERSet(v);
// Create PKCS#10 certificate request
p10request = new PKCS10CertificationRequest("SHA1WithRSA",
CertTools.stringToBcX509Name(reqdn), keys.getPublic(), attributes, keys.getPrivate());
// Create self signed cert, validity 1 day
cert = CertTools.genSelfCert(reqdn,24*60*60*1000,null,keys.getPrivate(),keys.getPublic(),AlgorithmConstants.SIGALG_SHA1_WITH_RSA,false);
// wrap message in pkcs#7
byte[] msg = wrap(p10request.getEncoded(), "19");
return msg;
}
public byte[] generateGetCertInitial(String dn, X509Certificate ca) throws NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException, CertStoreException, IOException, CMSException {
this.cacert = ca;
this.reqdn = dn;
ASN1EncodableVector vec = new ASN1EncodableVector();
vec.add(new DERUTF8String(ca.getIssuerDN().getName()));
vec.add(new DERUTF8String(dn));
DERSequence seq = new DERSequence(vec);
// wrap message in pkcs#7
byte[] msg = wrap(seq.getEncoded(), "20");
return msg;
}
private CMSEnvelopedData envelope(CMSProcessable envThis) throws NoSuchAlgorithmException, NoSuchProviderException, CMSException {
CMSEnvelopedDataGenerator edGen = new CMSEnvelopedDataGenerator();
// Envelope the CMS message
edGen.addKeyTransRecipient(cacert);
CMSEnvelopedData ed = edGen.generate(envThis, SMIMECapability.dES_CBC.getId(), "BC");
return ed;
}
private CMSSignedData sign(CMSProcessable signThis, String messageType) throws NoSuchAlgorithmException, NoSuchProviderException, CMSException, InvalidAlgorithmParameterException, CertStoreException {
CMSSignedDataGenerator gen1 = new CMSSignedDataGenerator();
// add authenticated attributes...status, transactionId, sender- and more...
Hashtable attributes = new Hashtable();
DERObjectIdentifier oid;
Attribute attr;
DERSet value;
// Message type (certreq)
oid = new DERObjectIdentifier(ScepRequestMessage.id_messageType);
value = new DERSet(new DERPrintableString(messageType));
attr = new Attribute(oid, value);
attributes.put(attr.getAttrType(), attr);
// TransactionId
byte[] digest = CertTools.generateMD5Fingerprint(cert.getPublicKey().getEncoded());
transactionId = new String(Base64.encode(digest));
oid = new DERObjectIdentifier(ScepRequestMessage.id_transId);
value = new DERSet(new DERPrintableString(Base64.encode(digest)));
attr = new Attribute(oid, value);
attributes.put(attr.getAttrType(), attr);
// senderNonce
byte[] nonce = new byte[16];
SecureRandom randomSource = SecureRandom.getInstance("SHA1PRNG");
randomSource.nextBytes(nonce);
senderNonce = new String(Base64.encode(nonce));
if (nonce != null) {
oid = new DERObjectIdentifier(ScepRequestMessage.id_senderNonce);
log.debug("Added senderNonce: " + senderNonce);
value = new DERSet(new DEROctetString(nonce));
attr = new Attribute(oid, value);
attributes.put(attr.getAttrType(), attr);
}
// Add our signer info and sign the message
ArrayList certList = new ArrayList();
certList.add(cert);
CertStore certs = CertStore.getInstance("Collection", new CollectionCertStoreParameters(certList), "BC");
gen1.addCertificatesAndCRLs(certs);
gen1.addSigner(keys.getPrivate(), cert, digestOid,
new AttributeTable(attributes), null);
// The signed data to be enveloped
CMSSignedData s = gen1.generate(signThis, true, "BC");
return s;
}
private byte[] wrap(byte[] envBytes, String messageType) throws IOException, NoSuchAlgorithmException, NoSuchProviderException, CMSException, InvalidAlgorithmParameterException, CertStoreException {
//
// Create inner enveloped data
//
CMSEnvelopedData ed = envelope(new CMSProcessableByteArray(envBytes));
log.debug("Enveloped data is " + ed.getEncoded().length + " bytes long");
CMSProcessable msg = new CMSProcessableByteArray(ed.getEncoded());
//
// Create the outer signed data
//
CMSSignedData s = sign(msg, messageType);
byte[] ret = s.getEncoded();
return ret;
}
}