summaryrefslogtreecommitdiff
path: root/src/mongo/crypto/README.JWT.md
blob: c447af6ef32d495fe54714d956b3df8fd6d363d0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
# JSON Web Token Library

At present, usage of JWS in MongoDB is limited to the Linux platform only and is not implemented on other platforms.
Since signature validation is not available on other platforms, use of the unvalidated JWT types, while present, is not useful.

* [Glossary](#glossary)
* [`JWKManager`](#jwkmanager)
* [`JWSValidator`](#jwsvalidator)
* [`JWSValidatedToken`](#jwsvalidatedtoken)
* [Compact Serialization Format](#compact-serialization-format)

## Glossary

* **JWK** (JSON Web Key): A human readable representation of a cryptographic key.
  * See [RFC 7517](https://www.rfc-editor.org/rfc/rfc7517) JSON Web Key
  * Note: This library currently supports [RSA](https://www.rfc-editor.org/rfc/rfc7517#section-9.3) based keys only.
* **JWS** (JSON Web Signature): A cryptographic signature on a JWT, typically presented as a single object with the token and a header.
  * See [RFC 7515](https://www.rfc-editor.org/rfc/rfc7515) JSON Web Signature
  * Note: This library currently supports the [Compact Serialization](https://www.rfc-editor.org/rfc/rfc7515#section-3.1) only.
* **JWT** (JSON Web Token): A JSON object representing a number of claims such as, but not limited to: bearer identity, issuer, and validity.
  * See [RFC 7519](https://www.rfc-editor.org/rfc/rfc7519) JSON Web Token

## JWKManager

[JWKManager](https://github.com/mongodb/mongo/blob/master/src/mongo/crypto/jwk_manager.h)
loads `JWKSet`s from an HTTPS endpoint, parses the received JSON using the
`JWKSet`, `JWK`, and `JWKRSA` types from IDL, and instantiates [`JWSValidator`s](#jwsvalidator) with the key material.

Later, when validating a client supplied token, the application will use the
[`JWSValidatedToken`](#jwsvalidatedtoken) type with a `JWKManager` to validate the token.

### JSON Web Keys

* `JWK`: The base key material type in IDL. This parses only the `kid` (Key ID) and `kty` (Key Type) fields.
   In order to expect and process key specific data, the `kty` must already be known, therefore
   type specific IDL structs are defined as chaining the base `JWK` type.
  * `JWKRSA`: Chains the base `JWK` type and adds expected fields `n` and `e` which represent the
     modulus and public-exponent portions of the RSA key respectively.
* `JWKSet`: A simple wrapper struct containing a single field named `keys` of type `array<object>`.
   This allows the [`JWKManager`](#jwkmanager) class to load a `JWKSet` URI resource and pull out a set of keys
   which are expected to conform to the `JWK` interface, and as of this writing, represent `JWKRSA` data specifically.

These types, as well as [`JWSHeader`](#jwsheader) and [`JWT`](#jwt) can be found in [jwt\_types.idl](jwt_types.idl).

### Example JWK file

A typical JSON file containing keys may look something like the following:

```json
{
  "keys": [
    {   
      "kid": "custom-key-1",
      "kty": "RSA",
      "n": "ALtUlNS31SzxwqMzMR9jKOJYDhHj8zZtLUYHi3s1en3wLdILp1Uy8O6Jy0Z66tPyM1u8lke0JK5gS-40yhJ-bvqioW8CnwbLSLPmzGNmZKdfIJ08Si8aEtrRXMxpDyz4Is7JLnpjIIUZ4lmqC3MnoZHd6qhhJb1v1Qy-QGlk4NJy1ZI0aPc_uNEUM7lWhPAJABZsWc6MN8flSWCnY8pJCdIk_cAktA0U17tuvVduuFX_94763nWYikZIMJS_cTQMMVxYNMf1xcNNOVFlUSJHYHClk46QT9nT8FWeFlgvvWhlXfhsp9aNAi3pX-KxIxqF2wABIAKnhlMa3CJW41323Js",
      "e": "AQAB"
    },  
    {   
      "kid": "custom-key-2",
      "kty": "RSA",
      "n": "ANBv7-YFoyL8EQVhig7yF8YJogUTW-qEkE81s_bs2CTsI1oepDFNAeMJ-Krfx1B7yllYAYtScZGo_l60R9Ou4X89LA66bnVRWVFCp1YV1r0UWtn5hJLlAbqKseSmjdwZlL_e420GlUAiyYsiIr6wltC1dFNYyykq62RhfYhM0xpnt0HiN-k71y9A0GO8H-dFU1WgOvEYMvHmDAZtAP6RTkALE3AXlIHNb4mkOc9gwwn-7cGBc08rufYcniKtS0ZHOtD1aE2CTi1MMQMKkqtVxWIdTI3wLJl1t966f9rBHR6qVtTV8Qpq1bquUc2oaHjR4lPTf0Z_hTaELJa5-BBbvJU",
      "e": "AQAB"
    }   
  ]
}
```

## JWSValidator

The [`JWSValidator`](https://github.com/mongodb/mongo/blob/master/src/mongo/crypto/jws_validator.h)
interface provides a platform agnostic API for verifying the signatures on `JWS` signed `JWT` payloads.
Each instance of a `JWSValidator` is created (via `JWSValidator::create`) with a specific key,
and may have it's `JWSValidator->validate()` invoked multiple times (concurrently) to validate tokens
as they are received from third parties such as connected clients.

Platform specific implementations of the cryptographic functions may be found in:

* Linux: [jws\_validator\_openssl.cpp](https://github.com/mongodb/mongo/blob/master/src/mongo/crypto/jws_validator_openssl.cpp)
* Windows: [jws\_validator\_windows.cpp](https://github.com/mongodb/mongo/blob/master/src/mongo/crypto/jws_validator_windows.cpp) UNIMPLEMENTED
* macOS: [jws\_validator\_apple.cpp](https://github.com/mongodb/mongo/blob/master/src/mongo/crypto/jws_validator_apple.cpp) UNIMPLEMENTED
* Non-TLS builds: [jws\_validator\_none.cpp](https://github.com/mongodb/mongo/blob/master/src/mongo/crypto/jws_validator_none.cpp) UNIMPLEMENTED

## JWSValidatedToken

A token **MUST NOT** be trusted from any third party unless its contents have been duly validated.
The `JWSValidatedToken` exists to encapsulate the processing of a signed token into usable fields while
maintaining a type on the post-processed token as well.

An application will construct a `JWSValidatedToken` by passing both a
signed [`JWS Compact Serialization`](#compact-serialization-format) and a [`JWKManager`](#jwkmanager).
1. The token's header is parsed using IDL type [`JWSHeader`](#jwsheader) to determine the `kid` (Key ID) which was used for signing.
2. The [`JWKManager`](#jwkmanager) is queried for a suitable [`JWSValidator`](#jwsvalidator).
  1. If the requested `kid` is unknown to the [`JWKManager`](#jwkmanager), it will requery its `JWKSet` URI to reload from the key server.
3. That validator is used to check the provided signature against the header and body payload.
4. The body of the token is parsed using IDL type [`JWT`](#jwt).
5. Relevant validity claims `nbf` ([Not before](https://www.rfc-editor.org/rfc/rfc7519.html#section-4.1.5)) and `exp` ([Expires At](https://www.rfc-editor.org/rfc/rfc7519.html#section-4.1.4)) are verified.

If, at any point during this construction, an error is encountered, a `DBException` will be thrown and no `JWSValidatedToken` will be created.

Callers **MUST** validate `iss` (Issuer) and `aud` (Audience) independently.

Callers **SHOULD** only retain `JWSValidatedToken` objects.
To access fields, use the `getBody()`/`getBodyBSON()` accessors on an as-needed basis
from the `JWSValidatedToken` object rather than storing these values by themselves.
This ensures that the data being used has been validated.

**Directly parsing the signed JWS compact serialization using *any* other means
than `JWSValidatedToken` should be considered an error and rejected during code review.**

## Compact Serialization Format

A typical Compact Serialization string, as received from a third party, may look something like the following:

```
eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6ImN1c3RvbS1rZXktMSJ9.
eyJpc3MiOiJodHRwczovL3Rlc3Qua2VybmVsLm1vbmdvZGIuY29tL29pZGMvaXNzdWVyMSIsInN1YiI6InVzZXIxQG1vbmdvZGIuY29tIiwibmJmIjoxNjYxMzc0MDc3LCJleHAiOjIxNDc0ODM2NDcsImF1ZCI6WyJqd3RAa2VybmVsLm1vbmdvZGIuY29tIl0sIm5vbmNlIjoiZ2RmaGpqMzI0ZWhqMjNrNCIsIm1vbmdvZGItcm9sZXMiOlsibXlSZWFkUm9sZSJdfQ.
nhM7C5oHy1jRT4we1oFBpzzBQ7e8ccZdUvtua6d5C4wg0W2Kf3Vf2ze7VTRzq1CdIeemlx0YuzxNoE1ujZI9W9zRZJmBWhahZHG-MYqtFioz-fZvAjHdK8VhLKF2wrvRBlFDNbJtPzIdF3dT1hnfYv4ASwCgEVZJA2CrPmTTnbgVoqWhyiGUWK-DtNhHRVVIXjXkV-f6bor2Oipp0EP2ukY30LNnBQm-cBIB01Q91PZHRHkG\_\_CtayFILWSdpZPGwOlO3yjUVw0lMdoAYamUPGfjNOQooFyevdHNGuvbh8nqQPgf5ZmRYP7EUJ9\_DipoV4q90TMHQi9pXjc72zSLJg
```

As described in [RFC 7515 Section 7.1](https://www.rfc-editor.org/rfc/rfc7515#section-7.1),
this is divided into three sections delimited by periods.

### JWSHeader

The first section in our example is the `base64url::encode()` output of the `JWSHeader`
represented as `JSON`, so it would decode as:

```json
{ "typ": "JWT", "alg": "RS256", "kid": "custom-key-1" }
```

This tells us that the payload in the second field of the compact serialized signature is a `JWT`
(this is currently expected to always be the case).
We also see that the `alg`orithm for signing uses [`RS256`](https://www.rfc-editor.org/rfc/rfc7518.html#section-3.3) (`RSASSA-PKCS1-v1_5 using SHA-256`) and that the signature can be verified using the key material associated with `custom-key-1`.

The IDL struct `JWSHeader` may be used to parse this section for access to the relevant `alg` and `kid` fields.
* See also: [RFC 7515 Section 4](https://www.rfc-editor.org/rfc/rfc7515#section-4) JOSE Header

### JWT

In the second of the three sections, we have the `JWT` token body itself,
again encoded using `base64url` on a `JSON` representation.
So in this example, it would decode as:

```json
{
    "iss": "https://test.kernel.mongodb.com/oidc/issuer1",
    "sub": "user1@mongodb.com",
    "nbf": 1661374077,
    "exp": 2147483647,
    "aud": [
        "jwt@kernel.mongodb.com"
    ],
    "nonce": "gdfhjj324ehj23k4",
    "mongodb-roles": [
        "myReadRole"
    ]
}
```

The IDL struct `JWT` will be used to parse this section by [`JWSValidatedToken`](#jwsvalidatedtoken),
however token payloads SHOULD NOT be inspected without processing them through
the validating infrastructure.
* See [RFC 7519 Section 4](https://www.rfc-editor.org/rfc/rfc7519#section-4) JWT Claims

Note that this token payload contains an additional field not defined by any RFC or ietf-draft.
The content of this, or any other unknown fields is treated as opaque and ignored by
[`JWSValidatedToken`](#jwsvalidatedtoken) as applications are permitted to define additional fields
provided they are uniquely named.

For information on how a field like `mongodb-roles` is used, refer to the `MONGODB-OIDC architecture guide`.

### JWS Signature

The third and final section of a JWS Compact Serialization is a non human-readable output of a
cryptographic signing operation as defined by the payload's `JWSHeader`.

In this case, `RS256` is specified as the signing `alg`orithm which roughly translates to
computing the SHA256 digest of the header, delimiting period, and payload sections,
then transforming the signature output (with PKCS#1v1.5 padding) using an RSA private key.
We validate this signature by transforming the digest output back out using our RSA public key
and comparing that to our locally produced digest output.

The heavy lifting of this operation is handled internally by OpenSSL on linux.