org.xipki.pki.scep.util.ScepUtil.java Source code

Java tutorial

Introduction

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

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.CRLException;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509CRL;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.bouncycastle.asn1.ASN1Encodable;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.ASN1OctetString;
import org.bouncycastle.asn1.ASN1Set;
import org.bouncycastle.asn1.DERNull;
import org.bouncycastle.asn1.DERPrintableString;
import org.bouncycastle.asn1.cms.Attribute;
import org.bouncycastle.asn1.cms.AttributeTable;
import org.bouncycastle.asn1.cms.SignedData;
import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
import org.bouncycastle.asn1.pkcs.CertificationRequest;
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
import org.bouncycastle.asn1.pkcs.RSAPublicKey;
import org.bouncycastle.asn1.pkcs.RSASSAPSSparams;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.asn1.x509.AuthorityKeyIdentifier;
import org.bouncycastle.asn1.x509.Certificate;
import org.bouncycastle.asn1.x509.CertificateList;
import org.bouncycastle.asn1.x509.Extension;
import org.bouncycastle.asn1.x509.Extensions;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.asn1.x509.X509ObjectIdentifiers;
import org.bouncycastle.cert.CertIOException;
import org.bouncycastle.cert.X509v3CertificateBuilder;
import org.bouncycastle.cert.jcajce.JcaCertStore;
import org.bouncycastle.cms.CMSException;
import org.bouncycastle.cms.CMSSignedDataGenerator;
import org.bouncycastle.jce.X509KeyUsage;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import org.bouncycastle.pkcs.PKCS10CertificationRequest;
import org.bouncycastle.pkcs.PKCS10CertificationRequestBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xipki.commons.common.util.ParamUtil;
import org.xipki.pki.scep.crypto.KeyUsage;
import org.xipki.pki.scep.crypto.ScepHashAlgoType;

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

public class ScepUtil {
    private static final Logger LOG = LoggerFactory.getLogger(ScepUtil.class);

    private static final long MIN_IN_MS = 60L * 1000;
    private static final long DAY_IN_MS = 24L * 60 * MIN_IN_MS;

    private static CertificateFactory certFact;
    private static Object certFactLock = new Object();

    private ScepUtil() {
    }

    public static SubjectPublicKeyInfo createSubjectPublicKeyInfo(final PublicKey publicKey) throws IOException {
        ParamUtil.requireNonNull("publicKey", publicKey);
        if (publicKey instanceof java.security.interfaces.RSAPublicKey) {
            java.security.interfaces.RSAPublicKey rsaPubKey = (java.security.interfaces.RSAPublicKey) publicKey;
            return new SubjectPublicKeyInfo(
                    new AlgorithmIdentifier(PKCSObjectIdentifiers.rsaEncryption, DERNull.INSTANCE),
                    new RSAPublicKey(rsaPubKey.getModulus(), rsaPubKey.getPublicExponent()));
        } else {
            throw new IllegalArgumentException("unsupported public key " + publicKey);
        }
    }

    public static PKCS10CertificationRequest generateRequest(final PrivateKey privatekey,
            final SubjectPublicKeyInfo subjectPublicKeyInfo, final X500Name subjectDn,
            final Map<ASN1ObjectIdentifier, ASN1Encodable> attributes) throws OperatorCreationException {
        ParamUtil.requireNonNull("privatekey", privatekey);
        ParamUtil.requireNonNull("subjectPublicKeyInfo", subjectPublicKeyInfo);
        ParamUtil.requireNonNull("subjectDn", subjectDn);

        PKCS10CertificationRequestBuilder csrBuilder = new PKCS10CertificationRequestBuilder(subjectDn,
                subjectPublicKeyInfo);

        if (attributes != null) {
            for (ASN1ObjectIdentifier attrType : attributes.keySet()) {
                csrBuilder.addAttribute(attrType, attributes.get(attrType));
            }
        }

        ContentSigner contentSigner = new JcaContentSignerBuilder(
                getSignatureAlgorithm(privatekey, ScepHashAlgoType.SHA1)).build(privatekey);
        return csrBuilder.build(contentSigner);
    }

