org.ccnx.ccn.impl.security.crypto.util.CryptoUtil.java Source code

Java tutorial

Introduction

Here is the source code for org.ccnx.ccn.impl.security.crypto.util.CryptoUtil.java

Source

/*
 * Part of the CCNx Java Library.
 *
 * Copyright (C) 2008, 2009, 2010, 2011 Palo Alto Research Center, Inc.
 *
 * This library is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License version 2.1
 * as published by the Free Software Foundation. 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details. You should have received
 * a copy of the GNU Lesser General Public License along with this library;
 * if not, write to the Free Software Foundation, Inc., 51 Franklin Street,
 * Fifth Floor, Boston, MA 02110-1301 USA.
 */

package org.ccnx.ccn.impl.security.crypto.util;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.Key;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
import java.util.ArrayList;
import java.util.Enumeration;

import org.bouncycastle.asn1.ASN1Encodable;
import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1OctetString;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.DEREncodable;
import org.bouncycastle.asn1.DERObject;
import org.bouncycastle.asn1.DEROctetString;
import org.bouncycastle.asn1.DEROutputStream;
import org.bouncycastle.asn1.DERString;
import org.bouncycastle.asn1.x509.GeneralName;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.asn1.x509.X509Extensions;
import org.ccnx.ccn.impl.security.crypto.CCNDigestHelper;
import org.ccnx.ccn.impl.support.Log;
import org.ccnx.ccn.impl.support.Tuple;

/**
 * A collection of crypto-related utility methods largely related to BouncyCastle.
 * 
 */
public class CryptoUtil {

    /**
     * Helper function to DER encode content.
     * @param encodable content to encode
     * @return encoded content
     * @throws CertificateEncodingException if there is a problem encoding the content
     */
    public static byte[] encode(DEREncodable encodable) throws CertificateEncodingException {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        try {
            DEROutputStream dos = new DEROutputStream(baos);
            dos.writeObject(encodable);
            dos.close();
        } catch (IOException ex) {
            throw new CertificateEncodingException("Cannot encode: " + ex.toString());
        }
        return baos.toByteArray();
    }

    /**
     * Helper function to decode DER content.
     * @param decodable content to decode
     * @return generic DERObject, result of decoding
     * @throws CertificateEncodingException if there is a problem decoding the content
     */
    public static DERObject decode(byte[] decodable) throws CertificateEncodingException {
        DERObject dobj = null;
        try {
            ByteArrayInputStream bais = new ByteArrayInputStream(decodable);
            ASN1InputStream dis = new ASN1InputStream(bais);
            dobj = dis.readObject();
            dis.close();
        } catch (IOException ex) {
            StringBuffer sb = new StringBuffer();
            sb.append("decode error - length " + decodable.length);
            for (byte b : decodable)
                sb.append(" " + Integer.toHexString((int) b));
            Log.severe(sb.toString());
            for (StackTraceElement ste : ex.getStackTrace())
                Log.severe(ste.toString());
            throw new CertificateEncodingException("Cannot encode: " + ex.toString());
        }
        return dobj;
    }

    /**
     * Helper function to unpack public keys from DER encoding into Java PublicKey format
     * @param spki a decoded SubjectPublicKeyInfo containing the desired public key
     * @return the decoded PublicKey
     * @throws CertificateEncodingException if there is a problem decoding the content
     * @throws NoSuchAlgorithmException if the key algorithm is unknown
     * @throws InvalidKeySpecException if the data in the SubjectPublicKeyInfo doesn't correctly represent a key
     */
    public static PublicKey getPublicKey(SubjectPublicKeyInfo spki)
            throws CertificateEncodingException, NoSuchAlgorithmException, InvalidKeySpecException {
        // Reencode SubjectPublicKeyInfo, let java decode it.
        byte[] encodedKey = encode(spki);

        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(encodedKey);
        String algorithmOID = spki.getAlgorithmId().getObjectId().getId();
        String algorithm = OIDLookup.getCipherName(algorithmOID);
        if (algorithm == null) {
            throw new CertificateEncodingException("Unknown key algorithm!");
        }
        KeyFactory fact = KeyFactory.getInstance(algorithm);
        return fact.generatePublic(keySpec);
    }

