Java tutorial
/************************************************************************* * * * EJBCA Community: 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.core.protocol.scep; import java.io.IOException; import java.security.PrivateKey; import java.security.cert.CRL; import java.security.cert.CRLException; import java.security.cert.Certificate; import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateException; import java.security.cert.CertificateParsingException; import java.security.cert.X509CRL; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Collection; import java.util.Hashtable; import java.util.List; import org.apache.log4j.Logger; import org.bouncycastle.asn1.ASN1ObjectIdentifier; import org.bouncycastle.asn1.DEROctetString; import org.bouncycastle.asn1.DERPrintableString; import org.bouncycastle.asn1.DERSet; import org.bouncycastle.asn1.cms.Attribute; import org.bouncycastle.asn1.cms.AttributeTable; import org.bouncycastle.asn1.smime.SMIMECapability; import org.bouncycastle.cert.jcajce.JcaX509CRLHolder; import org.bouncycastle.cms.CMSAbsentContent; import org.bouncycastle.cms.CMSEnvelopedData; import org.bouncycastle.cms.CMSEnvelopedDataGenerator; import org.bouncycastle.cms.CMSException; import org.bouncycastle.cms.CMSProcessableByteArray; import org.bouncycastle.cms.CMSSignedData; import org.bouncycastle.cms.CMSSignedDataGenerator; import org.bouncycastle.cms.CMSTypedData; import org.bouncycastle.cms.DefaultSignedAttributeTableGenerator; import org.bouncycastle.cms.jcajce.JcaSignerInfoGeneratorBuilder; import org.bouncycastle.cms.jcajce.JceCMSContentEncryptorBuilder; import org.bouncycastle.cms.jcajce.JceKeyTransRecipientInfoGenerator; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.operator.ContentSigner; import org.bouncycastle.operator.OperatorCreationException; import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder; import org.bouncycastle.util.CollectionStore; import org.cesecore.certificates.certificate.Base64CertData; import org.cesecore.certificates.certificate.CertificateData; import org.cesecore.certificates.certificate.request.CertificateResponseMessage; import org.cesecore.certificates.certificate.request.FailInfo; import org.cesecore.certificates.certificate.request.RequestMessage; import org.cesecore.certificates.certificate.request.ResponseStatus; import org.cesecore.certificates.util.AlgorithmTools; import org.cesecore.util.Base64; import org.cesecore.util.CertTools; /** * A response message for scep (pkcs7). * * @version $Id: ScepResponseMessage.java 20516 2015-01-08 17:26:01Z mikekushner $ */ public class ScepResponseMessage implements CertificateResponseMessage { /** * Determines if a de-serialized file is compatible with this class. * * Maintainers must change this value if and only if the new version * of this class is not compatible with old versions. See Sun docs * for <a href=http://java.sun.com/products/jdk/1.1/docs/guide * /serialization/spec/version.doc.html> details. </a> * */ static final long serialVersionUID = 2016710353393853879L; private static Logger log = Logger.getLogger(ScepResponseMessage.class); /** The encoded response message */ private byte[] responseMessage = null; /** status for the response */ private ResponseStatus status = ResponseStatus.SUCCESS; /** Possible fail information in the response. Defaults to 'badRequest (2)'. */ private FailInfo failInfo = FailInfo.BAD_REQUEST; /** Possible clear text error information in the response. Defaults to null. */ private String failText = null; /** * SenderNonce. This is base64 encoded bytes */ private String senderNonce = null; /** * RecipientNonce in a response is the senderNonce from the request. This is base64 encoded bytes */ private String recipientNonce = null; /** transaction id */ private String transactionId = null; /** recipient key identifier, usually IssuerAndSerialno in X509 world. */ private byte[] recipientKeyInfo = null; /** Certificate to be in response message, not serialized */ private transient Certificate cert = null; private transient CRL crl = null; /** Certificate for the signer of the response message (CA or RA) */ private transient Collection<Certificate> signCertChain = null; /** Certificate for the CA of the response certificate in successful responses, is the same as signCert if not using RA mode */ private transient Certificate caCert = null; /** Private key used to sign the response message */ private transient PrivateKey signKey = null; /** If the CA certificate should be included in the reponse or not, default to true = yes */ private transient boolean includeCACert = true; /** Default digest algorithm for SCEP response message, can be overridden */ private transient String digestAlg = CMSSignedDataGenerator.DIGEST_MD5; private transient CertificateData certificateData; private transient Base64CertData base64CertData; /** The default provider is BC, if nothing else is specified when setting SignKeyInfo */ private transient String provider = BouncyCastleProvider.PROVIDER_NAME; @Override public CertificateData getCertificateData() { return certificateData; } @Override public void setCertificateData(CertificateData certificateData) { if (certificateData != null) { this.certificateData = new CertificateData(certificateData); } else { this.certificateData = null; } } @Override public Base64CertData getBase64CertData() { return base64CertData; } @Override public void setBase64CertData(final Base64CertData base64CertData) { if (base64CertData != null) { this.base64CertData = new Base64CertData(base64CertData); } else { this.base64CertData = null; } } @Override public void setCertificate(Certificate cert) { this.cert = cert; } @Override public Certificate getCertificate() { try { return CertTools.getCertfromByteArray(cert.getEncoded()); } catch (CertificateEncodingException e) { throw new Error("Could not encode certificate. This should not happen", e); } catch (CertificateException e) { throw new Error("Response was created without containing valid certificate. This should not happen", e); } } @Override public void setCrl(CRL crl) { this.crl = crl; } @Override public void setIncludeCACert(boolean incCACert) { this.includeCACert = incCACert; } @Override public void setCACert(Certificate caCert) { this.caCert = caCert; } @Override public byte[] getResponseMessage() throws CertificateEncodingException { return responseMessage; } @Override public void setStatus(ResponseStatus status) { this.status = status; } @Override public ResponseStatus getStatus() { return status; } @Override public void setFailInfo(FailInfo failInfo) { this.failInfo = failInfo; } @Override public FailInfo getFailInfo() { return failInfo; } @Override public void setFailText(String failText) { this.failText = failText; } @Override public String getFailText() { return this.failText; } @Override public boolean create() throws CertificateEncodingException, CRLException { boolean ret = false; try { if (status.equals(ResponseStatus.SUCCESS)) { log.debug("Creating a STATUS_OK message."); } else { if (status.equals(ResponseStatus.FAILURE)) { log.debug("Creating a STATUS_FAILED message (or returning false)."); if (failInfo.equals(FailInfo.WRONG_AUTHORITY)) { return false; } if (failInfo.equals(FailInfo.INCORRECT_DATA)) { return false; } } else { log.debug("Creating a STATUS_PENDING message."); } } CMSTypedData msg; // Create encrypted response if this is success and NOT a CRL response message if (status.equals(ResponseStatus.SUCCESS)) { CMSEnvelopedDataGenerator edGen = new CMSEnvelopedDataGenerator(); // Add the issued certificate to the signed portion of the CMS (as signer, degenerate case) List<X509Certificate> certList = new ArrayList<X509Certificate>(); if (cert != null) { log.debug("Adding certificates to response message"); certList.add((X509Certificate) cert); // Add the CA cert, it's optional but Cisco VPN client complains if it isn't there if (includeCACert) { if (caCert != null) { // If we have an explicit CAcertificate log.debug("Including explicitly set CA certificate in SCEP response."); certList.add((X509Certificate) caCert); } else { // If we don't have an explicit caCert, we think that the signCert is the CA cert // If we have an explicit caCert, the signCert is probably the RA certificate, and we don't include that one log.debug("Including message signer certificate in SCEP response."); certList.add((X509Certificate) signCertChain.iterator().next()); } } } // Create the signed CMS message to be contained inside the envelope // this message does not contain any message, and no signerInfo CMSSignedDataGenerator gen = new CMSSignedDataGenerator(); gen.addCertificates(new CollectionStore(CertTools.convertToX509CertificateHolder(certList))); if (crl != null) { gen.addCRL(new JcaX509CRLHolder((X509CRL) crl)); } CMSSignedData s = gen.generate(new CMSAbsentContent(), false); // Envelope the CMS message if (recipientKeyInfo != null) { try { X509Certificate rec = (X509Certificate) CertTools.getCertfromByteArray(recipientKeyInfo); log.debug("Added recipient information - issuer: '" + CertTools.getIssuerDN(rec) + "', serno: '" + CertTools.getSerialNumberAsString(rec)); edGen.addRecipientInfoGenerator(new JceKeyTransRecipientInfoGenerator(rec) .setProvider(BouncyCastleProvider.PROVIDER_NAME)); } catch (CertificateParsingException e) { throw new IllegalArgumentException("Can not decode recipients self signed certificate!", e); } } else { edGen.addRecipientInfoGenerator(new JceKeyTransRecipientInfoGenerator((X509Certificate) cert) .setProvider(BouncyCastleProvider.PROVIDER_NAME)); } try { JceCMSContentEncryptorBuilder jceCMSContentEncryptorBuilder = new JceCMSContentEncryptorBuilder( SMIMECapability.dES_CBC).setProvider(BouncyCastleProvider.PROVIDER_NAME); CMSEnvelopedData ed = edGen.generate(new CMSProcessableByteArray(s.getEncoded()), jceCMSContentEncryptorBuilder.build()); if (log.isDebugEnabled()) { log.debug("Enveloped data is " + ed.getEncoded().length + " bytes long"); } msg = new CMSProcessableByteArray(ed.getEncoded()); } catch (IOException e) { throw new IllegalStateException("Unexpected IOException caught", e); } } else { // Create an empty message here //msg = new CMSProcessableByteArray("PrimeKey".getBytes()); msg = new CMSProcessableByteArray(new byte[0]); } // Create the outermost signed data CMSSignedDataGenerator gen1 = new CMSSignedDataGenerator(); // add authenticated attributes...status, transactionId, sender- and recipientNonce and more... Hashtable<ASN1ObjectIdentifier, Attribute> attributes = new Hashtable<ASN1ObjectIdentifier, Attribute>(); ASN1ObjectIdentifier oid; Attribute attr; DERSet value; // Message type (certrep) oid = new ASN1ObjectIdentifier(ScepRequestMessage.id_messageType); value = new DERSet(new DERPrintableString("3")); attr = new Attribute(oid, value); attributes.put(attr.getAttrType(), attr); // TransactionId if (transactionId != null) { oid = new ASN1ObjectIdentifier(ScepRequestMessage.id_transId); log.debug("Added transactionId: " + transactionId); value = new DERSet(new DERPrintableString(transactionId)); attr = new Attribute(oid, value); attributes.put(attr.getAttrType(), attr); } // status oid = new ASN1ObjectIdentifier(ScepRequestMessage.id_pkiStatus); value = new DERSet(new DERPrintableString(status.getStringValue())); attr = new Attribute(oid, value); attributes.put(attr.getAttrType(), attr); if (status.equals(ResponseStatus.FAILURE)) { oid = new ASN1ObjectIdentifier(ScepRequestMessage.id_failInfo); log.debug("Added failInfo: " + failInfo.getValue()); value = new DERSet(new DERPrintableString(failInfo.getValue())); attr = new Attribute(oid, value); attributes.put(attr.getAttrType(), attr); } // senderNonce if (senderNonce != null) { oid = new ASN1ObjectIdentifier(ScepRequestMessage.id_senderNonce); log.debug("Added senderNonce: " + senderNonce); value = new DERSet(new DEROctetString(Base64.decode(senderNonce.getBytes()))); attr = new Attribute(oid, value); attributes.put(attr.getAttrType(), attr); } // recipientNonce if (recipientNonce != null) { oid = new ASN1ObjectIdentifier(ScepRequestMessage.id_recipientNonce); log.debug("Added recipientNonce: " + recipientNonce); value = new DERSet(new DEROctetString(Base64.decode(recipientNonce.getBytes()))); attr = new Attribute(oid, value); attributes.put(attr.getAttrType(), attr); } // Add our signer info and sign the message Certificate cacert = signCertChain.iterator().next(); log.debug("Signing SCEP message with cert: " + CertTools.getSubjectDN(cacert)); String signatureAlgorithmName = AlgorithmTools.getAlgorithmNameFromDigestAndKey(digestAlg, signKey.getAlgorithm()); try { ContentSigner contentSigner = new JcaContentSignerBuilder(signatureAlgorithmName) .setProvider(provider).build(signKey); JcaDigestCalculatorProviderBuilder calculatorProviderBuilder = new JcaDigestCalculatorProviderBuilder() .setProvider(BouncyCastleProvider.PROVIDER_NAME); JcaSignerInfoGeneratorBuilder builder = new JcaSignerInfoGeneratorBuilder( calculatorProviderBuilder.build()); builder.setSignedAttributeGenerator( new DefaultSignedAttributeTableGenerator(new AttributeTable(attributes))); gen1.addSignerInfoGenerator(builder.build(contentSigner, (X509Certificate) cacert)); } catch (OperatorCreationException e) { throw new IllegalStateException("BouncyCastle failed in creating signature provider.", e); } // The un-encoded response message itself final CMSSignedData signedData = gen1.generate(msg, true); try { responseMessage = signedData.getEncoded(); } catch (IOException e) { throw new IllegalStateException("Unexpected IOException caught.", e); } if (responseMessage != null) { ret = true; } } catch (CMSException e) { log.error("Error creating CMS message: ", e); } return ret; } @Override public boolean requireSignKeyInfo() { return true; } @Override public void setSignKeyInfo(Collection<Certificate> certs, PrivateKey key, String prov) { this.signCertChain = certs; this.signKey = key; if (prov != null) { this.provider = prov; } } @Override public void setSenderNonce(String senderNonce) { this.senderNonce = senderNonce; } @Override public void setRecipientNonce(String recipientNonce) { this.recipientNonce = recipientNonce; } @Override public void setTransactionId(String transactionId) { this.transactionId = transactionId; } @Override public void setRecipientKeyInfo(byte[] recipientKeyInfo) { this.recipientKeyInfo = recipientKeyInfo; } @Override public void setPreferredDigestAlg(String digest) { this.digestAlg = digest; } @Override public void setRequestType(int reqtype) { } @Override public void setRequestId(int reqid) { } @Override public void setProtectionParamsFromRequest(RequestMessage reqMsg) { } }