org.xipki.pki.ca.client.impl.CmpRequestor.java Source code

Java tutorial

Introduction

Here is the source code for org.xipki.pki.ca.client.impl.CmpRequestor.java

Source

/*
 *
 * Copyright (c) 2013 - 2016 Lijun Liao
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License version 3
 * as published by the Free Software Foundation with the addition of the
 * following permission added to Section 15 as permitted in Section 7(a):
 *
 * FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY
 * THE AUTHOR LIJUN LIAO. LIJUN LIAO DISCLAIMS THE WARRANTY OF NON INFRINGEMENT
 * OF THIRD PARTY RIGHTS.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU Affero General Public License.
 *
 * You can be released from the requirements of the license by purchasing
 * a commercial license. Buying such a license is mandatory as soon as you
 * develop commercial activities involving the XiPKI software without
 * disclosing the source code of your own applications.
 *
 * For more information, please contact Lijun Liao at this
 * address: lijun.liao@gmail.com
 */

package org.xipki.pki.ca.client.impl;

import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Random;

import org.bouncycastle.asn1.ASN1Encodable;
import org.bouncycastle.asn1.ASN1EncodableVector;
import org.bouncycastle.asn1.ASN1GeneralizedTime;
import org.bouncycastle.asn1.ASN1Integer;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.ASN1OctetString;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.DEROctetString;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.asn1.cmp.CMPObjectIdentifiers;
import org.bouncycastle.asn1.cmp.ErrorMsgContent;
import org.bouncycastle.asn1.cmp.GenMsgContent;
import org.bouncycastle.asn1.cmp.GenRepContent;
import org.bouncycastle.asn1.cmp.InfoTypeAndValue;
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.x500.X500Name;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.asn1.x509.GeneralName;
import org.bouncycastle.cert.cmp.CMPException;
import org.bouncycastle.cert.cmp.GeneralPKIMessage;
import org.bouncycastle.cert.cmp.ProtectedPKIMessage;
import org.bouncycastle.operator.ContentVerifierProvider;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.util.encoders.Hex;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xipki.commons.common.RequestResponseDebug;
import org.xipki.commons.common.RequestResponsePair;
import org.xipki.commons.common.util.CollectionUtil;
import org.xipki.commons.common.util.ParamUtil;
import org.xipki.commons.common.util.StringUtil;
import org.xipki.commons.security.ConcurrentContentSigner;
import org.xipki.commons.security.ObjectIdentifiers;
import org.xipki.commons.security.SecurityFactory;
import org.xipki.commons.security.exception.NoIdleSignerException;
import org.xipki.commons.security.util.AlgorithmUtil;
import org.xipki.commons.security.util.CmpFailureUtil;
import org.xipki.pki.ca.client.api.PkiErrorException;
import org.xipki.pki.ca.common.cmp.CmpUtf8Pairs;
import org.xipki.pki.ca.common.cmp.CmpUtil;
import org.xipki.pki.ca.common.cmp.PkiResponse;
import org.xipki.pki.ca.common.cmp.ProtectionResult;
import org.xipki.pki.ca.common.cmp.ProtectionVerificationResult;

/**
 * @author Lijun Liao
 * @since 2.0.0
 */

abstract class CmpRequestor {

    private static final Logger LOG = LoggerFactory.getLogger(CmpRequestor.class);

    protected final SecurityFactory securityFactory;

    protected boolean signRequest;

    private final Random random = new Random();

    private final ConcurrentContentSigner requestor;

    private final GeneralName sender;

    private final CmpResponder responder;

    private final GeneralName recipient;

    private final X500Name recipientName;

    private boolean sendRequestorCert;

    public CmpRequestor(final X509Certificate requestorCert, final CmpResponder responder,
            final SecurityFactory securityFactory) {
        ParamUtil.requireNonNull("requestorCert", requestorCert);
        this.responder = ParamUtil.requireNonNull("responder", responder);
        this.securityFactory = ParamUtil.requireNonNull("securityFactory", securityFactory);
        this.requestor = null;
        this.signRequest = false;

        X500Name x500Name = X500Name.getInstance(requestorCert.getSubjectX500Principal().getEncoded());
        this.sender = new GeneralName(x500Name);

        X500Name subject = X500Name.getInstance(responder.getCert().getSubjectX500Principal().getEncoded());
        this.recipient = new GeneralName(subject);
        this.recipientName = subject;
    }

    public CmpRequestor(final ConcurrentContentSigner requestor, final CmpResponder responder,
            final SecurityFactory securityFactory) {
        this(requestor, responder, securityFactory, true);
    }

    public CmpRequestor(ConcurrentContentSigner requestor, final CmpResponder responder,
            final SecurityFactory securityFactory, final boolean signRequest) {
        this.requestor = ParamUtil.requireNonNull("requestor", requestor);
        if (requestor.getCertificate() == null) {
            throw new IllegalArgumentException("requestor without certificate is not allowed");
        }
        this.responder = ParamUtil.requireNonNull("responder", responder);
        this.securityFactory = ParamUtil.requireNonNull("securityFactory", securityFactory);
        this.signRequest = signRequest;

        X500Name x500Name = X500Name.getInstance(requestor.getCertificate().getSubjectX500Principal().getEncoded());
        this.sender = new GeneralName(x500Name);

        X500Name subject = X500Name.getInstance(responder.getCert().getSubjectX500Principal().getEncoded());
        this.recipient = new GeneralName(subject);
        this.recipientName = subject;
    }

    protected abstract byte[] send(final byte[] request) throws IOException;

    protected PKIMessage sign(final PKIMessage request) throws CmpRequestorException {
        ParamUtil.requireNonNull("request", request);
        if (requestor == null) {
            throw new CmpRequestorException("no request signer is configured");
        }

        try {
            return CmpUtil.addProtection(request, requestor, sender, sendRequestorCert);
        } catch (CMPException | NoIdleSignerException ex) {
            throw new CmpRequestorException("could not sign the request", ex);
        }
    }

    protected PkiResponse signAndSend(final PKIMessage request, final RequestResponseDebug debug)
            throws CmpRequestorException {
        ParamUtil.requireNonNull("request", request);

        PKIMessage tmpRequest = (signRequest) ? sign(request) : request;

        byte[] encodedRequest;
        try {
            encodedRequest = tmpRequest.getEncoded();
        } catch (IOException ex) {
            LOG.error("could not encode the PKI request {}", tmpRequest);
            throw new CmpRequestorException(ex.getMessage(), ex);
        }

        RequestResponsePair reqResp = null;
        if (debug != null) {
            reqResp = new RequestResponsePair();
            debug.add(reqResp);
            reqResp.setRequest(encodedRequest);
        }

        byte[] encodedResponse;
        try {
            encodedResponse = send(encodedRequest);
        } catch (IOException ex) {
            LOG.error("could not send the PKI request {} to server", tmpRequest);
            throw new CmpRequestorException("TRANSPORT_ERROR", ex);
        }

        if (reqResp != null) {
            reqResp.setResponse(encodedResponse);
        }

        GeneralPKIMessage response;
        try {
            response = new GeneralPKIMessage(encodedResponse);
        } catch (IOException ex) {
            LOG.error("could not decode the received PKI message: {}", Hex.toHexString(encodedResponse));
            throw new CmpRequestorException(ex.getMessage(), ex);
        }

        PKIHeader respHeader = response.getHeader();
        ASN1OctetString tid = respHeader.getTransactionID();
        GeneralName rec = respHeader.getRecipient();
        if (!sender.equals(rec)) {
            LOG.warn("tid={}: unknown CMP requestor '{}'", tid, rec);
        }

        PkiResponse ret = new PkiResponse(response);
        if (response.hasProtection()) {
            try {
                ProtectionVerificationResult verifyProtection = verifyProtection(Hex.toHexString(tid.getOctets()),
                        response);
                ret.setProtectionVerificationResult(verifyProtection);
            } catch (InvalidKeyException | OperatorCreationException | CMPException ex) {
                throw new CmpRequestorException(ex.getMessage(), ex);
            }
        } else if (signRequest) {
            PKIBody respBody = response.getBody();
            int bodyType = respBody.getType();
            if (bodyType != PKIBody.TYPE_ERROR) {
                throw new CmpRequestorException("response is not signed");
            }
        }

        return ret;
    } // method signAndSend

