2004-03-20 Sebastien Pouliot <sebastien@ximian.com>
[mono.git] / mcs / class / System.Security / System.Security.Cryptography.Xml / SignedXml.cs
1 //
2 // SignedXml.cs - SignedXml implementation for XML Signature
3 //
4 // Author:
5 //      Sebastien Pouliot  <sebastien@ximian.com>
6 //      Atsushi Enomoto <atsushi@ximian.com>
7 //
8 // (C) 2002, 2003 Motus Technologies Inc. (http://www.motus.com)
9 // (C) 2004 Novell (http://www.novell.com)
10 //
11
12 using System.Collections;
13 using System.IO;
14 using System.Runtime.InteropServices;
15 using System.Security.Cryptography;
16 using System.Net;
17 using System.Text;
18 using System.Xml;
19
20 namespace System.Security.Cryptography.Xml {
21
22         public class SignedXml {
23
24                 private Signature signature;
25                 private AsymmetricAlgorithm key;
26                 private string keyName;
27                 private XmlDocument envdoc;
28                 private IEnumerator pkEnumerator;
29                 private XmlElement signatureElement;
30                 private Hashtable hashes;
31
32                 public SignedXml () 
33                 {
34                         signature = new Signature ();
35                         signature.SignedInfo = new SignedInfo ();
36                         hashes = new Hashtable (2); // 98% SHA1 for now
37                 }
38
39                 public SignedXml (XmlDocument document) : this ()
40                 {
41                         if (document == null)
42                                 throw new ArgumentNullException ("document");
43                         envdoc = document;
44                 }
45
46                 public SignedXml (XmlElement elem) : this ()
47                 {
48                         if (elem == null)
49                                 throw new ArgumentNullException ("elem");
50                         envdoc = new XmlDocument ();
51                         envdoc.LoadXml (elem.OuterXml);
52                 }
53
54                 public const string XmlDsigCanonicalizationUrl = "http://www.w3.org/TR/2001/REC-xml-c14n-20010315";
55                 public const string XmlDsigCanonicalizationWithCommentsUrl = XmlDsigCanonicalizationUrl + "#WithComments";
56                 public const string XmlDsigNamespaceUrl = "http://www.w3.org/2000/09/xmldsig#";
57                 public const string XmlDsigDSAUrl = XmlDsigNamespaceUrl + "dsa-sha1";
58                 public const string XmlDsigHMACSHA1Url = XmlDsigNamespaceUrl + "hmac-sha1";
59                 public const string XmlDsigMinimalCanonicalizationUrl = XmlDsigNamespaceUrl + "minimal";
60                 public const string XmlDsigRSASHA1Url = XmlDsigNamespaceUrl + "rsa-sha1";
61                 public const string XmlDsigSHA1Url = XmlDsigNamespaceUrl + "sha1";
62
63                 public KeyInfo KeyInfo {
64                         get { return signature.KeyInfo; }
65                         set { signature.KeyInfo = value; }
66                 }
67
68                 public Signature Signature {
69                         get { return signature; }
70                 }
71
72                 public string SignatureLength {
73                         get { return signature.SignedInfo.SignatureLength; }
74                 }
75
76                 public string SignatureMethod {
77                         get { return signature.SignedInfo.SignatureMethod; }
78                 }
79
80                 public byte[] SignatureValue {
81                         get { return signature.SignatureValue; }
82                 }
83
84                 public SignedInfo SignedInfo {
85                         get { return signature.SignedInfo; }
86                 }
87
88                 public AsymmetricAlgorithm SigningKey {
89                         get { return key; }
90                         set { key = value; }
91                 }
92
93                 // NOTE: CryptoAPI related ? documented as fx internal
94                 public string SigningKeyName {
95                         get { return keyName; }
96                         set { keyName = value; }
97                 }
98
99                 public void AddObject (DataObject dataObject) 
100                 {
101                         signature.AddObject (dataObject);
102                 }
103
104                 public void AddReference (Reference reference) 
105                 {
106                         signature.SignedInfo.AddReference (reference);
107                 }
108
109                 private Stream ApplyTransform (Transform t, XmlDocument input) 
110                 {
111                         XmlDocument doc = (XmlDocument) input.Clone ();
112
113                         t.LoadInput (doc);
114                         if (t is XmlDsigEnvelopedSignatureTransform) {
115                                 // It returns XmlDocument for XmlDocument input.
116                                 doc = (XmlDocument) t.GetOutput ();
117                                 Transform c14n = GetC14NMethod ();
118                                 c14n.LoadInput (doc);
119                                 return (Stream) c14n.GetOutput ();
120                         }
121
122                         object obj = t.GetOutput ();
123                         if (obj is Stream)
124                                 return (Stream) obj;
125                         else {
126                                 // e.g. XmlDsigXPathTransform returns XmlNodeList
127                                 // TODO - fix
128                                 return null;
129                         }
130                 }
131
132                 [MonoTODO("incomplete")]
133                 private byte[] GetReferenceHash (Reference r) 
134                 {
135                         Stream s = null;
136                         XmlDocument doc = null;
137                         if (r.Uri == String.Empty) {
138                                 doc = envdoc;
139                         }
140                         else {
141                                 doc = new XmlDocument ();
142                                 doc.PreserveWhitespace = true;
143
144                                 if (r.Uri [0] == '#') {
145                                         foreach (DataObject obj in signature.ObjectList) {
146                                                 if ("#" + obj.Id == r.Uri) {
147                                                         doc.LoadXml (obj.GetXml ().OuterXml);
148                                                         break;
149                                                 }
150                                         }
151                                 }
152                                 else {
153                                         if (r.Uri.EndsWith (".xml")) {
154 #if ! NET_1_0
155                                                 doc.XmlResolver = xmlResolver;
156 #endif                                          
157                                                 doc.Load (r.Uri);
158                                         }
159                                         else {
160                                                 WebRequest req = WebRequest.Create (r.Uri);
161                                                 s = req.GetResponse ().GetResponseStream ();
162                                         }
163                                 }
164                         }
165
166                         if (r.TransformChain.Count > 0) {               
167                                 foreach (Transform t in r.TransformChain) {
168                                         if (s == null) {
169                                                 s = ApplyTransform (t, doc);
170                                         }
171                                         else {
172                                                 t.LoadInput (s);
173                                                 s = (Stream) t.GetOutput ();
174                                         }
175                                 }
176                         }
177                         else if (s == null) {
178                                 // apply default C14N transformation
179                                 s = ApplyTransform (new XmlDsigC14NTransform (), doc);
180                         }
181
182                         HashAlgorithm hash = GetHash (r.DigestMethod);
183                         return hash.ComputeHash (s);
184                 }
185
186                 private void DigestReferences () 
187                 {
188                         // we must tell each reference which hash algorithm to use 
189                         // before asking for the SignedInfo XML !
190                         foreach (Reference r in signature.SignedInfo.References) {
191                                 // assume SHA-1 if nothing is specified
192                                 if (r.DigestMethod == null)
193                                         r.DigestMethod = XmlDsigSHA1Url;
194                                 r.DigestValue = GetReferenceHash (r);
195                         }
196                 }
197
198                 private Transform GetC14NMethod ()
199                 {
200                         Transform t = (Transform) CryptoConfig.CreateFromName (signature.SignedInfo.CanonicalizationMethod);
201                         if (t == null)
202                                 throw new CryptographicException ("Unknown Canonicalization Method {0}", signature.SignedInfo.CanonicalizationMethod);
203                         return t;
204                 }
205
206                 private Stream SignedInfoTransformed () 
207                 {
208                         Transform t = GetC14NMethod ();
209
210                         if (signatureElement == null) {
211                                 // when creating signatures
212                                 XmlDocument doc = new XmlDocument ();
213                                 doc.PreserveWhitespace = true;
214                                 doc.LoadXml (signature.SignedInfo.GetXml ().OuterXml);
215
216                                 t.LoadInput (doc);
217                         }
218                         else {
219                                 // when verifying signatures
220                                 // TODO - check signature.SignedInfo.Id
221                                 XmlNodeList xnl = signatureElement.GetElementsByTagName (XmlSignature.ElementNames.SignedInfo, XmlSignature.NamespaceURI);
222                                 byte[] si = Encoding.UTF8.GetBytes (xnl [0].OuterXml);
223
224                                 MemoryStream ms = new MemoryStream ();
225                                 ms.Write (si, 0, si.Length);
226                                 ms.Position = 0;
227
228                                 t.LoadInput (ms);
229                         }
230                         // C14N and C14NWithComments always return a Stream in GetOutput
231                         return (Stream) t.GetOutput ();
232                 }
233
234                 // reuse hash - most document will always use the same hash
235                 private HashAlgorithm GetHash (string algorithm) 
236                 {
237                         HashAlgorithm hash = (HashAlgorithm) hashes [algorithm];
238                         if (hash == null) {
239                                 hash = HashAlgorithm.Create (algorithm);
240                                 if (hash == null)
241                                         throw new CryptographicException ("Unknown hash algorithm: {0}", algorithm);
242                                 hashes.Add (algorithm, hash);
243                                 // now ready to be used
244                         }
245                         else {
246                                 // important before reusing an hash object
247                                 hash.Initialize ();
248                         }
249                         return hash;
250                 }
251
252                 public bool CheckSignature () 
253                 {
254                         return (CheckSignatureInternal (null) != null);
255                 }
256
257                 private bool CheckReferenceIntegrity () 
258                 {
259                         // check digest (hash) for every reference
260                         foreach (Reference r in signature.SignedInfo.References) {
261                                 // stop at first broken reference
262                                 if (! Compare (r.DigestValue, GetReferenceHash (r)))
263                                         return false;
264                         }
265                         return true;
266                 }
267
268                 public bool CheckSignature (AsymmetricAlgorithm key) 
269                 {
270                         if (key == null)
271                                 throw new ArgumentNullException ("key");
272                         return (CheckSignatureInternal (key) != null);
273                 }
274
275                 private AsymmetricAlgorithm CheckSignatureInternal (AsymmetricAlgorithm key)
276                 {
277                         pkEnumerator = null;
278
279                         if (key != null) {
280                                 // check with supplied key
281                                 if (!CheckSignatureWithKey (key))
282                                         return null;
283                         }
284                         else {
285                                 // no supplied key, iterates all KeyInfo
286                                 while ((key = GetPublicKey ()) != null) {
287                                         if (CheckSignatureWithKey (key)) {
288                                                 break;
289                                         }
290                                 }
291                                 if (key == null)
292                                         throw new CryptographicException ("No public key found to verify the signature.");
293                         }
294
295                         // some parts may need to be downloaded
296                         // so where doing it last
297                         return (CheckReferenceIntegrity () ? key : null);
298                 }
299
300                 // Is the signature (over SignedInfo) valid ?
301                 private bool CheckSignatureWithKey (AsymmetricAlgorithm key) 
302                 {
303                         if (key == null)
304                                 return false;
305
306                         SignatureDescription sd = (SignatureDescription) CryptoConfig.CreateFromName (signature.SignedInfo.SignatureMethod);
307                         if (sd == null)
308                                 return false;
309
310                         AsymmetricSignatureDeformatter verifier = (AsymmetricSignatureDeformatter) CryptoConfig.CreateFromName (sd.DeformatterAlgorithm);
311                         if (verifier == null)
312                                 return false;
313
314                         verifier.SetKey (key);
315                         verifier.SetHashAlgorithm (sd.DigestAlgorithm);
316
317                         HashAlgorithm hash = GetHash (sd.DigestAlgorithm);
318                         // get the hash of the C14N SignedInfo element
319                         byte[] digest = hash.ComputeHash (SignedInfoTransformed ());
320                         return verifier.VerifySignature (digest, signature.SignatureValue); 
321                 }
322
323                 private bool Compare (byte[] expected, byte[] actual) 
324                 {
325                         bool result = ((expected != null) && (actual != null));
326                         if (result) {
327                                 int l = expected.Length;
328                                 result = (l == actual.Length);
329                                 if (result) {
330                                         for (int i=0; i < l; i++) {
331                                                 if (expected[i] != actual[i])
332                                                         return false;
333                                         }
334                                 }
335                         }
336                         return result;
337                 }
338
339                 public bool CheckSignature (KeyedHashAlgorithm macAlg) 
340                 {
341                         if (macAlg == null)
342                                 throw new ArgumentNullException ("macAlg");
343
344                         pkEnumerator = null;
345
346                         // Is the signature (over SignedInfo) valid ?
347                         Stream s = SignedInfoTransformed ();
348                         if (s == null)
349                                 return false;
350
351                         byte[] actual = macAlg.ComputeHash (s);
352                         // HMAC signature may be partial
353                         if (signature.SignedInfo.SignatureLength != null) {
354                                 int length = actual.Length;
355                                 try {
356                                         // SignatureLength is in bits
357                                         length = (Int32.Parse (signature.SignedInfo.SignatureLength) >> 3);
358                                 }
359                                 catch {
360                                 }
361
362                                 if (length != actual.Length) {
363                                         byte[] trunked = new byte [length];
364                                         Buffer.BlockCopy (actual, 0, trunked, 0, length);
365                                         actual = trunked;
366                                 }
367                         }
368
369                         if (Compare (signature.SignatureValue, actual)) {
370                                 // some parts may need to be downloaded
371                                 // so where doing it last
372                                 return CheckReferenceIntegrity ();
373                         }
374                         return false;
375                 }
376
377                 public bool CheckSignatureReturningKey (out AsymmetricAlgorithm signingKey) 
378                 {
379                         signingKey = CheckSignatureInternal (null);
380                         return (signingKey != null);
381                 }
382
383                 public void ComputeSignature () 
384                 {
385                         if (key != null) {
386                                 // required before hashing
387                                 signature.SignedInfo.SignatureMethod = key.SignatureAlgorithm;
388                                 DigestReferences ();
389
390                                 AsymmetricSignatureFormatter signer = null;
391                                 // in need for a CryptoConfig factory
392                                 if (key is DSA)
393                                         signer = new DSASignatureFormatter (key);
394                                 else if (key is RSA) 
395                                         signer = new RSAPKCS1SignatureFormatter (key);
396
397                                 if (signer != null) {
398                                         SignatureDescription sd = (SignatureDescription) CryptoConfig.CreateFromName (signature.SignedInfo.SignatureMethod);
399
400                                         HashAlgorithm hash = GetHash (sd.DigestAlgorithm);
401                                         // get the hash of the C14N SignedInfo element
402                                         byte[] digest = hash.ComputeHash (SignedInfoTransformed ());
403
404                                         signer.SetHashAlgorithm ("SHA1");
405                                         signature.SignatureValue = signer.CreateSignature (digest);
406                                 }
407                         }
408                 }
409
410                 public void ComputeSignature (KeyedHashAlgorithm macAlg) 
411                 {
412                         if (macAlg == null)
413                                 throw new ArgumentNullException ("macAlg");
414
415                         if (macAlg is HMACSHA1) {
416                                 DigestReferences ();
417
418                                 signature.SignedInfo.SignatureMethod = XmlDsigHMACSHA1Url;
419                                 signature.SignatureValue = macAlg.ComputeHash (SignedInfoTransformed ());
420                         }
421                         else 
422                                 throw new CryptographicException ("unsupported algorithm");
423                 }
424
425                 public virtual XmlElement GetIdElement (XmlDocument document, string idValue) 
426                 {
427                         // this works only if there's a DTD or XSD available to define the ID
428                         XmlElement xel = document.GetElementById (idValue);
429                         if (xel == null) {
430                                 // search an "undefined" ID
431                                 xel = (XmlElement) document.SelectSingleNode ("//*[@Id='" + idValue + "']");
432                         }
433                         return xel;
434                 }
435
436                 // According to book ".NET Framework Security" this method
437                 // iterates all possible keys then return null
438                 protected virtual AsymmetricAlgorithm GetPublicKey () 
439                 {
440                         if (signature.KeyInfo == null)
441                                 return null;
442
443                         if (pkEnumerator == null) {
444                                 pkEnumerator = signature.KeyInfo.GetEnumerator ();
445                         }
446
447                         if (pkEnumerator.MoveNext ()) {
448                                 AsymmetricAlgorithm key = null;
449                                 KeyInfoClause kic = (KeyInfoClause) pkEnumerator.Current;
450
451                                 if (kic is DSAKeyValue)
452                                         key = DSA.Create ();
453                                 else if (kic is RSAKeyValue) 
454                                         key = RSA.Create ();
455
456                                 if (key != null) {
457                                         key.FromXmlString (kic.GetXml ().InnerXml);
458                                         return key;
459                                 }
460                         }
461                         return null;
462                 }
463
464                 public XmlElement GetXml () 
465                 {
466                         return signature.GetXml ();
467                 }
468
469                 public void LoadXml (XmlElement value) 
470                 {
471                         if (value == null)
472                                 throw new ArgumentNullException ("value");
473
474                         signatureElement = value;
475                         signature.LoadXml (value);
476                 }
477
478 #if ! NET_1_0
479                 private XmlResolver xmlResolver;
480
481                 [MonoTODO("property not (yet) used in class")]
482                 [ComVisible(false)]
483                 public XmlResolver Resolver {
484                         set { xmlResolver = value; }
485                 }
486 #endif
487         }
488 }