//
-// RSACryptoServiceProvider.cs: Handles an RSA implementations.
+// RSACryptoServiceProvider.cs: Handles an RSA implementation.
//
-// Author:
-// Sebastien Pouliot (spouliot@motus.com)
+// Authors:
+// Sebastien Pouliot <sebastien@ximian.com>
+// Ben Maurer (bmaurer@users.sf.net)
//
-// (C) 2002 Motus Technologies Inc. (http://www.motus.com)
+// (C) 2002, 2003 Motus Technologies Inc. (http://www.motus.com)
+// Portions (C) 2003 Ben Maurer
+// (C) 2004 Novell (http://www.novell.com)
//
using System;
using System.IO;
-namespace System.Security.Cryptography {
-
-public class RSACryptoServiceProvider : RSA {
- private bool privateKeyExportable = false; // secure by default
- private bool m_disposed = false;
-
- public RSACryptoServiceProvider ()
- {
- // if no default, create a 1024 bits keypair
- GenerateKeyPair (1024);
- }
-
- // FIXME: We currently dont link with MS CAPI. Anyway this makes
- // only sense in Windows - what do we do elsewhere ?
- public RSACryptoServiceProvider (CspParameters parameters)
- {
- throw new NotSupportedException ("CspParameters not supported");
- }
-
- // Microsoft RSA CSP can do between 512 and 16384 bits keypair
- public RSACryptoServiceProvider (int dwKeySize)
- {
- GenerateKeyPair (dwKeySize);
- }
-
- // FIXME: We currently dont link with MS CAPI. Anyway this makes
- // only sense in Windows - what do we do elsewhere ?
- // Note: Microsoft RSA CSP can do between 512 and 16384 bits keypair
- public RSACryptoServiceProvider (int dwKeySize, CspParameters parameters)
- {
- throw new NotSupportedException ("CspParameters not supported");
- }
-
- private void GenerateKeyPair (int dwKeySize)
- {
- LegalKeySizesValue = new KeySizes [1];
- KeySizes ks = new KeySizes (384, 16384, 8);
- LegalKeySizes[0] = ks;
- KeySizeValue = 1024; // default
- // TODO
- }
-
- // Zeroize private key
- ~RSACryptoServiceProvider()
- {
- Dispose (false);
- }
-
- public override string KeyExchangeAlgorithm {
- get { return "RSA-PKCS1-KeyEx"; }
- }
-
- public override int KeySize {
- get { return 0; }
- }
-
- public bool PersistKeyInCsp {
- get { return false; }
- set { ; }
- }
+using Mono.Math;
+using Mono.Security.Cryptography;
- public override string SignatureAlgorithm {
- get { return "http://www.w3.org/2000/09/xmldsig#rsa-sha1"; }
- }
-
- public byte[] Decrypt (byte[] rgb, bool fOAEP)
- {
- if ((fOAEP) && (rgb.Length > (KeySize >> 3) - 11))
- throw new CryptographicException ();
- // choose between OAEP or PKCS#1 v.1.5 padding
- return null;
- }
-
- // NOTE: Unlike MS we need this method
- // LAMESPEC: Not available from MS .NET framework but MS don't tell
- // why! DON'T USE IT UNLESS YOU KNOW WHAT YOU ARE DOING!!! You should
- // only encrypt/decrypt session (secret) key using asymmetric keys.
- // Using this method to decrypt data IS dangerous (and very slow).
- [MonoTODO()]
- public override byte[] DecryptValue (byte[] rgb)
- {
- return null;
- }
-
- [MonoTODO()]
- public byte[] Encrypt (byte[] rgb, bool fOAEP)
- {
- if ((fOAEP) && (rgb.Length > (KeySize >> 3) - 11))
- throw new CryptographicException ();
- // choose between OAEP or PKCS#1 v.1.5 padding
- return null;
- }
-
- // NOTE: Unlike MS we need this method
- // LAMESPEC: Not available from MS .NET framework but MS don't tell
- // why! DON'T USE IT UNLESS YOU KNOW WHAT YOU ARE DOING!!! You should
- // only encrypt/decrypt session (secret) key using asymmetric keys.
- // Using this method to encrypt data IS dangerous (and very slow).
- [MonoTODO()]
- public override byte[] EncryptValue (byte[] rgb)
- {
- return null;
- }
-
- [MonoTODO()]
- public override RSAParameters ExportParameters (bool includePrivateParameters)
- {
- if ((includePrivateParameters) && (!privateKeyExportable))
- throw new CryptographicException ("cannot export private key");
- RSAParameters p = new RSAParameters();
- return p;
- }
-
- [MonoTODO()]
- public override void ImportParameters (RSAParameters parameters)
- {
- // if missing parameters
- // throw new CryptographicException ();
- }
-
- private HashAlgorithm GetHash (object halg)
- {
- if (halg == null)
- throw new ArgumentNullException ();
-
- HashAlgorithm hash = null;
- if (halg is String)
- hash = HashAlgorithm.Create ((String)halg);
- else if (halg is HashAlgorithm)
- hash = (HashAlgorithm) halg;
- else if (halg is Type)
- hash = (HashAlgorithm) Activator.CreateInstance ((Type)halg);
- else
- throw new ArgumentException ();
-
- return hash;
- }
-
- // better to send OID ?
- private string GetHashName (HashAlgorithm hash)
- {
- string str = null;
- if (hash is SHA1)
- str = "SHA1";
- else if (hash is MD5)
- str = "MD5";
- return str;
- }
+namespace System.Security.Cryptography {
- public byte[] SignData (byte[] buffer, object halg)
- {
- return SignData (buffer, 0, buffer.Length, halg);
- }
+ public sealed class RSACryptoServiceProvider : RSA {
+
+ private const int PROV_RSA_FULL = 1; // from WinCrypt.h
+
+ private KeyPairPersistence store;
+ private bool persistKey;
+ private bool persisted;
+
+ private bool privateKeyExportable = true;
+ private bool m_disposed;
+
+ private RSAManaged rsa;
+
+ public RSACryptoServiceProvider ()
+ {
+ // 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 RSA keypair
+ // 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 RSACryptoServiceProvider (CspParameters parameters)
+ {
+ Common (1024, parameters);
+ // no keypair generation done at this stage
+ }
+
+ public RSACryptoServiceProvider (int dwKeySize)
+ {
+ // Here it's clear that we need to generate a new keypair
+ Common (dwKeySize, null);
+ // no keypair generation done at this stage
+ }
+
+ public RSACryptoServiceProvider (int dwKeySize, CspParameters parameters)
+ {
+ Common (dwKeySize, parameters);
+ // no keypair generation done at this stage
+ }
+
+ private void Common (int dwKeySize, CspParameters p)
+ {
+ // Microsoft RSA CSP can do between 384 and 16384 bits keypair
+ LegalKeySizesValue = new KeySizes [1];
+ LegalKeySizesValue [0] = new KeySizes (384, 16384, 8);
+ base.KeySize = dwKeySize;
+
+ rsa = new RSAManaged (KeySize);
+ rsa.KeyGenerated += new RSAManaged.KeyGeneratedEventHandler (OnKeyGenerated);
+
+ persistKey = (p != null);
+ if (p == null) {
+ p = new CspParameters (PROV_RSA_FULL);
+#if ! NET_1_0
+ if (useMachineKeyStore)
+ p.Flags |= CspProviderFlags.UseMachineKeyStore;
+#endif
+ store = new KeyPairPersistence (p);
+ // no need to load - it cannot exists
+ }
+ else {
+ store = new KeyPairPersistence (p);
+ store.Load ();
+ if (store.KeyValue != null) {
+ persisted = true;
+ this.FromXmlString (store.KeyValue);
+ }
+ }
+ }
- public byte[] SignData (Stream inputStream, object halg)
- {
- HashAlgorithm hash = GetHash (halg);
- byte[] toBeSigned = hash.ComputeHash (inputStream);
+#if ! NET_1_0
+ private static bool useMachineKeyStore = false;
- string str = GetHashName (hash);
- return SignHash (toBeSigned, str);
- }
+ public static bool UseMachineKeyStore {
+ get { return useMachineKeyStore; }
+ set { useMachineKeyStore = value; }
+ }
+#endif
+
+ ~RSACryptoServiceProvider ()
+ {
+ // Zeroize private key
+ Dispose (false);
+ }
+
+ public override string KeyExchangeAlgorithm {
+ get { return "RSA-PKCS1-KeyEx"; }
+ }
+
+ public override int KeySize {
+ get {
+ if (rsa == null)
+ return KeySizeValue;
+ else
+ return rsa.KeySize;
+ }
+ }
- public byte[] SignData (byte[] buffer, int offset, int count, object halg)
- {
- HashAlgorithm hash = GetHash (halg);
- byte[] toBeSigned = hash.ComputeHash (buffer, offset, count);
- string str = GetHashName (hash);
- return SignHash (toBeSigned, str);
- }
+ public bool PersistKeyInCsp {
+ get { return persistKey; }
+ set {
+ persistKey = value;
+ if (persistKey)
+ OnKeyGenerated (rsa);
+ }
+ }
- [MonoTODO()]
- public byte[] SignHash (byte[] rgbHash, string str)
- {
- if (rgbHash == null)
- throw new ArgumentNullException ();
+#if (NET_1_0 || NET_1_1)
+ internal
+#else
+ public
+#endif
+ bool PublicOnly {
+ get { return rsa.PublicOnly; }
+ }
+
+ public override string SignatureAlgorithm {
+ get { return "http://www.w3.org/2000/09/xmldsig#rsa-sha1"; }
+ }
+
+ public byte[] Decrypt (byte[] rgb, bool fOAEP)
+ {
+#if NET_1_1
+ if (m_disposed)
+ throw new ObjectDisposedException ("rsa");
+#endif
+ // choose between OAEP or PKCS#1 v.1.5 padding
+ AsymmetricKeyExchangeDeformatter def = null;
+ if (fOAEP)
+ def = new RSAOAEPKeyExchangeDeformatter (rsa);
+ else
+ def = new RSAPKCS1KeyExchangeDeformatter (rsa);
+
+ return def.DecryptKeyExchange (rgb);
+ }
+
+ // NOTE: Unlike MS we need this method
+ // LAMESPEC: Not available from MS .NET framework but MS don't tell
+ // why! DON'T USE IT UNLESS YOU KNOW WHAT YOU ARE DOING!!! You should
+ // only encrypt/decrypt session (secret) key using asymmetric keys.
+ // Using this method to decrypt data IS dangerous (and very slow).
+ public override byte[] DecryptValue (byte[] rgb)
+ {
+ return rsa.DecryptValue (rgb);
+ }
+
+ public byte[] Encrypt (byte[] rgb, bool fOAEP)
+ {
+ // choose between OAEP or PKCS#1 v.1.5 padding
+ AsymmetricKeyExchangeFormatter fmt = null;
+ if (fOAEP)
+ fmt = new RSAOAEPKeyExchangeFormatter (rsa);
+ else
+ fmt = new RSAPKCS1KeyExchangeFormatter (rsa);
+
+ return fmt.CreateKeyExchange (rgb);
+ }
+
+ // NOTE: Unlike MS we need this method
+ // LAMESPEC: Not available from MS .NET framework but MS don't tell
+ // why! DON'T USE IT UNLESS YOU KNOW WHAT YOU ARE DOING!!! You should
+ // only encrypt/decrypt session (secret) key using asymmetric keys.
+ // Using this method to encrypt data IS dangerous (and very slow).
+ public override byte[] EncryptValue (byte[] rgb)
+ {
+ return rsa.EncryptValue (rgb);
+ }
+
+ public override RSAParameters ExportParameters (bool includePrivateParameters)
+ {
+ if ((includePrivateParameters) && (!privateKeyExportable))
+ throw new CryptographicException ("cannot export private key");
- if ((str == null) || (str == "SHA1")) {
- if (rgbHash.Length != 20)
- throw new CryptographicException ("wrong hash size for SHA1");
+ return rsa.ExportParameters (includePrivateParameters);
+ }
+
+ public override void ImportParameters (RSAParameters parameters)
+ {
+ rsa.ImportParameters (parameters);
+ }
+
+ private HashAlgorithm GetHash (object halg)
+ {
+ if (halg == null)
+ throw new ArgumentNullException ("halg");
+
+ HashAlgorithm hash = null;
+ if (halg is String)
+ hash = HashAlgorithm.Create ((String)halg);
+ else if (halg is HashAlgorithm)
+ hash = (HashAlgorithm) halg;
+ else if (halg is Type)
+ hash = (HashAlgorithm) Activator.CreateInstance ((Type)halg);
+ else
+ throw new ArgumentException ("halg");
+
+ return hash;
}
- else if (str == "MD5") {
- if (rgbHash.Length != 16)
- throw new CryptographicException ("wrong hash size for MD5");
+
+ // NOTE: this method can work with ANY configured (OID in machine.config)
+ // HashAlgorithm descendant
+ public byte[] SignData (byte[] buffer, object halg)
+ {
+#if NET_1_1
+ if (buffer == null)
+ throw new ArgumentNullException ("buffer");
+#endif
+ return SignData (buffer, 0, buffer.Length, halg);
+ }
+
+ // NOTE: this method can work with ANY configured (OID in machine.config)
+ // HashAlgorithm descendant
+ public byte[] SignData (Stream inputStream, object halg)
+ {
+ HashAlgorithm hash = GetHash (halg);
+ byte[] toBeSigned = hash.ComputeHash (inputStream);
+ return PKCS1.Sign_v15 (this, hash, toBeSigned);
+ }
+
+ // NOTE: this method can work with ANY configured (OID in machine.config)
+ // HashAlgorithm descendant
+ public byte[] SignData (byte[] buffer, int offset, int count, object halg)
+ {
+ HashAlgorithm hash = GetHash (halg);
+ byte[] toBeSigned = hash.ComputeHash (buffer, offset, count);
+ return PKCS1.Sign_v15 (this, hash, toBeSigned);
+ }
+
+ private string GetHashNameFromOID (string oid)
+ {
+ switch (oid) {
+ case "1.3.14.3.2.26":
+ return "SHA1";
+ case "1.2.840.113549.2.5":
+ return "MD5";
+ default:
+ throw new NotSupportedException (oid + " is an unsupported hash algorithm for RSA signing");
+ }
}
- else
- throw new NotSupportedException (str + " is an unsupported hash algorithm for RSA signing");
- return null;
- }
+ // LAMESPEC: str is not the hash name but an OID
+ // NOTE: this method is LIMITED to SHA1 and MD5 like the MS framework 1.0
+ // and 1.1 because there's no mathod to get a hash algorithm from an OID.
+ // However there's no such limit when using the [De]Formatter class.
+ public byte[] SignHash (byte[] rgbHash, string str)
+ {
+ if (rgbHash == null)
+ throw new ArgumentNullException ("rgbHash");
+
+ HashAlgorithm hash = HashAlgorithm.Create (GetHashNameFromOID (str));
+ return PKCS1.Sign_v15 (this, hash, rgbHash);
+ }
- public bool VerifyData (byte[] buffer, object halg, byte[] signature)
- {
- HashAlgorithm hash = GetHash (halg);
- byte[] toBeVerified = hash.ComputeHash (buffer);
- string str = GetHashName (hash);
- return VerifyHash (toBeVerified, str, signature);
- }
+ // NOTE: this method can work with ANY configured (OID in machine.config)
+ // HashAlgorithm descendant
+ public bool VerifyData (byte[] buffer, object halg, byte[] signature)
+ {
+#if NET_1_1
+ if (buffer == null)
+ throw new ArgumentNullException ("buffer");
+#endif
+ if (signature == null)
+ throw new ArgumentNullException ("signature");
+
+ HashAlgorithm hash = GetHash (halg);
+ byte[] toBeVerified = hash.ComputeHash (buffer);
+ return PKCS1.Verify_v15 (this, hash, toBeVerified, signature);
+ }
+
+ // LAMESPEC: str is not the hash name but an OID
+ // NOTE: this method is LIMITED to SHA1 and MD5 like the MS framework 1.0
+ // and 1.1 because there's no method to get a hash algorithm from an OID.
+ // However there's no such limit when using the [De]Formatter class.
+ public bool VerifyHash (byte[] rgbHash, string str, byte[] rgbSignature)
+ {
+ if (rgbHash == null)
+ throw new ArgumentNullException ("rgbHash");
+ if (rgbSignature == null)
+ throw new ArgumentNullException ("rgbSignature");
+
+ HashAlgorithm hash = HashAlgorithm.Create (GetHashNameFromOID (str));
+ return PKCS1.Verify_v15 (this, hash, rgbHash, rgbSignature);
+ }
+
+ protected override void Dispose (bool disposing)
+ {
+ if (!m_disposed) {
+ // the key is persisted and we do not want it persisted
+ if ((persisted) && (!persistKey)) {
+ store.Remove (); // delete the container
+ }
+ if (rsa != null)
+ rsa.Clear ();
+ // call base class
+ // no need as they all are abstract before us
+ m_disposed = true;
+ }
+ }
- [MonoTODO()]
- public bool VerifyHash (byte[] rgbHash, string str, byte[] rgbSignature)
- {
- if ((rgbHash == null) || (rgbSignature == null))
- throw new ArgumentNullException ();
- // TODO
- return false;
- }
+ // private stuff
- [MonoTODO()]
- protected override void Dispose (bool disposing)
- {
- if (!m_disposed) {
- // TODO: always zeroize private key
- if(disposing) {
- // TODO: Dispose managed resources
+ private void OnKeyGenerated (object sender)
+ {
+ // the key isn't persisted and we want it persisted
+ if ((persistKey) && (!persisted)) {
+ // save the current keypair
+ store.KeyValue = this.ToXmlString (!rsa.PublicOnly);
+ store.Save ();
+ persisted = true;
}
-
- // TODO: Dispose unmanaged resources
}
- // call base class
- // no need as they all are abstract before us
- m_disposed = true;
}
}
-
-}