org.globus.security.util.CertificateUtil.java Source code

Java tutorial

Introduction

Here is the source code for org.globus.security.util.CertificateUtil.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.security.util;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.StringTokenizer;

import javax.security.auth.x500.X500Principal;

import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1Object;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.ASN1Set;
import org.bouncycastle.asn1.DERBitString;
import org.bouncycastle.asn1.DERObject;
import org.bouncycastle.asn1.DERObjectIdentifier;
import org.bouncycastle.asn1.DERString;
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.globus.security.Constants;
import org.globus.security.proxyExtension.ProxyCertInfo;
import org.globus.security.proxyExtension.ProxyPolicy;

/**
 * FILL ME
 *
 * @author ranantha@mcs.anl.gov
 */
public final class CertificateUtil {

    public static final int DIGITAL_SIGNATURE = 0;
    public static final int NON_REPUDIATION = 1;
    public static final int KEY_ENCIPHERMENT = 2;
    public static final int DATA_ENCIPHERMENT = 3;
    public static final int KEY_AGREEMENT = 4;
    public static final int KEY_CERTSIGN = 5;
    public static final int CRL_SIGN = 6;
    public static final int ENCIPHER_ONLY = 7;
    public static final int DECIPHER_ONLY = 8;
    public static final int DEFAULT_USAGE_LENGTH = 9;

    private CertificateUtil() {
        //this should not be constructed;
    }

    /**
     * Return CA Path constraint
     *
     * @param crt
     * @return
     * @throws IOException
     */
    public static int getCAPathConstraint(TBSCertificateStructure crt) throws IOException {

        X509Extensions extensions = crt.getExtensions();
        if (extensions == null) {
            return -1;
        }
        X509Extension proxyExtension = extensions.getExtension(X509Extensions.BasicConstraints);
        if (proxyExtension != null) {
            BasicConstraints basicExt = getBasicConstraints(proxyExtension);
            if (basicExt.isCA()) {
                BigInteger pathLen = basicExt.getPathLenConstraint();
                return (pathLen == null) ? Integer.MAX_VALUE : pathLen.intValue();
            } else {
                return -1;
            }
        }
        return -1;
    }

    /**
     * Returns certificate type of the given TBS certificate. <BR> The
     * certificate type is {@link org.globus.security.Constants.CertificateType#CA
     * CertificateType.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 org.globus.security.Constants.CertificateType#GSI_2_PROXY
     * CertificateType.GSI_2_PROXY}) or <I>"CN=limited proxy"</I> (certificate
     * type {@link org.globus.security.Constants.CertificateType#GSI_2_LIMITED_PROXY
     * CertificateType.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 org.globus.security.proxyExtension.ProxyCertInfo
     * ProxyCertInfo} critical extension. The certificate type is {@link
     * org.globus.security.Constants.CertificateType#GSI_3_IMPERSONATION_PROXY
     * CertificateType.GSI_3_IMPERSONATION_PROXY} if the policy language of the
     * {@link org.globus.security.proxyExtension.ProxyCertInfo ProxyCertInfo}
     * extension is set to {@link org.globus.security.proxyExtension.ProxyPolicy#IMPERSONATION
     * ProxyPolicy.IMPERSONATION} OID. The certificate type is {@link
     * org.globus.security.Constants.CertificateType#GSI_3_LIMITED_PROXY
     * CertificateType.GSI_3_LIMITED_PROXY} if the policy language of the {@link
     * org.globus.security.proxyExtension.ProxyCertInfo ProxyCertInfo} extension
     * is set to {@link org.globus.security.proxyExtension.ProxyPolicy#LIMITED
     * ProxyPolicy.LIMITED} OID. The certificate type is {@link
     * org.globus.security.Constants.CertificateType#GSI_3_INDEPENDENT_PROXY
     * CertificateType.GSI_3_INDEPENDENT_PROXY} if the policy language of the
     * {@link org.globus.security.proxyExtension.ProxyCertInfo ProxyCertInfo}
     * extension is set to {@link org.globus.security.proxyExtension.ProxyPolicy#INDEPENDENT
     * ProxyPolicy.INDEPENDENT} OID. The certificate type is {@link
     * org.globus.security.Constants.CertificateType#GSI_3_RESTRICTED_PROXY
     * CertificateType.GSI_3_RESTRICTED_PROXY} if the policy language of the
     * {@link org.globus.security.proxyExtension.ProxyCertInfo ProxyCertInfo}
     * extension is set to any other OID then the above.<BR> The certificate
     * type is {@link org.globus.security.Constants.CertificateType#EEC
     * CertificateType.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.
     * @throws java.io.IOException if something goes wrong.
     * @throws java.security.cert.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.
     */
    public static Constants.CertificateType getCertificateType(TBSCertificateStructure crt)
            throws CertificateException, IOException {

        X509Extensions extensions = crt.getExtensions();
        X509Extension ext = null;

        if (extensions != null) {
            ext = extensions.getExtension(X509Extensions.BasicConstraints);
            if (ext != null) {
                BasicConstraints basicExt = getBasicConstraints(ext);
                if (basicExt.isCA()) {
                    return Constants.CertificateType.CA;
                }
            }
        }

        Constants.CertificateType type = Constants.CertificateType.EEC;

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

        ASN1Set entry = X509NameHelper.getLastNameEntry(subject);
        ASN1Sequence ava = (ASN1Sequence) entry.getObjectAt(0);
        if (X509Name.CN.equals(ava.getObjectAt(0))) {
            type = processCN(extensions, type, ava);
        }

        return type;
    }

