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

import org.apache.commons.logging.LogFactory;

import org.apache.commons.logging.Log;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.Security;
import java.security.interfaces.RSAPrivateCrtKey;
import java.security.spec.PKCS8EncodedKeySpec;
import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1Primitive;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.DERObjectIdentifier;
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
import org.bouncycastle.asn1.pkcs.RSAPrivateKeyStructure;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.globus.gsi.OpenSSLKey;

/**
 * BouncyCastle-based implementation of OpenSSLKey.
 *
 * @version ${version}
 * @since 1.0
 */
public class BouncyCastleOpenSSLKey extends OpenSSLKey {
	private static final long serialVersionUID = 1L;
	private Log logger = LogFactory.getLog(getClass().getCanonicalName());

	static {
		Security.addProvider(new BouncyCastleProvider());
	}

	/**
	 * Reads a OpenSSL private key from the specified input stream. The private
	 * key must be PEM encoded and can be encrypted.
	 *
	 * @param is
	 *            input stream with OpenSSL key in PEM format.
	 * @throws IOException
	 *             if I/O problems.
	 * @throws GeneralSecurityException
	 *             if problems with the key
	 */
	public BouncyCastleOpenSSLKey(InputStream is) throws IOException,
			GeneralSecurityException {
		super(is);
	}

	/**
	 * Reads a OpenSSL private key from the specified file. The private key must
	 * be PEM encoded and can be encrypted.
	 *
	 * @param file
	 *            file containing the OpenSSL key in PEM format.
	 * @throws IOException
	 *             if I/O problems.
	 * @throws GeneralSecurityException
	 *             if problems with the key
	 */
	public BouncyCastleOpenSSLKey(String file) throws IOException,
			GeneralSecurityException {
		super(file);
	}

	/**
	 * Converts a RSAPrivateCrtKey into OpenSSL key.
	 *
	 * @param key
	 *            private key - must be a RSAPrivateCrtKey
	 */
	public BouncyCastleOpenSSLKey(PrivateKey key) {
		super(key);
	}

	/**
	 * Initializes the OpenSSL key from raw byte array.
	 *
	 * @param algorithm
	 *            the algorithm of the key. Currently only RSA algorithm is
	 *            supported.
	 * @param data
	 *            the DER encoded key data. If RSA algorithm, the key must be in
	 *            PKCS#1 format.
	 * @throws GeneralSecurityException
	 *             if any security problems.
	 */
	public BouncyCastleOpenSSLKey(String algorithm, byte[] data)
			throws GeneralSecurityException {
		super(algorithm, data);
	}

	protected PrivateKey getKey(String alg, byte[] data)
			throws GeneralSecurityException {
		if (alg.equals("RSA")) {
			try {
				if (data.length == 0) {
					throw new GeneralSecurityException(
							"Cannot process empty byte stream.");
				}
				ByteArrayInputStream bis = new ByteArrayInputStream(data);
				ASN1InputStream derin = new ASN1InputStream(bis);
				ASN1Primitive keyInfo = derin.readObject();

				DERObjectIdentifier rsaOid = PKCSObjectIdentifiers.rsaEncryption;
				AlgorithmIdentifier rsa = new AlgorithmIdentifier(rsaOid);
				PrivateKeyInfo pkeyinfo = new PrivateKeyInfo(rsa, keyInfo);
				ASN1Primitive derkey = pkeyinfo.toASN1Primitive();
				byte[] keyData = BouncyCastleUtil.toByteArray(derkey);
				// The DER object needs to be mangled to
				// create a proper ProvateKeyInfo object
				PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyData);
				KeyFactory kfac = KeyFactory.getInstance("RSA");

				return kfac.generatePrivate(spec);
			} catch (IOException e) {
				// that should never happen
				return null;
			}

		} else {
			return null;
		}
	}

	protected byte[] getEncoded(PrivateKey key) {
		String format = key.getFormat();
		if (format != null
				&& (format.equalsIgnoreCase("PKCS#8") || format
						.equalsIgnoreCase("PKCS8"))) {
			try {
				ASN1Primitive keyInfo = BouncyCastleUtil.toASN1Primitive(key
						.getEncoded());
				PrivateKeyInfo pkey = new PrivateKeyInfo((ASN1Sequence) keyInfo);
				ASN1Primitive derKey = pkey.getPrivateKey();
				return BouncyCastleUtil.toByteArray(derKey);
			} catch (IOException e) {
				// that should never happen
				logger.warn("This shouldn't have happened.", e);
				return new byte[] {};
			}
		} else if (format != null && format.equalsIgnoreCase("PKCS#1")
				&& key instanceof RSAPrivateCrtKey) {
			// this condition will rarely be true
			RSAPrivateCrtKey pKey = (RSAPrivateCrtKey) key;
			RSAPrivateKeyStructure st = new RSAPrivateKeyStructure(pKey
					.getModulus(), pKey.getPublicExponent(), pKey
					.getPrivateExponent(), pKey.getPrimeP(), pKey.getPrimeQ(),
					pKey.getPrimeExponentP(), pKey.getPrimeExponentQ(), pKey
							.getCrtCoefficient());
			ASN1Primitive ob = st.toASN1Primitive();

			try {
				return BouncyCastleUtil.toByteArray(ob);
			} catch (IOException e) {
				// that should never happen
				return new byte[0];
			}
		} else {
			return new byte[0];
		}
	}

	protected String getProvider() {
		return "BC";
	}
}
