org.xipki.pki.ca.certprofile.XmlX509Certprofile.java Source code

Java tutorial

Introduction

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

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
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.Vector;
import java.util.regex.Pattern;

import org.bouncycastle.asn1.ASN1Encodable;
import org.bouncycastle.asn1.ASN1EncodableVector;
import org.bouncycastle.asn1.ASN1GeneralizedTime;
import org.bouncycastle.asn1.ASN1Integer;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.ASN1StreamParser;
import org.bouncycastle.asn1.DERGeneralizedTime;
import org.bouncycastle.asn1.DERIA5String;
import org.bouncycastle.asn1.DERNull;
import org.bouncycastle.asn1.DEROctetString;
import org.bouncycastle.asn1.DERPrintableString;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.asn1.DERSet;
import org.bouncycastle.asn1.DERTaggedObject;
import org.bouncycastle.asn1.DERUTF8String;
import org.bouncycastle.asn1.isismtt.x509.Admissions;
import org.bouncycastle.asn1.isismtt.x509.ProfessionInfo;
import org.bouncycastle.asn1.x500.DirectoryString;
import org.bouncycastle.asn1.x500.RDN;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.asn1.x509.Attribute;
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.SubjectDirectoryAttributes;
import org.bouncycastle.asn1.x509.qualified.BiometricData;
import org.bouncycastle.asn1.x509.qualified.Iso4217CurrencyCode;
import org.bouncycastle.asn1.x509.qualified.MonetaryValue;
import org.bouncycastle.asn1.x509.qualified.QCStatement;
import org.bouncycastle.asn1.x509.qualified.TypeOfBiometricData;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xipki.commons.common.util.CollectionUtil;
import org.xipki.commons.common.util.LogUtil;
import org.xipki.commons.common.util.ParamUtil;
import org.xipki.commons.common.util.StringUtil;
import org.xipki.commons.security.ObjectIdentifiers;
import org.xipki.commons.security.util.AlgorithmUtil;
import org.xipki.commons.security.util.X509Util;
import org.xipki.pki.ca.api.BadCertTemplateException;
import org.xipki.pki.ca.api.profile.CertValidity;
import org.xipki.pki.ca.api.profile.CertprofileException;
import org.xipki.pki.ca.api.profile.DirectoryStringType;
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.GeneralNameTag;
import org.xipki.pki.ca.api.profile.KeyParametersOption;
import org.xipki.pki.ca.api.profile.Range;
import org.xipki.pki.ca.api.profile.RdnControl;
import org.xipki.pki.ca.api.profile.StringType;
import org.xipki.pki.ca.api.profile.x509.AuthorityInfoAccessControl;
import org.xipki.pki.ca.api.profile.x509.BaseX509Certprofile;
import org.xipki.pki.ca.api.profile.x509.CertificatePolicyInformation;
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.SubjectControl;
import org.xipki.pki.ca.api.profile.x509.SubjectDirectoryAttributesControl;
import org.xipki.pki.ca.api.profile.x509.SubjectDnSpec;
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.X509CertprofileUtil;
import org.xipki.pki.ca.certprofile.commonpki.AdmissionSyntaxOption;
import org.xipki.pki.ca.certprofile.x509.jaxb.AdditionalInformation;
import org.xipki.pki.ca.certprofile.x509.jaxb.AdmissionSyntax;
import org.xipki.pki.ca.certprofile.x509.jaxb.AuthorityInfoAccess;
import org.xipki.pki.ca.certprofile.x509.jaxb.AuthorityKeyIdentifier;
import org.xipki.pki.ca.certprofile.x509.jaxb.AuthorizationTemplate;
import org.xipki.pki.ca.certprofile.x509.jaxb.BasicConstraints;
import org.xipki.pki.ca.certprofile.x509.jaxb.BiometricInfo;
import org.xipki.pki.ca.certprofile.x509.jaxb.CertificatePolicies;
import org.xipki.pki.ca.certprofile.x509.jaxb.ConstantExtValue;
import org.xipki.pki.ca.certprofile.x509.jaxb.ExtendedKeyUsage;
import org.xipki.pki.ca.certprofile.x509.jaxb.ExtensionType;
import org.xipki.pki.ca.certprofile.x509.jaxb.ExtensionsType;
import org.xipki.pki.ca.certprofile.x509.jaxb.InhibitAnyPolicy;
import org.xipki.pki.ca.certprofile.x509.jaxb.IntWithDescType;
import org.xipki.pki.ca.certprofile.x509.jaxb.KeyUsage;
import org.xipki.pki.ca.certprofile.x509.jaxb.NameConstraints;
import org.xipki.pki.ca.certprofile.x509.jaxb.NameValueType;
import org.xipki.pki.ca.certprofile.x509.jaxb.PdsLocationType;
import org.xipki.pki.ca.certprofile.x509.jaxb.PolicyConstraints;
import org.xipki.pki.ca.certprofile.x509.jaxb.PolicyMappings;
import org.xipki.pki.ca.certprofile.x509.jaxb.PrivateKeyUsagePeriod;
import org.xipki.pki.ca.certprofile.x509.jaxb.QcEuLimitValueType;
import org.xipki.pki.ca.certprofile.x509.jaxb.QcStatementType;
import org.xipki.pki.ca.certprofile.x509.jaxb.QcStatementValueType;
import org.xipki.pki.ca.certprofile.x509.jaxb.QcStatements;
import org.xipki.pki.ca.certprofile.x509.jaxb.Range2Type;
import org.xipki.pki.ca.certprofile.x509.jaxb.RdnType;
import org.xipki.pki.ca.certprofile.x509.jaxb.Restriction;
import org.xipki.pki.ca.certprofile.x509.jaxb.SMIMECapabilities;
import org.xipki.pki.ca.certprofile.x509.jaxb.SMIMECapability;
import org.xipki.pki.ca.certprofile.x509.jaxb.SubjectAltName;
import org.xipki.pki.ca.certprofile.x509.jaxb.SubjectDirectoryAttributs;
import org.xipki.pki.ca.certprofile.x509.jaxb.SubjectInfoAccess;
import org.xipki.pki.ca.certprofile.x509.jaxb.SubjectInfoAccess.Access;
import org.xipki.pki.ca.certprofile.x509.jaxb.SubjectToSubjectAltNameType;
import org.xipki.pki.ca.certprofile.x509.jaxb.SubjectToSubjectAltNameType.Target;
import org.xipki.pki.ca.certprofile.x509.jaxb.SubjectToSubjectAltNamesType;
import org.xipki.pki.ca.certprofile.x509.jaxb.TlsFeature;
import org.xipki.pki.ca.certprofile.x509.jaxb.ValidityModel;
import org.xipki.pki.ca.certprofile.x509.jaxb.X509ProfileType;
import org.xipki.pki.ca.certprofile.x509.jaxb.X509ProfileType.KeyAlgorithms;
import org.xipki.pki.ca.certprofile.x509.jaxb.X509ProfileType.Parameters;
import org.xipki.pki.ca.certprofile.x509.jaxb.X509ProfileType.Subject;

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

public class XmlX509Certprofile extends BaseX509Certprofile {

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

    private ExtensionValue additionalInformation;

    private AdmissionSyntaxOption admission;

    private AuthorityInfoAccessControl aiaControl;

    private Map<ASN1ObjectIdentifier, GeneralNameTag> subjectToSubjectAltNameModes;

    private Set<GeneralNameMode> subjectAltNameModes;

    private Map<ASN1ObjectIdentifier, Set<GeneralNameMode>> subjectInfoAccessModes;

    private ExtensionValue authorizationTemplate;

    private BiometricInfoOption biometricInfo;

    private X509CertLevel certLevel;

    private ExtensionValue certificatePolicies;

    private Map<ASN1ObjectIdentifier, ExtensionValue> constantExtensions;

    private boolean duplicateKeyPermitted;

    private boolean duplicateSubjectPermitted;

    private Set<ExtKeyUsageControl> extendedKeyusages;

    private Map<ASN1ObjectIdentifier, ExtensionControl> extensionControls;

    private boolean includeIssuerAndSerialInAki;

    private boolean incSerialNoIfSubjectExists;

    private ExtensionValue inhibitAnyPolicy;

    private Map<ASN1ObjectIdentifier, KeyParametersOption> keyAlgorithms;

    private Set<KeyUsageControl> keyusages;

    private Integer maxSize;

    private ExtensionValue nameConstraints;

    private boolean notBeforeMidnight;

    private Map<String, String> parameters;

    private Integer pathLen;

