使用 C# 将签名的 SOAP 消息创建为字符串
本文关键字:消息 创建 字符串 SOAP 使用 | 更新日期: 2023-09-27 18:33:45
我需要调用Web服务,我必须使用C#发送下面的此类soap请求。必须对 SoapBody 和 TimeStamp 进行签名。
<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:web="http://xyzt.com/">
<soap:Header>
<wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
<wsse:BinarySecurityToken EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary" ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509PKIPathv1" wsu:Id="X509-F4AF9673207AC5E0B614180667985061">MIIFsDCCBawwggSUoAMCAQICBgCaWhnEajANBgkqhkiG9w0BAQsFADBcMQswCQYDVQQGEwJUUjFNMEsGA1UEAwxETWFsaSBNw7xow7xyIEVsZWt0cm9uaWsgU2VydGlmaWthIEhpem1ldCBTYcSfbGF5xLFjxLFzxLEgLSBTw7xyw7xtIDEwHhcNMT</wsse:BinarySecurityToken>
<ds:Signature Id="SIG-3" xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:SignedInfo>
<ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#">
<ec:InclusiveNamespaces PrefixList="soap web" xmlns:ec="http://www.w3.org/2001/10/xml-exc-c14n#"/>
</ds:CanonicalizationMethod>
<ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
<ds:Reference URI="#id-2">
<ds:Transforms>
<ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#">
<ec:InclusiveNamespaces PrefixList="web" xmlns:ec="http://www.w3.org/2001/10/xml-exc-c14n#"/>
</ds:Transform>
</ds:Transforms>
<ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
<ds:DigestValue>IZVrIpPCxiPcvyVOVv/d4nRPZWM=</ds:DigestValue>
</ds:Reference>
<ds:Reference URI="#TS-1">
<ds:Transforms>
<ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#">
<ec:InclusiveNamespaces PrefixList="wsse soap web" xmlns:ec="http://www.w3.org/2001/10/xml-exc-c14n#"/>
</ds:Transform>
</ds:Transforms>
<ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
<ds:DigestValue>fltghgDztDtuVQX7y4t0ZJxAnxE=</ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue>IOVXxBTp053aNJMbQj+VTiBblZ63peyJ1vWazKmEWNxN7RaeFfKELoxede8xQEqzSaB/u8exC7LLGYiEdChboVCf9liLMN4MmNj5JR6gfDrsL3azThf5hxLQ+WIE20PRoU6ozpp20zC1IaO3IU4ZaRLw</ds:SignatureValue>
<ds:KeyInfo Id="KI-F4AF9673207AC5E0B614180667986422">
<wsse:SecurityTokenReference wsse11:TokenType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509PKIPathv1" wsu:Id="STR-F4AF9673207AC5E0B614180667986643" xmlns:wsse11="http://docs.oasis-open.org/wss/oasis-wss-wssecurity-secext-1.1.xsd">
<wsse:Reference URI="#X509-F4AF9673207AC5E0B614180667985061" ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509PKIPathv1"/>
</wsse:SecurityTokenReference>
</ds:KeyInfo>
</ds:Signature>
<wsu:Timestamp wsu:Id="TS-1">
<wsu:Created>2014-12-08T21:26:36.191Z</wsu:Created>
<wsu:Expires>2014-12-08T21:36:36.191Z</wsu:Expires>
</wsu:Timestamp>
</wsse:Security>
</soap:Header>
<soap:Body wsu:Id="id-2" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
<web:getStatus>
<itemID>1234567</itemID>
</web:getStatus>
</soap:Body>
</soap:Envelope>
我创建了此 soap 请求,并使用 WCF 客户端自定义绑定和具有私钥证书的 .pfx 文件获得了良好的响应。
有关对 SOAP 消息进行签名的大多数示例都使用证书存储或 pfx 文件中的证书。但是在我的方案中,用户的证书(带有私钥(存储在智能卡上,该证书私钥无法导出。因此,在这种情况下,我不能使用 WCF 自定义绑定,或者我不能使用 SignedXml 类对 SOAP 消息进行签名,因为当我尝试以编程方式获取证书时,私钥丢失了。(我也通过使用NCryptoki - PKCS包装器获得了私钥,但此私钥类型与RSA不同,我无法为WCF客户端或SignedXmlClass私钥设置。
因此,我尝试将此 SOAP 消息创建为字符串,并使用智能卡手动创建摘要值、二进制安全令牌和签名值。
我可以计算二进制安全令牌值为:
var certificate = GetX5092Certificate(); // X5092 certificate on smart card without private key
string binarySecToken= Convert.ToBase64String(certificate.RawData);
我也有一些代码来计算摘要值,如下所示:
byte[] dataToHashTS = Encoding.UTF8.GetBytes(TimeStampReference.OuterXml);
XmlDsigExcC14NTransform transformDataTS = new XmlDsigExcC14NTransform("wsse soap web");
transformDataTS.LoadInput(new MemoryStream(dataToHashTS));
byte[] bDigestDataTS = transformDataTS.GetDigestedOutput(SHA1Managed.Create());
string sDigestDataTS = Convert.ToBase64String(bDigestDataTS); //timestamp digest
我不确定如果我正确计算摘要值?
要计算签名值,我想我需要获取签名信息部分的哈希值。我有使用智能卡对内容(字节数组(进行签名的方法。那么如何将签名信息内容发送到此方法呢?我的意思是将 SignedInfo 块的哈希值作为字符串是否足够?或者我已经将 SignedInfo 作为 XmlElement,然后像我计算摘要值一样转换 + 哈希?
任何帮助将不胜感激。谢谢。
- 对于 DigestValue,您需要像这样规范化字符串:
<u:Timestamp u:Id="_0" xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"> <u:Created>2016-06-14T22:56:10.896Z</u:Created> <u:Expires>2016-06-14T23:01:10.896Z</u:Expires> </u:Timestamp>
因此,您可以将该字符串作为参数放在这里:
private string CanonicalizeDsig(string input)
{
XmlDocument doc = new XmlDocument();
doc.PreserveWhitespace = false;
try
{
doc.LoadXml(input);
XmlDsigC14NTransform trans = new XmlDsigC14NTransform();
trans.LoadInput(doc);
String c14NInput = new StreamReader((Stream)trans.GetOutput(typeof(Stream))).ReadToEnd();
return c14NInput;
}
catch (Exception ex)
{
return String.Empty;
}
}
规范化后,您现在可以计算哈希:(我的是 SHA1 示例(。 所以把上面方法的返回值放在这个参数上。得到类似 JCMdwz5g8iq05Lj6tjfDOxKqT4k=
private string ComputeHashSHA1(string input)
{
try
{
SHA1CryptoServiceProvider sha1Hasher = new SHA1CryptoServiceProvider();
byte[] hashedDataBytes = sha1Hasher.ComputeHash(Encoding.UTF8.GetBytes(input));
string digestValue = Convert.ToBase64String(hashedDataBytes);
return digestValue;
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
return String.Empty;
}
}
- 签名值是一个棘手的值,我只能介绍一个特定的例子。检查服务的 WSDL 它是否有如下所示的策略。
<sp:Trust10> <wsp:Policy> <sp:MustSupportIssuedTokens/> <sp:RequireClientEntropy/> <sp:RequireServerEntropy/> </wsp:Policy>
这意味着您需要组合客户端熵(这是您的密钥 - 您在获取令牌请求时发送到服务器的任何基于 64 的字符串(和服务器熵(返回基数 64 密钥(
您可以使用 Microsoft.IdentityModel dll 将它们组合在一起,其中有一个 KeyGenerator 对象。
您的输入将是这样的,它还需要使用 DsigExcC14N 进行规范化:
<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#"> <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>JCMdwz5g8iq05Lj6tjfDOxKqT4k=</DigestValue> </Reference> </SignedInfo>
这是规范化:
private string CanonicalizeExc(string input)
{
XmlDocument doc = new XmlDocument();
doc.PreserveWhitespace = false;
try
{
doc.LoadXml(input);
XmlDsigExcC14NTransform trans = new XmlDsigExcC14NTransform();
trans.LoadInput(doc);
String c14NInput = new StreamReader((Stream)trans.GetOutput(typeof(Stream))).ReadToEnd();
return c14NInput;
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
return String.Empty;
}
}
然后,以下是获取签名值的方法:
private string ComputeHMACSHA1_PSHA(string input, string serversecret, string clientsecret)
{
try
{
byte[] signedInfoBytes = Encoding.UTF8.GetBytes(input);
byte[] binarySecretBytesServer = Convert.FromBase64String(serversecret);
byte[] binarySecretBytesClient = Convert.FromBase64String(clientsecret);
byte[] key = KeyGenerator.ComputeCombinedKey(binarySecretBytesClient, binarySecretBytesServer, 256);
HMACSHA1 hmac = new HMACSHA1(key);
hmac.Initialize();
byte[] hmacHash = hmac.ComputeHash(signedInfoBytes);
string signatureValue = Convert.ToBase64String(hmacHash);
return signatureValue;
}
catch (Exception ex)
{
return string.Empty;
}
}
它会给你这样的东西。 kykmlowWIW4TXRcCi46OfZPUBKQ=