org.xipki.pki.scep.serveremulator.ScepResponder.java Source code

Java tutorial

Introduction

Here is the source code for org.xipki.pki.scep.serveremulator.ScepResponder.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.scep.serveremulator;

import java.security.PrivateKey;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;

import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.ASN1Set;
import org.bouncycastle.asn1.ASN1String;
import org.bouncycastle.asn1.cms.ContentInfo;
import org.bouncycastle.asn1.cms.IssuerAndSerialNumber;
import org.bouncycastle.asn1.pkcs.Attribute;
import org.bouncycastle.asn1.pkcs.CertificationRequest;
import org.bouncycastle.asn1.pkcs.CertificationRequestInfo;
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
import org.bouncycastle.asn1.x509.Certificate;
import org.bouncycastle.asn1.x509.CertificateList;
import org.bouncycastle.cert.X509CRLHolder;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cms.CMSAbsentContent;
import org.bouncycastle.cms.CMSAlgorithm;
import org.bouncycastle.cms.CMSException;
import org.bouncycastle.cms.CMSSignedData;
import org.bouncycastle.cms.CMSSignedDataGenerator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xipki.commons.audit.AuditEvent;
import org.xipki.commons.audit.AuditStatus;
import org.xipki.commons.common.util.ParamUtil;
import org.xipki.commons.security.util.X509Util;
import org.xipki.pki.scep.crypto.ScepHashAlgoType;
import org.xipki.pki.scep.exception.MessageDecodingException;
import org.xipki.pki.scep.message.CaCaps;
import org.xipki.pki.scep.message.DecodedPkiMessage;
import org.xipki.pki.scep.message.EnvelopedDataDecryptor;
import org.xipki.pki.scep.message.EnvelopedDataDecryptorInstance;
import org.xipki.pki.scep.message.IssuerAndSubject;
import org.xipki.pki.scep.message.NextCaMessage;
import org.xipki.pki.scep.message.PkiMessage;
import org.xipki.pki.scep.transaction.CaCapability;
import org.xipki.pki.scep.transaction.FailInfo;
import org.xipki.pki.scep.transaction.MessageType;
import org.xipki.pki.scep.transaction.Nonce;
import org.xipki.pki.scep.transaction.PkiStatus;
import org.xipki.pki.scep.transaction.TransactionId;
import org.xipki.pki.scep.util.ScepUtil;

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

public class ScepResponder {

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

    private static final long DFLT_MAX_SIGNINGTIME_BIAS = 5L * 60 * 1000; // 5 minutes

    private static final Set<ASN1ObjectIdentifier> AES_ENC_ALGS = new HashSet<ASN1ObjectIdentifier>();

    private final CaCaps caCaps;

    private final CaEmulator caEmulator;

    private final RaEmulator raEmulator;

    private final NextCaAndRa nextCaAndRa;

    private final ScepControl control;

    private long maxSigningTimeBiasInMs = DFLT_MAX_SIGNINGTIME_BIAS;

    static {
        AES_ENC_ALGS.add(CMSAlgorithm.AES128_CBC);
        AES_ENC_ALGS.add(CMSAlgorithm.AES128_CCM);
        AES_ENC_ALGS.add(CMSAlgorithm.AES128_GCM);
        AES_ENC_ALGS.add(CMSAlgorithm.AES192_CBC);
        AES_ENC_ALGS.add(CMSAlgorithm.AES192_CCM);
        AES_ENC_ALGS.add(CMSAlgorithm.AES192_GCM);
        AES_ENC_ALGS.add(CMSAlgorithm.AES256_CBC);
        AES_ENC_ALGS.add(CMSAlgorithm.AES256_CCM);
        AES_ENC_ALGS.add(CMSAlgorithm.AES256_GCM);
    }

    public ScepResponder(final CaCaps caCaps, final CaEmulator caEmulator, final RaEmulator raEmulator,
            final NextCaAndRa nextCaAndRa, final ScepControl control) throws Exception {
        this.caCaps = ParamUtil.requireNonNull("caCaps", caCaps);
        this.caEmulator = ParamUtil.requireNonNull("caEmulator", caEmulator);
        this.control = ParamUtil.requireNonNull("control", control);

        this.raEmulator = raEmulator;
        this.nextCaAndRa = nextCaAndRa;
        CaCaps caps = caCaps;
        if (nextCaAndRa == null) {
            caps.removeCapability(CaCapability.GetNextCACert);
        } else {
            caps.addCapability(CaCapability.GetNextCACert);
        }
    }

    /**
    *
    * @param ms signing time bias in milliseconds. non-positive value deactivate
    *        the check of signing time.
    */
    public void setMaxSigningTimeBias(final long ms) {
        this.maxSigningTimeBiasInMs = ms;
    }

    public ContentInfo servicePkiOperation(final CMSSignedData requestContent, final AuditEvent event)
            throws MessageDecodingException, CaException {
        ParamUtil.requireNonNull("requestContent", requestContent);
        PrivateKey recipientKey = (raEmulator != null) ? raEmulator.getRaKey() : caEmulator.getCaKey();
        Certificate recipientCert = (raEmulator != null) ? raEmulator.getRaCert() : caEmulator.getCaCert();
        X509Certificate recipientX509Obj;
        try {
            recipientX509Obj = X509Util.toX509Cert(recipientCert);
        } catch (CertificateException ex) {
            throw new MessageDecodingException(
                    "could not parse recipientCert " + recipientCert.getTBSCertificate().getSubject());
        }

        EnvelopedDataDecryptorInstance decInstance = new EnvelopedDataDecryptorInstance(recipientX509Obj,
                recipientKey);
        EnvelopedDataDecryptor recipient = new EnvelopedDataDecryptor(decInstance);

        DecodedPkiMessage req = DecodedPkiMessage.decode(requestContent, recipient, null);

        PkiMessage rep = doServicePkiOperation(req, event);
        event.addEventData(ScepAuditConstants.NAME_pkiStatus, rep.getPkiStatus());
        if (rep.getPkiStatus() == PkiStatus.FAILURE) {
            event.setStatus(AuditStatus.FAILED);
        }
        if (rep.getFailInfo() != null) {
            event.addEventData(ScepAuditConstants.NAME_failInfo, rep.getFailInfo());
        }

        String signatureAlgorithm = ScepUtil.getSignatureAlgorithm(getSigningKey(),
                ScepHashAlgoType.forNameOrOid(req.getDigestAlgorithm().getId()));

        try {
            X509Certificate jceSignerCert = X509Util.toX509Cert(getSigningCert());
            X509Certificate[] certs = control.isSendSignerCert() ? new X509Certificate[] { jceSignerCert } : null;

            return rep.encode(getSigningKey(), signatureAlgorithm, jceSignerCert, certs, req.getSignatureCert(),
                    req.getContentEncryptionAlgorithm());
        } catch (Exception ex) {
            throw new CaException(ex);
        }
    } // method servicePkiOperation

    public ContentInfo encode(final NextCaMessage nextCaMsg) throws CaException {
        ParamUtil.requireNonNull("nextCAMsg", nextCaMsg);
        try {
            X509Certificate jceSignerCert = X509Util.toX509Cert(getSigningCert());
            X509Certificate[] certs = control.isSendSignerCert() ? new X509Certificate[] { jceSignerCert } : null;
            return nextCaMsg.encode(getSigningKey(), jceSignerCert, certs);
        } catch (Exception ex) {
            throw new CaException(ex);
        }
    }

