Java tutorial
/* $Id$ Copyright (C) 2007-2010 Virginia Tech. All rights reserved. SEE LICENSE FOR MORE INFORMATION Author: Middleware Services Email: middleware@vt.edu Version: $Revision$ Updated: $Date$ */ package edu.vt.middleware.crypt.util; import java.io.BufferedInputStream; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.security.KeyFactory; import java.security.PrivateKey; import java.security.PublicKey; import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.spec.InvalidKeySpecException; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; import java.util.ArrayList; import java.util.List; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; import edu.vt.middleware.crypt.CryptException; import edu.vt.middleware.crypt.CryptProvider; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.bouncycastle.asn1.ASN1InputStream; import org.bouncycastle.asn1.DERObject; /** * Helper class for performing I/O read operations on cryptographic data. * * @author Middleware Services * @version $Revision: 578 $ */ public class CryptReader { /** X.509 certificate type. */ public static final String DEFAULT_CERTIFICATE_TYPE = "X.509"; /** Buffer size for read operations. */ private static final int BUFFER_SIZE = 4096; /** Protected constructor of utility class. */ protected CryptReader() { } /** * Reads the raw bytes of a symmetric encryption key from a file. * * @param keyFile File containing key data. * @param algorithm Symmetric cipher algorithm for which key is used. * * @return Secret key. * * @throws IOException On IO errors. */ public static SecretKey readSecretKey(final File keyFile, final String algorithm) throws IOException { return readSecretKey(new BufferedInputStream(new FileInputStream(keyFile)), algorithm); } /** * Reads the raw bytes of a symmetric encryption key from an input stream. * * @param keyStream Stream containing key data. * @param algorithm Symmetric cipher algorithm for which key is used. * * @return Secret key. * * @throws IOException On IO errors. */ public static SecretKey readSecretKey(final InputStream keyStream, final String algorithm) throws IOException { return new SecretKeySpec(readData(keyStream), algorithm); } /** * Reads a DER-encoded private key in PKCS#8 format from a file into a {@link * PrivateKey} object. SSLeay-format keys may also work in some cases; testing * revealed SSLeay-format RSA keys generated by the OpenSSL rsa command are * supported. * * @param keyFile Private key file. * @param algorithm Name of encryption algorithm used by key. * * @return Private key containing data read from file. * * @throws CryptException On key format errors. * @throws IOException On key read errors. */ public static PrivateKey readPrivateKey(final File keyFile, final String algorithm) throws CryptException, IOException { return readPrivateKey(new BufferedInputStream(new FileInputStream(keyFile)), algorithm); } /** * Reads a DER-encoded private key in PKCS#8 format from an input stream into * a {@link PrivateKey} object. SSLeay-format keys may also work in some * cases; testing revealed SSLeay-format RSA keys generated by the OpenSSL rsa * command are supported. * * @param keyStream Input stream containing private key data. * @param algorithm Name of encryption algorithm used by key. * * @return Private key containing data read from stream. * * @throws CryptException On key format errors. * @throws IOException On key read errors. */ public static PrivateKey readPrivateKey(final InputStream keyStream, final String algorithm) throws CryptException, IOException { final KeyFactory kf = CryptProvider.getKeyFactory(algorithm); try { final PKCS8EncodedKeySpec keysp = new PKCS8EncodedKeySpec(readData(keyStream)); return kf.generatePrivate(keysp); } catch (InvalidKeySpecException e) { throw new CryptException("Invalid private key format.", e); } } /** * Reads a PEM-encoded private key from a file into a {@link PrivateKey} * object. The header of the PEM-encoded file must meet the requirements * described in {@link #readPemPrivateKey(InputStream, char[])}. * * @param keyFile File containing private key data in PEM format. * @param password Password used to encrypt private key; may be null to * indicate no encryption. * * @return Private key containing data read from file. * * @throws CryptException On key format errors. * @throws IOException On key read errors. */ public static PrivateKey readPemPrivateKey(final File keyFile, final char[] password) throws CryptException, IOException { return readPemPrivateKey(new BufferedInputStream(new FileInputStream(keyFile)), password); } /** * Reads a PEM-encoded private key from an input stream into a {@link * PrivateKey} object. The header of the encoded key <strong>MUST</strong> * contain the cipher algorithm of the key, e.g.: * * <pre>-----BEGIN RSA PRIVATE KEY-----</pre> * * <p>This is the case for PEM-encoded SSLeay format private keys created by * the OpenSSL rsa command, but <em>not</em> for the OpenSSL pkcs8 command. * </p> * * @param keyStream Input stream containing private key data in PEM format. * @param password Password used to encrypt private key; may be null to * indicate no encryption. * * @return Private key containing data read from stream. * * @throws CryptException On key format errors. * @throws IOException On key read errors. */ public static PrivateKey readPemPrivateKey(final InputStream keyStream, final char[] password) throws CryptException, IOException { return PemHelper.decodeKey(readPem(keyStream), password); } /** * Reads a DER-encoded X.509 public key from an input stream into a {@link * PublicKey} object. * * @param keyFile File containing DER-encoded X.509 public key. * @param algorithm Name of encryption algorithm used by key. * * @return Public key containing data read from file. * * @throws CryptException On key format errors. * @throws IOException On key read errors. */ public static PublicKey readPublicKey(final File keyFile, final String algorithm) throws CryptException, IOException { return readPublicKey(new BufferedInputStream(new FileInputStream(keyFile)), algorithm); } /** * Reads a DER-encoded X.509 public key from an input stream into a {@link * PublicKey} object. * * @param keyStream Input stream containing DER-encoded X.509 public key. * @param algorithm Name of encryption algorithm used by key. * * @return Public key containing data read from stream. * * @throws CryptException On key format errors. * @throws IOException On key read errors. */ public static PublicKey readPublicKey(final InputStream keyStream, final String algorithm) throws CryptException, IOException { final KeyFactory kf = CryptProvider.getKeyFactory(algorithm); try { final X509EncodedKeySpec keySpec = new X509EncodedKeySpec(readData(keyStream)); return kf.generatePublic(keySpec); } catch (InvalidKeySpecException e) { throw new CryptException("Invalid public key format.", e); } } /** * Reads a PEM-encoded public key from a file into a {@link PublicKey} object. * * @param keyFile File containing public key data in PEM format. * * @return Public key containing data read from file. * * @throws CryptException On key format errors. * @throws IOException On key read errors. */ public static PublicKey readPemPublicKey(final File keyFile) throws CryptException, IOException { return readPemPublicKey(new BufferedInputStream(new FileInputStream(keyFile))); } /** * Reads a PEM-encoded public key from an input stream into a {@link * PublicKey} object. * * @param keyStream Input stream containing public key data in PEM format. * * @return Public key containing data read from stream. * * @throws CryptException On key format errors. * @throws IOException On key read errors. */ public static PublicKey readPemPublicKey(final InputStream keyStream) throws CryptException, IOException { return PemHelper.decodeKey(readPem(keyStream)); } /** * Reads a PEM or DER-encoded certificate of the default type from a file into * a {@link Certificate} object. * * @param certFile Path to certificate file. * * @return Certificate containing data read from file. * * @throws CryptException On certificate format errors. * @throws IOException On read errors. */ public static Certificate readCertificate(final File certFile) throws CryptException, IOException { return readCertificate(certFile, DEFAULT_CERTIFICATE_TYPE); } /** * Reads a PEM or DER-encoded certificate of the given type from a file into a * {@link Certificate} object. * * @param certFile Path to certificate file. * @param type Type of certificate to read, e.g. X.509. * * @return Certificate containing data read from file. * * @throws CryptException On certificate format errors. * @throws IOException On read errors. */ public static Certificate readCertificate(final File certFile, final String type) throws CryptException, IOException { return readCertificate(new BufferedInputStream(new FileInputStream(certFile))); } /** * Reads a PEM or DER-encoded certificate of the default type from an input * stream into a {@link Certificate} object. * * @param certStream Input stream with certificate data. * * @return Certificate created from data read from stream. * * @throws CryptException On certificate read or format errors. */ public static Certificate readCertificate(final InputStream certStream) throws CryptException { return readCertificate(certStream, DEFAULT_CERTIFICATE_TYPE); } /** * Reads a PEM or DER-encoded certificate of the default type from an input * stream into a {@link Certificate} object. * * @param certStream Input stream with certificate data. * @param type Type of certificate to read, e.g. X.509. * * @return Certificate created from data read from stream. * * @throws CryptException On certificate read or format errors. */ public static Certificate readCertificate(final InputStream certStream, final String type) throws CryptException { final CertificateFactory cf = CryptProvider.getCertificateFactory(type); try { return cf.generateCertificate(certStream); } catch (CertificateException e) { throw new CryptException("Certificate read/format error.", e); } } /** * Reads a certificate chain of the default certificate type from a file * containing data in any of the formats supported by {@link * #readCertificateChain(InputStream, String)}. * * @param chainFile Path to certificate chain file. * * @return Array of certificates in the order in which they appear in the * given file. * * @throws CryptException On certificate format errors. * @throws IOException On read errors. */ public static Certificate[] readCertificateChain(final File chainFile) throws CryptException, IOException { return readCertificateChain(chainFile, DEFAULT_CERTIFICATE_TYPE); } /** * Reads a certificate chain of the given type from a file containing data in * any of the formats supported by {@link #readCertificateChain(InputStream, * String)}. * * @param chainFile Path to certificate chain file. * @param type Type of certificate to read, e.g. X.509. * * @return Array of certificates in the order in which they appear in the * given file. * * @throws CryptException On certificate format errors. * @throws IOException On read errors. */ public static Certificate[] readCertificateChain(final File chainFile, final String type) throws CryptException, IOException { return readCertificateChain(new BufferedInputStream(new FileInputStream(chainFile))); } /** * Reads a certificate chain of the default certificate type from an input * stream containing data in any of the formats supported by {@link * #readCertificateChain(InputStream, String)}. * * @param chainStream Stream containing certificate chain data. * * @return Array of certificates in the order in which they appear in the * given input stream. * * @throws CryptException On certificate read or format errors. */ public static Certificate[] readCertificateChain(final InputStream chainStream) throws CryptException { return readCertificateChain(chainStream, DEFAULT_CERTIFICATE_TYPE); } /** * Reads a certificate chain of the default certificate type from an input * stream containing data in any of the following formats: * * <ul> * <li>Sequence of DER-encoded certificates</li> * <li>Concatenation of PEM-encoded certificates</li> * <li>PKCS#7 certificate chain</li> * </ul> * * @param chainStream Stream containing certificate chain data. * @param type Type of certificate to read, e.g. X.509. * * @return Array of certificates in the order in which they appear in the * stream. * * @throws CryptException On certificate read or format errors. */ public static Certificate[] readCertificateChain(final InputStream chainStream, final String type) throws CryptException { final CertificateFactory cf = CryptProvider.getCertificateFactory(type); InputStream in = chainStream; if (!chainStream.markSupported()) { in = new BufferedInputStream(chainStream); } final List<Certificate> certList = new ArrayList<Certificate>(); try { while (in.available() > 0) { final Certificate cert = cf.generateCertificate(in); if (cert != null) { certList.add(cert); } } } catch (CertificateException e) { throw new CryptException("Certificate read/format error.", e); } catch (IOException e) { throw new CryptException("Stream I/O error."); } return certList.toArray(new Certificate[certList.size()]); } /** * Attempts to create a Bouncy Castle <code>DERObject</code> from a byte array * representing ASN.1 encoded data. * * @param data ASN.1 encoded data as byte array. * @param discardWrapper In some cases the value of the encoded data may * itself be encoded data, where the latter encoded data is desired. Recall * ASN.1 data is of the form {TAG, SIZE, DATA}. Set this flag to true to skip * the first two bytes, e.g. TAG and SIZE, and treat the remaining bytes as * the encoded data. * * @return DER object. * * @throws IOException On I/O errors. */ public static DERObject readEncodedBytes(final byte[] data, final boolean discardWrapper) throws IOException { final ByteArrayInputStream inBytes = new ByteArrayInputStream(data); int size = data.length; if (discardWrapper) { inBytes.skip(2); size = data.length - 2; } final ASN1InputStream in = new ASN1InputStream(inBytes, size); try { return in.readObject(); } finally { try { in.close(); } catch (IOException e) { final Log logger = LogFactory.getLog(CryptReader.class); if (logger.isWarnEnabled()) { logger.warn("Error closing ASN.1 input stream.", e); } } } } /** * Reads a PEM object from an input stream into a string. * * @param in Input stream containing PEM-encoded data. * * @return Entire contents of stream as a string. * * @throws IOException On I/O read errors. */ private static String readPem(final InputStream in) throws IOException { return new String(readData(in), Convert.ASCII_CHARSET.name()); } /** * Reads all the data in the given stream and returns the contents as a byte * array. * * @param in Input stream to read. * * @return Entire contents of stream. * * @throws IOException On read errors. */ private static byte[] readData(final InputStream in) throws IOException { final byte[] buffer = new byte[BUFFER_SIZE]; final ByteArrayOutputStream bos = new ByteArrayOutputStream(BUFFER_SIZE); int count = 0; try { while ((count = in.read(buffer, 0, BUFFER_SIZE)) > 0) { bos.write(buffer, 0, count); } } finally { try { in.close(); } catch (IOException e) { final Log logger = LogFactory.getLog(CryptProvider.class); if (logger.isWarnEnabled()) { logger.warn("Error closing input stream.", e); } } } return bos.toByteArray(); } }