bring Mono Security to monotouch
[mono.git] / mcs / class / corlib / System.Security.Cryptography / RSACryptoServiceProvider.cs
1 //
2 // RSACryptoServiceProvider.cs: Handles an RSA implementation.
3 //
4 // Authors:
5 //      Sebastien Pouliot <sebastien@ximian.com>
6 //      Ben Maurer (bmaurer@users.sf.net)
7 //
8 // (C) 2002, 2003 Motus Technologies Inc. (http://www.motus.com)
9 // Portions (C) 2003 Ben Maurer
10 // Copyright (C) 2004-2005 Novell, Inc (http://www.novell.com)
11 //
12 // Permission is hereby granted, free of charge, to any person obtaining
13 // a copy of this software and associated documentation files (the
14 // "Software"), to deal in the Software without restriction, including
15 // without limitation the rights to use, copy, modify, merge, publish,
16 // distribute, sublicense, and/or sell copies of the Software, and to
17 // permit persons to whom the Software is furnished to do so, subject to
18 // the following conditions:
19 // 
20 // The above copyright notice and this permission notice shall be
21 // included in all copies or substantial portions of the Software.
22 // 
23 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
24 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
25 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
26 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
27 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
28 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
29 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
30 //
31
32 #if !NET_2_1 || MONOTOUCH
33
34 using System.IO;
35 using System.Runtime.InteropServices;
36
37 using Mono.Security.Cryptography;
38
39 namespace System.Security.Cryptography {
40
41 #if NET_2_0
42         [ComVisible (true)]
43         public sealed class RSACryptoServiceProvider : RSA, ICspAsymmetricAlgorithm {
44 #else
45         public sealed class RSACryptoServiceProvider : RSA {
46 #endif
47                 private const int PROV_RSA_FULL = 1;    // from WinCrypt.h
48
49                 private KeyPairPersistence store;
50                 private bool persistKey;
51                 private bool persisted;
52         
53                 private bool privateKeyExportable = true; 
54                 private bool m_disposed;
55
56                 private RSAManaged rsa;
57         
58                 public RSACryptoServiceProvider ()
59                 {
60                         // Here it's not clear if we need to generate a keypair
61                         // (note: MS implementation generates a keypair in this case).
62                         // However we:
63                         // (a) often use this constructor to import an existing keypair.
64                         // (b) take a LOT of time to generate the RSA keypair
65                         // So we'll generate the keypair only when (and if) it's being
66                         // used (or exported). This should save us a lot of time (at 
67                         // least in the unit tests).
68                         Common (1024, null);
69                 }
70         
71                 public RSACryptoServiceProvider (CspParameters parameters) 
72                 {
73                         Common (1024, parameters);
74                         // no keypair generation done at this stage
75                 }
76         
77                 public RSACryptoServiceProvider (int dwKeySize) 
78                 {
79                         // Here it's clear that we need to generate a new keypair
80                         Common (dwKeySize, null);
81                         // no keypair generation done at this stage
82                 }
83         
84                 public RSACryptoServiceProvider (int dwKeySize, CspParameters parameters) 
85                 {
86                         Common (dwKeySize, parameters);
87                         // no keypair generation done at this stage
88                 }
89         
90                 private void Common (int dwKeySize, CspParameters p) 
91                 {
92                         // Microsoft RSA CSP can do between 384 and 16384 bits keypair
93                         LegalKeySizesValue = new KeySizes [1];
94                         LegalKeySizesValue [0] = new KeySizes (384, 16384, 8);
95                         base.KeySize = dwKeySize;
96
97                         rsa = new RSAManaged (KeySize);
98                         rsa.KeyGenerated += new RSAManaged.KeyGeneratedEventHandler (OnKeyGenerated);
99
100                         persistKey = (p != null);
101                         if (p == null) {
102                                 p = new CspParameters (PROV_RSA_FULL);
103 #if NET_1_1
104                                 if (useMachineKeyStore)
105                                         p.Flags |= CspProviderFlags.UseMachineKeyStore;
106 #endif
107                                 store = new KeyPairPersistence (p);
108                                 // no need to load - it cannot exists
109                         }
110                         else {
111                                 store = new KeyPairPersistence (p);
112                                 store.Load ();
113                                 if (store.KeyValue != null) {
114                                         persisted = true;
115                                         this.FromXmlString (store.KeyValue);
116                                 }
117                         }
118                 }
119
120 #if NET_1_1
121                 private static bool useMachineKeyStore = false;
122
123                 public static bool UseMachineKeyStore {
124                         get { return useMachineKeyStore; }
125                         set { useMachineKeyStore = value; }
126                 }
127 #endif
128         
129                 ~RSACryptoServiceProvider () 
130                 {
131                         // Zeroize private key
132                         Dispose (false);
133                 }
134         
135                 public override string KeyExchangeAlgorithm {
136                         get { return "RSA-PKCS1-KeyEx"; }
137                 }
138         
139                 public override int KeySize {
140                         get { 
141                                 if (rsa == null)
142                                       return KeySizeValue; 
143                                 else
144                                       return rsa.KeySize;
145                         }
146                 }
147
148                 public bool PersistKeyInCsp {
149                         get { return persistKey; }
150                         set {
151                                 persistKey = value;
152                                 if (persistKey)
153                                         OnKeyGenerated (rsa, null);
154                         }
155                 }
156
157 #if (NET_2_0)
158                 [ComVisible (false)]
159                 public 
160 #else
161                 internal
162 #endif
163                 bool PublicOnly {
164                         get { return rsa.PublicOnly; }
165                 }
166         
167                 public override string SignatureAlgorithm {
168                         get { return "http://www.w3.org/2000/09/xmldsig#rsa-sha1"; }
169                 }
170         
171                 public byte[] Decrypt (byte[] rgb, bool fOAEP) 
172                 {
173 #if NET_1_1
174                         if (m_disposed)
175                                 throw new ObjectDisposedException ("rsa");
176 #endif
177                         // choose between OAEP or PKCS#1 v.1.5 padding
178                         AsymmetricKeyExchangeDeformatter def = null;
179                         if (fOAEP)
180                                 def = new RSAOAEPKeyExchangeDeformatter (rsa);
181                         else
182                                 def = new RSAPKCS1KeyExchangeDeformatter (rsa);
183
184                         return def.DecryptKeyExchange (rgb);
185                 }
186         
187                 // NOTE: Unlike MS we need this method
188                 // LAMESPEC: Not available from MS .NET framework but MS don't tell
189                 // why! DON'T USE IT UNLESS YOU KNOW WHAT YOU ARE DOING!!! You should
190                 // only encrypt/decrypt session (secret) key using asymmetric keys. 
191                 // Using this method to decrypt data IS dangerous (and very slow).
192                 public override byte[] DecryptValue (byte[] rgb) 
193                 {
194                         if (!rsa.IsCrtPossible)
195                                 throw new CryptographicException ("Incomplete private key - missing CRT.");
196
197                         return rsa.DecryptValue (rgb);
198                 }
199         
200                 public byte[] Encrypt (byte[] rgb, bool fOAEP) 
201                 {
202                         // choose between OAEP or PKCS#1 v.1.5 padding
203                         AsymmetricKeyExchangeFormatter fmt = null;
204                         if (fOAEP)
205                                 fmt = new RSAOAEPKeyExchangeFormatter (rsa);
206                         else
207                                 fmt = new RSAPKCS1KeyExchangeFormatter (rsa);
208
209                         return fmt.CreateKeyExchange (rgb);
210                 }
211         
212                 // NOTE: Unlike MS we need this method
213                 // LAMESPEC: Not available from MS .NET framework but MS don't tell
214                 // why! DON'T USE IT UNLESS YOU KNOW WHAT YOU ARE DOING!!! You should
215                 // only encrypt/decrypt session (secret) key using asymmetric keys. 
216                 // Using this method to encrypt data IS dangerous (and very slow).
217                 public override byte[] EncryptValue (byte[] rgb) 
218                 {
219                         return rsa.EncryptValue (rgb);
220                 }
221         
222                 public override RSAParameters ExportParameters (bool includePrivateParameters) 
223                 {
224                         if ((includePrivateParameters) && (!privateKeyExportable))
225                                 throw new CryptographicException ("cannot export private key");
226
227                         return rsa.ExportParameters (includePrivateParameters);
228                 }
229         
230                 public override void ImportParameters (RSAParameters parameters) 
231                 {
232                         rsa.ImportParameters (parameters);
233                 }
234         
235                 private HashAlgorithm GetHash (object halg) 
236                 {
237                         if (halg == null)
238                                 throw new ArgumentNullException ("halg");
239
240                         HashAlgorithm hash = null;
241                         if (halg is String)
242                                 hash = HashAlgorithm.Create ((String)halg);
243                         else if (halg is HashAlgorithm)
244                                 hash = (HashAlgorithm) halg;
245                         else if (halg is Type)
246                                 hash = (HashAlgorithm) Activator.CreateInstance ((Type)halg);
247                         else
248                                 throw new ArgumentException ("halg");
249
250                         return hash;
251                 }
252         
253                 // NOTE: this method can work with ANY configured (OID in machine.config) 
254                 // HashAlgorithm descendant
255                 public byte[] SignData (byte[] buffer, object halg) 
256                 {
257 #if NET_1_1
258                         if (buffer == null)
259                                 throw new ArgumentNullException ("buffer");
260 #endif
261                         return SignData (buffer, 0, buffer.Length, halg);
262                 }
263         
264                 // NOTE: this method can work with ANY configured (OID in machine.config) 
265                 // HashAlgorithm descendant
266                 public byte[] SignData (Stream inputStream, object halg) 
267                 {
268                         HashAlgorithm hash = GetHash (halg);
269                         byte[] toBeSigned = hash.ComputeHash (inputStream);
270                         return PKCS1.Sign_v15 (this, hash, toBeSigned);
271                 }
272         
273                 // NOTE: this method can work with ANY configured (OID in machine.config) 
274                 // HashAlgorithm descendant
275                 public byte[] SignData (byte[] buffer, int offset, int count, object halg) 
276                 {
277                         HashAlgorithm hash = GetHash (halg);
278                         byte[] toBeSigned = hash.ComputeHash (buffer, offset, count);
279                         return PKCS1.Sign_v15 (this, hash, toBeSigned);
280                 }
281         
282                 private string GetHashNameFromOID (string oid) 
283                 {
284                         switch (oid) {
285                                 case "1.3.14.3.2.26":
286                                         return "SHA1";
287                                 case "1.2.840.113549.2.5":
288                                         return "MD5";
289                                 default:
290                                         throw new NotSupportedException (oid + " is an unsupported hash algorithm for RSA signing");
291                         }
292                 }
293
294                 // LAMESPEC: str is not the hash name but an OID
295                 // NOTE: this method is LIMITED to SHA1 and MD5 like the MS framework 1.0 
296                 // and 1.1 because there's no method to get a hash algorithm from an OID. 
297                 // However there's no such limit when using the [De]Formatter class.
298                 public byte[] SignHash (byte[] rgbHash, string str) 
299                 {
300                         if (rgbHash == null)
301                                 throw new ArgumentNullException ("rgbHash");
302 #if NET_2_0
303                         // Fx 2.0 defaults to the SHA-1
304                         string hashName = (str == null) ? "SHA1" : GetHashNameFromOID (str);
305 #else
306                         if (str == null)
307                                 throw new CryptographicException (Locale.GetText ("No OID specified"));
308                         string hashName = GetHashNameFromOID (str);
309 #endif
310                         HashAlgorithm hash = HashAlgorithm.Create (hashName);
311                         return PKCS1.Sign_v15 (this, hash, rgbHash);
312                 }
313
314                 // NOTE: this method can work with ANY configured (OID in machine.config) 
315                 // HashAlgorithm descendant
316                 public bool VerifyData (byte[] buffer, object halg, byte[] signature) 
317                 {
318 #if NET_1_1
319                         if (buffer == null)
320                                 throw new ArgumentNullException ("buffer");
321 #endif
322                         if (signature == null)
323                                 throw new ArgumentNullException ("signature");
324
325                         HashAlgorithm hash = GetHash (halg);
326                         byte[] toBeVerified = hash.ComputeHash (buffer);
327                         return PKCS1.Verify_v15 (this, hash, toBeVerified, signature);
328                 }
329         
330                 // LAMESPEC: str is not the hash name but an OID
331                 // NOTE: this method is LIMITED to SHA1 and MD5 like the MS framework 1.0 
332                 // and 1.1 because there's no method to get a hash algorithm from an OID. 
333                 // However there's no such limit when using the [De]Formatter class.
334                 public bool VerifyHash (byte[] rgbHash, string str, byte[] rgbSignature) 
335                 {
336                         if (rgbHash == null) 
337                                 throw new ArgumentNullException ("rgbHash");
338                         if (rgbSignature == null)
339                                 throw new ArgumentNullException ("rgbSignature");
340 #if NET_2_0
341                         // Fx 2.0 defaults to the SHA-1
342                         string hashName = (str == null) ? "SHA1" : GetHashNameFromOID (str);
343 #else
344                         if (str == null)
345                                 throw new CryptographicException (Locale.GetText ("No OID specified"));
346                         string hashName = GetHashNameFromOID (str);
347 #endif
348                         HashAlgorithm hash = HashAlgorithm.Create (hashName);
349                         return PKCS1.Verify_v15 (this, hash, rgbHash, rgbSignature);
350                 }
351         
352                 protected override void Dispose (bool disposing) 
353                 {
354                         if (!m_disposed) {
355                                 // the key is persisted and we do not want it persisted
356                                 if ((persisted) && (!persistKey)) {
357                                         store.Remove ();        // delete the container
358                                 }
359                                 if (rsa != null)
360                                         rsa.Clear ();
361                                 // call base class 
362                                 // no need as they all are abstract before us
363                                 m_disposed = true;
364                         }
365                 }
366
367                 // private stuff
368
369                 private void OnKeyGenerated (object sender, EventArgs e) 
370                 {
371                         // the key isn't persisted and we want it persisted
372                         if ((persistKey) && (!persisted)) {
373                                 // save the current keypair
374                                 store.KeyValue = this.ToXmlString (!rsa.PublicOnly);
375                                 store.Save ();
376                                 persisted = true;
377                         }
378                 }
379 #if NET_2_0
380                 // ICspAsymmetricAlgorithm
381
382                 [MonoTODO ("Always return null")]
383                 // FIXME: call into KeyPairPersistence to get details
384                 [ComVisible (false)]
385                 public CspKeyContainerInfo CspKeyContainerInfo {
386                         get { return null; }
387                 }
388
389                 [ComVisible (false)]
390                 public byte[] ExportCspBlob (bool includePrivateParameters)
391                 {
392                         byte[] blob = null;
393                         if (includePrivateParameters)
394                                 blob = CryptoConvert.ToCapiPrivateKeyBlob (this);
395                         else
396                                 blob = CryptoConvert.ToCapiPublicKeyBlob (this);
397
398                         // ALGID (bytes 4-7) - default is KEYX
399                         // 00 24 00 00 (for CALG_RSA_SIGN)
400                         // 00 A4 00 00 (for CALG_RSA_KEYX)
401                         blob [5] = 0xA4;
402                         return blob;
403                 }
404
405                 [ComVisible (false)]
406                 public void ImportCspBlob (byte[] keyBlob)
407                 {
408                         if (keyBlob == null)
409                                 throw new ArgumentNullException ("keyBlob");
410
411                         RSA rsa = CryptoConvert.FromCapiKeyBlob (keyBlob);
412                         if (rsa is RSACryptoServiceProvider) {
413                                 // default (if no change are present in machine.config)
414                                 RSAParameters rsap = rsa.ExportParameters (!(rsa as RSACryptoServiceProvider).PublicOnly);
415                                 ImportParameters (rsap);
416                         } else {
417                                 // we can't know from RSA if the private key is available
418                                 try {
419                                         // so we try it...
420                                         RSAParameters rsap = rsa.ExportParameters (true);
421                                         ImportParameters (rsap);
422                                 }
423                                 catch {
424                                         // and fall back
425                                         RSAParameters rsap = rsa.ExportParameters (false);
426                                         ImportParameters (rsap);
427                                 }
428                         }
429                 }
430 #endif
431         }
432 }
433
434 #endif
435