bitwarden_crypto/keys/
key_id.rs1use std::str::FromStr;
2
3use bitwarden_encoding::FromStrVisitor;
4use rand::RngExt;
5use serde::{Deserialize, Serialize};
6use subtle::ConstantTimeEq;
7use zeroize::Zeroize;
8
9use crate::{CryptoError, error::EncodingError};
10
11#[cfg(feature = "wasm")]
12#[wasm_bindgen::prelude::wasm_bindgen(typescript_custom_section)]
13const TS_CUSTOM_TYPES: &'static str = r#"
14export type KeyId = Tagged<string, "KeyId">;
15"#;
16
17pub(crate) const KEY_ID_SIZE: usize = 16;
19
20#[derive(Clone, PartialEq, Zeroize)]
24pub struct KeyId([u8; KEY_ID_SIZE]);
25
26impl ConstantTimeEq for KeyId {
30 fn ct_eq(&self, other: &Self) -> subtle::Choice {
31 self.0.ct_eq(&other.0)
32 }
33}
34
35impl KeyId {
42 pub fn make() -> Self {
44 let mut rng = bitwarden_random::rng();
45 let mut key_id = [0u8; KEY_ID_SIZE];
46 rng.fill(&mut key_id);
47 Self(key_id)
48 }
49
50 pub fn as_slice(&self) -> &[u8] {
52 &self.0
53 }
54}
55
56impl TryFrom<&[u8]> for KeyId {
57 type Error = &'static str;
58
59 fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
60 if value.len() != KEY_ID_SIZE {
61 return Err("Invalid length for KeyId");
62 }
63 let mut key_id = [0u8; KEY_ID_SIZE];
64 key_id.copy_from_slice(value);
65 Ok(Self(key_id))
66 }
67}
68
69impl From<KeyId> for [u8; KEY_ID_SIZE] {
70 fn from(key_id: KeyId) -> Self {
71 key_id.0
72 }
73}
74
75impl From<&KeyId> for Vec<u8> {
76 fn from(key_id: &KeyId) -> Self {
77 key_id.0.as_slice().to_vec()
78 }
79}
80
81impl From<[u8; KEY_ID_SIZE]> for KeyId {
82 fn from(bytes: [u8; KEY_ID_SIZE]) -> Self {
83 Self(bytes)
84 }
85}
86
87impl std::fmt::Display for KeyId {
90 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
91 write!(f, "{}", hex::encode(self.0))
92 }
93}
94
95impl FromStr for KeyId {
98 type Err = CryptoError;
99
100 fn from_str(s: &str) -> Result<Self, Self::Err> {
101 let bytes = hex::decode(s).map_err(|_| EncodingError::InvalidValue("key id"))?;
102 Self::try_from(bytes.as_slice()).map_err(|_| EncodingError::InvalidValue("key id").into())
103 }
104}
105
106impl Serialize for KeyId {
107 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
108 where
109 S: serde::Serializer,
110 {
111 serializer.serialize_str(&self.to_string())
112 }
113}
114
115impl<'de> Deserialize<'de> for KeyId {
116 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
117 where
118 D: serde::Deserializer<'de>,
119 {
120 deserializer.deserialize_str(FromStrVisitor::new())
121 }
122}
123
124bitwarden_ffi::impl_wire_string!(KeyId);
126
127#[cfg(feature = "wasm")]
128impl TryFrom<wasm_bindgen::JsValue> for KeyId {
129 type Error = CryptoError;
130
131 fn try_from(value: wasm_bindgen::JsValue) -> Result<Self, Self::Error> {
132 let string = value
133 .as_string()
134 .ok_or(EncodingError::InvalidValue("key id"))?;
135 Self::from_str(&string)
136 }
137}
138
139impl std::fmt::Debug for KeyId {
140 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
141 write!(f, "KeyId({})", hex::encode(self.0))
142 }
143}
144
145#[cfg(test)]
146mod tests {
147 use super::*;
148
149 #[test]
150 #[ignore = "Manual test to verify debug format"]
151 fn test_key_id_debug() {
152 let key_id = KeyId::make();
153 println!("{:?}", key_id);
154 }
155
156 const TEST_KEY_ID_HEX: &str = "000102030405060708090a0b0c0d0e0f";
157
158 fn test_key_id() -> KeyId {
159 KeyId::from([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15])
160 }
161
162 #[test]
163 fn test_from_str_roundtrips_display() {
164 let key_id = KeyId::make();
165
166 assert_eq!(KeyId::from_str(&key_id.to_string()).unwrap(), key_id);
167 }
168
169 #[test]
170 fn test_from_str_parses_lowercase_hex() {
171 assert_eq!(KeyId::from_str(TEST_KEY_ID_HEX).unwrap(), test_key_id());
172 }
173
174 #[test]
175 fn test_from_str_rejects_non_hex() {
176 assert!(KeyId::from_str("not a key id at all, no sir").is_err());
177 }
178
179 #[test]
180 fn test_from_str_rejects_odd_length() {
181 assert!(KeyId::from_str("000102030405060708090a0b0c0d0e0").is_err());
182 }
183
184 #[test]
185 fn test_from_str_rejects_wrong_byte_length() {
186 assert!(KeyId::from_str("000102030405060708090a0b0c0d0e").is_err());
188 assert!(KeyId::from_str("000102030405060708090a0b0c0d0e0f10").is_err());
189 }
190
191 #[test]
192 fn test_serde_serializes_to_hex_string() {
193 assert_eq!(
194 serde_json::to_string(&test_key_id()).unwrap(),
195 format!("\"{TEST_KEY_ID_HEX}\"")
196 );
197 }
198
199 #[test]
200 fn test_serde_roundtrip() {
201 let key_id = KeyId::make();
202 let json = serde_json::to_string(&key_id).unwrap();
203
204 assert_eq!(serde_json::from_str::<KeyId>(&json).unwrap(), key_id);
205 }
206
207 #[test]
208 fn test_serde_rejects_malformed_hex() {
209 assert!(serde_json::from_str::<KeyId>("\"nothex\"").is_err());
210 }
211}