Signing HTTP Messages in .Net with NSign
One of the things I have been working on at work over the past few months is an
open source implementation for .Net of the
standard-to-be for HTTP message signatures.
I’ve ended up calling this NSign
which granted is a bit broad – the libraries
deal only with HTTP signatures – but I found that the name quite fitting.
The general idea of HTTP message signatures is that clients and/or servers can create and verify digital signatures or message authentication codes over HTTP messages, that is either request or response messages. As the standard-to-be puts this:
Message integrity and authenticity are security properties that are critical to the secure operation of many HTTP applications.
And also:
This document defines a mechanism for providing end-to-end integrity and authenticity for components of an HTTP message. The mechanism allows applications to create digital signatures or message authentication codes (MACs) over only the components of the message that are meaningful and appropriate for the application.
But why is TLS not enough?
While TLS encrypts the messages exchanged between two endpoints, and protects the integrity of the messages themselves, it does not give a server any means for verifying that the client is authorized to send the message. Now you may wonder why you couldn’t just use standard authentication mechanisms to do that? Well, of course you can, and in many cases people are correctly and safely already doing so.
Yet, there are some scenarios where authentication is easier to achieve through
message signatures. Consider for a moment webhooks. An interested party
subscribes for events raised by a server by registering an HTTP(S) endpoint that
the server sends POST
requests to whenever an even of interest is raised. Very
often such requests are routed through the internet and not just on an internal
network. And even on internal networks, for instance in a zero trust environment,
you might not just want to blindly trust that only the service you subscribed
with is sending messages. How will the receiving service (the service owning the
endpoint that gets the POST
request) know that the source of the request is
trustworthy?
Again, of course you can use any standard authentication mechanism, but that
means you’ll need to manage credentials one way or another. After all, the party
sending the POST
requests will need to prove that they are allowed to send.
You would want to discard unauthenticated requests right away.
Wouldn’t it be awesome if the sending service could just sign all outgoing
POST
requests and receiving services could just as easily verify those
signatures?
Digital Signatures FTW
Well that’s exactly where the standard comes in: If the sending service signs outgoing requests with a digital signature, and publishes the public key used for the signature, any receiving service can verify the signature and thus trust messages from the sending service. And as long as the private key is kept secret, only the sending service can create valid signatures!
No further credential management is required, and distribution of the material needed to verify signatures is straightforward and can even leverage mechanisms such as JWKS as used by JWT.
How Was This Done In The Past?
In the past, providers of such webhook integrations / event services often used message authentication code based mechanisms of their own to achieve more or less the same thing. None of this was standardized, and it usually meant dealing with separate credentials per tenant/user that could make subscriptions, let alone a separate mechanisms for every source of events one subscribed to. More code, more sources of problems.
The part about providers using their own message authentication code based mechanisms implies that integration with different providers/platforms means different logic to verify incoming messages. A standard helps to come up with a single flexible and configurable way for many different applications to use digital signatures and message authentication codes.
The part about using message authentication codes implies that providers need to track secrets per tenant/user – secrets that, mind you, need to be shared secrets: both the provider and the subscriber must know the secret. Compare that to asymmetric digital signatures, where the secret need only be known to one party.
What’s Next?
The standard-to-be needs to be made into a standard. Once that has happened, we
are going to evaluate the remaining changes, and we will start publishing a v1
of the libraries. Beyond that, the libraries do not currently offer digital
signatures with EdDSA using curve edwards25519 (for lack of available
implementation in .Net standard libraries), as well as support for the
Accept-Signature
header to announce expectations around signatures. Also,
JWT-based signatures are not supported yet.
Libraries / Nuget Packages Overview
NSign
is actually a set of libraries. Here’s a quick overview.
- NSign.Abstractions
- Holds abstractions, base classes and interfaces, as well as some default implementations needed for message signing. It should be abstract enough to use also with web servers and HTTP clients outside of what Microsoft ships as part of their official packages, such that one can implement HTTP message signatures for such stacks too.
- NSign.SignatureProviders
- Default implementations for signature providers mentioned in the standard. Currently, these include some asymmetric digital signatures: ECDSA using curve P-256 DSS and SHA-256 (ecdsa-p256-sha256), ECDSA using curve P-384 DSS and SHA-384 (ecdsa-p384-sha384), RSASSA-PSS using SHA-512 (rsa-pss-sha512), RSASSA-PKCS1-v1_5 using SHA-256 (rsa-v1_5-sha256). The following symmetric message authentication code is supported: HMAC using SHA-256 (hmac-sha256).
- NSign.AspNetCore
- Implementation for request message signature verification, and response message signing for ASP.NET Core-based web servers, using middlewares to hook into the request/response pipelines.
- NSign.Client
- Implementation for HTTP clients (using
System.Net.Http.HttpClient
) to sign request messages and verify signatures on response messages.
- Implementation for HTTP clients (using
As mentioned in the beginning, this is open source software, so you can check the sources, contribute, file bugs etc. as you see fit. Enjoy!