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;
20 namespace System.Security.Cryptography.Xml {
22 public class SignedXml {
24 private Signature signature;
25 private AsymmetricAlgorithm key;
26 private string keyName;
27 private XmlDocument envdoc;
28 private IEnumerator pkEnumerator;
29 private XmlElement signatureElement;
30 private Hashtable hashes;
34 signature = new Signature ();
35 signature.SignedInfo = new SignedInfo ();
36 hashes = new Hashtable (2); // 98% SHA1 for now
39 public SignedXml (XmlDocument document) : this ()
42 throw new ArgumentNullException ("document");
46 public SignedXml (XmlElement elem) : this ()
49 throw new ArgumentNullException ("elem");
50 envdoc = new XmlDocument ();
51 envdoc.LoadXml (elem.OuterXml);
54 public const string XmlDsigCanonicalizationUrl = "http://www.w3.org/TR/2001/REC-xml-c14n-20010315";
55 public const string XmlDsigCanonicalizationWithCommentsUrl = XmlDsigCanonicalizationUrl + "#WithComments";
56 public const string XmlDsigNamespaceUrl = "http://www.w3.org/2000/09/xmldsig#";
57 public const string XmlDsigDSAUrl = XmlDsigNamespaceUrl + "dsa-sha1";
58 public const string XmlDsigHMACSHA1Url = XmlDsigNamespaceUrl + "hmac-sha1";
59 public const string XmlDsigMinimalCanonicalizationUrl = XmlDsigNamespaceUrl + "minimal";
60 public const string XmlDsigRSASHA1Url = XmlDsigNamespaceUrl + "rsa-sha1";
61 public const string XmlDsigSHA1Url = XmlDsigNamespaceUrl + "sha1";
63 public KeyInfo KeyInfo {
64 get { return signature.KeyInfo; }
65 set { signature.KeyInfo = value; }
68 public Signature Signature {
69 get { return signature; }
72 public string SignatureLength {
73 get { return signature.SignedInfo.SignatureLength; }
76 public string SignatureMethod {
77 get { return signature.SignedInfo.SignatureMethod; }
80 public byte[] SignatureValue {
81 get { return signature.SignatureValue; }
84 public SignedInfo SignedInfo {
85 get { return signature.SignedInfo; }
88 public AsymmetricAlgorithm SigningKey {
93 // NOTE: CryptoAPI related ? documented as fx internal
94 public string SigningKeyName {
95 get { return keyName; }
96 set { keyName = value; }
99 public void AddObject (DataObject dataObject)
101 signature.AddObject (dataObject);
104 public void AddReference (Reference reference)
106 signature.SignedInfo.AddReference (reference);
109 private Stream ApplyTransform (Transform t, XmlDocument input)
111 XmlDocument doc = (XmlDocument) input.Clone ();
114 if (t is XmlDsigEnvelopedSignatureTransform) {
115 // It returns XmlDocument for XmlDocument input.
116 doc = (XmlDocument) t.GetOutput ();
117 Transform c14n = GetC14NMethod ();
118 c14n.LoadInput (doc);
119 return (Stream) c14n.GetOutput ();
122 object obj = t.GetOutput ();
126 // e.g. XmlDsigXPathTransform returns XmlNodeList
132 [MonoTODO("incomplete")]
133 private byte[] GetReferenceHash (Reference r)
136 XmlDocument doc = null;
137 if (r.Uri == String.Empty) {
141 doc = new XmlDocument ();
142 doc.PreserveWhitespace = true;
144 if (r.Uri [0] == '#') {
145 foreach (DataObject obj in signature.ObjectList) {
146 if ("#" + obj.Id == r.Uri) {
147 doc.LoadXml (obj.GetXml ().OuterXml);
153 if (r.Uri.EndsWith (".xml")) {
155 doc.XmlResolver = xmlResolver;
160 WebRequest req = WebRequest.Create (r.Uri);
161 s = req.GetResponse ().GetResponseStream ();
166 if (r.TransformChain.Count > 0) {
167 foreach (Transform t in r.TransformChain) {
169 s = ApplyTransform (t, doc);
173 s = (Stream) t.GetOutput ();
177 else if (s == null) {
178 // apply default C14N transformation
179 s = ApplyTransform (new XmlDsigC14NTransform (), doc);
182 HashAlgorithm hash = GetHash (r.DigestMethod);
183 return hash.ComputeHash (s);
186 private void DigestReferences ()
188 // we must tell each reference which hash algorithm to use
189 // before asking for the SignedInfo XML !
190 foreach (Reference r in signature.SignedInfo.References) {
191 // assume SHA-1 if nothing is specified
192 if (r.DigestMethod == null)
193 r.DigestMethod = XmlDsigSHA1Url;
194 r.DigestValue = GetReferenceHash (r);
198 private Transform GetC14NMethod ()
200 Transform t = (Transform) CryptoConfig.CreateFromName (signature.SignedInfo.CanonicalizationMethod);
202 throw new CryptographicException ("Unknown Canonicalization Method {0}", signature.SignedInfo.CanonicalizationMethod);
206 private Stream SignedInfoTransformed ()
208 Transform t = GetC14NMethod ();
210 if (signatureElement == null) {
211 // when creating signatures
212 XmlDocument doc = new XmlDocument ();
213 doc.PreserveWhitespace = true;
214 doc.LoadXml (signature.SignedInfo.GetXml ().OuterXml);
219 // when verifying signatures
220 // TODO - check signature.SignedInfo.Id
221 XmlNodeList xnl = signatureElement.GetElementsByTagName (XmlSignature.ElementNames.SignedInfo, XmlSignature.NamespaceURI);
222 byte[] si = Encoding.UTF8.GetBytes (xnl [0].OuterXml);
224 MemoryStream ms = new MemoryStream ();
225 ms.Write (si, 0, si.Length);
230 // C14N and C14NWithComments always return a Stream in GetOutput
231 return (Stream) t.GetOutput ();
234 // reuse hash - most document will always use the same hash
235 private HashAlgorithm GetHash (string algorithm)
237 HashAlgorithm hash = (HashAlgorithm) hashes [algorithm];
239 hash = HashAlgorithm.Create (algorithm);
241 throw new CryptographicException ("Unknown hash algorithm: {0}", algorithm);
242 hashes.Add (algorithm, hash);
243 // now ready to be used
246 // important before reusing an hash object
252 public bool CheckSignature ()
254 return (CheckSignatureInternal (null) != null);
257 private bool CheckReferenceIntegrity ()
259 // check digest (hash) for every reference
260 foreach (Reference r in signature.SignedInfo.References) {
261 // stop at first broken reference
262 if (! Compare (r.DigestValue, GetReferenceHash (r)))
268 public bool CheckSignature (AsymmetricAlgorithm key)
271 throw new ArgumentNullException ("key");
272 return (CheckSignatureInternal (key) != null);
275 private AsymmetricAlgorithm CheckSignatureInternal (AsymmetricAlgorithm key)
280 // check with supplied key
281 if (!CheckSignatureWithKey (key))
285 // no supplied key, iterates all KeyInfo
286 while ((key = GetPublicKey ()) != null) {
287 if (CheckSignatureWithKey (key)) {
292 throw new CryptographicException ("No public key found to verify the signature.");
295 // some parts may need to be downloaded
296 // so where doing it last
297 return (CheckReferenceIntegrity () ? key : null);
300 // Is the signature (over SignedInfo) valid ?
301 private bool CheckSignatureWithKey (AsymmetricAlgorithm key)
306 SignatureDescription sd = (SignatureDescription) CryptoConfig.CreateFromName (signature.SignedInfo.SignatureMethod);
310 AsymmetricSignatureDeformatter verifier = (AsymmetricSignatureDeformatter) CryptoConfig.CreateFromName (sd.DeformatterAlgorithm);
311 if (verifier == null)
314 verifier.SetKey (key);
315 verifier.SetHashAlgorithm (sd.DigestAlgorithm);
317 HashAlgorithm hash = GetHash (sd.DigestAlgorithm);
318 // get the hash of the C14N SignedInfo element
319 byte[] digest = hash.ComputeHash (SignedInfoTransformed ());
320 return verifier.VerifySignature (digest, signature.SignatureValue);
323 private bool Compare (byte[] expected, byte[] actual)
325 bool result = ((expected != null) && (actual != null));
327 int l = expected.Length;
328 result = (l == actual.Length);
330 for (int i=0; i < l; i++) {
331 if (expected[i] != actual[i])
339 public bool CheckSignature (KeyedHashAlgorithm macAlg)
342 throw new ArgumentNullException ("macAlg");
346 // Is the signature (over SignedInfo) valid ?
347 Stream s = SignedInfoTransformed ();
351 byte[] actual = macAlg.ComputeHash (s);
352 // HMAC signature may be partial
353 if (signature.SignedInfo.SignatureLength != null) {
354 int length = actual.Length;
356 // SignatureLength is in bits
357 length = (Int32.Parse (signature.SignedInfo.SignatureLength) >> 3);
362 if (length != actual.Length) {
363 byte[] trunked = new byte [length];
364 Buffer.BlockCopy (actual, 0, trunked, 0, length);
369 if (Compare (signature.SignatureValue, actual)) {
370 // some parts may need to be downloaded
371 // so where doing it last
372 return CheckReferenceIntegrity ();
377 public bool CheckSignatureReturningKey (out AsymmetricAlgorithm signingKey)
379 signingKey = CheckSignatureInternal (null);
380 return (signingKey != null);
383 public void ComputeSignature ()
386 // required before hashing
387 signature.SignedInfo.SignatureMethod = key.SignatureAlgorithm;
390 AsymmetricSignatureFormatter signer = null;
391 // in need for a CryptoConfig factory
393 signer = new DSASignatureFormatter (key);
395 signer = new RSAPKCS1SignatureFormatter (key);
397 if (signer != null) {
398 SignatureDescription sd = (SignatureDescription) CryptoConfig.CreateFromName (signature.SignedInfo.SignatureMethod);
400 HashAlgorithm hash = GetHash (sd.DigestAlgorithm);
401 // get the hash of the C14N SignedInfo element
402 byte[] digest = hash.ComputeHash (SignedInfoTransformed ());
404 signer.SetHashAlgorithm ("SHA1");
405 signature.SignatureValue = signer.CreateSignature (digest);
410 public void ComputeSignature (KeyedHashAlgorithm macAlg)
413 throw new ArgumentNullException ("macAlg");
415 if (macAlg is HMACSHA1) {
418 signature.SignedInfo.SignatureMethod = XmlDsigHMACSHA1Url;
419 signature.SignatureValue = macAlg.ComputeHash (SignedInfoTransformed ());
422 throw new CryptographicException ("unsupported algorithm");
425 public virtual XmlElement GetIdElement (XmlDocument document, string idValue)
427 // this works only if there's a DTD or XSD available to define the ID
428 XmlElement xel = document.GetElementById (idValue);
430 // search an "undefined" ID
431 xel = (XmlElement) document.SelectSingleNode ("//*[@Id='" + idValue + "']");
436 // According to book ".NET Framework Security" this method
437 // iterates all possible keys then return null
438 protected virtual AsymmetricAlgorithm GetPublicKey ()
440 if (signature.KeyInfo == null)
443 if (pkEnumerator == null) {
444 pkEnumerator = signature.KeyInfo.GetEnumerator ();
447 if (pkEnumerator.MoveNext ()) {
448 AsymmetricAlgorithm key = null;
449 KeyInfoClause kic = (KeyInfoClause) pkEnumerator.Current;
451 if (kic is DSAKeyValue)
453 else if (kic is RSAKeyValue)
457 key.FromXmlString (kic.GetXml ().InnerXml);
464 public XmlElement GetXml ()
466 return signature.GetXml ();
469 public void LoadXml (XmlElement value)
472 throw new ArgumentNullException ("value");
474 signatureElement = value;
475 signature.LoadXml (value);
479 private XmlResolver xmlResolver;
481 [MonoTODO("property not (yet) used in class")]
483 public XmlResolver Resolver {
484 set { xmlResolver = value; }