WCF SOAP 1.1 и WS-Security 1.0, транспортная аутентификация сертификата клиента, сертификат службы для подписи тела сообщения, UsernameToken, дайджест паролей, Nonce
резюме: Я работаю над клиентом .NET 4.0 WCF для использования веб-службы (DataPower, Java service на другом конце) с помощью SOAP 1.1 и WS-Security 1.0. Клиент WCF должен реализовать сертификат клиента для взаимной проверки подлинности на транспортном уровне. Тело сообщения должно быть подписано с помощью отдельного сертификата службы / подписи. Заголовок SOAP также должен содержать маркер имени пользователя с дайджестом паролей и включать теги Nonce и Created.
Я возможность использования этой веб-службы с помощью WSE 3.0 с BasicHTTPBinding. Но до сих пор мне не удалось реализовать то же самое с WCF, используя WSHttpBinding или CustomBinding. Я пробовал все элементы привязки безопасности и до сих пор не повезло.
Я также использую библиотеку usernametoken отсюда (http://blogs.msdn.com/b/aszego/archive/2010/06/24/usernametoken-profile-vs-wcf.aspx) поэтому я могу добавить дайджест пароля / nonce / созданный в UsernameToken в заголовке SOAP.
в настоящее время я использую SecurityBindingElement.CreateMutualCertificateBindingElement я также пробовал несколько других, таких как AsymmetricSecurityBindingElement, TransportSecurityBindingElement и т. д. (прокомментировано в коде ниже)
сертификаты: У меня есть сертификат клиента и сертификат службы, загруженный в хранилище сертификатов с помощью MMC (я нахожусь в Windows 7 btw.) Как сертификат клиента, так и сертификат службы имеют частный ключи. Я загрузил оба файла PFX в LocalMachine / Personal, LocalMachine/Root и LocalMachine/TrustedPeople. Я также запустил FindPrivateKey/ICACLS, чтобы дать разрешение учетной записи "IIS App Pool / DefaultAppPool". Хотя ничто из этого не должно иметь значения, так как я могу запустить код WSE 3.0 с моей машины, и он работает без каких-либо проблем с сертификатом.
команды:
FindPrivateKey.exe My LocalMachine -t "thumbprint of client cert"
FindPrivateKey.exe My LocalMachine -t "thumbprint of service cert"
icacls C:ProgramDataMicrosoftCryptoRSAMachineKeys{privateKeyOfClientCert} /grant "IIS AppPoolDefaultAppPool":R <<Successfully processed 1 files; Failed processing 0 files>>
icacls C:ProgramDataMicrosoftCryptoRSAMachineKeys{privateKeyOfServiceCert} /grant "IIS AppPoolDefaultAppPool":R <<Successfully processed 1 files; Failed processing 0 files>>
ПРОБЛЕМА WCF: В настоящее время я получаю " не удалось установить безопасный канал для SSL / TLS с полномочиями "x.x.com" сообщение от шлюза DataPower. Я полагаю, это может быть потому, что шлюз принимает сертификат службы и использует его для аутентификации клиента вместо использования сертификата клиента, который я отправляю. Я говорю это, потому что, когда я не указываю идентификатор DNS для конечной точки, я получаю сообщение о том, что шлюз ожидает, что идентификатор DNS будет "{имя субъекта службы/сертификат подписи}".
здесь Запрос SOAP, сгенерированный WCF, который дает вышеуказанную ошибку. Запрос WCF SOAP очень похож на запрос WSE SOAP. Вышеуказанная ошибка, скорее всего, возникает из-за проблемы сертификата на уровне SSL/Transport.
WCF SOAP запрос:
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
<s:Header>
<o:Security s:mustUnderstand="1" xmlns:o="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
<u:Timestamp u:Id="uuid-8533d9a5-865e-4a4b-a750-fadb7c1ce36c-1">
<u:Created>2013-02-06T20:53:04.679Z</u:Created>
<u:Expires>2013-02-06T20:58:04.679Z</u:Expires>
</u:Timestamp>
<o:BinarySecurityToken u:Id="uuid-0bab08ce-3e3b-4360-a44b-694b06a3dd67-2" ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3">Removed Service Cert Encoded Value</o:BinarySecurityToken>
<wsse:UsernameToken wsu:Id="7843ab92-f69a-4d00-a5ba-117e32a74f49" 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:Username>USER_Removed</wsse:Username>
<wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest">XXX=</wsse:Password>
<wsse:Nonce>XXX==</wsse:Nonce>
<wsu:Created>2013-02-06T20:53:04Z</wsu:Created>
</wsse:UsernameToken>
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
<SignedInfo>
<CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"></CanonicalizationMethod>
<SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"></SignatureMethod>
<Reference URI="#_1">
<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>XXX=</DigestValue>
</Reference>
<Reference URI="#uuid-8533d9a5-865e-4a4b-a750-fadb7c1ce36c-1">
<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>XXX=</DigestValue>
</Reference>
<Reference URI="#7843ab92-f69a-4d00-a5ba-117e32a74f49">
<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>XXX=</DigestValue>
</Reference>
</SignedInfo>
<SignatureValue>XXXLongXXX=</SignatureValue>
<KeyInfo>
<o:SecurityTokenReference>
<o:Reference URI="#uuid-0bab08ce-3e3b-4360-a44b-694b06a3dd67-2"></o:Reference>
</o:SecurityTokenReference>
</KeyInfo>
</Signature>
</o:Security>
</s:Header>
<s:Body u:Id="_1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<ping xmlns="https://x.x.com/xxx/v1">
<pingRequest xmlns="">hello</pingRequest>
</ping>
</s:Body>
WSE 3.0 SOAP запрос (это работает):
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing"
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:Header>
<wsa:Action wsu:Id="Id-4271fb72-464a-467d-ab1f-4d32542e20f0"/>
<wsa:MessageID wsu:Id="Id-11657f64-d856-47d8-b600-d5379fb91a0d">urn:uuid:ff8becb7-74c2-4844-ab46-8ae23f1355a7</wsa:MessageID>
<wsa:ReplyTo wsu:Id="Id-40b2e6e8-e67b-4a6c-a545-071ce0f0107a">
<wsa:Address>http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</wsa:Address>
</wsa:ReplyTo>
<wsa:To wsu:Id="Id-d5e0b488-6f8a-479c-940d-2b85833dbc66">https://x.x.com/xxx/v1</wsa:To>
<wsse:Security soap:mustUnderstand="1">
<wsu:Timestamp wsu:Id="Timestamp-68476551-5c58-4a47-967b-54ec18257b1b">
<wsu:Created>2013-02-06T19:38:39Z</wsu:Created>
<wsu:Expires>2013-02-06T19:43:39Z</wsu:Expires>
</wsu:Timestamp>
<wsse:UsernameToken wsu:Id="SecurityToken-e5f65166-a825-48cb-a939-8e515a637e01">
<wsse:Username>USER_Removed</wsse:Username>
<wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest">XXX=</wsse:Password>
<wsse:Nonce>XXX==</wsse:Nonce>
<wsu:Created>2013-02-06T19:38:39Z</wsu:Created>
</wsse:UsernameToken>
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
<SignedInfo>
<ds:CanonicalizationMethod xmlns:ds="http://www.w3.org/2000/09/xmldsig#" Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
<SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
<Reference URI="#Id-4271fb72-464a-467d-ab1f-4d32542e20f0">
<Transforms>
<Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
</Transforms>
<DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
<DigestValue>XXX=</DigestValue>
</Reference>
<Reference URI="#Id-11657f64-d856-47d8-b600-d5379fb91a0d">
<Transforms>
<Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
</Transforms>
<DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
<DigestValue>XXX=</DigestValue>
</Reference>
<Reference URI="#Id-40b2e6e8-e67b-4a6c-a545-071ce0f0107a">
<Transforms>
<Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
</Transforms>
<DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
<DigestValue>XXX=</DigestValue>
</Reference>
<Reference URI="#Id-d5e0b488-6f8a-479c-940d-2b85833dbc66">
<Transforms>
<Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
</Transforms>
<DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
<DigestValue>XXX=</DigestValue>
</Reference>
<Reference URI="#Timestamp-68476551-5c58-4a47-967b-54ec18257b1b">
<Transforms>
<Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
</Transforms>
<DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
<DigestValue>XXX=</DigestValue>
</Reference>
<Reference URI="#Id-6f76e50e-932c-4878-bbc0-3ef4c8a36990">
<Transforms>
<Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
</Transforms>
<DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
<DigestValue>XXX=</DigestValue>
</Reference>
</SignedInfo>
<SignatureValue>XXXLongXXX=</SignatureValue>
<KeyInfo>
<wsse:SecurityTokenReference>
<wsse:KeyIdentifier ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509SubjectKeyIdentifier"
EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">XXX=</wsse:KeyIdentifier>
</wsse:SecurityTokenReference>
</KeyInfo>
</Signature>
</wsse:Security>
</soap:Header>
<soap:Body wsu:Id="Id-6f76e50e-932c-4878-bbc0-3ef4c8a36990">
<ping xmlns="https://x.x.com/xxx/v1">
<pingRequest xmlns="">hello</pingRequest>
</ping>
</soap:Body>
вот вся конфигурация, пожалуйста, позвольте мне знай, что я делаю не так!
WCF web.конфиг: я удалил все из интернета.config, поскольку я делаю всю конфигурацию в коде.
конфигурация WCF в коде:
var proxy = GetProxy();
pingResponseMessage resp = proxy.ping("hello");
lblStatus.Text = resp.status.ToString();
private XXXClient GetProxy()
{
System.Net.ServicePointManager.ServerCertificateValidationCallback += (se, cert, chain, sslerror) => { return true; };
XXXClient proxy = new XXXClient(GetCustomBinding(), new EndpointAddress(new Uri("https://xxx"), EndpointIdentity.CreateDnsIdentity("I am forced to put the signing cert subject here, nothing else works"), new AddressHeaderCollection()));
proxy.Endpoint.Behaviors.Remove(typeof(ClientCredentials));
proxy.Endpoint.Behaviors.Add(new UsernameClientCredentials(new UsernameInfo(@"USER_Removed", "X")));
proxy.ClientCredentials.ClientCertificate.SetCertificate(StoreLocation.LocalMachine, StoreName.My, X509FindType.FindByThumbprint, "REMOVED");
proxy.ClientCredentials.ServiceCertificate.SetDefaultCertificate(StoreLocation.LocalMachine, StoreName.My, X509FindType.FindByThumbprint, "REMOVED");
proxy.ClientCredentials.ServiceCertificate.Authentication.CertificateValidationMode = System.ServiceModel.Security.X509CertificateValidationMode.None;
return proxy;
}
private Binding GetCustomBinding()
{
//TransportSecurityBindingElement secBE = SecurityBindingElement.CreateCertificateOverTransportBindingElement(MessageSecurityVersion.WSSecurity10WSTrust13WSSecureConversation13WSSecurityPolicy12BasicSecurityProfile10);
//AsymmetricSecurityBindingElement secBE = (AsymmetricSecurityBindingElement)SecurityBindingElement.CreateMutualCertificateBindingElement(MessageSecurityVersion.WSSecurity10WSTrustFebruary2005WSSecureConversationFebruary2005WSSecurityPolicy11BasicSecurityProfile10);
//secBE.InitiatorTokenParameters = new System.ServiceModel.Security.Tokens.X509SecurityTokenParameters { InclusionMode = SecurityTokenInclusionMode.AlwaysToRecipient, RequireDerivedKeys = false, X509ReferenceStyle = X509KeyIdentifierClauseType.SubjectKeyIdentifier };
//secBE.RecipientTokenParameters = new System.ServiceModel.Security.Tokens.X509SecurityTokenParameters { InclusionMode = SecurityTokenInclusionMode.AlwaysToInitiator, RequireDerivedKeys = false, X509ReferenceStyle = X509KeyIdentifierClauseType.SubjectKeyIdentifier };
//secBE.MessageProtectionOrder = System.ServiceModel.Security.MessageProtectionOrder.SignBeforeEncrypt;
//secBE.EndpointSupportingTokenParameters.Signed.Add(new UserNameSecurityTokenParameters() { InclusionMode = SecurityTokenInclusionMode.AlwaysToRecipient, RequireDerivedKeys = false });
//secBE.EndpointSupportingTokenParameters.Signed.Add(new X509SecurityTokenParameters(X509KeyIdentifierClauseType.SubjectKeyIdentifier, SecurityTokenInclusionMode.Never) { InclusionMode = SecurityTokenInclusionMode.Never, RequireDerivedKeys = false, X509ReferenceStyle = X509KeyIdentifierClauseType.SubjectKeyIdentifier });
//secBE.ProtectionTokenParameters = new System.ServiceModel.Security.Tokens.X509SecurityTokenParameters { InclusionMode = SecurityTokenInclusionMode.AlwaysToRecipient };
//secBE.DefaultAlgorithmSuite = new CustomSecurityAlgorithm();
SecurityBindingElement secBE = SecurityBindingElement.CreateMutualCertificateBindingElement(MessageSecurityVersion.WSSecurity10WSTrustFebruary2005WSSecureConversationFebruary2005WSSecurityPolicy11BasicSecurityProfile10);
secBE.MessageSecurityVersion = MessageSecurityVersion.WSSecurity10WSTrustFebruary2005WSSecureConversationFebruary2005WSSecurityPolicy11BasicSecurityProfile10;
secBE.EndpointSupportingTokenParameters.Signed.Add(new UsernameTokenParameters() { InclusionMode= SecurityTokenInclusionMode.AlwaysToRecipient, ReferenceStyle = SecurityTokenReferenceStyle.External, RequireDerivedKeys = false });
secBE.SecurityHeaderLayout = SecurityHeaderLayout.Strict;
//secBE.AllowInsecureTransport = false;
//secBE.AllowSerializedSigningTokenOnReply = false;
secBE.EnableUnsecuredResponse = true;
secBE.IncludeTimestamp = true;
secBE.SetKeyDerivation(false);
TextMessageEncodingBindingElement textEncBE = new TextMessageEncodingBindingElement(MessageVersion.Soap11, System.Text.Encoding.UTF8);
HttpsTransportBindingElement httpsBE = new HttpsTransportBindingElement();
httpsBE.RequireClientCertificate = true;
//httpsBindingElement.AllowCookies = false;
//httpsBindingElement.AuthenticationScheme = System.Net.AuthenticationSchemes.Basic;
httpsBE.BypassProxyOnLocal = false;
httpsBE.HostNameComparisonMode = HostNameComparisonMode.StrongWildcard;
//httpsBindingElement.KeepAliveEnabled = false;
httpsBE.TransferMode = TransferMode.Buffered;
httpsBE.UseDefaultWebProxy = true;
CustomBinding myBinding = new CustomBinding();
myBinding.Elements.Add(secBE);
myBinding.Elements.Add(textEncBE);
myBinding.Elements.Add(httpsBE);
return myBinding;
}
я добавил уровень защиты.Войдите в ServiceContract и OperationContracts, так как мне нужно только подписать тело сообщения. Я еще не добрался до этого, чтобы проверить это.
[System.ServiceModel.ServiceContractAttribute(Namespace = "https://x.x.com/xxx/v1", ConfigurationName = "x.x", ProtectionLevel = System.Net.Security.ProtectionLevel.Sign)]
public interface XXXService {
[System.ServiceModel.OperationContractAttribute(Action = "", ReplyAction = "*", ProtectionLevel = System.Net.Security.ProtectionLevel.Sign)]
[System.ServiceModel.XmlSerializerFormatAttribute(SupportFaults=true)]
[return: System.ServiceModel.MessageParameterAttribute(Name="return")]
XXX.pingResponse ping(XXX.ping request);
[System.ServiceModel.ServiceContractAttribute(Namespace = "https://x.x.com/xxx/v1", ProtectionLevel = System.Net.Security.ProtectionLevel.Sign)]
public partial class XXXClient : System.ServiceModel.ClientBase<XXXService> {
[System.ServiceModel.OperationContractAttribute(Action = "", ReplyAction = "*", ProtectionLevel = System.Net.Security.ProtectionLevel.Sign)]
public XXX.pingResponseMessage ping(string pingRequest) {
я добавил следующее ниже в web.config, чтобы разрешить ведение журнала всего soap включая данные pii
(for pii, also added <machineSettings enableLoggingKnownPii="true" /> under <system.serviceModel> to C:WindowsMicrosoft.NETFrameworkvXCONFIGmachine.config)
<system.serviceModel>
<diagnostics>
<messageLogging logKnownPii="true" logEntireMessage="true" logMalformedMessages="true" logMessagesAtServiceLevel="true" logMessagesAtTransportLevel="true" maxMessagesToLog="3000"/>
</diagnostics>
</system.serviceModel>
<system.diagnostics>
<sources>
<source name="System.ServiceModel.MessageLogging" logKnownPii="true">
<listeners>
<add initializeData="C:trace.log" type="System.Diagnostics.XmlWriterTraceListener" name="messages"/>
</listeners>
</source>
</sources>
</system.diagnostics>
===============
WSE 3.0 (рабочая конфигурация и код): сеть.config:
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="myBinding" closeTimeout="00:01:00" openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00" allowCookies="false" bypassProxyOnLocal="false" hostNameComparisonMode="StrongWildcard" maxBufferSize="65536" maxBufferPoolSize="524288" maxReceivedMessageSize="65536" messageEncoding="Text" textEncoding="utf-8" transferMode="Buffered" useDefaultWebProxy="true">
<readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384" maxBytesPerRead="4096" maxNameTableCharCount="16384"/>
<security mode="Transport">
<transport clientCredentialType="None" proxyCredentialType="None" realm=""/>
<message clientCredentialType="UserName" algorithmSuite="Default"/>
</security>
</binding>
</basicHttpBinding>
</bindings>
<client>
<endpoint address="https://x.x.com/xxx/v1" binding="basicHttpBinding" bindingConfiguration="myBinding" contract="XXXService" name="XXX"/>
</client>
</system.serviceModel>
<appSettings>
<add key="XXXImplService" value="https://x.x.com/xxx/v1"/>
</appSettings>
... и WSE3 код:
var proxy = new XXXImplServiceWse();
UsernameToken usernameToken = new UsernameToken(@"USER_Removed", "X");
proxy.RequestSoapContext.Security.Tokens.Add(usernameToken);
X509Certificate2 mutualCert = LoadCertFromStore(StoreLocation.LocalMachine, StoreName.My, "Client Cert Subject Name");
proxy.ClientCertificates.Add(mutualCert);
X509Certificate2 signCert = LoadCertFromStore(StoreLocation.LocalMachine, StoreName.My, "Service Cert Subject Name");
X509SecurityToken signatureToken = new X509SecurityToken(signCert);
MessageSignature signature = new MessageSignature(signatureToken); // <!-- IS THIS SAME AS THIS STEP IN WCF: secBE.EndpointSupportingTokenParameters.Signed.Add(new UsernameTokenParameters()) -->
proxy.RequestSoapContext.Security.Elements.Add(signature);
==========
Итак, как преобразовать приведенный выше код WSE 3.0 в WCF?
2 ответов
я смог решить свою проблему и подключиться к шлюзу веб-службы DataPower (IBM Xi50), используя следующие пользовательские привязки WCF (CertificateOverTransport) и CustomCredentials (UsernameToken с дайджестом паролей, сертификат клиента для проверки подлинности транспорта и сертификат службы для подписи тела сообщения.) Я не уверен, что именно исправил проблему, но вот мой рабочий код WCF! Надеюсь, это поможет другим, кто находится в такой же ситуации, как и я.
пожалуйста, убедитесь, что Шлюз DataPower Xi50 также настроен для WCF. От IBM: "при использовании BasicHttpBinding с SSL: вы можете использовать параметр disable-ssl-cipher-check для отключения проверки шифрования для любых утверждений TransportBinding. Основной Заголовок Auth не поддерживается по умолчанию в прокси-сервере веб-служб. Пользовательская конфигурация правила on-error для ввода заголовка WWW-Authenticate требуется для взаимодействия с WCF.- Подробности здесь.: https://publib.boulder.ibm.com/infocenter/ieduasst/v1r1m0/index.jsp?topic=/com.ibm.iea.wdatapower/wdatapower/1.0/xa35/380DataPowerWCFIntegration/player.html.
убедитесь, что вы установили ProtectionLevel.Подпишите контракт на обслуживание, если вы хотите, чтобы тело сообщения было подписано только (и не зашифровано.)
для идентификатора DNS, с которым у меня были проблемы ранее, теперь я смог поместить имя субъекта сертификата клиента - раньше это не сработало бы.
I в моей сети нет никакой конфигурации.конфиг.
вот Прокси, использующий CustomBinding:
private ClientProxy GetProxy()
{
XXXServiceClient proxy = new XXXServiceClient(GetCustomBinding(), new EndpointAddress(new Uri("<<GatewayURLHere>>"), EndpointIdentity.CreateDnsIdentity("<<DNS or Client Cert Subject Name>>"), new AddressHeaderCollection()));
proxy.Endpoint.Behaviors.Remove(typeof(ClientCredentials));
proxy.Endpoint.Behaviors.Add(new CustomCredentials(<clientCertHere>, <signingCertHere>));
proxy.ClientCredentials.UserName.UserName = @"XXX";
proxy.ClientCredentials.UserName.Password = "yyy";
return proxy;
}
private Binding GetCustomBinding()
{
TransportSecurityBindingElement secBE = SecurityBindingElement.CreateCertificateOverTransportBindingElement(MessageSecurityVersion.WSSecurity10WSTrustFebruary2005WSSecureConversationFebruary2005WSSecurityPolicy11BasicSecurityProfile10);
secBE.EndpointSupportingTokenParameters.Signed.Add(new UserNameSecurityTokenParameters { InclusionMode = SecurityTokenInclusionMode.Never, RequireDerivedKeys = false });
secBE.EnableUnsecuredResponse = true;
secBE.IncludeTimestamp = true;
TextMessageEncodingBindingElement textEncBE = new TextMessageEncodingBindingElement(MessageVersion.Soap11WSAddressingAugust2004, System.Text.Encoding.UTF8);
HttpsTransportBindingElement httpsBE = new HttpsTransportBindingElement();
httpsBE.RequireClientCertificate = true;
CustomBinding myBinding = new CustomBinding();
myBinding.Elements.Add(secBE);
myBinding.Elements.Add(textEncBE);
myBinding.Elements.Add(httpsBE);
return myBinding;
}
вот мой класс CustomCredentials, который я собрал из нескольких источников, включая вышеупомянутый сертификат клиента библиотеки UsernameToken для (взаимный?) аутентификация на транспортном уровне, сертификат службы / подписи для подписи тела сообщения и UsernameToken с дайджестом паролей в заголовке SOAP:
using System;
using System.IdentityModel.Selectors;
using System.IdentityModel.Tokens;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.ServiceModel.Security;
using System.Text;
namespace XXX_WCF
{
public class CustomCredentials : ClientCredentials
{
private X509Certificate2 clientAuthCert;
private X509Certificate2 clientSigningCert;
public CustomCredentials() : base() { }
public CustomCredentials(CustomCredentials other)
: base(other)
{
clientSigningCert = other.clientSigningCert;
clientAuthCert = other.clientAuthCert;
}
protected override ClientCredentials CloneCore()
{
CustomCredentials scc = new CustomCredentials(this);
return scc;
}
public CustomCredentials(X509Certificate2 ClientAuthCert, X509Certificate2 ClientSigningCert)
: base()
{
clientAuthCert = ClientAuthCert;
clientSigningCert = ClientSigningCert;
}
public X509Certificate2 ClientAuthCert
{
get { return clientAuthCert; }
set { clientAuthCert = value; }
}
public X509Certificate2 ClientSigningCert
{
get { return clientSigningCert; }
set { clientSigningCert = value; }
}
public override SecurityTokenManager CreateSecurityTokenManager()
{
return new CustomTokenManager(this);
}
}
public class CustomTokenManager : ClientCredentialsSecurityTokenManager
{
private CustomCredentials custCreds;
public CustomTokenManager(CustomCredentials CustCreds)
: base(CustCreds)
{
custCreds = CustCreds;
}
public override SecurityTokenProvider CreateSecurityTokenProvider(SecurityTokenRequirement tokenRequirement)
{
if (tokenRequirement.TokenType == SecurityTokenTypes.X509Certificate)
{
x509CustomSecurityTokenProvider prov;
object temp = null;
TransportSecurityBindingElement secBE = null;
if (tokenRequirement.Properties.TryGetValue("http://schemas.microsoft.com/ws/2006/05/servicemodel/securitytokenrequirement/SecurityBindingElement", out temp))
{
secBE = (TransportSecurityBindingElement)temp;
}
if (secBE == null)
prov = new x509CustomSecurityTokenProvider(custCreds.ClientAuthCert);
else
prov = new x509CustomSecurityTokenProvider(custCreds.ClientSigningCert);
return prov;
}
return base.CreateSecurityTokenProvider(tokenRequirement);
}
public override System.IdentityModel.Selectors.SecurityTokenSerializer CreateSecurityTokenSerializer(System.IdentityModel.Selectors.SecurityTokenVersion version)
{
return new CustomTokenSerializer(System.ServiceModel.Security.SecurityVersion.WSSecurity10);
}
}
class x509CustomSecurityTokenProvider : SecurityTokenProvider
{
private X509Certificate2 clientCert;
public x509CustomSecurityTokenProvider(X509Certificate2 cert)
: base()
{
clientCert = cert;
}
protected override SecurityToken GetTokenCore(TimeSpan timeout)
{
return new X509SecurityToken(clientCert);
}
}
public class CustomTokenSerializer : WSSecurityTokenSerializer
{
public CustomTokenSerializer(SecurityVersion sv) : base(sv) { }
protected override void WriteTokenCore(System.Xml.XmlWriter writer, System.IdentityModel.Tokens.SecurityToken token)
{
if (writer == null)
{
throw new ArgumentNullException("writer");
}
if (token == null)
{
throw new ArgumentNullException("token");
}
if (token.GetType() == new UserNameSecurityToken("x", "y").GetType())
{
UserNameSecurityToken userToken = token as UserNameSecurityToken;
if (userToken == null)
{
throw new ArgumentNullException("userToken: " + token.ToString());
}
string tokennamespace = "o";
DateTime created = DateTime.Now;
string createdStr = created.ToString("yyyy-MM-ddThh:mm:ss.fffZ");
string phrase = Guid.NewGuid().ToString();
string nonce = GetSHA1String(phrase);
string password = GetSHA1String(nonce + createdStr + userToken.Password);
//string password = userToken.Password;
writer.WriteStartElement(tokennamespace, "UsernameToken", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd");
writer.WriteAttributeString("u", "Id", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd", token.Id);
writer.WriteElementString(tokennamespace, "Username", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd", userToken.UserName);
writer.WriteStartElement(tokennamespace, "Password", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd");
writer.WriteAttributeString("Type", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest");
writer.WriteValue(password);
writer.WriteEndElement();
writer.WriteStartElement(tokennamespace, "Nonce", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd");
writer.WriteAttributeString("EncodingType", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary");
writer.WriteValue(nonce);
writer.WriteEndElement();
writer.WriteElementString(tokennamespace, "Created", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd", createdStr);
writer.WriteEndElement();
writer.Flush();
}
else
{
base.WriteTokenCore(writer, token);
}
}
protected string GetSHA1String(string phrase)
{
SHA1CryptoServiceProvider sha1Hasher = new SHA1CryptoServiceProvider();
byte[] hashedDataBytes = sha1Hasher.ComputeHash(Encoding.UTF8.GetBytes(phrase));
return Convert.ToBase64String(hashedDataBytes);
}
}//CustomTokenSerializer
}
хорошее удачи!
Я прошел через ваш код, и он выглядит правильно. Существует небольшая разница в сообщении WSE и WCF soap, но разница только в том, как ссылается сертификат, используемый для подписи сообщения.
Я думаю, что основная проблема здесь-неправильное использование сертификатов. Вы используете как транспорт, так и взаимную безопасность сообщений. Теоретически для этого требуется четыре сертификата. Вам нужно
- сертификат Службы безопасности транспорта-этот сертификат используется сервер для построения SSL-соединения. Для успешного построения соединения клиент должен доверять сертификату (необходимо либо доверять центру, выдавшему сертификат, либо сертификат сервера должен быть помещен в хранилище доверенных лиц).
- сертификат клиента для транспортной безопасности-этот сертификат используется для аутентификации клиента на сервере на транспортном уровне-у вас должен быть сертификат и его закрытый ключ в вашем личном хранилище
- сертификат сервис для безопасность сообщений-этот сертификат используется для шифрования ответа на запрос и подпись (при использовании WS-Security 1.0). Вам нужно иметь этот сертификат где-то на вашем компьютере (это зависит от вас, какое место будет использоваться для загрузки сертификата).
- сертификат клиента для защиты сообщений-этот сертификат используется для шифрования ответа и запроса подписи (когда используется WS-Security 1.0). Вы должны иметь этот сертификат и его закрытый ключ где-то на вашем компьютере (это до вас, какое место будет использоваться для загрузки сертификата).
похоже, что у вас есть только два сертификата - один клиент и один сервер. В таком случае их, вероятно, следует использовать как для обеспечения безопасности транспорта, так и для защиты сообщений. Но вот интересная проблема - ваш" подписывающий " сертификат на стороне клиента в Примере WSE на самом деле является сертификатом службы. Если это действительно так, это означает, что клиент должен иметь доступ к закрытому ключу сервера - этого никогда не должно случиться. Это худшее нарушение инфраструктуры PKI. Инфраструктура PKI основана на доверии к центрам сертификации и на защите закрытых ключей, где каждый участник имеет свой собственный закрытый ключ, недоступный никому другому. Совместное использование закрытых ключей снижает безопасность. В худшем случае это может быть равно нулю безопасности вообще, потому что любой, у кого есть доступ к закрытому ключу, может перехватить сообщение или поддельную подпись на сообщении.
если я прав, вы должны использовать WSE 3.0 и быть довольны что. Просто заставить WCF использовать другой сертификат клиента для HTTPS и безопасности сообщений может быть довольно сложно. У вас есть один ClientCertificate
свойство, но вам нужно загрузить другой сертификат для HTTPS и безопасности сообщений. Это требует создания пользовательских ClientCredentials
с двумя свойствами и custom SecurityTokenManager
чтобы вернуть правильный поставщик сертификатов (путем реализации для каждого использования (это теория - я никогда не пробовал).
кстати. ваша проблема с EndpointIdentity
основано на факте что ваше обслуживание предоставляется на некоторых DNS, и если субъект в сертификате службы (который в вашем случае также сертификат подписи) отличается, Вы должны создать новый идентификатор DNS для своей конечной точки. В противном случае WCF не будет доверять сертификату. Сертификат сервера должен быть выдан с субъектом, соответствующим DNS-имени, используемому для доступа к серверу.