Package org.ejbca.core.protocol.cmp

Source Code of org.ejbca.core.protocol.cmp.CmpMessageHelper

/*************************************************************************
*                                                                       *
*  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.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.CertificateEncodingException;
import java.security.cert.X509Certificate;
import java.util.Date;
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.ASN1InputStream;
import org.bouncycastle.asn1.DERBitString;
import org.bouncycastle.asn1.DERGeneralizedTime;
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.ocsp.BasicOCSPResponse;
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.asn1.x509.GeneralName;
import org.bouncycastle.asn1.x509.X509CertificateStructure;
import org.bouncycastle.asn1.x509.X509Name;
import org.bouncycastle.cms.CMSSignedGenerator;
import org.bouncycastle.ocsp.BasicOCSPResp;
import org.ejbca.core.model.InternalResources;
import org.ejbca.core.model.ca.SignRequestException;
import org.ejbca.core.model.ra.NotFoundException;
import org.ejbca.core.protocol.FailInfo;
import org.ejbca.core.protocol.IResponseMessage;
import org.ejbca.core.protocol.ResponseStatus;
import org.ejbca.util.Base64;
import org.ejbca.util.CertTools;

import com.novosec.pkix.asn1.cmp.CMPObjectIdentifiers;
import com.novosec.pkix.asn1.cmp.CertRepMessage;
import com.novosec.pkix.asn1.cmp.CertResponse;
import com.novosec.pkix.asn1.cmp.PKIBody;
import com.novosec.pkix.asn1.cmp.PKIHeader;
import com.novosec.pkix.asn1.cmp.PKIMessage;
import com.novosec.pkix.asn1.cmp.PKIStatusInfo;
import com.novosec.pkix.asn1.crmf.PBMParameter;

/**
* Helper class to create different standard parts of CMP messages
*
* @author tomas
* @version $Id: CmpMessageHelper.java 12098 2011-05-31 12:14:24Z anatom $
*/
public class CmpMessageHelper {
  private static Logger LOG = Logger.getLogger(CmpMessageHelper.class);
    private static final InternalResources INTRES = InternalResources.getInstance();

  private static final String CMP_ERRORGENERAL = "cmp.errorgeneral";