    private ExtensionValue policyConstraints;

    private ExtensionValue policyMappings;

    private CertValidity privateKeyUsagePeriod;

    private ExtensionValue qcStatments;

    private List<QcStatementOption> qcStatementsOption;

    private boolean raOnly;

    private ExtensionValue restriction;

    private boolean serialNumberInReqPermitted;

    private List<String> signatureAlgorithms;

    private ExtensionValue smimeCapabilities;

    private SpecialX509CertprofileBehavior specialBehavior;

    private SubjectControl subjectControl;

    private ExtensionValue tlsFeature;

    private CertValidity validity;

    private X509CertVersion version;

    private ExtensionValue validityModel;

    private SubjectDirectoryAttributesControl subjectDirAttrsControl;

    private void reset() {
        additionalInformation = null;
        admission = null;
        aiaControl = null;
        subjectToSubjectAltNameModes = null;
        subjectAltNameModes = null;
        subjectInfoAccessModes = null;
        authorizationTemplate = null;
        biometricInfo = null;
        certLevel = null;
        certificatePolicies = null;
        constantExtensions = null;
        duplicateKeyPermitted = true;
        duplicateSubjectPermitted = true;
        extendedKeyusages = null;
        extensionControls = null;
        includeIssuerAndSerialInAki = false;
        incSerialNoIfSubjectExists = false;
        inhibitAnyPolicy = null;
        keyAlgorithms = null;
        keyusages = null;
        maxSize = null;
        nameConstraints = null;
        notBeforeMidnight = false;
        parameters = null;
        pathLen = null;
        policyConstraints = null;
        policyMappings = null;
        privateKeyUsagePeriod = null;
        qcStatments = null;
        qcStatementsOption = null;
        raOnly = false;
        restriction = null;
        serialNumberInReqPermitted = true;
        signatureAlgorithms = null;
        smimeCapabilities = null;
        specialBehavior = null;
        subjectControl = null;
        tlsFeature = null;
        validity = null;
        validityModel = null;
        version = null;
        subjectDirAttrsControl = null;
    } // method reset

    @Override
    public void initialize(final String data) throws CertprofileException {
        ParamUtil.requireNonBlank("data", data);

        reset();
        try {
            byte[] bytes;
            try {
                bytes = data.getBytes("UTF-8");
            } catch (UnsupportedEncodingException ex) {
                bytes = data.getBytes();
            }

            X509ProfileType conf = XmlX509CertprofileUtil.parse(new ByteArrayInputStream(bytes));
            doInitialize(conf);
        } catch (RuntimeException ex) {
            LogUtil.error(LOG, ex);
            throw new CertprofileException(
                    "caught RuntimeException while initializing certprofile: " + ex.getMessage());
        }
    } // method initialize

    public void initialize(X509ProfileType conf) throws CertprofileException {
        ParamUtil.requireNonNull("conf", conf);

        reset();
        try {
            doInitialize(conf);
        } catch (RuntimeException ex) {
            LogUtil.error(LOG, ex);
            throw new CertprofileException(
                    "caught RuntimeException while initializing certprofile: " + ex.getMessage());
        }
    } // method initialize

    private void doInitialize(X509ProfileType conf) throws CertprofileException {
        if (conf.getVersion() != null) {
            String versionText = conf.getVersion();
            this.version = X509CertVersion.forName(versionText);
            if (this.version == null) {
                throw new CertprofileException(String.format("invalid version '%s'", versionText));
            }
        } else {
            this.version = X509CertVersion.v3;
        }

        if (conf.getSignatureAlgorithms() != null) {
            List<String> algoNames = conf.getSignatureAlgorithms().getAlgorithm();
            List<String> list = new ArrayList<>(algoNames.size());
            for (String algoName : algoNames) {
                try {
                    list.add(AlgorithmUtil.canonicalizeSignatureAlgo(algoName));
                } catch (NoSuchAlgorithmException ex) {
                    throw new CertprofileException(ex.getMessage(), ex);
                }
            }

            this.signatureAlgorithms = Collections.unmodifiableList(list);
        }

        this.raOnly = conf.isRaOnly();
        this.maxSize = conf.getMaxSize();

        this.validity = CertValidity.getInstance(conf.getValidity());
        String str = conf.getCertLevel();
        if ("RootCA".equalsIgnoreCase(str)) {
            this.certLevel = X509CertLevel.RootCA;
        } else if ("SubCA".equalsIgnoreCase(str)) {
            this.certLevel = X509CertLevel.SubCA;
        } else if ("EndEntity".equalsIgnoreCase(str)) {
            this.certLevel = X509CertLevel.EndEntity;
        } else {
            throw new CertprofileException("invalid CertLevel '" + str + "'");
        }

        str = conf.getNotBeforeTime();
        if ("midnight".equalsIgnoreCase(str)) {
            this.notBeforeMidnight = true;
        } else if ("current".equalsIgnoreCase(str)) {
            this.notBeforeMidnight = false;
        } else {
            throw new CertprofileException("invalid notBefore '" + str + "'");
        }

        String specBehavior = conf.getSpecialBehavior();
        if (specBehavior != null) {
            this.specialBehavior = SpecialX509CertprofileBehavior.forName(specBehavior);
        }

        this.duplicateKeyPermitted = conf.isDuplicateKey();
        this.serialNumberInReqPermitted = conf.isSerialNumberInReq();

        // KeyAlgorithms
        KeyAlgorithms keyAlgos = conf.getKeyAlgorithms();
        if (keyAlgos != null) {
            this.keyAlgorithms = XmlX509CertprofileUtil.buildKeyAlgorithms(keyAlgos);
        }

        // parameters
        Parameters confParams = conf.getParameters();
        if (confParams == null) {
            parameters = null;
        } else {
            Map<String, String> tmpMap = new HashMap<>();
            for (NameValueType nv : confParams.getParameter()) {
                tmpMap.put(nv.getName(), nv.getValue());
            }
            parameters = Collections.unmodifiableMap(tmpMap);
        }

        // Subject
        Subject subject = conf.getSubject();
        duplicateSubjectPermitted = subject.isDuplicateSubjectPermitted();

        List<RdnControl> subjectDnControls = new LinkedList<>();

        for (RdnType rdn : subject.getRdn()) {
            ASN1ObjectIdentifier type = new ASN1ObjectIdentifier(rdn.getType().getValue());

            List<Pattern> patterns = null;
            if (CollectionUtil.isNonEmpty(rdn.getRegex())) {
                patterns = new LinkedList<>();
                for (String regex : rdn.getRegex()) {
                    Pattern pattern = Pattern.compile(regex);
                    patterns.add(pattern);
                }
            }

            if (patterns == null) {
                Pattern pattern = SubjectDnSpec.getPattern(type);
                if (pattern != null) {
                    patterns = Arrays.asList(pattern);
                }
            }

            Range range = (rdn.getMinLen() != null || rdn.getMaxLen() != null)
                    ? new Range(rdn.getMinLen(), rdn.getMaxLen())
                    : null;

            RdnControl rdnControl = new RdnControl(type, rdn.getMinOccurs(), rdn.getMaxOccurs());
            subjectDnControls.add(rdnControl);

            StringType stringType = XmlX509CertprofileUtil.convertStringType(rdn.getStringType());
            rdnControl.setStringType(stringType);
            rdnControl.setStringLengthRange(range);
            rdnControl.setPatterns(patterns);
            rdnControl.setPrefix(rdn.getPrefix());
            rdnControl.setSuffix(rdn.getSuffix());
            rdnControl.setGroup(rdn.getGroup());
            SubjectDnSpec.fixRdnControl(rdnControl);
        }
        this.subjectControl = new SubjectControl(subjectDnControls, subject.isKeepRdnOrder());
        this.incSerialNoIfSubjectExists = subject.isIncSerialNumber();

        // Extensions
        ExtensionsType extensionsType = conf.getExtensions();

        // SubjectToSubjectAltName
        initSubjectToSubjectAltNames(extensionsType);

        // Extension controls
        this.extensionControls = XmlX509CertprofileUtil.buildExtensionControls(extensionsType);

        // AdditionalInformation
        initAdditionalInformation(extensionsType);

        // Admission
        initAdmission(extensionsType);

        // AuthorityInfoAccess
        initAuthorityInfoAccess(extensionsType);

        // AuthorityKeyIdentifier
        initAuthorityKeyIdentifier(extensionsType);

        // AuthorizationTemplate
        initAuthorizationTemplate(extensionsType);

        // BasicConstrains
        initBasicConstraints(extensionsType);

        // BiometricInfo
        initBiometricInfo(extensionsType);

        // Certificate Policies
        initCertificatePolicies(extensionsType);

        // ExtendedKeyUsage
        initExtendedKeyUsage(extensionsType);

        // Inhibit anyPolicy
        initInhibitAnyPolicy(extensionsType);

        // KeyUsage
        initKeyUsage(extensionsType);

        // Name Constrains
        initNameConstraints(extensionsType);

        // Policy Constraints
        initPolicyConstraints(extensionsType);

        // Policy Mappings
        initPolicyMappings(extensionsType);

        // PrivateKeyUsagePeriod
        initPrivateKeyUsagePeriod(extensionsType);

        // QCStatements
        initQcStatements(extensionsType);

        // Restriction
        initRestriction(extensionsType);

        // SMIMECapatibilities
        initSmimeCapabilities(extensionsType);

        // SubjectAltNameMode
        initSubjectAlternativeName(extensionsType);

        // SubjectInfoAccess
        initSubjectInfoAccess(extensionsType);

        // TlsFeature
        initTlsFeature(extensionsType);

        // validityModel
        initValidityModel(extensionsType);

        // SubjectDirectoryAttributes
        initSubjectDirAttrs(extensionsType);

        // constant extensions
        this.constantExtensions = XmlX509CertprofileUtil.buildConstantExtesions(extensionsType);

        // validate the configuration
        if (subjectToSubjectAltNameModes != null) {
            if (!extensionControls.containsKey(Extension.subjectAlternativeName)) {
                throw new CertprofileException(
                        "subjectToSubjectAltNames cannot be configured if extension subjectAltNames"
                                + " is not permitted");
            }

            if (subjectAltNameModes != null) {
                for (ASN1ObjectIdentifier attrType : subjectToSubjectAltNameModes.keySet()) {
                    GeneralNameTag nameTag = subjectToSubjectAltNameModes.get(attrType);
                    boolean allowed = false;
                    for (GeneralNameMode m : subjectAltNameModes) {
                        if (m.getTag() == nameTag) {
                            allowed = true;
                            break;
                        }
                    }

                    if (!allowed) {
                        throw new CertprofileException("target SubjectAltName type " + nameTag + " is not allowed");
                    }
                }
            }
        }
    } // method doInitialize

