File: System\Security\Cryptography\Xml\XmlLicenseTransform.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.Diagnostics.CodeAnalysis;
using System.IO;
using System.Xml;
 
namespace System.Security.Cryptography.Xml
{
    [RequiresDynamicCode(CryptoHelpers.XsltRequiresDynamicCodeMessage)]
    [RequiresUnreferencedCode(CryptoHelpers.CreateFromNameUnreferencedCodeMessage)]
    public class XmlLicenseTransform : Transform
    {
        private readonly Type[] _inputTypes = { typeof(XmlDocument) };
        private readonly Type[] _outputTypes = { typeof(XmlDocument) };
        private XmlNamespaceManager? _namespaceManager;
        private XmlDocument? _license;
        private IRelDecryptor? _relDecryptor;
 
        // work around https://github.com/dotnet/runtime/issues/81864 by splitting these into a separate class.
        internal static class Consts
        {
            internal const string ElementIssuer = "issuer";
            internal const string NamespaceUriCore = "urn:mpeg:mpeg21:2003:01-REL-R-NS";
        }
 
        public XmlLicenseTransform()
        {
            Algorithm = SignedXml.XmlLicenseTransformUrl;
        }
 
        public override Type[] InputTypes
        {
            get { return _inputTypes; }
        }
 
        public override Type[] OutputTypes
        {
            get { return _outputTypes; }
        }
 
        public IRelDecryptor? Decryptor
        {
            get { return _relDecryptor; }
            set { _relDecryptor = value; }
        }
 
        private void DecryptEncryptedGrants(XmlNodeList encryptedGrantList)
        {
            XmlElement? encryptionMethod;
            XmlElement? keyInfo;
            XmlElement? cipherData;
            EncryptionMethod encryptionMethodObj;
            KeyInfo keyInfoObj;
            CipherData cipherDataObj;
 
            for (int i = 0, count = encryptedGrantList.Count; i < count; i++)
            {
                encryptionMethod = encryptedGrantList[i]!.SelectSingleNode("//r:encryptedGrant/enc:EncryptionMethod", _namespaceManager!) as XmlElement;
                keyInfo = encryptedGrantList[i]!.SelectSingleNode("//r:encryptedGrant/dsig:KeyInfo", _namespaceManager!) as XmlElement;
                cipherData = encryptedGrantList[i]!.SelectSingleNode("//r:encryptedGrant/enc:CipherData", _namespaceManager!) as XmlElement;
                if ((encryptionMethod != null) &&
                    (keyInfo != null) &&
                    (cipherData != null))
                {
                    encryptionMethodObj = new EncryptionMethod();
                    keyInfoObj = new KeyInfo();
                    cipherDataObj = new CipherData();
 
                    encryptionMethodObj.LoadXml(encryptionMethod);
                    keyInfoObj.LoadXml(keyInfo);
                    cipherDataObj.LoadXml(cipherData);
 
                    MemoryStream? toDecrypt = null;
                    Stream? decryptedContent = null;
                    StreamReader? streamReader = null;
 
                    try
                    {
                        toDecrypt = new MemoryStream(cipherDataObj.CipherValue!);
                        decryptedContent = _relDecryptor!.Decrypt(encryptionMethodObj,
                                                                keyInfoObj, toDecrypt);
 
                        if ((decryptedContent == null) || (decryptedContent.Length == 0))
                            throw new CryptographicException(SR.Cryptography_Xml_XrmlUnableToDecryptGrant);
 
                        streamReader = new StreamReader(decryptedContent);
                        string clearContent = streamReader.ReadToEnd();
 
                        // red flag
                        encryptedGrantList[i]!.ParentNode!.InnerXml = clearContent;
                    }
                    finally
                    {
                        toDecrypt?.Close();
                        decryptedContent?.Close();
                        streamReader?.Close();
                    }
                }
            }
        }
 
        // License transform has no inner XML elements
        protected override XmlNodeList? GetInnerXml()
        {
            return null;
        }
 
        public override object GetOutput()
        {
            return _license!;
        }
 
        public override object GetOutput(Type type)
        {
            if ((type != typeof(XmlDocument)) && (!type.IsSubclassOf(typeof(XmlDocument))))
                throw new ArgumentException(SR.Cryptography_Xml_TransformIncorrectInputType, nameof(type));
 
            return GetOutput();
        }
 
        // License transform has no inner XML elements
        public override void LoadInnerXml(XmlNodeList nodeList)
        {
            if (nodeList != null && nodeList.Count > 0)
                throw new CryptographicException(SR.Cryptography_Xml_UnknownTransform);
        }
 
        public override void LoadInput(object obj)
        {
            // Check if the Context property is set before this transform is invoked.
            if (Context == null)
                throw new CryptographicException(SR.Cryptography_Xml_XrmlMissingContext);
 
            _license = new XmlDocument();
            _license.PreserveWhitespace = true;
            _namespaceManager = new XmlNamespaceManager(_license.NameTable);
            _namespaceManager.AddNamespace("dsig", SignedXml.XmlDsigNamespaceUrl);
            _namespaceManager.AddNamespace("enc", EncryptedXml.XmlEncNamespaceUrl);
            _namespaceManager.AddNamespace("r", Consts.NamespaceUriCore);
 
            XmlElement? currentIssuerContext;
            XmlElement? currentLicenseContext;
            XmlNode? signatureNode;
 
            // Get the nearest issuer node
            currentIssuerContext = Context.SelectSingleNode("ancestor-or-self::r:issuer[1]", _namespaceManager) as XmlElement;
            if (currentIssuerContext == null)
                throw new CryptographicException(SR.Cryptography_Xml_XrmlMissingIssuer);
 
            signatureNode = currentIssuerContext.SelectSingleNode("descendant-or-self::dsig:Signature[1]", _namespaceManager) as XmlElement;
            signatureNode?.ParentNode!.RemoveChild(signatureNode);
 
            // Get the nearest license node
            currentLicenseContext = currentIssuerContext.SelectSingleNode("ancestor-or-self::r:license[1]", _namespaceManager) as XmlElement;
            if (currentLicenseContext == null)
                throw new CryptographicException(SR.Cryptography_Xml_XrmlMissingLicence);
 
            XmlNodeList issuerList = currentLicenseContext.SelectNodes("descendant-or-self::r:license[1]/r:issuer", _namespaceManager)!;
 
            // Remove all issuer nodes except current
            for (int i = 0, count = issuerList.Count; i < count; i++)
            {
                if (issuerList[i]! == currentIssuerContext)
                    continue;
 
                if ((issuerList[i]!.LocalName == Consts.ElementIssuer) &&
                    (issuerList[i]!.NamespaceURI == Consts.NamespaceUriCore))
                    issuerList[i]!.ParentNode!.RemoveChild(issuerList[i]!);
            }
 
            XmlNodeList encryptedGrantList = currentLicenseContext.SelectNodes("/r:license/r:grant/r:encryptedGrant", _namespaceManager)!;
 
            if (encryptedGrantList.Count > 0)
            {
                if (_relDecryptor == null)
                    throw new CryptographicException(SR.Cryptography_Xml_XrmlMissingIRelDecryptor);
 
                DecryptEncryptedGrants(encryptedGrantList);
            }
 
            _license.InnerXml = currentLicenseContext.OuterXml;
        }
    }
}