org.ejbca.ui.cmpclient.commands.KeyUpdateRequestCommand.java Source code

Java tutorial

Introduction

Here is the source code for org.ejbca.ui.cmpclient.commands.KeyUpdateRequestCommand.java

Source

/*************************************************************************
 *                                                                       *
 *  EJBCA - Proprietary Modules: Enterprise Certificate Authority        *
 *                                                                       *
 *  Copyright (c), PrimeKey Solutions AB. All rights reserved.           *
 *  The use of the Proprietary Modules are subject to specific           * 
 *  commercial license terms.                                            *
 *                                                                       *
 *************************************************************************/
package org.ejbca.ui.cmpclient.commands;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.security.KeyPair;
import java.security.Signature;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Date;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.bouncycastle.asn1.ASN1EncodableVector;
import org.bouncycastle.asn1.ASN1GeneralizedTime;
import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.DERBitString;
import org.bouncycastle.asn1.DERGeneralizedTime;
import org.bouncycastle.asn1.DEROctetString;
import org.bouncycastle.asn1.DEROutputStream;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.asn1.DERTaggedObject;
import org.bouncycastle.asn1.DERUTF8String;
import org.bouncycastle.asn1.cmp.CMPCertificate;
import org.bouncycastle.asn1.cmp.CertOrEncCert;
import org.bouncycastle.asn1.cmp.CertRepMessage;
import org.bouncycastle.asn1.cmp.CertResponse;
import org.bouncycastle.asn1.cmp.CertifiedKeyPair;
import org.bouncycastle.asn1.cmp.ErrorMsgContent;
import org.bouncycastle.asn1.cmp.PKIBody;
import org.bouncycastle.asn1.cmp.PKIHeaderBuilder;
import org.bouncycastle.asn1.cmp.PKIMessage;
import org.bouncycastle.asn1.cmp.PKIStatus;
import org.bouncycastle.asn1.cmp.PKIStatusInfo;
import org.bouncycastle.asn1.crmf.AttributeTypeAndValue;
import org.bouncycastle.asn1.crmf.CRMFObjectIdentifiers;
import org.bouncycastle.asn1.crmf.CertReqMessages;
import org.bouncycastle.asn1.crmf.CertReqMsg;
import org.bouncycastle.asn1.crmf.CertRequest;
import org.bouncycastle.asn1.crmf.CertTemplateBuilder;
import org.bouncycastle.asn1.crmf.OptionalValidity;
import org.bouncycastle.asn1.crmf.POPOSigningKey;
import org.bouncycastle.asn1.crmf.ProofOfPossession;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.asn1.x509.GeneralName;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.cesecore.certificates.util.AlgorithmConstants;
import org.cesecore.certificates.util.AlgorithmTools;
import org.cesecore.keys.util.KeyTools;
import org.cesecore.util.CertTools;
import org.ejbca.config.CmpConfiguration;
import org.ejbca.ui.cli.infrastructure.command.CommandResult;
import org.ejbca.ui.cli.infrastructure.parameter.Parameter;
import org.ejbca.ui.cli.infrastructure.parameter.ParameterContainer;
import org.ejbca.ui.cli.infrastructure.parameter.enums.MandatoryMode;
import org.ejbca.ui.cli.infrastructure.parameter.enums.ParameterMode;
import org.ejbca.ui.cli.infrastructure.parameter.enums.StandaloneMode;
import org.ejbca.ui.cmpclient.CmpClientMessageHelper;

public class KeyUpdateRequestCommand extends CmpCommandBase {

    private static final Logger log = Logger.getLogger(KeyUpdateRequestCommand.class);

    private static final String COMMAND = "update";

    private static final String CMP_ALIAS_KEY = "--alias";
    private static final String SUBJECTDN_KEY = "--dn";
    private static final String ISSUERDN_KEY = "--issuer";
    private static final String DESTINATION_KEY = "--dest";
    private static final String EXTRACERT_FRIENDLYNAME_KEY = "--extraCertsFriendlyName";
    private static final String KEYSTORE_KEY = "--keystore";
    private static final String KEYSTOREPWD_KEY = "--keystorepwd";
    private static final String INCLUDE_POPO_KEY = "--includepopo";
    private static final String HOST_KEY = "--host";
    private static final String URL_KEY = "--url";
    private static final String VERBOSE_KEY = "--v";

    //Register all parameters
    {
        registerParameter(new Parameter(CMP_ALIAS_KEY, "CMP Configuration Alias", MandatoryMode.OPTIONAL,
                StandaloneMode.ALLOW, ParameterMode.ARGUMENT, "The CMP configuration alias. Default value: cmp"));
        registerParameter(new Parameter(SUBJECTDN_KEY, "SubjectDN", MandatoryMode.MANDATORY, StandaloneMode.ALLOW,
                ParameterMode.ARGUMENT, "The certificate's SubjectDN."));
        registerParameter(new Parameter(ISSUERDN_KEY, "IssuerDN", MandatoryMode.MANDATORY, StandaloneMode.ALLOW,
                ParameterMode.ARGUMENT, "The certificate's issuerDN"));
        registerParameter(new Parameter(KEYSTORE_KEY, "Path to the keystore", MandatoryMode.MANDATORY,
                StandaloneMode.ALLOW, ParameterMode.ARGUMENT,
                "The path to the keystore containing the private key used to sign the request"));
        registerParameter(new Parameter(KEYSTOREPWD_KEY, "Keystore password", MandatoryMode.MANDATORY,
                StandaloneMode.ALLOW, ParameterMode.ARGUMENT,
                "The password to the keystore containing the certificate and private key used to sign the request"));
        registerParameter(new Parameter(EXTRACERT_FRIENDLYNAME_KEY, "ExtraCert Friendly Name",
                MandatoryMode.MANDATORY, StandaloneMode.ALLOW, ParameterMode.ARGUMENT,
                "The friendlyname in the keystore for the certificate that will be attachded in the extraCerts field"));
        registerParameter(new Parameter(DESTINATION_KEY, "Destination Directory", MandatoryMode.OPTIONAL,
                StandaloneMode.ALLOW, ParameterMode.ARGUMENT,
                "The path to the directory where the newly issued certificate is stored. Default is './dest/'."));
        registerParameter(new Parameter(INCLUDE_POPO_KEY, "Include Proof-of-Possession", MandatoryMode.OPTIONAL,
                StandaloneMode.FORBID, ParameterMode.FLAG,
                "If present, a Proof-of-Possession is included in the CMP request"));
        registerParameter(new Parameter(HOST_KEY, "Host", MandatoryMode.OPTIONAL, StandaloneMode.ALLOW,
                ParameterMode.ARGUMENT, "The name or IP adress to the CMP server. Default value is 'localhost'"));
        registerParameter(new Parameter(URL_KEY, "URL", MandatoryMode.OPTIONAL, StandaloneMode.ALLOW,
                ParameterMode.ARGUMENT,
                "The whole URL to the CMP server. This URL must include the CMP configuration alias. Default: http://localhost:8080/ejbca/publicweb/cmp/<CmpConfigurationAlias>"));
        registerParameter(new Parameter(VERBOSE_KEY, "Verbose", MandatoryMode.OPTIONAL, StandaloneMode.FORBID,
                ParameterMode.FLAG, "Prints out extra messages while executing"));

    }

    @Override
    public String getMainCommand() {
        return COMMAND;
    }

    @Override
    public String getCommandDescription() {
        return "Sends a KeyUpdate request and stores the returned certificate in a local directory. "
                + "The certificate file name will have the format <CN>.pem";
    }

