File: System\Security\Cryptography\Xml\SignedXml.cs
Web Access
Project: src\src\libraries\System.Security.Cryptography.Xml\src\System.Security.Cryptography.Xml.csproj (System.Security.Cryptography.Xml)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Security.Cryptography.X509Certificates;
using System.Xml;
 
namespace System.Security.Cryptography.Xml
{
    public class SignedXml
    {
        protected Signature m_signature;
        protected string? m_strSigningKeyName;
 
        private AsymmetricAlgorithm? _signingKey;
        private XmlDocument? _containingDocument;
        private IEnumerator? _keyInfoEnum;
        private X509Certificate2Collection? _x509Collection;
        private X509Certificate2Enumerator? _x509Enum;
 
        private bool[]? _refProcessed;
        private int[]? _refLevelCache;
 
        internal XmlResolver? _xmlResolver;
        internal XmlElement? _context;
        private bool _bResolverSet;
 
        private Func<SignedXml, bool> _signatureFormatValidator = DefaultSignatureFormatValidator;
        private Collection<string> _safeCanonicalizationMethods;
 
        // Built in canonicalization algorithm URIs
        private static IList<string>? s_knownCanonicalizationMethods;
        // Built in transform algorithm URIs (excluding canonicalization URIs)
        private static IList<string>? s_defaultSafeTransformMethods;
 
        // additional HMAC Url identifiers
        private const string XmlDsigMoreHMACMD5Url = "http://www.w3.org/2001/04/xmldsig-more#hmac-md5";
        private const string XmlDsigMoreHMACSHA256Url = "http://www.w3.org/2001/04/xmldsig-more#hmac-sha256";
        private const string XmlDsigMoreHMACSHA384Url = "http://www.w3.org/2001/04/xmldsig-more#hmac-sha384";
        private const string XmlDsigMoreHMACSHA512Url = "http://www.w3.org/2001/04/xmldsig-more#hmac-sha512";
        private const string XmlDsigMoreHMACRIPEMD160Url = "http://www.w3.org/2001/04/xmldsig-more#hmac-ripemd160";
 
        // defines the XML encryption processing rules
        private EncryptedXml? _exml;
 
        //
        // public constant Url identifiers most frequently used within the XML Signature classes
        //
 
        public const string XmlDsigNamespaceUrl = "http://www.w3.org/2000/09/xmldsig#";
        public const string XmlDsigMinimalCanonicalizationUrl = "http://www.w3.org/2000/09/xmldsig#minimal";
        public const string XmlDsigCanonicalizationUrl = XmlDsigC14NTransformUrl;
        public const string XmlDsigCanonicalizationWithCommentsUrl = XmlDsigC14NWithCommentsTransformUrl;
 
        public const string XmlDsigSHA1Url = "http://www.w3.org/2000/09/xmldsig#sha1";
        public const string XmlDsigDSAUrl = "http://www.w3.org/2000/09/xmldsig#dsa-sha1";
        public const string XmlDsigRSASHA1Url = "http://www.w3.org/2000/09/xmldsig#rsa-sha1";
        public const string XmlDsigHMACSHA1Url = "http://www.w3.org/2000/09/xmldsig#hmac-sha1";
 
        public const string XmlDsigSHA256Url = "http://www.w3.org/2001/04/xmlenc#sha256";
        public const string XmlDsigRSASHA256Url = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256";
 
        // Yes, SHA384 is in the xmldsig-more namespace even though all the other SHA variants are in xmlenc. That's the standard.
        public const string XmlDsigSHA384Url = "http://www.w3.org/2001/04/xmldsig-more#sha384";
        public const string XmlDsigRSASHA384Url = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha384";
 
        public const string XmlDsigSHA512Url = "http://www.w3.org/2001/04/xmlenc#sha512";
        public const string XmlDsigRSASHA512Url = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha512";
 
        public const string XmlDsigC14NTransformUrl = "http://www.w3.org/TR/2001/REC-xml-c14n-20010315";
        public const string XmlDsigC14NWithCommentsTransformUrl = "http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments";
        public const string XmlDsigExcC14NTransformUrl = "http://www.w3.org/2001/10/xml-exc-c14n#";
        public const string XmlDsigExcC14NWithCommentsTransformUrl = "http://www.w3.org/2001/10/xml-exc-c14n#WithComments";
        public const string XmlDsigBase64TransformUrl = "http://www.w3.org/2000/09/xmldsig#base64";
        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 XmlDsigEnvelopedSignatureTransformUrl = "http://www.w3.org/2000/09/xmldsig#enveloped-signature";
        public const string XmlDecryptionTransformUrl = "http://www.w3.org/2002/07/decrypt#XML";
        public const string XmlLicenseTransformUrl = "urn:mpeg:mpeg21:2003:01-REL-R-NS:licenseTransform";
 
        //
        // public constructors
        //
 
        [RequiresDynamicCode(CryptoHelpers.XsltRequiresDynamicCodeMessage)]
        [RequiresUnreferencedCode(CryptoHelpers.CreateFromNameUnreferencedCodeMessage)]
        public SignedXml()
        {
            Initialize(null);
        }
 
        [RequiresDynamicCode(CryptoHelpers.XsltRequiresDynamicCodeMessage)]
        [RequiresUnreferencedCode(CryptoHelpers.CreateFromNameUnreferencedCodeMessage)]
        public SignedXml(XmlDocument document)
        {
            if (document is null)
            {
                throw new ArgumentNullException(nameof(document));
            }
 
            Initialize(document.DocumentElement);
        }
 
        [RequiresDynamicCode(CryptoHelpers.XsltRequiresDynamicCodeMessage)]
        [RequiresUnreferencedCode(CryptoHelpers.CreateFromNameUnreferencedCodeMessage)]
        public SignedXml(XmlElement elem)
        {
            if (elem is null)
            {
                throw new ArgumentNullException(nameof(elem));
            }
 
            Initialize(elem);
        }
 
        [MemberNotNull(nameof(m_signature))]
        [MemberNotNull(nameof(_safeCanonicalizationMethods))]
        [RequiresDynamicCode(CryptoHelpers.XsltRequiresDynamicCodeMessage)]
        [RequiresUnreferencedCode(CryptoHelpers.CreateFromNameUnreferencedCodeMessage)]
        private void Initialize(XmlElement? element)
        {
            _containingDocument = element?.OwnerDocument;
            _context = element;
            m_signature = new Signature();
            m_signature.SignedXml = this;
            m_signature.SignedInfo = new SignedInfo();
            _signingKey = null;
 
            _safeCanonicalizationMethods = new Collection<string>(KnownCanonicalizationMethods);
        }
 
        //
        // public properties
        //
 
        /// <internalonly/>
        public string? SigningKeyName
        {
            get { return m_strSigningKeyName; }
            set { m_strSigningKeyName = value; }
        }
 
        public XmlResolver Resolver
        {
            // This property only has a setter. The rationale for this is that we don't have a good value
            // to return when it has not been explicitely set, as we are using XmlSecureResolver by default
            set
            {
                _xmlResolver = value;
                _bResolverSet = true;
            }
        }
 
        internal bool ResolverSet
        {
            get { return _bResolverSet; }
        }
 
        public Func<SignedXml, bool> SignatureFormatValidator
        {
            get { return _signatureFormatValidator; }
            set { _signatureFormatValidator = value; }
        }
 
        public Collection<string> SafeCanonicalizationMethods
        {
            get { return _safeCanonicalizationMethods; }
        }
 
        public AsymmetricAlgorithm? SigningKey
        {
            get { return _signingKey; }
            set { _signingKey = value; }
        }
 
        [AllowNull]
        public EncryptedXml EncryptedXml
        {
            [UnconditionalSuppressMessage("AOT", "IL3050:RequiresDynamicCode", Justification = "ctors are marked as RDC")]
            [UnconditionalSuppressMessage("ILLink", "IL2026:RequiresUnreferencedCode", Justification = "ctors are marked as RUC")]
            get => _exml ??= new EncryptedXml(_containingDocument!); // default processing rules
            set => _exml = value;
        }
 
        public Signature Signature
        {
            get { return m_signature; }
        }
 
        public SignedInfo? SignedInfo
        {
            get { return m_signature.SignedInfo; }
        }
 
        public string? SignatureMethod
        {
            get { return m_signature.SignedInfo!.SignatureMethod; }
        }
 
        public string? SignatureLength
        {
            get { return m_signature.SignedInfo!.SignatureLength; }
        }
 
        public byte[]? SignatureValue
        {
            get { return m_signature.SignatureValue; }
        }
 
        public KeyInfo KeyInfo
        {
            get { return m_signature.KeyInfo; }
            set { m_signature.KeyInfo = value; }
        }
 
        public XmlElement GetXml()
        {
            // If we have a document context, then return a signature element in this context
            if (_containingDocument != null)
                return m_signature.GetXml(_containingDocument);
            else
                return m_signature.GetXml();
        }
 
        [UnconditionalSuppressMessage("AOT", "IL3050:RequiresDynamicCode", Justification = "ctors are marked as RDC")]
        [UnconditionalSuppressMessage("ILLink", "IL2026:RequiresUnreferencedCode", Justification = "ctors are marked as RUC")]
        public void LoadXml(XmlElement value)
        {
            if (value is null)
            {
                throw new ArgumentNullException(nameof(value));
            }
 
            m_signature.LoadXml(value);
 
            _context ??= value;
 
            _bCacheValid = false;
        }
 
        //
        // public methods
        //
 
        public void AddReference(Reference reference)
        {
            m_signature.SignedInfo!.AddReference(reference);
        }
 
        public void AddObject(DataObject dataObject)
        {
            m_signature.AddObject(dataObject);
        }
 
        public bool CheckSignature()
        {
            return CheckSignatureReturningKey(out _);
        }
 
        public bool CheckSignatureReturningKey(out AsymmetricAlgorithm? signingKey)
        {
            SignedXmlDebugLog.LogBeginSignatureVerification(this, _context);
 
            signingKey = null;
            bool bRet = false;
            AsymmetricAlgorithm? key;
 
            if (!CheckSignatureFormat())
            {
                return false;
            }
 
            do
            {
                key = GetPublicKey();
                if (key != null)
                {
                    bRet = CheckSignature(key);
                    SignedXmlDebugLog.LogVerificationResult(this, key, bRet);
                }
            } while (key != null && bRet == false);
 
            signingKey = key;
            return bRet;
        }
 
