2 // SignedXml.cs - SignedXml implementation for XML Signature
5 // Sebastien Pouliot <sebastien@ximian.com>
6 // Atsushi Enomoto <atsushi@ximian.com>
7 // Tim Coleman <tim@timcoleman.com>
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:
23 // The above copyright notice and this permission notice shall be
24 // included in all copies or substantial portions of the Software.
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;
37 using System.Runtime.InteropServices;
38 using System.Security.Cryptography;
39 using System.Security.Policy;
45 using System.Security.Cryptography.X509Certificates;
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";
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;
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
84 private XmlResolver xmlResolver = new XmlSecureResolver (new XmlUrlResolver (), new Evidence ());
86 private XmlResolver xmlResolver = new XmlUrlResolver ();
88 private ArrayList manifests;
90 private static readonly char [] whitespaceChars = new char [] {' ', '\r', '\n', '\t'};
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");
106 public SignedXml (XmlElement elem) : this ()
109 throw new ArgumentNullException ("elem");
110 envdoc = new XmlDocument ();
111 envdoc.LoadXml (elem.OuterXml);
115 public EncryptedXml EncryptedXml {
116 get { return encryptedXml; }
117 set { encryptedXml = value; }
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 {
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 input = (XmlDocument) input.Clone ();
177 if (t is XmlDsigEnvelopedSignatureTransform)
178 // It returns XmlDocument for XmlDocument input.
179 return CanonicalizeOutput (t.GetOutput ());
181 object obj = t.GetOutput ();
184 else if (obj is XmlDocument) {
185 MemoryStream ms = new MemoryStream ();
186 XmlTextWriter xtw = new XmlTextWriter (ms, Encoding.UTF8);
187 ((XmlDocument) obj).WriteTo (xtw);
190 else if (obj == null) {
191 throw new NotImplementedException ("This should not occur. Transform is " + t + ".");
194 // e.g. XmlDsigXPathTransform returns XmlNodeList
195 return CanonicalizeOutput (obj);
199 private Stream CanonicalizeOutput (object obj)
201 Transform c14n = GetC14NMethod ();
202 c14n.LoadInput (obj);
203 return (Stream) c14n.GetOutput ();
206 private XmlDocument GetManifest (Reference r)
208 XmlDocument doc = new XmlDocument ();
209 doc.PreserveWhitespace = true;
211 if (r.Uri [0] == '#') {
213 if (signatureElement != null) {
214 XmlElement xel = GetIdElement (signatureElement.OwnerDocument, r.Uri.Substring (1));
216 throw new CryptographicException ("Manifest targeted by Reference was not found: " + r.Uri.Substring (1));
217 doc.LoadXml (xel.OuterXml);
218 FixupNamespaceNodes (xel, doc.DocumentElement);
221 else if (xmlResolver != null) {
222 // TODO: need testing
223 Stream s = (Stream) xmlResolver.GetEntity (new Uri (r.Uri), null, typeof (Stream));
227 if (doc.FirstChild != null) {
228 // keep a copy of the manifests to check their references later
229 if (manifests == null)
230 manifests = new ArrayList ();
238 private void FixupNamespaceNodes (XmlElement src, XmlElement dst)
240 // add namespace nodes
241 foreach (XmlAttribute attr in src.SelectNodes ("namespace::*")) {
242 if (attr.LocalName == "xml")
244 if (attr.OwnerElement == src)
246 dst.SetAttributeNode (dst.OwnerDocument.ImportNode (attr, true) as XmlAttribute);
250 [MonoTODO ("Need testing")]
251 private byte[] GetReferenceHash (Reference r)
254 XmlDocument doc = null;
255 if (r.Uri == String.Empty) {
258 else if (r.Type == XmlSignature.Uri.Manifest) {
259 doc = GetManifest (r);
262 doc = new XmlDocument ();
263 doc.PreserveWhitespace = true;
264 string objectName = null;
266 if (r.Uri.StartsWith ("#xpointer")) {
267 string uri = string.Join ("", r.Uri.Substring (9).Split (whitespaceChars));
268 if (uri.Length < 2 || uri [0] != '(' || uri [uri.Length - 1] != ')')
269 // FIXME: how to handle invalid xpointer?
272 uri = uri.Substring (1, uri.Length - 2);
275 else if (uri.Length > 6 && uri.StartsWith ("id(") && uri [uri.Length - 1] == ')')
276 // id('foo'), id("foo")
277 objectName = uri.Substring (4, uri.Length - 6);
279 else if (r.Uri [0] == '#') {
280 objectName = r.Uri.Substring (1);
282 else if (xmlResolver != null) {
283 // TODO: test but doc says that Resolver = null -> no access
285 // no way to know if valid without throwing an exception
286 Uri uri = new Uri (r.Uri);
287 s = (Stream) xmlResolver.GetEntity (new Uri (r.Uri), null, typeof (Stream));
290 // may still be a local file (and maybe not xml)
291 s = File.OpenRead (r.Uri);
294 if (objectName != null) {
295 foreach (DataObject obj in m_signature.ObjectList) {
296 if (obj.Id == objectName) {
297 XmlElement xel = obj.GetXml ();
298 doc.LoadXml (xel.OuterXml);
299 FixupNamespaceNodes (xel, doc.DocumentElement);
306 if (r.TransformChain.Count > 0) {
307 foreach (Transform t in r.TransformChain) {
309 s = ApplyTransform (t, doc);
313 object o = t.GetOutput ();
317 s = CanonicalizeOutput (o);
321 else if (s == null) {
322 // we must not C14N references from outside the document
323 // e.g. non-xml documents
324 if (r.Uri [0] != '#') {
325 s = new MemoryStream ();
329 // apply default C14N transformation
330 s = ApplyTransform (new XmlDsigC14NTransform (), doc);
333 HashAlgorithm digest = GetHash (r.DigestMethod);
334 return digest.ComputeHash (s);
337 private void DigestReferences ()
339 // we must tell each reference which hash algorithm to use
340 // before asking for the SignedInfo XML !
341 foreach (Reference r in m_signature.SignedInfo.References) {
342 // assume SHA-1 if nothing is specified
343 if (r.DigestMethod == null)
344 r.DigestMethod = XmlDsigSHA1Url;
345 r.DigestValue = GetReferenceHash (r);
349 private Transform GetC14NMethod ()
351 Transform t = (Transform) CryptoConfig.CreateFromName (m_signature.SignedInfo.CanonicalizationMethod);
353 throw new CryptographicException ("Unknown Canonicalization Method {0}", m_signature.SignedInfo.CanonicalizationMethod);
357 private Stream SignedInfoTransformed ()
359 Transform t = GetC14NMethod ();
361 if (signatureElement == null) {
362 // when creating signatures
363 XmlDocument doc = new XmlDocument ();
364 doc.PreserveWhitespace = true;
365 doc.LoadXml (m_signature.SignedInfo.GetXml ().OuterXml);
367 foreach (XmlAttribute attr in envdoc.DocumentElement.SelectNodes ("namespace::*")) {
368 if (attr.LocalName == "xml")
370 if (attr.Prefix == doc.DocumentElement.Prefix)
372 doc.DocumentElement.SetAttributeNode (doc.ImportNode (attr, true) as XmlAttribute);
377 // when verifying signatures
378 // TODO - check m_signature.SignedInfo.Id
379 XmlElement el = signatureElement.GetElementsByTagName (XmlSignature.ElementNames.SignedInfo, XmlSignature.NamespaceURI) [0] as XmlElement;
380 StringWriter sw = new StringWriter ();
381 XmlTextWriter xtw = new XmlTextWriter (sw);
382 xtw.WriteStartElement (el.Prefix, el.LocalName, el.NamespaceURI);
384 // context namespace nodes (except for "xmlns:xml")
385 XmlNodeList nl = el.SelectNodes ("namespace::*");
386 foreach (XmlAttribute attr in nl) {
387 if (attr.ParentNode == el)
389 if (attr.LocalName == "xml")
391 if (attr.Prefix == el.Prefix)
395 foreach (XmlNode attr in el.Attributes)
397 foreach (XmlNode n in el.ChildNodes)
400 xtw.WriteEndElement ();
401 byte [] si = Encoding.UTF8.GetBytes (sw.ToString ());
403 MemoryStream ms = new MemoryStream ();
404 ms.Write (si, 0, si.Length);
409 // C14N and C14NWithComments always return a Stream in GetOutput
410 return (Stream) t.GetOutput ();
413 // reuse hash - most document will always use the same hash
414 private HashAlgorithm GetHash (string algorithm)
416 HashAlgorithm hash = (HashAlgorithm) hashes [algorithm];
418 hash = HashAlgorithm.Create (algorithm);
420 throw new CryptographicException ("Unknown hash algorithm: {0}", algorithm);
421 hashes.Add (algorithm, hash);
422 // now ready to be used
425 // important before reusing an hash object
431 public bool CheckSignature ()
433 return (CheckSignatureInternal (null) != null);
436 private bool CheckReferenceIntegrity (ArrayList referenceList)
438 if (referenceList == null)
441 // check digest (hash) for every reference
442 foreach (Reference r in referenceList) {
443 // stop at first broken reference
444 byte[] hash = GetReferenceHash (r);
445 if (! Compare (r.DigestValue, hash))
451 public bool CheckSignature (AsymmetricAlgorithm key)
454 throw new ArgumentNullException ("key");
455 return (CheckSignatureInternal (key) != null);
458 private AsymmetricAlgorithm CheckSignatureInternal (AsymmetricAlgorithm key)
463 // check with supplied key
464 if (!CheckSignatureWithKey (key))
468 if (Signature.KeyInfo == null)
469 throw new CryptographicException ("At least one KeyInfo is required.");
470 // no supplied key, iterates all KeyInfo
471 while ((key = GetPublicKey ()) != null) {
472 if (CheckSignatureWithKey (key)) {
481 // some parts may need to be downloaded
482 // so where doing it last
483 if (!CheckReferenceIntegrity (m_signature.SignedInfo.References))
486 if (manifests != null) {
487 // do not use foreach as a manifest could contain manifests...
488 for (int i=0; i < manifests.Count; i++) {
489 Manifest manifest = new Manifest ((manifests [i] as XmlDocument).DocumentElement);
490 if (! CheckReferenceIntegrity (manifest.References))
497 // Is the signature (over SignedInfo) valid ?
498 private bool CheckSignatureWithKey (AsymmetricAlgorithm key)
503 SignatureDescription sd = (SignatureDescription) CryptoConfig.CreateFromName (m_signature.SignedInfo.SignatureMethod);
507 AsymmetricSignatureDeformatter verifier = (AsymmetricSignatureDeformatter) CryptoConfig.CreateFromName (sd.DeformatterAlgorithm);
508 if (verifier == null)
512 verifier.SetKey (key);
513 verifier.SetHashAlgorithm (sd.DigestAlgorithm);
515 HashAlgorithm hash = GetHash (sd.DigestAlgorithm);
516 // get the hash of the C14N SignedInfo element
517 MemoryStream ms = (MemoryStream) SignedInfoTransformed ();
519 byte[] digest = hash.ComputeHash (ms);
520 return verifier.VerifySignature (digest, m_signature.SignatureValue);
523 // e.g. SignatureMethod != AsymmetricAlgorithm type
528 private bool Compare (byte[] expected, byte[] actual)
530 bool result = ((expected != null) && (actual != null));
532 int l = expected.Length;
533 result = (l == actual.Length);
535 for (int i=0; i < l; i++) {
536 if (expected[i] != actual[i])
544 public bool CheckSignature (KeyedHashAlgorithm macAlg)
547 throw new ArgumentNullException ("macAlg");
551 // Is the signature (over SignedInfo) valid ?
552 Stream s = SignedInfoTransformed ();
556 byte[] actual = macAlg.ComputeHash (s);
557 // HMAC signature may be partial
558 if (m_signature.SignedInfo.SignatureLength != null) {
559 int length = actual.Length;
561 // SignatureLength is in bits
562 length = (Int32.Parse (m_signature.SignedInfo.SignatureLength) >> 3);
567 if (length != actual.Length) {
568 byte[] trunked = new byte [length];
569 Buffer.BlockCopy (actual, 0, trunked, 0, length);
574 if (Compare (m_signature.SignatureValue, actual)) {
575 // some parts may need to be downloaded
576 // so where doing it last
577 return CheckReferenceIntegrity (m_signature.SignedInfo.References);
584 public bool CheckSignature (X509CertificateEx certificate, bool verifySignatureOnly)
586 throw new NotImplementedException ();
590 public bool CheckSignatureReturningKey (out AsymmetricAlgorithm signingKey)
592 signingKey = CheckSignatureInternal (null);
593 return (signingKey != null);
596 public void ComputeSignature ()
599 // required before hashing
600 m_signature.SignedInfo.SignatureMethod = key.SignatureAlgorithm;
603 AsymmetricSignatureFormatter signer = null;
604 // in need for a CryptoConfig factory
606 signer = new DSASignatureFormatter (key);
608 signer = new RSAPKCS1SignatureFormatter (key);
610 if (signer != null) {
611 SignatureDescription sd = (SignatureDescription) CryptoConfig.CreateFromName (m_signature.SignedInfo.SignatureMethod);
613 HashAlgorithm hash = GetHash (sd.DigestAlgorithm);
614 // get the hash of the C14N SignedInfo element
615 byte[] digest = hash.ComputeHash (SignedInfoTransformed ());
617 signer.SetHashAlgorithm ("SHA1");
618 m_signature.SignatureValue = signer.CreateSignature (digest);
623 public void ComputeSignature (KeyedHashAlgorithm macAlg)
626 throw new ArgumentNullException ("macAlg");
628 if (macAlg is HMACSHA1) {
631 m_signature.SignedInfo.SignatureMethod = XmlDsigHMACSHA1Url;
632 m_signature.SignatureValue = macAlg.ComputeHash (SignedInfoTransformed ());
635 throw new CryptographicException ("unsupported algorithm");
638 public virtual XmlElement GetIdElement (XmlDocument document, string idValue)
640 // this works only if there's a DTD or XSD available to define the ID
641 XmlElement xel = document.GetElementById (idValue);
643 // search an "undefined" ID
644 xel = (XmlElement) document.SelectSingleNode ("//*[@Id='" + idValue + "']");
649 // According to book ".NET Framework Security" this method
650 // iterates all possible keys then return null
651 protected virtual AsymmetricAlgorithm GetPublicKey ()
653 if (m_signature.KeyInfo == null)
656 if (pkEnumerator == null) {
657 pkEnumerator = m_signature.KeyInfo.GetEnumerator ();
660 if (pkEnumerator.MoveNext ()) {
661 AsymmetricAlgorithm key = null;
662 KeyInfoClause kic = (KeyInfoClause) pkEnumerator.Current;
664 if (kic is DSAKeyValue)
666 else if (kic is RSAKeyValue)
670 key.FromXmlString (kic.GetXml ().InnerXml);
677 public XmlElement GetXml ()
679 return m_signature.GetXml ();
682 public void LoadXml (XmlElement value)
685 throw new ArgumentNullException ("value");
687 signatureElement = value;
688 m_signature.LoadXml (value);
693 public XmlResolver Resolver {
694 set { xmlResolver = value; }