imported everything from my branch (which is slightly harmless).
[mono.git] / mcs / class / corlib / System.Security.Cryptography / DSACryptoServiceProvider.cs
index caa050dd4766c036ba4c053482c3fee58fb55f03..7e000fbdfcef3041192da69e35298b139885f963 100644 (file)
 //
 // Authors:
 //     Dan Lewis (dihlewis@yahoo.co.uk)
-//     Sebastien Pouliot (spouliot@motus.com)
+//     Sebastien Pouliot <sebastien@ximian.com>
 //     Ben Maurer (bmaurer@users.sf.net)
 //
 // (C) 2002
-// Portions (C) 2002 Motus Technologies Inc. (http://www.motus.com)
+// Portions (C) 2002, 2003 Motus Technologies Inc. (http://www.motus.com)
 // Portions (C) 2003 Ben Maurer
+// Copyright (C) 2004-2005 Novell, Inc (http://www.novell.com)
 //
-// Key generation 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
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+// 
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+// 
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 //
 
-using System;
 using System.IO;
+using System.Globalization;
+using System.Runtime.InteropServices;
 
-using Mono.Math;
+using Mono.Security.Cryptography;
 
 namespace System.Security.Cryptography {
 
-       public class DSACryptoServiceProvider : DSA {
+#if NET_2_0
+       [ComVisible (true)]
+       public sealed class DSACryptoServiceProvider : DSA, ICspAsymmetricAlgorithm {
+#elif NET_1_1
+       public sealed class DSACryptoServiceProvider : DSA {
+#else
+       public class DSACryptoServiceProvider : DSA {
+#endif
+               private const int PROV_DSS_DH = 13;             // from WinCrypt.h
 
-               private CspParameters cspParams;
-               private RandomNumberGenerator rng;
+               private KeyPairPersistence store;
+               private bool persistKey;
+               private bool persisted;
 
                private bool privateKeyExportable = true;
-               private bool m_disposed = false;
-               private bool keypairGenerated = false;
-               private bool persistKey = false;
-
-               private BigInteger p;
-               private BigInteger q;
-               private BigInteger g;
-               private BigInteger x;   // private key
-               private BigInteger y;
-               private BigInteger j;
-               private BigInteger seed;
-               private int counter;
+               private bool m_disposed;
+
+               private DSAManaged dsa;
+
+               // MS implementation generates a keypair everytime a new DSA 
+               // object is created (unless an existing key container is 
+               // specified in the CspParameters).
+               // However we:
+               // (a) often use DSA to import an existing keypair.
+               // (b) take a LOT of time to generate the DSA group
+               // So we'll generate the keypair only when (and if) it's being
+               // used (or exported). This should save us a lot of time (at
+               // least in the unit tests).
 
                public DSACryptoServiceProvider ()
+                       : this (1024, null)
                {
-                       // Here it's not clear if we need to generate a keypair
-                       // (note: MS implementation generates a keypair in this case).
-                       // However we:
-                       // (a) often use this constructor to import an existing keypair.
-                       // (b) take a LOT of time to generate the DSA group
-                       // So we'll generate the keypair only when (and if) it's being
-                       // used (or exported). This should save us a lot of time (at
-                       // least in the unit tests).
-                       Common (1024, null);
                }
 
                public DSACryptoServiceProvider (CspParameters parameters)
+                       : this (1024, parameters)
                {
-                       Common (1024, parameters);
-                       // no keypair generation done at this stage
                }
 
-               // This constructor will generate a new keypair
                public DSACryptoServiceProvider (int dwKeySize)
+                       : this (dwKeySize, null)
                {
-                       // Here it's clear that we need to generate a new keypair
-                       Common (dwKeySize, null);
-                       Generate ();
                }
 
-               // This constructor will generate a new keypair
                public DSACryptoServiceProvider (int dwKeySize, CspParameters parameters)
                {
-                       Common (dwKeySize, parameters);
-                       Generate ();
-               }
-
-               ~DSACryptoServiceProvider ()
-               {
-                       Dispose (false);
-               }
-
-               [MonoTODO("Persistance")]
-               private void Common (int dwKeySize, CspParameters p) 
-               {
-                       rng = RandomNumberGenerator.Create ();
-                       cspParams = new CspParameters ();
-                       if (p == null) {
-                               // TODO: set default values (for keypair persistance)
-                       }
-                       else {
-                               cspParams = p;
-                               // FIXME: We'll need this to support some kind of persistance
-                               throw new NotSupportedException ("CspParameters not supported");
-                       }
                        LegalKeySizesValue = new KeySizes [1];
                        LegalKeySizesValue [0] = new KeySizes (512, 1024, 64);
 
                        // will throw an exception is key size isn't supported
                        KeySize = dwKeySize;
-               }
-
-               // generate both the group and the keypair
-               private void Generate () 
-               {
-                       GenerateParams (base.KeySize);
-                       GenerateKeyPair ();
-                       keypairGenerated = true;
-               }
-
-               // this part is quite fast
-               private void GenerateKeyPair ()
-               {
-                       x = BigInteger.genRandom (160);
-                       while ((x == 0) || (x >= q)) {
-                               // size of x (private key) isn't affected by the keysize (512-1024)
-                               x.randomize ();
-                       }
-
-                       // calculate the public key y = g^x % p
-                       y = g.modPow (x, p);
-               }
-
-               private void add (byte[] a, byte[] b, int value)
-               {
-                       uint x = (uint) ((b [b.Length - 1] & 0xff) + value);
-
-                       a [b.Length - 1] = (byte)x;
-                       x >>= 8;
-
-                       for (int i = b.Length - 2; i >= 0; i--) {
-                               x += (uint) (b [i] & 0xff);
-                               a [i] = (byte)x;
-                               x >>= 8;
+                       dsa = new DSAManaged (dwKeySize);
+                       dsa.KeyGenerated += new DSAManaged.KeyGeneratedEventHandler (OnKeyGenerated);
+
+                       persistKey = (parameters != null);
+                       if (parameters == null) {
+                               parameters = new CspParameters (PROV_DSS_DH);
+#if NET_1_1
+                               if (useMachineKeyStore)
+                                       parameters.Flags |= CspProviderFlags.UseMachineKeyStore;
+#endif
+                               store = new KeyPairPersistence (parameters);
+                               // no need to load - it cannot exists
                        }
-               }
-
-               private void GenerateParams (int keyLength)
-               {
-                       byte[] seed = new byte[20];
-                       byte[] part1 = new byte[20];
-                       byte[] part2 = new byte[20];
-                       byte[] u = new byte[20];
-
-                       // TODO: a prime generator should be made for this
-
-                       SHA1 sha = SHA1.Create ();
-
-                       int n = (keyLength - 1) / 160;
-                       byte[] w = new byte [keyLength / 8];
-                       bool primesFound = false;
-
-                       while (!primesFound) {
-                               do {
-                                       rng.GetBytes (seed);
-                                       part1 = sha.ComputeHash (seed);
-                                       Array.Copy(seed, 0, part2, 0, seed.Length);
-
-                                       add (part2, seed, 1);
-
-                                       part2 = sha.ComputeHash (part2);
-
-                                       for (int i = 0; i != u.Length; i++)
-                                               u [i] = (byte)(part1 [i] ^ part2 [i]);
-
-                                       // first bit must be set (to respect key length)
-                                       u[0] |= (byte)0x80;
-                                       // last bit must be set (prime are all odds - except 2)
-                                       u[19] |= (byte)0x01;
-
-                                       q = new BigInteger (u);
-                               }
-                               while (!q.isProbablePrime ());
-
-                               counter = 0;
-                               int offset = 2;
-                               while (counter < 4096) {
-                                       for (int k = 0; k < n; k++) {
-                                               add(part1, seed, offset + k);
-                                               part1 = sha.ComputeHash (part1);
-                                               Array.Copy (part1, 0, w, w.Length - (k + 1) * part1.Length, part1.Length);
-                                       }
-
-                                       add(part1, seed, offset + n);
-                                       part1 = sha.ComputeHash (part1);
-                                       Array.Copy (part1, part1.Length - ((w.Length - (n) * part1.Length)), w, 0, w.Length - n * part1.Length);
-
-                                       w[0] |= (byte)0x80;
-                                       BigInteger x = new BigInteger (w);
-
-                                       BigInteger c = x % (q * 2);
-
-                                       p = x - (c - 1);
-
-                                       if (p.testBit ((uint)(keyLength - 1))) {
-                                               if (p.isProbablePrime ()) {
-                                                       primesFound = true;
-                                                       break;
-                                               }
-                                       }
-
-                                       counter += 1;
-                                       offset += n + 1;
+                       else {
+                               store = new KeyPairPersistence (parameters);
+                               store.Load ();
+                               if (store.KeyValue != null) {
+                                       persisted = true;
+                                       this.FromXmlString (store.KeyValue);
                                }
                        }
-
-                       // calculate the generator g
-                       BigInteger pMinusOneOverQ = (p - 1) / q;
-                       for (;;) {
-                               BigInteger h = BigInteger.genRandom (keyLength);
-                               if ((h <= 1) || (h >= (p - 1)))
-                                       continue;
-
-                               g = h.modPow (pMinusOneOverQ, p);
-                               if (g <= 1)
-                                       continue;
-                               break;
-                       }
-
-                       this.seed = new BigInteger (seed);
-                       j = (p - 1) / q;
                }
 
-               [MonoTODO()]
-               private bool Validate ()
+               ~DSACryptoServiceProvider ()
                {
-                       // J is optional
-                       bool okJ = ((j == 0) || (j == ((p - 1) / q)));
-                       // TODO: Validate the key parameters (P, Q, G, J) using the Seed and Counter
-                       return okJ;
+                       Dispose (false);
                }
 
                // DSA isn't used for key exchange
                public override string KeyExchangeAlgorithm {
-                       get { return ""; }
+                       get { return null; }
                }
 
                public override int KeySize {
-                       get { return p.bitCount (); }
+                       get { return dsa.KeySize; }
                }
 
+#if !NET_2_0
                public override KeySizes[] LegalKeySizes {
                        get { return LegalKeySizesValue; }
                }
+#endif
+
+               public bool PersistKeyInCsp {
+                       get { return persistKey; }
+                       set { persistKey = value; }
+               }
+
+#if NET_2_0
+               [ComVisible (false)]
+               public 
+#else
+               internal
+#endif
+               bool PublicOnly {
+                       get { return dsa.PublicOnly; }
+               }
 
                public override string SignatureAlgorithm {
                        get { return "http://www.w3.org/2000/09/xmldsig#dsa-sha1"; }
                }
 
-               [MonoTODO("Persistance")]
-               public bool PersistKeyInCsp {
-                       get { return persistKey; }
-                       set {
-                               persistKey = value;
-                               // FIXME: We'll need this to support some kind of persistance
-                               if (value)
-                                       throw new NotSupportedException ("CspParameters not supported");
-                       }
+#if NET_1_1
+               private static bool useMachineKeyStore = false;
+
+               public static bool UseMachineKeyStore {
+                       get { return useMachineKeyStore; }
+                       set { useMachineKeyStore = value; }
                }
+#endif
 
-               protected override void Dispose (bool disposing)
+               public override DSAParameters ExportParameters (bool includePrivateParameters) 
                {
-                       if (!m_disposed) {
-                               // TODO: always zeroize private key
-                               if(disposing) {
-                                       // TODO: Dispose managed resources
-                               }
-
-                               // TODO: Dispose unmanaged resources
+                       if ((includePrivateParameters) && (!privateKeyExportable)) {
+                               throw new CryptographicException (
+                                       Locale.GetText ("Cannot export private key"));
                        }
-                       // call base class
-                       // no need as they all are abstract before us
-                       m_disposed = true;
+
+                       return dsa.ExportParameters (includePrivateParameters);
+               }
+
+               public override void ImportParameters (DSAParameters parameters) 
+               {
+                       dsa.ImportParameters (parameters);
                }
 
                public override byte[] CreateSignature (byte[] rgbHash)
                {
-                       return SignHash (rgbHash, "SHA1");
+                       return dsa.CreateSignature (rgbHash);
                }
 
                public byte[] SignData (byte[] data)
                {
-                       return SignData (data, 0, data.Length);
+                       // right now only SHA1 is supported by FIPS186-2
+                       HashAlgorithm hash = SHA1.Create ();
+                       byte[] toBeSigned = hash.ComputeHash (data);
+                       return dsa.CreateSignature (toBeSigned);
                }
 
                public byte[] SignData (byte[] data, int offset, int count)
@@ -287,7 +194,7 @@ namespace System.Security.Cryptography {
                        // right now only SHA1 is supported by FIPS186-2
                        HashAlgorithm hash = SHA1.Create ();
                        byte[] toBeSigned = hash.ComputeHash (data, offset, count);
-                       return SignHash (toBeSigned, "SHA1");
+                       return dsa.CreateSignature (toBeSigned);
                }
 
                public byte[] SignData (Stream inputStream)
@@ -295,181 +202,96 @@ namespace System.Security.Cryptography {
                        // right now only SHA1 is supported by FIPS186-2
                        HashAlgorithm hash = SHA1.Create ();
                        byte[] toBeSigned = hash.ComputeHash (inputStream);
-                       return SignHash (toBeSigned, "SHA1");
+                       return dsa.CreateSignature (toBeSigned);
                }
 
                public byte[] SignHash (byte[] rgbHash, string str)
                {
-                       if (rgbHash == null)
-                               throw new ArgumentNullException ();
-                       if (x.ToString() == "0")
-                               throw new CryptographicException ("no private key available for signature");
                        // right now only SHA1 is supported by FIPS186-2
-                       if (str.ToUpper () != "SHA1")
-                               throw new Exception (); // not documented
-                       if (rgbHash.Length != 20)
-                               throw new Exception (); // not documented
-
-                       if (!keypairGenerated)
-                               Generate ();
-       
-                       BigInteger m = new BigInteger (rgbHash);
-                       // (a) Select a random secret integer k; 0 < k < q.
-                       BigInteger k = BigInteger.genRandom(160);
-                       while (k >= q)
-                               k.randomize();
-                       // (b) Compute r = (g^k mod p) mod q
-                       BigInteger r = (g.modPow (k, p)) % q;
-                       // (c) Compute k -1 mod q (e.g., using Algorithm 2.142).
-                       // (d) Compute s = k -1 fh(m) +arg mod q.
-                       BigInteger s = (k.modInverse (q) * (m + x * r)) % q;
-                       // (e) A\19s signature for m is the pair (r; s).
-                       byte[] signature = new byte [40];
-                       byte[] part1 = r.getBytes ();
-                       byte[] part2 = s.getBytes ();
-                       // note: sometime (1/256) we may get less than 20 bytes (if first is 00)
-                       int start = 20 - part1.Length;
-                       Array.Copy (part1, 0, signature, start, part1.Length);
-                       start = 40 - part2.Length;
-                       Array.Copy (part2, 0, signature, start, part2.Length);
-                       return signature;
+                       if (String.Compare (str, "SHA1", true, CultureInfo.InvariantCulture) != 0) {
+                               // not documented
+                               throw new CryptographicException (Locale.GetText ("Only SHA1 is supported."));
+                       }
+
+                       return dsa.CreateSignature (rgbHash);
                }
 
                public bool VerifyData (byte[] rgbData, byte[] rgbSignature)
                {
-                       // signature is always 40 bytes (no matter the size of the
-                       // public key). In fact it is 2 times the size of the private
-                       // key (which is 20 bytes for 512 to 1024 bits DSA keypairs)
-                       if (rgbSignature.Length != 40)
-                               throw new Exception(); // not documented
                        // right now only SHA1 is supported by FIPS186-2
                        HashAlgorithm hash = SHA1.Create();
                        byte[] toBeVerified = hash.ComputeHash (rgbData);
-                       return VerifyHash (toBeVerified, "SHA1", rgbSignature);
+                       return dsa.VerifySignature (toBeVerified, rgbSignature);
                }
 
                // LAMESPEC: MD5 isn't allowed with DSA
                public bool VerifyHash (byte[] rgbHash, string str, byte[] rgbSignature)
                {
-                       if (rgbHash == null)
-                               throw new ArgumentNullException ("rgbHash");
-                       if (rgbSignature == null)
-                               throw new ArgumentNullException ("rgbSignature");
                        if (str == null)
                                str = "SHA1"; // default value
-                       if (str != "SHA1")
-                               throw new CryptographicException ();
-
-                       // it would be stupid to verify a signature with a newly
-                       // generated keypair - so we return false
-                       if (!keypairGenerated)
-                               return false;
-
-                       try {
-                               BigInteger m = new BigInteger (rgbHash);
-                               byte[] half = new byte [20];
-                               Array.Copy (rgbSignature, 0, half, 0, 20);
-                               BigInteger r = new BigInteger (half);
-                               Array.Copy (rgbSignature, 20, half, 0, 20);
-                               BigInteger s = new BigInteger (half);
-
-                               if ((r < 0) || (q <= r))
-                                       return false;
-
-                               if ((s < 0) || (q <= s))
-                                       return false;
-
-                               BigInteger w = s.modInverse(q);
-                               BigInteger u1 = m * w % q;
-                               BigInteger u2 = r * w % q;
-
-                               u1 = g.modPow(u1, p);
-                               u2 = y.modPow(u2, p);
-
-                               BigInteger v = ((u1 * u2 % p) % q);
-                               return (v == r);
-                       }
-                       catch {
-                               throw new CryptographicException ();
+                       if (String.Compare (str, "SHA1", true, CultureInfo.InvariantCulture) != 0) {
+                               throw new CryptographicException (Locale.GetText ("Only SHA1 is supported."));
                        }
+
+                       return dsa.VerifySignature (rgbHash, rgbSignature);
                }
 
-               public override bool VerifySignature(byte[] rgbHash, byte[] rgbSignature)
+               public override bool VerifySignature (byte[] rgbHash, byte[] rgbSignature)
                {
-                       return VerifyHash (rgbHash, "SHA1", rgbSignature);
+                       return dsa.VerifySignature (rgbHash, rgbSignature);
                }
 
-               private byte[] NormalizeArray (byte[] array)
+               protected override void Dispose (bool disposing) 
                {
-                       int n = (array.Length % 4);
-                       if (n > 0) {
-                               byte[] temp = new byte [array.Length + 4 - n];
-                               Array.Copy (array, 0, temp, (4 - n), array.Length);
-                               return temp;
+                       if (!m_disposed) {
+                               // the key is persisted and we do not want it persisted
+                               if ((persisted) && (!persistKey)) {
+                                       store.Remove ();        // delete the container
+                               }
+                               if (dsa != null)
+                                       dsa.Clear ();
+                               // call base class 
+                               // no need as they all are abstract before us
+                               m_disposed = true;
                        }
-                       else
-                               return array;
                }
 
-               public override DSAParameters ExportParameters (bool includePrivateParameters)
+               // private stuff
+
+               private void OnKeyGenerated (object sender, EventArgs e) 
                {
-                       if ((includePrivateParameters) && (!privateKeyExportable))
-                               throw new CryptographicException ("cannot export private key");
-                       if (!keypairGenerated)
-                               Generate ();
-       
-                       DSAParameters param = new DSAParameters();
-                       // all parameters must be in multiple of 4 bytes arrays
-                       // this isn't (generally) a problem for most of the parameters
-                       // except for J (but we won't take a chance)
-                       param.P = NormalizeArray (p.getBytes ());
-                       param.Q = NormalizeArray (q.getBytes ());
-                       param.G = NormalizeArray (g.getBytes ());
-                       param.Y = NormalizeArray (y.getBytes ());
-                       param.J = NormalizeArray (j.getBytes ());
-                       if (seed != 0) {
-                               param.Seed = NormalizeArray (seed.getBytes ());
-                               param.Counter = counter;
-                       }
-                       if (includePrivateParameters) {
-                               byte[] privateKey = x.getBytes ();
-                               if (privateKey.Length == 20) {
-                                       param.X = NormalizeArray (privateKey);
-                               }
+                       // the key isn't persisted and we want it persisted
+                       if ((persistKey) && (!persisted)) {
+                               // save the current keypair
+                               store.KeyValue = this.ToXmlString (!dsa.PublicOnly);
+                               store.Save ();
+                               persisted = true;
                        }
-                       return param;
                }
+#if NET_2_0
+               // ICspAsymmetricAlgorithm
 
-               public override void ImportParameters (DSAParameters parameters)
+               [MonoTODO ("call into KeyPairPersistence to get details")]
+               [ComVisible (false)]
+               public CspKeyContainerInfo CspKeyContainerInfo {
+                       get { return null; }
+               }
+
+               [MonoTODO ("call into CryptoConvert (doesn't currently support DSA)")]
+               [ComVisible (false)]
+               public byte[] ExportCspBlob (bool includePrivateParameters)
                {
-                       // if missing "mandatory" parameters
-                       if ((parameters.P == null) || (parameters.Q == null) || (parameters.G == null) || (parameters.Y == null))
-                               throw new CryptographicException ();
-
-                       p = new BigInteger (parameters.P);
-                       q = new BigInteger (parameters.Q);
-                       g = new BigInteger (parameters.G);
-                       y = new BigInteger (parameters.Y);
-                       // optional parameter - private key
-                       if (parameters.X != null)
-                               x = new BigInteger (parameters.X);
-                       else
-                               x = 0;
-                       // optional parameter - pre-computation
-                       if (parameters.J != null)
-                               j = new BigInteger (parameters.J);
-                       else
-                               j = (p - 1) / q;
-                       // optional - seed and counter must both be present (or absent)
-                       if (parameters.Seed != null) {
-                               seed = new BigInteger (parameters.Seed);
-                               counter = parameters.Counter;
-                       }
-                       else
-                               seed = 0;
+                       throw new NotImplementedException ("CryptoConvert doesn't currently support DSA");
+               }
 
-                       // we now have a keypair
-                       keypairGenerated = true;
+               [MonoTODO ("call into CryptoConvert (doesn't currently support DSA)")]
+               [ComVisible (false)]
+               public void ImportCspBlob (byte[] rawData)
+               {
+                       if (rawData == null)
+                               throw new ArgumentNullException ("rawData");
+                       throw new NotImplementedException ("CryptoConvert doesn't currently support DSA");
                }
+#endif
        }
 }