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

Java tutorial

Introduction

Here is the source code for org.xipki.pki.ca.server.impl.IdentifiedX509Certprofile.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.server.impl;

import java.security.cert.X509Certificate;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TimeZone;

import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;

import org.bouncycastle.asn1.ASN1Encodable;
import org.bouncycastle.asn1.ASN1EncodableVector;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.DERNull;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.asn1.x500.RDN;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x500.style.IETFUtils;
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.commons.common.util.CollectionUtil;
import org.xipki.commons.common.util.ParamUtil;
import org.xipki.commons.security.ExtensionExistence;
import org.xipki.commons.security.HashAlgoType;
import org.xipki.commons.security.KeyUsage;
import org.xipki.commons.security.ObjectIdentifiers;
import org.xipki.commons.security.util.X509Util;
import org.xipki.pki.ca.api.BadCertTemplateException;
import org.xipki.pki.ca.api.BadFormatException;
import org.xipki.pki.ca.api.EnvParameterResolver;
import org.xipki.pki.ca.api.profile.CertValidity;
import org.xipki.pki.ca.api.profile.CertprofileException;
import org.xipki.pki.ca.api.profile.ExtensionControl;
import org.xipki.pki.ca.api.profile.ExtensionValue;
import org.xipki.pki.ca.api.profile.ExtensionValues;
import org.xipki.pki.ca.api.profile.GeneralNameMode;
import org.xipki.pki.ca.api.profile.x509.AuthorityInfoAccessControl;
import org.xipki.pki.ca.api.profile.x509.ExtKeyUsageControl;
import org.xipki.pki.ca.api.profile.x509.KeyUsageControl;
import org.xipki.pki.ca.api.profile.x509.SpecialX509CertprofileBehavior;
import org.xipki.pki.ca.api.profile.x509.SubjectDnSpec;
import org.xipki.pki.ca.api.profile.x509.SubjectInfo;
import org.xipki.pki.ca.api.profile.x509.X509CertLevel;
import org.xipki.pki.ca.api.profile.x509.X509CertVersion;
import org.xipki.pki.ca.api.profile.x509.X509Certprofile;
import org.xipki.pki.ca.api.profile.x509.X509CertprofileUtil;
import org.xipki.pki.ca.server.impl.util.CaUtil;
import org.xipki.pki.ca.server.mgmt.api.CertprofileEntry;

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

class IdentifiedX509Certprofile {

    private static final Set<ASN1ObjectIdentifier> CRITICAL_ONLY_EXTENSION_TYPES;

    private static final Set<ASN1ObjectIdentifier> CA_CRITICAL_ONLY_EXTENSION_TYPES;

    private static final Set<ASN1ObjectIdentifier> NONCRITICAL_ONLY_EXTENSION_TYPES;

    private static final Set<ASN1ObjectIdentifier> CA_ONLY_EXTENSION_TYPES;

    private static final Set<ASN1ObjectIdentifier> NONE_REQUEST_EXTENSION_TYPES;

    private static final Set<ASN1ObjectIdentifier> REQUIRED_CA_EXTENSION_TYPES;

    private static final Set<ASN1ObjectIdentifier> REQUIRED_EE_EXTENSION_TYPES;

    static {
        CRITICAL_ONLY_EXTENSION_TYPES = new HashSet<>();
        CRITICAL_ONLY_EXTENSION_TYPES.add(Extension.keyUsage);
        CRITICAL_ONLY_EXTENSION_TYPES.add(Extension.policyMappings);
        CRITICAL_ONLY_EXTENSION_TYPES.add(Extension.nameConstraints);
        CRITICAL_ONLY_EXTENSION_TYPES.add(Extension.policyConstraints);
        CRITICAL_ONLY_EXTENSION_TYPES.add(Extension.inhibitAnyPolicy);
        CRITICAL_ONLY_EXTENSION_TYPES.add(ObjectIdentifiers.id_pe_tlsfeature);

        CA_CRITICAL_ONLY_EXTENSION_TYPES = new HashSet<>();
        CA_CRITICAL_ONLY_EXTENSION_TYPES.add(Extension.basicConstraints);

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

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

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

        REQUIRED_CA_EXTENSION_TYPES = new HashSet<>();
        REQUIRED_CA_EXTENSION_TYPES.add(Extension.basicConstraints);
        REQUIRED_CA_EXTENSION_TYPES.add(Extension.subjectKeyIdentifier);
        REQUIRED_CA_EXTENSION_TYPES.add(Extension.keyUsage);

        REQUIRED_EE_EXTENSION_TYPES = new HashSet<>();
        REQUIRED_EE_EXTENSION_TYPES.add(Extension.authorityKeyIdentifier);
        REQUIRED_EE_EXTENSION_TYPES.add(Extension.subjectKeyIdentifier);
    } // end static

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

