org.xipki.ca.server.impl.IdentifiedX509Certprofile.java Source code

Java tutorial

Introduction

Here is the source code for org.xipki.ca.server.impl.IdentifiedX509Certprofile.java

Source

/*
 *
 * This file is part of the XiPKI project.
 * Copyright (c) 2014 - 2015 Lijun Liao
 * Author: 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.ca.server.impl;

import java.io.IOException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.cert.X509Certificate;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TimeZone;

import org.bouncycastle.asn1.ASN1Encodable;
import org.bouncycastle.asn1.ASN1EncodableVector;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.ASN1String;
import org.bouncycastle.asn1.ASN1TaggedObject;
import org.bouncycastle.asn1.DERNull;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.asn1.DERTaggedObject;
import org.bouncycastle.asn1.DERUTF8String;
import org.bouncycastle.asn1.x500.DirectoryString;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x509.AccessDescription;
import org.bouncycastle.asn1.x509.AuthorityInformationAccess;
import org.bouncycastle.asn1.x509.AuthorityKeyIdentifier;
import org.bouncycastle.asn1.x509.BasicConstraints;
import org.bouncycastle.asn1.x509.CRLDistPoint;
import org.bouncycastle.asn1.x509.ExtendedKeyUsage;
import org.bouncycastle.asn1.x509.Extension;
import org.bouncycastle.asn1.x509.Extensions;
import org.bouncycastle.asn1.x509.GeneralName;
import org.bouncycastle.asn1.x509.GeneralNames;
import org.bouncycastle.asn1.x509.KeyPurposeId;
import org.bouncycastle.asn1.x509.SubjectKeyIdentifier;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.xipki.ca.api.BadCertTemplateException;
import org.xipki.ca.api.BadFormatException;
import org.xipki.ca.api.CertprofileException;
import org.xipki.ca.api.EnvironmentParameterResolver;
import org.xipki.ca.api.profile.CertValidity;
import org.xipki.ca.api.profile.ExtensionControl;
import org.xipki.ca.api.profile.ExtensionValue;
import org.xipki.ca.api.profile.ExtensionValues;
import org.xipki.ca.api.profile.GeneralNameMode;
import org.xipki.ca.api.profile.SubjectInfo;
import org.xipki.ca.api.profile.x509.AuthorityInfoAccessControl;
import org.xipki.ca.api.profile.x509.ExtKeyUsageControl;
import org.xipki.ca.api.profile.x509.KeyUsageControl;
import org.xipki.ca.api.profile.x509.SpecialX509CertprofileBehavior;
import org.xipki.ca.api.profile.x509.X509CertUtil;
import org.xipki.ca.api.profile.x509.X509CertVersion;
import org.xipki.ca.api.profile.x509.X509Certprofile;
import org.xipki.ca.certprofile.XmlX509Certprofile;
import org.xipki.ca.server.mgmt.api.CertprofileEntry;
import org.xipki.common.KeyUsage;
import org.xipki.common.ObjectIdentifiers;
import org.xipki.common.ParamChecker;
import org.xipki.common.util.CollectionUtil;
import org.xipki.common.util.StringUtil;
import org.xipki.common.util.X509Util;
import org.xipki.security.api.ExtensionExistence;

/**
 * @author Lijun Liao
 */

class IdentifiedX509Certprofile {
    private static final Set<ASN1ObjectIdentifier> criticalOnlyExtensionTypes;
    private static final Set<ASN1ObjectIdentifier> noncriticalOnlyExtensionTypes;
    private static final Set<ASN1ObjectIdentifier> caOnlyExtensionTypes;
    private static final Set<ASN1ObjectIdentifier> noneRequestExtensionTypes;

