// Sebastien Pouliot <sebastien@ximian.com>
//
// (C) 2003 Motus Technologies Inc. (http://www.motus.com)
-// Copyright (C) 2004 Novell, Inc (http://www.novell.com)
+// Copyright (C) 2004, 2006-2007 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
private PKCS7.SignedData pkcs7;
private string description;
private Uri url;
- private byte [] entry;
public AuthenticodeFormatter () : base ()
{
public string Hash {
get {
if (hash == null)
- hash = "MD5";
+ hash = "SHA1";
return hash;
}
set {
}
// 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";
- private static byte[] version = { 0x01 };
+ //private static byte[] version = { 0x01 };
private static byte[] obsolete = { 0x03, 0x01, 0x00, 0xA0, 0x20, 0xA2, 0x1E, 0x80, 0x1C, 0x00, 0x3C, 0x00, 0x3C, 0x00, 0x3C, 0x00, 0x4F, 0x00, 0x62, 0x00, 0x73, 0x00, 0x6F, 0x00, 0x6C, 0x00, 0x65, 0x00, 0x74, 0x00, 0x65, 0x00, 0x3E, 0x00, 0x3E, 0x00, 0x3E };
private byte[] Header (byte[] fileHash, string hashAlgorithm)
ASN1 opus = null;
if (url == null)
- Attribute (spcSpOpusInfo, Opus (description, null));
+ opus = Attribute (spcSpOpusInfo, Opus (description, null));
else
- Attribute (spcSpOpusInfo, Opus (description, url.ToString ()));
+ opus = Attribute (spcSpOpusInfo, Opus (description, url.ToString ()));
pkcs7.SignerInfo.AuthenticatedAttributes.Add (opus);
-
- pkcs7.SignerInfo.AuthenticatedAttributes.Add (Attribute (contentType, ASN1Convert.FromOid (spcIndirectDataContext)));
- pkcs7.SignerInfo.AuthenticatedAttributes.Add (Attribute (spcStatementType, new ASN1 (0x30, ASN1Convert.FromOid (commercialCodeSigning).GetBytes ())));
-
- ASN1 temp = pkcs7.ASN1; // sign
+// 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;
}
pkcs7.SignerInfo.UnauthenticatedAttributes.Add (Attribute (countersignature, ts[1][0][4][0]));
}
- public bool Sign (string fileName)
+ private byte[] Timestamp (byte[] signature)
{
- string hashAlgorithm = "MD5";
- 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 ();
-
- // MZ - DOS header
- if (BitConverter.ToUInt16 (file, 0) != 0x5A4D)
- return false;
-
- // find offset of PE header
- int peOffset = BitConverter.ToInt32 (file, 60);
- if (peOffset > file.Length)
- return false;
-
- // PE - NT header
- if (BitConverter.ToUInt16 (file, peOffset) != 0x4550)
- return false;
-
- // IMAGE_DIRECTORY_ENTRY_SECURITY
- int dirSecurityOffset = BitConverter.ToInt32 (file, peOffset + 152);
- int dirSecuritySize = BitConverter.ToInt32 (file, peOffset + 156);
-
- if (dirSecuritySize > 8) {
- entry = new byte [dirSecuritySize - 8];
- Buffer.BlockCopy (file, dirSecurityOffset + 8, entry, 0, entry.Length);
- }
- else
- entry = null;
-
- HashAlgorithm hash = HashAlgorithm.Create (hashAlgorithm);
- // 0 to 215 (216) then skip 4 (checksum)
- int pe = peOffset + 88;
- hash.TransformBlock (file, 0, pe, file, 0);
- pe += 4;
- // 220 to 279 (60) then skip 8 (IMAGE_DIRECTORY_ENTRY_SECURITY)
- hash.TransformBlock (file, pe, 60, file, pe);
- 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);
-
- //
- byte[] signature = Header (hash.Hash, hashAlgorithm);
- if (timestamp != null) {
- ASN1 tsreq = TimestampRequest (signature);
- WebClient wc = new WebClient ();
- wc.Headers.Add ("Content-Type", "application/octet-stream");
- wc.Headers.Add ("Accept", "application/octet-stream");
- byte[] tsdata = Encoding.ASCII.GetBytes (Convert.ToBase64String (tsreq.GetBytes ()));
- byte[] tsres = wc.UploadData (timestamp.ToString (), tsdata);
- ProcessTimestamp (tsres);
+ ASN1 tsreq = TimestampRequest (signature);
+ WebClient wc = new WebClient ();
+ wc.Headers.Add ("Content-Type", "application/octet-stream");
+ wc.Headers.Add ("Accept", "application/octet-stream");
+ byte[] tsdata = Encoding.ASCII.GetBytes (Convert.ToBase64String (tsreq.GetBytes ()));
+ return wc.UploadData (timestamp.ToString (), tsdata);
+ }
+
+ private bool Save (string fileName, byte[] asn)
+ {
+#if DEBUG
+ using (FileStream fs = File.Open (fileName + ".sig", FileMode.Create, FileAccess.Write)) {
+ fs.Write (asn, 0, asn.Length);
+ fs.Close ();
}
- PKCS7.ContentInfo sign = new PKCS7.ContentInfo (signedData);
- sign.Content.Add (pkcs7.ASN1);
- authenticode = sign.ASN1;
+#endif
+ // someday I may be sure enough to move this into DEBUG ;-)
+ File.Copy (fileName, fileName + ".bak", true);
- // debug
- fs = File.Open (fileName + ".sig", FileMode.Create, FileAccess.Write);
- byte[] asn = authenticode.GetBytes ();
- fs.Write (asn, 0, asn.Length);
- fs.Close ();
+ using (FileStream fs = File.Open (fileName, FileMode.Open, FileAccess.ReadWrite)) {
+ int filesize;
+ if (SecurityOffset > 0) {
+ // file was already signed, we'll reuse the position for the updated signature
+ filesize = SecurityOffset;
+ } else if (CoffSymbolTableOffset > 0) {
+ // strip (deprecated) COFF symbol table
+ fs.Seek (PEOffset + 12, SeekOrigin.Begin);
+ for (int i = 0; i < 8; i++)
+ fs.WriteByte (0);
+ // we'll put the Authenticode signature at this same place (just after the last section)
+ filesize = CoffSymbolTableOffset;
+ } else {
+ // file was never signed, nor does it contains (deprecated) COFF symbols
+ filesize = (int)fs.Length;
+ }
+ // must be a multiple of 8 bytes
+ int addsize = (filesize & 7);
+ if (addsize > 0)
+ addsize = 8 - addsize;
+
+ // IMAGE_DIRECTORY_ENTRY_SECURITY (offset, size)
+ byte[] data = BitConverterLE.GetBytes (filesize + addsize);
+ 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);
+ fs.Write (data, 0, 4);
+ fs.Seek (filesize, SeekOrigin.Begin);
+ // align certificate entry to a multiple of 8 bytes
+ if (addsize > 0) {
+ byte[] fillup = new byte[addsize];
+ fs.Write (fillup, 0, fillup.Length);
+ }
+ fs.Write (data, 0, data.Length); // length (again)
+ data = BitConverterLE.GetBytes (0x00020200); // magic
+ fs.Write (data, 0, data.Length);
+ fs.Write (asn, 0, asn.Length);
+ if (addsize_signature > 0) {
+ byte[] fillup = new byte[addsize_signature];
+ fs.Write (fillup, 0, fillup.Length);
+ }
+ fs.Close ();
+ }
+ return true;
+ }
- File.Copy (fileName, fileName + ".bak", true);
+ public bool Sign (string fileName)
+ {
+ try {
+ Open (fileName);
+
+ HashAlgorithm hash = HashAlgorithm.Create (Hash);
+ // 0 to 215 (216) then skip 4 (checksum)
+
+ byte[] digest = GetHash (hash);
+ byte[] signature = Header (digest, Hash);
+ if (timestamp != null) {
+ byte[] ts = Timestamp (signature);
+ // add timestamp information inside the current pkcs7 SignedData instance
+ // (this is possible because the data isn't yet signed)
+ ProcessTimestamp (ts);
+ }
- fs = File.Open (fileName, FileMode.Create, FileAccess.Write);
- // IMAGE_DIRECTORY_ENTRY_SECURITY (offset, size)
- byte[] data = BitConverter.GetBytes (file.Length);
- file [peOffset + 152] = data [0];
- file [peOffset + 153] = data [1];
- file [peOffset + 154] = data [2];
- file [peOffset + 155] = data [3];
- int size = asn.Length + 8;
- // must be a multiple of 8 bytes
- int addsize = (size % 8);
- if (addsize > 0)
- addsize = 8 - addsize;
- size += addsize;
- data = BitConverter.GetBytes (size); // header
- file [peOffset + 156] = data [0];
- file [peOffset + 157] = data [1];
- file [peOffset + 158] = data [2];
- file [peOffset + 159] = data [3];
- fs.Write (file, 0, file.Length);
- fs.Write (data, 0, data.Length); // length (again)
- data = BitConverter.GetBytes (0x00020200); // magic
- fs.Write (data, 0, data.Length);
- fs.Write (asn, 0, asn.Length);
- // fill up
- byte[] fillup = new byte [addsize];
- fs.Write (fillup, 0, fillup.Length);
- fs.Close ();
+ PKCS7.ContentInfo sign = new PKCS7.ContentInfo (signedData);
+ sign.Content.Add (pkcs7.ASN1);
+ authenticode = sign.ASN1;
+ Close ();
- return true;
+ return Save (fileName, authenticode.GetBytes ());
+ }
+ catch (Exception e) {
+ Console.WriteLine (e);
+ }
+ return false;
}
// in case we just want to timestamp the file
public bool Timestamp (string fileName)
{
- return true;
+ try {
+ AuthenticodeDeformatter def = new AuthenticodeDeformatter (fileName);
+ byte[] signature = def.Signature;
+ if (signature != null) {
+ Open (fileName);
+ PKCS7.ContentInfo ci = new PKCS7.ContentInfo (signature);
+ pkcs7 = new PKCS7.SignedData (ci.Content);
+
+ byte[] response = Timestamp (pkcs7.SignerInfo.Signature);
+ ASN1 ts = new ASN1 (Convert.FromBase64String (Encoding.ASCII.GetString (response)));
+ // insert new certificates and countersignature into the original signature
+ ASN1 asn = new ASN1 (signature);
+ ASN1 content = asn.Element (1, 0xA0);
+ if (content == null)
+ return false;
+
+ ASN1 signedData = content.Element (0, 0x30);
+ if (signedData == null)
+ return false;
+
+ // add the supplied certificates inside our signature
+ ASN1 certificates = signedData.Element (3, 0xA0);
+ if (certificates == null) {
+ certificates = new ASN1 (0xA0);
+ signedData.Add (certificates);
+ }
+ for (int i = 0; i < ts[1][0][3].Count; i++) {
+ certificates.Add (ts[1][0][3][i]);
+ }
+
+ // add an unauthentified attribute to our signature
+ ASN1 signerInfoSet = signedData[signedData.Count - 1];
+ ASN1 signerInfo = signerInfoSet[0];
+ ASN1 unauthenticated = signerInfo[signerInfo.Count - 1];
+ if (unauthenticated.Tag != 0xA1) {
+ unauthenticated = new ASN1 (0xA1);
+ signerInfo.Add (unauthenticated);
+ }
+ unauthenticated.Add (Attribute (countersignature, ts[1][0][4][0]));
+
+ return Save (fileName, asn.GetBytes ());
+ }
+ }
+ catch (Exception e) {
+ Console.WriteLine (e);
+ }
+ return false;
}
}
}