        public bool CheckSignature(AsymmetricAlgorithm key)
        {
            if (!CheckSignatureFormat())
            {
                return false;
            }
 
            if (!CheckSignedInfo(key))
            {
                SignedXmlDebugLog.LogVerificationFailure(this, SR.Log_VerificationFailed_SignedInfo);
                return false;
            }
 
            // Now is the time to go through all the references and see if their DigestValues are good
            if (!CheckDigestedReferences())
            {
                SignedXmlDebugLog.LogVerificationFailure(this, SR.Log_VerificationFailed_References);
                return false;
            }
 
            SignedXmlDebugLog.LogVerificationResult(this, key, true);
            return true;
        }
 
        public bool CheckSignature(KeyedHashAlgorithm macAlg)
        {
            if (!CheckSignatureFormat())
            {
                return false;
            }
 
            if (!CheckSignedInfo(macAlg))
            {
                SignedXmlDebugLog.LogVerificationFailure(this, SR.Log_VerificationFailed_SignedInfo);
                return false;
            }
 
            if (!CheckDigestedReferences())
            {
                SignedXmlDebugLog.LogVerificationFailure(this, SR.Log_VerificationFailed_References);
                return false;
            }
 
            SignedXmlDebugLog.LogVerificationResult(this, macAlg, true);
            return true;
        }
 
        public bool CheckSignature(X509Certificate2 certificate, bool verifySignatureOnly)
        {
            if (!verifySignatureOnly)
            {
                // Check key usages to make sure it is good for signing.
                foreach (X509Extension extension in certificate.Extensions)
                {
                    if (string.Equals(extension.Oid!.Value, "2.5.29.15" /* szOID_KEY_USAGE */, StringComparison.OrdinalIgnoreCase))
                    {
                        X509KeyUsageExtension keyUsage = new X509KeyUsageExtension();
                        keyUsage.CopyFrom(extension);
                        SignedXmlDebugLog.LogVerifyKeyUsage(this, certificate, keyUsage);
 
                        bool validKeyUsage = (keyUsage.KeyUsages & X509KeyUsageFlags.DigitalSignature) != 0 ||
                                             (keyUsage.KeyUsages & X509KeyUsageFlags.NonRepudiation) != 0;
 
                        if (!validKeyUsage)
                        {
                            SignedXmlDebugLog.LogVerificationFailure(this, SR.Log_VerificationFailed_X509KeyUsage);
                            return false;
                        }
                        break;
                    }
                }
 
                // Do the chain verification to make sure the certificate is valid.
                X509Chain chain = new X509Chain();
                chain.ChainPolicy.ExtraStore.AddRange(BuildBagOfCerts());
                bool chainVerified = chain.Build(certificate);
                SignedXmlDebugLog.LogVerifyX509Chain(this, chain, certificate);
 
                if (!chainVerified)
                {
                    SignedXmlDebugLog.LogVerificationFailure(this, SR.Log_VerificationFailed_X509Chain);
                    return false;
                }
            }
 
            using (AsymmetricAlgorithm? publicKey = Utils.GetAnyPublicKey(certificate))
            {
                if (!CheckSignature(publicKey!))
                {
                    return false;
                }
            }
 
            SignedXmlDebugLog.LogVerificationResult(this, certificate, true);
            return true;
        }
 
        [UnconditionalSuppressMessage("ILLink", "IL2026:RequiresUnreferencedCode", Justification = "ctors are marked as RDC")]
        public void ComputeSignature()
        {
            SignedXmlDebugLog.LogBeginSignatureComputation(this, _context!);
 
            BuildDigestedReferences();
 
            // Load the key
            AsymmetricAlgorithm? key = SigningKey;
 
            if (key == null)
                throw new CryptographicException(SR.Cryptography_Xml_LoadKeyFailed);
 
            // Check the signature algorithm associated with the key so that we can accordingly set the signature method
            if (SignedInfo!.SignatureMethod == null)
            {
                if (key is DSA)
                {
                    SignedInfo.SignatureMethod = XmlDsigDSAUrl;
                }
                else if (key is RSA)
                {
                    // Default to RSA-SHA256
                    SignedInfo.SignatureMethod ??= XmlDsigRSASHA256Url;
                }
                else
                {
                    throw new CryptographicException(SR.Cryptography_Xml_CreatedKeyFailed);
                }
            }
 
            // See if there is a signature description class defined in the Config file
            SignatureDescription? signatureDescription = CryptoHelpers.CreateNonTransformFromName<SignatureDescription>(SignedInfo.SignatureMethod);
            if (signatureDescription == null)
                throw new CryptographicException(SR.Cryptography_Xml_SignatureDescriptionNotCreated);
            HashAlgorithm? hashAlg = signatureDescription.CreateDigest();
            if (hashAlg == null)
                throw new CryptographicException(SR.Cryptography_Xml_CreateHashAlgorithmFailed);
 
            // Updates the HashAlgorithm's state for signing with the signature formatter below.
            // The return value is not needed.
            GetC14NDigest(hashAlg);
 
            AsymmetricSignatureFormatter asymmetricSignatureFormatter = signatureDescription.CreateFormatter(key);
 
            SignedXmlDebugLog.LogSigning(this, key, signatureDescription, hashAlg, asymmetricSignatureFormatter);
            m_signature.SignatureValue = asymmetricSignatureFormatter.CreateSignature(hashAlg);
        }
 
