diff options
author | Sara Golemon <sara.golemon@mongodb.com> | 2023-02-08 11:05:46 -0600 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2023-02-14 21:11:29 +0000 |
commit | 28bb49c88d432b60f4b6d9a747886782981fda89 (patch) | |
tree | 74ba37a19ce5fb7c807a1f3645ad4ed6a0742b79 | |
parent | 9355b6a325e2606e41198caa07ded85e68a5b353 (diff) | |
download | mongo-28bb49c88d432b60f4b6d9a747886782981fda89.tar.gz |
SERVER-56031 Architecture guide for JWT crypto
-rw-r--r-- | src/mongo/crypto/README.JWT.md | 184 | ||||
-rw-r--r-- | src/mongo/crypto/README.md | 3 |
2 files changed, 187 insertions, 0 deletions
diff --git a/src/mongo/crypto/README.JWT.md b/src/mongo/crypto/README.JWT.md new file mode 100644 index 00000000000..c447af6ef32 --- /dev/null +++ b/src/mongo/crypto/README.JWT.md @@ -0,0 +1,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. diff --git a/src/mongo/crypto/README.md b/src/mongo/crypto/README.md new file mode 100644 index 00000000000..e2088ca48b1 --- /dev/null +++ b/src/mongo/crypto/README.md @@ -0,0 +1,3 @@ +# MongoDB Crypto library architecture guides + +* [JSON Web Tokens (JWT)](README.JWT.md) |