oqs_safe/
kem.rs

1// Copyright (c) 2025 Orlando Trajano
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4//! KEM API with safe accessors and feature-gated RNG for the mock backend.
5
6use crate::OqsError;
7use zeroize::Zeroize;
8
9#[cfg(not(feature = "liboqs"))]
10use rand_core::{OsRng, RngCore};
11
12#[cfg(feature = "serde")]
13use serde::{Deserialize, Serialize};
14
15/// Public key newtype
16#[derive(Clone, Debug)]
17#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
18pub struct PublicKey(pub(crate) Vec<u8>);
19
20/// Secret key newtype (zeroizes on drop)
21#[derive(Clone, Debug, Zeroize)]
22#[zeroize(drop)]
23#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
24pub struct SecretKey(pub(crate) Vec<u8>);
25
26/// Ciphertext newtype
27#[derive(Clone, Debug)]
28#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
29pub struct Ciphertext(pub(crate) Vec<u8>);
30
31/// Shared secret newtype
32#[derive(Clone, Debug)]
33#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
34pub struct SharedSecret(pub(crate) Vec<u8>);
35
36impl PublicKey {
37    #[inline]
38    pub fn as_bytes(&self) -> &[u8] {
39        &self.0
40    }
41    #[inline]
42    pub fn len(&self) -> usize {
43        self.0.len()
44    }
45    #[inline]
46    pub fn is_empty(&self) -> bool {
47        self.0.is_empty()
48    }
49}
50impl SecretKey {
51    #[inline]
52    pub fn as_bytes(&self) -> &[u8] {
53        &self.0
54    }
55    #[inline]
56    pub fn len(&self) -> usize {
57        self.0.len()
58    }
59    #[inline]
60    pub fn is_empty(&self) -> bool {
61        self.0.is_empty()
62    }
63}
64impl Ciphertext {
65    #[inline]
66    pub fn as_bytes(&self) -> &[u8] {
67        &self.0
68    }
69    #[inline]
70    pub fn len(&self) -> usize {
71        self.0.len()
72    }
73    #[inline]
74    pub fn is_empty(&self) -> bool {
75        self.0.is_empty()
76    }
77}
78impl SharedSecret {
79    #[inline]
80    pub fn as_bytes(&self) -> &[u8] {
81        &self.0
82    }
83    #[inline]
84    pub fn len(&self) -> usize {
85        self.0.len()
86    }
87    #[inline]
88    pub fn is_empty(&self) -> bool {
89        self.0.is_empty()
90    }
91}
92
93/// KEM trait
94pub trait Kem {
95    fn keypair() -> Result<(PublicKey, SecretKey), OqsError>;
96    fn encapsulate(pk: &PublicKey) -> Result<(Ciphertext, SharedSecret), OqsError>;
97    fn decapsulate(ct: &Ciphertext, sk: &SecretKey) -> Result<SharedSecret, OqsError>;
98}
99
100/// Kyber768 implementation
101pub struct Kyber768;
102
103impl Kem for Kyber768 {
104    fn keypair() -> Result<(PublicKey, SecretKey), OqsError> {
105        #[cfg(feature = "liboqs")]
106        {
107            let (pk, sk) = crate::ffi::kyber768_keypair()?;
108            Ok((PublicKey(pk), SecretKey(sk)))
109        }
110        #[cfg(not(feature = "liboqs"))]
111        {
112            // Mock path: size-faithful random buffers for CI / no-liboqs environments.
113            let mut pk = vec![0u8; 1184]; // Kyber768 pk size
114            let mut sk = vec![0u8; 2400]; // Kyber768 sk size
115            OsRng.fill_bytes(&mut pk);
116            OsRng.fill_bytes(&mut sk);
117            Ok((PublicKey(pk), SecretKey(sk)))
118        }
119    }
120
121    fn encapsulate(pk: &PublicKey) -> Result<(Ciphertext, SharedSecret), OqsError> {
122        #[cfg(feature = "liboqs")]
123        {
124            crate::ffi::kyber768_encapsulate(pk.as_bytes())
125                .map(|(c, s)| (Ciphertext(c), SharedSecret(s)))
126        }
127        #[cfg(not(feature = "liboqs"))]
128        {
129            if pk.len() != 1184 {
130                return Err(OqsError::InvalidLength);
131            }
132            let mut ct = vec![0u8; 1088]; // Kyber768 ct size
133            let mut ss = vec![0u8; 32]; // Shared secret size (bytes)
134            OsRng.fill_bytes(&mut ct);
135            OsRng.fill_bytes(&mut ss);
136            Ok((Ciphertext(ct), SharedSecret(ss)))
137        }
138    }
139
140    fn decapsulate(ct: &Ciphertext, sk: &SecretKey) -> Result<SharedSecret, OqsError> {
141        #[cfg(feature = "liboqs")]
142        {
143            crate::ffi::kyber768_decapsulate(ct.as_bytes(), sk.as_bytes()).map(SharedSecret)
144        }
145        #[cfg(not(feature = "liboqs"))]
146        {
147            if ct.len() != 1088 || sk.len() != 2400 {
148                return Err(OqsError::InvalidLength);
149            }
150            let mut ss = vec![0u8; 32];
151            OsRng.fill_bytes(&mut ss);
152            Ok(SharedSecret(ss))
153        }
154    }
155}
156
157// ---- TEST-ONLY CONSTRUCTORS (gated behind the "testing" feature) ----
158#[cfg(feature = "testing")]
159impl PublicKey {
160    /// Construct without size checks (tests only).
161    pub fn from_bytes_unchecked(bytes: Vec<u8>) -> Self {
162        Self(bytes)
163    }
164}
165#[cfg(feature = "testing")]
166impl SecretKey {
167    /// Construct without size checks (tests only).
168    pub fn from_bytes_unchecked(bytes: Vec<u8>) -> Self {
169        Self(bytes)
170    }
171}
172#[cfg(feature = "testing")]
173impl Ciphertext {
174    /// Construct without size checks (tests only).
175    pub fn from_bytes_unchecked(bytes: Vec<u8>) -> Self {
176        Self(bytes)
177    }
178}