Skip to main content

bitwarden_access_token_crypto/
seed.rs

1//! The seed an [`crate::AccessTokenKey`] is derived from.
2
3use std::fmt;
4
5use rand::RngExt;
6use thiserror::Error;
7use zeroize::Zeroizing;
8
9const SEED_LEN: usize = 16;
10
11/// The 16-byte secret an [`crate::AccessTokenKey`] is derived from. Never sent to a server; the
12/// caller transports it to the token holder (e.g. inside a token string).
13pub struct AccessTokenSeed(Zeroizing<[u8; SEED_LEN]>);
14
15impl fmt::Debug for AccessTokenSeed {
16    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
17        f.debug_tuple("AccessTokenSeed")
18            .field(&"[REDACTED]")
19            .finish()
20    }
21}
22
23/// Returned when a byte slice is the wrong length to be an [`AccessTokenSeed`].
24#[derive(Debug, Clone, Copy, PartialEq, Eq, Error)]
25#[error("invalid access token seed length: expected {expected}, got {got}")]
26pub struct AccessTokenSeedError {
27    /// The length an access token seed must be.
28    pub expected: usize,
29    /// The length that was actually given.
30    pub got: usize,
31}
32
33impl TryFrom<&[u8]> for AccessTokenSeed {
34    type Error = AccessTokenSeedError;
35
36    fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
37        let array: [u8; SEED_LEN] = bytes.try_into().map_err(|_| AccessTokenSeedError {
38            expected: SEED_LEN,
39            got: bytes.len(),
40        })?;
41        Ok(Self(Zeroizing::new(array)))
42    }
43}
44
45impl AccessTokenSeed {
46    pub(crate) fn make() -> Self {
47        Self(Zeroizing::new(
48            bitwarden_random::rng().random::<[u8; SEED_LEN]>(),
49        ))
50    }
51
52    /// The raw seed bytes.
53    pub fn as_bytes(&self) -> &[u8; SEED_LEN] {
54        &self.0
55    }
56}
57
58#[cfg(test)]
59mod tests {
60    use super::*;
61
62    #[test]
63    fn exact_length_is_accepted() {
64        let bytes = [0u8; SEED_LEN];
65        assert!(AccessTokenSeed::try_from(bytes.as_slice()).is_ok());
66    }
67
68    #[test]
69    fn wrong_length_is_rejected() {
70        let bytes = [0u8; SEED_LEN - 1];
71        let err = AccessTokenSeed::try_from(bytes.as_slice()).unwrap_err();
72        assert_eq!(
73            err,
74            AccessTokenSeedError {
75                expected: SEED_LEN,
76                got: SEED_LEN - 1
77            }
78        );
79    }
80}