    private void initSubjectToSubjectAltNames(ExtensionsType extensionsType) throws CertprofileException {
        SubjectToSubjectAltNamesType s2sType = extensionsType.getSubjectToSubjectAltNames();
        if (s2sType == null) {
            return;
        }

        subjectToSubjectAltNameModes = new HashMap<>();
        for (SubjectToSubjectAltNameType m : s2sType.getSubjectToSubjectAltName()) {
            Target target = m.getTarget();
            GeneralNameTag nameTag = null;

            if (target.getDirectoryName() != null) {
                nameTag = GeneralNameTag.directoryName;
            } else if (target.getDnsName() != null) {
                nameTag = GeneralNameTag.dNSName;
            } else if (target.getIpAddress() != null) {
                nameTag = GeneralNameTag.iPAddress;
            } else if (target.getRfc822Name() != null) {
                nameTag = GeneralNameTag.rfc822Name;
            } else if (target.getUniformResourceIdentifier() != null) {
                nameTag = GeneralNameTag.uniformResourceIdentifier;
            } else if (target.getRegisteredID() != null) {
                nameTag = GeneralNameTag.registeredID;
            } else {
                throw new RuntimeException("should not reach here, unknown SubjectToSubjectAltName target");
            }

            subjectToSubjectAltNameModes.put(new ASN1ObjectIdentifier(m.getSource().getValue()), nameTag);
        }
    }

    private void initAdditionalInformation(ExtensionsType extensionsType) throws CertprofileException {
        ASN1ObjectIdentifier type = ObjectIdentifiers.id_extension_additionalInformation;
        if (!extensionControls.containsKey(type)) {
            return;
        }

        AdditionalInformation extConf = (AdditionalInformation) getExtensionValue(type, extensionsType,
                AdditionalInformation.class);
        if (extConf == null) {
            return;
        }

        DirectoryStringType stringType = XmlX509CertprofileUtil.convertDirectoryStringType(extConf.getType());
        ASN1Encodable extValue = stringType.createDirectoryString(extConf.getText());
        additionalInformation = new ExtensionValue(extensionControls.get(type).isCritical(), extValue);
    }

    private void initAdmission(ExtensionsType extensionsType) throws CertprofileException {
        ASN1ObjectIdentifier type = ObjectIdentifiers.id_extension_admission;
        if (!extensionControls.containsKey(type)) {
            return;
        }

        AdmissionSyntax extConf = (AdmissionSyntax) getExtensionValue(type, extensionsType, AdmissionSyntax.class);
        if (extConf == null) {
            return;
        }

        this.admission = XmlX509CertprofileUtil.buildAdmissionSyntax(extensionControls.get(type).isCritical(),
                extConf);
    }

    private void initAuthorityInfoAccess(ExtensionsType extensionsType) throws CertprofileException {
        ASN1ObjectIdentifier type = Extension.authorityInfoAccess;
        if (!extensionControls.containsKey(type)) {
            return;
        }

        AuthorityInfoAccess extConf = (AuthorityInfoAccess) getExtensionValue(type, extensionsType,
                AuthorityInfoAccess.class);
        if (extConf == null) {
            return;
        }

        this.aiaControl = new AuthorityInfoAccessControl(extConf.isIncludeCaIssuers(), extConf.isIncludeOcsp());
    }

    private void initAuthorityKeyIdentifier(ExtensionsType extensionsType) throws CertprofileException {
        ASN1ObjectIdentifier type = Extension.authorityKeyIdentifier;
        if (!extensionControls.containsKey(type)) {
            return;
        }

        AuthorityKeyIdentifier extConf = (AuthorityKeyIdentifier) getExtensionValue(type, extensionsType,
                AuthorityKeyIdentifier.class);
        if (extConf == null) {
            return;
        }

        this.includeIssuerAndSerialInAki = extConf.isIncludeIssuerAndSerial();
    }

    private void initAuthorizationTemplate(ExtensionsType extensionsType) throws CertprofileException {
        ASN1ObjectIdentifier type = ObjectIdentifiers.id_xipki_ext_authorizationTemplate;
        if (!extensionControls.containsKey(type)) {
            return;
        }

        AuthorizationTemplate extConf = (AuthorizationTemplate) getExtensionValue(type, extensionsType,
                AuthorizationTemplate.class);
        if (extConf == null) {
            return;
        }

        ASN1EncodableVector vec = new ASN1EncodableVector();
        vec.add(new ASN1ObjectIdentifier(extConf.getType().getValue()));
        vec.add(new DEROctetString(extConf.getAccessRights().getValue()));
        ASN1Encodable extValue = new DERSequence(vec);
        authorizationTemplate = new ExtensionValue(extensionControls.get(type).isCritical(), extValue);
    }

    private void initBasicConstraints(ExtensionsType extensionsType) throws CertprofileException {
        ASN1ObjectIdentifier type = Extension.basicConstraints;
        if (!extensionControls.containsKey(type)) {
            return;
        }

        BasicConstraints extConf = (BasicConstraints) getExtensionValue(type, extensionsType,
                BasicConstraints.class);
        if (extConf == null) {
            return;
        }
        this.pathLen = extConf.getPathLen();
    }

    private void initBiometricInfo(ExtensionsType extensionsType) throws CertprofileException {
        ASN1ObjectIdentifier type = Extension.biometricInfo;
        if (!extensionControls.containsKey(type)) {
            return;
        }

        BiometricInfo extConf = (BiometricInfo) getExtensionValue(type, extensionsType, BiometricInfo.class);
        if (extConf == null) {
            return;
        }

        try {
            this.biometricInfo = new BiometricInfoOption(extConf);
        } catch (NoSuchAlgorithmException ex) {
            throw new CertprofileException("NoSuchAlgorithmException: " + ex.getMessage());
        }
    }

    private void initCertificatePolicies(ExtensionsType extensionsType) throws CertprofileException {
        ASN1ObjectIdentifier type = Extension.certificatePolicies;
        if (!extensionControls.containsKey(type)) {
            return;
        }

        CertificatePolicies extConf = (CertificatePolicies) getExtensionValue(type, extensionsType,
                CertificatePolicies.class);
        if (extConf == null) {
            return;
        }

        List<CertificatePolicyInformation> policyInfos = XmlX509CertprofileUtil.buildCertificatePolicies(extConf);
        org.bouncycastle.asn1.x509.CertificatePolicies value = XmlX509CertprofileUtil
                .createCertificatePolicies(policyInfos);
        this.certificatePolicies = new ExtensionValue(extensionControls.get(type).isCritical(), value);
    }

