1#![cfg_attr(not(feature = "std"), no_std)]
2#![deny(unsafe_code)]
3#![deny(missing_docs)]
4#![deny(clippy::all)]
5#![deny(clippy::pedantic)]
6
7use base64_ng::{DecodedBuffer, EncodedBuffer};
20use subtle::{Choice, ConstantTimeEq};
21
22#[cfg(feature = "alloc")]
23use base64_ng::SecretBuffer;
24
25pub trait SubtleEqExt {
31 #[must_use = "use Choice or convert it deliberately with bool::from(choice)"]
35 fn subtle_ct_eq(&self, expected: &[u8]) -> Choice;
36
37 #[must_use]
42 fn subtle_verify(&self, expected: &[u8]) -> bool {
43 bool::from(self.subtle_ct_eq(expected))
44 }
45}
46
47impl SubtleEqExt for [u8] {
48 fn subtle_ct_eq(&self, expected: &[u8]) -> Choice {
49 subtle_ct_eq_public_len(self, expected)
50 }
51}
52
53impl SubtleEqExt for &[u8] {
54 fn subtle_ct_eq(&self, expected: &[u8]) -> Choice {
55 subtle_ct_eq_public_len(self, expected)
56 }
57}
58
59impl<const CAP: usize> SubtleEqExt for DecodedBuffer<CAP> {
60 fn subtle_ct_eq(&self, expected: &[u8]) -> Choice {
61 subtle_ct_eq_public_len(self.as_bytes(), expected)
62 }
63}
64
65impl<const CAP: usize> SubtleEqExt for EncodedBuffer<CAP> {
66 fn subtle_ct_eq(&self, expected: &[u8]) -> Choice {
67 subtle_ct_eq_public_len(self.as_bytes(), expected)
68 }
69}
70
71#[cfg(feature = "alloc")]
72impl SubtleEqExt for SecretBuffer {
73 fn subtle_ct_eq(&self, expected: &[u8]) -> Choice {
74 subtle_ct_eq_public_len(self.expose_secret(), expected)
75 }
76}
77
78#[must_use = "use Choice or convert it deliberately with bool::from(choice)"]
86pub fn subtle_ct_eq_public_len(left: &[u8], right: &[u8]) -> Choice {
87 if left.len() == right.len() {
88 left.ct_eq(right)
89 } else {
90 Choice::from(0)
91 }
92}
93
94#[cfg(test)]
95mod tests {
96 use super::{SubtleEqExt, subtle_ct_eq_public_len};
97 use base64_ng::STANDARD;
98
99 #[cfg(feature = "alloc")]
100 use base64_ng::ct;
101
102 #[test]
103 fn compares_raw_slices_with_public_length() {
104 assert!(bool::from(subtle_ct_eq_public_len(b"hello", b"hello")));
105 assert!(!bool::from(subtle_ct_eq_public_len(b"hello", b"world")));
106 assert!(!bool::from(subtle_ct_eq_public_len(b"hello", b"hello!")));
107 }
108
109 #[test]
110 fn compares_stack_backed_buffers() {
111 let decoded = STANDARD.decode_buffer::<5>(b"aGVsbG8=").unwrap();
112 assert!(decoded.subtle_verify(b"hello"));
113 assert!(!decoded.subtle_verify(b"world"));
114
115 let encoded = STANDARD.encode_buffer::<8>(b"hello").unwrap();
116 assert!(encoded.subtle_verify(b"aGVsbG8="));
117 assert!(!encoded.subtle_verify(b"aGVsbG8h"));
118 }
119
120 #[cfg(feature = "alloc")]
121 #[test]
122 fn compares_secret_buffer() {
123 let decoded = ct::STANDARD.decode_secret(b"aGVsbG8=").unwrap();
124 assert!(decoded.subtle_verify(b"hello"));
125 assert!(!decoded.subtle_verify(b"world"));
126 }
127}