bring Mono Security to monotouch
[mcs.git] / class / corlib / Mono.Security / StrongName.cs
blobafa43f84dc77a4e39f98f30265b5260f23bed6b3
1 //
2 // StrongName.cs - Strong Name Implementation
3 //
4 // Author:
5 // Sebastien Pouliot (sebastien@ximian.com)
6 //
7 // (C) 2003 Motus Technologies Inc. (http://www.motus.com)
8 // (C) 2004 Novell (http://www.novell.com)
9 //
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:
19 //
20 // The above copyright notice and this permission notice shall be
21 // included in all copies or substantial portions of the Software.
22 //
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.
32 using System;
33 using System.Configuration.Assemblies;
34 using System.Globalization;
35 using System.IO;
36 using System.Reflection;
37 using System.Security.Cryptography;
39 using Mono.Security.Cryptography;
41 namespace Mono.Security {
43 #if INSIDE_CORLIB
44 internal
45 #else
46 public
47 #endif
48 sealed class StrongName {
50 internal class StrongNameSignature {
51 private byte[] hash;
52 private byte[] signature;
53 private UInt32 signaturePosition;
54 private UInt32 signatureLength;
55 private UInt32 metadataPosition;
56 private UInt32 metadataLength;
57 private byte cliFlag;
58 private UInt32 cliFlagPosition;
60 public byte[] Hash {
61 get { return hash; }
62 set { hash = value; }
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
92 public byte CliFlag {
93 get { return cliFlag; }
94 set { cliFlag = value; }
97 public UInt32 CliFlagPosition {
98 get { return cliFlagPosition; }
99 set { cliFlagPosition = value; }
103 internal enum StrongNameOptions {
104 Metadata,
105 Signature
108 private RSA rsa;
109 private byte[] publicKey;
110 private byte[] keyToken;
111 private string tokenAlgorithm;
113 public StrongName ()
117 public StrongName (int keySize)
119 rsa = new RSAManaged (keySize);
122 public StrongName (byte[] data)
124 if (data == null)
125 throw new ArgumentNullException ("data");
127 // check for ECMA key
128 if (data.Length == 16) {
129 int i = 0;
130 int sum = 0;
131 while (i < data.Length)
132 sum += data [i++];
133 if (sum == 4) {
134 // it is the ECMA key
135 publicKey = (byte[]) data.Clone ();
138 else {
139 RSA = CryptoConvert.FromCapiKeyBlob (data);
140 if (rsa == null)
141 throw new ArgumentException ("data isn't a correctly encoded RSA public key");
145 public StrongName (RSA rsa)
147 if (rsa == null)
148 throw new ArgumentNullException ("rsa");
150 RSA = rsa;
153 private void InvalidateCache ()
155 publicKey = null;
156 keyToken = null;
159 public bool CanSign {
160 get {
161 if (rsa == null)
162 return false;
163 #if INSIDE_CORLIB && (!NET_2_1 || MONOTOUCH)
164 // the easy way
165 if (RSA is RSACryptoServiceProvider) {
166 // available as internal for corlib
167 return !(rsa as RSACryptoServiceProvider).PublicOnly;
169 else
170 #endif
171 if (RSA is RSAManaged) {
172 return !(rsa as RSAManaged).PublicOnly;
174 else {
175 // the hard way
176 try {
177 RSAParameters p = rsa.ExportParameters (true);
178 return ((p.D != null) && (p.P != null) && (p.Q != null));
180 catch (CryptographicException) {
181 return false;
187 public RSA RSA {
188 get {
189 // if none then we create a new keypair
190 if (rsa == null)
191 rsa = (RSA) RSA.Create ();
192 return rsa;
194 set {
195 rsa = value;
196 InvalidateCache ();
200 public byte[] PublicKey {
201 get {
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 {
237 get {
238 if (keyToken == null) {
239 byte[] publicKey = PublicKey;
240 if (publicKey == null)
241 return 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 {
254 get {
255 if (tokenAlgorithm == null)
256 tokenAlgorithm = "SHA1";
257 return tokenAlgorithm;
259 set {
260 string algo = value.ToUpper (CultureInfo.InvariantCulture);
261 if ((algo == "SHA1") || (algo == "MD5")) {
262 tokenAlgorithm = value;
263 InvalidateCache ();
265 else
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)) {
282 return p + r - s;
285 return 0;
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)
300 return null;
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)
314 return null;
315 if (BitConverterLE.ToUInt16 (pe, 4) != 0x14c)
316 return null;
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);
333 // CLI Header
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) {
348 cs.Close ();
349 hash.Initialize ();
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);
354 return info;
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);
367 if (before > 0) {
368 cs.Write (section, 0, before);
370 // copy signature
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);
377 if (after > 0) {
378 cs.Write (section, s, after);
381 else
382 cs.Write (section, 0, length);
385 cs.Close ();
386 info.Hash = hash.Hash;
387 return info;
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);
395 fs.Close ();
397 return sn.Hash;
400 public bool Sign (string fileName)
402 bool result = false;
403 StrongNameSignature sn;
404 using (FileStream fs = File.OpenRead (fileName)) {
405 sn = StrongHash (fs, StrongNameOptions.Signature);
406 fs.Close ();
408 if (sn.Hash == null)
409 return false;
411 byte[] signature = null;
412 try {
413 RSAPKCS1SignatureFormatter sign = new RSAPKCS1SignatureFormatter (rsa);
414 sign.SetHashAlgorithm (TokenAlgorithm);
415 signature = sign.CreateSignature (sn.Hash);
416 Array.Reverse (signature);
418 catch (CryptographicException) {
419 return false;
422 using (FileStream fs = File.OpenWrite (fileName)) {
423 fs.Position = sn.SignaturePosition;
424 fs.Write (signature, 0, signature.Length);
425 fs.Close ();
426 result = true;
428 return result;
431 public bool Verify (string fileName)
433 bool result = false;
434 using (FileStream fs = File.OpenRead (fileName)) {
435 result = Verify (fs);
436 fs.Close ();
438 return result;
441 public bool Verify (Stream stream)
443 StrongNameSignature sn = StrongHash (stream, StrongNameOptions.Signature);
444 if (sn.Hash == null) {
445 return false;
448 try {
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
456 return false;
460 #if INSIDE_CORLIB
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)
467 if (!initialized) {
468 lock (lockObject) {
469 if (!initialized) {
470 string config = Environment.GetMachineConfigPath ();
471 StrongNameManager.LoadConfig (config);
472 initialized = true;
477 try {
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);
481 if (an == null)
482 return false;
484 byte[] publicKey = StrongNameManager.GetMappedPublicKey (an.GetPublicKeyToken ());
485 if ((publicKey == null) || (publicKey.Length < 12)) {
486 // no mapping
487 publicKey = an.GetPublicKey ();
488 if ((publicKey == null) || (publicKey.Length < 12))
489 return false;
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)) {
495 return true;
498 RSA rsa = CryptoConvert.FromCapiPublicKeyBlob (publicKey, 12);
499 StrongName sn = new StrongName (rsa);
500 bool result = sn.Verify (assemblyName);
501 return result;
503 catch {
504 // no exception allowed
505 return false;
509 // TODO
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)
516 try {
517 RSA rsa = CryptoConvert.FromCapiPublicKeyBlob (publicKey);
518 return Verify (rsa, (AssemblyHashAlgorithm) algorithm, hash, signature);
520 catch {
521 // no exception allowed
522 return false;
525 #endif
526 static private bool Verify (RSA rsa, AssemblyHashAlgorithm algorithm, byte[] hash, byte[] signature)
528 RSAPKCS1SignatureDeformatter vrfy = new RSAPKCS1SignatureDeformatter (rsa);
529 switch (algorithm) {
530 case AssemblyHashAlgorithm.MD5:
531 vrfy.SetHashAlgorithm ("MD5");
532 break;
533 case AssemblyHashAlgorithm.SHA1:
534 case AssemblyHashAlgorithm.None:
535 default:
536 vrfy.SetHashAlgorithm ("SHA1");
537 break;
539 return vrfy.VerifySignature (hash, signature);