WCF: Passing Kerboros token via HTTP call instead of HTTPS - wcf

I want to pass Kerberos token via HTTP call to a server using WCF.
I have a piece of code that successfully does this. But it only works if I make a request to HTTPS URI.
var httpBinding = new WebHttpBinding(WebHttpSecurityMode.Transport) { MaxReceivedMessageSize = Int32.MaxValue };
httpBinding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Windows;
httpBinding.Security.Transport.Realm = "MyCompany.com";
var endPoint = new EndpointAddress("https:xxxxxxxx.com/my/service); // This works
var endPoint = new EndpointAddress("http:xxxxxxxx.com/my/service); // This does not work
var channelFactory = new ChannelFactory<IMyServiceContract>(httpBinding, endPoint);
channelFactory.Endpoint.Behaviors.Add(new WebHttpBehavior());
_channel = channelFactory.CreateChannel();
_channel.ConsumeService();
If I make a request via the channel, and if the end point is https. It works and I can verify the Kerberos Token is in the HTTP requst.
If the service end point is HTTP, it gives error:
System.ArgumentException : The provided URI scheme 'http' is invalid; expected 'https'.
Parameter name: via
Can someone let me know how to Configure WCF so that it send Kerboros token with HTTP URI.
Regards,
Kevin

When you don't want to use HTTPS you must set your security mode to WebHttpSecurityMode.TransportCredentialOnly. If you use WebHttpSecurityMode.Transport it demands HTTPS.

Related

Can I self-host an HTTPS service in WCF without the certificate store and without using netsh http add sslcert?

I am attempting to host a service that serves up basic web content (HTML, javascript, json) using a WebHttpBinding with minimal administrator involvement.
Thus far I have been successful, the only admin priviledges necessary are at install time (register the http reservation for the service account and to create the service itself). However, now I am running into issues with SSL. Ideally I would like to support a certificate outside the windows certificate store. I found this article - http://www.codeproject.com/KB/WCF/wcfcertificates.aspx - which seems to indicate you can specify the certificate on the service host, however at runtime navigating a browser to https://localhost/Dev/MyService results in a 404.
[ServiceContract]
public interface IWhoAmIService
{
[OperationContract]
[WebInvoke(
Method = "GET",
UriTemplate = "/")]
Stream WhoAmI();
}
public class WhoAmIService : IWhoAmIService
{
public Stream WhoAmI()
{
string html = "<html><head><title>Hello, world!</title></head><body><p>Hello from {0}</p></body></html>";
html = string.Format(html, WindowsIdentity.GetCurrent().Name);
WebOperationContext.Current.OutgoingResponse.ContentType = "text/html";
return new MemoryStream(Encoding.UTF8.GetBytes(html));
}
}
static void Main(string[] args)
{
ServiceHost host = new ServiceHost(typeof(WhoAmIService), new Uri("https://localhost:443/Dev/WhoAmI"));
host.Credentials.ServiceCertificate.Certificate = new X509Certificate2(#"D:\dev\Server.pfx", "private");
WebHttpBehavior behvior = new WebHttpBehavior();
behvior.DefaultBodyStyle = WebMessageBodyStyle.Bare;
behvior.DefaultOutgoingResponseFormat = WebMessageFormat.Json;
behvior.AutomaticFormatSelectionEnabled = false;
WebHttpBinding secureBinding = new WebHttpBinding();
secureBinding.Security.Mode = WebHttpSecurityMode.Transport;
secureBinding.Security.Transport.ClientCredentialType = HttpClientCredentialType.None;
ServiceEndpoint secureEndpoint = host.AddServiceEndpoint(typeof(IWhoAmIService), secureBinding, "");
secureEndpoint.Behaviors.Add(behvior);
host.Open();
Console.WriteLine("Press enter to exit...");
Console.ReadLine();
host.Close();
}
If I change my binding security to none and the base uri to start with http, it serves up okay. This post seems to indicate that an additional command needs to be executed to register a certificate with a port with netsh (http://social.msdn.microsoft.com/Forums/en-US/wcf/thread/6907d765-7d4c-48e8-9e29-3ac5b4b9c405/). When I try this, it fails with some obscure error (1312).
C:\Windows\system32>netsh http add sslcert ipport=0.0.0.0:443 certhash=0b740a29f
29f2cc795bf4f8730b83f303f26a6d5 appid={00112233-4455-6677-8899-AABBCCDDEEFF}
SSL Certificate add failed, Error: 1312
A specified logon session does not exist. It may already have been terminated.
How can I host this service using HTTPS without the Windows Certificate Store?
It is not possible. HTTPS is provided on OS level (http.sys kernel driver) - it is the same as providing HTTP reservation and OS level demands certificate in certificate store. You must use netsh to assign the certificate to selected port and allow accessing the private key.
The article uses certificates from files because it doesn't use HTTPS. It uses message security and message security is not possible (unless you develop your own non-interoperable) with REST services and webHttpBinding.
The only way to make this work with HTTPS is not using built-in HTTP processing dependent on http.sys = you will either have to implement whole HTTP yourselves and prepare new HTTP channel for WCF or you will have to find such implementation.

WCF : Configuring message security programmatically

I'm coding an Azure WCF Service Bus service, which is to be configured programmatically to have message security using certificates:
ServiceBusEnvironment.SystemConnectivity.Mode = ConnectivityMode.Tcp;
// create the service URI based on the service namespace
Uri address = ServiceBusEnvironment.CreateServiceUri("sb", ConfigurationManager.AppSettings["serviceNamespace"], "TestService");
// create the credentials object for the endpoint
TransportClientEndpointBehavior sharedSecretServiceBusCredential = new TransportClientEndpointBehavior();
sharedSecretServiceBusCredential.TokenProvider = TokenProvider.CreateSharedSecretTokenProvider(ConfigurationManager.AppSettings["issuerName"], ConfigurationManager.AppSettings["issuerSecret"]);
//Create and bind the serviceEndpoint
ContractDescription contractDescription = ContractDescription.GetContract(typeof(ITestContract), typeof(TestServiceImpl));
ServiceEndpoint serviceEndPoint = new ServiceEndpoint(contractDescription);
serviceEndPoint.Address = new EndpointAddress(address);
var NetTcpRelayBinding = new NetTcpRelayBinding(EndToEndSecurityMode.TransportWithMessageCredential, RelayClientAuthenticationType.RelayAccessToken);
NetTcpRelayBinding.Security.Message.ClientCredentialType = MessageCredentialType.Certificate; //The serivice will check the TrustedPeople store for the client
serviceEndPoint.Binding = NetTcpRelayBinding;
serviceEndPoint.Behaviors.Add(sharedSecretServiceBusCredential);
Host = new ServiceHost(typeof(TestServiceImpl), address);
//Add a service certificate
Host.Credentials.ClientCertificate.Authentication.CertificateValidationMode = X509CertificateValidationMode.PeerTrust;
Host.Credentials.ServiceCertificate.SetCertificate(StoreLocation.LocalMachine,StoreName.My,X509FindType.FindByThumbprint,"E86870F0118CE39D771A49B9337C28444F3C7348");
// create the service host reading the configuration
Host.Description.Endpoints.Add(serviceEndPoint);
I can get this service up and running, however, any client )with just the ServiceBus SharedSecret, clientCredentials NOT set to use any cert) is able to call my service without any errors.
Is the above code sufficient to indicate that certificates (and only certificates base authorization) should be used for message security ?
Any good articles on configuring WCF message security programmatically ?
Turns out that lack of sleep was the culprit; I was running an older version of the service. Clients without any certificates do error out (with System.ServiceModel.ProtocolException was unhandled Message=Error while reading message framing format at position 1 of stream (state: Start).
A properly coded up client for this is :
ServiceBusEnvironment.SystemConnectivity.Mode = ConnectivityMode.Tcp;
string serviceNamespace = "valid-namespace";
string issuerName = "owner";
string issuerSecret = "validSecret";
// create the service URI based on the service namespace
Uri serviceUri = ServiceBusEnvironment.CreateServiceUri("sb", serviceNamespace, "valid-namespace");
// create the credentials object for the endpoint
TransportClientEndpointBehavior sharedSecretServiceBusCredential = new TransportClientEndpointBehavior();
sharedSecretServiceBusCredential.CredentialType = TransportClientCredentialType.SharedSecret;
sharedSecretServiceBusCredential.Credentials.SharedSecret.IssuerName = issuerName;
sharedSecretServiceBusCredential.Credentials.SharedSecret.IssuerSecret = issuerSecret;
ChannelFactory<ITestChannel> channelFactory = new ChannelFactory<ITestChannel>();
channelFactory.Endpoint.Address = new EndpointAddress(serviceUri);
var NTRB = new NetTcpRelayBinding();
NTRB.Security.Mode = EndToEndSecurityMode.TransportWithMessageCredential;
NTRB.Security.Message.ClientCredentialType = MessageCredentialType.Certificate;
channelFactory.Endpoint.Binding = NTRB;
channelFactory.Endpoint.Contract.ContractType = typeof(ITestChannel);
// apply the Service Bus credentials
channelFactory.Endpoint.Behaviors.Add(sharedSecretServiceBusCredential);
//Question : Why doesn't use of the following line effect Service-Validation ? I can successfully call the service from a machine where the server's certificate does NOT exist in the trusted-people store
//channelFactory.Credentials.ServiceCertificate.Authentication.CertificateValidationMode = System.ServiceModel.Security.X509CertificateValidationMode.PeerTrust;
channelFactory.Credentials.ClientCertificate.SetCertificate(StoreLocation.LocalMachine, StoreName.My, X509FindType.FindByThumbprint, "valid-thubmprint");
// create and open the client channel
ITestChannel channel = channelFactory.CreateChannel();
Console.WriteLine(channel.ServiceMethod());
Console.ReadKey();
channel.Close();
channelFactory.Close();
Still have the problem of the ServiceCertificate always being assumed valid, even when PeerTrust is used for channelFactory.Credentials.ServiceCertificate.Authentication.CertificateValidationMode and the service certificate isn't in the TrustedPeople store.
Anyone with ideas on why this happens ?

Adding authentication to security header in WCF to consume Metro WSIT service

I use this simple way to attach username and password to the SOAP request header. This works fine inside Java boundaries, but I want to be able to call it with my WCF client. How do I do this?
I've tried the following code, but it does not include the credentials in the header:
wsClient.ClientCredentials.UserName.UserName = "Hello";
wsClient.ClientCredentials.UserName.Password = "World";
Thanks in advance!
That is quite awful non-standardized way. It uses custom HTTP Headers so you cannot expect that built in WCF mechanism will magically support such approach. How should WCF know that you want to add custom non-standard HTTP header to HTTP request (not SOAP header)?
Use this:
var proxy = new YourServiceClient();
using (var scope = new OperationContextScope(proxy.InnerChannel))
{
var prop = new HttpRequestMessageProperty();
prop.Headers.Add("UserName", "Hello");
prop.Headers.Add("Password", "World");
OperationContext context = OperationContext.Current;
context.OutgoingMessageProperties[HttpRequestMessageProperty.Name] = prop;
proxy.CallYourOperation();
}

WCF, REST, SSL, Client, custom certificate validation

I have a specific problem that I can't solve. Let me explain in detail. I'm new to this technology so I might be using some wrong terms. Please correct and explain or ask for explanation if you don't understand.
I am creating a self hosted WCF REST server, hosted in WPF application. It uses https, SLL with WebHttpSecurityMode.Transport. I am using my own generated certificate.
I would like to create a WinForms client that would use this service. The format of the response form the server is JSON.
I would like to validate the certificate on the client with my custom validator inherited from X509CertificateValidator.
This is my server side code. I'm using a custom username validator that works fine. I have configured the certificate in the IIS Manager on my machine for the Default Website > Bindings, where I have generated the certificate (Windows 7).
WebServiceHost sh = new WebServiceHost(typeof(ReachService));
string uri = "https://localhost:9000/Service";
WebHttpBinding wb = new WebHttpBinding();
wb.Security.Mode = WebHttpSecurityMode.Transport;
wb.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic;
sh.AddServiceEndpoint(typeof(IReachService), wb, uri);
sh.Credentials.UserNameAuthentication.CustomUserNamePasswordValidator = new CustomUserNameValidator();
sh.Credentials.UserNameAuthentication.UserNamePasswordValidationMode = UserNamePasswordValidationMode.Custom;
sh.Open();
and this is my client code
Uri uri = new Uri("https://localhost:9000/Service");
WebChannelFactory<ReachService> cf = new WebChannelFactory<IReachService>(uri);
WebHttpBinding wb = cf.Endpoint.Binding as WebHttpBinding;
wb.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic;
wb.Security.Mode = WebHttpSecurityMode.Transport;
cf.Credentials.ServiceCertificate.Authentication.CertificateValidationMode = X509CertificateValidationMode.Custom;
cf.Credentials.ServiceCertificate.Authentication.CustomCertificateValidator = new CustomCertificateValidator("PL2"); // this is the name that issued the certificate
cf.Credentials.UserName.UserName = "user1";
cf.Credentials.UserName.Password = "user1";
IReachService service = cf.CreateChannel();
try
{
CustomersList auth = service.GetCustomers();
}
catch (Exception ex)
{
throw new Exception(ex.Message);
}
on calling service.GetCustomers() I get:
Could not establish trust relationship for the SSL/TLS secure channel with authority
'localhost:9000'.
InnerException Message:
The underlying connection was closed: Could not establish trust relationship for the SSL/TLS secure channel.
InnerException Message:
The remote certificate is invalid according to the validation procedure.
The server is working fine when I test in the browser.
But the client code is wrong cause it doesn't go to the custom cert validator class. And this class is the same as in the MSDN example on http://msdn.microsoft.com/en-us/library/system.identitymodel.selectors.x509certificatevalidator.aspx.
Can anyone please tell me where am I going wrong with this approach?
If you need more info please ask.
Thank you
It looks like the issue occurs because certificate was issued for some other hostname. You can check this (and customize if necessary) by providing custom ServicePointManager.ServerCertificateValidationCallback.
//don't use HttpWebRequest --you lose all of the strongly-typed method and data contracts!
//the code to create the channel and call a method:
SetCertPolicy();
var cf1 = new WebChannelFactory<TService>(new Uri(remoteServiceAddressSecure));
var service = cf1.CreateChannel();
sevice.DoMethod();
protected static void SetCertPolicy()
{
ServicePointManager.ServerCertificateValidationCallback += RemoteCertValidate;
}
private static bool RemoteCertValidate(object sender, X509Certificate cert, X509Chain chain,
SslPolicyErrors error)
{
// trust any cert!!!
return true;
}
If you want to use WCF on the client, then don't use WebHttpBinding, stick with the SOAP stuff it will work much better.
However, if you want to use a standard HTTP client like, WebClient or HttpWebRequest or HttpClient V.prototype or HttpClient V.Next then stick with the webHttpBinding.
Sorry for not addressing your direct question but you are likely to run into more problems because you are using a binding that was intended to make WCF services accessible to non-WCF platforms but then using WCF to try and access it.

HttpListner: intercept requests to WCF DataService

I want to use the .net class HttpListener to intercept requests to my selfhosted (WebServiceHost) WCF Data Service in order to add the "WWW-Authenticate" header to the response (for user authentication). But it seems like that the HttpListener doesn't intercept any requests that go to my dataservice. The HttpListner works for different paths just fine. Example: HttpListner Prefix: http://localhost/somePath/Works: http://localhost/somePath/Doesn't Work: http://localhost/somePath/myWCFDataService
Is it possible to intercept also requests that go to a selfhosted WCF Data Service (WebServiceHost) with the HttpListner?
Here are the relevant code snippets...
Hosting the WCF DataService:
WebServiceHost dataServiceHost = new WebServiceHost(typeof(MyWCFDataService));
WebHttpBinding binding = new WebHttpBinding();
dataServiceHost.AddServiceEndpoint(typeof(IRequestHandler), binding,
"http://localhost/somePath/myWCFDataService");
dataServiceHost.Open();
The HTTP Listner:
HttpListener httpListener = new HttpListener();
httpListener.Prefixes.Add("http://localhost/somePath/");
httpListener.AuthenticationSchemes = AuthenticationSchemes.Anonymous;
httpListener.Start();
while (true)
{
HttpListenerContext context = httpListener.GetContext();
string authorization = context.Request.Headers["Authorization"];
if (string.IsNullOrEmpty(authorization))
{
context.Response.StatusCode = 401;
context.Response.AddHeader("WWW-Authenticate", "Basic realm=\"myDataService\"");
context.Response.OutputStream.Close();
context.Response.Close();
}
}
Is there a better way for doing HTTP basic authentication within WCF Data Services? I wan't to be able to authenticate via the login dialog of the web browser.
Many thanks,
JeHo
You're barking up the wrong tree messing with proxying via HttpListener. Have a look at this.