**** Merged from MCS ****
[mono-project.git] / mcs / class / Mono.Security / Mono.Security / StrongName.cs
blob925b2707082aedf3338729002b2dac9e1f60a70b
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 //
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.
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;
39 using Mono.Security.Cryptography;
41 namespace Mono.Security {
43 #if INSIDE_CORLIB
44 internal
45 #else
46 public
47 #endif
48 sealed class StrongName {
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;
60 public byte[] Hash {
61 get { return hash; }
62 set { hash = value; }
65 public byte[] Signature {
66 get { return signature; }
67 set { signature = value; }
70 public UInt32 MetadataPosition {
71 get { return metadataPosition; }
72 set { metadataPosition = value; }
75 public UInt32 MetadataLength {
76 get { return metadataLength; }
77 set { metadataLength = value; }
80 public UInt32 SignaturePosition {
81 get { return signaturePosition; }
82 set { signaturePosition = value; }
85 public UInt32 SignatureLength {
86 get { return signatureLength; }
87 set { signatureLength = value; }
90 // delay signed -> flag = 0x01
91 // strongsigned -> flag = 0x09
92 public byte CliFlag {
93 get { return cliFlag; }
94 set { cliFlag = value; }
97 public UInt32 CliFlagPosition {
98 get { return cliFlagPosition; }
99 set { cliFlagPosition = value; }
103 internal enum StrongNameOptions {
104 Metadata,
105 Signature
108 private RSA rsa;
109 private byte[] publicKey;
110 private byte[] keyToken;
111 private string tokenAlgorithm;
113 public StrongName ()
117 public StrongName (byte[] data)
119 if (data == null)
120 throw new ArgumentNullException ("data");
122 // check for ECMA key
123 if (data.Length == 16) {
124 int i = 0;
125 int sum = 0;
126 while (i < data.Length)
127 sum += data [i++];
128 if (sum == 4) {
129 // it is the ECMA key
130 publicKey = (byte[]) data.Clone ();
133 else {
134 RSA = CryptoConvert.FromCapiKeyBlob (data);
135 if (rsa == null)
136 throw new ArgumentException ("data isn't a correctly encoded RSA public key");
140 public StrongName (RSA rsa)
142 if (rsa == null)
143 throw new ArgumentNullException ("rsa");
145 RSA = rsa;
148 private void InvalidateCache ()
150 publicKey = null;
151 keyToken = null;
154 public bool CanSign {
155 get {
156 if (rsa == null)
157 return false;
158 #if INSIDE_CORLIB
159 // the easy way
160 if (RSA is RSACryptoServiceProvider) {
161 // available as internal for corlib
162 return !(rsa as RSACryptoServiceProvider).PublicOnly;
164 else
165 #endif
166 if (RSA is RSAManaged) {
167 return !(rsa as RSAManaged).PublicOnly;
169 else {
170 // the hard way
171 try {
172 RSAParameters p = rsa.ExportParameters (true);
173 return ((p.D != null) && (p.P != null) && (p.Q != null));
175 catch (CryptographicException) {
176 return false;
182 public RSA RSA {
183 get {
184 // if none then we create a new keypair
185 if (rsa == null)
186 rsa = (RSA) RSA.Create ();
187 return rsa;
189 set {
190 rsa = value;
191 InvalidateCache ();
195 public byte[] PublicKey {
196 get {
197 if (publicKey == null) {
198 byte[] keyPair = CryptoConvert.ToCapiKeyBlob (rsa, false);
199 publicKey = new byte [32 + 128]; // always 1024 bits
201 // The first 12 bytes are documented at:
202 // http://msdn.microsoft.com/library/en-us/cprefadd/html/grfungethashfromfile.asp
203 // ALG_ID - Signature
204 publicKey [0] = keyPair [4];
205 publicKey [1] = keyPair [5];
206 publicKey [2] = keyPair [6];
207 publicKey [3] = keyPair [7];
208 // ALG_ID - Hash (SHA1 == 0x8004)
209 publicKey [4] = 0x04;
210 publicKey [5] = 0x80;
211 publicKey [6] = 0x00;
212 publicKey [7] = 0x00;
213 // Length of Public Key (in bytes)
214 byte[] lastPart = BitConverterLE.GetBytes (publicKey.Length - 12);
215 publicKey [8] = lastPart [0];
216 publicKey [9] = lastPart [1];
217 publicKey [10] = lastPart [2];
218 publicKey [11] = lastPart [3];
219 // Ok from here - Same structure as keypair - expect for public key
220 publicKey [12] = 0x06; // PUBLICKEYBLOB
221 // we can copy this part
222 Buffer.BlockCopy (keyPair, 1, publicKey, 13, publicKey.Length - 13);
223 // and make a small adjustment
224 publicKey [23] = 0x31; // (RSA1 not RSA2)
226 return (byte[]) publicKey.Clone ();
230 public byte[] PublicKeyToken {
231 get {
232 if (keyToken == null) {
233 byte[] publicKey = PublicKey;
234 if (publicKey == null)
235 return null;
236 HashAlgorithm ha = SHA1.Create (TokenAlgorithm);
237 byte[] hash = ha.ComputeHash (publicKey);
238 // we need the last 8 bytes in reverse order
239 keyToken = new byte [8];
240 Buffer.BlockCopy (hash, (hash.Length - 8), keyToken, 0, 8);
241 Array.Reverse (keyToken, 0, 8);
243 return (byte[]) keyToken.Clone ();
247 public string TokenAlgorithm {
248 get {
249 if (tokenAlgorithm == null)
250 tokenAlgorithm = "SHA1";
251 return tokenAlgorithm;
253 set {
254 string algo = value.ToUpper (CultureInfo.InvariantCulture);
255 if ((algo == "SHA1") || (algo == "MD5")) {
256 tokenAlgorithm = value;
257 InvalidateCache ();
259 else
260 throw new ArgumentException ("Unsupported hash algorithm for token");
264 public byte[] GetBytes ()
266 return CryptoConvert.ToCapiPrivateKeyBlob (RSA);
269 private UInt32 RVAtoPosition (UInt32 r, int sections, byte[] headers)
271 for (int i=0; i < sections; i++) {
272 UInt32 p = BitConverterLE.ToUInt32 (headers, i * 40 + 20);
273 UInt32 s = BitConverterLE.ToUInt32 (headers, i * 40 + 12);
274 int l = (int) BitConverterLE.ToUInt32 (headers, i * 40 + 8);
275 if ((s <= r) && (r < s + l)) {
276 return p + r - s;
279 return 0;
282 internal StrongNameSignature StrongHash (Stream stream, StrongNameOptions options)
284 StrongNameSignature info = new StrongNameSignature ();
286 HashAlgorithm hash = HashAlgorithm.Create (TokenAlgorithm);
287 CryptoStream cs = new CryptoStream (Stream.Null, hash, CryptoStreamMode.Write);
289 // MS-DOS Header - always 128 bytes
290 // ref: Section 24.2.1, Partition II Metadata
291 byte[] mz = new byte [128];
292 stream.Read (mz, 0, 128);
293 if (BitConverterLE.ToUInt16 (mz, 0) != 0x5a4d)
294 return null;
295 UInt32 peHeader = BitConverterLE.ToUInt32 (mz, 60);
296 cs.Write (mz, 0, 128);
297 if (peHeader != 128) {
298 byte[] mzextra = new byte [peHeader - 128];
299 stream.Read (mzextra, 0, mzextra.Length);
300 cs.Write (mzextra, 0, mzextra.Length);
303 // PE File Header - always 248 bytes
304 // ref: Section 24.2.2, Partition II Metadata
305 byte[] pe = new byte [248];
306 stream.Read (pe, 0, 248);
307 if (BitConverterLE.ToUInt32 (pe, 0) != 0x4550)
308 return null;
309 if (BitConverterLE.ToUInt16 (pe, 4) != 0x14c)
310 return null;
311 // MUST zeroize both CheckSum and Security Directory
312 byte[] v = new byte [8];
313 Buffer.BlockCopy (v, 0, pe, 88, 4);
314 Buffer.BlockCopy (v, 0, pe, 152, 8);
315 cs.Write (pe, 0, 248);
317 UInt16 numSection = BitConverterLE.ToUInt16 (pe, 6);
318 int sectionLength = (numSection * 40);
319 byte[] sectionHeaders = new byte [sectionLength];
320 stream.Read (sectionHeaders, 0, sectionLength);
321 cs.Write (sectionHeaders, 0, sectionLength);
323 UInt32 cliHeaderRVA = BitConverterLE.ToUInt32 (pe, 232);
324 UInt32 cliHeaderPos = RVAtoPosition (cliHeaderRVA, numSection, sectionHeaders);
325 int cliHeaderSiz = (int) BitConverterLE.ToUInt32 (pe, 236);
327 // CLI Header
328 // ref: Section 24.3.3, Partition II Metadata
329 byte[] cli = new byte [cliHeaderSiz];
330 stream.Position = cliHeaderPos;
331 stream.Read (cli, 0, cliHeaderSiz);
333 UInt32 strongNameSignatureRVA = BitConverterLE.ToUInt32 (cli, 32);
334 info.SignaturePosition = RVAtoPosition (strongNameSignatureRVA, numSection, sectionHeaders);
335 info.SignatureLength = BitConverterLE.ToUInt32 (cli, 36);
337 UInt32 metadataRVA = BitConverterLE.ToUInt32 (cli, 8);
338 info.MetadataPosition = RVAtoPosition (metadataRVA, numSection, sectionHeaders);
339 info.MetadataLength = BitConverterLE.ToUInt32 (cli, 12);
341 if (options == StrongNameOptions.Metadata) {
342 cs.Close ();
343 hash.Initialize ();
344 byte[] metadata = new byte [info.MetadataLength];
345 stream.Position = info.MetadataPosition;
346 stream.Read (metadata, 0, metadata.Length);
347 info.Hash = hash.ComputeHash (metadata);
348 return info;
351 // now we hash every section EXCEPT the signature block
352 for (int i=0; i < numSection; i++) {
353 UInt32 start = BitConverterLE.ToUInt32 (sectionHeaders, i * 40 + 20);
354 int length = (int) BitConverterLE.ToUInt32 (sectionHeaders, i * 40 + 16);
355 byte[] section = new byte [length];
356 stream.Position = start;
357 stream.Read (section, 0, length);
358 if ((start <= info.SignaturePosition) && (info.SignaturePosition < start + length)) {
359 // hash before the signature
360 int before = (int)(info.SignaturePosition - start);
361 if (before > 0) {
362 cs.Write (section, 0, before);
364 // copy signature
365 info.Signature = new byte [info.SignatureLength];
366 Buffer.BlockCopy (section, before, info.Signature, 0, (int)info.SignatureLength);
367 Array.Reverse (info.Signature);
368 // hash after the signature
369 int s = (int)(before + info.SignatureLength);
370 int after = (int)(length - s);
371 if (after > 0) {
372 cs.Write (section, s, after);
375 else
376 cs.Write (section, 0, length);
379 cs.Close ();
380 info.Hash = hash.Hash;
381 return info;
384 // return the same result as the undocumented and unmanaged GetHashFromAssemblyFile
385 public byte[] Hash (string fileName)
387 FileStream fs = File.OpenRead (fileName);
388 StrongNameSignature sn = StrongHash (fs, StrongNameOptions.Metadata);
389 fs.Close ();
391 return sn.Hash;
394 public bool Sign (string fileName)
396 bool result = false;
397 StrongNameSignature sn;
398 using (FileStream fs = File.OpenRead (fileName)) {
399 sn = StrongHash (fs, StrongNameOptions.Signature);
400 fs.Close ();
402 if (sn.Hash == null)
403 return false;
405 byte[] signature = null;
406 try {
407 RSAPKCS1SignatureFormatter sign = new RSAPKCS1SignatureFormatter (rsa);
408 sign.SetHashAlgorithm (TokenAlgorithm);
409 signature = sign.CreateSignature (sn.Hash);
410 Array.Reverse (signature);
412 catch (CryptographicException) {
413 return false;
416 using (FileStream fs = File.OpenWrite (fileName)) {
417 fs.Position = sn.SignaturePosition;
418 fs.Write (signature, 0, signature.Length);
419 fs.Close ();
420 result = true;
422 return result;
425 public bool Verify (string fileName)
427 StrongNameSignature sn;
428 using (FileStream fs = File.OpenRead (fileName)) {
429 sn = StrongHash (fs, StrongNameOptions.Signature);
430 fs.Close ();
432 if (sn.Hash == null) {
433 return false;
436 try {
437 AssemblyHashAlgorithm algorithm = AssemblyHashAlgorithm.SHA1;
438 if (tokenAlgorithm == "MD5")
439 algorithm = AssemblyHashAlgorithm.MD5;
440 return Verify (rsa, algorithm, sn.Hash, sn.Signature);
442 catch (CryptographicException) {
443 // no exception allowed
444 return false;
448 #if INSIDE_CORLIB
449 static object lockObject = new object ();
450 static bool initialized = false;
452 // We don't want a dependency on StrongNameManager in Mono.Security.dll
453 static public bool IsAssemblyStrongnamed (string assemblyName)
455 if (!initialized) {
456 lock (lockObject) {
457 if (!initialized) {
458 string config = Environment.GetMachineConfigPath ();
459 StrongNameManager.LoadConfig (config);
460 initialized = true;
465 try {
466 // this doesn't load the assembly (well it unloads it ;)
467 // http://weblogs.asp.net/nunitaddin/posts/9991.aspx
468 AssemblyName an = AssemblyName.GetAssemblyName (assemblyName);
469 if (an == null)
470 return false;
472 byte[] publicKey = StrongNameManager.GetMappedPublicKey (an.GetPublicKeyToken ());
473 if ((publicKey == null) || (publicKey.Length < 12)) {
474 // no mapping
475 publicKey = an.GetPublicKey ();
476 if ((publicKey == null) || (publicKey.Length < 12))
477 return false;
480 // Note: MustVerify is based on the original token (by design). Public key
481 // remapping won't affect if the assembly is verified or not.
482 if (!StrongNameManager.MustVerify (an)) {
483 return true;
486 RSA rsa = CryptoConvert.FromCapiPublicKeyBlob (publicKey, 12);
487 StrongName sn = new StrongName (rsa);
488 bool result = sn.Verify (assemblyName);
489 return result;
491 catch {
492 // no exception allowed
493 return false;
497 // TODO
498 // we would get better performance if the runtime hashed the
499 // assembly - as we wouldn't have to load it from disk a
500 // second time. The runtime already have implementations of
501 // SHA1 (and even MD5 if required someday).
502 static public bool VerifySignature (byte[] publicKey, int algorithm, byte[] hash, byte[] signature)
504 try {
505 RSA rsa = CryptoConvert.FromCapiPublicKeyBlob (publicKey);
506 return Verify (rsa, (AssemblyHashAlgorithm) algorithm, hash, signature);
508 catch {
509 // no exception allowed
510 return false;
513 #endif
514 static private bool Verify (RSA rsa, AssemblyHashAlgorithm algorithm, byte[] hash, byte[] signature)
516 RSAPKCS1SignatureDeformatter vrfy = new RSAPKCS1SignatureDeformatter (rsa);
517 switch (algorithm) {
518 case AssemblyHashAlgorithm.MD5:
519 vrfy.SetHashAlgorithm ("MD5");
520 break;
521 case AssemblyHashAlgorithm.SHA1:
522 case AssemblyHashAlgorithm.None:
523 default:
524 vrfy.SetHashAlgorithm ("SHA1");
525 break;
527 return vrfy.VerifySignature (hash, signature);