        public void ComputeSignature(KeyedHashAlgorithm macAlg)
        {
            if (macAlg is null)
            {
                throw new ArgumentNullException(nameof(macAlg));
            }
 
            HMAC? hash = macAlg as HMAC;
            if (hash == null)
                throw new CryptographicException(SR.Cryptography_Xml_SignatureMethodKeyMismatch);
 
            int signatureLength;
            if (m_signature.SignedInfo!.SignatureLength == null)
                signatureLength = hash.HashSize;
            else
                signatureLength = Convert.ToInt32(m_signature.SignedInfo.SignatureLength, null);
            // signatureLength should be less than hash size
            if (signatureLength < 0 || signatureLength > hash.HashSize)
                throw new CryptographicException(SR.Cryptography_Xml_InvalidSignatureLength);
            if (signatureLength % 8 != 0)
                throw new CryptographicException(SR.Cryptography_Xml_InvalidSignatureLength2);
 
            BuildDigestedReferences();
            SignedInfo!.SignatureMethod = hash.HashName switch
            {
                "SHA1" => SignedXml.XmlDsigHMACSHA1Url,
                "SHA256" => SignedXml.XmlDsigMoreHMACSHA256Url,
                "SHA384" => SignedXml.XmlDsigMoreHMACSHA384Url,
                "SHA512" => SignedXml.XmlDsigMoreHMACSHA512Url,
                "MD5" => SignedXml.XmlDsigMoreHMACMD5Url,
                "RIPEMD160" => SignedXml.XmlDsigMoreHMACRIPEMD160Url,
                _ => throw new CryptographicException(SR.Cryptography_Xml_SignatureMethodKeyMismatch),
            };
            byte[] hashValue = GetC14NDigest(hash);
 
            SignedXmlDebugLog.LogSigning(this, hash);
            m_signature.SignatureValue = new byte[signatureLength / 8];
            Buffer.BlockCopy(hashValue, 0, m_signature.SignatureValue, 0, signatureLength / 8);
        }
 
        //
        // virtual methods
        //
 
        protected virtual AsymmetricAlgorithm? GetPublicKey()
        {
            if (KeyInfo == null)
                throw new CryptographicException(SR.Cryptography_Xml_KeyInfoRequired);
 
            if (_x509Enum != null)
            {
                AsymmetricAlgorithm? key = GetNextCertificatePublicKey();
                if (key != null)
                    return key;
            }
 
            _keyInfoEnum ??= KeyInfo.GetEnumerator();
 
            // In our implementation, we move to the next KeyInfo clause which is an RSAKeyValue, DSAKeyValue or KeyInfoX509Data
            while (_keyInfoEnum.MoveNext())
            {
                switch (_keyInfoEnum.Current)
                {
                    case RSAKeyValue rsaKeyValue:
                        return rsaKeyValue.Key;
 
                    case DSAKeyValue dsaKeyValue:
                        return dsaKeyValue.Key;
 
                    case KeyInfoX509Data x509Data:
                        _x509Collection = Utils.BuildBagOfCerts(x509Data, CertUsageType.Verification);
                        if (_x509Collection.Count > 0)
                        {
                            _x509Enum = _x509Collection.GetEnumerator();
                            AsymmetricAlgorithm? key = GetNextCertificatePublicKey();
                            if (key != null)
                                return key;
                        }
                        break;
                }
            }
 
            return null;
        }
 
        private X509Certificate2Collection BuildBagOfCerts()
        {
            X509Certificate2Collection collection = new X509Certificate2Collection();
            if (KeyInfo != null)
            {
                foreach (KeyInfoClause clause in KeyInfo)
                {
                    if (clause is KeyInfoX509Data x509Data)
                    {
                        collection.AddRange(Utils.BuildBagOfCerts(x509Data, CertUsageType.Verification));
                    }
                }
            }
 
            return collection;
        }
 
        private AsymmetricAlgorithm? GetNextCertificatePublicKey()
        {
            while (_x509Enum!.MoveNext())
            {
                X509Certificate2? certificate = (X509Certificate2?)_x509Enum.Current;
                if (certificate != null)
                    return Utils.GetAnyPublicKey(certificate);
            }
 
            return null;
        }
 
        public virtual XmlElement? GetIdElement(XmlDocument? document, string idValue)
        {
            return DefaultGetIdElement(document, idValue);
        }
 
        internal static XmlElement? DefaultGetIdElement(XmlDocument? document, string idValue)
        {
            if (document == null)
                return null;
 
            try
            {
                XmlConvert.VerifyNCName(idValue);
            }
            catch (XmlException)
            {
                // Identifiers are required to be an NCName
                //   (xml:id version 1.0, part 4, paragraph 2, bullet 1)
                //
                // If it isn't an NCName, it isn't allowed to match.
                return null;
            }
 
            // Get the element with idValue
            XmlElement? elem = document.GetElementById(idValue);
 
            if (elem != null)
            {
                // Have to check for duplicate ID values from the DTD.
 
                XmlDocument docClone = (XmlDocument)document.CloneNode(true);
                XmlElement? cloneElem = docClone.GetElementById(idValue);
 
                // If it's null here we want to know about it, because it means that
                // GetElementById failed to work across the cloning, and our uniqueness
                // test is invalid.
                System.Diagnostics.Debug.Assert(cloneElem != null);
 
                // Guard against null anyways
                if (cloneElem != null)
                {
                    cloneElem.Attributes.RemoveAll();
 
                    XmlElement? cloneElem2 = docClone.GetElementById(idValue);
 
                    if (cloneElem2 != null)
                    {
                        throw new CryptographicException(
                            SR.Cryptography_Xml_InvalidReference);
                    }
                }
 
                return elem;
            }
 
            elem = GetSingleReferenceTarget(document, "Id", idValue);
            if (elem != null)
                return elem;
            elem = GetSingleReferenceTarget(document, "id", idValue);
            if (elem != null)
                return elem;
            elem = GetSingleReferenceTarget(document, "ID", idValue);
 
            return elem;
        }
 
