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;
33 signature = new Signature ();
34 signature.SignedInfo = new SignedInfo ();
37 public SignedXml (XmlDocument document)
39 signature = new Signature ();
40 signature.SignedInfo = new SignedInfo ();
44 public SignedXml (XmlElement elem) : this ()
47 throw new ArgumentNullException ("elem");
48 signature = new Signature ();
49 signature.SignedInfo = new SignedInfo ();
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 XmlDsigNamespaceUrl = "http://www.w3.org/2000/09/xmldsig#";
55 public const string XmlDsigDSAUrl = XmlDsigNamespaceUrl + "dsa-sha1";
56 public const string XmlDsigHMACSHA1Url = XmlDsigNamespaceUrl + "hmac-sha1";
57 public const string XmlDsigMinimalCanonicalizationUrl = XmlDsigNamespaceUrl + "minimal";
58 public const string XmlDsigRSASHA1Url = XmlDsigNamespaceUrl + "rsa-sha1";
59 public const string XmlDsigSHA1Url = XmlDsigNamespaceUrl + "sha1";
61 public KeyInfo KeyInfo {
62 get { return signature.KeyInfo; }
63 set { signature.KeyInfo = value; }
66 public Signature Signature {
67 get { return signature; }
70 public string SignatureLength {
71 get { return signature.SignedInfo.SignatureLength; }
74 public string SignatureMethod {
75 get { return signature.SignedInfo.SignatureMethod; }
78 public byte[] SignatureValue {
79 get { return signature.SignatureValue; }
82 public SignedInfo SignedInfo {
83 get { return signature.SignedInfo; }
86 public AsymmetricAlgorithm SigningKey {
91 public string SigningKeyName {
92 get { return keyName; }
93 set { keyName = value; }
96 public void AddObject (DataObject dataObject)
98 signature.AddObject (dataObject);
101 public void AddReference (Reference reference)
103 signature.SignedInfo.AddReference (reference);
106 private Stream ApplyTransform (Transform t, XmlDocument input)
108 XmlDocument doc = (XmlDocument) input.Clone ();
111 if (t is XmlDsigEnvelopedSignatureTransform) {
112 // It returns XmlDocument for XmlDocument input.
113 doc = (XmlDocument) t.GetOutput ();
114 Transform c14n = GetC14NMethod ();
115 c14n.LoadInput (doc);
116 return (Stream) c14n.GetOutput ();
119 object obj = t.GetOutput ();
123 // e.g. XmlDsigXPathTransform returns XmlNodeList
129 private Stream ApplyTransform (Transform t, Stream s)
133 s = (Stream) t.GetOutput ();
135 catch (Exception e) {
136 string temp = e.ToString (); // stop debugger
141 [MonoTODO("incomplete")]
142 private byte[] GetReferenceHash (Reference r)
145 XmlDocument doc = null;
146 if (r.Uri == String.Empty) {
150 doc = new XmlDocument ();
151 doc.PreserveWhitespace = true;
153 if (r.Uri [0] == '#') {
154 foreach (DataObject obj in signature.ObjectList) {
155 if ("#" + obj.Id == r.Uri) {
156 doc.LoadXml (obj.GetXml ().OuterXml);
162 if (r.Uri.EndsWith (".xml"))
165 WebRequest req = WebRequest.Create (r.Uri);
166 s = req.GetResponse ().GetResponseStream ();
171 if (r.TransformChain.Count > 0) {
172 foreach (Transform t in r.TransformChain) {
174 s = ApplyTransform (t, doc);
176 s = ApplyTransform (t, s);
179 else if (s == null) {
180 // apply default C14N transformation
181 s = ApplyTransform (new XmlDsigC14NTransform (), doc);
184 // TODO: We should reuse the same hash object (when possible)
185 HashAlgorithm hash = (HashAlgorithm) CryptoConfig.CreateFromName (r.DigestMethod);
187 throw new CryptographicException ("Unknown digest algorithm {0}", r.DigestMethod);
188 return hash.ComputeHash (s);
191 private void DigestReferences ()
193 // we must tell each reference which hash algorithm to use
194 // before asking for the SignedInfo XML !
195 foreach (Reference r in signature.SignedInfo.References) {
196 // assume SHA-1 if nothing is specified
197 if (r.DigestMethod == null)
198 r.DigestMethod = XmlDsigSHA1Url;
199 r.DigestValue = GetReferenceHash (r);
203 private Transform GetC14NMethod ()
205 Transform t = (Transform) CryptoConfig.CreateFromName (signature.SignedInfo.CanonicalizationMethod);
207 throw new CryptographicException ("Unknown Canonicalization Method {0}", signature.SignedInfo.CanonicalizationMethod);
211 private Stream SignedInfoTransformed ()
213 Transform t = GetC14NMethod ();
215 if (signatureElement == null) {
216 // when creating signatures
217 XmlDocument doc = new XmlDocument ();
218 doc.PreserveWhitespace = true;
219 doc.LoadXml (signature.SignedInfo.GetXml ().OuterXml);
224 // when verifying signatures
225 // TODO - check signature.SignedInfo.Id
226 XmlNodeList xnl = signatureElement.GetElementsByTagName (XmlSignature.ElementNames.SignedInfo, XmlSignature.NamespaceURI);
227 byte[] si = Encoding.UTF8.GetBytes (xnl [0].OuterXml);
228 MemoryStream ms = new MemoryStream ();
229 ms.Write (si, 0, si.Length);
234 // C14N and C14NWithComments always return a Stream in GetOutput
235 return (Stream) t.GetOutput ();
238 private byte[] Hash (string hashAlgorithm)
240 HashAlgorithm hash = HashAlgorithm.Create (hashAlgorithm);
241 // get the hash of the C14N SignedInfo element
242 return hash.ComputeHash (SignedInfoTransformed ());
245 public bool CheckSignature ()
247 return (CheckSignatureInternal (null) != null);
250 private bool CheckReferenceIntegrity ()
253 // check digest (hash) for every reference
254 foreach (Reference r in signature.SignedInfo.References) {
255 // stop at first broken reference
256 if (! Compare (r.DigestValue, GetReferenceHash (r)))
262 public bool CheckSignature (AsymmetricAlgorithm key)
265 throw new ArgumentNullException ("key");
266 return (CheckSignatureInternal (key) != null);
269 private AsymmetricAlgorithm CheckSignatureInternal (AsymmetricAlgorithm key)
271 // Part 1: Are all references digest valid ?
272 if (!CheckReferenceIntegrity ())
276 // check with supplied key
277 if (CheckSignatureWithKey (key))
281 // no supplied key, iterates all KeyInfo
282 while ((key = GetPublicKey ()) != null) {
283 if (CheckSignatureWithKey (key))
291 // Is the signature (over SignedInfo) valid ?
292 private bool CheckSignatureWithKey (AsymmetricAlgorithm key)
297 SignatureDescription sd = (SignatureDescription) CryptoConfig.CreateFromName (signature.SignedInfo.SignatureMethod);
301 AsymmetricSignatureDeformatter verifier = (AsymmetricSignatureDeformatter) CryptoConfig.CreateFromName (sd.DeformatterAlgorithm);
302 if (verifier == null)
305 verifier.SetKey (key);
306 verifier.SetHashAlgorithm (sd.DigestAlgorithm);
308 byte[] hash = Hash (sd.DigestAlgorithm);
309 return verifier.VerifySignature (hash, signature.SignatureValue);
312 private bool Compare (byte[] expected, byte[] actual)
314 bool result = ((expected != null) && (actual != null));
316 int l = expected.Length;
317 result = (l == actual.Length);
319 for (int i=0; i < l; i++) {
320 if (expected[i] != actual[i])
328 public bool CheckSignature (KeyedHashAlgorithm macAlg)
331 throw new ArgumentNullException ("macAlg");
333 // Part 1: Are all references digest valid ?
334 bool result = CheckReferenceIntegrity ();
336 // Part 2: Is the signature (over SignedInfo) valid ?
337 Stream s = SignedInfoTransformed ();
341 byte[] actual = macAlg.ComputeHash (s);
342 int length = actual.Length;
343 // HMAC signature may be partial
344 if (signature.SignedInfo.SignatureLength != null) {
346 // SignatureLength is in bits
347 length = (Int32.Parse (signature.SignedInfo.SignatureLength) >> 3);
352 if (length != actual.Length) {
353 byte[] trunked = new byte [length];
354 Buffer.BlockCopy (actual, 0, trunked, 0, length);
358 result = Compare (signature.SignatureValue, actual);
363 public bool CheckSignatureReturningKey (out AsymmetricAlgorithm signingKey)
365 signingKey = CheckSignatureInternal (null);
366 return (signingKey != null);
369 public void ComputeSignature ()
372 // required before hashing
373 signature.SignedInfo.SignatureMethod = key.SignatureAlgorithm;
376 SignatureDescription sd = (SignatureDescription) CryptoConfig.CreateFromName (signature.SignedInfo.SignatureMethod);
378 // the hard part - C14Ning the KeyInfo
379 byte[] hash = Hash (sd.DigestAlgorithm);
380 AsymmetricSignatureFormatter signer = null;
382 // in need for a CryptoConfig factory
384 signer = new DSASignatureFormatter (key);
386 signer = new RSAPKCS1SignatureFormatter (key);
388 if (signer != null) {
389 signer.SetHashAlgorithm ("SHA1");
390 signature.SignatureValue = signer.CreateSignature (hash);
395 public void ComputeSignature (KeyedHashAlgorithm macAlg)
398 throw new ArgumentNullException ("macAlg");
400 if (macAlg is HMACSHA1) {
403 signature.SignedInfo.SignatureMethod = XmlDsigHMACSHA1Url;
404 signature.SignatureValue = macAlg.ComputeHash (SignedInfoTransformed ());
407 throw new CryptographicException ("unsupported algorithm");
410 public virtual XmlElement GetIdElement (XmlDocument document, string idValue)
412 // this works only if there's a DTD or XSD available to define the ID
413 XmlElement xel = document.GetElementById (idValue);
415 // search an "undefined" ID
416 xel = (XmlElement) document.SelectSingleNode ("//*[@Id='" + idValue + "']");
421 // According to book ".NET Framework Security" this method
422 // iterates all possible keys then return null
423 protected virtual AsymmetricAlgorithm GetPublicKey ()
425 if (pkEnumerator == null) {
426 pkEnumerator = signature.KeyInfo.GetEnumerator ();
429 if (pkEnumerator.MoveNext ()) {
430 AsymmetricAlgorithm key = null;
431 KeyInfoClause kic = (KeyInfoClause) pkEnumerator.Current;
433 if (kic is DSAKeyValue)
435 else if (kic is RSAKeyValue)
439 key.FromXmlString (kic.GetXml ().InnerXml);
446 public XmlElement GetXml ()
448 return signature.GetXml ();
451 public void LoadXml (XmlElement value)
454 throw new ArgumentNullException ("value");
456 signatureElement = value;
457 signature.LoadXml (value);
461 private XmlResolver xmlResolver;
463 [MonoTODO("property not (yet) used in class")]
465 public XmlResolver Resolver {
466 set { xmlResolver = value; }