Ulf*_*sen 4 wcf ws-security soap cryptography .net-core
我想从带有 C# 的 .Net Core 3.1 连接到需要我根据 WS-Security WS-Policy 2004/09签署Soap 1.1正文的 Web 服务。
这是政策要求的文字描述:
AsymmetricBindingAssertion表示使用非对称加密,其中签名必须使用请求者的证书(X509v3)。该InitiatorToken字段指示请求令牌必须是 X509v3 令牌并且它必须包含在所有请求消息中,而该RecipientToken字段指示响应令牌必须是 X509v3 但不会包含在任何消息中。为了识别令牌,将使用 keyIdentifier – 由MustSupportKeyRefIdentitier字段指定 。Timestamp还需要包含在内以规避重放攻击,因此 - 默认情况下 - 这也是签名的。该OnlySignEntireHeadersAndBody字段规定只允许对整个标题或正文进行签名 - 以减轻 XML 签名包装。最后,我们只规定 SOAP Envelope 的 Bodyelement 需要签名。
我添加了一个与Microsoft WCF Web Reference Provider的连接服务在 Visual Studio 2019 中并且所有实体都添加到 Reference.cs 中。我可以在没有 WS-Policy 要求的情况下很好地连接到 SoapUI 中服务的模拟版本。我已经验证了证书和东西,我就是不知道如何在肥皂体上签名。
我无法使用,WSHttpBinding因为它生成 Soap 1.2 并且我尝试使用的服务只理解Soap 1.1。
我用 尝试了不同的方法CustomBinding,但似乎它总是烧毁使用AsymmetricSecurityBindingElement.Net Core 中不存在的。
我们在 JavaScript 中有一个实现,可以产生我想要的:
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:tns="xx"
xmlns:cmn="xxx">
<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" soap: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#X509v3" wsu:Id="x509-uidxxx">MIIE...base64=</wsse:BinarySecurityToken>
<Timestamp xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" Id="_1">
<Created>2019-09-21T12:33:36Z</Created>
<Expires>2019-09-21T12:43:36Z</Expires>
</Timestamp>
<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#rsa-sha1" />
<Reference URI="#_0">
<Transforms>
<Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" />
<Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
</Transforms>
<DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
<DigestValue>sc...base64=</DigestValue>
</Reference>
<Reference URI="#_1">
<Transforms>
<Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" />
<Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
</Transforms>
<DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
<DigestValue>5J...base64=</DigestValue>
</Reference>
</SignedInfo>
<SignatureValue>pa...base64=</SignatureValue>
<KeyInfo>
<wsse:SecurityTokenReference>
<wsse:Reference URI="#x509-uidxxx" ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3"/>
</wsse:SecurityTokenReference>
</KeyInfo>
</Signature>
</wsse:Security>
</soap:Header>
<soap:Body Id="_0">
// Lots of stuff
</soap:Body>
</soap:Envelope>
Run Code Online (Sandbox Code Playgroud)
有谁知道是否可以在 .Net Core 3.1 中使用 C# 使用非对称加密对肥皂体进行签名并生成 Soap 1.1?
小智 7
这是一个迟到的响应,但我有一个类似的要求,即使用 .net core 3.1 调用需要单向 TLS 和 ws-security 的soap 端点。
首先,添加安全标头非常简单。下面是一个 MessageHeader 实现,它添加了带有时间戳的 Security 标头。类 (WsSecurityHeader) 的一个实例用于如下所示的消息检查器。您也可以将此标头烘焙到消息检查器本身中,而不是在消息检查器中使用 WsSecurityHeader,因为无论如何消息检查器都会重写整个soap 消息。
using System;
using System.ServiceModel.Channels;
using System.Xml;
namespace MyClient.WsSecurity
{
/// <summary>
/// Adds a WS-Security header to the message, with a Timestamp. The header does not include the message signature,
/// as the framework provides no mechanism to access the message body inside of a MessageHeader implementation.
/// </summary>
public sealed class WsSecurityHeader : MessageHeader
{
public override bool MustUnderstand => true;
public override string Name => "Security";
public const string SoapEnvelopeNamespace = "http://schemas.xmlsoap.org/soap/envelope/";
public const string WsseUtilityNamespaceUrl = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd";
public const string WsseNamespace = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd";
public override string Namespace => WsseNamespace;
protected override void OnWriteStartHeader(XmlDictionaryWriter writer, MessageVersion messageVersion)
{
writer.WriteStartElement("wsse", Name, Namespace);
writer.WriteAttributeString("s", "mustUnderstand", SoapEnvelopeNamespace, "1");
writer.WriteXmlnsAttribute("wsse", Namespace);
writer.WriteXmlnsAttribute("wsu", WsseUtilityNamespaceUrl);
}
protected override void OnWriteHeaderContents(XmlDictionaryWriter writer, MessageVersion messageVersion)
{
// Timestamp
writer.WriteStartElement("wsu", "Timestamp", WsseUtilityNamespaceUrl);
writer.WriteAttributeString("wsu", "Id", WsseUtilityNamespaceUrl, "ws-security-timestamp");
writer.WriteStartElement("wsu", "Created", WsseUtilityNamespaceUrl);
writer.WriteValue(DateTimeOffset.Now.ToString("o"));
writer.WriteEndElement();
writer.WriteStartElement("wsu", "Expires", WsseUtilityNamespaceUrl);
writer.WriteValue(DateTimeOffset.Now.AddMinutes(120).ToString("o"));
writer.WriteEndElement();
writer.WriteEndElement(); // Timestamp
}
}
}
Run Code Online (Sandbox Code Playgroud)
为了对消息的 Body 元素进行签名,您需要实现一个消息检查器。消息检查器让我们可以访问整个消息,包括正文和标题。我们需要修改两者。下面的消息检查器添加了我们的 Security 标头(WsSecurityHeader 类,如前所示)。我们修改消息的 Body 元素以添加在安全标头中使用的 Id 属性,以标识我们正在签名的元素。然后,我们通过对 Body 元素进行签名来创建签名 xml 元素,并将签名 xml 元素添加到标头中。然后从我们的 XmlDocument 重构整个soap 消息。
using System.Security.Cryptography.X509Certificates;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Dispatcher;
using System.Xml;
using System.Security.Cryptography.Xml;
using System.IO;
namespace MyClient.WsSecurity
{
/// <summary>
/// Adds a ws-security x509 xml body signature to the outgoing message header. It's annoying that Microsoft contributed to this
/// standard but it's not supported in .NET core.
/// </summary>
public sealed class WsSecurityMessageInspector : IClientMessageInspector
{
public const string BodyIdentifier = "ws-security-body-id"; // This can be whatever xml Id attribute value value we want
public X509Certificate2 X509Certificate { get; }
public WsSecurityMessageInspector() { }
public WsSecurityMessageInspector(X509Certificate2 cert)
{
X509Certificate = cert;
}
public void AfterReceiveReply(ref Message reply, object correlationState) { }
public object BeforeSendRequest(ref Message request, IClientChannel channel)
{
// Add the ws-Security header
request.Headers.Add(new WsSecurityHeader());
// Get the entire message as an xml doc, so we can sign the body.
var xml = GetMessageAsString(request);
XmlDocument doc = new XmlDocument();
doc.PreserveWhitespace = false;
doc.LoadXml(xml);
XmlNamespaceManager nsmgr = new XmlNamespaceManager(doc.NameTable);
nsmgr.AddNamespace("soapenv", WsSecurityHeader.SoapEnvelopeNamespace);
nsmgr.AddNamespace("wsse", WsSecurityHeader.WsseNamespace);
// The Body is the element we want to sign.
var body = doc.SelectSingleNode("//soapenv:Body", nsmgr) as XmlElement;
// Add the Id attribute to the Body, for the Reference element URI..
var id = doc.CreateAttribute("wsu", "Id", WsSecurityHeader.WsseUtilityNamespaceUrl);
id.Value = BodyIdentifier;
body.Attributes.Append(id);
// Here we do not adopt the SecurityTokenReference recommendation in the KeyInfo
// section because it is not defined in the XML Signature standard. In lieu of the SecurityTokenReference, we
// add KeyInfoX509Data directly to the KeyInfo node, in accordance with the XML Signature rfc (rfc3075). The SignedXml
// class does not seem to support the SecurityTokenReference, and it's not required.
var signedXml = new SignedXmlWithUriFix(doc);
signedXml.SignedInfo.SignatureMethod = SignedXml.XmlDsigRSASHA1Url;
// This cannonicalization method is "recommended" in the ws-security standard, but seems to be required, at least
// by Data Power.
signedXml.SignedInfo.CanonicalizationMethod = SignedXml.XmlDsigExcC14NTransformUrl;
// Add the X509 certificate info to the KeyInfo section
var keyInfo = new KeyInfo();
var keyInfoData = new KeyInfoX509Data();
keyInfoData.AddIssuerSerial(X509Certificate.IssuerName.Name, X509Certificate.SerialNumber);
keyInfo.AddClause(keyInfoData);
signedXml.SigningKey = X509Certificate.PrivateKey;
signedXml.KeyInfo = keyInfo;
// Add the reference to the SignedXml object.
Reference reference = new Reference($"#{BodyIdentifier}");
reference.DigestMethod = SignedXml.XmlDsigSHA1Url;
signedXml.AddReference(reference);
// Compute the signature.
signedXml.ComputeSignature();
// Get the Signature element
XmlElement xmlDigitalSignature = signedXml.GetXml();
// Append the Signature element to the XML document's Security header.
XmlNode header = doc.SelectSingleNode("//soapenv:Envelope/soapenv:Header/wsse:Security", nsmgr);
header.AppendChild(doc.ImportNode(xmlDigitalSignature, true));
// Generate a new message from our XmlDocument. We have to be careful here so that the XML is serialized
// with the same whitespace handling (via XmlWriter) as the signed xml (via XmlDocument). A bit sketchy.
var newMessage = CreateMessageFromXmlDocument(request, doc);
request = newMessage;
return null;
}
private Message CreateMessageFromXmlDocument(Message message, XmlDocument doc)
{
MemoryStream ms = new MemoryStream();
using (XmlWriter xmlWriter = XmlWriter.Create(ms, new XmlWriterSettings { OmitXmlDeclaration = true, Indent = false }))
{
doc.WriteTo(xmlWriter);
xmlWriter.Flush();
xmlWriter.Close();
ms.Position = 0;
}
XmlDictionaryReader xdr = XmlDictionaryReader.CreateTextReader(ms, new XmlDictionaryReaderQuotas());
var newMessage = Message.CreateMessage(xdr, int.MaxValue, message.Version);
newMessage.Properties.CopyProperties(message.Properties);
return newMessage;
}
private string GetMessageAsString(Message msg)
{
using (var sw = new StringWriter())
using (var xw = new XmlTextWriter(sw))
{
msg.WriteMessage(xw);
return sw.ToString();
}
}
/// <summary>
/// The SignedXml class chokes on a URI prefixed with "#", so we override the GetIdElement here. The #
/// is allowed by the XML Signature rfc (rfc3075), so this is really a bug fix for SignedXml.
/// </summary>
public class SignedXmlWithUriFix : SignedXml
{
public SignedXmlWithUriFix(XmlDocument xml) : base(xml)
{
}
public SignedXmlWithUriFix(XmlElement xmlElement)
: base(xmlElement)
{
}
public override XmlElement GetIdElement(XmlDocument doc, string id)
{
XmlNamespaceManager nsManager = new XmlNamespaceManager(doc.NameTable);
nsManager.AddNamespace("wsu", WsSecurityHeader.WsseUtilityNamespaceUrl);
return doc.SelectSingleNode($"//*[@wsu:Id=\"{id}\"]", nsManager) as XmlElement;
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
接下来,创建一个行为并添加消息检查器。
using System.Security.Cryptography.X509Certificates;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
namespace MyClient.WsSecurity
{
public sealed class WsSecurityHeaderBehavior : IEndpointBehavior
{
public X509Certificate2 X509Certificate { get; }
public WsSecurityHeaderBehavior() { }
public WsSecurityHeaderBehavior(X509Certificate2 cert)
{
X509Certificate = cert;
}
public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters) { }
public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
var inspector = new WsSecurityMessageInspector(X509Certificate);
clientRuntime.ClientMessageInspectors.Add(inspector);
}
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher) { }
public void Validate(ServiceEndpoint endpoint) { }
}
}
Run Code Online (Sandbox Code Playgroud)
最后,将行为添加到您的soap 客户端(有用的提示:重新使用相同的绑定实例和endpointAddress 以允许.net 核心缓存通道工厂——至少我记得它是这样工作的)。不要忘记将您的客户端包装在 using 块中,或者在使用后以其他方式处理它。
var binding = new BasicHttpsBinding();
binding.Security.Mode = BasicHttpsSecurityMode.Transport;
var client= new YourWcfClient(binding, endpointAddress);
// Configure ws-security signing
client.ChannelFactory.Endpoint.EndpointBehaviors.Add(new WsSecurityHeaderBehavior(cert));
Run Code Online (Sandbox Code Playgroud)
此代码已成功用于调用需要带时间戳的单向 TLS 和 ws-security 的 DataPower 端点。可能有更好的方法,但我找不到任何适用于 .net 核心的工作实现。我可能在这里遗漏了一些东西,因为我对 SOAP 和 Ws-Security 的细节都不太熟悉(我只熟悉到足以一起破解它)。祝你好运!
| 归档时间: |
|
| 查看次数: |
1929 次 |
| 最近记录: |