Skip to content

Commit cb4e130

Browse files
committed
Rewrite Appendix B with RFC9421 HTTP signature examples
- Fix http-sig capability to reference RFC7517 instead of RFC7515 - Add RFC7517 (JWK) and RFC8032 (EdDSA) to references - Fix RFC7515 reference formatting - Replace draft-cavage examples with RFC9421 signature format - Add JWKS endpoint and Ed25519 signing/verification examples
1 parent e48af89 commit cb4e130

File tree

1 file changed

+80
-105
lines changed

1 file changed

+80
-105
lines changed

IETF-RFC.md

Lines changed: 80 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -729,7 +729,7 @@ contain the following information about its OCM API:
729729
for a short-lived bearer token.
730730
_ `"http-sig"` - to indicate that this OCM Server supports
731731
[RFC9421] HTTP Message Signatures and advertises public keys in the
732-
format specified by [RFC7515] at the `/.well-known/jwks.json`
732+
format specified by [RFC7517] at the `/.well-known/jwks.json`
733733
endpoint for signature verification.
734734
_ `"invites"` - to indicate the server would support acting as an
735735
Invite Sender or Invite Receiver OCM Server. This might be useful
@@ -1302,7 +1302,14 @@ https://datatracker.ietf.org/html/rfc6749)", October 2012.
13021302
(URIs)](https://datatracker.ietf.org/doc/html/rfc8615)", May 2019
13031303

13041304
[RFC7515] Jones, M., Bradley, J., Sakimura, N., "[JSON Web Signature
1305-
(JWS)](https://datatracker.ietf.org/doc/html/rfc7515), May 2015."
1305+
(JWS)](https://datatracker.ietf.org/doc/html/rfc7515)", May 2015.
1306+
1307+
[RFC7517] Jones, M., "[JSON Web Key (JWK)](
1308+
https://datatracker.ietf.org/doc/html/rfc7517)", May 2015.
1309+
1310+
[RFC8032] Josefsson, S., Liusvaara, I., "[Edwards-Curve Digital
1311+
Signature Algorithm (EdDSA)](
1312+
https://datatracker.ietf.org/doc/html/rfc8032)", January 2017.
13061313

13071314

13081315
# Appendix A: Multi-factor Authentication
@@ -1328,126 +1335,95 @@ out of scope for this specification: a mechanism similar to the
13281335
[ScienceMesh](https://sciencemesh.io) integration for the
13291336
[Invite](#invite-flow) capability may be envisaged.
13301337

1331-
# Appendix B: Request Signing
1332-
1333-
A request is signed by adding the signature in the headers. The sender
1334-
also needs to expose the public key used to generate the signature. The
1335-
receiver can then validate the signature and therefore the origin of
1336-
the request.
1337-
To help debugging, it is RECOMMENDED to also add all properties used in
1338-
the signature as headers, even if they can easily be re-generated from
1339-
the payload.
1338+
# Appendix B: JWKS and HTTP Signature Examples
13401339

1341-
Note: Signed requests prove the identity of the sender but do not
1342-
encrypt nor affect its payload.
1340+
## JWKS Endpoint
13431341

1344-
Here is an example of headers needed to sign a request.
1342+
An OCM Server that advertises the `http-sig` capability MUST expose its
1343+
public keys at `/.well-known/jwks.json` in the format specified by
1344+
[RFC7517]. Here is an example response from
1345+
`https://sender.example.org/.well-known/jwks.json`:
13451346

1347+
```json
1348+
{
1349+
"keys": [
1350+
{
1351+
"kty": "OKP",
1352+
"crv": "Ed25519",
1353+
"kid": "sender.example.org#key1",
1354+
"x": "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo"
1355+
}
1356+
]
1357+
}
13461358
```
1347-
{
1348-
"@request-target": "post /path",
1349-
"content-length": 380,
1350-
"date": "Mon, 08 Jul 2024 14:16:20 GMT",
1351-
"content-digest": "SHA-256=U7gNVUQiixe5BRbp4...",
1352-
"host": "hostname.of.the.recipient",
1353-
"Signature": "keyId=\"https://author.hostname/key\",algorithm=
1354-
\"rsa-sha256\",headers=\"content-length date digest host\",
1355-
signature=\"DzN12OCS1rsA[...]o0VmxjQooRo6HHabg==\""
1356-
}
1357-
```
13581359

1359-
* '@request-target' (optional) contains the reached endpoint and
1360-
the used method,
1361-
* 'content-length' is the total length of the payload of the
1362-
request,
1363-
* 'date' is the date and time when the request has been
1364-
sent,
1365-
* 'content-digest' is a checksum of the payload of the
1366-
request,
1367-
* 'host' is the hostname of the recipient of the request (remote when
1368-
signing outgoing request, local on incoming request),
1369-
* 'Signature' contains the signature generated using the private key
1370-
and details on its generation:
1371-
- 'keyId' is a unique id, formatted as an url; hostname is used to
1372-
retrieve the public key via custom discovery
1373-
- 'algorithm' specify the algorithm used to generate signature
1374-
- 'headers' specify the properties used when generating the
1375-
signature
1376-
- 'signature' the signature of an array containing the properties
1377-
listed in 'headers'. Some properties like content-length, date,
1378-
content-digest, and host are mandatory to protect against
1379-
authenticity override.
1380-
1381-
## How to generate the Signature for outgoing request
1382-
1383-
After properties are set in the headers, the Signature is generated and
1384-
added to the list.
1385-
1386-
This is a pseudo-code example for generating the `Signature` header for
1387-
outgoing requests:
1360+
## Signing a Request (Sender)
1361+
1362+
Given a Share Creation Notification request:
13881363

13891364
```
1390-
headers = {
1391-
'content-length': length_of(payload),
1392-
# Use a function to get the current GMT date as 'D, d M Y H:i:s T'
1393-
'date': current_gmt_datetime(),
1394-
'content-digest': 'SHA-256=' + base64_encode(hash('sha256',
1395-
utf8_encode(payload))),
1396-
'host': 'recipient-fqdn',
1397-
}
1365+
POST /ocm/shares HTTP/1.1
1366+
Host: receiver.example.org
1367+
Date: Tue, 20 Apr 2021 02:07:55 GMT
1368+
Content-Type: application/json
1369+
Content-Digest: sha-256=:LkpHyFOVbBDPxc7YbHDOWNzAv88qWuVfLNf4TUf9Uo8=:
13981370
1399-
signed = ssl_sign(concatenate_with_newlines(headers),
1400-
private_key, 'sha256')
1401-
signature = {
1402-
'keyId': 'sender.fqdn', # The sending server's FQDN
1403-
'algorithm': 'rsa-sha256',
1404-
'headers': 'content-length date content-digest host',
1405-
'signature': signed,
1371+
{
1372+
"shareWith": "[email protected]",
1373+
"name": "spec.yaml",
1374+
"providerId": "7c084226-d9a1-11e6-bf26-cec0c932ce01",
1375+
"owner": "[email protected]",
1376+
"sender": "[email protected]",
1377+
"ownerDisplayName": "Albert Einstein",
1378+
"senderDisplayName": "Albert Einstein",
1379+
"shareType": "user",
1380+
"resourceType": "file",
1381+
"protocol": {
1382+
"name": "multi",
1383+
"webdav": {
1384+
"uri": "spec.yaml",
1385+
"sharedSecret": "hfiuhworzwnur98d3wjiwhr",
1386+
"permissions": ["read", "write"]
1387+
}
1388+
}
14061389
}
1407-
1408-
headers['Signature'] = format_signature(signature)
14091390
```
14101391

1411-
## How to confirm Signature on incoming request
1412-
1413-
The first step would be to confirm the validity of each
1414-
properties:
1392+
The signature base is constructed according to [RFC9421]:
14151393

1416-
* `content-length` and `content-digest` can be regenerated and compared
1417-
from the payload of the request,
1418-
* a maximum TTL MUST be applied to `date` and current
1419-
timestamp,
1420-
* regarding data contained in the `Signature`
1421-
header:
1422-
- using `keyId` to get the public key from remote
1423-
signatory,
1424-
- `headers` is used to generate the clear version of the
1425-
signature and MUST contain at least `content-length`, `date`,
1426-
`content-digest` and `host`,
1427-
- `signature` is the encrypted version of the
1428-
signature.
1394+
```
1395+
"@method": POST
1396+
"@target-uri": https://receiver.example.org/ocm/shares
1397+
"content-digest": sha-256=:LkpHyFOVbBDPxc7YbHDOWNzAv88qWuVfLNf4TUf9Uo8=:
1398+
"@signature-params": ("@method" "@target-uri" "content-digest")\
1399+
;created=1618884475;keyid="sender.example.org#key1";alg="ed25519"
1400+
```
14291401

1430-
Here is an example of how to verify the signature using the headers,
1431-
the signature and the public key:
1402+
Sign this base using, for example Ed25519 ([RFC8032]) to produce the
1403+
signature, then add headers:
14321404

14331405
```
1434-
clear = {
1435-
'content-length': length_of(payload),
1436-
'date': 'Mon, 08 Jul 2024 14:16:20 GMT',
1437-
'content-digest': 'SHA-256=' + base64_encode(hash('sha256',
1438-
utf8_encode(payload))), # Recompute digest for verification
1439-
'host': 'sender-fqdn',
1440-
}
1406+
Signature-Input: sig1=("@method" "@target-uri" "content-digest")\
1407+
;created=1618884475;keyid="sender.example.org#key1";alg="ed25519"
1408+
Signature: sig1=:dGVzdCBzaWduYXR1cmUgdmFsdWU=:
1409+
```
14411410

1442-
signed = headers['Signature']
1443-
verification_result = ssl_verify(concatenate_with_newlines(clear),
1444-
signed, public_key, 'sha256')
1411+
## Verifying a Signature (Receiver)
14451412

1446-
if not verification_result then
1447-
raise InvalidSignatureException
1448-
```
1413+
To verify an incoming signed request:
14491414

1450-
## Validating the payload
1415+
1. Extract the provider domain from the `sender` field in the
1416+
request body
1417+
2. Fetch the public key from
1418+
`https://<provider-domain>/.well-known/jwks.json`
1419+
3. Extract `keyid` from `Signature-Input` header and find the key
1420+
matching the `kid` value in the [RFC7517] response
1421+
4. Reconstruct the signature base from the request using the
1422+
components listed in `Signature-Input` as specified in [RFC9421]
1423+
5. Verify the signature using the appropriate algorithm
1424+
(e.g., Ed25519 [RFC8032])
1425+
1426+
## Validating the Payload
14511427

14521428
Following the validation of the signature, the host SHOULD also confirm
14531429
the validity of the payload, that is ensuring that the actions implied
@@ -1836,7 +1812,6 @@ to model a few key properties.
18361812
* __type__: Type of Resource (file, folder, calendar, etc.)
18371813

18381814

1839-
18401815
# Acknowledgements
18411816

18421817
Our deepest thanks and appreciation go to the people who started the

0 commit comments

Comments
 (0)