2004-03-23 Sebastien Pouliot <sebastien@ximian.com>
[mono.git] / mcs / class / Mono.Security / Mono.Security / StrongName.cs
1 //
2 // StrongName.cs - Strong Name Implementation
3 //
4 // Author:
5 //      Sebastien Pouliot (spouliot@motus.com)
6 //
7 // (C) 2003 Motus Technologies Inc. (http://www.motus.com)
8 //
9
10 using System;
11 using System.IO;
12 using System.Security.Cryptography;
13
14 using Mono.Security.Cryptography;
15
16 namespace Mono.Security {
17
18 #if INSIDE_CORLIB
19         internal
20 #else
21         public
22 #endif
23         class StrongName {
24
25                 internal class StrongNameSignature {
26                         private byte[] hash;
27                         private byte[] signature;
28                         private UInt32 signaturePosition;
29                         private UInt32 signatureLength;
30                         private UInt32 metadataPosition;
31                         private UInt32 metadataLength;
32                         private byte cliFlag;
33                         private UInt32 cliFlagPosition;
34
35                         public byte[] Hash {
36                                 get { return hash; }
37                                 set { hash = value; }
38                         }
39
40                         public byte[] Signature {
41                                 get { return signature; }
42                                 set { signature = value; }
43                         }
44
45                         public UInt32 MetadataPosition {
46                                 get { return metadataPosition; }
47                                 set { metadataPosition = value; }
48                         }
49
50                         public UInt32 MetadataLength {
51                                 get { return metadataLength; }
52                                 set { metadataLength = value; }
53                         }
54
55                         public UInt32 SignaturePosition {
56                                 get { return signaturePosition; }
57                                 set { signaturePosition = value; }
58                         }
59
60                         public UInt32 SignatureLength {
61                                 get { return signatureLength; }
62                                 set { signatureLength = value; }
63                         }
64
65                         // delay signed -> flag = 0x01
66                         // strongsigned -> flag = 0x09
67                         public byte CliFlag {
68                                 get { return cliFlag; }
69                                 set { cliFlag = value; }
70                         }
71
72                         public UInt32 CliFlagPosition {
73                                 get { return cliFlagPosition; }
74                                 set { cliFlagPosition = value; }
75                         }
76                 }
77
78                 internal enum StrongNameOptions {
79                         Metadata,
80                         Signature
81                 }
82
83                 private RSA rsa;
84                 private byte[] publicKey;
85                 private byte[] keyToken;
86                 private string tokenAlgorithm;
87
88                 public StrongName ()
89                 {
90                 }
91
92                 public StrongName (byte[] data)
93                 {
94                         if (data == null)
95                                 throw new ArgumentNullException ("data");
96
97                         RSA = CryptoConvert.FromCapiKeyBlob (data);
98                         if (rsa == null)
99                                 throw new ArgumentException ("data isn't a correctly encoded RSA public key");
100                 }
101
102                 public StrongName (RSA rsa) : base ()
103                 {
104                         if (rsa == null)
105                                 throw new ArgumentNullException ("rsa");
106
107                         RSA = rsa;
108                 }
109
110                 private void InvalidateCache () 
111                 {
112                         publicKey = null;
113                         keyToken = null;
114                 }
115
116                 public RSA RSA {
117                         get {
118                                 // if none then we create a new keypair
119                                 if (rsa == null)
120                                         rsa = (RSA) RSA.Create ();
121                                 return rsa; 
122                         }
123                         set { 
124                                 rsa = value;
125                                 InvalidateCache ();
126                         }
127                 }
128
129                 public byte[] PublicKey {
130                         get { 
131                                 if (publicKey == null) {
132                                         byte[] keyPair = CryptoConvert.ToCapiKeyBlob (rsa, false); 
133                                         publicKey = new byte [32 + 128]; // always 1024 bits
134
135                                         // The first 12 bytes are documented at:
136                                         // http://msdn.microsoft.com/library/en-us/cprefadd/html/grfungethashfromfile.asp
137                                         // ALG_ID - Signature
138                                         publicKey [0] = keyPair [4];
139                                         publicKey [1] = keyPair [5];    
140                                         publicKey [2] = keyPair [6];    
141                                         publicKey [3] = keyPair [7];    
142                                         // ALG_ID - Hash (SHA1 == 0x8004)
143                                         publicKey [4] = 0x04;
144                                         publicKey [5] = 0x80;
145                                         publicKey [6] = 0x00;
146                                         publicKey [7] = 0x00;
147                                         // Length of Public Key (in bytes)
148                                         byte[] lastPart = BitConverter.GetBytes (publicKey.Length - 12);
149                                         publicKey [8] = lastPart [0];
150                                         publicKey [9] = lastPart [1];
151                                         publicKey [10] = lastPart [2];
152                                         publicKey [11] = lastPart [3];
153                                         // Ok from here - Same structure as keypair - expect for public key
154                                         publicKey [12] = 0x06;          // PUBLICKEYBLOB
155                                         // we can copy this part
156                                         Array.Copy (keyPair, 1, publicKey, 13, publicKey.Length - 13);
157                                         // and make a small adjustment 
158                                         publicKey [23] = 0x31;          // (RSA1 not RSA2)
159                                 }
160                                 return publicKey;
161                         }
162                 }
163
164                 public byte[] PublicKeyToken {
165                         get {
166                                 if (keyToken != null)
167                                         return keyToken;
168                                 byte[] publicKey = PublicKey;
169                                 if (publicKey == null)
170                                         return null;
171                                 HashAlgorithm ha = SHA1.Create (TokenAlgorithm);
172                                 byte[] hash = ha.ComputeHash (publicKey);
173                                 // we need the last 8 bytes in reverse order
174                                 keyToken = new byte [8];
175                                 Array.Copy (hash, (hash.Length - 8), keyToken, 0, 8);
176                                 Array.Reverse (keyToken, 0, 8);
177                                 return keyToken;
178                         }
179                 }
180
181                 public string TokenAlgorithm {
182                         get { 
183                                 if (tokenAlgorithm == null)
184                                         tokenAlgorithm = "SHA1";
185                                 return tokenAlgorithm; 
186                         }
187                         set {
188                                 string algo = value.ToUpper ();
189                                 if ((algo == "SHA1") || (algo == "MD5")) {
190                                         tokenAlgorithm = value;
191                                         InvalidateCache ();
192                                 }
193                                 else
194                                         throw new ArgumentException ("Unsupported hash algorithm for token");
195                         }
196                 }
197
198                 public byte[] GetBytes () 
199                 {
200                         return CryptoConvert.ToCapiPrivateKeyBlob (rsa);
201                 }
202
203                 private UInt32 RVAtoPosition (UInt32 r, int sections, byte[] headers) 
204                 {
205                         for (int i=0; i < sections; i++) {
206                                 UInt32 p = BitConverter.ToUInt32 (headers, i * 40 + 20);
207                                 UInt32 s = BitConverter.ToUInt32 (headers, i * 40 + 12);
208                                 int l = (int) BitConverter.ToUInt32 (headers, i * 40 + 8);
209                                 if ((s <= r) && (r < s + l)) {
210                                         return p + r - s;
211                                 }
212                         }
213                         return 0;
214                 }
215
216                 internal StrongNameSignature StrongHash (Stream stream, StrongNameOptions options)
217                 {
218                         StrongNameSignature info = new StrongNameSignature ();
219
220                         HashAlgorithm hash = HashAlgorithm.Create (TokenAlgorithm);
221                         CryptoStream cs = new CryptoStream (Stream.Null, hash, CryptoStreamMode.Write);
222
223                         // MS-DOS Header - always 128 bytes
224                         // ref: Section 24.2.1, Partition II Metadata
225                         byte[] mz = new byte [128];
226                         stream.Read (mz, 0, 128);
227                         if (BitConverter.ToUInt16 (mz, 0) != 0x5a4d)
228                                 return null;
229                         UInt32 peHeader = BitConverter.ToUInt32 (mz, 60);
230                         cs.Write (mz, 0, 128);
231                         if (peHeader != 128) {
232                                 byte[] mzextra = new byte [peHeader - 128];
233                                 stream.Read (mzextra, 0, mzextra.Length);
234                                 cs.Write (mzextra, 0, mzextra.Length);
235                         }
236
237                         // PE File Header - always 248 bytes
238                         // ref: Section 24.2.2, Partition II Metadata
239                         byte[] pe = new byte [248];
240                         stream.Read (pe, 0, 248);
241                         if (BitConverter.ToUInt32 (pe, 0) != 0x4550)
242                                 return null;
243                         if (BitConverter.ToUInt16 (pe, 4) != 0x14c)
244                                 return null;
245                         // MUST zeroize both CheckSum and Security Directory
246                         byte[] v = new byte [8];
247                         Buffer.BlockCopy (v, 0, pe, 88, 4);
248                         Buffer.BlockCopy (v, 0, pe, 152, 8);
249                         cs.Write (pe, 0, 248);
250
251                         UInt16 numSection = BitConverter.ToUInt16 (pe, 6);
252                         int sectionLength = (numSection * 40);
253                         byte[] sectionHeaders = new byte [sectionLength];
254                         stream.Read (sectionHeaders, 0, sectionLength);
255                         cs.Write (sectionHeaders, 0, sectionLength);
256
257                         UInt32 cliHeaderRVA = BitConverter.ToUInt32 (pe, 232);
258                         UInt32 cliHeaderPos = RVAtoPosition (cliHeaderRVA, numSection, sectionHeaders);
259                         int cliHeaderSiz = (int) BitConverter.ToUInt32 (pe, 236);
260
261                         // CLI Header
262                         // ref: Section 24.3.3, Partition II Metadata
263                         byte[] cli = new byte [cliHeaderSiz];
264                         stream.Position = cliHeaderPos;
265                         stream.Read (cli, 0, cliHeaderSiz);
266
267                         UInt32 strongNameSignatureRVA = BitConverter.ToUInt32 (cli, 32);
268                         info.SignaturePosition = RVAtoPosition (strongNameSignatureRVA, numSection, sectionHeaders);
269                         info.SignatureLength = BitConverter.ToUInt32 (cli, 36);
270
271                         UInt32 metadataRVA = BitConverter.ToUInt32 (cli, 8);
272                         info.MetadataPosition = RVAtoPosition (metadataRVA, numSection, sectionHeaders);
273                         info.MetadataLength = BitConverter.ToUInt32 (cli, 12);
274
275                         if (options == StrongNameOptions.Metadata) {
276                                 cs.Close ();
277                                 hash.Initialize ();
278                                 byte[] metadata = new byte [info.MetadataLength];
279                                 stream.Position = info.MetadataPosition;
280                                 stream.Read (metadata, 0, metadata.Length);
281                                 info.Hash = hash.ComputeHash (metadata);
282                                 return info;
283                         }
284
285                         // now we hash every section EXCEPT the signature block
286                         for (int i=0; i < numSection; i++) {
287                                 UInt32 start = BitConverter.ToUInt32 (sectionHeaders, i * 40 + 20);
288                                 int length = (int) BitConverter.ToUInt32 (sectionHeaders, i * 40 + 16);
289                                 byte[] section = new byte [length];
290                                 stream.Position = start;
291                                 stream.Read (section, 0, length);
292                                 if ((start <= info.SignaturePosition) && (info.SignaturePosition < start + length)) {
293                                         // hash before the signature
294                                         int before = (int)(info.SignaturePosition - start);
295                                         if (before > 0) {
296                                                 cs.Write (section, 0, before);
297                                         }
298                                         // copy signature
299                                         info.Signature = new byte [info.SignatureLength];
300                                         Buffer.BlockCopy (section, before, info.Signature, 0, (int)info.SignatureLength);
301                                         Array.Reverse (info.Signature);
302                                         // hash after the signature
303                                         int s = (int)(before + info.SignatureLength);
304                                         int after = (int)(length - s);
305                                         if (after > 0) {
306                                                 cs.Write (section, s, after);
307                                         }
308                                 }
309                                 else
310                                         cs.Write (section, 0, length);
311                         }
312
313                         cs.Close ();
314                         info.Hash = hash.Hash;
315                         return info;
316                 }
317
318                 // return the same result as the undocumented and unmanaged GetHashFromAssemblyFile
319                 public byte[] Hash (string fileName) 
320                 {
321                         FileStream fs = File.OpenRead (fileName);
322                         StrongNameSignature sn = StrongHash (fs, StrongNameOptions.Metadata);
323                         fs.Close ();
324
325                         return sn.Hash;
326                 }
327
328                 public bool Sign (string fileName) 
329                 {
330                         bool result = false;
331                         StrongNameSignature sn;
332                         FileStream fs = File.OpenRead (fileName);
333                         try {
334                                 sn = StrongHash (fs, StrongNameOptions.Signature);
335                                 if (sn.Hash == null)
336                                         return false;
337                         }
338                         finally {
339                                 fs.Close ();
340                         }
341
342                         byte[] signature = null;
343                         try {
344                                 RSAPKCS1SignatureFormatter sign = new RSAPKCS1SignatureFormatter (rsa);
345                                 sign.SetHashAlgorithm (TokenAlgorithm);
346                                 signature = sign.CreateSignature (sn.Hash);
347                                 Array.Reverse (signature);
348                         }
349                         catch {
350                                 return false;
351                         }
352
353                         try {
354                                 fs = File.OpenWrite (fileName);
355                                 fs.Position = sn.SignaturePosition;
356                                 fs.Write (signature, 0, signature.Length);
357                         }
358                         catch {
359                         }
360                         finally {
361                                 fs.Close ();
362                                 result = true;
363                         }
364                         return result;
365                 }
366
367                 public bool Verify (string fileName) 
368                 {
369                         bool result = false;
370                         StrongNameSignature sn;
371                         FileStream fs = File.OpenRead (fileName);
372                         try {
373                                 sn = StrongHash (fs, StrongNameOptions.Signature);
374                                 if (sn.Hash == null)
375                                         return false;
376                         }
377                         finally {
378                                 fs.Close ();
379                         }
380
381                         try {
382                                 RSAPKCS1SignatureDeformatter vrfy = new RSAPKCS1SignatureDeformatter (rsa);
383                                 vrfy.SetHashAlgorithm (TokenAlgorithm);
384                                 result = vrfy.VerifySignature (sn.Hash, sn.Signature);
385                         }
386                         catch {
387                         }
388                         return result;
389                 }
390         }       
391 }