    IdentifiedX509Certprofile(final CertprofileEntry dbEntry, final X509Certprofile certProfile)
            throws CertprofileException {
        this.dbEntry = ParamUtil.requireNonNull("entry", dbEntry);
        this.name = dbEntry.getName();
        this.certprofile = ParamUtil.requireNonNull("certProfile", certProfile);

        this.certprofile.initialize(dbEntry.getConf());
        if (certProfile.getSpecialCertprofileBehavior() == SpecialX509CertprofileBehavior.gematik_gSMC_K) {
            String paramName = SpecialX509CertprofileBehavior.PARAMETER_MAXLIFTIME;
            String str = certProfile.getParameter(paramName);
            if (str == null) {
                throw new CertprofileException("parameter " + paramName + " is not defined");
            }

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

    } // constructor

    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 setEnvParameterResolver(final EnvParameterResolver envParameterResolver) {
        if (certprofile != null) {
            certprofile.setEnvParameterResolver(envParameterResolver);
        }
    }

    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 {
        SubjectInfo subjectInfo = certprofile.getSubject(requestedSubject);
        RDN[] countryRdns = subjectInfo.getGrantedSubject().getRDNs(ObjectIdentifiers.DN_C);
        if (countryRdns != null) {
            for (RDN rdn : countryRdns) {
                String textValue = IETFUtils.valueToString(rdn.getFirst().getValue());
                if (!SubjectDnSpec.isValidCountryAreaCode(textValue)) {
                    throw new BadCertTemplateException("invalid country/area code '" + textValue + "'");
                }
            }
        }
        return subjectInfo;
    }

    public ExtensionValues getExtensions(@NonNull final X500Name requestedSubject,
            @NonNull final X500Name grantedSubject, @Nullable final Extensions requestedExtensions,
            @NonNull final SubjectPublicKeyInfo publicKeyInfo, @NonNull final PublicCaInfo publicCaInfo,
            @Nullable final X509Certificate crlSignerCert, @NonNull final Date notBefore,
            @NonNull final Date notAfter) throws CertprofileException, BadCertTemplateException {
        ParamUtil.requireNonNull("publicKeyInfo", publicKeyInfo);
        ExtensionValues values = new ExtensionValues();

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

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

            for (ASN1ObjectIdentifier oid : neededExtTypes) {
                if (wantedExtTypes.contains(oid)) {
                    wantedExtTypes.remove(oid);
                }

                if (!controls.containsKey(oid)) {
                    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, neededExtTypes, wantedExtTypes)) {
            byte[] encodedSpki = publicKeyInfo.getPublicKeyData().getBytes();
            byte[] skiValue = HashAlgoType.SHA1.hash(encodedSpki);
            SubjectKeyIdentifier value = new SubjectKeyIdentifier(skiValue);
            addExtension(values, extType, value, extControl, neededExtTypes, wantedExtTypes);
        }

        // Authority key identifier
        extType = Extension.authorityKeyIdentifier;
        extControl = controls.remove(extType);
        if (extControl != null && addMe(extType, extControl, neededExtTypes, wantedExtTypes)) {
            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, neededExtTypes, wantedExtTypes);
        }

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

        // AuthorityInfoAccess
        extType = Extension.authorityInfoAccess;
        extControl = controls.remove(extType);
        if (extControl != null && addMe(extType, extControl, neededExtTypes, wantedExtTypes)) {
            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();
            }

            if (CollectionUtil.isNonEmpty(caIssuers) || CollectionUtil.isNonEmpty(ocspUris)) {
                AuthorityInformationAccess value = CaUtil.createAuthorityInformationAccess(caIssuers, ocspUris);
                addExtension(values, extType, value, extControl, neededExtTypes, wantedExtTypes);
            }
        }

