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

Java tutorial

Introduction

Here is the source code for org.xipki.ca.certprofile.XmlX509Certprofile.java

Source

/*
 *
 * This file is part of the XiPKI project.
 * Copyright (c) 2014 - 2015 Lijun Liao
 * Author: Lijun Liao
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License version 3
 * as published by the Free Software Foundation with the addition of the
 * following permission added to Section 15 as permitted in Section 7(a):
 * FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY
 * THE AUTHOR LIJUN LIAO. LIJUN LIAO DISCLAIMS THE WARRANTY OF NON INFRINGEMENT
 * OF THIRD PARTY RIGHTS.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU Affero General Public License.
 *
 * You can be released from the requirements of the license by purchasing
 * a commercial license. Buying such a license is mandatory as soon as you
 * develop commercial activities involving the XiPKI software without
 * disclosing the source code of your own applications.
 *
 * For more information, please contact Lijun Liao at this
 * address: lijun.liao@gmail.com
 */

package org.xipki.ca.certprofile;

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

import org.bouncycastle.asn1.ASN1EncodableVector;
import org.bouncycastle.asn1.ASN1Integer;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.ASN1OctetString;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.DEROctetString;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.asn1.isismtt.x509.AdmissionSyntax;
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.Extension;
import org.bouncycastle.asn1.x509.Extensions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xipki.ca.api.BadCertTemplateException;
import org.xipki.ca.api.CertprofileException;
import org.xipki.ca.api.profile.CertValidity;
import org.xipki.ca.api.profile.DirectoryStringType;
import org.xipki.ca.api.profile.ExtensionControl;
import org.xipki.ca.api.profile.ExtensionValue;
import org.xipki.ca.api.profile.ExtensionValues;
import org.xipki.ca.api.profile.GeneralNameMode;
import org.xipki.ca.api.profile.KeyParametersOption;
import org.xipki.ca.api.profile.RDNControl;
import org.xipki.ca.api.profile.SubjectInfo;
import org.xipki.ca.api.profile.x509.AuthorityInfoAccessControl;
import org.xipki.ca.api.profile.x509.BaseX509Certprofile;
import org.xipki.ca.api.profile.x509.CertificatePolicyInformation;
import org.xipki.ca.api.profile.x509.ExtKeyUsageControl;
import org.xipki.ca.api.profile.x509.KeyUsageControl;
import org.xipki.ca.api.profile.x509.SpecialX509CertprofileBehavior;
import org.xipki.ca.api.profile.x509.X509CertUtil;
import org.xipki.ca.api.profile.x509.X509CertVersion;
import org.xipki.ca.certprofile.x509.jaxb.Admission;
import org.xipki.ca.certprofile.x509.jaxb.AuthorityInfoAccess;
import org.xipki.ca.certprofile.x509.jaxb.AuthorityKeyIdentifier;
import org.xipki.ca.certprofile.x509.jaxb.BasicConstraints;
import org.xipki.ca.certprofile.x509.jaxb.CertificatePolicies;
import org.xipki.ca.certprofile.x509.jaxb.ConstantExtValue;
import org.xipki.ca.certprofile.x509.jaxb.ExtendedKeyUsage;
import org.xipki.ca.certprofile.x509.jaxb.ExtensionType;
import org.xipki.ca.certprofile.x509.jaxb.ExtensionsType;
import org.xipki.ca.certprofile.x509.jaxb.InhibitAnyPolicy;
import org.xipki.ca.certprofile.x509.jaxb.KeyUsage;
import org.xipki.ca.certprofile.x509.jaxb.NameConstraints;
import org.xipki.ca.certprofile.x509.jaxb.NameValueType;
import org.xipki.ca.certprofile.x509.jaxb.OidWithDescType;
import org.xipki.ca.certprofile.x509.jaxb.PolicyConstraints;
import org.xipki.ca.certprofile.x509.jaxb.PolicyMappings;
import org.xipki.ca.certprofile.x509.jaxb.RdnType;
import org.xipki.ca.certprofile.x509.jaxb.SubjectAltName;
import org.xipki.ca.certprofile.x509.jaxb.SubjectInfoAccess;
import org.xipki.ca.certprofile.x509.jaxb.SubjectInfoAccess.Access;
import org.xipki.ca.certprofile.x509.jaxb.X509ProfileType;
import org.xipki.ca.certprofile.x509.jaxb.X509ProfileType.KeyAlgorithms;
import org.xipki.ca.certprofile.x509.jaxb.X509ProfileType.Parameters;
import org.xipki.ca.certprofile.x509.jaxb.X509ProfileType.Subject;
import org.xipki.common.ObjectIdentifiers;
import org.xipki.common.ParamChecker;
import org.xipki.common.util.AlgorithmUtil;
import org.xipki.common.util.CollectionUtil;
import org.xipki.common.util.LogUtil;
import org.xipki.common.util.StringUtil;
import org.xipki.common.util.X509Util;

/**
 * @author Lijun Liao
 */

public class XmlX509Certprofile extends BaseX509Certprofile {
    private static final Logger LOG = LoggerFactory.getLogger(XmlX509Certprofile.class);

    private SpecialX509CertprofileBehavior specialBehavior;

    private Map<ASN1ObjectIdentifier, KeyParametersOption> keyAlgorithms;

    private Map<ASN1ObjectIdentifier, SubjectDNOption> subjectDNOptions;
    private Set<RDNControl> subjectDNControls;
    private Map<String, String> parameters;
    private Map<ASN1ObjectIdentifier, ExtensionControl> extensionControls;

    private CertValidity validity;
    private X509CertVersion version;
    private List<String> signatureAlgorithms;
    private boolean incSerialNoIfSubjectExists;
    private boolean raOnly;
    private boolean qaOnly;
    private boolean backwardsSubject;
    private boolean ca;
    private boolean duplicateKeyPermitted;
    private boolean duplicateSubjectPermitted;
    private boolean serialNumberInReqPermitted;
    private boolean notBeforeMidnight;
    private boolean includeIssuerAndSerialInAKI;
    private Integer pathLen;
    private AuthorityInfoAccessControl aIAControl;
    private Set<KeyUsageControl> keyusages;
    private Set<ExtKeyUsageControl> extendedKeyusages;
    private Set<GeneralNameMode> allowedSubjectAltNameModes;
    private Map<ASN1ObjectIdentifier, Set<GeneralNameMode>> allowedSubjectInfoAccessModes;

    private ExtensionValue certificatePolicies;
    private ExtensionValue policyMappings;
    private ExtensionValue nameConstraints;
    private ExtensionValue policyConstraints;
    private ExtensionValue inhibitAnyPolicy;
    private ExtensionValue admission;

    private Map<ASN1ObjectIdentifier, ExtensionValue> constantExtensions;

    private void reset() {
        version = null;
        signatureAlgorithms = null;
        keyAlgorithms = null;
        subjectDNOptions = null;
        subjectDNControls = null;
        extensionControls = null;
        validity = null;
        notBeforeMidnight = false;
        includeIssuerAndSerialInAKI = false;
        incSerialNoIfSubjectExists = false;
        raOnly = false;
        qaOnly = false;
        backwardsSubject = false;
        ca = false;
        duplicateKeyPermitted = true;
        duplicateSubjectPermitted = true;
        serialNumberInReqPermitted = true;
        pathLen = null;
        aIAControl = null;
        keyusages = null;
        extendedKeyusages = null;
        allowedSubjectAltNameModes = null;
        allowedSubjectInfoAccessModes = null;
        certificatePolicies = null;
        nameConstraints = null;
        policyMappings = null;
        inhibitAnyPolicy = null;
        admission = null;
        constantExtensions = null;
    }

    @Override
    public void initialize(final String data) throws CertprofileException {
        ParamChecker.assertNotBlank("data", data);
        reset();
        try {
            doInitialize(data);
        } catch (RuntimeException e) {
            final String message = "RuntimeException";
            if (LOG.isErrorEnabled()) {
                LOG.error(LogUtil.buildExceptionLogFormat(message), e.getClass().getName(), e.getMessage());
            }
            LOG.debug(message, e);
            throw new CertprofileException(
                    "caught RuntimeException while initializing certprofile: " + e.getMessage());
        }
    }

