WCF SOAP 1.1 и WS-Security 1.0, транспортная аутентификация сертификата клиента, сертификат службы для подписи тела сообщения, UsernameToken, дайджест пароля, Nonce

Резюме: я работаю над клиентом .NET 4.0 WCF для использования веб-службы (DataPower, служба Java на другом конце) с использованием 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), поэтому я могу добавить дайджест / одноразовый пароль / созданный пароль в UsernameToken в заголовке SOAP .

В настоящее время я использую SecurityBindingElement.CreateMutualCertificateBindingElement. Я также пробовал несколько других, таких как AsymmetricSecurityBindingElement, TransportSecurityBindingElement и т. Д. (Закомментировано в коде ниже)

ЦЕРТЫ: у меня есть сертификат клиента и сертификат службы, загруженные в хранилище сертификатов с помощью MMC (я использую Windows 7, кстати). И сертификат клиента, и сертификат службы имеют закрытые ключи. Я загрузил оба файла PFX в LocalMachine / Personal, LocalMachine / Root и LocalMachine / TrustedPeople. Я также запустил FindPrivateKey / ICACLS, чтобы предоставить разрешение учетной записи «Пул приложений IIS / DefaultAppPool». Хотя все это не имеет значения, поскольку я могу запустить код WSE 3.0 со своего компьютера, и он работает без каких-либо проблем с сертификатом.

Выполнение команд:

FindPrivateKey.exe My LocalMachine -t "thumbprint of client cert"
FindPrivateKey.exe My LocalMachine -t "thumbprint of service cert"
icacls C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys\{privateKeyOfClientCert} /grant "IIS AppPool\DefaultAppPool":R      <<Successfully processed 1 files; Failed processing 0 files>>
icacls C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys\{privateKeyOfServiceCert} /grant "IIS AppPool\DefaultAppPool":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 / транспорта.

Запрос 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: я удалил все из 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;
}

Я добавил ProtectionLevel.Sign в 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, чтобы разрешить регистрацию всего мыла, включая данные pii

(for pii, also added <machineSettings enableLoggingKnownPii="true" /> under <system.serviceModel> to C:\Windows\Microsoft.NET\Framework\vX\CONFIG\machine.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 (рабочая конфигурация и код): web.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?


person Jawad    schedule 06.02.2013    source источник
comment
Почему вы вообще хотите конвертировать, когда у вас есть работающее решение в WSE 3.0? На ваш вопрос в коде WSE: нет токенов, поддерживающих конечную точку, - это совершенно другая функция. Кстати. Вам также нужна WS-адресация (она используется в клиенте WSE).   -  person Ladislav Mrnka    schedule 07.02.2013
comment
У нас есть клиент, который настаивает, чтобы мы предоставили им версию WCF, поскольку они говорят, что WSE 3.0 - это старая технология ... Я не думаю, что решает эти проблемы, но, конечно, для соответствия, мы сохраним его Soap11WSAddressing10   -  person Jawad    schedule 08.02.2013
comment
Я рассмотрю эту проблему в течение выходных - я сам заинтересован, но будьте готовы к тому, что для WCF может не быть решения (кроме написания безопасности вручную) - WCF не включает все параметры, ранее доступные в WSE 3.0. Из любопытства: есть ли у вас WS-SecurityPolicy (обычно часть развертывания службы WSDL или Java) или образец запроса и ответа для службы (не генерируемые WSE, а клиентом Java)?   -  person Ladislav Mrnka    schedule 08.02.2013
comment
Я просмотрел WSDL, и он не указывает никаких параметров безопасности. Поскольку я подключаюсь к DataPower (только что узнал, что это IBM DataPower xi50 v3.8.1) - вот что ожидается с точки зрения безопасности: шлюз ожидает как минимум: • Сертификат клиента SSL • Подписанное тело • Заголовки имени пользователя и пароля wsse • Одноразовый номер • Отметка времени   -  person Jawad    schedule 08.02.2013
comment
1. Шлюз ожидает сертификат клиента SSL на транспортном уровне. Этот сертификат используется только для первоначальной аутентификации клиента. 2. Затем он проверит, подписано ли тело сообщения сертификатом подписи SOAP на уровне сообщения. 3. Затем он проверит токен имени пользователя. 4. Затем шлюз проверит, что выполняемая операция SOAP разрешена для аутентифицированного клиента. 5. Если все это верно, он установит маршрут к серверной части и отправит запрос на серверы приложений.   -  person Jawad    schedule 08.02.2013
comment
С конфигурацией, которая у меня есть прямо сейчас, я не перейду выше шага 1. Шлюз возвращает ошибку: [SelectWS] [ssl] [error] valcred (valcred-WebServiceCryptoProfile) Профиль прокси SSL 'WebServiceFirewall': ошибка подключения: узел не отправил сертификат Но я отправляю оба сертификата клиента как а также сертификат службы: proxy.ClientCredentials.ClientCertificate.SetCertificate (ClientCertInfoHere); proxy.ClientCredentials.ServiceCertificate.SetDefaultCertificate (SigningCertInfoHere);   -  person Jawad    schedule 08.02.2013


Ответы (2)


Мне удалось решить свою проблему и подключиться к шлюзу веб-служб DataPower (IBM Xi50), используя следующие пользовательские привязки WCF (CertificateOverTransport) и CustomCredentials (UsernameToken с дайджестом пароля, сертификат клиента для аутентификации транспорта и сертификат службы для подписи тела сообщения). не уверен, что именно решило проблему, но вот мой рабочий код WCF! Я надеюсь, что это поможет другим, которые находятся в такой же ситуации, как и я.

Убедитесь, что шлюз DataPower Xi50 также настроен для WCF. От IBM: «При использовании BasicHttpBinding с SSL: вы можете использовать параметр disable-ssl-cipher-check, чтобы отключить проверки шифрования для любых утверждений TransportBinding. Заголовок Basic Auth не поддерживается по умолчанию в прокси-сервере веб-служб. Пользовательская конфигурация правило при ошибке для вставки заголовка 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.Sign в контракте на обслуживание, если хотите, чтобы тело сообщения было только подписано (а не зашифровано).

Для DNS Identity, с которым у меня раньше были проблемы, теперь я мог указать имя субъекта сертификата клиента - раньше это не сработало.

У меня нет конфигурации в моем web.config.

Вот прокси, использующий 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
}

Удачи!

person Jawad    schedule 11.02.2013

Я просмотрел ваш код, и он выглядит правильно. В мыльном сообщении WSE и WCF есть небольшая разница, но разница только в способе ссылки на сертификат, используемый для подписи сообщения.

Я думаю, что основная проблема здесь - неправильное использование сертификатов. Вы используете взаимную безопасность транспорта и сообщений. Теоретически для этого требуется четыре сертификата. Тебе нужно

  • Сертификат службы безопасности транспорта - этот сертификат используется сервером для создания SSL-соединения. Чтобы успешно установить соединение, клиент должен доверять сертификату (вам нужно либо доверять органу, выдавшему сертификат, либо сертификат сервера должен быть помещен в хранилище доверенных лиц).
  • Сертификат клиента для безопасности транспорта - этот сертификат используется для аутентификации клиента на сервере на транспортном уровне - у вас должен быть сертификат и его закрытый ключ в вашем личном хранилище.
  • Сертификат службы безопасности сообщений - этот сертификат используется для шифрования запроса и подписи ответа (когда используется WS-Security 1.0). Вам необходимо иметь этот сертификат где-то на вашем компьютере (вам решать, какое место будет использоваться для загрузки сертификата).
  • Сертификат клиента для безопасности сообщений - этот сертификат используется для шифрования ответа и запроса подписи (когда используется WS-Security 1.0). Вам необходимо иметь этот сертификат и его закрытый ключ где-то на вашем компьютере (вам решать, какое место будет использоваться для загрузки сертификата).

Похоже, у вас всего два сертификата - один клиентский и один серверный. В таком случае их, вероятно, следует использовать как для транспорта, так и для защиты сообщений. Но тут возникает интересная проблема - ваш «подписывающий» сертификат на стороне клиента в примере WSE на самом деле является сервисным сертификатом. Если это действительно так, это означает, что клиент должен иметь доступ к закрытому ключу сервера - этого никогда не должно происходить. Это худшее нарушение инфраструктуры PKI. Инфраструктура PKI основана на доверии к центрам сертификации и защите закрытых ключей, при этом у каждого участника есть собственный закрытый ключ, недоступный для других. Совместное использование закрытых ключей снижает безопасность. В худшем случае это может означать полное отсутствие безопасности, потому что любой, у кого есть доступ к закрытому ключу, может перехватить сообщение или фальшивую подпись в сообщении.

Если я прав, вам следует использовать WSE 3.0, и вы будете довольны этим. Просто заставить WCF использовать другой сертификат клиента для HTTPS и безопасности сообщений может быть довольно сложно. У вас есть одно свойство ClientCertificate, но вам нужно загрузить другой сертификат для HTTPS и безопасности сообщений. Требуется создать пользовательский ClientCredentials с двумя свойствами и пользовательский SecurityTokenManager для возврата правильного поставщика сертификатов (путем реализации для каждого использования (это теория - я никогда не пробовал).

Кстати. ваша проблема с EndpointIdentity основана на том факте, что ваша служба представлена ​​в некотором DNS, и если тема в сертификате службы (который в вашем случае также является сертификатом подписи) отличается, вы должны создать новый идентификатор DNS для своей конечной точки. В противном случае WCF не будет доверять сертификату. Сертификат сервера должен быть выпущен с субъектом, совпадающим с DNS-именем, используемым для доступа к серверу.

person Ladislav Mrnka    schedule 10.02.2013
comment
Спасибо за ваш отзыв и помощь с этим Ладиславом. Моя проблема решена. Я не уверен, что это разрешило. Я нашел приведенное ниже на сайте IBM и рассказал об этом нашей группе по инфраструктуре ИТ-шлюзов. Говорят, в DataPower никаких изменений не вносили, но он заработал сегодня утром !!! Я собираюсь ответить на вопрос с помощью рабочего кода вместе с классом CustomCredential, который я собрал. Спасибо! - person Jawad; 12.02.2013
comment
IBM: publib.boulder.ibm.com/infocenter/ieduasst/v1r1m0/ При использовании BasicHttpBinding с SSL: вы можете использовать параметр disable-ssl-cipher-check, чтобы отключить проверку шифрования для любых Утверждения TransportBinding. Заголовок базовой аутентификации по умолчанию не поддерживается прокси-сервером веб-служб. Для взаимодействия с WCF требуется настраиваемая конфигурация правила при ошибке для внедрения заголовка WWW-Authenticate. - person Jawad; 12.02.2013