2017-02-09 51 views
12

Próbuję spożywać Java Web Service przy użyciu C# w aplikacji komputerowej.
My first attempt używał WebServicesClientProtocol, ale nie jestem w stanie dodać niezbędny atrybut, który jest wymagany przez WSSE Username and Token Security Spec 1.1
ClientMessageInspector Dodaj BinarySecurityToken i Signature

muszę utworzyć żądanie, które ma tę strukturę:

<soap:Envelope xmlns:dz="http://dom.query.api.com" xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:xsd="http://dz.api.swd.zbp.pl/xsd"> 
    <soap:Header> 
     <wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"> 
      <wsse:UsernameToken wsu:Id="UsernameToken-E94CEB6F4708FB7C23148611494797612"> 
       <wsse:Username>my_login</wsse:Username> 
       <wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest">XqEwZ/CxaBfFvh487TjvN8qD63c=</wsse:Password> 
       <wsse:Nonce EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">JzURe0CxvzRjmEcH/ndldw==</wsse:Nonce> 
       <wsu:Created>2017-02-09T09:42:27.976Z</wsu:Created> 
      </wsse:UsernameToken> 
      <wsse:BinarySecurityToken EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary" ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509PKIPathv1" wsu:Id="X509-E94CEB6F4708FB7C2314861149479517">MIIKnDCCB.........nmIngeg6d6TNI=</wsse:BinarySecurityToken> 
      <ds:Signature Id="SIG-E94CEB6F4708FB7C23148611494795311" xmlns:ds="http://www.w3.org/2000/09/xmldsig#"> 
       <ds:SignedInfo> 
        <ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"> 
         <ec:InclusiveNamespaces PrefixList="dz soap xsd" xmlns:ec="http://www.w3.org/2001/10/xml-exc-c14n#"/> 
        </ds:CanonicalizationMethod> 
        <ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/> 
        <ds:Reference URI="#id-E94CEB6F4708FB7C23148611494795310"> 
         <ds:Transforms> 
          <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"> 
           <ec:InclusiveNamespaces PrefixList="dz xsd" xmlns:ec="http://www.w3.org/2001/10/xml-exc-c14n#"/> 
          </ds:Transform> 
         </ds:Transforms> 
         <ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/> 
         <ds:DigestValue>mlABQuNUFOmLqsDswxXxQ6XnjpQ=</ds:DigestValue> 
        </ds:Reference> 
       </ds:SignedInfo> 
       <ds:SignatureValue>lYhBHSQ/L...XL1HEbMQjJ/Q2Rvg==</ds:SignatureValue> 
       <ds:KeyInfo Id="KI-E94CEB6F4708FB7C2314861149479518"> 
        <wsse:SecurityTokenReference wsse11:TokenType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509PKIPathv1" wsu:Id="STR-E94CEB6F4708FB7C2314861149479519" xmlns:wsse11="http://docs.oasis-open.org/wss/oasis-wss-wssecurity-secext-1.1.xsd"> 
         <wsse:Reference URI="#X509-E94CEB6F4708FB7C2314861149479517" ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509PKIPathv1"/> 
        </wsse:SecurityTokenReference> 
       </ds:KeyInfo> 
      </ds:Signature> 
     </wsse:Security> 
    </soap:Header> 
    <soap:Body wsu:Id="id-E94CEB6F4708FB7C23148611494795310" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"> 
     <dz:query> 
      <dz:param> 
       <xsd:userQueryId>27467</xsd:userQueryId> 
      </dz:param> 
     </dz:query> 
    </soap:Body> 
</soap:Envelope> 

udało mi się stworzyć Własne klasy używając IEndpointBehavior i IClientMessageInspector, ale z nich jestem tylko w stanie dodać UsernameToken

public class InspectorBehavior : IEndpointBehavior 
{ 
    /// <summary> 
    /// Gets or sets the custom ClientInspector. 
    /// </summary> 
    public ClientInspector ClientInspector { get; set; } 

    /// <summary> 
    /// Constructs a new InspectorBehavior 
    /// </summary> 
    /// <param name="clientInspector"><see cref="ClientInspector"/></param> 
    public InspectorBehavior(ClientInspector clientInspector) 
    { 
     ClientInspector = clientInspector; 
    } 

    /// <summary> 
    /// Implement to confirm that the endpoint meets some intended criteria. 
    /// </summary> 
    /// <param name="endpoint"><see cref="ServiceEndpoint"/></param> 
    public void Validate(ServiceEndpoint endpoint) 
    { 
     // not calling the base implementation 
    } 

