// Author:
// Sebastien Pouliot <sebastien@ximian.com>
// Atsushi Enomoto <atsushi@ximian.com>
+// Tim Coleman <tim@timcoleman.com>
//
// (C) 2002, 2003 Motus Technologies Inc. (http://www.motus.com)
-// (C) 2004 Novell (http://www.novell.com)
-//
-
+// Copyright (C) Tim Coleman, 2004
+// Copyright (C) 2004-2005 Novell, Inc (http://www.novell.com)
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
using System.Net;
using System.Text;
using System.Xml;
+using System.Security.Cryptography.X509Certificates;
namespace System.Security.Cryptography.Xml {
public class SignedXml {
+ public const string XmlDsigCanonicalizationUrl = "http://www.w3.org/TR/2001/REC-xml-c14n-20010315";
+ public const string XmlDsigCanonicalizationWithCommentsUrl = XmlDsigCanonicalizationUrl + "#WithComments";
+ public const string XmlDsigDSAUrl = XmlDsigNamespaceUrl + "dsa-sha1";
+ public const string XmlDsigHMACSHA1Url = XmlDsigNamespaceUrl + "hmac-sha1";
+ public const string XmlDsigMinimalCanonicalizationUrl = XmlDsigNamespaceUrl + "minimal";
+ public const string XmlDsigNamespaceUrl = "http://www.w3.org/2000/09/xmldsig#";
+ public const string XmlDsigRSASHA1Url = XmlDsigNamespaceUrl + "rsa-sha1";
+ public const string XmlDsigSHA1Url = XmlDsigNamespaceUrl + "sha1";
+
+ public const string XmlDecryptionTransformUrl = "http://www.w3.org/2002/07/decrypt#XML";
+ public const string XmlDsigBase64TransformUrl = XmlDsigNamespaceUrl + "base64";
+ public const string XmlDsigC14NTransformUrl = XmlDsigCanonicalizationUrl;
+ public const string XmlDsigC14NWithCommentsTransformUrl = XmlDsigCanonicalizationWithCommentsUrl;
+ public const string XmlDsigEnvelopedSignatureTransformUrl = XmlDsigNamespaceUrl + "enveloped-signature";
+ public const string XmlDsigExcC14NTransformUrl = "http://www.w3.org/2001/10/xml-exc-c14n#";
+ public const string XmlDsigExcC14NWithCommentsTransformUrl = XmlDsigExcC14NTransformUrl + "WithComments";
+ public const string XmlDsigXPathTransformUrl = "http://www.w3.org/TR/1999/REC-xpath-19991116";
+ public const string XmlDsigXsltTransformUrl = "http://www.w3.org/TR/1999/REC-xslt-19991116";
+ public const string XmlLicenseTransformUrl = "urn:mpeg:mpeg21:2003:01-REL-R-NS:licenseTransform";
+
+ private EncryptedXml encryptedXml;
+
protected Signature m_signature;
private AsymmetricAlgorithm key;
protected string m_strSigningKeyName;
private XmlElement signatureElement;
private Hashtable hashes;
// FIXME: enable it after CAS implementation
-#if false //NET_1_1
- private XmlResolver xmlResolver = new XmlSecureResolver (new XmlUrlResolver (), new Evidence ());
-#else
private XmlResolver xmlResolver = new XmlUrlResolver ();
-#endif
private ArrayList manifests;
-
+ private IEnumerator _x509Enumerator;
+
private static readonly char [] whitespaceChars = new char [] {' ', '\r', '\n', '\t'};
public SignedXml ()
envdoc.LoadXml (elem.OuterXml);
}
- public const string XmlDsigCanonicalizationUrl = "http://www.w3.org/TR/2001/REC-xml-c14n-20010315";
- public const string XmlDsigCanonicalizationWithCommentsUrl = XmlDsigCanonicalizationUrl + "#WithComments";
- public const string XmlDsigNamespaceUrl = "http://www.w3.org/2000/09/xmldsig#";
- public const string XmlDsigDSAUrl = XmlDsigNamespaceUrl + "dsa-sha1";
- public const string XmlDsigHMACSHA1Url = XmlDsigNamespaceUrl + "hmac-sha1";
- public const string XmlDsigMinimalCanonicalizationUrl = XmlDsigNamespaceUrl + "minimal";
- public const string XmlDsigRSASHA1Url = XmlDsigNamespaceUrl + "rsa-sha1";
- public const string XmlDsigSHA1Url = XmlDsigNamespaceUrl + "sha1";
+ [ComVisible (false)]
+ public EncryptedXml EncryptedXml {
+ get { return encryptedXml; }
+ set { encryptedXml = value; }
+ }
public KeyInfo KeyInfo {
- get { return m_signature.KeyInfo; }
+ get {
+ if (m_signature.KeyInfo == null)
+ m_signature.KeyInfo = new KeyInfo ();
+ return m_signature.KeyInfo;
+ }
set { m_signature.KeyInfo = value; }
}
public void AddReference (Reference reference)
{
+ if (reference == null)
+ throw new ArgumentNullException ("reference");
m_signature.SignedInfo.AddReference (reference);
}
{
// These transformer modify input document, which should
// not affect to the input itself.
- if (t is XmlDsigXPathTransform ||
- t is XmlDsigEnvelopedSignatureTransform)
+ if (t is XmlDsigXPathTransform
+ || t is XmlDsigEnvelopedSignatureTransform
+ || t is XmlDecryptionTransform
+ )
input = (XmlDocument) input.Clone ();
t.LoadInput (input);
MemoryStream ms = new MemoryStream ();
XmlTextWriter xtw = new XmlTextWriter (ms, Encoding.UTF8);
((XmlDocument) obj).WriteTo (xtw);
+
+ xtw.Flush ();
+
+ // Rewind to the start of the stream
+ ms.Position = 0;
return ms;
}
else if (obj == null) {
XmlElement xel = GetIdElement (signatureElement.OwnerDocument, r.Uri.Substring (1));
if (xel == null)
throw new CryptographicException ("Manifest targeted by Reference was not found: " + r.Uri.Substring (1));
- doc.LoadXml (xel.OuterXml);
- FixupNamespaceNodes (xel, doc.DocumentElement);
+ doc.AppendChild (doc.ImportNode (xel, true));
+ FixupNamespaceNodes (xel, doc.DocumentElement, false);
}
}
else if (xmlResolver != null) {
return null;
}
- private void FixupNamespaceNodes (XmlElement src, XmlElement dst)
+ private void FixupNamespaceNodes (XmlElement src, XmlElement dst, bool ignoreDefault)
{
// add namespace nodes
foreach (XmlAttribute attr in src.SelectNodes ("namespace::*")) {
if (attr.LocalName == "xml")
continue;
- if (attr.OwnerElement == src)
+ if (ignoreDefault && attr.LocalName == "xmlns")
continue;
dst.SetAttributeNode (dst.OwnerDocument.ImportNode (attr, true) as XmlAttribute);
}
}
- [MonoTODO ("Need testing")]
- private byte[] GetReferenceHash (Reference r)
+ private byte[] GetReferenceHash (Reference r, bool check_hmac)
{
Stream s = null;
XmlDocument doc = null;
try {
// no way to know if valid without throwing an exception
Uri uri = new Uri (r.Uri);
- s = (Stream) xmlResolver.GetEntity (new Uri (r.Uri), null, typeof (Stream));
+ s = (Stream) xmlResolver.GetEntity (uri, null, typeof (Stream));
}
catch {
// may still be a local file (and maybe not xml)
}
}
if (objectName != null) {
+ XmlElement found = null;
foreach (DataObject obj in m_signature.ObjectList) {
if (obj.Id == objectName) {
- XmlElement xel = obj.GetXml ();
- doc.LoadXml (xel.OuterXml);
- FixupNamespaceNodes (xel, doc.DocumentElement);
+ found = obj.GetXml ();
+ found.SetAttribute ("xmlns", SignedXml.XmlDsigNamespaceUrl);
+ doc.AppendChild (doc.ImportNode (found, true));
+ // FIXME: there should be theoretical justification of copying namespace declaration nodes this way.
+ foreach (XmlNode n in found.ChildNodes)
+ // Do not copy default namespace as it must be xmldsig namespace for "Object" element.
+ if (n.NodeType == XmlNodeType.Element)
+ FixupNamespaceNodes (n as XmlElement, doc.DocumentElement, true);
break;
}
}
+ if (found == null && envdoc != null) {
+ found = GetIdElement (envdoc, objectName);
+ if (found != null) {
+ doc.AppendChild (doc.ImportNode (found, true));
+ FixupNamespaceNodes (found, doc.DocumentElement, false);
+ }
+ }
+ if (found == null)
+ throw new CryptographicException (String.Format ("Malformed reference object: {0}", objectName));
}
}
s = ApplyTransform (new XmlDsigC14NTransform (), doc);
}
}
- HashAlgorithm digest = GetHash (r.DigestMethod);
- return digest.ComputeHash (s);
+ HashAlgorithm digest = GetHash (r.DigestMethod, check_hmac);
+ return (digest == null) ? null : digest.ComputeHash (s);
}
private void DigestReferences ()
// assume SHA-1 if nothing is specified
if (r.DigestMethod == null)
r.DigestMethod = XmlDsigSHA1Url;
- r.DigestValue = GetReferenceHash (r);
+ r.DigestValue = GetReferenceHash (r, false);
}
}
}
// reuse hash - most document will always use the same hash
- private HashAlgorithm GetHash (string algorithm)
+ private HashAlgorithm GetHash (string algorithm, bool check_hmac)
{
HashAlgorithm hash = (HashAlgorithm) hashes [algorithm];
if (hash == null) {
// important before reusing an hash object
hash.Initialize ();
}
+ // we can sign using any hash algorith, including HMAC, but we can only verify hash (MS compatibility)
+ if (check_hmac && (hash is KeyedHashAlgorithm))
+ return null;
return hash;
}
// check digest (hash) for every reference
foreach (Reference r in referenceList) {
// stop at first broken reference
- byte[] hash = GetReferenceHash (r);
+ byte[] hash = GetReferenceHash (r, true);
if (! Compare (r.DigestValue, hash))
return false;
}
// check with supplied key
if (!CheckSignatureWithKey (key))
return null;
- }
- else {
+ } else {
if (Signature.KeyInfo == null)
- throw new CryptographicException ("At least one KeyInfo is required.");
+ return null;
// no supplied key, iterates all KeyInfo
while ((key = GetPublicKey ()) != null) {
if (CheckSignatureWithKey (key)) {
verifier.SetKey (key);
verifier.SetHashAlgorithm (sd.DigestAlgorithm);
- HashAlgorithm hash = GetHash (sd.DigestAlgorithm);
+ HashAlgorithm hash = GetHash (sd.DigestAlgorithm, true);
// get the hash of the C14N SignedInfo element
MemoryStream ms = (MemoryStream) SignedInfoTransformed ();
return false;
byte[] actual = macAlg.ComputeHash (s);
- // HMAC signature may be partial
+ // HMAC signature may be partial and specified by <HMACOutputLength>
if (m_signature.SignedInfo.SignatureLength != null) {
- int length = actual.Length;
- try {
- // SignatureLength is in bits
- length = (Int32.Parse (m_signature.SignedInfo.SignatureLength) >> 3);
- }
- catch {
- }
-
- if (length != actual.Length) {
+ int length = Int32.Parse (m_signature.SignedInfo.SignatureLength);
+ // we only support signatures with a multiple of 8 bits
+ // and the value must match the signature length
+ if ((length & 7) != 0)
+ throw new CryptographicException ("Signature length must be a multiple of 8 bits.");
+
+ // SignatureLength is in bits (and we works on bytes, only in multiple of 8 bits)
+ // and both values must match for a signature to be valid
+ length >>= 3;
+ if (length != m_signature.SignatureValue.Length)
+ throw new CryptographicException ("Invalid signature length.");
+
+ // is the length "big" enough to make the signature meaningful ?
+ // we use a minimum of 80 bits (10 bytes) or half the HMAC normal output length
+ // e.g. HMACMD5 output 128 bits but our minimum is 80 bits (not 64 bits)
+ int minimum = Math.Max (10, actual.Length / 2);
+ if (length < minimum)
+ throw new CryptographicException ("HMAC signature is too small");
+
+ if (length < actual.Length) {
byte[] trunked = new byte [length];
Buffer.BlockCopy (actual, 0, trunked, 0, length);
actual = trunked;
return false;
}
+ [MonoTODO]
+ [ComVisible (false)]
+ public bool CheckSignature (X509Certificate2 certificate, bool verifySignatureOnly)
+ {
+ throw new NotImplementedException ();
+ }
+
public bool CheckSignatureReturningKey (out AsymmetricAlgorithm signingKey)
{
signingKey = CheckSignatureInternal (null);
public void ComputeSignature ()
{
if (key != null) {
- // required before hashing
- m_signature.SignedInfo.SignatureMethod = key.SignatureAlgorithm;
+ if (m_signature.SignedInfo.SignatureMethod == null)
+ // required before hashing
+ m_signature.SignedInfo.SignatureMethod = key.SignatureAlgorithm;
+ else if (m_signature.SignedInfo.SignatureMethod != key.SignatureAlgorithm)
+ throw new CryptographicException ("Specified SignatureAlgorithm is not supported by the signing key.");
DigestReferences ();
AsymmetricSignatureFormatter signer = null;
if (signer != null) {
SignatureDescription sd = (SignatureDescription) CryptoConfig.CreateFromName (m_signature.SignedInfo.SignatureMethod);
- HashAlgorithm hash = GetHash (sd.DigestAlgorithm);
+ HashAlgorithm hash = GetHash (sd.DigestAlgorithm, false);
// get the hash of the C14N SignedInfo element
byte[] digest = hash.ComputeHash (SignedInfoTransformed ());
m_signature.SignatureValue = signer.CreateSignature (digest);
}
}
+ else
+ throw new CryptographicException ("signing key is not specified");
}
public void ComputeSignature (KeyedHashAlgorithm macAlg)
if (macAlg == null)
throw new ArgumentNullException ("macAlg");
- if (macAlg is HMACSHA1) {
- DigestReferences ();
+ string method = null;
- m_signature.SignedInfo.SignatureMethod = XmlDsigHMACSHA1Url;
- m_signature.SignatureValue = macAlg.ComputeHash (SignedInfoTransformed ());
+ if (macAlg is HMACSHA1) {
+ method = XmlDsigHMACSHA1Url;
+ } else if (macAlg is HMACSHA256) {
+ method = "http://www.w3.org/2001/04/xmldsig-more#hmac-sha256";
+ } else if (macAlg is HMACSHA384) {
+ method = "http://www.w3.org/2001/04/xmldsig-more#hmac-sha384";
+ } else if (macAlg is HMACSHA512) {
+ method = "http://www.w3.org/2001/04/xmldsig-more#hmac-sha512";
+ } else if (macAlg is HMACRIPEMD160) {
+ method = "http://www.w3.org/2001/04/xmldsig-more#hmac-ripemd160";
}
- else
+
+ if (method == null)
throw new CryptographicException ("unsupported algorithm");
+
+ DigestReferences ();
+ m_signature.SignedInfo.SignatureMethod = method;
+ m_signature.SignatureValue = macAlg.ComputeHash (SignedInfoTransformed ());
}
public virtual XmlElement GetIdElement (XmlDocument document, string idValue)
{
+ if ((document == null) || (idValue == null))
+ return null;
+
// this works only if there's a DTD or XSD available to define the ID
XmlElement xel = document.GetElementById (idValue);
if (xel == null) {
if (pkEnumerator == null) {
pkEnumerator = m_signature.KeyInfo.GetEnumerator ();
}
-
- if (pkEnumerator.MoveNext ()) {
+
+#if SECURITY_DEP
+ if (_x509Enumerator != null) {
+ if (_x509Enumerator.MoveNext ()) {
+ X509Certificate cert = (X509Certificate) _x509Enumerator.Current;
+ return new X509Certificate2 (cert.GetRawCertData ()).PublicKey.Key;
+ } else {
+ _x509Enumerator = null;
+ }
+ }
+#endif
+ while (pkEnumerator.MoveNext ()) {
AsymmetricAlgorithm key = null;
KeyInfoClause kic = (KeyInfoClause) pkEnumerator.Current;
key.FromXmlString (kic.GetXml ().InnerXml);
return key;
}
+
+#if SECURITY_DEP
+ if (kic is KeyInfoX509Data) {
+ _x509Enumerator = ((KeyInfoX509Data) kic).Certificates.GetEnumerator ();
+ if (_x509Enumerator.MoveNext ()) {
+ X509Certificate cert = (X509Certificate) _x509Enumerator.Current;
+ return new X509Certificate2 (cert.GetRawCertData ()).PublicKey.Key;
+ }
+ }
+#endif
}
return null;
}
public XmlElement GetXml ()
{
- return m_signature.GetXml ();
+ return m_signature.GetXml (envdoc);
}
public void LoadXml (XmlElement value)
signatureElement = value;
m_signature.LoadXml (value);
+ // Need to give the EncryptedXml object to the
+ // XmlDecryptionTransform to give it a fighting
+ // chance at decrypting the document.
+ foreach (Reference r in m_signature.SignedInfo.References) {
+ foreach (Transform t in r.TransformChain) {
+ if (t is XmlDecryptionTransform)
+ ((XmlDecryptionTransform) t).EncryptedXml = EncryptedXml;
+ }
+ }
}
-#if NET_1_1
[ComVisible (false)]
public XmlResolver Resolver {
set { xmlResolver = value; }
}
-#endif
}
}