How to authenticate a client using a certificate in ServiceStack?

I am studying using ServiceStack as an alternative to WCF. One of my requirements is that the server and client must mutually authenticate using certificates. The client is a service, so I can’t use any type of authentication, which includes user input. In addition, the client must run on Linux using mono, so there is no Windows authentication.

I linked my server certificate to my server port using netsh.exe, confirmed that the client receives the server certificate, and the data is encrypted using wirehark. However, I can’t understand for life how to configure the server for a client certificate.

Some people have suggested using request filters to verify a client certificate, but this seems very inefficient as each request verifies a client certificate. Performance is a very high priority. Creating a custom IAuthProvider seems promising, but all of the documentation and examples focus on authentication types, which at some point are related to user interaction, not certificates.

https://github.com/ServiceStack/ServiceStack/wiki/Authentication-and-authorization

Can I use certificates for mutual authentication of client and server using my own ServiceStack?

Here is my test service for reference.

public class Host : AppHostHttpListenerBase { public Host() : base("Self-hosted thing", typeof(PutValueService).Assembly) { //TODO - add custom IAuthProvider to validate the client certificate? this.RequestFilters.Add(ValidateRequest); //add protobuf plugin //https://github.com/ServiceStack/ServiceStack/wiki/Protobuf-format Plugins.Add(new ProtoBufFormat()); //register protobuf base.ContentTypeFilters.Register(ContentType.ProtoBuf, (reqCtx, res, stream) => ProtoBuf.Serializer.NonGeneric.Serialize(stream, res), ProtoBuf.Serializer.NonGeneric.Deserialize); } public override void Configure(Funq.Container container) {} void ValidateRequest(IHttpRequest request, IHttpResponse response, object dto) { //TODO - get client certificate? } } [DataContract] [Route("/putvalue", "POST")] //dto public class PutValueMessage : IReturnVoid { [DataMember(Order=1)] public string StreamID { get; set; } [DataMember(Order=2)] public byte[] Data { get; set; } } //service public class PutValueService : Service { public void Any(PutValueMessage request) { //Comment out for performance testing Console.WriteLine(DateTime.Now); Console.WriteLine(request.StreamID); Console.WriteLine(Encoding.UTF8.GetString(request.Data)); } } 
+7
c # servicestack client-certificates mutual-authentication
source share
1 answer

Some people have suggested using request filters to verify a client certificate, but this seems very inefficient as each request verifies a client certificate. Performance is a very high priority.

REST is stateless, so if you do not want to check the client certificate for each request, you need to provide an alternative authentication token to show that a valid identifier is already provided.

This way you can avoid checking the certificate for subsequent requests if, after authenticating the client certificate, you provide the client with a session identifier cookie, which can be verified instead.

However, I can’t understand for life how to configure the server for a client certificate.

The client certificate is only available in the source object of the HTTP request, which means that you need to pass the request object to access this value. The code below is for querying a ListenerRequest request, which is used by self-service.

Server process:

The query filter will check:

  • First, for a valid session cookie, which, if valid, will allow the request without further processing, therefore, does not require verification of the client certificate on subsequent requests.

  • If no valid session is found, the filter will try to validate the client certificate request. If it exists, try to match it based on some criteria and, after acceptance, create a session for the client and return the cookie.

  • If the client certificate has not been mapped, throw an authorization exception.

 GlobalRequestFilters.Add((req, res, requestDto) => { // Check for the session cookie const string cookieName = "auth"; var sessionCookie = req.GetCookieValue(cookieName); if(sessionCookie != null) { // Try authenticate using the session cookie var cache = req.GetCacheClient(); var session = cache.Get<MySession>(sessionCookie); if(session != null && session.Expires > DateTime.Now) { // Session is valid permit the request return; } } // Fallback to checking the client certificate var originalRequest = req.OriginalRequest as ListenerRequest; if(originalRequest != null) { // Get the certificate from the request var certificate = originalRequest.HttpRequest.GetClientCertificate(); /* * Check the certificate is valid * (Replace with your own checks here) * You can do this by checking a database of known certificate serial numbers or the public key etc. * * If you need database access you can resolve it from the container * var db = HostContext.TryResolve<IDbConnection>(); */ bool isValid = certificate != null && certificate.SerialNumber == "XXXXXXXXXXXXXXXX"; // Handle valid certificates if(isValid) { // Create a session for the user var sessionId = SessionExtensions.CreateRandomBase64Id(); var expiration = DateTime.Now.AddHours(1); var session = new MySession { Id = sessionId, Name = certificate.SubjectName, ClientCertificateSerialNumber = certificate.SerialNumber, Expires = expiration }; // Add the session to the cache var cache = req.GetCacheClient(); cache.Add<MySession>(sessionId, session); // Set the session cookie res.SetCookie(cookieName, sessionId, expiration); // Permit the request return; } } // No valid session cookie or client certificate throw new HttpError(System.Net.HttpStatusCode.Unauthorized, "401", "A valid client certificate or session is required"); }); 

In this case, a custom session class called MySession , which you can replace with your own session object as needed.

 public class MySession { public string Id { get; set; } public DateTime Expires { get; set; } public string Name { get; set; } public string ClientCertificateSerialNumber { get; set; } } 

Client process:

The client needs to install a client certificate to send with the request.

 var client = new JsonServiceClient("https://servername:port/"); client.RequestFilter += (httpReq) => { var certificate = ... // Load the client certificate httpReq.ClientCertificates.Add( certificate ); }; 

After you make the first request to the server, your client will receive a session identifier cookie, and you can delete the client’s certificate so that it is sent until the session becomes invalid.

I hope this helps.

+9
source share

All Articles