    /// <summary> 
    /// Implement to pass data at runtime to bindings to support custom behavior. 
    /// </summary> 
    /// <param name="endpoint"><see cref="ServiceEndpoint"/></param> 
    /// <param name="bindingParameters"><see cref="BindingParameterCollection"/></param> 
    public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters) 
    { 
     // not calling the base implementation 
    } 

    /// <summary> 
    /// Implements a modification or extension of the service across an endpoint. 
    /// </summary> 
    /// <param name="endponit"><see cref="ServiceEndpoint"/></param> 
    /// <param name="endpointDispatcher"><see cref="EndpointDispatcher"/></param> 
    public void ApplyDispatchBehavior(ServiceEndpoint endponit, EndpointDispatcher endpointDispatcher) 
    { 
     // not calling the base implementation 
    } 

    /// <summary> 
    /// Implements the custom modification of the WCF client across an endpoint. 
    /// </summary> 
    /// <param name="endpoint"><see cref="ServiceEndpoint"/></param> 
    /// <param name="clientRuntime"><see cref="ClientRuntime"/></param> 
    public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime) 
    { 
     if (this.ClientInspector == null) 
      throw new InvalidOperationException("Caller must supply ClientInspector."); 

     clientRuntime.ClientMessageInspectors.Add(ClientInspector); 
    } 
} 

public class ClientInspector : IClientMessageInspector 
{ 
    /// <summary> 
    /// Gets or sets the custom MessageHeader. 
    /// </summary> 
    public MessageHeader[] Headers 
    { 
     get; 
     set; 
    } 

    /// <summary> 
    /// Constructs a new ClientInspector 
    /// </summary> 
    /// <param name="headers"><see cref="MessageHeader"/></param> 
    public ClientInspector(params MessageHeader[] headers) 
    { 
     Headers = headers; 
    } 

    /// <summary> 
    /// Enables inspection or modification of a message before a request message is sent to a service. 
    /// </summary> 
    /// <param name="request"><see cref="Message"/></param> 
    /// <param name="channel"><see cref="IClientChannel"/></param> 
    /// <returns></returns> 
    public object BeforeSendRequest(ref Message request, IClientChannel channel) 
    { 
     if (Headers != null) 
     { 
      for (int i = Headers.Length - 1; i >= 0; i--) 
       request.Headers.Insert(0, Headers[i]); 
     } 

     return request; 
    } 

    /// <summary> 
    /// Enables inspection or modification of a message after a reply message is received but 
    /// prior to passing it back to the client. 
    /// </summary> 
    /// <param name="reply"><see cref="Message"/></param> 
    /// <param name="correlationState">object</param> 
    public void AfterReceiveReply(ref Message reply, object correlationState) 
    { 
     // not calling the base implementation 
    } 
} 

public class SecurityHeader : MessageHeader 
{ 
    private readonly APIConfig config; 

    /// <summary> 
    /// Constructors a new SecurityHeader 
    /// </summary> 
    /// <param name="config"><see cref="APIConfig"/></param> 
    public SecurityHeader(APIConfig config) 
    { 
     this.config = config; 
    } 

    /// <summary> 
    /// Gets or sets a value that indicates whether the header must be understood, according to SOAP 1.1/1.2 specification. 
    /// </summary> 
    public override bool MustUnderstand 
    { 
     get 
     { 
      return true; 
     } 
    } 

    /// <summary> 
    /// Gets the name of the message header. 
    /// </summary> 
    public override string Name 
    { 
     get 
     { 
      return "Security"; 
     } 
    } 

    /// <summary> 
    /// Gets the namespace of the message header. 
    /// </summary> 
    public override string Namespace 
    { 
     get 
     { 
      return "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"; 
     } 
    } 

    protected override void OnWriteStartHeader(XmlDictionaryWriter writer, MessageVersion messageVersion) 
    { 
     writer.WriteStartElement("wsse", Name, Namespace); 
     writer.WriteXmlnsAttribute("wsse", Namespace); 
    } 

    /// <summary> 
    /// Called when the header content is serialized using the specified XML writer. 
    /// </summary> 
    /// <param name="writer"><see cref="XmlDictionaryWriter"/></param> 
    /// <param name="messageVersion"><see cref="MessageVersion"/></param> 
    protected override void OnWriteHeaderContents(XmlDictionaryWriter writer, MessageVersion messageVersion) 
    { 
     WriteHeader(writer); 
    } 

