Package org.ejbca.core.protocol.cmp

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

/*************************************************************************
*                                                                       *
*  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.math.BigInteger;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
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.spec.X509EncodedKeySpec;
import java.util.Date;

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.DEREncodable;
import org.bouncycastle.asn1.DERInteger;
import org.bouncycastle.asn1.DEROctetString;
import org.bouncycastle.asn1.DEROutputStream;
import org.bouncycastle.asn1.DERUTF8String;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.asn1.x509.Time;
import org.bouncycastle.asn1.x509.X509Extension;
import org.bouncycastle.asn1.x509.X509Extensions;
import org.bouncycastle.asn1.x509.X509Name;
import org.bouncycastle.cms.CMSSignedGenerator;
import org.bouncycastle.util.Arrays;
import org.ejbca.core.protocol.IRequestMessage;
import org.ejbca.core.protocol.IResponseMessage;
import org.ejbca.util.Base64;
import org.ejbca.util.CertTools;
import org.ejbca.util.RequestMessageUtils;

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.crmf.AttributeTypeAndValue;
import com.novosec.pkix.asn1.crmf.CRMFObjectIdentifiers;
import com.novosec.pkix.asn1.crmf.CertReqMessages;
import com.novosec.pkix.asn1.crmf.CertReqMsg;
import com.novosec.pkix.asn1.crmf.CertRequest;
import com.novosec.pkix.asn1.crmf.CertTemplate;
import com.novosec.pkix.asn1.crmf.OptionalValidity;
import com.novosec.pkix.asn1.crmf.POPOSigningKey;
import com.novosec.pkix.asn1.crmf.POPOSigningKeyInput;
import com.novosec.pkix.asn1.crmf.ProofOfPossession;

/**
* Certificate request message (crmf) according to RFC4211.
* - Supported POPO:
* -- raVerified (null), i.e. no POPO verification is done, it should be configurable if the CA should allow this or require a real POPO
* -- Self signature, using the key in CertTemplate, or POPOSigningKeyInput (name and public key), option 2 and 3 in RFC4211, section "4.1.  Signature Key POP"
*
* @author tomas
* @version $Id: CrmfRequestMessage.java 12107 2011-06-01 10:16:12Z anatom $
*/
public class CrmfRequestMessage extends BaseCmpMessage implements ICrmfRequestMessage {
 
  private static final Logger log = Logger.getLogger(CrmfRequestMessage.class);
 
    /**
     * 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 = 1002L;

    private int requestType = 0;
    private int requestId = 0;
  private String b64SenderNonce = null;
  private String b64TransId = null;
  /** Default CA DN */
  private String defaultCADN = null;
  private boolean allowRaVerifyPopo = false;
  private String extractUsernameComponent = null;
    /** manually set username */
    private String username = null;
    /** manually set password */
    private String password = null;

  /** Because PKIMessage is not serializable we need to have the serializable bytes save as well, so
   * we can restore the PKIMessage after serialization/deserialization. */
  private byte[] pkimsgbytes = null;
  private transient CertReqMsg req = null;
  /** Because CertReqMsg is not serializable we may need to encode/decode bytes if the object is lost during deserialization. */
  private CertReqMsg getReq() {
    if (req == null) {
      init();
    }
    return this.req;
  }

    /** preferred digest algorithm to use in replies, if applicable */
    private String preferredDigestAlg = CMSSignedGenerator.DIGEST_SHA1;

    public CrmfRequestMessage() {
       
    }
   
    /**
     *
     * @param msg PKIMessage
     * @param defaultCA possibility to enforce a certain CA, instead of taking the CA subject DN from the request, if set to null the CA subject DN is taken from the request
     * @param allowRaVerifyPopo true if we allows the user/RA to specify the POP should not be verified
     * @param extractUsernameComponent Defines which component from the DN should be used as username in EJBCA. Can be CN, UID or nothing. Null means that the username should have been pre-set, or that here it is the same as CN.
     */
  public CrmfRequestMessage(final PKIMessage msg, final String defaultCADN, final boolean allowRaVerifyPopo, final String extractUsernameComponent) {
      if (log.isTraceEnabled()) {
        log.trace(">CrmfRequestMessage");
      }
    setPKIMessage(msg);
    this.defaultCADN = defaultCADN;
    this.allowRaVerifyPopo = allowRaVerifyPopo;
    this.extractUsernameComponent = extractUsernameComponent;
        init();
      if (log.isTraceEnabled()) {
        log.trace("<CrmfRequestMessage");
      }
  }