    private void doInitialize(final String data) throws CertprofileException {
        byte[] bytes;
        try {
            bytes = data.getBytes("UTF-8");
        } catch (UnsupportedEncodingException e) {
            bytes = data.getBytes();
        }

        X509ProfileType conf = XmlX509CertprofileUtil.parse(new ByteArrayInputStream(bytes));

        if (conf.getVersion() != null) {
            int intVersion = conf.getVersion().intValue();
            this.version = X509CertVersion.getInstance(intVersion);
            if (this.version == null) {
                throw new CertprofileException("invalid version " + intVersion);
            }
        } else {
            this.version = X509CertVersion.V3;
        }

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

        this.raOnly = conf.isRaOnly();
        this.qaOnly = conf.isQaOnly();

        this.validity = CertValidity.getInstance(conf.getValidity());
        this.ca = conf.isCa();
        this.notBeforeMidnight = "midnight".equalsIgnoreCase(conf.getNotBeforeTime());

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

        if (conf.isDuplicateKey() != null) {
            duplicateKeyPermitted = conf.isDuplicateKey().booleanValue();
        }

        if (conf.isDuplicateSubject() != null) {
            duplicateSubjectPermitted = conf.isDuplicateSubject().booleanValue();
        }

        if (conf.isSerialNumberInReq() != null) {
            serialNumberInReqPermitted = conf.isSerialNumberInReq().booleanValue();
        }

        // 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> tMap = new HashMap<>();
            for (NameValueType nv : confParams.getParameter()) {
                tMap.put(nv.getName(), nv.getValue());
            }
            parameters = Collections.unmodifiableMap(tMap);
        }

        // Subject
        Subject subject = conf.getSubject();
        if (subject != null) {
            this.backwardsSubject = subject.isDnBackwards();
            this.incSerialNoIfSubjectExists = subject.isIncSerialNumber();

            this.subjectDNControls = new HashSet<RDNControl>();
            this.subjectDNOptions = new HashMap<>();

            for (RdnType t : subject.getRdn()) {
                DirectoryStringType directoryStringEnum = XmlX509CertprofileUtil
                        .convertDirectoryStringType(t.getDirectoryStringType());
                ASN1ObjectIdentifier type = new ASN1ObjectIdentifier(t.getType().getValue());
                RDNControl occ = new RDNControl(type, t.getMinOccurs(), t.getMaxOccurs(), directoryStringEnum);
                this.subjectDNControls.add(occ);

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

                SubjectDNOption option = new SubjectDNOption(t.getPrefix(), t.getSuffix(), patterns, t.getMinLen(),
                        t.getMaxLen());
                this.subjectDNOptions.put(type, option);
            }
        }

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

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

        // BasicConstrains
        ASN1ObjectIdentifier type = Extension.basicConstraints;
        if (extensionControls.containsKey(type)) {
            BasicConstraints extConf = (BasicConstraints) getExtensionValue(type, extensionsType,
                    BasicConstraints.class);
            if (extConf != null) {
                this.pathLen = extConf.getPathLen();
            }
        }

        // AuthorityInfoAccess
        type = Extension.authorityInfoAccess;
        if (extensionControls.containsKey(type)) {
            AuthorityInfoAccess extConf = (AuthorityInfoAccess) getExtensionValue(type, extensionsType,
                    AuthorityInfoAccess.class);
            if (extConf != null) {
                Boolean b = extConf.isIncludeCaIssuers();
                boolean includesCaIssuers = b == null ? true : b.booleanValue();

                b = extConf.isIncludeOcsp();
                boolean includesOcsp = b == null ? true : b.booleanValue();

                this.aIAControl = new AuthorityInfoAccessControl(includesCaIssuers, includesOcsp);
            }
        }

        // Extension KeyUsage
        type = Extension.keyUsage;
        if (extensionControls.containsKey(type)) {
            KeyUsage extConf = (KeyUsage) getExtensionValue(type, extensionsType, KeyUsage.class);
            if (extConf != null) {
                this.keyusages = XmlX509CertprofileUtil.buildKeyUsageOptions(extConf);
            }
        }

        // ExtendedKeyUsage
        type = Extension.extendedKeyUsage;
        if (extensionControls.containsKey(type)) {
            ExtendedKeyUsage extConf = (ExtendedKeyUsage) getExtensionValue(type, extensionsType,
                    ExtendedKeyUsage.class);
            if (extConf != null) {
                this.extendedKeyusages = XmlX509CertprofileUtil.buildExtKeyUsageOptions(extConf);
            }
        }

