|
// 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()) 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();
}
}
}
|