    private void initExtendedKeyUsage(ExtensionsType extensionsType) throws CertprofileException {
        ASN1ObjectIdentifier type = Extension.extendedKeyUsage;
        if (!extensionControls.containsKey(type)) {
            return;
        }

        ExtendedKeyUsage extConf = (ExtendedKeyUsage) getExtensionValue(type, extensionsType,
                ExtendedKeyUsage.class);
        if (extConf == null) {
            return;
        }

        this.extendedKeyusages = XmlX509CertprofileUtil.buildExtKeyUsageOptions(extConf);
    }

    private void initInhibitAnyPolicy(ExtensionsType extensionsType) throws CertprofileException {
        ASN1ObjectIdentifier type = Extension.inhibitAnyPolicy;
        if (!extensionControls.containsKey(type)) {
            return;
        }

        InhibitAnyPolicy extConf = (InhibitAnyPolicy) getExtensionValue(type, extensionsType,
                InhibitAnyPolicy.class);
        if (extConf == null) {
            return;
        }

        int skipCerts = extConf.getSkipCerts();
        if (skipCerts < 0) {
            throw new CertprofileException("negative inhibitAnyPolicy.skipCerts is not allowed: " + skipCerts);
        }
        ASN1Integer value = new ASN1Integer(BigInteger.valueOf(skipCerts));
        this.inhibitAnyPolicy = new ExtensionValue(extensionControls.get(type).isCritical(), value);
    }

    private void initKeyUsage(ExtensionsType extensionsType) throws CertprofileException {
        ASN1ObjectIdentifier type = Extension.keyUsage;
        if (!extensionControls.containsKey(type)) {
            return;
        }

        KeyUsage extConf = (KeyUsage) getExtensionValue(type, extensionsType, KeyUsage.class);
        if (extConf == null) {
            return;
        }

        this.keyusages = XmlX509CertprofileUtil.buildKeyUsageOptions(extConf);
    }

    private void initNameConstraints(ExtensionsType extensionsType) throws CertprofileException {
        ASN1ObjectIdentifier type = Extension.nameConstraints;
        if (!extensionControls.containsKey(type)) {
            return;
        }

        NameConstraints extConf = (NameConstraints) getExtensionValue(type, extensionsType, NameConstraints.class);
        if (extConf == null) {
            return;
        }

        org.bouncycastle.asn1.x509.NameConstraints value = XmlX509CertprofileUtil.buildNameConstrains(extConf);
        this.nameConstraints = new ExtensionValue(extensionControls.get(type).isCritical(), value);
    }

    private void initPrivateKeyUsagePeriod(ExtensionsType extensionsType) throws CertprofileException {
        ASN1ObjectIdentifier type = Extension.privateKeyUsagePeriod;
        if (!extensionControls.containsKey(type)) {
            return;
        }

        PrivateKeyUsagePeriod extConf = (PrivateKeyUsagePeriod) getExtensionValue(type, extensionsType,
                PrivateKeyUsagePeriod.class);
        if (extConf == null) {
            return;
        }
        privateKeyUsagePeriod = CertValidity.getInstance(extConf.getValidity());
    }

    private void initPolicyConstraints(ExtensionsType extensionsType) throws CertprofileException {
        ASN1ObjectIdentifier type = Extension.policyConstraints;
        if (!extensionControls.containsKey(type)) {
            return;
        }

        PolicyConstraints extConf = (PolicyConstraints) getExtensionValue(type, extensionsType,
                PolicyConstraints.class);
        if (extConf == null) {
            return;
        }

        ASN1Sequence value = XmlX509CertprofileUtil.buildPolicyConstrains(extConf);
        this.policyConstraints = new ExtensionValue(extensionControls.get(type).isCritical(), value);
    }

    private void initPolicyMappings(ExtensionsType extensionsType) throws CertprofileException {
        ASN1ObjectIdentifier type = Extension.policyMappings;
        if (!extensionControls.containsKey(type)) {
            return;
        }

        PolicyMappings extConf = (PolicyMappings) getExtensionValue(type, extensionsType, PolicyMappings.class);
        if (extConf == null) {
            return;
        }

        org.bouncycastle.asn1.x509.PolicyMappings value = XmlX509CertprofileUtil.buildPolicyMappings(extConf);
        this.policyMappings = new ExtensionValue(extensionControls.get(type).isCritical(), value);
    }

    private void initQcStatements(ExtensionsType extensionsType) throws CertprofileException {
        ASN1ObjectIdentifier type = Extension.qCStatements;
        if (!extensionControls.containsKey(type)) {
            return;
        }

        QcStatements extConf = (QcStatements) getExtensionValue(type, extensionsType, QcStatements.class);

        if (extConf == null) {
            return;
        }

        List<QcStatementType> qcStatementTypes = extConf.getQcStatement();

        this.qcStatementsOption = new ArrayList<>(qcStatementTypes.size());
        Set<String> currencyCodes = new HashSet<>();
        boolean requireInfoFromReq = false;

        for (QcStatementType m : qcStatementTypes) {
            ASN1ObjectIdentifier qcStatementId = new ASN1ObjectIdentifier(m.getStatementId().getValue());
            QcStatementOption qcStatementOption;

            QcStatementValueType statementValue = m.getStatementValue();
            if (statementValue == null) {
                QCStatement qcStatment = new QCStatement(qcStatementId);
                qcStatementOption = new QcStatementOption(qcStatment);
            } else if (statementValue.getQcRetentionPeriod() != null) {
                QCStatement qcStatment = new QCStatement(qcStatementId,
                        new ASN1Integer(statementValue.getQcRetentionPeriod()));
                qcStatementOption = new QcStatementOption(qcStatment);
            } else if (statementValue.getConstant() != null) {
                ASN1Encodable constantStatementValue;
                try {
                    constantStatementValue = new ASN1StreamParser(statementValue.getConstant().getValue())
                            .readObject();
                } catch (IOException ex) {
                    throw new CertprofileException("can not parse the constant value of QcStatement");
                }
                QCStatement qcStatment = new QCStatement(qcStatementId, constantStatementValue);
                qcStatementOption = new QcStatementOption(qcStatment);
            } else if (statementValue.getQcEuLimitValue() != null) {
                QcEuLimitValueType euLimitType = statementValue.getQcEuLimitValue();
                String tmpCurrency = euLimitType.getCurrency().toUpperCase();
                if (currencyCodes.contains(tmpCurrency)) {
                    throw new CertprofileException("Duplicated definition of qcStatments with QCEuLimitValue for "
                            + "the currency " + tmpCurrency);
                }

                Iso4217CurrencyCode currency = StringUtil.isNumber(tmpCurrency)
                        ? new Iso4217CurrencyCode(Integer.parseInt(tmpCurrency))
                        : new Iso4217CurrencyCode(tmpCurrency);

                Range2Type r1 = euLimitType.getAmount();
                Range2Type r2 = euLimitType.getExponent();
                if (r1.getMin() == r1.getMax() && r2.getMin() == r2.getMax()) {
                    MonetaryValue monetaryValue = new MonetaryValue(currency, r1.getMin(), r2.getMin());
                    QCStatement qcStatement = new QCStatement(qcStatementId, monetaryValue);
                    qcStatementOption = new QcStatementOption(qcStatement);
                } else {
                    MonetaryValueOption monetaryValueOption = new MonetaryValueOption(currency, r1, r2);
                    qcStatementOption = new QcStatementOption(qcStatementId, monetaryValueOption);
                    requireInfoFromReq = true;
                }
                currencyCodes.add(tmpCurrency);
            } else if (statementValue.getPdsLocations() != null) {
                ASN1EncodableVector vec = new ASN1EncodableVector();
                for (PdsLocationType pl : statementValue.getPdsLocations().getPdsLocation()) {
                    ASN1EncodableVector vec2 = new ASN1EncodableVector();
                    vec2.add(new DERIA5String(pl.getUrl()));
                    String lang = pl.getLanguage();
                    if (lang.length() != 2) {
                        throw new RuntimeException("invalid language '" + lang + "'");
                    }
                    vec2.add(new DERPrintableString(lang));
                    DERSequence seq = new DERSequence(vec2);
                    vec.add(seq);
                }
                QCStatement qcStatement = new QCStatement(qcStatementId, new DERSequence(vec));
                qcStatementOption = new QcStatementOption(qcStatement);
            } else {
                throw new RuntimeException("unknown value of qcStatment");
            }

            this.qcStatementsOption.add(qcStatementOption);
        } // end for

        if (requireInfoFromReq) {
            return;
        }

        ASN1EncodableVector vec = new ASN1EncodableVector();
        for (QcStatementOption m : qcStatementsOption) {
            if (m.getStatement() == null) {
                throw new RuntimeException("should not reach here");
            }
            vec.add(m.getStatement());
        }
        ASN1Sequence seq = new DERSequence(vec);
        qcStatments = new ExtensionValue(extensionControls.get(type).isCritical(), seq);
        qcStatementsOption = null;
    }

    private void initRestriction(ExtensionsType extensionsType) throws CertprofileException {
        ASN1ObjectIdentifier type = ObjectIdentifiers.id_extension_restriction;
        if (!extensionControls.containsKey(type)) {
            return;
        }

        Restriction extConf = (Restriction) getExtensionValue(type, extensionsType, Restriction.class);
        if (extConf == null) {
            return;
        }

        DirectoryStringType stringType = XmlX509CertprofileUtil.convertDirectoryStringType(extConf.getType());
        ASN1Encodable extValue = stringType.createDirectoryString(extConf.getText());
        restriction = new ExtensionValue(extensionControls.get(type).isCritical(), extValue);
    }

    private void initSmimeCapabilities(ExtensionsType extensionsType) throws CertprofileException {
        ASN1ObjectIdentifier type = ObjectIdentifiers.id_smimeCapabilities;
        if (!extensionControls.containsKey(type)) {
            return;
        }

        SMIMECapabilities extConf = (SMIMECapabilities) getExtensionValue(type, extensionsType,
                SMIMECapabilities.class);
        if (extConf == null) {
            return;
        }

        List<SMIMECapability> list = extConf.getSMIMECapability();

        ASN1EncodableVector vec = new ASN1EncodableVector();
        for (SMIMECapability m : list) {
            ASN1ObjectIdentifier oid = new ASN1ObjectIdentifier(m.getCapabilityID().getValue());
            ASN1Encodable params = null;
            org.xipki.pki.ca.certprofile.x509.jaxb.SMIMECapability.Parameters capParams = m.getParameters();
            if (capParams != null) {
                if (capParams.getInteger() != null) {
                    params = new ASN1Integer(capParams.getInteger());
                } else if (capParams.getBase64Binary() != null) {
                    params = readAsn1Encodable(capParams.getBase64Binary().getValue());
                }
            }
            org.bouncycastle.asn1.smime.SMIMECapability cap = new org.bouncycastle.asn1.smime.SMIMECapability(oid,
                    params);
            vec.add(cap);
        }

        ASN1Encodable extValue = new DERSequence(vec);
        smimeCapabilities = new ExtensionValue(extensionControls.get(type).isCritical(), extValue);
    }

    private void initSubjectAlternativeName(ExtensionsType extensionsType) throws CertprofileException {
        ASN1ObjectIdentifier type = Extension.subjectAlternativeName;
        if (!extensionControls.containsKey(type)) {
            return;
        }

        SubjectAltName extConf = (SubjectAltName) getExtensionValue(type, extensionsType, SubjectAltName.class);
        if (extConf == null) {
            return;
        }

        this.subjectAltNameModes = XmlX509CertprofileUtil.buildGeneralNameMode(extConf);
    }

    private void initSubjectInfoAccess(ExtensionsType extensionsType) throws CertprofileException {
        ASN1ObjectIdentifier type = Extension.subjectInfoAccess;
        if (!extensionControls.containsKey(type)) {
            return;
        }

        SubjectInfoAccess extConf = (SubjectInfoAccess) getExtensionValue(type, extensionsType,
                SubjectInfoAccess.class);
        if (extConf == null) {
            return;
        }

        List<Access> list = extConf.getAccess();
        this.subjectInfoAccessModes = new HashMap<>();
        for (Access entry : list) {
            this.subjectInfoAccessModes.put(new ASN1ObjectIdentifier(entry.getAccessMethod().getValue()),
                    XmlX509CertprofileUtil.buildGeneralNameMode(entry.getAccessLocation()));
        }
    }

    private void initTlsFeature(ExtensionsType extensionsType) throws CertprofileException {
        ASN1ObjectIdentifier type = ObjectIdentifiers.id_pe_tlsfeature;
        if (!extensionControls.containsKey(type)) {
            return;
        }

        TlsFeature extConf = (TlsFeature) getExtensionValue(type, extensionsType, TlsFeature.class);

        if (extConf == null) {
            return;
        }

        List<Integer> features = new ArrayList<>(extConf.getFeature().size());
        for (IntWithDescType m : extConf.getFeature()) {
            int value = m.getValue();
            if (value < 0 || value > 65535) {
                throw new CertprofileException("invalid TLS feature (extensionType) " + value);
            }
            features.add(value);
        }
        Collections.sort(features);

        ASN1EncodableVector vec = new ASN1EncodableVector();
        for (Integer m : features) {
            vec.add(new ASN1Integer(m));
        }
        ASN1Encodable extValue = new DERSequence(vec);
        tlsFeature = new ExtensionValue(extensionControls.get(type).isCritical(), extValue);
    }

    private void initValidityModel(ExtensionsType extensionsType) throws CertprofileException {
        ASN1ObjectIdentifier type = ObjectIdentifiers.id_extension_validityModel;
        if (!extensionControls.containsKey(type)) {
            return;
        }

        ValidityModel extConf = (ValidityModel) getExtensionValue(type, extensionsType, ValidityModel.class);
        if (extConf == null) {
            return;
        }

        ASN1ObjectIdentifier oid = new ASN1ObjectIdentifier(extConf.getModelId().getValue());
        ASN1Encodable extValue = new DERSequence(oid);
        validityModel = new ExtensionValue(extensionControls.get(type).isCritical(), extValue);
    }

    private void initSubjectDirAttrs(ExtensionsType extensionsType) throws CertprofileException {
        ASN1ObjectIdentifier type = Extension.subjectDirectoryAttributes;
        if (!extensionControls.containsKey(type)) {
            return;
        }

        SubjectDirectoryAttributs extConf = (SubjectDirectoryAttributs) getExtensionValue(type, extensionsType,
                SubjectDirectoryAttributs.class);
        if (extConf == null) {
            return;
        }

        List<ASN1ObjectIdentifier> types = XmlX509CertprofileUtil.toOidList(extConf.getType());
        subjectDirAttrsControl = new SubjectDirectoryAttributesControl(types);
    }

    @Override
    public CertValidity getValidity() {
        return validity;
    }

    @Override
    public String getParameter(final String paramName) {
        return (parameters == null) ? null : parameters.get(paramName);
    }