    /**
     * Helper function to decode and unpack a public key from DER encoding to a Java PublicKey
     * @param derEncodedPublicKey DER encoding of public key in standard format (SubjectPublicKeyInfo)
     * @return the decoded PublicKey
     * @throws CertificateEncodingException if there is a problem decoding the content
     * @throws NoSuchAlgorithmException if the key algorithm is unknown
     * @throws InvalidKeySpecException if the data in the SubjectPublicKeyInfo doesn't correctly represent a key
     */
    public static PublicKey getPublicKey(byte[] derEncodedPublicKey)
            throws CertificateEncodingException, InvalidKeySpecException {

        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(derEncodedPublicKey);
        // Problem is, we need the algorithm identifier inside
        // the key to decode it. So in essence we need to
        // decode it twice.
        DERObject genericObject = decode(derEncodedPublicKey);
        if (!(genericObject instanceof ASN1Sequence)) {
            throw new InvalidKeySpecException("This object is not a public key!");
        }

        // At this point it might also be a certificate, or
        // any number of things. 
        SubjectPublicKeyInfo keyInfo = new SubjectPublicKeyInfo((ASN1Sequence) genericObject);
        String keyTypeOID = keyInfo.getAlgorithmId().getObjectId().toString();
        String keyType = OIDLookup.getCipherName(keyTypeOID);
        if (keyType == null) {
            Log.warning("Cannot find key type corresponding to OID: " + keyTypeOID);
            throw new InvalidKeySpecException("Unknown key type OID " + keyTypeOID + " in stored key.");
        }

        KeyFactory keyFactory = null;
        PublicKey key = null;
        try {
            keyFactory = KeyFactory.getInstance(keyType);
            key = keyFactory.generatePublic(keySpec);
        } catch (NoSuchAlgorithmException e) {
            Log.warning("Unknown key type " + keyType + " in stored key.");
            throw new InvalidKeySpecException("Unknown key type " + keyType + " in stored key.");
        }
        return key;
    }

    /**
     * Helper method to decode a certificate.
     * @param encodedCert DER encoded X.509 certificate
     * @return the decoded X509Certificate
     * @throws CertificateException if there is an error in decoding
     */
    public static X509Certificate getCertificate(byte[] encodedCert) throws CertificateException {
        // Will make default provider's certificate if it has one.
        CertificateFactory cf = CertificateFactory.getInstance("X.509");
        return (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(encodedCert));
    }

    private CryptoUtil() {
        super();
    }

    /**
     * Generates a CertID -- the digest of the DER encoding
     * of a java.security.cert.Certificate
     * @param digestAlg the digest algorithm to use
     * @param cert the certificate to digest
     * @return the CertID
     * @throws CertificateEncodingException if there is an error in the certificate encoding
     */
    public static byte[] generateCertID(String digestAlg, Certificate cert) throws CertificateEncodingException {
        byte[] id = null;
        try {
            byte[] encoding = cert.getEncoded();
            id = DigestHelper.digest(digestAlg, encoding);
        } catch (java.security.NoSuchAlgorithmException ex) {
            // DKS --big configuration problem
            throw new RuntimeException("Error: can't find " + digestAlg + "!  " + ex.toString());
        }
        return id;
    }

    /**
     * Generates a CertID -- the digest of the DER encoding
     * of a java.security.cert.Certificate
     * @param cert the certificate
     * @return the CertID
     */
    public static byte[] generateCertID(Certificate cert) throws CertificateEncodingException {
        return generateCertID(CCNDigestHelper.DEFAULT_DIGEST_ALGORITHM, cert);
    }

    /**
     * Generates a KeyID -- the digest of the DER encoding
     * of a SubjectPublicKeyInfo, or of a raw encoding of a 
     * symmetric key.  Note that the former is slightly uncommon;
     * but it is more general and complete than digesting the BIT STRING
     * component of the SubjectPublicKeyInfo itself (and no standard dictates
     * how you must generate a key ID).
     * @param digestAlg the digest algorithm to use
     * @param key the key to digest
     * @return the KeyID
     */
    public static byte[] generateKeyID(String digestAlg, Key key) {

        byte[] id = null;
        try {
            byte[] encoding = key.getEncoded();
            id = DigestHelper.digest(digestAlg, encoding);
        } catch (java.security.NoSuchAlgorithmException ex) {
            // DKS --big configuration problem
            throw new RuntimeException("Error: can't find " + digestAlg + "!  " + ex.toString());
        }
        return id;
    }

    /**
     * Generates a KeyID -- the digest of the DER encoding
     * of a SubjectPublicKeyInfo, or of a raw encoding of a 
     * symmetric key.  Note that the former is slightly uncommon;
     * but it is more general and complete than digesting the BIT STRING
     * component of the SubjectPublicKeyInfo itself (and no standard dictates
     * how you must generate a key ID).
     * @param key the key to digest
     * @return the KeyID
     */
    public static byte[] generateKeyID(Key key) {
        return generateKeyID(CCNDigestHelper.DEFAULT_DIGEST_ALGORITHM, key);
    }

