org.xipki.pki.ca.qa.ExtensionsChecker.java Source code

Java tutorial

Introduction

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

import java.io.IOException;
import java.math.BigInteger;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
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 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.ASN1OctetString;
import org.bouncycastle.asn1.ASN1Primitive;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.ASN1StreamParser;
import org.bouncycastle.asn1.ASN1String;
import org.bouncycastle.asn1.ASN1TaggedObject;
import org.bouncycastle.asn1.DERBMPString;
import org.bouncycastle.asn1.DERIA5String;
import org.bouncycastle.asn1.DEROctetString;
import org.bouncycastle.asn1.DERPrintableString;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.asn1.DERT61String;
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.AccessDescription;
import org.bouncycastle.asn1.x509.Attribute;
import org.bouncycastle.asn1.x509.AuthorityInformationAccess;
import org.bouncycastle.asn1.x509.AuthorityKeyIdentifier;
import org.bouncycastle.asn1.x509.BasicConstraints;
import org.bouncycastle.asn1.x509.CRLDistPoint;
import org.bouncycastle.asn1.x509.CertPolicyId;
import org.bouncycastle.asn1.x509.Certificate;
import org.bouncycastle.asn1.x509.DistributionPoint;
import org.bouncycastle.asn1.x509.DistributionPointName;
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.GeneralSubtree;
import org.bouncycastle.asn1.x509.KeyPurposeId;
import org.bouncycastle.asn1.x509.PolicyInformation;
import org.bouncycastle.asn1.x509.PolicyQualifierId;
import org.bouncycastle.asn1.x509.PolicyQualifierInfo;
import org.bouncycastle.asn1.x509.SubjectDirectoryAttributes;
import org.bouncycastle.asn1.x509.SubjectKeyIdentifier;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.asn1.x509.UserNotice;
import org.bouncycastle.asn1.x509.X509ObjectIdentifiers;
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.bouncycastle.util.encoders.Hex;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xipki.commons.common.qa.ValidationIssue;
import org.xipki.commons.common.util.CollectionUtil;
import org.xipki.commons.common.util.CompareUtil;
import org.xipki.commons.common.util.LogUtil;
import org.xipki.commons.common.util.ParamUtil;
import org.xipki.commons.security.ExtensionExistence;
import org.xipki.commons.security.HashAlgoType;
import org.xipki.commons.security.KeyUsage;
import org.xipki.commons.security.ObjectIdentifiers;
import org.xipki.commons.security.util.X509Util;
import org.xipki.pki.ca.api.BadCertTemplateException;
import org.xipki.pki.ca.api.profile.CertValidity;
import org.xipki.pki.ca.api.profile.CertprofileException;
import org.xipki.pki.ca.api.profile.ExtensionControl;
import org.xipki.pki.ca.api.profile.GeneralNameMode;
import org.xipki.pki.ca.api.profile.GeneralNameTag;
import org.xipki.pki.ca.api.profile.Range;
import org.xipki.pki.ca.api.profile.x509.AuthorityInfoAccessControl;
import org.xipki.pki.ca.api.profile.x509.ExtKeyUsageControl;
import org.xipki.pki.ca.api.profile.x509.KeyUsageControl;
import org.xipki.pki.ca.api.profile.x509.SubjectDirectoryAttributesControl;
import org.xipki.pki.ca.api.profile.x509.SubjectDnSpec;
import org.xipki.pki.ca.api.profile.x509.X509CertLevel;
import org.xipki.pki.ca.certprofile.BiometricInfoOption;
import org.xipki.pki.ca.certprofile.XmlX509Certprofile;
import org.xipki.pki.ca.certprofile.XmlX509CertprofileUtil;
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.AuthorizationTemplate;
import org.xipki.pki.ca.certprofile.x509.jaxb.ConstantExtValue;
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.PdsLocationType;
import org.xipki.pki.ca.certprofile.x509.jaxb.PdsLocationsType;
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.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.RangeType;
import org.xipki.pki.ca.certprofile.x509.jaxb.RangesType;
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.TlsFeature;
import org.xipki.pki.ca.certprofile.x509.jaxb.TripleState;
import org.xipki.pki.ca.certprofile.x509.jaxb.ValidityModel;
import org.xipki.pki.ca.certprofile.x509.jaxb.X509ProfileType;
import org.xipki.pki.ca.qa.internal.QaAuthorizationTemplate;
import org.xipki.pki.ca.qa.internal.QaCertificatePolicies;
import org.xipki.pki.ca.qa.internal.QaCertificatePolicies.QaCertificatePolicyInformation;
import org.xipki.pki.ca.qa.internal.QaDirectoryString;
import org.xipki.pki.ca.qa.internal.QaExtensionValue;
import org.xipki.pki.ca.qa.internal.QaGeneralSubtree;
import org.xipki.pki.ca.qa.internal.QaInhibitAnyPolicy;
import org.xipki.pki.ca.qa.internal.QaNameConstraints;
import org.xipki.pki.ca.qa.internal.QaPolicyConstraints;
import org.xipki.pki.ca.qa.internal.QaPolicyMappingsOption;
import org.xipki.pki.ca.qa.internal.QaPolicyQualifierInfo;
import org.xipki.pki.ca.qa.internal.QaPolicyQualifierInfo.QaCpsUriPolicyQualifier;
import org.xipki.pki.ca.qa.internal.QaPolicyQualifierInfo.QaUserNoticePolicyQualifierInfo;
import org.xipki.pki.ca.qa.internal.QaPolicyQualifiers;
import org.xipki.pki.ca.qa.internal.QaTlsFeature;

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

public class ExtensionsChecker {

    private static final byte[] DER_NULL = new byte[] { 5, 0 };

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

    private static final List<String> ALL_USAGES = Arrays.asList(KeyUsage.digitalSignature.getName(), // 0
            KeyUsage.contentCommitment.getName(), // 1
            KeyUsage.keyEncipherment.getName(), // 2
            KeyUsage.dataEncipherment.getName(), // 3
            KeyUsage.keyAgreement.getName(), // 4
            KeyUsage.keyCertSign.getName(), // 5
            KeyUsage.cRLSign.getName(), // 6
            KeyUsage.encipherOnly.getName(), // 7
            KeyUsage.decipherOnly.getName() // 8
    );

    private QaCertificatePolicies certificatePolicies;

    private QaPolicyMappingsOption policyMappings;

    private QaNameConstraints nameConstraints;

    private QaPolicyConstraints policyConstraints;

    private QaInhibitAnyPolicy inhibitAnyPolicy;

    private QaDirectoryString restriction;

    private QaDirectoryString additionalInformation;

    private ASN1ObjectIdentifier validityModelId;

    private QcStatements qcStatements;

    private QaAuthorizationTemplate authorizationTemplate;

    private QaTlsFeature tlsFeature;

    private QaExtensionValue smimeCapabilities;

    private Map<ASN1ObjectIdentifier, QaExtensionValue> constantExtensions;

    private XmlX509Certprofile certProfile;

    public ExtensionsChecker(final X509ProfileType conf, final XmlX509Certprofile certProfile)
            throws CertprofileException {
        this.certProfile = ParamUtil.requireNonNull("certProfile", certProfile);

        ParamUtil.requireNonNull("conf", conf);

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

        // Extension controls
        Map<ASN1ObjectIdentifier, ExtensionControl> extensionControls = certProfile.getExtensionControls();

        // Certificate Policies
        ASN1ObjectIdentifier type = Extension.certificatePolicies;
        if (extensionControls.containsKey(type)) {
            org.xipki.pki.ca.certprofile.x509.jaxb.CertificatePolicies extConf = (org.xipki.pki.ca.certprofile.x509.jaxb.CertificatePolicies) getExtensionValue(
                    type, extensionsType, org.xipki.pki.ca.certprofile.x509.jaxb.CertificatePolicies.class);
            if (extConf != null) {
                this.certificatePolicies = new QaCertificatePolicies(extConf);
            }
        }

        // Policy Mappings
        type = Extension.policyMappings;
        if (extensionControls.containsKey(type)) {
            PolicyMappings extConf = (PolicyMappings) getExtensionValue(type, extensionsType, PolicyMappings.class);
            if (extConf != null) {
                this.policyMappings = new QaPolicyMappingsOption(extConf);
            }
        }

        // Name Constrains
        type = Extension.nameConstraints;
        if (extensionControls.containsKey(type)) {
            org.xipki.pki.ca.certprofile.x509.jaxb.NameConstraints extConf = (org.xipki.pki.ca.certprofile.x509.jaxb.NameConstraints) getExtensionValue(
                    type, extensionsType, org.xipki.pki.ca.certprofile.x509.jaxb.NameConstraints.class);
            if (extConf != null) {
                this.nameConstraints = new QaNameConstraints(extConf);
            }
        }

        // Policy Constraints
        type = Extension.policyConstraints;
        if (extensionControls.containsKey(type)) {
            PolicyConstraints extConf = (PolicyConstraints) getExtensionValue(type, extensionsType,
                    PolicyConstraints.class);
            if (extConf != null) {
                this.policyConstraints = new QaPolicyConstraints(extConf);
            }
        }

        // Inhibit anyPolicy
        type = Extension.inhibitAnyPolicy;
        if (extensionControls.containsKey(type)) {
            InhibitAnyPolicy extConf = (InhibitAnyPolicy) getExtensionValue(type, extensionsType,
                    InhibitAnyPolicy.class);
            if (extConf != null) {
                this.inhibitAnyPolicy = new QaInhibitAnyPolicy(extConf);
            }
        }

        // restriction
        type = ObjectIdentifiers.id_extension_restriction;
        if (extensionControls.containsKey(type)) {
            Restriction extConf = (Restriction) getExtensionValue(type, extensionsType, Restriction.class);
            if (extConf != null) {
                restriction = new QaDirectoryString(
                        XmlX509CertprofileUtil.convertDirectoryStringType(extConf.getType()), extConf.getText());
            }
        }

        // additionalInformation
        type = ObjectIdentifiers.id_extension_additionalInformation;
        if (extensionControls.containsKey(type)) {
            AdditionalInformation extConf = (AdditionalInformation) getExtensionValue(type, extensionsType,
                    AdditionalInformation.class);
            if (extConf != null) {
                additionalInformation = new QaDirectoryString(
                        XmlX509CertprofileUtil.convertDirectoryStringType(extConf.getType()), extConf.getText());
            }
        }

        // validityModel
        type = ObjectIdentifiers.id_extension_validityModel;
        if (extensionControls.containsKey(type)) {
            ValidityModel extConf = (ValidityModel) getExtensionValue(type, extensionsType, ValidityModel.class);
            if (extConf != null) {
                validityModelId = new ASN1ObjectIdentifier(extConf.getModelId().getValue());
            }
        }

        // QCStatements
        type = Extension.qCStatements;
        if (extensionControls.containsKey(type)) {
            QcStatements extConf = (QcStatements) getExtensionValue(type, extensionsType, QcStatements.class);
            if (extConf != null) {
                qcStatements = extConf;
            }
        }

        // tlsFeature
        type = ObjectIdentifiers.id_pe_tlsfeature;
        if (extensionControls.containsKey(type)) {
            TlsFeature extConf = (TlsFeature) getExtensionValue(type, extensionsType, TlsFeature.class);
            if (extConf != null) {
                tlsFeature = new QaTlsFeature(extConf);
            }
        }

        // AuthorizationTemplate
        type = ObjectIdentifiers.id_xipki_ext_authorizationTemplate;
        if (extensionControls.containsKey(type)) {
            AuthorizationTemplate extConf = (AuthorizationTemplate) getExtensionValue(type, extensionsType,
                    AuthorizationTemplate.class);
            if (extConf != null) {
                authorizationTemplate = new QaAuthorizationTemplate(extConf);
            }
        }

        // SMIMECapabilities
        type = ObjectIdentifiers.id_smimeCapabilities;
        if (extensionControls.containsKey(type)) {
            SMIMECapabilities extConf = (SMIMECapabilities) getExtensionValue(type, extensionsType,
                    SMIMECapabilities.class);
            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);
            }

