Skip to main content

dcrypt_api/
types.rs

1// File: crates/api/src/types.rs
2
3//! Core types with explicit ownership and memory-hygiene behavior
4//!
5//! Secret wrappers explicitly clear owned initialized bytes on drop. This is a
6//! best-effort safe-Rust behavior, not a physical-erasure guarantee for caller,
7//! compiler/register, freed-storage, swap, or crash-dump copies.
8
9use crate::{
10    error::Error,
11    traits::serialize::{Serialize, SerializeSecret},
12    Result,
13};
14use core::fmt;
15use core::ops::{Deref, DerefMut};
16use dcrypt_internal::constant_time::ct_eq;
17pub use dcrypt_internal::zeroing::ZeroizingBytes;
18use dcrypt_internal::{
19    random::try_fill_bytes_zeroing_on_error,
20    zeroing::{
21        boxed_bytes_from_slice, boxed_bytes_zeroed, zeroizing_bytes_from_slice, Zeroize,
22        ZeroizeOnDrop, Zeroizing,
23    },
24};
25
26#[cfg(not(feature = "std"))]
27use alloc::{boxed::Box, vec::Vec};
28#[cfg(feature = "std")]
29use std::{boxed::Box, vec::Vec};
30
31/// A fixed-size byte array that explicitly clears its owned storage on drop.
32#[derive(Clone)]
33pub struct SecretBytes<const N: usize> {
34    data: [u8; N],
35}
36
37impl<const N: usize> Zeroize for SecretBytes<N> {
38    fn zeroize(&mut self) {
39        self.data.zeroize();
40    }
41}
42
43impl<const N: usize> ZeroizeOnDrop for SecretBytes<N> {}
44
45impl<const N: usize> Drop for SecretBytes<N> {
46    fn drop(&mut self) {
47        self.zeroize();
48    }
49}
50
51impl<const N: usize> SecretBytes<N> {
52    pub fn new(data: [u8; N]) -> Self {
53        Self { data }
54    }
55    pub fn from_slice(slice: &[u8]) -> Result<Self> {
56        if slice.len() != N {
57            return Err(Error::InvalidLength {
58                context: "SecretBytes::from_slice",
59                expected: N,
60                actual: slice.len(),
61            });
62        }
63        let mut data = [0u8; N];
64        data.copy_from_slice(slice);
65        Ok(Self { data })
66    }
67    pub fn zeroed() -> Self {
68        Self { data: [0u8; N] }
69    }
70    pub fn random<R: dcrypt_internal::random::CryptoRng + ?Sized>(rng: &mut R) -> Result<Self> {
71        let mut data = [0u8; N];
72        try_fill_bytes_zeroing_on_error(rng, &mut data).map_err(|_| {
73            Error::RandomGenerationError {
74                context: "SecretBytes::random",
75                #[cfg(feature = "std")]
76                message: "caller-provided randomness source failed".into(),
77            }
78        })?;
79        Ok(Self { data })
80    }
81    pub fn len(&self) -> usize {
82        N
83    }
84    pub fn is_empty(&self) -> bool {
85        N == 0
86    }
87
88    /// Serialize into exact-size storage that clears itself on drop.
89    pub fn to_bytes_zeroizing_boxed(&self) -> ZeroizingBytes {
90        zeroizing_bytes_from_slice(&self.data)
91    }
92}
93
94impl<const N: usize> AsRef<[u8]> for SecretBytes<N> {
95    fn as_ref(&self) -> &[u8] {
96        &self.data
97    }
98}
99
100impl<const N: usize> AsMut<[u8]> for SecretBytes<N> {
101    fn as_mut(&mut self) -> &mut [u8] {
102        &mut self.data
103    }
104}
105
106impl<const N: usize> Deref for SecretBytes<N> {
107    type Target = [u8; N];
108    fn deref(&self) -> &Self::Target {
109        &self.data
110    }
111}
112
113impl<const N: usize> DerefMut for SecretBytes<N> {
114    fn deref_mut(&mut self) -> &mut Self::Target {
115        &mut self.data
116    }
117}
118
119impl<const N: usize> PartialEq for SecretBytes<N> {
120    fn eq(&self, other: &Self) -> bool {
121        ct_eq(self.data.as_slice(), other.data.as_slice())
122    }
123}
124
125impl<const N: usize> Eq for SecretBytes<N> {}
126
127impl<const N: usize> fmt::Debug for SecretBytes<N> {
128    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
129        write!(f, "SecretBytes<{}>[REDACTED]", N)
130    }
131}
132
133impl<const N: usize> SerializeSecret for SecretBytes<N> {
134    fn from_bytes(bytes: &[u8]) -> Result<Self> {
135        Self::from_slice(bytes)
136    }
137    fn to_bytes_zeroizing(&self) -> ZeroizingBytes {
138        self.to_bytes_zeroizing_boxed()
139    }
140}
141
142/// Exact-size owned bytes whose initialized storage is explicitly cleared on drop.
143///
144/// This is best-effort software memory hygiene, not a physical-erasure guarantee
145/// for caller, compiler/register, allocator, swap, or crash-dump copies.
146pub struct SecretVec {
147    data: Box<[u8]>,
148}
149
150impl Zeroize for SecretVec {
151    fn zeroize(&mut self) {
152        self.data.zeroize();
153    }
154}
155
156impl ZeroizeOnDrop for SecretVec {}
157
158impl Drop for SecretVec {
159    fn drop(&mut self) {
160        self.zeroize();
161    }
162}
163
164impl SecretVec {
165    /// Take ownership of an exact-size boxed secret allocation.
166    ///
167    /// This constructor intentionally does not accept `Vec<u8>`: safe Rust
168    /// cannot inspect or erase a vector's spare capacity. Use [`from_slice`](Self::from_slice)
169    /// when copying from caller-owned storage.
170    pub fn new(data: Box<[u8]>) -> Self {
171        Self { data }
172    }
173
174    /// Copy a slice directly into exact-size owned storage.
175    pub fn from_slice(slice: &[u8]) -> Self {
176        Self::new(boxed_bytes_from_slice(slice))
177    }
178
179    /// Create an empty secret value.
180    pub fn empty() -> Self {
181        Self::new(boxed_bytes_zeroed(0))
182    }
183
184    /// Create an exact-size zero-filled secret value.
185    pub fn zeroed(len: usize) -> Self {
186        Self::new(boxed_bytes_zeroed(len))
187    }
188
189    /// Create an exact-size secret value from caller-provided randomness.
190    /// Any partial output is erased if the RNG reports failure.
191    pub fn random<R: dcrypt_internal::random::CryptoRng + ?Sized>(
192        rng: &mut R,
193        len: usize,
194    ) -> Result<Self> {
195        let mut data = boxed_bytes_zeroed(len);
196        try_fill_bytes_zeroing_on_error(rng, &mut data).map_err(|_| {
197            Error::RandomGenerationError {
198                context: "SecretVec::random",
199                #[cfg(feature = "std")]
200                message: "caller-provided randomness source failed".into(),
201            }
202        })?;
203        Ok(Self::new(data))
204    }
205    pub fn len(&self) -> usize {
206        self.data.len()
207    }
208    pub fn is_empty(&self) -> bool {
209        self.data.is_empty()
210    }
211
212    /// Return the secret bytes as a slice.
213    pub fn as_slice(&self) -> &[u8] {
214        &self.data
215    }
216
217    /// Return the secret bytes as a mutable slice.
218    ///
219    /// Slice access permits in-place byte changes but cannot resize or
220    /// reallocate the backing storage.
221    pub fn as_mut_slice(&mut self) -> &mut [u8] {
222        &mut self.data
223    }
224
225    /// Copy into exact-size storage that clears itself on drop.
226    pub fn to_bytes_zeroizing_boxed(&self) -> ZeroizingBytes {
227        zeroizing_bytes_from_slice(&self.data)
228    }
229
230    /// Consume this value without copying, transferring its exact-size
231    /// allocation into a zeroizing wrapper.
232    pub fn into_bytes_zeroizing_boxed(mut self) -> ZeroizingBytes {
233        let data = core::mem::replace(&mut self.data, boxed_bytes_zeroed(0));
234        Zeroizing::new(data)
235    }
236
237    /// Return the allocation capacity.
238    pub fn capacity(&self) -> usize {
239        self.data.len()
240    }
241
242    /// Extend the value, wiping the old allocation before freeing it if growth
243    /// requires a larger allocation.
244    pub fn extend_from_slice(&mut self, slice: &[u8]) {
245        let new_len = self
246            .data
247            .len()
248            .checked_add(slice.len())
249            .expect("SecretVec length overflow");
250        let mut replacement = boxed_bytes_zeroed(new_len);
251        replacement[..self.data.len()].copy_from_slice(&self.data);
252        replacement[self.data.len()..].copy_from_slice(slice);
253        self.replace_and_zeroize(replacement);
254    }
255
256    /// Resize the value. Bytes removed by shrinking are wiped first.
257    pub fn resize(&mut self, new_len: usize, value: u8) {
258        if new_len <= self.data.len() {
259            self.truncate(new_len);
260            return;
261        }
262
263        let mut replacement = boxed_bytes_zeroed(new_len);
264        replacement[..self.data.len()].copy_from_slice(&self.data);
265        replacement[self.data.len()..].fill(value);
266        self.replace_and_zeroize(replacement);
267    }
268
269    /// Shorten the value, wiping every removed byte before changing its length.
270    pub fn truncate(&mut self, len: usize) {
271        if len >= self.data.len() {
272            return;
273        }
274
275        let replacement = boxed_bytes_from_slice(&self.data[..len]);
276        self.replace_and_zeroize(replacement);
277    }
278
279    /// Remove all bytes while retaining a fully zeroed allocation.
280    pub fn clear(&mut self) {
281        self.replace_and_zeroize(boxed_bytes_zeroed(0));
282    }
283
284    /// Append one byte, securely replacing the allocation when it is full.
285    pub fn push(&mut self, value: u8) {
286        self.extend_from_slice(&[value]);
287    }
288
289    /// Remove and return the last byte, wiping its allocation slot first.
290    pub fn pop(&mut self) -> Option<u8> {
291        let value = self.data.last().copied()?;
292        self.truncate(self.data.len() - 1);
293        Some(value)
294    }
295
296    fn replace_and_zeroize(&mut self, replacement: Box<[u8]>) {
297        self.data.zeroize();
298        self.data = replacement;
299    }
300}
301
302impl Clone for SecretVec {
303    fn clone(&self) -> Self {
304        Self::from_slice(&self.data)
305    }
306}
307
308impl From<Box<[u8]>> for SecretVec {
309    fn from(data: Box<[u8]>) -> Self {
310        Self::new(data)
311    }
312}
313
314impl AsRef<[u8]> for SecretVec {
315    fn as_ref(&self) -> &[u8] {
316        self.data.as_ref()
317    }
318}
319
320impl AsMut<[u8]> for SecretVec {
321    fn as_mut(&mut self) -> &mut [u8] {
322        self.data.as_mut()
323    }
324}
325
326impl Deref for SecretVec {
327    type Target = [u8];
328    fn deref(&self) -> &Self::Target {
329        self.data.as_ref()
330    }
331}
332
333impl DerefMut for SecretVec {
334    fn deref_mut(&mut self) -> &mut Self::Target {
335        self.data.as_mut()
336    }
337}
338
339impl PartialEq for SecretVec {
340    fn eq(&self, other: &Self) -> bool {
341        ct_eq(self.data.as_ref(), other.data.as_ref())
342    }
343}
344
345impl Eq for SecretVec {}
346
347impl fmt::Debug for SecretVec {
348    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
349        write!(f, "SecretVec({})[REDACTED]", self.data.len())
350    }
351}
352
353impl SerializeSecret for SecretVec {
354    fn from_bytes(bytes: &[u8]) -> Result<Self> {
355        Ok(Self::from_slice(bytes))
356    }
357    fn to_bytes_zeroizing(&self) -> ZeroizingBytes {
358        self.to_bytes_zeroizing_boxed()
359    }
360}
361
362#[cfg(test)]
363mod secret_vec_tests {
364    use super::{Key, SecretBytes, SecretVec};
365    use crate::traits::SerializeSecret;
366    use dcrypt_internal::random::{CryptoRng, Error as RandomError, RngCore};
367
368    #[test]
369    fn constructor_uses_exact_size_storage() {
370        let secret = SecretVec::new([0xA5; 4].into());
371
372        assert_eq!(secret.as_slice(), &[0xA5; 4]);
373        assert_eq!(secret.capacity(), secret.len());
374    }
375
376    #[test]
377    fn shrinking_operations_wipe_removed_slots() {
378        let mut secret = SecretVec::from_slice(&[1, 2, 3, 4, 5, 6]);
379
380        secret.truncate(4);
381        assert_eq!(secret.as_slice(), &[1, 2, 3, 4]);
382        assert_eq!(secret.capacity(), secret.len());
383
384        secret.resize(2, 0xFF);
385        assert_eq!(secret.as_slice(), &[1, 2]);
386        assert_eq!(secret.capacity(), secret.len());
387
388        assert_eq!(secret.pop(), Some(2));
389        assert_eq!(secret.as_slice(), &[1]);
390        assert_eq!(secret.capacity(), secret.len());
391
392        secret.clear();
393        assert!(secret.is_empty());
394        assert_eq!(secret.capacity(), secret.len());
395    }
396
397    #[test]
398    fn growth_and_shrink_replace_allocations_securely() {
399        let mut secret = SecretVec::from_slice(&[0x11; 4]);
400        secret.extend_from_slice(&[0x22, 0x33]);
401        assert_eq!(secret.capacity(), 6);
402        assert_eq!(secret.as_slice(), &[0x11, 0x11, 0x11, 0x11, 0x22, 0x33]);
403    }
404
405    #[test]
406    fn exact_size_serialization_supports_copy_and_ownership_transfer() {
407        let fixed = SecretBytes::<4>::new([1, 2, 3, 4]);
408        let fixed_bytes = fixed.to_bytes_zeroizing_boxed();
409        assert_eq!(&**fixed_bytes, &[1, 2, 3, 4]);
410        let trait_fixed_bytes = fixed.to_bytes_zeroizing();
411        assert_eq!(&**trait_fixed_bytes, &[1, 2, 3, 4]);
412
413        let secret = SecretVec::from_slice(&[5, 6, 7]);
414        let copied = secret.to_bytes_zeroizing_boxed();
415        assert_eq!(&**copied, &[5, 6, 7]);
416        let trait_copied = secret.to_bytes_zeroizing();
417        assert_eq!(&**trait_copied, &[5, 6, 7]);
418
419        let transferred = secret.into_bytes_zeroizing_boxed();
420        assert_eq!(&**transferred, &[5, 6, 7]);
421
422        let key = Key::from_slice(&[8, 9]);
423        let key_bytes = key.to_bytes_zeroizing_boxed();
424        assert_eq!(&**key_bytes, &[8, 9]);
425        let trait_key_bytes = key.to_bytes_zeroizing();
426        assert_eq!(&**trait_key_bytes, &[8, 9]);
427    }
428
429    struct PartiallyFailingRng;
430
431    impl RngCore for PartiallyFailingRng {
432        fn try_fill_bytes(&mut self, destination: &mut [u8]) -> Result<(), RandomError> {
433            let written = core::cmp::min(destination.len(), 5);
434            destination[..written].fill(0xA5);
435            Err(RandomError)
436        }
437    }
438
439    impl CryptoRng for PartiallyFailingRng {}
440
441    #[test]
442    fn random_secret_constructors_propagate_caller_rng_failure() {
443        let mut rng = PartiallyFailingRng;
444        assert!(SecretBytes::<32>::random(&mut rng).is_err());
445        assert!(SecretVec::random(&mut rng, 32).is_err());
446    }
447}
448
449/// Base key type backed by exact-size secret storage.
450#[derive(Clone)]
451pub struct Key {
452    data: Box<[u8]>,
453}
454
455impl Zeroize for Key {
456    fn zeroize(&mut self) {
457        self.data.zeroize();
458    }
459}
460
461impl ZeroizeOnDrop for Key {}
462
463impl Drop for Key {
464    fn drop(&mut self) {
465        self.zeroize();
466    }
467}
468
469impl Key {
470    /// Copy key bytes directly into exact-size owned storage.
471    ///
472    /// The caller retains responsibility for clearing the source buffer. This
473    /// constructor intentionally accepts a slice rather than taking a `Vec`:
474    /// safe Rust cannot inspect or erase a vector's spare capacity.
475    pub fn new(data: &[u8]) -> Self {
476        Self::from_boxed_slice(boxed_bytes_from_slice(data))
477    }
478
479    /// Take ownership of an exact-size boxed key allocation.
480    pub fn from_boxed_slice(data: Box<[u8]>) -> Self {
481        Self { data }
482    }
483
484    /// Copy key bytes directly into exact-size owned storage.
485    pub fn from_slice(data: &[u8]) -> Self {
486        Self::new(data)
487    }
488
489    pub fn new_zeros(len: usize) -> Self {
490        Self::from_boxed_slice(boxed_bytes_zeroed(len))
491    }
492    pub fn len(&self) -> usize {
493        self.data.len()
494    }
495    pub fn is_empty(&self) -> bool {
496        self.data.is_empty()
497    }
498
499    /// Serialize into exact-size storage that clears itself on drop.
500    pub fn to_bytes_zeroizing_boxed(&self) -> ZeroizingBytes {
501        zeroizing_bytes_from_slice(&self.data)
502    }
503}
504
505impl From<Box<[u8]>> for Key {
506    fn from(data: Box<[u8]>) -> Self {
507        Self::from_boxed_slice(data)
508    }
509}
510
511impl AsRef<[u8]> for Key {
512    fn as_ref(&self) -> &[u8] {
513        self.data.as_ref()
514    }
515}
516
517impl AsMut<[u8]> for Key {
518    fn as_mut(&mut self) -> &mut [u8] {
519        self.data.as_mut()
520    }
521}
522
523impl SerializeSecret for Key {
524    fn from_bytes(bytes: &[u8]) -> Result<Self> {
525        Ok(Self::from_slice(bytes))
526    }
527    fn to_bytes_zeroizing(&self) -> ZeroizingBytes {
528        self.to_bytes_zeroizing_boxed()
529    }
530}
531
532/// Wrapper for public key data
533#[derive(Clone)]
534pub struct PublicKey {
535    data: Vec<u8>,
536}
537
538impl PublicKey {
539    /// Create a new PublicKey.
540    ///
541    /// Accepts `Vec<u8>` (move) or `&[u8]` (copy).
542    /// Moving a `Vec<u8>` avoids unnecessary allocation.
543    pub fn new<T: Into<Vec<u8>>>(data: T) -> Self {
544        Self { data: data.into() }
545    }
546    pub fn len(&self) -> usize {
547        self.data.len()
548    }
549    pub fn is_empty(&self) -> bool {
550        self.data.is_empty()
551    }
552}
553
554impl From<Vec<u8>> for PublicKey {
555    fn from(data: Vec<u8>) -> Self {
556        Self::new(data)
557    }
558}
559
560impl AsRef<[u8]> for PublicKey {
561    fn as_ref(&self) -> &[u8] {
562        &self.data
563    }
564}
565
566impl AsMut<[u8]> for PublicKey {
567    fn as_mut(&mut self) -> &mut [u8] {
568        &mut self.data
569    }
570}
571
572impl Serialize for PublicKey {
573    fn to_bytes(&self) -> Vec<u8> {
574        self.data.clone()
575    }
576    fn from_bytes(bytes: &[u8]) -> Result<Self> {
577        Ok(Self::new(bytes))
578    }
579}
580
581/// Wrapper for ciphertext data
582#[derive(Clone)]
583pub struct Ciphertext {
584    data: Vec<u8>,
585}
586
587impl Ciphertext {
588    /// Create a new Ciphertext.
589    ///
590    /// Accepts `Vec<u8>` (move) or `&[u8]` (copy).
591    /// Moving a `Vec<u8>` avoids unnecessary allocation, which is critical
592    /// for large ciphertexts.
593    pub fn new<T: Into<Vec<u8>>>(data: T) -> Self {
594        Self { data: data.into() }
595    }
596    pub fn len(&self) -> usize {
597        self.data.len()
598    }
599    pub fn is_empty(&self) -> bool {
600        self.data.is_empty()
601    }
602}
603
604impl From<Vec<u8>> for Ciphertext {
605    fn from(data: Vec<u8>) -> Self {
606        Self::new(data)
607    }
608}
609
610impl AsRef<[u8]> for Ciphertext {
611    fn as_ref(&self) -> &[u8] {
612        &self.data
613    }
614}
615
616impl AsMut<[u8]> for Ciphertext {
617    fn as_mut(&mut self) -> &mut [u8] {
618        &mut self.data
619    }
620}
621
622impl Serialize for Ciphertext {
623    fn to_bytes(&self) -> Vec<u8> {
624        self.data.clone()
625    }
626    fn from_bytes(bytes: &[u8]) -> Result<Self> {
627        Ok(Self::new(bytes))
628    }
629}