bitwarden_importers/importers/keeper/crypto/
utils.rs1use rand::Rng;
2
3use super::KeeperCryptoError;
4
5pub(crate) fn get_random_bytes(length: usize) -> Vec<u8> {
7 let mut buf = vec![0u8; length];
8 bitwarden_random::rng().fill_bytes(&mut buf);
9 buf
10}
11
12pub(crate) fn base64_url_encode(data: &[u8]) -> String {
14 data_encoding::BASE64URL_NOPAD.encode(data)
15}
16
17pub(crate) fn base64_url_decode(text: &str) -> Result<Vec<u8>, KeeperCryptoError> {
21 let trimmed = text.trim_end_matches('=');
22 data_encoding::BASE64URL_NOPAD
23 .decode(trimmed.as_bytes())
24 .map_err(|_| KeeperCryptoError::InvalidData)
25}
26
27#[cfg(test)]
28mod tests {
29 use super::*;
30
31 #[test]
32 fn get_random_bytes_generates_correct_length() {
33 assert_eq!(get_random_bytes(0).len(), 0);
34 assert_eq!(get_random_bytes(1).len(), 1);
35 assert_eq!(get_random_bytes(32).len(), 32);
36 assert_eq!(get_random_bytes(256).len(), 256);
37 }
38
39 #[test]
40 fn get_random_bytes_produces_different_values() {
41 let bytes1 = get_random_bytes(32);
42 let bytes2 = get_random_bytes(32);
43 assert_ne!(bytes1, bytes2);
45 }
46
47 #[test]
48 fn base64_url_encode_empty() {
49 assert_eq!(base64_url_encode(&[]), "");
50 }
51
52 #[test]
53 fn base64_url_encode_single_byte() {
54 assert_eq!(base64_url_encode(&[0]), "AA");
55 assert_eq!(base64_url_encode(&[255]), "_w");
56 }
57
58 #[test]
59 fn base64_url_encode_uses_url_safe_alphabet() {
60 let data = &[0xfb, 0xff, 0x00];
62 let encoded = base64_url_encode(data);
63 assert!(!encoded.contains('+'));
64 assert!(!encoded.contains('/'));
65 assert!(encoded.contains('-') || encoded.contains('_') || encoded == "u_8A");
66 }
67
68 #[test]
69 fn base64_url_encode_has_no_padding() {
70 let encoded = base64_url_encode(&[0u8; 10]);
72 assert!(!encoded.contains('='));
73 }
74
75 #[test]
76 fn base64_url_decode_empty() {
77 assert_eq!(base64_url_decode("").unwrap(), Vec::<u8>::new());
78 }
79
80 #[test]
81 fn base64_url_decode_single_byte() {
82 assert_eq!(base64_url_decode("AA").unwrap(), vec![0]);
83 assert_eq!(base64_url_decode("_w").unwrap(), vec![255]);
84 }
85
86 #[test]
87 fn base64_url_decode_tolerates_padding() {
88 assert_eq!(base64_url_decode("AA").unwrap(), vec![0]);
90 assert_eq!(base64_url_decode("AA=").unwrap(), vec![0]);
91 assert_eq!(base64_url_decode("AA==").unwrap(), vec![0]);
92 }
93
94 #[test]
95 fn base64_url_decode_rejects_invalid_input() {
96 assert!(base64_url_decode("!!!").is_err());
97 assert!(base64_url_decode("@@@@").is_err());
98 assert!(base64_url_decode("\x00\x01\x02").is_err());
99 }
100
101 #[test]
102 fn base64_url_round_trip() {
103 let original = b"Hello, Keeper!";
104 let encoded = base64_url_encode(original);
105 let decoded = base64_url_decode(&encoded).unwrap();
106 assert_eq!(decoded, original);
107 }
108
109 #[test]
110 fn base64_url_round_trip_binary() {
111 let original = [0xfb, 0xff, 0x00, 0x10, 0x3e, 0x7d];
112 let encoded = base64_url_encode(&original);
113 let decoded = base64_url_decode(&encoded).unwrap();
114 assert_eq!(decoded.as_slice(), &original);
115 }
116
117 #[test]
118 fn base64_url_round_trip_empty() {
119 let original: &[u8] = &[];
120 let encoded = base64_url_encode(original);
121 let decoded: Vec<u8> = base64_url_decode(&encoded).unwrap();
122 assert_eq!(decoded.as_slice(), original);
123 }
124
125 #[test]
126 fn base64_url_round_trip_various_lengths() {
127 for length in [0, 1, 2, 3, 4, 5, 10, 31, 32, 33, 64, 255] {
128 let original = get_random_bytes(length);
129 let encoded = base64_url_encode(&original);
130 let decoded = base64_url_decode(&encoded).unwrap();
131 assert_eq!(decoded, original, "Round trip failed for length {}", length);
132 }
133 }
134
135 #[test]
136 fn base64_url_encode_known_vectors() {
137 assert_eq!(base64_url_encode(b"abc"), "YWJj");
139 assert_eq!(base64_url_encode(b"abcd"), "YWJjZA");
140 assert_eq!(base64_url_encode(b"ab"), "YWI");
142 }
143
144 #[test]
145 fn base64_url_decode_known_vectors() {
146 assert_eq!(base64_url_decode("YWJj").unwrap(), b"abc");
147 assert_eq!(base64_url_decode("YWJjZA").unwrap(), b"abcd");
148 assert_eq!(base64_url_decode("YWI").unwrap(), b"ab");
149 }
150}