Merge pull request #645 from knocte/nitpicks
[mono.git] / mcs / class / corlib / Mono.Security.X509 / PKCS12.cs
index 0df82b90c314fd6cb2b52bd9b18437f99fed17f7..848ce803589e6c26a5cf9c519fa9c555df18d1e5 100644 (file)
@@ -5,12 +5,10 @@
 //     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
@@ -267,13 +265,15 @@ namespace Mono.Security.X509 {
                        }
                }
 
-               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;
@@ -285,8 +285,10 @@ namespace Mono.Security.X509 {
                {
                        _iterations = recommendedIterationCount;
                        _keyBags = new ArrayList ();
+                       _secretBags = new ArrayList ();
                        _certs = new X509CertificateCollection ();
                        _keyBagsChanged = false;
+                       _secretBagsChanged = false;
                        _certsChanged = false;
                        _safeBags = new ArrayList ();
                }
@@ -327,14 +329,14 @@ namespace Mono.Security.X509 {
                        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);
@@ -428,13 +430,26 @@ namespace Mono.Security.X509 {
                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
                        }
                }
 
@@ -494,6 +509,24 @@ namespace Mono.Security.X509 {
                        }
                }
 
+               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) {
@@ -660,13 +693,32 @@ namespace Mono.Security.X509 {
                        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));
@@ -711,7 +763,8 @@ namespace Mono.Security.X509 {
                                        // TODO
                                        break;
                                case secretBag: 
-                                       // TODO
+                                       byte[] secret = bagValue.Value;
+                                       _secretBags.Add(secret);
                                        break;
                                case safeContentsBag:
                                        // TODO - ? recurse ?
@@ -906,6 +959,63 @@ namespace Mono.Security.X509 {
                        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);
@@ -984,7 +1094,7 @@ namespace Mono.Security.X509 {
                        return hmac.ComputeHash (data, 0, data.Length);
                }
 
-                /*
+               /*
                 * SafeContents ::= SEQUENCE OF SafeBag
                 * 
                 * SafeBag ::= SEQUENCE {
@@ -1057,36 +1167,8 @@ namespace Mono.Security.X509 {
                                }
 
                                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);
                                }
                        }
 
@@ -1108,6 +1190,21 @@ namespace Mono.Security.X509 {
                                }
                        }
 
+                       // 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);
@@ -1147,6 +1244,41 @@ namespace Mono.Security.X509 {
                        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);
@@ -1242,28 +1374,11 @@ namespace Mono.Security.X509 {
 
                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)
@@ -1444,6 +1559,56 @@ namespace Mono.Security.X509 {
                        }
                }
 
+               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)
                {
@@ -1526,6 +1691,52 @@ namespace Mono.Security.X509 {
                        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) {
@@ -1687,11 +1898,9 @@ namespace Mono.Security.X509 {
                        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 ();
                        }
                }
 
@@ -1708,6 +1917,29 @@ namespace Mono.Security.X509 {
                        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)