        if (controls.containsKey(Extension.cRLDistributionPoints) || controls.containsKey(Extension.freshestCRL)) {
            X500Name crlSignerSubject = (crlSignerCert == null) ? null
                    : X500Name.getInstance(crlSignerCert.getSubjectX500Principal().getEncoded());
            X500Name x500CaPrincipal = publicCaInfo.getX500Subject();

            // CRLDistributionPoints
            extType = Extension.cRLDistributionPoints;
            extControl = controls.remove(extType);
            if (extControl != null && addMe(extType, extControl, neededExtTypes, wantedExtTypes)) {
                if (CollectionUtil.isNonEmpty(publicCaInfo.getCrlUris())) {
                    CRLDistPoint value = CaUtil.createCrlDistributionPoints(publicCaInfo.getCrlUris(),
                            x500CaPrincipal, crlSignerSubject);
                    addExtension(values, extType, value, extControl, neededExtTypes, wantedExtTypes);
                }
            }

            // FreshestCRL
            extType = Extension.freshestCRL;
            extControl = controls.remove(extType);
            if (extControl != null && addMe(extType, extControl, neededExtTypes, wantedExtTypes)) {
                if (CollectionUtil.isNonEmpty(publicCaInfo.getDeltaCrlUris())) {
                    CRLDistPoint value = CaUtil.createCrlDistributionPoints(publicCaInfo.getDeltaCrlUris(),
                            x500CaPrincipal, crlSignerSubject);
                    addExtension(values, extType, value, extControl, neededExtTypes, wantedExtTypes);
                }
            }
        }

        // BasicConstraints
        extType = Extension.basicConstraints;
        extControl = controls.remove(extType);
        if (extControl != null && addMe(extType, extControl, neededExtTypes, wantedExtTypes)) {
            BasicConstraints value = CaUtil.createBasicConstraints(certprofile.getCertLevel(),
                    certprofile.getPathLenBasicConstraint());
            addExtension(values, extType, value, extControl, neededExtTypes, wantedExtTypes);
        }

        // KeyUsage
        extType = Extension.keyUsage;
        extControl = controls.remove(extType);
        if (extControl != null && addMe(extType, extControl, neededExtTypes, wantedExtTypes)) {
            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 (requestedExtensions != null && extControl.isRequest()) {
                addRequestedKeyusage(usages, requestedExtensions, usageOccs);
            }

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

        // ExtendedKeyUsage
        extType = Extension.extendedKeyUsage;
        extControl = controls.remove(extType);
        if (extControl != null && addMe(extType, extControl, neededExtTypes, wantedExtTypes)) {
            List<ASN1ObjectIdentifier> usages = new LinkedList<>();
            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 (requestedExtensions != null && extControl.isRequest()) {
                addRequestedExtKeyusage(usages, requestedExtensions, usageOccs);
            }

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

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

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

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

        ExtensionValues subvalues = certprofile.getExtensions(Collections.unmodifiableMap(controls),
                requestedSubject, grantedSubject, requestedExtensions, notBefore, notAfter);

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

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

                addExtension(values, type, value, extControl, neededExtTypes, wantedExtTypes);
            }
        }

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

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

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

