3 // Copyright (c) Microsoft Corporation. All rights reserved.
6 // <OWNER>Microsoft</OWNER>
12 // Evidence corresponding to a hash of the assembly bits.
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;
28 namespace System.Security.Policy
32 public sealed class Hash : EvidenceBase, ISerializable
34 private RuntimeAssembly m_assembly;
35 private Dictionary<Type, byte[]> m_hashes;
36 private WeakReference m_rawData;
39 /// Deserialize a serialized hash evidence object
42 internal Hash(SerializationInfo info, StreamingContext context)
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.
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
54 Dictionary<Type, byte[]> hashes = info.GetValueNoThrow("Hashes", typeof(Dictionary<Type, byte[]>)) as Dictionary<Type, byte[]>;
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[]>();
65 byte[] md5 = info.GetValueNoThrow("Md5", typeof(byte[])) as byte[];
68 m_hashes[typeof(MD5)] = md5;
71 byte[] sha1 = info.GetValueNoThrow("Sha1", typeof(byte[])) as byte[];
74 m_hashes[typeof(SHA1)] = sha1;
77 byte[] rawData = info.GetValueNoThrow("RawData", typeof(byte[])) as byte[];
80 GenerateDefaultHashes(rawData);
86 /// Create hash evidence for the specified assembly
88 public Hash(Assembly assembly)
91 throw new ArgumentNullException("assembly");
92 Contract.EndContractBlock();
93 if (assembly.IsDynamic)
94 throw new ArgumentException(Environment.GetResourceString("Security_CannotGenerateHash"), "assembly");
96 m_hashes = new Dictionary<Type, byte[]>();
97 m_assembly = assembly as RuntimeAssembly;
99 if (m_assembly == null)
100 throw new ArgumentException(Environment.GetResourceString("Argument_MustBeRuntimeAssembly"), "assembly");
104 /// Create a copy of some hash evidence
106 private Hash(Hash hash)
108 Contract.Assert(hash != null);
110 m_assembly = hash.m_assembly;
111 m_rawData = hash.m_rawData;
112 m_hashes = new Dictionary<Type, byte[]>(hash.m_hashes);
116 /// Create a hash evidence prepopulated with a specific hash value
118 private Hash(Type hashType, byte[] hashValue)
120 Contract.Assert(hashType != null);
121 Contract.Assert(hashValue != null);
123 m_hashes = new Dictionary<Type, byte[]>();
125 byte[] hashClone = new byte[hashValue.Length];
126 Array.Copy(hashValue, hashClone, hashClone.Length);
128 m_hashes[hashType] = hashValue;
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.
135 public static Hash CreateSHA1(byte[] sha1)
138 throw new ArgumentNullException("sha1");
139 Contract.EndContractBlock();
141 return new Hash(typeof(SHA1), sha1);
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.
148 public static Hash CreateSHA256(byte[] sha256)
151 throw new ArgumentNullException("sha256");
152 Contract.EndContractBlock();
154 return new Hash(typeof(SHA256), sha256);
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.
161 public static Hash CreateMD5(byte[] md5)
164 throw new ArgumentNullException("md5");
165 Contract.EndContractBlock();
167 return new Hash(typeof(MD5), md5);
171 /// Make a copy of this evidence object
173 public override EvidenceBase Clone()
175 return new Hash(this);
179 /// Prepare the hash evidence for serialization
182 private void OnSerializing(StreamingContext ctx)
184 GenerateDefaultHashes();
188 /// Serialize the hash evidence
191 public void GetObjectData(SerializationInfo info, StreamingContext context)
193 GenerateDefaultHashes();
196 // Backwards compatibility with Whidbey
202 // Whidbey expects the MD5 and SHA1 hashes stored separately.
203 if (m_hashes.TryGetValue(typeof(MD5), out md5Hash))
205 info.AddValue("Md5", md5Hash);
207 if (m_hashes.TryGetValue(typeof(SHA1), out sha1Hash))
209 info.AddValue("Sha1", sha1Hash);
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);
220 // Current implementation
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);
229 /// Get the SHA-1 hash value of the assembly
236 if (!m_hashes.TryGetValue(typeof(SHA1), out sha1))
238 sha1 = GenerateHash(GetDefaultHashImplementationOrFallback(typeof(SHA1), typeof(SHA1)));
241 byte[] returnHash = new byte[sha1.Length];
242 Array.Copy(sha1, returnHash, returnHash.Length);
248 /// Get the SHA-256 hash value of the assembly
254 byte[] sha256 = null;
255 if (!m_hashes.TryGetValue(typeof(SHA256), out sha256))
257 sha256 = GenerateHash(GetDefaultHashImplementationOrFallback(typeof(SHA256), typeof(SHA256)));
260 byte[] returnHash = new byte[sha256.Length];
261 Array.Copy(sha256, returnHash, returnHash.Length);
267 /// Get the MD5 hash value of the assembly
274 if (!m_hashes.TryGetValue(typeof(MD5), out md5))
276 md5 = GenerateHash(GetDefaultHashImplementationOrFallback(typeof(MD5), typeof(MD5)));
279 byte[] returnHash = new byte[md5.Length];
280 Array.Copy(md5, returnHash, returnHash.Length);
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.
289 public byte[] GenerateHash(HashAlgorithm hashAlg)
292 throw new ArgumentNullException("hashAlg");
293 Contract.EndContractBlock();
295 byte[] hashValue = GenerateHash(hashAlg.GetType());
297 byte[] returnHash = new byte[hashValue.Length];
298 Array.Copy(hashValue, returnHash, returnHash.Length);
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.
307 private byte[] GenerateHash(Type hashType)
309 Contract.Assert(hashType != null && typeof(HashAlgorithm).IsAssignableFrom(hashType), "Expected a hash algorithm");
311 Type indexType = GetHashIndexType(hashType);
312 byte[] hashValue = null;
313 if (!m_hashes.TryGetValue(indexType, out hashValue))
315 // If we're not attached to an assembly, then we cannot generate hashes on demand
316 if (m_assembly == null)
318 throw new InvalidOperationException(Environment.GetResourceString("Security_CannotGenerateHash"));
321 hashValue = GenerateHash(hashType, GetRawData());
322 m_hashes[indexType] = hashValue;
329 /// Generate a hash of the given type for the assembly data
331 private static byte[] GenerateHash(Type hashType, byte[] assemblyBytes)
333 Contract.Assert(hashType != null && typeof(HashAlgorithm).IsAssignableFrom(hashType), "Expected a hash algorithm");
334 Contract.Assert(assemblyBytes != null);
336 using (HashAlgorithm hash = HashAlgorithm.Create(hashType.FullName))
338 return hash.ComputeHash(assemblyBytes);
344 /// Build the default set of hash values that will be available for all assemblies
346 private void GenerateDefaultHashes()
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)
352 GenerateDefaultHashes(GetRawData());
357 /// Build the default set of hash values that will be available for all assemblies given the raw
358 /// assembly data to hash.
360 private void GenerateDefaultHashes(byte[] assemblyBytes)
362 Contract.Assert(assemblyBytes != null);
364 Type[] defaultHashTypes = new Type[]
366 GetHashIndexType(typeof(SHA1)),
367 GetHashIndexType(typeof(SHA256)),
368 GetHashIndexType(typeof(MD5))
371 foreach (Type defaultHashType in defaultHashTypes)
373 Type hashImplementationType = GetDefaultHashImplementation(defaultHashType);
374 if (hashImplementationType != null)
376 if (!m_hashes.ContainsKey(defaultHashType))
378 m_hashes[defaultHashType] = GenerateHash(hashImplementationType, assemblyBytes);
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).
391 private static Type GetDefaultHashImplementationOrFallback(Type hashAlgorithm,
392 Type fallbackImplementation)
394 Contract.Assert(hashAlgorithm != null && typeof(HashAlgorithm).IsAssignableFrom(hashAlgorithm));
395 Contract.Assert(fallbackImplementation != null && GetHashIndexType(hashAlgorithm).IsAssignableFrom(fallbackImplementation));
397 Type defaultImplementation = GetDefaultHashImplementation(hashAlgorithm);
398 return defaultImplementation != null ? defaultImplementation : fallbackImplementation;
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.
406 private static Type GetDefaultHashImplementation(Type hashAlgorithm)
408 Contract.Assert(hashAlgorithm != null && typeof(HashAlgorithm).IsAssignableFrom(hashAlgorithm));
410 if (hashAlgorithm.IsAssignableFrom(typeof(MD5)))
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)
417 return typeof(MD5CryptoServiceProvider);
424 else if (hashAlgorithm.IsAssignableFrom(typeof(SHA256)))
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);
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;
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
443 private static Type GetHashIndexType(Type hashType)
445 Contract.Assert(hashType != null && typeof(HashAlgorithm).IsAssignableFrom(hashType));
447 Type currentType = hashType;
449 // Walk up the inheritence hierarchy looking for the first class that derives from HashAlgorithm
450 while (currentType != null && currentType.BaseType != typeof(HashAlgorithm))
452 currentType = currentType.BaseType;
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)
459 BCLDebug.Assert(hashType == typeof(HashAlgorithm), "hashType == typeof(HashAlgorithm)");
460 currentType = typeof(HashAlgorithm);
467 /// Raw bytes of the assembly being hashed
469 private byte[] GetRawData()
471 byte[] rawData = null;
473 // We can only generate hashes on demand if we're associated with an assembly
474 if (m_assembly != null)
476 // See if we still hold a reference to the assembly data
477 if (m_rawData != null)
479 rawData = m_rawData.Target as byte[];
482 // If not, load the raw bytes up
485 rawData = m_assembly.GetRawBytes();
486 m_rawData = new WeakReference(rawData);
493 private SecurityElement ToXml()
495 GenerateDefaultHashes();
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!");
502 root.AddAttribute("version", "2");
503 foreach (KeyValuePair<Type, byte[]> hashValue in m_hashes)
505 SecurityElement hashElement = new SecurityElement("hash");
506 hashElement.AddAttribute("algorithm", hashValue.Key.Name);
507 hashElement.AddAttribute("value", Hex.EncodeHexString(hashValue.Value));
509 root.AddChild(hashElement);
515 public override String ToString()
517 return ToXml().ToString();