            DERSequence extValue = new DERSequence(vec);
            try {
                smimeCapabilities = new QaExtensionValue(extensionControls.get(type).isCritical(),
                        extValue.getEncoded());
            } catch (IOException ex) {
                throw new CertprofileException("Cannot encode SMIMECapabilities: " + ex.getMessage());
            }
        }

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

    public List<ValidationIssue> checkExtensions(final Certificate cert, final X509IssuerInfo issuerInfo,
            final Extensions requestedExtensions, final X500Name requestedSubject) {
        ParamUtil.requireNonNull("cert", cert);
        ParamUtil.requireNonNull("issuerInfo", issuerInfo);

        X509Certificate jceCert;
        try {
            jceCert = X509Util.toX509Cert(cert);
        } catch (CertificateException ex) {
            throw new IllegalArgumentException("invalid cert: " + ex.getMessage());
        }

        List<ValidationIssue> result = new LinkedList<>();

        // detect the list of extension types in certificate
        Set<ASN1ObjectIdentifier> presentExtenionTypes = getExensionTypes(cert, issuerInfo, requestedExtensions);

        Extensions extensions = cert.getTBSCertificate().getExtensions();
        ASN1ObjectIdentifier[] oids = extensions.getExtensionOIDs();

        if (oids == null) {
            ValidationIssue issue = new ValidationIssue("X509.EXT.GEN", "extension general");
            result.add(issue);
            issue.setFailureMessage("no extension is present");
            return result;
        }

        List<ASN1ObjectIdentifier> certExtTypes = Arrays.asList(oids);

        for (ASN1ObjectIdentifier extType : presentExtenionTypes) {
            if (!certExtTypes.contains(extType)) {
                ValidationIssue issue = createExtensionIssue(extType);
                result.add(issue);
                issue.setFailureMessage("extension is absent but is required");
            }
        }

        Map<ASN1ObjectIdentifier, ExtensionControl> extensionControls = certProfile.getExtensionControls();
        for (ASN1ObjectIdentifier oid : certExtTypes) {
            ValidationIssue issue = createExtensionIssue(oid);
            result.add(issue);
            if (!presentExtenionTypes.contains(oid)) {
                issue.setFailureMessage("extension is present but is not permitted");
                continue;
            }

            Extension ext = extensions.getExtension(oid);
            StringBuilder failureMsg = new StringBuilder();
            ExtensionControl extControl = extensionControls.get(oid);

            if (extControl.isCritical() != ext.isCritical()) {
                addViolation(failureMsg, "critical", ext.isCritical(), extControl.isCritical());
            }

            byte[] extensionValue = ext.getExtnValue().getOctets();
            try {
                if (Extension.authorityKeyIdentifier.equals(oid)) {
                    // AuthorityKeyIdentifier
                    checkExtensionIssuerKeyIdentifier(failureMsg, extensionValue, issuerInfo);
                } else if (Extension.subjectKeyIdentifier.equals(oid)) {
                    // SubjectKeyIdentifier
                    checkExtensionSubjectKeyIdentifier(failureMsg, extensionValue, cert.getSubjectPublicKeyInfo());
                } else if (Extension.keyUsage.equals(oid)) {
                    // KeyUsage
                    checkExtensionKeyUsage(failureMsg, extensionValue, jceCert.getKeyUsage(), requestedExtensions,
                            extControl);
                } else if (Extension.certificatePolicies.equals(oid)) {
                    // CertificatePolicies
                    checkExtensionCertificatePolicies(failureMsg, extensionValue, requestedExtensions, extControl);
                } else if (Extension.policyMappings.equals(oid)) {
                    // Policy Mappings
                    checkExtensionPolicyMappings(failureMsg, extensionValue, requestedExtensions, extControl);
                } else if (Extension.subjectAlternativeName.equals(oid)) {
                    // SubjectAltName
                    checkExtensionSubjectAltName(failureMsg, extensionValue, requestedExtensions, extControl,
                            requestedSubject);
                } else if (Extension.subjectDirectoryAttributes.equals(oid)) {
                    // SubjectDirectoryAttributes
                    checkExtensionSubjectDirAttrs(failureMsg, extensionValue, requestedExtensions, extControl);
                } else if (Extension.issuerAlternativeName.equals(oid)) {
                    // IssuerAltName
                    checkExtensionIssuerAltNames(failureMsg, extensionValue, issuerInfo);
                } else if (Extension.basicConstraints.equals(oid)) {
                    // Basic Constraints
                    checkExtensionBasicConstraints(failureMsg, extensionValue);
                } else if (Extension.nameConstraints.equals(oid)) {
                    // Name Constraints
                    checkExtensionNameConstraints(failureMsg, extensionValue, extensions, extControl);
                } else if (Extension.policyConstraints.equals(oid)) {
                    // PolicyConstrains
                    checkExtensionPolicyConstraints(failureMsg, extensionValue, requestedExtensions, extControl);
                } else if (Extension.extendedKeyUsage.equals(oid)) {
                    // ExtendedKeyUsage
                    checkExtensionExtendedKeyUsage(failureMsg, extensionValue, requestedExtensions, extControl);
                } else if (Extension.cRLDistributionPoints.equals(oid)) {
                    // CRL Distribution Points
                    checkExtensionCrlDistributionPoints(failureMsg, extensionValue, issuerInfo);
                } else if (Extension.inhibitAnyPolicy.equals(oid)) {
                    // Inhibit anyPolicy
                    checkExtensionInhibitAnyPolicy(failureMsg, extensionValue, extensions, extControl);
                } else if (Extension.freshestCRL.equals(oid)) {
                    // Freshest CRL
                    checkExtensionDeltaCrlDistributionPoints(failureMsg, extensionValue, issuerInfo);
                } else if (Extension.authorityInfoAccess.equals(oid)) {
                    // Authority Information Access
                    checkExtensionAuthorityInfoAccess(failureMsg, extensionValue, issuerInfo);
                } else if (Extension.subjectInfoAccess.equals(oid)) {
                    // SubjectInfoAccess
                    checkExtensionSubjectInfoAccess(failureMsg, extensionValue, requestedExtensions, extControl);
                } else if (ObjectIdentifiers.id_extension_admission.equals(oid)) {
                    // Admission
                    checkExtensionAdmission(failureMsg, extensionValue, requestedExtensions, extControl);
                } else if (ObjectIdentifiers.id_extension_pkix_ocsp_nocheck.equals(oid)) {
                    // ocsp-nocheck
                    checkExtensionOcspNocheck(failureMsg, extensionValue);
                } else if (ObjectIdentifiers.id_extension_restriction.equals(oid)) {
                    // restriction
                    checkExtensionRestriction(failureMsg, extensionValue, requestedExtensions, extControl);
                } else if (ObjectIdentifiers.id_extension_additionalInformation.equals(oid)) {
                    // additionalInformation
                    checkExtensionAdditionalInformation(failureMsg, extensionValue, requestedExtensions,
                            extControl);
                } else if (ObjectIdentifiers.id_extension_validityModel.equals(oid)) {
                    // validityModel
                    checkExtensionValidityModel(failureMsg, extensionValue, requestedExtensions, extControl);
                } else if (Extension.privateKeyUsagePeriod.equals(oid)) {
                    // privateKeyUsagePeriod
                    checkExtensionPrivateKeyUsagePeriod(failureMsg, extensionValue, jceCert.getNotBefore(),
                            jceCert.getNotAfter());
                } else if (Extension.qCStatements.equals(oid)) {
                    // qCStatements
                    checkExtensionQcStatements(failureMsg, extensionValue, requestedExtensions, extControl);
                } else if (Extension.biometricInfo.equals(oid)) {
                    // biometricInfo
                    checkExtensionBiometricInfo(failureMsg, extensionValue, requestedExtensions, extControl);
                } else if (ObjectIdentifiers.id_pe_tlsfeature.equals(oid)) {
                    // tlsFeature
                    checkExtensionTlsFeature(failureMsg, extensionValue, requestedExtensions, extControl);
                } else if (ObjectIdentifiers.id_xipki_ext_authorizationTemplate.equals(oid)) {
                    // authorizationTemplate
                    checkExtensionAuthorizationTemplate(failureMsg, extensionValue, requestedExtensions,
                            extControl);
                } else {
                    byte[] expected;
                    if (ObjectIdentifiers.id_smimeCapabilities.equals(oid)) {
                        // SMIMECapabilities
                        expected = smimeCapabilities.getValue();
                    } else {
                        expected = getExpectedExtValue(oid, requestedExtensions, extControl);
                    }

                    if (!Arrays.equals(expected, extensionValue)) {
                        addViolation(failureMsg, "extension valus", hex(extensionValue),
                                (expected == null) ? "not present" : hex(expected));
                    }
                }

                if (failureMsg.length() > 0) {
                    issue.setFailureMessage(failureMsg.toString());
                }

            } catch (IllegalArgumentException | ClassCastException | ArrayIndexOutOfBoundsException ex) {
                LOG.debug("extension value does not have correct syntax", ex);
                issue.setFailureMessage("extension value does not have correct syntax");
            }
        }

        return result;
    } // method checkExtensions

    private byte[] getExpectedExtValue(final ASN1ObjectIdentifier type, final Extensions requestedExtensions,
            final ExtensionControl extControl) {
        if (constantExtensions != null && constantExtensions.containsKey(type)) {
            return constantExtensions.get(type).getValue();
        } else if (requestedExtensions != null && extControl.isRequest()) {
            Extension reqExt = requestedExtensions.getExtension(type);
            if (reqExt != null) {
                return reqExt.getExtnValue().getOctets();
            }
        }

        return null;
    } // getExpectedExtValue

    private Set<ASN1ObjectIdentifier> getExensionTypes(final Certificate cert, final X509IssuerInfo issuerInfo,
            final Extensions requestedExtensions) {
        Set<ASN1ObjectIdentifier> types = new HashSet<>();
        // profile required extension types
        Map<ASN1ObjectIdentifier, ExtensionControl> extensionControls = certProfile.getExtensionControls();
        for (ASN1ObjectIdentifier oid : extensionControls.keySet()) {
            if (extensionControls.get(oid).isRequired()) {
                types.add(oid);
            }
        }

        Set<ASN1ObjectIdentifier> wantedExtensionTypes = new HashSet<>();

        if (requestedExtensions != null) {
            Extension reqExtension = requestedExtensions
                    .getExtension(ObjectIdentifiers.id_xipki_ext_cmpRequestExtensions);
            if (reqExtension != null) {
                ExtensionExistence ee = ExtensionExistence.getInstance(reqExtension.getParsedValue());
                types.addAll(ee.getNeedExtensions());
                wantedExtensionTypes.addAll(ee.getWantExtensions());
            }
        }

        if (CollectionUtil.isEmpty(wantedExtensionTypes)) {
            return types;
        }

        // wanted extension types
        // Authority key identifier
        ASN1ObjectIdentifier type = Extension.authorityKeyIdentifier;
        if (wantedExtensionTypes.contains(type)) {
            types.add(type);
        }

        // Subject key identifier
        type = Extension.subjectKeyIdentifier;
        if (wantedExtensionTypes.contains(type)) {
            types.add(type);
        }

        // KeyUsage
        type = Extension.keyUsage;
        if (wantedExtensionTypes.contains(type)) {
            boolean required = false;
            if (requestedExtensions != null && requestedExtensions.getExtension(type) != null) {
                required = true;
            }

            if (!required) {
                Set<KeyUsageControl> requiredKeyusage = getKeyusage(true);
                if (CollectionUtil.isNonEmpty(requiredKeyusage)) {
                    required = true;
                }
            }

            if (required) {
                types.add(type);
            }
        }

        // CertificatePolicies
        type = Extension.certificatePolicies;
        if (wantedExtensionTypes.contains(type)) {
            if (certificatePolicies != null) {
                types.add(type);
            }
        }

        // Policy Mappings
        type = Extension.policyMappings;
        if (wantedExtensionTypes.contains(type)) {
            if (policyMappings != null) {
                types.add(type);
            }
        }

        // SubjectAltNames
        type = Extension.subjectAlternativeName;
        if (wantedExtensionTypes.contains(type)) {
            if (requestedExtensions != null && requestedExtensions.getExtension(type) != null) {
                types.add(type);
            }
        }

        // IssuerAltName
        type = Extension.issuerAlternativeName;
        if (wantedExtensionTypes.contains(type)) {
            if (cert.getTBSCertificate().getExtensions().getExtension(Extension.subjectAlternativeName) != null) {
                types.add(type);
            }
        }

        // BasicConstraints
        type = Extension.basicConstraints;
        if (wantedExtensionTypes.contains(type)) {
            types.add(type);
        }

        // Name Constraints
        type = Extension.nameConstraints;
        if (wantedExtensionTypes.contains(type)) {
            if (nameConstraints != null) {
                types.add(type);
            }
        }

        // PolicyConstrains
        type = Extension.policyConstraints;
        if (wantedExtensionTypes.contains(type)) {
            if (policyConstraints != null) {
                types.add(type);
            }
        }

        // ExtendedKeyUsage
        type = Extension.extendedKeyUsage;
        if (wantedExtensionTypes.contains(type)) {
            boolean required = false;
            if (requestedExtensions != null && requestedExtensions.getExtension(type) != null) {
                required = true;
            }

            if (!required) {
                Set<ExtKeyUsageControl> requiredExtKeyusage = getExtKeyusage(true);
                if (CollectionUtil.isNonEmpty(requiredExtKeyusage)) {
                    required = true;
                }
            }

            if (required) {
                types.add(type);
            }
        }

        // CRLDistributionPoints
        type = Extension.cRLDistributionPoints;
        if (wantedExtensionTypes.contains(type)) {
            if (issuerInfo.getCrlUrls() != null) {
                types.add(type);
            }
        }

        // Inhibit anyPolicy
        type = Extension.inhibitAnyPolicy;
        if (wantedExtensionTypes.contains(type)) {
            if (inhibitAnyPolicy != null) {
                types.add(type);
            }
        }

        // FreshestCRL
        type = Extension.freshestCRL;
        if (wantedExtensionTypes.contains(type)) {
            if (issuerInfo.getDeltaCrlUrls() != null) {
                types.add(type);
            }
        }

        // AuthorityInfoAccess
        type = Extension.authorityInfoAccess;
        if (wantedExtensionTypes.contains(type)) {
            if (issuerInfo.getOcspUrls() != null) {
                types.add(type);
            }
        }

        // SubjectInfoAccess
        type = Extension.subjectInfoAccess;
        if (wantedExtensionTypes.contains(type)) {
            if (requestedExtensions != null && requestedExtensions.getExtension(type) != null) {
                types.add(type);
            }
        }

        // Admission
        type = ObjectIdentifiers.id_extension_admission;
        if (wantedExtensionTypes.contains(type)) {
            if (certProfile.getAdmission() != null) {
                types.add(type);
            }
        }

        // ocsp-nocheck
        type = ObjectIdentifiers.id_extension_pkix_ocsp_nocheck;
        if (wantedExtensionTypes.contains(type)) {
            types.add(type);
        }

        wantedExtensionTypes.removeAll(types);

        for (ASN1ObjectIdentifier oid : wantedExtensionTypes) {
            if (requestedExtensions != null && requestedExtensions.getExtension(oid) != null) {
                if (constantExtensions.containsKey(oid)) {
                    types.add(oid);
                }
            }
        }

        return types;
    } // method getExensionTypes

    private ValidationIssue createExtensionIssue(final ASN1ObjectIdentifier extId) {
        ValidationIssue issue;
        String extName = ObjectIdentifiers.getName(extId);
        if (extName == null) {
            extName = extId.getId().replace('.', '_');
            issue = new ValidationIssue("X509.EXT." + extName, "extension " + extId.getId());
        } else {
            issue = new ValidationIssue("X509.EXT." + extName, "extension " + extName + " (" + extId.getId() + ")");
        }
        return issue;
    } // method createExtensionIssue

    private void checkExtensionBasicConstraints(final StringBuilder failureMsg, final byte[] extensionValue) {
        BasicConstraints bc = BasicConstraints.getInstance(extensionValue);
        X509CertLevel certLevel = certProfile.getCertLevel();
        boolean ca = (X509CertLevel.RootCA == certLevel) || (X509CertLevel.SubCA == certLevel);
        if (ca != bc.isCA()) {
            addViolation(failureMsg, "ca", bc.isCA(), ca);
        }

        if (bc.isCA()) {
            BigInteger tmpPathLen = bc.getPathLenConstraint();
            Integer pathLen = certProfile.getPathLen();
            if (pathLen == null) {
                if (tmpPathLen != null) {
                    addViolation(failureMsg, "pathLen", tmpPathLen, "absent");
                }
            } else {
                if (tmpPathLen == null) {
                    addViolation(failureMsg, "pathLen", "null", pathLen);
                } else if (!BigInteger.valueOf(pathLen).equals(tmpPathLen)) {
                    addViolation(failureMsg, "pathLen", tmpPathLen, pathLen);
                }
            }
        }
    } // method checkExtensionBasicConstraints

    private void checkExtensionSubjectKeyIdentifier(final StringBuilder failureMsg, final byte[] extensionValue,
            final SubjectPublicKeyInfo subjectPublicKeyInfo) {
        // subjectKeyIdentifier
        SubjectKeyIdentifier asn1 = SubjectKeyIdentifier.getInstance(extensionValue);
        byte[] ski = asn1.getKeyIdentifier();
        byte[] pkData = subjectPublicKeyInfo.getPublicKeyData().getBytes();
        byte[] expectedSki = HashAlgoType.SHA1.hash(pkData);
        if (!Arrays.equals(expectedSki, ski)) {
            addViolation(failureMsg, "SKI", hex(ski), hex(expectedSki));
        }
    } // method checkExtensionSubjectKeyIdentifier

    private void checkExtensionIssuerKeyIdentifier(final StringBuilder failureMsg, final byte[] extensionValue,
            final X509IssuerInfo issuerInfo) {
        AuthorityKeyIdentifier asn1 = AuthorityKeyIdentifier.getInstance(extensionValue);
        byte[] keyIdentifier = asn1.getKeyIdentifier();
        if (keyIdentifier == null) {
            failureMsg.append("keyIdentifier is 'absent' but expected 'present'; ");
        } else if (!Arrays.equals(issuerInfo.getSubjectKeyIdentifier(), keyIdentifier)) {
            addViolation(failureMsg, "keyIdentifier", hex(keyIdentifier),
                    hex(issuerInfo.getSubjectKeyIdentifier()));
        }

        BigInteger serialNumber = asn1.getAuthorityCertSerialNumber();
        GeneralNames names = asn1.getAuthorityCertIssuer();

        if (certProfile.isIncludeIssuerAndSerialInAki()) {
            if (serialNumber == null) {
                failureMsg.append("authorityCertSerialNumber is 'absent' but expected 'present'; ");
            } else {
                if (!issuerInfo.getCert().getSerialNumber().equals(serialNumber)) {
                    addViolation(failureMsg, "authorityCertSerialNumber", LogUtil.formatCsn(serialNumber),
                            LogUtil.formatCsn(issuerInfo.getCert().getSerialNumber()));
                }
            }

            if (names == null) {
                failureMsg.append("authorityCertIssuer is 'absent' but expected 'present'; ");
            } else {
                GeneralName[] genNames = names.getNames();
                X500Name x500GenName = null;
                for (GeneralName genName : genNames) {
                    if (genName.getTagNo() != GeneralName.directoryName) {
                        continue;
                    }

                    if (x500GenName != null) {
                        failureMsg.append("authorityCertIssuer contains at least two ");
                        failureMsg.append("directoryName but expected one; ");
                        break;
                    } else {
                        x500GenName = (X500Name) genName.getName();
                    }
                }

                if (x500GenName == null) {
                    failureMsg.append("authorityCertIssuer does not contain directoryName but expected one; ");
                } else {
                    X500Name caSubject = issuerInfo.getBcCert().getTBSCertificate().getSubject();
                    if (!caSubject.equals(x500GenName)) {
                        addViolation(failureMsg, "authorityCertIssuer", x500GenName, caSubject);
                    }
                }
            }
        } else {
            if (serialNumber != null) {
                failureMsg.append("authorityCertSerialNumber is 'absent' but expected 'present'; ");
            }

            if (names != null) {
                failureMsg.append("authorityCertIssuer is 'absent' but expected 'present'; ");
            }
        }
    } // method checkExtensionIssuerKeyIdentifier

    private void checkExtensionNameConstraints(final StringBuilder failureMsg, final byte[] extensionValue,
            final Extensions requestedExtensions, final ExtensionControl extControl) {
        QaNameConstraints conf = nameConstraints;

        if (conf == null) {
            byte[] expected = getExpectedExtValue(Extension.nameConstraints, requestedExtensions, extControl);
            if (!Arrays.equals(expected, extensionValue)) {
                addViolation(failureMsg, "extension values", hex(extensionValue),
                        (expected == null) ? "not present" : hex(expected));
            }
            return;
        }

        org.bouncycastle.asn1.x509.NameConstraints tmpNameConstraints = org.bouncycastle.asn1.x509.NameConstraints
                .getInstance(extensionValue);

        checkExtensionNameConstraintsSubtrees(failureMsg, "PermittedSubtrees",
                tmpNameConstraints.getPermittedSubtrees(), conf.getPermittedSubtrees());
        checkExtensionNameConstraintsSubtrees(failureMsg, "ExcludedSubtrees",
                tmpNameConstraints.getExcludedSubtrees(), conf.getExcludedSubtrees());
    } // method checkExtensionNameConstraints

    private void checkExtensionNameConstraintsSubtrees(final StringBuilder failureMsg, final String description,
            final GeneralSubtree[] subtrees, final List<QaGeneralSubtree> expectedSubtrees) {
        int isSize = (subtrees == null) ? 0 : subtrees.length;
        int expSize = (expectedSubtrees == null) ? 0 : expectedSubtrees.size();
        if (isSize != expSize) {
            addViolation(failureMsg, "size of " + description, isSize, expSize);
            return;
        }

        if (subtrees == null || expectedSubtrees == null) {
            return;
        }

        for (int i = 0; i < isSize; i++) {
            GeneralSubtree isSubtree = subtrees[i];
            QaGeneralSubtree expSubtree = expectedSubtrees.get(i);
            BigInteger bigInt = isSubtree.getMinimum();
            int isMinimum = (bigInt == null) ? 0 : bigInt.intValue();
            Integer minimum = expSubtree.getMinimum();
            int expMinimum = (minimum == null) ? 0 : minimum.intValue();
            String desc = description + " [" + i + "]";
            if (isMinimum != expMinimum) {
                addViolation(failureMsg, "minimum of " + desc, isMinimum, expMinimum);
            }

            bigInt = isSubtree.getMaximum();
            Integer isMaximum = (bigInt == null) ? null : bigInt.intValue();
            Integer expMaximum = expSubtree.getMaximum();
            if (!CompareUtil.equalsObject(isMaximum, expMaximum)) {
                addViolation(failureMsg, "maxmum of " + desc, isMaximum, expMaximum);
            }

            GeneralName isBase = isSubtree.getBase();

            GeneralName expBase;
            if (expSubtree.getDirectoryName() != null) {
                expBase = new GeneralName(X509Util.reverse(new X500Name(expSubtree.getDirectoryName())));
            } else if (expSubtree.getDnsName() != null) {
                expBase = new GeneralName(GeneralName.dNSName, expSubtree.getDnsName());
            } else if (expSubtree.getIpAddress() != null) {
                expBase = new GeneralName(GeneralName.iPAddress, expSubtree.getIpAddress());
            } else if (expSubtree.getRfc822Name() != null) {
                expBase = new GeneralName(GeneralName.rfc822Name, expSubtree.getRfc822Name());
            } else if (expSubtree.getUri() != null) {
                expBase = new GeneralName(GeneralName.uniformResourceIdentifier, expSubtree.getUri());
            } else {
                throw new RuntimeException("should not reach here, unknown child of GeneralName");
            }

            if (!isBase.equals(expBase)) {
                addViolation(failureMsg, "base of " + desc, isBase, expBase);
            }
        }
    } // method checkExtensionNameConstraintsSubtrees

    private void checkExtensionPolicyConstraints(final StringBuilder failureMsg, final byte[] extensionValue,
            final Extensions requestedExtensions, final ExtensionControl extControl) {
        QaPolicyConstraints conf = policyConstraints;
        if (conf == null) {
            byte[] expected = getExpectedExtValue(Extension.policyConstraints, requestedExtensions, extControl);
            if (!Arrays.equals(expected, extensionValue)) {
                addViolation(failureMsg, "extension values", hex(extensionValue),
                        (expected == null) ? "not present" : hex(expected));
            }
            return;
        }

        org.bouncycastle.asn1.x509.PolicyConstraints isPolicyConstraints = org.bouncycastle.asn1.x509.PolicyConstraints
                .getInstance(extensionValue);
        Integer expRequireExplicitPolicy = conf.getRequireExplicitPolicy();
        BigInteger bigInt = isPolicyConstraints.getRequireExplicitPolicyMapping();
        Integer isRequireExplicitPolicy = (bigInt == null) ? null : bigInt.intValue();

        boolean match = true;
        if (expRequireExplicitPolicy == null) {
            if (isRequireExplicitPolicy != null) {
                match = false;
            }
        } else if (!expRequireExplicitPolicy.equals(isRequireExplicitPolicy)) {
            match = false;
        }

        if (!match) {
            addViolation(failureMsg, "requireExplicitPolicy", isRequireExplicitPolicy, expRequireExplicitPolicy);
        }

        Integer expInhibitPolicyMapping = conf.getInhibitPolicyMapping();
        bigInt = isPolicyConstraints.getInhibitPolicyMapping();
        Integer isInhibitPolicyMapping = (bigInt == null) ? null : bigInt.intValue();

        match = true;
        if (expInhibitPolicyMapping == null) {
            if (isInhibitPolicyMapping != null) {
                match = false;
            }
        } else if (!expInhibitPolicyMapping.equals(isInhibitPolicyMapping)) {
            match = false;
        }

        if (!match) {
            addViolation(failureMsg, "inhibitPolicyMapping", isInhibitPolicyMapping, expInhibitPolicyMapping);
        }
    } // method checkExtensionPolicyConstraints

    private void checkExtensionKeyUsage(final StringBuilder failureMsg, final byte[] extensionValue,
            final boolean[] usages, final Extensions requestedExtensions, final ExtensionControl extControl) {
        int len = usages.length;

        if (len > 9) {
            failureMsg.append("invalid syntax: size of valid bits is larger than 9: ").append(len);
            failureMsg.append("; ");
        }

        Set<String> isUsages = new HashSet<>();
        for (int i = 0; i < len; i++) {
            if (usages[i]) {
                isUsages.add(ALL_USAGES.get(i));
            }
        }

        Set<String> expectedUsages = new HashSet<>();
        Set<KeyUsageControl> requiredKeyusage = getKeyusage(true);
        for (KeyUsageControl usage : requiredKeyusage) {
            expectedUsages.add(usage.getKeyUsage().getName());
        }

        Set<KeyUsageControl> optionalKeyusage = getKeyusage(false);
        if (requestedExtensions != null && extControl.isRequest() && CollectionUtil.isNonEmpty(optionalKeyusage)) {
            Extension extension = requestedExtensions.getExtension(Extension.keyUsage);
            if (extension != null) {
                org.bouncycastle.asn1.x509.KeyUsage reqKeyUsage = org.bouncycastle.asn1.x509.KeyUsage
                        .getInstance(extension.getParsedValue());
                for (KeyUsageControl k : optionalKeyusage) {
                    if (reqKeyUsage.hasUsages(k.getKeyUsage().getBcUsage())) {
                        expectedUsages.add(k.getKeyUsage().getName());
                    }
                }
            }
        }

        if (CollectionUtil.isEmpty(expectedUsages)) {
            byte[] constantExtValue = getConstantExtensionValue(Extension.keyUsage);
            if (constantExtValue != null) {
                expectedUsages = getKeyUsage(constantExtValue);
            }
        }

        Set<String> diffs = strInBnotInA(expectedUsages, isUsages);
        if (CollectionUtil.isNonEmpty(diffs)) {
            failureMsg.append("usages ").append(diffs.toString()).append(" are present but not expected; ");
        }

        diffs = strInBnotInA(isUsages, expectedUsages);
        if (CollectionUtil.isNonEmpty(diffs)) {
            failureMsg.append("usages ").append(diffs.toString()).append(" are absent but are required; ");
        }
    } // method checkExtensionKeyUsage

    private void checkExtensionExtendedKeyUsage(final StringBuilder failureMsg, final byte[] extensionValue,
            final Extensions requestedExtensions, final ExtensionControl extControl) {
        Set<String> isUsages = new HashSet<>();
        org.bouncycastle.asn1.x509.ExtendedKeyUsage keyusage = org.bouncycastle.asn1.x509.ExtendedKeyUsage
                .getInstance(extensionValue);
        KeyPurposeId[] usages = keyusage.getUsages();
        if (usages != null) {
            for (KeyPurposeId usage : usages) {
                isUsages.add(usage.getId());
            }
        }

        Set<String> expectedUsages = new HashSet<>();
        Set<ExtKeyUsageControl> requiredExtKeyusage = getExtKeyusage(true);
        if (requiredExtKeyusage != null) {
            for (ExtKeyUsageControl usage : requiredExtKeyusage) {
                expectedUsages.add(usage.getExtKeyUsage().getId());
            }
        }

        Set<ExtKeyUsageControl> optionalExtKeyusage = getExtKeyusage(false);
        if (requestedExtensions != null && extControl.isRequest()
                && CollectionUtil.isNonEmpty(optionalExtKeyusage)) {
            Extension extension = requestedExtensions.getExtension(Extension.extendedKeyUsage);
            if (extension != null) {
                org.bouncycastle.asn1.x509.ExtendedKeyUsage reqKeyUsage = org.bouncycastle.asn1.x509.ExtendedKeyUsage
                        .getInstance(extension.getParsedValue());
                for (ExtKeyUsageControl k : optionalExtKeyusage) {
                    if (reqKeyUsage.hasKeyPurposeId(KeyPurposeId.getInstance(k.getExtKeyUsage()))) {
                        expectedUsages.add(k.getExtKeyUsage().getId());
                    }
                }
            }
        }

        if (CollectionUtil.isEmpty(expectedUsages)) {
            byte[] constantExtValue = getConstantExtensionValue(Extension.keyUsage);
            if (constantExtValue != null) {
                expectedUsages = getExtKeyUsage(constantExtValue);
            }
        }

        Set<String> diffs = strInBnotInA(expectedUsages, isUsages);
        if (CollectionUtil.isNonEmpty(diffs)) {
            failureMsg.append("usages ").append(diffs.toString()).append(" are present but not expected; ");
        }

        diffs = strInBnotInA(isUsages, expectedUsages);
        if (CollectionUtil.isNonEmpty(diffs)) {
            failureMsg.append("usages ").append(diffs.toString()).append(" are absent but are required; ");
        }
    } // method checkExtensionExtendedKeyUsage

    private void checkExtensionTlsFeature(final StringBuilder failureMsg, final byte[] extensionValue,
            final Extensions requestedExtensions, final ExtensionControl extControl) {
        QaTlsFeature conf = tlsFeature;
        if (conf == null) {
            byte[] expected = getExpectedExtValue(ObjectIdentifiers.id_pe_tlsfeature, requestedExtensions,
                    extControl);
            if (!Arrays.equals(expected, extensionValue)) {
                addViolation(failureMsg, "extension values", hex(extensionValue),
                        (expected == null) ? "not present" : hex(expected));
            }
            return;
        }

        Set<String> isFeatures = new HashSet<>();
        ASN1Sequence seq = ASN1Sequence.getInstance(extensionValue);
        final int n = seq.size();
        for (int i = 0; i < n; i++) {
            ASN1Integer asn1Feature = ASN1Integer.getInstance(seq.getObjectAt(i));
            isFeatures.add(asn1Feature.getPositiveValue().toString());
        }

        Set<String> expFeatures = new HashSet<>();
        for (Integer m : conf.getFeatures()) {
            expFeatures.add(m.toString());
        }

        Set<String> diffs = strInBnotInA(expFeatures, isFeatures);
        if (CollectionUtil.isNonEmpty(diffs)) {
            failureMsg.append("features ").append(diffs.toString()).append(" are present but not expected; ");
        }

        diffs = strInBnotInA(isFeatures, expFeatures);
        if (CollectionUtil.isNonEmpty(diffs)) {
            failureMsg.append("features ").append(diffs.toString()).append(" are absent but are required; ");
        }
    } // method checkExtensionTlsFeature

    private void checkExtensionCertificatePolicies(final StringBuilder failureMsg, final byte[] extensionValue,
            final Extensions requestedExtensions, final ExtensionControl extControl) {
        QaCertificatePolicies conf = certificatePolicies;
        if (conf == null) {
            byte[] expected = getExpectedExtValue(Extension.certificatePolicies, requestedExtensions, extControl);
            if (!Arrays.equals(expected, extensionValue)) {
                addViolation(failureMsg, "extension values", hex(extensionValue),
                        (expected == null) ? "not present" : hex(expected));
            }
            return;
        }

        org.bouncycastle.asn1.x509.CertificatePolicies asn1 = org.bouncycastle.asn1.x509.CertificatePolicies
                .getInstance(extensionValue);
        PolicyInformation[] isPolicyInformations = asn1.getPolicyInformation();

        for (PolicyInformation isPolicyInformation : isPolicyInformations) {
            ASN1ObjectIdentifier isPolicyId = isPolicyInformation.getPolicyIdentifier();
            QaCertificatePolicyInformation expCp = conf.getPolicyInformation(isPolicyId.getId());
            if (expCp == null) {
                failureMsg.append("certificate policy '").append(isPolicyId);
                failureMsg.append("' is not expected; ");
                continue;
            }

            QaPolicyQualifiers expCpPq = expCp.getPolicyQualifiers();
            if (expCpPq == null) {
                continue;
            }

            ASN1Sequence isPolicyQualifiers = isPolicyInformation.getPolicyQualifiers();
            List<String> isCpsUris = new LinkedList<>();
            List<String> isUserNotices = new LinkedList<>();

            int size = isPolicyQualifiers.size();
            for (int i = 0; i < size; i++) {
                PolicyQualifierInfo isPolicyQualifierInfo = (PolicyQualifierInfo) isPolicyQualifiers.getObjectAt(i);
                ASN1ObjectIdentifier isPolicyQualifierId = isPolicyQualifierInfo.getPolicyQualifierId();
                ASN1Encodable isQualifier = isPolicyQualifierInfo.getQualifier();
                if (PolicyQualifierId.id_qt_cps.equals(isPolicyQualifierId)) {
                    String isCpsUri = ((DERIA5String) isQualifier).getString();
                    isCpsUris.add(isCpsUri);
                } else if (PolicyQualifierId.id_qt_unotice.equals(isPolicyQualifierId)) {
                    UserNotice isUserNotice = UserNotice.getInstance(isQualifier);
                    if (isUserNotice.getExplicitText() != null) {
                        isUserNotices.add(isUserNotice.getExplicitText().getString());
                    }
                }
            }

            List<QaPolicyQualifierInfo> qualifierInfos = expCpPq.getPolicyQualifiers();
            for (QaPolicyQualifierInfo qualifierInfo : qualifierInfos) {
                if (qualifierInfo instanceof QaCpsUriPolicyQualifier) {
                    String value = ((QaCpsUriPolicyQualifier) qualifierInfo).getCpsUri();
                    if (!isCpsUris.contains(value)) {
                        failureMsg.append("CPSUri '").append(value);
                        failureMsg.append("' is absent but is required; ");
                    }
                } else if (qualifierInfo instanceof QaUserNoticePolicyQualifierInfo) {
                    String value = ((QaUserNoticePolicyQualifierInfo) qualifierInfo).getUserNotice();
                    if (!isUserNotices.contains(value)) {
                        failureMsg.append("userNotice '").append(value);
                        failureMsg.append("' is absent but is required; ");
                    }
                } else {
                    throw new RuntimeException("should not reach here");
                }
            }
        }

        for (QaCertificatePolicyInformation cp : conf.getPolicyInformations()) {
            boolean present = false;
            for (PolicyInformation isPolicyInformation : isPolicyInformations) {
                if (isPolicyInformation.getPolicyIdentifier().getId().equals(cp.getPolicyId())) {
                    present = true;
                    break;
                }
            }

            if (present) {
                continue;
            }

            failureMsg.append("certificate policy '").append(cp.getPolicyId());
            failureMsg.append("' is absent but is required; ");
        }
    } // method checkExtensionCertificatePolicies

    private void checkExtensionPolicyMappings(final StringBuilder failureMsg, final byte[] extensionValue,
            final Extensions requestedExtensions, final ExtensionControl extControl) {
        QaPolicyMappingsOption conf = policyMappings;
        if (conf == null) {
            byte[] expected = getExpectedExtValue(Extension.policyMappings, requestedExtensions, extControl);
            if (!Arrays.equals(expected, extensionValue)) {
                addViolation(failureMsg, "extension values", hex(extensionValue),
                        (expected == null) ? "not present" : hex(expected));
            }
            return;
        }

        ASN1Sequence isPolicyMappings = DERSequence.getInstance(extensionValue);
        Map<String, String> isMap = new HashMap<>();
        int size = isPolicyMappings.size();
        for (int i = 0; i < size; i++) {
            ASN1Sequence seq = ASN1Sequence.getInstance(isPolicyMappings.getObjectAt(i));
            CertPolicyId issuerDomainPolicy = CertPolicyId.getInstance(seq.getObjectAt(0));
            CertPolicyId subjectDomainPolicy = CertPolicyId.getInstance(seq.getObjectAt(1));
            isMap.put(issuerDomainPolicy.getId(), subjectDomainPolicy.getId());
        }

        Set<String> expIssuerDomainPolicies = conf.getIssuerDomainPolicies();
        for (String expIssuerDomainPolicy : expIssuerDomainPolicies) {
            String expSubjectDomainPolicy = conf.getSubjectDomainPolicy(expIssuerDomainPolicy);

            String isSubjectDomainPolicy = isMap.remove(expIssuerDomainPolicy);
            if (isSubjectDomainPolicy == null) {
                failureMsg.append("issuerDomainPolicy '").append(expIssuerDomainPolicy)
                        .append("' is absent but is required; ");
            } else if (!isSubjectDomainPolicy.equals(expSubjectDomainPolicy)) {
                addViolation(failureMsg, "subjectDomainPolicy for issuerDomainPolicy", isSubjectDomainPolicy,
                        expSubjectDomainPolicy);
            }
        }

        if (CollectionUtil.isNonEmpty(isMap)) {
            failureMsg.append("issuerDomainPolicies '").append(isMap.keySet());
            failureMsg.append("' are present but not expected; ");
        }
    } // method checkExtensionPolicyMappings

    private void checkExtensionInhibitAnyPolicy(final StringBuilder failureMsg, final byte[] extensionValue,
            final Extensions requestedExtensions, final ExtensionControl extControl) {
        QaInhibitAnyPolicy conf = inhibitAnyPolicy;
        if (conf == null) {
            byte[] expected = getExpectedExtValue(Extension.inhibitAnyPolicy, requestedExtensions, extControl);
            if (!Arrays.equals(expected, extensionValue)) {
                addViolation(failureMsg, "extension values", extensionValue,
                        (expected == null) ? "not present" : hex(expected));
            }
            return;
        }

        ASN1Integer asn1Int = ASN1Integer.getInstance(extensionValue);
        int isSkipCerts = asn1Int.getPositiveValue().intValue();
        if (isSkipCerts != conf.getSkipCerts()) {
            addViolation(failureMsg, "skipCerts", isSkipCerts, conf.getSkipCerts());
        }
    } // method checkExtensionInhibitAnyPolicy

    private void checkExtensionSubjectDirAttrs(final StringBuilder failureMsg, final byte[] extensionValue,
            final Extensions requestedExtensions, final ExtensionControl extControl) {
        SubjectDirectoryAttributesControl conf = certProfile.getSubjectDirAttrsControl();
        if (conf == null) {
            failureMsg.append("extension is present but not expected; ");
            return;
        }

        ASN1Encodable extInRequest = null;
        if (requestedExtensions != null) {
            extInRequest = requestedExtensions.getExtensionParsedValue(Extension.subjectDirectoryAttributes);
        }

        if (extInRequest == null) {
            failureMsg.append("extension is present but not expected; ");
            return;
        }

        SubjectDirectoryAttributes requested = SubjectDirectoryAttributes.getInstance(extInRequest);
        Vector<?> reqSubDirAttrs = requested.getAttributes();
        ASN1GeneralizedTime expDateOfBirth = null;
        String expPlaceOfBirth = null;
        String expGender = null;
        Set<String> expCountryOfCitizenshipList = new HashSet<>();
        Set<String> expCountryOfResidenceList = new HashSet<>();
        Map<ASN1ObjectIdentifier, Set<ASN1Encodable>> expOtherAttrs = new HashMap<>();

        final int expN = reqSubDirAttrs.size();
        for (int i = 0; i < expN; i++) {
            Attribute attr = Attribute.getInstance(reqSubDirAttrs.get(i));
            ASN1ObjectIdentifier attrType = attr.getAttrType();
            ASN1Encodable attrVal = attr.getAttributeValues()[0];

            if (ObjectIdentifiers.DN_DATE_OF_BIRTH.equals(attrType)) {
                expDateOfBirth = ASN1GeneralizedTime.getInstance(attrVal);
            } else if (ObjectIdentifiers.DN_PLACE_OF_BIRTH.equals(attrType)) {
                expPlaceOfBirth = DirectoryString.getInstance(attrVal).getString();
            } else if (ObjectIdentifiers.DN_GENDER.equals(attrType)) {
                expGender = DERPrintableString.getInstance(attrVal).getString();
            } else if (ObjectIdentifiers.DN_COUNTRY_OF_CITIZENSHIP.equals(attrType)) {
                String country = DERPrintableString.getInstance(attrVal).getString();
                expCountryOfCitizenshipList.add(country);
            } else if (ObjectIdentifiers.DN_COUNTRY_OF_RESIDENCE.equals(attrType)) {
                String country = DERPrintableString.getInstance(attrVal).getString();
                expCountryOfResidenceList.add(country);
            } else {
                Set<ASN1Encodable> otherAttrVals = expOtherAttrs.get(attrType);
                if (otherAttrVals == null) {
                    otherAttrVals = new HashSet<>();
                    expOtherAttrs.put(attrType, otherAttrVals);
                }
                otherAttrVals.add(attrVal);
            }
        }

        SubjectDirectoryAttributes ext = SubjectDirectoryAttributes.getInstance(extensionValue);
        Vector<?> subDirAttrs = ext.getAttributes();
        ASN1GeneralizedTime dateOfBirth = null;
        String placeOfBirth = null;
        String gender = null;
        Set<String> countryOfCitizenshipList = new HashSet<>();
        Set<String> countryOfResidenceList = new HashSet<>();
        Map<ASN1ObjectIdentifier, Set<ASN1Encodable>> otherAttrs = new HashMap<>();

        List<ASN1ObjectIdentifier> attrTypes = new LinkedList<>(conf.getTypes());
        final int n = subDirAttrs.size();
        for (int i = 0; i < n; i++) {
            Attribute attr = Attribute.getInstance(subDirAttrs.get(i));
            ASN1ObjectIdentifier attrType = attr.getAttrType();
            if (!attrTypes.contains(attrType)) {
                failureMsg.append("attribute of type " + attrType.getId() + " is present but not expected; ");
                continue;
            }

            ASN1Encodable[] attrs = attr.getAttributeValues();
            if (attrs.length != 1) {
                failureMsg.append("attribute of type " + attrType.getId() + " does not single-value value: "
                        + attrs.length + "; ");
                continue;
            }

            ASN1Encodable attrVal = attrs[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 {
                Set<ASN1Encodable> otherAttrVals = otherAttrs.get(attrType);
                if (otherAttrVals == null) {
                    otherAttrVals = new HashSet<>();
                    otherAttrs.put(attrType, otherAttrVals);
                }
                otherAttrVals.add(attrVal);
            }
        }

        if (dateOfBirth != null) {
            attrTypes.remove(ObjectIdentifiers.DN_DATE_OF_BIRTH);
        }

        if (placeOfBirth != null) {
            attrTypes.remove(ObjectIdentifiers.DN_PLACE_OF_BIRTH);
        }

        if (gender != null) {
            attrTypes.remove(ObjectIdentifiers.DN_GENDER);
        }

        if (!countryOfCitizenshipList.isEmpty()) {
            attrTypes.remove(ObjectIdentifiers.DN_COUNTRY_OF_CITIZENSHIP);
        }

        if (!countryOfResidenceList.isEmpty()) {
            attrTypes.remove(ObjectIdentifiers.DN_COUNTRY_OF_RESIDENCE);
        }

        attrTypes.removeAll(otherAttrs.keySet());

        if (!attrTypes.isEmpty()) {
            List<String> attrTypeTexts = new LinkedList<>();
            for (ASN1ObjectIdentifier oid : attrTypes) {
                attrTypeTexts.add(oid.getId());
            }
            failureMsg.append("required attributes of types " + attrTypeTexts + " are not present; ");
        }

        if (dateOfBirth != null) {
            String timeStirng = dateOfBirth.getTimeString();
            if (!SubjectDnSpec.PATTERN_DATE_OF_BIRTH.matcher(timeStirng).matches()) {
                failureMsg.append("invalid dateOfBirth: " + timeStirng + "; ");
            }

            String exp = (expDateOfBirth == null) ? null : expDateOfBirth.getTimeString();
            if (!timeStirng.equalsIgnoreCase(exp)) {
                addViolation(failureMsg, "dateOfBirth", timeStirng, exp);
            }
        }

        if (gender != null) {
            if (!(gender.equalsIgnoreCase("F") || gender.equalsIgnoreCase("M"))) {
                failureMsg.append("invalid gender: " + gender + "; ");
            }
            if (!gender.equalsIgnoreCase(expGender)) {
                addViolation(failureMsg, "gender", gender, expGender);
            }
        }

        if (placeOfBirth != null) {
            if (!placeOfBirth.equals(expPlaceOfBirth)) {
                addViolation(failureMsg, "placeOfBirth", placeOfBirth, expPlaceOfBirth);
            }
        }

        if (!countryOfCitizenshipList.isEmpty()) {
            Set<String> diffs = strInBnotInA(expCountryOfCitizenshipList, countryOfCitizenshipList);
            if (CollectionUtil.isNonEmpty(diffs)) {
                failureMsg.append("countryOfCitizenship ").append(diffs.toString());
                failureMsg.append(" are present but not expected; ");
            }

            diffs = strInBnotInA(countryOfCitizenshipList, expCountryOfCitizenshipList);
            if (CollectionUtil.isNonEmpty(diffs)) {
                failureMsg.append("countryOfCitizenship ").append(diffs.toString());
                failureMsg.append(" are absent but are required; ");
            }
        }

        if (!countryOfResidenceList.isEmpty()) {
            Set<String> diffs = strInBnotInA(expCountryOfResidenceList, countryOfResidenceList);
            if (CollectionUtil.isNonEmpty(diffs)) {
                failureMsg.append("countryOfResidence ").append(diffs.toString());
                failureMsg.append(" are present but not expected; ");
            }

            diffs = strInBnotInA(countryOfResidenceList, expCountryOfResidenceList);
            if (CollectionUtil.isNonEmpty(diffs)) {
                failureMsg.append("countryOfResidence ").append(diffs.toString());
                failureMsg.append(" are absent but are required; ");
            }
        }

        if (!otherAttrs.isEmpty()) {
            for (ASN1ObjectIdentifier attrType : otherAttrs.keySet()) {
                Set<ASN1Encodable> expAttrValues = expOtherAttrs.get(attrType);
                if (expAttrValues == null) {
                    failureMsg.append("attribute of type " + attrType.getId() + " is present but not requested; ");
                    continue;
                }
                Set<ASN1Encodable> attrValues = otherAttrs.get(attrType);
                if (!attrValues.equals(expAttrValues)) {
                    failureMsg
                            .append("attribute of type " + attrType.getId() + " differs from the requested one; ");
                    continue;
                }
            }
        }
    } // method checkExtensionSubjectDirectoryAttributes

    private void checkExtensionSubjectAltName(final StringBuilder failureMsg, final byte[] extensionValue,
            final Extensions requestedExtensions, final ExtensionControl extControl,
            final X500Name requestedSubject) {
        Set<GeneralNameMode> conf = certProfile.getSubjectAltNameModes();

        GeneralName[] requested;
        try {
            requested = getRequestedSubjectAltNames(requestedSubject, requestedExtensions);
        } catch (CertprofileException | BadCertTemplateException ex) {
            String msg = "error while derive grantedSubject from requestedSubject";
            LogUtil.warn(LOG, ex, msg);
            failureMsg.append(msg);
            return;
        }

        if (requested == null) {
            failureMsg.append("extension is present but not expected; ");
            return;
        }

        GeneralName[] is = GeneralNames.getInstance(extensionValue).getNames();

        GeneralName[] expected = new GeneralName[requested.length];
        for (int i = 0; i < is.length; i++) {
            try {
                expected[i] = createGeneralName(is[i], conf);
            } catch (BadCertTemplateException ex) {
                failureMsg.append("could not process ").append(i + 1).append("-th name: ").append(ex.getMessage())
                        .append("; ");
                return;
            }
        }

        if (is.length != expected.length) {
            addViolation(failureMsg, "size of GeneralNames", is.length, expected.length);
            return;
        }

        for (int i = 0; i < is.length; i++) {
            if (!is[i].equals(expected[i])) {
                failureMsg.append(i + 1).append("-th name does not match the requested one; ");
            }
        }
    } // method checkExtensionSubjectAltName

    private GeneralName[] getRequestedSubjectAltNames(final X500Name requestedSubject,
            final Extensions requestedExtensions) throws CertprofileException, BadCertTemplateException {
        ASN1Encodable extValue = (requestedExtensions == null) ? null
                : requestedExtensions.getExtensionParsedValue(Extension.subjectAlternativeName);

        Map<ASN1ObjectIdentifier, GeneralNameTag> subjectToSubjectAltNameModes = certProfile
                .getSubjectToSubjectAltNameModes();
        if (extValue == null && subjectToSubjectAltNameModes == null) {
            return null;
        }

        GeneralNames reqNames = (extValue == null) ? null : GeneralNames.getInstance(extValue);

        Set<GeneralNameMode> subjectAltNameModes = certProfile.getSubjectAltNameModes();
        if (subjectAltNameModes == null && subjectToSubjectAltNameModes == null) {
            return (reqNames == null) ? null : reqNames.getNames();
        }

        List<GeneralName> grantedNames = new LinkedList<>();
        // copy the required attributes of Subject
        if (subjectToSubjectAltNameModes != null) {
            X500Name grantedSubject;
            try {
                grantedSubject = certProfile.getSubject(requestedSubject).getGrantedSubject();
            } catch (CertprofileException | BadCertTemplateException ex) {
                if (certProfile.getSpecialCertprofileBehavior() == null) {
                    throw ex;
                }

                LogUtil.warn(LOG, ex, "could not derive granted subject from requested subject");
                grantedSubject = requestedSubject;
            }

            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(reqL[i]);
            }
        }

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

    private void checkExtensionSubjectInfoAccess(final StringBuilder failureMsg, final byte[] extensionValue,
            final Extensions requestedExtensions, final ExtensionControl extControl) {
        Map<ASN1ObjectIdentifier, Set<GeneralNameMode>> conf = certProfile.getSubjectInfoAccessModes();
        if (conf == null) {
            failureMsg.append("extension is present but not expected; ");
            return;
        }

        ASN1Encodable requestExtValue = null;
        if (requestedExtensions != null) {
            requestExtValue = requestedExtensions.getExtensionParsedValue(Extension.subjectInfoAccess);
        }
        if (requestExtValue == null) {
            failureMsg.append("extension is present but not expected; ");
            return;
        }

        ASN1Sequence requestSeq = ASN1Sequence.getInstance(requestExtValue);
        ASN1Sequence certSeq = ASN1Sequence.getInstance(extensionValue);

        int size = requestSeq.size();

        if (certSeq.size() != size) {
            addViolation(failureMsg, "size of GeneralNames", certSeq.size(), size);
            return;
        }

        for (int i = 0; i < size; i++) {
            AccessDescription ad = AccessDescription.getInstance(requestSeq.getObjectAt(i));
            ASN1ObjectIdentifier accessMethod = ad.getAccessMethod();
            Set<GeneralNameMode> generalNameModes = conf.get(accessMethod);

            if (generalNameModes == null) {
                failureMsg.append("accessMethod in requestedExtension ");
                failureMsg.append(accessMethod.getId()).append(" is not allowed; ");
                continue;
            }

            AccessDescription certAccessDesc = AccessDescription.getInstance(certSeq.getObjectAt(i));
            ASN1ObjectIdentifier certAccessMethod = certAccessDesc.getAccessMethod();

            boolean bo = (accessMethod == null) ? (certAccessMethod == null)
                    : accessMethod.equals(certAccessMethod);

            if (!bo) {
                addViolation(failureMsg, "accessMethod",
                        (certAccessMethod == null) ? "null" : certAccessMethod.getId(),
                        (accessMethod == null) ? "null" : accessMethod.getId());
                continue;
            }

            GeneralName accessLocation;
            try {
                accessLocation = createGeneralName(ad.getAccessLocation(), generalNameModes);
            } catch (BadCertTemplateException ex) {
                failureMsg.append("invalid requestedExtension: ").append(ex.getMessage());
                failureMsg.append("; ");
                continue;
            }

            GeneralName certAccessLocation = certAccessDesc.getAccessLocation();
            if (!certAccessLocation.equals(accessLocation)) {
                failureMsg.append("accessLocation does not match the requested one; ");
            }
        }
    } // method checkExtensionSubjectInfoAccess

    private void checkExtensionIssuerAltNames(final StringBuilder failureMsg, final byte[] extensionValue,
            final X509IssuerInfo issuerInfo) {
        Extension caSubjectAltExtension = issuerInfo.getBcCert().getTBSCertificate().getExtensions()
                .getExtension(Extension.subjectAlternativeName);
        if (caSubjectAltExtension == null) {
            failureMsg.append("issuerAlternativeName is present but expected 'none'; ");
            return;
        }

        byte[] caSubjectAltExtensionValue = caSubjectAltExtension.getExtnValue().getOctets();
        if (!Arrays.equals(caSubjectAltExtensionValue, extensionValue)) {
            addViolation(failureMsg, "issuerAltNames", hex(extensionValue), hex(caSubjectAltExtensionValue));
        }
    } // method checkExtensionIssuerAltNames

    private void checkExtensionCrlDistributionPoints(final StringBuilder failureMsg, final byte[] extensionValue,
            final X509IssuerInfo issuerInfo) {
        CRLDistPoint isCrlDistPoints = CRLDistPoint.getInstance(extensionValue);
        DistributionPoint[] isDistributionPoints = isCrlDistPoints.getDistributionPoints();
        if (isDistributionPoints == null) {
            addViolation(failureMsg, "size of CRLDistributionPoints", 0, 1);
            return;
        } else {
            int len = isDistributionPoints.length;
            if (len != 1) {
                addViolation(failureMsg, "size of CRLDistributionPoints", len, 1);
                return;
            }
        }

        Set<String> isCrlUrls = new HashSet<>();
        for (DistributionPoint entry : isDistributionPoints) {
            int asn1Type = entry.getDistributionPoint().getType();
            if (asn1Type != DistributionPointName.FULL_NAME) {
                addViolation(failureMsg, "tag of DistributionPointName of CRLDistibutionPoints", asn1Type,
                        DistributionPointName.FULL_NAME);
                continue;
            }

            GeneralNames isDistributionPointNames = GeneralNames
                    .getInstance(entry.getDistributionPoint().getName());
            GeneralName[] names = isDistributionPointNames.getNames();

            for (int i = 0; i < names.length; i++) {
                GeneralName name = names[i];
                if (name.getTagNo() != GeneralName.uniformResourceIdentifier) {
                    addViolation(failureMsg, "tag of CRL URL", name.getTagNo(),
                            GeneralName.uniformResourceIdentifier);
                } else {
                    String uri = ((ASN1String) name.getName()).getString();
                    isCrlUrls.add(uri);
                }
            }

            Set<String> expCrlUrls = issuerInfo.getCrlUrls();
            Set<String> diffs = strInBnotInA(expCrlUrls, isCrlUrls);
            if (CollectionUtil.isNonEmpty(diffs)) {
                failureMsg.append("CRL URLs ").append(diffs.toString()).append(" are present but not expected; ");
            }

            diffs = strInBnotInA(isCrlUrls, expCrlUrls);
            if (CollectionUtil.isNonEmpty(diffs)) {
                failureMsg.append("CRL URLs ").append(diffs.toString()).append(" are absent but are required; ");
            }
        }
    } // method checkExtensionCrlDistributionPoints

    private void checkExtensionDeltaCrlDistributionPoints(final StringBuilder failureMsg,
            final byte[] extensionValue, final X509IssuerInfo issuerInfo) {
        CRLDistPoint isCrlDistPoints = CRLDistPoint.getInstance(extensionValue);
        DistributionPoint[] isDistributionPoints = isCrlDistPoints.getDistributionPoints();
        if (isDistributionPoints == null) {
            addViolation(failureMsg, "size of CRLDistributionPoints (deltaCRL)", 0, 1);
            return;
        } else {
            int len = isDistributionPoints.length;
            if (len != 1) {
                addViolation(failureMsg, "size of CRLDistributionPoints (deltaCRL)", len, 1);
                return;
            }
        }

        Set<String> isCrlUrls = new HashSet<>();
        for (DistributionPoint entry : isDistributionPoints) {
            int asn1Type = entry.getDistributionPoint().getType();
            if (asn1Type != DistributionPointName.FULL_NAME) {
                addViolation(failureMsg, "tag of DistributionPointName of CRLDistibutionPoints (deltaCRL)",
                        asn1Type, DistributionPointName.FULL_NAME);
                continue;
            }

            GeneralNames isDistributionPointNames = GeneralNames
                    .getInstance(entry.getDistributionPoint().getName());
            GeneralName[] names = isDistributionPointNames.getNames();

            for (int i = 0; i < names.length; i++) {
                GeneralName name = names[i];
                if (name.getTagNo() != GeneralName.uniformResourceIdentifier) {
                    addViolation(failureMsg, "tag of deltaCRL URL", name.getTagNo(),
                            GeneralName.uniformResourceIdentifier);
                } else {
                    String uri = ((ASN1String) name.getName()).getString();
                    isCrlUrls.add(uri);
                }
            }

            Set<String> expCrlUrls = issuerInfo.getCrlUrls();
            Set<String> diffs = strInBnotInA(expCrlUrls, isCrlUrls);
            if (CollectionUtil.isNonEmpty(diffs)) {
                failureMsg.append("deltaCRL URLs ").append(diffs.toString())
                        .append(" are present but not expected; ");
            }

            diffs = strInBnotInA(isCrlUrls, expCrlUrls);
            if (CollectionUtil.isNonEmpty(diffs)) {
                failureMsg.append("deltaCRL URLs ").append(diffs.toString())
                        .append(" are absent but are required; ");
            }
        }
    } // method checkExtensionDeltaCrlDistributionPoints

    private void checkExtensionAdmission(final StringBuilder failureMsg, final byte[] extensionValue,
            final Extensions requestedExtensions, final ExtensionControl extControl) {
        AdmissionSyntaxOption conf = certProfile.getAdmission();
        ASN1ObjectIdentifier type = ObjectIdentifiers.id_extension_admission;
        if (conf == null) {
            byte[] expected = getExpectedExtValue(type, requestedExtensions, extControl);
            if (!Arrays.equals(expected, extensionValue)) {
                addViolation(failureMsg, "extension value", hex(extensionValue),
                        (expected == null) ? "not present" : hex(expected));
            }
            return;
        }

        List<List<String>> reqRegNumsList = null;
        if (requestedExtensions != null && conf.isInputFromRequestRequired()) {
            Extension extension = requestedExtensions.getExtension(type);
            if (extension == null) {
                failureMsg.append("no Admission extension is contained in the request;");
                return;
            }

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

            final int n = reqAdmissions.length;
            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);
                }
            }
        }

        try {
            byte[] expected = conf.getExtensionValue(reqRegNumsList).getValue().toASN1Primitive().getEncoded();
            if (!Arrays.equals(expected, extensionValue)) {
                addViolation(failureMsg, "extension valus", hex(extensionValue), hex(expected));
            }
        } catch (IOException ex) {
            LogUtil.error(LOG, ex);
            failureMsg.append("IOException while computing the expected extension value;");
            return;
        } catch (BadCertTemplateException ex) {
            LogUtil.error(LOG, ex);
            failureMsg.append("BadCertTemplateException while computing the expected extension value;");
        }

    } // method checkExtensionAdmission

    private void checkExtensionAuthorityInfoAccess(final StringBuilder failureMsg, final byte[] extensionValue,
            final X509IssuerInfo issuerInfo) {
        AuthorityInfoAccessControl aiaControl = certProfile.getAiaControl();
        Set<String> expCaIssuerUris = (aiaControl == null || aiaControl.includesCaIssuers())
                ? issuerInfo.getCaIssuerUrls()
                : Collections.emptySet();

        Set<String> expOcspUris = (aiaControl == null || aiaControl.includesOcsp()) ? issuerInfo.getOcspUrls()
                : Collections.emptySet();

        if (CollectionUtil.isEmpty(expCaIssuerUris) && CollectionUtil.isEmpty(expOcspUris)) {
            failureMsg.append("AIA is present but expected is 'none'; ");
            return;
        }

        AuthorityInformationAccess isAia = AuthorityInformationAccess.getInstance(extensionValue);
        checkAia(failureMsg, isAia, X509ObjectIdentifiers.id_ad_caIssuers, expCaIssuerUris);
        checkAia(failureMsg, isAia, X509ObjectIdentifiers.id_ad_ocsp, expOcspUris);
    } // method checkExtensionAuthorityInfoAccess

    private void checkExtensionOcspNocheck(final StringBuilder failureMsg, final byte[] extensionValue) {
        if (!Arrays.equals(DER_NULL, extensionValue)) {
            failureMsg.append("value is not DER NULL; ");
        }
    }

    private void checkExtensionRestriction(final StringBuilder failureMsg, final byte[] extensionValue,
            final Extensions requestedExtensions, final ExtensionControl extControl) {
        checkDirectoryString(ObjectIdentifiers.id_extension_restriction, restriction, failureMsg, extensionValue,
                requestedExtensions, extControl);
    }

    private void checkExtensionAdditionalInformation(final StringBuilder failureMsg, final byte[] extensionValue,
            final Extensions requestedExtensions, final ExtensionControl extControl) {
        checkDirectoryString(ObjectIdentifiers.id_extension_additionalInformation, additionalInformation,
                failureMsg, extensionValue, requestedExtensions, extControl);
    }

    private void checkDirectoryString(final ASN1ObjectIdentifier extType, final QaDirectoryString conf,
            final StringBuilder failureMsg, final byte[] extensionValue, final Extensions requestedExtensions,
            final ExtensionControl extControl) {
        if (conf == null) {
            byte[] expected = getExpectedExtValue(extType, requestedExtensions, extControl);
            if (!Arrays.equals(expected, extensionValue)) {
                addViolation(failureMsg, "extension values", hex(extensionValue),
                        (expected == null) ? "not present" : hex(expected));
            }
            return;
        }

        ASN1Primitive asn1;
        try {
            asn1 = ASN1Primitive.fromByteArray(extensionValue);
        } catch (IOException ex) {
            failureMsg.append("invalid syntax of extension value; ");
            return;
        }

        boolean correctStringType;
        switch (conf.getType()) {
        case bmpString:
            correctStringType = (asn1 instanceof DERBMPString);
            break;
        case printableString:
            correctStringType = (asn1 instanceof DERPrintableString);
            break;
        case teletexString:
            correctStringType = (asn1 instanceof DERT61String);
            break;
        case utf8String:
            correctStringType = (asn1 instanceof DERUTF8String);
            break;
        default:
            throw new RuntimeException("should not reach here, unknown DirectoryStringType " + conf.getType());
        } // end switch

        if (!correctStringType) {
            failureMsg.append("extension value is not of type DirectoryString.").append(conf.getText())
                    .append("; ");
            return;
        }

        String extTextValue = ((ASN1String) asn1).getString();
        if (!conf.getText().equals(extTextValue)) {
            addViolation(failureMsg, "content", extTextValue, conf.getText());
        }
    } // method checkDirectoryString

    private void checkExtensionValidityModel(final StringBuilder failureMsg, final byte[] extensionValue,
            final Extensions requestedExtensions, final ExtensionControl extControl) {
        ASN1ObjectIdentifier conf = validityModelId;
        if (conf == null) {
            byte[] expected = getExpectedExtValue(ObjectIdentifiers.id_extension_validityModel, requestedExtensions,
                    extControl);
            if (!Arrays.equals(expected, extensionValue)) {
                addViolation(failureMsg, "extension values", hex(extensionValue),
                        (expected == null) ? "not present" : hex(expected));
            }
            return;
        }

        ASN1ObjectIdentifier extValue = ASN1ObjectIdentifier.getInstance(extensionValue);
        if (!conf.equals(extValue)) {
            addViolation(failureMsg, "content", extValue, conf);
        }
    } // method checkExtensionValidityModel

    private void checkExtensionPrivateKeyUsagePeriod(final StringBuilder failureMsg, final byte[] extensionValue,
            final Date certNotBefore, final Date certNotAfter) {
        ASN1GeneralizedTime notBefore = new ASN1GeneralizedTime(certNotBefore);
        Date dateNotAfter;
        CertValidity privateKeyUsagePeriod = certProfile.getPrivateKeyUsagePeriod();
        if (privateKeyUsagePeriod == null) {
            dateNotAfter = certNotAfter;
        } else {
            dateNotAfter = privateKeyUsagePeriod.add(certNotBefore);
            if (dateNotAfter.after(certNotAfter)) {
                dateNotAfter = certNotAfter;
            }
        }
        ASN1GeneralizedTime notAfter = new ASN1GeneralizedTime(dateNotAfter);

        org.bouncycastle.asn1.x509.PrivateKeyUsagePeriod extValue = org.bouncycastle.asn1.x509.PrivateKeyUsagePeriod
                .getInstance(extensionValue);

        ASN1GeneralizedTime time = extValue.getNotBefore();
        if (time == null) {
            failureMsg.append("notBefore is absent but expected present; ");
        } else if (!time.equals(notBefore)) {
            addViolation(failureMsg, "notBefore", time.getTimeString(), notBefore.getTimeString());
        }

        time = extValue.getNotAfter();
        if (time == null) {
            failureMsg.append("notAfter is absent but expected present; ");
        } else if (!time.equals(notAfter)) {
            addViolation(failureMsg, "notAfter", time.getTimeString(), notAfter.getTimeString());
        }
    } // method checkExtensionPrivateKeyUsagePeriod

    private void checkExtensionQcStatements(final StringBuilder failureMsg, final byte[] extensionValue,
            final Extensions requestedExtensions, final ExtensionControl extControl) {
        QcStatements conf = qcStatements;
        if (conf == null) {
            byte[] expected = getExpectedExtValue(Extension.qCStatements, requestedExtensions, extControl);
            if (!Arrays.equals(expected, extensionValue)) {
                addViolation(failureMsg, "extension values", extensionValue,
                        (expected == null) ? "not present" : hex(expected));
            }
            return;
        }

        final int expSize = conf.getQcStatement().size();
        ASN1Sequence extValue = ASN1Sequence.getInstance(extensionValue);
        final int isSize = extValue.size();
        if (isSize != expSize) {
            addViolation(failureMsg, "number of statements", isSize, expSize);
            return;
        }

        // extract the euLimit and pdsLocations data from request
        Map<String, int[]> reqQcEuLimits = new HashMap<>();
        Extension reqExtension = (requestedExtensions == null) ? null
                : requestedExtensions.getExtension(Extension.qCStatements);
        if (reqExtension != null) {
            ASN1Sequence seq = ASN1Sequence.getInstance(reqExtension.getParsedValue());

            final int n = seq.size();
            for (int j = 0; j < n; j++) {
                QCStatement stmt = QCStatement.getInstance(seq.getObjectAt(j));
                if (ObjectIdentifiers.id_etsi_qcs_QcLimitValue.equals(stmt.getStatementId())) {
                    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());
                    reqQcEuLimits.put(currencyS, new int[] { amount, exponent });
                }
            }
        }

        for (int i = 0; i < expSize; i++) {
            QCStatement is = QCStatement.getInstance(extValue.getObjectAt(i));
            QcStatementType exp = conf.getQcStatement().get(i);
            if (!is.getStatementId().getId().equals(exp.getStatementId().getValue())) {
                addViolation(failureMsg, "statmentId[" + i + "]", is.getStatementId().getId(),
                        exp.getStatementId().getValue());
                continue;
            }

            if (exp.getStatementValue() == null) {
                if (is.getStatementInfo() != null) {
                    addViolation(failureMsg, "statmentInfo[" + i + "]", "present", "absent");
                }
                continue;
            }

            if (is.getStatementInfo() == null) {
                addViolation(failureMsg, "statmentInfo[" + i + "]", "absent", "present");
                continue;
            }

            QcStatementValueType expStatementValue = exp.getStatementValue();
            try {
                if (expStatementValue.getConstant() != null) {
                    byte[] expValue = expStatementValue.getConstant().getValue();
                    byte[] isValue = is.getStatementInfo().toASN1Primitive().getEncoded();
                    if (!Arrays.equals(isValue, expValue)) {
                        addViolation(failureMsg, "statementInfo[" + i + "]", hex(isValue), hex(expValue));
                    }
                } else if (expStatementValue.getQcRetentionPeriod() != null) {
                    String isValue = ASN1Integer.getInstance(is.getStatementInfo()).toString();
                    String expValue = expStatementValue.getQcRetentionPeriod().toString();
                    if (!isValue.equals(expValue)) {
                        addViolation(failureMsg, "statementInfo[" + i + "]", isValue, expValue);
                    }
                } else if (expStatementValue.getPdsLocations() != null) {
                    Set<String> pdsLocations = new HashSet<>();
                    ASN1Sequence pdsLocsSeq = ASN1Sequence.getInstance(is.getStatementInfo());
                    int size = pdsLocsSeq.size();
                    for (int k = 0; k < size; k++) {
                        ASN1Sequence pdsLocSeq = ASN1Sequence.getInstance(pdsLocsSeq.getObjectAt(k));
                        int size2 = pdsLocSeq.size();
                        if (size2 != 2) {
                            throw new IllegalArgumentException("sequence size is " + size2 + " but expected 2");
                        }
                        String url = DERIA5String.getInstance(pdsLocSeq.getObjectAt(0)).getString();
                        String lang = DERPrintableString.getInstance(pdsLocSeq.getObjectAt(1)).getString();
                        pdsLocations.add("url=" + url + ",lang=" + lang);
                    }

                    PdsLocationsType pdsLocationsConf = expStatementValue.getPdsLocations();
                    Set<String> expectedPdsLocations = new HashSet<>();
                    for (PdsLocationType m : pdsLocationsConf.getPdsLocation()) {
                        expectedPdsLocations.add("url=" + m.getUrl() + ",lang=" + m.getLanguage());
                    }

                    Set<String> diffs = strInBnotInA(expectedPdsLocations, pdsLocations);
                    if (CollectionUtil.isNonEmpty(diffs)) {
                        failureMsg.append("statementInfo[" + i + "]: ").append(diffs.toString());
                        failureMsg.append(" are present but not expected; ");
                    }

                    diffs = strInBnotInA(pdsLocations, expectedPdsLocations);
                    if (CollectionUtil.isNonEmpty(diffs)) {
                        failureMsg.append("statementInfo[" + i + "]: ").append(diffs.toString());
                        failureMsg.append(" are absent but are required; ");
                    }
                } else if (expStatementValue.getQcEuLimitValue() != null) {
                    QcEuLimitValueType euLimitConf = expStatementValue.getQcEuLimitValue();
                    String expCurrency = euLimitConf.getCurrency().toUpperCase();
                    int[] expAmountExp = reqQcEuLimits.get(expCurrency);

                    Range2Type range = euLimitConf.getAmount();
                    int value;
                    if (range.getMin() == range.getMax()) {
                        value = range.getMin();
                    } else if (expAmountExp != null) {
                        value = expAmountExp[0];
                    } else {
                        failureMsg.append("found no QcEuLimit for currency '").append(expCurrency).append("'; ");
                        return;
                    }
                    // CHECKSTYLE:SKIP
                    String expAmount = Integer.toString(value);

                    range = euLimitConf.getExponent();
                    if (range.getMin() == range.getMax()) {
                        value = range.getMin();
                    } else if (expAmountExp != null) {
                        value = expAmountExp[1];
                    } else {
                        failureMsg.append("found no QcEuLimit for currency '").append(expCurrency).append("'; ");
                        return;
                    }
                    String expExponent = Integer.toString(value);

                    MonetaryValue monterayValue = MonetaryValue.getInstance(is.getStatementInfo());
                    Iso4217CurrencyCode currency = monterayValue.getCurrency();
                    String isCurrency = currency.isAlphabetic() ? currency.getAlphabetic()
                            : Integer.toString(currency.getNumeric());
                    String isAmount = monterayValue.getAmount().toString();
                    String isExponent = monterayValue.getExponent().toString();
                    if (!isCurrency.equals(expCurrency)) {
                        addViolation(failureMsg, "statementInfo[" + i + "].qcEuLimit.currency", isCurrency,
                                expCurrency);
                    }
                    if (!isAmount.equals(expAmount)) {
                        addViolation(failureMsg, "statementInfo[" + i + "].qcEuLimit.amount", isAmount, expAmount);
                    }
                    if (!isExponent.equals(expExponent)) {
                        addViolation(failureMsg, "statementInfo[" + i + "].qcEuLimit.exponent", isExponent,
                                expExponent);
                    }
                } else {
                    throw new RuntimeException("statementInfo[" + i + "]should not reach here");
                }
            } catch (IOException ex) {
                failureMsg.append("statementInfo[").append(i).append("] has incorrect syntax; ");
            }
        }
    } // method checkExtensionQcStatements

    private void checkExtensionBiometricInfo(final StringBuilder failureMsg, final byte[] extensionValue,
            final Extensions requestedExtensions, final ExtensionControl extControl) {
        BiometricInfoOption conf = certProfile.getBiometricInfo();

        if (conf == null) {
            failureMsg.append("extension is present but not expected; ");
            return;
        }

        ASN1Encodable extInRequest = null;
        if (requestedExtensions != null) {
            extInRequest = requestedExtensions.getExtensionParsedValue(Extension.biometricInfo);
        }

        if (extInRequest == null) {
            failureMsg.append("extension is present but not expected; ");
            return;
        }

        ASN1Sequence extValueInReq = ASN1Sequence.getInstance(extInRequest);
        final int expSize = extValueInReq.size();

        ASN1Sequence extValue = ASN1Sequence.getInstance(extensionValue);
        final int isSize = extValue.size();
        if (isSize != expSize) {
            addViolation(failureMsg, "number of biometricData", isSize, expSize);
            return;
        }

        for (int i = 0; i < expSize; i++) {
            BiometricData isData = BiometricData.getInstance(extValue.getObjectAt(i));
            BiometricData expData = BiometricData.getInstance(extValueInReq.getObjectAt(i));

            TypeOfBiometricData isType = isData.getTypeOfBiometricData();
            TypeOfBiometricData expType = expData.getTypeOfBiometricData();
            if (!isType.equals(expType)) {
                String isStr = isType.isPredefined() ? Integer.toString(isType.getPredefinedBiometricType())
                        : isType.getBiometricDataOid().getId();
                String expStr = expType.isPredefined() ? Integer.toString(expType.getPredefinedBiometricType())
                        : expType.getBiometricDataOid().getId();

                addViolation(failureMsg, "biometricData[" + i + "].typeOfBiometricData", isStr, expStr);
            }

            ASN1ObjectIdentifier is = isData.getHashAlgorithm().getAlgorithm();
            ASN1ObjectIdentifier exp = expData.getHashAlgorithm().getAlgorithm();
            if (!is.equals(exp)) {
                addViolation(failureMsg, "biometricData[" + i + "].hashAlgorithm", is.getId(), exp.getId());
            }

            ASN1Encodable isHashAlgoParam = isData.getHashAlgorithm().getParameters();
            if (isHashAlgoParam == null) {
                failureMsg.append("biometricData[").append(i).append("].hashAlgorithm.parameters is 'present'");
                failureMsg.append(" but expected 'absent'; ");
            } else {
                try {
                    byte[] isBytes = isHashAlgoParam.toASN1Primitive().getEncoded();
                    if (!Arrays.equals(isBytes, DER_NULL)) {
                        addViolation(failureMsg, "biometricData[" + i + "].biometricDataHash.parameters",
                                hex(isBytes), hex(DER_NULL));
                    }
                } catch (IOException ex) {
                    failureMsg.append("biometricData[").append(i)
                            .append("].biometricDataHash.parameters has incorrect syntax; ");
                }
            }

            byte[] isBytes = isData.getBiometricDataHash().getOctets();
            byte[] expBytes = expData.getBiometricDataHash().getOctets();
            if (!Arrays.equals(isBytes, expBytes)) {
                addViolation(failureMsg, "biometricData[" + i + "].biometricDataHash", hex(isBytes), hex(expBytes));
            }

            DERIA5String str = isData.getSourceDataUri();
            String isSourceDataUri = (str == null) ? null : str.getString();

            String expSourceDataUri = null;
            if (conf.getSourceDataUriOccurrence() != TripleState.FORBIDDEN) {
                str = expData.getSourceDataUri();
                expSourceDataUri = (str == null) ? null : str.getString();
            }

            if (expSourceDataUri == null) {
                if (isSourceDataUri != null) {
                    addViolation(failureMsg, "biometricData[" + i + "].sourceDataUri", "present", "absent");
                }
            } else {
                if (isSourceDataUri == null) {
                    failureMsg.append("biometricData[").append(i).append("].sourceDataUri is 'absent'");
                    failureMsg.append(" but expected 'present'; ");
                } else if (!isSourceDataUri.equals(expSourceDataUri)) {
                    addViolation(failureMsg, "biometricData[" + i + "].sourceDataUri", isSourceDataUri,
                            expSourceDataUri);
                }
            }
        }
    } // method checkExtensionBiometricInfo

    private void checkExtensionAuthorizationTemplate(final StringBuilder failureMsg, final byte[] extensionValue,
            final Extensions requestedExtensions, final ExtensionControl extControl) {
        QaAuthorizationTemplate conf = authorizationTemplate;

        if (conf == null) {
            byte[] expected = getExpectedExtValue(ObjectIdentifiers.id_xipki_ext_authorizationTemplate,
                    requestedExtensions, extControl);
            if (!Arrays.equals(expected, extensionValue)) {
                addViolation(failureMsg, "extension values", hex(extensionValue),
                        (expected == null) ? "not present" : hex(expected));
            }
            return;
        }

        ASN1Sequence seq = ASN1Sequence.getInstance(extensionValue);
        ASN1ObjectIdentifier type = ASN1ObjectIdentifier.getInstance(seq.getObjectAt(0));
        ASN1OctetString accessRights = DEROctetString.getInstance(seq.getObjectAt(1));
        if (!conf.getType().equals(type.getId())) {
            addViolation(failureMsg, "type", type.getId(), conf.getType());
        }

        byte[] isRights = accessRights.getOctets();
        if (!Arrays.equals(conf.getAccessRights(), isRights)) {
            addViolation(failureMsg, "accessRights", hex(isRights), hex(conf.getAccessRights()));
        }
    } // method checkExtensionAuthorizationTemplate

    private Set<KeyUsageControl> getKeyusage(final boolean required) {
        Set<KeyUsageControl> ret = new HashSet<>();

        Set<KeyUsageControl> controls = certProfile.getKeyusages();
        if (controls != null) {
            for (KeyUsageControl control : controls) {
                if (control.isRequired() == required) {
                    ret.add(control);
                }
            }
        }
        return ret;
    }

    private Set<ExtKeyUsageControl> getExtKeyusage(final boolean required) {
        Set<ExtKeyUsageControl> ret = new HashSet<>();

        Set<ExtKeyUsageControl> controls = certProfile.getExtendedKeyusages();
        if (controls != null) {
            for (ExtKeyUsageControl control : controls) {
                if (control.isRequired() == required) {
                    ret.add(control);
                }
            }
        }
        return ret;
    }

    private byte[] getConstantExtensionValue(final ASN1ObjectIdentifier type) {
        return (constantExtensions == null) ? null : constantExtensions.get(type).getValue();
    }

    private 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

    public static Map<ASN1ObjectIdentifier, QaExtensionValue> buildConstantExtesions(
            final ExtensionsType extensionsType) throws CertprofileException {
        if (extensionsType == null) {
            return null;
        }

        Map<ASN1ObjectIdentifier, QaExtensionValue> map = new HashMap<>();

        for (ExtensionType m : extensionsType.getExtension()) {
            if (m.getValue() == null || !(m.getValue().getAny() instanceof ConstantExtValue)) {
                continue;
            }

            ASN1ObjectIdentifier oid = new ASN1ObjectIdentifier(m.getType().getValue());
            if (Extension.subjectAlternativeName.equals(oid) || Extension.subjectInfoAccess.equals(oid)
                    || Extension.biometricInfo.equals(oid)) {
                continue;
            }

            ConstantExtValue extConf = (ConstantExtValue) m.getValue().getAny();
            byte[] encodedValue = extConf.getValue();
            ASN1StreamParser parser = new ASN1StreamParser(encodedValue);
            try {
                parser.readObject();
            } catch (IOException ex) {
                throw new CertprofileException("could not parse the constant extension value", ex);
            }
            QaExtensionValue extension = new QaExtensionValue(m.isCritical(), encodedValue);
            map.put(oid, extension);
        }

        if (CollectionUtil.isEmpty(map)) {
            return null;
        }

        return Collections.unmodifiableMap(map);
    } // method buildConstantExtesions

    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);
        }
    }

    private static String hex(final byte[] bytes) {
        return Hex.toHexString(bytes);
    }

    private static Set<String> strInBnotInA(final Collection<String> collectionA,
            final Collection<String> collectionB) {
        if (collectionB == null) {
            return Collections.emptySet();
        }

        Set<String> result = new HashSet<>();
        for (String entry : collectionB) {
            if (collectionA == null || !collectionA.contains(entry)) {
                result.add(entry);
            }
        }
        return result;
    }

    static Set<Range> buildParametersMap(final RangesType ranges) {
        if (ranges == null) {
            return null;
        }

        Set<Range> ret = new HashSet<>();
        for (RangeType range : ranges.getRange()) {
            if (range.getMin() != null || range.getMax() != null) {
                ret.add(new Range(range.getMin(), range.getMax()));
            }
        }
        return ret;
    }

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

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

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

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

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

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

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

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

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

    private static Set<String> getKeyUsage(final byte[] extensionValue) {
        Set<String> usages = new HashSet<>();
        org.bouncycastle.asn1.x509.KeyUsage reqKeyUsage = org.bouncycastle.asn1.x509.KeyUsage
                .getInstance(extensionValue);
        for (KeyUsage k : KeyUsage.values()) {
            if (reqKeyUsage.hasUsages(k.getBcUsage())) {
                usages.add(k.getName());
            }
        }

        return usages;
    }

    private static Set<String> getExtKeyUsage(final byte[] extensionValue) {
        Set<String> usages = new HashSet<>();
        org.bouncycastle.asn1.x509.ExtendedKeyUsage reqKeyUsage = org.bouncycastle.asn1.x509.ExtendedKeyUsage
                .getInstance(extensionValue);
        for (KeyPurposeId usage : reqKeyUsage.getUsages()) {
            usages.add(usage.getId());
        }
        return usages;
    }

    private static void checkAia(final StringBuilder failureMsg, final AuthorityInformationAccess aia,
            final ASN1ObjectIdentifier accessMethod, final Set<String> expectedUris) {
        String typeDesc;
        if (X509ObjectIdentifiers.id_ad_ocsp.equals(accessMethod)) {
            typeDesc = "OCSP";
        } else if (X509ObjectIdentifiers.id_ad_caIssuers.equals(accessMethod)) {
            typeDesc = "caIssuer";
        } else {
            typeDesc = accessMethod.getId();
        }

        List<AccessDescription> isAccessDescriptions = new LinkedList<>();
        for (AccessDescription accessDescription : aia.getAccessDescriptions()) {
            if (accessMethod.equals(accessDescription.getAccessMethod())) {
                isAccessDescriptions.add(accessDescription);
            }
        }

        int size = isAccessDescriptions.size();
        if (size != expectedUris.size()) {
            addViolation(failureMsg, "number of AIA " + typeDesc + " URIs", size, expectedUris.size());
            return;
        }

        Set<String> isUris = new HashSet<>();
        for (int i = 0; i < size; i++) {
            GeneralName isAccessLocation = isAccessDescriptions.get(i).getAccessLocation();
            if (isAccessLocation.getTagNo() != GeneralName.uniformResourceIdentifier) {
                addViolation(failureMsg, "tag of accessLocation of AIA ", isAccessLocation.getTagNo(),
                        GeneralName.uniformResourceIdentifier);
            } else {
                String isOcspUri = ((ASN1String) isAccessLocation.getName()).getString();
                isUris.add(isOcspUri);
            }
        }

        Set<String> diffs = strInBnotInA(expectedUris, isUris);
        if (CollectionUtil.isNonEmpty(diffs)) {
            failureMsg.append(typeDesc).append(" URIs ").append(diffs.toString());
            failureMsg.append(" are present but not expected; ");
        }

        diffs = strInBnotInA(isUris, expectedUris);
        if (CollectionUtil.isNonEmpty(diffs)) {
            failureMsg.append(typeDesc).append(" URIs ").append(diffs.toString());
            failureMsg.append(" are absent but are required; ");
        }
    } // method checkAia

    private static void addViolation(final StringBuilder failureMsg, final String field, final Object is,
            final Object expected) {
        failureMsg.append(field).append(" is '").append(is);
        failureMsg.append("' but expected '").append(expected).append("';");
    }

}