    static {
        criticalOnlyExtensionTypes = new HashSet<>();
        criticalOnlyExtensionTypes.add(Extension.basicConstraints);
        criticalOnlyExtensionTypes.add(Extension.keyUsage);
        criticalOnlyExtensionTypes.add(Extension.policyMappings);
        criticalOnlyExtensionTypes.add(Extension.nameConstraints);
        criticalOnlyExtensionTypes.add(Extension.policyConstraints);
        criticalOnlyExtensionTypes.add(Extension.inhibitAnyPolicy);

        noncriticalOnlyExtensionTypes = new HashSet<>();
        noncriticalOnlyExtensionTypes.add(Extension.authorityKeyIdentifier);
        noncriticalOnlyExtensionTypes.add(Extension.subjectKeyIdentifier);
        noncriticalOnlyExtensionTypes.add(Extension.issuerAlternativeName);
        noncriticalOnlyExtensionTypes.add(Extension.subjectDirectoryAttributes);
        noncriticalOnlyExtensionTypes.add(Extension.freshestCRL);
        noncriticalOnlyExtensionTypes.add(Extension.authorityInfoAccess);
        noncriticalOnlyExtensionTypes.add(Extension.subjectInfoAccess);

        caOnlyExtensionTypes = new HashSet<>();
        caOnlyExtensionTypes.add(Extension.policyMappings);
        caOnlyExtensionTypes.add(Extension.nameConstraints);
        caOnlyExtensionTypes.add(Extension.policyConstraints);
        caOnlyExtensionTypes.add(Extension.inhibitAnyPolicy);

        noneRequestExtensionTypes = new HashSet<ASN1ObjectIdentifier>();
        noneRequestExtensionTypes.add(Extension.subjectKeyIdentifier);
        noneRequestExtensionTypes.add(Extension.authorityKeyIdentifier);
        noneRequestExtensionTypes.add(Extension.issuerAlternativeName);
        noneRequestExtensionTypes.add(Extension.cRLDistributionPoints);
        noneRequestExtensionTypes.add(Extension.freshestCRL);
        noneRequestExtensionTypes.add(Extension.basicConstraints);
        noneRequestExtensionTypes.add(Extension.inhibitAnyPolicy);
    }

    private final String name;
    private final CertprofileEntry dbEntry;
    private final X509Certprofile certprofile;
    private EnvironmentParameterResolver parameterResolver;

    public IdentifiedX509Certprofile(final CertprofileEntry dbEntry, final String realType)
            throws CertprofileException {
        ParamChecker.assertNotNull("entry", dbEntry);
        this.dbEntry = dbEntry;
        this.name = dbEntry.getName();
        X509Certprofile tmpCertprofile = null;

        final String type = realType == null ? dbEntry.getType() : realType;
        String className;
        if (type.equalsIgnoreCase("xml")) {
            tmpCertprofile = new XmlX509Certprofile();
        } else if (StringUtil.startsWithIgnoreCase(type, "java:")) {
            className = type.substring("java:".length());
            try {
                Class<?> clazz = Class.forName(className);
                tmpCertprofile = (X509Certprofile) clazz.newInstance();
            } catch (ClassNotFoundException | InstantiationException | IllegalAccessException
                    | ClassCastException e) {
                throw new CertprofileException(
                        "invalid type " + type + ", " + e.getClass().getName() + ": " + e.getMessage());
            }
        } else {
            throw new CertprofileException("invalid type " + type);
        }

        tmpCertprofile.initialize(dbEntry.getConf());

        if (parameterResolver != null) {
            tmpCertprofile.setEnvironmentParameterResolver(parameterResolver);
        }

        if (tmpCertprofile.getSpecialCertprofileBehavior() == SpecialX509CertprofileBehavior.gematik_gSMC_K) {
            String paramName = SpecialX509CertprofileBehavior.PARAMETER_MAXLIFTIME;
            String s = tmpCertprofile.getParameter(paramName);
            if (s == null) {
                throw new CertprofileException("parameter " + paramName + " is not defined");
            }

            s = s.trim();
            int i;
            try {
                i = Integer.parseInt(s);
            } catch (NumberFormatException e) {
                throw new CertprofileException("invalid " + paramName + ": " + s);
            }
            if (i < 1) {
                throw new CertprofileException("invalid " + paramName + ": " + s);
            }
        }

        this.certprofile = tmpCertprofile;
    }

    public String getName() {
        return name;
    }

    public CertprofileEntry getDbEntry() {
        return dbEntry;
    }

    public X509CertVersion getVersion() {
        return certprofile.getVersion();
    }

    public List<String> getSignatureAlgorithms() {
        return certprofile.getSignatureAlgorithms();
    }

    public SpecialX509CertprofileBehavior getSpecialCertprofileBehavior() {
        return certprofile.getSpecialCertprofileBehavior();
    }

    public void setEnvironmentParameterResolver(final EnvironmentParameterResolver parameterResolver) {
        this.parameterResolver = parameterResolver;
        if (certprofile != null) {
            certprofile.setEnvironmentParameterResolver(parameterResolver);
        }
    }

    public Date getNotBefore(final Date notBefore) {
        return certprofile.getNotBefore(notBefore);
    }

    public CertValidity getValidity() {
        return certprofile.getValidity();
    }

    public boolean hasMidnightNotBefore() {
        return certprofile.hasMidnightNotBefore();
    }

    public TimeZone getTimezone() {
        return certprofile.getTimezone();
    }