    private PkiMessage doServicePkiOperation(final DecodedPkiMessage req, final AuditEvent event)
            throws MessageDecodingException, CaException {

        TransactionId tid = req.getTransactionId();
        PkiMessage rep = new PkiMessage(tid, MessageType.CertRep, Nonce.randomNonce());
        rep.setPkiStatus(PkiStatus.SUCCESS);

        rep.setRecipientNonce(req.getSenderNonce());

        if (req.getFailureMessage() != null) {
            rep.setPkiStatus(PkiStatus.FAILURE);
            rep.setFailInfo(FailInfo.badRequest);
        }

        Boolean bo = req.isSignatureValid();
        if (bo != null && !bo.booleanValue()) {
            rep.setPkiStatus(PkiStatus.FAILURE);
            rep.setFailInfo(FailInfo.badMessageCheck);
        }

        bo = req.isDecryptionSuccessful();
        if (bo != null && !bo.booleanValue()) {
            rep.setPkiStatus(PkiStatus.FAILURE);
            rep.setFailInfo(FailInfo.badRequest);
        }

        Date signingTime = req.getSigningTime();
        if (maxSigningTimeBiasInMs > 0) {
            boolean isTimeBad = false;
            if (signingTime == null) {
                isTimeBad = true;
            } else {
                long now = System.currentTimeMillis();
                long diff = now - signingTime.getTime();
                if (diff < 0) {
                    diff = -1 * diff;
                }
                isTimeBad = diff > maxSigningTimeBiasInMs;
            }

            if (isTimeBad) {
                rep.setPkiStatus(PkiStatus.FAILURE);
                rep.setFailInfo(FailInfo.badTime);
            }
        }

        // check the digest algorithm
        String oid = req.getDigestAlgorithm().getId();
        ScepHashAlgoType hashAlgoType = ScepHashAlgoType.forNameOrOid(oid);
        if (hashAlgoType == null) {
            LOG.warn("tid={}: unknown digest algorithm {}", tid, oid);
            rep.setPkiStatus(PkiStatus.FAILURE);
            rep.setFailInfo(FailInfo.badAlg);
        } else {
            boolean supported = false;
            if (hashAlgoType == ScepHashAlgoType.SHA1) {
                if (caCaps.containsCapability(CaCapability.SHA1)) {
                    supported = true;
                }
            } else if (hashAlgoType == ScepHashAlgoType.SHA256) {
                if (caCaps.containsCapability(CaCapability.SHA256)) {
                    supported = true;
                }
            } else if (hashAlgoType == ScepHashAlgoType.SHA512) {
                if (caCaps.containsCapability(CaCapability.SHA512)) {
                    supported = true;
                }
            } else if (hashAlgoType == ScepHashAlgoType.MD5) {
                if (control.isUseInsecureAlg()) {
                    supported = true;
                }
            }

            if (!supported) {
                LOG.warn("tid={}: unsupported digest algorithm {}", tid, oid);
                rep.setPkiStatus(PkiStatus.FAILURE);
                rep.setFailInfo(FailInfo.badAlg);
            } // end if
        } // end if

        // check the content encryption algorithm
        ASN1ObjectIdentifier encOid = req.getContentEncryptionAlgorithm();
        if (CMSAlgorithm.DES_EDE3_CBC.equals(encOid)) {
            if (!caCaps.containsCapability(CaCapability.DES3)) {
                LOG.warn("tid={}: encryption with DES3 algorithm is not permitted", tid, encOid);
                rep.setPkiStatus(PkiStatus.FAILURE);
                rep.setFailInfo(FailInfo.badAlg);
            }
        } else if (AES_ENC_ALGS.contains(encOid)) {
            if (!caCaps.containsCapability(CaCapability.AES)) {
                LOG.warn("tid={}: encryption with AES algorithm {} is not permitted", tid, encOid);
                rep.setPkiStatus(PkiStatus.FAILURE);
                rep.setFailInfo(FailInfo.badAlg);
            }
        } else if (CMSAlgorithm.DES_CBC.equals(encOid)) {
            if (!control.isUseInsecureAlg()) {
                LOG.warn("tid={}: encryption with DES algorithm {} is not permitted", tid, encOid);
                rep.setPkiStatus(PkiStatus.FAILURE);
                rep.setFailInfo(FailInfo.badAlg);
            }
        } else {
            LOG.warn("tid={}: encryption with algorithm {} is not permitted", tid, encOid);
            rep.setPkiStatus(PkiStatus.FAILURE);
            rep.setFailInfo(FailInfo.badAlg);
        }

        if (rep.getPkiStatus() == PkiStatus.FAILURE) {
            return rep;
        }

        MessageType messageType = req.getMessageType();

        switch (messageType) {
        case PKCSReq:
            CertificationRequest csr = CertificationRequest.getInstance(req.getMessageData());

            String challengePwd = getChallengePassword(csr.getCertificationRequestInfo());
            if (challengePwd == null || !control.getSecret().equals(challengePwd)) {
                LOG.warn("challengePassword is not trusted");
                rep.setPkiStatus(PkiStatus.FAILURE);
                rep.setFailInfo(FailInfo.badRequest);
            }

            Certificate cert;
            try {
                cert = caEmulator.generateCert(csr);
            } catch (Exception ex) {
                throw new CaException("system failure: " + ex.getMessage(), ex);
            }

            if (cert != null && control.isPendingCert()) {
                rep.setPkiStatus(PkiStatus.PENDING);
            } else if (cert != null) {
                ContentInfo messageData = createSignedData(cert);
                rep.setMessageData(messageData);
            } else {
                rep.setPkiStatus(PkiStatus.FAILURE);
                rep.setFailInfo(FailInfo.badCertId);
            }

            break;
        case CertPoll:
            IssuerAndSubject is = IssuerAndSubject.getInstance(req.getMessageData());
            cert = caEmulator.pollCert(is.getIssuer(), is.getSubject());
            if (cert != null) {
                rep.setMessageData(createSignedData(cert));
            } else {
                rep.setPkiStatus(PkiStatus.FAILURE);
                rep.setFailInfo(FailInfo.badCertId);
            }

            break;
        case GetCert:
            IssuerAndSerialNumber isn = IssuerAndSerialNumber.getInstance(req.getMessageData());
            cert = caEmulator.getCert(isn.getName(), isn.getSerialNumber().getValue());
            if (cert != null) {
                rep.setMessageData(createSignedData(cert));
            } else {
                rep.setPkiStatus(PkiStatus.FAILURE);
                rep.setFailInfo(FailInfo.badCertId);
            }

            break;
        case RenewalReq:
            if (!caCaps.containsCapability(CaCapability.Renewal)) {
                rep.setPkiStatus(PkiStatus.FAILURE);
                rep.setFailInfo(FailInfo.badRequest);
            } else {
                csr = CertificationRequest.getInstance(req.getMessageData());
                try {
                    cert = caEmulator.generateCert(csr);
                } catch (Exception ex) {
                    throw new CaException("system failure: " + ex.getMessage(), ex);
                }
                if (cert != null) {
                    rep.setMessageData(createSignedData(cert));
                } else {
                    rep.setPkiStatus(PkiStatus.FAILURE);
                    rep.setFailInfo(FailInfo.badCertId);
                }
            }
            break;
        case UpdateReq:
            if (!caCaps.containsCapability(CaCapability.Update)) {
                rep.setPkiStatus(PkiStatus.FAILURE);
                rep.setFailInfo(FailInfo.badRequest);
            } else {
                csr = CertificationRequest.getInstance(req.getMessageData());
                try {
                    cert = caEmulator.generateCert(csr);
                } catch (Exception ex) {
                    throw new CaException("system failure: " + ex.getMessage(), ex);
                }
                if (cert != null) {
                    rep.setMessageData(createSignedData(cert));
                } else {
                    rep.setPkiStatus(PkiStatus.FAILURE);
                    rep.setFailInfo(FailInfo.badCertId);
                }
            }
            break;
        case GetCRL:
            isn = IssuerAndSerialNumber.getInstance(req.getMessageData());
            CertificateList crl;
            try {
                crl = caEmulator.getCrl(isn.getName(), isn.getSerialNumber().getValue());
            } catch (Exception ex) {
                throw new CaException("system failure: " + ex.getMessage(), ex);
            }
            if (crl != null) {
                rep.setMessageData(createSignedData(crl));
            } else {
                rep.setPkiStatus(PkiStatus.FAILURE);
                rep.setFailInfo(FailInfo.badCertId);
            }
            break;
        default:
            rep.setPkiStatus(PkiStatus.FAILURE);
            rep.setFailInfo(FailInfo.badRequest);
        } // end switch

        return rep;
    } // method doServicePkiOperation

    private ContentInfo createSignedData(final CertificateList crl) throws CaException {
        CMSSignedDataGenerator cmsSignedDataGen = new CMSSignedDataGenerator();
        cmsSignedDataGen.addCRL(new X509CRLHolder(crl));

        CMSSignedData cmsSigneddata;
        try {
            cmsSigneddata = cmsSignedDataGen.generate(new CMSAbsentContent());
        } catch (CMSException ex) {
            throw new CaException(ex.getMessage(), ex);
        }

        return cmsSigneddata.toASN1Structure();

    }

    private ContentInfo createSignedData(final Certificate cert) throws CaException {
        CMSSignedDataGenerator cmsSignedDataGen = new CMSSignedDataGenerator();

        CMSSignedData cmsSigneddata;
        try {
            cmsSignedDataGen.addCertificate(new X509CertificateHolder(cert));
            if (control.isSendCaCert()) {
                cmsSignedDataGen.addCertificate(new X509CertificateHolder(caEmulator.getCaCert()));
            }

            cmsSigneddata = cmsSignedDataGen.generate(new CMSAbsentContent());
        } catch (CMSException ex) {
            throw new CaException(ex);
        }

        return cmsSigneddata.toASN1Structure();
    }

    public PrivateKey getSigningKey() {
        return (raEmulator != null) ? raEmulator.getRaKey() : caEmulator.getCaKey();
    }

    public Certificate getSigningCert() {
        return (raEmulator != null) ? raEmulator.getRaCert() : caEmulator.getCaCert();
    }

    public CaCaps getCaCaps() {
        return caCaps;
    }

    public CaEmulator getCaEmulator() {
        return caEmulator;
    }

    public RaEmulator getRaEmulator() {
        return raEmulator;
    }

    public NextCaAndRa getNextCaAndRa() {
        return nextCaAndRa;
    }

    private static String getChallengePassword(final CertificationRequestInfo csr) {
        ASN1Set attrs = csr.getAttributes();
        for (int i = 0; i < attrs.size(); i++) {
            Attribute attr = Attribute.getInstance(attrs.getObjectAt(i));
            if (PKCSObjectIdentifiers.pkcs_9_at_challengePassword.equals(attr.getAttrType())) {
                ASN1String str = (ASN1String) attr.getAttributeValues()[0];
                return str.getString();
            }
        }
        return null;
    }

}