  public PKIMessage getPKIMessage() {
    if (getMessage() == null) {
      try {
        setMessage(PKIMessage.getInstance(new ASN1InputStream(new ByteArrayInputStream(pkimsgbytes)).readObject()));       
      } catch (IOException e) {
        log.error("Error decoding bytes for PKIMessage: ", e);
      }
    }
    return getMessage();
  }
  public void setPKIMessage(final PKIMessage msg) {
    try {
      this.pkimsgbytes = msg.getDERObject().getEncoded();
    } catch (IOException e) {
      log.error("Error getting encoded bytes from PKIMessage: ", e);
    }
    setMessage(msg);
  }

  private void init() {
    final PKIBody body = getPKIMessage().getBody();
    final PKIHeader header = getPKIMessage().getHeader();
    requestType = body.getTagNo();
    final CertReqMessages msgs = getCertReqFromTag(body, requestType);
    requestId = msgs.getCertReqMsg(0).getCertReq().getCertReqId().getValue().intValue();
    this.req = msgs.getCertReqMsg(0);
    DEROctetString os = header.getTransactionID();
    if (os != null) {
      byte[] val = os.getOctets();
      if (val != null) {
        setTransactionId(new String(Base64.encode(val)));             
      }
    }
    os = header.getSenderNonce();
    if (os != null) {
      byte[] val = os.getOctets();
      if (val != null) {
        setSenderNonce(new String(Base64.encode(val)));             
      }
    }
    setRecipient(header.getRecipient());
    setSender(header.getSender());
  }
 
  @Override
  public PublicKey getRequestPublicKey() throws InvalidKeyException, NoSuchAlgorithmException, NoSuchProviderException {
    final CertRequest request = getReq().getCertReq();
    final CertTemplate templ = request.getCertTemplate();
    final SubjectPublicKeyInfo keyInfo = templ.getPublicKey();
    final PublicKey pk = getPublicKey(keyInfo, "BC");
    return pk;
  }
  private PublicKey getPublicKey(final SubjectPublicKeyInfo subjectPKInfo, final String  provider) throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException {   
    try {
      final X509EncodedKeySpec xspec = new X509EncodedKeySpec(new DERBitString(subjectPKInfo).getBytes());
      final AlgorithmIdentifier keyAlg = subjectPKInfo.getAlgorithmId ();
      return KeyFactory.getInstance(keyAlg.getObjectId().getId (), provider).generatePublic(xspec);
    } catch (java.security.spec.InvalidKeySpecException e) {
      final InvalidKeyException newe = new InvalidKeyException("Error decoding public key.");
      newe.initCause(e);
      throw newe;
    }
  }
 
    /** force a password, i.e. ignore the password in the request
     */
  public void setPassword(final String pwd) {
        this.password = pwd;
    }
 
  @Override
  public String getPassword() {
    String ret = null;
    if (password != null) {
      if (log.isDebugEnabled()) {
        log.debug("Returning a pre-set password in CRMF request");
      }
      ret = password;
    } else {
      // If there is "Registration Token Control" in the CertReqMsg regInfo containing a password, we can use that
      AttributeTypeAndValue av = null;
      int i = 0;
      do {
        av = getReq().getRegInfo(i);
        if (av != null) {
          if (StringUtils.equals(CRMFObjectIdentifiers.regCtrl_regToken.getId(), av.getObjectId().getId())) {
            final DEREncodable enc = av.getParameters();
            final DERUTF8String str = DERUTF8String.getInstance(enc);
            ret = str.getString();
            if (log.isDebugEnabled()) {
              log.debug("Found a request password in CRMF request regCtrl_regToken");
            }
          }
        }
        i++;
      } while ( (av != null) && (ret == null) );
    }   
    if (ret == null) {
      // If there is "Registration Token Control" in the CertRequest controls containing a password, we can use that
      // Note, this is the correct way to use the regToken according to RFC4211, section "6.1.  Registration Token Control"
      AttributeTypeAndValue av = null;
      int i = 0;
      do {
        av = getReq().getCertReq().getControls(i);
        if (av != null) {
          if (StringUtils.equals(CRMFObjectIdentifiers.regCtrl_regToken.getId(), av.getObjectId().getId())) {
            final DEREncodable enc = av.getParameters();
            final DERUTF8String str = DERUTF8String.getInstance(enc);
            ret = str.getString();
            if (log.isDebugEnabled()) {
              log.debug("Found a request password in CRMF request regCtrl_regToken");
            }
          }
        }
        i++;
      } while ( (av != null) && (ret == null) );
    }
    return ret;
  }

    /** force a username, i.e. ignore the DN/username in the request
     */
    public void setUsername(final String username) {
        this.username = username;
    }
   
  @Override
  public String getUsername() {
    String ret = null;
        if (username != null) {
            ret = username;
        } else {
          // We can configure which part of the users DN should be used as username in EJBCA, for example CN or UID
          String component = extractUsernameComponent;
          if (StringUtils.isEmpty(component)) {
            component = "CN";
          }
            String name = CertTools.getPartFromDN(getRequestDN(), component);
            if (name == null) {
                log.error("No component "+component+" in DN: "+getRequestDN());
            } else {
              ret = name;
            }
        }
    if (log.isDebugEnabled()) {
      log.debug("Username is: "+ret);
    }
        return ret;
  }

