GetIdElement must look for "ID" and "id". Fixes #4938
[mono.git] / mcs / class / System.Security / System.Security.Cryptography.Xml / SignedXml.cs
index 0deb351d9d15c09678bd9dfd2bd60476a1206086..05c2b091d98b229262f43bc1c25577656f0009a2 100644 (file)
@@ -38,10 +38,7 @@ using System.Security.Policy;
 using System.Net;
 using System.Text;
 using System.Xml;
-
-#if NET_2_0
 using System.Security.Cryptography.X509Certificates;
-#endif
 
 namespace System.Security.Cryptography.Xml {
 
@@ -56,7 +53,6 @@ namespace System.Security.Cryptography.Xml {
                public const string XmlDsigRSASHA1Url                           = XmlDsigNamespaceUrl + "rsa-sha1";
                public const string XmlDsigSHA1Url                              = XmlDsigNamespaceUrl + "sha1";
 
-#if NET_2_0
                public const string XmlDecryptionTransformUrl                   = "http://www.w3.org/2002/07/decrypt#XML";
                public const string XmlDsigBase64TransformUrl                   = XmlDsigNamespaceUrl + "base64";
                public const string XmlDsigC14NTransformUrl                     = XmlDsigCanonicalizationUrl;
@@ -69,7 +65,6 @@ namespace System.Security.Cryptography.Xml {
                public const string XmlLicenseTransformUrl                      = "urn:mpeg:mpeg21:2003:01-REL-R-NS:licenseTransform";
 
                private EncryptedXml encryptedXml;
-#endif
 
                protected Signature m_signature;
                private AsymmetricAlgorithm key;
@@ -79,15 +74,9 @@ namespace System.Security.Cryptography.Xml {
                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;
-#if NET_2_0
                private IEnumerator _x509Enumerator;
-#endif
 
                private static readonly char [] whitespaceChars = new char [] {' ', '\r', '\n', '\t'};
 
@@ -113,20 +102,16 @@ namespace System.Security.Cryptography.Xml {
                        envdoc.LoadXml (elem.OuterXml);
                }
 
-#if NET_2_0
                [ComVisible (false)]
                public EncryptedXml EncryptedXml {
                        get { return encryptedXml; }
                        set { encryptedXml = value; }
                }
-#endif
 
                public KeyInfo KeyInfo {
                        get {
-#if NET_2_0
                                if (m_signature.KeyInfo == null)
                                        m_signature.KeyInfo = new KeyInfo ();
-#endif
                                return m_signature.KeyInfo;
                        }
                        set { m_signature.KeyInfo = value; }
@@ -170,10 +155,8 @@ namespace System.Security.Cryptography.Xml {
 
                public void AddReference (Reference reference) 
                {
-#if NET_2_0
                        if (reference == null)
                                throw new ArgumentNullException ("reference");
-#endif
                        m_signature.SignedInfo.AddReference (reference);
                }
 
@@ -183,9 +166,7 @@ namespace System.Security.Cryptography.Xml {
                        // not affect to the input itself.
                        if (t is XmlDsigXPathTransform 
                                || t is XmlDsigEnvelopedSignatureTransform
-#if NET_2_0
                                || t is XmlDecryptionTransform
-#endif
                        )
                                input = (XmlDocument) input.Clone ();
 
@@ -236,8 +217,8 @@ namespace System.Security.Cryptography.Xml {
                                        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) {
@@ -257,20 +238,19 @@ namespace System.Security.Cryptography.Xml {
                        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;
@@ -314,14 +294,29 @@ namespace System.Security.Cryptography.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));
                                }
                        }
 
@@ -352,8 +347,8 @@ namespace System.Security.Cryptography.Xml {
                                        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 () 
@@ -364,7 +359,7 @@ namespace System.Security.Cryptography.Xml {
                                // assume SHA-1 if nothing is specified
                                if (r.DigestMethod == null)
                                        r.DigestMethod = XmlDsigSHA1Url;
-                               r.DigestValue = GetReferenceHash (r);
+                               r.DigestValue = GetReferenceHash (r, false);
                        }
                }
 
@@ -433,7 +428,7 @@ namespace System.Security.Cryptography.Xml {
                }
 
                // 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) {
@@ -447,6 +442,9 @@ namespace System.Security.Cryptography.Xml {
                                // 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;
                }
 
@@ -463,7 +461,7 @@ namespace System.Security.Cryptography.Xml {
                        // 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;
                        }
@@ -486,13 +484,8 @@ namespace System.Security.Cryptography.Xml {
                                if (!CheckSignatureWithKey (key))
                                        return null;
                        } else {
-#if NET_2_0
                                if (Signature.KeyInfo == null)
                                        return null;
-#else
-                               if (Signature.KeyInfo == null)
-                                       throw new CryptographicException ("At least one KeyInfo is required.");
-#endif
                                // no supplied key, iterates all KeyInfo
                                while ((key = GetPublicKey ()) != null) {
                                        if (CheckSignatureWithKey (key)) {
@@ -538,7 +531,7 @@ namespace System.Security.Cryptography.Xml {
                                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 ();
 
@@ -580,17 +573,28 @@ namespace System.Security.Cryptography.Xml {
                                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;
@@ -605,14 +609,12 @@ namespace System.Security.Cryptography.Xml {
                        return false;
                }
 
-#if NET_2_0
                [MonoTODO]
                [ComVisible (false)]
                public bool CheckSignature (X509Certificate2 certificate, bool verifySignatureOnly)
                {
                        throw new NotImplementedException ();
                }
-#endif
 
                public bool CheckSignatureReturningKey (out AsymmetricAlgorithm signingKey) 
                {
@@ -640,7 +642,7 @@ namespace System.Security.Cryptography.Xml {
                                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 ());
 
@@ -648,6 +650,8 @@ namespace System.Security.Cryptography.Xml {
                                        m_signature.SignatureValue = signer.CreateSignature (digest);
                                }
                        }
+                       else
+                               throw new CryptographicException ("signing key is not specified");
                }
 
                public void ComputeSignature (KeyedHashAlgorithm macAlg) 
@@ -655,23 +659,44 @@ namespace System.Security.Cryptography.Xml {
                        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 ());
-                       }
-                       else 
+                       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";
+                       }
+
+                       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) {
                                // search an "undefined" ID
                                xel = (XmlElement) document.SelectSingleNode ("//*[@Id='" + idValue + "']");
+                               if (xel == null) {
+                                       xel = (XmlElement) document.SelectSingleNode ("//*[@ID='" + idValue + "']");
+                                       if (xel == null) {
+                                               xel = (XmlElement) document.SelectSingleNode ("//*[@id='" + idValue + "']");
+                                       }
+                               }
                        }
                        return xel;
                }
@@ -687,7 +712,7 @@ namespace System.Security.Cryptography.Xml {
                                pkEnumerator = m_signature.KeyInfo.GetEnumerator ();
                        }
                        
-#if NET_2_0
+#if SECURITY_DEP
                        if (_x509Enumerator != null) {
                                if (_x509Enumerator.MoveNext ()) {
                                        X509Certificate cert = (X509Certificate) _x509Enumerator.Current;
@@ -711,7 +736,7 @@ namespace System.Security.Cryptography.Xml {
                                        return key;
                                }
 
-#if NET_2_0
+#if SECURITY_DEP
                                if (kic is KeyInfoX509Data) {
                                        _x509Enumerator = ((KeyInfoX509Data) kic).Certificates.GetEnumerator ();
                                        if (_x509Enumerator.MoveNext ()) {
@@ -736,7 +761,6 @@ namespace System.Security.Cryptography.Xml {
 
                        signatureElement = value;
                        m_signature.LoadXml (value);
-#if NET_2_0
                        // Need to give the EncryptedXml object to the 
                        // XmlDecryptionTransform to give it a fighting 
                        // chance at decrypting the document.
@@ -746,14 +770,11 @@ namespace System.Security.Cryptography.Xml {
                                                ((XmlDecryptionTransform) t).EncryptedXml = EncryptedXml;
                                }
                        }
-#endif
                }
 
-#if NET_1_1
                [ComVisible (false)]
                public XmlResolver Resolver {
                        set { xmlResolver = value; }
                }
-#endif
        }
 }