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