        return values;
    } // method getExtensions

    public X509CertLevel getCertLevel() {
        return certprofile.getCertLevel();
    }

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

    public SubjectPublicKeyInfo checkPublicKey(final SubjectPublicKeyInfo publicKey)
            throws BadCertTemplateException {
        ParamUtil.requireNonNull("publicKey", publicKey);
        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 int getMaxCertSize() {
        return certprofile.getMaxCertSize();
    }

    public void validate() throws CertprofileException {
        StringBuilder msg = new StringBuilder();

        Map<ASN1ObjectIdentifier, ExtensionControl> controls = getExtensionControls();

        // make sure that non-request extensions are not permitted in requests
        Set<ASN1ObjectIdentifier> set = new HashSet<>();
        for (ASN1ObjectIdentifier type : NONE_REQUEST_EXTENSION_TYPES) {
            ExtensionControl control = controls.get(type);
            if (control != null && control.isRequest()) {
                set.add(type);
            }
        }

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

        X509CertLevel level = getCertLevel();
        boolean ca = (level == X509CertLevel.RootCA) || (level == X509CertLevel.SubCA);

        // make sure that CA-only extensions are not permitted in EE certificate
        set.clear();
        if (!ca) {
            set.clear();
            for (ASN1ObjectIdentifier type : CA_ONLY_EXTENSION_TYPES) {
                if (controls.containsKey(type)) {
                    set.add(type);
                }
            }

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

        // make sure that critical only extensions are not marked as non-critical.
        set.clear();
        for (ASN1ObjectIdentifier type : controls.keySet()) {
            ExtensionControl control = controls.get(type);
            if (CRITICAL_ONLY_EXTENSION_TYPES.contains(type)) {
                if (!control.isCritical()) {
                    set.add(type);
                }
            }

            if (ca && CA_CRITICAL_ONLY_EXTENSION_TYPES.contains(type)) {
                if (!control.isCritical()) {
                    set.add(type);
                }
            }
        }

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

        // make sure that non-critical only extensions are not marked as critical.
        set.clear();
        for (ASN1ObjectIdentifier type : controls.keySet()) {
            ExtensionControl control = controls.get(type);
            if (NONCRITICAL_ONLY_EXTENSION_TYPES.contains(type)) {
                if (control.isCritical()) {
                    set.add(type);
                }
            }
        }

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

        // make sure that required extensions are present
        set.clear();
        Set<ASN1ObjectIdentifier> requiredTypes = ca ? REQUIRED_CA_EXTENSION_TYPES : REQUIRED_EE_EXTENSION_TYPES;

        for (ASN1ObjectIdentifier type : requiredTypes) {
            ExtensionControl extCtrl = controls.get(type);
            if (extCtrl == null || !extCtrl.isRequired()) {
                set.add(type);
            }
        }

        if (level == X509CertLevel.SubCA) {
            ASN1ObjectIdentifier type = Extension.authorityKeyIdentifier;
            ExtensionControl extCtrl = controls.get(type);
            if (extCtrl == null || !extCtrl.isRequired()) {
                set.add(type);
            }
        }

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

        // KeyUsage
        Set<KeyUsageControl> usages = getKeyUsage();

        if (ca) {
            // make sure the CA certificate contains usage keyCertSign
            if (!containsKeyusage(usages, KeyUsage.keyCertSign)) {
                msg.append("CA profile does not contain keyUsage ");
                msg.append(KeyUsage.keyCertSign).append(", ");
            }
        } else {
            // make sure the EE certificate does not contain CA-only usages
            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.isNonEmpty(set)) {
                msg.append("EE profile contains CA-only keyUsage ").append(setUsages).append(", ");
            }
        }

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

    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.isNonEmpty(oids)) {
            int len = sb.length();
            sb.delete(len - 2, len);
        }
        sb.append("]");

        return sb.toString();
    } // method 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 boolean addMe(final ASN1ObjectIdentifier extType, final ExtensionControl extControl,
            final Set<ASN1ObjectIdentifier> neededExtTypes, final Set<ASN1ObjectIdentifier> wantedExtTypes) {
        boolean addMe = extControl.isRequired();
        if (addMe) {
            return true;
        }

        return neededExtTypes.contains(extType) || wantedExtTypes.contains(extType);
    } // method addMe

    private static void addRequestedKeyusage(final Set<KeyUsage> usages, final Extensions requestedExtensions,
            final Set<KeyUsageControl> usageOccs) {
        Extension extension = requestedExtensions.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());
            }
        }
    } // method addRequestedKeyusage

    private static void addRequestedExtKeyusage(final List<ASN1ObjectIdentifier> usages,
            final Extensions requestedExtensions, final Set<ExtKeyUsageControl> usageOccs) {
        Extension extension = requestedExtensions.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());
            }
        }
    } // method addRequestedExtKeyusage

    private static ASN1Sequence createSubjectInfoAccess(final Extensions requestedExtensions,
            final Map<ASN1ObjectIdentifier, Set<GeneralNameMode>> modes) throws BadCertTemplateException {
        if (modes == null) {
            return null;
        }

        ASN1Encodable extValue = requestedExtensions.getExtensionParsedValue(Extension.subjectInfoAccess);
        if (extValue == null) {
            return null;
        }

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

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

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

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

        return vec.size() > 0 ? new DERSequence(vec) : null;
    } // method createSubjectInfoAccess

    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()) {
            return;
        }

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

    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()) {
            return;
        }

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

}