This document describes how to accomplish EWP client authentication with the use of HTTP Signatures.
This client authentication method makes use of:
- The
Digest
header, specified in RFC 3230. - The
SHA-256
Digest Algorithm, specified in RFC 5843. - The
Authorization
header, specified in draft-cavage-http-signatures-07 (presumably, soon to become RFC), along with itsrsa-sha256
signature algorithm. - Optional "Nonce & Timestamp" replay attack prevention.
The order of the following steps seems optimal, but you MAY perform them in different order, if you wish (as long as you perform all of them).
Make sure that:
-
The client's request contains the
Authorization
header with theSignature
method, as specified here. -
The
algorithm
parameter of theAuthorization
header is equal torsa-sha256
. You MAY support other algorithms too, butrsa-sha256
is (currently) the only one required. -
The
headers
parameter of theAuthorization
header contains at least the following values:(request-target)
,host
,date
ororiginal-date
(it MUST contain at least one of those, it also MAY contain both),digest
,x-request-id
.
Note, that you MUST allow
headers
parameter to contain more values than the required ones listed here.
If one or more of these conditions is not met, then the client doesn't use this method of authentication, or is using it incorrectly. In this case:
-
If you support other client authentication methods at this endpoint, then you SHOULD check if the client doesn't use any of those.
-
If the client doesn't use any of the supported authentication methods, then you MUST respond with HTTP 400 or HTTP 401 error response, with a proper
<developer-message>
(it SHOULD describe the reason why you consider the request to be invalid).If HTTP Signature Authentication is the preferred method of authentication at this endpoint, then is it RECOMMENDED to respond with HTTP 401, and include the following headers:
WWW-Authenticate
, as described here. Yourrealm
value SHOULD beEWP
.Want-Digest
, as described here.
For example:
WWW-Authenticate: Signature realm="EWP" Want-Digest: SHA-256
You MUST verify that the value of the Host
header included in the request
matches your own domain (the one on which the endpoint is served).
This verification is sometimes handled by the web server itself, and you simply won't receive requests which don't match your host. If you're not sure, then verify it in your code anyway.
Extract the keyId
from the request's Authorization
header. (In case of
problems, respond with HTTP 400 error message.)
The key MUST match at least one of the public client keys published in the
Registry Service. Consult Registry API for
information on how to find a match. If you cannot find a match, then you MUST
respond with HTTP 403 error response. As usual, including a proper
<developer-message>
is RECOMMENDED.
You need to parse and verify the values of the Date
and
Original-Date
headers, if they are included in the
request (at least one of them MUST be). In particular, you MUST verify at least
the ones which have been listed in the request's Authorization
header, but it
is RECOMMENDED to verify both (if both are included in the request).
Verification process consists of parsing the date, and matching it against the date reported by your own server clock. The verification fails if:
-
The date cannot be parsed.
-
The date does not match your server clock within a certain threshold of time.
- It is RECOMMENDED to use the 5 minutes threshold.
- You MAY choose a greater threshold than 5 minutes, but you MUST NOT choose a lower threshold than this.
If the verification fails, then you MUST respond with HTTP 400 error response.
Your error response SHOULD include a proper <developer-message>
.
Also note, that you MUST make sure that your clock is synchronized (otherwise your clients won't be able to use your service).
This step is OPTIONAL if TLS is used for transport (in EWP, most APIs require TLS to be used). However, if TLS is not used (or cannot be trusted for any reason), then this step is REQUIRED in order to prevent replay attacks. Also see Security Considerations section below.
The X-Request-Id
header can be used as a cryptographic
nonce. If you decide to
implement nonce verification, then you MUST reject requests who's nonce has
already been used. You will need to store the set of nonces which have been
used. Thanks to the Date
header (or Original-Date
header), you don't have
to store the nonces which have been used earlier than 5 minutes ago.
Even if you don't verify the nonce in the previous step, it is still
RECOMMENDED to verify the format of the X-Request-Id
header. It SHOULD be an
an UUID formatted in a canonical form, e.g. dc05b425-4e86-4106-8dde-1257fccf53e5
.
If it's not, then you SHOULD respond with HTTP 400 error response.
If you're wondering why we are recommending this, then it's because we want to
force clients to use proper values in their X-Request-Id
header. We expect
that most servers won't be verifying nonces at this time, and without this
recommendation, some clients might be tempted to include "dummy" values in
their X-Request-Id
header, and no servers would notice that. We want the
network to collectively prevent that.
You MUST verify the request's signature, as explained here.
If the signature is invalid, then you MUST respond with a HTTP 400 error
response. Your error response SHOULD include a proper <developer-message>
.
Calculate the Base64-encoded SHA-256 digest of the HTTP request's body,
according to RFC 3230 and RFC 5843. Compare it
to the Digest
header which should be present in the request. The values MUST
match.
In case of mismatch, you MUST respond with HTTP 400 error response. Your error
response SHOULD include a proper <developer-message>
.
Servers MUST ignore all request headers which hadn't been signed by the client. They might have been added by the attacker in transport. This is important especially in cases when TLS is not used in some parts of the transport, or when you don't fully trust the partner's TLS implementation.
The safest way to properly ignore such headers is to modify your Request
object now (during the authentication and authorization process), by either
removing the suspicious headers, or at least changing their name (e.g.
prepending it with Unsigned-
). Then, pass the modified Request
along as the
result of your authentication, so that the actual API you are implementing
believes that the client didn't supply these headers at all. This approach is
much safer than trusting yourself to remember to verify this every time before
you access every header in every single ones of your APIs (and some APIs might
be dependent on request's headers).
In most cases, you will also need to identify which HEI is covered by the requester (most APIs will require that). Note that the requester may not cover any HEI.
In the previous steps you have already found a <host>
element
bound to the client's public key. Now, you will need to build on that
information, and retrieve the HEI this host covers. Consult
Registry API specification for useful hints (i.e., examples of
XPath expressions).
You need to generate an RSA key-pair for your client. You MAY use the same key-pair you use for your TLS communication if you want to.
Each partner declares (in his Manifest file) a list of public keys it will use for communicating with other hosts. This list is later fetched by registry, and the keys (and/or their fingerprints) are served to all other partners see (see Registry API for details).
Public keys are bound to the HEI you cover. Once the server confirms that the client is in possession of a proper private key, it is then able to identify (with the help of the Registry again) which HEI such client covers.
Note that the Registry will verify if your keys meet certain security standards (i.e., their length). These standards MAY change in time.
-
You MUST include either the
Date
orOriginal-Date
header in your request. You MAY include both of them. The format of theOriginal-Date
header, if included, MUST match the "regular" format of theDate
header, as defined in RFC 2616. You MUST make sure that your clock is synchronized (otherwise your request may fail). -
You MUST include
X-Request-Id
header in your request. It's value MUST be an UUID (preferably, version 4), unpredictable and unique for each request, formatted in a canonical form, e.g.dc05b425-4e86-4106-8dde-1257fccf53e5
. If it's not unique, or it is in different format, then your request MAY fail. -
You MUST include the
Digest
header in your request, as explained here. You MUST use SHA-256 algorithm for generating the digest.
You MUST include the Authorization
header in your request, as explained
here. You MUST use the rsa-sha256
signature
algorithm, and you MUST include at least the following values in your
headers
parameter:
(request-target)
,host
,date
ororiginal-date
(it MUST contain at least one of those, it also MAY contain both),digest
,x-request-id
.
If it is important for the server to recognize any other of your headers, then you MUST sign all these headers too. The headers mentioned above are important for handling authentication, non-repudiation and security described in this document, but other headers also MAY be essential in your case.
One such example would be the Accept-Signature
header, as explained
here.
The keyId
parameter of the Authorization
header MUST contain a
HEX-encoded SHA-256 fingerprint of the public key part of the key-pair which
you have used to sign your request. It MUST match one of the keys you
previously published in your manifest file.
Many frameworks or proxies might try to automatically modify your request after you sign it. For example, it might add or replace some headers with more proper values. In many cases, this would be a good thing, but in this case, such changes could break your HTTP Signature. Make sure that you try to disable all such automatic modifications when you use HTTP Signatures for signing.
HTTP Signatures, on their own, do not protect servers against replay attacks. For this reason, most EWP APIs require all communication to occur over TLS. TLS, when properly deployed, does protect the servers against replay attacks.
Some partners pointed out that in some cases we cannot be 100% sure that TLS
has been securely deployed by all the partners. For example, if the client's
machine is tricked into installing attacker's root certificate,
man-in-the-middle attacks are possible. In this scenario, the use of end-to-end
HTTP signatures prevents the attacker from modifying the message, but it
doesn't prevent him from executing replay-attacks. With help of the Date
(Original-Date
) and X-Request-Id
headers, partners MAY implement additional
security measures to prevent such attacks.
It's worth noting, that servers may store the request, along with its headers, in order to be able to prove in the future that the request actually took place. In some cases, for example approving important documents by the other party, this feature might be handy.
The Authentication and Security document recommends that each client authentication method specification explicitly answers the following questions:
How the client's request must look like? How can the server detect that the client is using this particular method for authentication?
See Implementing a client chapter above. The server detect this method by
checking for the existence of a proper set of headers (in particular, the
Authorization: Signature
header).
How can the server verify which HEI is covered by the requester?
This is described in the Identify the covered HEI chapter above (in the Implementing a server section).
How can the server verify that the request has not been tampered with, nor replayed by a third party?
Tampering would invalidate the signature. As to the replay attacks - see a separate chapter above.
Does it provide non-repudiation? Can a server provide a solid proof later on, that a particular request took place, and that it originated from a certain client?
Yes. See Non-repudiation section above.