aro*_*oth 5 java wcf soap soap-client
我有一个Java(基于JAX-WS)的SOAP客户端,试图与基于(第三方)WCF的服务器进行通讯。我发现这里表达的观点非常准确。但是目标仍然存在。
长话短说,我可以从服务器中哄骗有效的“安全上下文令牌”,但是却挂在消息签名问题上(我相信)。
服务器似乎期望hmac-sha1使用客户端/服务器秘密密钥(PSHA1算法)使用身份验证代码对消息进行签名。很公平。但是,JAX-WS似乎要使用rsa-sha1X509证书来签名出站消息(服务器不喜欢),并且似乎仅在提供hmac-sha1a 时才使用UsernameToken(服务器也不喜欢)。
因此,我尝试从SOAPHandler实现中手动签名出站SOAP消息。客户端发送以获取安全上下文令牌的请求如下所示:
<t:RequestSecurityToken xmlns:t="http://schemas.xmlsoap.org/ws/2005/02/trust">
<t:TokenType>http://schemas.xmlsoap.org/ws/2005/02/sc/sct</t:TokenType>
<t:RequestType>http://schemas.xmlsoap.org/ws/2005/02/trust/Issue</t:RequestType>
<t:Entropy>
<t:BinarySecret Type="http://schemas.xmlsoap.org/ws/2005/02/trust/Nonce">NzM1MDZjYWVkMTEzNDlkNGEyODY0ZDBlMjlkODEyMTM=</t:BinarySecret>
</t:Entropy>
<t:KeySize>256</t:KeySize>
</t:RequestSecurityToken>
Run Code Online (Sandbox Code Playgroud)
服务器发回的令牌如下所示:
<t:RequestSecurityTokenResponse xmlns:t="http://schemas.xmlsoap.org/ws/2005/02/trust">
<t:TokenType>http://schemas.xmlsoap.org/ws/2005/02/sc/sct</t:TokenType>
<t:RequestedSecurityToken>
<c:SecurityContextToken xmlns:c="http://schemas.xmlsoap.org/ws/2005/02/sc" u:Id="uuid-106bdbae-76e5-4195-b5d0-cc1c1a7a813e-13">
<c:Identifier>urn:uuid:c0be4929-da8d-4955-8e13-b25aa7a37217</c:Identifier>
</c:SecurityContextToken>
</t:RequestedSecurityToken>
<t:RequestedAttachedReference>
<o:SecurityTokenReference xmlns:o="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
<o:Reference ValueType="http://schemas.xmlsoap.org/ws/2005/02/sc/sct" URI="#uuid-106bdbae-76e5-4195-b5d0-cc1c1a7a813e-13" />
</o:SecurityTokenReference>
</t:RequestedAttachedReference>
<t:RequestedUnattachedReference>
<o:SecurityTokenReference xmlns:o="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
<o:Reference URI="urn:uuid:c0be4929-da8d-4955-8e13-b25aa7a37217" ValueType="http://schemas.xmlsoap.org/ws/2005/02/sc/sct" />
</o:SecurityTokenReference>
</t:RequestedUnattachedReference>
<t:RequestedProofToken>
<t:ComputedKey>http://schemas.xmlsoap.org/ws/2005/02/trust/CK/PSHA1</t:ComputedKey>
</t:RequestedProofToken>
<t:Entropy>
<t:BinarySecret u:Id="uuid-106bdbae-76e5-4195-b5d0-cc1c1a7a813e-14" Type="http://schemas.xmlsoap.org/ws/2005/02/trust/Nonce">dssunihZGy2dnnDHV9PMe3vU3lg/kKKZQkFohvGvCAk=</t:BinarySecret>
</t:Entropy>
<t:Lifetime>
<u:Created>2016-04-08T04:11:54.392Z</u:Created>
<u:Expires>2016-04-08T19:11:54.392Z</u:Expires>
</t:Lifetime>
<t:KeySize>256</t:KeySize>
</t:RequestSecurityTokenResponse>
Run Code Online (Sandbox Code Playgroud)
我BinarySecret使用PSHA1以下方法组合客户端和服务器密钥:
private byte[] getSharedKey() {
try {
//FIXME: client key first, or server key first?
P_SHA1 algo = new P_SHA1();
return algo.createKey(getBinaryClientEntropy(), getBinaryServerEntropy(), 0, getSharedKeySize() / 8);
}
catch (Throwable e) {
LOG.error("Unable to compute shared key!", e);
}
return null;
}
Run Code Online (Sandbox Code Playgroud)
然后,我使用该密钥为消息计算MAC,例如:
Mac mac = Mac.getInstance("HmacSHA1");
SecretKeySpec key = new SecretKeySpec(getSharedKey(), "HmacSHA1");
mac.init(key);
byte[] signatureBytes = mac.doFinal(content);
String signature = Base64.encodeBytes(signatureBytes);
Run Code Online (Sandbox Code Playgroud)
然后,该请求就进入了出站请求(以及大量其他样板文件),例如SignatureValue。最终,我最终得到如下结果:
<S:Envelope xmlns:S="http://www.w3.org/2003/05/soap-envelope">
<S:Header xmlns:a="http://www.w3.org/2005/08/addressing" xmlns:scon="http://schemas.xmlsoap.org/ws/2005/02/sc" xmlns:sec="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
<sec:Security xmlns:env="http://www.w3.org/2003/05/soap-envelope" env:mustUnderstand="true">
<scon:SecurityContextToken xmlns:util="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" util:Id="uuid-106bdbae-76e5-4195-b5d0-cc1c1a7a813e-55">
<scon:Identifier>urn:uuid:3ab0f3fb-edd4-4880-af77-d700dda371bb</scon:Identifier>
</scon:SecurityContextToken>
<sig:Signature xmlns:sig="http://www.w3.org/2000/09/xmldsig#">
<sig:SignedInfo>
<sig:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
<sig:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#hmac-sha1" />
</sig:SignedInfo>
<sig:SignatureValue>ohqViTbUYBG2E3hLldUA1AsPBJM=</sig:SignatureValue>
<sig:KeyInfo>
<sec:SecurityTokenReference>
<sec:Reference URI="#uuid-106bdbae-76e5-4195-b5d0-cc1c1a7a813e-55" ValueType="http://schemas.xmlsoap.org/ws/2005/02/sc/sct" />
</sec:SecurityTokenReference>
</sig:KeyInfo>
</sig:Signature>
</sec:Security>
</S:Header>
<S:Body>
<ns2:HelloWorld xmlns:ns2="http://tempuri.org/" xmlns:ns3="http://schemas.microsoft.com/2003/10/Serialization/">
<ns2:name>Test</ns2:name>
</ns2:HelloWorld>
</S:Body>
</S:Envelope>
Run Code Online (Sandbox Code Playgroud)
这导致从服务器返回“响应消息的安全性时发生错误”响应。
使用wcf-storm发出请求并使用Fiddler2检查传出数据包,我知道我应该关闭了。以下请求正常工作:
<S:Envelope xmlns:S="http://www.w3.org/2003/05/soap-envelope">
<S:Header xmlns:a="http://www.w3.org/2005/08/addressing" xmlns:scon="http://schemas.xmlsoap.org/ws/2005/02/sc" xmlns:sec="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
<o:Security xmlns:o="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" s:mustUnderstand="1">
<u:Timestamp u:Id="_0">
<u:Created>2016-04-05T23:48:06.110Z</u:Created>
<u:Expires>2016-04-05T23:53:06.110Z</u:Expires>
</u:Timestamp>
<c:SecurityContextToken xmlns:c="http://schemas.xmlsoap.org/ws/2005/02/sc" u:Id="uuid-8085da33-b25c-4f09-b5a9-110635a3ae39-2005">
<c:Identifier>urn:uuid:91349027-cb32-4c46-9f16-74a6bcb11126</c:Identifier>
</c:SecurityContextToken>
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
<SignedInfo>
<CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
<SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#hmac-sha1" />
<Reference URI="#_0">
<Transforms>
<Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
</Transforms>
<DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
<DigestValue>AvRXi7pyjulsfdg9afInSFMM+5k=</DigestValue>
</Reference>
</SignedInfo>
<SignatureValue>TQup7BBN43b8CefrdSRd+X8MBgg=</SignatureValue>
<KeyInfo>
<o:SecurityTokenReference>
<o:Reference ValueType="http://schemas.xmlsoap.org/ws/2005/02/sc/sct" URI="#uuid-8085da33-b25c-4f09-b5a9-110635a3ae39-2005" />
</o:SecurityTokenReference>
</KeyInfo>
</Signature>
</o:Security>
</S:Header>
<S:Body>
<ns2:HelloWorld xmlns:ns2="http://tempuri.org/" xmlns:ns3="http://schemas.microsoft.com/2003/10/Serialization/">
<ns2:name>Test</ns2:name>
</ns2:HelloWorld>
</S:Body>
</S:Envelope>
Run Code Online (Sandbox Code Playgroud)
主要区别在于:
Timestamp元素(尽管我尝试将其包括在内,但似乎没有任何区别)。SignedInfo/Reference元素,因为我不确定该如何DigestValue计算。所以毕竟,我想主要的问题是:
签署出站邮件的实际算法是什么? 如图所示,如果我有:
<Envelope>
<Header>
HHH...
</Header>
<Body>
BBB...
</Body>
</Envelope>
Run Code Online (Sandbox Code Playgroud)
...我是要计算签名值<Envelope>...</Envelope>(是整个东西),还是仅仅是<Body>...</Body>,甚至只是BBB...一部分?并且,如果我打算使用整个内容,该如何与将签名信息添加到标头会更改计算签名时用作输入内容的事实相协调呢?
是否有更直接的方法让JAX-WS使用我忽略的必需签名约定来生成请求?
然后还有一些次要的奖金问题:
是否有关于BinarySecret使用组合客户端和服务器值时传递客户端和服务器值的顺序的既定标准PSHA1?
Timestamp和SignedInfo/Reference项是否重要?如果是,那么计算的正确方法是DigestValue什么?
经过一些研究和反复试验,我设法找到了可行的解决方案。我将从奖金问题开始:
我没有找到任何正式文档,遇到的每个参考实现和代码示例始终总是首先传递客户端密钥,这也是服务器(Microsoft IIS v8.5)所期望的。因此,即使它不是正式的标准,也似乎是标准。
是的,Timestamp和Reference值在很大程度上具有重要意义,并且与主要问题密切相关。
那么,如果必须使用Java使用JAX-WS手动签名出站SOAP消息中的内容,那么实际的算法是什么?
该参考资料是一个有用的起点,应该使您对SOAP世界中已变得过于架构化的事情有了一个很好的了解。引导它的过程有些晦涩难懂。例如:
3.2.2签名验证
KeyInfo从外部来源获取密钥信息。- 获得的规范形式
SignatureMethod使用CanonicalizationMethod,并使用结果(和先前获得的KeyInfo),以确认SignatureValue在SignedInfo元件。
如果你KeyInfo是SecurityTokenReference一个SecurityContextToken实际上并不包含任何关键数据本身,和你的SignatureMethod就是Algorithm="http://www.w3.org/2000/09/xmldsig#hmac-sha1",很明显泥浆如何任何CanonicalizationMethod涉及或你应该如何从去知道你需要在服务器相结合,客户BinarySecret价值,并将结果作为您的关键。但是我离题了。
该Signature块中大致描述了要应用的算法。例如,如果您正在与之交谈的服务器期望如下:
<o:Security xmlns:o="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" s:mustUnderstand="1">
<u:Timestamp u:Id="_0">
<u:Created>2016-04-11T00:53:44.050Z</u:Created>
<u:Expires>2016-04-11T00:58:44.050Z</u:Expires>
</u:Timestamp>
<c:SecurityContextToken xmlns:c="http://schemas.xmlsoap.org/ws/2005/02/sc" u:Id="uuid-41b0578e-dc47-4467-9b65-b0cebde98309-1">
<c:Identifier>urn:uuid:9eba64a2-5cf8-4ea9-85e9-359b2edbb13c</c:Identifier>
</c:SecurityContextToken>
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
<SignedInfo>
<CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
<SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#hmac-sha1" />
<Reference URI="#_0">
<Transforms>
<Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
</Transforms>
<DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
<DigestValue>CwJgqLNOoHJpuiqIOylvVvFli1E=</DigestValue>
</Reference>
</SignedInfo>
<SignatureValue>fJxof0blfd6abX0V4EmPYZ/NGJI=</SignatureValue>
<KeyInfo>
<o:SecurityTokenReference>
<o:Reference ValueType="http://schemas.xmlsoap.org/ws/2005/02/sc/sct" URI="#uuid-41b0578e-dc47-4467-9b65-b0cebde98309-1" />
</o:SecurityTokenReference>
</KeyInfo>
</Signature>
</o:Security>
Run Code Online (Sandbox Code Playgroud)
...您想从Reference元素开始,该元素指向带有id“ _0” 的元素(在本例中为Timestamp元素)。然后,您可以根据指定的Transform算法规范化引用的元素。使用Apache XML Security最简单的方法是:
SOAPElement timestamp = secHeader.addChildElement(soapFactory.createName("Timestamp", "u", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"));
//[add 'Created' and 'Expires' values, as required]
//once you're done adding stuff, you can canonicalize the element
Canonicalizer canonizer = Canonicalizer.getInstance(Canonicalizer.ALGO_ID_C14N_EXCL_OMIT_COMMENTS);
byte[] canonTimestamp = canonizer.canonicalizeSubtree(timestamp);
Run Code Online (Sandbox Code Playgroud)
那会给你这样的东西(换行符不是规范的,抱歉):
<u:Timestamp xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" u:Id="_0"><u:Created>2016-04-11T00:53:44.050Z</u:Created><u:Expires>2016-04-11T00:58:44.050Z</u:Expires></u:Timestamp>
Run Code Online (Sandbox Code Playgroud)
现在,您需要计算该DigestValue字符串的。在DigestMethod我们的元Reference元告诉我们,这应该是一个SHA1哈希(base64编码)。如此简单:
MessageDigest sha1 = MessageDigest.getInstance("SHA-1");
String canonDigestValue = Base64.encodeBytes(sha1.digest(canonTimestamp));
Run Code Online (Sandbox Code Playgroud)
您获得的值将包含在Reference/DigestValue元素中(假设您正在构建出站请求)。一旦完成,Reference就完成了,并且由于没有任何其他Reference元素,因此SignedInfo块也是如此。
现在SignatureValue,您可以将SignedInfo元素标准化,与之前相同:
SOAPElement sigInfo = sigElem.addChildElement(new QName("SignedInfo"));
SOAPElement canon = sigInfo.addChildElement(new QName("CanonicalizationMethod"));
canon.addAttribute(soapFactory.createName("Algorithm"), Canonicalizer.ALGO_ID_C14N_EXCL_OMIT_COMMENTS);
//[continue adding the other elements...]
//canonicalize the entire, completed 'SignedInfo' block
byte[] bytesToSign = canonizer.canonicalizeSubtree(sigInfo);
Run Code Online (Sandbox Code Playgroud)
哪个应该使您网购:
<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#"><CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"></CanonicalizationMethod><SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#hmac-sha1"></SignatureMethod><Reference URI="#_0"><Transforms><Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"></Transform></Transforms><DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"></DigestMethod><DigestValue>CwJgqLNOoHJpuiqIOylvVvFli1E=</DigestValue></Reference></SignedInfo>
Run Code Online (Sandbox Code Playgroud)
...然后您根据指定的SignatureMethod算法对整个事物进行签名,在我们的例子中是HmacSHA1:
Mac mac = Mac.getInstance("HmacSHA1");
SecretKeySpec key = new SecretKeySpec(getSharedKey(), "HmacSHA1");
mac.init(key);
String signature = Base64.encodeBytes(mac.doFinal(bytesToSign));
Run Code Online (Sandbox Code Playgroud)
getSharedKey()在这种情况下,... where 返回使用BinarySecret客户端和服务器在初始RequestSecurityToken交换期间发送的值派生的密钥。如:
private byte[] getSharedKey() {
try {
//XXX: doesn't seem to be formally specified anywhere, but convention appears to be that the client key always goes first
P_SHA1 algo = new P_SHA1();
return algo.createKey(getBinaryClientEntropy(), //the 'BinarySecret' value that the client sent to the server, decoded to raw binary
getBinaryServerEntropy(), //the 'BinarySecret' value that the server sent to the client, decoded to raw binary
0, //offset, '0' is what we want here
getSharedKeySize() / 8); //'KeySize' is 256 bits in this case (specified by server), divide by '8' to convert to bytes
}
catch (Throwable e) {
LOG.error("Unable to compute shared key!", e);
}
return null;
}
Run Code Online (Sandbox Code Playgroud)
无论如何,这时您应该具有一个签名值,并且可以将其附加到Security出站消息的标头中,例如:
SOAPElement sigValue = sigElem.addChildElement(new QName("SignatureValue"));
sigValue.addTextNode(signature);
Run Code Online (Sandbox Code Playgroud)
如果一切顺利,则消息现在已经成功签名并且服务器的质量可以接受。
尽管我最后指出了一个警告,那就是该Timestamp值需要在服务器的时区中生成(在本例中为UTC),否则它将由于时戳或将来的时间戳而拒绝该请求。已经过期了。一个简单的问题可以通过对UNIX纪元时间戳进行标准化来解决。但是出于某种原因,他们改用“ yyyy-mm-dd'T'hh:mm:ss.msec'Z'”。去搞清楚。
我希望这对下一个不幸的灵魂有所帮助,他不得不尝试使Java使用SOAP / XML与.NET进行通信。
最后一点,如果您使用的是Apache XML Security。您需要先调用,org.apache.xml.security.Init.init()然后再尝试使用Canonicalizer,例如从static初始化程序块中进行调用。如果不这样做,则在尝试规范化时会出现异常(我认为是NPE)。