        // AuthorityKeyIdentifier
        type = Extension.authorityKeyIdentifier;
        if (extensionControls.containsKey(type)) {
            AuthorityKeyIdentifier extConf = (AuthorityKeyIdentifier) getExtensionValue(type, extensionsType,
                    AuthorityKeyIdentifier.class);
            if (extConf != null) {
                this.includeIssuerAndSerialInAKI = extConf.isIncludeIssuerAndSerial();
            }
        }

        // Certificate Policies
        type = Extension.certificatePolicies;
        if (extensionControls.containsKey(type)) {
            CertificatePolicies extConf = (CertificatePolicies) getExtensionValue(type, extensionsType,
                    CertificatePolicies.class);
            if (extConf != null) {
                List<CertificatePolicyInformation> policyInfos = XmlX509CertprofileUtil
                        .buildCertificatePolicies(extConf);
                org.bouncycastle.asn1.x509.CertificatePolicies value = X509CertUtil
                        .createCertificatePolicies(policyInfos);
                this.certificatePolicies = new ExtensionValue(extensionControls.get(type).isCritical(), value);
            }
        }

        // Policy Mappings
        type = Extension.policyMappings;
        if (extensionControls.containsKey(type)) {
            PolicyMappings extConf = (PolicyMappings) getExtensionValue(type, extensionsType, PolicyMappings.class);
            if (extConf != null) {
                org.bouncycastle.asn1.x509.PolicyMappings value = XmlX509CertprofileUtil
                        .buildPolicyMappings(extConf);
                this.policyMappings = new ExtensionValue(extensionControls.get(type).isCritical(), value);
            }
        }

        // Name Constrains
        type = Extension.nameConstraints;
        if (extensionControls.containsKey(type)) {
            NameConstraints extConf = (NameConstraints) getExtensionValue(type, extensionsType,
                    NameConstraints.class);
            if (extConf != null) {
                org.bouncycastle.asn1.x509.NameConstraints value = XmlX509CertprofileUtil
                        .buildNameConstrains(extConf);
                this.nameConstraints = new ExtensionValue(extensionControls.get(type).isCritical(), value);
            }
        }

        // Policy Constraints
        type = Extension.policyConstraints;
        if (extensionControls.containsKey(type)) {
            PolicyConstraints extConf = (PolicyConstraints) getExtensionValue(type, extensionsType,
                    PolicyConstraints.class);
            if (extConf != null) {
                ASN1Sequence value = XmlX509CertprofileUtil.buildPolicyConstrains(extConf);
                this.policyConstraints = new ExtensionValue(extensionControls.get(type).isCritical(), value);
            }
        }

