org.xipki.commons.security.util.X509Util.java Source code

Java tutorial

Introduction

Here is the source code for org.xipki.commons.security.util.X509Util.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.commons.security.util;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.security.NoSuchProviderException;
import java.security.cert.CRLException;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509CRL;
import java.security.cert.X509Certificate;
import java.security.spec.InvalidKeySpecException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import javax.security.auth.x500.X500Principal;

import org.bouncycastle.asn1.ASN1Encodable;
import org.bouncycastle.asn1.ASN1EncodableVector;
import org.bouncycastle.asn1.ASN1Encoding;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.ASN1OctetString;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.ASN1String;
import org.bouncycastle.asn1.DERNull;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.asn1.DERTaggedObject;
import org.bouncycastle.asn1.DERUTF8String;
import org.bouncycastle.asn1.DERUniversalString;
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
import org.bouncycastle.asn1.x500.AttributeTypeAndValue;
import org.bouncycastle.asn1.x500.DirectoryString;
import org.bouncycastle.asn1.x500.RDN;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x500.style.IETFUtils;
import org.bouncycastle.asn1.x500.style.RFC4519Style;
import org.bouncycastle.asn1.x509.AccessDescription;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.asn1.x509.AuthorityKeyIdentifier;
import org.bouncycastle.asn1.x509.CertificateList;
import org.bouncycastle.asn1.x509.DSAParameter;
import org.bouncycastle.asn1.x509.ExtendedKeyUsage;
import org.bouncycastle.asn1.x509.Extension;
import org.bouncycastle.asn1.x509.GeneralName;
import org.bouncycastle.asn1.x509.GeneralNames;
import org.bouncycastle.asn1.x509.KeyPurposeId;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
import org.bouncycastle.cert.X509AttributeCertificateHolder;
import org.bouncycastle.util.encoders.Base64;
import org.bouncycastle.util.encoders.Hex;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xipki.commons.common.ConfPairs;
import org.xipki.commons.common.util.CollectionUtil;
import org.xipki.commons.common.util.IoUtil;
import org.xipki.commons.common.util.ParamUtil;
import org.xipki.commons.security.FpIdCalculator;
import org.xipki.commons.security.KeyUsage;
import org.xipki.commons.security.ObjectIdentifiers;
import org.xipki.commons.security.exception.BadInputException;

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

public class X509Util {
    private static final Logger LOG = LoggerFactory.getLogger(X509Util.class);

    private static CertificateFactory certFact;
    private static Object certFactLock = new Object();

    private X509Util() {
    }

    public static String getCommonName(final X500Principal name) {
        ParamUtil.requireNonNull("name", name);
        return getCommonName(X500Name.getInstance(name.getEncoded()));
    }

    public static String getCommonName(final X500Name name) {
        ParamUtil.requireNonNull("name", name);
        RDN[] rdns = name.getRDNs(ObjectIdentifiers.DN_CN);
        if (rdns != null && rdns.length > 0) {
            RDN rdn = rdns[0];
            AttributeTypeAndValue atv = null;
            if (rdn.isMultiValued()) {
                for (AttributeTypeAndValue m : rdn.getTypesAndValues()) {
                    if (m.getType().equals(ObjectIdentifiers.DN_CN)) {
                        atv = m;
                        break;
                    }
                }
            } else {
                atv = rdn.getFirst();
            }
            return (atv == null) ? null : rdnValueToString(atv.getValue());
        }
        return null;
    }

    public static X500Name reverse(final X500Name name) {
        ParamUtil.requireNonNull("name", name);
        RDN[] orig = name.getRDNs();
        final int n = orig.length;
        RDN[] newRdn = new RDN[n];
        for (int i = 0; i < n; i++) {
            newRdn[i] = orig[n - 1 - i];
        }
        return new X500Name(newRdn);
    }

    public static X509Certificate parseCert(final String fileName) throws IOException, CertificateException {
        ParamUtil.requireNonNull("fileName", fileName);
        return parseCert(new File(IoUtil.expandFilepath(fileName)));
    }

