1 //
2 // SignedXml.cs - SignedXml implementation for XML Signature
3 //
4 // Author:
5 //	Sebastien Pouliot  <sebastien@ximian.com>
6 //	Atsushi Enomoto <atsushi@ximian.com>
7 //      Tim Coleman <tim@timcoleman.com>
8 //
9 // (C) 2002, 2003 Motus Technologies Inc. (http://www.motus.com)
10 // Copyright (C) Tim Coleman, 2004
11 // Copyright (C) 2004-2005 Novell, Inc (http://www.novell.com)
12 //
13 // Permission is hereby granted, free of charge, to any person obtaining
14 // a copy of this software and associated documentation files (the
15 // "Software"), to deal in the Software without restriction, including
16 // without limitation the rights to use, copy, modify, merge, publish,
17 // distribute, sublicense, and/or sell copies of the Software, and to
18 // permit persons to whom the Software is furnished to do so, subject to
19 // the following conditions:
20 //
21 // The above copyright notice and this permission notice shall be
22 // included in all copies or substantial portions of the Software.
23 //
24 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
25 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
26 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
27 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
28 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
29 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
30 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
31 //
32 
33 using System.Collections;
34 using System.IO;
35 using System.Runtime.InteropServices;
36 using System.Security.Cryptography;
37 using System.Security.Policy;
38 using System.Net;
39 using System.Text;
40 using System.Xml;
41 using System.Security.Cryptography.X509Certificates;
42 
43 namespace System.Security.Cryptography.Xml {
44 
45 	public class SignedXml {
46 		public const string XmlDsigNamespaceUrl = "http://www.w3.org/2000/09/xmldsig#";
47 		public const string XmlDsigMinimalCanonicalizationUrl = "http://www.w3.org/2000/09/xmldsig#minimal";
48 		public const string XmlDsigCanonicalizationUrl = XmlDsigC14NTransformUrl;
49 		public const string XmlDsigCanonicalizationWithCommentsUrl = XmlDsigC14NWithCommentsTransformUrl;
50 
51 		public const string XmlDsigSHA1Url = "http://www.w3.org/2000/09/xmldsig#sha1";
52 		public const string XmlDsigDSAUrl = "http://www.w3.org/2000/09/xmldsig#dsa-sha1";
53 		public const string XmlDsigRSASHA1Url = "http://www.w3.org/2000/09/xmldsig#rsa-sha1";
54 		public const string XmlDsigHMACSHA1Url = "http://www.w3.org/2000/09/xmldsig#hmac-sha1";
55 
56 		public const string XmlDsigSHA256Url = "http://www.w3.org/2001/04/xmlenc#sha256";
57 		public const string XmlDsigRSASHA256Url = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256";
58 
59 		// Yes, SHA384 is in the xmldsig-more namespace even though all the other SHA variants are in xmlenc. That's the standard.
60 		public const string XmlDsigSHA384Url = "http://www.w3.org/2001/04/xmldsig-more#sha384";
61 		public const string XmlDsigRSASHA384Url = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha384";
62 
63 		public const string XmlDsigSHA512Url = "http://www.w3.org/2001/04/xmlenc#sha512";
64 		public const string XmlDsigRSASHA512Url = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha512";
65 
66 		public const string XmlDsigC14NTransformUrl = "http://www.w3.org/TR/2001/REC-xml-c14n-20010315";
67 		public const string XmlDsigC14NWithCommentsTransformUrl = "http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments";
68 		public const string XmlDsigExcC14NTransformUrl = "http://www.w3.org/2001/10/xml-exc-c14n#";
69 		public const string XmlDsigExcC14NWithCommentsTransformUrl = "http://www.w3.org/2001/10/xml-exc-c14n#WithComments";
70 		public const string XmlDsigBase64TransformUrl = "http://www.w3.org/2000/09/xmldsig#base64";
71 		public const string XmlDsigXPathTransformUrl = "http://www.w3.org/TR/1999/REC-xpath-19991116";
72 		public const string XmlDsigXsltTransformUrl = "http://www.w3.org/TR/1999/REC-xslt-19991116";
73 		public const string XmlDsigEnvelopedSignatureTransformUrl = "http://www.w3.org/2000/09/xmldsig#enveloped-signature";
74 		public const string XmlDecryptionTransformUrl = "http://www.w3.org/2002/07/decrypt#XML";
75 		public const string XmlLicenseTransformUrl = "urn:mpeg:mpeg21:2003:01-REL-R-NS:licenseTransform";
76 
77 		private EncryptedXml encryptedXml;
78 
79 		protected Signature m_signature;
80 		private AsymmetricAlgorithm key;
81 		protected string m_strSigningKeyName;
82 		private XmlDocument envdoc;
83 		private IEnumerator pkEnumerator;
84 		private XmlElement signatureElement;
85 		private Hashtable hashes;
86 		// FIXME: enable it after CAS implementation
87 		internal XmlResolver _xmlResolver = new XmlUrlResolver ();
88 		private bool _bResolverSet = true;
89 		internal XmlElement _context;
90 		private ArrayList manifests;
91 		private IEnumerator _x509Enumerator;
92 
93 		private static readonly char [] whitespaceChars = new char [] {' ', '\r', '\n', '\t'};
94 
SignedXml()95 		public SignedXml ()
96 		{
97 			m_signature = new Signature ();
98 			m_signature.SignedInfo = new SignedInfo ();
99 			hashes = new Hashtable (2); // 98% SHA1 for now
100 			_context = null;
101 		}
102 
SignedXml(XmlDocument document)103 		public SignedXml (XmlDocument document) : this ()
104 		{
105 			if (document == null)
106 				throw new ArgumentNullException ("document");
107 			envdoc = document;
108 			_context = document.DocumentElement;
109 		}
110 
SignedXml(XmlElement elem)111 		public SignedXml (XmlElement elem) : this ()
112 		{
113 			if (elem == null)
114 				throw new ArgumentNullException ("elem");
115 			envdoc = new XmlDocument ();
116 			_context = elem;
117 			envdoc.LoadXml (elem.OuterXml);
118 		}
119 
120 		[ComVisible (false)]
121 		public EncryptedXml EncryptedXml {
122 			get { return encryptedXml; }
123 			set { encryptedXml = value; }
124 		}
125 
126 		public KeyInfo KeyInfo {
127 			get {
128 				if (m_signature.KeyInfo == null)
129 					m_signature.KeyInfo = new KeyInfo ();
130 				return m_signature.KeyInfo;
131 			}
132 			set { m_signature.KeyInfo = value; }
133 		}
134 
135 		public Signature Signature {
136 			get { return m_signature; }
137 		}
138 
139 		public string SignatureLength {
140 			get { return m_signature.SignedInfo.SignatureLength; }
141 		}
142 
143 		public string SignatureMethod {
144 			get { return m_signature.SignedInfo.SignatureMethod; }
145 		}
146 
147 		public byte[] SignatureValue {
148 			get { return m_signature.SignatureValue; }
149 		}
150 
151 		public SignedInfo SignedInfo {
152 			get { return m_signature.SignedInfo; }
153 		}
154 
155 		public AsymmetricAlgorithm SigningKey {
156 			get { return key; }
157 			set { key = value; }
158 		}
159 
160 		// NOTE: CryptoAPI related ? documented as fx internal
161 		public string SigningKeyName {
162 			get { return m_strSigningKeyName; }
163 			set { m_strSigningKeyName = value; }
164 		}
165 
166 		public XmlResolver Resolver
167 		{
168 			// This property only has a setter. The rationale for this is that we don't have a good value
169 			// to return when it has not been explicitely set, as we are using XmlSecureResolver by default
170 			set
171 			{
172 				_xmlResolver = value;
173 				_bResolverSet = true;
174 			}
175 		}
176 
177 		internal bool ResolverSet
178 		{
179 			get { return _bResolverSet; }
180 		}
181 
AddObject(DataObject dataObject)182 		public void AddObject (DataObject dataObject)
183 		{
184 			m_signature.AddObject (dataObject);
185 		}
186 
AddReference(Reference reference)187 		public void AddReference (Reference reference)
188 		{
189 			if (reference == null)
190 				throw new ArgumentNullException ("reference");
191 			m_signature.SignedInfo.AddReference (reference);
192 		}
193 
ApplyTransform(Transform t, XmlDocument input)194 		private Stream ApplyTransform (Transform t, XmlDocument input)
195 		{
196 			// These transformer modify input document, which should
197 			// not affect to the input itself.
198 			if (t is XmlDsigXPathTransform
199 				|| t is XmlDsigEnvelopedSignatureTransform
200 				|| t is XmlDecryptionTransform
201 			)
202 				input = (XmlDocument) input.Clone ();
203 
204 			t.LoadInput (input);
205 
206 			if (t is XmlDsigEnvelopedSignatureTransform)
207 				// It returns XmlDocument for XmlDocument input.
208 				return CanonicalizeOutput (t.GetOutput ());
209 
210 			object obj = t.GetOutput ();
211 			if (obj is Stream)
212 				return (Stream) obj;
213 			else if (obj is XmlDocument) {
214 				MemoryStream ms = new MemoryStream ();
215 				XmlTextWriter xtw = new XmlTextWriter (ms, Encoding.UTF8);
216 				((XmlDocument) obj).WriteTo (xtw);
217 
218 				xtw.Flush ();
219 
220 				// Rewind to the start of the stream
221 				ms.Position = 0;
222 				return ms;
223 			}
224 			else if (obj == null) {
225 				throw new NotImplementedException ("This should not occur. Transform is " + t + ".");
226 			}
227 			else {
228 				// e.g. XmlDsigXPathTransform returns XmlNodeList
229 				return CanonicalizeOutput (obj);
230 			}
231 		}
232 
CanonicalizeOutput(object obj)233 		private Stream CanonicalizeOutput (object obj)
234 		{
235 			Transform c14n = GetC14NMethod ();
236 			c14n.LoadInput (obj);
237 			return (Stream) c14n.GetOutput ();
238 		}
239 
GetManifest(Reference r)240 		private XmlDocument GetManifest (Reference r)
241 		{
242 			XmlDocument doc = new XmlDocument ();
243 			doc.PreserveWhitespace = true;
244 
245 			if (r.Uri [0] == '#') {
246 				// local manifest
247 				if (signatureElement != null) {
248 					XmlElement xel = GetIdElement (signatureElement.OwnerDocument, r.Uri.Substring (1));
249 					if (xel == null)
250 						throw new CryptographicException ("Manifest targeted by Reference was not found: " + r.Uri.Substring (1));
251 					doc.AppendChild (doc.ImportNode (xel, true));
252 					FixupNamespaceNodes (xel, doc.DocumentElement, false);
253 				}
254 			}
255 			else if (_xmlResolver != null) {
256 				// TODO: need testing
257 				Stream s = (Stream) _xmlResolver.GetEntity (new Uri (r.Uri), null, typeof (Stream));
258 				doc.Load (s);
259 			}
260 
261 			if (doc.FirstChild != null) {
262 				// keep a copy of the manifests to check their references later
263 				if (manifests == null)
264 					manifests = new ArrayList ();
265 				manifests.Add (doc);
266 
267 				return doc;
268 			}
269 			return null;
270 		}
271 
FixupNamespaceNodes(XmlElement src, XmlElement dst, bool ignoreDefault)272 		private void FixupNamespaceNodes (XmlElement src, XmlElement dst, bool ignoreDefault)
273 		{
274 			// add namespace nodes
275 			foreach (XmlAttribute attr in src.SelectNodes ("namespace::*")) {
276 				if (attr.LocalName == "xml")
277 					continue;
278 				if (ignoreDefault && attr.LocalName == "xmlns")
279 					continue;
280 				dst.SetAttributeNode (dst.OwnerDocument.ImportNode (attr, true) as XmlAttribute);
281 			}
282 		}
283 
GetReferenceHash(Reference r, bool check_hmac)284 		private byte[] GetReferenceHash (Reference r, bool check_hmac)
285 		{
286 			Stream s = null;
287 			XmlDocument doc = null;
288 			if (r.Uri == String.Empty) {
289 				doc = envdoc;
290 			}
291 			else if (r.Type == XmlSignature.Uri.Manifest) {
292 				doc = GetManifest (r);
293 			}
294 			else {
295 				doc = new XmlDocument ();
296 				doc.PreserveWhitespace = true;
297 				string objectName = null;
298 
299 				if (r.Uri.StartsWith ("#xpointer")) {
300 					string uri = string.Join ("", r.Uri.Substring (9).Split (whitespaceChars));
301 					if (uri.Length < 2 || uri [0] != '(' || uri [uri.Length - 1] != ')')
302 						// FIXME: how to handle invalid xpointer?
303 						uri = String.Empty;
304 					else
305 						uri = uri.Substring (1, uri.Length - 2);
306 					if (uri == "/")
307 						doc = envdoc;
308 					else if (uri.Length > 6 && uri.StartsWith ("id(") && uri [uri.Length - 1] == ')')
309 						// id('foo'), id("foo")
310 						objectName = uri.Substring (4, uri.Length - 6);
311 				}
312 				else if (r.Uri [0] == '#') {
313 					objectName = r.Uri.Substring (1);
314 				}
315 				else if (_xmlResolver != null) {
316 					// TODO: test but doc says that Resolver = null -> no access
317 					try {
318 						// no way to know if valid without throwing an exception
319 						Uri uri = new Uri (r.Uri);
320 						s = (Stream) _xmlResolver.GetEntity (uri, null, typeof (Stream));
321 					}
322 					catch {
323 						// may still be a local file (and maybe not xml)
324 						s = File.OpenRead (r.Uri);
325 					}
326 				}
327 				if (objectName != null) {
328 					XmlElement found = null;
329 					foreach (DataObject obj in m_signature.ObjectList) {
330 						if (obj.Id == objectName) {
331 							found = obj.GetXml ();
332 							found.SetAttribute ("xmlns", SignedXml.XmlDsigNamespaceUrl);
333 							doc.AppendChild (doc.ImportNode (found, true));
334 							// FIXME: there should be theoretical justification of copying namespace declaration nodes this way.
335 							foreach (XmlNode n in found.ChildNodes)
336 								// Do not copy default namespace as it must be xmldsig namespace for "Object" element.
337 								if (n.NodeType == XmlNodeType.Element)
338 									FixupNamespaceNodes (n as XmlElement, doc.DocumentElement, true);
339 							break;
340 						}
341 					}
342 					if (found == null && envdoc != null) {
343 						found = GetIdElement (envdoc, objectName);
344 						if (found != null) {
345 							doc.AppendChild (doc.ImportNode (found, true));
346 							FixupNamespaceNodes (found, doc.DocumentElement, false);
347 						}
348 					}
349 					if (found == null)
350 						throw new CryptographicException (String.Format ("Malformed reference object: {0}", objectName));
351 				}
352 			}
353 
354 			if (r.TransformChain.Count > 0) {
355 				foreach (Transform t in r.TransformChain) {
356 					if (s == null) {
357 						s = ApplyTransform (t, doc);
358 					}
359 					else {
360 						t.LoadInput (s);
361 						object o = t.GetOutput ();
362 						if (o is Stream)
363 							s = (Stream) o;
364 						else
365 							s = CanonicalizeOutput (o);
366 					}
367 				}
368 			}
369 			else if (s == null) {
370 				// we must not C14N references from outside the document
371 				// e.g. non-xml documents
372 				if (r.Uri [0] != '#') {
373 					s = new MemoryStream ();
374 					doc.Save (s);
375 				}
376 				else {
377 					// apply default C14N transformation
378 					s = ApplyTransform (new XmlDsigC14NTransform (), doc);
379 				}
380 			}
381 			HashAlgorithm digest = GetHash (r.DigestMethod, check_hmac);
382 			return (digest == null) ? null : digest.ComputeHash (s);
383 		}
384 
DigestReferences()385 		private void DigestReferences ()
386 		{
387 			// we must tell each reference which hash algorithm to use
388 			// before asking for the SignedInfo XML !
389 			foreach (Reference r in m_signature.SignedInfo.References) {
390 				// assume SHA-1 if nothing is specified
391 				if (r.DigestMethod == null)
392 					r.DigestMethod = XmlDsigSHA1Url;
393 				r.DigestValue = GetReferenceHash (r, false);
394 			}
395 		}
396 
GetC14NMethod()397 		private Transform GetC14NMethod ()
398 		{
399 			Transform t = (Transform) CryptoConfig.CreateFromName (m_signature.SignedInfo.CanonicalizationMethod);
400 			if (t == null)
401 				throw new CryptographicException ("Unknown Canonicalization Method {0}", m_signature.SignedInfo.CanonicalizationMethod);
402 			return t;
403 		}
404 
SignedInfoTransformed()405 		private Stream SignedInfoTransformed ()
406 		{
407 			Transform t = GetC14NMethod ();
408 
409 			if (signatureElement == null) {
410 				// when creating signatures
411 				XmlDocument doc = new XmlDocument ();
412 				doc.PreserveWhitespace = true;
413 				doc.LoadXml (m_signature.SignedInfo.GetXml ().OuterXml);
414 				if (envdoc != null)
415 				foreach (XmlAttribute attr in envdoc.DocumentElement.SelectNodes ("namespace::*")) {
416 					if (attr.LocalName == "xml")
417 						continue;
418 					if (attr.Prefix == doc.DocumentElement.Prefix)
419 						continue;
420 					doc.DocumentElement.SetAttributeNode (doc.ImportNode (attr, true) as XmlAttribute);
421 				}
422 				t.LoadInput (doc);
423 			}
424 			else {
425 				// when verifying signatures
426 				// TODO - check m_signature.SignedInfo.Id
427 				XmlElement el = signatureElement.GetElementsByTagName (XmlSignature.ElementNames.SignedInfo, XmlSignature.NamespaceURI) [0] as XmlElement;
428 				StringWriter sw = new StringWriter ();
429 				XmlTextWriter xtw = new XmlTextWriter (sw);
430 				xtw.WriteStartElement (el.Prefix, el.LocalName, el.NamespaceURI);
431 
432 				// context namespace nodes (except for "xmlns:xml")
433 				XmlNodeList nl = el.SelectNodes ("namespace::*");
434 				foreach (XmlAttribute attr in nl) {
435 					if (attr.ParentNode == el)
436 						continue;
437 					if (attr.LocalName == "xml")
438 						continue;
439 					if (attr.Prefix == el.Prefix)
440 						continue;
441 					attr.WriteTo (xtw);
442 				}
443 				foreach (XmlNode attr in el.Attributes)
444 					attr.WriteTo (xtw);
445 				foreach (XmlNode n in el.ChildNodes)
446 					n.WriteTo (xtw);
447 
448 				xtw.WriteEndElement ();
449 				byte [] si = Encoding.UTF8.GetBytes (sw.ToString ());
450 
451 				MemoryStream ms = new MemoryStream ();
452 				ms.Write (si, 0, si.Length);
453 				ms.Position = 0;
454 
455 				t.LoadInput (ms);
456 			}
457 			// C14N and C14NWithComments always return a Stream in GetOutput
458 			return (Stream) t.GetOutput ();
459 		}
460 
461 		// reuse hash - most document will always use the same hash
GetHash(string algorithm, bool check_hmac)462 		private HashAlgorithm GetHash (string algorithm, bool check_hmac)
463 		{
464 			HashAlgorithm hash = (HashAlgorithm) hashes [algorithm];
465 			if (hash == null) {
466 				hash = HashAlgorithm.Create (algorithm);
467 				if (hash == null)
468 					throw new CryptographicException ("Unknown hash algorithm: {0}", algorithm);
469 				hashes.Add (algorithm, hash);
470 				// now ready to be used
471 			}
472 			else {
473 				// important before reusing an hash object
474 				hash.Initialize ();
475 			}
476 			// we can sign using any hash algorith, including HMAC, but we can only verify hash (MS compatibility)
477 			if (check_hmac && (hash is KeyedHashAlgorithm))
478 				return null;
479 			return hash;
480 		}
481 
CheckSignature()482 		public bool CheckSignature ()
483 		{
484 			return (CheckSignatureInternal (null) != null);
485 		}
486 
CheckReferenceIntegrity(ArrayList referenceList)487 		private bool CheckReferenceIntegrity (ArrayList referenceList)
488 		{
489 			if (referenceList == null)
490 				return false;
491 
492 			// check digest (hash) for every reference
493 			foreach (Reference r in referenceList) {
494 				// stop at first broken reference
495 				byte[] hash = GetReferenceHash (r, true);
496 				if (! Compare (r.DigestValue, hash))
497 					return false;
498 			}
499 			return true;
500 		}
501 
CheckSignature(AsymmetricAlgorithm key)502 		public bool CheckSignature (AsymmetricAlgorithm key)
503 		{
504 			if (key == null)
505 				throw new ArgumentNullException ("key");
506 			return (CheckSignatureInternal (key) != null);
507 		}
508 
CheckSignatureInternal(AsymmetricAlgorithm key)509 		private AsymmetricAlgorithm CheckSignatureInternal (AsymmetricAlgorithm key)
510 		{
511 			pkEnumerator = null;
512 
513 			if (key != null) {
514 				// check with supplied key
515 				if (!CheckSignatureWithKey (key))
516 					return null;
517 			} else {
518 				if (Signature.KeyInfo == null)
519 					return null;
520 				// no supplied key, iterates all KeyInfo
521 				while ((key = GetPublicKey ()) != null) {
522 					if (CheckSignatureWithKey (key)) {
523 						break;
524 					}
525 				}
526 				pkEnumerator = null;
527 				if (key == null)
528 					return null;
529 			}
530 
531 			// some parts may need to be downloaded
532 			// so where doing it last
533 			if (!CheckReferenceIntegrity (m_signature.SignedInfo.References))
534 				return null;
535 
536 			if (manifests != null) {
537 				// do not use foreach as a manifest could contain manifests...
538 				for (int i=0; i < manifests.Count; i++) {
539 					Manifest manifest = new Manifest ((manifests [i] as XmlDocument).DocumentElement);
540 					if (! CheckReferenceIntegrity (manifest.References))
541 						return null;
542 				}
543 			}
544 			return key;
545 		}
546 
547 		// Is the signature (over SignedInfo) valid ?
CheckSignatureWithKey(AsymmetricAlgorithm key)548 		private bool CheckSignatureWithKey (AsymmetricAlgorithm key)
549 		{
550 			if (key == null)
551 				return false;
552 
553 			SignatureDescription sd = (SignatureDescription) CryptoConfig.CreateFromName (m_signature.SignedInfo.SignatureMethod);
554 			if (sd == null)
555 				return false;
556 
557 			AsymmetricSignatureDeformatter verifier = (AsymmetricSignatureDeformatter) CryptoConfig.CreateFromName (sd.DeformatterAlgorithm);
558 			if (verifier == null)
559 				return false;
560 
561 			try {
562 				verifier.SetKey (key);
563 				verifier.SetHashAlgorithm (sd.DigestAlgorithm);
564 
565 				HashAlgorithm hash = GetHash (sd.DigestAlgorithm, true);
566 				// get the hash of the C14N SignedInfo element
567 				MemoryStream ms = (MemoryStream) SignedInfoTransformed ();
568 
569 				byte[] digest = hash.ComputeHash (ms);
570 				return verifier.VerifySignature (digest, m_signature.SignatureValue);
571 			}
572 			catch {
573 				// e.g. SignatureMethod != AsymmetricAlgorithm type
574 				return false;
575 			}
576 		}
577 
Compare(byte[] expected, byte[] actual)578 		private bool Compare (byte[] expected, byte[] actual)
579 		{
580 			bool result = ((expected != null) && (actual != null));
581 			if (result) {
582 				int l = expected.Length;
583 				result = (l == actual.Length);
584 				if (result) {
585 					for (int i=0; i < l; i++) {
586 						if (expected[i] != actual[i])
587 							return false;
588 					}
589 				}
590 			}
591 			return result;
592 		}
593 
CheckSignature(KeyedHashAlgorithm macAlg)594 		public bool CheckSignature (KeyedHashAlgorithm macAlg)
595 		{
596 			if (macAlg == null)
597 				throw new ArgumentNullException ("macAlg");
598 
599 			pkEnumerator = null;
600 
601 			// Is the signature (over SignedInfo) valid ?
602 			Stream s = SignedInfoTransformed ();
603 			if (s == null)
604 				return false;
605 
606 			byte[] actual = macAlg.ComputeHash (s);
607 			// HMAC signature may be partial and specified by <HMACOutputLength>
608 			if (m_signature.SignedInfo.SignatureLength != null) {
609 				int length = Int32.Parse (m_signature.SignedInfo.SignatureLength);
610 				// we only support signatures with a multiple of 8 bits
611 				// and the value must match the signature length
612 				if ((length & 7) != 0)
613 					throw new CryptographicException ("Signature length must be a multiple of 8 bits.");
614 
615 				// SignatureLength is in bits (and we works on bytes, only in multiple of 8 bits)
616 				// and both values must match for a signature to be valid
617 				length >>= 3;
618 				if (length != m_signature.SignatureValue.Length)
619 					throw new CryptographicException ("Invalid signature length.");
620 
621 				// is the length "big" enough to make the signature meaningful ?
622 				// we use a minimum of 80 bits (10 bytes) or half the HMAC normal output length
623 				// e.g. HMACMD5 output 128 bits but our minimum is 80 bits (not 64 bits)
624 				int minimum = Math.Max (10, actual.Length / 2);
625 				if (length < minimum)
626 					throw new CryptographicException ("HMAC signature is too small");
627 
628 				if (length < actual.Length) {
629 					byte[] trunked = new byte [length];
630 					Buffer.BlockCopy (actual, 0, trunked, 0, length);
631 					actual = trunked;
632 				}
633 			}
634 
635 			if (Compare (m_signature.SignatureValue, actual)) {
636 				// some parts may need to be downloaded
637 				// so where doing it last
638 				return CheckReferenceIntegrity (m_signature.SignedInfo.References);
639 			}
640 			return false;
641 		}
642 
643 		[MonoTODO]
644 		[ComVisible (false)]
CheckSignature(X509Certificate2 certificate, bool verifySignatureOnly)645 		public bool CheckSignature (X509Certificate2 certificate, bool verifySignatureOnly)
646 		{
647 			throw new NotImplementedException ();
648 		}
649 
CheckSignatureReturningKey(out AsymmetricAlgorithm signingKey)650 		public bool CheckSignatureReturningKey (out AsymmetricAlgorithm signingKey)
651 		{
652 			signingKey = CheckSignatureInternal (null);
653 			return (signingKey != null);
654 		}
655 
ComputeSignature()656 		public void ComputeSignature ()
657 		{
658 			DigestReferences ();
659 
660 			if (key == null)
661 				throw new CryptographicException (SR.Cryptography_Xml_LoadKeyFailed);
662 
663 			// Check the signature algorithm associated with the key so that we can accordingly set the signature method
664 			if (SignedInfo.SignatureMethod == null) {
665 				if (key is DSA) {
666 					SignedInfo.SignatureMethod = XmlDsigDSAUrl;
667 				} else if (key is RSA) {
668 					// Default to RSA-SHA1
669 					SignedInfo.SignatureMethod = XmlDsigRSASHA1Url;
670 				} else {
671 					throw new CryptographicException (SR.Cryptography_Xml_CreatedKeyFailed);
672 				}
673 			}
674 
675 			// See if there is a signature description class defined in the Config file
676 			SignatureDescription signatureDescription = CryptoConfig.CreateFromName (SignedInfo.SignatureMethod) as SignatureDescription;
677 			if (signatureDescription == null)
678 				throw new CryptographicException (SR.Cryptography_Xml_SignatureDescriptionNotCreated);
679 
680 			HashAlgorithm hashAlg = signatureDescription.CreateDigest ();
681 			if (hashAlg == null)
682 				throw new CryptographicException (SR.Cryptography_Xml_CreateHashAlgorithmFailed);
683 
684 			byte[] hashvalue = hashAlg.ComputeHash (SignedInfoTransformed ());
685 			AsymmetricSignatureFormatter asymmetricSignatureFormatter = signatureDescription.CreateFormatter (key);
686 
687 			m_signature.SignatureValue = asymmetricSignatureFormatter.CreateSignature (hashAlg);
688 		}
689 
ComputeSignature(KeyedHashAlgorithm macAlg)690 		public void ComputeSignature (KeyedHashAlgorithm macAlg)
691 		{
692 			if (macAlg == null)
693 				throw new ArgumentNullException ("macAlg");
694 
695 			string method = null;
696 
697 			if (macAlg is HMACSHA1) {
698 				method = XmlDsigHMACSHA1Url;
699 			} else if (macAlg is HMACSHA256) {
700 				method = "http://www.w3.org/2001/04/xmldsig-more#hmac-sha256";
701 			} else if (macAlg is HMACSHA384) {
702 				method = "http://www.w3.org/2001/04/xmldsig-more#hmac-sha384";
703 			} else if (macAlg is HMACSHA512) {
704 				method = "http://www.w3.org/2001/04/xmldsig-more#hmac-sha512";
705 			} else if (macAlg is HMACRIPEMD160) {
706 				method = "http://www.w3.org/2001/04/xmldsig-more#hmac-ripemd160";
707 			}
708 
709 			if (method == null)
710 				throw new CryptographicException ("unsupported algorithm");
711 
712 			DigestReferences ();
713 			m_signature.SignedInfo.SignatureMethod = method;
714 			m_signature.SignatureValue = macAlg.ComputeHash (SignedInfoTransformed ());
715 		}
716 
GetIdElement(XmlDocument document, string idValue)717 		public virtual XmlElement GetIdElement (XmlDocument document, string idValue)
718 		{
719 			if ((document == null) || (idValue == null))
720 				return null;
721 
722 			// this works only if there's a DTD or XSD available to define the ID
723 			XmlElement xel = document.GetElementById (idValue);
724 			if (xel == null) {
725 				// search an "undefined" ID
726 				xel = (XmlElement) document.SelectSingleNode ("//*[@Id='" + idValue + "']");
727 				if (xel == null) {
728 					xel = (XmlElement) document.SelectSingleNode ("//*[@ID='" + idValue + "']");
729 					if (xel == null) {
730 						xel = (XmlElement) document.SelectSingleNode ("//*[@id='" + idValue + "']");
731 					}
732 				}
733 			}
734 			return xel;
735 		}
736 
DefaultGetIdElement(XmlDocument document, string idValue)737 		internal static XmlElement DefaultGetIdElement(XmlDocument document, string idValue)
738 		{
739 			if (document == null)
740 				return null;
741 
742 			try
743 			{
744 				XmlConvert.VerifyNCName(idValue);
745 			}
746 			catch
747 			{
748 				// Identifiers are required to be an NCName
749 				//   (xml:id version 1.0, part 4, paragraph 2, bullet 1)
750 				//
751 				// If it isn't an NCName, it isn't allowed to match.
752 				return null;
753 			}
754 
755 			// Get the element with idValue
756 			XmlElement elem = document.GetElementById(idValue);
757 
758 			if (elem != null)
759 			{
760 				// Have to check for duplicate ID values from the DTD.
761 
762 				XmlDocument docClone = (XmlDocument)document.CloneNode(true);
763 				XmlElement cloneElem = docClone.GetElementById(idValue);
764 
765 				// If it's null here we want to know about it, because it means that
766 				// GetElementById failed to work across the cloning, and our uniqueness
767 				// test is invalid.
768 				System.Diagnostics.Debug.Assert(cloneElem != null);
769 
770 				// Guard against null anyways
771 				if (cloneElem != null)
772 				{
773 					cloneElem.Attributes.RemoveAll();
774 
775 					XmlElement cloneElem2 = docClone.GetElementById(idValue);
776 
777 					if (cloneElem2 != null)
778 					{
779 						throw new CryptographicException(
780 							SR.Cryptography_Xml_InvalidReference);
781 					}
782 				}
783 
784 				return elem;
785 			}
786 
787 			elem = GetSingleReferenceTarget(document, "Id", idValue);
788 			if (elem != null)
789 				return elem;
790 			elem = GetSingleReferenceTarget(document, "id", idValue);
791 			if (elem != null)
792 				return elem;
793 			elem = GetSingleReferenceTarget(document, "ID", idValue);
794 
795 			return elem;
796 		}
797 
GetSingleReferenceTarget(XmlDocument document, string idAttributeName, string idValue)798 		private static XmlElement GetSingleReferenceTarget(XmlDocument document, string idAttributeName, string idValue)
799 		{
800 			// idValue has already been tested as an NCName (unless overridden for compatibility), so there's no
801 			// escaping that needs to be done here.
802 			string xPath = "//*[@" + idAttributeName + "=\"" + idValue + "\"]";
803 
804 			// http://www.w3.org/TR/xmldsig-core/#sec-ReferenceProcessingModel says that for the form URI="#chapter1":
805 			//
806 			//   Identifies a node-set containing the element with ID attribute value 'chapter1' ...
807 			//
808 			// Note that it uses the singular. Therefore, if the match is ambiguous, we should consider the document invalid.
809 			//
810 			// In this case, we'll treat it the same as having found nothing across all fallbacks (but shortcut so that we don't
811 			// fall into a trap of finding a secondary element which wasn't the originally signed one).
812 
813 			XmlNodeList nodeList = document.SelectNodes(xPath);
814 
815 			if (nodeList == null || nodeList.Count == 0)
816 			{
817 				return null;
818 			}
819 
820 			if (nodeList.Count == 1)
821 			{
822 				return nodeList[0] as XmlElement;
823 			}
824 
825 			throw new CryptographicException(SR.Cryptography_Xml_InvalidReference);
826 		}
827 
828 		// According to book ".NET Framework Security" this method
829 		// iterates all possible keys then return null
GetPublicKey()830 		protected virtual AsymmetricAlgorithm GetPublicKey ()
831 		{
832 			if (m_signature.KeyInfo == null)
833 				return null;
834 
835 			if (pkEnumerator == null) {
836 				pkEnumerator = m_signature.KeyInfo.GetEnumerator ();
837 			}
838 
839 #if SECURITY_DEP
840 			if (_x509Enumerator != null) {
841 				if (_x509Enumerator.MoveNext ()) {
842 					X509Certificate cert = (X509Certificate) _x509Enumerator.Current;
843 					return new X509Certificate2 (cert.GetRawCertData ()).PublicKey.Key;
844 				} else {
845 					_x509Enumerator = null;
846 				}
847 			}
848 #endif
849 			while (pkEnumerator.MoveNext ()) {
850 				AsymmetricAlgorithm key = null;
851 				KeyInfoClause kic = (KeyInfoClause) pkEnumerator.Current;
852 
853 				if (kic is DSAKeyValue)
854 					key = DSA.Create ();
855 				else if (kic is RSAKeyValue)
856 					key = RSA.Create ();
857 
858 				if (key != null) {
859 					key.FromXmlString (kic.GetXml ().InnerXml);
860 					return key;
861 				}
862 
863 #if SECURITY_DEP
864 				if (kic is KeyInfoX509Data) {
865 					_x509Enumerator = ((KeyInfoX509Data) kic).Certificates.GetEnumerator ();
866 					if (_x509Enumerator.MoveNext ()) {
867 						X509Certificate cert = (X509Certificate) _x509Enumerator.Current;
868 						return new X509Certificate2 (cert.GetRawCertData ()).PublicKey.Key;
869 					}
870 				}
871 #endif
872 			}
873 			return null;
874 		}
875 
GetXml()876 		public XmlElement GetXml ()
877 		{
878 			return m_signature.GetXml (envdoc);
879 		}
880 
LoadXml(XmlElement value)881 		public void LoadXml (XmlElement value)
882 		{
883 			if (value == null)
884 				throw new ArgumentNullException ("value");
885 
886 			signatureElement = value;
887 			m_signature.LoadXml (value);
888 
889 			if (_context == null) {
890 				_context = value;
891 			}
892 
893 			// Need to give the EncryptedXml object to the
894 			// XmlDecryptionTransform to give it a fighting
895 			// chance at decrypting the document.
896 			foreach (Reference r in m_signature.SignedInfo.References) {
897 				foreach (Transform t in r.TransformChain) {
898 					if (t is XmlDecryptionTransform)
899 						((XmlDecryptionTransform) t).EncryptedXml = EncryptedXml;
900 				}
901 			}
902 		}
903 	}
904 }
905