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.cmp; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.security.InvalidKeyException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.PrivateKey; import java.security.PublicKey; import java.security.Signature; import java.security.SignatureException; import java.security.cert.Certificate; import java.security.cert.CertificateEncodingException; import java.security.cert.X509Certificate; import java.util.Collection; import java.util.Date; import java.util.Iterator; import java.util.Random; import javax.crypto.Mac; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; import org.apache.commons.lang.StringUtils; import org.apache.log4j.Logger; import org.bouncycastle.asn1.ASN1Encodable; import org.bouncycastle.asn1.ASN1EncodableVector; import org.bouncycastle.asn1.ASN1InputStream; import org.bouncycastle.asn1.ASN1Integer; import org.bouncycastle.asn1.ASN1ObjectIdentifier; import org.bouncycastle.asn1.ASN1OctetString; import org.bouncycastle.asn1.ASN1Sequence; import org.bouncycastle.asn1.ASN1TaggedObject; import org.bouncycastle.asn1.DERBitString; import org.bouncycastle.asn1.DERGeneralizedTime; import org.bouncycastle.asn1.DERNull; import org.bouncycastle.asn1.DEROctetString; import org.bouncycastle.asn1.DEROutputStream; import org.bouncycastle.asn1.DERSequence; import org.bouncycastle.asn1.cmp.CMPCertificate; import org.bouncycastle.asn1.cmp.CMPObjectIdentifiers; import org.bouncycastle.asn1.cmp.CertRepMessage; import org.bouncycastle.asn1.cmp.CertResponse; import org.bouncycastle.asn1.cmp.PBMParameter; import org.bouncycastle.asn1.cmp.PKIBody; import org.bouncycastle.asn1.cmp.PKIFailureInfo; import org.bouncycastle.asn1.cmp.PKIHeader; import org.bouncycastle.asn1.cmp.PKIHeaderBuilder; import org.bouncycastle.asn1.cmp.PKIMessage; import org.bouncycastle.asn1.cmp.PKIStatusInfo; import org.bouncycastle.asn1.cmp.RevDetails; import org.bouncycastle.asn1.cmp.RevReqContent; import org.bouncycastle.asn1.crmf.AttributeTypeAndValue; import org.bouncycastle.asn1.crmf.CertReqMessages; import org.bouncycastle.asn1.crmf.CertReqMsg; import org.bouncycastle.asn1.crmf.CertRequest; import org.bouncycastle.asn1.crmf.CertTemplate; import org.bouncycastle.asn1.crmf.POPOPrivKey; import org.bouncycastle.asn1.crmf.POPOSigningKey; import org.bouncycastle.asn1.crmf.ProofOfPossession; import org.bouncycastle.asn1.x509.AlgorithmIdentifier; import org.bouncycastle.asn1.x509.Extension; import org.bouncycastle.asn1.x509.Extensions; import org.bouncycastle.asn1.x509.ExtensionsGenerator; import org.bouncycastle.asn1.x509.GeneralName; import org.bouncycastle.asn1.x509.ReasonFlags; import org.bouncycastle.util.encoders.Hex; import org.cesecore.certificates.certificate.request.FailInfo; import org.cesecore.certificates.certificate.request.ResponseMessage; import org.cesecore.certificates.certificate.request.ResponseStatus; import org.cesecore.certificates.util.AlgorithmTools; import org.cesecore.util.Base64; import org.cesecore.util.CertTools; import org.ejbca.core.model.InternalEjbcaResources; /** * Helper class to create different standard parts of CMP messages * * @version $Id: CmpMessageHelper.java 20994 2015-03-25 10:27:01Z mikekushner $ */ public class CmpMessageHelper { private static Logger LOG = Logger.getLogger(CmpMessageHelper.class); private static final InternalEjbcaResources INTRES = InternalEjbcaResources.getInstance(); private static final String CMP_ERRORGENERAL = "cmp.errorgeneral"; /** Array that converts our error codes from FailInfo to CMP BITString error codes. FailInfo use plain integer codes, which are * the same as positions in the CMP bit string * @see org.bouncycastle.asn1.cmp.PKIFailureInfo */ private static int[] bcconversion = { PKIFailureInfo.badAlg, PKIFailureInfo.badMessageCheck, PKIFailureInfo.badRequest, PKIFailureInfo.badTime, PKIFailureInfo.badCertId, PKIFailureInfo.badDataFormat, PKIFailureInfo.wrongAuthority, PKIFailureInfo.incorrectData, PKIFailureInfo.missingTimeStamp, PKIFailureInfo.badPOP, PKIFailureInfo.certRevoked, PKIFailureInfo.certConfirmed, PKIFailureInfo.wrongIntegrity, PKIFailureInfo.badRecipientNonce, PKIFailureInfo.timeNotAvailable, PKIFailureInfo.unacceptedPolicy, PKIFailureInfo.unacceptedExtension, PKIFailureInfo.addInfoNotAvailable, PKIFailureInfo.badSenderNonce, PKIFailureInfo.badCertTemplate, PKIFailureInfo.signerNotTrusted, PKIFailureInfo.transactionIdInUse, PKIFailureInfo.unsupportedVersion, PKIFailureInfo.notAuthorized, PKIFailureInfo.systemUnavail, PKIFailureInfo.systemFailure, PKIFailureInfo.duplicateCertReq }; /** Returns the PKIFailureInfo that is the correct format for CMP, i.e. a DERBitString as specified in PKIFailureInfo. * @see org.bouncycastle.asn1.cmp.PKIFailureInfo * @see org.cesecore.certificates.certificate.request.FailInfo * * @param failInfo * @return PKIFailureInfo for use in CMP error messages */ public static PKIFailureInfo getPKIFailureInfo(int failInfo) { return new PKIFailureInfo(bcconversion[failInfo]); } public static PKIHeaderBuilder createPKIHeaderBuilder(GeneralName sender, GeneralName recipient, String senderNonce, String recipientNonce, String transactionId) { PKIHeaderBuilder myPKIHeader = new PKIHeaderBuilder(2, sender, recipient); myPKIHeader.setMessageTime(new DERGeneralizedTime(new Date())); if (senderNonce != null) { myPKIHeader.setSenderNonce(new DEROctetString(Base64.decode(senderNonce.getBytes()))); } if (recipientNonce != null) { myPKIHeader.setRecipNonce(new DEROctetString(Base64.decode(recipientNonce.getBytes()))); } if (transactionId != null) { myPKIHeader.setTransactionID(new DEROctetString(Base64.decode(transactionId.getBytes()))); } return myPKIHeader; } public static byte[] signPKIMessage(PKIMessage myPKIMessage, Collection<Certificate> signCertChain, PrivateKey signKey, String digestAlg, String provider) throws InvalidKeyException, NoSuchProviderException, NoSuchAlgorithmException, SecurityException, SignatureException, CertificateEncodingException { if (LOG.isTraceEnabled()) { LOG.trace(">signPKIMessage()"); } CMPCertificate[] extraCerts = new CMPCertificate[signCertChain.size()]; Iterator<Certificate> itr = signCertChain.iterator(); int i = 0; while (itr.hasNext()) { X509Certificate tmp = (X509Certificate) itr.next(); ASN1InputStream asn1InputStream = null; try { try { asn1InputStream = new ASN1InputStream(new ByteArrayInputStream(tmp.getEncoded())); CMPCertificate signStruct = CMPCertificate.getInstance(asn1InputStream.readObject()); extraCerts[i] = signStruct; } finally { asn1InputStream.close(); } } catch (IOException e) { throw new IllegalStateException("Caught unexpected IOException", e); } i++; } myPKIMessage = CmpMessageHelper.buildCertBasedPKIProtection(myPKIMessage, extraCerts, signKey, digestAlg, provider); if (LOG.isTraceEnabled()) { LOG.trace("<signPKIMessage()"); } // Return response as byte array return CmpMessageHelper.pkiMessageToByteArray(myPKIMessage); } public static PKIMessage buildCertBasedPKIProtection(PKIMessage pKIMessage, CMPCertificate[] extraCerts, PrivateKey key, String digestAlg, String provider) throws NoSuchProviderException, NoSuchAlgorithmException, SecurityException, SignatureException, InvalidKeyException { // Select which signature algorithm we should use for the response, based on the digest algorithm and key type. ASN1ObjectIdentifier oid = AlgorithmTools.getSignAlgOidFromDigestAndKey(digestAlg, key.getAlgorithm()); if (LOG.isDebugEnabled()) { LOG.debug("Selected signature alg oid: " + oid.getId() + ", key algorithm: " + key.getAlgorithm()); } // According to PKCS#1 AlgorithmIdentifier for RSA-PKCS#1 has null Parameters, this means a DER Null (asn.1 encoding of null), not Java null. // For the RSA signature algorithms specified above RFC3447 states "...the parameters MUST be present and MUST be NULL." PKIHeaderBuilder headerBuilder = getHeaderBuilder(pKIMessage.getHeader()); AlgorithmIdentifier pAlg = null; if ("RSA".equalsIgnoreCase(key.getAlgorithm())) { pAlg = new AlgorithmIdentifier(oid, DERNull.INSTANCE); } else { pAlg = new AlgorithmIdentifier(oid); } headerBuilder.setProtectionAlg(pAlg); // Most PKCS#11 providers don't like to be fed an OID as signature algorithm, so // we use BC classes to translate it into a signature algorithm name instead PKIHeader head = headerBuilder.build(); String signatureAlgorithmName = AlgorithmTools.getAlgorithmNameFromOID(oid); if (LOG.isDebugEnabled()) { LOG.debug("Signing CMP message with signature alg: " + signatureAlgorithmName); } Signature sig = Signature.getInstance(signatureAlgorithmName, provider); sig.initSign(key); sig.update(CmpMessageHelper.getProtectedBytes(head, pKIMessage.getBody())); if ((extraCerts != null) && (extraCerts.length > 0)) { pKIMessage = new PKIMessage(head, pKIMessage.getBody(), new DERBitString(sig.sign()), extraCerts); } else { pKIMessage = new PKIMessage(head, pKIMessage.getBody(), new DERBitString(sig.sign())); } return pKIMessage; } //TODO see if we could do this in a better way public static PKIHeaderBuilder getHeaderBuilder(PKIHeader head) { PKIHeaderBuilder builder = new PKIHeaderBuilder(head.getPvno().getValue().intValue(), head.getSender(), head.getRecipient()); builder.setFreeText(head.getFreeText()); builder.setGeneralInfo(head.getGeneralInfo()); builder.setMessageTime(head.getMessageTime()); builder.setRecipKID((DEROctetString) head.getRecipKID()); builder.setRecipNonce(head.getRecipNonce()); builder.setSenderKID(head.getSenderKID()); builder.setSenderNonce(head.getSenderNonce()); builder.setTransactionID(head.getTransactionID()); return builder; } /** verifies signature protection on CMP PKI messages * * @param pKIMessage the CMP message to verify signature on, if protected by signature protection * @param pubKey the public key used to verify the signature * @return true if verification is ok or false if verification fails * @throws NoSuchAlgorithmException message is signed by an unknown algorithm * @throws NoSuchProviderException the BouncyCastle (BC) provider is not installed * @throws InvalidKeyException pubKey is not valid for signature verification * @throws SignatureException if the passed-in signature is improperly encoded or of the wrong type, if this signature algorithm is unable to process the input data provided, etc. */ public static boolean verifyCertBasedPKIProtection(PKIMessage pKIMessage, PublicKey pubKey) throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException, SignatureException { AlgorithmIdentifier sigAlg = pKIMessage.getHeader().getProtectionAlg(); if (LOG.isDebugEnabled()) { LOG.debug("Verifying signature with algorithm: " + sigAlg.getAlgorithm().getId()); } Signature sig = Signature.getInstance(sigAlg.getAlgorithm().getId(), "BC"); sig.initVerify(pubKey); sig.update(CmpMessageHelper.getProtectedBytes(pKIMessage)); boolean result = sig.verify(pKIMessage.getProtection().getBytes()); if (LOG.isDebugEnabled()) { LOG.debug("Verification result: " + result); } return result; } public static byte[] protectPKIMessageWithPBE(PKIMessage msg, String keyId, String raSecret, String digestAlgId, String macAlgId, int iterationCount) throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException { if (LOG.isTraceEnabled()) { LOG.trace(">protectPKIMessageWithPBE()"); } // Create the PasswordBased protection of the message PKIHeaderBuilder head = getHeaderBuilder(msg.getHeader()); byte[] keyIdBytes; try { keyIdBytes = keyId.getBytes("UTF-8"); } catch (UnsupportedEncodingException e) { keyIdBytes = keyId.getBytes(); LOG.info("UTF-8 not available, using platform default encoding for keyIdBytes."); } head.setSenderKID(new DEROctetString(keyIdBytes)); // SHA1 AlgorithmIdentifier owfAlg = new AlgorithmIdentifier(digestAlgId); // iterations, usually something like 1024 ASN1Integer iteration = new ASN1Integer(iterationCount); // HMAC/SHA1 AlgorithmIdentifier macAlg = new AlgorithmIdentifier(macAlgId); // We need some random bytes for the nonce byte[] saltbytes = createSenderNonce(); DEROctetString derSalt = new DEROctetString(saltbytes); // Create the new protected return message //String objectId = "1.2.840.113533.7.66.13" = passwordBasedMac; String objectId = CMPObjectIdentifiers.passwordBasedMac.getId(); PBMParameter pp = new PBMParameter(derSalt, owfAlg, iteration, macAlg); AlgorithmIdentifier pAlg = new AlgorithmIdentifier(new ASN1ObjectIdentifier(objectId), pp); head.setProtectionAlg(pAlg); // Calculate the protection bits byte[] rasecret = raSecret.getBytes(); byte[] basekey = new byte[rasecret.length + saltbytes.length]; System.arraycopy(rasecret, 0, basekey, 0, rasecret.length); System.arraycopy(saltbytes, 0, basekey, rasecret.length, saltbytes.length); // Construct the base key according to rfc4210, section 5.1.3.1 MessageDigest dig = MessageDigest.getInstance(owfAlg.getAlgorithm().getId(), "BC"); for (int i = 0; i < iterationCount; i++) { basekey = dig.digest(basekey); dig.reset(); } PKIHeader pkiHeader = head.build(); // Do the mac String macOid = macAlg.getAlgorithm().getId(); byte[] protectedBytes = CmpMessageHelper.getProtectedBytes(pkiHeader, msg.getBody()); //ret.getProtectedBytes(); Mac mac = Mac.getInstance(macOid, "BC"); SecretKey key = new SecretKeySpec(basekey, macOid); mac.init(key); mac.reset(); mac.update(protectedBytes, 0, protectedBytes.length); byte[] out = mac.doFinal(); DERBitString bs = new DERBitString(out); if (LOG.isTraceEnabled()) { LOG.trace("<protectPKIMessageWithPBE()"); } // Return response as byte array return CmpMessageHelper .pkiMessageToByteArray(new PKIMessage(pkiHeader, msg.getBody(), bs, msg.getExtraCerts())); } public static byte[] pkiMessageToByteArray(PKIMessage msg) { // Return response as byte array ByteArrayOutputStream baos = new ByteArrayOutputStream(); DEROutputStream mout = new DEROutputStream(baos); try { mout.writeObject(msg); mout.close(); } catch (IOException e) { throw new IllegalStateException("Caught unexpected IOException."); } return baos.toByteArray(); } /** Creates a 16 bytes random sender nonce * * @return byte array of length 16 */ public static byte[] createSenderNonce() { // Sendernonce is a random number byte[] senderNonce = new byte[16]; Random randomSource; randomSource = new Random(); randomSource.nextBytes(senderNonce); return senderNonce; } /** * creates a very simple error message in response to msg (that's why we switch sender and recipient) * @param msg * @param status * @param failInfo * @param failText * @return IResponseMessage that can be sent to user */ public static ResponseMessage createUnprotectedErrorMessage(BaseCmpMessage msg, ResponseStatus status, FailInfo failInfo, String failText) { // Create a failure message if (LOG.isDebugEnabled()) { LOG.debug("Creating an unprotected error message with status=" + status.getValue() + ", failInfo=" + failInfo + ", failText=" + failText); } CmpErrorResponseMessage resp = new CmpErrorResponseMessage(); resp.setSenderNonce(new String(Base64.encode(CmpMessageHelper.createSenderNonce()))); if (msg != null) { resp.setRecipientNonce(msg.getSenderNonce()); resp.setSender(msg.getRecipient()); resp.setRecipient(msg.getSender()); resp.setTransactionId(msg.getTransactionId()); } else { // We didn't even have a request to get these from, so send back some dummy values resp.setSender(new GeneralName(CertTools.stringToBcX500Name("CN=Failure Sender"))); resp.setRecipient(new GeneralName(CertTools.stringToBcX500Name("CN=Failure Recipient"))); } resp.setFailInfo(failInfo); resp.setStatus(status); resp.setFailText(failText); try { resp.create(); } catch (InvalidKeyException e) { LOG.error("Exception during CMP processing: ", e); } catch (NoSuchAlgorithmException e) { LOG.error("Exception during CMP processing: ", e); } catch (NoSuchProviderException e) { LOG.error("Exception during CMP processing: ", e); } return resp; } /** * creates a simple error message in response to msg. * * The protection paramters can be null to create an unprotected message * * @return IResponseMessage that can be sent to user */ public static CmpErrorResponseMessage createErrorMessage(BaseCmpMessage msg, FailInfo failInfo, String failText, int requestId, int requestType, CmpPbeVerifyer verifyer, String keyId, String responseProt) { CmpErrorResponseMessage resp = null; final CmpErrorResponseMessage cresp = new CmpErrorResponseMessage(); cresp.setRecipientNonce(msg.getSenderNonce()); cresp.setSenderNonce(new String(Base64.encode(CmpMessageHelper.createSenderNonce()))); cresp.setSender(msg.getRecipient()); cresp.setRecipient(msg.getSender()); cresp.setTransactionId(msg.getTransactionId()); cresp.setFailText(failText); cresp.setFailInfo(failInfo); cresp.setRequestId(requestId); cresp.setRequestType(requestType); // Set all protection parameters, this is another message than if we generated a cert above if (verifyer != null) { final String pbeDigestAlg = verifyer.getOwfOid(); final String pbeMacAlg = verifyer.getMacOid(); final int pbeIterationCount = verifyer.getIterationCount(); final String raAuthSecret = verifyer.getLastUsedRaSecret(); if (StringUtils.equals(responseProt, "pbe") && (pbeDigestAlg != null) && (pbeMacAlg != null) && (keyId != null) && (raAuthSecret != null)) { cresp.setPbeParameters(keyId, raAuthSecret, pbeDigestAlg, pbeMacAlg, pbeIterationCount); } } resp = cresp; try { // Here we need to create the response message, when coming from SignSession it has already been "created" resp.create(); } catch (InvalidKeyException e) { LOG.error(INTRES.getLocalizedMessage(CMP_ERRORGENERAL), e); } catch (NoSuchAlgorithmException e) { LOG.error(INTRES.getLocalizedMessage(CMP_ERRORGENERAL), e); } catch (NoSuchProviderException e) { LOG.error(INTRES.getLocalizedMessage(CMP_ERRORGENERAL), e); } return resp; } /** * creates a very simple error message in response to msg (that's why we switch sender and recipient) * @param msg * @param status * @param failInfo * @param failText * @return IResponseMessage that can be sent to user * @throws IOException */ public static PKIBody createCertRequestRejectBody(PKIStatusInfo info, int requestId, int requestType) { // Create a failure message if (LOG.isDebugEnabled()) { LOG.debug("Creating a cert request rejection message"); LOG.debug("Creating a CertRepMessage 'rejected'"); } CertResponse myCertResponse = new CertResponse(new ASN1Integer(requestId), info); CertResponse[] resps = { myCertResponse }; CertRepMessage myCertRepMessage = new CertRepMessage(null, resps); int respType = requestType + 1; // 1 = intitialization response, 3 = certification response etc if (LOG.isDebugEnabled()) { LOG.debug("Creating response body of type " + respType); } PKIBody myPKIBody = new PKIBody(respType, myCertRepMessage); return myPKIBody; } /** * Converts the header and the body of a PKIMessage to an ASN1Encodable and * returns the as a byte array * * @param msg * @return the PKIMessage's header and body in byte array */ public static byte[] getProtectedBytes(PKIMessage msg) { return getProtectedBytes(msg.getHeader(), msg.getBody()); } /** * Converts the header and the body of a PKIMessage to an ASN1Encodable and * returns the as a byte array * * @param header * @param body * @return the PKIMessage's header and body in byte array */ public static byte[] getProtectedBytes(PKIHeader header, PKIBody body) { byte[] res = null; ASN1EncodableVector v = new ASN1EncodableVector(); v.add(header); v.add(body); ASN1Encodable protectedPart = new DERSequence(v); try { ByteArrayOutputStream bao = new ByteArrayOutputStream(); DEROutputStream out = new DEROutputStream(bao); out.writeObject(protectedPart); res = bao.toByteArray(); } catch (Exception ex) { LOG.error(ex.getLocalizedMessage(), ex); } return res; } /** * Parses a CRMF request created with novosec library classes and return a bouncycastle CertReqMsg object * * @param messages * @return */ public static CertReqMsg getNovosecCertReqMsg(CertReqMessages messages) { // The encoding of the ProofOfPosession in bouncycastle and novosec is different. // Novosec generator explicitly tags the PopoSigningKey while it should be implicitly tagged. // Through novosec, the ProofOfPosession comes through as: // Sequence // DERSequence // DERSequence // ObjectIdentifier(1.2.840.113549.1.1.5) // DERBitString[64,0] // // But it should be: // DERSequence // DERSequence // ObjectIdentifier(1.2.840.113549.1.1.5) // DERBitString[64,0] // // The bouncycastle parser expects an implicit tag, so to it, it looks like the sequence is containing a single element. //-------------------------------------- // A comment from bouncycastle that might not effect anything here but maybe effect something else in the future: // What's happened is the novosec generator has explicitly tagged the PopoSigningKey structure, it should be // implicitly tagged (this isn't true if it's a POPOPrivKey, but that's because it's a CHOICE item so the tag // has to be preserved, but that is a different story). // Reconstructing the CertRequest ASN1Encodable o2 = ((DERSequence) messages.toASN1Primitive()).getObjectAt(0); ASN1Encodable o3 = ((DERSequence) o2).getObjectAt(0); CertRequest cr = CertRequest.getInstance(o3); // Reconstructing the proof-of-posession ASN1TaggedObject o4 = (ASN1TaggedObject) ((DERSequence) o2).getObjectAt(1); ProofOfPossession pp; int tagnr = o4.getTagNo(); ASN1Encodable o5; switch (tagnr) { case 0: o5 = DERNull.INSTANCE; pp = new ProofOfPossession(); break; case 1: o5 = POPOSigningKey.getInstance(o4.getObject()); pp = new ProofOfPossession((POPOSigningKey) o5); break; case 2: case 3: o5 = POPOPrivKey.getInstance(o4, false); pp = new ProofOfPossession(tagnr, (POPOPrivKey) o5); break; default: throw new IllegalArgumentException("unknown tag: " + tagnr); } // Reconstructing the regToken ASN1Sequence o6 = (ASN1Sequence) ((ASN1Sequence) o2.toASN1Primitive()).getObjectAt(2); final AttributeTypeAndValue av = AttributeTypeAndValue.getInstance(((ASN1Sequence) o6).getObjectAt(0)); final AttributeTypeAndValue[] avs = { av }; // finally, recreating the CertReqMsg object return new CertReqMsg(cr, pp, avs); } public static RevDetails getNovosecRevDetails(RevReqContent revContent) { // Novosec implements RFC2510, while bouncycastle 1.47 implements RFC4210. // // In RFC2510/novosec, the RevDetails structure looks like this: // RevDetails ::= SEQUENCE { // certDetails CertTemplate, // revocationReason ReasonFlags OPTIONAL, // badSinceDate GeneralizedTime OPTIONAL, // crlEntryDetails Extensions OPTIONAL // } // // In RFC4210/bouncycastle, the REVDetails structure looks like this: // RevDetails ::= SEQUENCE { // certDetails CertTemplate, // crlEntryDetails Extensions OPTIONAL // } // // This means that there is a chance that the request generated using novosec specifies the revocation reason in 'revocationReason' and not // as an extension, leading to Ejbca not being able to parse the request using bouncycastle OR not setting the correct revocation reason. ASN1Encodable o2 = ((DERSequence) revContent.toASN1Primitive()).getObjectAt(0); ASN1Encodable o3 = ((DERSequence) o2).getObjectAt(0); CertTemplate ct = CertTemplate.getInstance(o3); ReasonFlags reasonbits = null; Extensions crlEntryDetails = null; int seqSize = ((DERSequence) o2).size(); for (int i = 1; i < seqSize; i++) { ASN1Encodable o4 = ((DERSequence) o2).getObjectAt(i); if (o4 instanceof DERBitString) { reasonbits = new ReasonFlags((DERBitString) o4); } else if (o4 instanceof DERGeneralizedTime) { DERGeneralizedTime.getInstance(o4); // bad since time, not used in the bouncycastle class } else if (o4 instanceof DERSequence) { crlEntryDetails = Extensions.getInstance(o4); } } if ((crlEntryDetails != null) && (reasonbits != null)) { Extension reason = crlEntryDetails.getExtension(Extension.reasonCode); if (reason == null) { reason = new Extension(Extension.reasonCode, true, ASN1OctetString.getInstance(reasonbits.getBytes())); } } else if ((crlEntryDetails == null) && (reasonbits != null)) { ExtensionsGenerator extgen = new ExtensionsGenerator(); try { extgen.addExtension(Extension.reasonCode, true, ASN1OctetString.getInstance(reasonbits.getBytes())); crlEntryDetails = extgen.generate(); } catch (IOException e) { LOG.error(e.getLocalizedMessage(), e); } } //The constructor RevDetails(certTemplate, crlEntryDetails) only sets 'crlEntryDetails' and ignores 'certTemplate' //This is a reported bug in bouncycastle. For now, the only way to have both of them set is to create a ASN1/DERSequence ASN1EncodableVector seq = new ASN1EncodableVector(); seq.add(ct); seq.add(crlEntryDetails); RevDetails res = RevDetails.getInstance(new DERSequence(seq)); return res; } /** @return SenderKeyId of in the header or null none was found. */ public static String getStringFromOctets(final ASN1OctetString octets) { String str = null; if (octets != null) { try { str = new String(octets.getOctets(), "UTF-8"); } catch (UnsupportedEncodingException e2) { str = new String(octets.getOctets()); LOG.info("UTF-8 not available, using platform default encoding for keyId."); } if (!StringUtils.isAsciiPrintable(str)) { str = new String(Hex.encode(octets.getOctets())); if (LOG.isDebugEnabled()) { LOG.debug("DEROCtetString content is not asciiPrintable, converting to hex: " + str); } } if (LOG.isDebugEnabled()) { LOG.debug("Found string: " + str); } } return str; } }