    public SubjectInfo getSubject(final X500Name requestedSubject)
            throws CertprofileException, BadCertTemplateException {
        return certprofile.getSubject(requestedSubject);
    }

    public ExtensionValues getExtensions(final X500Name requestedSubject, final Extensions requestExtensions,
            final SubjectPublicKeyInfo publicKeyInfo, final PublicCAInfo publicCaInfo,
            final X509Certificate crlSignerCert) throws CertprofileException, BadCertTemplateException {
        ExtensionValues values = new ExtensionValues();

        Map<ASN1ObjectIdentifier, ExtensionControl> controls = new HashMap<>(certprofile.getExtensionControls());

        Set<ASN1ObjectIdentifier> neededExtensionTypes = new HashSet<>();
        Set<ASN1ObjectIdentifier> wantedExtensionTypes = new HashSet<>();
        if (requestExtensions != null) {
            Extension reqExtension = requestExtensions
                    .getExtension(ObjectIdentifiers.id_xipki_ext_cmpRequestExtensions);
            if (reqExtension != null) {
                ExtensionExistence ee = ExtensionExistence.getInstance(reqExtension.getParsedValue());
                neededExtensionTypes.addAll(ee.getNeedExtensions());
                wantedExtensionTypes.addAll(ee.getWantExtensions());
            }

            for (ASN1ObjectIdentifier oid : neededExtensionTypes) {
                if (wantedExtensionTypes.contains(oid)) {
                    wantedExtensionTypes.remove(oid);
                }

                if (controls.containsKey(oid) == false) {
                    throw new BadCertTemplateException("could not add needed extension " + oid.getId());
                }
            }
        }

        // SubjectKeyIdentifier
        ASN1ObjectIdentifier extType = Extension.subjectKeyIdentifier;
        ExtensionControl extControl = controls.remove(extType);
        if (extControl != null && addMe(extType, extControl, neededExtensionTypes, wantedExtensionTypes)) {
            MessageDigest sha1;
            try {
                sha1 = MessageDigest.getInstance("SHA-1");
            } catch (NoSuchAlgorithmException e) {
                throw new CertprofileException(e.getMessage(), e);
            }
            byte[] skiValue = sha1.digest(publicKeyInfo.getPublicKeyData().getBytes());

            SubjectKeyIdentifier value = new SubjectKeyIdentifier(skiValue);
            addExtension(values, extType, value, extControl, neededExtensionTypes, wantedExtensionTypes);
        }

        // Authority key identifier
        extType = Extension.authorityKeyIdentifier;
        extControl = controls.remove(extType);
        if (extControl != null && addMe(extType, extControl, neededExtensionTypes, wantedExtensionTypes)) {
            byte[] ikiValue = publicCaInfo.getSubjectKeyIdentifer();
            AuthorityKeyIdentifier value = null;
            if (ikiValue != null) {
                if (certprofile.includeIssuerAndSerialInAKI()) {
                    GeneralNames x509CaSubject = new GeneralNames(new GeneralName(publicCaInfo.getX500Subject()));
                    value = new AuthorityKeyIdentifier(ikiValue, x509CaSubject, publicCaInfo.getSerialNumber());
                } else {
                    value = new AuthorityKeyIdentifier(ikiValue);
                }
            }

            addExtension(values, extType, value, extControl, neededExtensionTypes, wantedExtensionTypes);
        }

        // IssuerAltName
        extType = Extension.issuerAlternativeName;
        extControl = controls.remove(extType);
        if (extControl != null && addMe(extType, extControl, neededExtensionTypes, wantedExtensionTypes)) {
            GeneralNames value = publicCaInfo.getSubjectAltName();
            addExtension(values, extType, value, extControl, neededExtensionTypes, wantedExtensionTypes);
        }

        // AuthorityInfoAccess
        extType = Extension.authorityInfoAccess;
        extControl = controls.remove(extType);
        if (extControl != null && addMe(extType, extControl, neededExtensionTypes, wantedExtensionTypes)) {
            AuthorityInfoAccessControl aiaControl = certprofile.getAIAControl();

            List<String> caIssuers = null;
            if (aiaControl == null || aiaControl.includesCaIssuers()) {
                caIssuers = publicCaInfo.getCaCertUris();
            }

            List<String> ocspUris = null;
            if (aiaControl == null || aiaControl.includesOcsp()) {
                ocspUris = publicCaInfo.getOcspUris();
            }
            AuthorityInformationAccess value = X509CertUtil.createAuthorityInformationAccess(caIssuers, ocspUris);
            addExtension(values, extType, value, extControl, neededExtensionTypes, wantedExtensionTypes);
        }

        if (controls.containsKey(Extension.cRLDistributionPoints) || controls.containsKey(Extension.freshestCRL)) {
            X500Name crlSignerSubject = null;
            if (crlSignerCert != null) {
                crlSignerSubject = X500Name.getInstance(crlSignerCert.getSubjectX500Principal().getEncoded());
            }

            X500Name x500CaPrincipal = publicCaInfo.getX500Subject();

            // CRLDistributionPoints
            extType = Extension.cRLDistributionPoints;
            extControl = controls.remove(extType);
            if (extControl != null && addMe(extType, extControl, neededExtensionTypes, wantedExtensionTypes)) {
                CRLDistPoint value;
                try {
                    value = X509CertUtil.createCRLDistributionPoints(publicCaInfo.getCrlUris(), x500CaPrincipal,
                            crlSignerSubject);
                } catch (IOException e) {
                    throw new CertprofileException(e.getMessage(), e);
                }
                addExtension(values, extType, value, extControl, neededExtensionTypes, wantedExtensionTypes);
            }

            // FreshestCRL
            extType = Extension.freshestCRL;
            extControl = controls.remove(extType);
            if (extControl != null && addMe(extType, extControl, neededExtensionTypes, wantedExtensionTypes)) {
                CRLDistPoint value;
                try {
                    value = X509CertUtil.createCRLDistributionPoints(publicCaInfo.getDeltaCrlUris(),
                            x500CaPrincipal, crlSignerSubject);
                } catch (IOException e) {
                    throw new CertprofileException(e.getMessage(), e);
                }
                addExtension(values, extType, value, extControl, neededExtensionTypes, wantedExtensionTypes);
            }
        }

        // BasicConstraints
        extType = Extension.basicConstraints;
        extControl = controls.remove(extType);
        if (extControl != null && addMe(extType, extControl, neededExtensionTypes, wantedExtensionTypes)) {
            BasicConstraints value = X509CertUtil.createBasicConstraints(certprofile.isCA(),
                    certprofile.getPathLenBasicConstraint());
            addExtension(values, extType, value, extControl, neededExtensionTypes, wantedExtensionTypes);
        }

        // KeyUsage
        extType = Extension.keyUsage;
        extControl = controls.remove(extType);
        if (extControl != null && addMe(extType, extControl, neededExtensionTypes, wantedExtensionTypes)) {
            Set<KeyUsage> usages = new HashSet<>();
            Set<KeyUsageControl> usageOccs = certprofile.getKeyUsage();
            for (KeyUsageControl k : usageOccs) {
                if (k.isRequired()) {
                    usages.add(k.getKeyUsage());
                }
            }

            // the optional KeyUsage will only be set if requested explicitly
            if (requestExtensions != null && extControl.isRequest()) {
                addRequestedKeyusage(usages, requestExtensions, usageOccs);
            }

            org.bouncycastle.asn1.x509.KeyUsage value = X509Util.createKeyUsage(usages);
            addExtension(values, extType, value, extControl, neededExtensionTypes, wantedExtensionTypes);
        }

        // ExtendedKeyUsage
        extType = Extension.extendedKeyUsage;
        extControl = controls.remove(extType);
        if (extControl != null && addMe(extType, extControl, neededExtensionTypes, wantedExtensionTypes)) {
            Set<ASN1ObjectIdentifier> usages = new HashSet<>();
            Set<ExtKeyUsageControl> usageOccs = certprofile.getExtendedKeyUsages();
            for (ExtKeyUsageControl k : usageOccs) {
                if (k.isRequired()) {
                    usages.add(k.getExtKeyUsage());
                }
            }

            // the optional ExtKeyUsage will only be set if requested explicitly
            if (requestExtensions != null && extControl.isRequest()) {
                addRequestedExtKeyusage(usages, requestExtensions, usageOccs);
            }

            if (extControl.isCritical() && usages.contains(ObjectIdentifiers.anyExtendedKeyUsage)) {
                extControl = new ExtensionControl(false, extControl.isRequired(), extControl.isRequest());
            }

            ExtendedKeyUsage value = X509Util.createExtendedUsage(usages);
            addExtension(values, extType, value, extControl, neededExtensionTypes, wantedExtensionTypes);
        }

        // ocsp-nocheck
        extType = ObjectIdentifiers.id_extension_pkix_ocsp_nocheck;
        extControl = controls.remove(extType);
        if (extControl != null && addMe(extType, extControl, neededExtensionTypes, wantedExtensionTypes)) {
            // the extension ocsp-nocheck will only be set if requested explicitly
            DERNull value = DERNull.INSTANCE;
            addExtension(values, extType, value, extControl, neededExtensionTypes, wantedExtensionTypes);
        }

        // SubjectAltName
        extType = Extension.subjectAlternativeName;
        extControl = controls.remove(extType);
        if (extControl != null && addMe(extType, extControl, neededExtensionTypes, wantedExtensionTypes)) {
            GeneralNames value = null;
            if (requestExtensions != null && extControl.isRequest()) {
                value = createRequestedSubjectAltNames(requestExtensions, certprofile.getSubjectAltNameModes());
            }
            addExtension(values, extType, value, extControl, neededExtensionTypes, wantedExtensionTypes);
        }

        // SubjectInfoAccess
        extType = Extension.subjectInfoAccess;
        extControl = controls.remove(extType);
        if (extControl != null && addMe(extType, extControl, neededExtensionTypes, wantedExtensionTypes)) {
            ASN1Sequence value = null;
            if (requestExtensions != null && extControl.isRequest()) {
                value = createSubjectInfoAccess(requestExtensions, certprofile.getSubjectInfoAccessModes());
            }
            addExtension(values, extType, value, extControl, neededExtensionTypes, wantedExtensionTypes);
        }

        ExtensionValues subvalues = certprofile.getExtensions(Collections.unmodifiableMap(controls),
                requestedSubject, requestExtensions);

        Set<ASN1ObjectIdentifier> extTypes = new HashSet<>(controls.keySet());
        for (ASN1ObjectIdentifier type : extTypes) {
            extControl = controls.remove(type);
            boolean addMe = addMe(type, extControl, neededExtensionTypes, wantedExtensionTypes);
            if (addMe) {
                ExtensionValue value = null;
                if (extControl.isRequest()) {
                    Extension reqExt = requestExtensions.getExtension(type);
                    if (reqExt != null) {
                        value = new ExtensionValue(reqExt.isCritical(), reqExt.getParsedValue());
                    }
                }

                if (value == null) {
                    value = subvalues.getExtensionValue(type);
                }

                addExtension(values, type, value, extControl, neededExtensionTypes, wantedExtensionTypes);
            }
        }

        Set<ASN1ObjectIdentifier> unprocessedExtTypes = new HashSet<>();
        for (ASN1ObjectIdentifier type : controls.keySet()) {
            if (controls.get(type).isRequired()) {
                unprocessedExtTypes.add(type);
            }
        }

        if (CollectionUtil.isNotEmpty(unprocessedExtTypes)) {
            throw new CertprofileException("could not add required extensions " + toString(unprocessedExtTypes));
        }

        if (CollectionUtil.isNotEmpty(neededExtensionTypes)) {
            throw new BadCertTemplateException(
                    "could not add requested extensions " + toString(neededExtensionTypes));
        }

        return values;
    }

    private static void addExtension(final ExtensionValues values, final ASN1ObjectIdentifier extType,
            final ExtensionValue extValue, final ExtensionControl extControl,
            final Set<ASN1ObjectIdentifier> neededExtensionTypes,
            final Set<ASN1ObjectIdentifier> wantedExtensionTypes) throws CertprofileException {
        if (extValue != null) {
            values.addExtension(extType, extValue);
            neededExtensionTypes.remove(extType);
            wantedExtensionTypes.remove(extType);
            return;
        }

        if (extControl.isRequired() == false) {
            return;
        }

        String description = ObjectIdentifiers.getName(extType);
        if (description == null) {
            description = extType.getId();
        }
        throw new CertprofileException("could not add required extension " + description);
    }

    private static void addExtension(final ExtensionValues values, final ASN1ObjectIdentifier extType,
            final ASN1Encodable extValue, final ExtensionControl extControl,
            final Set<ASN1ObjectIdentifier> neededExtensionTypes,
            final Set<ASN1ObjectIdentifier> wantedExtensionTypes) throws CertprofileException {
        if (extValue != null) {
            values.addExtension(extType, extControl.isCritical(), extValue);
            neededExtensionTypes.remove(extType);
            wantedExtensionTypes.remove(extType);
            return;
        }

        if (extControl.isRequired() == false) {
            return;
        }

        String description = ObjectIdentifiers.getName(extType);
        if (description == null) {
            description = extType.getId();
        }
        throw new CertprofileException("could not add required extension " + description);
    }