    @Override
    public ExtensionValues getExtensions(final Map<ASN1ObjectIdentifier, ExtensionControl> extensionOccurences,
            final X500Name requestedSubject, final X500Name grantedSubject, final Extensions requestedExtensions,
            final Date notBefore, final Date notAfter) throws CertprofileException, BadCertTemplateException {
        ExtensionValues values = new ExtensionValues();
        if (CollectionUtil.isEmpty(extensionOccurences)) {
            return values;
        }

        ParamUtil.requireNonNull("requestedSubject", requestedSubject);
        ParamUtil.requireNonNull("notBefore", notBefore);
        ParamUtil.requireNonNull("notAfter", notAfter);

        Set<ASN1ObjectIdentifier> occurences = new HashSet<>(extensionOccurences.keySet());

        // AuthorityKeyIdentifier
        // processed by the CA

        // SubjectKeyIdentifier
        // processed by the CA

        // KeyUsage
        // processed by the CA

        // CertificatePolicies
        ASN1ObjectIdentifier type = Extension.certificatePolicies;
        if (certificatePolicies != null) {
            if (occurences.remove(type)) {
                values.addExtension(type, certificatePolicies);
            }
        }

        // Policy Mappings
        type = Extension.policyMappings;
        if (policyMappings != null) {
            if (occurences.remove(type)) {
                values.addExtension(type, policyMappings);
            }
        }

        // SubjectAltName
        type = Extension.subjectAlternativeName;
        if (occurences.contains(type)) {
            GeneralNames genNames = createRequestedSubjectAltNames(requestedSubject, grantedSubject,
                    requestedExtensions);
            if (genNames != null) {
                ExtensionValue value = new ExtensionValue(extensionControls.get(type).isCritical(), genNames);
                values.addExtension(type, value);
                occurences.remove(type);
            }
        }

        // IssuerAltName
        // processed by the CA

        // Subject Directory Attributes
        type = Extension.subjectDirectoryAttributes;
        if (occurences.contains(type) && subjectDirAttrsControl != null) {
            Extension extension = (requestedExtensions == null) ? null : requestedExtensions.getExtension(type);
            if (extension == null) {
                throw new BadCertTemplateException(
                        "no SubjectDirecotryAttributes extension is contained in the request");
            }

            ASN1GeneralizedTime dateOfBirth = null;
            String placeOfBirth = null;
            String gender = null;
            List<String> countryOfCitizenshipList = new LinkedList<>();
            List<String> countryOfResidenceList = new LinkedList<>();
            Map<ASN1ObjectIdentifier, List<ASN1Encodable>> otherAttrs = new HashMap<>();

            Vector<?> reqSubDirAttrs = SubjectDirectoryAttributes.getInstance(extension.getParsedValue())
                    .getAttributes();
            final int n = reqSubDirAttrs.size();
            for (int i = 0; i < n; i++) {
                Attribute attr = (Attribute) reqSubDirAttrs.get(i);
                ASN1ObjectIdentifier attrType = attr.getAttrType();
                ASN1Encodable attrVal = attr.getAttributeValues()[0];

                if (ObjectIdentifiers.DN_DATE_OF_BIRTH.equals(attrType)) {
                    dateOfBirth = ASN1GeneralizedTime.getInstance(attrVal);
                } else if (ObjectIdentifiers.DN_PLACE_OF_BIRTH.equals(attrType)) {
                    placeOfBirth = DirectoryString.getInstance(attrVal).getString();
                } else if (ObjectIdentifiers.DN_GENDER.equals(attrType)) {
                    gender = DERPrintableString.getInstance(attrVal).getString();
                } else if (ObjectIdentifiers.DN_COUNTRY_OF_CITIZENSHIP.equals(attrType)) {
                    String country = DERPrintableString.getInstance(attrVal).getString();
                    countryOfCitizenshipList.add(country);
                } else if (ObjectIdentifiers.DN_COUNTRY_OF_RESIDENCE.equals(attrType)) {
                    String country = DERPrintableString.getInstance(attrVal).getString();
                    countryOfResidenceList.add(country);
                } else {
                    List<ASN1Encodable> otherAttrVals = otherAttrs.get(attrType);
                    if (otherAttrVals == null) {
                        otherAttrVals = new LinkedList<>();
                        otherAttrs.put(attrType, otherAttrVals);
                    }
                    otherAttrVals.add(attrVal);
                }
            }

            Vector<Attribute> attrs = new Vector<>();
            for (ASN1ObjectIdentifier attrType : subjectDirAttrsControl.getTypes()) {
                if (ObjectIdentifiers.DN_DATE_OF_BIRTH.equals(attrType)) {
                    if (dateOfBirth != null) {
                        String timeStirng = dateOfBirth.getTimeString();
                        if (!SubjectDnSpec.PATTERN_DATE_OF_BIRTH.matcher(timeStirng).matches()) {
                            throw new BadCertTemplateException("invalid dateOfBirth " + timeStirng);
                        }
                        attrs.add(new Attribute(attrType, new DERSet(dateOfBirth)));
                        continue;
                    }
                } else if (ObjectIdentifiers.DN_PLACE_OF_BIRTH.equals(attrType)) {
                    if (placeOfBirth != null) {
                        ASN1Encodable attrVal = new DERUTF8String(placeOfBirth);
                        attrs.add(new Attribute(attrType, new DERSet(attrVal)));
                        continue;
                    }
                } else if (ObjectIdentifiers.DN_GENDER.equals(attrType)) {
                    if (gender != null && !gender.isEmpty()) {
                        char ch = gender.charAt(0);
                        if (!(gender.length() == 1 && (ch == 'f' || ch == 'F' || ch == 'm' || ch == 'M'))) {
                            throw new BadCertTemplateException("invalid gender " + gender);
                        }
                        ASN1Encodable attrVal = new DERPrintableString(gender);
                        attrs.add(new Attribute(attrType, new DERSet(attrVal)));
                        continue;
                    }
                } else if (ObjectIdentifiers.DN_COUNTRY_OF_CITIZENSHIP.equals(attrType)) {
                    if (!countryOfCitizenshipList.isEmpty()) {
                        for (String country : countryOfCitizenshipList) {
                            if (!SubjectDnSpec.isValidCountryAreaCode(country)) {
                                throw new BadCertTemplateException("invalid countryOfCitizenship code " + country);
                            }
                            ASN1Encodable attrVal = new DERPrintableString(country);
                            attrs.add(new Attribute(attrType, new DERSet(attrVal)));
                        }
                        continue;
                    }
                } else if (ObjectIdentifiers.DN_COUNTRY_OF_RESIDENCE.equals(attrType)) {
                    if (!countryOfResidenceList.isEmpty()) {
                        for (String country : countryOfResidenceList) {
                            if (!SubjectDnSpec.isValidCountryAreaCode(country)) {
                                throw new BadCertTemplateException("invalid countryOfResidence code " + country);
                            }
                            ASN1Encodable attrVal = new DERPrintableString(country);
                            attrs.add(new Attribute(attrType, new DERSet(attrVal)));
                        }
                        continue;
                    }
                } else if (otherAttrs.containsKey(attrType)) {
                    for (ASN1Encodable attrVal : otherAttrs.get(attrType)) {
                        attrs.add(new Attribute(attrType, new DERSet(attrVal)));
                    }

                    continue;
                }

                throw new BadCertTemplateException(
                        "could not process type " + attrType.getId() + " in extension SubjectDirectoryAttributes");
            }

            SubjectDirectoryAttributes subjDirAttrs = new SubjectDirectoryAttributes(attrs);
            ExtensionValue extValue = new ExtensionValue(extensionControls.get(type).isCritical(), subjDirAttrs);
            values.addExtension(type, extValue);
            occurences.remove(type);
        }

        // Basic Constraints
        // processed by the CA

        // Name Constraints
        type = Extension.nameConstraints;
        if (nameConstraints != null) {
            if (occurences.remove(type)) {
                values.addExtension(type, nameConstraints);
            }
        }

        // PolicyConstrains
        type = Extension.policyConstraints;
        if (policyConstraints != null) {
            if (occurences.remove(type)) {
                values.addExtension(type, policyConstraints);
            }
        }

        // ExtendedKeyUsage
        // processed by CA

        // CRL Distribution Points
        // processed by the CA

        // Inhibit anyPolicy
        type = Extension.inhibitAnyPolicy;
        if (inhibitAnyPolicy != null) {
            if (occurences.remove(type)) {
                values.addExtension(type, inhibitAnyPolicy);
            }
        }

        // Freshest CRL
        // processed by the CA

        // Authority Information Access
        // processed by the CA

        // Subject Information Access
        // processed by the CA

        // Admission
        type = ObjectIdentifiers.id_extension_admission;
        if (occurences.contains(type) && admission != null) {
            if (admission.isInputFromRequestRequired()) {
                Extension extension = (requestedExtensions == null) ? null : requestedExtensions.getExtension(type);
                if (extension == null) {
                    throw new BadCertTemplateException("No Admission extension is contained in the request");
                }

                Admissions[] reqAdmissions = org.bouncycastle.asn1.isismtt.x509.AdmissionSyntax
                        .getInstance(extension.getParsedValue()).getContentsOfAdmissions();

                final int n = reqAdmissions.length;
                List<List<String>> reqRegNumsList = new ArrayList<>(n);
                for (int i = 0; i < n; i++) {
                    Admissions reqAdmission = reqAdmissions[i];
                    ProfessionInfo[] reqPis = reqAdmission.getProfessionInfos();
                    List<String> reqNums = new ArrayList<>(reqPis.length);
                    reqRegNumsList.add(reqNums);
                    for (ProfessionInfo reqPi : reqPis) {
                        String reqNum = reqPi.getRegistrationNumber();
                        reqNums.add(reqNum);
                    }
                }
                values.addExtension(type, admission.getExtensionValue(reqRegNumsList));
                occurences.remove(type);
            } else {
                values.addExtension(type, admission.getExtensionValue(null));
                occurences.remove(type);
            }
        }

        // OCSP Nocheck
        // processed by the CA

        // restriction
        type = ObjectIdentifiers.id_extension_restriction;
        if (restriction != null) {
            if (occurences.remove(type)) {
                values.addExtension(type, restriction);
            }
        }

        // AdditionalInformation
        type = ObjectIdentifiers.id_extension_additionalInformation;
        if (additionalInformation != null) {
            if (occurences.remove(type)) {
                values.addExtension(type, additionalInformation);
            }
        }

        // ValidityModel
        type = ObjectIdentifiers.id_extension_validityModel;
        if (validityModel != null) {
            if (occurences.remove(type)) {
                values.addExtension(type, validityModel);
            }
        }

        // PrivateKeyUsagePeriod
        type = Extension.privateKeyUsagePeriod;
        if (occurences.contains(type)) {
            Date tmpNotAfter;
            if (privateKeyUsagePeriod == null) {
                tmpNotAfter = notAfter;
            } else {
                tmpNotAfter = privateKeyUsagePeriod.add(notBefore);
                if (tmpNotAfter.after(notAfter)) {
                    tmpNotAfter = notAfter;
                }
            }

            ASN1EncodableVector vec = new ASN1EncodableVector();
            vec.add(new DERTaggedObject(false, 0, new DERGeneralizedTime(notBefore)));
            vec.add(new DERTaggedObject(false, 1, new DERGeneralizedTime(tmpNotAfter)));
            ExtensionValue extValue = new ExtensionValue(extensionControls.get(type).isCritical(),
                    new DERSequence(vec));
            values.addExtension(type, extValue);
            occurences.remove(type);
        }

        // QCStatements
        type = Extension.qCStatements;
        if (occurences.contains(type) && (qcStatments != null || qcStatementsOption != null)) {
            if (qcStatments != null) {
                values.addExtension(type, qcStatments);
                occurences.remove(type);
            } else if (requestedExtensions != null && qcStatementsOption != null) {
                // extract the euLimit data from request
                Extension extension = requestedExtensions.getExtension(type);
                if (extension == null) {
                    throw new BadCertTemplateException("No QCStatement extension is contained in the request");
                }
                ASN1Sequence seq = ASN1Sequence.getInstance(extension.getParsedValue());

                Map<String, int[]> qcEuLimits = new HashMap<>();
                final int n = seq.size();
                for (int i = 0; i < n; i++) {
                    QCStatement stmt = QCStatement.getInstance(seq.getObjectAt(i));
                    if (!ObjectIdentifiers.id_etsi_qcs_QcLimitValue.equals(stmt.getStatementId())) {
                        continue;
                    }

                    MonetaryValue monetaryValue = MonetaryValue.getInstance(stmt.getStatementInfo());
                    int amount = monetaryValue.getAmount().intValue();
                    int exponent = monetaryValue.getExponent().intValue();
                    Iso4217CurrencyCode currency = monetaryValue.getCurrency();
                    String currencyS = currency.isAlphabetic() ? currency.getAlphabetic().toUpperCase()
                            : Integer.toString(currency.getNumeric());
                    qcEuLimits.put(currencyS, new int[] { amount, exponent });
                }

                ASN1EncodableVector vec = new ASN1EncodableVector();
                for (QcStatementOption m : qcStatementsOption) {
                    if (m.getStatement() != null) {
                        vec.add(m.getStatement());
                        continue;
                    }

                    MonetaryValueOption monetaryOption = m.getMonetaryValueOption();
                    String currencyS = monetaryOption.getCurrencyString();
                    int[] limit = qcEuLimits.get(currencyS);
                    if (limit == null) {
                        throw new BadCertTemplateException(
                                "no EuLimitValue is specified for currency '" + currencyS + "'");
                    }

                    int amount = limit[0];
                    Range2Type range = monetaryOption.getAmountRange();
                    if (amount < range.getMin() || amount > range.getMax()) {
                        throw new BadCertTemplateException("amount for currency '" + currencyS + "' is not within ["
                                + range.getMin() + ", " + range.getMax() + "]");
                    }

                    int exponent = limit[1];
                    range = monetaryOption.getExponentRange();
                    if (exponent < range.getMin() || exponent > range.getMax()) {
                        throw new BadCertTemplateException("exponent for currency '" + currencyS
                                + "' is not within [" + range.getMin() + ", " + range.getMax() + "]");
                    }

                    MonetaryValue monetaryVale = new MonetaryValue(monetaryOption.getCurrency(), amount, exponent);
                    QCStatement qcStatment = new QCStatement(m.getStatementId(), monetaryVale);
                    vec.add(qcStatment);
                }

                ExtensionValue extValue = new ExtensionValue(extensionControls.get(type).isCritical(),
                        new DERSequence(vec));
                values.addExtension(type, extValue);
                occurences.remove(type);
            } else {
                throw new RuntimeException("should not reach here");
            }
        }

        // BiometricData
        type = Extension.biometricInfo;
        if (occurences.contains(type) && biometricInfo != null) {
            Extension extension = (requestedExtensions == null) ? null : requestedExtensions.getExtension(type);
            if (extension == null) {
                throw new BadCertTemplateException("no biometricInfo extension is contained in the request");
            }
            ASN1Sequence seq = ASN1Sequence.getInstance(extension.getParsedValue());
            final int n = seq.size();
            if (n < 1) {
                throw new BadCertTemplateException("biometricInfo extension in request contains empty sequence");
            }

            ASN1EncodableVector vec = new ASN1EncodableVector();

            for (int i = 0; i < n; i++) {
                BiometricData bd = BiometricData.getInstance(seq.getObjectAt(i));
                TypeOfBiometricData bdType = bd.getTypeOfBiometricData();
                if (!biometricInfo.isTypePermitted(bdType)) {
                    throw new BadCertTemplateException(
                            "biometricInfo[" + i + "].typeOfBiometricData is not permitted");
                }

                ASN1ObjectIdentifier hashAlgo = bd.getHashAlgorithm().getAlgorithm();
                if (!biometricInfo.isHashAlgorithmPermitted(hashAlgo)) {
                    throw new BadCertTemplateException("biometricInfo[" + i + "].hashAlgorithm is not permitted");
                }

                int expHashValueSize;
                try {
                    expHashValueSize = AlgorithmUtil.getHashOutputSizeInOctets(hashAlgo);
                } catch (NoSuchAlgorithmException ex) {
                    throw new CertprofileException("should not happen, unknown hash algorithm " + hashAlgo);
                }

                byte[] hashValue = bd.getBiometricDataHash().getOctets();
                if (hashValue.length != expHashValueSize) {
                    throw new BadCertTemplateException(
                            "biometricInfo[" + i + "].biometricDataHash has incorrect length");
                }

                DERIA5String sourceDataUri = bd.getSourceDataUri();
                switch (biometricInfo.getSourceDataUriOccurrence()) {
                case FORBIDDEN:
                    sourceDataUri = null;
                    break;
                case REQUIRED:
                    if (sourceDataUri == null) {
                        throw new BadCertTemplateException("biometricInfo[" + i
                                + "].sourceDataUri is not specified in request but is required");
                    }
                    break;
                case OPTIONAL:
                    break;
                default:
                    throw new BadCertTemplateException("could not reach here, unknown tripleState");
                }

                AlgorithmIdentifier newHashAlg = new AlgorithmIdentifier(hashAlgo, DERNull.INSTANCE);
                BiometricData newBiometricData = new BiometricData(bdType, newHashAlg,
                        new DEROctetString(hashValue), sourceDataUri);
                vec.add(newBiometricData);
            }

            ExtensionValue extValue = new ExtensionValue(extensionControls.get(type).isCritical(),
                    new DERSequence(vec));
            values.addExtension(type, extValue);
            occurences.remove(type);
        }

        // TlsFeature
        type = ObjectIdentifiers.id_pe_tlsfeature;
        if (tlsFeature != null) {
            if (occurences.remove(type)) {
                values.addExtension(type, tlsFeature);
            }
        }

        // AuthorizationTemplate
        type = ObjectIdentifiers.id_xipki_ext_authorizationTemplate;
        if (authorizationTemplate != null) {
            if (occurences.remove(type)) {
                values.addExtension(type, authorizationTemplate);
            }
        }

        // SMIME
        type = ObjectIdentifiers.id_smimeCapabilities;
        if (smimeCapabilities != null) {
            if (occurences.remove(type)) {
                values.addExtension(type, smimeCapabilities);
            }
        }

        // constant extensions
        if (constantExtensions != null) {
            for (ASN1ObjectIdentifier m : constantExtensions.keySet()) {
                if (!occurences.remove(m)) {
                    continue;
                }

                ExtensionValue extensionValue = constantExtensions.get(m);
                if (extensionValue != null) {
                    values.addExtension(m, extensionValue);
                }
            }
        }

        return values;
    } // method getExtensions