  public static PKIHeader createPKIHeader(X509Name sender, X509Name recipient, String senderNonce, String recipientNonce, String transactionId) {
    PKIHeader myPKIHeader =
      new PKIHeader(
          new DERInteger(2),
          new GeneralName(sender),
          new GeneralName(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, X509Certificate signCert, PrivateKey signKey, String digestAlg, String provider) throws InvalidKeyException, NoSuchProviderException, NoSuchAlgorithmException, SecurityException, SignatureException, IOException, CertificateEncodingException {
      if (LOG.isTraceEnabled()) {
        LOG.trace(">signPKIMessage()");
      }
    X509CertificateStructure signStruct = X509CertificateStructure.getInstance(new ASN1InputStream(new ByteArrayInputStream(signCert.getEncoded())).readObject());
    CmpMessageHelper.buildCertBasedPKIProtection( myPKIMessage, signStruct, signKey, digestAlg, provider);
      if (LOG.isTraceEnabled()) {
        LOG.trace("<signPKIMessage()");
      }
    // Return response as byte array
    return CmpMessageHelper.pkiMessageToByteArray(myPKIMessage);
    }
   
  public static void buildCertBasedPKIProtection( PKIMessage pKIMessage, X509CertificateStructure cert, 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.
    DERObjectIdentifier oid = PKCSObjectIdentifiers.sha1WithRSAEncryption;
    if (digestAlg.equals(CMSSignedGenerator.DIGEST_SHA256)) {
      oid = PKCSObjectIdentifiers.sha256WithRSAEncryption;     
    }
    if (digestAlg.equals(CMSSignedGenerator.DIGEST_MD5)) {
      oid = PKCSObjectIdentifiers.md5WithRSAEncryption;     
    }
      if (LOG.isDebugEnabled()) {
        LOG.debug("Selected signature alg oid: "+oid.getId());
      }
      // 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."
    pKIMessage.getHeader().setProtectionAlg(new AlgorithmIdentifier(oid, new DERNull()));
    // 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
    final String sigAlg = new BasicOCSPResp(new BasicOCSPResponse(null, new AlgorithmIdentifier(oid), null, null)).getSignatureAlgName();
      if (LOG.isDebugEnabled()) {
        LOG.debug("Signing CMP message with signature alg: "+sigAlg);
      }
    Signature sig = Signature.getInstance(sigAlg , provider );
    sig.initSign(key);
    sig.update( pKIMessage.getProtectedBytes() );
   
    pKIMessage.setProtection( new DERBitString(sig.sign()) );
    pKIMessage.addExtraCert( cert );
  }
 
  /** 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.getObjectId().getId());
    }
    Signature sig = Signature.getInstance(sigAlg.getObjectId().getId(), "BC");
    sig.initVerify(pubKey);
    sig.update(pKIMessage.getProtectedBytes());
    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, IOException {
      if (LOG.isTraceEnabled()) {
        LOG.trace(">protectPKIMessageWithPBE()");
      }
    // Create the PasswordBased protection of the message
    PKIHeader head = 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("1.3.14.3.2.26");
    AlgorithmIdentifier owfAlg = new AlgorithmIdentifier(digestAlgId);
    // iterations, usually something like 1024
    DERInteger iteration = new DERInteger(iterationCount);
    // HMAC/SHA1
    //AlgorithmIdentifier macAlg = new AlgorithmIdentifier("1.2.840.113549.2.7");
    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 DERObjectIdentifier(objectId), pp);
    head.setProtectionAlg(pAlg);
    PKIBody body = msg.getBody();
    PKIMessage ret = new PKIMessage(head, body);

    // Calculate the protection bits
    byte[] rasecret = raSecret.getBytes();
    byte[] basekey = new byte[rasecret.length + saltbytes.length];
    for (int i = 0; i < rasecret.length; i++) {
      basekey[i] = rasecret[i];
    }
    for (int i = 0; i < saltbytes.length; i++) {
      basekey[rasecret.length+i] = saltbytes[i];
    }
    // Construct the base key according to rfc4210, section 5.1.3.1
    MessageDigest dig = MessageDigest.getInstance(owfAlg.getObjectId().getId(), "BC");
    for (int i = 0; i < iterationCount; i++) {
      basekey = dig.digest(basekey);
      dig.reset();
    }
    // Do the mac
    String macOid = macAlg.getObjectId().getId();
    byte[] protectedBytes = 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);

    // Finally store the protection bytes in the msg
    ret.setProtection(bs);
   
      if (LOG.isTraceEnabled()) {
        LOG.trace("<protectPKIMessageWithPBE()");
      }
    // Return response as byte array
    return CmpMessageHelper.pkiMessageToByteArray(ret);
  }

  public static byte[] pkiMessageToByteArray(PKIMessage msg) throws IOException {
    // Return response as byte array
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    DEROutputStream mout = new DEROutputStream( baos );
    mout.writeObject( msg );
    mout.close();
    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 IResponseMessage 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 the get these from, so send back some dummy values
      resp.setSender(new GeneralName(CertTools.stringToBcX509Name("CN=Failure Sender")));
      resp.setRecipient(new GeneralName(CertTools.stringToBcX509Name("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);     
    } catch (SignRequestException e) {
      LOG.error("Exception during CMP processing: ", e);     
    } catch (NotFoundException e) {
      LOG.error("Exception during CMP processing: ", e);     
    } catch (IOException 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
    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);
    } catch (SignRequestException e) {
      LOG.error(INTRES.getLocalizedMessage(CMP_ERRORGENERAL), e);
    } catch (NotFoundException e) {
      LOG.error(INTRES.getLocalizedMessage(CMP_ERRORGENERAL), e);
    } catch (IOException 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
   */
  public static PKIBody createCertRequestRejectBody(PKIHeader header, 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'");
    }

    /*
    String senderNonce = new String(Base64.encode(CmpMessageHelper.createSenderNonce()));
    String rcptNonce = null;
    X509Name sender = CertTools.stringToBcX509Name("CN=Failure Sender");
    X509Name rcpt = CertTools.stringToBcX509Name("CN=Failure Recipient");
    String transactionId = msg.getTransactionId();
    PKIHeader myPKIHeader = CmpMessageHelper.createPKIHeader(sender, rcpt, senderNonce, rcptNonce, transactionId);
    */
   
    CertResponse myCertResponse = new CertResponse(new DERInteger(requestId), info);
    CertRepMessage myCertRepMessage = new CertRepMessage(myCertResponse);

    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(myCertRepMessage, respType);
   
    return myPKIBody;
  }
}
TOP

Related Classes of org.ejbca.core.protocol.cmp.CmpMessageHelper

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.