    public boolean isCA() {
        return certprofile.isCA();
    }

    public boolean isOnlyForRA() {
        return certprofile.isOnlyForRA();
    }

    public boolean isOnlyForQA() {
        return certprofile.isOnlyForQA();
    }

    public SubjectPublicKeyInfo checkPublicKey(final SubjectPublicKeyInfo publicKey)
            throws BadCertTemplateException {
        return certprofile.checkPublicKey(publicKey);
    }

    public boolean incSerialNumberIfSubjectExists() {
        return certprofile.incSerialNumberIfSubjectExists();
    }

    public void shutdown() {
        if (certprofile != null) {
            certprofile.shutdown();
        }
    }

    public boolean includeIssuerAndSerialInAKI() {
        return certprofile.includeIssuerAndSerialInAKI();
    }

    public String incSerialNumber(final String currentSerialNumber) throws BadFormatException {
        return certprofile.incSerialNumber(currentSerialNumber);
    }

    public boolean isDuplicateKeyPermitted() {
        return certprofile.isDuplicateKeyPermitted();
    }

    public boolean isDuplicateSubjectPermitted() {
        return certprofile.isDuplicateSubjectPermitted();
    }

    public boolean isSerialNumberInReqPermitted() {
        return certprofile.isSerialNumberInReqPermitted();
    }

    public String getParameter(final String paramName) {
        return certprofile.getParameter(paramName);
    }

    public Map<ASN1ObjectIdentifier, ExtensionControl> getExtensionControls() {
        return certprofile.getExtensionControls();
    }

    public Set<KeyUsageControl> getKeyUsage() {
        return certprofile.getKeyUsage();
    }

    public Integer getPathLenBasicConstraint() {
        return certprofile.getPathLenBasicConstraint();
    }

    public Set<ExtKeyUsageControl> getExtendedKeyUsages() {
        return certprofile.getExtendedKeyUsages();
    }

    public void validate() throws CertprofileException {
        if (isOnlyForQA()) {
            return;
        }

        StringBuilder msg = new StringBuilder();

        Map<ASN1ObjectIdentifier, ExtensionControl> controls = getExtensionControls();
        Set<ASN1ObjectIdentifier> set = new HashSet<>();
        for (ASN1ObjectIdentifier type : noneRequestExtensionTypes) {
            ExtensionControl control = controls.get(type);
            if (control != null && control.isRequest()) {
                set.add(type);
            }
        }

        if (CollectionUtil.isNotEmpty(set)) {
            msg.append("extensions ").append(toString(set)).append(" could not be contained in request, ");
        }

        boolean ca = isCA();

        set.clear();
        if (ca == false) {
            set.clear();
            for (ASN1ObjectIdentifier type : caOnlyExtensionTypes) {
                if (controls.containsKey(type)) {
                    set.add(type);
                }
            }

            if (CollectionUtil.isNotEmpty(set)) {
                msg.append("EE profile contains CA-only extensions ").append(toString(set)).append(", ");
            }
        }

        set.clear();
        for (ASN1ObjectIdentifier type : controls.keySet()) {
            ExtensionControl control = controls.get(type);
            if (criticalOnlyExtensionTypes.contains(type)) {
                if (control.isCritical() == false) {
                    set.add(type);
                }
            }
        }

        if (CollectionUtil.isNotEmpty(set)) {
            msg.append("critical only extensions are marked as non-critical ").append(toString(set)).append(", ");
        }

        set.clear();
        for (ASN1ObjectIdentifier type : controls.keySet()) {
            ExtensionControl control = controls.get(type);
            if (noncriticalOnlyExtensionTypes.contains(type)) {
                if (control.isCritical()) {
                    set.add(type);
                }
            }
        }

        if (CollectionUtil.isNotEmpty(set)) {
            msg.append("none-critical extensions are marked as critical ").append(toString(set)).append(", ");
        }

        Set<KeyUsageControl> usages = getKeyUsage();

        boolean b = containsKeyusage(usages, KeyUsage.digitalSignature);
        if (b == false) {
            b = containsKeyusage(usages, KeyUsage.contentCommitment);
        }
        if (b == false) {
            b = containsKeyusage(usages, KeyUsage.keyCertSign);
        }
        if (b == false) {
            b = containsKeyusage(usages, KeyUsage.cRLSign);
        }

        if (b) {
            ASN1ObjectIdentifier[] types = new ASN1ObjectIdentifier[] { Extension.basicConstraints,
                    Extension.keyUsage };

            set.clear();
            for (ASN1ObjectIdentifier type : types) {
                if (controls.containsKey(type) == false || controls.get(type).isRequired() == false) {
                    set.add(type);
                }
            }

            if (CollectionUtil.isNotEmpty(set)) {
                msg.append("required extensions are not marked as required ").append(toString(set)).append(", ");
            }
        }

        if (ca) {
            if (containsKeyusage(usages, KeyUsage.keyCertSign) == false) {
                msg.append("CA profile does not contain keyUsage ").append(KeyUsage.keyCertSign).append(", ");
            }
        } else {
            KeyUsage[] caOnlyUsages = new KeyUsage[] { KeyUsage.keyCertSign, KeyUsage.cRLSign };

            Set<KeyUsage> setUsages = new HashSet<>();
            for (KeyUsage caOnlyUsage : caOnlyUsages) {
                if (containsKeyusage(usages, caOnlyUsage)) {
                    setUsages.add(caOnlyUsage);
                }
            }

            if (CollectionUtil.isNotEmpty(set)) {
                msg.append("EE profile contains CA-only keyUsage ").append(setUsages).append(", ");
            }
        }

        int len = msg.length();
        if (len > 2) {
            msg.delete(len - 2, len);
            throw new CertprofileException(msg.toString());
        }
    }