    /// <summary> 
    /// Overwrites the default SOAP Security Header values generated by WCF with 
    /// those required by the UserService which implements WSE 2.0. This is required 
    /// for interoperability between a WCF Client and a WSE 2.0 Service. 
    /// </summary> 
    /// <param name="writer"><see cref="XmlDictionaryWriter"/></param> 
    private void WriteHeader(XmlDictionaryWriter writer) 
    { 
     // Create the Nonce 
     byte[] nonce = GenerateNonce(); 

     // Create the Created Date 
     string created = DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ss.fffZ"); 

     // Create the WSSE Security Header, starting with the Username Element 
     writer.WriteStartElement("wsse", "UsernameToken", Namespace); 
     writer.WriteXmlnsAttribute("wsu", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"); 
     writer.WriteStartElement("wsse", "Username", null); 
     writer.WriteString(config.Username); 
     writer.WriteEndElement(); 

     // Add the Password Element 
     writer.WriteStartElement("wsse", "Password", null); 
     writer.WriteAttributeString("Type", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest"); 
     writer.WriteString(GeneratePasswordDigest(nonce, created, config.Password)); 
     writer.WriteEndElement(); 

     // Add the Nonce Element 
     writer.WriteStartElement("wsse", "Nonce", null); 
     writer.WriteAttributeString("EncodingType", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary"); 
     writer.WriteBase64(nonce, 0, nonce.Length); 
     writer.WriteEndElement(); 

     // Lastly, add the Created Element 
     writer.WriteStartElement("wsu", "Created", null); 
     writer.WriteString(created); 
     writer.WriteEndElement(); 
     writer.WriteEndElement(); 
     writer.Flush(); 
    } 

    /// <summary> 
    /// Generates a random Nonce for encryption purposes 
    /// </summary> 
    /// <returns>byte[]</returns> 
    private byte[] GenerateNonce() 
    { 
     RNGCryptoServiceProvider rand = new RNGCryptoServiceProvider(); 
     byte[] buf = new byte[0x10]; 
     rand.GetBytes(buf); 
     return buf; 
    } 

    /// <summary> 
    /// Generates the PasswordDigest using a SHA1 Hash 
    /// </summary> 
    /// <param name="nonceBytes">byte[]</param> 
    /// <param name="created">string</param> 
    /// <param name="password">string</param> 
    /// <returns>string</returns> 
    private string GeneratePasswordDigest(byte[] nonceBytes, string created, string password) 
    { 
     // Convert the values to be hashed to bytes 
     byte[] createdBytes = Encoding.UTF8.GetBytes(created); 
     byte[] passwordBytes = Encoding.UTF8.GetBytes(password); 
     byte[] msgBytes = new byte[nonceBytes.Length + createdBytes.Length + passwordBytes.Length]; 

     // Combine the values into one byte array 
     Array.Copy(nonceBytes, msgBytes, nonceBytes.Length); 
     Array.Copy(createdBytes, 0, msgBytes, nonceBytes.Length, createdBytes.Length); 
     Array.Copy(passwordBytes, 0, msgBytes, (nonceBytes.Length + createdBytes.Length), passwordBytes.Length); 

     // Generate the hash 
     SHA1CryptoServiceProvider sha1 = new SHA1CryptoServiceProvider(); 
     byte[] hashBytes = sha1.ComputeHash(msgBytes); 
     return Convert.ToBase64String(hashBytes); 
    } 
} 

public class APIConfig 
{ 
    /// <summary> 
    /// Gets or Sets the Password property 
    /// </summary> 
    public string Password 
    { 
     get; 
     set; 
    } 

    /// <summary> 
    /// Gets or Sets the Username property 
    /// </summary> 
    public string Username 
    { 
     get; 
     set; 
    } 
} 

z Powyższy kod jestem w stanie stworzyć ten wniosek:

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"> 
    <s:Header> 
     <wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"> 
      <wsse:UsernameToken xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"> 
       <wsse:Username>Demo</wsse:Username> 
       <wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest">1TiCoKWfNF3EdEH3qdU4inKklaw=</wsse:Password> 
       <wsse:Nonce EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">mAyz3SywR8sR9IkhDGJRIw==</wsse:Nonce> 
       <wsu:Created>2017-02-09T23:29:14.371Z</wsu:Created> 
      </wsse:UsernameToken> 
     </wsse:Security> 
    </s:Header> 
    <s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> 
     <query xmlns="http://dom.query.api.com"> 
      <param> 
       <userQueryId xsi:nil="true" xmlns="http://dom.query.api.com/xsd"/> 
      </param> 
     </query> 
    </s:Body> 
</s:Envelope> 

Jak widać jestem brakuje BinarySecurityToken i Signature elementy w moim Security elementu. Próbowałem używać Microsoft.Web.Services3, ale bez powodzenia.
Na przykład konstruktor BinarySecurityToken jest chroniony.

Mam certyfikat mojego klienta zaimportowany do mojego sklepu z certyfikatami. Muszę podpisać tylko treść mojej prośby.

Jak mogę dodać te dwa elementy do elementu Security wewnątrz Header? Wiem, że muszę użyć Microsoft.Web.Services3, ale nie wiem jak.

Szukałem w internecie na podobne pytania, ale wszystko znalazłem tutoriale na temat sposobu dodawania nazwy użytkownika i hasła, pytania o dodanie Signature i BinarySecurityToken pozostaje bez odpowiedzi - How to sign xml with X509 cert, add digest value and signature to xml template

Odpowiedz

4

to zakodowane wiążące powinny produkować podobny wiadomość:

var b = new CustomBinding(); 

      var sec = (AsymmetricSecurityBindingElement)SecurityBindingElement.CreateMutualCertificateBindingElement(MessageSecurityVersion.WSSecurity10WSTrust13WSSecureConversation13WSSecurityPolicy12BasicSecurityProfile10); 
      sec.EndpointSupportingTokenParameters.Signed.Add(new UserNameSecurityTokenParameters()); 
      sec.MessageSecurityVersion = 
       MessageSecurityVersion. 
        WSSecurity10WSTrust13WSSecureConversation13WSSecurityPolicy12BasicSecurityProfile10; 
      sec.IncludeTimestamp = false; 
      sec.MessageProtectionOrder = System.ServiceModel.Security.MessageProtectionOrder.EncryptBeforeSign; 

      b.Elements.Add(sec); 
      b.Elements.Add(new TextMessageEncodingBindingElement(MessageVersion.Soap11, Encoding.UTF8)); 
      b.Elements.Add(new HttpsTransportBindingElement()); 


      var c = 
       new ServiceReference1.SimpleServiceSoapClient(b, new EndpointAddress(new Uri("https://www.bankhapoalim.co.il/"), new DnsEndpointIdentity("WSE2QuickStartServer"), new AddressHeaderCollection())); 

      c.ClientCredentials.UserName.UserName = "yaron"; 
      c.ClientCredentials.UserName.Password = "1234"; 

      c.ClientCredentials.ServiceCertificate.Authentication.CertificateValidationMode = 
       System.ServiceModel.Security.X509CertificateValidationMode.None; 
      c.ClientCredentials.ServiceCertificate.DefaultCertificate = new X509Certificate2(@"C:\Program Files\Microsoft WSE\v2.0\Samples\Sample Test Certificates\Server Public.cer"); 

      c.ClientCredentials.ClientCertificate.Certificate = new X509Certificate2(@"C:\Program Files\Microsoft WSE\v2.0\Samples\Sample Test Certificates\Client Private.pfx", "wse2qs"); 

      c.Endpoint.Contract.ProtectionLevel = System.Net.Security.ProtectionLevel.Sign; 

Wybrana ścieżka wymagałaby samodzielnego wdrożenia podpisywania wiadomości.

+0

Dziękuję bardzo za odpowiedź. Żądanie stworzone przez Twój kod wygląda prawie dobrze, brakuje tylko 'Nonce' w UsernameToken. Czy powinienem używać niestandardowego 'WSSecurityTokenSerializer'? jak pokazano tutaj: https://weblog.west-wind.com/posts/2012/nov/24/wcf-wssecurity-and-wse-nonce-authentication. Potrzebuję zarówno "Nonce", jak i ciała podpisanego w nagłówku bezpieczeństwa. – Misiu

+0

nie wiesz, czy ten link zadziała, powinieneś spróbować. w przeciwnym razie powinny istnieć inne sposoby tworzenia niestandardowego tokena użytkownika w wcf, np. https://msdn.microsoft.com/en-us/library/ms731872(v=vs.110).aspx. również może jeśli utworzysz własną nazwę użytkownika w inspektorze wiadomości, to nadal będzie on działał (w tym przypadku usuń go z konfiguracji kodu). Inną opcją jest zaimplementowanie niestandardowego kodera, który dodaje nonce do nazwy użytkownika (użyj https://gist.github.com/yaronn/44bb89cd14c54b24240d152e64ab37bb jako bazy), ale ostrożnie z białą spacją, aby nie unieważnić podpisu. –

+0

Próbowałem używać mojego 'MessageInspector', ale tworzy on dwa znaczniki' o: Security' w 's: Header', jeden zawierający' o: UsernameToken' z 'Nonce' i drugi bez niego, ale z' BinarySecurityToken' i 'Signature' – Misiu