解密 SAML 响应

Decrypt SAML response

我正在使用集成到某些 SAML2 身份提供程序的 Keycloak 身份代理测试一些基于 SAML2 的登录流程。登录服务时,我会生成一个 SAML 响应,其中包含如下所示的断言。我想要一种解析/解密整个结构的通用方法,以便我可以获得解密的 XML 的响应(这样我就可以在测试期间看到原始的 XML )。最好,我想构建一个执行此操作的 .NET Core 应用程序,或者 Java 应用程序。我的身份代理已将 public 密钥交换给身份提供者。

我不是这个话题的专家,提前致歉。我见过多个用于处理 SAML 响应的 .NET 代码片段,但在我看来,有多种 SAML 响应格式,我不知道如何处理这种特定类型的 SAML 响应。

<?xml version="1.0" encoding="UTF-8"?>
<saml2p:Response xmlns:saml2p="urn:oasis:names:tc:SAML:2.0:protocol" Destination="https://somedestination.com" ID="_8665f62b-e462-402f-88e9-2bfbbff836f4" InResponseTo="ID_d3975e20-95dc-4e08-9c75-8d611d8d92f8" IssueInstant="2021-02-16T13:10:03.619Z" Version="2.0">
    <saml2:Issuer xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion">https://someissuer.com</saml2:Issuer>
    <saml2p:Status>
        <saml2p:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>
    </saml2p:Status>
    <saml2:EncryptedAssertion xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion">
        <xenc:EncryptedData xmlns:xenc="http://www.w3.org/2001/04/xmlenc#" Id="_96f98f75573fe57e6cb95d4f84a4460e" Type="http://www.w3.org/2001/04/xmlenc#Element">
            <xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes128-cbc"
                xmlns:xenc="http://www.w3.org/2001/04/xmlenc#"/>
            <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
                <xenc:EncryptedKey Id="_f494c7faf84f0cc2cf704adc6ce7b7e4"
                    xmlns:xenc="http://www.w3.org/2001/04/xmlenc#">
                    <xenc:EncryptionMethod Algorithm="http://www.w3.org/2009/xmlenc11#rsa-oaep"
                        xmlns:xenc="http://www.w3.org/2001/04/xmlenc#"/>
                    <xenc:CipherData xmlns:xenc="http://www.w3.org/2001/04/xmlenc#">
                        <xenc:CipherValue>someCipherValueElementPoppingUpHere</xenc:CipherValue>
                    </xenc:CipherData>
                </xenc:EncryptedKey>
            </ds:KeyInfo>
            <xenc:CipherData xmlns:xenc="http://www.w3.org/2001/04/xmlenc#">
                <xenc:CipherValue>someOtherCipherValueElementPoppingUpHere</xenc:CipherValue>
            </xenc:CipherData>
        </xenc:EncryptedData>
    </saml2:EncryptedAssertion>
</saml2p:Response>

我最终让这个工作(我可能会在此过程中改进)。诀窍是首先使用 RSA 私钥解密第一个 CipherValue,然后使用解密后的值作为 AES 密钥来解密后一部分。我不是 AES 方面的专家,但在阅读了该标准之后,我了解到可以(据称)通过从 AES 密钥中获取前 16 位来确定所谓的 AES 密钥 IV 密钥。出于某种原因,我遇到了一些奇怪的填充问题,我必须通过简单的字符串替换来纠正这些问题。此外,如果使用其他加密标准,则应修改实现。该实现基于 .NET 5。据我所知,唯一使用的 .NET 5 特定功能是 X509Certificate2.CreateFromPemFile 方法。

namespace SAMLDecrypter
{
    class Program
    {
        static void Main(string[] args)
        {
            var certPath = "cartPath";
            var samlPath = "samlresponse.xml";
            var saml = XElement.Parse(File.ReadAllText(samlPath));
            var cert = X509Certificate2.CreateFromPemFile(certPath);
            var cipherKeys = GetCipherValues(saml);
            var privateKey = cert.GetRSAPrivateKey();
            var aeskey = privateKey.Decrypt(Convert.FromBase64String(cipherKeys[0]), RSAEncryptionPadding.OaepSHA1);
            var assertions = AesDecrypt(Convert.FromBase64String(cipherKeys[1]), aeskey);
            assertions = XElement.Parse(assertions.Substring(assertions.IndexOf("<"))).ToString();

            Console.WriteLine(assertions);
        }

        static string[] GetCipherValues(XElement saml)
        {
            var alg = XName.Get("Algorithm");
            var encryptedData = XPathSelectElements(saml, "./saml2:EncryptedAssertion/xenc:EncryptedData").First();
            var encryptionMethod = XPathSelectElements(encryptedData, "./xenc:EncryptionMethod").First().Attribute(alg).Value;
            var keyInfo = XPathSelectElements(encryptedData, "./ds:KeyInfo").First();
            var encryptionMethodKey = XPathSelectElements(keyInfo, "./xenc:EncryptedKey/xenc:EncryptionMethod").First().Attribute(alg).Value;
            var cipherValueKey = XPathSelectElements(keyInfo, "./xenc:EncryptedKey/xenc:CipherData/xenc:CipherValue").First().Value;
            var cipherValue = XPathSelectElements(encryptedData, "./xenc:CipherData/xenc:CipherValue").First().Value;
            return new[] { cipherValueKey, cipherValue };
        }

        private static Dictionary<string, string> GetDefaultDict()
        {
            return new Dictionary<string, string>
            {
                { "saml2", "urn:oasis:names:tc:SAML:2.0:assertion" },
                { "saml2p", "urn:oasis:names:tc:SAML:2.0:protocol" },
                { "xenc", "http://www.w3.org/2001/04/xmlenc#" },
                { "ds", "http://www.w3.org/2000/09/xmldsig#" }
            };
        }

        private static IEnumerable<XElement> XPathSelectElements(XNode element, string query)
        {
            return XPathSelectElements(element, query, GetDefaultDict());
        }

        private static IEnumerable<XElement> XPathSelectElements(XNode element, string query, Dictionary<string,string> namespaces)
        {
            var namespaceManager = new XmlNamespaceManager(new NameTable());
            namespaces.ToList().ForEach(entry => namespaceManager.AddNamespace(entry.Key, entry.Value));
            return element.XPathSelectElements(query, namespaceManager);
        }

        static string AesDecrypt(byte[] cipherText, byte[] aesKey)
        {
            // Check arguments.
            if (cipherText == null || cipherText.Length <= 0)
                throw new ArgumentNullException("cipherText");
            if (aesKey == null || aesKey.Length < 16)
                throw new ArgumentNullException("Key");

            var aesIV = aesKey.Take(16).ToArray();
            using (var ms = new MemoryStream())
            {
                using (var aes = new AesManaged()) {
                    aes.Key = aesKey;
                    aes.IV = aesIV;
                    aes.Padding = PaddingMode.ISO10126;
                    CryptoStream cs = new CryptoStream(ms, aes.CreateDecryptor(), CryptoStreamMode.Write);
                    cs.Write(cipherText, 0, cipherText.Length);
                    cs.Close();
                    var result = Encoding.UTF8.GetString(ms.ToArray());
                    return result;
                }
            }
        }

    }
}