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