org.globus.gsi.bc.BouncyCastleUtil.java Source code

Java tutorial

Introduction

Here is the source code for org.globus.gsi.bc.BouncyCastleUtil.java

Source

/*
 * Copyright 1999-2010 University of Chicago
 *
 * 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.globus.gsi.bc;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.security.Security;
import java.security.cert.CertStore;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.X509CertSelector;
import java.security.cert.X509Certificate;
import java.util.Collection;

import javax.naming.InvalidNameException;
import javax.naming.ldap.LdapName;
import javax.security.auth.x500.X500Principal;

import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.ASN1OctetString;
import org.bouncycastle.asn1.ASN1Primitive;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.ASN1Set;
import org.bouncycastle.asn1.ASN1String;
import org.bouncycastle.asn1.DERBitString;
import org.bouncycastle.asn1.DEROutputStream;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x500.style.BCStyle;
import org.bouncycastle.asn1.x509.BasicConstraints;
import org.bouncycastle.asn1.x509.TBSCertificateStructure;
import org.bouncycastle.asn1.x509.X509Extension;
import org.bouncycastle.asn1.x509.X509Extensions;
import org.bouncycastle.asn1.x509.X509Name;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.globus.gsi.GSIConstants;
import org.globus.gsi.TrustedCertificates;
import org.globus.gsi.TrustedCertificatesUtil;
import org.globus.gsi.proxy.ext.ProxyCertInfo;
import org.globus.gsi.proxy.ext.ProxyPolicy;
import org.globus.gsi.util.ProxyCertificateUtil;
import org.globus.util.I18n;

// COMMENT: BCB: removed methods createCertificateType(...) that took a TBSCertificateStructure as parameter
/**
 * A collection of various utility functions.
 */
public class BouncyCastleUtil {

    static {
        Security.addProvider(new BouncyCastleProvider());
    }

    private static I18n i18n = I18n.getI18n("org.globus.gsi.errors", BouncyCastleUtil.class.getClassLoader());

    /**
     * Converts given <code>DERObject</code> into
     * a DER-encoded byte array.
     *
     * @param obj DERObject to convert.
     * @return the DER-encoded byte array
     * @exception IOException if conversion fails
     */
    public static byte[] toByteArray(ASN1Primitive obj) throws IOException {
        ByteArrayOutputStream bout = new ByteArrayOutputStream();
        DEROutputStream der = new DEROutputStream(bout);
        der.writeObject(obj);
        return bout.toByteArray();
    }

    /**
     * Converts the DER-encoded byte array into a
     * <code>DERObject</code>.
     *
     * @param data the DER-encoded byte array to convert.
     * @return the DERObject.
     * @exception IOException if conversion fails
     */
    public static ASN1Primitive toASN1Primitive(byte[] data) throws IOException {
        ByteArrayInputStream inStream = new ByteArrayInputStream(data);
        ASN1InputStream derInputStream = new ASN1InputStream(inStream);
        return derInputStream.readObject();
    }

    /**
     * Replicates a given <code>DERObject</code>.
     *
     * @param obj the DERObject to replicate.
     * @return a copy of the DERObject.
     * @exception IOException if replication fails
     */
    public static ASN1Primitive duplicate(ASN1Primitive obj) throws IOException {
        return toASN1Primitive(toByteArray(obj));
    }

    /**
     * Extracts the TBS certificate from the given certificate.
     *
     * @param cert the X.509 certificate to extract the TBS certificate from.
     * @return the TBS certificate
     * @exception IOException if extraction fails.
     * @exception CertificateEncodingException if extraction fails.
     */
    public static TBSCertificateStructure getTBSCertificateStructure(X509Certificate cert)
            throws CertificateEncodingException, IOException {
        ASN1Primitive obj = BouncyCastleUtil.toASN1Primitive(cert.getTBSCertificate());
        return TBSCertificateStructure.getInstance(obj);
    }

    /**
     * Extracts the value of a certificate extension.
     *
     * @param ext the certificate extension to extract the value from.
     * @exception IOException if extraction fails.
     */
    public static ASN1Primitive getExtensionObject(X509Extension ext) throws IOException {
        return toASN1Primitive(ext.getValue().getOctets());
    }