    public static X509Certificate parseCert(final File file) throws IOException, CertificateException {
        ParamUtil.requireNonNull("file", file);
        FileInputStream in = new FileInputStream(IoUtil.expandFilepath(file));
        try {
            return parseCert(in);
        } finally {
            in.close();
        }
    }

    public static X509Certificate parseCert(final byte[] certBytes) throws CertificateException {
        ParamUtil.requireNonNull("certBytes", certBytes);
        return parseCert(new ByteArrayInputStream(certBytes));
    }

    public static X509Certificate parseCert(final InputStream certStream) throws CertificateException {
        ParamUtil.requireNonNull("certStream", certStream);
        X509Certificate cert = (X509Certificate) getCertFactory().generateCertificate(certStream);
        if (cert == null) {
            throw new CertificateEncodingException("the given one is not a valid X.509 certificate");
        }
        return cert;
    }

    private static CertificateFactory getCertFactory() throws CertificateException {
        synchronized (certFactLock) {
            if (certFact == null) {
                try {
                    certFact = CertificateFactory.getInstance("X.509", "BC");
                } catch (NoSuchProviderException ex) {
                    throw new CertificateException("NoSuchProviderException: " + ex.getMessage());
                }
            }
            return certFact;
        }
    }

    public static X509Certificate parseBase64EncodedCert(final String base64EncodedCert)
            throws CertificateException {
        ParamUtil.requireNonNull("base64EncodedCert", base64EncodedCert);
        return parseCert(Base64.decode(base64EncodedCert));
    }

    public static X509Certificate parsePemEncodedCert(final String pemEncodedCert) throws CertificateException {
        ParamUtil.requireNonNull("pemEncodedCert", pemEncodedCert);
        String b64 = pemEncodedCert.replace("-----BEGIN CERTIFICATE-----", "").replace("-----END CERTIFICATE-----",
                "");
        return parseBase64EncodedCert(b64);
    }

    public static X509Certificate toX509Cert(final org.bouncycastle.asn1.x509.Certificate asn1Cert)
            throws CertificateException {
        byte[] encodedCert;
        try {
            encodedCert = asn1Cert.getEncoded();
        } catch (IOException ex) {
            throw new CertificateEncodingException("could not get encoded certificate", ex);
        }
        return parseCert(encodedCert);
    }

    public static X509CRL toX509Crl(final CertificateList asn1CertList) throws CertificateException, CRLException {
        byte[] encodedCrl;
        try {
            encodedCrl = asn1CertList.getEncoded();
        } catch (IOException ex) {
            throw new CRLException("could not get encoded CRL", ex);
        }
        return parseCrl(encodedCrl);
    }

    public static X509CRL parseCrl(final String file) throws IOException, CertificateException, CRLException {
        ParamUtil.requireNonBlank("file", file);
        return parseCrl(new FileInputStream(IoUtil.expandFilepath(file)));
    }

    public static X509CRL parseCrl(final byte[] encodedCrl) throws CertificateException, CRLException {
        ParamUtil.requireNonNull("encodedCrl", encodedCrl);
        return parseCrl(new ByteArrayInputStream(encodedCrl));
    }

    public static X509CRL parseCrl(final InputStream crlStream) throws CertificateException, CRLException {
        ParamUtil.requireNonNull("crlStream", crlStream);
        X509CRL crl = (X509CRL) getCertFactory().generateCRL(crlStream);
        if (crl == null) {
            throw new CRLException("the given one is not a valid X.509 CRL");
        }
        return crl;
    }

    public static String getRfc4519Name(final X500Principal name) {
        ParamUtil.requireNonNull("name", name);
        return getRfc4519Name(X500Name.getInstance(name.getEncoded()));
    }

    public static String getRfc4519Name(final X500Name name) {
        ParamUtil.requireNonNull("name", name);
        return RFC4519Style.INSTANCE.toString(name);
    }

    /**
     * First canonicalized the name, and then compute the SHA-1 finger-print over the
     * canonicalized subject string.
     */
    public static long fpCanonicalizedName(final X500Principal prin) {
        ParamUtil.requireNonNull("prin", prin);
        X500Name x500Name = X500Name.getInstance(prin.getEncoded());
        return fpCanonicalizedName(x500Name);
    }

    public static long fpCanonicalizedName(final X500Name name) {
        ParamUtil.requireNonNull("name", name);
        String canonicalizedName = canonicalizName(name);
        byte[] encoded;
        try {
            encoded = canonicalizedName.getBytes("UTF-8");
        } catch (UnsupportedEncodingException ex) {
            encoded = canonicalizedName.getBytes();
        }
        return FpIdCalculator.hash(encoded);
    }

    public static String canonicalizName(final X500Name name) {
        ParamUtil.requireNonNull("name", name);
        ASN1ObjectIdentifier[] tmpTypes = name.getAttributeTypes();
        int len = tmpTypes.length;
        List<String> types = new ArrayList<>(len);
        for (ASN1ObjectIdentifier type : tmpTypes) {
            types.add(type.getId());
        }

        Collections.sort(types);

        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < len; i++) {
            String type = types.get(i);
            if (i > 0) {
                sb.append(",");
            }
            sb.append(type).append("=");
            RDN[] rdns = name.getRDNs(new ASN1ObjectIdentifier(type));

            List<String> values = new ArrayList<>(1);
            for (int j = 0; j < rdns.length; j++) {
                RDN rdn = rdns[j];
                if (rdn.isMultiValued()) {
                    AttributeTypeAndValue[] atvs = rdn.getTypesAndValues();
                    for (AttributeTypeAndValue atv : atvs) {
                        if (type.equals(atv.getType().getId())) {
                            String textValue = IETFUtils.valueToString(atv.getValue()).toLowerCase();
                            values.add(textValue);
                        }
                    }
                } else {
                    String textValue = IETFUtils.valueToString(rdn.getFirst().getValue()).toLowerCase();
                    values.add(textValue);
                }
            } // end for(j)

            sb.append(values.get(0));

            final int n2 = values.size();
            if (n2 > 1) {
                for (int j = 1; j < n2; j++) {
                    sb.append(";").append(values.get(j));
                }
            }
        } // end for(i)