    protected ASN1Encodable extractGeneralRepContent(final PkiResponse response, final String expectedType)
            throws CmpRequestorException, PkiErrorException {
        ParamUtil.requireNonNull("response", response);
        ParamUtil.requireNonNull("expectedType", expectedType);
        return extractGeneralRepContent(response, expectedType, true);
    }

    private ASN1Encodable extractGeneralRepContent(final PkiResponse response, final String expectedType,
            final boolean requireProtectionCheck) throws CmpRequestorException, PkiErrorException {
        ParamUtil.requireNonNull("response", response);
        ParamUtil.requireNonNull("expectedType", expectedType);
        if (requireProtectionCheck) {
            checkProtection(response);
        }

        PKIBody respBody = response.getPkiMessage().getBody();
        int bodyType = respBody.getType();

        if (PKIBody.TYPE_ERROR == bodyType) {
            ErrorMsgContent content = ErrorMsgContent.getInstance(respBody.getContent());
            throw new CmpRequestorException(CmpFailureUtil.formatPkiStatusInfo(content.getPKIStatusInfo()));
        } else if (PKIBody.TYPE_GEN_REP != bodyType) {
            throw new CmpRequestorException(String.format("unknown PKI body type %s instead the expected [%s, %s]",
                    bodyType, PKIBody.TYPE_GEN_REP, PKIBody.TYPE_ERROR));
        }

        GenRepContent genRep = GenRepContent.getInstance(respBody.getContent());

        InfoTypeAndValue[] itvs = genRep.toInfoTypeAndValueArray();
        InfoTypeAndValue itv = null;
        if (itvs != null && itvs.length > 0) {
            for (InfoTypeAndValue entry : itvs) {
                if (expectedType.equals(entry.getInfoType().getId())) {
                    itv = entry;
                    break;
                }
            }
        }
        if (itv == null) {
            throw new CmpRequestorException("the response does not contain InfoTypeAndValue " + expectedType);
        }

        return itv.getInfoValue();
    } // method extractGeneralRepContent

    protected ASN1Encodable extractXipkiActionRepContent(final PkiResponse response, final int action)
            throws CmpRequestorException, PkiErrorException {
        ParamUtil.requireNonNull("response", response);
        ASN1Encodable itvValue = extractGeneralRepContent(response,
                ObjectIdentifiers.id_xipki_cmp_cmpGenmsg.getId(), true);
        return extractXipkiActionContent(itvValue, action);
    }

    protected ASN1Encodable extractXipkiActionContent(final ASN1Encodable itvValue, final int action)
            throws CmpRequestorException {
        ParamUtil.requireNonNull("itvValue", itvValue);
        ASN1Sequence seq;
        try {
            seq = ASN1Sequence.getInstance(itvValue);
        } catch (IllegalArgumentException ex) {
            throw new CmpRequestorException("invalid syntax of the response");
        }
        int size = seq.size();
        if (size != 1 && size != 2) {
            throw new CmpRequestorException("invalid syntax of the response");
        }

        int tmpAction;
        try {
            tmpAction = ASN1Integer.getInstance(seq.getObjectAt(0)).getPositiveValue().intValue();
        } catch (IllegalArgumentException ex) {
            throw new CmpRequestorException("invalid syntax of the response");
        }

        if (action != tmpAction) {
            throw new CmpRequestorException(
                    "received XiPKI action '" + tmpAction + "' instead the expected '" + action + "'");
        }

        return (size == 1) ? null : seq.getObjectAt(1);
    } // method extractXipkiActionContent

    protected PKIHeader buildPkiHeader(final ASN1OctetString tid) {
        return buildPkiHeader(false, tid, (CmpUtf8Pairs) null, (InfoTypeAndValue[]) null);
    }

