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
174 || t
is XmlDecryptionTransform
177 input
= (XmlDocument
) input
.Clone ();
181 if (t
is XmlDsigEnvelopedSignatureTransform
)
182 // It returns XmlDocument for XmlDocument input.
183 return CanonicalizeOutput (t
.GetOutput ());
185 object obj
= t
.GetOutput ();
188 else if (obj
is XmlDocument
) {
189 MemoryStream ms
= new MemoryStream ();
190 XmlTextWriter xtw
= new XmlTextWriter (ms
, Encoding
.UTF8
);
191 ((XmlDocument
) obj
).WriteTo (xtw
);
195 // Rewind to the start of the stream
199 else if (obj
== null) {
200 throw new NotImplementedException ("This should not occur. Transform is " + t
+ ".");
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] == '#') {
222 if (signatureElement
!= null) {
223 XmlElement xel
= GetIdElement (signatureElement
.OwnerDocument
, r
.Uri
.Substring (1));
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
));
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 ();
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")
253 if (attr
.OwnerElement
== src
)
255 dst
.SetAttributeNode (dst
.OwnerDocument
.ImportNode (attr
, true) as XmlAttribute
);
259 [MonoTODO ("Need testing")]
260 private byte[] GetReferenceHash (Reference r
)
263 XmlDocument doc
= null;
264 if (r
.Uri
== String
.Empty
) {
267 else if (r
.Type
== XmlSignature
.Uri
.Manifest
) {
268 doc
= GetManifest (r
);
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?
281 uri
= uri
.Substring (1, uri
.Length
- 2);
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
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
));
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
);
315 if (r
.TransformChain
.Count
> 0) {
316 foreach (Transform t
in r
.TransformChain
) {
318 s
= ApplyTransform (t
, doc
);
322 object o
= t
.GetOutput ();
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 ();
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
);
362 throw new CryptographicException ("Unknown Canonicalization Method {0}", m_signature
.SignedInfo
.CanonicalizationMethod
);
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
);
376 foreach (XmlAttribute attr
in envdoc
.DocumentElement
.SelectNodes ("namespace::*")) {
377 if (attr
.LocalName
== "xml")
379 if (attr
.Prefix
== doc
.DocumentElement
.Prefix
)
381 doc
.DocumentElement
.SetAttributeNode (doc
.ImportNode (attr
, true) as XmlAttribute
);
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
)
398 if (attr
.LocalName
== "xml")
400 if (attr
.Prefix
== el
.Prefix
)
404 foreach (XmlNode attr
in el
.Attributes
)
406 foreach (XmlNode n
in el
.ChildNodes
)
409 xtw
.WriteEndElement ();
410 byte [] si
= Encoding
.UTF8
.GetBytes (sw
.ToString ());
412 MemoryStream ms
= new MemoryStream ();
413 ms
.Write (si
, 0, si
.Length
);
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
];
427 hash
= HashAlgorithm
.Create (algorithm
);
429 throw new CryptographicException ("Unknown hash algorithm: {0}", algorithm
);
430 hashes
.Add (algorithm
, hash
);
431 // now ready to be used
434 // important before reusing an hash object
440 public bool CheckSignature ()
442 return (CheckSignatureInternal (null) != null);
445 private bool CheckReferenceIntegrity (ArrayList referenceList
)
447 if (referenceList
== null)
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
))
460 public bool CheckSignature (AsymmetricAlgorithm key
)
463 throw new ArgumentNullException ("key");
464 return (CheckSignatureInternal (key
) != null);
467 private AsymmetricAlgorithm
CheckSignatureInternal (AsymmetricAlgorithm key
)
472 // check with supplied key
473 if (!CheckSignatureWithKey (key
))
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
)) {
490 // some parts may need to be downloaded
491 // so where doing it last
492 if (!CheckReferenceIntegrity (m_signature
.SignedInfo
.References
))
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
))
506 // Is the signature (over SignedInfo) valid ?
507 private bool CheckSignatureWithKey (AsymmetricAlgorithm key
)
512 SignatureDescription sd
= (SignatureDescription
) CryptoConfig
.CreateFromName (m_signature
.SignedInfo
.SignatureMethod
);
516 AsymmetricSignatureDeformatter verifier
= (AsymmetricSignatureDeformatter
) CryptoConfig
.CreateFromName (sd
.DeformatterAlgorithm
);
517 if (verifier
== null)
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
);
532 // e.g. SignatureMethod != AsymmetricAlgorithm type
537 private bool Compare (byte[] expected
, byte[] actual
)
539 bool result
= ((expected
!= null) && (actual
!= null));
541 int l
= expected
.Length
;
542 result
= (l
== actual
.Length
);
544 for (int i
=0; i
< l
; i
++) {
545 if (expected
[i
] != actual
[i
])
553 public bool CheckSignature (KeyedHashAlgorithm macAlg
)
556 throw new ArgumentNullException ("macAlg");
560 // Is the signature (over SignedInfo) valid ?
561 Stream s
= SignedInfoTransformed ();
565 byte[] actual
= macAlg
.ComputeHash (s
);
566 // HMAC signature may be partial
567 if (m_signature
.SignedInfo
.SignatureLength
!= null) {
568 int length
= actual
.Length
;
570 // SignatureLength is in bits
571 length
= (Int32
.Parse (m_signature
.SignedInfo
.SignatureLength
) >> 3);
576 if (length
!= actual
.Length
) {
577 byte[] trunked
= new byte [length
];
578 Buffer
.BlockCopy (actual
, 0, trunked
, 0, length
);
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
);
593 public bool CheckSignature (X509CertificateEx certificate
, bool verifySignatureOnly
)
595 throw new NotImplementedException ();
599 public bool CheckSignatureReturningKey (out AsymmetricAlgorithm signingKey
)
601 signingKey
= CheckSignatureInternal (null);
602 return (signingKey
!= null);
605 public void ComputeSignature ()
608 // required before hashing
609 m_signature
.SignedInfo
.SignatureMethod
= key
.SignatureAlgorithm
;
612 AsymmetricSignatureFormatter signer
= null;
613 // in need for a CryptoConfig factory
615 signer
= new DSASignatureFormatter (key
);
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
)
635 throw new ArgumentNullException ("macAlg");
637 if (macAlg
is HMACSHA1
) {
640 m_signature
.SignedInfo
.SignatureMethod
= XmlDsigHMACSHA1Url
;
641 m_signature
.SignatureValue
= macAlg
.ComputeHash (SignedInfoTransformed ());
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
);
652 // search an "undefined" ID
653 xel
= (XmlElement
) document
.SelectSingleNode ("//*[@Id='" + idValue
+ "']");
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)
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
)
675 else if (kic
is RSAKeyValue
)
679 key
.FromXmlString (kic
.GetXml ().InnerXml
);
686 public XmlElement
GetXml ()
688 return m_signature
.GetXml (envdoc
);
691 public void LoadXml (XmlElement
value)
694 throw new ArgumentNullException ("value");
696 signatureElement
= value;
697 m_signature
.LoadXml (value);
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
;
713 public XmlResolver Resolver
{
714 set { xmlResolver = value; }