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
{
163 #if INSIDE_CORLIB && (!NET_2_1 || MONOTOUCH)
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
= false;
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
);