2 // AuthenticodeFormatter.cs: Authenticode signature generator
5 // Sebastien Pouliot <sebastien@ximian.com>
7 // (C) 2003 Motus Technologies Inc. (http://www.motus.com)
8 // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
10 // Permission is hereby granted, free of charge, to any person obtaining
11 // a copy of this software and associated documentation files (the
12 // "Software"), to deal in the Software without restriction, including
13 // without limitation the rights to use, copy, modify, merge, publish,
14 // distribute, sublicense, and/or sell copies of the Software, and to
15 // permit persons to whom the Software is furnished to do so, subject to
16 // the following conditions:
18 // The above copyright notice and this permission notice shall be
19 // included in all copies or substantial portions of the Software.
21 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
22 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
23 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
24 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
25 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
26 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
27 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
31 using System
.Collections
;
32 using System
.Globalization
;
34 using System
.Security
.Cryptography
;
39 using Mono
.Security
.X509
;
41 namespace Mono
.Security
.Authenticode
{
43 public class AuthenticodeFormatter
: AuthenticodeBase
{
45 private Authority authority
;
46 private X509CertificateCollection certs
;
47 private ArrayList crls
;
50 private Uri timestamp
;
51 private ASN1 authenticode
;
52 private PKCS7
.SignedData pkcs7
;
53 private string description
;
55 private byte [] entry
;
57 public AuthenticodeFormatter () : base ()
59 certs
= new X509CertificateCollection ();
60 crls
= new ArrayList ();
61 authority
= Authority
.Maximum
;
62 pkcs7
= new PKCS7
.SignedData ();
65 public Authority Authority
{
66 get { return authority; }
67 set { authority = value; }
70 public X509CertificateCollection Certificates
{
74 public ArrayList Crl
{
86 throw new ArgumentNullException ("Hash");
88 string h
= value.ToUpper (CultureInfo
.InvariantCulture
);
95 throw new ArgumentException ("Invalid Authenticode hash algorithm");
105 public Uri TimestampUrl
{
106 get { return timestamp; }
107 set { timestamp = value; }
110 public string Description
{
111 get { return description; }
112 set { description = value; }
120 private ASN1
AlgorithmIdentifier (string oid
)
122 ASN1 ai
= new ASN1 (0x30);
123 ai
.Add (ASN1Convert
.FromOid (oid
));
124 ai
.Add (new ASN1 (0x05)); // NULL
128 private ASN1
Attribute (string oid
, ASN1
value)
130 ASN1 attr
= new ASN1 (0x30);
131 attr
.Add (ASN1Convert
.FromOid (oid
));
132 ASN1 aset
= attr
.Add (new ASN1 (0x31));
137 private ASN1
Opus (string description
, string url
)
139 ASN1 opus
= new ASN1 (0x30);
140 if (description
!= null) {
141 ASN1 part1
= opus
.Add (new ASN1 (0xA0));
142 part1
.Add (new ASN1 (0x80, Encoding
.BigEndianUnicode
.GetBytes (description
)));
145 ASN1 part2
= opus
.Add (new ASN1 (0xA1));
146 part2
.Add (new ASN1 (0x80, Encoding
.ASCII
.GetBytes (url
)));
152 private const string rsaEncryption
= "1.2.840.113549.1.1.1";
154 private const string data
= "1.2.840.113549.1.7.1";
155 private const string signedData
= "1.2.840.113549.1.7.2";
157 private const string contentType
= "1.2.840.113549.1.9.3";
158 private const string messageDigest
= "1.2.840.113549.1.9.4";
159 private const string countersignature
= "1.2.840.113549.1.9.6";
160 // microsoft spc (software publisher certificate)
161 private const string spcStatementType
= "1.3.6.1.4.1.311.2.1.11";
162 private const string spcSpOpusInfo
= "1.3.6.1.4.1.311.2.1.12";
163 private const string spcPelmageData
= "1.3.6.1.4.1.311.2.1.15";
164 private const string individualCodeSigning
= "1.3.6.1.4.1.311.2.1.21";
165 private const string commercialCodeSigning
= "1.3.6.1.4.1.311.2.1.22";
166 private const string timestampCountersignature
= "1.3.6.1.4.1.311.3.2.1";
168 //private static byte[] version = { 0x01 };
169 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 }
;
171 private byte[] Header (byte[] fileHash
, string hashAlgorithm
)
173 string hashOid
= CryptoConfig
.MapNameToOID (hashAlgorithm
);
174 ASN1 content
= new ASN1 (0x30);
175 ASN1 c1
= content
.Add (new ASN1 (0x30));
176 c1
.Add (ASN1Convert
.FromOid (spcPelmageData
));
177 c1
.Add (new ASN1 (0x30, obsolete
));
178 ASN1 c2
= content
.Add (new ASN1 (0x30));
179 c2
.Add (AlgorithmIdentifier (hashOid
));
180 c2
.Add (new ASN1 (0x04, fileHash
));
182 pkcs7
.HashName
= hashAlgorithm
;
183 pkcs7
.Certificates
.AddRange (certs
);
184 pkcs7
.ContentInfo
.ContentType
= spcIndirectDataContext
;
185 pkcs7
.ContentInfo
.Content
.Add (content
);
187 pkcs7
.SignerInfo
.Certificate
= certs
[0];
188 pkcs7
.SignerInfo
.Key
= rsa
;
192 opus
= Attribute (spcSpOpusInfo
, Opus (description
, null));
194 opus
= Attribute (spcSpOpusInfo
, Opus (description
, url
.ToString ()));
195 pkcs7
.SignerInfo
.AuthenticatedAttributes
.Add (opus
);
196 pkcs7
.SignerInfo
.AuthenticatedAttributes
.Add (Attribute (spcStatementType
, new ASN1 (0x30, ASN1Convert
.FromOid (commercialCodeSigning
).GetBytes ())));
197 pkcs7
.GetASN1 (); // sign
198 return pkcs7
.SignerInfo
.Signature
;
201 public ASN1
TimestampRequest (byte[] signature
)
203 PKCS7
.ContentInfo ci
= new PKCS7
.ContentInfo (PKCS7
.Oid
.data
);
204 ci
.Content
.Add (new ASN1 (0x04, signature
));
205 return PKCS7
.AlgorithmIdentifier (timestampCountersignature
, ci
.ASN1
);
208 public void ProcessTimestamp (byte[] response
)
210 ASN1 ts
= new ASN1 (Convert
.FromBase64String (Encoding
.ASCII
.GetString (response
)));
211 // first validate the received message
214 // add the supplied certificates inside our signature
215 for (int i
=0; i
< ts
[1][0][3].Count
; i
++)
216 pkcs7
.Certificates
.Add (new X509Certificate (ts
[1][0][3][i
].GetBytes ()));
218 // add an unauthentified attribute to our signature
219 pkcs7
.SignerInfo
.UnauthenticatedAttributes
.Add (Attribute (countersignature
, ts
[1][0][4][0]));
222 public bool Sign (string fileName
)
224 string hashAlgorithm
= "MD5";
227 using (FileStream fs
= new FileStream (fileName
, FileMode
.Open
, FileAccess
.Read
, FileShare
.Read
)) {
228 file
= new byte [fs
.Length
];
229 fs
.Read (file
, 0, file
.Length
);
234 if (BitConverterLE
.ToUInt16 (file
, 0) != 0x5A4D)
237 // find offset of PE header
238 int peOffset
= BitConverterLE
.ToInt32 (file
, 60);
239 if (peOffset
> file
.Length
)
243 if (BitConverterLE
.ToUInt16 (file
, peOffset
) != 0x4550)
246 // IMAGE_DIRECTORY_ENTRY_SECURITY
247 int dirSecurityOffset
= BitConverterLE
.ToInt32 (file
, peOffset
+ 152);
248 int dirSecuritySize
= BitConverterLE
.ToInt32 (file
, peOffset
+ 156);
250 if (dirSecuritySize
> 8) {
251 entry
= new byte [dirSecuritySize
- 8];
252 Buffer
.BlockCopy (file
, dirSecurityOffset
+ 8, entry
, 0, entry
.Length
);
257 HashAlgorithm hash
= HashAlgorithm
.Create (hashAlgorithm
);
258 // 0 to 215 (216) then skip 4 (checksum)
259 int pe
= peOffset
+ 88;
260 hash
.TransformBlock (file
, 0, pe
, file
, 0);
262 // 220 to 279 (60) then skip 8 (IMAGE_DIRECTORY_ENTRY_SECURITY)
263 hash
.TransformBlock (file
, pe
, 60, file
, pe
);
265 // 288 to end of file
266 int n
= file
.Length
- pe
;
267 // minus any authenticode signature (with 8 bytes header)
268 if (dirSecurityOffset
!= 0)
269 n
-= (dirSecuritySize
);
270 hash
.TransformFinalBlock (file
, pe
, n
);
273 byte[] signature
= Header (hash
.Hash
, hashAlgorithm
);
274 if (timestamp
!= null) {
275 ASN1 tsreq
= TimestampRequest (signature
);
276 WebClient wc
= new WebClient ();
277 wc
.Headers
.Add ("Content-Type", "application/octet-stream");
278 wc
.Headers
.Add ("Accept", "application/octet-stream");
279 byte[] tsdata
= Encoding
.ASCII
.GetBytes (Convert
.ToBase64String (tsreq
.GetBytes ()));
280 byte[] tsres
= wc
.UploadData (timestamp
.ToString (), tsdata
);
281 ProcessTimestamp (tsres
);
283 PKCS7
.ContentInfo sign
= new PKCS7
.ContentInfo (signedData
);
284 sign
.Content
.Add (pkcs7
.ASN1
);
285 authenticode
= sign
.ASN1
;
287 byte[] asn
= authenticode
.GetBytes ();
289 using (FileStream fs
= File
.Open (fileName
+ ".sig", FileMode
.Create
, FileAccess
.Write
)) {
290 fs
.Write (asn
, 0, asn
.Length
);
294 // someday I may be sure enough to move this into DEBUG ;-)
295 File
.Copy (fileName
, fileName
+ ".bak", true);
297 using (FileStream fs
= File
.Open (fileName
, FileMode
.Create
, FileAccess
.Write
)) {
298 int filesize
= (dirSecurityOffset
== 0) ? file
.Length
: dirSecurityOffset
;
299 // IMAGE_DIRECTORY_ENTRY_SECURITY (offset, size)
300 byte[] data
= BitConverterLE
.GetBytes (filesize
);
301 file
[peOffset
+ 152] = data
[0];
302 file
[peOffset
+ 153] = data
[1];
303 file
[peOffset
+ 154] = data
[2];
304 file
[peOffset
+ 155] = data
[3];
305 int size
= asn
.Length
+ 8;
306 // must be a multiple of 8 bytes
307 int addsize
= (size
% 8);
309 addsize
= 8 - addsize
;
311 data
= BitConverterLE
.GetBytes (size
); // header
312 file
[peOffset
+ 156] = data
[0];
313 file
[peOffset
+ 157] = data
[1];
314 file
[peOffset
+ 158] = data
[2];
315 file
[peOffset
+ 159] = data
[3];
316 fs
.Write (file
, 0, filesize
);
317 fs
.Write (data
, 0, data
.Length
); // length (again)
318 data
= BitConverterLE
.GetBytes (0x00020200); // magic
319 fs
.Write (data
, 0, data
.Length
);
320 fs
.Write (asn
, 0, asn
.Length
);
322 byte[] fillup
= new byte [addsize
];
323 fs
.Write (fillup
, 0, fillup
.Length
);
329 // in case we just want to timestamp the file
330 public bool Timestamp (string fileName
)