// // StrongName.cs - Strong Name Implementation // // Author: // Sebastien Pouliot (sebastien@ximian.com) // // (C) 2003 Motus Technologies Inc. (http://www.motus.com) // (C) 2004 Novell (http://www.novell.com) // // // Copyright (C) 2004 Novell, Inc (http://www.novell.com) // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // using System; using System.Configuration.Assemblies; using System.Globalization; using System.IO; using System.Reflection; using System.Security.Cryptography; using Mono.Security.Cryptography; namespace Mono.Security { #if INSIDE_CORLIB internal #else public #endif sealed class StrongName { internal class StrongNameSignature { private byte[] hash; private byte[] signature; private UInt32 signaturePosition; private UInt32 signatureLength; private UInt32 metadataPosition; private UInt32 metadataLength; private byte cliFlag; private UInt32 cliFlagPosition; public byte[] Hash { get { return hash; } set { hash = value; } } public byte[] Signature { get { return signature; } set { signature = value; } } public UInt32 MetadataPosition { get { return metadataPosition; } set { metadataPosition = value; } } public UInt32 MetadataLength { get { return metadataLength; } set { metadataLength = value; } } public UInt32 SignaturePosition { get { return signaturePosition; } set { signaturePosition = value; } } public UInt32 SignatureLength { get { return signatureLength; } set { signatureLength = value; } } // delay signed -> flag = 0x01 // strongsigned -> flag = 0x09 public byte CliFlag { get { return cliFlag; } set { cliFlag = value; } } public UInt32 CliFlagPosition { get { return cliFlagPosition; } set { cliFlagPosition = value; } } } internal enum StrongNameOptions { Metadata, Signature } private RSA rsa; private byte[] publicKey; private byte[] keyToken; private string tokenAlgorithm; public StrongName () { } public StrongName (byte[] data) { if (data == null) throw new ArgumentNullException ("data"); // check for ECMA key if (data.Length == 16) { int i = 0; int sum = 0; while (i < data.Length) sum += data [i++]; if (sum == 4) { // it is the ECMA key publicKey = (byte[]) data.Clone (); } } else { RSA = CryptoConvert.FromCapiKeyBlob (data); if (rsa == null) throw new ArgumentException ("data isn't a correctly encoded RSA public key"); } } public StrongName (RSA rsa) { if (rsa == null) throw new ArgumentNullException ("rsa"); RSA = rsa; } private void InvalidateCache () { publicKey = null; keyToken = null; } public bool CanSign { get { if (rsa == null) return false; #if INSIDE_CORLIB // the easy way if (RSA is RSACryptoServiceProvider) { // available as internal for corlib return !(rsa as RSACryptoServiceProvider).PublicOnly; } else #endif if (RSA is RSAManaged) { return !(rsa as RSAManaged).PublicOnly; } else { // the hard way try { RSAParameters p = rsa.ExportParameters (true); return ((p.D != null) && (p.P != null) && (p.Q != null)); } catch (CryptographicException) { return false; } } } } public RSA RSA { get { // if none then we create a new keypair if (rsa == null) rsa = (RSA) RSA.Create (); return rsa; } set { rsa = value; InvalidateCache (); } } public byte[] PublicKey { get { if (publicKey == null) { byte[] keyPair = CryptoConvert.ToCapiKeyBlob (rsa, false); publicKey = new byte [32 + 128]; // always 1024 bits // The first 12 bytes are documented at: // http://msdn.microsoft.com/library/en-us/cprefadd/html/grfungethashfromfile.asp // ALG_ID - Signature publicKey [0] = keyPair [4]; publicKey [1] = keyPair [5]; publicKey [2] = keyPair [6]; publicKey [3] = keyPair [7]; // ALG_ID - Hash (SHA1 == 0x8004) publicKey [4] = 0x04; publicKey [5] = 0x80; publicKey [6] = 0x00; publicKey [7] = 0x00; // Length of Public Key (in bytes) byte[] lastPart = BitConverterLE.GetBytes (publicKey.Length - 12); publicKey [8] = lastPart [0]; publicKey [9] = lastPart [1]; publicKey [10] = lastPart [2]; publicKey [11] = lastPart [3]; // Ok from here - Same structure as keypair - expect for public key publicKey [12] = 0x06; // PUBLICKEYBLOB // we can copy this part Buffer.BlockCopy (keyPair, 1, publicKey, 13, publicKey.Length - 13); // and make a small adjustment publicKey [23] = 0x31; // (RSA1 not RSA2) } return (byte[]) publicKey.Clone (); } } public byte[] PublicKeyToken { get { if (keyToken == null) { byte[] publicKey = PublicKey; if (publicKey == null) return null; HashAlgorithm ha = SHA1.Create (TokenAlgorithm); byte[] hash = ha.ComputeHash (publicKey); // we need the last 8 bytes in reverse order keyToken = new byte [8]; Buffer.BlockCopy (hash, (hash.Length - 8), keyToken, 0, 8); Array.Reverse (keyToken, 0, 8); } return (byte[]) keyToken.Clone (); } } public string TokenAlgorithm { get { if (tokenAlgorithm == null) tokenAlgorithm = "SHA1"; return tokenAlgorithm; } set { string algo = value.ToUpper (CultureInfo.InvariantCulture); if ((algo == "SHA1") || (algo == "MD5")) { tokenAlgorithm = value; InvalidateCache (); } else throw new ArgumentException ("Unsupported hash algorithm for token"); } } public byte[] GetBytes () { return CryptoConvert.ToCapiPrivateKeyBlob (RSA); } private UInt32 RVAtoPosition (UInt32 r, int sections, byte[] headers) { for (int i=0; i < sections; i++) { UInt32 p = BitConverterLE.ToUInt32 (headers, i * 40 + 20); UInt32 s = BitConverterLE.ToUInt32 (headers, i * 40 + 12); int l = (int) BitConverterLE.ToUInt32 (headers, i * 40 + 8); if ((s <= r) && (r < s + l)) { return p + r - s; } } return 0; } internal StrongNameSignature StrongHash (Stream stream, StrongNameOptions options) { StrongNameSignature info = new StrongNameSignature (); HashAlgorithm hash = HashAlgorithm.Create (TokenAlgorithm); CryptoStream cs = new CryptoStream (Stream.Null, hash, CryptoStreamMode.Write); // MS-DOS Header - always 128 bytes // ref: Section 24.2.1, Partition II Metadata byte[] mz = new byte [128]; stream.Read (mz, 0, 128); if (BitConverterLE.ToUInt16 (mz, 0) != 0x5a4d) return null; UInt32 peHeader = BitConverterLE.ToUInt32 (mz, 60); cs.Write (mz, 0, 128); if (peHeader != 128) { byte[] mzextra = new byte [peHeader - 128]; stream.Read (mzextra, 0, mzextra.Length); cs.Write (mzextra, 0, mzextra.Length); } // PE File Header - always 248 bytes // ref: Section 24.2.2, Partition II Metadata byte[] pe = new byte [248]; stream.Read (pe, 0, 248); if (BitConverterLE.ToUInt32 (pe, 0) != 0x4550) return null; if (BitConverterLE.ToUInt16 (pe, 4) != 0x14c) return null; // MUST zeroize both CheckSum and Security Directory byte[] v = new byte [8]; Buffer.BlockCopy (v, 0, pe, 88, 4); Buffer.BlockCopy (v, 0, pe, 152, 8); cs.Write (pe, 0, 248); UInt16 numSection = BitConverterLE.ToUInt16 (pe, 6); int sectionLength = (numSection * 40); byte[] sectionHeaders = new byte [sectionLength]; stream.Read (sectionHeaders, 0, sectionLength); cs.Write (sectionHeaders, 0, sectionLength); UInt32 cliHeaderRVA = BitConverterLE.ToUInt32 (pe, 232); UInt32 cliHeaderPos = RVAtoPosition (cliHeaderRVA, numSection, sectionHeaders); int cliHeaderSiz = (int) BitConverterLE.ToUInt32 (pe, 236); // CLI Header // ref: Section 24.3.3, Partition II Metadata byte[] cli = new byte [cliHeaderSiz]; stream.Position = cliHeaderPos; stream.Read (cli, 0, cliHeaderSiz); UInt32 strongNameSignatureRVA = BitConverterLE.ToUInt32 (cli, 32); info.SignaturePosition = RVAtoPosition (strongNameSignatureRVA, numSection, sectionHeaders); info.SignatureLength = BitConverterLE.ToUInt32 (cli, 36); UInt32 metadataRVA = BitConverterLE.ToUInt32 (cli, 8); info.MetadataPosition = RVAtoPosition (metadataRVA, numSection, sectionHeaders); info.MetadataLength = BitConverterLE.ToUInt32 (cli, 12); if (options == StrongNameOptions.Metadata) { cs.Close (); hash.Initialize (); byte[] metadata = new byte [info.MetadataLength]; stream.Position = info.MetadataPosition; stream.Read (metadata, 0, metadata.Length); info.Hash = hash.ComputeHash (metadata); return info; } // now we hash every section EXCEPT the signature block for (int i=0; i < numSection; i++) { UInt32 start = BitConverterLE.ToUInt32 (sectionHeaders, i * 40 + 20); int length = (int) BitConverterLE.ToUInt32 (sectionHeaders, i * 40 + 16); byte[] section = new byte [length]; stream.Position = start; stream.Read (section, 0, length); if ((start <= info.SignaturePosition) && (info.SignaturePosition < start + length)) { // hash before the signature int before = (int)(info.SignaturePosition - start); if (before > 0) { cs.Write (section, 0, before); } // copy signature info.Signature = new byte [info.SignatureLength]; Buffer.BlockCopy (section, before, info.Signature, 0, (int)info.SignatureLength); Array.Reverse (info.Signature); // hash after the signature int s = (int)(before + info.SignatureLength); int after = (int)(length - s); if (after > 0) { cs.Write (section, s, after); } } else cs.Write (section, 0, length); } cs.Close (); info.Hash = hash.Hash; return info; } // return the same result as the undocumented and unmanaged GetHashFromAssemblyFile public byte[] Hash (string fileName) { FileStream fs = File.OpenRead (fileName); StrongNameSignature sn = StrongHash (fs, StrongNameOptions.Metadata); fs.Close (); return sn.Hash; } public bool Sign (string fileName) { bool result = false; StrongNameSignature sn; using (FileStream fs = File.OpenRead (fileName)) { sn = StrongHash (fs, StrongNameOptions.Signature); fs.Close (); } if (sn.Hash == null) return false; byte[] signature = null; try { RSAPKCS1SignatureFormatter sign = new RSAPKCS1SignatureFormatter (rsa); sign.SetHashAlgorithm (TokenAlgorithm); signature = sign.CreateSignature (sn.Hash); Array.Reverse (signature); } catch (CryptographicException) { return false; } using (FileStream fs = File.OpenWrite (fileName)) { fs.Position = sn.SignaturePosition; fs.Write (signature, 0, signature.Length); fs.Close (); result = true; } return result; } public bool Verify (string fileName) { StrongNameSignature sn; using (FileStream fs = File.OpenRead (fileName)) { sn = StrongHash (fs, StrongNameOptions.Signature); fs.Close (); } if (sn.Hash == null) { return false; } try { AssemblyHashAlgorithm algorithm = AssemblyHashAlgorithm.SHA1; if (tokenAlgorithm == "MD5") algorithm = AssemblyHashAlgorithm.MD5; return Verify (rsa, algorithm, sn.Hash, sn.Signature); } catch (CryptographicException) { // no exception allowed return false; } } #if INSIDE_CORLIB static object lockObject = new object (); static bool initialized = false; // We don't want a dependency on StrongNameManager in Mono.Security.dll static public bool IsAssemblyStrongnamed (string assemblyName) { if (!initialized) { lock (lockObject) { if (!initialized) { string config = Environment.GetMachineConfigPath (); StrongNameManager.LoadConfig (config); initialized = true; } } } try { // this doesn't load the assembly (well it unloads it ;) // http://weblogs.asp.net/nunitaddin/posts/9991.aspx AssemblyName an = AssemblyName.GetAssemblyName (assemblyName); if (an == null) return false; byte[] publicKey = StrongNameManager.GetMappedPublicKey (an.GetPublicKeyToken ()); if ((publicKey == null) || (publicKey.Length < 12)) { // no mapping publicKey = an.GetPublicKey (); if ((publicKey == null) || (publicKey.Length < 12)) return false; } // Note: MustVerify is based on the original token (by design). Public key // remapping won't affect if the assembly is verified or not. if (!StrongNameManager.MustVerify (an)) { return true; } RSA rsa = CryptoConvert.FromCapiPublicKeyBlob (publicKey, 12); StrongName sn = new StrongName (rsa); bool result = sn.Verify (assemblyName); return result; } catch { // no exception allowed return false; } } // TODO // we would get better performance if the runtime hashed the // assembly - as we wouldn't have to load it from disk a // second time. The runtime already have implementations of // SHA1 (and even MD5 if required someday). static public bool VerifySignature (byte[] publicKey, int algorithm, byte[] hash, byte[] signature) { try { RSA rsa = CryptoConvert.FromCapiPublicKeyBlob (publicKey); return Verify (rsa, (AssemblyHashAlgorithm) algorithm, hash, signature); } catch { // no exception allowed return false; } } #endif static private bool Verify (RSA rsa, AssemblyHashAlgorithm algorithm, byte[] hash, byte[] signature) { RSAPKCS1SignatureDeformatter vrfy = new RSAPKCS1SignatureDeformatter (rsa); switch (algorithm) { case AssemblyHashAlgorithm.MD5: vrfy.SetHashAlgorithm ("MD5"); break; case AssemblyHashAlgorithm.SHA1: case AssemblyHashAlgorithm.None: default: vrfy.SetHashAlgorithm ("SHA1"); break; } return vrfy.VerifySignature (hash, signature); } } }