    private GeneralNames createRequestedSubjectAltNames(final X500Name requestedSubject,
            final X500Name grantedSubject, final Extensions requestedExtensions) throws BadCertTemplateException {
        ASN1Encodable extValue = (requestedExtensions == null) ? null
                : requestedExtensions.getExtensionParsedValue(Extension.subjectAlternativeName);

        if (extValue == null && subjectToSubjectAltNameModes == null) {
            return null;
        }

        GeneralNames reqNames = (extValue == null) ? null : GeneralNames.getInstance(extValue);
        if (subjectAltNameModes == null && subjectToSubjectAltNameModes == null) {
            return reqNames;
        }

        List<GeneralName> grantedNames = new LinkedList<>();
        // copy the required attributes of Subject
        if (subjectToSubjectAltNameModes != null) {
            for (ASN1ObjectIdentifier attrType : subjectToSubjectAltNameModes.keySet()) {
                GeneralNameTag tag = subjectToSubjectAltNameModes.get(attrType);

                RDN[] rdns = grantedSubject.getRDNs(attrType);
                if (rdns == null) {
                    rdns = requestedSubject.getRDNs(attrType);
                }

                if (rdns == null) {
                    continue;
                }

                for (RDN rdn : rdns) {
                    String rdnValue = X509Util.rdnValueToString(rdn.getFirst().getValue());
                    switch (tag) {
                    case rfc822Name:
                    case dNSName:
                    case uniformResourceIdentifier:
                    case iPAddress:
                    case directoryName:
                    case registeredID:
                        grantedNames.add(new GeneralName(tag.getTag(), rdnValue));
                        break;
                    default:
                        throw new RuntimeException("should not reach here, unknown GeneralName tag " + tag);
                    } // end switch (tag)
                }
            }
        }

        // copy the requested SubjectAltName entries
        if (reqNames != null) {
            GeneralName[] reqL = reqNames.getNames();
            for (int i = 0; i < reqL.length; i++) {
                grantedNames.add(X509CertprofileUtil.createGeneralName(reqL[i], subjectAltNameModes));
            }
        }

        return grantedNames.isEmpty() ? null : new GeneralNames(grantedNames.toArray(new GeneralName[0]));
    }

