使用 .NET 4.5 (System.IdentityModel) / WIF 解密 SAML 2 断言
Decrypting SAML 2 assertion using .NET 4.5 (System.IdentityModel) / WIF
我正在尝试解密从基于 Java 的身份提供者发出的加密 SAML 2.0 断言。
给定以下安全令牌处理程序设置:
X509Certificate2 cert = ... // Contains private key
var serviceTokens = new List<SecurityToken>();
serviceTokens.Add(new X509SecurityToken(cert));
var issuers = new ConfigurationBasedIssuerNameRegistry();
issuers.AddTrustedIssuer("...thumbprint...", "nottherealname");
var configuration = new SecurityTokenHandlerConfiguration
{
AudienceRestriction = { AudienceMode = AudienceUriMode.Never },
CertificateValidationMode = X509CertificateValidationMode.None,
RevocationMode = X509RevocationMode.NoCheck,
IssuerNameRegistry = issuers,
MaxClockSkew = TimeSpan.FromMinutes(5),
ServiceTokenResolver = SecurityTokenResolver.CreateDefaultSecurityTokenResolver(serviceTokens.AsReadOnly(), false)
};
var tokenHandlers = SecurityTokenHandlerCollection.CreateDefaultSecurityTokenHandlerCollection(configuration);
我得到一个加密的 SAML 断言,如下所示:
<saml:EncryptedAssertion xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">
<xenc:EncryptedData xmlns:xenc="http://www.w3.org/2001/04/xmlenc#" Type="http://www.w3.org/2001/04/xmlenc#Element">
<xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes128-cbc" />
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<xenc:EncryptedKey>
<xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-1_5" />
<xenc:CipherData>
<xenc:CipherValue>Fwhv/zEVi3eQvQN372L1S+pVDM5JKs1Kc2I25djuiOPdwKReCXRhnd5QL4Y8wJDWZ5vAlOxHkNAZ
OwOg4NsSI8KssrygNk4fwvNdVAGMB5ytI1QTGRqG6WwP4Em+uLN3VXbqiLWA9D6uO0BwATF9HdTb
j/IMhGCxZ1ZKrKQF5OL2PHKf4DqyNa5d9CNZenhYyYghgYrhgZtQVl/VARAp9VKsM/lbkPsEU8Ty
ow4LnTlYqBnykrOEJowN5B+HXGvfhbIBHyGzdCC+WbcEbI898zy/VhZ63VyFL2GSTdDWv10IEMy5
CHom4Qruer1xpyQMrxJ6EK30HMhVppToivgoFQ==</xenc:CipherValue>
</xenc:CipherData>
</xenc:EncryptedKey>
</ds:KeyInfo>
<xenc:CipherData>
<xenc:CipherValue>...</xenc:CipherValue>
</xenc:CipherData>
</xenc:EncryptedData>
</saml:EncryptedAssertion>
当我尝试读取令牌时:
var tokenReader = new XmlNodeReader(xmlDoc); // XML document with root element <saml:EncryptedAssertion ....
if (!tokenHandlers.CanReadToken(tokenReader)) throw new Exception("Unreadable token");
var token = tokenHandlers.ReadToken(tokenReader);
然后我在最后一行代码中得到以下异常:
ID4022: The key needed to decrypt the encrypted security token could not be resolved. Ensure that the SecurityTokenResolver is populated with the required key.
根据身份提供者的说法,用于加密负载的对称密钥是用我的 public 密钥加密的。尽管如此,似乎无法使用X509证书中的私钥来解密密钥。这是我对错误消息的解释。难道是报错信息不对?还有什么可能是错的?我的配置不完整吗?
我也遇到了这个问题,最后我解决了这个问题,就像这个问题的答案一样:How to disable Subject Key Identifier in SecurityTokenResolver
更改现有线路
ServiceTokenResolver = SecurityTokenResolver.CreateDefaultSecurityTokenResolver(serviceTokens.AsReadOnly(), false)
到
ServiceTokenResolver = new Saml2SSOSecurityTokenResolver(serviceTokens)
并像这样添加 class:
private class Saml2SSOSecurityTokenResolver : SecurityTokenResolver
{
List<SecurityToken> _tokens;
public Saml2SSOSecurityTokenResolver(List<SecurityToken> tokens)
{
_tokens = tokens;
}
protected override bool TryResolveSecurityKeyCore(System.IdentityModel.Tokens.SecurityKeyIdentifierClause keyIdentifierClause, out System.IdentityModel.Tokens.SecurityKey key)
{
var token = _tokens[0] as X509SecurityToken;
var myCert = token.Certificate;
key = null;
var ekec = keyIdentifierClause as EncryptedKeyIdentifierClause;
if (ekec != null)
{
if (ekec.EncryptionMethod == "http://www.w3.org/2001/04/xmlenc#rsa-1_5")
{
var encKey = ekec.GetEncryptedKey();
var rsa = myCert.PrivateKey as RSACryptoServiceProvider;
var decKey = rsa.Decrypt(encKey, false);
key = new InMemorySymmetricSecurityKey(decKey);
return true;
}
var data = ekec.GetEncryptedKey();
var id = ekec.EncryptingKeyIdentifier;
}
return true;
}
protected override bool TryResolveTokenCore(System.IdentityModel.Tokens.SecurityKeyIdentifierClause keyIdentifierClause, out System.IdentityModel.Tokens.SecurityToken token)
{
throw new NotImplementedException();
}
protected override bool TryResolveTokenCore(System.IdentityModel.Tokens.SecurityKeyIdentifier keyIdentifier, out System.IdentityModel.Tokens.SecurityToken token)
{
throw new NotImplementedException();
}
}
我尝试以不同的方式解密 EncryptedAssertion,最终使用 https://www.nuget.org/packages/SAML2.Core (github: https://github.com/elerch/SAML2) 的这种方法。
我遵循了这些步骤:
- 创建一个包含您的 public 证书和私钥的 pfx 文件 (PKCS#12),如下所示:
openssl pkcs12 -export -in -inkey -out cert_key.p12
打开以 saml:EncryptedAssertion 作为根元素的文档
读取证书pkcs12容器
设置文档和密钥
- 解密文档
完整代码:
var doc = LoadXmlDocument(@"path\to\xml\withencryptedassertion");
var cert = new X509Certificate2(@"path\to\cert_key.p12", "<container_password>");
var encryptedAssertion = new SAML2.Saml20EncryptedAssertion((RSA)cert.PrivateKey, doc);
encryptedAssertion.Decrypt();
var decryptedContent = encryptedAssertion.Assertion.InnerXml;
LoadXmlDocument 是基本文件reader:
public static XmlDocument LoadXmlDocument(string assertionFile) {
using (var fs = File.OpenRead(assertionFile))
{
var document = new XmlDocument { PreserveWhitespace = true };
document.Load(fs);
fs.Close();
return document;
}
}
我使用 https://developers.onelogin.com/saml/online-tools/ 生成证书和示例数据来测试此代码
我正在尝试解密从基于 Java 的身份提供者发出的加密 SAML 2.0 断言。
给定以下安全令牌处理程序设置:
X509Certificate2 cert = ... // Contains private key
var serviceTokens = new List<SecurityToken>();
serviceTokens.Add(new X509SecurityToken(cert));
var issuers = new ConfigurationBasedIssuerNameRegistry();
issuers.AddTrustedIssuer("...thumbprint...", "nottherealname");
var configuration = new SecurityTokenHandlerConfiguration
{
AudienceRestriction = { AudienceMode = AudienceUriMode.Never },
CertificateValidationMode = X509CertificateValidationMode.None,
RevocationMode = X509RevocationMode.NoCheck,
IssuerNameRegistry = issuers,
MaxClockSkew = TimeSpan.FromMinutes(5),
ServiceTokenResolver = SecurityTokenResolver.CreateDefaultSecurityTokenResolver(serviceTokens.AsReadOnly(), false)
};
var tokenHandlers = SecurityTokenHandlerCollection.CreateDefaultSecurityTokenHandlerCollection(configuration);
我得到一个加密的 SAML 断言,如下所示:
<saml:EncryptedAssertion xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">
<xenc:EncryptedData xmlns:xenc="http://www.w3.org/2001/04/xmlenc#" Type="http://www.w3.org/2001/04/xmlenc#Element">
<xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes128-cbc" />
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<xenc:EncryptedKey>
<xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-1_5" />
<xenc:CipherData>
<xenc:CipherValue>Fwhv/zEVi3eQvQN372L1S+pVDM5JKs1Kc2I25djuiOPdwKReCXRhnd5QL4Y8wJDWZ5vAlOxHkNAZ
OwOg4NsSI8KssrygNk4fwvNdVAGMB5ytI1QTGRqG6WwP4Em+uLN3VXbqiLWA9D6uO0BwATF9HdTb
j/IMhGCxZ1ZKrKQF5OL2PHKf4DqyNa5d9CNZenhYyYghgYrhgZtQVl/VARAp9VKsM/lbkPsEU8Ty
ow4LnTlYqBnykrOEJowN5B+HXGvfhbIBHyGzdCC+WbcEbI898zy/VhZ63VyFL2GSTdDWv10IEMy5
CHom4Qruer1xpyQMrxJ6EK30HMhVppToivgoFQ==</xenc:CipherValue>
</xenc:CipherData>
</xenc:EncryptedKey>
</ds:KeyInfo>
<xenc:CipherData>
<xenc:CipherValue>...</xenc:CipherValue>
</xenc:CipherData>
</xenc:EncryptedData>
</saml:EncryptedAssertion>
当我尝试读取令牌时:
var tokenReader = new XmlNodeReader(xmlDoc); // XML document with root element <saml:EncryptedAssertion ....
if (!tokenHandlers.CanReadToken(tokenReader)) throw new Exception("Unreadable token");
var token = tokenHandlers.ReadToken(tokenReader);
然后我在最后一行代码中得到以下异常:
ID4022: The key needed to decrypt the encrypted security token could not be resolved. Ensure that the SecurityTokenResolver is populated with the required key.
根据身份提供者的说法,用于加密负载的对称密钥是用我的 public 密钥加密的。尽管如此,似乎无法使用X509证书中的私钥来解密密钥。这是我对错误消息的解释。难道是报错信息不对?还有什么可能是错的?我的配置不完整吗?
我也遇到了这个问题,最后我解决了这个问题,就像这个问题的答案一样:How to disable Subject Key Identifier in SecurityTokenResolver
更改现有线路
ServiceTokenResolver = SecurityTokenResolver.CreateDefaultSecurityTokenResolver(serviceTokens.AsReadOnly(), false)
到
ServiceTokenResolver = new Saml2SSOSecurityTokenResolver(serviceTokens)
并像这样添加 class:
private class Saml2SSOSecurityTokenResolver : SecurityTokenResolver
{
List<SecurityToken> _tokens;
public Saml2SSOSecurityTokenResolver(List<SecurityToken> tokens)
{
_tokens = tokens;
}
protected override bool TryResolveSecurityKeyCore(System.IdentityModel.Tokens.SecurityKeyIdentifierClause keyIdentifierClause, out System.IdentityModel.Tokens.SecurityKey key)
{
var token = _tokens[0] as X509SecurityToken;
var myCert = token.Certificate;
key = null;
var ekec = keyIdentifierClause as EncryptedKeyIdentifierClause;
if (ekec != null)
{
if (ekec.EncryptionMethod == "http://www.w3.org/2001/04/xmlenc#rsa-1_5")
{
var encKey = ekec.GetEncryptedKey();
var rsa = myCert.PrivateKey as RSACryptoServiceProvider;
var decKey = rsa.Decrypt(encKey, false);
key = new InMemorySymmetricSecurityKey(decKey);
return true;
}
var data = ekec.GetEncryptedKey();
var id = ekec.EncryptingKeyIdentifier;
}
return true;
}
protected override bool TryResolveTokenCore(System.IdentityModel.Tokens.SecurityKeyIdentifierClause keyIdentifierClause, out System.IdentityModel.Tokens.SecurityToken token)
{
throw new NotImplementedException();
}
protected override bool TryResolveTokenCore(System.IdentityModel.Tokens.SecurityKeyIdentifier keyIdentifier, out System.IdentityModel.Tokens.SecurityToken token)
{
throw new NotImplementedException();
}
}
我尝试以不同的方式解密 EncryptedAssertion,最终使用 https://www.nuget.org/packages/SAML2.Core (github: https://github.com/elerch/SAML2) 的这种方法。
我遵循了这些步骤:
- 创建一个包含您的 public 证书和私钥的 pfx 文件 (PKCS#12),如下所示:
openssl pkcs12 -export -in -inkey -out cert_key.p12
打开以 saml:EncryptedAssertion 作为根元素的文档
读取证书pkcs12容器
设置文档和密钥
- 解密文档
完整代码:
var doc = LoadXmlDocument(@"path\to\xml\withencryptedassertion");
var cert = new X509Certificate2(@"path\to\cert_key.p12", "<container_password>");
var encryptedAssertion = new SAML2.Saml20EncryptedAssertion((RSA)cert.PrivateKey, doc);
encryptedAssertion.Decrypt();
var decryptedContent = encryptedAssertion.Assertion.InnerXml;
LoadXmlDocument 是基本文件reader:
public static XmlDocument LoadXmlDocument(string assertionFile) {
using (var fs = File.OpenRead(assertionFile))
{
var document = new XmlDocument { PreserveWhitespace = true };
document.Load(fs);
fs.Close();
return document;
}
}
我使用 https://developers.onelogin.com/saml/online-tools/ 生成证书和示例数据来测试此代码