blob: 92bf8f9d4a544cad580c71ce6de50860bf5642ee [file] [log] [blame]
package org.bouncycastle.openssl;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.Reader;
import java.security.AlgorithmParameters;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PublicKey;
import java.security.cert.CertificateFactory;
import java.security.spec.DSAPrivateKeySpec;
import java.security.spec.DSAPublicKeySpec;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.RSAPrivateCrtKeySpec;
import java.security.spec.RSAPublicKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.PBEParameterSpec;
import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1Object;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.DERInteger;
import org.bouncycastle.asn1.DERObjectIdentifier;
import org.bouncycastle.asn1.cms.ContentInfo;
import org.bouncycastle.asn1.pkcs.EncryptedPrivateKeyInfo;
import org.bouncycastle.asn1.pkcs.EncryptionScheme;
import org.bouncycastle.asn1.pkcs.KeyDerivationFunc;
import org.bouncycastle.asn1.pkcs.PBEParameter;
import org.bouncycastle.asn1.pkcs.PBES2Parameters;
import org.bouncycastle.asn1.pkcs.PBKDF2Params;
import org.bouncycastle.asn1.pkcs.PKCS12PBEParams;
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
import org.bouncycastle.asn1.sec.ECPrivateKeyStructure;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.asn1.x509.RSAPublicKeyStructure;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
import org.bouncycastle.jce.ECNamedCurveTable;
import org.bouncycastle.jce.PKCS10CertificationRequest;
import org.bouncycastle.util.encoders.Hex;
import org.bouncycastle.util.io.pem.PemHeader;
import org.bouncycastle.util.io.pem.PemObject;
import org.bouncycastle.util.io.pem.PemObjectParser;
import org.bouncycastle.util.io.pem.PemReader;
import org.bouncycastle.x509.X509V2AttributeCertificate;
/**
* Class for reading OpenSSL PEM encoded streams containing
* X509 certificates, PKCS8 encoded keys and PKCS7 objects.
* <p>
* In the case of PKCS7 objects the reader will return a CMS ContentInfo object. Keys and
* Certificates will be returned using the appropriate java.security type (KeyPair, PublicKey, X509Certificate,
* or X509CRL). In the case of a Certificate Request a PKCS10CertificationRequest will be returned.
* </p>
*/
public class PEMReader
extends PemReader
{
private final Map parsers = new HashMap();
private PasswordFinder pFinder;
/**
* Create a new PEMReader
*
* @param reader the Reader
*/
public PEMReader(
Reader reader)
{
this(reader, null, "BC");
}
/**
* Create a new PEMReader with a password finder
*
* @param reader the Reader
* @param pFinder the password finder
*/
public PEMReader(
Reader reader,
PasswordFinder pFinder)
{
this(reader, pFinder, "BC");
}
/**
* Create a new PEMReader with a password finder
*
* @param reader the Reader
* @param pFinder the password finder
* @param provider the cryptography provider to use
*/
public PEMReader(
Reader reader,
PasswordFinder pFinder,
String provider)
{
this(reader, pFinder, provider, provider);
}
/**
* Create a new PEMReader with a password finder and differing providers for secret and public key
* operations.
*
* @param reader the Reader
* @param pFinder the password finder
* @param symProvider provider to use for symmetric operations
* @param asymProvider provider to use for asymmetric (public/private key) operations
*/
public PEMReader(
Reader reader,
PasswordFinder pFinder,
String symProvider,
String asymProvider)
{
super(reader);
this.pFinder = pFinder;
parsers.put("CERTIFICATE REQUEST", new PKCS10CertificationRequestParser());
parsers.put("NEW CERTIFICATE REQUEST", new PKCS10CertificationRequestParser());
parsers.put("CERTIFICATE", new X509CertificateParser(asymProvider));
parsers.put("X509 CERTIFICATE", new X509CertificateParser(asymProvider));
parsers.put("X509 CRL", new X509CRLParser(asymProvider));
parsers.put("PKCS7", new PKCS7Parser());
parsers.put("ATTRIBUTE CERTIFICATE", new X509AttributeCertificateParser());
parsers.put("EC PARAMETERS", new ECNamedCurveSpecParser());
parsers.put("PUBLIC KEY", new PublicKeyParser(asymProvider));
parsers.put("RSA PUBLIC KEY", new RSAPublicKeyParser(asymProvider));
parsers.put("RSA PRIVATE KEY", new RSAKeyPairParser(asymProvider));
parsers.put("DSA PRIVATE KEY", new DSAKeyPairParser(asymProvider));
parsers.put("EC PRIVATE KEY", new ECDSAKeyPairParser(asymProvider));
parsers.put("ENCRYPTED PRIVATE KEY", new EncryptedPrivateKeyParser(symProvider, asymProvider));
parsers.put("PRIVATE KEY", new PrivateKeyParser(asymProvider));
}
public Object readObject()
throws IOException
{
PemObject obj = readPemObject();
if (obj != null)
{
String type = obj.getType();
if (parsers.containsKey(type))
{
return ((PemObjectParser)parsers.get(type)).parseObject(obj);
}
else
{
throw new IOException("unrecognised object: " + type);
}
}
return null;
}
private abstract class KeyPairParser
implements PemObjectParser
{
protected String provider;
public KeyPairParser(String provider)
{
this.provider = provider;
}
/**
* Read a Key Pair
*/
protected ASN1Sequence readKeyPair(
PemObject obj)
throws IOException
{
boolean isEncrypted = false;
String dekInfo = null;
List headers = obj.getHeaders();
for (Iterator it = headers.iterator(); it.hasNext();)
{
PemHeader hdr = (PemHeader)it.next();
if (hdr.getName().equals("Proc-Type") && hdr.getValue().equals("4,ENCRYPTED"))
{
isEncrypted = true;
}
else if (hdr.getName().equals("DEK-Info"))
{
dekInfo = hdr.getValue();
}
}
//
// extract the key
//
byte[] keyBytes = obj.getContent();
if (isEncrypted)
{
if (pFinder == null)
{
throw new PasswordException("No password finder specified, but a password is required");
}
char[] password = pFinder.getPassword();
if (password == null)
{
throw new PasswordException("Password is null, but a password is required");
}
StringTokenizer tknz = new StringTokenizer(dekInfo, ",");
String dekAlgName = tknz.nextToken();
byte[] iv = Hex.decode(tknz.nextToken());
keyBytes = PEMUtilities.crypt(false, provider, keyBytes, password, dekAlgName, iv);
}
try
{
return (ASN1Sequence)ASN1Object.fromByteArray(keyBytes);
}
catch (IOException e)
{
if (isEncrypted)
{
throw new PEMException("exception decoding - please check password and data.", e);
}
else
{
throw new PEMException(e.getMessage(), e);
}
}
catch (ClassCastException e)
{
if (isEncrypted)
{
throw new PEMException("exception decoding - please check password and data.", e);
}
else
{
throw new PEMException(e.getMessage(), e);
}
}
}
}
private class DSAKeyPairParser
extends KeyPairParser
{
public DSAKeyPairParser(String provider)
{
super(provider);
}
public Object parseObject(PemObject obj)
throws IOException
{
try
{
ASN1Sequence seq = readKeyPair(obj);
if (seq.size() != 6)
{
throw new PEMException("malformed sequence in DSA private key");
}
// DERInteger v = (DERInteger)seq.getObjectAt(0);
DERInteger p = (DERInteger)seq.getObjectAt(1);
DERInteger q = (DERInteger)seq.getObjectAt(2);
DERInteger g = (DERInteger)seq.getObjectAt(3);
DERInteger y = (DERInteger)seq.getObjectAt(4);
DERInteger x = (DERInteger)seq.getObjectAt(5);
DSAPrivateKeySpec privSpec = new DSAPrivateKeySpec(
x.getValue(), p.getValue(),
q.getValue(), g.getValue());
DSAPublicKeySpec pubSpec = new DSAPublicKeySpec(
y.getValue(), p.getValue(),
q.getValue(), g.getValue());
KeyFactory fact = KeyFactory.getInstance("DSA", provider);
return new KeyPair(
fact.generatePublic(pubSpec),
fact.generatePrivate(privSpec));
}
catch (IOException e)
{
throw e;
}
catch (Exception e)
{
throw new PEMException(
"problem creating DSA private key: " + e.toString(), e);
}
}
}
private class ECDSAKeyPairParser
extends KeyPairParser
{
public ECDSAKeyPairParser(String provider)
{
super(provider);
}
public Object parseObject(PemObject obj)
throws IOException
{
try
{
ASN1Sequence seq = readKeyPair(obj);
ECPrivateKeyStructure pKey = new ECPrivateKeyStructure(seq);
AlgorithmIdentifier algId = new AlgorithmIdentifier(X9ObjectIdentifiers.id_ecPublicKey, pKey.getParameters());
PrivateKeyInfo privInfo = new PrivateKeyInfo(algId, pKey.getDERObject());
SubjectPublicKeyInfo pubInfo = new SubjectPublicKeyInfo(algId, pKey.getPublicKey().getBytes());
PKCS8EncodedKeySpec privSpec = new PKCS8EncodedKeySpec(privInfo.getEncoded());
X509EncodedKeySpec pubSpec = new X509EncodedKeySpec(pubInfo.getEncoded());
KeyFactory fact = KeyFactory.getInstance("ECDSA", provider);
return new KeyPair(
fact.generatePublic(pubSpec),
fact.generatePrivate(privSpec));
}
catch (IOException e)
{
throw e;
}
catch (Exception e)
{
throw new PEMException(
"problem creating EC private key: " + e.toString(), e);
}
}
}
private class RSAKeyPairParser
extends KeyPairParser
{
public RSAKeyPairParser(String provider)
{
super(provider);
}
public Object parseObject(PemObject obj)
throws IOException
{
try
{
ASN1Sequence seq = readKeyPair(obj);
if (seq.size() != 9)
{
throw new PEMException("malformed sequence in RSA private key");
}
// DERInteger v = (DERInteger)seq.getObjectAt(0);
DERInteger mod = (DERInteger)seq.getObjectAt(1);
DERInteger pubExp = (DERInteger)seq.getObjectAt(2);
DERInteger privExp = (DERInteger)seq.getObjectAt(3);
DERInteger p1 = (DERInteger)seq.getObjectAt(4);
DERInteger p2 = (DERInteger)seq.getObjectAt(5);
DERInteger exp1 = (DERInteger)seq.getObjectAt(6);
DERInteger exp2 = (DERInteger)seq.getObjectAt(7);
DERInteger crtCoef = (DERInteger)seq.getObjectAt(8);
RSAPublicKeySpec pubSpec = new RSAPublicKeySpec(
mod.getValue(), pubExp.getValue());
RSAPrivateCrtKeySpec privSpec = new RSAPrivateCrtKeySpec(
mod.getValue(), pubExp.getValue(), privExp.getValue(),
p1.getValue(), p2.getValue(),
exp1.getValue(), exp2.getValue(),
crtCoef.getValue());
KeyFactory fact = KeyFactory.getInstance("RSA", provider);
return new KeyPair(
fact.generatePublic(pubSpec),
fact.generatePrivate(privSpec));
}
catch (IOException e)
{
throw e;
}
catch (Exception e)
{
throw new PEMException(
"problem creating RSA private key: " + e.toString(), e);
}
}
}
private class PublicKeyParser
implements PemObjectParser
{
private String provider;
public PublicKeyParser(String provider)
{
this.provider = provider;
}
public Object parseObject(PemObject obj)
throws IOException
{
KeySpec keySpec = new X509EncodedKeySpec(obj.getContent());
String[] algorithms = {"DSA", "RSA"};
for (int i = 0; i < algorithms.length; i++)
{
try
{
KeyFactory keyFact = KeyFactory.getInstance(algorithms[i], provider);
PublicKey pubKey = keyFact.generatePublic(keySpec);
return pubKey;
}
catch (NoSuchAlgorithmException e)
{
// ignore
}
catch (InvalidKeySpecException e)
{
// ignore
}
catch (NoSuchProviderException e)
{
throw new RuntimeException("can't find provider " + provider);
}
}
return null;
}
}
private class RSAPublicKeyParser
implements PemObjectParser
{
private String provider;
public RSAPublicKeyParser(String provider)
{
this.provider = provider;
}
public Object parseObject(PemObject obj)
throws IOException
{
try
{
ASN1InputStream ais = new ASN1InputStream(obj.getContent());
Object asnObject = ais.readObject();
ASN1Sequence sequence = (ASN1Sequence)asnObject;
RSAPublicKeyStructure rsaPubStructure = new RSAPublicKeyStructure(sequence);
RSAPublicKeySpec keySpec = new RSAPublicKeySpec(
rsaPubStructure.getModulus(),
rsaPubStructure.getPublicExponent());
KeyFactory keyFact = KeyFactory.getInstance("RSA", provider);
return keyFact.generatePublic(keySpec);
}
catch (IOException e)
{
throw e;
}
catch (NoSuchProviderException e)
{
throw new IOException("can't find provider " + provider);
}
catch (Exception e)
{
throw new PEMException("problem extracting key: " + e.toString(), e);
}
}
}
private class X509CertificateParser
implements PemObjectParser
{
private String provider;
public X509CertificateParser(String provider)
{
this.provider = provider;
}
/**
* Reads in a X509Certificate.
*
* @return the X509Certificate
* @throws IOException if an I/O error occured
*/
public Object parseObject(PemObject obj)
throws IOException
{
ByteArrayInputStream bIn = new ByteArrayInputStream(obj.getContent());
try
{
CertificateFactory certFact
= CertificateFactory.getInstance("X.509", provider);
return certFact.generateCertificate(bIn);
}
catch (Exception e)
{
throw new PEMException("problem parsing cert: " + e.toString(), e);
}
}
}
private class X509CRLParser
implements PemObjectParser
{
private String provider;
public X509CRLParser(String provider)
{
this.provider = provider;
}
/**
* Reads in a X509CRL.
*
* @return the X509Certificate
* @throws IOException if an I/O error occured
*/
public Object parseObject(PemObject obj)
throws IOException
{
ByteArrayInputStream bIn = new ByteArrayInputStream(obj.getContent());
try
{
CertificateFactory certFact
= CertificateFactory.getInstance("X.509", provider);
return certFact.generateCRL(bIn);
}
catch (Exception e)
{
throw new PEMException("problem parsing cert: " + e.toString(), e);
}
}
}
private class PKCS10CertificationRequestParser
implements PemObjectParser
{
/**
* Reads in a PKCS10 certification request.
*
* @return the certificate request.
* @throws IOException if an I/O error occured
*/
public Object parseObject(PemObject obj)
throws IOException
{
try
{
return new PKCS10CertificationRequest(obj.getContent());
}
catch (Exception e)
{
throw new PEMException("problem parsing certrequest: " + e.toString(), e);
}
}
}
private class PKCS7Parser
implements PemObjectParser
{
/**
* Reads in a PKCS7 object. This returns a ContentInfo object suitable for use with the CMS
* API.
*
* @return the X509Certificate
* @throws IOException if an I/O error occured
*/
public Object parseObject(PemObject obj)
throws IOException
{
try
{
ASN1InputStream aIn = new ASN1InputStream(obj.getContent());
return ContentInfo.getInstance(aIn.readObject());
}
catch (Exception e)
{
throw new PEMException("problem parsing PKCS7 object: " + e.toString(), e);
}
}
}
private class X509AttributeCertificateParser
implements PemObjectParser
{
public Object parseObject(PemObject obj)
throws IOException
{
return new X509V2AttributeCertificate(obj.getContent());
}
}
private class ECNamedCurveSpecParser
implements PemObjectParser
{
public Object parseObject(PemObject obj)
throws IOException
{
try
{
DERObjectIdentifier oid = (DERObjectIdentifier)ASN1Object.fromByteArray(obj.getContent());
Object params = ECNamedCurveTable.getParameterSpec(oid.getId());
if (params == null)
{
throw new IOException("object ID not found in EC curve table");
}
return params;
}
catch (IOException e)
{
throw e;
}
catch (Exception e)
{
throw new PEMException("exception extracting EC named curve: " + e.toString());
}
}
}
private class EncryptedPrivateKeyParser
implements PemObjectParser
{
private String symProvider;
private String asymProvider;
public EncryptedPrivateKeyParser(String symProvider, String asymProvider)
{
this.symProvider = symProvider;
this.asymProvider = asymProvider;
}
/**
* Reads in a X509CRL.
*
* @return the X509Certificate
* @throws IOException if an I/O error occured
*/
public Object parseObject(PemObject obj)
throws IOException
{
try
{
EncryptedPrivateKeyInfo info = EncryptedPrivateKeyInfo.getInstance(ASN1Object.fromByteArray(obj.getContent()));
AlgorithmIdentifier algId = info.getEncryptionAlgorithm();
if (pFinder == null)
{
throw new PEMException("no PasswordFinder specified");
}
if (PEMUtilities.isPKCS5Scheme2(algId.getAlgorithm()))
{
PBES2Parameters params = PBES2Parameters.getInstance(algId.getParameters());
KeyDerivationFunc func = params.getKeyDerivationFunc();
EncryptionScheme scheme = params.getEncryptionScheme();
PBKDF2Params defParams = (PBKDF2Params)func.getParameters();
int iterationCount = defParams.getIterationCount().intValue();
byte[] salt = defParams.getSalt();
String algorithm = scheme.getAlgorithm().getId();
SecretKey key = PEMUtilities.generateSecretKeyForPKCS5Scheme2(algorithm, pFinder.getPassword(), salt, iterationCount);
Cipher cipher = Cipher.getInstance(algorithm, symProvider);
AlgorithmParameters algParams = AlgorithmParameters.getInstance(algorithm, symProvider);
algParams.init(scheme.getParameters().getDERObject().getEncoded());
cipher.init(Cipher.DECRYPT_MODE, key, algParams);
PrivateKeyInfo pInfo = PrivateKeyInfo.getInstance(ASN1Object.fromByteArray(cipher.doFinal(info.getEncryptedData())));
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(pInfo.getEncoded());
KeyFactory keyFact = KeyFactory.getInstance(pInfo.getAlgorithmId().getAlgorithm().getId(), asymProvider);
return keyFact.generatePrivate(keySpec);
}
else if (PEMUtilities.isPKCS12(algId.getAlgorithm()))
{
PKCS12PBEParams params = PKCS12PBEParams.getInstance(algId.getParameters());
String algorithm = algId.getAlgorithm().getId();
PBEKeySpec pbeSpec = new PBEKeySpec(pFinder.getPassword());
SecretKeyFactory secKeyFact = SecretKeyFactory.getInstance(algorithm, symProvider);
PBEParameterSpec defParams = new PBEParameterSpec(params.getIV(), params.getIterations().intValue());
Cipher cipher = Cipher.getInstance(algorithm, symProvider);
cipher.init(Cipher.DECRYPT_MODE, secKeyFact.generateSecret(pbeSpec), defParams);
PrivateKeyInfo pInfo = PrivateKeyInfo.getInstance(ASN1Object.fromByteArray(cipher.doFinal(info.getEncryptedData())));
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(pInfo.getEncoded());
KeyFactory keyFact = KeyFactory.getInstance(pInfo.getAlgorithmId().getAlgorithm().getId(), asymProvider);
return keyFact.generatePrivate(keySpec);
}
else if (PEMUtilities.isPKCS5Scheme1(algId.getAlgorithm()))
{
PBEParameter params = PBEParameter.getInstance(algId.getParameters());
String algorithm = algId.getAlgorithm().getId();
PBEKeySpec pbeSpec = new PBEKeySpec(pFinder.getPassword());
SecretKeyFactory secKeyFact = SecretKeyFactory.getInstance(algorithm, symProvider);
PBEParameterSpec defParams = new PBEParameterSpec(params.getSalt(), params.getIterationCount().intValue());
Cipher cipher = Cipher.getInstance(algorithm, symProvider);
cipher.init(Cipher.DECRYPT_MODE, secKeyFact.generateSecret(pbeSpec), defParams);
PrivateKeyInfo pInfo = PrivateKeyInfo.getInstance(ASN1Object.fromByteArray(cipher.doFinal(info.getEncryptedData())));
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(pInfo.getEncoded());
KeyFactory keyFact = KeyFactory.getInstance(pInfo.getAlgorithmId().getAlgorithm().getId(), asymProvider);
return keyFact.generatePrivate(keySpec);
}
else
{
throw new PEMException("Unknown algorithm: " + algId.getAlgorithm());
}
}
catch (IOException e)
{
throw e;
}
catch (Exception e)
{
throw new PEMException("problem parsing ENCRYPTED PRIVATE KEY: " + e.toString(), e);
}
}
}
private class PrivateKeyParser
implements PemObjectParser
{
private String provider;
public PrivateKeyParser(String provider)
{
this.provider = provider;
}
public Object parseObject(PemObject obj)
throws IOException
{
try
{
PrivateKeyInfo info = PrivateKeyInfo.getInstance(ASN1Object.fromByteArray(obj.getContent()));
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(obj.getContent());
KeyFactory keyFact = KeyFactory.getInstance(info.getAlgorithmId().getAlgorithm().getId(), provider);
return keyFact.generatePrivate(keySpec);
}
catch (Exception e)
{
throw new PEMException("problem parsing PRIVATE KEY: " + e.toString(), e);
}
}
}
}