| File: XmlDocumentDecryptor.cs | Web Access |
| Project: src\runtime\src\libraries\Microsoft.Extensions.Configuration.Xml\src\Microsoft.Extensions.Configuration.Xml.csproj (Microsoft.Extensions.Configuration.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; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Runtime.Versioning; using System.Security.Cryptography.Xml; using System.Xml; namespace Microsoft.Extensions.Configuration.Xml { /// <summary> /// Encrypts and decrypts XML. /// </summary> public class XmlDocumentDecryptor { internal const string RequiresDynamicCodeMessage = "Microsoft.Extensions.Configuration.Xml can use EncryptedXml which may contain XSLTs in the xml. XSLTs require dynamic code."; internal const string RequiresUnreferencedCodeMessage = "Microsoft.Extensions.Configuration.Xml can use EncryptedXml. If you use encrypted XML files, your application might not have the algorithm implementations it needs. To avoid this problem, one option you can use is a DynamicDependency attribute to keep the algorithm implementations in your application."; /// <summary> /// Accesses the singleton decryptor instance. /// </summary> public static readonly XmlDocumentDecryptor Instance = new XmlDocumentDecryptor(); private readonly Func<XmlDocument, EncryptedXml>? _encryptedXmlFactory; /// <summary> /// Initializes a new instance of the XmlDocumentDecryptor class. /// </summary> // don't create an instance of this directly protected XmlDocumentDecryptor() { // _encryptedXmlFactory stays null by default } // for testing only internal XmlDocumentDecryptor(Func<XmlDocument, EncryptedXml> encryptedXmlFactory) { _encryptedXmlFactory = encryptedXmlFactory; } private static bool ContainsEncryptedData(XmlDocument document) { // EncryptedXml will simply decrypt the document in-place without telling // us that it did so, so we need to perform a check to see if EncryptedXml // will actually do anything. The below check for an encrypted data blob // is the same one that EncryptedXml would have performed. var namespaceManager = new XmlNamespaceManager(document.NameTable); namespaceManager.AddNamespace("enc", "http://www.w3.org/2001/04/xmlenc#"); return (document.SelectSingleNode("//enc:EncryptedData", namespaceManager) != null); } /// <summary> /// Creates an <see cref="XmlReader"/> that decrypts data transparently. /// </summary> /// <param name="input">The input <see cref="Stream"/> to read the XML configuration data from.</param> /// <param name="settings">The settings for the new <see cref="XmlReader"/> instance.</param> /// <returns>An <see cref="XmlReader"/> that decrypts data transparently.</returns> [RequiresDynamicCode(RequiresDynamicCodeMessage)] [RequiresUnreferencedCode(RequiresUnreferencedCodeMessage)] public XmlReader CreateDecryptingXmlReader(Stream input, XmlReaderSettings? settings) { // XML-based configurations aren't really all that big, so we can buffer // the whole thing in memory while we determine decryption operations. var memStream = new MemoryStream(); input.CopyTo(memStream); memStream.Position = 0; // First, consume the entire XmlReader as an XmlDocument. var document = new XmlDocument(); using (var reader = XmlReader.Create(memStream, settings)) { document.Load(reader); } memStream.Position = 0; if (ContainsEncryptedData(document)) { // DecryptDocumentAndCreateXmlReader is not supported on 'browser', // but we only call it depending on the input XML document. If the document // is encrypted and this is running on 'browser', just let the PNSE throw. #pragma warning disable CA1416 return DecryptDocumentAndCreateXmlReader(document); #pragma warning restore CA1416 } else { // If no decryption would have taken place, return a new fresh reader // based on the memory stream (which doesn't need to be disposed). return XmlReader.Create(memStream, settings); } } /// <summary> /// Creates a reader that can decrypt an encrypted XML document. /// </summary> /// <param name="document">The document.</param> /// <returns>An XmlReader that can read the document.</returns> [UnsupportedOSPlatform("browser")] [RequiresDynamicCode(RequiresDynamicCodeMessage)] [RequiresUnreferencedCode(RequiresUnreferencedCodeMessage)] protected virtual XmlReader DecryptDocumentAndCreateXmlReader(XmlDocument document) { #if !NETSTANDARD2_1 && !NETSTANDARD2_0 && !NETFRAMEWORK // TODO remove with https://github.com/dotnet/runtime/pull/107185 if (OperatingSystem.IsWasi() || OperatingSystem.IsBrowser()) throw new PlatformNotSupportedException(); #else #pragma warning disable CA1416 #endif // Perform the actual decryption step, updating the XmlDocument in-place. EncryptedXml encryptedXml = _encryptedXmlFactory?.Invoke(document) ?? new EncryptedXml(document); encryptedXml.DecryptDocument(); // Finally, return the new XmlReader from the updated XmlDocument. // Error messages based on this XmlReader won't show line numbers, // but that's fine since we transformed the document anyway. return document.CreateNavigator()!.ReadSubtree(); } } }