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 (byte[] data
)
120 throw new ArgumentNullException ("data");
122 // check for ECMA key
123 if (data
.Length
== 16) {
126 while (i
< data
.Length
)
129 // it is the ECMA key
130 publicKey
= (byte[]) data
.Clone ();
134 RSA
= CryptoConvert
.FromCapiKeyBlob (data
);
136 throw new ArgumentException ("data isn't a correctly encoded RSA public key");
140 public StrongName (RSA rsa
)
143 throw new ArgumentNullException ("rsa");
148 private void InvalidateCache ()
154 public bool CanSign
{
160 if (RSA
is RSACryptoServiceProvider
) {
161 // available as internal for corlib
162 return !(rsa
as RSACryptoServiceProvider
).PublicOnly
;
166 if (RSA
is RSAManaged
) {
167 return !(rsa
as RSAManaged
).PublicOnly
;
172 RSAParameters p
= rsa
.ExportParameters (true);
173 return ((p
.D
!= null) && (p
.P
!= null) && (p
.Q
!= null));
175 catch (CryptographicException
) {
184 // if none then we create a new keypair
186 rsa
= (RSA
) RSA
.Create ();
195 public byte[] PublicKey
{
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
{
232 if (keyToken
== null) {
233 byte[] publicKey
= PublicKey
;
234 if (publicKey
== 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
{
249 if (tokenAlgorithm
== null)
250 tokenAlgorithm
= "SHA1";
251 return tokenAlgorithm
;
254 string algo
= value.ToUpper (CultureInfo
.InvariantCulture
);
255 if ((algo
== "SHA1") || (algo
== "MD5")) {
256 tokenAlgorithm
= value;
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
)) {
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)
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)
309 if (BitConverterLE
.ToUInt16 (pe
, 4) != 0x14c)
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);
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
) {
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
);
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
);
362 cs
.Write (section
, 0, before
);
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
);
372 cs
.Write (section
, s
, after
);
376 cs
.Write (section
, 0, length
);
380 info
.Hash
= hash
.Hash
;
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
);
394 public bool Sign (string fileName
)
397 StrongNameSignature sn
;
398 using (FileStream fs
= File
.OpenRead (fileName
)) {
399 sn
= StrongHash (fs
, StrongNameOptions
.Signature
);
405 byte[] signature
= null;
407 RSAPKCS1SignatureFormatter sign
= new RSAPKCS1SignatureFormatter (rsa
);
408 sign
.SetHashAlgorithm (TokenAlgorithm
);
409 signature
= sign
.CreateSignature (sn
.Hash
);
410 Array
.Reverse (signature
);
412 catch (CryptographicException
) {
416 using (FileStream fs
= File
.OpenWrite (fileName
)) {
417 fs
.Position
= sn
.SignaturePosition
;
418 fs
.Write (signature
, 0, signature
.Length
);
425 public bool Verify (string fileName
)
427 StrongNameSignature sn
;
428 using (FileStream fs
= File
.OpenRead (fileName
)) {
429 sn
= StrongHash (fs
, StrongNameOptions
.Signature
);
432 if (sn
.Hash
== null) {
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
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
)
458 string config
= Environment
.GetMachineConfigPath ();
459 StrongNameManager
.LoadConfig (config
);
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
);
472 byte[] publicKey
= StrongNameManager
.GetMappedPublicKey (an
.GetPublicKeyToken ());
473 if ((publicKey
== null) || (publicKey
.Length
< 12)) {
475 publicKey
= an
.GetPublicKey ();
476 if ((publicKey
== null) || (publicKey
.Length
< 12))
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
)) {
486 RSA rsa
= CryptoConvert
.FromCapiPublicKeyBlob (publicKey
, 12);
487 StrongName sn
= new StrongName (rsa
);
488 bool result
= sn
.Verify (assemblyName
);
492 // no exception allowed
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
)
505 RSA rsa
= CryptoConvert
.FromCapiPublicKeyBlob (publicKey
);
506 return Verify (rsa
, (AssemblyHashAlgorithm
) algorithm
, hash
, signature
);
509 // no exception allowed
514 static private bool Verify (RSA rsa
, AssemblyHashAlgorithm algorithm
, byte[] hash
, byte[] signature
)
516 RSAPKCS1SignatureDeformatter vrfy
= new RSAPKCS1SignatureDeformatter (rsa
);
518 case AssemblyHashAlgorithm
.MD5
:
519 vrfy
.SetHashAlgorithm ("MD5");
521 case AssemblyHashAlgorithm
.SHA1
:
522 case AssemblyHashAlgorithm
.None
:
524 vrfy
.SetHashAlgorithm ("SHA1");
527 return vrfy
.VerifySignature (hash
, signature
);