edu.vt.middleware.crypt.util.CryptReader.java Source code

Java tutorial

Introduction

Here is the source code for edu.vt.middleware.crypt.util.CryptReader.java

Source

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