    /**
     * Returns certificate type of the given certificate.
     * Please see {@link #getCertificateType(TBSCertificateStructure,
     * TrustedCertificates) getCertificateType} for details for
     * determining the certificate type.
     *
     * @param cert the certificate to get the type of.
     * @param trustedCerts the trusted certificates to double check the
     *                     {@link GSIConstants#EEC GSIConstants.EEC}
     *                     certificate against.
     * @return the certificate type as determined by
     *             {@link #getCertificateType(TBSCertificateStructure,
     *              TrustedCertificates) getCertificateType}.
     * @exception CertificateException if something goes wrong.
     * @deprecated
     */
    public static GSIConstants.CertificateType getCertificateType(X509Certificate cert,
            TrustedCertificates trustedCerts) throws CertificateException {
        try {
            return getCertificateType(cert, TrustedCertificatesUtil.createCertStore(trustedCerts));
        } catch (Exception e) {
            throw new CertificateException("", e);
        }
    }

    /**
     * Returns the certificate type of the given certificate.
     * Please see {@link #getCertificateType(TBSCertificateStructure,
     * TrustedCertificates) getCertificateType} for details for
     * determining the certificate type.
     *
     * @param cert the certificate to get the type of.
     * @param trustedCerts the trusted certificates to double check the
     *                     {@link GSIConstants#EEC GSIConstants.EEC}
     *                     certificate against.
     * @return the certificate type as determined by
     *             {@link #getCertificateType(TBSCertificateStructure,
     *              TrustedCertificates) getCertificateType}.
     * @exception CertificateException if something goes wrong.
     */
    public static GSIConstants.CertificateType getCertificateType(X509Certificate cert, CertStore trustedCerts)
            throws CertificateException {
        try {
            TBSCertificateStructure crt = getTBSCertificateStructure(cert);
            GSIConstants.CertificateType type = getCertificateType(crt);

            // check subject of the cert in trusted cert list
            // to make sure the cert is not a ca cert
            if (type == GSIConstants.CertificateType.EEC) {
                X509CertSelector selector = new X509CertSelector();
                selector.setSubject(cert.getSubjectX500Principal());
                Collection c = trustedCerts.getCertificates(selector);
                if (c != null && c.size() > 0) {
                    type = GSIConstants.CertificateType.CA;
                }
            }
            return type;
        } catch (Exception e) {
            // but this should not happen
            throw new CertificateException("", e);
        }
    }

    /**
     * Returns certificate type of the given certificate.
     * Please see {@link #getCertificateType(TBSCertificateStructure)
     * getCertificateType} for details for determining the certificate type.
     *
     * @param cert the certificate to get the type of.
     * @return the certificate type as determined by
     *             {@link #getCertificateType(TBSCertificateStructure)
     *              getCertificateType}.
     * @exception CertificateException if something goes wrong.
     */
    public static GSIConstants.CertificateType getCertificateType(X509Certificate cert)
            throws CertificateException {
        try {
            TBSCertificateStructure crt = getTBSCertificateStructure(cert);
            return getCertificateType(crt);
        } catch (IOException e) {
            // but this should not happen
            throw new CertificateException("", e);
        }
    }

    public static GSIConstants.CertificateType getCertificateType(TBSCertificateStructure crt,
            TrustedCertificates trustedCerts) throws CertificateException, IOException {
        GSIConstants.CertificateType type = getCertificateType(crt);

        // check subject of the cert in trusted cert list
        // to make sure the cert is not a ca cert
        if (type == GSIConstants.CertificateType.EEC) {
            if (trustedCerts == null) {
                trustedCerts = TrustedCertificates.getDefaultTrustedCertificates();
            }
            if (trustedCerts != null && trustedCerts.getCertificate(crt.getSubject().toString()) != null) {
                type = GSIConstants.CertificateType.CA;
            }
        }

        return type;
    }

    /**
     * Returns certificate type of the given TBS certificate. <BR>
     * The certificate type is {@link GSIConstants#CA GSIConstants.CA}
     * <B>only</B> if the certificate contains a
     * BasicConstraints extension and it is marked as CA.<BR>
     * A certificate is a GSI-2 proxy when the subject DN of the certificate
     * ends with <I>"CN=proxy"</I> (certificate type {@link
     * GSIConstants#GSI_2_PROXY GSIConstants.GSI_2_PROXY}) or
     * <I>"CN=limited proxy"</I> (certificate type {@link
     * GSIConstants#GSI_2_LIMITED_PROXY GSIConstants.LIMITED_PROXY}) component
     * and the issuer DN of the certificate matches the subject DN without
     * the last proxy <I>CN</I> component.<BR>
     * A certificate is a GSI-3 proxy when the subject DN of the certificate
     * ends with a <I>CN</I> component, the issuer DN of the certificate
     * matches the subject DN without the last <I>CN</I> component and
     * the certificate contains {@link ProxyCertInfo ProxyCertInfo} critical
     * extension.
     * The certificate type is {@link GSIConstants#GSI_3_IMPERSONATION_PROXY
     * GSIConstants.GSI_3_IMPERSONATION_PROXY} if the policy language of
     * the {@link ProxyCertInfo ProxyCertInfo} extension is set to
     * {@link ProxyPolicy#IMPERSONATION ProxyPolicy.IMPERSONATION} OID.
     * The certificate type is {@link GSIConstants#GSI_3_LIMITED_PROXY
     * GSIConstants.GSI_3_LIMITED_PROXY} if the policy language of
     * the {@link ProxyCertInfo ProxyCertInfo} extension is set to
     * {@link ProxyPolicy#LIMITED ProxyPolicy.LIMITED} OID.
     * The certificate type is {@link GSIConstants#GSI_3_INDEPENDENT_PROXY
     * GSIConstants.GSI_3_INDEPENDENT_PROXY} if the policy language of
     * the {@link ProxyCertInfo ProxyCertInfo} extension is set to
     * {@link ProxyPolicy#INDEPENDENT ProxyPolicy.INDEPENDENT} OID.
     * The certificate type is {@link GSIConstants#GSI_3_RESTRICTED_PROXY
     * GSIConstants.GSI_3_RESTRICTED_PROXY} if the policy language of
     * the {@link ProxyCertInfo ProxyCertInfo} extension is set to
     * any other OID then the above.<BR>
     * The certificate type is {@link GSIConstants#EEC GSIConstants.EEC}
     * if the certificate is not a CA certificate or a GSI-2 or GSI-3 proxy.
     *
     * @param crt the TBS certificate to get the type of.
     * @return the certificate type. The certificate type is determined
     *         by rules described above.
     * @exception IOException if something goes wrong.
     * @exception CertificateException for proxy certificates, if
     *            the issuer DN of the certificate does not match
     *            the subject DN of the certificate without the
     *            last <I>CN</I> component. Also, for GSI-3 proxies
     *            when the <code>ProxyCertInfo</code> extension is
     *            not marked as critical.
     */
    private static GSIConstants.CertificateType getCertificateType(TBSCertificateStructure crt)
            throws CertificateException, IOException {
        X509Extensions extensions = crt.getExtensions();
        X509Extension ext = null;

        if (extensions != null) {
            ext = extensions.getExtension(X509Extension.basicConstraints);
            if (ext != null) {
                BasicConstraints basicExt = BasicConstraints.getInstance(ext);
                if (basicExt.isCA()) {
                    return GSIConstants.CertificateType.CA;
                }
            }
        }

        GSIConstants.CertificateType type = GSIConstants.CertificateType.EEC;

        // does not handle multiple AVAs
        X500Name subject = crt.getSubject();

        ASN1Set entry = X509NameHelper.getLastNameEntry(subject);
        ASN1Sequence ava = (ASN1Sequence) entry.getObjectAt(0);
        if (BCStyle.CN.equals(ava.getObjectAt(0))) {
            String value = ((ASN1String) ava.getObjectAt(1)).getString();
            if (value.equalsIgnoreCase("proxy")) {
                type = GSIConstants.CertificateType.GSI_2_PROXY;
            } else if (value.equalsIgnoreCase("limited proxy")) {
                type = GSIConstants.CertificateType.GSI_2_LIMITED_PROXY;
            } else if (extensions != null) {
                boolean gsi4 = true;
                // GSI_4
                ext = extensions.getExtension(ProxyCertInfo.OID);
                if (ext == null) {
                    // GSI_3
                    ext = extensions.getExtension(ProxyCertInfo.OLD_OID);
                    gsi4 = false;
                }
                if (ext != null) {
                    if (ext.isCritical()) {
                        ProxyCertInfo proxyCertExt = getProxyCertInfo(ext);
                        ProxyPolicy proxyPolicy = proxyCertExt.getProxyPolicy();
                        ASN1ObjectIdentifier oid = proxyPolicy.getPolicyLanguage();
                        if (ProxyPolicy.IMPERSONATION.equals(oid)) {
                            if (gsi4) {
                                type = GSIConstants.CertificateType.GSI_4_IMPERSONATION_PROXY;
                            } else {
                                type = GSIConstants.CertificateType.GSI_3_IMPERSONATION_PROXY;
                            }
                        } else if (ProxyPolicy.INDEPENDENT.equals(oid)) {
                            if (gsi4) {
                                type = GSIConstants.CertificateType.GSI_4_INDEPENDENT_PROXY;
                            } else {
                                type = GSIConstants.CertificateType.GSI_3_INDEPENDENT_PROXY;
                            }
                        } else if (ProxyPolicy.LIMITED.equals(oid)) {
                            if (gsi4) {
                                type = GSIConstants.CertificateType.GSI_4_LIMITED_PROXY;
                            } else {
                                type = GSIConstants.CertificateType.GSI_3_LIMITED_PROXY;
                            }
                        } else {
                            if (gsi4) {
                                type = GSIConstants.CertificateType.GSI_4_RESTRICTED_PROXY;
                            } else {
                                type = GSIConstants.CertificateType.GSI_3_RESTRICTED_PROXY;
                            }
                        }

                    } else {
                        String err = i18n.getMessage("proxyCertCritical");
                        throw new CertificateException(err);
                    }
                }
            }

            if (ProxyCertificateUtil.isProxy(type)) {
                X509NameHelper iss = new X509NameHelper(crt.getIssuer());
                iss.add((ASN1Set) BouncyCastleUtil.duplicate(entry));
                X509Name issuer = iss.getAsName();
                if (!issuer.equals(X509Name.getInstance(subject))) {
                    String err = i18n.getMessage("proxyDNErr");
                    throw new CertificateException(err);
                }
            }
        }

        return type;
    }

    /**
     * Gets a boolean array representing bits of the KeyUsage extension.
     *
     * @see java.security.cert.X509Certificate#getKeyUsage
     * @exception IOException if failed to extract the KeyUsage extension value.
     */
    public static boolean[] getKeyUsage(X509Extension ext) throws IOException {
        DERBitString bits = (DERBitString) getExtensionObject(ext);

        // copied from X509CertificateObject
        byte[] bytes = bits.getBytes();
        int length = (bytes.length * 8) - bits.getPadBits();

        boolean[] keyUsage = new boolean[(length < 9) ? 9 : length];

        for (int i = 0; i != length; i++) {
            keyUsage[i] = (bytes[i / 8] & (0x80 >>> (i % 8))) != 0;
        }

        return keyUsage;
    }

    /**
     * Creates a <code>ProxyCertInfo</code> object from given
     * extension.
     *
     * @param ext the extension.
     * @return the <code>ProxyCertInfo</code> object.
     * @exception IOException if something fails.
     */
    public static ProxyCertInfo getProxyCertInfo(X509Extension ext) throws IOException {
        return ProxyCertInfo.getInstance(BouncyCastleUtil.getExtensionObject(ext));
    }

    /**
     * Returns the subject DN of the given certificate in the Globus format.
     *
     * @param cert the certificate to get the subject of. The certificate
     *             must be of <code>X509CertificateObject</code> type.
     * @return the subject DN of the certificate in the Globus format.
     */
    public static String getIdentity(X509Certificate cert) {
        if (cert == null) {
            return null;
        }

        String subjectDN = cert.getSubjectX500Principal().getName(X500Principal.RFC2253);
        X509Name name = new X509Name(true, subjectDN);
        return X509NameHelper.toString(name);
    }

