All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.xipki.ca.api.profile.x509.BaseX509Certprofile Maven / Gradle / Ivy

The newest version!
/*
 *
 * Copyright (c) 2013 - 2017 Lijun Liao
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.xipki.ca.api.profile.x509;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;

import org.bouncycastle.asn1.ASN1Encodable;
import org.bouncycastle.asn1.ASN1EncodableVector;
import org.bouncycastle.asn1.ASN1GeneralizedTime;
import org.bouncycastle.asn1.ASN1Integer;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.ASN1String;
import org.bouncycastle.asn1.DERGeneralizedTime;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.asn1.DERUniversalString;
import org.bouncycastle.asn1.x500.AttributeTypeAndValue;
import org.bouncycastle.asn1.x500.RDN;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.jcajce.provider.asymmetric.util.ECUtil;
import org.bouncycastle.math.ec.ECCurve;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xipki.ca.api.BadCertTemplateException;
import org.xipki.ca.api.EnvParameterResolver;
import org.xipki.ca.api.profile.CertprofileException;
import org.xipki.ca.api.profile.KeyParametersOption;
import org.xipki.ca.api.profile.KeyParametersOption.AllowAllParametersOption;
import org.xipki.ca.api.profile.KeyParametersOption.DSAParametersOption;
import org.xipki.ca.api.profile.KeyParametersOption.ECParamatersOption;
import org.xipki.ca.api.profile.KeyParametersOption.RSAParametersOption;
import org.xipki.ca.api.profile.Range;
import org.xipki.ca.api.profile.RdnControl;
import org.xipki.ca.api.profile.StringType;
import org.xipki.common.LruCache;
import org.xipki.common.util.CollectionUtil;
import org.xipki.common.util.LogUtil;
import org.xipki.common.util.ParamUtil;
import org.xipki.security.ObjectIdentifiers;
import org.xipki.security.util.AlgorithmUtil;
import org.xipki.security.util.X509Util;

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

public abstract class BaseX509Certprofile extends X509Certprofile {

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

    private static LruCache ecCurveFieldSizes = new LruCache<>(100);

    protected EnvParameterResolver envParameterResolver;

    protected BaseX509Certprofile() {
    }

    public abstract Map keyAlgorithms();

    protected String[] sortRdns(final RdnControl control, final String[] values) {
        ParamUtil.requireNonNull("values", values);

        if (control == null) {
            return values;
        }

        List patterns = control.patterns();
        if (CollectionUtil.isEmpty(patterns)) {
            return values;
        }

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

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

    /**
     * Get the SubjectControl.
     *
     * @return the SubjectControl, must not be null.
     */
    protected abstract SubjectControl subjectControl();

    @Override
    public Date getNotBefore(final Date notBefore) {
        Date now = new Date();
        return (notBefore != null && notBefore.after(now)) ? notBefore : now;
    }

    @Override
    public SubjectInfo getSubject(final X500Name requestedSubject)
            throws CertprofileException, BadCertTemplateException {
        ParamUtil.requireNonNull("requestedSubject", requestedSubject);

        verifySubjectDnOccurence(requestedSubject);

        RDN[] requstedRdns = requestedSubject.getRDNs();
        SubjectControl scontrol = subjectControl();

        List rdns = new LinkedList<>();

        for (ASN1ObjectIdentifier type : scontrol.types()) {
            RdnControl control = scontrol.getControl(type);
            if (control == null) {
                continue;
            }

            RDN[] thisRdns = getRdns(requstedRdns, type);
            if (thisRdns == null) {
                continue;
            }
            int len = thisRdns.length;
            if (len == 0) {
                continue;
            }

            if (ObjectIdentifiers.DN_EmailAddress.equals(type)) {
                throw new BadCertTemplateException("emailAddress is not allowed");
            }

            if (len == 1) {
                ASN1Encodable rdnValue = thisRdns[0].getFirst().getValue();
                RDN rdn;
                if (ObjectIdentifiers.DN_DATE_OF_BIRTH.equals(type)) {
                    rdn = createDateOfBirthRdn(type, rdnValue);
                } else if (ObjectIdentifiers.DN_POSTAL_ADDRESS.equals(type)) {
                    rdn = createPostalAddressRdn(type, rdnValue, control, 0);
                } else {
                    String value = X509Util.rdnValueToString(rdnValue);
                    rdn = createSubjectRdn(value, type, control, 0);
                }

                if (rdn != null) {
                    rdns.add(rdn);
                }
            } else {
                if (ObjectIdentifiers.DN_DATE_OF_BIRTH.equals(type)) {
                    for (int i = 0; i < len; i++) {
                        RDN rdn = createDateOfBirthRdn(type, thisRdns[i].getFirst().getValue());
                        rdns.add(rdn);
                    }
                } else if (ObjectIdentifiers.DN_POSTAL_ADDRESS.equals(type)) {
                    for (int i = 0; i < len; i++) {
                        RDN rdn = createPostalAddressRdn(type, thisRdns[i].getFirst().getValue(),
                                control, i);
                        rdns.add(rdn);
                    }
                } else {
                    String[] values = new String[len];
                    for (int i = 0; i < len; i++) {
                        values[i] = X509Util.rdnValueToString(thisRdns[i].getFirst().getValue());
                    }
                    values = sortRdns(control, values);

                    int idx = 0;
                    for (String value : values) {
                        rdns.add(createSubjectRdn(value, type, control, idx++));
                    }
                } // if
            } // if
        } // for

        Set subjectDnGroups = scontrol.groups();
        if (CollectionUtil.isNonEmpty(subjectDnGroups)) {
            Set consideredGroups = new HashSet<>();
            final int n = rdns.size();

            List newRdns = new ArrayList<>(rdns.size());
            for (int i = 0; i < n; i++) {
                RDN rdn = rdns.get(i);
                ASN1ObjectIdentifier type = rdn.getFirst().getType();
                String group = scontrol.getGroup(type);
                if (group == null) {
                    newRdns.add(rdn);
                } else if (!consideredGroups.contains(group)) {
                    List atvs = new LinkedList<>();
                    atvs.add(rdn.getFirst());
                    for (int j = i + 1; j < n; j++) {
                        RDN rdn2 = rdns.get(j);
                        ASN1ObjectIdentifier type2 = rdn2.getFirst().getType();
                        String group2 = scontrol.getGroup(type2);
                        if (group.equals(group2)) {
                            atvs.add(rdn2.getFirst());
                        }
                    }

                    newRdns.add(new RDN(atvs.toArray(new AttributeTypeAndValue[0])));
                    consideredGroups.add(group);
                }
            } // for

            rdns = newRdns;
        } // if

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

    @Override
    public void setEnvParameterResolver(final EnvParameterResolver envParameterResolver) {
        this.envParameterResolver = envParameterResolver;
    }

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

    @Override
    public SubjectPublicKeyInfo checkPublicKey(final SubjectPublicKeyInfo publicKey)
            throws BadCertTemplateException {
        ParamUtil.requireNonNull("publicKey", publicKey);

        Map keyAlgorithms = keyAlgorithms();
        if (CollectionUtil.isEmpty(keyAlgorithms)) {
            return publicKey;
        }

        ASN1ObjectIdentifier keyType = publicKey.getAlgorithm().getAlgorithm();
        if (!keyAlgorithms.containsKey(keyType)) {
            throw new BadCertTemplateException("key type " + keyType.getId() + " is not permitted");
        }

        KeyParametersOption keyParamsOption = keyAlgorithms.get(keyType);
        if (keyParamsOption instanceof AllowAllParametersOption) {
            return publicKey;
        } else if (keyParamsOption instanceof ECParamatersOption) {
            ECParamatersOption ecOption = (ECParamatersOption) keyParamsOption;
            // parameters
            ASN1Encodable algParam = publicKey.getAlgorithm().getParameters();
            ASN1ObjectIdentifier curveOid;

            if (algParam instanceof ASN1ObjectIdentifier) {
                curveOid = (ASN1ObjectIdentifier) algParam;
                if (!ecOption.allowsCurve(curveOid)) {
                    throw new BadCertTemplateException(String.format(
                            "EC curve %s (OID: %s) is not allowed",
                            AlgorithmUtil.getCurveName(curveOid), curveOid.getId()));
                }
            } else {
                throw new BadCertTemplateException(
                        "only namedCurve EC public key is supported");
            }

            // point encoding
            if (ecOption.pointEncodings() != null) {
                byte[] keyData = publicKey.getPublicKeyData().getBytes();
                if (keyData.length < 1) {
                    throw new BadCertTemplateException("invalid publicKeyData");
                }
                byte pointEncoding = keyData[0];
                if (!ecOption.pointEncodings().contains(pointEncoding)) {
                    throw new BadCertTemplateException(String.format(
                            "not accepted EC point encoding '%s'", pointEncoding));
                }
            }

            byte[] keyData = publicKey.getPublicKeyData().getBytes();
            try {
                checkEcSubjectPublicKeyInfo(curveOid, keyData);
            } catch (BadCertTemplateException ex) {
                throw ex;
            } catch (Exception ex) {
                LogUtil.warn(LOG, ex, "checkEcSubjectPublicKeyInfo");
                throw new BadCertTemplateException(String.format(
                        "invalid public key: %s", ex.getMessage()));
            }
            return publicKey;
        } else if (keyParamsOption instanceof RSAParametersOption) {
            RSAParametersOption rsaOption = (RSAParametersOption) keyParamsOption;

            ASN1Integer modulus;
            try {
                ASN1Sequence seq = ASN1Sequence.getInstance(
                        publicKey.getPublicKeyData().getBytes());
                modulus = ASN1Integer.getInstance(seq.getObjectAt(0));
            } catch (IllegalArgumentException ex) {
                throw new BadCertTemplateException("invalid publicKeyData");
            }

            int modulusLength = modulus.getPositiveValue().bitLength();
            if ((rsaOption.allowsModulusLength(modulusLength))) {
                return publicKey;
            }
        } else if (keyParamsOption instanceof DSAParametersOption) {
            DSAParametersOption dsaOption = (DSAParametersOption) keyParamsOption;
            ASN1Encodable params = publicKey.getAlgorithm().getParameters();
            if (params == null) {
                throw new BadCertTemplateException("null Dss-Parms is not permitted");
            }

            int plength;
            int qlength;

            try {
                ASN1Sequence seq = ASN1Sequence.getInstance(params);
                ASN1Integer rsaP = ASN1Integer.getInstance(seq.getObjectAt(0));
                ASN1Integer rsaQ = ASN1Integer.getInstance(seq.getObjectAt(1));
                plength = rsaP.getPositiveValue().bitLength();
                qlength = rsaQ.getPositiveValue().bitLength();
            } catch (IllegalArgumentException | ArrayIndexOutOfBoundsException ex) {
                throw new BadCertTemplateException("illegal Dss-Parms");
            }

            boolean match = dsaOption.allowsPlength(plength);
            if (match) {
                match = dsaOption.allowsQlength(qlength);
            }

            if (match) {
                return publicKey;
            }
        } else {
            throw new RuntimeException(String.format(
                    "should not reach here, unknown KeyParametersOption %s", keyParamsOption));
        }

        throw new BadCertTemplateException("the given publicKey is not permitted");
    } // method checkPublicKey

    @Override
    public void initialize(final String data) throws CertprofileException {
    }

    protected void verifySubjectDnOccurence(final X500Name requestedSubject)
            throws BadCertTemplateException {
        ParamUtil.requireNonNull("requestedSubject", requestedSubject);

        SubjectControl occurences = subjectControl();
        if (occurences == null) {
            return;
        }

        ASN1ObjectIdentifier[] types = requestedSubject.getAttributeTypes();
        for (ASN1ObjectIdentifier type : types) {
            RdnControl occu = occurences.getControl(type);
            if (occu == null) {
                throw new BadCertTemplateException(String.format(
                        "subject DN of type %s is not allowed", oidToDisplayName(type)));
            }

            RDN[] rdns = requestedSubject.getRDNs(type);
            if (rdns.length > occu.maxOccurs() || rdns.length < occu.minOccurs()) {
                throw new BadCertTemplateException(String.format(
                        "occurrence of subject DN of type %s not within the allowed range. "
                        + "%d is not within [%d, %d]", oidToDisplayName(type),  rdns.length,
                        occu.minOccurs(), occu.maxOccurs()));
            }
        }

        for (ASN1ObjectIdentifier m : occurences.types()) {
            RdnControl occurence = occurences.getControl(m);
            if (occurence.minOccurs() == 0) {
                continue;
            }

            boolean present = false;
            for (ASN1ObjectIdentifier type : types) {
                if (occurence.type().equals(type)) {
                    present = true;
                    break;
                }
            }

            if (!present) {
                throw new BadCertTemplateException(String.format(
                        "required subject DN of type %s is not present",
                        oidToDisplayName(occurence.type())));
            }
        }
    } // method verifySubjectDnOccurence

    protected RDN createSubjectRdn(final String text, final ASN1ObjectIdentifier type,
            final RdnControl option, final int index) throws BadCertTemplateException {
        ASN1Encodable rdnValue = createRdnValue(text, type, option, index);
        return (rdnValue == null) ? null : new RDN(type, rdnValue);
    }

    private static RDN createDateOfBirthRdn(final ASN1ObjectIdentifier type,
            final ASN1Encodable rdnValue) throws BadCertTemplateException {
        ParamUtil.requireNonNull("type", type);

        String text;
        ASN1Encodable newRdnValue = null;
        if (rdnValue instanceof ASN1GeneralizedTime) {
            text = ((ASN1GeneralizedTime) rdnValue).getTimeString();
            newRdnValue = rdnValue;
        } else if (rdnValue instanceof ASN1String && !(rdnValue instanceof DERUniversalString)) {
            text = ((ASN1String) rdnValue).getString();
        } else {
            throw new BadCertTemplateException("Value of RDN dateOfBirth has incorrect syntax");
        }

        if (!SubjectDnSpec.PATTERN_DATE_OF_BIRTH.matcher(text).matches()) {
            throw new BadCertTemplateException(
                    "Value of RDN dateOfBirth does not have format YYYMMDD000000Z");
        }

        if (newRdnValue == null) {
            newRdnValue = new DERGeneralizedTime(text);
        }

        return new RDN(type, newRdnValue);
    }

    private static RDN createPostalAddressRdn(final ASN1ObjectIdentifier type,
            final ASN1Encodable rdnValue, final RdnControl control, final int index)
            throws BadCertTemplateException {
        ParamUtil.requireNonNull("type", type);

        if (!(rdnValue instanceof ASN1Sequence)) {
            throw new BadCertTemplateException(
                    "rdnValue of RDN postalAddress has incorrect syntax");
        }

        ASN1Sequence seq = (ASN1Sequence) rdnValue;
        final int size = seq.size();
        if (size < 1 || size > 6) {
            throw new BadCertTemplateException(
                    "Sequence size of RDN postalAddress is not within [1, 6]: " + size);
        }

        ASN1EncodableVector vec = new ASN1EncodableVector();
        for (int i = 0; i < size; i++) {
            ASN1Encodable line = seq.getObjectAt(i);
            String text;
            if (line instanceof ASN1String && !(line instanceof DERUniversalString)) {
                text = ((ASN1String) line).getString();
            } else {
                throw new BadCertTemplateException(
                    String.format("postalAddress[%d] has incorrect syntax", i));
            }

            ASN1Encodable asn1Line = createRdnValue(text, type, control, index);
            vec.add(asn1Line);
        }

        return new RDN(type, new DERSequence(vec));
    }

    private static RDN[] getRdns(final RDN[] rdns, final ASN1ObjectIdentifier type) {
        ParamUtil.requireNonNull("rdns", rdns);
        ParamUtil.requireNonNull("type", type);

        List ret = new ArrayList<>(1);
        for (int i = 0; i < rdns.length; i++) {
            RDN rdn = rdns[i];
            if (rdn.getFirst().getType().equals(type)) {
                ret.add(rdn);
            }
        }

        return CollectionUtil.isEmpty(ret) ? null : ret.toArray(new RDN[0]);
    }

    private static ASN1Encodable createRdnValue(final String text, final ASN1ObjectIdentifier type,
            final RdnControl option, final int index) throws BadCertTemplateException {
        ParamUtil.requireNonNull("text", text);
        ParamUtil.requireNonNull("type", type);

        String tmpText = text.trim();

        StringType stringType = null;

        if (option != null) {
            stringType = option.stringType();
            String prefix = option.prefix();
            String suffix = option.suffix();

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

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

            List patterns = option.patterns();
            if (patterns != null) {
                Pattern pattern = patterns.get(index);
                if (!pattern.matcher(tmpText).matches()) {
                    throw new BadCertTemplateException(
                        String.format("invalid subject %s '%s' against regex '%s'",
                                ObjectIdentifiers.oidToDisplayName(type), tmpText,
                                pattern.pattern()));
                }
            }

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

            int len = tmpText.length();
            Range range = option.stringLengthRange();
            Integer minLen = (range == null) ? null : range.min();

            if (minLen != null && len < minLen) {
                throw new BadCertTemplateException(
                    String.format("subject %s '%s' is too short (length (%d) < minLen (%d))",
                        ObjectIdentifiers.oidToDisplayName(type), tmpText, len, minLen));
            }

            Integer maxLen = (range == null) ? null : range.max();

            if (maxLen != null && len > maxLen) {
                throw new BadCertTemplateException(
                        String.format("subject %s '%s' is too long (length (%d) > maxLen (%d))",
                                ObjectIdentifiers.oidToDisplayName(type), tmpText, len, maxLen));
            }
        }

        if (stringType == null) {
            stringType = StringType.utf8String;
        }

        return stringType.createString(tmpText.trim());
    } // method createRdnValue

    private static String oidToDisplayName(final ASN1ObjectIdentifier type) {
        return ObjectIdentifiers.oidToDisplayName(type);
    }

    private static void checkEcSubjectPublicKeyInfo(final ASN1ObjectIdentifier curveOid,
            final byte[] encoded) throws BadCertTemplateException {
        ParamUtil.requireNonNull("curveOid", curveOid);
        ParamUtil.requireNonNull("encoded", encoded);
        ParamUtil.requireMin("encoded.length", encoded.length, 1);

        Integer expectedLength = ecCurveFieldSizes.get(curveOid);
        if (expectedLength == null) {
            X9ECParameters ecP = ECUtil.getNamedCurveByOid(curveOid);
            ECCurve curve = ecP.getCurve();
            expectedLength = (curve.getFieldSize() + 7) / 8;
            ecCurveFieldSizes.put(curveOid, expectedLength);
        }

        switch (encoded[0]) {
        case 0x02: // compressed
        case 0x03: // compressed
            if (encoded.length != (expectedLength + 1)) {
                throw new BadCertTemplateException("incorrect length for compressed encoding");
            }
            break;
        case 0x04: // uncompressed
        case 0x06: // hybrid
        case 0x07: // hybrid
            if (encoded.length != (2 * expectedLength + 1)) {
                throw new BadCertTemplateException(
                        "incorrect length for uncompressed/hybrid encoding");
            }
            break;
        default:
            throw new BadCertTemplateException(
                    String.format("invalid point encoding 0x%02x", encoded[0]));
        }
    } // method checkEcSubjectPublicKeyInfo

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy