// Sebastien Pouliot <sebastien@ximian.com>
//
// (C) 2003 Motus Technologies Inc. (http://www.motus.com)
-// (C) 2004 Novell (http://www.novell.com)
+// Copyright (C) 2004,2005,2006 Novell Inc. (http://www.novell.com)
//
// Key derivation translated from Bouncy Castle JCE (http://www.bouncycastle.org/)
// See bouncycastle.txt for license.
-//
-
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
}
}
- static private int recommendedIterationCount = 2000;
+ const int recommendedIterationCount = 2000;
//private int _version;
private byte[] _password;
private ArrayList _keyBags;
+ private ArrayList _secretBags;
private X509CertificateCollection _certs;
private bool _keyBagsChanged;
+ private bool _secretBagsChanged;
private bool _certsChanged;
private int _iterations;
private ArrayList _safeBags;
{
_iterations = recommendedIterationCount;
_keyBags = new ArrayList ();
+ _secretBags = new ArrayList ();
_certs = new X509CertificateCollection ();
_keyBagsChanged = false;
+ _secretBagsChanged = false;
_certsChanged = false;
_safeBags = new ArrayList ();
}
Password = password;
Decode (data);
}
-#if NET_2_0
+
public PKCS12 (byte[] data, byte[] password)
: this ()
{
_password = password;
Decode (data);
}
-#endif
+
private void Decode (byte[] data)
{
ASN1 pfx = new ASN1 (data);
public string Password {
set {
if (value != null) {
- if (value.EndsWith ("\0"))
- _password = Encoding.BigEndianUnicode.GetBytes (value);
- else
- _password = Encoding.BigEndianUnicode.GetBytes (value + "\0");
+ if (value.Length > 0) {
+ int size = value.Length;
+ int nul = 0;
+ if (size < MaximumPasswordLength) {
+ // if not present, add space for a NULL (0x00) character
+ if (value[size - 1] != 0x00)
+ nul = 1;
+ } else {
+ size = MaximumPasswordLength;
+ }
+ _password = new byte[(size + nul) << 1]; // double for unicode
+ Encoding.BigEndianUnicode.GetBytes (value, 0, size, _password, 0);
+ } else {
+ // double-byte (Unicode) NULL (0x00) - see bug #79617
+ _password = new byte[2];
+ }
+ } else {
+ // no password
+ _password = null;
}
- else
- _password = null; // no password
}
}
}
}
+ public ArrayList Secrets {
+ get {
+ if (_secretBagsChanged) {
+ _secretBags.Clear ();
+ foreach (SafeBag sb in _safeBags) {
+ if (sb.BagOID.Equals (secretBag)) {
+ ASN1 safeBag = sb.ASN1;
+ ASN1 bagValue = safeBag [1];
+ byte[] secret = bagValue.Value;
+ _secretBags.Add(secret);
+ }
+ }
+ _secretBagsChanged = false;
+ }
+ return ArrayList.ReadOnly(_secretBags);
+ }
+ }
+
public X509CertificateCollection Certificates {
get {
if (_certsChanged) {
return result;
}
+ private DSAParameters GetExistingParameters (out bool found)
+ {
+ foreach (X509Certificate cert in Certificates) {
+ // FIXME: that won't work if parts of the parameters are missing
+ if (cert.KeyAlgorithmParameters != null) {
+ DSA dsa = cert.DSA;
+ if (dsa != null) {
+ found = true;
+ return dsa.ExportParameters (false);
+ }
+ }
+ }
+ found = false;
+ return new DSAParameters ();
+ }
+
private void AddPrivateKey (PKCS8.PrivateKeyInfo pki)
{
byte[] privateKey = pki.PrivateKey;
switch (privateKey [0]) {
case 0x02:
- DSAParameters p = new DSAParameters (); // FIXME
- _keyBags.Add (PKCS8.PrivateKeyInfo.DecodeDSA (privateKey, p));
+ bool found;
+ DSAParameters p = GetExistingParameters (out found);
+ if (found) {
+ _keyBags.Add (PKCS8.PrivateKeyInfo.DecodeDSA (privateKey, p));
+ }
break;
case 0x30:
_keyBags.Add (PKCS8.PrivateKeyInfo.DecodeRSA (privateKey));
// TODO
break;
case secretBag:
- // TODO
+ byte[] secret = bagValue.Value;
+ _secretBags.Add(secret);
break;
case safeContentsBag:
// TODO - ? recurse ?
return safeBag;
}
+ private ASN1 SecretBagSafeBag (byte[] secret, IDictionary attributes)
+ {
+ ASN1 safeBag = new ASN1 (0x30);
+ safeBag.Add (ASN1Convert.FromOid (secretBag));
+ ASN1 bagValue = new ASN1 (0x80, secret);
+ safeBag.Add (bagValue);
+
+ if (attributes != null) {
+ ASN1 bagAttributes = new ASN1 (0x31);
+ IDictionaryEnumerator de = attributes.GetEnumerator ();
+
+ while (de.MoveNext ()) {
+ string oid = (string)de.Key;
+ switch (oid) {
+ case PKCS9.friendlyName:
+ ArrayList names = (ArrayList)de.Value;
+ if (names.Count > 0) {
+ ASN1 pkcs12Attribute = new ASN1 (0x30);
+ pkcs12Attribute.Add (ASN1Convert.FromOid (PKCS9.friendlyName));
+ ASN1 attrValues = new ASN1 (0x31);
+ foreach (byte[] name in names) {
+ ASN1 attrValue = new ASN1 (0x1e);
+ attrValue.Value = name;
+ attrValues.Add (attrValue);
+ }
+ pkcs12Attribute.Add (attrValues);
+ bagAttributes.Add (pkcs12Attribute);
+ }
+ break;
+ case PKCS9.localKeyId:
+ ArrayList keys = (ArrayList)de.Value;
+ if (keys.Count > 0) {
+ ASN1 pkcs12Attribute = new ASN1 (0x30);
+ pkcs12Attribute.Add (ASN1Convert.FromOid (PKCS9.localKeyId));
+ ASN1 attrValues = new ASN1 (0x31);
+ foreach (byte[] key in keys) {
+ ASN1 attrValue = new ASN1 (0x04);
+ attrValue.Value = key;
+ attrValues.Add (attrValue);
+ }
+ pkcs12Attribute.Add (attrValues);
+ bagAttributes.Add (pkcs12Attribute);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (bagAttributes.Count > 0) {
+ safeBag.Add (bagAttributes);
+ }
+ }
+
+ return safeBag;
+ }
+
private ASN1 CertificateSafeBag (X509Certificate x509, IDictionary attributes)
{
ASN1 encapsulatedCertificate = new ASN1 (0x04, x509.RawData);
return hmac.ComputeHash (data, 0, data.Length);
}
- /*
+ /*
* SafeContents ::= SEQUENCE OF SafeBag
*
* SafeBag ::= SEQUENCE {
}
if (certsSafeBag.Count > 0) {
- byte[] certsSalt = new byte [8];
- RNG.GetBytes (certsSalt);
-
- ASN1 seqParams = new ASN1 (0x30);
- seqParams.Add (new ASN1 (0x04, certsSalt));
- seqParams.Add (ASN1Convert.FromInt32 (_iterations));
-
- ASN1 seqPbe = new ASN1 (0x30);
- seqPbe.Add (ASN1Convert.FromOid (pbeWithSHAAnd3KeyTripleDESCBC));
- seqPbe.Add (seqParams);
-
- byte[] encrypted = Encrypt (pbeWithSHAAnd3KeyTripleDESCBC, certsSalt, _iterations, certsSafeBag.GetBytes ());
- ASN1 encryptedCerts = new ASN1 (0x80, encrypted);
-
- ASN1 seq = new ASN1 (0x30);
- seq.Add (ASN1Convert.FromOid (PKCS7.Oid.data));
- seq.Add (seqPbe);
- seq.Add (encryptedCerts);
-
- ASN1 certsVersion = new ASN1 (0x02, new byte [1] { 0x00 });
- ASN1 encData = new ASN1 (0x30);
- encData.Add (certsVersion);
- encData.Add (seq);
-
- ASN1 certsContent = new ASN1 (0xA0);
- certsContent.Add (encData);
-
- PKCS7.ContentInfo bag = new PKCS7.ContentInfo (PKCS7.Oid.encryptedData);
- bag.Content = certsContent;
- safeBagSequence.Add (bag.ASN1);
+ PKCS7.ContentInfo contentInfo = EncryptedContentInfo (certsSafeBag, pbeWithSHAAnd3KeyTripleDESCBC);
+ safeBagSequence.Add (contentInfo.ASN1);
}
}
}
}
+ // Doing SecretBags separately in case we want to change their encryption independently.
+ if (_safeBags.Count > 0) {
+ ASN1 secretsSafeBag = new ASN1 (0x30);
+ foreach (SafeBag sb in _safeBags) {
+ if (sb.BagOID.Equals (secretBag)) {
+ secretsSafeBag.Add (sb.ASN1);
+ }
+ }
+
+ if (secretsSafeBag.Count > 0) {
+ PKCS7.ContentInfo contentInfo = EncryptedContentInfo (secretsSafeBag, pbeWithSHAAnd3KeyTripleDESCBC);
+ safeBagSequence.Add (contentInfo.ASN1);
+ }
+ }
+
ASN1 encapsulates = new ASN1 (0x04, safeBagSequence.GetBytes ());
ASN1 ci = new ASN1 (0xA0);
return pfx.GetBytes ();
}
+ // Creates an encrypted PKCS#7 ContentInfo with safeBags as its SafeContents. Used in GetBytes(), above.
+ private PKCS7.ContentInfo EncryptedContentInfo(ASN1 safeBags, string algorithmOid)
+ {
+ byte[] salt = new byte [8];
+ RNG.GetBytes (salt);
+
+ ASN1 seqParams = new ASN1 (0x30);
+ seqParams.Add (new ASN1 (0x04, salt));
+ seqParams.Add (ASN1Convert.FromInt32 (_iterations));
+
+ ASN1 seqPbe = new ASN1 (0x30);
+ seqPbe.Add (ASN1Convert.FromOid (algorithmOid));
+ seqPbe.Add (seqParams);
+
+ byte[] encrypted = Encrypt (algorithmOid, salt, _iterations, safeBags.GetBytes ());
+ ASN1 encryptedContent = new ASN1 (0x80, encrypted);
+
+ ASN1 seq = new ASN1 (0x30);
+ seq.Add (ASN1Convert.FromOid (PKCS7.Oid.data));
+ seq.Add (seqPbe);
+ seq.Add (encryptedContent);
+
+ ASN1 version = new ASN1 (0x02, new byte [1] { 0x00 });
+ ASN1 encData = new ASN1 (0x30);
+ encData.Add (version);
+ encData.Add (seq);
+
+ ASN1 finalContent = new ASN1 (0xA0);
+ finalContent.Add (encData);
+
+ PKCS7.ContentInfo bag = new PKCS7.ContentInfo (PKCS7.Oid.encryptedData);
+ bag.Content = finalContent;
+ return bag;
+ }
+
public void AddCertificate (X509Certificate cert)
{
AddCertificate (cert, null);
private bool CompareAsymmetricAlgorithm (AsymmetricAlgorithm a1, AsymmetricAlgorithm a2)
{
- bool result = a1.KeyExchangeAlgorithm.Equals (a2.KeyExchangeAlgorithm);
- result = result && a1.KeySize == a2.KeySize;
-
- KeySizes[] keysizes = a2.LegalKeySizes;
- if (a1.LegalKeySizes.Length != keysizes.Length)
+ // fast path
+ if (a1.KeySize != a2.KeySize)
return false;
-
- for (int i = 0; i < a1.LegalKeySizes.Length; i++) {
- result = result && CompareKeySizes (a1.LegalKeySizes [i], keysizes [i]);
- }
-
- result = result && a1.SignatureAlgorithm == a2.SignatureAlgorithm;
-
- return result;
- }
-
- private bool CompareKeySizes (KeySizes k1, KeySizes k2)
- {
- bool result = k1.MaxSize == k2.MaxSize;
- result = result && k1.MinSize == k2.MinSize;
- result = result && k1.SkipSize == k2.SkipSize;
- return result;
+ // compare public keys - if they match we can assume the private match too
+ return (a1.ToXmlString (false) == a2.ToXmlString (false));
}
public void AddPkcs8ShroudedKeyBag (AsymmetricAlgorithm aa)
}
}
+ public void AddSecretBag (byte[] secret)
+ {
+ AddSecretBag (secret, null);
+ }
+
+ public void AddSecretBag (byte[] secret, IDictionary attributes)
+ {
+ bool found = false;
+
+ for (int i = 0; !found && i < _safeBags.Count; i++) {
+ SafeBag sb = (SafeBag)_safeBags [i];
+
+ if (sb.BagOID.Equals (secretBag)) {
+ ASN1 bagValue = sb.ASN1 [1];
+ byte[] ssecret = bagValue.Value;
+
+ if (Compare (secret, ssecret)) {
+ found = true;
+ }
+ }
+ }
+
+ if (!found) {
+ _safeBags.Add (new SafeBag (secretBag, SecretBagSafeBag (secret, attributes)));
+ _secretBagsChanged = true;
+ }
+ }
+
+ public void RemoveSecretBag (byte[] secret)
+ {
+ int sIndex = -1;
+
+ for (int i = 0; sIndex == -1 && i < _safeBags.Count; i++) {
+ SafeBag sb = (SafeBag)_safeBags [i];
+
+ if (sb.BagOID.Equals (secretBag)) {
+ ASN1 bagValue = sb.ASN1 [1];
+ byte[] ssecret = bagValue.Value;
+
+ if (Compare (secret, ssecret)) {
+ sIndex = i;
+ }
+ }
+ }
+
+ if (sIndex != -1) {
+ _safeBags.RemoveAt (sIndex);
+ _secretBagsChanged = true;
+ }
+ }
public AsymmetricAlgorithm GetAsymmetricAlgorithm (IDictionary attrs)
{
return null;
}
+ public byte[] GetSecret (IDictionary attrs)
+ {
+ foreach (SafeBag sb in _safeBags) {
+ if (sb.BagOID.Equals (secretBag)) {
+ ASN1 safeBag = sb.ASN1;
+
+ if (safeBag.Count == 3) {
+ ASN1 bagAttributes = safeBag [2];
+
+ int bagAttributesFound = 0;
+ for (int i = 0; i < bagAttributes.Count; i++) {
+ ASN1 pkcs12Attribute = bagAttributes [i];
+ ASN1 attrId = pkcs12Attribute [0];
+ string ao = ASN1Convert.ToOid (attrId);
+ ArrayList dattrValues = (ArrayList)attrs [ao];
+
+ if (dattrValues != null) {
+ ASN1 attrValues = pkcs12Attribute [1];
+
+ if (dattrValues.Count == attrValues.Count) {
+ int attrValuesFound = 0;
+ for (int j = 0; j < attrValues.Count; j++) {
+ ASN1 attrValue = attrValues [j];
+ byte[] value = (byte[])dattrValues [j];
+
+ if (Compare (value, attrValue.Value)) {
+ attrValuesFound += 1;
+ }
+ }
+ if (attrValuesFound == attrValues.Count) {
+ bagAttributesFound += 1;
+ }
+ }
+ }
+ }
+ if (bagAttributesFound == bagAttributes.Count) {
+ ASN1 bagValue = safeBag [1];
+ return bagValue.Value;
+ }
+ }
+ }
+ }
+
+ return null;
+ }
+
public X509Certificate GetCertificate (IDictionary attrs)
{
foreach (SafeBag sb in _safeBags) {
if (filename == null)
throw new ArgumentNullException ("filename");
- using (FileStream fs = File.OpenWrite (filename)) {
+ using (FileStream fs = File.Create (filename)) {
byte[] data = GetBytes ();
fs.Write (data, 0, data.Length);
- fs.Flush ();
- fs.Close ();
}
}
return clone;
}
+ // static
+
+ public const int CryptoApiPasswordLimit = 32;
+
+ static private int password_max_length = Int32.MaxValue;
+
+ // static properties
+
+ // MS CryptoAPI limits the password to a maximum of 31 characters
+ // other implementations, like OpenSSL, have no such limitation.
+ // Setting a maximum value will truncate the password length to
+ // ensure compatibility with MS's PFXImportCertStore API.
+ static public int MaximumPasswordLength {
+ get { return password_max_length; }
+ set {
+ if (value < CryptoApiPasswordLimit) {
+ string msg = Locale.GetText ("Maximum password length cannot be less than {0}.", CryptoApiPasswordLimit);
+ throw new ArgumentOutOfRangeException (msg);
+ }
+ password_max_length = value;
+ }
+ }
+
// static methods
static private byte[] LoadFile (string filename)