java.lang.NullPointerException SecurityHelper.prepareSignatureParams() OpenSAML 调用期间出错

java.lang.NullPointerException Error during the SecurityHelper.prepareSignatureParams() OpenSAML call

我在使用 OpenSAML 支持准备 SAML 负载来完成与我正在使用的另一个服务的 SSO 事务时遇到了死胡同。我收到在使用 SecurityHelper.prepareSignatureParams() 例程后抛出的 NullPointerException。我有一个 Stacktrace,但是追加它会很难看。

让我先说说我能做什么...

为了学习这项技术并确保它能正常工作,我能够成功构建一个 SAML 负载,使用存储在 Java 密钥中的证书和私钥对其进行签名使用带有 -genkeypair 选项的 Keytool 程序将我在本地创建的文件存储在我的工作站上。

据我了解,我的 JKS 文件包含一个自签名证书和一个私钥。我能够打开 JKS 文件,收集证书和私钥以构建签名证书。签名证书用于签署我创建的 SAML 有效负载如果您查看我将附加的代码示例,您就会明白我是如何做到这一点的。

什么不起作用...

我想使用相同的 SAML 支持,使用我从 GoDaddy 收到的用于我的网站的可信证书来签署我的 SAML 负载。为此,我将可信证书安装到我的网络服务器的密钥库中:'\Program Files\Java\jre1.8.0_102\lib\security\cacerts'。我知道 cacerts 文件是我们网络服务器的密钥库。我使用 Keytool -importcert 命令安装了可信证书。一个很大的区别是可信证书没有私钥。因此,在使用 Open SAML 支持准备签名证书时,我无法将私钥添加到凭证对象(因为我没有)。

在为可信证书尝试上述操作时,我能够到达准备签名参数的部分 (SecurityHelper.prepareSignatureParams())。那就是我得到空指针的地方。

如果你能看一下我正在使用的代码。我包括从本地 JKS 文件读取的代码(成功签署我的有效负载)以及当我尝试在服务器上使用受信任证书时的代码(获取空指针异常)(两种情况)。这两种情况没有太大区别:

//  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 负载的签名进行签名:

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。

使用可信证书时,我没有私钥,对吗?我怎样才能提供私钥?对于受信任的证书,我从 KeyStore 中读取了受信任的证书 (TrustedCertificateEntry)。 TrustedCertificateEntry 对象允许我访问证书,但没有获取私钥的方法(也不应该有)。

但是,当我使用我的自签名证书执行签名操作时,我知道我确实拥有 JKS 文件中包含的证书(Public 密钥)和私钥(KeyStore ).我认为这就是为什么当我从 JKS 文件中读取时,我能够读取私钥条目 (KeyStore.PrivateKeyEntry),它具有访问 Public 密钥(证书)和私钥的方法。

关于可信证书案例,我遗漏了什么? OpenSAML 支持似乎期望私钥能够计算签名。

对于可信证书,有没有办法将原始私钥打包到我的密钥库中(连同可信证书)?我不确定这是否是通常所做的,甚至是可能的。

希望对我在这里所做的事情有一些指导,拜托!

编辑 (01/26/2017) - 2 以提供更多详细信息。

我将分享一部分已发送的 SAML 负载...

对于自签名证书,我看到一个 SignatureValue 标签和一个 X509Certificate 标签。两者都在标记的开头和结尾包含二进制数据。

对于可信证书,我有一个空的签名值标签,如下所示:

<ds:SignatureValue/>

证书标签仍然存在并包含证书字节。

因此,查看我从 OpenSAML 看到的错误,更明显的是它无法使用受信任证书中可用的数据计算签名。

好的,这是一个很长的问题。据我了解,问题的根源是消息 "Sorry, you supplied the wrong key type for this operation! You supplied a null but a java.security.PrivateKey is needed." 您正在尝试使用 public 密钥签署消息。这不可能。仅从逻辑上看,使用 public 密钥签名不会提供任何证据证明签名者是有意的,因为每个人都可以使用它。

您需要做的是使用私钥进行签名。在你的情况下,你已经在你的计算机上生成了一个 public 和私钥,然后将 CSR 发送到 CA 并收到了由 CA 签名的证书。 您应该使用本地计算机上的私钥对消息进行签名,并将 CA 签名的证书发送给收件人,以便他们可以使用它来确认您的签名。

this blog post of mine 中,我解释了如何获取凭据,包括来自 Java 密钥库的私钥。