        //
        // private methods
        //
 
        private bool _bCacheValid;
        private byte[]? _digestedSignedInfo;
 
        private static bool DefaultSignatureFormatValidator(SignedXml signedXml)
        {
            // Reject the signature if it uses a truncated HMAC
            if (signedXml.DoesSignatureUseTruncatedHmac())
            {
                return false;
            }
 
            // Reject the signature if it uses a canonicalization algorithm other than
            // one of the ones explicitly allowed
            if (!signedXml.DoesSignatureUseSafeCanonicalizationMethod())
            {
                return false;
            }
 
            // Otherwise accept it
            return true;
        }
 
        // Validation function to see if the current signature is signed with a truncated HMAC - one which
        // has a signature length of fewer bits than the whole HMAC output.
        [UnconditionalSuppressMessage("ILLink", "IL2026:RequiresUnreferencedCode", Justification = "ctors are marked as RDC")]
        private bool DoesSignatureUseTruncatedHmac()
        {
            // If we're not using the SignatureLength property, then we're not truncating the signature length
            if (SignedInfo!.SignatureLength == null)
            {
                return false;
            }
 
            // See if we're signed witn an HMAC algorithm
            HMAC? hmac = CryptoHelpers.CreateNonTransformFromName<HMAC>(SignatureMethod!);
            if (hmac == null)
            {
                // We aren't signed with an HMAC algorithm, so we cannot have a truncated HMAC
                return false;
            }
 
            // Figure out how many bits the signature is using
            int actualSignatureSize;
            if (!int.TryParse(SignedInfo.SignatureLength, out actualSignatureSize))
            {
                // If the value wasn't a valid integer, then we'll conservatively reject it all together
                return true;
            }
 
            // Make sure the full HMAC signature size is the same size that was specified in the XML
            // signature.  If the actual signature size is not exactly the same as the full HMAC size, then
            // reject the signature.
            return actualSignatureSize != hmac.HashSize;
        }
 
        // Validation function to see if the signature uses a canonicalization algorithm from our list
        // of approved algorithm URIs.
        private bool DoesSignatureUseSafeCanonicalizationMethod()
        {
            foreach (string safeAlgorithm in SafeCanonicalizationMethods)
            {
                if (string.Equals(safeAlgorithm, SignedInfo!.CanonicalizationMethod, StringComparison.OrdinalIgnoreCase))
                {
                    return true;
                }
            }
 
            SignedXmlDebugLog.LogUnsafeCanonicalizationMethod(this, SignedInfo!.CanonicalizationMethod, SafeCanonicalizationMethods);
            return false;
        }
 
        private bool ReferenceUsesSafeTransformMethods(Reference reference)
        {
            TransformChain transformChain = reference.TransformChain;
            int transformCount = transformChain.Count;
 
            for (int i = 0; i < transformCount; i++)
            {
                Transform transform = transformChain[i];
 
                if (!IsSafeTransform(transform.Algorithm!))
                {
                    return false;
                }
            }
 
            return true;
        }
 
        private bool IsSafeTransform(string transformAlgorithm)
        {
            // All canonicalization algorithms are valid transform algorithms.
            foreach (string safeAlgorithm in SafeCanonicalizationMethods)
            {
                if (string.Equals(safeAlgorithm, transformAlgorithm, StringComparison.OrdinalIgnoreCase))
                {
                    return true;
                }
            }
 
            foreach (string safeAlgorithm in DefaultSafeTransformMethods)
            {
                if (string.Equals(safeAlgorithm, transformAlgorithm, StringComparison.OrdinalIgnoreCase))
                {
                    return true;
                }
            }
 
            SignedXmlDebugLog.LogUnsafeTransformMethod(
                this,
                transformAlgorithm,
                SafeCanonicalizationMethods,
                DefaultSafeTransformMethods);
 
            return false;
        }
 
        // Get a list of the built in canonicalization algorithms, as well as any that the machine admin has
        // added to the valid set.
        private static IList<string> KnownCanonicalizationMethods
        {
            get
            {
                if (s_knownCanonicalizationMethods == null)
                {
                    // Start with the list that the machine admin added, if any
                    List<string> safeAlgorithms = new List<string>();
 
                    // Built in algorithms
                    safeAlgorithms.Add(XmlDsigC14NTransformUrl);
                    safeAlgorithms.Add(XmlDsigC14NWithCommentsTransformUrl);
                    safeAlgorithms.Add(XmlDsigExcC14NTransformUrl);
                    safeAlgorithms.Add(XmlDsigExcC14NWithCommentsTransformUrl);
 
                    s_knownCanonicalizationMethods = safeAlgorithms;
                }
 
                return s_knownCanonicalizationMethods;
            }
        }
 
        private static IList<string> DefaultSafeTransformMethods
        {
            get
            {
                if (s_defaultSafeTransformMethods == null)
                {
                    List<string> safeAlgorithms = new List<string>();
 
                    // Built in algorithms
 
                    // KnownCanonicalizationMethods don't need to be added here, because
                    // the validator will automatically accept those.
                    //
                    // xmldsig 6.6.1:
                    //     Any canonicalization algorithm that can be used for
                    //     CanonicalizationMethod can be used as a Transform.
                    safeAlgorithms.Add(XmlDsigEnvelopedSignatureTransformUrl);
                    safeAlgorithms.Add(XmlDsigBase64TransformUrl);
                    safeAlgorithms.Add(XmlLicenseTransformUrl);
                    safeAlgorithms.Add(XmlDecryptionTransformUrl);
 
                    s_defaultSafeTransformMethods = safeAlgorithms;
                }
 
                return s_defaultSafeTransformMethods;
            }
        }
 
        [UnconditionalSuppressMessage("ILLink", "IL2026:RequiresUnreferencedCode", Justification = "ctors are marked as RDC")]
        private byte[] GetC14NDigest(HashAlgorithm hash)
        {
            bool isKeyedHashAlgorithm = hash is KeyedHashAlgorithm;
            if (isKeyedHashAlgorithm || !_bCacheValid || !SignedInfo!.CacheValid)
            {
                string? baseUri = _containingDocument?.BaseURI;
                XmlResolver? resolver = (_bResolverSet ? _xmlResolver : XmlResolverHelper.GetThrowingResolver());
                XmlDocument doc = Utils.PreProcessElementInput(SignedInfo!.GetXml(), resolver!, baseUri);
 
                // Add non default namespaces in scope
                CanonicalXmlNodeList? namespaces = (_context == null ? null : Utils.GetPropagatedAttributes(_context));
                SignedXmlDebugLog.LogNamespacePropagation(this, namespaces);
                Utils.AddNamespaces(doc.DocumentElement!, namespaces);
 
                Transform c14nMethodTransform = SignedInfo.CanonicalizationMethodObject;
                c14nMethodTransform.Resolver = resolver;
                c14nMethodTransform.BaseURI = baseUri;
 
                SignedXmlDebugLog.LogBeginCanonicalization(this, c14nMethodTransform);
                c14nMethodTransform.LoadInput(doc);
                SignedXmlDebugLog.LogCanonicalizedOutput(this, c14nMethodTransform);
                _digestedSignedInfo = c14nMethodTransform.GetDigestedOutput(hash);
 
                _bCacheValid = !isKeyedHashAlgorithm;
            }
            return _digestedSignedInfo!;
        }
 
        private int GetReferenceLevel(int index, ArrayList references)
        {
            Debug.Assert(_refProcessed != null);
            Debug.Assert(_refLevelCache != null);
 
            if (_refProcessed[index]) return _refLevelCache[index];
            _refProcessed[index] = true;
            Reference reference = (Reference)references[index]!;
            if (string.IsNullOrEmpty(reference.Uri) || (reference.Uri.Length > 0 && reference.Uri[0] != '#'))
            {
                _refLevelCache[index] = 0;
                return 0;
            }
            if (reference.Uri.Length > 0 && reference.Uri[0] == '#')
            {
                string idref = Utils.ExtractIdFromLocalUri(reference.Uri);
                if (idref == "xpointer(/)")
                {
                    _refLevelCache[index] = 0;
                    return 0;
                }
                // If this is pointing to another reference
                for (int j = 0; j < references.Count; ++j)
                {
                    if (((Reference)references[j]!).Id == idref)
                    {
                        _refLevelCache[index] = GetReferenceLevel(j, references) + 1;
                        return (_refLevelCache[index]);
                    }
                }
                // Then the reference points to an object tag
                _refLevelCache[index] = 0;
                return 0;
            }
            // Malformed reference
            throw new CryptographicException(SR.Cryptography_Xml_InvalidReference);
        }
 
        private sealed class ReferenceLevelSortOrder : IComparer
        {
            private ArrayList? _references;
            public ReferenceLevelSortOrder() { }
 
            public ArrayList References
            {
                get { return _references!; }
                set { _references = value; }
            }
 
            public int Compare(object? a, object? b)
            {
                Reference? referenceA = a as Reference;
                Reference? referenceB = b as Reference;
 
                // Get the indexes
                int iIndexA = 0;
                int iIndexB = 0;
                int i = 0;
                foreach (Reference reference in References)
                {
                    if (reference == referenceA) iIndexA = i;
                    if (reference == referenceB) iIndexB = i;
                    i++;
                }
 
                int iLevelA = referenceA!.SignedXml!.GetReferenceLevel(iIndexA, References);
                int iLevelB = referenceB!.SignedXml!.GetReferenceLevel(iIndexB, References);
                return iLevelA.CompareTo(iLevelB);
            }
        }
 
