添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

本文将专注讨论OpenSAML中安全特性,包括如何管理密钥,如何对断言信息签名,如何对断言信息加密等。

源码地址:​ ​https://github.com/sunrongxin7666/OpenSAML-ref-project-demo-v3.git​



1. OpenSAML中的证书对象

证书对象( Credential )中包含密钥信息,密钥可以是对称的,也可以是非对称的,可能是一个也可能是多个,其重要的作用就作为密钥的容器存在。证书对象和我们通常说的数字证书不是一个概念。

1.1 生成证书对象

证书对象 Credential 可以通过 KeySupport 工具自动生成。 KeySupport 可以生成对称密钥和非对称密钥对,下面的例子是生成 RSA 密钥对,并生成证书对象:



KeyPair keyPair = KeySupport.generateKeyPair("RSA", 1024, null);
return CredentialSupport.getSimpleCredential(keyPair.getPublic(), keyPair.getPrivate());



1.2 读取已用的密钥到证书对象

如何读取已经存在的密钥到证书对象中?OpenSAML推荐的方法是去使用各种 CredentialResolvers 类对象把已经存在的密钥放入新创建的证书对象中。

比如密钥被存放在 JavaKeyStore 中,相应地久应该使用 KeyStoreCredentialResolver ,其构造函数需要key store和密钥别名与口令的Map。



Map<String, String> passwordMap 
= new HashMap<String, String>();
passwordMap.put(KEY_ENTRY_ID, KEY_STORE_ENTRY_PASSWORD);
KeyStoreCredentialResolver resolver =
new KeyStoreCredentialResolver(keystore, passwordMap);


通过带有key别名的criterion来查询credential resolver中的credentials。



Criterion criterion = new EntityIDCriterion(KEY_ENTRY_ID);
CriteriaSet criteriaSet = new CriteriaSet(criterion);
resolver.resolveSingle(criteriaSet);




除此之外还有如下常用CredentialResolver:

  • MetadataCredentialResolver :在SAML Metadata中获得密钥创建证书对象;
  • FilesystemCredentialResolver :在文件系统中获得密钥创建证书对象。

2. OpenSAML中的密码签名

签名是密码学一个证明数据完整性的手段。OpenSAML中提供了工具来对短信信息经行签名以及验证签名。由于断言的表现形式是一种XML,所以其签名也是基于XML签名方法。

2.0 XML的签名方法

这里先简要介绍下XML文件的签名方法,具体关于细节可以参见以下文章:

OpenSAML 使用引导 IV: 安全特性_xml

OpenSAML 使用引导 IV: 安全特性_数据_02





其大致流程为:

  1. 确定需要确认XML文档中哪些内容签名,通过URI将这些数据项目表示为 引用资源 ,以 Reference 元素表示。对于断言消息来说,整个断言都是需要加密的,所以其URI为断言的ID;
  2. 对待签名的数据进行 转化 处理,包括制定的编码规则、规范化算法以及应用于已签名数据的XSLT转换, transform 元素用于指定要应用的算法;
  3. 转化后,对每个 Reference 元素中引用的URI资源应用消息摘要算法(由于是对整个断言签名,所以只会出现一个Reference元素); DigestMethod 元素指定元素应用的消息摘要算法,消息摘要值将存储在 DigestValue 元素中;
  4. 构造包含 Reference 元素的 SignedInfo 元素,
  5. 使用 CananonicalizationMethod 元素指定的规范化算法对 SignedInfo 元素进行​ ​规范化​ ​,如果不进行规范化,验证XML签名将可能因为XML结构或者表示方便不同而失败; XML规范标准见​ ​此处​ ​;
  6. 计算 SignedInfo 元素的摘要,并使用 SignatureMethod 元素中声明的签名算法,对其进行签名,得到的签名值放到 SignatureValue 元素中;
  7. 添加 KeyInfo 元素(可选),表面签名所使用的密钥信息,如果双方已经提前协商好密钥信息,就可以省却该项;
  8. 构造 Signature 元素,其中包括 SignedInfo SignedValue KeyInfo 等元素;

核实和验证XML的签名:

  1. 核实和验证摘要值: 重新计算 Refernce 元素引用的数据对象的摘要值,计算过程包括应用 Transforms 元素制定进行转换,并使用 DigestMethod 元素指定的算法计算转化结果摘要,最后将计算得到的摘要值与 DigestValue 元素中的值进行比较
  2. 核实和验证签名: 使用 KeyInfo 元素包含的或者从外部资源获得的密钥信息重新计算 SignedInfo 元素中的签名;将计算得到的签名值与 SignatureVlue 元素的值进行比较。

