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