        return sb.toString();
    } // method canonicalizName

    public static byte[] extractSki(final X509Certificate cert) throws CertificateEncodingException {
        byte[] extValue = getCoreExtValue(cert, Extension.subjectKeyIdentifier);
        if (extValue == null) {
            return null;
        }

        try {
            return ASN1OctetString.getInstance(extValue).getOctets();
        } catch (IllegalArgumentException ex) {
            throw new CertificateEncodingException(ex.getMessage());
        }
    }

    public static byte[] extractSki(final org.bouncycastle.asn1.x509.Certificate cert)
            throws CertificateEncodingException {
        ParamUtil.requireNonNull("cert", cert);
        Extension encodedSkiValue = cert.getTBSCertificate().getExtensions()
                .getExtension(Extension.subjectKeyIdentifier);
        if (encodedSkiValue == null) {
            return null;
        }

        try {
            return ASN1OctetString.getInstance(encodedSkiValue.getParsedValue()).getOctets();
        } catch (IllegalArgumentException ex) {
            throw new CertificateEncodingException("invalid extension SubjectKeyIdentifier: " + ex.getMessage());
        }
    }

    public static byte[] extractAki(final X509Certificate cert) throws CertificateEncodingException {
        byte[] extValue = getCoreExtValue(cert, Extension.authorityKeyIdentifier);
        if (extValue == null) {
            return null;
        }

        try {
            AuthorityKeyIdentifier aki = AuthorityKeyIdentifier.getInstance(extValue);
            return aki.getKeyIdentifier();
        } catch (IllegalArgumentException ex) {
            throw new CertificateEncodingException("invalid extension AuthorityKeyIdentifier: " + ex.getMessage());
        }
    }

    public static byte[] extractAki(final org.bouncycastle.asn1.x509.Certificate cert)
            throws CertificateEncodingException {
        ParamUtil.requireNonNull("cert", cert);
        try {
            AuthorityKeyIdentifier aki = AuthorityKeyIdentifier
                    .fromExtensions(cert.getTBSCertificate().getExtensions());
            return (aki == null) ? null : aki.getKeyIdentifier();
        } catch (IllegalArgumentException ex) {
            throw new CertificateEncodingException("invalid extension AuthorityKeyIdentifier: " + ex.getMessage());
        }
    }

    public static String rdnValueToString(final ASN1Encodable value) {
        ParamUtil.requireNonNull("value", value);
        if (value instanceof ASN1String && !(value instanceof DERUniversalString)) {
            return ((ASN1String) value).getString();
        } else {
            try {
                return "#" + bytesToString(Hex.encode(value.toASN1Primitive().getEncoded(ASN1Encoding.DER)));
            } catch (IOException ex) {
                throw new IllegalArgumentException("other value has no encoded form");
            }
        }
    }

    private static String bytesToString(final byte[] data) {
        char[] cs = new char[data.length];

        for (int i = 0; i != cs.length; i++) {
            cs[i] = (char) (data[i] & 0xff);
        }

        return new String(cs);
    }

    public static org.bouncycastle.asn1.x509.KeyUsage createKeyUsage(final Set<KeyUsage> usages) {
        if (CollectionUtil.isEmpty(usages)) {
            return null;
        }

        int usage = 0;
        for (KeyUsage keyUsage : usages) {
            usage |= keyUsage.getBcUsage();
        }

        return new org.bouncycastle.asn1.x509.KeyUsage(usage);
    }

    public static ExtendedKeyUsage createExtendedUsage(final Collection<ASN1ObjectIdentifier> usages) {
        if (CollectionUtil.isEmpty(usages)) {
            return null;
        }

        List<ASN1ObjectIdentifier> list = new ArrayList<>(usages);
        List<ASN1ObjectIdentifier> sortedUsages = sortOidList(list);
        KeyPurposeId[] kps = new KeyPurposeId[sortedUsages.size()];

        int idx = 0;
        for (ASN1ObjectIdentifier oid : sortedUsages) {
            kps[idx++] = KeyPurposeId.getInstance(oid);
        }

        return new ExtendedKeyUsage(kps);
    }

    // sort the list and remove duplicated OID.
    public static List<ASN1ObjectIdentifier> sortOidList(final List<ASN1ObjectIdentifier> oids) {
        ParamUtil.requireNonNull("oids", oids);
        List<String> list = new ArrayList<>(oids.size());
        for (ASN1ObjectIdentifier m : oids) {
            list.add(m.getId());
        }
        Collections.sort(list);

        List<ASN1ObjectIdentifier> sorted = new ArrayList<>(oids.size());
        for (String m : list) {
            for (ASN1ObjectIdentifier n : oids) {
                if (m.equals(n.getId()) && !sorted.contains(n)) {
                    sorted.add(n);
                }
            }
        }
        return sorted;
    }

    public static boolean hasKeyusage(final X509Certificate cert, final KeyUsage usage) {
        ParamUtil.requireNonNull("cert", cert);
        boolean[] keyusage = cert.getKeyUsage();
        if (keyusage != null && keyusage.length > usage.getBit()) {
            return keyusage[usage.getBit()];
        }
        return false;
    }

    public static byte[] getCoreExtValue(final X509Certificate cert, final ASN1ObjectIdentifier type)
            throws CertificateEncodingException {
        ParamUtil.requireNonNull("cert", cert);
        ParamUtil.requireNonNull("type", type);
        byte[] fullExtValue = cert.getExtensionValue(type.getId());
        if (fullExtValue == null) {
            return null;
        }
        try {
            return ASN1OctetString.getInstance(fullExtValue).getOctets();
        } catch (IllegalArgumentException ex) {
            throw new CertificateEncodingException("invalid extension " + type.getId() + ": " + ex.getMessage());
        }
    }

    public static byte[] getCoreExtValue(final X509AttributeCertificateHolder cert, final ASN1ObjectIdentifier type)
            throws CertificateEncodingException {
        ParamUtil.requireNonNull("cert", cert);
        ParamUtil.requireNonNull("type", type);
        Extension ext = cert.getExtension(type);
        if (ext == null) {
            return null;
        }

        return ext.getExtnValue().getOctets();
    }

    /**
     * Cross certificate will not be considered.
     */
    public static X509Certificate[] buildCertPath(final X509Certificate cert,
            final Set<? extends Certificate> certs) {
        ParamUtil.requireNonNull("cert", cert);
        List<X509Certificate> certChain = new LinkedList<>();
        certChain.add(cert);
        try {
            if (certs != null && !isSelfSigned(cert)) {
                while (true) {
                    X509Certificate caCert = getCaCertOf(certChain.get(certChain.size() - 1), certs);
                    if (caCert == null) {
                        break;
                    }
                    certChain.add(caCert);
                    if (isSelfSigned(caCert)) {
                        // reaches root self-signed certificate
                        break;
                    }
                }
            }
        } catch (CertificateEncodingException ex) {
            LOG.warn("CertificateEncodingException: {}", ex.getMessage());
        }

        final int n = certChain.size();
        int len = n;
        if (n > 1) {
            for (int i = 1; i < n; i++) {
                int pathLen = certChain.get(i).getBasicConstraints();
                if (pathLen < 0 || pathLen < i) {
                    len = i;
                    break;
                }
            }
        } // end for

        if (len == n) {
            return certChain.toArray(new X509Certificate[0]);
        } else {
            X509Certificate[] ret = new X509Certificate[len];
            for (int i = 0; i < len; i++) {
                ret[i] = certChain.get(i);
            }
            return ret;
        }
    } // method buildCertPath

    private static X509Certificate getCaCertOf(final X509Certificate cert, final Set<? extends Certificate> caCerts)
            throws CertificateEncodingException {
        ParamUtil.requireNonNull("cert", cert);
        if (isSelfSigned(cert)) {
            return null;
        }

        for (Certificate caCert : caCerts) {
            if (!(caCert instanceof X509Certificate)) {
                continue;
            }

            X509Certificate x509CaCert = (X509Certificate) caCert;
            if (!issues(x509CaCert, cert)) {
                continue;
            }

            try {
                cert.verify(x509CaCert.getPublicKey());
                return x509CaCert;
            } catch (Exception ex) {
                LOG.warn("could not verify certificate: {}", ex.getMessage());
            }
        }

        return null;
    }

    public static boolean isSelfSigned(final X509Certificate cert) throws CertificateEncodingException {
        ParamUtil.requireNonNull("cert", cert);
        boolean equals = cert.getSubjectX500Principal().equals(cert.getIssuerX500Principal());
        if (equals) {
            byte[] ski = extractSki(cert);
            byte[] aki = extractAki(cert);
            if (ski != null && aki != null) {
                equals = Arrays.equals(ski, aki);
            }
        }
        return equals;
    }

    public static boolean issues(final X509Certificate issuerCert, final X509Certificate cert)
            throws CertificateEncodingException {
        ParamUtil.requireNonNull("issuerCert", issuerCert);
        ParamUtil.requireNonNull("cert", cert);
        boolean isCa = issuerCert.getBasicConstraints() >= 0;
        if (!isCa) {
            return false;
        }

        boolean issues = issuerCert.getSubjectX500Principal().equals(cert.getIssuerX500Principal());
        if (issues) {
            byte[] ski = extractSki(issuerCert);
            byte[] aki = extractAki(cert);
            if (ski != null) {
                issues = Arrays.equals(ski, aki);
            }
        }

        if (issues) {
            long issuerNotBefore = issuerCert.getNotBefore().getTime();
            long issuerNotAfter = issuerCert.getNotAfter().getTime();
            long notBefore = cert.getNotBefore().getTime();
            issues = notBefore <= issuerNotAfter && notBefore >= issuerNotBefore;
        }

        return issues;
    }

    public static SubjectPublicKeyInfo toRfc3279Style(final SubjectPublicKeyInfo publicKeyInfo)
            throws InvalidKeySpecException {
        ParamUtil.requireNonNull("publicKeyInfo", publicKeyInfo);
        ASN1ObjectIdentifier algOid = publicKeyInfo.getAlgorithm().getAlgorithm();
        ASN1Encodable keyParameters = publicKeyInfo.getAlgorithm().getParameters();

        if (PKCSObjectIdentifiers.rsaEncryption.equals(algOid)) {
            if (DERNull.INSTANCE.equals(keyParameters)) {
                return publicKeyInfo;
            } else {
                AlgorithmIdentifier keyAlgId = new AlgorithmIdentifier(algOid, DERNull.INSTANCE);
                return new SubjectPublicKeyInfo(keyAlgId, publicKeyInfo.getPublicKeyData().getBytes());
            }
        } else if (X9ObjectIdentifiers.id_dsa.equals(algOid)) {
            if (keyParameters == null) {
                return publicKeyInfo;
            } else if (DERNull.INSTANCE.equals(keyParameters)) {
                AlgorithmIdentifier keyAlgId = new AlgorithmIdentifier(algOid);
                return new SubjectPublicKeyInfo(keyAlgId, publicKeyInfo.getPublicKeyData().getBytes());
            } else {
                try {
                    DSAParameter.getInstance(keyParameters);
                } catch (IllegalArgumentException ex) {
                    throw new InvalidKeySpecException("keyParameters is not null and Dss-Parms");
                }
                return publicKeyInfo;
            }
        } else if (X9ObjectIdentifiers.id_ecPublicKey.equals(algOid)) {
            if (keyParameters == null) {
                throw new InvalidKeySpecException("keyParameters is not an OBJECT IDENTIFIER");
            }
            try {
                ASN1ObjectIdentifier.getInstance(keyParameters);
            } catch (IllegalArgumentException ex) {
                throw new InvalidKeySpecException("keyParameters is not an OBJECT IDENTIFIER");
            }
            return publicKeyInfo;
        } else {
            return publicKeyInfo;
        }
    }

    public static String cutText(final String text, final int maxLen) {
        ParamUtil.requireNonNull("text", text);
        if (text.length() <= maxLen) {
            return text;
        }
        StringBuilder sb = new StringBuilder(maxLen);
        sb.append(text.substring(0, maxLen - 13));
        sb.append("...skipped...");
        return sb.toString();
    }

    public static String cutX500Name(final X500Name name, final int maxLen) {
        String text = getRfc4519Name(name);
        return cutText(text, maxLen);
    }

    public static String cutX500Name(final X500Principal name, final int maxLen) {
        String text = getRfc4519Name(name);
        return cutText(text, maxLen);
    }

    public static Extension createExtensionSubjectAltName(final List<String> taggedValues, final boolean critical)
            throws BadInputException {
        GeneralNames names = createGeneralNames(taggedValues);
        if (names == null) {
            return null;
        }

        try {
            return new Extension(Extension.subjectAlternativeName, critical, names.getEncoded());
        } catch (IOException ex) {
            throw new RuntimeException(ex.getMessage(), ex);
        }
    }

    public static Extension createExtensionSubjectInfoAccess(final List<String> accessMethodAndLocations,
            final boolean critical) throws BadInputException {
        if (CollectionUtil.isEmpty(accessMethodAndLocations)) {
            return null;
        }

        ASN1EncodableVector vector = new ASN1EncodableVector();
        for (String accessMethodAndLocation : accessMethodAndLocations) {
            vector.add(createAccessDescription(accessMethodAndLocation));
        }
        ASN1Sequence seq = new DERSequence(vector);
        try {
            return new Extension(Extension.subjectInfoAccess, critical, seq.getEncoded());
        } catch (IOException ex) {
            throw new RuntimeException(ex.getMessage(), ex);
        }
    }

    public static AccessDescription createAccessDescription(final String accessMethodAndLocation)
            throws BadInputException {
        ParamUtil.requireNonNull("accessMethodAndLocation", accessMethodAndLocation);
        ConfPairs pairs;
        try {
            pairs = new ConfPairs(accessMethodAndLocation);
        } catch (IllegalArgumentException ex) {
            throw new BadInputException("invalid accessMethodAndLocation " + accessMethodAndLocation);
        }

        Set<String> oids = pairs.getNames();
        if (oids == null || oids.size() != 1) {
            throw new BadInputException("invalid accessMethodAndLocation " + accessMethodAndLocation);
        }

        String accessMethodS = oids.iterator().next();
        String taggedValue = pairs.getValue(accessMethodS);
        ASN1ObjectIdentifier accessMethod = new ASN1ObjectIdentifier(accessMethodS);

        GeneralName location = createGeneralName(taggedValue);
        return new AccessDescription(accessMethod, location);
    }

    public static GeneralNames createGeneralNames(final List<String> taggedValues) throws BadInputException {
        if (CollectionUtil.isEmpty(taggedValues)) {
            return null;
        }

        int len = taggedValues.size();
        GeneralName[] names = new GeneralName[len];
        for (int i = 0; i < len; i++) {
            names[i] = createGeneralName(taggedValues.get(i));
        }
        return new GeneralNames(names);
    }

    /**
    *
    * @param taggedValue [tag]value, and the value for tags otherName and ediPartyName is
    *     type=value.
    */
    public static GeneralName createGeneralName(final String taggedValue) throws BadInputException {
        ParamUtil.requireNonBlank("taggedValue", taggedValue);

        int tag = -1;
        String value = null;
        if (taggedValue.charAt(0) == '[') {
            int idx = taggedValue.indexOf(']', 1);
            if (idx > 1 && idx < taggedValue.length() - 1) {
                String tagS = taggedValue.substring(1, idx);
                try {
                    tag = Integer.parseInt(tagS);
                    value = taggedValue.substring(idx + 1);
                } catch (NumberFormatException ex) {
                    throw new BadInputException("invalid tag '" + tagS + "'");
                }
            }
        }

        if (tag == -1) {
            throw new BadInputException("invalid taggedValue " + taggedValue);
        }

        switch (tag) {
        case GeneralName.otherName:
            if (value == null) {
                throw new BadInputException("invalid otherName: no value specified");
            }

            int idxSep = value.indexOf("=");
            if (idxSep == -1 || idxSep == 0 || idxSep == value.length() - 1) {
                throw new BadInputException("invalid otherName " + value);
            }
            String otherTypeOid = value.substring(0, idxSep);
            ASN1ObjectIdentifier type = new ASN1ObjectIdentifier(otherTypeOid);
            String otherValue = value.substring(idxSep + 1);
            ASN1EncodableVector vector = new ASN1EncodableVector();
            vector.add(type);
            vector.add(new DERTaggedObject(true, 0, new DERUTF8String(otherValue)));
            DERSequence seq = new DERSequence(vector);
            return new GeneralName(GeneralName.otherName, seq);
        case GeneralName.rfc822Name:
            return new GeneralName(tag, value);
        case GeneralName.dNSName:
            return new GeneralName(tag, value);
        case GeneralName.directoryName:
            X500Name x500Name = reverse(new X500Name(value));
            return new GeneralName(GeneralName.directoryName, x500Name);
        case GeneralName.ediPartyName:
            if (value == null) {
                throw new BadInputException("invalid ediPartyName: no value specified");
            }
            idxSep = value.indexOf("=");
            if (idxSep == -1 || idxSep == value.length() - 1) {
                throw new BadInputException("invalid ediPartyName " + value);
            }
            String nameAssigner = (idxSep == 0) ? null : value.substring(0, idxSep);
            String partyName = value.substring(idxSep + 1);
            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);
        case GeneralName.uniformResourceIdentifier:
            return new GeneralName(tag, value);
        case GeneralName.iPAddress:
            return new GeneralName(tag, value);
        case GeneralName.registeredID:
            return new GeneralName(tag, value);
        default:
            throw new RuntimeException("unsupported tag " + tag);
        } // end switch (tag)
    } // method createGeneralName

}