2.1 对数据签名

在OpenSAML中,每一个实现 SignableXMLObject 接口的对象都可以被签名。签名的产生一共分为4步:

1. 创建签名类对象并赋予签名属性:



Signature signature = OpenSAMLUtils
.buildSAMLObject(Signature.class);
//设定证书对象,其中包含密钥
signature.setSigningCredential(credential);
//设定签名算法
signature
.setSignatureAlgorithm(SignatureConstants
.ALGO_ID_SIGNATURE_RSA_SHA1);
//设定XML规范化算法
signature
.setCanonicalizationAlgorithm(SignatureConstants.ALGO_ID_C14N_EXCL_OMIT_COMMENTS);


2. 将 SignableXMLObject 对象和签名类对象绑定起来:



signableXMLObject.setSignature(signature);


3. 将SignableXMLObject对象序列化为XML对象



XMLObjectProviderRegistrySupport
.getMarshallerFactory()
.getMarshaller(object)
.marshall(object);


4. 使用Signer工具类生成签名



Signer.signObject(signature);




在一些情况下,OpenSAML会自动完成了签名的的过程,比如在传输SAML消息时,详见关于 HTTPRedirectDeflateEncoder 的内容。

2.2 验证签名

在验证签名之前最好是判断该消息是否被签名:



if (!assertion.isSigned()) {
throw new RuntimeException("The SAML Assertion was not signed");
}


验证签名的第一步是判断该签名是否符合SAML签名的标准声明,也就是是否应用了XML的规范化算法:



SAMLSignatureProfileValidator profileValidator = new SAMLSignatureProfileValidator();
profileValidator.validate(assertion.getSignature());


然后再进行真正密码学意义上的验证签名:



SignatureValidator sigValidator = new SignatureValidator(credential);
sigValidator.validate(assertion.getSignature());




SignatureValidator 对象中被设置了 credential 对象,其中包含密钥信息。

3. OpenSAML中的加密

在OpenSAML中断言信息需要加密处理,要求使用一个对称密钥来来加密数据,同时用另外一个而非对称密钥用来加密这个对称密钥。断言的密文和密钥的密文都会在ArtifactResponse体现出来。

OpenSAML 使用引导 IV: 安全特性_非对称_03

3.1 加密断言流程

1. 为加密过程设置参数,即定义使用什么算法来加密断言数据:



DataEncryptionParameters encryptionParameters = 
new DataEncryptionParameters();
encryptionParameters
.setAlgorithm(EncryptionConstants.ALGO_ID_BLOCKCIPHER_AES128);


2. 设定使用何种算法对上一步用来加密数据的对称密钥进行非对称加密:



KeyEncryptionParameters keyEncryptionParameters
= new KeyEncryptionParameters();
keyEncryptionParameters
.setEncryptionCredential(SPCredentials.getCredential());
keyEncryptionParameters.setAlgorithm(
EncryptionConstants
.ALGO_ID_KEYTRANSPORT_RSAOAEP);


3. 利用以上两个参数来生成加密对象:



Encrypter encrypter = new Encrypter(encryptionParameters, keyEncryptionParameters);


4. 设置被加密的对称密钥的位置,这里设置为ArtifactResponse中KeyInfo元素的内置元素,还可以设置为Peer模式就是和KeyInfo元素并列:



encrypter.setKeyPlacement(Encrypter.KeyPlacement.INLINE);


5. 使用Encrypter加密数据



EncryptedAssertion encryptedAssertion = encrypter.encrypt(assertion);




Encrypter不光可以加密Assertion,Attribute和NameID等XMLObject都可以被加密。

3.2 解密流程

keyInfoCredentialResolver用于获得非对称密码要,该密钥用于解密对称密钥;再使用对称密钥获得数据。



StaticKeyInfoCredentialResolver keyInfoCredentialResolver = new StaticKeyInfoCredentialResolver(
SPCredentials.getCredential());

Decrypter decrypter = new Decrypter(
null,
keyInfoCredentialResolver,
new InlineEncryptedKeyResolver());

decrypter.decrypt(encryptedAssertion);



作者:登高且赋