org.qipki.crypto.x509.X509ExtensionsReaderImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.qipki.crypto.x509.X509ExtensionsReaderImpl.java

Source

/*
 * Copyright (c) 2010, Paul Merlin. All Rights Reserved.
 *
 * 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.qipki.crypto.x509;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.security.cert.X509Certificate;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.Enumeration;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.security.auth.x500.X500Principal;

import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1Object;
import org.bouncycastle.asn1.ASN1OctetString;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.ASN1Set;
import org.bouncycastle.asn1.DERBitString;
import org.bouncycastle.asn1.DEREncodable;
import org.bouncycastle.asn1.DERInteger;
import org.bouncycastle.asn1.DERObjectIdentifier;
import org.bouncycastle.asn1.DEROctetString;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.asn1.DERTaggedObject;
import org.bouncycastle.asn1.misc.MiscObjectIdentifiers;
import org.bouncycastle.asn1.pkcs.CertificationRequestInfo;
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
import org.bouncycastle.asn1.x509.Attribute;
import org.bouncycastle.asn1.x509.AuthorityKeyIdentifier;
import org.bouncycastle.asn1.x509.BasicConstraints;
import org.bouncycastle.asn1.x509.CRLDistPoint;
import org.bouncycastle.asn1.x509.DistributionPoint;
import org.bouncycastle.asn1.x509.GeneralName;
import org.bouncycastle.asn1.x509.GeneralNames;
import org.bouncycastle.asn1.x509.NameConstraints;
import org.bouncycastle.asn1.x509.PolicyInformation;
import org.bouncycastle.asn1.x509.PrivateKeyUsagePeriod;
import org.bouncycastle.asn1.x509.ReasonFlags;
import org.bouncycastle.asn1.x509.SubjectKeyIdentifier;
import org.bouncycastle.asn1.x509.X509Extension;
import org.bouncycastle.asn1.x509.X509Extensions;
import org.bouncycastle.asn1.x509.X509Name;
import org.bouncycastle.jce.PKCS10CertificationRequest;
import org.bouncycastle.x509.extension.X509ExtensionUtil;

import org.qipki.crypto.CryptoFailure;
import org.qipki.crypto.codec.CryptCodex;

import org.joda.time.DateTime;
import org.joda.time.Interval;

import org.qi4j.api.injection.scope.Service;

public class X509ExtensionsReaderImpl implements X509ExtensionsReader {

    private final CryptCodex cryptCodex;

    public X509ExtensionsReaderImpl(@Service CryptCodex cryptCodex) {
        this.cryptCodex = cryptCodex;
    }

    @Override
    public List<X509ExtensionHolder> extractRequestedExtensions(PKCS10CertificationRequest pkcs10) {
        final List<X509ExtensionHolder> extractedExtensions = new ArrayList<X509ExtensionHolder>();
        final CertificationRequestInfo certificationRequestInfo = pkcs10.getCertificationRequestInfo();
        final ASN1Set attributesAsn1Set = certificationRequestInfo.getAttributes();
        if (attributesAsn1Set == null) {
            return extractedExtensions;
        }
        // The `Extension Request` attribute is contained within an ASN.1 Set,
        // usually as the first element.
        X509Extensions requestedExtensions = null;
        for (int i = 0; i < attributesAsn1Set.size(); ++i) {
            // There should be only only one attribute in the set. (that is, only
            // the `Extension Request`, but loop through to find it properly)
            final DEREncodable derEncodable = attributesAsn1Set.getObjectAt(i);
            if (derEncodable instanceof DERSequence) {
                final Attribute attribute = new Attribute((DERSequence) attributesAsn1Set.getObjectAt(i));

                if (attribute.getAttrType().equals(PKCSObjectIdentifiers.pkcs_9_at_extensionRequest)) {
                    // The `Extension Request` attribute is present.
                    final ASN1Set attributeValues = attribute.getAttrValues();

                    // The X509Extensions are contained as a value of the ASN.1 Set.
                    // WARN Assuming that it is the first value of the set.
                    if (attributeValues.size() >= 1) {
                        DEREncodable extensionsDEREncodable = attributeValues.getObjectAt(0);
                        ASN1Sequence extensionsASN1Sequence = (ASN1Sequence) extensionsDEREncodable;
                        requestedExtensions = new X509Extensions(extensionsASN1Sequence);
                        // No need to search any more.
                        break;
                    }
                }
            }
        }
        if (requestedExtensions != null) {
            Enumeration<?> e = requestedExtensions.oids();
            while (e.hasMoreElements()) {
                DERObjectIdentifier oid = (DERObjectIdentifier) e.nextElement();
                X509Extension extension = requestedExtensions.getExtension(oid);
                extractedExtensions.add(new X509ExtensionHolder(oid, extension.isCritical(),
                        X509Extension.convertValueToObject(extension)));
            }
        }
        return extractedExtensions;
    }

    @Override
    public AuthorityKeyIdentifier getAuthorityKeyIdentifier(X509Certificate cert) {
        try {
            byte[] value = cert.getExtensionValue(X509Extensions.AuthorityKeyIdentifier.getId());
            if (value == null) {
                return null;
            }
            DEROctetString oct = (DEROctetString) (new ASN1InputStream(new ByteArrayInputStream(value))
                    .readObject());
            return new AuthorityKeyIdentifier(
                    (ASN1Sequence) new ASN1InputStream(new ByteArrayInputStream(oct.getOctets())).readObject());
        } catch (IOException ex) {
            throw new CryptoFailure("Unable to extract AuthorityKeyIdentifier from X509Certificate extensions", ex);
        }
    }

    @Override
    public byte[] getSubjectKeyIdentifier(X509Certificate cert) {
        try {
            byte[] value = cert.getExtensionValue(X509Extensions.SubjectKeyIdentifier.getId());
            if (value == null) {
                return null;
            }
            byte[] octets = ((ASN1OctetString) ASN1Object.fromByteArray(value)).getOctets();
            return SubjectKeyIdentifier.getInstance(ASN1Object.fromByteArray(octets)).getKeyIdentifier();
        } catch (IOException ex) {
            throw new CryptoFailure("Unable to extract SubjectKeyIdentifier from X509Certificate extensions", ex);
        }
    }

    @Override
    @SuppressWarnings("SetReplaceableByEnumSet")
    public Set<KeyUsage> getKeyUsages(X509Certificate cert) {
        Set<KeyUsage> keyUsages = new LinkedHashSet<KeyUsage>();
        boolean[] keyUsagesBools = cert.getKeyUsage();
        if (keyUsagesBools != null) {
            KeyUsage[] allKeyUsages = KeyUsage.values();
            for (int idx = 0; idx < keyUsagesBools.length; idx++) {
                if (keyUsagesBools[idx]) {
                    keyUsages.add(allKeyUsages[idx]);
                }
            }
        }
        return keyUsages;
    }

    @Override
    @SuppressWarnings("SetReplaceableByEnumSet")
    public Set<ExtendedKeyUsage> getExtendedKeyUsages(X509Certificate cert) {
        try {
            byte[] value = cert.getExtensionValue(X509Extensions.ExtendedKeyUsage.getId());
            if (value == null) {
                return Collections.emptySet();
            }
            byte[] asn1octets = ((ASN1OctetString) ASN1Object.fromByteArray(value)).getOctets();
            org.bouncycastle.asn1.x509.ExtendedKeyUsage usages = org.bouncycastle.asn1.x509.ExtendedKeyUsage
                    .getInstance((ASN1Sequence) ASN1Sequence.fromByteArray(asn1octets));
            Set<ExtendedKeyUsage> keyUsages = new LinkedHashSet<ExtendedKeyUsage>();
            for (ExtendedKeyUsage eachPossible : ExtendedKeyUsage.values()) {
                if (usages.hasKeyPurposeId(eachPossible.getKeyPurposeId())) {
                    keyUsages.add(eachPossible);
                }
            }
            return keyUsages;
        } catch (IOException ex) {
            throw new CryptoFailure("Unable to extract ExtendedKeyUsages from X509Certificate extensions", ex);
        }
    }

    @Override
    @SuppressWarnings("SetReplaceableByEnumSet")
    public Set<NetscapeCertType> getNetscapeCertTypes(X509Certificate cert) {
        try {
            byte[] value = cert.getExtensionValue(MiscObjectIdentifiers.netscapeCertType.getId());
            if (value == null) {
                return Collections.emptySet();
            }
            byte[] asn1octets = ((ASN1OctetString) ASN1Object.fromByteArray(value)).getOctets();
            int nctIntValue = new org.bouncycastle.asn1.misc.NetscapeCertType(
                    (DERBitString) ASN1Object.fromByteArray(asn1octets)).intValue();
            Set<NetscapeCertType> netscapeCertTypes = new LinkedHashSet<NetscapeCertType>();
            for (NetscapeCertType eachCertType : NetscapeCertType.values()) {
                if ((nctIntValue & eachCertType.getIntValue()) == eachCertType.getIntValue()) {
                    netscapeCertTypes.add(eachCertType);
                }
            }
            return netscapeCertTypes;
        } catch (IOException ex) {
            throw new CryptoFailure("Unable to extract NetscapeCertType from X509Certificate extensions", ex);
        }
    }

    @Override
    public String getNetscapeCertComment(X509Certificate cert) {
        try {
            byte[] value = cert.getExtensionValue(MiscObjectIdentifiers.netscapeCertComment.getId());
            if (value == null) {
                return null;
            }
            return ASN1Object.fromByteArray(value).toString();
        } catch (IOException ex) {
            throw new CryptoFailure("Unable to extract NetscapeCertComment from X509Certificate extensions", ex);
        }
    }

    @Override
    public Interval getPrivateKeyUsagePeriod(X509Certificate cert) {
        try {
            byte[] value = cert.getExtensionValue(X509Extensions.PrivateKeyUsagePeriod.getId());
            if (value == null) {
                return null;
            }
            PrivateKeyUsagePeriod privKeyUsagePeriod = PrivateKeyUsagePeriod
                    .getInstance(ASN1Object.fromByteArray(value));
            SimpleDateFormat derDateFormat = new SimpleDateFormat("yyyyMMddHHmmssz");
            Date notBefore = derDateFormat.parse(privKeyUsagePeriod.getNotBefore().getTime());
            Date notAfter = derDateFormat.parse(privKeyUsagePeriod.getNotAfter().getTime());
            return new Interval(new DateTime(notBefore), new DateTime(notAfter));
        } catch (ParseException ex) {
            throw new CryptoFailure("Unable to extract PrivateKeyUsagePeriod from X509Certificate extensions", ex);
        } catch (IOException ex) {
            throw new CryptoFailure("Unable to extract PrivateKeyUsagePeriod from X509Certificate extensions", ex);
        }
    }

    @Override
    public DistributionPoint[] getCRLDistributionPoints(X509Certificate cert) {
        try {
            byte[] value = cert.getExtensionValue(X509Extensions.CRLDistributionPoints.getId());
            if (value == null) {
                return null;
            }
            CRLDistPoint crlDistPoints = CRLDistPoint.getInstance(X509ExtensionUtil.fromExtensionValue(value));
            return crlDistPoints.getDistributionPoints();
        } catch (IOException ex) {
            throw new CryptoFailure("Unable to extract CRLDistributionPoints from X509Certificate extensions", ex);
        }

    }

    @Override
    public Set<PolicyInformation> getCertificatePolicies(X509Certificate cert) {
        try {
            byte[] value = cert.getExtensionValue(X509Extensions.CertificatePolicies.getId());
            if (value == null) {
                return Collections.emptySet();
            }
            ASN1Sequence policiesSequence = (ASN1Sequence) ASN1Object.fromByteArray(value);
            Set<PolicyInformation> certPolicies = new LinkedHashSet<PolicyInformation>();
            for (int idx = 0; idx < policiesSequence.size(); idx++) {
                PolicyInformation policy = PolicyInformation.getInstance(policiesSequence.getObjectAt(idx));
                certPolicies.add(policy);
            }
            return certPolicies;
        } catch (IOException ex) {
            throw new CryptoFailure("Unable to extract CertificatePolicies from X509Certificate extensions", ex);
        }
    }

    // See swisscom_root_ca_1 in cacerts for an example
    @Override
    public Set<PolicyMapping> getPolicyMappings(X509Certificate cert) {
        try {
            byte[] value = cert.getExtensionValue(X509Extensions.PolicyMappings.getId());
            if (value == null) {
                return Collections.emptySet();
            }
            ASN1Sequence mappingsSequence = (ASN1Sequence) ASN1Object.fromByteArray(value);
            Set<PolicyMapping> mappings = new LinkedHashSet<PolicyMapping>();
            for (int idx = 0; idx < mappingsSequence.size(); idx++) {
                ASN1Sequence seq = (ASN1Sequence) mappingsSequence.getObjectAt(idx);
                PolicyMapping mapping = new PolicyMapping();
                if (seq.size() > 0) {
                    mapping.setIssuerDomainPolicyOID(((DERObjectIdentifier) seq.getObjectAt(0)).getId());
                }
                if (seq.size() > 1) {
                    mapping.setIssuerDomainPolicyOID(((DERObjectIdentifier) seq.getObjectAt(1)).getId());
                }
                mappings.add(mapping);
            }
            return mappings;
        } catch (IOException ex) {
            throw new CryptoFailure("Unable to extract PolicyMappings from X509Certificate extensions", ex);
        }
    }

    @Override
    public GeneralNames getSubjectAlternativeNames(X509Certificate cert) {
        try {
            byte[] value = cert.getExtensionValue(X509Extensions.SubjectAlternativeName.getId());
            if (value == null) {
                return null;
            }
            return GeneralNames.getInstance(
                    ASN1Object.fromByteArray(((ASN1OctetString) ASN1Object.fromByteArray(value)).getOctets()));
        } catch (IOException ex) {
            throw new CryptoFailure("Unable to extract SubjectAlternativeName from X509Certificate extensions", ex);
        }
    }

    @Override
    public GeneralNames getIssuerAlternativeNames(X509Certificate cert) {
        try {
            byte[] value = cert.getExtensionValue(X509Extensions.IssuerAlternativeName.getId());
            if (value == null) {
                return null;
            }
            return GeneralNames.getInstance(
                    ASN1Object.fromByteArray(((ASN1OctetString) ASN1Object.fromByteArray(value)).getOctets()));
        } catch (IOException ex) {
            throw new CryptoFailure("Unable to extract IssuerAlternativeName from X509Certificate extensions", ex);
        }
    }

    @Override
    public BasicConstraints getBasicConstraints(X509Certificate cert) {
        try {
            byte[] value = cert.getExtensionValue(X509Extensions.BasicConstraints.getId());
            if (value == null) {
                return null;
            }
            return BasicConstraints.getInstance(
                    ASN1Object.fromByteArray(((ASN1OctetString) ASN1Object.fromByteArray(value)).getOctets()));
            // return BasicConstraints.getInstance( ASN1Object.fromByteArray( value ) );
        } catch (IOException ex) {
            throw new CryptoFailure("Unable to extract BasicConstraints from X509Certificate extensions", ex);
        }
    }

    @Override
    public NameConstraints getNameConstraints(X509Certificate cert) {
        try {
            byte[] value = cert.getExtensionValue(X509Extensions.NameConstraints.getId());
            if (value == null) {
                return null;
            }
            return new NameConstraints((ASN1Sequence) ASN1Object.fromByteArray(value));
        } catch (IOException ex) {
            throw new CryptoFailure("Unable to extract NameConstraints from X509Certificate extensions", ex);
        }
    }

    @Override
    public Set<PolicyConstraint> getPolicyConstraints(X509Certificate cert) {
        try {
            byte[] value = cert.getExtensionValue(X509Extensions.PolicyConstraints.getId());
            if (value == null) {
                return Collections.emptySet();
            }
            ASN1Sequence constraintsSequence = (ASN1Sequence) ASN1Object.fromByteArray(value);
            Set<PolicyConstraint> constraints = new LinkedHashSet<PolicyConstraint>();
            for (int idx = 0; idx < constraintsSequence.size(); idx++) {
                DERTaggedObject asn1Constraint = (DERTaggedObject) constraintsSequence.getObjectAt(idx);
                DERInteger skipCerts = new DERInteger(((DEROctetString) asn1Constraint.getObject()).getOctets());
                PolicyConstraint constraint = new PolicyConstraint();
                switch (asn1Constraint.getTagNo()) {
                case 0:
                    constraint.setRequireExplicitPolicy(skipCerts.getValue().intValue());
                    break;
                case 1:
                    constraint.setInhibitPolicyMapping(skipCerts.getValue().intValue());
                }
                constraints.add(constraint);
            }
            return constraints;
        } catch (IOException ex) {
            throw new CryptoFailure("Unable to extract PolicyConstraints from X509Certificate extensions", ex);
        }
    }

    @Override
    @SuppressWarnings("SetReplaceableByEnumSet")
    public Set<RevocationReason> getRevocationReasons(ReasonFlags reasonFlags) {
        if (reasonFlags == null) {
            return Collections.emptySet();
        }
        int reasons = reasonFlags.intValue();
        Set<RevocationReason> revocationReasons = new LinkedHashSet<RevocationReason>();
        for (RevocationReason eachPossibleReason : RevocationReason.values()) {
            if ((reasons & eachPossibleReason.reason()) == eachPossibleReason.reason()) {
                revocationReasons.add(eachPossibleReason);
            }
        }
        return revocationReasons;
    }

    @Override
    @SuppressWarnings("MapReplaceableByEnumMap")
    public Map<X509GeneralName, String> asMap(GeneralNames generalNames) {
        if (generalNames == null) {
            return Collections.emptyMap();
        }
        Map<X509GeneralName, String> map = new LinkedHashMap<X509GeneralName, String>();
        for (GeneralName eachGeneralName : generalNames.getNames()) {
            Map.Entry<X509GeneralName, String> entry = asImmutableMapEntry(eachGeneralName);
            map.put(entry.getKey(), entry.getValue());
        }
        return map;
    }

    @Override
    public Map.Entry<X509GeneralName, String> asImmutableMapEntry(GeneralName generalName) {
        int nameType = generalName.getTagNo();
        X509GeneralName x509GeneralName = null;
        String value = null;
        switch (nameType) {
        case GeneralName.otherName:
            ASN1Sequence otherName = (ASN1Sequence) generalName.getName();
            // String oid = ( ( DERObjectIdentifier ) otherName.getObjectAt( 0 ) ).getId();
            x509GeneralName = X509GeneralName.otherName;
            value = cryptCodex.toString(otherName.getObjectAt(1));
            break;
        case GeneralName.rfc822Name:
            x509GeneralName = X509GeneralName.rfc822Name;
            value = generalName.getName().toString();
            break;
        case GeneralName.dNSName:
            x509GeneralName = X509GeneralName.dNSName;
            value = generalName.getName().toString();
            break;
        case GeneralName.registeredID:
            x509GeneralName = X509GeneralName.registeredID;
            value = generalName.getName().toString();
            break;
        case GeneralName.x400Address:
            x509GeneralName = X509GeneralName.x400Address;
            value = generalName.getName().toString();
            break;
        case GeneralName.ediPartyName:
            x509GeneralName = X509GeneralName.ediPartyName;
            value = generalName.getName().toString();
            break;
        case GeneralName.directoryName:
            x509GeneralName = X509GeneralName.directoryName;
            value = new X500Principal(((X509Name) generalName.getName()).toString())
                    .getName(X500Principal.CANONICAL);
            break;
        case GeneralName.uniformResourceIdentifier:
            x509GeneralName = X509GeneralName.uniformResourceIdentifier;
            value = generalName.getName().toString();
            break;
        case GeneralName.iPAddress: // What about IPv6 addresses ?
            ASN1OctetString iPAddress = (ASN1OctetString) generalName.getName();
            byte[] iPAddressBytes = iPAddress.getOctets();
            StringBuilder sb = new StringBuilder();
            for (int idx = 0; idx < iPAddressBytes.length; idx++) {
                sb.append(iPAddressBytes[idx] & 0xFF);
                if (idx + 1 < iPAddressBytes.length) {
                    sb.append(".");
                }
            }
            x509GeneralName = X509GeneralName.iPAddress;
            value = sb.toString();
            break;
        default:
            x509GeneralName = X509GeneralName.unknownGeneralName;
            value = generalName.getName().toString();
        }
        return new ImmutableMapEntry(x509GeneralName, value);
    }

    private static class ImmutableMapEntry implements Map.Entry<X509GeneralName, String> {

        private final X509GeneralName key;
        private final String value;

        public ImmutableMapEntry(X509GeneralName key, String value) {
            this.key = key;
            this.value = value;
        }

        @Override
        public X509GeneralName getKey() {
            return key;
        }

        @Override
        public String getValue() {
            return value;
        }

        @Override
        public String setValue(String value) {
            throw new UnsupportedOperationException("This Map.Entry is immutable.");
        }

    }

}