    @Override
    protected CommandResult execute(ParameterContainer parameters) {
        try {
            final PKIMessage pkimessage = generatePKIMessage(parameters);

            final PKIMessage protectedPKIMessage = CmpClientMessageHelper.getInstance().createProtectedMessage(
                    pkimessage, CmpConfiguration.AUTHMODULE_ENDENTITY_CERTIFICATE,
                    parameters.get(EXTRACERT_FRIENDLYNAME_KEY), parameters.get(KEYSTORE_KEY),
                    parameters.get(KEYSTOREPWD_KEY), parameters.containsKey(VERBOSE_KEY));

            byte[] requestBytes = CmpClientMessageHelper.getInstance().getRequestBytes(protectedPKIMessage);

            byte[] responseBytes = CmpClientMessageHelper.getInstance().sendCmpHttp(requestBytes, 200,
                    parameters.get(CMP_ALIAS_KEY), parameters.get(HOST_KEY), parameters.get(URL_KEY));

            return handleCMPResponse(responseBytes, parameters);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return CommandResult.CLI_FAILURE;
    }

    @Override
    public PKIMessage generatePKIMessage(ParameterContainer parameters) throws Exception {
        boolean verbose = parameters.containsKey(VERBOSE_KEY);

        final X500Name userDN = new X500Name(parameters.get(SUBJECTDN_KEY));
        final X500Name issuerDN = new X500Name(parameters.get(ISSUERDN_KEY));
        boolean includePopo = parameters.containsKey(INCLUDE_POPO_KEY);

        if (verbose) {
            log.info("Creating KeyUpdate request with: SubjectDN=" + userDN.toString());
            log.info("Creating KeyUpdate request with: IssuerDN=" + issuerDN.toString());
            log.info("Creating KeyUpdate request with: IncludePopo=" + includePopo);
        }

        byte[] nonce = CmpClientMessageHelper.getInstance().createSenderNonce();
        byte[] transid = CmpClientMessageHelper.getInstance().createSenderNonce();
        KeyPair keys = KeyTools.genKeys("1024", AlgorithmConstants.KEYALGORITHM_RSA);

        CertTemplateBuilder myCertTemplate = new CertTemplateBuilder();

        ASN1EncodableVector optionalValidityV = new ASN1EncodableVector();
        org.bouncycastle.asn1.x509.Time nb = new org.bouncycastle.asn1.x509.Time(
                new DERGeneralizedTime("20030211002120Z"));
        org.bouncycastle.asn1.x509.Time na = new org.bouncycastle.asn1.x509.Time(new Date());
        optionalValidityV.add(new DERTaggedObject(true, 0, nb));
        optionalValidityV.add(new DERTaggedObject(true, 1, na));
        OptionalValidity myOptionalValidity = OptionalValidity.getInstance(new DERSequence(optionalValidityV));

        myCertTemplate.setValidity(myOptionalValidity);

        byte[] bytes = keys.getPublic().getEncoded();
        ByteArrayInputStream bIn = new ByteArrayInputStream(bytes);
        ASN1InputStream dIn = new ASN1InputStream(bIn);
        try {
            SubjectPublicKeyInfo keyInfo = new SubjectPublicKeyInfo((ASN1Sequence) dIn.readObject());
            myCertTemplate.setPublicKey(keyInfo);
        } finally {
            dIn.close();
        }

        myCertTemplate.setSubject(userDN);

        CertRequest myCertRequest = new CertRequest(4, myCertTemplate.build(), null);

        // POPO
        /*
         * PKMACValue myPKMACValue = new PKMACValue( new AlgorithmIdentifier(new
         * ASN1ObjectIdentifier("8.2.1.2.3.4"), new DERBitString(new byte[] { 8,
         * 1, 1, 2 })), new DERBitString(new byte[] { 12, 29, 37, 43 }));
         * 
         * POPOPrivKey myPOPOPrivKey = new POPOPrivKey(new DERBitString(new
         * byte[] { 44 }), 2); //take choice pos tag 2
         * 
         * POPOSigningKeyInput myPOPOSigningKeyInput = new POPOSigningKeyInput(
         * myPKMACValue, new SubjectPublicKeyInfo( new AlgorithmIdentifier(new
         * ASN1ObjectIdentifier("9.3.3.9.2.2"), new DERBitString(new byte[] { 2,
         * 9, 7, 3 })), new byte[] { 7, 7, 7, 4, 5, 6, 7, 7, 7 }));
         */
        ProofOfPossession myProofOfPossession = null;
        if (includePopo) {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            DEROutputStream mout = new DEROutputStream(baos);
            mout.writeObject(myCertRequest);
            mout.close();
            byte[] popoProtectionBytes = baos.toByteArray();
            String sigalg = AlgorithmTools.getSignAlgOidFromDigestAndKey(null, keys.getPrivate().getAlgorithm())
                    .getId();
            Signature sig = Signature.getInstance(sigalg);
            sig.initSign(keys.getPrivate());
            sig.update(popoProtectionBytes);

            DERBitString bs = new DERBitString(sig.sign());

            POPOSigningKey myPOPOSigningKey = new POPOSigningKey(null,
                    new AlgorithmIdentifier(new ASN1ObjectIdentifier(sigalg)), bs);
            myProofOfPossession = new ProofOfPossession(myPOPOSigningKey);
        } else {
            // raVerified POPO (meaning there is no POPO)
            myProofOfPossession = new ProofOfPossession();
        }

        // myCertReqMsg.addRegInfo(new AttributeTypeAndValue(new
        // ASN1ObjectIdentifier("1.3.6.2.2.2.2.3.1"), new
        // DERInteger(1122334455)));
        AttributeTypeAndValue av = new AttributeTypeAndValue(CRMFObjectIdentifiers.id_regCtrl_regToken,
                new DERUTF8String(""));
        AttributeTypeAndValue[] avs = { av };

        CertReqMsg myCertReqMsg = new CertReqMsg(myCertRequest, myProofOfPossession, avs);

        CertReqMessages myCertReqMessages = new CertReqMessages(myCertReqMsg);

        PKIHeaderBuilder myPKIHeader = new PKIHeaderBuilder(2, new GeneralName(userDN), new GeneralName(issuerDN));
        myPKIHeader.setMessageTime(new ASN1GeneralizedTime(new Date()));
        // senderNonce
        myPKIHeader.setSenderNonce(new DEROctetString(nonce));
        // TransactionId
        myPKIHeader.setTransactionID(new DEROctetString(transid));
        myPKIHeader.setProtectionAlg(null);

        PKIBody myPKIBody = new PKIBody(PKIBody.TYPE_KEY_UPDATE_REQ, myCertReqMessages); // Key Update Request
        PKIMessage myPKIMessage = new PKIMessage(myPKIHeader.build(), myPKIBody);

        return myPKIMessage;
    }

    @Override
    public CommandResult handleCMPResponse(byte[] response, ParameterContainer parameters) throws Exception {
        String dest = parameters.get(DESTINATION_KEY);
        if (dest == null) {
            dest = "dest";
            new File("./" + dest).mkdirs();
            log.info("Using default destination directory: ./dest/");
        }

        PKIMessage respObject = null;
        ASN1InputStream asn1InputStream = new ASN1InputStream(new ByteArrayInputStream(response));
        try {
            respObject = PKIMessage.getInstance(asn1InputStream.readObject());
        } finally {
            asn1InputStream.close();
        }
        if (respObject == null) {
            log.error("Cannot construct response object");
            return CommandResult.FUNCTIONAL_FAILURE;
        }

        PKIBody body = respObject.getBody();
        int tag = body.getType();
        if (tag == PKIBody.TYPE_KEY_UPDATE_REP) {
            CertRepMessage c = (CertRepMessage) body.getContent();
            CertResponse resp = c.getResponse()[0];
            PKIStatusInfo status = resp.getStatus();

            if (status.getStatus().intValue() == PKIStatus.GRANTED) {
                final X509Certificate cert = getCertFromResponse(resp);
                final ArrayList<Certificate> certs = new ArrayList<>();
                certs.add(cert);
                final byte[] certBytes = CertTools.getPemFromCertificateChain(certs);

                String certFileName = getDestinationCertFile(dest, parameters.get(SUBJECTDN_KEY));
                final FileOutputStream fos = new FileOutputStream(new File(certFileName));
                fos.write(certBytes);
                fos.close();
                log.info("CRMF request successful. Received certificate stored in " + certFileName);
                return CommandResult.SUCCESS;
            } else {
                final String errMsg = status.getStatusString().getStringAt(0).getString();
                log.error("Recieved CRMF response with status '" + status.getStatus().intValue()
                        + "' and error message: " + errMsg);
            }

        } else if (tag == PKIBody.TYPE_ERROR) {
            log.error("Error response was recieved");
            ErrorMsgContent c = (ErrorMsgContent) body.getContent();
            PKIStatusInfo info = c.getPKIStatusInfo();
            log.error("Error message: " + info.getStatusString().getStringAt(0));
        } else {
            log.error("Recieved response with body type(See PKIBody.java): " + tag);
        }
        return CommandResult.FUNCTIONAL_FAILURE;
    }

    private X509Certificate getCertFromResponse(final CertResponse resp) throws Exception {
        final CertifiedKeyPair kp = resp.getCertifiedKeyPair();
        final CertOrEncCert cc = kp.getCertOrEncCert();
        final CMPCertificate cmpcert = cc.getCertificate();
        return (X509Certificate) CertTools.getCertfromByteArray(cmpcert.getEncoded());
    }

    private String getDestinationCertFile(final String destDirectory, final String subjectDN) {
        return destDirectory + (StringUtils.endsWith(destDirectory, "/") ? "" : "/")
                + CertTools.getPartFromDN(subjectDN, "CN") + ".pem";
    }

    @Override
    public String getFullHelpText() {
        return "The KeyUpdate request always uses " + CmpConfiguration.AUTHMODULE_ENDENTITY_CERTIFICATE
                + " authentication module";
    }

    protected Logger getLogger() {
        return log;
    }

}