  public void setIssuerDN(final String issuer) {
    this.defaultCADN = issuer;
  }
  @Override
  public String getIssuerDN() {
    String ret = null;
    final CertTemplate templ = getReq().getCertReq().getCertTemplate();
    final X509Name name = templ.getIssuer();
    if (name != null) {
      ret = CertTools.stringToBCDNString(name.toString());
    } else {
      ret = defaultCADN;
    }
    if (log.isDebugEnabled()) {
      log.debug("Issuer DN is: "+ret);
    }
    return ret;
  }

  @Override
  public BigInteger getSerialNo() {
    return null;
  }

  @Override
  public String getCRLIssuerDN() {
    return null;
  }

  @Override
  public BigInteger getCRLSerialNo() {
    return null;
  }

  /** Gets a requested certificate serial number of the subject. This is a standard field in the CertTemplate in the request.
   * However the standard RFC 4211, section 5 (CertRequest syntax) says it MUST not be used.
   * Requesting custom certificate serial numbers is a very non-standard procedure anyhow, so we use it anyway.
   *
   * @return BigInteger the requested custom certificate serial number or null, normally this should return null.
   */
  public BigInteger getSubjectCertSerialNo() {
    BigInteger ret = null;
    final CertRequest request = getReq().getCertReq();
    final CertTemplate templ = request.getCertTemplate();
    final DERInteger serno = templ.getSerialNumber();
    if (serno != null) {
      ret = serno.getValue();     
    }
    return ret;
  }

  @Override
  public String getRequestDN() {
    String ret = null;
    final X509Name name = getRequestX509Name();
    if (name != null) {
      ret = CertTools.stringToBCDNString(name.toString());
    }
    if (log.isDebugEnabled()) {
      log.debug("Request DN is: "+ret);
    }
    return ret;
  }


  @Override
  public X509Name getRequestX509Name() {
    final CertTemplate templ = getReq().getCertReq().getCertTemplate();
    final X509Name name = templ.getSubject();
    if (log.isDebugEnabled()) {
      log.debug("Request X509Name is: "+name);
    }
    return name;
  }

  @Override
  public String getRequestAltNames() {
      String ret = null;
      final CertTemplate templ = getReq().getCertReq().getCertTemplate();
      final X509Extensions exts = templ.getExtensions();
    if (exts != null) {
      final X509Extension ext = exts.getExtension(X509Extensions.SubjectAlternativeName);
      if (ext != null) {
        ret = CertTools.getAltNameStringFromExtension(ext);
      }
    }
    if (log.isDebugEnabled()) {
      log.debug("Request altName is: "+ret);
    }
      return ret;
    }

  @Override
  public Date getRequestValidityNotBefore() {
    Date ret = null;
    final CertTemplate templ = getReq().getCertReq().getCertTemplate();
    final OptionalValidity val = templ.getValidity();
    if (val != null) {
      final Time time = val.getNotBefore();
      if (time != null) {
        ret = time.getDate();
      }
    }
    if (log.isDebugEnabled()) {
      log.debug("Request validity notBefore is: "+(ret == null ? "null" : ret.toString()));
    }
    return ret;
  }
 
  @Override
  public Date getRequestValidityNotAfter() {
    Date ret = null;
    final CertTemplate templ = getReq().getCertReq().getCertTemplate();
    final OptionalValidity val = templ.getValidity();
    if (val != null) {
      Time time = val.getNotAfter();
      if (time != null) {
        ret = time.getDate();
      }
    }
    if (log.isDebugEnabled()) {
      log.debug("Request validity notAfter is: "+(ret == null ? "null" : ret.toString()));
    }
    return ret;
  }

  @Override
  public X509Extensions getRequestExtensions() {
    final CertTemplate templ = getReq().getCertReq().getCertTemplate();
    final X509Extensions exts = templ.getExtensions();
    if (log.isDebugEnabled()) {
      if (exts != null) {
        log.debug("Request contains extensions");     
      } else {
        log.debug("Request does not contain extensions");           
      }
    }
    return exts;
  }

