使用OpenSAML支持来准备SAML有效负载来完成与我正在使用的另一服务的SSO事务时,我走到了尽头。我收到使用SecurityHelper.prepareSignatureParams()例程后引发的NullPointerException。我有一个Stacktrace,但是附加起来会很丑陋。
首先让我说说我能做的…
为了学习该技术并确保其有效,我能够成功构建SAML有效负载,使用存储在我在工作站上本地创建的Java密钥存储文件中的证书和私钥对它进行签名使用带有-genkeypair选项的Keytool程序。
据我了解,我的JKS文件包含一个自签名证书和一个私钥。我能够打开JKS文件,收集证书和私钥以构建签名证书。签名证书用于对我创建的SAML有效负载进行签名。如果查看我将附加的代码示例,您将看到我是如何做到的。
什么不起作用…
我想使用从GoDaddy收到的我的网站所拥有的受信任证书,使用相同的SAML支持来签署SAML有效负载。为此,我将受信任的证书安装到了Web服务器的密钥库中,位于:\ Program Files \ Java \ jre1.8.0_102 \ lib \ security \ cacerts。我了解cacerts文件是我们网络服务器的KeyStore。我使用Keytool -importcert命令安装了受信任证书。一个很大的不同是,受信任的证书没有私钥。因此,在使用Open SAML支持准备签名证书时,我无法向凭据对象添加私钥(因为我没有一个)。
尝试对受信任证书进行以上操作时,我可以转到准备签名参数(SecurityHelper.prepareSignatureParams())的部分。那就是我得到空指针的地方。
如果您可以看一下我正在使用的代码。我包括从本地JKS文件读取的代码(成功签名我的有效负载),以及尝试在服务器上使用这两种情况下的代码(获得Null指针异常)。两种情况之间没有太大区别:
// Signing process using OpenSAML // Get instance of an OpenSAML 'KeyStore' object... KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); // Read KeyStore as File Input Stream. This is either the local JKS // file or the server's cacerts file. File ksFile = new File(keyStoreFileName); // Open an Input Stream with the Key Store File FileInputStream ksfInputStream = new FileInputStream(ksFile); // Load KeyStore. The keyStorePassord is the password assigned to the keystore, Usually 'changeit' // before being changed. keyStore.load(ksfInputStream, keyStorePassword); // Close InputFileStream. No longer needed. ksfInputStream.close(); // Used to get Entry objects from the Key Store KeyStore.PrivateKeyEntry pkEntry = null; KeyStore.TrustedCertificateEntry tcEntry = null; PrivateKey pk = null; X509Certificate x509Certificate = null; BasicX509Credential credential = null; // The Java Key Store specific code... // Get Key Entry From the Key Store. CertificateAliasName identifies the // Entry in the KeyStore. KeyPassword is assigned to the Private Key. pkEntry = (KeyStore.PrivateKeyEntry) keyStore.getEntry(certificateAliasName, new KeyStore.PasswordProtection(keyPassword)); // Get the Private Key from the Entry pk = pkEntry.getPrivateKey(); // Get the Certificate from the Entry x509Certificate = (X509Certificate) pkEntry.getCertificate(); // Create the Credential. Assign the X509Certificate and the Privatekey credential = new BasicX509Credential(); credential.setEntityCertificate(x509Certificate); credential.setPrivateKey(pk); // The Trusted Certificate specific code... // Accessing a Certificate that was issued from a trusted source - like GoDaddy.com // // Get Certificate Entry From the Key Store. CertificateAliasName identifies the Entry in the KeyStore. // There is NO password as there is no Private Key associate with this Certificate tcEntry = (TrustedCertificateEntry) keyStore.getEntry(certificateAliasName, null); // Get the Certificate from the Entry x509Certificate = (X509Certificate) tcEntry.getTrustedCertificate(); // Create the Credential. There is no Provate Ley to assign into the Credential credential = new BasicX509Credential(); credential.setEntityCertificate(x509Certificate); // Back to code that is not specific to either method... // // Assign the X509Credential object into a Credential Object. The BasicX509Credential object // that has a Certificate and a Private Key OR just a Certificate added to it is now saved as a // Cendential object. Credential signingCredential = credential; // Use the OpenSAML builder to create a signature object. Signature signingSignature = (Signature) Configuration.getBuilderFactory().getBuilder(Signature.DEFAULT_ELEMENT_NAME).build Object(Signature.DEFAULT_ELEMENT_NAME); // Set the previously created signing credential signingSignature.setSigningCredential(signingCredential); // Get a Global Security Configuration object. SecurityConfiguration secConfig = Configuration.getGlobalSecurityConfiguration(); // KeyInfoGenerator. Not sure what this is, but the example I am working from shows // this being passed as null. String keyInfoGeneratorProfile = "XMLSignature"; // Prepare the Signature Parameters. // // This works fine for the JKS version of the KeyStore, but gets a Null Pointer exception when I run to the cacerts file. SecurityHelper.prepareSignatureParams(signingSignature, signingCredential, secConfig, keyInfoGeneratorProfile <or null>); // I need to set into the SigningSignature object the signing algorithm. This is required when using the TrustedCertificate signingSignature.setSignatureAlgorithm(SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA256); // This is my code that builds a SAML Response. The SAML Payload contains data // about the SSO session that I will be creating... Response samlResponse = createSamlResponse.buildSamlResponseMessage(); // Sign the Response using the Certificate that was created earlier samlResponse.setSignature(signingSignature); // Get the marshaller factory to marshall the SamlResponse MarshallerFactory marshallerFactory = Configuration.getMarshallerFactory(); Marshaller responseMarshaller = marshallerFactory.getMarshaller(samlResponse); // Marshall the Response Element responseElement = responseElement = responseMarshaller.marshall(samlResponse); // Sign the Object... Signer.signObject(signingSignature);
注意:我对SAML有效负载进行签名的尝试是根据在这里找到的OPENSAML示例进行的:https ://narendrakadali.wordpress.com/2011/06/05/sign-assertion-using-opensaml/
希望有人可以告诉我我的方式错误或我所缺少的东西。
感谢您的任何建议。
编辑(01/26/2016)
在准备签名参数(SecurityHelper.prepareSignatureParams())时,我能够跳过收到的NULL指针。代码更改包括将xmlsec.jar文件更新到版本2.0.8(xmlsec-2.0.8.jar),并且在使用“受信任的证书”(来自GoDaddy)时,我将签名算法显式设置为SHA256。请参阅我的代码示例以了解如何使用:
signingSignature.setSignatureAlgorithm(SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA256);
通过以上更改,可以构建SAML有效负载并将其发送到连接端点。
但是,我仍未建立与端点的SSO连接。
这是我看到的情况:
在构造SAML有效负载的过程中,特别是在SAML有效负载的Signature被签名的过程中:
Signer.signObject(signature);
我收到来自SAML的错误消息:
ERROR: org.opensaml.xml.signature.Signer - An error occured computing the digital signature
堆栈跟踪(仅结束部分):
org.apache.xml.security.signature.XMLSignatureException: Sorry, you supplied the wrong key type for this operation! You supplied a null but a java.security.PrivateKey is needed. at org.apache.xml.security.algorithms.implementations.SignatureBaseRSA.engineInitSign(SignatureBaseRSA.java:149) at org.apache.xml.security.algorithms.implementations.SignatureBaseRSA.engineInitSign(SignatureBaseRSA.java:165) at org.apache.xml.security.algorithms.SignatureAlgorithm.initSign(SignatureAlgorithm.java:238) at org.apache.xml.security.signature.XMLSignature.sign(XMLSignature.java:631) at org.opensaml.xml.signature.Signer.signObject(Signer.java:77)
我搜索了错误消息,但没有提出太多建议。
我不明白错误消息的根源-提供了错误的密钥类型(空),并且OpenSAML似乎期望使用java.Security.PrivateKey。
使用受信任的证书时,我没有私钥,对吗?我将如何提供私钥?对于可信证书,我从密钥库中读取了可信证书(TrustedCertificateEntry)。TrustedCertificateEntry对象允许我访问证书,但是没有获取私钥的方法(同样也不应该)。
但是,当我使用自己的自签名证书执行签名操作时,我了解我确实在JKS文件(密钥库)中同时包含了证书(公共密钥)和私有密钥。我认为这就是为什么当我从JKS文件读取内容时,我能够读取一个私钥条目(KeyStore.PrivateKeyEntry),该私钥条目具有用于访问公钥(证书)和私钥的方法。
我对“受信任的证书”案件有什么遗漏?OpenSAML支持似乎期望私钥能够计算签名。
对于受信任证书,是否可以将原始私钥(连同受信任证书)打包到我的密钥存储中?我不确定这是正常完成还是什至可行。
希望对我在这里的工作有一些指导,请!
编辑(2017年1月26日)-2提供其他详细信息。
我将共享发送的SAML有效负载的一部分…
对于“自签名证书”,我看到一个SignatureValue标签和一个X509Certificate标签。两者的标记开头和结尾都包含二进制数据。
对于可信证书,我有一个空的“签名值”标签,看起来像:
<ds:SignatureValue/>
证书标签仍然存在,并且包含证书字节。
因此,从我从OpenSAML中看到的错误来看,很明显它无法使用受信任证书中可用的数据来计算签名。