问题
I'm trying to connect to a Web Service, which is located in rather non-standard environment.
Before IIS server with a Web Service I have a reverse proxy server, which requires a client certificate to authenticate the connection. After that the Web Service itself requires additional UserName authentication. Moreover Web Service uses basicHttpsBinding with streamed transfer mode, as sending of quite large binary files is required (up to 1GB).
The problem is that I can't get it working on the client side. I tried to use following binding security configuration on client side:
<security mode="TransportWithMessageCredential">
<transport clientCredentialType="Certificate" />
<message clientCredentialType="UserName" />
</security>
The certificate itself is attached in code:
client.ClientCredentials.ClientCertificate.SetCertificate(
System.Security.Cryptography.X509Certificates.StoreLocation.CurrentUser, System.Security.Cryptography.X509Certificates.StoreName.My,
System.Security.Cryptography.X509Certificates.X509FindType.FindByThumbprint, "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX");
Unfortunately I got a SecurityNegotiationException: Could not establish secure channel for SSL/TLS with authority "xxx". When I checked the captured network traffic with Wireshark I could see, that after server's certificate request client didn't sent any certificate:
Transmission Control Protocol, Src Port: 8325, Dst Port: 443, Seq: 174, Ack: 3566, Len: 197
Secure Sockets Layer
TLSv1.2 Record Layer: Handshake Protocol: Multiple Handshake Messages
Content Type: Handshake (22)
Version: TLS 1.2 (0x0303)
Length: 141
Handshake Protocol: Certificate
Handshake Type: Certificate (11)
Length: 3
Certificates Length: 0
Handshake Protocol: Client Key Exchange
TLSv1.2 Record Layer: Change Cipher Spec Protocol: Change Cipher Spec
TLSv1.2 Record Layer: Handshake Protocol: Encrypted Handshake Message
However, if I switch only to Transport security mode:
<security mode="Transport">
<transport clientCredentialType="Certificate" />
<message clientCredentialType="UserName" />
</security>
the certificate is clearly passed:
Transmission Control Protocol, Src Port: 8894, Dst Port: 443, Seq: 174, Ack: 3566, Len: 3215
Secure Sockets Layer
TLSv1.2 Record Layer: Handshake Protocol: Multiple Handshake Messages
Content Type: Handshake (22)
Version: TLS 1.2 (0x0303)
Length: 3159
Handshake Protocol: Certificate
Handshake Type: Certificate (11)
Length: 2757
Certificates Length: 2754
Certificates (2754 bytes)
Certificate Length: 1261
Certificate: 308204e9308203d1a003020102020e78d7243f087eb6a900... (pkcs-9-at-emailAddress=xxx@domain,id-at-commonName=xxx)
Certificate Length: 1487
Certificate: 308205cb308203b3a003020102020e1882d07958679ac300... (id-at-commonName=xxx,id-at-organizationalUnitName=xxx,id-at-organizationName=xxx,id-at-countryName=xxx)
Handshake Protocol: Client Key Exchange
Handshake Protocol: Certificate Verify
TLSv1.2 Record Layer: Change Cipher Spec Protocol: Change Cipher Spec
TLSv1.2 Record Layer: Handshake Protocol: Encrypted Handshake Message
Now the communication is passed forward by reverse proxy, but is rejected by IIS with following exception:
System.ServiceModel.Security.MessageSecurityException: Security processor was unable to find a security header in the message. This might be because the message is an unsecured fault or because there is a binding mismatch between the communicating parties. This can occur if the service is configured for security and the client is not using security.
This is clear, since I explicitly switched off the message credentials.
Unfortunately I didn't manage to find any precise information if basicHttpsBinding with TransportWithMessageCredentials security mode does, or does not support Certificate for transport and UserName for message authentication.
Have anybody tried similar configuration? I found for instance the following article How to setup a WCF service using basic Http bindings with SSL transport level security but is doesn't show how to attach specific client certificate if a proxy on the server side requests one.
I will be very grateful for any hint.
回答1:
As far as I know, BasicHttpBinding support certificate for transport and username for message authentication. Here is an example I wrote before, wish it is useful to you.
Server(10.157.13.69, Console application)
class Program
{
static void Main(string[] args)
{
Uri uri = new Uri("https://localhost:11011");
BasicHttpBinding binding = new BasicHttpBinding();
binding.Security.Mode = BasicHttpSecurityMode.TransportWithMessageCredential;
binding.Security.Message.ClientCredentialType = BasicHttpMessageCredentialType.UserName;
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.None;
using (ServiceHost sh = new ServiceHost(typeof(MyService), uri))
{
sh.AddServiceEndpoint(typeof(IService), binding, "");
ServiceMetadataBehavior smb;
smb = sh.Description.Behaviors.Find<ServiceMetadataBehavior>();
if (smb == null)
{
smb = new ServiceMetadataBehavior()
{
HttpsGetEnabled = true
};
sh.Description.Behaviors.Add(smb);
}
sh.Credentials.UserNameAuthentication.UserNamePasswordValidationMode = System.ServiceModel.Security.UserNamePasswordValidationMode.Custom;
sh.Credentials.UserNameAuthentication.CustomUserNamePasswordValidator = new CustUserNamePasswordVal();
Binding mexbinding = MetadataExchangeBindings.CreateMexHttpsBinding();
sh.AddServiceEndpoint(typeof(IMetadataExchange), mexbinding, "mex");
sh.Opened += delegate
{
Console.WriteLine("Service is ready");
};
sh.Closed += delegate
{
Console.WriteLine("Service is clsoed");
};
sh.Open();
Console.ReadLine();
sh.Close();
Console.ReadLine();
}
}
}
[ServiceContract]
public interface IService
{
[OperationContract]
string SayHello();
}
public class MyService : IService
{
public string SayHello()
{
return $"Hello Stranger,{DateTime.Now.ToLongTimeString()}";
}
}
internal class CustUserNamePasswordVal : UserNamePasswordValidator
{
public override void Validate(string userName, string password)
{
if (userName != "jack" || password != "123456")
{
throw new Exception("Username/Password is not correct");
}
}
}
Binding the certificate to the port.
netsh http add sslcert ipport=0.0.0.0:11011 certhash=6e48c590717cb2c61da97346d5901b260e983850 appid={ED4CE60F-6B2E-4EE6-828F-C1A6A1B12565}
Client end (calling service by adding service reference)
var client = new ServiceReference1.ServiceClient();
client.ClientCredentials.UserName.UserName = "jack";
client.ClientCredentials.UserName.Password = "123456";
try
{
var result = client.SayHello();
Console.WriteLine(result);
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
App.config(Auto-generated)
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="BasicHttpBinding_IService">
<security mode="TransportWithMessageCredential" />
</binding>
</basicHttpBinding>
</bindings>
<client>
<endpoint address="https://10.157.13.69:11011/" binding="basicHttpBinding"
bindingConfiguration="BasicHttpBinding_IService" contract="ServiceReference1.IService"
name="BasicHttpBinding_IService" />
</client>
</system.serviceModel>
Feel free to contact me if there is anything I can help with.
来源:https://stackoverflow.com/questions/55119756/use-both-certificate-and-username-for-client-authentication-in-wcf-with-basichtt