    private static Constants.CertificateType processCN(X509Extensions extensions, Constants.CertificateType type,
            ASN1Sequence ava) throws CertificateException {
        X509Extension ext;
        String value = ((DERString) ava.getObjectAt(1)).getString();
        Constants.CertificateType certType = type;
        if (value.equalsIgnoreCase("proxy")) {
            certType = Constants.CertificateType.GSI_2_PROXY;
        } else if (value.equalsIgnoreCase("limited proxy")) {
            certType = Constants.CertificateType.GSI_2_LIMITED_PROXY;
        } else if (extensions != null) {
            boolean gsi4 = true;
            // GSI_4
            ext = extensions.getExtension(Constants.PROXY_OID);
            if (ext == null) {
                // GSI_3
                ext = extensions.getExtension(Constants.PROXY_OLD_OID);
                gsi4 = false;
            }
            if (ext != null) {
                if (ext.isCritical()) {
                    certType = processCriticalExtension(ext, gsi4);
                } else {
                    String err = "proxyCertCritical";
                    throw new CertificateException(err);
                }
            }
        }

        /** FIXME: this looks like validation
         if (ProxyCertificateUtil.isProxy(type)) {
         X509NameHelper iss = new X509NameHelper(crt.getIssuer());
         iss.add((ASN1Set)BouncyCastleUtil.duplicate(entry));
         X509Name issuer = iss.getAsName();
         if (!issuer.equals(subject)) {
         String err = i18n.getMessage("proxyDNErr");
         throw new CertificateException(err);
         }
         }
         */
        return certType;
    }

    private static Constants.CertificateType processCriticalExtension(X509Extension ext, boolean gsi4) {
        Constants.CertificateType type;
        ProxyCertInfo proxyCertExt = ProxyCertificateUtil.getProxyCertInfo(ext);
        ProxyPolicy proxyPolicy = proxyCertExt.getProxyPolicy();
        DERObjectIdentifier oid = proxyPolicy.getPolicyLanguage();
        if (ProxyPolicy.IMPERSONATION.equals(oid)) {
            if (gsi4) {
                type = Constants.CertificateType.GSI_4_IMPERSONATION_PROXY;
            } else {
                type = Constants.CertificateType.GSI_3_IMPERSONATION_PROXY;
            }
        } else if (ProxyPolicy.INDEPENDENT.equals(oid)) {
            if (gsi4) {
                type = Constants.CertificateType.GSI_4_INDEPENDENT_PROXY;
            } else {
                type = Constants.CertificateType.GSI_3_INDEPENDENT_PROXY;
            }
        } else if (ProxyPolicy.LIMITED.equals(oid)) {
            if (gsi4) {
                type = Constants.CertificateType.GSI_4_LIMITED_PROXY;
            } else {
                type = Constants.CertificateType.GSI_3_LIMITED_PROXY;
            }
        } else {
            if (gsi4) {
                type = Constants.CertificateType.GSI_4_RESTRICTED_PROXY;
            } else {
                type = Constants.CertificateType.GSI_3_RESTRICTED_PROXY;
            }
        }
        return type;
    }

    /**
     * Creates a <code>BasicConstraints</code> object from given extension.
     *
     * @param ext the extension.
     * @return the <code>BasicConstraints</code> object.
     * @throws IOException if something fails.
     */
    public static BasicConstraints getBasicConstraints(X509Extension ext) throws IOException {

        ASN1Object object = X509Extension.convertValueToObject(ext);
        return BasicConstraints.getInstance(object);
    }

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

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

    public static boolean[] getKeyUsage(TBSCertificateStructure crt) throws IOException {
        X509Extensions extensions = crt.getExtensions();
        if (extensions == null) {
            return new boolean[0];
        }
        X509Extension extension = extensions.getExtension(X509Extensions.KeyUsage);
        return (extension != null) ? getKeyUsage(extension) : new boolean[0];
    }

    /**
     * Gets a boolean array representing bits of the KeyUsage extension.
     *
     * @throws IOException if failed to extract the KeyUsage extension value.
     * @see java.security.cert.X509Certificate#getKeyUsage
     */
    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 < DEFAULT_USAGE_LENGTH) ? DEFAULT_USAGE_LENGTH : length];

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

        return keyUsage;
    }

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

    /**
     * Converts DN of the form "CN=A, OU=B, O=C" into Globus format
     * "/O=C/OU=B/CN=A" <BR> This function might return incorrect
     * Globus-formatted ID when one of the RDNs in the DN contains commas.
     *
     * @return the converted DN in Globus format.
     */
    public static String toGlobusID(X500Principal principal) {

        if (principal == null) {
            return null;
        }

        String dn = principal.getName();

        StringTokenizer tokens = new StringTokenizer(dn, ",");
        StringBuffer buf = new StringBuffer();
        String token;

        while (tokens.hasMoreTokens()) {
            token = tokens.nextToken().trim();
            buf.insert(0, token);
            buf.insert(0, "/");
        }
        return buf.toString();
    }

    public static X500Principal toPrincipal(String globusID) {

        if (globusID == null) {
            return null;
        }
        String id = globusID.trim();
        StringTokenizer tokens = new StringTokenizer(id, "/");
        StringBuffer buf = new StringBuffer();
        String token;

        if (tokens.hasMoreTokens()) {
            token = tokens.nextToken().trim();
            buf.insert(0, token);
        }

        while (tokens.hasMoreTokens()) {
            token = tokens.nextToken().trim();

            buf.insert(0, ",");
            buf.insert(0, token);
        }

        String dn = buf.toString();

        return new X500Principal(dn);
    }

}