我意识到这个问题涉及旧技术。我正在调用供应商系统,无法更改服务。我们需要调用XML / SOAP WS,然后对请求进行签名。 10年前,我会使用像Web Services Enhancements(WSE)3.0这样的东西,然后继续前进。就像今天一样,我仍然坚持在.Net Core(.Net Standard 2.0)应用程序中做什么。
我愿意使用多种解决方案,包括商业解决方案。我看着奇尔卡特,但似乎我们放弃了太多而无法使用它。
然而,他们确实有一个很好的例子。
请求如下:
<?xml version="1.0" encoding="UTF8"?>
<SOAP-ENV:Envelope xmlns:SOAPENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Header>
<wsse:Security xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
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"
xmlns:xenc="http://www.w3.org/2001/04/xmlenc#" SOAP-ENV:mustUnderstand="1">
<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#X509"
wsu:Id="x509cert00">BASE64_CERT</wsse:BinarySecurityToken>
</wsse:Security>
</SOAP-ENV:Header>
<SOAP-ENV:Body xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="TheBody">
<getVersion xmlns="http://msgsec.wssecfvt.ws.ibm.com"/>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
我们希望能够使用证书并签名如下:
<?xml version="1.0" encoding="UTF8" ?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Header>
<wsse:Security xmlns:ds="http://www.w3.org/2000/09/xmldsig#" 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" xmlns:xenc="http://www.w3.org/2001/04/xmlenc#" SOAP-ENV:mustUnderstand="1">
<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#X509" wsu:Id="x509cert00">MIIDgzCCAmugAwIBAgIBADANBgkqhkiG9w0BAQUFADBcMRUwEwYDVQQDDAxUZXN0
IENvbXBhbnkxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYD
VQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwHhcNMTcwOTEzMDA1NTM1WhcN
MTgwOTEzMDA1NTM1WjBcMRUwEwYDVQQDDAxUZXN0IENvbXBhbnkxCzAJBgNVBAYT
AkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRn
aXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDiWRKl
x+88u4SKZnfCMraqMsfJCs6tcz3EjMYTWmRKhhUOE9pDkvZfv0mgF7pNHsTKvFRt
oVnEVQaZC5TlHNOGa2QWit9YuruWjW8VSaU4s9gR1/Cg9/Zc8Z0yUEDpsaVnwuoA
RpVzvzoRzPmTNpMNEcQ07aBjHP7OJrwyvcdqQA1BbfDVMmRmw1d+/i8tyR3cTyzl
/3TismN5nrmhGh/ZF75FFA/xDN7PbVYDPowiFnEVHiBrYh2mFTabRUnb7K4oLx+d
1L5x3Az299F/HYZlBenXpJLtnCL3+HY6qsGXVbzKjlKNqbXsmlzVkChu093weN/q
UvWO2883cEiXmdqxAgMBAAGjUDBOMB0GA1UdDgQWBBRsMy2bxsCKYyUYtTYz/zZb
z7Le0zAfBgNVHSMEGDAWgBRsMy2bxsCKYyUYtTYz/zZbz7Le0zAMBgNVHRMEBTAD
AQH/MA0GCSqGSIb3DQEBBQUAA4IBAQBnFQ+Sc3s8y79DTsA7CvvAFeG/zvWQiu8y
UM5LO1QcWeQQj29GMThqrY21dNfkynl7mZUMEeXKvwwzweFCc2odiUPHxoV1G4FE
tzNaZ8Ap9jye78YQ8SB8NPQwC7ovecfSqNflT4NMAThSuxpGp8Ugf7a24LXozLzL
bCRvG9sLGyRneZbfU8B43ELRLCkjzWR32N7D2pmKk4CEMiW0ScphU1JEHaimneMa
TFc63hNzKpuj7+BGv4ZuvB1j/Mbmz53PGgFKnGQHPb2TIvMxyB+lML5vE0Bm8YWt
P8DNyx11CCCdBdMWfeta6MjmmqcV5/YEq92c5O2Ql94tWFNLR6wQ</wsse:BinarySecurityToken>
<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:SignedInfo>
<ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#">
<InclusiveNamespaces xmlns="http://www.w3.org/2001/10/xml-exc-c14n#" PrefixList="wsse SOAP-ENV" />
</ds:CanonicalizationMethod>
<ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256" />
<ds:Reference URI="#TheBody">
<ds:Transforms>
<ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
</ds:Transforms>
<ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
<ds:DigestValue>VhsSnaEAFsY0OYegKQh99v9csXg=</ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue>Ynp3H4rtzpXIh4TaVxkpEkS1bMCCu672aeCzUOzheNNfnpmLsCZz3+zQjMBbchPggCayC5ihpEdhRe3XvPXjPXXAgxDP4mic091QPmjHlmUcu8yqRKfxnPtD35nqaxDtCYw+jGIzj+ch094vA4RPCfY8JQnb1mpy1ZjjsMW8741CIh1epbsd/0bZt6tfINUQ37seg07yvLbCJZ/Zf+h8FlFryQk6lHTTeZl/GfQ9NlDBcShby3x8Hc1KwW++zFqEA7G783R9AYPYn3fWTOBhYk5gkgFc+HaPRLR/L0Bp7ZPbmOR/iZQ+HK4W672tTdN/R2GdN7/deV7QTp2DYK1Z8w==</ds:SignatureValue>
<ds:KeyInfo>
<wsse:SecurityTokenReference>
<wsse:Reference URI="#x509cert00" ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509" />
</wsse:SecurityTokenReference>
</ds:KeyInfo>
</ds:Signature>
</wsse:Security>
</SOAP-ENV:Header>
<SOAP-ENV:Body xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="TheBody">
<getVersion xmlns="http://msgsec.wssecfvt.ws.ibm.com" />
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
答案 0 :(得分:7)
我通过滚动我们自己的肥皂信封,签署它们并在HttpClient上铺设它来解决这个问题。 .NET Core中的WCF无法为我们获得与我们的第三方服务的各种怪癖有关的结果。
这里是代码,它应该很容易改变你的要求:
// ...
private static HttpClient Client = new HttpClient(); // https://aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong/
// ...
Uri uri = new Uri("https://thirdparty.com/service.svc");
X509Certificate2 cert = // from some store etc
var envelope = BuildEnvelope(cert);
using (HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, uri))
{
request.Content = new StringContent(envelope, Encoding.UTF8, "application/soap+xml");
using (HttpResponseMessage response = Client.SendAsync(request).Result)
{
if (response.IsSuccessStatusCode)
{
response.Content.ReadAsStringAsync().ContinueWith(task =>
{
string thirdparty_envelope = task.Result;
XElement thirdparty_root = XElement.Parse(thirdparty_envelope);
// etc
}, TaskContinuationOptions.ExecuteSynchronously);
}
}
}
private string BuildEnvelope(X509Certificate2 certificate)
{
string envelope = null;
// note - lots of bits here specific to my thirdparty
string cert_id = string.Format("uuid-{0}-1", Guid.NewGuid().ToString());
using (var stream = new MemoryStream())
{
Encoding utf8 = new UTF8Encoding(false); // omit BOM
using (var writer = new XmlTextWriter(stream, utf8))
{
// timestamp
DateTime dt = DateTime.UtcNow;
string now = dt.ToString("o").Substring(0, 23) + "Z";
string plus5 = dt.AddMinutes(5).ToString("o").Substring(0, 23) + "Z";
// soap envelope
// <s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://www.w3.org/2005/08/addressing" xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
writer.WriteStartDocument();
writer.WriteStartElement("s", "Envelope", "http://www.w3.org/2003/05/soap-envelope");
writer.WriteAttributeString("xmlns", "a", null, "http://www.w3.org/2005/08/addressing");
writer.WriteAttributeString("xmlns", "u", null, "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd");
writer.WriteStartElement("s", "Header", null);
/////////////////
// saml guts //
/////////////////
//<a:Action s:mustUnderstand="1">http://docs.oasis-open.org/ws-sx/ws-trust/200512/RST/Issue</a:Action>
writer.WriteStartElement("a", "Action", null);
writer.WriteAttributeString("s", "mustUnderstand", null, "1");
writer.WriteString("http://docs.oasis-open.org/ws-sx/ws-trust/200512/RST/Issue");
writer.WriteEndElement(); //Action
//<a:MessageID>urn:uuid:0cc426dd-35bf-4c8b-a737-7e2ae94bd44d</a:MessageID>
string msg_id = string.Format("urn:uuid:{0}", Guid.NewGuid().ToString());
writer.WriteStartElement("a", "MessageID", null);
writer.WriteString(msg_id);
writer.WriteEndElement(); //MessageID
//<a:ReplyTo><a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address></a:ReplyTo>
writer.WriteStartElement("a", "ReplyTo", null);
writer.WriteStartElement("a", "Address", null);
writer.WriteString("http://www.w3.org/2005/08/addressing/anonymous");
writer.WriteEndElement(); //Address
writer.WriteEndElement(); //ReplyTo
writer.WriteStartElement("a", "To", "http://www.w3.org/2005/08/addressing");
writer.WriteAttributeString("s", "mustUnderstand", null, "1");
writer.WriteAttributeString("u", "Id", null, "_1");
writer.WriteString("https://thirdparty.com/service.svc");
writer.WriteEndElement(); //To
//<o:Security xmlns:o="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" s:mustUnderstand="1">
writer.WriteStartElement("o", "Security", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd");
writer.WriteAttributeString("s", "mustUnderstand", null, "1");
//<u:Timestamp u:Id="_0">
writer.WriteStartElement("u", "Timestamp", null);
writer.WriteAttributeString("u", "Id", null, "_0");
//<u:Created>2018-02-08T15:03:13.115Z</u:Created>
writer.WriteElementString("u", "Created", null, now);
//<u:Expires>2018-02-08T15:08:13.115Z</u:Expires>
writer.WriteElementString("u", "Expires", null, plus5);
writer.WriteEndElement(); //Timestamp
writer.WriteStartElement("o", "BinarySecurityToken", null);
writer.WriteAttributeString("u", "Id", null, cert_id);
writer.WriteAttributeString("ValueType", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3");
writer.WriteAttributeString("EncodingType", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary");
byte[] rawData = certificate.GetRawCertData();
writer.WriteBase64(rawData, 0, rawData.Length);
writer.WriteEndElement(); //BinarySecurityToken
writer.WriteEndElement(); //Security
writer.WriteEndElement(); //Header
//<s:Body xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
writer.WriteStartElement("s", "Body", "http://www.w3.org/2003/05/soap-envelope");
writer.WriteAttributeString("xmlns", "xsd", null, "http://www.w3.org/2001/XMLSchema");
writer.WriteAttributeString("xmlns", "xsi", null, "http://www.w3.org/2001/XMLSchema-instance");
// your 3rd-party soap payload goes here
writer.WriteStartElement("???", "http://docs.oasis-open.org/ws-sx/ws-trust/200512");
// ...
writer.WriteEndElement(); //
writer.WriteEndElement(); // Body
writer.WriteEndElement(); //Envelope
}
// signing pass
var signable = Encoding.UTF8.GetString(stream.ToArray());
XmlDocument doc = new XmlDocument();
doc.LoadXml(signable);
// see https://stackoverflow.com/a/6467877
var signedXml = new SignedXmlWithId(doc);
var key = certificate.GetRSAPrivateKey();
signedXml.SigningKey = key;
// these values may not be supported by your 3rd party - they may use e.g. SHA256 miniumum
signedXml.SignedInfo.CanonicalizationMethod = SignedXml.XmlDsigExcC14NTransformUrl;
signedXml.SignedInfo.SignatureMethod = SignedXml.XmlDsigRSASHA1Url;
//
KeyInfo keyInfo = new KeyInfo();
KeyInfoX509Data x509data = new KeyInfoX509Data(certificate);
keyInfo.AddClause(x509data);
signedXml.KeyInfo = keyInfo;
// 3rd party wants us to only sign the timestamp fragment- ymmv
Reference reference0 = new Reference();
reference0.Uri = "#_0";
var t0 = new XmlDsigExcC14NTransform();
reference0.AddTransform(t0);
reference0.DigestMethod = SignedXml.XmlDsigSHA1Url;
signedXml.AddReference(reference0);
// etc
// get the sig fragment
signedXml.ComputeSignature();
XmlElement xmlDigitalSignature = signedXml.GetXml();
// modify the fragment so it points at BinarySecurityToken instead
XmlNode info = null;
for (int i = 0; i < xmlDigitalSignature.ChildNodes.Count; i++)
{
var node = xmlDigitalSignature.ChildNodes[i];
if (node.Name == "KeyInfo")
{
info = node;
break;
}
}
info.RemoveAll();
//
XmlElement securityTokenReference = doc.CreateElement("o", "SecurityTokenReference", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd");
XmlElement reference = doc.CreateElement("o", "Reference", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd");
reference.SetAttribute("ValueType", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3");
// cert id
reference.SetAttribute("URI", "#" + cert_id);
securityTokenReference.AppendChild(reference);
info.AppendChild(securityTokenReference);
var nsmgr = new XmlNamespaceManager(doc.NameTable);
nsmgr.AddNamespace("o", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd");
nsmgr.AddNamespace("s", "http://www.w3.org/2003/05/soap-envelope");
var security_node = doc.SelectSingleNode("/s:Envelope/s:Header/o:Security", nsmgr);
security_node.AppendChild(xmlDigitalSignature);
envelope = doc.OuterXml;
}
return envelope;
}