summary refs log tree commit diff
path: root/Crypto/src/openssl/PEMReader.cs
diff options
context:
space:
mode:
Diffstat (limited to 'Crypto/src/openssl/PEMReader.cs')
-rw-r--r--Crypto/src/openssl/PEMReader.cs407
1 files changed, 407 insertions, 0 deletions
diff --git a/Crypto/src/openssl/PEMReader.cs b/Crypto/src/openssl/PEMReader.cs
new file mode 100644
index 000000000..a2fedab96
--- /dev/null
+++ b/Crypto/src/openssl/PEMReader.cs
@@ -0,0 +1,407 @@
+using System;
+using System.Collections;
+using System.Diagnostics;
+using System.IO;
+using System.Text;
+
+using Org.BouncyCastle.Asn1;
+using Org.BouncyCastle.Asn1.Nist;
+using Org.BouncyCastle.Asn1.Pkcs;
+using Org.BouncyCastle.Asn1.Sec;
+using Org.BouncyCastle.Asn1.TeleTrust;
+using Org.BouncyCastle.Asn1.X509;
+using Org.BouncyCastle.Asn1.X9;
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Generators;
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Pkcs;
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.Utilities.Encoders;
+using Org.BouncyCastle.Utilities.IO.Pem;
+using Org.BouncyCastle.X509;
+
+namespace Org.BouncyCastle.OpenSsl
+{
+	/**
+	* 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.</p>
+	*/
+	public class PemReader
+		: Org.BouncyCastle.Utilities.IO.Pem.PemReader
+	{
+//		private static readonly IDictionary parsers = new Hashtable();
+
+		static PemReader()
+		{
+//			parsers.Add("CERTIFICATE REQUEST", new PKCS10CertificationRequestParser());
+//			parsers.Add("NEW CERTIFICATE REQUEST", new PKCS10CertificationRequestParser());
+//			parsers.Add("CERTIFICATE", new X509CertificateParser(provider));
+//			parsers.Add("X509 CERTIFICATE", new X509CertificateParser(provider));
+//			parsers.Add("X509 CRL", new X509CRLParser(provider));
+//			parsers.Add("PKCS7", new PKCS7Parser());
+//			parsers.Add("ATTRIBUTE CERTIFICATE", new X509AttributeCertificateParser());
+//			parsers.Add("EC PARAMETERS", new ECNamedCurveSpecParser());
+//			parsers.Add("PUBLIC KEY", new PublicKeyParser(provider));
+//			parsers.Add("RSA PUBLIC KEY", new RSAPublicKeyParser(provider));
+//			parsers.Add("RSA PRIVATE KEY", new RSAKeyPairParser(provider));
+//			parsers.Add("DSA PRIVATE KEY", new DSAKeyPairParser(provider));
+//			parsers.Add("EC PRIVATE KEY", new ECDSAKeyPairParser(provider));
+//			parsers.Add("ENCRYPTED PRIVATE KEY", new EncryptedPrivateKeyParser(provider));
+//			parsers.Add("PRIVATE KEY", new PrivateKeyParser(provider));
+		}
+
+		private readonly IPasswordFinder pFinder;
+
+		/**
+		* Create a new PemReader
+		*
+		* @param reader the Reader
+		*/
+		public PemReader(
+			TextReader reader)
+			: this(reader, null)
+		{
+		}
+
+		/**
+		* Create a new PemReader with a password finder
+		*
+		* @param reader the Reader
+		* @param pFinder the password finder
+		*/
+		public PemReader(
+			TextReader		reader,
+			IPasswordFinder	pFinder)
+			: base(reader)
+		{
+			this.pFinder = pFinder;
+		}
+
+		public object ReadObject()
+		{
+			PemObject obj = ReadPemObject();
+
+			if (obj == null)
+				return null;
+
+			// TODO Follow Java build and map to parser objects?
+//			if (parsers.Contains(obj.Type))
+//				return ((PemObjectParser)parsers[obj.Type]).ParseObject(obj);
+
+			if (obj.Type.EndsWith("PRIVATE KEY"))
+				return ReadPrivateKey(obj);
+
+			switch (obj.Type)
+			{
+				case "PUBLIC KEY":
+					return ReadPublicKey(obj);
+				case "RSA PUBLIC KEY":
+					return ReadRsaPublicKey(obj);
+				case "CERTIFICATE REQUEST":
+				case "NEW CERTIFICATE REQUEST":
+					return ReadCertificateRequest(obj);
+				case "CERTIFICATE":
+				case "X509 CERTIFICATE":
+					return ReadCertificate(obj);
+				case "PKCS7":
+					return ReadPkcs7(obj);
+				case "X509 CRL":
+					return ReadCrl(obj);
+				case "ATTRIBUTE CERTIFICATE":
+					return ReadAttributeCertificate(obj);
+				// TODO Add back in when tests done, and return type issue resolved
+				//case "EC PARAMETERS":
+				//	return ReadECParameters(obj);
+				default:
+					throw new IOException("unrecognised object: " + obj.Type);
+			}
+		}
+
+		private AsymmetricKeyParameter ReadRsaPublicKey(PemObject pemObject)
+		{
+			RsaPublicKeyStructure rsaPubStructure = RsaPublicKeyStructure.GetInstance(
+				Asn1Object.FromByteArray(pemObject.Content));
+
+			return new RsaKeyParameters(
+				false, // not private
+				rsaPubStructure.Modulus, 
+				rsaPubStructure.PublicExponent);
+		}
+
+		private AsymmetricKeyParameter ReadPublicKey(PemObject pemObject)
+		{
+			return PublicKeyFactory.CreateKey(pemObject.Content);
+		}
+
+		/**
+		* Reads in a X509Certificate.
+		*
+		* @return the X509Certificate
+		* @throws IOException if an I/O error occured
+		*/
+		private X509Certificate ReadCertificate(PemObject pemObject)
+		{
+			try
+			{
+				return new X509CertificateParser().ReadCertificate(pemObject.Content);
+			}
+			catch (Exception e)
+			{
+				throw new PemException("problem parsing cert: " + e.ToString());
+			}
+		}
+
+		/**
+		* Reads in a X509CRL.
+		*
+		* @return the X509Certificate
+		* @throws IOException if an I/O error occured
+		*/
+		private X509Crl ReadCrl(PemObject pemObject)
+		{
+			try
+			{
+				return new X509CrlParser().ReadCrl(pemObject.Content);
+			}
+			catch (Exception e)
+			{
+				throw new PemException("problem parsing cert: " + e.ToString());
+			}
+		}
+
+		/**
+		* Reads in a PKCS10 certification request.
+		*
+		* @return the certificate request.
+		* @throws IOException if an I/O error occured
+		*/
+		private Pkcs10CertificationRequest ReadCertificateRequest(PemObject pemObject)
+		{
+			try
+			{
+				return new Pkcs10CertificationRequest(pemObject.Content);
+			}
+			catch (Exception e)
+			{
+				throw new PemException("problem parsing cert: " + e.ToString());
+			}
+		}
+
+		/**
+		* Reads in a X509 Attribute Certificate.
+		*
+		* @return the X509 Attribute Certificate
+		* @throws IOException if an I/O error occured
+		*/
+		private IX509AttributeCertificate ReadAttributeCertificate(PemObject pemObject)
+		{
+			return new X509V2AttributeCertificate(pemObject.Content);
+		}
+
+		/**
+		* 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
+		*/
+		// TODO Consider returning Asn1.Pkcs.ContentInfo
+		private Asn1.Cms.ContentInfo ReadPkcs7(PemObject pemObject)
+		{
+			try
+			{
+				return Asn1.Cms.ContentInfo.GetInstance(
+					Asn1Object.FromByteArray(pemObject.Content));
+			}
+			catch (Exception e)
+			{
+				throw new PemException("problem parsing PKCS7 object: " + e.ToString());
+			}
+		}
+
+		/**
+		* Read a Key Pair
+		*/
+		private object ReadPrivateKey(PemObject pemObject)
+		{
+			//
+			// extract the key
+			//
+			Debug.Assert(pemObject.Type.EndsWith("PRIVATE KEY"));
+
+			string type = pemObject.Type.Substring(0, pemObject.Type.Length - "PRIVATE KEY".Length).Trim();
+			byte[] keyBytes = pemObject.Content;
+
+			IDictionary fields = Platform.CreateHashtable();
+			foreach (PemHeader header in pemObject.Headers)
+			{
+				fields[header.Name] = header.Value;
+			}
+
+			string procType = (string) fields["Proc-Type"];
+
+			if (procType == "4,ENCRYPTED")
+			{
+				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");
+
+				string dekInfo = (string) fields["DEK-Info"];
+				string[] tknz = dekInfo.Split(',');
+
+				string dekAlgName = tknz[0].Trim();
+				byte[] iv = Hex.Decode(tknz[1].Trim());
+
+				keyBytes = PemUtilities.Crypt(false, keyBytes, password, dekAlgName, iv);
+			}
+
+			try
+			{
+				AsymmetricKeyParameter pubSpec, privSpec;
+				Asn1Sequence seq = (Asn1Sequence) Asn1Object.FromByteArray(keyBytes);
+
+				switch (type)
+				{
+					case "RSA":
+					{
+						if (seq.Count != 9)
+							throw new PemException("malformed sequence in RSA private key");
+
+						RsaPrivateKeyStructure rsa = new RsaPrivateKeyStructure(seq);
+
+						pubSpec = new RsaKeyParameters(false, rsa.Modulus, rsa.PublicExponent);
+						privSpec = new RsaPrivateCrtKeyParameters(
+							rsa.Modulus, rsa.PublicExponent, rsa.PrivateExponent,
+							rsa.Prime1, rsa.Prime2, rsa.Exponent1, rsa.Exponent2,
+							rsa.Coefficient);
+
+						break;
+					}
+
+					case "DSA":
+					{
+						if (seq.Count != 6)
+							throw new PemException("malformed sequence in DSA private key");
+
+						// TODO Create an ASN1 object somewhere for this?
+						//DerInteger v = (DerInteger)seq[0];
+						DerInteger p = (DerInteger)seq[1];
+						DerInteger q = (DerInteger)seq[2];
+						DerInteger g = (DerInteger)seq[3];
+						DerInteger y = (DerInteger)seq[4];
+						DerInteger x = (DerInteger)seq[5];
+
+						DsaParameters parameters = new DsaParameters(p.Value, q.Value, g.Value);
+
+						privSpec = new DsaPrivateKeyParameters(x.Value, parameters);
+						pubSpec = new DsaPublicKeyParameters(y.Value, parameters);
+
+						break;
+					}
+
+					case "EC":
+					{
+						ECPrivateKeyStructure pKey = new ECPrivateKeyStructure(seq);
+						AlgorithmIdentifier algId = new AlgorithmIdentifier(
+							X9ObjectIdentifiers.IdECPublicKey, pKey.GetParameters());
+
+						PrivateKeyInfo privInfo = new PrivateKeyInfo(algId, pKey.ToAsn1Object());
+
+						// TODO Are the keys returned here ECDSA, as Java version forces?
+						privSpec = PrivateKeyFactory.CreateKey(privInfo);
+
+						DerBitString pubKey = pKey.GetPublicKey();
+						if (pubKey != null)
+						{
+							SubjectPublicKeyInfo pubInfo = new SubjectPublicKeyInfo(algId, pubKey.GetBytes());
+
+							// TODO Are the keys returned here ECDSA, as Java version forces?
+							pubSpec = PublicKeyFactory.CreateKey(pubInfo);
+						}
+						else
+						{
+							pubSpec = ECKeyPairGenerator.GetCorrespondingPublicKey(
+								(ECPrivateKeyParameters)privSpec);
+						}
+
+						break;
+					}
+
+					case "ENCRYPTED":
+					{
+						char[] password = pFinder.GetPassword();
+
+						if (password == null)
+							throw new PasswordException("Password is null, but a password is required");
+
+						return PrivateKeyFactory.DecryptKey(password, EncryptedPrivateKeyInfo.GetInstance(seq));
+					}
+
+					case "":
+					{
+						return PrivateKeyFactory.CreateKey(PrivateKeyInfo.GetInstance(seq));
+					}
+
+					default:
+						throw new ArgumentException("Unknown key type: " + type, "type");
+				}
+
+				return new AsymmetricCipherKeyPair(pubSpec, privSpec);
+			}
+			catch (IOException e)
+			{
+				throw e;
+			}
+			catch (Exception e)
+			{
+				throw new PemException(
+					"problem creating " + type + " private key: " + e.ToString());
+			}
+		}
+
+		// TODO Add an equivalent class for ECNamedCurveParameterSpec?
+		//private ECNamedCurveParameterSpec ReadECParameters(
+//		private X9ECParameters ReadECParameters(PemObject pemObject)
+//		{
+//			DerObjectIdentifier oid = (DerObjectIdentifier)Asn1Object.FromByteArray(pemObject.Content);
+//
+//			//return ECNamedCurveTable.getParameterSpec(oid.Id);
+//			return GetCurveParameters(oid.Id);
+//		}
+
+		//private static ECDomainParameters GetCurveParameters(
+		private static X9ECParameters GetCurveParameters(
+			string name)
+		{
+			// TODO ECGost3410NamedCurves support (returns ECDomainParameters though)
+			X9ECParameters ecP = X962NamedCurves.GetByName(name);
+
+			if (ecP == null)
+			{
+				ecP = SecNamedCurves.GetByName(name);
+				if (ecP == null)
+				{
+					ecP = NistNamedCurves.GetByName(name);
+					if (ecP == null)
+					{
+						ecP = TeleTrusTNamedCurves.GetByName(name);
+
+						if (ecP == null)
+							throw new Exception("unknown curve name: " + name);
+					}
+				}
+			}
+
+			//return new ECDomainParameters(ecP.Curve, ecP.G, ecP.N, ecP.H, ecP.GetSeed());
+			return ecP;
+		}
+	}
+}