184faec0ee70988bed502ad7f7d87c0530f7587e
[mono.git] / mcs / class / referencesource / mscorlib / system / security / policy / hash.cs
1 // ==++==
2 // 
3 //   Copyright (c) Microsoft Corporation.  All rights reserved.
4 // 
5 // ==--==
6 // <OWNER>Microsoft</OWNER>
7 // 
8
9 //
10 // Hash
11 //
12 // Evidence corresponding to a hash of the assembly bits.
13 //
14
15 using System;
16 using System.Diagnostics.Contracts;
17 using System.Collections.Generic;
18 using System.Reflection;
19 using System.Runtime.CompilerServices;
20 using System.Runtime.ConstrainedExecution;
21 using System.Runtime.InteropServices;
22 using System.Runtime.Serialization;
23 using System.Runtime.Versioning;
24 using System.Security.Cryptography;
25 using System.Security.Util;
26 using Microsoft.Win32.SafeHandles;
27
28 namespace System.Security.Policy
29 {
30     [Serializable]
31     [ComVisible(true)]
32     public sealed class Hash : EvidenceBase, ISerializable
33     {
34         private RuntimeAssembly m_assembly;
35         private Dictionary<Type, byte[]> m_hashes;
36         private WeakReference m_rawData;
37
38         /// <summary>
39         ///     Deserialize a serialized hash evidence object
40         /// </summary>
41         [SecurityCritical]
42         internal Hash(SerializationInfo info, StreamingContext context)
43         {
44             //
45             // We have three serialization formats that we might be deserializing, the Whidbey format which
46             // contains hash values directly, the Whidbey format which contains a pointer to a PEImage, and
47             // the v4 format which contains a dictionary of calculated hashes.
48             // 
49             // If we have the Whidbey version that has built in hash values, we can convert that, but we
50             // cannot do anything with the PEImage format since that is a serialized pointer into another
51             // runtime's VM.
52             // 
53
54             Dictionary<Type, byte[]> hashes = info.GetValueNoThrow("Hashes", typeof(Dictionary<Type, byte[]>)) as Dictionary<Type, byte[]>;
55             if (hashes != null)
56             {
57                 m_hashes = hashes;
58             }
59             else
60             {
61                 // If there is no hash value dictionary, then check to see if we have the Whidbey multiple
62                 // hashes version of the evidence.
63                 m_hashes = new Dictionary<Type, byte[]>();
64
65                 byte[] md5 = info.GetValueNoThrow("Md5", typeof(byte[])) as byte[];
66                 if (md5 != null)
67                 {
68                     m_hashes[typeof(MD5)] = md5;
69                 }
70
71                 byte[] sha1 = info.GetValueNoThrow("Sha1", typeof(byte[])) as byte[];
72                 if (sha1 != null)
73                 {
74                     m_hashes[typeof(SHA1)] = sha1;
75                 }
76
77                 byte[] rawData = info.GetValueNoThrow("RawData", typeof(byte[])) as byte[];
78                 if (rawData != null)
79                 {
80                     GenerateDefaultHashes(rawData);
81                 }
82             }
83         }
84
85         /// <summary>
86         ///     Create hash evidence for the specified assembly
87         /// </summary>
88         public Hash(Assembly assembly)
89         {
90             if (assembly == null)
91                 throw new ArgumentNullException("assembly");
92             Contract.EndContractBlock();
93             if (assembly.IsDynamic)
94                 throw new ArgumentException(Environment.GetResourceString("Security_CannotGenerateHash"), "assembly");
95
96             m_hashes = new Dictionary<Type, byte[]>();
97             m_assembly = assembly as RuntimeAssembly;
98
99             if (m_assembly == null)
100                 throw new ArgumentException(Environment.GetResourceString("Argument_MustBeRuntimeAssembly"), "assembly");
101         }
102
103         /// <summary>
104         ///     Create a copy of some hash evidence
105         /// </summary>
106         private Hash(Hash hash)
107         {
108             Contract.Assert(hash != null);
109
110             m_assembly = hash.m_assembly;
111             m_rawData = hash.m_rawData;
112             m_hashes = new Dictionary<Type, byte[]>(hash.m_hashes);
113         }
114
115         /// <summary>
116         ///     Create a hash evidence prepopulated with a specific hash value
117         /// </summary>
118         private Hash(Type hashType, byte[] hashValue)
119         {
120             Contract.Assert(hashType != null);
121             Contract.Assert(hashValue != null);
122
123             m_hashes = new Dictionary<Type, byte[]>();
124
125             byte[] hashClone = new byte[hashValue.Length];
126             Array.Copy(hashValue, hashClone, hashClone.Length);
127
128             m_hashes[hashType] = hashValue;
129         }
130
131         /// <summary>
132         ///     Build a Hash evidence that contains the specific SHA-1 hash.  The input hash is not validated,
133         ///     and the resulting Hash object cannot calculate additional hash values.
134         /// </summary>
135         public static Hash CreateSHA1(byte[] sha1)
136         {
137             if (sha1 == null)
138                 throw new ArgumentNullException("sha1");
139             Contract.EndContractBlock();
140
141             return new Hash(typeof(SHA1), sha1);
142         }
143
144         /// <summary>
145         ///     Build a Hash evidence that contains the specific SHA-256 hash.  The input hash is not
146         ///     validated, and the resulting Hash object cannot calculate additional hash values.
147         /// </summary>
148         public static Hash CreateSHA256(byte[] sha256)
149         {
150             if (sha256 == null)
151                 throw new ArgumentNullException("sha256");
152             Contract.EndContractBlock();
153
154             return new Hash(typeof(SHA256), sha256);
155         }
156
157         /// <summary>
158         ///     Build a Hash evidence that contains the specific MD5 hash.  The input hash is not validated,
159         ///     and the resulting Hash object cannot calculate additional hash values.
160         /// </summary>
161         public static Hash CreateMD5(byte[] md5)
162         {
163             if (md5 == null)
164                 throw new ArgumentNullException("md5");
165             Contract.EndContractBlock();
166
167             return new Hash(typeof(MD5), md5);
168         }
169
170         /// <summary>
171         ///     Make a copy of this evidence object
172         /// </summary>
173         public override EvidenceBase Clone()
174         {
175             return new Hash(this);
176         }
177
178         /// <summary>
179         ///     Prepare the hash evidence for serialization
180         /// </summary>
181         [OnSerializing]
182         private void OnSerializing(StreamingContext ctx)
183         {
184             GenerateDefaultHashes();
185         }
186
187         /// <summary>
188         ///     Serialize the hash evidence
189         /// </summary>
190         [SecurityCritical]
191         public void GetObjectData(SerializationInfo info, StreamingContext context)
192         {
193             GenerateDefaultHashes();
194
195             //
196             // Backwards compatibility with Whidbey
197             //
198
199             byte[] sha1Hash;
200             byte[] md5Hash;
201
202             // Whidbey expects the MD5 and SHA1 hashes stored separately.
203             if (m_hashes.TryGetValue(typeof(MD5), out md5Hash))
204             {
205                 info.AddValue("Md5", md5Hash);
206             }
207             if (m_hashes.TryGetValue(typeof(SHA1), out sha1Hash))
208             {
209                 info.AddValue("Sha1", sha1Hash);
210             }
211             
212             // For perf, don't serialize the assembly binary content.
213             // This has the side-effect that the Whidbey runtime will not be able to compute any 
214             // hashes besides the provided MD5 and SHA1.
215             info.AddValue("RawData", null);
216             // It doesn't make sense to serialize a memory pointer cross-runtime.
217             info.AddValue("PEFile", IntPtr.Zero);
218
219             //
220             // Current implementation
221             //
222
223             // Add all the computed hashes. While this can duplicate the MD5 and SHA1 hashes, 
224             // it allows for a clean separation between legacy support and the current implementation.
225             info.AddValue("Hashes", m_hashes);
226         }
227
228         /// <summary>
229         ///     Get the SHA-1 hash value of the assembly
230         /// </summary>
231         public byte[] SHA1
232         {
233             get
234             {
235                 byte[] sha1 = null;
236                 if (!m_hashes.TryGetValue(typeof(SHA1), out sha1))
237                 {
238                     sha1 = GenerateHash(GetDefaultHashImplementationOrFallback(typeof(SHA1), typeof(SHA1)));
239                 }
240
241                 byte[] returnHash = new byte[sha1.Length];
242                 Array.Copy(sha1, returnHash, returnHash.Length);
243                 return returnHash;
244             }
245         }
246
247         /// <summary>
248         ///     Get the SHA-256 hash value of the assembly
249         /// </summary>
250         public byte[] SHA256
251         {
252             get
253             {
254                 byte[] sha256 = null;
255                 if (!m_hashes.TryGetValue(typeof(SHA256), out sha256))
256                 {
257                     sha256 = GenerateHash(GetDefaultHashImplementationOrFallback(typeof(SHA256), typeof(SHA256)));
258                 }
259
260                 byte[] returnHash = new byte[sha256.Length];
261                 Array.Copy(sha256, returnHash, returnHash.Length);
262                 return returnHash;
263             }
264         }
265
266         /// <summary>
267         ///     Get the MD5 hash value of the assembly
268         /// </summary>
269         public byte[] MD5
270         {
271             get
272             {
273                 byte[] md5 = null;
274                 if (!m_hashes.TryGetValue(typeof(MD5), out md5))
275                 {
276                     md5 = GenerateHash(GetDefaultHashImplementationOrFallback(typeof(MD5), typeof(MD5)));
277                 }
278
279                 byte[] returnHash = new byte[md5.Length];
280                 Array.Copy(md5, returnHash, returnHash.Length);
281                 return returnHash;
282             }
283         }
284
285         /// <summary>
286         ///     Get the hash value of the assembly when hashed with a specific algorithm.  The actual hash
287         ///     algorithm object is not used, however the same type of object will be used.
288         /// </summary>
289         public byte[] GenerateHash(HashAlgorithm hashAlg)
290         {
291             if (hashAlg == null)
292                 throw new ArgumentNullException("hashAlg");
293             Contract.EndContractBlock();
294
295             byte[] hashValue = GenerateHash(hashAlg.GetType());
296
297             byte[] returnHash = new byte[hashValue.Length];
298             Array.Copy(hashValue, returnHash, returnHash.Length);
299             return returnHash;
300         }
301
302         /// <summary>
303         ///     Generate the hash value of an assembly when hashed with the specified algorithm. The result
304         ///     may be a direct reference to our internal table of hashes, so it should be copied before
305         ///     returning it to user code.
306         /// </summary>
307         private byte[] GenerateHash(Type hashType)
308         {
309             Contract.Assert(hashType != null && typeof(HashAlgorithm).IsAssignableFrom(hashType), "Expected a hash algorithm");
310
311             Type indexType = GetHashIndexType(hashType);
312             byte[] hashValue = null;
313             if (!m_hashes.TryGetValue(indexType, out hashValue))
314             {
315                 // If we're not attached to an assembly, then we cannot generate hashes on demand
316                 if (m_assembly == null)
317                 {
318                     throw new InvalidOperationException(Environment.GetResourceString("Security_CannotGenerateHash"));
319                 }
320
321                 hashValue = GenerateHash(hashType, GetRawData());
322                 m_hashes[indexType] = hashValue;
323             }
324
325             return hashValue;
326         }
327
328         /// <summary>
329         ///     Generate a hash of the given type for the assembly data
330         /// </summary>
331         private static byte[] GenerateHash(Type hashType, byte[] assemblyBytes)
332         {
333             Contract.Assert(hashType != null && typeof(HashAlgorithm).IsAssignableFrom(hashType), "Expected a hash algorithm");
334             Contract.Assert(assemblyBytes != null);
335
336             using (HashAlgorithm hash = HashAlgorithm.Create(hashType.FullName))
337             {
338                 return hash.ComputeHash(assemblyBytes);
339             }
340         }
341
342
343         /// <summary>
344         ///     Build the default set of hash values that will be available for all assemblies
345         /// </summary>
346         private void GenerateDefaultHashes()
347         {
348             // We can't generate any hash values that we don't already have if there isn't an attached
349             // assembly to get the hash value of.
350             if (m_assembly != null)
351             {
352                 GenerateDefaultHashes(GetRawData());
353             }
354         }
355
356         /// <summary>
357         ///     Build the default set of hash values that will be available for all assemblies given the raw
358         ///     assembly data to hash.
359         /// </summary>
360         private void GenerateDefaultHashes(byte[] assemblyBytes)
361         {
362             Contract.Assert(assemblyBytes != null);
363
364             Type[] defaultHashTypes = new Type[]
365             {
366                 GetHashIndexType(typeof(SHA1)),
367                 GetHashIndexType(typeof(SHA256)),
368                 GetHashIndexType(typeof(MD5))
369             };
370
371             foreach (Type defaultHashType in defaultHashTypes)
372             {
373                 Type hashImplementationType = GetDefaultHashImplementation(defaultHashType);
374                 if (hashImplementationType != null)
375                 {
376                     if (!m_hashes.ContainsKey(defaultHashType))
377                     {
378                         m_hashes[defaultHashType] = GenerateHash(hashImplementationType, assemblyBytes);
379                     }
380                 }
381             }
382         }
383
384         /// <summary>
385         ///     Map a hash algorithm to the default implementation of that algorithm, falling back to a given
386         ///     implementation if no suitable default can be found.  This option may be used for situations
387         ///     where it is better to throw an informative exception when trying to use an unsuitable hash
388         ///     algorithm than to just return null.  (For instance, throwing a FIPS not supported exception
389         ///     when trying to get the MD5 hash evidence).
390         /// </summary>
391         private static Type GetDefaultHashImplementationOrFallback(Type hashAlgorithm,
392                                                                    Type fallbackImplementation)
393         {
394             Contract.Assert(hashAlgorithm != null && typeof(HashAlgorithm).IsAssignableFrom(hashAlgorithm));
395             Contract.Assert(fallbackImplementation != null && GetHashIndexType(hashAlgorithm).IsAssignableFrom(fallbackImplementation));
396
397             Type defaultImplementation = GetDefaultHashImplementation(hashAlgorithm);
398             return defaultImplementation != null ? defaultImplementation : fallbackImplementation;
399         }
400
401         /// <summary>
402         ///     Map a hash algorithm to the default implementation of that algorithm to use for Hash
403         ///     evidence, taking into account things such as FIPS support.  If there is no suitable
404         ///     implementation for the algorithm, GetDefaultHashImplementation returns null.
405         /// </summary>
406         private static Type GetDefaultHashImplementation(Type hashAlgorithm)
407         {
408             Contract.Assert(hashAlgorithm != null && typeof(HashAlgorithm).IsAssignableFrom(hashAlgorithm));
409
410             if (hashAlgorithm.IsAssignableFrom(typeof(MD5)))
411             {
412                 // MD5 is not a FIPS compliant algorithm, so if we need to allow only FIPS implementations,
413                 // we have no way to create an MD5 hash.  Otherwise, we can just use the standard CAPI
414                 // implementation since that is available on all operating systems we support.
415                 if (!CryptoConfig.AllowOnlyFipsAlgorithms)
416                 {
417                     return typeof(MD5CryptoServiceProvider);
418                 }
419                 else
420                 {
421                     return null;
422                 }
423             }
424             else if (hashAlgorithm.IsAssignableFrom(typeof(SHA256)))
425             {
426                 // The managed SHA256 implementation is not a FIPS certified implementation, however on
427                 // we have a FIPS alternative.
428                 return Type.GetType("System.Security.Cryptography.SHA256CryptoServiceProvider, " + AssemblyRef.SystemCore);
429             }
430             else
431             {
432                 // Otherwise we don't have a better suggestion for the algorithm, so we can just fallback to
433                 // the input algorithm.
434                 return hashAlgorithm;
435             }
436         }
437
438         /// <summary>
439         ///     Get the type used to index into the saved hash value dictionary.  We want this to be the
440         ///     class which immediately derives from HashAlgorithm so that we can reuse the same hash value
441         ///     if we're asked for (e.g.) SHA256Managed and SHA256CryptoServiceProvider
442         /// </summary>
443         private static Type GetHashIndexType(Type hashType)
444         {
445             Contract.Assert(hashType != null && typeof(HashAlgorithm).IsAssignableFrom(hashType));
446
447             Type currentType = hashType;
448
449             // Walk up the inheritence hierarchy looking for the first class that derives from HashAlgorithm
450             while (currentType != null && currentType.BaseType != typeof(HashAlgorithm))
451             {
452                 currentType = currentType.BaseType;
453             }
454
455             // If this is the degenerate case where we started out with HashAlgorithm, we won't find it
456             // further up our inheritence tree.
457             if (currentType == null)
458             {
459                 BCLDebug.Assert(hashType == typeof(HashAlgorithm), "hashType == typeof(HashAlgorithm)");
460                 currentType = typeof(HashAlgorithm);
461             }
462
463             return currentType;
464         }
465
466         /// <summary>
467         ///     Raw bytes of the assembly being hashed
468         /// </summary>
469         private byte[] GetRawData()
470         {
471             byte[] rawData = null;
472
473             // We can only generate hashes on demand if we're associated with an assembly
474             if (m_assembly != null)
475             {
476                 // See if we still hold a reference to the assembly data
477                 if (m_rawData != null)
478                 {
479                     rawData = m_rawData.Target as byte[];
480                 }
481
482                 // If not, load the raw bytes up
483                 if (rawData == null)
484                 {
485                     rawData = m_assembly.GetRawBytes();
486                     m_rawData = new WeakReference(rawData);
487                 }
488             }
489
490             return rawData;
491         }
492
493         private SecurityElement ToXml()
494         {
495             GenerateDefaultHashes();
496
497             SecurityElement root = new SecurityElement("System.Security.Policy.Hash");
498             // If you hit this assert then most likely you are trying to change the name of this class. 
499             // This is ok as long as you change the hard coded string above and change the assert below.
500             BCLDebug.Assert(this.GetType().FullName.Equals("System.Security.Policy.Hash"), "Class name changed!");
501
502             root.AddAttribute("version", "2");
503             foreach (KeyValuePair<Type, byte[]> hashValue in m_hashes)
504             {
505                 SecurityElement hashElement = new SecurityElement("hash");
506                 hashElement.AddAttribute("algorithm", hashValue.Key.Name);
507                 hashElement.AddAttribute("value", Hex.EncodeHexString(hashValue.Value));
508
509                 root.AddChild(hashElement);
510             }
511
512             return root;
513         }
514
515         public override String ToString()
516         {
517             return ToXml().ToString();
518         }
519     }
520 }