bitwarden_access_token_crypto/
seed.rs1use std::fmt;
4
5use rand::RngExt;
6use thiserror::Error;
7use zeroize::Zeroizing;
8
9const SEED_LEN: usize = 16;
10
11pub 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#[derive(Debug, Clone, Copy, PartialEq, Eq, Error)]
25#[error("invalid access token seed length: expected {expected}, got {got}")]
26pub struct AccessTokenSeedError {
27 pub expected: usize,
29 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 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}