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 // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
14 // Permission is hereby granted, free of charge, to any person obtaining
15 // a copy of this software and associated documentation files (the
16 // "Software"), to deal in the Software without restriction, including
17 // without limitation the rights to use, copy, modify, merge, publish,
18 // distribute, sublicense, and/or sell copies of the Software, and to
19 // permit persons to whom the Software is furnished to do so, subject to
20 // the following conditions:
22 // The above copyright notice and this permission notice shall be
23 // included in all copies or substantial portions of the Software.
25 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
26 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
27 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
28 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
29 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
30 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
31 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
35 using System
.Configuration
.Assemblies
;
36 using System
.Globalization
;
38 using System
.Reflection
;
39 using System
.Security
.Cryptography
;
41 using Mono
.Security
.Cryptography
;
43 namespace Mono
.Security
{
50 sealed class StrongName
{
52 internal class StrongNameSignature
{
54 private byte[] signature
;
55 private UInt32 signaturePosition
;
56 private UInt32 signatureLength
;
57 private UInt32 metadataPosition
;
58 private UInt32 metadataLength
;
60 private UInt32 cliFlagPosition
;
67 public byte[] Signature
{
68 get { return signature; }
69 set { signature = value; }
72 public UInt32 MetadataPosition
{
73 get { return metadataPosition; }
74 set { metadataPosition = value; }
77 public UInt32 MetadataLength
{
78 get { return metadataLength; }
79 set { metadataLength = value; }
82 public UInt32 SignaturePosition
{
83 get { return signaturePosition; }
84 set { signaturePosition = value; }
87 public UInt32 SignatureLength
{
88 get { return signatureLength; }
89 set { signatureLength = value; }
92 // delay signed -> flag = 0x01
93 // strongsigned -> flag = 0x09
95 get { return cliFlag; }
96 set { cliFlag = value; }
99 public UInt32 CliFlagPosition
{
100 get { return cliFlagPosition; }
101 set { cliFlagPosition = value; }
105 internal enum StrongNameOptions
{
111 private byte[] publicKey
;
112 private byte[] keyToken
;
113 private string tokenAlgorithm
;
119 public StrongName (byte[] data
)
122 throw new ArgumentNullException ("data");
124 // check for ECMA key
125 if (data
.Length
== 16) {
128 while (i
< data
.Length
)
131 // it is the ECMA key
132 publicKey
= (byte[]) data
.Clone ();
136 RSA
= CryptoConvert
.FromCapiKeyBlob (data
);
138 throw new ArgumentException ("data isn't a correctly encoded RSA public key");
142 public StrongName (RSA rsa
)
145 throw new ArgumentNullException ("rsa");
150 private void InvalidateCache ()
156 public bool CanSign
{
162 if (RSA
is RSACryptoServiceProvider
) {
163 // available as internal for corlib
164 return !(rsa
as RSACryptoServiceProvider
).PublicOnly
;
168 if (RSA
is RSAManaged
) {
169 return !(rsa
as RSAManaged
).PublicOnly
;
174 RSAParameters p
= rsa
.ExportParameters (true);
175 return ((p
.D
!= null) && (p
.P
!= null) && (p
.Q
!= null));
177 catch (CryptographicException
) {
186 // if none then we create a new keypair
188 rsa
= (RSA
) RSA
.Create ();
197 public byte[] PublicKey
{
199 if (publicKey
== null) {
200 byte[] keyPair
= CryptoConvert
.ToCapiKeyBlob (rsa
, false);
201 publicKey
= new byte [32 + 128]; // always 1024 bits
203 // The first 12 bytes are documented at:
204 // http://msdn.microsoft.com/library/en-us/cprefadd/html/grfungethashfromfile.asp
205 // ALG_ID - Signature
206 publicKey
[0] = keyPair
[4];
207 publicKey
[1] = keyPair
[5];
208 publicKey
[2] = keyPair
[6];
209 publicKey
[3] = keyPair
[7];
210 // ALG_ID - Hash (SHA1 == 0x8004)
211 publicKey
[4] = 0x04;
212 publicKey
[5] = 0x80;
213 publicKey
[6] = 0x00;
214 publicKey
[7] = 0x00;
215 // Length of Public Key (in bytes)
216 byte[] lastPart
= BitConverterLE
.GetBytes (publicKey
.Length
- 12);
217 publicKey
[8] = lastPart
[0];
218 publicKey
[9] = lastPart
[1];
219 publicKey
[10] = lastPart
[2];
220 publicKey
[11] = lastPart
[3];
221 // Ok from here - Same structure as keypair - expect for public key
222 publicKey
[12] = 0x06; // PUBLICKEYBLOB
223 // we can copy this part
224 Buffer
.BlockCopy (keyPair
, 1, publicKey
, 13, publicKey
.Length
- 13);
225 // and make a small adjustment
226 publicKey
[23] = 0x31; // (RSA1 not RSA2)
228 return (byte[]) publicKey
.Clone ();
232 public byte[] PublicKeyToken
{
234 if (keyToken
== null) {
235 byte[] publicKey
= PublicKey
;
236 if (publicKey
== null)
238 HashAlgorithm ha
= SHA1
.Create (TokenAlgorithm
);
239 byte[] hash
= ha
.ComputeHash (publicKey
);
240 // we need the last 8 bytes in reverse order
241 keyToken
= new byte [8];
242 Buffer
.BlockCopy (hash
, (hash
.Length
- 8), keyToken
, 0, 8);
243 Array
.Reverse (keyToken
, 0, 8);
245 return (byte[]) keyToken
.Clone ();
249 public string TokenAlgorithm
{
251 if (tokenAlgorithm
== null)
252 tokenAlgorithm
= "SHA1";
253 return tokenAlgorithm
;
256 string algo
= value.ToUpper (CultureInfo
.InvariantCulture
);
257 if ((algo
== "SHA1") || (algo
== "MD5")) {
258 tokenAlgorithm
= value;
262 throw new ArgumentException ("Unsupported hash algorithm for token");
266 public byte[] GetBytes ()
268 return CryptoConvert
.ToCapiPrivateKeyBlob (RSA
);
271 private UInt32
RVAtoPosition (UInt32 r
, int sections
, byte[] headers
)
273 for (int i
=0; i
< sections
; i
++) {
274 UInt32 p
= BitConverterLE
.ToUInt32 (headers
, i
* 40 + 20);
275 UInt32 s
= BitConverterLE
.ToUInt32 (headers
, i
* 40 + 12);
276 int l
= (int) BitConverterLE
.ToUInt32 (headers
, i
* 40 + 8);
277 if ((s
<= r
) && (r
< s
+ l
)) {
284 internal StrongNameSignature
StrongHash (Stream stream
, StrongNameOptions options
)
286 StrongNameSignature info
= new StrongNameSignature ();
288 HashAlgorithm hash
= HashAlgorithm
.Create (TokenAlgorithm
);
289 CryptoStream cs
= new CryptoStream (Stream
.Null
, hash
, CryptoStreamMode
.Write
);
291 // MS-DOS Header - always 128 bytes
292 // ref: Section 24.2.1, Partition II Metadata
293 byte[] mz
= new byte [128];
294 stream
.Read (mz
, 0, 128);
295 if (BitConverterLE
.ToUInt16 (mz
, 0) != 0x5a4d)
297 UInt32 peHeader
= BitConverterLE
.ToUInt32 (mz
, 60);
298 cs
.Write (mz
, 0, 128);
299 if (peHeader
!= 128) {
300 byte[] mzextra
= new byte [peHeader
- 128];
301 stream
.Read (mzextra
, 0, mzextra
.Length
);
302 cs
.Write (mzextra
, 0, mzextra
.Length
);
305 // PE File Header - always 248 bytes
306 // ref: Section 24.2.2, Partition II Metadata
307 byte[] pe
= new byte [248];
308 stream
.Read (pe
, 0, 248);
309 if (BitConverterLE
.ToUInt32 (pe
, 0) != 0x4550)
311 if (BitConverterLE
.ToUInt16 (pe
, 4) != 0x14c)
313 // MUST zeroize both CheckSum and Security Directory
314 byte[] v
= new byte [8];
315 Buffer
.BlockCopy (v
, 0, pe
, 88, 4);
316 Buffer
.BlockCopy (v
, 0, pe
, 152, 8);
317 cs
.Write (pe
, 0, 248);
319 UInt16 numSection
= BitConverterLE
.ToUInt16 (pe
, 6);
320 int sectionLength
= (numSection
* 40);
321 byte[] sectionHeaders
= new byte [sectionLength
];
322 stream
.Read (sectionHeaders
, 0, sectionLength
);
323 cs
.Write (sectionHeaders
, 0, sectionLength
);
325 UInt32 cliHeaderRVA
= BitConverterLE
.ToUInt32 (pe
, 232);
326 UInt32 cliHeaderPos
= RVAtoPosition (cliHeaderRVA
, numSection
, sectionHeaders
);
327 int cliHeaderSiz
= (int) BitConverterLE
.ToUInt32 (pe
, 236);
330 // ref: Section 24.3.3, Partition II Metadata
331 byte[] cli
= new byte [cliHeaderSiz
];
332 stream
.Position
= cliHeaderPos
;
333 stream
.Read (cli
, 0, cliHeaderSiz
);
335 UInt32 strongNameSignatureRVA
= BitConverterLE
.ToUInt32 (cli
, 32);
336 info
.SignaturePosition
= RVAtoPosition (strongNameSignatureRVA
, numSection
, sectionHeaders
);
337 info
.SignatureLength
= BitConverterLE
.ToUInt32 (cli
, 36);
339 UInt32 metadataRVA
= BitConverterLE
.ToUInt32 (cli
, 8);
340 info
.MetadataPosition
= RVAtoPosition (metadataRVA
, numSection
, sectionHeaders
);
341 info
.MetadataLength
= BitConverterLE
.ToUInt32 (cli
, 12);
343 if (options
== StrongNameOptions
.Metadata
) {
346 byte[] metadata
= new byte [info
.MetadataLength
];
347 stream
.Position
= info
.MetadataPosition
;
348 stream
.Read (metadata
, 0, metadata
.Length
);
349 info
.Hash
= hash
.ComputeHash (metadata
);
353 // now we hash every section EXCEPT the signature block
354 for (int i
=0; i
< numSection
; i
++) {
355 UInt32 start
= BitConverterLE
.ToUInt32 (sectionHeaders
, i
* 40 + 20);
356 int length
= (int) BitConverterLE
.ToUInt32 (sectionHeaders
, i
* 40 + 16);
357 byte[] section
= new byte [length
];
358 stream
.Position
= start
;
359 stream
.Read (section
, 0, length
);
360 if ((start
<= info
.SignaturePosition
) && (info
.SignaturePosition
< start
+ length
)) {
361 // hash before the signature
362 int before
= (int)(info
.SignaturePosition
- start
);
364 cs
.Write (section
, 0, before
);
367 info
.Signature
= new byte [info
.SignatureLength
];
368 Buffer
.BlockCopy (section
, before
, info
.Signature
, 0, (int)info
.SignatureLength
);
369 Array
.Reverse (info
.Signature
);
370 // hash after the signature
371 int s
= (int)(before
+ info
.SignatureLength
);
372 int after
= (int)(length
- s
);
374 cs
.Write (section
, s
, after
);
378 cs
.Write (section
, 0, length
);
382 info
.Hash
= hash
.Hash
;
386 // return the same result as the undocumented and unmanaged GetHashFromAssemblyFile
387 public byte[] Hash (string fileName
)
389 FileStream fs
= File
.OpenRead (fileName
);
390 StrongNameSignature sn
= StrongHash (fs
, StrongNameOptions
.Metadata
);
396 public bool Sign (string fileName
)
399 StrongNameSignature sn
;
400 using (FileStream fs
= File
.OpenRead (fileName
)) {
401 sn
= StrongHash (fs
, StrongNameOptions
.Signature
);
407 byte[] signature
= null;
409 RSAPKCS1SignatureFormatter sign
= new RSAPKCS1SignatureFormatter (rsa
);
410 sign
.SetHashAlgorithm (TokenAlgorithm
);
411 signature
= sign
.CreateSignature (sn
.Hash
);
412 Array
.Reverse (signature
);
414 catch (CryptographicException
) {
418 using (FileStream fs
= File
.OpenWrite (fileName
)) {
419 fs
.Position
= sn
.SignaturePosition
;
420 fs
.Write (signature
, 0, signature
.Length
);
427 public bool Verify (string fileName
)
429 StrongNameSignature sn
;
430 using (FileStream fs
= File
.OpenRead (fileName
)) {
431 sn
= StrongHash (fs
, StrongNameOptions
.Signature
);
434 if (sn
.Hash
== null) {
439 AssemblyHashAlgorithm algorithm
= AssemblyHashAlgorithm
.SHA1
;
440 if (tokenAlgorithm
== "MD5")
441 algorithm
= AssemblyHashAlgorithm
.MD5
;
442 return Verify (rsa
, algorithm
, sn
.Hash
, sn
.Signature
);
444 catch (CryptographicException
) {
445 // no exception allowed
451 static object lockObject
= new object ();
452 static bool initialized
= false;
454 // We don't want a dependency on StrongNameManager in Mono.Security.dll
455 static public bool IsAssemblyStrongnamed (string assemblyName
)
460 string config
= Environment
.GetMachineConfigPath ();
461 StrongNameManager
.LoadConfig (config
);
468 // this doesn't load the assembly (well it unloads it ;)
469 // http://weblogs.asp.net/nunitaddin/posts/9991.aspx
470 AssemblyName an
= AssemblyName
.GetAssemblyName (assemblyName
);
474 byte[] publicKey
= StrongNameManager
.GetMappedPublicKey (an
.GetPublicKeyToken ());
475 if ((publicKey
== null) || (publicKey
.Length
< 12)) {
477 publicKey
= an
.GetPublicKey ();
478 if ((publicKey
== null) || (publicKey
.Length
< 12))
482 // Note: MustVerify is based on the original token (by design). Public key
483 // remapping won't affect if the assembly is verified or not.
484 if (!StrongNameManager
.MustVerify (an
)) {
488 RSA rsa
= CryptoConvert
.FromCapiPublicKeyBlob (publicKey
, 12);
489 StrongName sn
= new StrongName (rsa
);
490 bool result
= sn
.Verify (assemblyName
);
494 // no exception allowed
500 // we would get better performance if the runtime hashed the
501 // assembly - as we wouldn't have to load it from disk a
502 // second time. The runtime already have implementations of
503 // SHA1 (and even MD5 if required someday).
504 static public bool VerifySignature (byte[] publicKey
, int algorithm
, byte[] hash
, byte[] signature
)
507 RSA rsa
= CryptoConvert
.FromCapiPublicKeyBlob (publicKey
);
508 return Verify (rsa
, (AssemblyHashAlgorithm
) algorithm
, hash
, signature
);
511 // no exception allowed
516 static private bool Verify (RSA rsa
, AssemblyHashAlgorithm algorithm
, byte[] hash
, byte[] signature
)
518 RSAPKCS1SignatureDeformatter vrfy
= new RSAPKCS1SignatureDeformatter (rsa
);
520 case AssemblyHashAlgorithm
.MD5
:
521 vrfy
.SetHashAlgorithm ("MD5");
523 case AssemblyHashAlgorithm
.SHA1
:
524 case AssemblyHashAlgorithm
.None
:
526 vrfy
.SetHashAlgorithm ("SHA1");
529 return vrfy
.VerifySignature (hash
, signature
);