/* * 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; } }