    public static String getIdentityPrefix(X509Certificate cert) {
        if (cert == null) {
            return null;
        }

        String subjectDN = cert.getSubjectX500Principal().getName(X500Principal.RFC2253);
        LdapName ldapname = null;
        try {
            ldapname = new LdapName(subjectDN);
            ldapname.remove(ldapname.size() - 1);
        } catch (InvalidNameException e) {
            return null;
        }
        X509Name name = new X509Name(true, ldapname.toString());
        return X509NameHelper.toString(name);
    }

    /**
     * Finds the identity certificate in the given chain and
     * returns the subject DN of that certificate in the Globus format.
     *
     * @param chain the certificate chain to find the identity
     *              certificate in. The certificates must be
     *              of <code>X509CertificateObject</code> type.
     * @return the subject DN of the identity certificate in
     *         the Globus format.
     * @exception CertificateException if something goes wrong.
     */
    public static String getIdentity(X509Certificate[] chain) throws CertificateException {
        return getIdentity(getIdentityCertificate(chain));
    }

    /**
     * Finds the identity certificate in the given chain.
     * The identity certificate is the first certificate in the
     * chain that is not an impersonation proxy (full or limited)
     *
     * @param chain the certificate chain to find the identity
     *              certificate in.
     * @return the identity certificate.
     * @exception CertificateException if something goes wrong.
     */
    public static X509Certificate getIdentityCertificate(X509Certificate[] chain) throws CertificateException {
        if (chain == null) {
            throw new IllegalArgumentException(i18n.getMessage("certChainNull"));
        }
        GSIConstants.CertificateType certType;
        for (int i = 0; i < chain.length; i++) {
            certType = getCertificateType(chain[i]);
            if (!ProxyCertificateUtil.isImpersonationProxy(certType)) {
                return chain[i];
            }
        }
        return null;
    }

    /**
     * Retrieves the actual value of the X.509 extension.
     *
     * @param certExtValue the DER-encoded OCTET string value of the extension.
     * @return the decoded/actual value of the extension (the octets).
     */
    public static byte[] getExtensionValue(byte[] certExtValue) throws IOException {
        ByteArrayInputStream inStream = new ByteArrayInputStream(certExtValue);
        ASN1InputStream derInputStream = new ASN1InputStream(inStream);
        ASN1Primitive object = derInputStream.readObject();
        if (object instanceof ASN1OctetString) {
            return ((ASN1OctetString) object).getOctets();
        } else {
            throw new IOException(i18n.getMessage("octectExp"));
        }
    }

    /**
     * Returns the actual value of the extension.
     *
     * @param cert the certificate that contains the extensions to retrieve.
     * @param oid the oid of the extension to retrieve.
     * @return the actual value of the extension (not octet string encoded)
     * @exception IOException if decoding the extension fails.
     */
    public static byte[] getExtensionValue(X509Certificate cert, String oid) throws IOException {
        if (cert == null) {
            throw new IllegalArgumentException(i18n.getMessage("certNull"));
        }
        if (oid == null) {
            throw new IllegalArgumentException(i18n.getMessage("oidNull"));
        }

        byte[] value = cert.getExtensionValue(oid);
        if (value == null) {
            return null;
        }

        return getExtensionValue(value);
    }

    public static int getProxyPathConstraint(X509Certificate cert)
            throws IOException, CertificateEncodingException {

        TBSCertificateStructure crt = getTBSCertificateStructure(cert);
        return getProxyPathConstraint(crt);
    }

    public static int getProxyPathConstraint(TBSCertificateStructure crt) throws IOException {

        ProxyCertInfo proxyCertExt = getProxyCertInfo(crt);
        return (proxyCertExt != null) ? proxyCertExt.getPathLenConstraint() : -1;
    }

    public static ProxyCertInfo getProxyCertInfo(TBSCertificateStructure crt) throws IOException {

        X509Extensions extensions = crt.getExtensions();
        if (extensions == null) {
            return null;
        }
        X509Extension ext = extensions.getExtension(ProxyCertInfo.OID);
        if (ext == null) {
            ext = extensions.getExtension(ProxyCertInfo.OLD_OID);
        }
        return (ext != null) ? BouncyCastleUtil.getProxyCertInfo(ext) : null;
    }

}