2003-09-06 Sebastien Pouliot <spouliot@videotron.ca>
[mono.git] / mcs / class / Microsoft.Web.Services / Microsoft.Web.Services.Security / SignedXml.cs
1 //
2 // SignedXml.cs - SignedXml implementation for XML Signature
3 //
4 // Author:
5 //      Sebastien Pouliot (spouliot@motus.com)
6 //
7 // (C) 2002, 2003 Motus Technologies Inc. (http://www.motus.com)
8 //
9
10 using System;
11 using System.Collections;
12 using System.IO;
13 using System.Runtime.InteropServices;
14 using System.Security.Cryptography;
15 using System.Xml;
16
17 using SSCX = System.Security.Cryptography.Xml;
18
19 #if (WSE1 || WSE2)
20 using Microsoft.Web.Services.Security;
21
22 namespace Microsoft.Web.Services.Security {
23 #else
24 using System.Security.Cryptography.Xml;
25
26 namespace System.Security.Cryptography.Xml {
27 #endif
28         public class SignedXml {
29
30 #if (WSE1 || WSE2)
31                 private SignedXmlSignature signature;
32
33                 public SignedXml () 
34                 {
35                         signature = new SignedXmlSignature ();
36                         signature.SignedInfo = new SignedInfo ();
37                 }
38 #else
39                 private Signature signature;
40
41                 public SignedXml () 
42                 {
43                         signature = new Signature ();
44                         signature.SignedInfo = new SignedInfo ();
45                 }
46 #endif
47                 private AsymmetricAlgorithm key;
48                 private string keyName;
49                 private XmlDocument envdoc;
50
51                 public SignedXml (XmlDocument document) : this ()
52                 {
53                         envdoc = document;
54                 }
55
56                 public SignedXml (XmlElement elem) : this ()
57                 {
58                         if (elem == null)
59                                 throw new ArgumentNullException ("elem");
60                 }
61
62                 public const string XmlDsigCanonicalizationUrl = "http://www.w3.org/TR/2001/REC-xml-c14n-20010315";
63                 public const string XmlDsigCanonicalizationWithCommentsUrl = XmlDsigCanonicalizationUrl + "#WithComments";
64                 public const string XmlDsigNamespaceUrl = "http://www.w3.org/2000/09/xmldsig#";
65                 public const string XmlDsigDSAUrl = XmlDsigNamespaceUrl + "dsa-sha1";
66                 public const string XmlDsigHMACSHA1Url = XmlDsigNamespaceUrl + "hmac-sha1";
67                 public const string XmlDsigMinimalCanonicalizationUrl = XmlDsigNamespaceUrl + "minimal";
68                 public const string XmlDsigRSASHA1Url = XmlDsigNamespaceUrl + "rsa-sha1";
69                 public const string XmlDsigSHA1Url = XmlDsigNamespaceUrl + "sha1";
70
71                 public SSCX.KeyInfo KeyInfo {
72                         get { return signature.KeyInfo; }
73                         set { signature.KeyInfo = value; }
74                 }
75
76 #if (WSE1 || WSE2)
77                 public SignedXmlSignature Signature {
78                         get { return signature; }
79                 }
80 #else
81                 public Signature Signature {
82                         get { return signature; }
83                 }
84 #endif
85                 public string SignatureLength {
86                         get { return signature.SignedInfo.SignatureLength; }
87                 }
88
89                 public string SignatureMethod {
90                         get { return signature.SignedInfo.SignatureMethod; }
91                 }
92
93                 public byte[] SignatureValue {
94                         get { return signature.SignatureValue; }
95                 }
96
97                 public SignedInfo SignedInfo {
98                         get { return signature.SignedInfo; }
99                 }
100
101                 public AsymmetricAlgorithm SigningKey {
102                         get { return key; }
103                         set { key = value; }
104                 }
105
106                 public string SigningKeyName {
107                         get { return keyName; }
108                         set { keyName = value; }
109                 }
110
111                 public void AddObject (SSCX.DataObject dataObject) 
112                 {
113                         signature.AddObject (dataObject);
114                 }
115
116                 public void AddReference (Reference reference) 
117                 {
118                         signature.SignedInfo.AddReference (reference);
119                 }
120
121                 private Stream ApplyTransform (SSCX.Transform t, XmlDocument doc) 
122                 {
123                         t.LoadInput (doc);
124                         if (t is SSCX.XmlDsigEnvelopedSignatureTransform) {
125                                 XmlDocument d = (XmlDocument) t.GetOutput ();
126                                 MemoryStream ms = new MemoryStream ();
127                                 d.Save (ms);
128                                 return ms;
129                         }
130                         else
131                                 return (Stream) t.GetOutput ();
132                 }
133
134                 private Stream ApplyTransform (SSCX.Transform t, Stream s) 
135                 {
136                         try {
137                                 t.LoadInput (s);
138                                 s = (Stream) t.GetOutput ();
139                         }
140                         catch (Exception e) {
141                                 string temp = e.ToString (); // stop debugger
142                         }
143                         return s;
144                 }
145
146                 [MonoTODO("incomplete")]
147                 private byte[] GetReferenceHash (Reference r) 
148                 {
149                         XmlDocument doc = new XmlDocument ();
150                         doc.PreserveWhitespace = true;
151                         if (r.Uri == "")
152                                 doc = envdoc;
153                         else {
154                                 foreach (SSCX.DataObject obj in signature.ObjectList) {
155                                         if ("#" + obj.Id == r.Uri) {
156                                                 doc.LoadXml (obj.GetXml ().OuterXml);
157                                                 break;
158                                         }
159                                 }
160                         }
161
162                         Stream s = null;
163                         if (r.TransformChain.Count > 0) {               
164                                 foreach (SSCX.Transform t in r.TransformChain) {
165                                         if (s == null)
166                                                 s = ApplyTransform (t, doc);
167                                         else
168                                                 s = ApplyTransform (t, s);
169                                 }
170                         }
171                         else
172                                 s = ApplyTransform (new SSCX.XmlDsigC14NTransform (), doc);
173
174                         // TODO: We should reuse the same hash object (when possible)
175                         HashAlgorithm hash = (HashAlgorithm) CryptoConfig.CreateFromName (r.DigestMethod);
176                         return hash.ComputeHash (s);
177                 }
178
179                 private void DigestReferences () 
180                 {
181                         // we must tell each reference which hash algorithm to use 
182                         // before asking for the SignedInfo XML !
183                         foreach (Reference r in signature.SignedInfo.References) {
184                                 // assume SHA-1 if nothing is specified
185                                 if (r.DigestMethod == null)
186                                         r.DigestMethod = XmlDsigSHA1Url;
187                                 r.DigestValue = GetReferenceHash (r);
188                         }
189                 }
190                 
191                 private Stream SignedInfoTransformed () 
192                 {
193                         SSCX.Transform t = (SSCX.Transform) CryptoConfig.CreateFromName (signature.SignedInfo.CanonicalizationMethod);
194                         if (t == null)
195                                 return null;
196
197                         XmlDocument doc = new XmlDocument ();
198                         doc.LoadXml (signature.SignedInfo.GetXml ().OuterXml);
199                         return ApplyTransform (t, doc); 
200                 }
201
202                 private byte[] Hash (string hashAlgorithm) 
203                 {
204                         HashAlgorithm hash = HashAlgorithm.Create (hashAlgorithm);
205                         // get the hash of the C14N SignedInfo element
206                         return hash.ComputeHash (SignedInfoTransformed ());
207                 }
208
209                 public bool CheckSignature () 
210                 {
211                         // CryptographicException
212                         if (key == null)
213                                 key = GetPublicKey ();
214                         return CheckSignature (key);
215                 }
216
217                 private bool CheckReferenceIntegrity () 
218                 {
219                         // check digest (hash) for every reference
220                         foreach (Reference r in signature.SignedInfo.References) {
221                                 // stop at first broken reference
222                                 if (! Compare (r.DigestValue, GetReferenceHash (r)))
223                                         return false;
224                         }
225                         return true;
226                 }
227
228                 public bool CheckSignature (AsymmetricAlgorithm key) 
229                 {
230                         if (key == null)
231                                 throw new ArgumentNullException ("key");
232
233                         // Part 1: Are all references digest valid ?
234                         bool result = CheckReferenceIntegrity ();
235                         if (result) {
236                                 // Part 2: Is the signature (over SignedInfo) valid ?
237                                 SignatureDescription sd = (SignatureDescription) CryptoConfig.CreateFromName (signature.SignedInfo.SignatureMethod);
238
239                                 byte[] hash = Hash (sd.DigestAlgorithm);
240                                 AsymmetricSignatureDeformatter verifier = (AsymmetricSignatureDeformatter) CryptoConfig.CreateFromName (sd.DeformatterAlgorithm);
241
242                                 if (verifier != null) {
243                                         verifier.SetHashAlgorithm (sd.DigestAlgorithm);
244                                         result = verifier.VerifySignature (hash, signature.SignatureValue); 
245                                 }
246                                 else
247                                         result = false;
248                         }
249
250                         return result;
251                 }
252
253                 private bool Compare (byte[] expected, byte[] actual) 
254                 {
255                         bool result = ((expected != null) && (actual != null));
256                         if (result) {
257                                 int l = expected.Length;
258                                 result = (l == actual.Length);
259                                 if (result) {
260                                         for (int i=0; i < l; i++) {
261                                                 if (expected[i] != actual[i])
262                                                         return false;
263                                         }
264                                 }
265                         }
266                         return result;
267                 }
268
269                 public bool CheckSignature (KeyedHashAlgorithm macAlg) 
270                 {
271                         if (macAlg == null)
272                                 throw new ArgumentNullException ("macAlg");
273
274                         // Part 1: Are all references digest valid ?
275                         bool result = CheckReferenceIntegrity ();
276                         if (result) {
277                                 // Part 2: Is the signature (over SignedInfo) valid ?
278                                 byte[] actual = macAlg.ComputeHash (SignedInfoTransformed ());
279                                 result = Compare (signature.SignatureValue, actual);
280                         }
281                         return result;
282                 }
283
284                 public bool CheckSignatureReturningKey (out AsymmetricAlgorithm signingKey) 
285                 {
286                         // here's the key used for verifying the signature
287                         if (key == null)
288                                 key = GetPublicKey ();
289                         signingKey = key;
290                         // we'll find the key if we haven't already
291                         return CheckSignature (key);
292                 }
293
294                 public void ComputeSignature () 
295                 {
296                         if (key != null) {
297                                 // required before hashing
298                                 signature.SignedInfo.SignatureMethod = key.SignatureAlgorithm;
299                                 DigestReferences ();
300
301                                 SignatureDescription sd = (SignatureDescription) CryptoConfig.CreateFromName (signature.SignedInfo.SignatureMethod);
302
303                                 // the hard part - C14Ning the KeyInfo
304                                 byte[] hash = Hash (sd.DigestAlgorithm);
305                                 AsymmetricSignatureFormatter signer = null;
306
307                                 // in need for a CryptoConfig factory
308                                 if (key is DSA)
309                                         signer = new DSASignatureFormatter (key);
310                                 else if (key is RSA) 
311                                         signer = new RSAPKCS1SignatureFormatter (key);
312
313                                 if (signer != null) {
314                                         signer.SetHashAlgorithm ("SHA1");
315                                         signature.SignatureValue = signer.CreateSignature (hash);
316                                 }
317                         }
318                 }
319
320                 public void ComputeSignature (KeyedHashAlgorithm macAlg) 
321                 {
322                         if (macAlg == null)
323                                 throw new ArgumentNullException ("macAlg");
324
325                         if (macAlg is HMACSHA1) {
326                                 DigestReferences ();
327
328                                 signature.SignedInfo.SignatureMethod = XmlDsigHMACSHA1Url;
329                                 signature.SignatureValue = macAlg.ComputeHash (SignedInfoTransformed ());
330                         }
331                         else 
332                                 throw new CryptographicException ("unsupported algorithm");
333                 }
334
335                 // is that all ?
336                 public virtual XmlElement GetIdElement (XmlDocument document, string idValue) 
337                 {
338                         return document.GetElementById (idValue);
339                 }
340
341                 protected virtual AsymmetricAlgorithm GetPublicKey () 
342                 {
343                         AsymmetricAlgorithm key = null;
344                         if (signature.KeyInfo != null) {
345                                 foreach (SSCX.KeyInfoClause kic in signature.KeyInfo) {
346                                         if (kic is SSCX.DSAKeyValue)
347                                                 key = DSA.Create ();
348                                         else if (kic is SSCX.RSAKeyValue) 
349                                                 key = RSA.Create ();
350
351                                         if (key != null) {
352                                                 key.FromXmlString (kic.GetXml ().InnerXml);
353                                                 break;
354                                         }
355                                 }
356                         }
357                         return key;
358                 }
359
360                 public XmlElement GetXml () 
361                 {
362                         return signature.GetXml ();
363                 }
364
365                 public void LoadXml (XmlElement value) 
366                 {
367                         signature.LoadXml (value);
368                 }
369
370 #if ! NET_1_0
371                 private XmlResolver xmlResolver;
372
373                 [MonoTODO("property not (yet) used in class")]
374                 [ComVisible(false)]
375                 XmlResolver Resolver {
376                         set { xmlResolver = value; }
377                 }
378 #endif
379         }
380 }