        [UnconditionalSuppressMessage("ILLink", "IL2026:RequiresUnreferencedCode", Justification = "ctors are marked as RDC")]
        private void BuildDigestedReferences()
        {
            // Default the DigestMethod and Canonicalization
            ArrayList references = SignedInfo!.References;
            // Reset the cache
            _refProcessed = new bool[references.Count];
            _refLevelCache = new int[references.Count];
 
            ReferenceLevelSortOrder sortOrder = new ReferenceLevelSortOrder();
            sortOrder.References = references;
            // Don't alter the order of the references array list
            ArrayList sortedReferences = new ArrayList();
            foreach (Reference reference in references)
            {
                sortedReferences.Add(reference);
            }
            sortedReferences.Sort(sortOrder);
 
            CanonicalXmlNodeList nodeList = new CanonicalXmlNodeList();
            foreach (DataObject obj in m_signature.ObjectList)
            {
                nodeList.Add(obj.GetXml());
            }
            foreach (Reference reference in sortedReferences)
            {
                // If no DigestMethod has yet been set, default it to sha1
                reference.DigestMethod ??= Reference.DefaultDigestMethod;
 
                SignedXmlDebugLog.LogSigningReference(this, reference);
 
                reference.UpdateHashValue(_containingDocument!, nodeList);
                // If this reference has an Id attribute, add it
                if (reference.Id != null)
                    nodeList.Add(reference.GetXml());
            }
        }
 
        [UnconditionalSuppressMessage("ILLink", "IL2026:RequiresUnreferencedCode", Justification = "ctors are marked as RDC")]
        private bool CheckDigestedReferences()
        {
            ArrayList references = m_signature.SignedInfo!.References;
            for (int i = 0; i < references.Count; ++i)
            {
                Reference digestedReference = (Reference)references[i]!;
 
                if (!ReferenceUsesSafeTransformMethods(digestedReference))
                {
                    return false;
                }
 
                SignedXmlDebugLog.LogVerifyReference(this, digestedReference);
                byte[]? calculatedHash;
                try
                {
                    calculatedHash = digestedReference.CalculateHashValue(_containingDocument!, m_signature.ReferencedItems);
                }
                catch (CryptoSignedXmlRecursionException)
                {
                    SignedXmlDebugLog.LogSignedXmlRecursionLimit(this, digestedReference);
                    return false;
                }
                // Compare both hashes
                SignedXmlDebugLog.LogVerifyReferenceHash(this, digestedReference, calculatedHash, digestedReference.DigestValue);
 
                if (!CryptographicEquals(calculatedHash, digestedReference.DigestValue))
                {
                    return false;
                }
            }
 
            return true;
        }
 
