You've already forked linux-packaging-mono
Imported Upstream version 5.12.0.220
Former-commit-id: c477e03582759447177c6d4bf412cd2355aad476
This commit is contained in:
parent
8bd104cef2
commit
8fc30896db
@ -31,6 +31,64 @@ using System;
|
||||
using System.IO;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
/*
|
||||
FIXME There are a number of problems and deficiencies in this code.
|
||||
|
||||
- It requires the PE header to fit in 4K. This is not guaranteed
|
||||
by the file format and it is easy to construct valid files that violate it.
|
||||
i.e. with a large MS-DOS header. The code should just read the entire
|
||||
file into memory.
|
||||
|
||||
- It has a number of missing or incorrect range checks.
|
||||
Incorrect, as in, checking that record or field starts within
|
||||
range, but does not end within range.
|
||||
|
||||
- It removes/ignores COFF symbols. These rarely/never occur, but removing
|
||||
them is not likely correct. It is not mentioned in either of the two specifications.
|
||||
This seems to be extra unnecessary incorrect code.
|
||||
|
||||
- There are two specifications, Authenticode and PE:
|
||||
https://download.microsoft.com/download/9/c/5/9c5b2167-8017-4bae-9fde-d599bac8184a/Authenticode_PE.docx
|
||||
https://www.microsoft.com/whdc/system/platform/firmware/PECOFF.mspx
|
||||
https://msdn.microsoft.com/library/windows/desktop/ms680547(v=vs.85).aspx
|
||||
|
||||
These are in contradiction regarding hashing of data after the sections.
|
||||
Such data is usually absent. More comparison is need between
|
||||
Mono runtime and desktop runtime/tools.
|
||||
The most common such data is an older signature, which is supposed to be ignored.
|
||||
The next most common is appended setup data, which isn't likely with managed code.
|
||||
However this code has nothing to do with signing managed code specifically, just PEs.
|
||||
There is a slight inconsistency in the Authenticode_PE.doc around the location
|
||||
of the signature vs. other data past sections.
|
||||
The picture has the signature first, the text puts last.
|
||||
|
||||
- A buffer size of 4K is small and probably not performant.
|
||||
Buffering makes the code harder to update and correct, vs.
|
||||
reading the entire file into memory.
|
||||
|
||||
- It does not validate NumberOfRvasAndSizes field.
|
||||
Usually it is valid.
|
||||
|
||||
- It is missing a number of other validations.
|
||||
For example, the optional header magic was ignored, so in the
|
||||
interest of small change, we treat all non-0x20B values the same.
|
||||
|
||||
Mail with Microsoft confirms the documents do not agree, and that the PE document
|
||||
is outdated and/or incorrect and/or referring to no longer supported v1 Authenticode,
|
||||
and that the intent is for the signature to be at the end, per the text and not the picture.
|
||||
And that data past the sections is to be hashed -- there rarely is any.
|
||||
|
||||
The plan is therefore:
|
||||
read the entire file into memory
|
||||
add missing validation
|
||||
hash, excluding checksum, security directory, and security content
|
||||
place security content at the end, replacing what is there if anything
|
||||
remove the symbol code (here and in formatter)
|
||||
expose more offsets from here to cleanup the formatter code
|
||||
|
||||
There is also no unit tests for this code it seems.
|
||||
*/
|
||||
|
||||
namespace Mono.Security.Authenticode {
|
||||
|
||||
// References:
|
||||
@ -64,9 +122,20 @@ namespace Mono.Security.Authenticode {
|
||||
private int dirSecurityOffset;
|
||||
private int dirSecuritySize;
|
||||
private int coffSymbolTableOffset;
|
||||
private bool pe64;
|
||||
|
||||
internal bool PE64 {
|
||||
get {
|
||||
if (blockNo < 1)
|
||||
ReadFirstBlock ();
|
||||
return pe64;
|
||||
}
|
||||
}
|
||||
|
||||
public AuthenticodeBase ()
|
||||
{
|
||||
// FIXME Read the entire file into memory.
|
||||
// See earlier comments.
|
||||
fileblock = new byte [4096];
|
||||
}
|
||||
|
||||
@ -142,11 +211,17 @@ namespace Mono.Security.Authenticode {
|
||||
peOffset = BitConverterLE.ToInt32 (fileblock, 60);
|
||||
if (peOffset > fileblock.Length) {
|
||||
// just in case (0.1%) this can actually happen
|
||||
// FIXME This does not mean the file is invalid,
|
||||
// just that this code cannot handle it.
|
||||
// FIXME Read the entire file into memory.
|
||||
// See earlier comments.
|
||||
string msg = String.Format (Locale.GetText (
|
||||
"Header size too big (> {0} bytes)."),
|
||||
fileblock.Length);
|
||||
throw new NotSupportedException (msg);
|
||||
}
|
||||
// FIXME This verifies that PE starts within the file,
|
||||
// but not that it fits.
|
||||
if (peOffset > fs.Length)
|
||||
return 4;
|
||||
|
||||
@ -156,12 +231,33 @@ namespace Mono.Security.Authenticode {
|
||||
if (BitConverterLE.ToUInt32 (fileblock, peOffset) != 0x4550)
|
||||
return 5;
|
||||
|
||||
// 2.2. Locate IMAGE_DIRECTORY_ENTRY_SECURITY (offset and size)
|
||||
dirSecurityOffset = BitConverterLE.ToInt32 (fileblock, peOffset + 152);
|
||||
dirSecuritySize = BitConverterLE.ToInt32 (fileblock, peOffset + 156);
|
||||
// PE signature is followed by 20 byte file header, and
|
||||
// then 2 byte magic 0x10B for PE32 or 0x20B for PE32+,
|
||||
// or 0x107 for the obscure ROM case.
|
||||
// FIXME The code historically ignored this magic value
|
||||
// entirely, so we only treat 0x20B differently to maintain
|
||||
// this dubious behavior.
|
||||
// FIXME The code also lacks range checks in a number of places,
|
||||
// and will access arrays out of bounds for valid files.
|
||||
|
||||
// COFF symbol tables are deprecated - we'll strip them if we see them!
|
||||
// (otherwise the signature won't work on MS and we don't want to support COFF for that)
|
||||
ushort magic = BitConverterLE.ToUInt16 (fileblock, peOffset + 24);
|
||||
const int IMAGE_NT_OPTIONAL_HDR64_MAGIC = 0x20B;
|
||||
pe64 = magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC;
|
||||
|
||||
// FIXME This fails to validate NumberOfRvasAndSizes.
|
||||
// 2.2. Locate IMAGE_DIRECTORY_ENTRY_SECURITY (offset and size)
|
||||
// These offsets are from the documentation, but add 24 for
|
||||
// PE signature and file header.
|
||||
if (pe64) {
|
||||
dirSecurityOffset = BitConverterLE.ToInt32 (fileblock, peOffset + 168);
|
||||
dirSecuritySize = BitConverterLE.ToInt32 (fileblock, peOffset + 168 + 4);
|
||||
}
|
||||
else {
|
||||
dirSecurityOffset = BitConverterLE.ToInt32 (fileblock, peOffset + 152);
|
||||
dirSecuritySize = BitConverterLE.ToInt32 (fileblock, peOffset + 156);
|
||||
}
|
||||
|
||||
// FIXME Remove this code and the dependency on it.
|
||||
coffSymbolTableOffset = BitConverterLE.ToInt32 (fileblock, peOffset + 12);
|
||||
|
||||
return 0;
|
||||
@ -229,14 +325,38 @@ namespace Mono.Security.Authenticode {
|
||||
|
||||
// Authenticode(r) gymnastics
|
||||
// Hash from (generally) 0 to 215 (216 bytes)
|
||||
// 88 = 64 + 24
|
||||
// 64 is the offset of Checksum within OptionalHeader.
|
||||
// 24 is offset of OptionalHeader within PEHeader.
|
||||
int pe = peOffset + 88;
|
||||
hash.TransformBlock (fileblock, 0, pe, fileblock, 0);
|
||||
// then skip 4 for checksum
|
||||
pe += 4;
|
||||
// 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;
|
||||
|
||||
if (pe64) {
|
||||
// security_directory, if present, is at offset 144 within OptionalHeader64
|
||||
// FIXME This code fails to check if the security_directory is present.
|
||||
// If it is absent, it may or may not be difficult to add, and reject
|
||||
// the file as valid but unsignable.
|
||||
// Checksum is at [64, 68].
|
||||
// 144 - 68 = 76
|
||||
// Hash from checksum to security_directory.
|
||||
hash.TransformBlock (fileblock, pe, 76, fileblock, pe);
|
||||
// then skip 8 bytes for IMAGE_DIRECTORY_ENTRY_SECURITY
|
||||
pe += 76 + 8;
|
||||
}
|
||||
else {
|
||||
// security_directory, if present, is at offset 128 within OptionalHeader32
|
||||
// FIXME This code fails to check if the security_directory is present.
|
||||
// If it is absent, it may or may not be difficult to add, and reject
|
||||
// the file as valid but unsignable.
|
||||
// Checksum is at [64, 68].
|
||||
// 128 - 68 = 60
|
||||
// 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;
|
||||
}
|
||||
|
||||
// everything is present so start the hashing
|
||||
if (n == 0) {
|
||||
|
@ -148,19 +148,19 @@ namespace Mono.Security.Authenticode {
|
||||
}
|
||||
|
||||
// pkcs 1
|
||||
// private const string rsaEncryption = "1.2.840.113549.1.1.1";
|
||||
// private const string rsaEncryption = "1.2.840.113549.1.1.1";
|
||||
// pkcs 7
|
||||
// private const string data = "1.2.840.113549.1.7.1";
|
||||
// private const string data = "1.2.840.113549.1.7.1";
|
||||
private const string signedData = "1.2.840.113549.1.7.2";
|
||||
// pkcs 9
|
||||
// private const string contentType = "1.2.840.113549.1.9.3";
|
||||
// private const string messageDigest = "1.2.840.113549.1.9.4";
|
||||
// private const string contentType = "1.2.840.113549.1.9.3";
|
||||
// private const string messageDigest = "1.2.840.113549.1.9.4";
|
||||
private const string countersignature = "1.2.840.113549.1.9.6";
|
||||
// microsoft spc (software publisher certificate)
|
||||
private const string spcStatementType = "1.3.6.1.4.1.311.2.1.11";
|
||||
private const string spcSpOpusInfo = "1.3.6.1.4.1.311.2.1.12";
|
||||
private const string spcPelmageData = "1.3.6.1.4.1.311.2.1.15";
|
||||
// private const string individualCodeSigning = "1.3.6.1.4.1.311.2.1.21";
|
||||
// private const string individualCodeSigning = "1.3.6.1.4.1.311.2.1.21";
|
||||
private const string commercialCodeSigning = "1.3.6.1.4.1.311.2.1.22";
|
||||
private const string timestampCountersignature = "1.3.6.1.4.1.311.3.2.1";
|
||||
|
||||
@ -192,8 +192,8 @@ namespace Mono.Security.Authenticode {
|
||||
else
|
||||
opus = Attribute (spcSpOpusInfo, Opus (description, url.ToString ()));
|
||||
pkcs7.SignerInfo.AuthenticatedAttributes.Add (opus);
|
||||
// When using the MS Root Agency (test) we can't include this attribute in the signature or it won't validate!
|
||||
// pkcs7.SignerInfo.AuthenticatedAttributes.Add (Attribute (spcStatementType, new ASN1 (0x30, ASN1Convert.FromOid (commercialCodeSigning).GetBytes ())));
|
||||
// When using the MS Root Agency (test) we can't include this attribute in the signature or it won't validate!
|
||||
// pkcs7.SignerInfo.AuthenticatedAttributes.Add (Attribute (spcStatementType, new ASN1 (0x30, ASN1Convert.FromOid (commercialCodeSigning).GetBytes ())));
|
||||
pkcs7.GetASN1 (); // sign
|
||||
return pkcs7.SignerInfo.Signature;
|
||||
}
|
||||
@ -243,9 +243,14 @@ namespace Mono.Security.Authenticode {
|
||||
using (FileStream fs = File.Open (fileName, FileMode.Open, FileAccess.ReadWrite)) {
|
||||
int filesize;
|
||||
if (SecurityOffset > 0) {
|
||||
// FIXME Does it fit? Is it always the same size?
|
||||
// file was already signed, we'll reuse the position for the updated signature
|
||||
filesize = SecurityOffset;
|
||||
} else if (CoffSymbolTableOffset > 0) {
|
||||
// FIXME This is not documented as something to remove.
|
||||
// However some documentation says to remove after the last
|
||||
// section, and some does not, and this might be there,
|
||||
// or it might not.
|
||||
// strip (deprecated) COFF symbol table
|
||||
fs.Seek (PEOffset + 12, SeekOrigin.Begin);
|
||||
for (int i = 0; i < 8; i++)
|
||||
@ -263,14 +268,22 @@ namespace Mono.Security.Authenticode {
|
||||
|
||||
// IMAGE_DIRECTORY_ENTRY_SECURITY (offset, size)
|
||||
byte[] data = BitConverterLE.GetBytes (filesize + addsize);
|
||||
fs.Seek (PEOffset + 152, SeekOrigin.Begin);
|
||||
if (PE64)
|
||||
fs.Seek (PEOffset + 168, SeekOrigin.Begin);
|
||||
else
|
||||
fs.Seek (PEOffset + 152, SeekOrigin.Begin);
|
||||
|
||||
fs.Write (data, 0, 4);
|
||||
int size = asn.Length + 8;
|
||||
int addsize_signature = (size & 7);
|
||||
if (addsize_signature > 0)
|
||||
addsize_signature = 8 - addsize_signature;
|
||||
data = BitConverterLE.GetBytes (size + addsize_signature);
|
||||
fs.Seek (PEOffset + 156, SeekOrigin.Begin);
|
||||
if (PE64)
|
||||
fs.Seek (PEOffset + 168 + 4, SeekOrigin.Begin);
|
||||
else
|
||||
fs.Seek (PEOffset + 156, SeekOrigin.Begin);
|
||||
|
||||
fs.Write (data, 0, 4);
|
||||
fs.Seek (filesize, SeekOrigin.Begin);
|
||||
// align certificate entry to a multiple of 8 bytes
|
||||
@ -278,8 +291,40 @@ namespace Mono.Security.Authenticode {
|
||||
byte[] fillup = new byte[addsize];
|
||||
fs.Write (fillup, 0, fillup.Length);
|
||||
}
|
||||
|
||||
/*
|
||||
https://download.microsoft.com/download/9/c/5/9c5b2167-8017-4bae-9fde-d599bac8184a/Authenticode_PE.docx
|
||||
The Authenticode signature is in a WIN_CERTIFICATE structure, which is declared in Wintrust.h as follows:
|
||||
typedef struct _WIN_CERTIFICATE
|
||||
{
|
||||
DWORD dwLength;
|
||||
WORD wRevision;
|
||||
WORD wCertificateType;
|
||||
BYTE bCertificate[ANYSIZE_ARRAY];
|
||||
} WIN_CERTIFICATE, *LPWIN_CERTIFICATE;
|
||||
|
||||
The fields in WIN_CERTIFICATE are set to the following values:
|
||||
dwLength is set to the length of bCertificate.
|
||||
wRevision is set to the WIN_CERTIFICATE version number.
|
||||
|
||||
wCertificateType is set to 0x0002 for Authenticode signatures.
|
||||
This value is defined in Wintrust.h as WIN_CERT_TYPE_PKCS_SIGNED_DATA.
|
||||
bCertificate is set to a variable-length binary array that contains the Authenticode PKCS #7 signedData.
|
||||
The PKCS #7 integrity is verified as described in ”PKCS #7: Cryptographic Message Syntax Standard.”
|
||||
*/
|
||||
// write WIN_CERTIFICATE.dwLength
|
||||
fs.Write (data, 0, data.Length); // length (again)
|
||||
data = BitConverterLE.GetBytes (0x00020200); // magic
|
||||
// write WIN_CERTIFICATE.wRevision = 0x0200 and wCertificateType = 2.
|
||||
// /usr/local/Cellar/mingw-w64/5.0.3/toolchain-x86_64/x86_64-w64-mingw32/include/wintrust.h
|
||||
// const short WIN_CERT_REVISION_1_0 = 0x0100;
|
||||
const short WIN_CERT_REVISION_2_0 = 0x0200;
|
||||
// const short WIN_CERT_TYPE_X509 = 0x0001;
|
||||
const short WIN_CERT_TYPE_PKCS_SIGNED_DATA = 0x0002;
|
||||
// const short WIN_CERT_TYPE_RESERVED_1 = 0x0003;
|
||||
// const short WIN_CERT_TYPE_TS_STACK_SIGNED = 0x0004;
|
||||
data = BitConverterLE.GetBytes (WIN_CERT_REVISION_2_0);
|
||||
fs.Write (data, 0, data.Length);
|
||||
data = BitConverterLE.GetBytes (WIN_CERT_TYPE_PKCS_SIGNED_DATA);
|
||||
fs.Write (data, 0, data.Length);
|
||||
fs.Write (asn, 0, asn.Length);
|
||||
if (addsize_signature > 0) {
|
||||
|
Reference in New Issue
Block a user