(DISTFILES): Comment out a few missing files.
[mono-project.git] / mcs / class / System.Security / System.Security.Cryptography.Xml / SignedXml.cs
blob002af1064135a6e56fb184be960170fcd7817de3
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 // Tim Coleman <tim@timcoleman.com>
8 //
9 // (C) 2002, 2003 Motus Technologies Inc. (http://www.motus.com)
10 // Copyright (C) Tim Coleman, 2004
11 // (C) 2004 Novell (http://www.novell.com)
15 // Permission is hereby granted, free of charge, to any person obtaining
16 // a copy of this software and associated documentation files (the
17 // "Software"), to deal in the Software without restriction, including
18 // without limitation the rights to use, copy, modify, merge, publish,
19 // distribute, sublicense, and/or sell copies of the Software, and to
20 // permit persons to whom the Software is furnished to do so, subject to
21 // the following conditions:
22 //
23 // The above copyright notice and this permission notice shall be
24 // included in all copies or substantial portions of the Software.
25 //
26 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
27 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
28 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
29 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
30 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
31 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
32 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
35 using System.Collections;
36 using System.IO;
37 using System.Runtime.InteropServices;
38 using System.Security.Cryptography;
39 using System.Security.Policy;
40 using System.Net;
41 using System.Text;
42 using System.Xml;
44 #if NET_2_0
45 using System.Security.Cryptography.X509Certificates;
46 #endif
48 namespace System.Security.Cryptography.Xml {
50 public class SignedXml {
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 XmlDsigDSAUrl = XmlDsigNamespaceUrl + "dsa-sha1";
55 public const string XmlDsigHMACSHA1Url = XmlDsigNamespaceUrl + "hmac-sha1";
56 public const string XmlDsigMinimalCanonicalizationUrl = XmlDsigNamespaceUrl + "minimal";
57 public const string XmlDsigNamespaceUrl = "http://www.w3.org/2000/09/xmldsig#";
58 public const string XmlDsigRSASHA1Url = XmlDsigNamespaceUrl + "rsa-sha1";
59 public const string XmlDsigSHA1Url = XmlDsigNamespaceUrl + "sha1";
61 #if NET_2_0
62 public const string XmlDecryptionTransformUrl = "http://www.w3.org/2002/07/decrypt#XML";
63 public const string XmlDsigBase64TransformUrl = XmlDsigNamespaceUrl + "base64";
64 public const string XmlDsigC14NTransformUrl = XmlDsigCanonicalizationUrl;
65 public const string XmlDsigC14NWithCommentsTransformUrl = XmlDsigCanonicalizationWithCommentsUrl;
66 public const string XmlDsigEnvelopedSignatureTransformUrl = XmlDsigNamespaceUrl + "enveloped-signature";
67 public const string XmlDsigExcC14NTransformUrl = "http://www.w3.org/2001/10/xml-exc-c14n#";
68 public const string XmlDsigExcC14NWithCommentsTransformUrl = XmlDsigExcC14NTransformUrl + "WithComments";
69 public const string XmlDsigXPathTransformUrl = "http://www.w3.org/TR/1999/REC-xpath-19991116";
70 public const string XmlDsigXsltTransformUrl = "http://www.w3.org/TR/1999/REC-xslt-19991116";
72 private EncryptedXml encryptedXml;
73 #endif
75 protected Signature m_signature;
76 private AsymmetricAlgorithm key;
77 protected string m_strSigningKeyName;
78 private XmlDocument envdoc;
79 private IEnumerator pkEnumerator;
80 private XmlElement signatureElement;
81 private Hashtable hashes;
82 // FIXME: enable it after CAS implementation
83 #if false //NET_1_1
84 private XmlResolver xmlResolver = new XmlSecureResolver (new XmlUrlResolver (), new Evidence ());
85 #else
86 private XmlResolver xmlResolver = new XmlUrlResolver ();
87 #endif
88 private ArrayList manifests;
90 private static readonly char [] whitespaceChars = new char [] {' ', '\r', '\n', '\t'};
92 public SignedXml ()
94 m_signature = new Signature ();
95 m_signature.SignedInfo = new SignedInfo ();
96 hashes = new Hashtable (2); // 98% SHA1 for now
99 public SignedXml (XmlDocument document) : this ()
101 if (document == null)
102 throw new ArgumentNullException ("document");
103 envdoc = document;
106 public SignedXml (XmlElement elem) : this ()
108 if (elem == null)
109 throw new ArgumentNullException ("elem");
110 envdoc = new XmlDocument ();
111 envdoc.LoadXml (elem.OuterXml);
114 #if NET_2_0
115 public EncryptedXml EncryptedXml {
116 get { return encryptedXml; }
117 set { encryptedXml = value; }
119 #endif
121 public KeyInfo KeyInfo {
122 get { return m_signature.KeyInfo; }
123 set { m_signature.KeyInfo = value; }
126 public Signature Signature {
127 get { return m_signature; }
130 public string SignatureLength {
131 get { return m_signature.SignedInfo.SignatureLength; }
134 public string SignatureMethod {
135 get { return m_signature.SignedInfo.SignatureMethod; }
138 public byte[] SignatureValue {
139 get { return m_signature.SignatureValue; }
142 public SignedInfo SignedInfo {
143 get { return m_signature.SignedInfo; }
146 public AsymmetricAlgorithm SigningKey {
147 get { return key; }
148 set { key = value; }
151 // NOTE: CryptoAPI related ? documented as fx internal
152 public string SigningKeyName {
153 get { return m_strSigningKeyName; }
154 set { m_strSigningKeyName = value; }
157 public void AddObject (DataObject dataObject)
159 m_signature.AddObject (dataObject);
162 public void AddReference (Reference reference)
164 m_signature.SignedInfo.AddReference (reference);
167 private Stream ApplyTransform (Transform t, XmlDocument input)
169 // These transformer modify input document, which should
170 // not affect to the input itself.
171 if (t is XmlDsigXPathTransform
172 || t is XmlDsigEnvelopedSignatureTransform
173 #if NET_2_0
174 || t is XmlDecryptionTransform
175 #endif
177 input = (XmlDocument) input.Clone ();
179 t.LoadInput (input);
181 if (t is XmlDsigEnvelopedSignatureTransform)
182 // It returns XmlDocument for XmlDocument input.
183 return CanonicalizeOutput (t.GetOutput ());
185 object obj = t.GetOutput ();
186 if (obj is Stream)
187 return (Stream) obj;
188 else if (obj is XmlDocument) {
189 MemoryStream ms = new MemoryStream ();
190 XmlTextWriter xtw = new XmlTextWriter (ms, Encoding.UTF8);
191 ((XmlDocument) obj).WriteTo (xtw);
193 xtw.Flush ();
195 // Rewind to the start of the stream
196 ms.Position = 0;
197 return ms;
199 else if (obj == null) {
200 throw new NotImplementedException ("This should not occur. Transform is " + t + ".");
202 else {
203 // e.g. XmlDsigXPathTransform returns XmlNodeList
204 return CanonicalizeOutput (obj);
208 private Stream CanonicalizeOutput (object obj)
210 Transform c14n = GetC14NMethod ();
211 c14n.LoadInput (obj);
212 return (Stream) c14n.GetOutput ();
215 private XmlDocument GetManifest (Reference r)
217 XmlDocument doc = new XmlDocument ();
218 doc.PreserveWhitespace = true;
220 if (r.Uri [0] == '#') {
221 // local manifest
222 if (signatureElement != null) {
223 XmlElement xel = GetIdElement (signatureElement.OwnerDocument, r.Uri.Substring (1));
224 if (xel == null)
225 throw new CryptographicException ("Manifest targeted by Reference was not found: " + r.Uri.Substring (1));
226 doc.LoadXml (xel.OuterXml);
227 FixupNamespaceNodes (xel, doc.DocumentElement);
230 else if (xmlResolver != null) {
231 // TODO: need testing
232 Stream s = (Stream) xmlResolver.GetEntity (new Uri (r.Uri), null, typeof (Stream));
233 doc.Load (s);
236 if (doc.FirstChild != null) {
237 // keep a copy of the manifests to check their references later
238 if (manifests == null)
239 manifests = new ArrayList ();
240 manifests.Add (doc);
242 return doc;
244 return null;
247 private void FixupNamespaceNodes (XmlElement src, XmlElement dst)
249 // add namespace nodes
250 foreach (XmlAttribute attr in src.SelectNodes ("namespace::*")) {
251 if (attr.LocalName == "xml")
252 continue;
253 if (attr.OwnerElement == src)
254 continue;
255 dst.SetAttributeNode (dst.OwnerDocument.ImportNode (attr, true) as XmlAttribute);
259 [MonoTODO ("Need testing")]
260 private byte[] GetReferenceHash (Reference r)
262 Stream s = null;
263 XmlDocument doc = null;
264 if (r.Uri == String.Empty) {
265 doc = envdoc;
267 else if (r.Type == XmlSignature.Uri.Manifest) {
268 doc = GetManifest (r);
270 else {
271 doc = new XmlDocument ();
272 doc.PreserveWhitespace = true;
273 string objectName = null;
275 if (r.Uri.StartsWith ("#xpointer")) {
276 string uri = string.Join ("", r.Uri.Substring (9).Split (whitespaceChars));
277 if (uri.Length < 2 || uri [0] != '(' || uri [uri.Length - 1] != ')')
278 // FIXME: how to handle invalid xpointer?
279 uri = String.Empty;
280 else
281 uri = uri.Substring (1, uri.Length - 2);
282 if (uri == "/")
283 doc = envdoc;
284 else if (uri.Length > 6 && uri.StartsWith ("id(") && uri [uri.Length - 1] == ')')
285 // id('foo'), id("foo")
286 objectName = uri.Substring (4, uri.Length - 6);
288 else if (r.Uri [0] == '#') {
289 objectName = r.Uri.Substring (1);
291 else if (xmlResolver != null) {
292 // TODO: test but doc says that Resolver = null -> no access
293 try {
294 // no way to know if valid without throwing an exception
295 Uri uri = new Uri (r.Uri);
296 s = (Stream) xmlResolver.GetEntity (uri, null, typeof (Stream));
298 catch {
299 // may still be a local file (and maybe not xml)
300 s = File.OpenRead (r.Uri);
303 if (objectName != null) {
304 foreach (DataObject obj in m_signature.ObjectList) {
305 if (obj.Id == objectName) {
306 XmlElement xel = obj.GetXml ();
307 doc.LoadXml (xel.OuterXml);
308 FixupNamespaceNodes (xel, doc.DocumentElement);
309 break;
315 if (r.TransformChain.Count > 0) {
316 foreach (Transform t in r.TransformChain) {
317 if (s == null) {
318 s = ApplyTransform (t, doc);
320 else {
321 t.LoadInput (s);
322 object o = t.GetOutput ();
323 if (o is Stream)
324 s = (Stream) o;
325 else
326 s = CanonicalizeOutput (o);
330 else if (s == null) {
331 // we must not C14N references from outside the document
332 // e.g. non-xml documents
333 if (r.Uri [0] != '#') {
334 s = new MemoryStream ();
335 doc.Save (s);
337 else {
338 // apply default C14N transformation
339 s = ApplyTransform (new XmlDsigC14NTransform (), doc);
342 HashAlgorithm digest = GetHash (r.DigestMethod);
343 return digest.ComputeHash (s);
346 private void DigestReferences ()
348 // we must tell each reference which hash algorithm to use
349 // before asking for the SignedInfo XML !
350 foreach (Reference r in m_signature.SignedInfo.References) {
351 // assume SHA-1 if nothing is specified
352 if (r.DigestMethod == null)
353 r.DigestMethod = XmlDsigSHA1Url;
354 r.DigestValue = GetReferenceHash (r);
358 private Transform GetC14NMethod ()
360 Transform t = (Transform) CryptoConfig.CreateFromName (m_signature.SignedInfo.CanonicalizationMethod);
361 if (t == null)
362 throw new CryptographicException ("Unknown Canonicalization Method {0}", m_signature.SignedInfo.CanonicalizationMethod);
363 return t;
366 private Stream SignedInfoTransformed ()
368 Transform t = GetC14NMethod ();
370 if (signatureElement == null) {
371 // when creating signatures
372 XmlDocument doc = new XmlDocument ();
373 doc.PreserveWhitespace = true;
374 doc.LoadXml (m_signature.SignedInfo.GetXml ().OuterXml);
375 if (envdoc != null)
376 foreach (XmlAttribute attr in envdoc.DocumentElement.SelectNodes ("namespace::*")) {
377 if (attr.LocalName == "xml")
378 continue;
379 if (attr.Prefix == doc.DocumentElement.Prefix)
380 continue;
381 doc.DocumentElement.SetAttributeNode (doc.ImportNode (attr, true) as XmlAttribute);
383 t.LoadInput (doc);
385 else {
386 // when verifying signatures
387 // TODO - check m_signature.SignedInfo.Id
388 XmlElement el = signatureElement.GetElementsByTagName (XmlSignature.ElementNames.SignedInfo, XmlSignature.NamespaceURI) [0] as XmlElement;
389 StringWriter sw = new StringWriter ();
390 XmlTextWriter xtw = new XmlTextWriter (sw);
391 xtw.WriteStartElement (el.Prefix, el.LocalName, el.NamespaceURI);
393 // context namespace nodes (except for "xmlns:xml")
394 XmlNodeList nl = el.SelectNodes ("namespace::*");
395 foreach (XmlAttribute attr in nl) {
396 if (attr.ParentNode == el)
397 continue;
398 if (attr.LocalName == "xml")
399 continue;
400 if (attr.Prefix == el.Prefix)
401 continue;
402 attr.WriteTo (xtw);
404 foreach (XmlNode attr in el.Attributes)
405 attr.WriteTo (xtw);
406 foreach (XmlNode n in el.ChildNodes)
407 n.WriteTo (xtw);
409 xtw.WriteEndElement ();
410 byte [] si = Encoding.UTF8.GetBytes (sw.ToString ());
412 MemoryStream ms = new MemoryStream ();
413 ms.Write (si, 0, si.Length);
414 ms.Position = 0;
416 t.LoadInput (ms);
418 // C14N and C14NWithComments always return a Stream in GetOutput
419 return (Stream) t.GetOutput ();
422 // reuse hash - most document will always use the same hash
423 private HashAlgorithm GetHash (string algorithm)
425 HashAlgorithm hash = (HashAlgorithm) hashes [algorithm];
426 if (hash == null) {
427 hash = HashAlgorithm.Create (algorithm);
428 if (hash == null)
429 throw new CryptographicException ("Unknown hash algorithm: {0}", algorithm);
430 hashes.Add (algorithm, hash);
431 // now ready to be used
433 else {
434 // important before reusing an hash object
435 hash.Initialize ();
437 return hash;
440 public bool CheckSignature ()
442 return (CheckSignatureInternal (null) != null);
445 private bool CheckReferenceIntegrity (ArrayList referenceList)
447 if (referenceList == null)
448 return false;
450 // check digest (hash) for every reference
451 foreach (Reference r in referenceList) {
452 // stop at first broken reference
453 byte[] hash = GetReferenceHash (r);
454 if (! Compare (r.DigestValue, hash))
455 return false;
457 return true;
460 public bool CheckSignature (AsymmetricAlgorithm key)
462 if (key == null)
463 throw new ArgumentNullException ("key");
464 return (CheckSignatureInternal (key) != null);
467 private AsymmetricAlgorithm CheckSignatureInternal (AsymmetricAlgorithm key)
469 pkEnumerator = null;
471 if (key != null) {
472 // check with supplied key
473 if (!CheckSignatureWithKey (key))
474 return null;
476 else {
477 if (Signature.KeyInfo == null)
478 throw new CryptographicException ("At least one KeyInfo is required.");
479 // no supplied key, iterates all KeyInfo
480 while ((key = GetPublicKey ()) != null) {
481 if (CheckSignatureWithKey (key)) {
482 break;
485 pkEnumerator = null;
486 if (key == null)
487 return null;
490 // some parts may need to be downloaded
491 // so where doing it last
492 if (!CheckReferenceIntegrity (m_signature.SignedInfo.References))
493 return null;
495 if (manifests != null) {
496 // do not use foreach as a manifest could contain manifests...
497 for (int i=0; i < manifests.Count; i++) {
498 Manifest manifest = new Manifest ((manifests [i] as XmlDocument).DocumentElement);
499 if (! CheckReferenceIntegrity (manifest.References))
500 return null;
503 return key;
506 // Is the signature (over SignedInfo) valid ?
507 private bool CheckSignatureWithKey (AsymmetricAlgorithm key)
509 if (key == null)
510 return false;
512 SignatureDescription sd = (SignatureDescription) CryptoConfig.CreateFromName (m_signature.SignedInfo.SignatureMethod);
513 if (sd == null)
514 return false;
516 AsymmetricSignatureDeformatter verifier = (AsymmetricSignatureDeformatter) CryptoConfig.CreateFromName (sd.DeformatterAlgorithm);
517 if (verifier == null)
518 return false;
520 try {
521 verifier.SetKey (key);
522 verifier.SetHashAlgorithm (sd.DigestAlgorithm);
524 HashAlgorithm hash = GetHash (sd.DigestAlgorithm);
525 // get the hash of the C14N SignedInfo element
526 MemoryStream ms = (MemoryStream) SignedInfoTransformed ();
528 byte[] digest = hash.ComputeHash (ms);
529 return verifier.VerifySignature (digest, m_signature.SignatureValue);
531 catch {
532 // e.g. SignatureMethod != AsymmetricAlgorithm type
533 return false;
537 private bool Compare (byte[] expected, byte[] actual)
539 bool result = ((expected != null) && (actual != null));
540 if (result) {
541 int l = expected.Length;
542 result = (l == actual.Length);
543 if (result) {
544 for (int i=0; i < l; i++) {
545 if (expected[i] != actual[i])
546 return false;
550 return result;
553 public bool CheckSignature (KeyedHashAlgorithm macAlg)
555 if (macAlg == null)
556 throw new ArgumentNullException ("macAlg");
558 pkEnumerator = null;
560 // Is the signature (over SignedInfo) valid ?
561 Stream s = SignedInfoTransformed ();
562 if (s == null)
563 return false;
565 byte[] actual = macAlg.ComputeHash (s);
566 // HMAC signature may be partial
567 if (m_signature.SignedInfo.SignatureLength != null) {
568 int length = actual.Length;
569 try {
570 // SignatureLength is in bits
571 length = (Int32.Parse (m_signature.SignedInfo.SignatureLength) >> 3);
573 catch {
576 if (length != actual.Length) {
577 byte[] trunked = new byte [length];
578 Buffer.BlockCopy (actual, 0, trunked, 0, length);
579 actual = trunked;
583 if (Compare (m_signature.SignatureValue, actual)) {
584 // some parts may need to be downloaded
585 // so where doing it last
586 return CheckReferenceIntegrity (m_signature.SignedInfo.References);
588 return false;
591 #if NET_2_0
592 [MonoTODO]
593 public bool CheckSignature (X509CertificateEx certificate, bool verifySignatureOnly)
595 throw new NotImplementedException ();
597 #endif
599 public bool CheckSignatureReturningKey (out AsymmetricAlgorithm signingKey)
601 signingKey = CheckSignatureInternal (null);
602 return (signingKey != null);
605 public void ComputeSignature ()
607 if (key != null) {
608 // required before hashing
609 m_signature.SignedInfo.SignatureMethod = key.SignatureAlgorithm;
610 DigestReferences ();
612 AsymmetricSignatureFormatter signer = null;
613 // in need for a CryptoConfig factory
614 if (key is DSA)
615 signer = new DSASignatureFormatter (key);
616 else if (key is RSA)
617 signer = new RSAPKCS1SignatureFormatter (key);
619 if (signer != null) {
620 SignatureDescription sd = (SignatureDescription) CryptoConfig.CreateFromName (m_signature.SignedInfo.SignatureMethod);
622 HashAlgorithm hash = GetHash (sd.DigestAlgorithm);
623 // get the hash of the C14N SignedInfo element
624 byte[] digest = hash.ComputeHash (SignedInfoTransformed ());
626 signer.SetHashAlgorithm ("SHA1");
627 m_signature.SignatureValue = signer.CreateSignature (digest);
632 public void ComputeSignature (KeyedHashAlgorithm macAlg)
634 if (macAlg == null)
635 throw new ArgumentNullException ("macAlg");
637 if (macAlg is HMACSHA1) {
638 DigestReferences ();
640 m_signature.SignedInfo.SignatureMethod = XmlDsigHMACSHA1Url;
641 m_signature.SignatureValue = macAlg.ComputeHash (SignedInfoTransformed ());
643 else
644 throw new CryptographicException ("unsupported algorithm");
647 public virtual XmlElement GetIdElement (XmlDocument document, string idValue)
649 // this works only if there's a DTD or XSD available to define the ID
650 XmlElement xel = document.GetElementById (idValue);
651 if (xel == null) {
652 // search an "undefined" ID
653 xel = (XmlElement) document.SelectSingleNode ("//*[@Id='" + idValue + "']");
655 return xel;
658 // According to book ".NET Framework Security" this method
659 // iterates all possible keys then return null
660 protected virtual AsymmetricAlgorithm GetPublicKey ()
662 if (m_signature.KeyInfo == null)
663 return null;
665 if (pkEnumerator == null) {
666 pkEnumerator = m_signature.KeyInfo.GetEnumerator ();
669 if (pkEnumerator.MoveNext ()) {
670 AsymmetricAlgorithm key = null;
671 KeyInfoClause kic = (KeyInfoClause) pkEnumerator.Current;
673 if (kic is DSAKeyValue)
674 key = DSA.Create ();
675 else if (kic is RSAKeyValue)
676 key = RSA.Create ();
678 if (key != null) {
679 key.FromXmlString (kic.GetXml ().InnerXml);
680 return key;
683 return null;
686 public XmlElement GetXml ()
688 return m_signature.GetXml (envdoc);
691 public void LoadXml (XmlElement value)
693 if (value == null)
694 throw new ArgumentNullException ("value");
696 signatureElement = value;
697 m_signature.LoadXml (value);
698 #if NET_2_0
699 // Need to give the EncryptedXml object to the
700 // XmlDecryptionTransform to give it a fighting
701 // chance at decrypting the document.
702 foreach (Reference r in m_signature.SignedInfo.References) {
703 foreach (Transform t in r.TransformChain) {
704 if (t is XmlDecryptionTransform)
705 ((XmlDecryptionTransform) t).EncryptedXml = EncryptedXml;
708 #endif
711 #if NET_1_1
712 [ComVisible (false)]
713 public XmlResolver Resolver {
714 set { xmlResolver = value; }
716 #endif