        // Inhibit anyPolicy
        type = Extension.inhibitAnyPolicy;
        if (extensionControls.containsKey(type)) {
            InhibitAnyPolicy extConf = (InhibitAnyPolicy) getExtensionValue(type, extensionsType,
                    InhibitAnyPolicy.class);
            if (extConf != null) {
                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);
            }
        }

        // admission
        type = ObjectIdentifiers.id_extension_admission;
        if (extensionControls.containsKey(type)) {
            Admission extConf = (Admission) getExtensionValue(type, extensionsType, Admission.class);
            if (extConf != null) {
                List<ASN1ObjectIdentifier> professionOIDs;
                List<String> professionItems;

                List<String> items = type == null ? null : extConf.getProfessionItem();
                professionItems = CollectionUtil.unmodifiableList(items, true, true);

                List<OidWithDescType> oidWithDescs = (type == null) ? null : extConf.getProfessionOid();
                professionOIDs = XmlX509CertprofileUtil.toOIDList(oidWithDescs);

                this.admission = createAdmission(extensionControls.get(type).isCritical(), professionOIDs,
                        professionItems, extConf.getRegistrationNumber(), extConf.getAddProfessionInfo());
            }
        }

        // SubjectAltNameMode
        type = Extension.subjectAlternativeName;
        if (extensionControls.containsKey(type)) {
            SubjectAltName extConf = (SubjectAltName) getExtensionValue(type, extensionsType, SubjectAltName.class);
            if (extConf != null) {
                this.allowedSubjectAltNameModes = XmlX509CertprofileUtil.buildGeneralNameMode(extConf);
            }
        }

        // SubjectInfoAccess
        type = Extension.subjectInfoAccess;
        if (extensionControls.containsKey(type)) {
            SubjectInfoAccess extConf = (SubjectInfoAccess) getExtensionValue(type, extensionsType,
                    SubjectInfoAccess.class);
            if (extConf != null) {
                List<Access> list = extConf.getAccess();
                this.allowedSubjectInfoAccessModes = new HashMap<>();
                for (Access entry : list) {
                    this.allowedSubjectInfoAccessModes.put(
                            new ASN1ObjectIdentifier(entry.getAccessMethod().getValue()),
                            XmlX509CertprofileUtil.buildGeneralNameMode(entry.getAccessLocation()));
                }
            }
        }

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

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

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

    @Override
    public SubjectInfo getSubject(final X500Name requestedSubject)
            throws CertprofileException, BadCertTemplateException {
        verifySubjectDNOccurence(requestedSubject);
        checkSubjectContent(requestedSubject);

        RDN[] requstedRDNs = requestedSubject.getRDNs();
        Set<RDNControl> occurences = getSubjectDNControls();
        List<RDN> rdns = new LinkedList<>();
        List<ASN1ObjectIdentifier> types = backwardsSubject() ? ObjectIdentifiers.getBackwardDNs()
                : ObjectIdentifiers.getForwardDNs();

        for (ASN1ObjectIdentifier type : types) {
            if (Extension.subjectAlternativeName.equals(type) || Extension.subjectInfoAccess.equals(type)) {
                continue;
            }

            RDNControl control = null;
            if (occurences != null) {
                control = getRDNControl(occurences, type);
                if (control == null || control.getMaxOccurs() < 1) {
                    continue;
                }
            }

            RDN[] thisRDNs = getRDNs(requstedRDNs, type);
            int n = thisRDNs == null ? 0 : thisRDNs.length;
            if (n == 0) {
                continue;
            }

            if (n == 1) {
                String value = X509Util.rdnValueToString(thisRDNs[0].getFirst().getValue());
                rdns.add(createSubjectRDN(value, type, control, 0));
            } else {
                String[] values = new String[n];
                for (int i = 0; i < n; i++) {
                    values[i] = X509Util.rdnValueToString(thisRDNs[i].getFirst().getValue());
                }
                values = sortRDNs(type, values);

                int i = 0;
                for (String value : values) {
                    rdns.add(createSubjectRDN(value, type, control, i++));
                }
            }
        }

        X500Name grantedSubject = new X500Name(rdns.toArray(new RDN[0]));
        return new SubjectInfo(grantedSubject, null);
    }

    @Override
    protected RDN createSubjectRDN(final String text, final ASN1ObjectIdentifier type, final RDNControl rdnControl,
            final int index) throws BadCertTemplateException {
        String ttext = text.trim();

        SubjectDNOption option = subjectDNOptions.get(type);
        if (option != null) {
            String prefix = option.getPrefix();
            String suffix = option.getSufix();

            if (prefix != null || suffix != null) {
                String _text = ttext.toLowerCase();
                if (prefix != null && _text.startsWith(prefix.toLowerCase())) {
                    ttext = ttext.substring(prefix.length());
                    _text = ttext.toLowerCase();
                }

                if (suffix != null && _text.endsWith(suffix.toLowerCase())) {
                    ttext = ttext.substring(0, ttext.length() - suffix.length());
                }
            }

            List<Pattern> patterns = option.getPatterns();
            if (patterns != null) {
                Pattern p = patterns.get(index);
                if (p.matcher(ttext).matches() == false) {
                    throw new BadCertTemplateException("invalid subject " + ObjectIdentifiers.oidToDisplayName(type)
                            + " '" + ttext + "' against regex '" + p.pattern() + "'");
                }
            }

            StringBuilder sb = new StringBuilder();
            if (prefix != null) {
                sb.append(prefix);
            }
            sb.append(ttext);
            if (suffix != null) {
                sb.append(suffix);
            }
            ttext = sb.toString();

            int len = ttext.length();
            Integer minLen = option.getMinLen();
            if (minLen != null && len < minLen) {
                throw new BadCertTemplateException("subject " + ObjectIdentifiers.oidToDisplayName(type) + " '"
                        + ttext + "' is too short (length (" + len + ") < minLen (" + minLen + ")");
            }

            Integer maxLen = option.getMaxLen();
            if (maxLen != null && len > maxLen) {
                throw new BadCertTemplateException("subject " + ObjectIdentifiers.oidToDisplayName(type) + " '"
                        + ttext + "' is too long (length (" + len + ") > maxLen (" + maxLen + ")");
            }
        }

        return super.createSubjectRDN(ttext, type, rdnControl, index);
    }

    @Override
    protected String[] sortRDNs(final ASN1ObjectIdentifier type, final String[] values) {
        SubjectDNOption option = subjectDNOptions.get(type);
        if (option == null) {
            return values;
        }

        List<Pattern> patterns = option.getPatterns();
        if (CollectionUtil.isEmpty(patterns)) {
            return values;
        }

        List<String> result = new ArrayList<>(values.length);
        for (Pattern p : patterns) {
            for (String value : values) {
                if (result.contains(value) == false && p.matcher(value).matches()) {
                    result.add(value);
                }
            }
        }
        for (String value : values) {
            if (result.contains(value) == false) {
                result.add(value);
            }
        }

        return result.toArray(new String[0]);
    }

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

        Map<ASN1ObjectIdentifier, ExtensionControl> occurences = new HashMap<>(extensionOccurences);

        // AuthorityKeyIdentifier
        // processed by the CA

        // SubjectKeyIdentifier
        // processed by the CA

        // KeyUsage
        // processed by the CA

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

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

        // SubjectAltName
        // processed by the CA

        // IssuerAltName
        // processed by the CA

        // Subject Directory Attributes
        // Will not supported

        // Basic Constraints
        // processed by the CA

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

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

        // ExtendedKeyUsage
        // processed by CA

        // CRL Distribution Points
        // processed by the CA

        // Inhibit anyPolicy
        type = Extension.inhibitAnyPolicy;
        if (inhibitAnyPolicy != null && occurences.remove(type) != null) {
            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 (admission != null && occurences.remove(type) != null) {
            values.addExtension(type, admission);
        }

        // OCSP Nocheck
        // processed by the CA

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

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

        return values;
    }

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

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

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

    @Override
    public boolean isCA() {
        return ca;
    }

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

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

    @Override
    public boolean isOnlyForQA() {
        return qaOnly;
    }

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

    @Override
    public Set<RDNControl> getSubjectDNControls() {
        return subjectDNControls;
    }

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

    private ExtensionValue createAdmission(final boolean critical, final List<ASN1ObjectIdentifier> professionOIDs,
            final List<String> professionItems, final String registrationNumber, final byte[] addProfessionInfo)
            throws CertprofileException {
        if (CollectionUtil.isEmpty(professionItems) && CollectionUtil.isEmpty(professionOIDs)
                && StringUtil.isBlank(registrationNumber)
                && (addProfessionInfo == null || addProfessionInfo.length == 0)) {
            return null;
        }

        DirectoryString[] _professionItems = null;
        if (professionItems != null && professionItems.size() > 0) {
            int n = professionItems.size();
            _professionItems = new DirectoryString[n];
            for (int i = 0; i < n; i++) {
                _professionItems[i] = new DirectoryString(professionItems.get(i));
            }
        }

        ASN1ObjectIdentifier[] _professionOIDs = null;
        if (professionOIDs != null && professionOIDs.size() > 0) {
            _professionOIDs = professionOIDs.toArray(new ASN1ObjectIdentifier[0]);
        }

        ASN1OctetString _addProfessionInfo = null;
        if (addProfessionInfo != null && addProfessionInfo.length > 0) {
            _addProfessionInfo = new DEROctetString(addProfessionInfo);
        }

        ProfessionInfo professionInfo = new ProfessionInfo(null, _professionItems, _professionOIDs,
                registrationNumber, _addProfessionInfo);

        Admissions admissions = new Admissions(null, null, new ProfessionInfo[] { professionInfo });

        ASN1EncodableVector vector = new ASN1EncodableVector();
        vector.add(admissions);

        AdmissionSyntax value = new AdmissionSyntax(null, new DERSequence(vector));
        return new ExtensionValue(critical, value);
    }

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

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

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

    @Override
    public Set<GeneralNameMode> getSubjectAltNameModes() {
        return allowedSubjectAltNameModes;
    }

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

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

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

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

    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()) == false) {
                continue;
            }

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

            Object o = m.getValue().getAny();
            if (expectedClass.isAssignableFrom(o.getClass())) {
                return o;
            } else if (ConstantExtValue.class.isAssignableFrom(o.getClass())) {
                // will be processed later
            } 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));
    }

}