wire_e2e_identity/
lib.rs

1//! Support for obtaining X.509 certificates that bind keys to Wire users.
2//!
3//! # Overview
4//!
5//! On a high level, the process of getting a certificate for a particular Wire client looks like
6//! the following:
7//!
8//! 1. the client requests an [ACME](https://www.rfc-editor.org/rfc/rfc8555.html) server to create a new certificate
9//! 2. the ACME server responds with two challenges, [DPoP](#dpop-challenge-wire-dpop-01) and
10//!    [OIDC](#oidc-challenge-wire-oidc-01)
11//! 3. the client completes the DPoP challenge
12//! 4. the client completes the OIDC challenge
13//! 5. the ACME server verifies that both challenges are succesfully completed
14//! 6. the client generates and submits a CSR (certificate signing request) to the ACME server
15//! 7. the ACME server issues a new certificate
16//!
17//! The client must complete both challenges in order to get a certficate -- completing one
18//! challenge is not enough.
19//!
20//! ```text
21//!     ┌───────────────────┐    ┌───────────────────┐     ┌───────────────────┐
22//!     │                   │◄───┤                   ├────►│                   │
23//!     │    ACME server    │    │    Wire client    │     │    Wire server    │
24//!     │                   ├───►│                   │◄────┤                   │
25//!     └───────────────────┘    └───────────┬───────┘     └───────────────────┘
26//!                                      ▲   │
27//!                                      │   │             ┌───────────────────┐
28//!                                      │   └────────────►│                   │
29//!                                      │                 │     OIDC IdP      │
30//!                                      └─────────────────┤                   │
31//!                                                        └───────────────────┘
32//! ```
33//!
34//! # Challenges
35//!
36//! The only ACME server implementation supporting the two custom challenge types necessary for the flow is
37//! [step-ca](https://github.com/smallstep/certificates).
38//!
39//! ## DPoP challenge: `wire-dpop-01`
40//!
41//! This challenge checks whether the data stored with the Wire server (client ID, user name) matches
42//! the data in the device ID that the client submitted when creating the corresponding order.
43//!
44//! The challenge requires a Wire server that supports the `/clients/{cid}/access-token` endpoint.
45//!
46//! The flow is roughly as follows:
47//!  - the client requests a fresh nonce from the Wire server via the `/clients/{cid}/nonce` endpoint
48//!  - the Wire server responds with a nonce
49//!  - the client constructs a [DPoP](https://www.rfc-editor.org/rfc/rfc9449.html) token
50//!     - the token contains client ID, team and user name
51//!     - the token is signed by the clients ACME account key (see [Terminology](https://www.rfc-editor.org/rfc/rfc8555.html#section-3))
52//!  - the client sends the DPoP, together with the nonce, to the Wire server, via the `/clients/{cid}/access-token`
53//!    endpoint
54//!  - the Wire server responds with an access token
55//!     - the access token contains the DPoP provided by the client
56//!     - the access token is signed by the key whose public part is known to the ACME server
57//!  - the client sends the Wire access token to the ACME server
58//!  - the ACME server verifies the challenge by
59//!     - verifying the outer, Wire access token signature
60//!     - verifying the inner, DPoP token signature
61//!     - verifying that the DPoP claims match values specified by the challenge
62//!
63//! [Validation function](https://github.com/smallstep/certificates/blob/2746cd06fb8d68c6720a00e96257038b7e0bbb54/acme/challenge.go#L537)
64//!
65//! ## OIDC challenge: `wire-oidc-01`
66//!
67//! This challenge checks whether the data stored with the IdP (name, preferred username) matches
68//! the data in the user ID that the client submitted when creating the corresponding order.
69//!
70//! The challenge requires an OpenID Connect 1.0 conformant provider with
71//! support for the [`claims` parameter](https://openid.net/specs/openid-connect-core-1_0.html#ClaimsParameter)
72//! in the authorization request.
73//!
74//! **Important**: the IdP (identity provider) has to support specifying the values of the claims
75//! requested and those values must be present in the returned ID token. In particular, this
76//! mechanism is used by the client to specify values of the `keyauth` and `acme_aud` claims.
77//! The client expects the IdP to copy those values to the ID token,
78//! e.g.
79//! ```text
80//!  {
81//!   ...
82//!   "name": "Alice Smith",
83//!   "acme_aud": "https://stepca:32791/acme/wire/challenge/vkdvGckMpDfPFDwOYO6LZhSBx2...
84//!   "keyauth": "MXwp2QZezi1xShr3wjNuqKDmmapvtnxv.StoKb1FuB59XWMLjhsdeU94T95R_AoMP3u2g9HscYog",
85//!   ...
86//!  }
87//! ```
88//!
89//! Strictly speaking, this usage of the `claims` parameter is not according to the OIDC spec as
90//! the provider has no way to check that the specified values match.
91//!
92//! The flow makes use of the [PKCE extension](https://www.rfc-editor.org/rfc/rfc7636) to the
93//! [OAuth 2.0 Authorization Code flow](https://openid.net/specs/openid-connect-core-1_0.html#CodeFlowAuth)
94//! and looks roughly as follows:
95//!  - the client generates a PKCE `(code challenge, code verifier)` pair
96//!  - the client makes an authorization request to the user's IdP
97//!     - the request makes use of the `claims` parameter to instruct the IdP to include `keyauth` and `acme_aud` claims
98//!       with the provided values in the ID token
99//!     - the request specifies the `redirect_uri` parameter which is going to be used by the IdP to redirect the user
100//!       back after a login
101//!     - the request includes the PKCE code challenge value, to be verified later by the IdP when the client requests
102//!       an access token
103//!  - the user is redirected to the IdP's login page, where authentication is performed
104//!  - after a successful authentication, the IdP redirects the user back to the OIDC (in this case Wire) client,
105//!    returning the authorization code
106//!  - the client uses the authorization code and the PKCE code verifier to make an access token request to the IdP
107//!  - the IdP verifies both authorization code and the PKCE code verifier and returns the access token with the ID
108//!    token embedded in it; at this point the IdP is no longer needed
109//!  - the client extracts the ID token from the access token and sends it to the ACME server
110//!  - the ACME server verifies the challenge by
111//!     - verifying the ID token signature
112//!     - verifying that the `keyauth` value matches the key authorization value for this challenge
113//!     - verifying that the `acme_aud` value matches the URL of this challenge
114//!     - verifying that the ID token claims match values specified by the challenge (name, Wire handle)
115//!
116//! [Validation function](https://github.com/smallstep/certificates/blob/2746cd06fb8d68c6720a00e96257038b7e0bbb54/acme/challenge.go#L407)
117//!
118//! ## References
119//!
120//! - [RFC7519: JSON Web Token (JWT)](https://www.rfc-editor.org/rfc/rfc7519)
121//! - [RFC8725: JSON Web Token Best Current Practices](https://www.ietf.org/rfc/rfc8725)
122//! - [RFC7636: Proof Key for Code Exchange by OAuth Public Clients](https://www.rfc-editor.org/rfc/rfc7636)
123//! - [RFC8555: Automatic Certificate Management Environment (ACME)](https://www.rfc-editor.org/rfc/rfc8555)
124//! - [RFC9449: OAuth 2.0 Demonstrating Proof of Possession (DPoP)](https://www.rfc-editor.org/rfc/rfc9449)
125//! - [OpenID Connect Core 1.0](https://openid.net/specs/openid-connect-core-1_0.html)
126
127mod e2e_identity;
128mod error;
129mod types;
130
131pub mod acme;
132pub mod pki_env;
133pub mod pki_env_hooks;
134pub mod x509_check;
135
136pub use acme::{
137    AcmeDirectory, RustyAcme, RustyAcmeError, WireIdentity, WireIdentityReader, compute_raw_key_thumbprint,
138};
139pub use e2e_identity::RustyE2eIdentity;
140pub use error::{E2eIdentityError, E2eIdentityResult};
141pub use pki_env::{NewCrlDistributionPoints, PkiEnvironmentProvider};
142#[cfg(feature = "builder")]
143pub use rusty_jwt_tools::prelude::generate_jwk;
144pub use rusty_jwt_tools::prelude::{
145    ClientId as E2eiClientId, Handle, HashAlgorithm, JwsAlgorithm, RustyJwtError, parse_json_jwk,
146};
147pub use types::{
148    E2eiAcmeAccount, E2eiAcmeAuthorization, E2eiAcmeChallenge, E2eiAcmeFinalize, E2eiAcmeOrder, E2eiNewAcmeOrder,
149};
150pub use x509_check::IdentityStatus;