diff options
Diffstat (limited to 'mcs/class/corlib/Mono.Security.Authenticode')
3 files changed, 228 insertions, 85 deletions
diff --git a/mcs/class/corlib/Mono.Security.Authenticode/AuthenticodeBase.cs b/mcs/class/corlib/Mono.Security.Authenticode/AuthenticodeBase.cs index 9e5923667b5..c2d609ecec2 100755 --- a/mcs/class/corlib/Mono.Security.Authenticode/AuthenticodeBase.cs +++ b/mcs/class/corlib/Mono.Security.Authenticode/AuthenticodeBase.cs @@ -5,10 +5,6 @@ // 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 @@ -60,63 +56,167 @@ namespace Mono.Security.Authenticode { public const string spcIndirectDataContext = "1.3.6.1.4.1.311.2.1.4"; - internal byte[] rawData; + private byte[] fileblock; + private FileStream fs; + private int blockNo; + private int blockLength; + private int peOffset; + private int dirSecurityOffset; + private int dirSecuritySize; public AuthenticodeBase () { + fileblock = new byte [4096]; } - protected byte[] HashFile (string fileName, string hashName) + internal void Open (string filename) { - FileStream fs = new FileStream (fileName, FileMode.Open, FileAccess.Read, FileShare.Read); - byte[] file = new byte [fs.Length]; - fs.Read (file, 0, file.Length); - fs.Close (); + if (fs != null) + Close (); + fs = new FileStream (filename, FileMode.Open, FileAccess.Read, FileShare.Read); + } - // MZ - DOS header - if (BitConverterLE.ToUInt16 (file, 0) != 0x5A4D) - return null; + internal void Close () + { + if (fs != null) { + fs.Close (); + fs = null; + blockNo = 0; + } + } - // find offset of PE header - int peOffset = BitConverterLE.ToInt32 (file, 60); - if (peOffset > file.Length) - return null; + internal bool ReadFirstBlock () + { + if (fs == null) + return false; + + fs.Position = 0; + // read first block - it will include (100% sure) + // the MZ header and (99.9% sure) the PE header + blockLength = fs.Read (fileblock, 0, fileblock.Length); + blockNo = 1; + if (blockLength < 64) + return false; // invalid PE file + + // 1. Validate the MZ header informations + // 1.1. Check for magic MZ at start of header + if (BitConverterLE.ToUInt16 (fileblock, 0) != 0x5A4D) + return false; + + // 1.2. Find the offset of the PE header + peOffset = BitConverterLE.ToInt32 (fileblock, 60); + if (peOffset > fileblock.Length) { + // just in case (0.1%) this can actually happen + string msg = String.Format (Locale.GetText ( + "Header size too big (> {0} bytes)."), + fileblock.Length); + throw new NotSupportedException (msg); + } + if (peOffset > fs.Length) + return false; - // PE - NT header - if (BitConverterLE.ToUInt16 (file, peOffset) != 0x4550) - return null; + // 2. Read between DOS header and first part of PE header + // 2.1. Check for magic PE at start of header + if (BitConverterLE.ToUInt16 (fileblock, peOffset) != 0x4550) + return false; - // IMAGE_DIRECTORY_ENTRY_SECURITY - int dirSecurityOffset = BitConverterLE.ToInt32 (file, peOffset + 152); - int dirSecuritySize = BitConverterLE.ToInt32 (file, peOffset + 156); + // 2.2. Locate IMAGE_DIRECTORY_ENTRY_SECURITY (offset and size) + dirSecurityOffset = BitConverterLE.ToInt32 (fileblock, peOffset + 152); + dirSecuritySize = BitConverterLE.ToInt32 (fileblock, peOffset + 156); + + return true; + } + + internal byte[] GetSecurityEntry () + { + if (blockNo < 1) + ReadFirstBlock (); if (dirSecuritySize > 8) { - rawData = new byte [dirSecuritySize - 8]; - Buffer.BlockCopy (file, dirSecurityOffset + 8, rawData, 0, rawData.Length); -/* DEBUG - FileStream debug = new FileStream (fileName + ".sig", FileMode.Create, FileAccess.Write); - debug.Write (rawData, 0, rawData.Length); - debug.Close ();*/ + // remove header from size (not ASN.1 based) + byte[] secEntry = new byte [dirSecuritySize - 8]; + // position after header and read entry + fs.Position = dirSecurityOffset + 8; + fs.Read (secEntry, 0, secEntry.Length); + return secEntry; + } + return null; + } + + // returns null if the file isn't signed + internal byte[] GetHash (HashAlgorithm hash) + { + if (blockNo < 1) + ReadFirstBlock (); + fs.Position = blockLength; + + // hash the rest of the file + long n = fs.Length - blockLength; + // minus any authenticode signature (with 8 bytes header) + if (dirSecurityOffset > 0) { + // it is also possible that the signature block + // starts within the block in memory (small EXE) + if (dirSecurityOffset < blockLength) { + blockLength = dirSecurityOffset; + n = 0; + } + else + n -= (dirSecuritySize); } - else - rawData = null; - HashAlgorithm hash = HashAlgorithm.Create (hashName); - // 0 to 215 (216) then skip 4 (checksum) + // Authenticode(r) gymnastics + // Hash from (generally) 0 to 215 (216 bytes) int pe = peOffset + 88; - hash.TransformBlock (file, 0, pe, file, 0); + hash.TransformBlock (fileblock, 0, pe, fileblock, 0); + // then skip 4 for checksum pe += 4; - // 220 to 279 (60) then skip 8 (IMAGE_DIRECTORY_ENTRY_SECURITY) - hash.TransformBlock (file, pe, 60, file, pe); + // Continue hashing from (generally) 220 to 279 (60 bytes) + hash.TransformBlock (fileblock, pe, 60, fileblock, pe); + // then skip 8 bytes for IMAGE_DIRECTORY_ENTRY_SECURITY pe += 68; - // 288 to end of file - int n = file.Length - pe; - // minus any authenticode signature (with 8 bytes header) - if (dirSecurityOffset != 0) - n -= (dirSecuritySize); - hash.TransformFinalBlock (file, pe, n); + // everything is present so start the hashing + if (n == 0) { + // hash the (only) block + hash.TransformFinalBlock (fileblock, pe, blockLength - pe); + } + else { + // hash the last part of the first (already in memory) block + hash.TransformBlock (fileblock, pe, blockLength - pe, fileblock, 0); + + // hash by blocks of 4096 bytes + long blocks = (n >> 12); + int remainder = (int)(n - (blocks << 12)); + if (remainder == 0) { + blocks--; + remainder = 4096; + } + // blocks + while (blocks-- > 0) { + fs.Read (fileblock, 0, fileblock.Length); + hash.TransformBlock (fileblock, 0, fileblock.Length, fileblock, 0); + } + // remainder + if (fs.Read (fileblock, 0, remainder) != remainder) + return null; + hash.TransformFinalBlock (fileblock, 0, remainder); + } return hash.Hash; } + + // for compatibility only + protected byte[] HashFile (string fileName, string hashName) + { + try { + Open (fileName); + HashAlgorithm hash = HashAlgorithm.Create (hashName); + byte[] result = GetHash (hash); + Close (); + return result; + } + catch { + return null; + } + } } } diff --git a/mcs/class/corlib/Mono.Security.Authenticode/AuthenticodeDeformatter.cs b/mcs/class/corlib/Mono.Security.Authenticode/AuthenticodeDeformatter.cs index 803781954f7..ea0bd30e242 100755 --- a/mcs/class/corlib/Mono.Security.Authenticode/AuthenticodeDeformatter.cs +++ b/mcs/class/corlib/Mono.Security.Authenticode/AuthenticodeDeformatter.cs @@ -2,12 +2,9 @@ // AuthenticodeDeformatter.cs: Authenticode signature validator // // Author: -// Sebastien Pouliot (spouliot@motus.com) +// Sebastien Pouliot <sebastien@ximian.com> // // (C) 2003 Motus Technologies Inc. (http://www.motus.com) -// - -// // Copyright (C) 2004 Novell, Inc (http://www.novell.com) // // Permission is hereby granted, free of charge, to any person obtaining @@ -57,6 +54,9 @@ namespace Mono.Security.Authenticode { private DateTime timestamp; private X509Certificate signingCertificate; private int reason; + private bool trustedRoot; + private bool trustedTimestampRoot; + private byte[] entry; private X509Chain signerChain; private X509Chain timestampChain; @@ -70,17 +70,20 @@ namespace Mono.Security.Authenticode { public AuthenticodeDeformatter (string fileName) : this () { - if (!CheckSignature (fileName)) { - // invalid or no signature - if (signedHash != null) - throw new COMException ("Invalid signature"); - // no exception is thrown when there's no signature in the PE file - } + FileName = fileName; } public string FileName { get { return filename; } - set { CheckSignature (value); } + set { + Reset (); + try { + CheckSignature (value); + } + catch { + reason = 1; + } + } } public byte[] Hash { @@ -101,7 +104,7 @@ namespace Mono.Security.Authenticode { public bool IsTrusted () { - if (rawData == null) { + if (entry == null) { reason = 1; return false; } @@ -111,13 +114,13 @@ namespace Mono.Security.Authenticode { return false; } - if (signerChain.Root == null) { + if ((signerChain.Root == null) || !trustedRoot) { reason = 6; return false; } if (timestamp != DateTime.MinValue) { - if (timestampChain.Root == null) { + if ((timestampChain.Root == null) || !trustedTimestampRoot) { reason = 6; return false; } @@ -139,7 +142,11 @@ namespace Mono.Security.Authenticode { } public byte[] Signature { - get { return (byte[]) rawData.Clone (); } + get { + if (entry == null) + return null; + return (byte[]) entry.Clone (); + } } public DateTime Timestamp { @@ -157,43 +164,58 @@ namespace Mono.Security.Authenticode { private bool CheckSignature (string fileName) { filename = fileName; - - // by default we try with MD5 - string hashName = "MD5"; - // compare the signature's hash with the hash of the file - hash = HashFile (filename, hashName); - - // is a signature present ? - if (rawData == null) + base.Open (filename); + entry = base.GetSecurityEntry (); + if (entry == null) { + // no signature is present + reason = 1; + base.Close (); return false; + } - PKCS7.ContentInfo ci = new PKCS7.ContentInfo (rawData); - if (ci.ContentType != PKCS7.Oid.signedData) + PKCS7.ContentInfo ci = new PKCS7.ContentInfo (entry); + if (ci.ContentType != PKCS7.Oid.signedData) { + base.Close (); return false; + } PKCS7.SignedData sd = new PKCS7.SignedData (ci.Content); - if (sd.ContentInfo.ContentType != spcIndirectDataContext) + if (sd.ContentInfo.ContentType != spcIndirectDataContext) { + base.Close (); return false; + } coll = sd.Certificates; ASN1 spc = sd.ContentInfo.Content; signedHash = spc [0][1][1]; - if (signedHash.Length == 20) { - // seems to be SHA-1, restart hashing - hashName = "SHA1"; - hash = HashFile (filename, hashName); + + HashAlgorithm ha = null; + switch (signedHash.Length) { + case 16: + ha = HashAlgorithm.Create ("MD5"); + hash = GetHash (ha); + break; + case 20: + ha = HashAlgorithm.Create ("SHA1"); + hash = GetHash (ha); + break; + default: + reason = 5; + base.Close (); + return false; } + base.Close (); if (!signedHash.CompareValue (hash)) return false; // messageDigest is a hash of spcIndirectDataContext (which includes the file hash) byte[] spcIDC = spc [0].Value; - HashAlgorithm ha = HashAlgorithm.Create (hashName); + ha.Initialize (); // re-using hash instance byte[] messageDigest = ha.ComputeHash (spcIDC); - return VerifySignature (sd, messageDigest, hashName); + return VerifySignature (sd, messageDigest, ha); } private bool CompareIssuerSerial (string issuer, byte[] serial, X509Certificate x509) @@ -213,7 +235,7 @@ namespace Mono.Security.Authenticode { } //private bool VerifySignature (ASN1 cs, byte[] calculatedMessageDigest, string hashName) - private bool VerifySignature (PKCS7.SignedData sd, byte[] calculatedMessageDigest, string hashName) + private bool VerifySignature (PKCS7.SignedData sd, byte[] calculatedMessageDigest, HashAlgorithm ha) { string contentType = null; ASN1 messageDigest = null; @@ -242,7 +264,7 @@ namespace Mono.Security.Authenticode { case "1.3.6.1.4.1.311.2.1.12": // spcSpOpusInfo (Microsoft code signing) try { - spcSpOpusInfo = System.Text.Encoding.UTF8.GetString (attr[1][0][1][0].Value); + spcSpOpusInfo = System.Text.Encoding.UTF8.GetString (attr[1][0][0][0].Value); } catch (NullReferenceException) { spcSpOpusInfo = null; @@ -262,13 +284,13 @@ namespace Mono.Security.Authenticode { return false; // verify signature - string hashOID = CryptoConfig.MapNameToOID (hashName); + string hashOID = CryptoConfig.MapNameToOID (ha.ToString ()); // change to SET OF (not [0]) as per PKCS #7 1.5 ASN1 aa = new ASN1 (0x31); foreach (ASN1 a in sd.SignerInfo.AuthenticatedAttributes) aa.Add (a); - HashAlgorithm ha = HashAlgorithm.Create (hashName); + ha.Initialize (); byte[] p7hash = ha.ComputeHash (aa.GetBytes ()); byte[] signature = sd.SignerInfo.Signature; @@ -282,10 +304,9 @@ namespace Mono.Security.Authenticode { RSACryptoServiceProvider rsa = (RSACryptoServiceProvider) x509.RSA; if (rsa.VerifyHash (p7hash, hashOID, signature)) { signerChain.LoadCertificates (coll); - if (signerChain.Build (x509)) - signingCertificate = x509; - else - return false; + trustedRoot = signerChain.Build (x509); + signingCertificate = x509; + break; } } } @@ -301,17 +322,18 @@ namespace Mono.Security.Authenticode { // countersignature (1 2 840 113549 1 9 6) // SET { PKCS7.SignerInfo cs = new PKCS7.SignerInfo (attr [1]); - return VerifyCounterSignature (cs, signature, hashName); + trustedTimestampRoot = VerifyCounterSignature (cs, signature); + break; default: // we don't support other unauthenticated attributes break; } } - return true; + return (trustedRoot && trustedTimestampRoot); } - private bool VerifyCounterSignature (PKCS7.SignerInfo cs, byte[] signature, string hashName) + private bool VerifyCounterSignature (PKCS7.SignerInfo cs, byte[] signature) { // SEQUENCE { // INTEGER 1 @@ -358,6 +380,7 @@ namespace Mono.Security.Authenticode { if (messageDigest == null) return false; // TODO: must be read from the ASN.1 structure + string hashName = null; switch (messageDigest.Length) { case 16: hashName = "MD5"; @@ -398,5 +421,20 @@ namespace Mono.Security.Authenticode { // no certificate can verify this signature! return false; } + + private void Reset () + { + filename = null; + entry = null; + hash = null; + signedHash = null; + signingCertificate = null; + reason = -1; + trustedRoot = false; + trustedTimestampRoot = false; + signerChain.Reset (); + timestampChain.Reset (); + timestamp = DateTime.MinValue; + } } } diff --git a/mcs/class/corlib/Mono.Security.Authenticode/ChangeLog b/mcs/class/corlib/Mono.Security.Authenticode/ChangeLog index 7f9398815f2..da0e586a096 100755 --- a/mcs/class/corlib/Mono.Security.Authenticode/ChangeLog +++ b/mcs/class/corlib/Mono.Security.Authenticode/ChangeLog @@ -1,3 +1,8 @@ +2004-09-07 Sebastien Pouliot <sebastien@ximian.com> + + * AuthenticodeBase.cs: Merge optimizations from HEAD. + * AuthenticodeDeformatter.cs: Merge optimizations from HEAD. + 2004-04-28 Sebastien Pouliot <sebastien@ximian.com> * AuthenticodeBase.cs: In sync with Mono.Security.dll version. |