New tests.
[mono.git] / mcs / class / corlib / Mono.Security / StrongName.cs
1 //
2 // StrongName.cs - Strong Name Implementation
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 // 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 using System;
33 using System.Configuration.Assemblies;
34 using System.Globalization;
35 using System.IO;
36 using System.Reflection;
37 using System.Security.Cryptography;
38
39 using Mono.Security.Cryptography;
40
41 namespace Mono.Security {
42
43 #if INSIDE_CORLIB
44         internal
45 #else
46         public
47 #endif
48         sealed class StrongName {
49
50                 internal class StrongNameSignature {
51                         private byte[] hash;
52                         private byte[] signature;
53                         private UInt32 signaturePosition;
54                         private UInt32 signatureLength;
55                         private UInt32 metadataPosition;
56                         private UInt32 metadataLength;
57                         private byte cliFlag;
58                         private UInt32 cliFlagPosition;
59
60                         public byte[] Hash {
61                                 get { return hash; }
62                                 set { hash = value; }
63                         }
64
65                         public byte[] Signature {
66                                 get { return signature; }
67                                 set { signature = value; }
68                         }
69
70                         public UInt32 MetadataPosition {
71                                 get { return metadataPosition; }
72                                 set { metadataPosition = value; }
73                         }
74
75                         public UInt32 MetadataLength {
76                                 get { return metadataLength; }
77                                 set { metadataLength = value; }
78                         }
79
80                         public UInt32 SignaturePosition {
81                                 get { return signaturePosition; }
82                                 set { signaturePosition = value; }
83                         }
84
85                         public UInt32 SignatureLength {
86                                 get { return signatureLength; }
87                                 set { signatureLength = value; }
88                         }
89
90                         // delay signed -> flag = 0x01
91                         // strongsigned -> flag = 0x09
92                         public byte CliFlag {
93                                 get { return cliFlag; }
94                                 set { cliFlag = value; }
95                         }
96
97                         public UInt32 CliFlagPosition {
98                                 get { return cliFlagPosition; }
99                                 set { cliFlagPosition = value; }
100                         }
101                 }
102
103                 internal enum StrongNameOptions {
104                         Metadata,
105                         Signature
106                 }
107
108                 private RSA rsa;
109                 private byte[] publicKey;
110                 private byte[] keyToken;
111                 private string tokenAlgorithm;
112
113                 public StrongName ()
114                 {
115                 }
116
117                 public StrongName (int keySize)
118                 {
119                         rsa = new RSAManaged (keySize);
120                 }
121
122                 public StrongName (byte[] data)
123                 {
124                         if (data == null)
125                                 throw new ArgumentNullException ("data");
126
127                         // check for ECMA key
128                         if (data.Length == 16) {
129                                 int i = 0;
130                                 int sum = 0;
131                                 while (i < data.Length)
132                                         sum += data [i++];
133                                 if (sum == 4) {
134                                         // it is the ECMA key
135                                         publicKey = (byte[]) data.Clone ();
136                                 }
137                         }
138                         else {
139                                 RSA = CryptoConvert.FromCapiKeyBlob (data);
140                                 if (rsa == null)
141                                         throw new ArgumentException ("data isn't a correctly encoded RSA public key");
142                         }
143                 }
144
145                 public StrongName (RSA rsa)
146                 {
147                         if (rsa == null)
148                                 throw new ArgumentNullException ("rsa");
149
150                         RSA = rsa;
151                 }
152
153                 private void InvalidateCache () 
154                 {
155                         publicKey = null;
156                         keyToken = null;
157                 }
158
159                 public bool CanSign {
160                         get {
161                                 if (rsa == null)
162                                         return false;
163 #if INSIDE_CORLIB && !MOONLIGHT
164                                 // the easy way
165                                 if (RSA is RSACryptoServiceProvider) {
166                                         // available as internal for corlib
167                                         return !(rsa as RSACryptoServiceProvider).PublicOnly;
168                                 }
169                                 else 
170 #endif
171                                 if (RSA is RSAManaged) {
172                                         return !(rsa as RSAManaged).PublicOnly;
173                                 }
174                                 else {
175                                         // the hard way
176                                         try {
177                                                 RSAParameters p = rsa.ExportParameters (true);
178                                                 return ((p.D != null) && (p.P != null) && (p.Q != null));
179                                         }
180                                         catch (CryptographicException) {
181                                                 return false;
182                                         }
183                                 }
184                         }
185                 }
186
187                 public RSA RSA {
188                         get {
189                                 // if none then we create a new keypair
190                                 if (rsa == null)
191                                         rsa = (RSA) RSA.Create ();
192                                 return rsa; 
193                         }
194                         set { 
195                                 rsa = value;
196                                 InvalidateCache ();
197                         }
198                 }
199
200                 public byte[] PublicKey {
201                         get { 
202                                 if (publicKey == null) {
203                                         byte[] keyPair = CryptoConvert.ToCapiKeyBlob (rsa, false);
204                                         // since 2.0 public keys can vary from 384 to 16384 bits
205                                         publicKey = new byte [32 + (rsa.KeySize >> 3)];
206
207                                         // The first 12 bytes are documented at:
208                                         // http://msdn.microsoft.com/library/en-us/cprefadd/html/grfungethashfromfile.asp
209                                         // ALG_ID - Signature
210                                         publicKey [0] = keyPair [4];
211                                         publicKey [1] = keyPair [5];    
212                                         publicKey [2] = keyPair [6];    
213                                         publicKey [3] = keyPair [7];    
214                                         // ALG_ID - Hash (SHA1 == 0x8004)
215                                         publicKey [4] = 0x04;
216                                         publicKey [5] = 0x80;
217                                         publicKey [6] = 0x00;
218                                         publicKey [7] = 0x00;
219                                         // Length of Public Key (in bytes)
220                                         byte[] lastPart = BitConverterLE.GetBytes (publicKey.Length - 12);
221                                         publicKey [8] = lastPart [0];
222                                         publicKey [9] = lastPart [1];
223                                         publicKey [10] = lastPart [2];
224                                         publicKey [11] = lastPart [3];
225                                         // Ok from here - Same structure as keypair - expect for public key
226                                         publicKey [12] = 0x06;          // PUBLICKEYBLOB
227                                         // we can copy this part
228                                         Buffer.BlockCopy (keyPair, 1, publicKey, 13, publicKey.Length - 13);
229                                         // and make a small adjustment 
230                                         publicKey [23] = 0x31;          // (RSA1 not RSA2)
231                                 }
232                                 return (byte[]) publicKey.Clone ();
233                         }
234                 }
235
236                 public byte[] PublicKeyToken {
237                         get {
238                                 if (keyToken == null) {
239                                         byte[] publicKey = PublicKey;
240                                         if (publicKey == null)
241                                                 return null;
242                                         HashAlgorithm ha = HashAlgorithm.Create (TokenAlgorithm);
243                                         byte[] hash = ha.ComputeHash (publicKey);
244                                         // we need the last 8 bytes in reverse order
245                                         keyToken = new byte [8];
246                                         Buffer.BlockCopy (hash, (hash.Length - 8), keyToken, 0, 8);
247                                         Array.Reverse (keyToken, 0, 8);
248                                 }
249                                 return (byte[]) keyToken.Clone ();
250                         }
251                 }
252
253                 public string TokenAlgorithm {
254                         get { 
255                                 if (tokenAlgorithm == null)
256                                         tokenAlgorithm = "SHA1";
257                                 return tokenAlgorithm; 
258                         }
259                         set {
260                                 string algo = value.ToUpper (CultureInfo.InvariantCulture);
261                                 if ((algo == "SHA1") || (algo == "MD5")) {
262                                         tokenAlgorithm = value;
263                                         InvalidateCache ();
264                                 }
265                                 else
266                                         throw new ArgumentException ("Unsupported hash algorithm for token");
267                         }
268                 }
269
270                 public byte[] GetBytes () 
271                 {
272                         return CryptoConvert.ToCapiPrivateKeyBlob (RSA);
273                 }
274
275                 private UInt32 RVAtoPosition (UInt32 r, int sections, byte[] headers) 
276                 {
277                         for (int i=0; i < sections; i++) {
278                                 UInt32 p = BitConverterLE.ToUInt32 (headers, i * 40 + 20);
279                                 UInt32 s = BitConverterLE.ToUInt32 (headers, i * 40 + 12);
280                                 int l = (int) BitConverterLE.ToUInt32 (headers, i * 40 + 8);
281                                 if ((s <= r) && (r < s + l)) {
282                                         return p + r - s;
283                                 }
284                         }
285                         return 0;
286                 }
287
288                 internal StrongNameSignature StrongHash (Stream stream, StrongNameOptions options)
289                 {
290                         StrongNameSignature info = new StrongNameSignature ();
291
292                         HashAlgorithm hash = HashAlgorithm.Create (TokenAlgorithm);
293                         CryptoStream cs = new CryptoStream (Stream.Null, hash, CryptoStreamMode.Write);
294
295                         // MS-DOS Header - always 128 bytes
296                         // ref: Section 24.2.1, Partition II Metadata
297                         byte[] mz = new byte [128];
298                         stream.Read (mz, 0, 128);
299                         if (BitConverterLE.ToUInt16 (mz, 0) != 0x5a4d)
300                                 return null;
301                         UInt32 peHeader = BitConverterLE.ToUInt32 (mz, 60);
302                         cs.Write (mz, 0, 128);
303                         if (peHeader != 128) {
304                                 byte[] mzextra = new byte [peHeader - 128];
305                                 stream.Read (mzextra, 0, mzextra.Length);
306                                 cs.Write (mzextra, 0, mzextra.Length);
307                         }
308
309                         // PE File Header - always 248 bytes
310                         // ref: Section 24.2.2, Partition II Metadata
311                         byte[] pe = new byte [248];
312                         stream.Read (pe, 0, 248);
313                         if (BitConverterLE.ToUInt32 (pe, 0) != 0x4550)
314                                 return null;
315                         if (BitConverterLE.ToUInt16 (pe, 4) != 0x14c)
316                                 return null;
317                         // MUST zeroize both CheckSum and Security Directory
318                         byte[] v = new byte [8];
319                         Buffer.BlockCopy (v, 0, pe, 88, 4);
320                         Buffer.BlockCopy (v, 0, pe, 152, 8);
321                         cs.Write (pe, 0, 248);
322
323                         UInt16 numSection = BitConverterLE.ToUInt16 (pe, 6);
324                         int sectionLength = (numSection * 40);
325                         byte[] sectionHeaders = new byte [sectionLength];
326                         stream.Read (sectionHeaders, 0, sectionLength);
327                         cs.Write (sectionHeaders, 0, sectionLength);
328
329                         UInt32 cliHeaderRVA = BitConverterLE.ToUInt32 (pe, 232);
330                         UInt32 cliHeaderPos = RVAtoPosition (cliHeaderRVA, numSection, sectionHeaders);
331                         int cliHeaderSiz = (int) BitConverterLE.ToUInt32 (pe, 236);
332
333                         // CLI Header
334                         // ref: Section 24.3.3, Partition II Metadata
335                         byte[] cli = new byte [cliHeaderSiz];
336                         stream.Position = cliHeaderPos;
337                         stream.Read (cli, 0, cliHeaderSiz);
338
339                         UInt32 strongNameSignatureRVA = BitConverterLE.ToUInt32 (cli, 32);
340                         info.SignaturePosition = RVAtoPosition (strongNameSignatureRVA, numSection, sectionHeaders);
341                         info.SignatureLength = BitConverterLE.ToUInt32 (cli, 36);
342
343                         UInt32 metadataRVA = BitConverterLE.ToUInt32 (cli, 8);
344                         info.MetadataPosition = RVAtoPosition (metadataRVA, numSection, sectionHeaders);
345                         info.MetadataLength = BitConverterLE.ToUInt32 (cli, 12);
346
347                         if (options == StrongNameOptions.Metadata) {
348                                 cs.Close ();
349                                 hash.Initialize ();
350                                 byte[] metadata = new byte [info.MetadataLength];
351                                 stream.Position = info.MetadataPosition;
352                                 stream.Read (metadata, 0, metadata.Length);
353                                 info.Hash = hash.ComputeHash (metadata);
354                                 return info;
355                         }
356
357                         // now we hash every section EXCEPT the signature block
358                         for (int i=0; i < numSection; i++) {
359                                 UInt32 start = BitConverterLE.ToUInt32 (sectionHeaders, i * 40 + 20);
360                                 int length = (int) BitConverterLE.ToUInt32 (sectionHeaders, i * 40 + 16);
361                                 byte[] section = new byte [length];
362                                 stream.Position = start;
363                                 stream.Read (section, 0, length);
364                                 if ((start <= info.SignaturePosition) && (info.SignaturePosition < start + length)) {
365                                         // hash before the signature
366                                         int before = (int)(info.SignaturePosition - start);
367                                         if (before > 0) {
368                                                 cs.Write (section, 0, before);
369                                         }
370                                         // copy signature
371                                         info.Signature = new byte [info.SignatureLength];
372                                         Buffer.BlockCopy (section, before, info.Signature, 0, (int)info.SignatureLength);
373                                         Array.Reverse (info.Signature);
374                                         // hash after the signature
375                                         int s = (int)(before + info.SignatureLength);
376                                         int after = (int)(length - s);
377                                         if (after > 0) {
378                                                 cs.Write (section, s, after);
379                                         }
380                                 }
381                                 else
382                                         cs.Write (section, 0, length);
383                         }
384
385                         cs.Close ();
386                         info.Hash = hash.Hash;
387                         return info;
388                 }
389
390                 // return the same result as the undocumented and unmanaged GetHashFromAssemblyFile
391                 public byte[] Hash (string fileName) 
392                 {
393                         FileStream fs = File.OpenRead (fileName);
394                         StrongNameSignature sn = StrongHash (fs, StrongNameOptions.Metadata);
395                         fs.Close ();
396
397                         return sn.Hash;
398                 }
399
400                 public bool Sign (string fileName) 
401                 {
402                         bool result = false;
403                         StrongNameSignature sn;
404                         using (FileStream fs = File.OpenRead (fileName)) {
405                                 sn = StrongHash (fs, StrongNameOptions.Signature);
406                                 fs.Close ();
407                         }
408                         if (sn.Hash == null)
409                                 return false;
410
411                         byte[] signature = null;
412                         try {
413                                 RSAPKCS1SignatureFormatter sign = new RSAPKCS1SignatureFormatter (rsa);
414                                 sign.SetHashAlgorithm (TokenAlgorithm);
415                                 signature = sign.CreateSignature (sn.Hash);
416                                 Array.Reverse (signature);
417                         }
418                         catch (CryptographicException) {
419                                 return false;
420                         }
421
422                         using (FileStream fs = File.OpenWrite (fileName)) {
423                                 fs.Position = sn.SignaturePosition;
424                                 fs.Write (signature, 0, signature.Length);
425                                 fs.Close ();
426                                 result = true;
427                         }
428                         return result;
429                 }
430
431                 public bool Verify (string fileName) 
432                 {
433                         bool result = false;
434                         using (FileStream fs = File.OpenRead (fileName)) {
435                                 result = Verify (fs);
436                                 fs.Close ();
437                         }
438                         return result;
439                 }
440
441                 public bool Verify (Stream stream)
442                 {
443                         StrongNameSignature sn = StrongHash (stream, StrongNameOptions.Signature);
444                         if (sn.Hash == null) {
445                                 return false;
446                         }
447
448                         try {
449                                 AssemblyHashAlgorithm algorithm = AssemblyHashAlgorithm.SHA1;
450                                 if (tokenAlgorithm == "MD5")
451                                         algorithm = AssemblyHashAlgorithm.MD5;
452                                 return Verify (rsa, algorithm, sn.Hash, sn.Signature);
453                         }
454                         catch (CryptographicException) {
455                                 // no exception allowed
456                                 return false;
457                         }
458                 }
459
460 #if INSIDE_CORLIB
461                 static object lockObject = new object ();
462                 static bool initialized = false;
463
464                 // We don't want a dependency on StrongNameManager in Mono.Security.dll
465                 static public bool IsAssemblyStrongnamed (string assemblyName) 
466                 {
467                         if (!initialized) {
468                                 lock (lockObject) {
469                                         if (!initialized) {
470 #if NET_2_1
471                                                 // Moonlight cannot depend on machine.config
472 #else
473                                                 string config = Environment.GetMachineConfigPath ();
474                                                 StrongNameManager.LoadConfig (config);
475 #endif
476                                                 initialized = true;
477                                         }
478                                 }
479                         }
480
481                         try {
482                                 // this doesn't load the assembly (well it unloads it ;)
483                                 // http://weblogs.asp.net/nunitaddin/posts/9991.aspx
484                                 AssemblyName an = AssemblyName.GetAssemblyName (assemblyName);
485                                 if (an == null)
486                                         return false;
487
488                                 byte[] publicKey = StrongNameManager.GetMappedPublicKey (an.GetPublicKeyToken ());
489                                 if ((publicKey == null) || (publicKey.Length < 12)) {
490                                         // no mapping
491                                         publicKey = an.GetPublicKey ();
492                                         if ((publicKey == null) || (publicKey.Length < 12))
493                                                 return false;
494                                 }
495
496                                 // Note: MustVerify is based on the original token (by design). Public key
497                                 // remapping won't affect if the assembly is verified or not.
498                                 if (!StrongNameManager.MustVerify (an)) {
499                                         return true;
500                                 }
501
502                                 RSA rsa = CryptoConvert.FromCapiPublicKeyBlob (publicKey, 12);
503                                 StrongName sn = new StrongName (rsa);
504                                 bool result = sn.Verify (assemblyName);
505                                 return result;
506                         }
507                         catch {
508                                 // no exception allowed
509                                 return false;
510                         }
511                 }
512
513                 // TODO
514                 // we would get better performance if the runtime hashed the
515                 // assembly - as we wouldn't have to load it from disk a 
516                 // second time. The runtime already have implementations of
517                 // SHA1 (and even MD5 if required someday).
518                 static public bool VerifySignature (byte[] publicKey, int algorithm, byte[] hash, byte[] signature) 
519                 {
520                         try {
521                                 RSA rsa = CryptoConvert.FromCapiPublicKeyBlob (publicKey);
522                                 return Verify (rsa, (AssemblyHashAlgorithm) algorithm, hash, signature);
523                         }
524                         catch {
525                                 // no exception allowed
526                                 return false;
527                         }
528                 }
529 #endif
530                 static private bool Verify (RSA rsa, AssemblyHashAlgorithm algorithm, byte[] hash, byte[] signature) 
531                 {
532                         RSAPKCS1SignatureDeformatter vrfy = new RSAPKCS1SignatureDeformatter (rsa);
533                         switch (algorithm) {
534                         case AssemblyHashAlgorithm.MD5:
535                                 vrfy.SetHashAlgorithm ("MD5");
536                                 break;
537                         case AssemblyHashAlgorithm.SHA1:
538                         case AssemblyHashAlgorithm.None:
539                         default:
540                                 vrfy.SetHashAlgorithm ("SHA1");
541                                 break;
542                         }
543                         return vrfy.VerifySignature (hash, signature);
544                 }
545         }
546 }