    public static PKCS10CertificationRequest generateRequest(final PrivateKey privatekey,
            final SubjectPublicKeyInfo subjectPublicKeyInfo, final X500Name subjectDn,
            final String challengePassword, final List<Extension> extensions) throws OperatorCreationException {
        ParamUtil.requireNonNull("privatekey", privatekey);
        ParamUtil.requireNonNull("subjectPublicKeyInfo", subjectPublicKeyInfo);
        ParamUtil.requireNonNull("subjectDn", subjectDn);

        Map<ASN1ObjectIdentifier, ASN1Encodable> attributes = new HashMap<ASN1ObjectIdentifier, ASN1Encodable>();

        if (challengePassword != null && !challengePassword.isEmpty()) {
            DERPrintableString asn1Pwd = new DERPrintableString(challengePassword);
            attributes.put(PKCSObjectIdentifiers.pkcs_9_at_challengePassword, asn1Pwd);
        }

        if (extensions != null && !extensions.isEmpty()) {
            Extensions asn1Extensions = new Extensions(extensions.toArray(new Extension[0]));
            attributes.put(PKCSObjectIdentifiers.pkcs_9_at_extensionRequest, asn1Extensions);
        }

        return generateRequest(privatekey, subjectPublicKeyInfo, subjectDn, attributes);
    }

    public static X509Certificate generateSelfsignedCert(final CertificationRequest csr,
            final PrivateKey identityKey) throws CertificateException {
        ParamUtil.requireNonNull("csr", csr);
        return generateSelfsignedCert(csr.getCertificationRequestInfo().getSubject(),
                csr.getCertificationRequestInfo().getSubjectPublicKeyInfo(), identityKey);
    }

    public static X509Certificate generateSelfsignedCert(final X500Name subjectDn, final PublicKey pubKey,
            final PrivateKey identityKey) throws CertificateException {
        SubjectPublicKeyInfo pubKeyInfo;
        try {
            pubKeyInfo = createSubjectPublicKeyInfo(pubKey);
        } catch (IOException ex) {
            throw new CertificateException(ex.getMessage(), ex);
        }
        return generateSelfsignedCert(subjectDn, pubKeyInfo, identityKey);
    }

    public static X509Certificate generateSelfsignedCert(final X500Name subjectDn,
            final SubjectPublicKeyInfo pubKeyInfo, final PrivateKey identityKey) throws CertificateException {
        ParamUtil.requireNonNull("subjectDn", subjectDn);
        ParamUtil.requireNonNull("pubKeyInfo", pubKeyInfo);
        ParamUtil.requireNonNull("identityKey", identityKey);

        Date notBefore = new Date(System.currentTimeMillis() - 5 * MIN_IN_MS);
        Date notAfter = new Date(notBefore.getTime() + 30 * DAY_IN_MS);

        X509v3CertificateBuilder certGenerator = new X509v3CertificateBuilder(subjectDn, BigInteger.ONE, notBefore,
                notAfter, subjectDn, pubKeyInfo);

        X509KeyUsage ku = new X509KeyUsage(X509KeyUsage.digitalSignature | X509KeyUsage.dataEncipherment
                | X509KeyUsage.keyAgreement | X509KeyUsage.keyEncipherment);
        try {
            certGenerator.addExtension(Extension.keyUsage, true, ku);
        } catch (CertIOException ex) {
            throw new CertificateException("could not generate self-signed certificate: " + ex.getMessage(), ex);
        }

        String sigAlgorithm = ScepUtil.getSignatureAlgorithm(identityKey, ScepHashAlgoType.SHA1);
        ContentSigner contentSigner;
        try {
            contentSigner = new JcaContentSignerBuilder(sigAlgorithm).build(identityKey);
        } catch (OperatorCreationException ex) {
            throw new CertificateException("error while creating signer", ex);
        }

        Certificate asn1Cert = certGenerator.build(contentSigner).toASN1Structure();
        return toX509Cert(asn1Cert);
    } // method generateSelfsignedCert

