本文将专注讨论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文件的签名方法,具体关于细节可以参见以下文章:
其大致流程为:
- 确定需要确认XML文档中哪些内容签名,通过URI将这些数据项目表示为引用资源 ,以Reference元素表示。对于断言消息来说,整个断言都是需要加密的,所以其URI为断言的ID;
- 对待签名的数据进行转化处理,包括制定的编码规则、规范化算法以及应用于已签名数据的XSLT转换,transform元素用于指定要应用的算法;
- 转化后,对每个Reference元素中引用的URI资源应用消息摘要算法(由于是对整个断言签名,所以只会出现一个Reference元素);DigestMethod元素指定元素应用的消息摘要算法,消息摘要值将存储在DigestValue元素中;
- 构造包含Reference元素的SignedInfo元素,
- 使用CananonicalizationMethod元素指定的规范化算法对SignedInfo元素进行规范化,如果不进行规范化,验证XML签名将可能因为XML结构或者表示方便不同而失败; XML规范标准见此处;
- 计算SignedInfo元素的摘要,并使用SignatureMethod元素中声明的签名算法,对其进行签名,得到的签名值放到SignatureValue元素中;
- 添加KeyInfo元素(可选),表面签名所使用的密钥信息,如果双方已经提前协商好密钥信息,就可以省却该项;
- 构造Signature元素,其中包括SignedInfo,SignedValue,KeyInfo等元素;
核实和验证XML的签名:
- 核实和验证摘要值: 重新计算Refernce元素引用的数据对象的摘要值,计算过程包括应用Transforms元素制定进行转换,并使用DigestMethod元素指定的算法计算转化结果摘要,最后将计算得到的摘要值与DigestValue元素中的值进行比较
- 核实和验证签名: 使用KeyInfo元素包含的或者从外部资源获得的密钥信息重新计算SignedInfo元素中的签名;将计算得到的签名值与SignatureVlue元素的值进行比较。
2.1 对数据签名
在OpenSAML中,每一个实现SignableXMLObject接口的对象都可以被签名。签名的产生一共分为4步:
- 创建签名类对象并赋予签名属性:
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);
- 将SignableXMLObject对象和签名类对象绑定起来:
signableXMLObject.setSignature(signature);
- 将SignableXMLObject对象序列化为XML对象
XMLObjectProviderRegistrySupport
.getMarshallerFactory()
.getMarshaller(object)
.marshall(object);
- 使用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体现出来。
3.1 加密断言流程
- 为加密过程设置参数,即定义使用什么算法来加密断言数据:
DataEncryptionParameters encryptionParameters =
new DataEncryptionParameters();
encryptionParameters
.setAlgorithm(EncryptionConstants.ALGO_ID_BLOCKCIPHER_AES128);
- 设定使用何种算法对上一步用来加密数据的对称密钥进行非对称加密:
KeyEncryptionParameters keyEncryptionParameters
= new KeyEncryptionParameters();
keyEncryptionParameters
.setEncryptionCredential(SPCredentials.getCredential());
keyEncryptionParameters.setAlgorithm(
EncryptionConstants
.ALGO_ID_KEYTRANSPORT_RSAOAEP);
- 利用以上两个参数来生成加密对象:
Encrypter encrypter = new Encrypter(encryptionParameters, keyEncryptionParameters);
- 设置被加密的对称密钥的位置,这里设置为ArtifactResponse中KeyInfo元素的内置元素,还可以设置为Peer模式就是和KeyInfo元素并列:
encrypter.setKeyPlacement(Encrypter.KeyPlacement.INLINE);
- 使用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);
更多关于SAML协议的是实现的内容,请参见本人编写的一系列教程文章,其介绍如何使用OpenSAML,欢迎阅读指正: