2
// CryptoConvert.cs - Crypto Convertion Routines
5
// Sebastien Pouliot <sebastien@ximian.com>
7
// (C) 2003 Motus Technologies Inc. (http://www.motus.com)
8
// Copyright (C) 2004-2006 Novell Inc. (http://www.novell.com)
10
// Permission is hereby granted, free of charge, to any person obtaining
11
// a copy of this software and associated documentation files (the
12
// "Software"), to deal in the Software without restriction, including
13
// without limitation the rights to use, copy, modify, merge, publish,
14
// distribute, sublicense, and/or sell copies of the Software, and to
15
// permit persons to whom the Software is furnished to do so, subject to
16
// the following conditions:
18
// The above copyright notice and this permission notice shall be
19
// included in all copies or substantial portions of the Software.
21
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
22
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
23
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
24
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
25
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
26
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
27
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
31
using System.Globalization;
32
using System.Security.Cryptography;
35
namespace Mono.Security.Cryptography {
42
sealed class CryptoConvert {
44
private CryptoConvert ()
48
static private int ToInt32LE (byte [] bytes, int offset)
50
return (bytes [offset+3] << 24) | (bytes [offset+2] << 16) | (bytes [offset+1] << 8) | bytes [offset];
53
static private uint ToUInt32LE (byte [] bytes, int offset)
55
return (uint)((bytes [offset+3] << 24) | (bytes [offset+2] << 16) | (bytes [offset+1] << 8) | bytes [offset]);
58
static private byte [] GetBytesLE (int val)
62
(byte) ((val >> 8) & 0xff),
63
(byte) ((val >> 16) & 0xff),
64
(byte) ((val >> 24) & 0xff)
68
static private byte[] Trim (byte[] array)
70
for (int i=0; i < array.Length; i++) {
71
if (array [i] != 0x00) {
72
byte[] result = new byte [array.Length - i];
73
Buffer.BlockCopy (array, i, result, 0, result.Length);
80
// convert the key from PRIVATEKEYBLOB to RSA
81
// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/security/Security/private_key_blobs.asp
82
// e.g. SNK files, PVK files
83
static public RSA FromCapiPrivateKeyBlob (byte[] blob)
85
return FromCapiPrivateKeyBlob (blob, 0);
88
static public RSA FromCapiPrivateKeyBlob (byte[] blob, int offset)
91
throw new ArgumentNullException ("blob");
92
if (offset >= blob.Length)
93
throw new ArgumentException ("blob is too small.");
95
RSAParameters rsap = new RSAParameters ();
97
if ((blob [offset] != 0x07) || // PRIVATEKEYBLOB (0x07)
98
(blob [offset+1] != 0x02) || // Version (0x02)
99
(blob [offset+2] != 0x00) || // Reserved (word)
100
(blob [offset+3] != 0x00) ||
101
(ToUInt32LE (blob, offset+8) != 0x32415352)) // DWORD magic = RSA2
102
throw new CryptographicException ("Invalid blob header");
104
// ALGID (CALG_RSA_SIGN, CALG_RSA_KEYX, ...)
105
// int algId = ToInt32LE (blob, offset+4);
108
int bitLen = ToInt32LE (blob, offset+12);
110
// DWORD public exponent
111
byte[] exp = new byte [4];
112
Buffer.BlockCopy (blob, offset+16, exp, 0, 4);
114
rsap.Exponent = Trim (exp);
117
// BYTE modulus[rsapubkey.bitlen/8];
118
int byteLen = (bitLen >> 3);
119
rsap.Modulus = new byte [byteLen];
120
Buffer.BlockCopy (blob, pos, rsap.Modulus, 0, byteLen);
121
Array.Reverse (rsap.Modulus);
124
// BYTE prime1[rsapubkey.bitlen/16];
125
int byteHalfLen = (byteLen >> 1);
126
rsap.P = new byte [byteHalfLen];
127
Buffer.BlockCopy (blob, pos, rsap.P, 0, byteHalfLen);
128
Array.Reverse (rsap.P);
131
// BYTE prime2[rsapubkey.bitlen/16];
132
rsap.Q = new byte [byteHalfLen];
133
Buffer.BlockCopy (blob, pos, rsap.Q, 0, byteHalfLen);
134
Array.Reverse (rsap.Q);
137
// BYTE exponent1[rsapubkey.bitlen/16];
138
rsap.DP = new byte [byteHalfLen];
139
Buffer.BlockCopy (blob, pos, rsap.DP, 0, byteHalfLen);
140
Array.Reverse (rsap.DP);
143
// BYTE exponent2[rsapubkey.bitlen/16];
144
rsap.DQ = new byte [byteHalfLen];
145
Buffer.BlockCopy (blob, pos, rsap.DQ, 0, byteHalfLen);
146
Array.Reverse (rsap.DQ);
149
// BYTE coefficient[rsapubkey.bitlen/16];
150
rsap.InverseQ = new byte [byteHalfLen];
151
Buffer.BlockCopy (blob, pos, rsap.InverseQ, 0, byteHalfLen);
152
Array.Reverse (rsap.InverseQ);
155
// ok, this is hackish but CryptoAPI support it so...
156
// note: only works because CRT is used by default
157
// http://bugzilla.ximian.com/show_bug.cgi?id=57941
158
rsap.D = new byte [byteLen]; // must be allocated
159
if (pos + byteLen + offset <= blob.Length) {
160
// BYTE privateExponent[rsapubkey.bitlen/8];
161
Buffer.BlockCopy (blob, pos, rsap.D, 0, byteLen);
162
Array.Reverse (rsap.D);
165
catch (Exception e) {
166
throw new CryptographicException ("Invalid blob.", e);
170
RSA rsa = RSA.Create ();
171
rsa.ImportParameters (rsap);
176
rsa.ImportParameters (rsap);
178
catch (CryptographicException ce) {
179
// this may cause problem when this code is run under
180
// the SYSTEM identity on Windows (e.g. ASP.NET). See
181
// http://bugzilla.ximian.com/show_bug.cgi?id=77559
183
CspParameters csp = new CspParameters ();
184
csp.Flags = CspProviderFlags.UseMachineKeyStore;
185
rsa = new RSACryptoServiceProvider (csp);
186
rsa.ImportParameters (rsap);
189
// rethrow original, not the later, exception if this fails
197
static public DSA FromCapiPrivateKeyBlobDSA (byte[] blob)
199
return FromCapiPrivateKeyBlobDSA (blob, 0);
202
static public DSA FromCapiPrivateKeyBlobDSA (byte[] blob, int offset)
205
throw new ArgumentNullException ("blob");
206
if (offset >= blob.Length)
207
throw new ArgumentException ("blob is too small.");
209
DSAParameters dsap = new DSAParameters ();
211
if ((blob [offset] != 0x07) || // PRIVATEKEYBLOB (0x07)
212
(blob [offset + 1] != 0x02) || // Version (0x02)
213
(blob [offset + 2] != 0x00) || // Reserved (word)
214
(blob [offset + 3] != 0x00) ||
215
(ToUInt32LE (blob, offset + 8) != 0x32535344)) // DWORD magic
216
throw new CryptographicException ("Invalid blob header");
218
int bitlen = ToInt32LE (blob, offset + 12);
219
int bytelen = bitlen >> 3;
220
int pos = offset + 16;
222
dsap.P = new byte [bytelen];
223
Buffer.BlockCopy (blob, pos, dsap.P, 0, bytelen);
224
Array.Reverse (dsap.P);
227
dsap.Q = new byte [20];
228
Buffer.BlockCopy (blob, pos, dsap.Q, 0, 20);
229
Array.Reverse (dsap.Q);
232
dsap.G = new byte [bytelen];
233
Buffer.BlockCopy (blob, pos, dsap.G, 0, bytelen);
234
Array.Reverse (dsap.G);
237
dsap.X = new byte [20];
238
Buffer.BlockCopy (blob, pos, dsap.X, 0, 20);
239
Array.Reverse (dsap.X);
242
dsap.Counter = ToInt32LE (blob, pos);
245
dsap.Seed = new byte [20];
246
Buffer.BlockCopy (blob, pos, dsap.Seed, 0, 20);
247
Array.Reverse (dsap.Seed);
250
catch (Exception e) {
251
throw new CryptographicException ("Invalid blob.", e);
255
DSA dsa = (DSA)DSA.Create ();
256
dsa.ImportParameters (dsap);
260
dsa = (DSA)DSA.Create ();
261
dsa.ImportParameters (dsap);
263
catch (CryptographicException ce) {
264
// this may cause problem when this code is run under
265
// the SYSTEM identity on Windows (e.g. ASP.NET). See
266
// http://bugzilla.ximian.com/show_bug.cgi?id=77559
268
CspParameters csp = new CspParameters ();
269
csp.Flags = CspProviderFlags.UseMachineKeyStore;
270
dsa = new DSACryptoServiceProvider (csp);
271
dsa.ImportParameters (dsap);
274
// rethrow original, not the later, exception if this fails
282
static public byte[] ToCapiPrivateKeyBlob (RSA rsa)
284
RSAParameters p = rsa.ExportParameters (true);
285
int keyLength = p.Modulus.Length; // in bytes
286
byte[] blob = new byte [20 + (keyLength << 2) + (keyLength >> 1)];
288
blob [0] = 0x07; // Type - PRIVATEKEYBLOB (0x07)
289
blob [1] = 0x02; // Version - Always CUR_BLOB_VERSION (0x02)
290
// [2], [3] // RESERVED - Always 0
291
blob [5] = 0x24; // ALGID - Always 00 24 00 00 (for CALG_RSA_SIGN)
292
blob [8] = 0x52; // Magic - RSA2 (ASCII in hex)
297
byte[] bitlen = GetBytesLE (keyLength << 3);
298
blob [12] = bitlen [0]; // bitlen
299
blob [13] = bitlen [1];
300
blob [14] = bitlen [2];
301
blob [15] = bitlen [3];
303
// public exponent (DWORD)
305
int n = p.Exponent.Length;
307
blob [pos++] = p.Exponent [--n];
310
byte[] part = p.Modulus;
311
int len = part.Length;
312
Array.Reverse (part, 0, len);
313
Buffer.BlockCopy (part, 0, blob, pos, len);
318
Array.Reverse (part, 0, len);
319
Buffer.BlockCopy (part, 0, blob, pos, len);
324
Array.Reverse (part, 0, len);
325
Buffer.BlockCopy (part, 0, blob, pos, len);
330
Array.Reverse (part, 0, len);
331
Buffer.BlockCopy (part, 0, blob, pos, len);
336
Array.Reverse (part, 0, len);
337
Buffer.BlockCopy (part, 0, blob, pos, len);
342
Array.Reverse (part, 0, len);
343
Buffer.BlockCopy (part, 0, blob, pos, len);
348
Array.Reverse (part, 0, len);
349
Buffer.BlockCopy (part, 0, blob, pos, len);
354
static public byte[] ToCapiPrivateKeyBlob (DSA dsa)
356
DSAParameters p = dsa.ExportParameters (true);
357
int keyLength = p.P.Length; // in bytes
359
// header + P + Q + G + X + count + seed
360
byte[] blob = new byte [16 + keyLength + 20 + keyLength + 20 + 4 + 20];
362
blob [0] = 0x07; // Type - PRIVATEKEYBLOB (0x07)
363
blob [1] = 0x02; // Version - Always CUR_BLOB_VERSION (0x02)
364
// [2], [3] // RESERVED - Always 0
365
blob [5] = 0x22; // ALGID
366
blob [8] = 0x44; // Magic
371
byte[] bitlen = GetBytesLE (keyLength << 3);
372
blob [12] = bitlen [0];
373
blob [13] = bitlen [1];
374
blob [14] = bitlen [2];
375
blob [15] = bitlen [3];
379
Array.Reverse (part);
380
Buffer.BlockCopy (part, 0, blob, pos, keyLength);
384
Array.Reverse (part);
385
Buffer.BlockCopy (part, 0, blob, pos, 20);
389
Array.Reverse (part);
390
Buffer.BlockCopy (part, 0, blob, pos, keyLength);
394
Array.Reverse (part);
395
Buffer.BlockCopy (part, 0, blob, pos, 20);
398
Buffer.BlockCopy (GetBytesLE (p.Counter), 0, blob, pos, 4);
402
Array.Reverse (part);
403
Buffer.BlockCopy (part, 0, blob, pos, 20);
408
static public RSA FromCapiPublicKeyBlob (byte[] blob)
410
return FromCapiPublicKeyBlob (blob, 0);
413
static public RSA FromCapiPublicKeyBlob (byte[] blob, int offset)
416
throw new ArgumentNullException ("blob");
417
if (offset >= blob.Length)
418
throw new ArgumentException ("blob is too small.");
421
if ((blob [offset] != 0x06) || // PUBLICKEYBLOB (0x06)
422
(blob [offset+1] != 0x02) || // Version (0x02)
423
(blob [offset+2] != 0x00) || // Reserved (word)
424
(blob [offset+3] != 0x00) ||
425
(ToUInt32LE (blob, offset+8) != 0x31415352)) // DWORD magic = RSA1
426
throw new CryptographicException ("Invalid blob header");
428
// ALGID (CALG_RSA_SIGN, CALG_RSA_KEYX, ...)
429
// int algId = ToInt32LE (blob, offset+4);
432
int bitLen = ToInt32LE (blob, offset+12);
434
// DWORD public exponent
435
RSAParameters rsap = new RSAParameters ();
436
rsap.Exponent = new byte [3];
437
rsap.Exponent [0] = blob [offset+18];
438
rsap.Exponent [1] = blob [offset+17];
439
rsap.Exponent [2] = blob [offset+16];
442
// BYTE modulus[rsapubkey.bitlen/8];
443
int byteLen = (bitLen >> 3);
444
rsap.Modulus = new byte [byteLen];
445
Buffer.BlockCopy (blob, pos, rsap.Modulus, 0, byteLen);
446
Array.Reverse (rsap.Modulus);
448
RSA rsa = RSA.Create ();
449
rsa.ImportParameters (rsap);
454
rsa.ImportParameters (rsap);
456
catch (CryptographicException) {
457
// this may cause problem when this code is run under
458
// the SYSTEM identity on Windows (e.g. ASP.NET). See
459
// http://bugzilla.ximian.com/show_bug.cgi?id=77559
460
CspParameters csp = new CspParameters ();
461
csp.Flags = CspProviderFlags.UseMachineKeyStore;
462
rsa = new RSACryptoServiceProvider (csp);
463
rsa.ImportParameters (rsap);
468
catch (Exception e) {
469
throw new CryptographicException ("Invalid blob.", e);
473
static public DSA FromCapiPublicKeyBlobDSA (byte[] blob)
475
return FromCapiPublicKeyBlobDSA (blob, 0);
478
static public DSA FromCapiPublicKeyBlobDSA (byte[] blob, int offset)
481
throw new ArgumentNullException ("blob");
482
if (offset >= blob.Length)
483
throw new ArgumentException ("blob is too small.");
486
if ((blob [offset] != 0x06) || // PUBLICKEYBLOB (0x06)
487
(blob [offset + 1] != 0x02) || // Version (0x02)
488
(blob [offset + 2] != 0x00) || // Reserved (word)
489
(blob [offset + 3] != 0x00) ||
490
(ToUInt32LE (blob, offset + 8) != 0x31535344)) // DWORD magic
491
throw new CryptographicException ("Invalid blob header");
493
int bitlen = ToInt32LE (blob, offset + 12);
494
DSAParameters dsap = new DSAParameters ();
495
int bytelen = bitlen >> 3;
496
int pos = offset + 16;
498
dsap.P = new byte [bytelen];
499
Buffer.BlockCopy (blob, pos, dsap.P, 0, bytelen);
500
Array.Reverse (dsap.P);
503
dsap.Q = new byte [20];
504
Buffer.BlockCopy (blob, pos, dsap.Q, 0, 20);
505
Array.Reverse (dsap.Q);
508
dsap.G = new byte [bytelen];
509
Buffer.BlockCopy (blob, pos, dsap.G, 0, bytelen);
510
Array.Reverse (dsap.G);
513
dsap.Y = new byte [bytelen];
514
Buffer.BlockCopy (blob, pos, dsap.Y, 0, bytelen);
515
Array.Reverse (dsap.Y);
518
dsap.Counter = ToInt32LE (blob, pos);
521
dsap.Seed = new byte [20];
522
Buffer.BlockCopy (blob, pos, dsap.Seed, 0, 20);
523
Array.Reverse (dsap.Seed);
526
DSA dsa = (DSA)DSA.Create ();
527
dsa.ImportParameters (dsap);
530
catch (Exception e) {
531
throw new CryptographicException ("Invalid blob.", e);
535
static public byte[] ToCapiPublicKeyBlob (RSA rsa)
537
RSAParameters p = rsa.ExportParameters (false);
538
int keyLength = p.Modulus.Length; // in bytes
539
byte[] blob = new byte [20 + keyLength];
541
blob [0] = 0x06; // Type - PUBLICKEYBLOB (0x06)
542
blob [1] = 0x02; // Version - Always CUR_BLOB_VERSION (0x02)
543
// [2], [3] // RESERVED - Always 0
544
blob [5] = 0x24; // ALGID - Always 00 24 00 00 (for CALG_RSA_SIGN)
545
blob [8] = 0x52; // Magic - RSA1 (ASCII in hex)
550
byte[] bitlen = GetBytesLE (keyLength << 3);
551
blob [12] = bitlen [0]; // bitlen
552
blob [13] = bitlen [1];
553
blob [14] = bitlen [2];
554
blob [15] = bitlen [3];
556
// public exponent (DWORD)
558
int n = p.Exponent.Length;
560
blob [pos++] = p.Exponent [--n];
563
byte[] part = p.Modulus;
564
int len = part.Length;
565
Array.Reverse (part, 0, len);
566
Buffer.BlockCopy (part, 0, blob, pos, len);
571
static public byte[] ToCapiPublicKeyBlob (DSA dsa)
573
DSAParameters p = dsa.ExportParameters (false);
574
int keyLength = p.P.Length; // in bytes
576
// header + P + Q + G + Y + count + seed
577
byte[] blob = new byte [16 + keyLength + 20 + keyLength + keyLength + 4 + 20];
579
blob [0] = 0x06; // Type - PUBLICKEYBLOB (0x06)
580
blob [1] = 0x02; // Version - Always CUR_BLOB_VERSION (0x02)
581
// [2], [3] // RESERVED - Always 0
582
blob [5] = 0x22; // ALGID
583
blob [8] = 0x44; // Magic
588
byte[] bitlen = GetBytesLE (keyLength << 3);
589
blob [12] = bitlen [0];
590
blob [13] = bitlen [1];
591
blob [14] = bitlen [2];
592
blob [15] = bitlen [3];
598
Array.Reverse (part);
599
Buffer.BlockCopy (part, 0, blob, pos, keyLength);
603
Array.Reverse (part);
604
Buffer.BlockCopy (part, 0, blob, pos, 20);
608
Array.Reverse (part);
609
Buffer.BlockCopy (part, 0, blob, pos, keyLength);
613
Array.Reverse (part);
614
Buffer.BlockCopy (part, 0, blob, pos, keyLength);
617
Buffer.BlockCopy (GetBytesLE (p.Counter), 0, blob, pos, 4);
621
Array.Reverse (part);
622
Buffer.BlockCopy (part, 0, blob, pos, 20);
629
static public RSA FromCapiKeyBlob (byte[] blob)
631
return FromCapiKeyBlob (blob, 0);
634
static public RSA FromCapiKeyBlob (byte[] blob, int offset)
637
throw new ArgumentNullException ("blob");
638
if (offset >= blob.Length)
639
throw new ArgumentException ("blob is too small.");
641
switch (blob [offset]) {
643
// this could be a public key inside an header
644
// like "sn -e" would produce
645
if (blob [offset + 12] == 0x06) {
646
return FromCapiPublicKeyBlob (blob, offset + 12);
650
return FromCapiPublicKeyBlob (blob, offset);
652
return FromCapiPrivateKeyBlob (blob, offset);
654
throw new CryptographicException ("Unknown blob format.");
657
static public DSA FromCapiKeyBlobDSA (byte[] blob)
659
return FromCapiKeyBlobDSA (blob, 0);
662
static public DSA FromCapiKeyBlobDSA (byte[] blob, int offset)
665
throw new ArgumentNullException ("blob");
666
if (offset >= blob.Length)
667
throw new ArgumentException ("blob is too small.");
669
switch (blob [offset]) {
671
return FromCapiPublicKeyBlobDSA (blob, offset);
673
return FromCapiPrivateKeyBlobDSA (blob, offset);
675
throw new CryptographicException ("Unknown blob format.");
678
static public byte[] ToCapiKeyBlob (AsymmetricAlgorithm keypair, bool includePrivateKey)
681
throw new ArgumentNullException ("keypair");
683
// check between RSA and DSA (and potentially others like DH)
685
return ToCapiKeyBlob ((RSA)keypair, includePrivateKey);
686
else if (keypair is DSA)
687
return ToCapiKeyBlob ((DSA)keypair, includePrivateKey);
692
static public byte[] ToCapiKeyBlob (RSA rsa, bool includePrivateKey)
695
throw new ArgumentNullException ("rsa");
697
if (includePrivateKey)
698
return ToCapiPrivateKeyBlob (rsa);
700
return ToCapiPublicKeyBlob (rsa);
703
static public byte[] ToCapiKeyBlob (DSA dsa, bool includePrivateKey)
706
throw new ArgumentNullException ("dsa");
708
if (includePrivateKey)
709
return ToCapiPrivateKeyBlob (dsa);
711
return ToCapiPublicKeyBlob (dsa);
714
static public string ToHex (byte[] input)
719
StringBuilder sb = new StringBuilder (input.Length * 2);
720
foreach (byte b in input) {
721
sb.Append (b.ToString ("X2", CultureInfo.InvariantCulture));
723
return sb.ToString ();
726
static private byte FromHexChar (char c)
728
if ((c >= 'a') && (c <= 'f'))
729
return (byte) (c - 'a' + 10);
730
if ((c >= 'A') && (c <= 'F'))
731
return (byte) (c - 'A' + 10);
732
if ((c >= '0') && (c <= '9'))
733
return (byte) (c - '0');
734
throw new ArgumentException ("invalid hex char");
737
static public byte[] FromHex (string hex)
741
if ((hex.Length & 0x1) == 0x1)
742
throw new ArgumentException ("Length must be a multiple of 2");
744
byte[] result = new byte [hex.Length >> 1];
747
while (n < result.Length) {
748
result [n] = (byte) (FromHexChar (hex [i++]) << 4);
749
result [n++] += FromHexChar (hex [i++]);