    /**
     * The first one is a non-CA certificate if there exists one non-CA certificate.
     */
    public static List<X509Certificate> getCertsFromSignedData(final SignedData signedData)
            throws CertificateException {
        ParamUtil.requireNonNull("signedData", signedData);
        ASN1Set set = signedData.getCertificates();
        if (set == null) {
            return Collections.emptyList();
        }

        final int n = set.size();
        if (n == 0) {
            return Collections.emptyList();
        }

        List<X509Certificate> certs = new LinkedList<X509Certificate>();

        X509Certificate eeCert = null;
        for (int i = 0; i < n; i++) {
            X509Certificate cert;
            try {
                cert = toX509Cert(Certificate.getInstance(set.getObjectAt(i)));
            } catch (IllegalArgumentException ex) {
                throw new CertificateException(ex);
            }

            if (eeCert == null && cert.getBasicConstraints() == -1) {
                eeCert = cert;
            } else {
                certs.add(cert);
            }
        }

        if (eeCert != null) {
            certs.add(0, eeCert);
        }

        return certs;
    } // method getCertsFromSignedData

    public static X509CRL getCrlFromPkiMessage(final SignedData signedData) throws CRLException {
        ParamUtil.requireNonNull("signedData", signedData);
        ASN1Set set = signedData.getCRLs();
        if (set == null || set.size() == 0) {
            return null;
        }

        try {
            CertificateList cl = CertificateList.getInstance(set.getObjectAt(0));
            return ScepUtil.toX509Crl(cl);
        } catch (IllegalArgumentException | CertificateException | CRLException ex) {
            throw new CRLException(ex);
        }
    }

    public static String getSignatureAlgorithm(final PrivateKey key, final ScepHashAlgoType hashAlgo) {
        ParamUtil.requireNonNull("key", key);
        ParamUtil.requireNonNull("hashAlgo", hashAlgo);
        String algorithm = key.getAlgorithm();
        if ("RSA".equalsIgnoreCase(algorithm)) {
            return hashAlgo.getName() + "withRSA";
        } else {
            throw new UnsupportedOperationException("getSignatureAlgorithm() for non-RSA is not supported yet.");
        }
    }

    public static X509Certificate toX509Cert(final org.bouncycastle.asn1.x509.Certificate asn1Cert)
            throws CertificateException {
        byte[] encodedCert;
        try {
            encodedCert = asn1Cert.getEncoded();
        } catch (IOException ex) {
            throw new CertificateEncodingException("could not get encoded certificate", ex);
        }
        return parseCert(encodedCert);
    }

    public static X509CRL toX509Crl(final CertificateList asn1CertList) throws CertificateException, CRLException {
        byte[] encodedCrl;
        try {
            encodedCrl = asn1CertList.getEncoded();
        } catch (IOException ex) {
            throw new CRLException("could not get encoded CRL", ex);
        }
        return parseCrl(encodedCrl);
    }

    public static X509CRL parseCrl(final byte[] encodedCrl) throws CertificateException, CRLException {
        ParamUtil.requireNonNull("encodedCrl", encodedCrl);
        return parseCrl(new ByteArrayInputStream(encodedCrl));
    }

    public static X509CRL parseCrl(final InputStream crlStream) throws CertificateException, CRLException {
        ParamUtil.requireNonNull("crlStream", crlStream);
        X509CRL crl = (X509CRL) getCertFactory().generateCRL(crlStream);
        if (crl == null) {
            throw new CRLException("the given one is not a valid X.509 CRL");
        }
        return crl;
    }

    public static X509Certificate parseCert(final byte[] certBytes) throws CertificateException {
        ParamUtil.requireNonNull("certBytes", certBytes);
        return parseCert(new ByteArrayInputStream(certBytes));
    }

    private static X509Certificate parseCert(final InputStream certStream) throws CertificateException {
        ParamUtil.requireNonNull("certStream", certStream);
        return (X509Certificate) getCertFactory().generateCertificate(certStream);
    }

    private static byte[] extractSki(final X509Certificate cert) throws CertificateEncodingException {
        byte[] extValue = getCoreExtValue(cert, Extension.subjectKeyIdentifier);
        if (extValue == null) {
            return null;
        }

        try {
            return ASN1OctetString.getInstance(extValue).getOctets();
        } catch (IllegalArgumentException ex) {
            throw new CertificateEncodingException(ex.getMessage());
        }
    }

    private static byte[] extractAki(final X509Certificate cert) throws CertificateEncodingException {
        byte[] extValue = getCoreExtValue(cert, Extension.authorityKeyIdentifier);
        if (extValue == null) {
            return null;
        }

        try {
            AuthorityKeyIdentifier aki = AuthorityKeyIdentifier.getInstance(extValue);
            return aki.getKeyIdentifier();
        } catch (IllegalArgumentException ex) {
            throw new CertificateEncodingException("invalid extension AuthorityKeyIdentifier: " + ex.getMessage());
        }
    }

    public static boolean hasKeyusage(final X509Certificate cert, final KeyUsage usage) {
        boolean[] keyusage = cert.getKeyUsage();
        if (keyusage != null && keyusage.length > usage.getBit()) {
            return keyusage[usage.getBit()];
        }
        return false;
    }

    private static byte[] getCoreExtValue(final X509Certificate cert, final ASN1ObjectIdentifier type)
            throws CertificateEncodingException {
        ParamUtil.requireNonNull("cert", cert);
        ParamUtil.requireNonNull("type", type);
        byte[] fullExtValue = cert.getExtensionValue(type.getId());
        if (fullExtValue == null) {
            return null;
        }
        try {
            return ASN1OctetString.getInstance(fullExtValue).getOctets();
        } catch (IllegalArgumentException ex) {
            throw new CertificateEncodingException("invalid extension " + type.getId() + ": " + ex.getMessage());
        }
    }

    public static boolean isSelfSigned(final X509Certificate cert) {
        ParamUtil.requireNonNull("cert", cert);
        boolean equals = cert.getSubjectX500Principal().equals(cert.getIssuerX500Principal());
        if (!equals) {
            return false;
        }

        try {
            byte[] ski = extractSki(cert);
            byte[] aki = extractAki(cert);

            return (ski != null && aki != null) ? Arrays.equals(ski, aki) : true;
        } catch (CertificateEncodingException ex) {
            return false;
        }
    }

    public static boolean issues(final X509Certificate issuerCert, final X509Certificate cert)
            throws CertificateEncodingException {
        ParamUtil.requireNonNull("issuerCert", issuerCert);
        ParamUtil.requireNonNull("cert", cert);
        boolean isCa = issuerCert.getBasicConstraints() >= 0;
        if (!isCa) {
            return false;
        }

        boolean issues = issuerCert.getSubjectX500Principal().equals(cert.getIssuerX500Principal());
        if (issues) {
            byte[] ski = extractSki(issuerCert);
            byte[] aki = extractAki(cert);
            if (ski != null) {
                issues = Arrays.equals(ski, aki);
            }
        }

        if (issues) {
            long issuerNotBefore = issuerCert.getNotBefore().getTime();
            long issuerNotAfter = issuerCert.getNotAfter().getTime();
            long notBefore = cert.getNotBefore().getTime();
            issues = notBefore <= issuerNotAfter && notBefore >= issuerNotBefore;
        }

        return issues;
    }

    public static ASN1ObjectIdentifier extractDigesetAlgorithmIdentifier(final String sigOid,
            final byte[] sigParams) throws NoSuchAlgorithmException {
        ParamUtil.requireNonBlank("sigOid", sigOid);

        ASN1ObjectIdentifier algOid = new ASN1ObjectIdentifier(sigOid);

        ASN1ObjectIdentifier digestAlgOid;
        if (PKCSObjectIdentifiers.md5WithRSAEncryption.equals(algOid)) {
            digestAlgOid = PKCSObjectIdentifiers.md5;
        } else if (PKCSObjectIdentifiers.sha1WithRSAEncryption.equals(algOid)) {
            digestAlgOid = X509ObjectIdentifiers.id_SHA1;
        } else if (PKCSObjectIdentifiers.sha224WithRSAEncryption.equals(algOid)) {
            digestAlgOid = NISTObjectIdentifiers.id_sha224;
        } else if (PKCSObjectIdentifiers.sha256WithRSAEncryption.equals(algOid)) {
            digestAlgOid = NISTObjectIdentifiers.id_sha256;
        } else if (PKCSObjectIdentifiers.sha384WithRSAEncryption.equals(algOid)) {
            digestAlgOid = NISTObjectIdentifiers.id_sha384;
        } else if (PKCSObjectIdentifiers.sha512WithRSAEncryption.equals(algOid)) {
            digestAlgOid = NISTObjectIdentifiers.id_sha512;
        } else if (PKCSObjectIdentifiers.id_RSASSA_PSS.equals(algOid)) {
            RSASSAPSSparams param = RSASSAPSSparams.getInstance(sigParams);
            digestAlgOid = param.getHashAlgorithm().getAlgorithm();
        } else {
            throw new NoSuchAlgorithmException("unknown signature algorithm" + algOid.getId());
        }

        return digestAlgOid;
    }

    public static ASN1Encodable getFirstAttrValue(final AttributeTable attrs, final ASN1ObjectIdentifier type) {
        ParamUtil.requireNonNull("attrs", attrs);
        ParamUtil.requireNonNull("type", type);
        Attribute attr = attrs.get(type);
        if (attr == null) {
            return null;
        }
        ASN1Set set = attr.getAttrValues();
        return (set.size() == 0) ? null : set.getObjectAt(0);
    }

    public static byte[] read(final InputStream in) throws IOException {
        ParamUtil.requireNonNull("in", in);
        try {
            ByteArrayOutputStream bout = new ByteArrayOutputStream();
            int readed = 0;
            byte[] buffer = new byte[2048];
            while ((readed = in.read(buffer)) != -1) {
                bout.write(buffer, 0, readed);
            }

            return bout.toByteArray();
        } finally {
            try {
                in.close();
            } catch (IOException ex) {
                LOG.error("could not close stream: {}", ex.getMessage());
            }
        }
    }

    public static void addCmsCertSet(final CMSSignedDataGenerator generator, final X509Certificate[] cmsCertSet)
            throws CertificateEncodingException, CMSException {
        if (cmsCertSet == null || cmsCertSet.length == 0) {
            return;
        }
        ParamUtil.requireNonNull("geneator", generator);
        Collection<X509Certificate> certColl = new LinkedList<X509Certificate>();
        for (X509Certificate m : cmsCertSet) {
            certColl.add(m);
        }

        JcaCertStore certStore = new JcaCertStore(certColl);
        generator.addCertificates(certStore);
    }

    private static CertificateFactory getCertFactory() throws CertificateException {
        synchronized (certFactLock) {
            if (certFact == null) {
                try {
                    certFact = CertificateFactory.getInstance("X.509", "BC");
                } catch (NoSuchProviderException ex) {
                    throw new CertificateException("NoSuchProviderException: " + ex.getMessage());
                }
            }
            return certFact;
        }
    }

}