        // Methods _must_ be marked both No Inlining and No Optimization to be fully opted out of optimization.
        // This is because if a candidate method is inlined, its method level attributes, including the NoOptimization
        // attribute, are lost.
        // This method makes no attempt to disguise the length of either of its inputs. It is assumed the attacker has
        // knowledge of the algorithms used, and thus the output length. Length is difficult to properly blind in modern CPUs.
        [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
        private static bool CryptographicEquals(byte[]? a, byte[]? b)
        {
            System.Diagnostics.Debug.Assert(a != null);
            System.Diagnostics.Debug.Assert(b != null);
 
            int result = 0;
 
            // Short cut if the lengths are not identical
            if (a.Length != b.Length)
                return false;
 
            unchecked
            {
                // Normally this caching doesn't matter, but with the optimizer off, this nets a non-trivial speedup.
                int aLength = a.Length;
 
                for (int i = 0; i < aLength; i++)
                    // We use subtraction here instead of XOR because the XOR algorithm gets ever so
                    // slightly faster as more and more differences pile up.
                    // This cannot overflow more than once (and back to 0) because bytes are 1 byte
                    // in length, and result is 4 bytes. The OR propagates all set bytes, so the differences
                    // can't add up and overflow a second time.
                    result |= (a[i] - b[i]);
            }
 
            return (0 == result);
        }
 
        // If we have a signature format validation callback, check to see if this signature's format (not
        // the signautre itself) is valid according to the validator.  A return value of true indicates that
        // the signature format is acceptable, false means that the format is not valid.
        private bool CheckSignatureFormat()
        {
            if (_signatureFormatValidator == null)
            {
                // No format validator means that we default to accepting the signature.  (This is
                // effectively compatibility mode with v3.5).
                return true;
            }
 
            SignedXmlDebugLog.LogBeginCheckSignatureFormat(this, _signatureFormatValidator);
 
            bool formatValid = _signatureFormatValidator(this);
            SignedXmlDebugLog.LogFormatValidationResult(this, formatValid);
            return formatValid;
        }
 
        [UnconditionalSuppressMessage("ILLink", "IL2026:RequiresUnreferencedCode", Justification = "ctors are marked as RDC")]
        [UnconditionalSuppressMessage("ILLink", "IL2057:UnrecognizedReflectionPattern", Justification = "ctors are marked as RDC")]
        private bool CheckSignedInfo(AsymmetricAlgorithm key)
        {
            if (key is null)
            {
                throw new ArgumentNullException(nameof(key));
            }
 
            SignedXmlDebugLog.LogBeginCheckSignedInfo(this, m_signature.SignedInfo!);
 
            SignatureDescription? signatureDescription = CryptoHelpers.CreateNonTransformFromName<SignatureDescription>(SignatureMethod);
            if (signatureDescription == null)
                throw new CryptographicException(SR.Cryptography_Xml_SignatureDescriptionNotCreated);
 
            // Let's see if the key corresponds with the SignatureMethod
            Type ta = Type.GetType(signatureDescription.KeyAlgorithm!)!;
            if (!IsKeyTheCorrectAlgorithm(key, ta))
                return false;
 
            HashAlgorithm? hashAlgorithm = signatureDescription.CreateDigest();
            if (hashAlgorithm == null)
                throw new CryptographicException(SR.Cryptography_Xml_CreateHashAlgorithmFailed);
            byte[] hashval = GetC14NDigest(hashAlgorithm);
 
            AsymmetricSignatureDeformatter asymmetricSignatureDeformatter = signatureDescription.CreateDeformatter(key);
            SignedXmlDebugLog.LogVerifySignedInfo(this,
                                                  key,
                                                  signatureDescription,
                                                  hashAlgorithm,
                                                  asymmetricSignatureDeformatter,
                                                  hashval,
                                                  m_signature.SignatureValue);
            return asymmetricSignatureDeformatter.VerifySignature(hashval, m_signature.SignatureValue!);
        }
 
        private bool CheckSignedInfo(KeyedHashAlgorithm macAlg)
        {
            if (macAlg is null)
            {
                throw new ArgumentNullException(nameof(macAlg));
            }
 
            SignedXmlDebugLog.LogBeginCheckSignedInfo(this, m_signature.SignedInfo!);
 
            int signatureLength;
            if (m_signature.SignedInfo!.SignatureLength == null)
                signatureLength = macAlg.HashSize;
            else
                signatureLength = Convert.ToInt32(m_signature.SignedInfo.SignatureLength, null);
 
            // signatureLength should be less than hash size
            if (signatureLength < 0 || signatureLength > macAlg.HashSize)
                throw new CryptographicException(SR.Cryptography_Xml_InvalidSignatureLength);
            if (signatureLength % 8 != 0)
                throw new CryptographicException(SR.Cryptography_Xml_InvalidSignatureLength2);
            if (m_signature.SignatureValue == null)
                throw new CryptographicException(SR.Cryptography_Xml_SignatureValueRequired);
            if (m_signature.SignatureValue.Length != signatureLength / 8)
                throw new CryptographicException(SR.Cryptography_Xml_InvalidSignatureLength);
 
            // Calculate the hash
            byte[] hashValue = GetC14NDigest(macAlg);
            SignedXmlDebugLog.LogVerifySignedInfo(this, macAlg, hashValue, m_signature.SignatureValue);
 
            return m_signature.SignatureValue.AsSpan().SequenceEqual(hashValue.AsSpan(0, m_signature.SignatureValue.Length));
        }
 
        private static XmlElement? GetSingleReferenceTarget(XmlDocument document, string idAttributeName, string idValue)
        {
            // idValue has already been tested as an NCName (unless overridden for compatibility), so there's no
            // escaping that needs to be done here.
            string xPath = "//*[@" + idAttributeName + "=\"" + idValue + "\"]";
 
            // http://www.w3.org/TR/xmldsig-core/#sec-ReferenceProcessingModel says that for the form URI="#chapter1":
            //
            //   Identifies a node-set containing the element with ID attribute value 'chapter1' ...
            //
            // Note that it uses the singular. Therefore, if the match is ambiguous, we should consider the document invalid.
            //
            // In this case, we'll treat it the same as having found nothing across all fallbacks (but shortcut so that we don't
            // fall into a trap of finding a secondary element which wasn't the originally signed one).
 
            XmlNodeList? nodeList = document.SelectNodes(xPath);
 
            if (nodeList == null || nodeList.Count == 0)
            {
                return null;
            }
 
            if (nodeList.Count == 1)
            {
                return nodeList[0] as XmlElement;
            }
 
            throw new CryptographicException(SR.Cryptography_Xml_InvalidReference);
        }
 
        private static bool IsKeyTheCorrectAlgorithm(AsymmetricAlgorithm key, Type expectedType)
        {
            Type actualType = key.GetType();
 
            if (actualType == expectedType)
                return true;
 
            // This check exists solely for compatibility with 4.6. Normally, we would expect "expectedType" to be the superclass type and
            // the actualType to be the subclass.
            if (expectedType.IsSubclassOf(actualType))
                return true;
 
            //
            // "expectedType" comes from the KeyAlgorithm property of a SignatureDescription. The BCL SignatureDescription classes have historically
            // denoted provider-specific implementations ("RSACryptoServiceProvider") rather than the base class for the algorithm ("RSA"). We could
            // change those (at the risk of creating other compat problems) but we have no control over third party SignatureDescriptions.
            //
            // So, in the absence of a better approach, walk up the parent hierarchy until we find the ancestor that's a direct subclass of
            // AsymmetricAlgorithm and treat that as the algorithm identifier.
            //
            while (expectedType != null && expectedType.BaseType != typeof(AsymmetricAlgorithm))
            {
                expectedType = expectedType.BaseType!;
            }
 
            if (expectedType == null)
                return false;   // SignatureDescription specified something that isn't even a subclass of AsymmetricAlgorithm. For compatibility with 4.6, return false rather throw.
 
            if (actualType.IsSubclassOf(expectedType))
                return true;
 
            return false;
        }
    }
}