简体   繁体   中英

Decrypt SAML response

I am testing some SAML2 based login flow using a Keycloak Identity Broker integrating to some SAML2 Identity Provider. When logging in to the service, I get to generate a SAML Response with an assertion like the one shown below. I would like to have a generic way of parsing / decrypting the entire structure, so that I can obtain the response as decrypted XML (just so that I can see the raw XML during testing). Preferably, I would like to construct a .NET Core application doing this, alternatively a Java application. My identity broker has exchanged a public key to the Identity Provider.

  • I posses both the public key and private key of this certificate,
  • I posses the public key corresponding to the Identity Provider.

I apologize in advance for not being an expert on this topic. I have seen multiple .NET code pieces for handling SAML responses, but it seems to me that there are multiple SAML response formats, and I do not know how to handle this specific kind of SAML response.

<?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>

I ended up getting this to work (which I may improve along the way). The trick was to first decrypt the first CipherValue using the RSA private key, and then use the decrypted value as an AES key in order to decrypt the latter piece. I am no expert on AES, but after reading about the standard, I have read that one can (allegedly) determine a so-called IV key from the AES key by taking the first 16 bits from the AES key. For some reason I got some weird padding issues that I had to correct by a simple string replacement. Furthermore, one should modify the implemention if one uses other encryption standards. The implementation is based on .NET 5. To my knowledge the only .NET 5-specific feature being used is the X509Certificate2.CreateFromPemFile method.

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;
                }
            }
        }

    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM