2 // AuthenticodeDeformatter.cs: Authenticode signature validator
5 // Sebastien Pouliot <sebastien@ximian.com>
7 // (C) 2003 Motus Technologies Inc. (http://www.motus.com)
8 // Copyright (C) 2004-2006 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.
30 #if !NET_2_1 || MONOTOUCH
34 using System
.Runtime
.InteropServices
;
35 using System
.Security
;
36 using System
.Security
.Cryptography
;
38 using Mono
.Security
.Cryptography
;
39 using Mono
.Security
.X509
;
41 namespace Mono
.Security
.Authenticode
{
44 // a. http://www.cs.auckland.ac.nz/~pgut001/pubs/authenticode.txt
51 class AuthenticodeDeformatter
: AuthenticodeBase
{
53 private string filename
;
55 private X509CertificateCollection coll
;
56 private ASN1 signedHash
;
57 private DateTime timestamp
;
58 private X509Certificate signingCertificate
;
60 private bool trustedRoot
;
61 private bool trustedTimestampRoot
;
64 private X509Chain signerChain
;
65 private X509Chain timestampChain
;
67 public AuthenticodeDeformatter () : base ()
70 signerChain
= new X509Chain ();
71 timestampChain
= new X509Chain ();
74 public AuthenticodeDeformatter (string fileName
) : this ()
79 public string FileName
{
80 get { return filename; }
84 CheckSignature (value);
86 catch (SecurityException
) {
97 if (signedHash
== null)
99 return (byte[]) signedHash
.Value
.Clone ();
111 public bool IsTrusted ()
118 if (signingCertificate
== null) {
123 if ((signerChain
.Root
== null) || !trustedRoot
) {
128 if (timestamp
!= DateTime
.MinValue
) {
129 if ((timestampChain
.Root
== null) || !trustedTimestampRoot
) {
134 // check that file was timestamped when certificates were valid
135 if (!signingCertificate
.WasCurrent (Timestamp
)) {
140 else if (!signingCertificate
.IsCurrent
) {
141 // signature only valid if the certificate is valid
151 public byte[] Signature
{
155 return (byte[]) entry
.Clone ();
159 public DateTime Timestamp
{
160 get { return timestamp; }
163 public X509CertificateCollection Certificates
{
167 public X509Certificate SigningCertificate
{
168 get { return signingCertificate; }
171 private bool CheckSignature (string fileName
)
175 entry
= GetSecurityEntry ();
177 // no signature is present
183 PKCS7
.ContentInfo ci
= new PKCS7
.ContentInfo (entry
);
184 if (ci
.ContentType
!= PKCS7
.Oid
.signedData
) {
189 PKCS7
.SignedData sd
= new PKCS7
.SignedData (ci
.Content
);
190 if (sd
.ContentInfo
.ContentType
!= spcIndirectDataContext
) {
195 coll
= sd
.Certificates
;
197 ASN1 spc
= sd
.ContentInfo
.Content
;
198 signedHash
= spc
[0][1][1];
200 HashAlgorithm ha
= null;
201 switch (signedHash
.Length
) {
203 ha
= HashAlgorithm
.Create ("MD5");
207 ha
= HashAlgorithm
.Create ("SHA1");
217 if (!signedHash
.CompareValue (hash
)) {
221 // messageDigest is a hash of spcIndirectDataContext (which includes the file hash)
222 byte[] spcIDC
= spc
[0].Value
;
223 ha
.Initialize (); // re-using hash instance
224 byte[] messageDigest
= ha
.ComputeHash (spcIDC
);
226 bool sign
= VerifySignature (sd
, messageDigest
, ha
);
227 return (sign
&& (reason
== 0));
230 private bool CompareIssuerSerial (string issuer
, byte[] serial
, X509Certificate x509
)
232 if (issuer
!= x509
.IssuerName
)
234 if (serial
.Length
!= x509
.SerialNumber
.Length
)
236 // MS shows the serial number inversed (so is Mono.Security.X509.X509Certificate)
237 int n
= serial
.Length
;
238 for (int i
=0; i
< serial
.Length
; i
++) {
239 if (serial
[i
] != x509
.SerialNumber
[--n
])
246 //private bool VerifySignature (ASN1 cs, byte[] calculatedMessageDigest, string hashName)
247 private bool VerifySignature (PKCS7
.SignedData sd
, byte[] calculatedMessageDigest
, HashAlgorithm ha
)
249 string contentType
= null;
250 ASN1 messageDigest
= null;
251 // string spcStatementType = null;
252 // string spcSpOpusInfo = null;
254 for (int i
=0; i
< sd
.SignerInfo
.AuthenticatedAttributes
.Count
; i
++) {
255 ASN1 attr
= (ASN1
) sd
.SignerInfo
.AuthenticatedAttributes
[i
];
256 string oid
= ASN1Convert
.ToOid (attr
[0]);
258 case "1.2.840.113549.1.9.3":
260 contentType
= ASN1Convert
.ToOid (attr
[1][0]);
262 case "1.2.840.113549.1.9.4":
264 messageDigest
= attr
[1][0];
266 case "1.3.6.1.4.1.311.2.1.11":
267 // spcStatementType (Microsoft code signing)
269 // - individualCodeSigning (1 3 6 1 4 1 311 2 1 21)
270 // - commercialCodeSigning (1 3 6 1 4 1 311 2 1 22)
271 // spcStatementType = ASN1Convert.ToOid (attr[1][0][0]);
273 case "1.3.6.1.4.1.311.2.1.12":
274 // spcSpOpusInfo (Microsoft code signing)
276 spcSpOpusInfo = System.Text.Encoding.UTF8.GetString (attr[1][0][0][0].Value);
278 catch (NullReferenceException) {
279 spcSpOpusInfo = null;
286 if (contentType
!= spcIndirectDataContext
)
289 // verify message digest
290 if (messageDigest
== null)
292 if (!messageDigest
.CompareValue (calculatedMessageDigest
))
296 string hashOID
= CryptoConfig
.MapNameToOID (ha
.ToString ());
298 // change to SET OF (not [0]) as per PKCS #7 1.5
299 ASN1 aa
= new ASN1 (0x31);
300 foreach (ASN1 a
in sd
.SignerInfo
.AuthenticatedAttributes
)
303 byte[] p7hash
= ha
.ComputeHash (aa
.GetBytes ());
305 byte[] signature
= sd
.SignerInfo
.Signature
;
306 // we need to find the specified certificate
307 string issuer
= sd
.SignerInfo
.IssuerName
;
308 byte[] serial
= sd
.SignerInfo
.SerialNumber
;
309 foreach (X509Certificate x509
in coll
) {
310 if (CompareIssuerSerial (issuer
, serial
, x509
)) {
311 // don't verify is key size don't match
312 if (x509
.PublicKey
.Length
> (signature
.Length
>> 3)) {
313 // return the signing certificate even if the signature isn't correct
314 // (required behaviour for 2.0 support)
315 signingCertificate
= x509
;
316 RSACryptoServiceProvider rsa
= (RSACryptoServiceProvider
) x509
.RSA
;
317 if (rsa
.VerifyHash (p7hash
, hashOID
, signature
)) {
318 signerChain
.LoadCertificates (coll
);
319 trustedRoot
= signerChain
.Build (x509
);
326 // timestamp signature is optional
327 if (sd
.SignerInfo
.UnauthenticatedAttributes
.Count
== 0) {
328 trustedTimestampRoot
= true;
330 for (int i
= 0; i
< sd
.SignerInfo
.UnauthenticatedAttributes
.Count
; i
++) {
331 ASN1 attr
= (ASN1
) sd
.SignerInfo
.UnauthenticatedAttributes
[i
];
332 string oid
= ASN1Convert
.ToOid (attr
[0]);
334 case PKCS7
.Oid
.countersignature
:
337 // countersignature (1 2 840 113549 1 9 6)
339 PKCS7
.SignerInfo cs
= new PKCS7
.SignerInfo (attr
[1]);
340 trustedTimestampRoot
= VerifyCounterSignature (cs
, signature
);
343 // we don't support other unauthenticated attributes
349 return (trustedRoot
&& trustedTimestampRoot
);
352 private bool VerifyCounterSignature (PKCS7
.SignerInfo cs
, byte[] signature
)
361 string contentType
= null;
362 ASN1 messageDigest
= null;
363 for (int i
=0; i
< cs
.AuthenticatedAttributes
.Count
; i
++) {
366 ASN1 attr
= (ASN1
) cs
.AuthenticatedAttributes
[i
];
367 string oid
= ASN1Convert
.ToOid (attr
[0]);
369 case "1.2.840.113549.1.9.3":
371 contentType
= ASN1Convert
.ToOid (attr
[1][0]);
373 case "1.2.840.113549.1.9.4":
375 messageDigest
= attr
[1][0];
377 case "1.2.840.113549.1.9.5":
380 // signingTime (1 2 840 113549 1 9 5)
382 // UTCTime '030124013651Z'
385 timestamp
= ASN1Convert
.ToDateTime (attr
[1][0]);
392 if (contentType
!= PKCS7
.Oid
.data
)
395 // verify message digest
396 if (messageDigest
== null)
398 // TODO: must be read from the ASN.1 structure
399 string hashName
= null;
400 switch (messageDigest
.Length
) {
408 HashAlgorithm ha
= HashAlgorithm
.Create (hashName
);
409 if (!messageDigest
.CompareValue (ha
.ComputeHash (signature
)))
413 byte[] counterSignature
= cs
.Signature
;
415 // change to SET OF (not [0]) as per PKCS #7 1.5
416 ASN1 aa
= new ASN1 (0x31);
417 foreach (ASN1 a
in cs
.AuthenticatedAttributes
)
419 byte[] p7hash
= ha
.ComputeHash (aa
.GetBytes ());
421 // we need to try all certificates
422 string issuer
= cs
.IssuerName
;
423 byte[] serial
= cs
.SerialNumber
;
424 foreach (X509Certificate x509
in coll
) {
425 if (CompareIssuerSerial (issuer
, serial
, x509
)) {
426 if (x509
.PublicKey
.Length
> counterSignature
.Length
) {
427 RSACryptoServiceProvider rsa
= (RSACryptoServiceProvider
) x509
.RSA
;
428 // we need to HACK around bad (PKCS#1 1.5) signatures made by Verisign Timestamp Service
429 // and this means copying stuff into our own RSAManaged to get the required flexibility
430 RSAManaged rsam
= new RSAManaged ();
431 rsam
.ImportParameters (rsa
.ExportParameters (false));
432 if (PKCS1
.Verify_v15 (rsam
, ha
, p7hash
, counterSignature
, true)) {
433 timestampChain
.LoadCertificates (coll
);
434 return (timestampChain
.Build (x509
));
439 // no certificate can verify this signature!
443 private void Reset ()
449 signingCertificate
= null;
452 trustedTimestampRoot
= false;
453 signerChain
.Reset ();
454 timestampChain
.Reset ();
455 timestamp
= DateTime
.MinValue
;