    /**
     * Get the keyID from a CA certificate to use as the key ID in an AuthorityKeyIdentifier
     * extension for certificates issued by that CA. This should come out of the SubjectKeyIdentifier
     * extension of the certificate if present. If that extension is missing, this function
     * will return null, and generateKeyID can be used to generate a new key ID.
     * @param issuerCert the issuer certificate to extract the key ID from
     * @return the key ID
     * @throws IOException
     * @throws CertificateEncodingException
     */
    public static byte[] getKeyIDFromCertificate(X509Certificate issuerCert)
            throws IOException, CertificateEncodingException {
        byte[] keyIDExtensionValue = issuerCert.getExtensionValue(X509Extensions.SubjectKeyIdentifier.toString());
        if (null == keyIDExtensionValue)
            return null;
        // extension should decode to an OCTET STRING containing the key id.
        DERObject decodedValue = decode(keyIDExtensionValue);
        if (!(decodedValue instanceof ASN1OctetString)) {
            throw new CertificateEncodingException("Cannot parse SubjectKeyIdentifier extension!");
        }
        // now decode the inner octet string to get key ID
        DERObject keyID = decode(((ASN1OctetString) decodedValue).getOctets());
        if (!(keyID instanceof ASN1OctetString)) {
            throw new CertificateEncodingException("Cannot parse SubjectKeyIdentifier extension!");
        }
        return ((ASN1OctetString) keyID).getOctets();
    }

    /**
     * Helper method to pull SubjectAlternativeNames from a certificate. BouncyCastle has
     * one of these, but it isn't included on all platforms. We get one by default from X509Certificate
     * but it returns us a collection of ? and we can't ever know what the ? is because we might
     * get a different impl class on different platforms. So we have to roll our own.
     * 
     * We filter the general names down to ones we can handle.
     * @param certificate
     * @return
     * @throws IOException 
     * @throws CertificateEncodingException 
     */
    public static ArrayList<Tuple<Integer, String>> getSubjectAlternativeNames(X509Certificate certificate)
            throws IOException, CertificateEncodingException {

        byte[] encodedExtension = certificate.getExtensionValue(X509Extensions.SubjectAlternativeName.getId());

        ArrayList<Tuple<Integer, String>> list = new ArrayList<Tuple<Integer, String>>();

        if (null == encodedExtension) {
            return list;
        }

        // content of extension is wrapped in a DEROctetString
        DEROctetString content = (DEROctetString) CryptoUtil.decode(encodedExtension);
        byte[] encapsulatedOctetString = content.getOctets();

        ASN1InputStream aIn = new ASN1InputStream(encapsulatedOctetString);
        ASN1Encodable decodedObject = (ASN1Encodable) aIn.readObject();
        ASN1Sequence sequence = (ASN1Sequence) decodedObject.getDERObject();

        Integer tag;
        GeneralName generalName;

        Enumeration<?> it = sequence.getObjects();
        while (it.hasMoreElements()) {
            generalName = GeneralName.getInstance(it.nextElement());
            tag = generalName.getTagNo();

            switch (tag) {
            case GeneralName.dNSName:
            case GeneralName.rfc822Name:
            case GeneralName.uniformResourceIdentifier:
                list.add(new Tuple<Integer, String>(tag, ((DERString) generalName.getName()).getString()));
            default:
                // ignore other types
            }
        }
        return list;
    }

    /**
     * Get the first DNS name in the subject alternative names.
     * @throws IOException 
     * @throws CertificateEncodingException 
     */
    public static String getSubjectAlternativeNameDNSName(X509Certificate certificate)
            throws IOException, CertificateEncodingException {
        return findSubjectAlternativeName(GeneralName.dNSName, certificate);
    }

    /**
     * Get the first email address in the subject alternative names.
     * @throws IOException 
     * @throws CertificateEncodingException 
     */
    public static String getSubjectAlternativeNameEmailAddress(X509Certificate certificate)
            throws IOException, CertificateEncodingException {
        return findSubjectAlternativeName(GeneralName.rfc822Name, certificate);
    }

    /**
     * Get the first DNS name in the subject alternative names.
     * @throws IOException 
     * @throws URISyntaxException 
     * @throws CertificateEncodingException 
     */
    public static URI getSubjectAlternativeNameURI(X509Certificate certificate)
            throws IOException, URISyntaxException, CertificateEncodingException {
        String uriString = findSubjectAlternativeName(GeneralName.uniformResourceIdentifier, certificate);

        if (null == uriString) {
            return null;
        }
        return new URI(uriString);
    }

    public static String findSubjectAlternativeName(int tag, X509Certificate certificate)
            throws IOException, CertificateEncodingException {
        ArrayList<Tuple<Integer, String>> alternativeNames = getSubjectAlternativeNames(certificate);

        for (Tuple<Integer, String> name : alternativeNames) {
            if (name.first() == tag) {
                return name.second();
            }
        }
        return null;
    }
}