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 using System.Configuration.Assemblies;
14 using System.Reflection;
15 using System.Security.Cryptography;
17 using Mono.Security.Cryptography;
19 namespace Mono.Security {
26 sealed class StrongName {
28 internal class StrongNameSignature {
30 private byte[] signature;
31 private UInt32 signaturePosition;
32 private UInt32 signatureLength;
33 private UInt32 metadataPosition;
34 private UInt32 metadataLength;
36 private UInt32 cliFlagPosition;
43 public byte[] Signature {
44 get { return signature; }
45 set { signature = value; }
48 public UInt32 MetadataPosition {
49 get { return metadataPosition; }
50 set { metadataPosition = value; }
53 public UInt32 MetadataLength {
54 get { return metadataLength; }
55 set { metadataLength = value; }
58 public UInt32 SignaturePosition {
59 get { return signaturePosition; }
60 set { signaturePosition = value; }
63 public UInt32 SignatureLength {
64 get { return signatureLength; }
65 set { signatureLength = value; }
68 // delay signed -> flag = 0x01
69 // strongsigned -> flag = 0x09
71 get { return cliFlag; }
72 set { cliFlag = value; }
75 public UInt32 CliFlagPosition {
76 get { return cliFlagPosition; }
77 set { cliFlagPosition = value; }
81 internal enum StrongNameOptions {
87 private byte[] publicKey;
88 private byte[] keyToken;
89 private string tokenAlgorithm;
95 public StrongName (byte[] data)
98 throw new ArgumentNullException ("data");
100 RSA = CryptoConvert.FromCapiKeyBlob (data);
102 throw new ArgumentException ("data isn't a correctly encoded RSA public key");
105 public StrongName (RSA rsa) : base ()
108 throw new ArgumentNullException ("rsa");
113 private void InvalidateCache ()
119 public bool CanSign {
123 #if INSIDE_CORLIB || NET_1_2
125 if (RSA is RSACryptoServiceProvider) {
126 // available as internal for corlib
127 return !(rsa as RSACryptoServiceProvider).PublicOnly;
131 if (RSA is RSAManaged) {
132 return !(rsa as RSAManaged).PublicOnly;
137 RSAParameters p = rsa.ExportParameters (true);
138 return ((p.D != null) && (p.P != null) && (p.Q != null));
149 // if none then we create a new keypair
151 rsa = (RSA) RSA.Create ();
160 public byte[] PublicKey {
162 if (publicKey == null) {
163 byte[] keyPair = CryptoConvert.ToCapiKeyBlob (rsa, false);
164 publicKey = new byte [32 + 128]; // always 1024 bits
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)
195 public byte[] PublicKeyToken {
197 if (keyToken != null)
199 byte[] publicKey = PublicKey;
200 if (publicKey == 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);
212 public string TokenAlgorithm {
214 if (tokenAlgorithm == null)
215 tokenAlgorithm = "SHA1";
216 return tokenAlgorithm;
219 string algo = value.ToUpper ();
220 if ((algo == "SHA1") || (algo == "MD5")) {
221 tokenAlgorithm = value;
225 throw new ArgumentException ("Unsupported hash algorithm for token");
229 public byte[] GetBytes ()
231 return CryptoConvert.ToCapiPrivateKeyBlob (rsa);
234 private UInt32 RVAtoPosition (UInt32 r, int sections, byte[] headers)
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)) {
247 internal StrongNameSignature StrongHash (Stream stream, StrongNameOptions options)
249 StrongNameSignature info = new StrongNameSignature ();
251 HashAlgorithm hash = HashAlgorithm.Create (TokenAlgorithm);
252 CryptoStream cs = new CryptoStream (Stream.Null, hash, CryptoStreamMode.Write);
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)
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);
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)
274 if (BitConverter.ToUInt16 (pe, 4) != 0x14c)
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);
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);
288 UInt32 cliHeaderRVA = BitConverter.ToUInt32 (pe, 232);
289 UInt32 cliHeaderPos = RVAtoPosition (cliHeaderRVA, numSection, sectionHeaders);
290 int cliHeaderSiz = (int) BitConverter.ToUInt32 (pe, 236);
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);
298 UInt32 strongNameSignatureRVA = BitConverter.ToUInt32 (cli, 32);
299 info.SignaturePosition = RVAtoPosition (strongNameSignatureRVA, numSection, sectionHeaders);
300 info.SignatureLength = BitConverter.ToUInt32 (cli, 36);
302 UInt32 metadataRVA = BitConverter.ToUInt32 (cli, 8);
303 info.MetadataPosition = RVAtoPosition (metadataRVA, numSection, sectionHeaders);
304 info.MetadataLength = BitConverter.ToUInt32 (cli, 12);
306 if (options == StrongNameOptions.Metadata) {
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);
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);
327 cs.Write (section, 0, before);
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);
337 cs.Write (section, s, after);
341 cs.Write (section, 0, length);
345 info.Hash = hash.Hash;
349 // return the same result as the undocumented and unmanaged GetHashFromAssemblyFile
350 public byte[] Hash (string fileName)
352 FileStream fs = File.OpenRead (fileName);
353 StrongNameSignature sn = StrongHash (fs, StrongNameOptions.Metadata);
359 public bool Sign (string fileName)
362 StrongNameSignature sn;
363 FileStream fs = File.OpenRead (fileName);
365 sn = StrongHash (fs, StrongNameOptions.Signature);
373 byte[] signature = null;
375 RSAPKCS1SignatureFormatter sign = new RSAPKCS1SignatureFormatter (rsa);
376 sign.SetHashAlgorithm (TokenAlgorithm);
377 signature = sign.CreateSignature (sn.Hash);
378 Array.Reverse (signature);
385 fs = File.OpenWrite (fileName);
386 fs.Position = sn.SignaturePosition;
387 fs.Write (signature, 0, signature.Length);
398 public bool Verify (string fileName)
401 StrongNameSignature sn;
402 FileStream fs = File.OpenRead (fileName);
404 sn = StrongHash (fs, StrongNameOptions.Signature);
413 AssemblyHashAlgorithm algorithm = AssemblyHashAlgorithm.SHA1;
414 if (tokenAlgorithm == "MD5")
415 algorithm = AssemblyHashAlgorithm.MD5;
416 return Verify (rsa, algorithm, sn.Hash, sn.Signature);
419 // no exception allowed
425 // We don't want a dependency on StrongNameManager in Mono.Security.dll
426 static public bool IsAssemblyStrongnamed (string assemblyName)
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);
435 byte[] publicKey = StrongNameManager.GetMappedPublicKey (an.GetPublicKeyToken ());
436 if ((publicKey == null) || (publicKey.Length < 12)) {
438 publicKey = an.GetPublicKey ();
439 if ((publicKey == null) || (publicKey.Length < 12))
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)) {
449 RSA rsa = CryptoConvert.FromCapiPublicKeyBlob (publicKey, 12);
450 StrongName sn = new StrongName (rsa);
451 bool result = sn.Verify (assemblyName);
455 // no exception allowed
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)
468 RSA rsa = CryptoConvert.FromCapiPublicKeyBlob (publicKey);
469 return Verify (rsa, (AssemblyHashAlgorithm) algorithm, hash, signature);
472 // no exception allowed
477 static private bool Verify (RSA rsa, AssemblyHashAlgorithm algorithm, byte[] hash, byte[] signature)
479 RSAPKCS1SignatureDeformatter vrfy = new RSAPKCS1SignatureDeformatter (rsa);
481 case AssemblyHashAlgorithm.MD5:
482 vrfy.SetHashAlgorithm ("MD5");
484 case AssemblyHashAlgorithm.SHA1:
485 case AssemblyHashAlgorithm.None:
487 vrfy.SetHashAlgorithm ("SHA1");
490 return vrfy.VerifySignature (hash, signature);