    protected PKIHeader buildPkiHeader(final ASN1OctetString tid, final String username) {
        return buildPkiHeader(false, tid, username);
    }

    protected PKIHeader buildPkiHeader(final boolean addImplictConfirm, final ASN1OctetString tid,
            final String username) {
        CmpUtf8Pairs utf8Pairs = null;
        if (StringUtil.isNotBlank(username)) {
            utf8Pairs = new CmpUtf8Pairs(CmpUtf8Pairs.KEY_USER, username);
        }
        return buildPkiHeader(addImplictConfirm, tid, utf8Pairs, (InfoTypeAndValue[]) null);
    }

    protected PKIHeader buildPkiHeader(final boolean addImplictConfirm, final ASN1OctetString tid,
            final CmpUtf8Pairs utf8Pairs, final InfoTypeAndValue... additionalGeneralInfos) {
        if (additionalGeneralInfos != null) {
            for (InfoTypeAndValue itv : additionalGeneralInfos) {
                ASN1ObjectIdentifier type = itv.getInfoType();
                if (CMPObjectIdentifiers.it_implicitConfirm.equals(type)) {
                    throw new IllegalArgumentException(
                            "additionGeneralInfos contains not-permitted ITV implicitConfirm");
                }

                if (CMPObjectIdentifiers.regInfo_utf8Pairs.equals(type)) {
                    throw new IllegalArgumentException("additionGeneralInfos contains not-permitted ITV utf8Pairs");
                }
            }
        }

        PKIHeaderBuilder hdrBuilder = new PKIHeaderBuilder(PKIHeader.CMP_2000, sender, recipient);
        hdrBuilder.setMessageTime(new ASN1GeneralizedTime(new Date()));

        ASN1OctetString tmpTid = (tid == null) ? new DEROctetString(randomTransactionId()) : tid;
        hdrBuilder.setTransactionID(tmpTid);

        List<InfoTypeAndValue> itvs = new ArrayList<>(2);
        if (addImplictConfirm) {
            itvs.add(CmpUtil.getImplictConfirmGeneralInfo());
        }

        if (utf8Pairs != null) {
            itvs.add(CmpUtil.buildInfoTypeAndValue(utf8Pairs));
        }

        if (additionalGeneralInfos != null) {
            for (InfoTypeAndValue itv : additionalGeneralInfos) {
                if (itv != null) {
                    itvs.add(itv);
                }
            }
        }

        if (CollectionUtil.isNonEmpty(itvs)) {
            hdrBuilder.setGeneralInfo(itvs.toArray(new InfoTypeAndValue[0]));
        }

        return hdrBuilder.build();
    } // method buildPkiHeader

    protected PkiErrorException buildErrorResult(final ErrorMsgContent bodyContent) {
        ParamUtil.requireNonNull("bodyContent", bodyContent);

        org.xipki.pki.ca.common.cmp.PkiStatusInfo statusInfo = new org.xipki.pki.ca.common.cmp.PkiStatusInfo(
                bodyContent.getPKIStatusInfo());
        return new PkiErrorException(statusInfo.getStatus(), statusInfo.getPkiFailureInfo(),
                statusInfo.getStatusMessage());
    }

    private byte[] randomTransactionId() {
        byte[] tid = new byte[20];
        random.nextBytes(tid);
        return tid;
    }