  @Override
  public boolean verify() throws InvalidKeyException, NoSuchAlgorithmException, NoSuchProviderException {
    boolean ret = false;
    final ProofOfPossession pop = getReq().getPop();
    if (log.isDebugEnabled()) {
      log.debug("allowRaVerifyPopo: "+allowRaVerifyPopo);
      log.debug("pop.getRaVerified(): "+(pop.getRaVerified() != null));
      log.debug("pop.getSignature(): "+(pop.getSignature() != null));
    }
    if ( allowRaVerifyPopo && (pop.getRaVerified() != null)) {
      ret = true;
    } else if (pop.getSignature() != null) {
      try {
        final POPOSigningKey sk = pop.getSignature();
        final POPOSigningKeyInput pski = sk.getPoposkInput();
        Object protObject = pski;
        // Use of POPOSigningKeyInput or not, as described in RFC4211, section 4.1.
        if (pski == null) {
          if (log.isDebugEnabled()) {
            log.debug("Using CertRequest as POPO input.");
          }
          protObject = getReq().getCertReq();
        } else {
          // Assume POPOSigningKeyInput with the public key and name, MUST be the same as in the request according to RFC4211
          if (log.isDebugEnabled()) {
            log.debug("Using POPOSigningKeyInput as POPO input.");
          }
          final CertRequest req = getReq().getCertReq();
          // If subject is present in cert template it must be the same as in POPOSigningKeyInput
          final X509Name subject = req.getCertTemplate().getSubject();
          if (subject != null && !subject.toString().equals(pski.getSender().getName().toString())) {
            log.info("Subject '"+subject.toString()+"̈́', is not equal to '"+pski.getSender().toString()+"'.");
            protObject = null// pski is not a valid protection object
          }
          // If public key is present in cert template it must be the same as in POPOSigningKeyInput
          final SubjectPublicKeyInfo pk = req.getCertTemplate().getPublicKey();
          if (pk != null && !Arrays.areEqual(pk.getEncoded(), pski.getPublicKey().getEncoded())) {
            log.info("Subject key in cert template, is not equal to subject key in POPOSigningKeyInput.");
            protObject = null// pski is not a valid protection object
          }
        }
        // If a protectObject is present we extract the bytes and verify it
        if (protObject != null) {
          final ByteArrayOutputStream bao = new ByteArrayOutputStream();
          new DEROutputStream(bao).writeObject(protObject);
          final byte[] protBytes = bao.toByteArray();
          final AlgorithmIdentifier algId = sk.getAlgorithmIdentifier();
          if (log.isDebugEnabled()) {
            log.debug("POP protection bytes length: "+protBytes != null ? protBytes.length : "null");
            log.debug("POP algorithm identifier is: "+algId.getObjectId().getId());
          }
          final Signature sig = Signature.getInstance(algId.getObjectId().getId(), "BC");
          sig.initVerify(getRequestPublicKey());
          sig.update(protBytes);
          final DERBitString bs = sk.getSignature();
          ret = sig.verify(bs.getBytes());         
        }
      } catch (IOException e) {
        log.error("Error encoding CertReqMsg: ", e);
      } catch (SignatureException e) {
        log.error("SignatureException verifying POP: ", e);
      }     
    }
    return ret;
  }

  @Override
  public boolean requireKeyInfo() {
    return false;
  }

  @Override
  public void setKeyInfo(final Certificate cert, final PrivateKey key, final String provider) {
  }

  @Override
  public int getErrorNo() {
    return 0;
  }

  @Override
  public String getErrorText() {
    return null;
  }

  public void setSenderNonce(final String b64nonce) {
    this.b64SenderNonce = b64nonce;
  }
  @Override
  public String getSenderNonce() {
    return b64SenderNonce;
  }

  public void setTransactionId(final String b64transid) {
    this.b64TransId = b64transid;
  }
  @Override
  public String getTransactionId() {
    return b64TransId;
  }

  @Override
  public byte[] getRequestKeyInfo() {
    return null;
  }

  @Override
  public String getPreferredDigestAlg() {
    return preferredDigestAlg;
  }

  @Override
  public boolean includeCACert() {
    return false;
  }

  @Override
  public int getRequestType() {
      return requestType;
    }

  @Override
  public int getRequestId() {
      return requestId;
    }

  // Returns the subject DN from the request, used from CrmfMessageHandler
  public String getSubjectDN() {
    String ret = null;
    final CertTemplate templ = getReq().getCertReq().getCertTemplate();
    final X509Name name = templ.getSubject();
    if (name != null) {
      ret = CertTools.stringToBCDNString(name.toString());
    }
    return ret;
  }

  private CertReqMessages getCertReqFromTag(final PKIBody body, final int tag) {
    CertReqMessages msgs = null;
    switch (tag) {
    case 0:
      msgs = body.getIr();
      break;
    case 2:
      msgs = body.getCr();
      break;
    case 7:
      msgs = body.getKur();
      break;
    case 9:
      msgs = body.getKrr();
      break;
    case 13:
      msgs = body.getCcr();
      break;
    default:
      break;
    }
    return msgs;
  }

  @Override
  public IResponseMessage createResponseMessage(final Class responseClass, final IRequestMessage req, final Certificate cert, final PrivateKey signPriv, final String provider) {
      return RequestMessageUtils.createResponseMessage(responseClass, req, cert, signPriv, provider);
    }
}
TOP

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

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.