Skip to main content

bitwarden_access_token/
secrets.rs

1//! Minting an access token (the issuer side).
2
3use bitwarden_access_token_crypto::{AccessTokenKeyMaterial, AccessTokenSeed};
4use bitwarden_crypto::{EncString, KeySlotIds, KeyStoreContext};
5use bitwarden_encoding::B64;
6use uuid::Uuid;
7
8use crate::{AccessTokenError, AccessTokenKind, consts::TOKEN_VERSION};
9
10/// Registration secrets, held until the issuer allocates an API key id and client secret.
11pub struct AccessTokenSecrets {
12    /// The organization key, encrypted under the derived key. Sent as `encryptedPayload`.
13    pub encrypted_payload: EncString,
14    /// The derived key's base64, encrypted under the organization key. Sent as `key`.
15    pub key: EncString,
16    seed: AccessTokenSeed,
17    /// The kind the key was derived for, so `into_token` cannot build a mismatched shape.
18    kind: AccessTokenKind,
19}
20
21impl AccessTokenSecrets {
22    /// Assembles `0.<kind>.<api-key-id>.<client-secret>:<b64-seed>`, omitting `<kind>` for
23    /// Secrets Manager. Consumes `self` so the seed cannot mint a second token.
24    pub fn into_token(self, api_key_id: Uuid, client_secret: &str) -> String {
25        let seed_b64 = B64::from(self.seed.as_bytes().as_slice());
26        match self.kind.segment() {
27            Some(segment) => {
28                format!("{TOKEN_VERSION}.{segment}.{api_key_id}.{client_secret}:{seed_b64}")
29            }
30            None => format!("{TOKEN_VERSION}.{api_key_id}.{client_secret}:{seed_b64}"),
31        }
32    }
33}
34
35/// Generates the registration secrets for a `kind` access token.
36///
37/// A missing `organization_key` surfaces as [`AccessTokenError::Crypto`].
38pub fn make_access_token_secrets<Ids: KeySlotIds>(
39    ctx: &mut KeyStoreContext<Ids>,
40    organization_key: Ids::Symmetric,
41    kind: AccessTokenKind,
42) -> Result<AccessTokenSecrets, AccessTokenError> {
43    let material = AccessTokenKeyMaterial::make(ctx, organization_key, kind.key_purpose())?;
44
45    // `into_seed` consumes the material, so copy the public fields out first.
46    let encrypted_payload = material.encrypted_payload.clone();
47    let key = material.key.clone();
48    let seed = material.into_seed();
49
50    Ok(AccessTokenSecrets {
51        encrypted_payload,
52        key,
53        seed,
54        kind,
55    })
56}