2 // StrongName.cs - Strong Name Implementation
5 // Sebastien Pouliot (sebastien@ximian.com)
7 // (C) 2003 Motus Technologies Inc. (http://www.motus.com)
8 // (C) 2004 Novell (http://www.novell.com)
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:
20 // The above copyright notice and this permission notice shall be
21 // included in all copies or substantial portions of the Software.
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.
33 using System.Configuration.Assemblies;
34 using System.Globalization;
36 using System.Reflection;
37 using System.Security.Cryptography;
39 using Mono.Security.Cryptography;
41 namespace Mono.Security {
48 sealed class StrongName {
50 internal class StrongNameSignature {
52 private byte[] signature;
53 private UInt32 signaturePosition;
54 private UInt32 signatureLength;
55 private UInt32 metadataPosition;
56 private UInt32 metadataLength;
58 private UInt32 cliFlagPosition;
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
93 get { return cliFlag; }
94 set { cliFlag = value; }
97 public UInt32 CliFlagPosition {
98 get { return cliFlagPosition; }
99 set { cliFlagPosition = value; }
103 internal enum StrongNameOptions {
109 private byte[] publicKey;
110 private byte[] keyToken;
111 private string tokenAlgorithm;
117 public StrongName (int keySize)
119 rsa = new RSAManaged (keySize);
122 public StrongName (byte[] data)
125 throw new ArgumentNullException ("data");
127 // check for ECMA key
128 if (data.Length == 16) {
131 while (i < data.Length)
134 // it is the ECMA key
135 publicKey = (byte[]) data.Clone ();
139 RSA = CryptoConvert.FromCapiKeyBlob (data);
141 throw new ArgumentException ("data isn't a correctly encoded RSA public key");
145 public StrongName (RSA rsa)
148 throw new ArgumentNullException ("rsa");
153 private void InvalidateCache ()
159 public bool CanSign {
165 if (RSA is RSACryptoServiceProvider) {
166 // available as internal for corlib
167 return !(rsa as RSACryptoServiceProvider).PublicOnly;
171 if (RSA is RSAManaged) {
172 return !(rsa as RSAManaged).PublicOnly;
177 RSAParameters p = rsa.ExportParameters (true);
178 return ((p.D != null) && (p.P != null) && (p.Q != null));
180 catch (CryptographicException) {
189 // if none then we create a new keypair
191 rsa = (RSA) RSA.Create ();
200 public byte[] PublicKey {
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)];
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)
232 return (byte[]) publicKey.Clone ();
236 public byte[] PublicKeyToken {
238 if (keyToken == null) {
239 byte[] publicKey = PublicKey;
240 if (publicKey == 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);
249 return (byte[]) keyToken.Clone ();
253 public string TokenAlgorithm {
255 if (tokenAlgorithm == null)
256 tokenAlgorithm = "SHA1";
257 return tokenAlgorithm;
260 string algo = value.ToUpper (CultureInfo.InvariantCulture);
261 if ((algo == "SHA1") || (algo == "MD5")) {
262 tokenAlgorithm = value;
266 throw new ArgumentException ("Unsupported hash algorithm for token");
270 public byte[] GetBytes ()
272 return CryptoConvert.ToCapiPrivateKeyBlob (RSA);
275 private UInt32 RVAtoPosition (UInt32 r, int sections, byte[] headers)
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)) {
288 internal StrongNameSignature StrongHash (Stream stream, StrongNameOptions options)
290 StrongNameSignature info = new StrongNameSignature ();
292 HashAlgorithm hash = HashAlgorithm.Create (TokenAlgorithm);
293 CryptoStream cs = new CryptoStream (Stream.Null, hash, CryptoStreamMode.Write);
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)
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);
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)
315 if (BitConverterLE.ToUInt16 (pe, 4) != 0x14c)
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);
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);
329 UInt32 cliHeaderRVA = BitConverterLE.ToUInt32 (pe, 232);
330 UInt32 cliHeaderPos = RVAtoPosition (cliHeaderRVA, numSection, sectionHeaders);
331 int cliHeaderSiz = (int) BitConverterLE.ToUInt32 (pe, 236);
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);
339 UInt32 strongNameSignatureRVA = BitConverterLE.ToUInt32 (cli, 32);
340 info.SignaturePosition = RVAtoPosition (strongNameSignatureRVA, numSection, sectionHeaders);
341 info.SignatureLength = BitConverterLE.ToUInt32 (cli, 36);
343 UInt32 metadataRVA = BitConverterLE.ToUInt32 (cli, 8);
344 info.MetadataPosition = RVAtoPosition (metadataRVA, numSection, sectionHeaders);
345 info.MetadataLength = BitConverterLE.ToUInt32 (cli, 12);
347 if (options == StrongNameOptions.Metadata) {
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);
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);
368 cs.Write (section, 0, before);
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);
378 cs.Write (section, s, after);
382 cs.Write (section, 0, length);
386 info.Hash = hash.Hash;
390 // return the same result as the undocumented and unmanaged GetHashFromAssemblyFile
391 public byte[] Hash (string fileName)
393 FileStream fs = File.OpenRead (fileName);
394 StrongNameSignature sn = StrongHash (fs, StrongNameOptions.Metadata);
400 public bool Sign (string fileName)
403 StrongNameSignature sn;
404 using (FileStream fs = File.OpenRead (fileName)) {
405 sn = StrongHash (fs, StrongNameOptions.Signature);
411 byte[] signature = null;
413 RSAPKCS1SignatureFormatter sign = new RSAPKCS1SignatureFormatter (rsa);
414 sign.SetHashAlgorithm (TokenAlgorithm);
415 signature = sign.CreateSignature (sn.Hash);
416 Array.Reverse (signature);
418 catch (CryptographicException) {
422 using (FileStream fs = File.OpenWrite (fileName)) {
423 fs.Position = sn.SignaturePosition;
424 fs.Write (signature, 0, signature.Length);
431 public bool Verify (string fileName)
434 using (FileStream fs = File.OpenRead (fileName)) {
435 result = Verify (fs);
441 public bool Verify (Stream stream)
443 StrongNameSignature sn = StrongHash (stream, StrongNameOptions.Signature);
444 if (sn.Hash == null) {
449 AssemblyHashAlgorithm algorithm = AssemblyHashAlgorithm.SHA1;
450 if (tokenAlgorithm == "MD5")
451 algorithm = AssemblyHashAlgorithm.MD5;
452 return Verify (rsa, algorithm, sn.Hash, sn.Signature);
454 catch (CryptographicException) {
455 // no exception allowed
461 static object lockObject = new object ();
462 static bool initialized;
464 // We don't want a dependency on StrongNameManager in Mono.Security.dll
465 static public bool IsAssemblyStrongnamed (string assemblyName)
470 string config = Environment.GetMachineConfigPath ();
471 StrongNameManager.LoadConfig (config);
478 // this doesn't load the assembly (well it unloads it ;)
479 // http://weblogs.asp.net/nunitaddin/posts/9991.aspx
480 AssemblyName an = AssemblyName.GetAssemblyName (assemblyName);
484 byte[] publicKey = StrongNameManager.GetMappedPublicKey (an.GetPublicKeyToken ());
485 if ((publicKey == null) || (publicKey.Length < 12)) {
487 publicKey = an.GetPublicKey ();
488 if ((publicKey == null) || (publicKey.Length < 12))
492 // Note: MustVerify is based on the original token (by design). Public key
493 // remapping won't affect if the assembly is verified or not.
494 if (!StrongNameManager.MustVerify (an)) {
498 RSA rsa = CryptoConvert.FromCapiPublicKeyBlob (publicKey, 12);
499 StrongName sn = new StrongName (rsa);
500 bool result = sn.Verify (assemblyName);
504 // no exception allowed
510 // we would get better performance if the runtime hashed the
511 // assembly - as we wouldn't have to load it from disk a
512 // second time. The runtime already have implementations of
513 // SHA1 (and even MD5 if required someday).
514 static public bool VerifySignature (byte[] publicKey, int algorithm, byte[] hash, byte[] signature)
517 RSA rsa = CryptoConvert.FromCapiPublicKeyBlob (publicKey);
518 return Verify (rsa, (AssemblyHashAlgorithm) algorithm, hash, signature);
521 // no exception allowed
526 static private bool Verify (RSA rsa, AssemblyHashAlgorithm algorithm, byte[] hash, byte[] signature)
528 RSAPKCS1SignatureDeformatter vrfy = new RSAPKCS1SignatureDeformatter (rsa);
530 case AssemblyHashAlgorithm.MD5:
531 vrfy.SetHashAlgorithm ("MD5");
533 case AssemblyHashAlgorithm.SHA1:
534 case AssemblyHashAlgorithm.None:
536 vrfy.SetHashAlgorithm ("SHA1");
539 return vrfy.VerifySignature (hash, signature);