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

org.globus.gsi.util.CertificateUtil Maven / Gradle / Ivy

/*
 * 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.util;

import org.apache.commons.logging.LogFactory;

import org.apache.commons.logging.Log;

import java.security.Provider;



import org.globus.common.CoGProperties;

import org.bouncycastle.jce.provider.BouncyCastleProvider;

import java.security.Security;

import java.security.KeyPairGenerator;

import java.security.GeneralSecurityException;

import java.security.KeyPair;

import java.security.Principal;

import org.globus.gsi.bc.X509NameHelper;

import java.util.ArrayDeque;
import java.util.Arrays;
import java.util.Deque;
import java.util.HashMap;
import java.util.Map;
import java.util.StringTokenizer;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.math.BigInteger;

import java.security.cert.CertificateFactory;
import java.security.cert.CertPath;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
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.gsi.GSIConstants;
import org.globus.gsi.proxy.ext.ProxyCertInfo;
import org.globus.gsi.proxy.ext.ProxyPolicy;

import static org.globus.gsi.util.Oid.*;

/**
 * FILL ME
 *
 * @author [email protected]
 */
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 static String provider;
    private static Log logger;

    static {
        Security.addProvider(new BouncyCastleProvider());
        setProvider("BC");
        logger = LogFactory.getLog(CertificateLoadUtil.class.getCanonicalName());
        installSecureRandomProvider();
    }

    private static final Map KEYWORD_MAP = new HashMap();

    private static final Map OID_MAP = new HashMap();


    static {
        // Taken from BouncyCastle 2.46
        KEYWORD_MAP.put("SN", SERIALNUMBER.oid);
        KEYWORD_MAP.put("E", EmailAddress.oid);
        KEYWORD_MAP.put("EMAIL", EmailAddress.oid);
        KEYWORD_MAP.put("UNSTRUCTUREDADDRESS", UnstructuredAddress.oid);
        KEYWORD_MAP.put("UNSTRUCTUREDNAME", UnstructuredName.oid);
        KEYWORD_MAP.put("UNIQUEIDENTIFIER", UNIQUE_IDENTIFIER.oid);
        KEYWORD_MAP.put("DN", DN_QUALIFIER.oid);
        KEYWORD_MAP.put("PSEUDONYM", PSEUDONYM.oid);
        KEYWORD_MAP.put("POSTALADDRESS", POSTAL_ADDRESS.oid);
        KEYWORD_MAP.put("NAMEOFBIRTH", NAME_AT_BIRTH.oid);
        KEYWORD_MAP.put("COUNTRYOFCITIZENSHIP", COUNTRY_OF_CITIZENSHIP.oid);
        KEYWORD_MAP.put("COUNTRYOFRESIDENCE", COUNTRY_OF_RESIDENCE.oid);
        KEYWORD_MAP.put("GENDER", GENDER.oid);
        KEYWORD_MAP.put("PLACEOFBIRTH", PLACE_OF_BIRTH.oid);
        KEYWORD_MAP.put("DATEOFBIRTH", DATE_OF_BIRTH.oid);
        KEYWORD_MAP.put("POSTALCODE", POSTAL_CODE.oid);
        KEYWORD_MAP.put("BUSINESSCATEGORY", BUSINESS_CATEGORY.oid);
        KEYWORD_MAP.put("TELEPHONENUMBER", TELEPHONE_NUMBER.oid);
        KEYWORD_MAP.put("NAME", NAME.oid);

        // Taken from CANL library
        KEYWORD_MAP.put("S", ST.oid);
        KEYWORD_MAP.put("DNQUALIFIER", DN_QUALIFIER.oid);
        KEYWORD_MAP.put("IP", IP.oid);

        OID_MAP.put(UnstructuredAddress.oid, "unstructuredAddress");
        OID_MAP.put(UnstructuredName.oid, "unstructuredName");
        OID_MAP.put(UNIQUE_IDENTIFIER.oid, "UniqueIdentifier");
        OID_MAP.put(PSEUDONYM.oid, "Pseudonym");
        OID_MAP.put(POSTAL_ADDRESS.oid, "PostalAddress");
        OID_MAP.put(NAME_AT_BIRTH.oid, "NameAtBirth");
        OID_MAP.put(COUNTRY_OF_CITIZENSHIP.oid, "CountryOfCitizenship");
        OID_MAP.put(COUNTRY_OF_RESIDENCE.oid, "CountryOfResidence");
        OID_MAP.put(GENDER.oid, "Fender");
        OID_MAP.put(PLACE_OF_BIRTH.oid, "PlaceOfBirth");
        OID_MAP.put(DATE_OF_BIRTH.oid, "DateOfBirth");
        OID_MAP.put(POSTAL_CODE.oid, "PostalCode");
        OID_MAP.put(BUSINESS_CATEGORY.oid, "BusinessCategory");
        OID_MAP.put(TELEPHONE_NUMBER.oid, "TelephoneNumber");
        OID_MAP.put(NAME.oid, "Name");
        OID_MAP.put(IP.oid, "IP");

        OID_MAP.put(T.oid, "T");
        OID_MAP.put(DN_QUALIFIER.oid, "DNQUALIFIER");
        OID_MAP.put(SURNAME.oid, "SURNAME");
        OID_MAP.put(GIVENNAME.oid, "GIVENNAME");
        OID_MAP.put(INITIALS.oid, "INITIALS");
        OID_MAP.put(GENERATION.oid, "GENERATION");
        OID_MAP.put(EmailAddress.oid, "EMAILADDRESS");
        OID_MAP.put(SERIALNUMBER.oid, "SERIALNUMBER");
    }

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

    /**
     * A no-op function that can be used to force the class
     * to load and initialize.
     */
    public static void init() {
        CertificateLoadUtil.init();
    }

    /**
     * Sets a provider name to use for loading certificates
     * and for generating key pairs.
     *
     * @param providerName provider name to use.
     */
    public static void setProvider(String providerName) {
        provider = providerName;
    }

    /**
     * Installs SecureRandom provider. 
     * This function is automatically called when this class is loaded.
     */
    public static void installSecureRandomProvider() {
        CoGProperties props = CoGProperties.getDefault();
        String providerName = props.getSecureRandomProvider();
        try {
            Class providerClass = Class.forName(providerName);
            Security.insertProviderAt( (Provider)providerClass.newInstance(), 
                                       1 );
        } catch (Exception e) {
            logger.debug("Unable to install PRNG. Using default PRNG.",e);
        }
    }

    /**
     * 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;
    }
    
    /**
     * Generates a key pair of given algorithm and strength.
     *
     * @param algorithm the algorithm of the key pair.
     * @param bits the strength
     * @return KeyPair the generated key pair.
     * @exception GeneralSecurityException if something goes wrong.
     */
    public static KeyPair generateKeyPair(String algorithm, int bits) 
        throws GeneralSecurityException {
        KeyPairGenerator generator = null;
        if (provider == null) {
            generator = KeyPairGenerator.getInstance(algorithm);
        } else {
            generator = KeyPairGenerator.getInstance(algorithm, provider);
        }
        generator.initialize(bits);
        return generator.generateKeyPair();
    }


    /**
     * Returns certificate type of the given TBS certificate. 
The * certificate type is {@link org.globus.gsi.GSIGSIConstants.CertificateType#CA * CertificateType.CA} only if the certificate contains a * BasicConstraints extension and it is marked as CA.
A certificate is a * GSI-2 proxy when the subject DN of the certificate ends with * "CN=proxy" (certificate type {@link org.globus.gsi.GSIGSIConstants.CertificateType#GSI_2_PROXY * CertificateType.GSI_2_PROXY}) or "CN=limited proxy" (certificate * type {@link org.globus.gsi.GSIGSIConstants.CertificateType#GSI_2_LIMITED_PROXY * CertificateType.LIMITED_PROXY}) component and the issuer DN of the * certificate matches the subject DN without the last proxy CN * component.
A certificate is a GSI-3 proxy when the subject DN of the * certificate ends with a CN component, the issuer DN of the * certificate matches the subject DN without the last CN component * and the certificate contains {@link org.globus.security.proxyExtension.ProxyCertInfo * ProxyCertInfo} critical extension. The certificate type is {@link * org.globus.gsi.GSIGSIConstants.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.gsi.GSIGSIConstants.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.gsi.GSIGSIConstants.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.gsi.GSIGSIConstants.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.
The certificate * type is {@link org.globus.gsi.GSIGSIConstants.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 CN * component. Also, for GSI-3 proxies when the * ProxyCertInfo extension is not * marked as critical. */ public static GSIConstants.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 GSIConstants.CertificateType.CA; } } } GSIConstants.CertificateType type = GSIConstants.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 GSIConstants.CertificateType processCN( X509Extensions extensions, GSIConstants.CertificateType type, ASN1Sequence ava) throws CertificateException { X509Extension ext; String value = ((DERString) ava.getObjectAt(1)).getString(); GSIConstants.CertificateType certType = type; if (value.equalsIgnoreCase("proxy")) { certType = GSIConstants.CertificateType.GSI_2_PROXY; } else if (value.equalsIgnoreCase("limited proxy")) { certType = 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()) { certType = processCriticalExtension(ext, gsi4); } else { String err = "proxyCertCritical"; throw new CertificateException(err); } } } return certType; } private static GSIConstants.CertificateType processCriticalExtension(X509Extension ext, boolean gsi4) { GSIConstants.CertificateType type; ProxyCertInfo proxyCertExt = ProxyCertificateUtil.getProxyCertInfo(ext); ProxyPolicy proxyPolicy = proxyCertExt.getProxyPolicy(); DERObjectIdentifier 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; } } return type; } /** * Creates a BasicConstraints object from given extension. * * @param ext the extension. * @return the BasicConstraints 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 DERObject. * * @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 "/CN=A/OU=B/O=C".
* This function might return incorrect Globus-formatted ID when one of * the RDNs in the DN contains commas. * @see #toGlobusID(String, boolean) * * @param dn the DN to convert to Globus format. * @return the converted DN in Globus format. */ public static String toGlobusID(String dn) { return toGlobusID(dn, true); } /** * Converts DN of the form "CN=A, OU=B, O=C" into Globus * format "/CN=A/OU=B/O=C" or "/O=C/OU=B/CN=A" depending on the * noreverse option. If noreverse is true * the order of the DN components is not reveresed - "/CN=A/OU=B/O=C" is * returned. If noreverse is false, the order of the * DN components is reversed - "/O=C/OU=B/CN=A" is returned.
* This function might return incorrect Globus-formatted ID when one of * the RDNs in the DN contains commas. * * @param dn the DN to convert to Globus format. * @param noreverse the direction of the conversion. * @return the converted DN in Globus format. */ public static String toGlobusID(String dn, boolean noreverse) { if (dn == null) { return null; } StringBuilder buf = new StringBuilder(); String[] tokens = dn.split(","); if (noreverse) { for (int i = 0; i < tokens.length; i++) { String token = tokens[i].trim(); if (!token.isEmpty()) { buf.append("/"); buf.append(token.trim()); } } } else { for (int i = tokens.length - 1; i >= 0; i--) { String token = tokens[i].trim(); if (!token.isEmpty()) { buf.append("/"); buf.append(token.trim()); } } } return buf.toString(); } /** * Converts the specified principal into Globus format. * If the principal is of unrecognized type a simple string-based * conversion is made using the {@link #toGlobusID(String) toGlobusID()} * function. * * @see #toGlobusID(String) * * @param name the principal to convert to Globus format. * @return the converted DN in Globus format. */ public static String toGlobusID(Principal name) { if (name instanceof X509Name) { return X509NameHelper.toString((X509Name)name); } else if (name instanceof X500Principal) { return CertificateUtil.toGlobusID((X500Principal) name); } else { return CertificateUtil.toGlobusID(name.getName()); } } /** * Converts DN of the form "CN=A, OU=B, O=C" into Globus format * "/O=C/OU=B/CN=A"
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(X500Principal.RFC2253, OID_MAP); StringBuilder buf = new StringBuilder(); String[] tokens = dn.split(","); for (int i = tokens.length - 1; i >= 0; i--) { String token = tokens[i].trim(); if (!token.isEmpty()) { buf.append("/"); buf.append(token); } } return buf.toString(); } /** * Converts Globus DN format "/O=C/OU=B/CN=A" into an X500Principal * representation, which accepts RFC 2253 or 1779 formatted DN's and also * attribute types as defined in RFC 2459 (e.g. "CN=A,OU=B,O=C"). This * method should allow the forward slash, "/", to occur in attribute values * (see GFD.125 section 3.2.2 -- RFC 2252 allows "/" in PrintableStrings). * @param globusID DN in Globus format * @return the X500Principal representation of the given DN */ public static X500Principal toPrincipal(String globusID) { if (globusID == null) { return null; } String id = globusID.trim(); StringBuilder buf = new StringBuilder(id.length()); if (!id.isEmpty()) { final int IDLE = 0; final int VALUE = 1; final int KEY = 2; int state = IDLE; int cEnd = 0; char[] asChars = id.toCharArray(); /* * walk in reverse order and split into RDN */ for (int i = asChars.length - 1; i >= 0; i--) { char c = asChars[i]; switch (state) { case KEY: if (c == '/' || c == ' ') { buf.append(id.substring(i + 1, cEnd + 1)).append(','); state = IDLE; } break; case VALUE: if (c == '=') { state = KEY; } break; default: // idle if (c == '/' || c == ' ') { continue; } else { cEnd = i; state = VALUE; } } } // delete last extra comma buf.deleteCharAt(buf.length() - 1); } String dn = buf.toString(); return new X500Principal(dn, KEYWORD_MAP); } // JGLOBUS-91 public static CertPath getCertPath(X509Certificate[] certs) throws CertificateException { CertificateFactory factory = CertificateFactory.getInstance("X.509"); return factory.generateCertPath(Arrays.asList(certs)); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy