解密安全令牌
CardSpace 为用户提供了管理其数字标识的功能,使用 Internet Explorer 7.0(或支持信息卡的其他浏览器),网站可以向用户请求数字标识。 此标识以加密的安全令牌的形式进行传输。 可以使用 Token
示例类来对令牌进行解密以使用令牌中包含的声明。 本主题将分析令牌及其工作方式。
Web 服务器安装
若要完成练习,必须进行网站配置。 使用示例文件夹中提供的以下安装批处理文件可以完成配置:
Setup.bat
有关安装网站的更多信息以及一些疑难解答提示,请参见安装 CardSpace 示例证书。
获取令牌
示例如何在 Internet Explorer 7.0 中使用 Windows CardSpace 中的代码演示了显示标识选择器所需的 HTML 代码。
使用的文件:
Sample.htm
标识选择器通过使用 <object>
元素或二进制行为对象进行显示。 本示例使用 <object>
元素和一些 JavaScript 向用户请求令牌,并在提交到网站之前将其显示在 <textarea>
中。
Sample.htm 文件:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head>
<title>Sample 2</title>
<object type="application/x-informationcard" name="_xmlToken">
<param name="tokenType" value="urn:oasis:names:tc:SAML:1.0:assertion" />
<param name="issuer" value="https://schemas.xmlsoap.org/ws/2005/05/identity/issuer/self" />
<param name="requiredClaims"
value="https://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname
https://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname
https://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress
https://schemas.xmlsoap.org/ws/2005/05/identity/claims/privatepersonalidentifier" />
</object>
<script language="javascript">
function GetIdentity(){
var xmltkn=document.getElementById("_xmltoken");
var thetextarea = document.getElementById("xmltoken");
thetextarea.value = xmltkn.value ;
}
</script>
</head>
<body>
<form id="form1" method="post" action="login.aspx">
<button name="go" id="go" onclick="javascript:GetIdentity();">Click here to get the token.</button>
<button type="submit">Click here to send the card to the server</button>
<textarea cols=100 rows=20 id="xmltoken" name="xmlToken" ></textarea>
</form>
</body>
</html>
当用户单击**“提交”**按钮后,令牌将被发布到服务器上的表单中,必须在其中对令牌进行解密和验证,然后才能使用声明。
用 C# 编写的用于接受发布令牌的 login.aspx
页。
<%@ Page Language="C#" Debug="true" ValidateRequest="false" %>
<%@ Import Namespace="Microsoft.IdentityModel.Samples" %>
<%@ Import Namespace="Microsoft.IdentityModel.TokenProcessor" %>
<script runat="server">
protected void ShowError(string text) {
fields.Visible = false;
errors.Visible = true;
errtext.Text = text;
}
protected void Page_Load(object sender, EventArgs e) {
string xmlToken;
xmlToken = Request.Params["xmlToken"];
if (xmlToken == null || xmlToken.Equals(""))
ShowError("Token presented was null");
else
{
Token token = new Token (xmlToken);
givenname.Text = token.Claims[SelfIssued.GivenName];
surname.Text = token.Claims[SelfIssued.Surname];
email.Text = token.Claims[SelfIssued.EmailAddress];
uid.Text = token.UniqueID;
}
}
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
<title>Login Page</title>
</head>
<body>
<form id="form1" runat="server">
<div runat="server" id="fields">
Given Name:<asp:Label ID="givenname" runat="server" Text=""></asp:Label><br/>
Surname:<asp:Label ID="surname" runat="server" Text=""></asp:Label><br/>
Email Address:<asp:Label ID="email" runat="server" Text=""></asp:Label><br/>
Unique ID:<asp:Label ID="uid" runat="server" Text=""></asp:Label><br/>
</div>
<div runat="server" id="errors" visible="false">
Error:<asp:Label ID="errtext" runat="server" Text=""></asp:Label><br/>
</div>
</form>
</body>
</html>
用 VB.NET 编写的用于接受发布令牌的 login.aspx
页:
<%@ Page Language="VB" Debug="true" ValidateRequest="false" %>
<%@ Import Namespace="Microsoft.IdentityModel.Samples" %>
<%@ Import Namespace="Microsoft.IdentityModel.TokenProcessor" %>
<script runat="server">
Protected Sub ShowError(ByVal text As String)
fields.Visible = False
errors.Visible = True
errtext.Text = text
End Sub
Protected Sub Page_Load(ByVal sender As Object, ByVal e As EventArgs)
Dim xmlToken As String
xmlToken = Request.Params("xmlToken")
If xmlToken = Nothing Or xmlToken.Equals("") Then
ShowError("Token presented was null")
Else
Dim token As New Token(xmlToken)
givenname.Text = token.Claims(ClaimTypes.GivenName)
surname.Text = token.Claims(ClaimTypes.Surname)
email.Text = token.Claims(ClaimTypes.Email)
uid.Text = token.UniqueID
End If
End Sub
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
<title>Login Page</title>
</head>
<body>
<form id="form1" runat="server">
<div runat="server" id="fields">
Given Name:<asp:Label ID="givenname" runat="server" Text=""></asp:Label><br/>
Surname:<asp:Label ID="surname" runat="server" Text=""></asp:Label><br/>
Email Address:<asp:Label ID="email" runat="server" Text=""></asp:Label><br/>
Unique ID:<asp:Label ID="uid" runat="server" Text=""></asp:Label><br/>
</div>
<div runat="server" id="errors" visible="false">
Error:<asp:Label ID="errtext" runat="server" Text=""></asp:Label><br/>
</div>
</form>
</body>
</html>
Token
类用于处理加密 XML 令牌的解密、验证和声明提取。
处理令牌:检查 XML 加密格式
从浏览器发布的令牌使用 W3C XML 加密标准进行加密。 架构描述文档为 XML 加密语法和处理(可能为英文网页)。 架构为核心架构(可能为英文网页)和 XML 加密(可能为英文网页)。 下面是一个典型的加密令牌示例。
<enc:EncryptedData Type="http://www.w3.org/2001/04/xmlenc#Element"
xmlns:enc="http://www.w3.org/2001/04/xmlenc#">
<enc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes256-cbc" />
<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
<e:EncryptedKey xmlns:e="http://www.w3.org/2001/04/xmlenc#">
<e:EncryptionMethod
Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p">
<DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
</e:EncryptionMethod>
<KeyInfo>
<o:SecurityTokenReference
xmlns:o="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-
1.0.xsd">
<o:KeyIdentifier
ValueType="http://docs.oasis-open.org/wss/oasis-wss-soap-message-security-
1.1#ThumbprintSHA1"
EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-
message-security-1.0#Base64Binary">
1H3mV/pJAlVZAst/Dt0rqbBd67g=
</o:KeyIdentifier>
</o:SecurityTokenReference>
</KeyInfo>
<e:CipherData>
<e:CipherValue>
YJHp...==</e:CipherValue>
</e:CipherData>
</e:EncryptedKey>
</KeyInfo>
<enc:CipherData>
<enc:CipherValue>
ctct...9A==</enc:CipherValue>
</enc:CipherData>
</enc:EncryptedData>
请查看发布的 XML 的构成部分,根元素为 <enc:EncryptedData>
。 它包含访问令牌所需的三项内容。
<enc:EncryptedData>
<enc:EncryptionMethod />
<KeyInfo />
<enc:CipherData />
</enc:EncryptedData>
首先,<enc:EncryptionMethod>
包含用于加密令牌的方法。
<enc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes256-cbc" />
在本示例中为 AES-256-cbc,这是一种对称密钥加密算法,在该算法中双方都使用同一密钥进行加密和解密。 对称密钥是由发起者随机生成的,该密钥也称为瞬态密钥。 瞬态密钥加密存储在 <e:EncryptedKey>
元素中,该元素由 <KeyInfo>
元素包装。
<KeyInfo >
<e:EncryptedKey />
<e:EncryptionMethod / >
<KeyInfo />
<e:CipherData />
</e:EncryptedKey>
</KeyInfo>
建议使用对称算法对令牌进行加密并发送使用该令牌加密的密钥,因为非对称加密比对称加密要慢一些,并且使用非对称密钥对大于密钥大小的数据进行加密是不可取的。 使用非对称算法仅加密密钥可以显著减少服务器端所需的处理量。
瞬态密钥使用依赖方的公钥(来自其证书)进行加密。 瞬态密钥的加密方法可在 <e:EncryptionMethod>
元素中找到。
<e:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p">
<DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
</e:EncryptionMethod>
在本示例中,使用 RSA-OAEP-MGF1P 算法进行加密。 此操作的密钥可在下面的 <KeyInfo>
元素中找到。
<KeyInfo>
<o:SecurityTokenReference xmlns:o="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-
wssecurity-secext-1.0.xsd">
<o:KeyIdentifier
ValueType="http://docs.oasis-open.org/wss/oasis-wss-soap-message-security-
1.1#ThumbprintSHA1"
EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-
security-1.0#Base64Binary">
1H3mV/pJAlVZAst/Dt0rqbBd67g=
</o:KeyIdentifier>
</o:SecurityTokenReference>
</KeyInfo>
发送方通过将其指纹(base64 编码)放入 <o:KeyIdentifier>
元素,可以通知依赖方是使用哪个证书的密钥对对称密钥进行加密的。
依赖方然后使用其私钥解密 <e:CipherData>
/<e:CypherValue>
元素中的数据。
<e:CipherData>
<e:CipherValue>
YJHp...==</e:CipherValue>
</e:CipherData>
在检索到对称密钥之后,将使用该对称密钥对令牌本身进行解密。
<enc:CipherData>
<enc:CipherValue>
ctct...9A==</enc:CipherValue>
</enc:CipherData>
令牌处理器代码的突出特点
若要了解解密流程,需要注意解密流程的以下突出特点。 若要查看完整流程,请查阅 Token
类的源代码。
Token
类构造函数使用 decryptToken
方法对传入构造函数的加密 XML 数据执行解密。
private static byte[] decryptToken(string xmlToken)
为循环访问 XML 数据,这里使用了 XmlReader
。
XmlReader reader = new XmlTextReader(new StringReader(xmlToken));
在搜索 XML 元素时,仅允许非常小的灵活性,如果使用了无效令牌,则会很快失败(使用 ArgumentException
)。 首先,必须找到 <EncryptionMethod>
元素,在其中访问 Algorithm
元素以检索令牌的加密方法。
if (!reader.ReadToDescendant(XmlEncryptionStrings.EncryptionMethod,
XmlEncryptionStrings.Namespace))
throw new ArgumentException("Cannot find token EncryptedMethod.");
encryptionAlgorithm = reader.GetAttribute(XmlEncryptionStrings.Algorithm).GetHashCode();
然后,找到瞬态密钥的 <EncryptionMethod>
元素,再次获取 Algorithm
,它存储为自身 HashCode
以加快查找。
if (!reader.ReadToFollowing(XmlEncryptionStrings.EncryptionMethod,
XmlEncryptionStrings.Namespace))
throw new ArgumentException("Cannot find key EncryptedMethod.");
m_keyEncryptionAlgorithm = reader.GetAttribute(XmlEncryptionStrings.Algorithm).GetHashCode();
接下来找到元素 <KeyIdentifier>
,它包含证书的指纹(解密对称密钥需要此指纹),并从 base64 字符串中提取该指纹。
if (!reader.ReadToFollowing(WSSecurityStrings.KeyIdentifier, WSSecurityStrings.Namespace))
throw new ArgumentException("Cannot find Key Identifier.");
reader.Read();
thumbprint = Convert.FromBase64String(reader.ReadContentAsString());
<CypherValue>
元素包含加密形式的对称密钥(base64 编码)。
if (!reader.ReadToFollowing(XmlEncryptionStrings.CipherValue, XmlEncryptionStrings.Namespace))
throw new ArgumentException("Cannot find symmetric key.");
reader.Read();
symmetricKeyData = Convert.FromBase64String(reader.ReadContentAsString());
包含实际加密令牌的 <CypherValue>
。
if (!reader.ReadToFollowing(XmlEncryptionStrings.CipherValue, XmlEncryptionStrings.Namespace))
throw new ArgumentException("Cannot find encrypted security token.");
reader.Read();
securityTokenData = Convert.FromBase64String(reader.ReadContentAsString());
确保关闭读取器以释放资源。
reader.Close();
安全令牌的加密可采用两种对称算法(AES 和 Triple DES)中的一种。 可使用加密算法 URI 进行查找。
SymmetricAlgorithm alg = null;
X509Certificate2 certificate = FindCertificate(thumbprint );
foreach( int i in Aes )
if (encryptionAlgorithm == i)
{
alg= new RijndaelManaged();
break;
}
if ( null == alg )
foreach (int i in TripleDes)
if (encryptionAlgorithm == i)
{
alg = new TripleDESCryptoServiceProvider();
break;
}
if (null == alg)
throw new ArgumentException("Could not determine Symmetric Algorithm");
若要获取对称密钥,请使用私钥对其进行解密。
alg.Key=(certificate.PrivateKey as RSACryptoServiceProvider).Decrypt(symmetricKeyData,true);
使用通过对称算法发现的算法解密令牌。
int ivSize = alg.BlockSize / 8;
byte[] iv = new byte[ivSize];
Buffer.BlockCopy(securityTokenData, 0, iv, 0, iv.Length);
alg.Padding = PaddingMode.ISO10126;
alg.Mode = CipherMode.CBC;
ICryptoTransform decrTransform = alg.CreateDecryptor(alg.Key, iv);
byte[] plainText = decrTransform.TransformFinalBlock(securityTokenData, iv.Length,
securityTokenData.Length iv.Length);
decrTransform.Dispose();
return plainText;
}
反序列化令牌并对令牌进行身份验证
若要使用嵌入的令牌,在解密之后,.NET Framework 3.0 将使用 SamlSecurityTokenAuthenticator 对令牌进行反序列化(通过 WSSecurityTokenSerializer)和身份验证。 目前 Token
类支持 SAML 令牌。 如果需要其他令牌类型,开发人员必须提供身份验证器以支持该类型。 在身份验证器对令牌进行验证之后,Token
类将声明提取到可用表单中。
public Token(String xmlToken)
{
byte[] decryptedData = decryptToken(xmlToken);
XmlReader reader = new XmlTextReader(
new StreamReader(new MemoryStream(decryptedData), Encoding.UTF8));
m_token = (SamlSecurityToken)WSSecurityTokenSerializer.DefaultInstance.ReadToken(
reader, null);
SamlSecurityTokenAuthenticator authenticator =
new SamlSecurityTokenAuthenticator(new List<SecurityTokenAuthenticator>(
new SecurityTokenAuthenticator[]{
new RsaSecurityTokenAuthenticator(),
new X509SecurityTokenAuthenticator() }), MaximumTokenSkew);
if (authenticator.CanValidateToken(m_token))
{
ReadOnlyCollection<IAuthorizationPolicy> policies = authenticator.ValidateToken(m_token);
m_authorizationContext = AuthorizationContext.CreateDefaultAuthorizationContext(policies);
FindIdentityClaims();
}
else
{
throw new Exception("Unable to validate the token.");
}
}
Token 类的用法
下表描述了 Token
类公开的用于从安全令牌中提取声明的属性。
名称 | 说明 |
---|---|
|
令牌中标识声明的 ClaimSet。 |
|
从令牌生成的 AuthorizationContext。 |
|
获取令牌的 UniqueID (IdentityClaim)。 默认情况下,它使用 PPID 和颁发者的公钥,并对它们共同执行哈希算法来生成一个 UniqueID。 若要使用其他字段,请添加下面的代码行。
将该值替换为唯一声明的 URI。 |
|
令牌中声明的只读字符串集合。 为索引的声明访问器提供支持。
|
|
返回颁发者的标识声明(通常为颁发者的公钥)。 |
版权所有 (C) 2007 Microsoft Corporation。保留所有权利。