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