    private ProtectionVerificationResult verifyProtection(final String tid, final GeneralPKIMessage pkiMessage)
            throws CMPException, InvalidKeyException, OperatorCreationException {
        ProtectedPKIMessage protectedMsg = new ProtectedPKIMessage(pkiMessage);

        if (protectedMsg.hasPasswordBasedMacProtection()) {
            LOG.warn("NOT_SIGNAUTRE_BASED: " + pkiMessage.getHeader().getProtectionAlg().getAlgorithm().getId());
            return new ProtectionVerificationResult(null, ProtectionResult.NOT_SIGNATURE_BASED);
        }

        PKIHeader header = protectedMsg.getHeader();

        if (recipientName != null) {
            boolean authorizedResponder = true;
            if (header.getSender().getTagNo() != GeneralName.directoryName) {
                authorizedResponder = false;
            } else {
                X500Name msgSender = X500Name.getInstance(header.getSender().getName());
                authorizedResponder = recipientName.equals(msgSender);
            }

            if (!authorizedResponder) {
                LOG.warn("tid={}: not authorized responder '{}'", tid, header.getSender());
                return new ProtectionVerificationResult(null, ProtectionResult.SENDER_NOT_AUTHORIZED);
            }
        }

        AlgorithmIdentifier protectionAlgo = protectedMsg.getHeader().getProtectionAlg();
        if (!responder.getSigAlgoValidator().isAlgorithmPermitted(protectionAlgo)) {
            String algoName;
            try {
                algoName = AlgorithmUtil.getSignatureAlgoName(protectionAlgo);
            } catch (NoSuchAlgorithmException ex) {
                algoName = protectionAlgo.getAlgorithm().getId();
            }
            LOG.warn("tid={}: response protected by untrusted protection algorithm '{}'", tid, algoName);
            return new ProtectionVerificationResult(null, ProtectionResult.INVALID);
        }

        X509Certificate cert = responder.getCert();
        ContentVerifierProvider verifierProvider = securityFactory.getContentVerifierProvider(cert);
        if (verifierProvider == null) {
            LOG.warn("tid={}: not authorized responder '{}'", tid, header.getSender());
            return new ProtectionVerificationResult(cert, ProtectionResult.SENDER_NOT_AUTHORIZED);
        }

        boolean signatureValid = protectedMsg.verify(verifierProvider);
        ProtectionResult protRes = signatureValid ? ProtectionResult.VALID : ProtectionResult.INVALID;
        return new ProtectionVerificationResult(cert, protRes);
    } // method verifyProtection

    protected PKIMessage buildMessageWithXipkAction(final int action, final ASN1Encodable value)
            throws CmpRequestorException {
        PKIHeader header = buildPkiHeader(null);

        ASN1EncodableVector vec = new ASN1EncodableVector();
        vec.add(new ASN1Integer(action));
        if (value != null) {
            vec.add(value);
        }
        InfoTypeAndValue itv = new InfoTypeAndValue(ObjectIdentifiers.id_xipki_cmp_cmpGenmsg, new DERSequence(vec));
        GenMsgContent genMsgContent = new GenMsgContent(itv);
        PKIBody body = new PKIBody(PKIBody.TYPE_GEN_MSG, genMsgContent);

        PKIMessage pkiMessage = new PKIMessage(header, body);
        return pkiMessage;
    }

    protected PKIMessage buildMessageWithGeneralMsgContent(final ASN1ObjectIdentifier type,
            final ASN1Encodable value) throws CmpRequestorException {
        ParamUtil.requireNonNull("type", type);

        PKIHeader header = buildPkiHeader(null);
        InfoTypeAndValue itv = (value != null) ? new InfoTypeAndValue(type, value) : new InfoTypeAndValue(type);
        GenMsgContent genMsgContent = new GenMsgContent(itv);
        PKIBody body = new PKIBody(PKIBody.TYPE_GEN_MSG, genMsgContent);

        PKIMessage pkiMessage = new PKIMessage(header, body);
        return pkiMessage;
    }

    protected void checkProtection(final PkiResponse response) throws PkiErrorException {
        ParamUtil.requireNonNull("response", response);

        if (!response.hasProtection()) {
            return;
        }

        ProtectionVerificationResult protectionVerificationResult = response.getProtectionVerificationResult();

        if (protectionVerificationResult == null
                || protectionVerificationResult.getProtectionResult() != ProtectionResult.VALID) {
            throw new PkiErrorException(ClientErrorCode.PKISTATUS_RESPONSE_ERROR, PKIFailureInfo.badMessageCheck,
                    "message check of the response failed");
        }
    }

    public boolean isSendRequestorCert() {
        return sendRequestorCert;
    }

    public void setSendRequestorCert(final boolean sendRequestorCert) {
        this.sendRequestorCert = sendRequestorCert;
    }

}