Initial Commit
This commit is contained in:
9
iTechSharp/srcbc/openssl/IPasswordFinder.cs
Normal file
9
iTechSharp/srcbc/openssl/IPasswordFinder.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using System;
|
||||
|
||||
namespace Org.BouncyCastle.OpenSsl
|
||||
{
|
||||
public interface IPasswordFinder
|
||||
{
|
||||
char[] GetPassword();
|
||||
}
|
||||
}
|
453
iTechSharp/srcbc/openssl/PEMReader.cs
Normal file
453
iTechSharp/srcbc/openssl/PEMReader.cs
Normal file
@@ -0,0 +1,453 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
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.Encoders;
|
||||
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
|
||||
{
|
||||
private readonly TextReader reader;
|
||||
private readonly IPasswordFinder pFinder;
|
||||
|
||||
public TextReader Reader
|
||||
{
|
||||
get { return reader; }
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)
|
||||
{
|
||||
if (reader == null)
|
||||
throw new ArgumentNullException("reader");
|
||||
|
||||
this.reader = reader;
|
||||
this.pFinder = pFinder;
|
||||
}
|
||||
|
||||
private const string BeginString = "-----BEGIN ";
|
||||
|
||||
public object ReadObject()
|
||||
{
|
||||
string line;
|
||||
while ((line = reader.ReadLine()) != null)
|
||||
{
|
||||
int startPos = line.IndexOf(BeginString);
|
||||
if (startPos == -1)
|
||||
continue;
|
||||
|
||||
startPos += BeginString.Length;
|
||||
|
||||
int endPos = line.IndexOf('-', startPos);
|
||||
if (endPos == -1)
|
||||
endPos = line.Length;
|
||||
|
||||
string headerName = line.Substring(startPos, endPos - startPos).Trim();
|
||||
//Console.WriteLine("[" + headerName + "]");
|
||||
|
||||
string endMarker = "-----END " + headerName;
|
||||
|
||||
switch (headerName)
|
||||
{
|
||||
case "PUBLIC KEY":
|
||||
return ReadPublicKey(endMarker);
|
||||
case "RSA PUBLIC KEY":
|
||||
return ReadRsaPublicKey(endMarker);
|
||||
case "CERTIFICATE REQUEST":
|
||||
case "NEW CERTIFICATE REQUEST":
|
||||
return ReadCertificateRequest(endMarker);
|
||||
case "CERTIFICATE":
|
||||
case "X509 CERTIFICATE":
|
||||
return ReadCertificate(endMarker);
|
||||
case "PKCS7":
|
||||
return ReadPkcs7(endMarker);
|
||||
case "X509 CRL":
|
||||
return ReadCrl(endMarker);
|
||||
case "ATTRIBUTE CERTIFICATE":
|
||||
return ReadAttributeCertificate(endMarker);
|
||||
case "RSA PRIVATE KEY":
|
||||
return ReadKeyPair("RSA", endMarker);
|
||||
case "DSA PRIVATE KEY":
|
||||
return ReadKeyPair("DSA", endMarker);
|
||||
// TODO Add back in when tests done, and return type issue resolved
|
||||
//case "EC PARAMETERS":
|
||||
// return ReadECParameters(endMarker);
|
||||
case "EC PRIVATE KEY":
|
||||
return ReadECPrivateKey(endMarker);
|
||||
default:
|
||||
// TODO Throw an exception for an unknown header?
|
||||
|
||||
// Ignore contents
|
||||
ReadBytes(endMarker);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private byte[] ReadBytes(
|
||||
string endMarker)
|
||||
{
|
||||
return ReadBytesAndFields(endMarker, null);
|
||||
}
|
||||
|
||||
private byte[] ReadBytesAndFields(
|
||||
string endMarker,
|
||||
IDictionary fields)
|
||||
{
|
||||
StringBuilder buf = new StringBuilder();
|
||||
|
||||
string line;
|
||||
while ((line = reader.ReadLine()) != null
|
||||
&& line.IndexOf(endMarker) == -1)
|
||||
{
|
||||
int colonPos = line.IndexOf(':');
|
||||
|
||||
if (colonPos == -1)
|
||||
{
|
||||
buf.Append(line.Trim());
|
||||
}
|
||||
else if (fields != null)
|
||||
{
|
||||
// Process field
|
||||
string fieldName = line.Substring(0, colonPos).Trim();
|
||||
|
||||
if (fieldName.StartsWith("X-"))
|
||||
fieldName = fieldName.Substring(2);
|
||||
|
||||
string fieldValue = line.Substring(colonPos + 1).Trim();
|
||||
|
||||
// TODO Complain if field already specified?
|
||||
fields[fieldName] = fieldValue;
|
||||
}
|
||||
}
|
||||
|
||||
if (line == null)
|
||||
{
|
||||
throw new IOException(endMarker + " not found");
|
||||
}
|
||||
|
||||
if (buf.Length % 4 != 0)
|
||||
{
|
||||
throw new IOException("base64 data appears to be truncated");
|
||||
}
|
||||
|
||||
return Base64.Decode(buf.ToString());
|
||||
}
|
||||
|
||||
private AsymmetricKeyParameter ReadRsaPublicKey(
|
||||
string endMarker)
|
||||
{
|
||||
RsaPublicKeyStructure rsaPubStructure = RsaPublicKeyStructure.GetInstance(
|
||||
Asn1Object.FromByteArray(
|
||||
ReadBytes(endMarker)));
|
||||
|
||||
return new RsaKeyParameters(
|
||||
false, // not private
|
||||
rsaPubStructure.Modulus,
|
||||
rsaPubStructure.PublicExponent);
|
||||
}
|
||||
|
||||
private AsymmetricKeyParameter ReadPublicKey(
|
||||
string endMarker)
|
||||
{
|
||||
return PublicKeyFactory.CreateKey(
|
||||
ReadBytes(endMarker));
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads in a X509Certificate.
|
||||
*
|
||||
* @return the X509Certificate
|
||||
* @throws IOException if an I/O error occured
|
||||
*/
|
||||
private X509Certificate ReadCertificate(
|
||||
string endMarker)
|
||||
{
|
||||
byte[] bytes = ReadBytes(endMarker);
|
||||
|
||||
try
|
||||
{
|
||||
return new X509CertificateParser().ReadCertificate(bytes);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new IOException("problem parsing cert: " + e.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads in a X509CRL.
|
||||
*
|
||||
* @return the X509Certificate
|
||||
* @throws IOException if an I/O error occured
|
||||
*/
|
||||
private X509Crl ReadCrl(
|
||||
string endMarker)
|
||||
{
|
||||
byte[] bytes = ReadBytes(endMarker);
|
||||
|
||||
try
|
||||
{
|
||||
return new X509CrlParser().ReadCrl(bytes);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new IOException("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(
|
||||
string endMarker)
|
||||
{
|
||||
byte[] bytes = ReadBytes(endMarker);
|
||||
|
||||
try
|
||||
{
|
||||
return new Pkcs10CertificationRequest(bytes);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new IOException("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(
|
||||
string endMarker)
|
||||
{
|
||||
byte[] bytes = ReadBytes(endMarker);
|
||||
|
||||
return new X509V2AttributeCertificate(bytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(
|
||||
string endMarker)
|
||||
{
|
||||
byte[] bytes = ReadBytes(endMarker);
|
||||
|
||||
try
|
||||
{
|
||||
return Asn1.Cms.ContentInfo.GetInstance(
|
||||
Asn1Object.FromByteArray(bytes));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new IOException("problem parsing PKCS7 object: " + e.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a Key Pair
|
||||
*/
|
||||
private AsymmetricCipherKeyPair ReadKeyPair(
|
||||
string type,
|
||||
string endMarker)
|
||||
{
|
||||
//
|
||||
// extract the key
|
||||
//
|
||||
IDictionary fields = new Hashtable();
|
||||
byte[] keyBytes = ReadBytesAndFields(endMarker, fields);
|
||||
|
||||
string procType = (string) fields["Proc-Type"];
|
||||
|
||||
if (procType == "4,ENCRYPTED")
|
||||
{
|
||||
if (pFinder == null)
|
||||
throw new InvalidOperationException("No password finder specified, but a password is required");
|
||||
|
||||
char[] password = pFinder.GetPassword();
|
||||
|
||||
if (password == null)
|
||||
throw new IOException("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":
|
||||
{
|
||||
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":
|
||||
{
|
||||
// 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;
|
||||
}
|
||||
|
||||
default:
|
||||
throw new ArgumentException("Unknown key type: " + type, "type");
|
||||
}
|
||||
|
||||
return new AsymmetricCipherKeyPair(pubSpec, privSpec);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new IOException(
|
||||
"problem creating " + type + " private key: " + e.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
// TODO Add an equivalent class for ECNamedCurveParameterSpec?
|
||||
//private ECNamedCurveParameterSpec ReadECParameters(
|
||||
private X9ECParameters ReadECParameters(
|
||||
string endMarker)
|
||||
{
|
||||
byte[] bytes = ReadBytes(endMarker);
|
||||
DerObjectIdentifier oid = (DerObjectIdentifier) Asn1Object.FromByteArray(bytes);
|
||||
|
||||
//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;
|
||||
}
|
||||
|
||||
private AsymmetricCipherKeyPair ReadECPrivateKey(
|
||||
string endMarker)
|
||||
{
|
||||
try
|
||||
{
|
||||
byte[] bytes = ReadBytes(endMarker);
|
||||
ECPrivateKeyStructure pKey = new ECPrivateKeyStructure(
|
||||
(Asn1Sequence) Asn1Object.FromByteArray(bytes));
|
||||
AlgorithmIdentifier algId = new AlgorithmIdentifier(
|
||||
X9ObjectIdentifiers.IdECPublicKey, pKey.GetParameters());
|
||||
|
||||
PrivateKeyInfo privInfo = new PrivateKeyInfo(algId, pKey.ToAsn1Object());
|
||||
SubjectPublicKeyInfo pubInfo = new SubjectPublicKeyInfo(algId, pKey.GetPublicKey().GetBytes());
|
||||
|
||||
// TODO Are the keys returned here ECDSA, as Java version forces?
|
||||
return new AsymmetricCipherKeyPair(
|
||||
PublicKeyFactory.CreateKey(pubInfo),
|
||||
PrivateKeyFactory.CreateKey(privInfo));
|
||||
}
|
||||
catch (InvalidCastException e)
|
||||
{
|
||||
throw new IOException("wrong ASN.1 object found in stream.", e);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new IOException("problem parsing EC private key.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
138
iTechSharp/srcbc/openssl/PEMUtilities.cs
Normal file
138
iTechSharp/srcbc/openssl/PEMUtilities.cs
Normal file
@@ -0,0 +1,138 @@
|
||||
using System;
|
||||
|
||||
using Org.BouncyCastle.Crypto;
|
||||
using Org.BouncyCastle.Crypto.Generators;
|
||||
using Org.BouncyCastle.Crypto.Parameters;
|
||||
using Org.BouncyCastle.Security;
|
||||
|
||||
namespace Org.BouncyCastle.OpenSsl
|
||||
{
|
||||
internal sealed class PemUtilities
|
||||
{
|
||||
internal static bool ParseDekAlgName(
|
||||
string dekAlgName,
|
||||
out string baseAlg,
|
||||
out string mode)
|
||||
{
|
||||
baseAlg = dekAlgName;
|
||||
mode = "ECB";
|
||||
|
||||
if (dekAlgName == "DES-EDE" || dekAlgName == "DES-EDE3")
|
||||
return true;
|
||||
|
||||
int pos = dekAlgName.LastIndexOf('-');
|
||||
if (pos < 0)
|
||||
return false;
|
||||
|
||||
baseAlg = dekAlgName.Substring(0, pos);
|
||||
mode = dekAlgName.Substring(pos + 1);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
internal static byte[] Crypt(
|
||||
bool encrypt,
|
||||
byte[] bytes,
|
||||
char[] password,
|
||||
string dekAlgName,
|
||||
byte[] iv)
|
||||
{
|
||||
string baseAlg, mode;
|
||||
if (!ParseDekAlgName(dekAlgName, out baseAlg, out mode))
|
||||
throw new ArgumentException("Unknown DEK algorithm: " + dekAlgName, "dekAlgName");
|
||||
|
||||
string padding;
|
||||
switch (mode)
|
||||
{
|
||||
case "CBC":
|
||||
case "ECB":
|
||||
padding = "PKCS5Padding";
|
||||
break;
|
||||
case "CFB":
|
||||
case "OFB":
|
||||
padding = "NoPadding";
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentException("Unknown DEK algorithm: " + dekAlgName, "dekAlgName");
|
||||
}
|
||||
|
||||
string algorithm;
|
||||
|
||||
byte[] salt = iv;
|
||||
switch (baseAlg)
|
||||
{
|
||||
case "AES-128":
|
||||
case "AES-192":
|
||||
case "AES-256":
|
||||
algorithm = "AES";
|
||||
if (salt.Length > 8)
|
||||
{
|
||||
salt = new byte[8];
|
||||
Array.Copy(iv, 0, salt, 0, salt.Length);
|
||||
}
|
||||
break;
|
||||
case "BF":
|
||||
algorithm = "BLOWFISH";
|
||||
break;
|
||||
case "DES":
|
||||
algorithm = "DES";
|
||||
break;
|
||||
case "DES-EDE":
|
||||
case "DES-EDE3":
|
||||
algorithm = "DESede";
|
||||
break;
|
||||
case "RC2":
|
||||
case "RC2-40":
|
||||
case "RC2-64":
|
||||
algorithm = "RC2";
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentException("Unknown DEK algorithm: " + dekAlgName, "dekAlgName");
|
||||
}
|
||||
|
||||
string cipherName = algorithm + "/" + mode + "/" + padding;
|
||||
IBufferedCipher cipher = CipherUtilities.GetCipher(cipherName);
|
||||
|
||||
ICipherParameters cParams = GetCipherParameters(password, baseAlg, salt);
|
||||
|
||||
if (mode != "ECB")
|
||||
{
|
||||
cParams = new ParametersWithIV(cParams, iv);
|
||||
}
|
||||
|
||||
cipher.Init(encrypt, cParams);
|
||||
|
||||
return cipher.DoFinal(bytes);
|
||||
}
|
||||
|
||||
private static ICipherParameters GetCipherParameters(
|
||||
char[] password,
|
||||
string baseAlg,
|
||||
byte[] salt)
|
||||
{
|
||||
string algorithm;
|
||||
int keyBits;
|
||||
switch (baseAlg)
|
||||
{
|
||||
case "AES-128": keyBits = 128; algorithm = "AES128"; break;
|
||||
case "AES-192": keyBits = 192; algorithm = "AES192"; break;
|
||||
case "AES-256": keyBits = 256; algorithm = "AES256"; break;
|
||||
case "BF": keyBits = 128; algorithm = "BLOWFISH"; break;
|
||||
case "DES": keyBits = 64; algorithm = "DES"; break;
|
||||
case "DES-EDE": keyBits = 128; algorithm = "DESEDE"; break;
|
||||
case "DES-EDE3": keyBits = 192; algorithm = "DESEDE3"; break;
|
||||
case "RC2": keyBits = 128; algorithm = "RC2"; break;
|
||||
case "RC2-40": keyBits = 40; algorithm = "RC2"; break;
|
||||
case "RC2-64": keyBits = 64; algorithm = "RC2"; break;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
OpenSslPbeParametersGenerator pGen = new OpenSslPbeParametersGenerator();
|
||||
|
||||
pGen.Init(PbeParametersGenerator.Pkcs5PasswordToBytes(password), salt);
|
||||
|
||||
return pGen.GenerateDerivedParameters(algorithm, keyBits);
|
||||
}
|
||||
}
|
||||
}
|
278
iTechSharp/srcbc/openssl/PEMWriter.cs
Normal file
278
iTechSharp/srcbc/openssl/PEMWriter.cs
Normal file
@@ -0,0 +1,278 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
using Org.BouncyCastle.Asn1;
|
||||
using Org.BouncyCastle.Asn1.Pkcs;
|
||||
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.Math;
|
||||
using Org.BouncyCastle.Pkcs;
|
||||
using Org.BouncyCastle.Security;
|
||||
using Org.BouncyCastle.Security.Certificates;
|
||||
using Org.BouncyCastle.Utilities.Encoders;
|
||||
using Org.BouncyCastle.X509;
|
||||
|
||||
namespace Org.BouncyCastle.OpenSsl
|
||||
{
|
||||
/// <remarks>General purpose writer for OpenSSL PEM objects.</remarks>
|
||||
public class PemWriter
|
||||
{
|
||||
private readonly TextWriter writer;
|
||||
|
||||
public TextWriter Writer
|
||||
{
|
||||
get { return writer; }
|
||||
}
|
||||
|
||||
/// <param name="writer">The TextWriter object to write the output to.</param>
|
||||
public PemWriter(
|
||||
TextWriter writer)
|
||||
{
|
||||
if (writer == null)
|
||||
throw new ArgumentNullException("writer");
|
||||
|
||||
this.writer = writer;
|
||||
}
|
||||
|
||||
public void WriteObject(
|
||||
object obj)
|
||||
{
|
||||
if (obj == null)
|
||||
throw new ArgumentNullException("obj");
|
||||
|
||||
string type;
|
||||
byte[] encoding;
|
||||
|
||||
if (obj is X509Certificate)
|
||||
{
|
||||
// TODO Should we prefer "X509 CERTIFICATE" here?
|
||||
type = "CERTIFICATE";
|
||||
try
|
||||
{
|
||||
encoding = ((X509Certificate)obj).GetEncoded();
|
||||
}
|
||||
catch (CertificateEncodingException e)
|
||||
{
|
||||
throw new IOException("Cannot Encode object: " + e.ToString());
|
||||
}
|
||||
}
|
||||
else if (obj is X509Crl)
|
||||
{
|
||||
type = "X509 CRL";
|
||||
try
|
||||
{
|
||||
encoding = ((X509Crl)obj).GetEncoded();
|
||||
}
|
||||
catch (CrlException e)
|
||||
{
|
||||
throw new IOException("Cannot Encode object: " + e.ToString());
|
||||
}
|
||||
}
|
||||
else if (obj is AsymmetricCipherKeyPair)
|
||||
{
|
||||
WriteObject(((AsymmetricCipherKeyPair)obj).Private);
|
||||
return;
|
||||
}
|
||||
else if (obj is AsymmetricKeyParameter)
|
||||
{
|
||||
AsymmetricKeyParameter akp = (AsymmetricKeyParameter) obj;
|
||||
if (akp.IsPrivate)
|
||||
{
|
||||
string keyType;
|
||||
encoding = EncodePrivateKey(akp, out keyType);
|
||||
|
||||
type = keyType + " PRIVATE KEY";
|
||||
}
|
||||
else
|
||||
{
|
||||
type = "PUBLIC KEY";
|
||||
|
||||
encoding = SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(akp).GetDerEncoded();
|
||||
}
|
||||
}
|
||||
else if (obj is IX509AttributeCertificate)
|
||||
{
|
||||
type = "ATTRIBUTE CERTIFICATE";
|
||||
encoding = ((X509V2AttributeCertificate)obj).GetEncoded();
|
||||
}
|
||||
else if (obj is Pkcs10CertificationRequest)
|
||||
{
|
||||
type = "CERTIFICATE REQUEST";
|
||||
encoding = ((Pkcs10CertificationRequest)obj).GetEncoded();
|
||||
}
|
||||
else if (obj is Asn1.Cms.ContentInfo)
|
||||
{
|
||||
type = "PKCS7";
|
||||
encoding = ((Asn1.Cms.ContentInfo)obj).GetEncoded();
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException("Object type not supported: " + obj.GetType().FullName, "obj");
|
||||
}
|
||||
|
||||
WritePemBlock(type, encoding);
|
||||
}
|
||||
|
||||
public void WriteObject(
|
||||
object obj,
|
||||
string algorithm,
|
||||
char[] password,
|
||||
SecureRandom random)
|
||||
{
|
||||
if (obj == null)
|
||||
throw new ArgumentNullException("obj");
|
||||
if (algorithm == null)
|
||||
throw new ArgumentNullException("algorithm");
|
||||
if (password == null)
|
||||
throw new ArgumentNullException("password");
|
||||
if (random == null)
|
||||
throw new ArgumentNullException("random");
|
||||
|
||||
if (obj is AsymmetricCipherKeyPair)
|
||||
{
|
||||
WriteObject(((AsymmetricCipherKeyPair) obj).Private, algorithm, password, random);
|
||||
return;
|
||||
}
|
||||
|
||||
string type = null;
|
||||
byte[] keyData = null;
|
||||
|
||||
if (obj is AsymmetricKeyParameter)
|
||||
{
|
||||
AsymmetricKeyParameter akp = (AsymmetricKeyParameter) obj;
|
||||
if (akp.IsPrivate)
|
||||
{
|
||||
string keyType;
|
||||
keyData = EncodePrivateKey(akp, out keyType);
|
||||
|
||||
type = keyType + " PRIVATE KEY";
|
||||
}
|
||||
}
|
||||
|
||||
if (type == null || keyData == null)
|
||||
{
|
||||
// TODO Support other types?
|
||||
throw new ArgumentException("Object type not supported: " + obj.GetType().FullName, "obj");
|
||||
}
|
||||
|
||||
|
||||
string dekAlgName = algorithm.ToUpper(CultureInfo.InvariantCulture);
|
||||
|
||||
// Note: For backward compatibility
|
||||
if (dekAlgName == "DESEDE")
|
||||
{
|
||||
dekAlgName = "DES-EDE3-CBC";
|
||||
}
|
||||
|
||||
int ivLength = dekAlgName.StartsWith("AES-") ? 16 : 8;
|
||||
|
||||
byte[] iv = new byte[ivLength];
|
||||
random.NextBytes(iv);
|
||||
|
||||
byte[] encData = PemUtilities.Crypt(true, keyData, password, dekAlgName, iv);
|
||||
byte[] hexIV = Hex.Encode(iv);
|
||||
|
||||
WritePemBlock(type, encData,
|
||||
"Proc-Type: 4,ENCRYPTED",
|
||||
"DEK-Info: " + dekAlgName + "," + Encoding.ASCII.GetString(hexIV, 0, hexIV.Length));
|
||||
}
|
||||
|
||||
private byte[] EncodePrivateKey(
|
||||
AsymmetricKeyParameter akp,
|
||||
out string keyType)
|
||||
{
|
||||
PrivateKeyInfo info = PrivateKeyInfoFactory.CreatePrivateKeyInfo(akp);
|
||||
|
||||
DerObjectIdentifier oid = info.AlgorithmID.ObjectID;
|
||||
|
||||
if (oid.Equals(X9ObjectIdentifiers.IdDsa))
|
||||
{
|
||||
keyType = "DSA";
|
||||
|
||||
DsaParameter p = DsaParameter.GetInstance(info.AlgorithmID.Parameters);
|
||||
|
||||
BigInteger x = ((DsaPrivateKeyParameters) akp).X;
|
||||
BigInteger y = p.G.ModPow(x, p.P);
|
||||
|
||||
// TODO Create an ASN1 object somewhere for this?
|
||||
return new DerSequence(
|
||||
new DerInteger(0),
|
||||
new DerInteger(p.P),
|
||||
new DerInteger(p.Q),
|
||||
new DerInteger(p.G),
|
||||
new DerInteger(y),
|
||||
new DerInteger(x)).GetEncoded();
|
||||
}
|
||||
|
||||
if (oid.Equals(PkcsObjectIdentifiers.RsaEncryption))
|
||||
{
|
||||
keyType = "RSA";
|
||||
return info.PrivateKey.GetEncoded();
|
||||
}
|
||||
|
||||
throw new ArgumentException("Cannot handle private key of type: " + akp.GetType().FullName, "akp");
|
||||
}
|
||||
|
||||
private void WritePemBlock(
|
||||
string type,
|
||||
byte[] data,
|
||||
params string[] fields)
|
||||
{
|
||||
WriteHeader(type);
|
||||
|
||||
if (fields.Length > 0)
|
||||
{
|
||||
foreach (string field in fields)
|
||||
{
|
||||
writer.WriteLine(field);
|
||||
}
|
||||
|
||||
writer.WriteLine();
|
||||
}
|
||||
|
||||
WriteBytes(Base64.Encode(data));
|
||||
|
||||
WriteFooter(type);
|
||||
}
|
||||
|
||||
private void WriteHeader(
|
||||
string type)
|
||||
{
|
||||
writer.WriteLine("-----BEGIN " + type + "-----");
|
||||
}
|
||||
|
||||
private void WriteFooter(
|
||||
string type)
|
||||
{
|
||||
writer.WriteLine("-----END " + type + "-----");
|
||||
}
|
||||
|
||||
private const int LineLength = 64;
|
||||
|
||||
private void WriteBytes(
|
||||
byte[] bytes)
|
||||
{
|
||||
int pos = 0;
|
||||
int remaining = bytes.Length;
|
||||
char[] buf = new char[LineLength];
|
||||
|
||||
while (remaining > LineLength)
|
||||
{
|
||||
Encoding.ASCII.GetChars(bytes, pos, LineLength, buf, 0);
|
||||
writer.WriteLine(buf);
|
||||
|
||||
pos += LineLength;
|
||||
remaining -= LineLength;
|
||||
}
|
||||
|
||||
Encoding.ASCII.GetChars(bytes, pos, remaining, buf, 0);
|
||||
writer.WriteLine(buf, 0, remaining);
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user