    private static String toString(final Set<ASN1ObjectIdentifier> oids) {
        if (oids == null) {
            return "null";
        }

        StringBuilder sb = new StringBuilder();
        sb.append("[");
        for (ASN1ObjectIdentifier oid : oids) {
            String name = ObjectIdentifiers.getName(oid);
            if (name != null) {
                sb.append(name);
                sb.append(" (").append(oid.getId()).append(")");
            } else {
                sb.append(oid.getId());
            }
            sb.append(", ");
        }
        if (CollectionUtil.isNotEmpty(oids)) {
            int len = sb.length();
            sb.delete(len - 2, len);
        }
        sb.append("]");

        return sb.toString();
    }

    private static boolean containsKeyusage(final Set<KeyUsageControl> usageControls, final KeyUsage usage) {
        for (KeyUsageControl entry : usageControls) {
            if (usage == entry.getKeyUsage()) {
                return true;
            }
        }
        return false;
    }

    private static GeneralName createGeneralName(final GeneralName reqName, final Set<GeneralNameMode> modes)
            throws BadCertTemplateException {
        int tag = reqName.getTagNo();
        GeneralNameMode mode = null;
        for (GeneralNameMode m : modes) {
            if (m.getTag().getTag() == tag) {
                mode = m;
                break;
            }
        }

        if (mode == null) {
            throw new BadCertTemplateException("generalName tag " + tag + " is not allowed");
        }

        switch (tag) {
        case GeneralName.rfc822Name:
        case GeneralName.dNSName:
        case GeneralName.uniformResourceIdentifier:
        case GeneralName.iPAddress:
        case GeneralName.registeredID:
        case GeneralName.directoryName: {
            return new GeneralName(tag, reqName.getName());
        }
        case GeneralName.otherName: {
            ASN1Sequence reqSeq = ASN1Sequence.getInstance(reqName.getName());
            ASN1ObjectIdentifier type = ASN1ObjectIdentifier.getInstance(reqSeq.getObjectAt(0));
            if (mode.getAllowedTypes().contains(type) == false) {
                throw new BadCertTemplateException("otherName.type " + type.getId() + " is not allowed");
            }

            ASN1Encodable value = ((ASN1TaggedObject) reqSeq.getObjectAt(1)).getObject();
            String text;
            if (value instanceof ASN1String == false) {
                throw new BadCertTemplateException("otherName.value is not a String");
            } else {
                text = ((ASN1String) value).getString();
            }

            ASN1EncodableVector vector = new ASN1EncodableVector();
            vector.add(type);
            vector.add(new DERTaggedObject(true, 0, new DERUTF8String(text)));
            DERSequence seq = new DERSequence(vector);

            return new GeneralName(GeneralName.otherName, seq);
        }
        case GeneralName.ediPartyName: {
            ASN1Sequence reqSeq = ASN1Sequence.getInstance(reqName.getName());

            int n = reqSeq.size();
            String nameAssigner = null;
            int idx = 0;
            if (n > 1) {
                DirectoryString ds = DirectoryString
                        .getInstance(((ASN1TaggedObject) reqSeq.getObjectAt(idx++)).getObject());
                nameAssigner = ds.getString();
            }

            DirectoryString ds = DirectoryString
                    .getInstance(((ASN1TaggedObject) reqSeq.getObjectAt(idx++)).getObject());
            String partyName = ds.getString();

            ASN1EncodableVector vector = new ASN1EncodableVector();
            if (nameAssigner != null) {
                vector.add(new DERTaggedObject(false, 0, new DirectoryString(nameAssigner)));
            }
            vector.add(new DERTaggedObject(false, 1, new DirectoryString(partyName)));
            ASN1Sequence seq = new DERSequence(vector);
            return new GeneralName(GeneralName.ediPartyName, seq);
        }
        default: {
            throw new RuntimeException("should not reach here, unknown GeneralName tag " + tag);
        }
        }// end switch(tag)
    }

    private static boolean addMe(final ASN1ObjectIdentifier extType, final ExtensionControl extControl,
            final Set<ASN1ObjectIdentifier> neededExtensionTypes,
            final Set<ASN1ObjectIdentifier> wantedExtensionTypes) {
        boolean addMe = extControl.isRequired();
        if (addMe == false) {
            if (neededExtensionTypes.contains(extType) || wantedExtensionTypes.contains(extType)) {
                addMe = true;
            }
        }
        return addMe;
    }

    private static void addRequestedKeyusage(final Set<KeyUsage> usages, final Extensions requestExtensions,
            final Set<KeyUsageControl> usageOccs) {
        Extension extension = requestExtensions.getExtension(Extension.keyUsage);
        if (extension == null) {
            return;
        }

        org.bouncycastle.asn1.x509.KeyUsage reqKeyUsage = org.bouncycastle.asn1.x509.KeyUsage
                .getInstance(extension.getParsedValue());
        for (KeyUsageControl k : usageOccs) {
            if (k.isRequired()) {
                continue;
            }

            if (reqKeyUsage.hasUsages(k.getKeyUsage().getBcUsage())) {
                usages.add(k.getKeyUsage());
            }
        }
    }

    private static void addRequestedExtKeyusage(final Set<ASN1ObjectIdentifier> usages,
            final Extensions requestExtensions, final Set<ExtKeyUsageControl> usageOccs) {
        Extension extension = requestExtensions.getExtension(Extension.extendedKeyUsage);
        if (extension == null) {
            return;
        }

        ExtendedKeyUsage reqKeyUsage = ExtendedKeyUsage.getInstance(extension.getParsedValue());
        for (ExtKeyUsageControl k : usageOccs) {
            if (k.isRequired()) {
                continue;
            }

            if (reqKeyUsage.hasKeyPurposeId(KeyPurposeId.getInstance(k.getExtKeyUsage()))) {
                usages.add(k.getExtKeyUsage());
            }
        }
    }

    private static GeneralNames createRequestedSubjectAltNames(final Extensions requestExtensions,
            final Set<GeneralNameMode> modes) throws BadCertTemplateException {
        ASN1Encodable extValue = requestExtensions.getExtensionParsedValue(Extension.subjectAlternativeName);
        if (extValue == null) {
            return null;
        }

        GeneralNames reqNames = GeneralNames.getInstance(extValue);
        if (modes == null) {
            return reqNames;
        }

        GeneralName[] reqL = reqNames.getNames();
        GeneralName[] l = new GeneralName[reqL.length];
        for (int i = 0; i < reqL.length; i++) {
            l[i] = createGeneralName(reqL[i], modes);
        }
        return new GeneralNames(l);
    }

    private static ASN1Sequence createSubjectInfoAccess(final Extensions requestExtensions,
            final Map<ASN1ObjectIdentifier, Set<GeneralNameMode>> modes) throws BadCertTemplateException {
        ASN1Encodable extValue = requestExtensions.getExtensionParsedValue(Extension.subjectInfoAccess);
        if (extValue == null) {
            return null;
        }

        ASN1Sequence reqSeq = ASN1Sequence.getInstance(extValue);
        int size = reqSeq.size();

        if (modes == null) {
            return reqSeq;
        }

        ASN1EncodableVector v = new ASN1EncodableVector();
        for (int i = 0; i < size; i++) {
            AccessDescription ad = AccessDescription.getInstance(reqSeq.getObjectAt(i));
            ASN1ObjectIdentifier accessMethod = ad.getAccessMethod();
            if (accessMethod == null) {
                accessMethod = X509Certprofile.OID_ZERO;
            }
            Set<GeneralNameMode> generalNameModes = modes.get(accessMethod);

            if (generalNameModes == null) {
                throw new BadCertTemplateException(
                        "subjectInfoAccess.accessMethod " + accessMethod.getId() + " is not allowed");
            }

            GeneralName accessLocation = createGeneralName(ad.getAccessLocation(), generalNameModes);
            v.add(new AccessDescription(accessMethod, accessLocation));
        } // end for

        return v.size() > 0 ? new DERSequence(v) : null;
    }
}