2016-04-08 145 views
2

我有一個基於Java(基於JAX-WS)的SOAP客戶端,我試圖與基於WCF的(第三方)服務器交談。我發現sentiment expressed here是相當準確的。但目標依然存在。我可以從服務器中哄騙一個有效的「安全上下文令牌」,但是在消息簽名問題上我掛了(我相信)。SOAP,WCF和消息簽名

服務器似乎希望使用客戶端/服務器密鑰(PSHA1算法)使用hmac-sha1認證碼對消息進行簽名。很公平。然而,JAX-WS似乎想要使用rsa-sha1和X509證書籤署出站消息(服務器不喜歡),並且似乎只使用hmac-sha1如果提供了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> 

,以及服務器發回的令牌是這樣的:

<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> 

我結合了客戶端並使用PSHA1服務器BinarySecret密鑰如下:

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; 

} 

我然後使用該密鑰來計算一個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); 

然後進入出站請求(以及大量其他樣板文件),如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> 

這導致未來從服務器返回的響應「爲消息驗證安全性時發生錯誤」。

使用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> 

的主要區別是:

  • 我省略了Timestamp元素(雖然我試過,包括它,似乎沒有任何區別) 。我不知道DigestValue是如何計算的。

所以,畢竟這一點,我想主要問題是:

什麼是簽訂出站郵件的實際算法?如在,如果我有:

<Envelope> 
    <Header> 
     HHH... 
    </Header> 
    <Body> 
     BBB... 
    </Body> 
</Envelope> 

...我的意思是計算<Envelope>...</Envelope>(整個事情)或<Body>...</Body>,甚至是BBB...部分的簽名值?如果我打算使用整個事物,那麼我如何協調這一點,即將簽名信息添加到標題時會更改在計算簽名時用作輸入的內容?

是否有一種更直接的方式讓JAX-WS使用我忽略的必需簽署約定來生成請求?

然後也有一些小的獎金問題:

  1. 是否有針對一個既定的標準,其中爲了在使用PSHA1結合他們我通過客戶端和服務器BinarySecret值?

  2. TimestampSignedInfo/Reference是否有意義,如果是這樣,那麼計算DigestValue的正確方法是什麼?

回答

3

經過一番研究和一些試驗和錯誤之後,我設法找到了一個可行的解決方案。我會用獎金問題第一次啓動:

  1. 我沒有發現任何正式文件,而且我碰到每一個參考實現和代碼示例總是先通過客戶端密鑰,而這也是什麼服務器(Microsoft IIS v8.5)期望。所以這似乎是標準,即使它不是正式的。

  2. 是的,TimestampReference值是重大的,並非常緊密地與主要問題聯繫起來。

那麼如果您必須在Java中使用JAX-WS手動執行操作,那麼在出站SOAP消息中籤署事物的實際算法是什麼?

This reference是一個有用的開始,並且應該給你一個關於SOAP世界中的架構已經變得如何的好主意。其中一些內容非常詳細地描述,以便引導。例如:

3.2.2簽名驗證

  1. KeyInfo,或者從外部源獲取的密鑰信息。
  2. 獲取SignatureMethod的規範形式使用 CanonicalizationMethod和使用的結果(和先前獲得 KeyInfo)確認SignatureValueSignedInfo元件。

如果您KeyInfoSecurityTokenReferenceSecurityContextToken實際上並不包含任何關鍵數據本身,而你的SignatureMethodAlgorithm="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> 

...你想開始與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); 

這會給你這樣的事情(換行不規範,不好意思):

<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> 

現在你需要計算的DigestValue那個字符串。我們的Reference元素中的DigestMethod元素告訴我們這應該是SHA1哈希(base64編碼)。所以,簡單地說:

MessageDigest sha1 = MessageDigest.getInstance("SHA-1"); 
String canonDigestValue = Base64.encodeBytes(sha1.digest(canonTimestamp)); 

,你得到進入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); 

哪些應該淨你是這樣的:

<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> 

...然後你簽署整個事情根據提名的SignatureMethod算法,在我們的例子中是HmacSHA1

Mac mac = Mac.getInstance("HmacSHA1"); 
SecretKeySpec key = new SecretKeySpec(getSharedKey(), "HmacSHA1"); 
mac.init(key); 

String signature = Base64.encodeBytes(mac.doFinal(bytesToSign)); 

...其中getSharedKey()在此實例中返回使用客戶端和服務器在初始RequestSecurityToken交換期間發送的值BinarySecret導出的密鑰。如:

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; 
} 

不管怎麼說,在這一點上,你應該有一個簽名值,並且可以附加到Security頭出站郵件中,如:

SOAPElement sigValue = sigElem.addChildElement(new QName("SignatureValue")); 
sigValue.addTextNode(signature); 

如果一切順利的話,該消息現在已成功簽署並且服務器的質量可以接受。

儘管我注意到了一個最後的警告,那就是Timestamp值需要在服務器的時區(在本例中是UTC)中生成,否則它會因時間戳而拒絕請求從未來或已經過期。一個簡單的問題,可以通過在UNIX紀元時間戳上標準化來解決。但由於某些原因,他們以「yyyy-mm-dd'T'hh:mm:ss.msec'Z'」代替。去搞清楚。

我希望這有助於下一個不幸的靈魂,他必須嘗試讓Java使用SOAP/XML與.NET交談。

如果您使用的是Apache XML Security,還有一個最後提示。在嘗試使用Canonicalizer之前,您需要撥打org.apache.xml.security.Init.init(),例如從static初始化程序塊中。如果你不這樣做,當你嘗試規範化時,你會得到一個異常(我認爲是NPE)。

+1

實際上,作爲'P_SHA1'參數的熵的順序由WS-Trust正式指定。在你的特定情況下,'ComputedKey' URI是'http:// schemas.xmlsoap.org/ws/2005/02/trust/CK/PSHA1',它表示2005年2月WS-Trust的修訂版(http:// specs .xmlsoap.org/WS/2005/02 /信賴/ WS-Trust.pdf)。根據規範:「使用來自TLS規範的P_SHA1計算密鑰以使用來自雙方的熵生成比特流。確切的形式是:key = P_SHA1(Ent_REQ,Ent_RES)' –

+0

我希望我可以upvote這1000倍。你在這裏跟隨你的發現是一個英雄。 – Jonathan