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;