    @Override
    public Set<KeyUsageControl> getKeyUsage() {
        return keyusages;
    }

    @Override
    public Set<ExtKeyUsageControl> getExtendedKeyUsages() {
        return extendedKeyusages;
    }

    @Override
    public X509CertLevel getCertLevel() {
        return certLevel;
    }

    @Override
    public Integer getPathLenBasicConstraint() {
        return pathLen;
    }

    @Override
    public AuthorityInfoAccessControl getAiaControl() {
        return aiaControl;
    }

    @Override
    public boolean hasMidnightNotBefore() {
        return notBeforeMidnight;
    }

    @Override
    public Map<ASN1ObjectIdentifier, ExtensionControl> getExtensionControls() {
        return extensionControls;
    }

    @Override
    public boolean isOnlyForRa() {
        return raOnly;
    }

    @Override
    public int getMaxCertSize() {
        return (maxSize == null) ? super.getMaxCertSize() : maxSize;
    }

    @Override
    public boolean includeIssuerAndSerialInAki() {
        return includeIssuerAndSerialInAki;
    }

    @Override
    public SubjectControl getSubjectControl() {
        return subjectControl;
    }

    @Override
    public SpecialX509CertprofileBehavior getSpecialCertprofileBehavior() {
        return specialBehavior;
    }

    @Override
    public boolean isDuplicateKeyPermitted() {
        return duplicateKeyPermitted;
    }

    @Override
    public boolean isDuplicateSubjectPermitted() {
        return duplicateSubjectPermitted;
    }

    @Override
    public boolean isSerialNumberInReqPermitted() {
        return serialNumberInReqPermitted;
    }

    @Override
    public Map<ASN1ObjectIdentifier, KeyParametersOption> getKeyAlgorithms() {
        return keyAlgorithms;
    }

    @Override
    public Map<ASN1ObjectIdentifier, Set<GeneralNameMode>> getSubjectInfoAccessModes() {
        return subjectInfoAccessModes;
    }

    @Override
    public X509CertVersion getVersion() {
        return version;
    }

    @Override
    public List<String> getSignatureAlgorithms() {
        return signatureAlgorithms;
    }

    @Override
    public boolean incSerialNumberIfSubjectExists() {
        return incSerialNoIfSubjectExists;
    }

    public ExtensionValue getAdditionalInformation() {
        return additionalInformation;
    }

    public AdmissionSyntaxOption getAdmission() {
        return admission;
    }

    public Map<ASN1ObjectIdentifier, GeneralNameTag> getSubjectToSubjectAltNameModes() {
        return subjectToSubjectAltNameModes;
    }

    public Set<GeneralNameMode> getSubjectAltNameModes() {
        return subjectAltNameModes;
    }

    public ExtensionValue getAuthorizationTemplate() {
        return authorizationTemplate;
    }

    public BiometricInfoOption getBiometricInfo() {
        return biometricInfo;
    }

    public ExtensionValue getCertificatePolicies() {
        return certificatePolicies;
    }

    public Map<ASN1ObjectIdentifier, ExtensionValue> getConstantExtensions() {
        return constantExtensions;
    }

    public Set<ExtKeyUsageControl> getExtendedKeyusages() {
        return extendedKeyusages;
    }

    public boolean isIncludeIssuerAndSerialInAki() {
        return includeIssuerAndSerialInAki;
    }

    public boolean isIncSerialNoIfSubjectExists() {
        return incSerialNoIfSubjectExists;
    }

    public ExtensionValue getInhibitAnyPolicy() {
        return inhibitAnyPolicy;
    }

    public Set<KeyUsageControl> getKeyusages() {
        return keyusages;
    }

    public Integer getMaxSize() {
        return maxSize;
    }

    public ExtensionValue getNameConstraints() {
        return nameConstraints;
    }

    public boolean isNotBeforeMidnight() {
        return notBeforeMidnight;
    }

    public Map<String, String> getParameters() {
        return parameters;
    }

    public Integer getPathLen() {
        return pathLen;
    }

    public ExtensionValue getPolicyConstraints() {
        return policyConstraints;
    }

    public ExtensionValue getPolicyMappings() {
        return policyMappings;
    }

    public CertValidity getPrivateKeyUsagePeriod() {
        return privateKeyUsagePeriod;
    }

    public ExtensionValue getQcStatments() {
        return qcStatments;
    }

    public List<QcStatementOption> getQcStatementsOption() {
        return qcStatementsOption;
    }

    public boolean isRaOnly() {
        return raOnly;
    }

    public ExtensionValue getRestriction() {
        return restriction;
    }

    public ExtensionValue getSmimeCapabilities() {
        return smimeCapabilities;
    }

    public SpecialX509CertprofileBehavior getSpecialBehavior() {
        return specialBehavior;
    }

    public ExtensionValue getTlsFeature() {
        return tlsFeature;
    }

    public ExtensionValue getValidityModel() {
        return validityModel;
    }

    public SubjectDirectoryAttributesControl getSubjectDirAttrsControl() {
        return subjectDirAttrsControl;
    }

    private static Object getExtensionValue(final ASN1ObjectIdentifier type, final ExtensionsType extensionsType,
            final Class<?> expectedClass) throws CertprofileException {
        for (ExtensionType m : extensionsType.getExtension()) {
            if (!m.getType().getValue().equals(type.getId())) {
                continue;
            }

            if (m.getValue() == null || m.getValue().getAny() == null) {
                return null;
            }

            Object obj = m.getValue().getAny();
            if (expectedClass.isAssignableFrom(obj.getClass())) {
                return obj;
            } else if (ConstantExtValue.class.isAssignableFrom(obj.getClass())) {
                // will be processed later
                return null;
            } else {
                String displayName = ObjectIdentifiers.oidToDisplayName(type);
                throw new CertprofileException("the extension configuration for " + displayName
                        + " is not of the expected type " + expectedClass.getName());
            }
        }

        throw new RuntimeException(
                "should not reach here: undefined extension " + ObjectIdentifiers.oidToDisplayName(type));
    } // method getExtensionValue

    private static ASN1Encodable readAsn1Encodable(final byte[] encoded) throws CertprofileException {
        ASN1StreamParser parser = new ASN1StreamParser(encoded);
        try {
            return parser.readObject();
        } catch (IOException ex) {
            throw new CertprofileException("could not parse the constant extension value", ex);
        }
    }

}