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

Java tutorial

Introduction

Here is the source code for org.ejbca.ui.cmpclient.commands.RevocationRequestCommand.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.math.BigInteger;
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.ASN1Integer;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.DEROctetString;
import org.bouncycastle.asn1.DERSequence;
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.PKIStatusInfo;
import org.bouncycastle.asn1.cmp.RevDetails;
import org.bouncycastle.asn1.cmp.RevRepContent;
import org.bouncycastle.asn1.cmp.RevReqContent;
import org.bouncycastle.asn1.crmf.CertTemplateBuilder;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x509.CRLReason;
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.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 RevocationRequestCommand extends CmpCommandBase {

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

    public static final String REVOCATION_REASON_UNSPECIFIED = "unspecified";
    public static final String REVOCATION_REASON_KEYCOMPROMISE = "keyCompromise";
    public static final String REVOCATION_REASON_CACOMPROMISE = "caCompromise";
    public static final String REVOCATION_REASON_AFFILIATIONCHANGED = "affiliationChanged";
    public static final String REVOCATION_REASON_SUPERSEDED = "superseded";
    public static final String REVOCATION_REASON_CESSATIONOFOPERATION = "cessationOfOperation";
    public static final String REVOCATION_REASON_CERTIFICATEHOLD = "certificateHold";

    private static final String COMMAND = "revoke";

    private static final String CMP_ALIAS_KEY = "--alias";
    private static final String ISSUERDN_KEY = "--issuer";
    private static final String SERNO_KEY = "--serno";
    private static final String REVOCATION_REASON_KEY = "--reason";
    private static final String AUTHENTICATION_MODULE_KEY = "--authmodule";
    private static final String AUTHENTICATION_PARAM_KEY = "--authparam";
    private static final String KEYSTORE_KEY = "--keystore";
    private static final String KEYSTOREPWD_KEY = "--keystorepwd";
    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(ISSUERDN_KEY, "IssuerDN", MandatoryMode.MANDATORY, StandaloneMode.ALLOW,
                ParameterMode.ARGUMENT, "The IssuerDN of the certificate to be removed"));
        registerParameter(new Parameter(SERNO_KEY, "Certificate Serialnumber", MandatoryMode.MANDATORY,
                StandaloneMode.ALLOW, ParameterMode.ARGUMENT,
                "The serialnumber of the certificate to be removed in Hexadecimal format."));
        registerParameter(new Parameter(REVOCATION_REASON_KEY, "Revocation Reason", MandatoryMode.OPTIONAL,
                StandaloneMode.ALLOW, ParameterMode.ARGUMENT, "The revocation reason. Default value: "
                        + REVOCATION_REASON_UNSPECIFIED + ". Possible values: " + getPossibleRevocationReasons()));
        registerParameter(new Parameter(AUTHENTICATION_MODULE_KEY, "Authentication Module", MandatoryMode.OPTIONAL,
                StandaloneMode.ALLOW, ParameterMode.ARGUMENT,
                "The authentication module used when creating the request. Default value: "
                        + CmpConfiguration.AUTHMODULE_HMAC + ". " + "Possible values: "
                        + CmpConfiguration.AUTHMODULE_HMAC + " or "
                        + CmpConfiguration.AUTHMODULE_ENDENTITY_CERTIFICATE + "\n" + "\nWhen using "
                        + CmpConfiguration.AUTHMODULE_HMAC
                        + " authentication module: In RA mode, the value of the authentication parameter "
                        + "should be the HMAC shared secret. In Client mode, it should be the end entity password (stored in clear text in EJBCA database)\n"
                        + "\nWhen using " + CmpConfiguration.AUTHMODULE_ENDENTITY_CERTIFICATE
                        + " authentication module, the value of the authentication "
                        + "parameter should be the friendlyname in the keystore for the certificate that will be attached in the extraCerts field"));
        registerParameter(new Parameter(AUTHENTICATION_PARAM_KEY, "Authentication Parameter",
                MandatoryMode.OPTIONAL, StandaloneMode.ALLOW, ParameterMode.ARGUMENT,
                "The authentication parameter is the parameter for the authentication module. Default value: foo123\n"
                        + "\nWhen using " + CmpConfiguration.AUTHMODULE_HMAC
                        + " authentication module: In RA mode, this value should be the HMAC shared secret. "
                        + "In Client mode, it should be the end entity password (stored in clear text in EJBCA database)\n"
                        + "\nWhen using " + CmpConfiguration.AUTHMODULE_ENDENTITY_CERTIFICATE
                        + " authentication module, this value should be the friendlyname in the keystore for "
                        + "the certificate that will be attached in the extraCerts field"));
        registerParameter(new Parameter(KEYSTORE_KEY, "Path to the keystore", MandatoryMode.OPTIONAL,
                StandaloneMode.ALLOW, ParameterMode.ARGUMENT,
                "The path to the keystore containing the certificate and private key used to sign the request. Mandatory when "
                        + CmpConfiguration.AUTHMODULE_ENDENTITY_CERTIFICATE + " authentication Module is used"));
        registerParameter(new Parameter(KEYSTOREPWD_KEY, "Keystore password", MandatoryMode.OPTIONAL,
                StandaloneMode.ALLOW, ParameterMode.ARGUMENT,
                "The password to the keystore containing the private key used to sign the request. Mandatory when "
                        + CmpConfiguration.AUTHMODULE_ENDENTITY_CERTIFICATE + " authentication Module is used"));
        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 CMP revocation request and displays the result";
    }

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

            String authmodule = parameters.get(AUTHENTICATION_MODULE_KEY);
            String authparam = parameters.get(AUTHENTICATION_PARAM_KEY);
            if (authmodule == null) {
                authmodule = CmpConfiguration.AUTHMODULE_HMAC;
                log.info("Using default authentication module: " + CmpConfiguration.AUTHMODULE_HMAC);
            }
            if (authparam == null) {
                authparam = "foo123";
                log.info("Using default value for authentication parameter: " + authparam);
            }
            final PKIMessage protectedPKIMessage = CmpClientMessageHelper.getInstance().createProtectedMessage(
                    pkimessage, authmodule, authparam, 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("CN=foo");
        final X500Name issuerDN = new X500Name(parameters.get(ISSUERDN_KEY));
        BigInteger serno = new BigInteger(parameters.get(SERNO_KEY), 16);

        if (verbose) {
            log.info("Creating revocation request with: SubjectDN=" + userDN.toString());
            log.info("Creating revocation request with: IssuerDN=" + issuerDN.toString());
            log.info("Creating revocation request with: CertSerno=" + serno.toString(16));
        }

        byte[] nonce = CmpClientMessageHelper.getInstance().createSenderNonce();
        byte[] transid = CmpClientMessageHelper.getInstance().createSenderNonce();

        CertTemplateBuilder myCertTemplate = new CertTemplateBuilder();
        myCertTemplate.setIssuer(issuerDN);
        myCertTemplate.setSubject(userDN);
        myCertTemplate.setSerialNumber(new ASN1Integer(serno));

        ExtensionsGenerator extgen = new ExtensionsGenerator();
        extgen.addExtension(Extension.reasonCode, false, getCRLReason(parameters.get(REVOCATION_REASON_KEY)));

        Extensions exts = extgen.generate();

        ASN1EncodableVector v = new ASN1EncodableVector();
        v.add(myCertTemplate.build());
        v.add(exts);
        ASN1Sequence seq = new DERSequence(v);

        RevDetails myRevDetails = RevDetails.getInstance(seq);

        RevReqContent myRevReqContent = new RevReqContent(myRevDetails);

        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);
        myPKIHeader.setSenderKID(new byte[0]);

        PKIBody myPKIBody = new PKIBody(PKIBody.TYPE_REVOCATION_REQ, myRevReqContent); // revocation request
        PKIMessage myPKIMessage = new PKIMessage(myPKIHeader.build(), myPKIBody);
        return myPKIMessage;
    }

    private CRLReason getCRLReason(String revreason) {
        if (revreason == null)
            return CRLReason.lookup(CRLReason.unspecified);
        if (StringUtils.equalsIgnoreCase(revreason, REVOCATION_REASON_UNSPECIFIED))
            return CRLReason.lookup(CRLReason.unspecified);
        if (StringUtils.equalsIgnoreCase(revreason, REVOCATION_REASON_KEYCOMPROMISE))
            return CRLReason.lookup(CRLReason.keyCompromise);
        if (StringUtils.equalsIgnoreCase(revreason, REVOCATION_REASON_CACOMPROMISE))
            return CRLReason.lookup(CRLReason.cACompromise);
        if (StringUtils.equalsIgnoreCase(revreason, REVOCATION_REASON_AFFILIATIONCHANGED))
            return CRLReason.lookup(CRLReason.affiliationChanged);
        if (StringUtils.equalsIgnoreCase(revreason, REVOCATION_REASON_SUPERSEDED))
            return CRLReason.lookup(CRLReason.superseded);
        if (StringUtils.equalsIgnoreCase(revreason, REVOCATION_REASON_CESSATIONOFOPERATION))
            return CRLReason.lookup(CRLReason.cessationOfOperation);
        if (StringUtils.equalsIgnoreCase(revreason, REVOCATION_REASON_CERTIFICATEHOLD))
            return CRLReason.lookup(CRLReason.certificateHold);
        return CRLReason.lookup(CRLReason.unspecified);
    }

    @Override
    public CommandResult handleCMPResponse(byte[] response, ParameterContainer parameters) throws Exception {
        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_REVOCATION_REP) {
            log.info("Revocation response was recieved");
            RevRepContent n = (RevRepContent) body.getContent();
            PKIStatusInfo info = n.getStatus()[0];
            if (info.getStatus().intValue() == 0) {
                log.info("Revocation request have succeeded");
                return CommandResult.SUCCESS;
            } else {
                log.error("Revocation request failed with status (See PKIStatusInfo.java): "
                        + info.getStatus().intValue());
            }
        } 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).getString());
        } else {
            log.error("Recieved response with body type(See PKIBody.java): " + tag);
        }
        return CommandResult.FUNCTIONAL_FAILURE;
    }

    @Override
    public String getFullHelpText() {
        return "'CN=foo' is used as SubjectDN in the revocation request because the SubjectDN is irrelevant on the server side";
    }

    public static String getPossibleRevocationReasons() {
        StringBuilder ret = new StringBuilder("");
        ret.append(REVOCATION_REASON_UNSPECIFIED).append(", ");
        ret.append(REVOCATION_REASON_KEYCOMPROMISE).append(", ");
        ret.append(REVOCATION_REASON_CACOMPROMISE).append(", ");
        ret.append(REVOCATION_REASON_AFFILIATIONCHANGED).append(", ");
        ret.append(REVOCATION_REASON_SUPERSEDED).append(", ");
        ret.append(REVOCATION_REASON_CESSATIONOFOPERATION).append(", ");
        ret.append(REVOCATION_REASON_CERTIFICATEHOLD);
        return ret.toString();
    }

    protected Logger getLogger() {
        return log;
    }

}