2005-01-31 Zoltan Varga <vargaz@freemail.hu>
[mono.git] / mcs / class / Mono.Security / Mono.Security.Cryptography / CryptoConvert.cs
1 //
2 // CryptoConvert.cs - Crypto Convertion Routines
3 //
4 // Author:
5 //      Sebastien Pouliot  <sebastien@ximian.com>
6 //
7 // (C) 2003 Motus Technologies Inc. (http://www.motus.com)
8 // (C) 2004 Novell (http://www.novell.com)
9 //
10
11 //
12 // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
13 //
14 // Permission is hereby granted, free of charge, to any person obtaining
15 // a copy of this software and associated documentation files (the
16 // "Software"), to deal in the Software without restriction, including
17 // without limitation the rights to use, copy, modify, merge, publish,
18 // distribute, sublicense, and/or sell copies of the Software, and to
19 // permit persons to whom the Software is furnished to do so, subject to
20 // the following conditions:
21 // 
22 // The above copyright notice and this permission notice shall be
23 // included in all copies or substantial portions of the Software.
24 // 
25 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
26 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
27 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
28 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
29 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
30 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
31 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
32 //
33
34 using System;
35 using System.Globalization;
36 using System.Security.Cryptography;
37 using System.Text;
38
39 namespace Mono.Security.Cryptography {
40
41 #if INSIDE_CORLIB
42         internal
43 #else
44         public
45 #endif
46         sealed class CryptoConvert {
47
48                 private CryptoConvert () 
49                 {
50                 }
51
52                 static private int ToInt32LE (byte [] bytes, int offset)
53                 {
54                         return (bytes [offset+3] << 24) | (bytes [offset+2] << 16) | (bytes [offset+1] << 8) | bytes [offset];
55                 }
56
57                 static private uint ToUInt32LE (byte [] bytes, int offset)
58                 {
59                         return (uint)((bytes [offset+3] << 24) | (bytes [offset+2] << 16) | (bytes [offset+1] << 8) | bytes [offset]);
60                 }
61
62                 static private byte [] GetBytesLE (int val)
63                 {
64                         return new byte [] { 
65                                 (byte) (val & 0xff), 
66                                 (byte) ((val >> 8) & 0xff), 
67                                 (byte) ((val >> 16) & 0xff), 
68                                 (byte) ((val >> 24) & 0xff)
69                         };
70                 }
71
72                 static private byte[] Trim (byte[] array) 
73                 {
74                         for (int i=0; i < array.Length; i++) {
75                                 if (array [i] != 0x00) {
76                                         byte[] result = new byte [array.Length - i];
77                                         Buffer.BlockCopy (array, i, result, 0, result.Length);
78                                         return result;
79                                 }
80                         }
81                         return null;
82                 }
83
84                 // convert the key from PRIVATEKEYBLOB to RSA
85                 // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/security/Security/private_key_blobs.asp
86                 // e.g. SNK files, PVK files
87                 static public RSA FromCapiPrivateKeyBlob (byte[] blob) 
88                 {
89                         return FromCapiPrivateKeyBlob (blob, 0);
90                 }
91
92                 static public RSA FromCapiPrivateKeyBlob (byte[] blob, int offset) 
93                 {
94                         if (blob == null)
95                                 throw new ArgumentNullException ("blob");
96                         if (offset >= blob.Length)
97                                 throw new ArgumentException ("blob is too small.");
98
99                         try {
100                                 if ((blob [offset]   != 0x07) ||                                // PRIVATEKEYBLOB (0x07)
101                                     (blob [offset+1] != 0x02) ||                                // Version (0x02)
102                                     (blob [offset+2] != 0x00) ||                                // Reserved (word)
103                                     (blob [offset+3] != 0x00) ||
104                                     (ToUInt32LE (blob, offset+8) != 0x32415352))        // DWORD magic = RSA2
105                                         throw new CryptographicException ("Invalid blob header");
106                                 
107                                 // ALGID (CALG_RSA_SIGN, CALG_RSA_KEYX, ...)
108                                 // int algId = ToInt32LE (blob, offset+4);
109
110                                 // DWORD bitlen
111                                 int bitLen = ToInt32LE (blob, offset+12);
112
113                                 // DWORD public exponent
114                                 RSAParameters rsap = new RSAParameters ();
115                                 byte[] exp = new byte [4];
116                                 Buffer.BlockCopy (blob, offset+16, exp, 0, 4);
117                                 Array.Reverse (exp);
118                                 rsap.Exponent = Trim (exp);
119                         
120                                 int pos = offset+20;
121                                 // BYTE modulus[rsapubkey.bitlen/8];
122                                 int byteLen = (bitLen >> 3);
123                                 rsap.Modulus = new byte [byteLen];
124                                 Buffer.BlockCopy (blob, pos, rsap.Modulus, 0, byteLen);
125                                 Array.Reverse (rsap.Modulus);
126                                 pos += byteLen;
127
128                                 // BYTE prime1[rsapubkey.bitlen/16];
129                                 int byteHalfLen = (byteLen >> 1);
130                                 rsap.P = new byte [byteHalfLen];
131                                 Buffer.BlockCopy (blob, pos, rsap.P, 0, byteHalfLen);
132                                 Array.Reverse (rsap.P);
133                                 pos += byteHalfLen;
134
135                                 // BYTE prime2[rsapubkey.bitlen/16];
136                                 rsap.Q = new byte [byteHalfLen];
137                                 Buffer.BlockCopy (blob, pos, rsap.Q, 0, byteHalfLen);
138                                 Array.Reverse (rsap.Q);
139                                 pos += byteHalfLen;
140
141                                 // BYTE exponent1[rsapubkey.bitlen/16];
142                                 rsap.DP = new byte [byteHalfLen];
143                                 Buffer.BlockCopy (blob, pos, rsap.DP, 0, byteHalfLen);
144                                 Array.Reverse (rsap.DP);
145                                 pos += byteHalfLen;
146
147                                 // BYTE exponent2[rsapubkey.bitlen/16];
148                                 rsap.DQ = new byte [byteHalfLen];
149                                 Buffer.BlockCopy (blob, pos, rsap.DQ, 0, byteHalfLen);
150                                 Array.Reverse (rsap.DQ);
151                                 pos += byteHalfLen;
152
153                                 // BYTE coefficient[rsapubkey.bitlen/16];
154                                 rsap.InverseQ = new byte [byteHalfLen];
155                                 Buffer.BlockCopy (blob, pos, rsap.InverseQ, 0, byteHalfLen);
156                                 Array.Reverse (rsap.InverseQ);
157                                 pos += byteHalfLen;
158
159                                 // ok, this is hackish but CryptoAPI support it so...
160                                 // note: only works because CRT is used by default
161                                 // http://bugzilla.ximian.com/show_bug.cgi?id=57941
162                                 rsap.D = new byte [byteLen]; // must be allocated
163                                 if (pos + byteLen + offset <= blob.Length) {
164                                         // BYTE privateExponent[rsapubkey.bitlen/8];
165                                         Buffer.BlockCopy (blob, pos, rsap.D, 0, byteLen);
166                                         Array.Reverse (rsap.D);
167                                 }
168
169                                 RSA rsa = (RSA)RSA.Create ();
170                                 rsa.ImportParameters (rsap);
171                                 return rsa;
172                         }
173                         catch (Exception e) {
174                                 throw new CryptographicException ("Invalid blob.", e);
175                         }
176                 }
177
178                 static public byte[] ToCapiPrivateKeyBlob (RSA rsa) 
179                 {
180                         RSAParameters p = rsa.ExportParameters (true);
181                         int keyLength = p.Modulus.Length; // in bytes
182                         byte[] blob = new byte [20 + (keyLength << 2) + (keyLength >> 1)];
183
184                         blob [0] = 0x07;        // Type - PRIVATEKEYBLOB (0x07)
185                         blob [1] = 0x02;        // Version - Always CUR_BLOB_VERSION (0x02)
186                         // [2], [3]             // RESERVED - Always 0
187                         blob [5] = 0x24;        // ALGID - Always 00 24 00 00 (for CALG_RSA_SIGN)
188                         blob [8] = 0x52;        // Magic - RSA2 (ASCII in hex)
189                         blob [9] = 0x53;
190                         blob [10] = 0x41;
191                         blob [11] = 0x32;
192
193                         byte[] bitlen = GetBytesLE (keyLength << 3);
194                         blob [12] = bitlen [0]; // bitlen
195                         blob [13] = bitlen [1]; 
196                         blob [14] = bitlen [2]; 
197                         blob [15] = bitlen [3];
198
199                         // public exponent (DWORD)
200                         int pos = 16;
201                         int n = p.Exponent.Length;
202                         while (n > 0)
203                                 blob [pos++] = p.Exponent [--n];
204                         // modulus
205                         pos = 20;
206                         byte[] part = p.Modulus;
207                         int len = part.Length;
208                         Array.Reverse (part, 0, len);
209                         Buffer.BlockCopy (part, 0, blob, pos, len);
210                         pos += len;
211                         // private key
212                         part = p.P;
213                         len = part.Length;
214                         Array.Reverse (part, 0, len);
215                         Buffer.BlockCopy (part, 0, blob, pos, len);
216                         pos += len;
217
218                         part = p.Q;
219                         len = part.Length;
220                         Array.Reverse (part, 0, len);
221                         Buffer.BlockCopy (part, 0, blob, pos, len);
222                         pos += len;
223
224                         part = p.DP;
225                         len = part.Length;
226                         Array.Reverse (part, 0, len);
227                         Buffer.BlockCopy (part, 0, blob, pos, len);
228                         pos += len;
229
230                         part = p.DQ;
231                         len = part.Length;
232                         Array.Reverse (part, 0, len);
233                         Buffer.BlockCopy (part, 0, blob, pos, len);
234                         pos += len;
235
236                         part = p.InverseQ;
237                         len = part.Length;
238                         Array.Reverse (part, 0, len);
239                         Buffer.BlockCopy (part, 0, blob, pos, len);
240                         pos += len;
241
242                         part = p.D;
243                         len = part.Length;
244                         Array.Reverse (part, 0, len);
245                         Buffer.BlockCopy (part, 0, blob, pos, len);
246
247                         return blob;
248                 }
249
250                 static public RSA FromCapiPublicKeyBlob (byte[] blob) 
251                 {
252                         return FromCapiPublicKeyBlob (blob, 0);
253                 }
254
255                 static public RSA FromCapiPublicKeyBlob (byte[] blob, int offset) 
256                 {
257                         if (blob == null)
258                                 throw new ArgumentNullException ("blob");
259                         if (offset >= blob.Length)
260                                 throw new ArgumentException ("blob is too small.");
261
262                         try {
263                                 if ((blob [offset]   != 0x06) ||                                // PUBLICKEYBLOB (0x06)
264                                     (blob [offset+1] != 0x02) ||                                // Version (0x02)
265                                     (blob [offset+2] != 0x00) ||                                // Reserved (word)
266                                     (blob [offset+3] != 0x00) || 
267                                     (ToUInt32LE (blob, offset+8) != 0x31415352))        // DWORD magic = RSA1
268                                         throw new CryptographicException ("Invalid blob header");
269
270                                 // ALGID (CALG_RSA_SIGN, CALG_RSA_KEYX, ...)
271                                 // int algId = ToInt32LE (blob, offset+4);
272
273                                 // DWORD bitlen
274                                 int bitLen = ToInt32LE (blob, offset+12);
275
276                                 // DWORD public exponent
277                                 RSAParameters rsap = new RSAParameters ();
278                                 rsap.Exponent = new byte [3];
279                                 rsap.Exponent [0] = blob [offset+18];
280                                 rsap.Exponent [1] = blob [offset+17];
281                                 rsap.Exponent [2] = blob [offset+16];
282                         
283                                 int pos = offset+20;
284                                 // BYTE modulus[rsapubkey.bitlen/8];
285                                 int byteLen = (bitLen >> 3);
286                                 rsap.Modulus = new byte [byteLen];
287                                 Buffer.BlockCopy (blob, pos, rsap.Modulus, 0, byteLen);
288                                 Array.Reverse (rsap.Modulus);
289
290                                 RSA rsa = (RSA)RSA.Create ();
291                                 rsa.ImportParameters (rsap);
292                                 return rsa;
293                         }
294                         catch (Exception e) {
295                                 throw new CryptographicException ("Invalid blob.", e);
296                         }
297                 }
298
299                 static public byte[] ToCapiPublicKeyBlob (RSA rsa) 
300                 {
301                         RSAParameters p = rsa.ExportParameters (false);
302                         int keyLength = p.Modulus.Length; // in bytes
303                         byte[] blob = new byte [20 + keyLength];
304
305                         blob [0] = 0x06;        // Type - PUBLICKEYBLOB (0x06)
306                         blob [1] = 0x02;        // Version - Always CUR_BLOB_VERSION (0x02)
307                         // [2], [3]             // RESERVED - Always 0
308                         blob [5] = 0x24;        // ALGID - Always 00 24 00 00 (for CALG_RSA_SIGN)
309                         blob [8] = 0x52;        // Magic - RSA1 (ASCII in hex)
310                         blob [9] = 0x53;
311                         blob [10] = 0x41;
312                         blob [11] = 0x31;
313
314                         byte[] bitlen = GetBytesLE (keyLength << 3);
315                         blob [12] = bitlen [0]; // bitlen
316                         blob [13] = bitlen [1]; 
317                         blob [14] = bitlen [2]; 
318                         blob [15] = bitlen [3];
319
320                         // public exponent (DWORD)
321                         int pos = 16;
322                         int n = p.Exponent.Length;
323                         while (n > 0)
324                                 blob [pos++] = p.Exponent [--n];
325                         // modulus
326                         pos = 20;
327                         byte[] part = p.Modulus;
328                         int len = part.Length;
329                         Array.Reverse (part, 0, len);
330                         Buffer.BlockCopy (part, 0, blob, pos, len);
331                         pos += len;
332                         return blob;
333                 }
334
335                 // PRIVATEKEYBLOB
336                 // PUBLICKEYBLOB
337                 static public RSA FromCapiKeyBlob (byte[] blob) 
338                 {
339                         return FromCapiKeyBlob (blob, 0);
340                 }
341
342                 static public RSA FromCapiKeyBlob (byte[] blob, int offset) 
343                 {
344                         if (blob == null)
345                                 throw new ArgumentNullException ("blob");
346                         if (offset >= blob.Length)
347                                 throw new ArgumentException ("blob is too small.");
348
349                         switch (blob [offset]) {
350                                 case 0x00:
351                                         // this could be a public key inside an header
352                                         // like "sn -e" would produce
353                                         if (blob [offset + 12] == 0x06) {
354                                                 return FromCapiPublicKeyBlob (blob, offset + 12);
355                                         }
356                                         break;
357                                 case 0x06:
358                                         return FromCapiPublicKeyBlob (blob, offset);
359                                 case 0x07:
360                                         return FromCapiPrivateKeyBlob (blob, offset);
361                         }
362                         throw new CryptographicException ("Unknown blob format.");
363                 }
364
365                 static public byte[] ToCapiKeyBlob (AsymmetricAlgorithm keypair, bool includePrivateKey) 
366                 {
367                         if (keypair == null)
368                                 throw new ArgumentNullException ("keypair");
369
370                         // check between RSA and DSA (and potentially others like DH)
371                         if (keypair is RSA)
372                                 return ToCapiKeyBlob ((RSA)keypair, includePrivateKey);
373                         else
374                                 return null;    // TODO
375                 }
376
377                 static public byte[] ToCapiKeyBlob (RSA rsa, bool includePrivateKey) 
378                 {
379                         if (rsa == null)
380                                 throw new ArgumentNullException ("rsa");
381
382                         if (includePrivateKey)
383                                 return ToCapiPrivateKeyBlob (rsa);
384                         else
385                                 return ToCapiPublicKeyBlob (rsa);
386                 }
387
388                 static public string ToHex (byte[] input) 
389                 {
390                         if (input == null)
391                                 return null;
392
393                         StringBuilder sb = new StringBuilder (input.Length * 2);
394                         foreach (byte b in input) {
395                                 sb.Append (b.ToString ("X2", CultureInfo.InvariantCulture));
396                         }
397                         return sb.ToString ();
398                 }
399
400                 static private byte FromHexChar (char c) 
401                 {
402                         if ((c >= 'a') && (c <= 'f'))
403                                 return (byte) (c - 'a' + 10);
404                         if ((c >= 'A') && (c <= 'F'))
405                                 return (byte) (c - 'A' + 10);
406                         if ((c >= '0') && (c <= '9'))
407                                 return (byte) (c - '0');
408                         throw new ArgumentException ("invalid hex char");
409                 }
410
411                 static public byte[] FromHex (string hex) 
412                 {
413                         if (hex == null)
414                                 return null;
415                         if ((hex.Length & 0x1) == 0x1)
416                                 throw new ArgumentException ("Length must be a multiple of 2");
417
418                         byte[] result = new byte [hex.Length >> 1];
419                         int n = 0;
420                         int i = 0;
421                         while (n < result.Length) {
422                                 result [n] = (byte) (FromHexChar (hex [i++]) << 4);
423                                 result [n++] += FromHexChar (hex [i++]);
424                         }
425                         return result;
426                 }
427         }
428 }