2 // SignedXml.cs - SignedXml implementation for XML Signature
5 // Sebastien Pouliot <sebastien@ximian.com>
6 // Atsushi Enomoto <atsushi@ximian.com>
8 // (C) 2002, 2003 Motus Technologies Inc. (http://www.motus.com)
9 // (C) 2004 Novell (http://www.novell.com)
12 using System.Collections;
14 using System.Runtime.InteropServices;
15 using System.Security.Cryptography;
16 using System.Security.Policy;
21 namespace System.Security.Cryptography.Xml {
23 public class SignedXml {
25 private Signature signature;
26 private AsymmetricAlgorithm key;
27 private string keyName;
28 private XmlDocument envdoc;
29 private IEnumerator pkEnumerator;
30 private XmlElement signatureElement;
31 private Hashtable hashes;
32 // FIXME: enable it after CAS implementation
34 private XmlResolver xmlResolver = new XmlSecureResolver (new XmlUrlResolver (), new Evidence ());
36 private XmlResolver xmlResolver = new XmlUrlResolver ();
38 private ArrayList manifests;
42 signature = new Signature ();
43 signature.SignedInfo = new SignedInfo ();
44 hashes = new Hashtable (2); // 98% SHA1 for now
47 public SignedXml (XmlDocument document) : this ()
50 throw new ArgumentNullException ("document");
54 public SignedXml (XmlElement elem) : this ()
57 throw new ArgumentNullException ("elem");
58 envdoc = new XmlDocument ();
59 envdoc.LoadXml (elem.OuterXml);
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";
71 public KeyInfo KeyInfo {
72 get { return signature.KeyInfo; }
73 set { signature.KeyInfo = value; }
76 public Signature Signature {
77 get { return signature; }
80 public string SignatureLength {
81 get { return signature.SignedInfo.SignatureLength; }
84 public string SignatureMethod {
85 get { return signature.SignedInfo.SignatureMethod; }
88 public byte[] SignatureValue {
89 get { return signature.SignatureValue; }
92 public SignedInfo SignedInfo {
93 get { return signature.SignedInfo; }
96 public AsymmetricAlgorithm SigningKey {
101 // NOTE: CryptoAPI related ? documented as fx internal
102 public string SigningKeyName {
103 get { return keyName; }
104 set { keyName = value; }
107 public void AddObject (DataObject dataObject)
109 signature.AddObject (dataObject);
112 public void AddReference (Reference reference)
114 signature.SignedInfo.AddReference (reference);
117 private Stream ApplyTransform (Transform t, XmlDocument input)
119 // These transformer modify input document, which should
120 // not affect to the input itself.
121 if (t is XmlDsigXPathTransform ||
122 t is XmlDsigEnvelopedSignatureTransform)
123 input = (XmlDocument) input.Clone ();
127 if (t is XmlDsigEnvelopedSignatureTransform)
128 // It returns XmlDocument for XmlDocument input.
129 return CanonicalizeOutput (t.GetOutput ());
131 object obj = t.GetOutput ();
134 else if (obj is XmlDocument) {
135 MemoryStream ms = new MemoryStream ();
136 XmlTextWriter xtw = new XmlTextWriter (ms, Encoding.UTF8);
137 ((XmlDocument) obj).WriteTo (xtw);
140 else if (obj == null) {
141 throw new NotImplementedException ("This should not occur. Transform is " + t + ".");
144 // e.g. XmlDsigXPathTransform returns XmlNodeList
145 return CanonicalizeOutput (obj);
149 private Stream CanonicalizeOutput (object obj)
151 Transform c14n = GetC14NMethod ();
152 c14n.LoadInput (obj);
153 return (Stream) c14n.GetOutput ();
156 private XmlDocument GetManifest (Reference r)
158 XmlDocument doc = new XmlDocument ();
159 doc.PreserveWhitespace = true;
161 if (r.Uri [0] == '#') {
163 if (signatureElement != null) {
164 XmlElement xel = GetIdElement (signatureElement.OwnerDocument, r.Uri.Substring (1));
166 throw new CryptographicException ("Manifest targeted by Reference was not found: " + r.Uri.Substring (1));
167 doc.LoadXml (xel.OuterXml);
168 FixupNamespaceNodes (xel, doc.DocumentElement);
171 else if (xmlResolver != null) {
172 // TODO: need testing
173 Stream s = (Stream) xmlResolver.GetEntity (new Uri (r.Uri), null, typeof (Stream));
177 if (doc.FirstChild != null) {
178 // keep a copy of the manifests to check their references later
179 if (manifests == null)
180 manifests = new ArrayList ();
188 private void FixupNamespaceNodes (XmlElement src, XmlElement dst)
190 // add namespace nodes
191 foreach (XmlAttribute attr in src.SelectNodes ("namespace::*")) {
192 if (attr.LocalName == "xml")
194 if (attr.OwnerElement == src)
196 dst.SetAttributeNode (dst.OwnerDocument.ImportNode (attr, true) as XmlAttribute);
200 [MonoTODO ("Need testing")]
201 private byte[] GetReferenceHash (Reference r)
204 XmlDocument doc = null;
205 if (r.Uri == String.Empty) {
208 else if (r.Type == XmlSignature.Uri.Manifest) {
209 doc = GetManifest (r);
212 doc = new XmlDocument ();
213 doc.PreserveWhitespace = true;
215 if (r.Uri [0] == '#') {
216 foreach (DataObject obj in signature.ObjectList) {
217 if ("#" + obj.Id == r.Uri) {
218 XmlElement xel = obj.GetXml ();
219 doc.LoadXml (xel.OuterXml);
220 FixupNamespaceNodes (xel, doc.DocumentElement);
225 else if (xmlResolver != null) {
226 // TODO: test but doc says that Resolver = null -> no access
228 // no way to know if valid without throwing an exception
229 Uri uri = new Uri (r.Uri);
230 s = (Stream) xmlResolver.GetEntity (new Uri (r.Uri), null, typeof (Stream));
233 // may still be a local file (and maybe not xml)
234 s = File.OpenRead (r.Uri);
239 if (r.TransformChain.Count > 0) {
240 foreach (Transform t in r.TransformChain) {
242 s = ApplyTransform (t, doc);
246 object o = t.GetOutput ();
250 s = CanonicalizeOutput (o);
254 else if (s == null) {
255 // we must not C14N references from outside the document
256 // e.g. non-xml documents
257 if (r.Uri [0] != '#') {
258 s = new MemoryStream ();
262 // apply default C14N transformation
263 s = ApplyTransform (new XmlDsigC14NTransform (), doc);
267 HashAlgorithm digest = GetHash (r.DigestMethod);
268 return digest.ComputeHash (s);
271 private void DigestReferences ()
273 // we must tell each reference which hash algorithm to use
274 // before asking for the SignedInfo XML !
275 foreach (Reference r in signature.SignedInfo.References) {
276 // assume SHA-1 if nothing is specified
277 if (r.DigestMethod == null)
278 r.DigestMethod = XmlDsigSHA1Url;
279 r.DigestValue = GetReferenceHash (r);
283 private Transform GetC14NMethod ()
285 Transform t = (Transform) CryptoConfig.CreateFromName (signature.SignedInfo.CanonicalizationMethod);
287 throw new CryptographicException ("Unknown Canonicalization Method {0}", signature.SignedInfo.CanonicalizationMethod);
291 private Stream SignedInfoTransformed ()
293 Transform t = GetC14NMethod ();
295 if (signatureElement == null) {
296 // when creating signatures
297 XmlDocument doc = new XmlDocument ();
298 doc.PreserveWhitespace = true;
299 doc.LoadXml (signature.SignedInfo.GetXml ().OuterXml);
304 // when verifying signatures
305 // TODO - check signature.SignedInfo.Id
306 XmlElement el = signatureElement.GetElementsByTagName (XmlSignature.ElementNames.SignedInfo, XmlSignature.NamespaceURI) [0] as XmlElement;
307 StringWriter sw = new StringWriter ();
308 XmlTextWriter xtw = new XmlTextWriter (sw);
309 xtw.WriteStartElement (el.Prefix, el.LocalName, el.NamespaceURI);
311 // context namespace nodes (except for "xmlns:xml")
312 XmlNodeList nl = el.SelectNodes ("namespace::*");
313 foreach (XmlAttribute attr in nl) {
314 if (attr.ParentNode == el)
316 if (attr.LocalName == "xml")
321 foreach (XmlNode attr in el.Attributes)
323 foreach (XmlNode n in el.ChildNodes)
326 xtw.WriteEndElement ();
328 byte [] si = Encoding.UTF8.GetBytes (sw.ToString ());
330 MemoryStream ms = new MemoryStream ();
331 ms.Write (si, 0, si.Length);
336 // C14N and C14NWithComments always return a Stream in GetOutput
337 return (Stream) t.GetOutput ();
340 // reuse hash - most document will always use the same hash
341 private HashAlgorithm GetHash (string algorithm)
343 HashAlgorithm hash = (HashAlgorithm) hashes [algorithm];
345 hash = HashAlgorithm.Create (algorithm);
347 throw new CryptographicException ("Unknown hash algorithm: {0}", algorithm);
348 hashes.Add (algorithm, hash);
349 // now ready to be used
352 // important before reusing an hash object
358 public bool CheckSignature ()
360 return (CheckSignatureInternal (null) != null);
363 private bool CheckReferenceIntegrity (ArrayList referenceList)
365 if (referenceList == null)
368 // check digest (hash) for every reference
369 foreach (Reference r in referenceList) {
370 // stop at first broken reference
371 byte[] hash = GetReferenceHash (r);
372 if (! Compare (r.DigestValue, hash))
378 public bool CheckSignature (AsymmetricAlgorithm key)
381 throw new ArgumentNullException ("key");
382 return (CheckSignatureInternal (key) != null);
385 private AsymmetricAlgorithm CheckSignatureInternal (AsymmetricAlgorithm key)
390 // check with supplied key
391 if (!CheckSignatureWithKey (key))
395 if (Signature.KeyInfo == null)
396 throw new CryptographicException ("At least one KeyInfo is required.");
397 // no supplied key, iterates all KeyInfo
398 while ((key = GetPublicKey ()) != null) {
399 if (CheckSignatureWithKey (key)) {
408 // some parts may need to be downloaded
409 // so where doing it last
410 if (! CheckReferenceIntegrity (signature.SignedInfo.References))
413 if (manifests != null) {
414 // do not use foreach as a manifest could contain manifests...
415 for (int i=0; i < manifests.Count; i++) {
416 Manifest manifest = new Manifest ((manifests [i] as XmlDocument).DocumentElement);
417 if (! CheckReferenceIntegrity (manifest.References))
424 // Is the signature (over SignedInfo) valid ?
425 private bool CheckSignatureWithKey (AsymmetricAlgorithm key)
430 SignatureDescription sd = (SignatureDescription) CryptoConfig.CreateFromName (signature.SignedInfo.SignatureMethod);
434 AsymmetricSignatureDeformatter verifier = (AsymmetricSignatureDeformatter) CryptoConfig.CreateFromName (sd.DeformatterAlgorithm);
435 if (verifier == null)
439 verifier.SetKey (key);
440 verifier.SetHashAlgorithm (sd.DigestAlgorithm);
442 HashAlgorithm hash = GetHash (sd.DigestAlgorithm);
443 // get the hash of the C14N SignedInfo element
444 MemoryStream ms = (MemoryStream) SignedInfoTransformed ();
445 byte[] debug = ms.ToArray ();
446 byte[] digest = hash.ComputeHash (ms);
447 return verifier.VerifySignature (digest, signature.SignatureValue);
450 // e.g. SignatureMethod != AsymmetricAlgorithm type
455 private bool Compare (byte[] expected, byte[] actual)
457 bool result = ((expected != null) && (actual != null));
459 int l = expected.Length;
460 result = (l == actual.Length);
462 for (int i=0; i < l; i++) {
463 if (expected[i] != actual[i])
471 public bool CheckSignature (KeyedHashAlgorithm macAlg)
474 throw new ArgumentNullException ("macAlg");
478 // Is the signature (over SignedInfo) valid ?
479 Stream s = SignedInfoTransformed ();
483 byte[] actual = macAlg.ComputeHash (s);
484 // HMAC signature may be partial
485 if (signature.SignedInfo.SignatureLength != null) {
486 int length = actual.Length;
488 // SignatureLength is in bits
489 length = (Int32.Parse (signature.SignedInfo.SignatureLength) >> 3);
494 if (length != actual.Length) {
495 byte[] trunked = new byte [length];
496 Buffer.BlockCopy (actual, 0, trunked, 0, length);
501 if (Compare (signature.SignatureValue, actual)) {
502 // some parts may need to be downloaded
503 // so where doing it last
504 return CheckReferenceIntegrity (signature.SignedInfo.References);
509 public bool CheckSignatureReturningKey (out AsymmetricAlgorithm signingKey)
511 signingKey = CheckSignatureInternal (null);
512 return (signingKey != null);
515 public void ComputeSignature ()
518 // required before hashing
519 signature.SignedInfo.SignatureMethod = key.SignatureAlgorithm;
522 AsymmetricSignatureFormatter signer = null;
523 // in need for a CryptoConfig factory
525 signer = new DSASignatureFormatter (key);
527 signer = new RSAPKCS1SignatureFormatter (key);
529 if (signer != null) {
530 SignatureDescription sd = (SignatureDescription) CryptoConfig.CreateFromName (signature.SignedInfo.SignatureMethod);
532 HashAlgorithm hash = GetHash (sd.DigestAlgorithm);
533 // get the hash of the C14N SignedInfo element
534 byte[] digest = hash.ComputeHash (SignedInfoTransformed ());
536 signer.SetHashAlgorithm ("SHA1");
537 signature.SignatureValue = signer.CreateSignature (digest);
542 public void ComputeSignature (KeyedHashAlgorithm macAlg)
545 throw new ArgumentNullException ("macAlg");
547 if (macAlg is HMACSHA1) {
550 signature.SignedInfo.SignatureMethod = XmlDsigHMACSHA1Url;
551 signature.SignatureValue = macAlg.ComputeHash (SignedInfoTransformed ());
554 throw new CryptographicException ("unsupported algorithm");
557 public virtual XmlElement GetIdElement (XmlDocument document, string idValue)
559 // this works only if there's a DTD or XSD available to define the ID
560 XmlElement xel = document.GetElementById (idValue);
562 // search an "undefined" ID
563 xel = (XmlElement) document.SelectSingleNode ("//*[@Id='" + idValue + "']");
568 // According to book ".NET Framework Security" this method
569 // iterates all possible keys then return null
570 protected virtual AsymmetricAlgorithm GetPublicKey ()
572 if (signature.KeyInfo == null)
575 if (pkEnumerator == null) {
576 pkEnumerator = signature.KeyInfo.GetEnumerator ();
579 if (pkEnumerator.MoveNext ()) {
580 AsymmetricAlgorithm key = null;
581 KeyInfoClause kic = (KeyInfoClause) pkEnumerator.Current;
583 if (kic is DSAKeyValue)
585 else if (kic is RSAKeyValue)
589 key.FromXmlString (kic.GetXml ().InnerXml);
596 public XmlElement GetXml ()
598 return signature.GetXml ();
601 public void LoadXml (XmlElement value)
604 throw new ArgumentNullException ("value");
606 signatureElement = value;
607 signature.LoadXml (value);
612 public XmlResolver Resolver {
613 set { xmlResolver = value; }