Skip to main content

sedsnet/
crypto.rs

1//! Feature-gated cryptography provider interfaces.
2//!
3//! Registered C/Rust providers are preferred so std builds can use OS crypto APIs
4//! and embedded builds can use hardware accelerators or secure elements. A
5//! small software fallback is also available when a key is registered.
6
7use crate::{TelemetryError, TelemetryResult};
8
9const SOFTWARE_KEY_SLOTS: usize = 8;
10const SOFTWARE_KEY_MIN_LEN: usize = 16;
11const SOFTWARE_TAG_LEN: usize = 16;
12pub const MANAGED_CREDENTIAL_LEN: usize = 80;
13const MANAGED_CREDENTIAL_BODY_LEN: usize = 48;
14const MANAGED_CREDENTIAL_MAGIC: &[u8; 8] = b"SEDSCR1\0";
15
16/// Rust-side AEAD-like payload cryptography provider.
17///
18/// `key_id` is application-defined. `nonce` and `aad` are passed through by the
19/// caller; the provider is responsible for enforcing the algorithm's expected sizes.
20pub trait CryptographyProvider: Sync {
21    fn seal(
22        &self,
23        key_id: u32,
24        nonce: &[u8],
25        aad: &[u8],
26        plaintext: &[u8],
27        ciphertext_out: &mut [u8],
28        tag_out: &mut [u8],
29    ) -> TelemetryResult<(usize, usize)>;
30
31    fn open(
32        &self,
33        key_id: u32,
34        nonce: &[u8],
35        aad: &[u8],
36        ciphertext: &[u8],
37        tag: &[u8],
38        plaintext_out: &mut [u8],
39    ) -> TelemetryResult<usize>;
40}
41
42/// Use a Rust provider directly without any C ABI involvement.
43pub fn seal_with<S: CryptographyProvider + ?Sized>(
44    provider: &S,
45    key_id: u32,
46    nonce: &[u8],
47    aad: &[u8],
48    plaintext: &[u8],
49    ciphertext_out: &mut [u8],
50    tag_out: &mut [u8],
51) -> TelemetryResult<(usize, usize)> {
52    provider.seal(key_id, nonce, aad, plaintext, ciphertext_out, tag_out)
53}
54
55/// Use a Rust provider directly without any C ABI involvement.
56pub fn open_with<S: CryptographyProvider + ?Sized>(
57    provider: &S,
58    key_id: u32,
59    nonce: &[u8],
60    aad: &[u8],
61    ciphertext: &[u8],
62    tag: &[u8],
63    plaintext_out: &mut [u8],
64) -> TelemetryResult<usize> {
65    provider.open(key_id, nonce, aad, ciphertext, tag, plaintext_out)
66}
67
68pub type CSealFn = unsafe extern "C" fn(
69    key_id: u32,
70    nonce: *const u8,
71    nonce_len: usize,
72    aad: *const u8,
73    aad_len: usize,
74    plaintext: *const u8,
75    plaintext_len: usize,
76    ciphertext_out: *mut u8,
77    ciphertext_cap: usize,
78    ciphertext_len_out: *mut usize,
79    tag_out: *mut u8,
80    tag_cap: usize,
81    tag_len_out: *mut usize,
82    user: *mut core::ffi::c_void,
83) -> i32;
84
85pub type COpenFn = unsafe extern "C" fn(
86    key_id: u32,
87    nonce: *const u8,
88    nonce_len: usize,
89    aad: *const u8,
90    aad_len: usize,
91    ciphertext: *const u8,
92    ciphertext_len: usize,
93    tag: *const u8,
94    tag_len: usize,
95    plaintext_out: *mut u8,
96    plaintext_cap: usize,
97    plaintext_len_out: *mut usize,
98    user: *mut core::ffi::c_void,
99) -> i32;
100
101#[derive(Clone, Copy)]
102pub struct CCryptographyProvider {
103    pub seal: Option<CSealFn>,
104    pub open: Option<COpenFn>,
105    pub user: *mut core::ffi::c_void,
106}
107
108static mut C_SHIM: CCryptographyProvider = CCryptographyProvider {
109    seal: None,
110    open: None,
111    user: core::ptr::null_mut(),
112};
113
114static mut RUST_SHIM: Option<&'static dyn CryptographyProvider> = None;
115
116#[derive(Clone, Copy)]
117struct SoftwareKey {
118    key_id: u32,
119    key: [u8; 32],
120}
121
122#[derive(Clone, Copy)]
123struct SoftwareKeyring {
124    keys: [Option<SoftwareKey>; SOFTWARE_KEY_SLOTS],
125}
126
127impl SoftwareKeyring {
128    const fn new() -> Self {
129        Self {
130            keys: [None; SOFTWARE_KEY_SLOTS],
131        }
132    }
133
134    fn is_registered(&self) -> bool {
135        self.keys.iter().any(Option::is_some)
136    }
137
138    fn get(&self, key_id: u32) -> Option<[u8; 32]> {
139        self.keys
140            .iter()
141            .flatten()
142            .find(|key| key.key_id == key_id)
143            .map(|key| key.key)
144    }
145
146    fn register(&mut self, key_id: u32, raw_key: &[u8]) -> TelemetryResult<()> {
147        if raw_key.len() < SOFTWARE_KEY_MIN_LEN {
148            return Err(TelemetryError::BadArg);
149        }
150        let key = normalize_software_key(raw_key);
151        if let Some(slot) = self
152            .keys
153            .iter_mut()
154            .find(|slot| slot.map(|key| key.key_id) == Some(key_id))
155        {
156            *slot = Some(SoftwareKey { key_id, key });
157            return Ok(());
158        }
159        if let Some(slot) = self.keys.iter_mut().find(|slot| slot.is_none()) {
160            *slot = Some(SoftwareKey { key_id, key });
161            return Ok(());
162        }
163        Err(TelemetryError::SizeMismatchError)
164    }
165}
166
167static mut SOFTWARE_KEYS: SoftwareKeyring = SoftwareKeyring::new();
168
169/// Register a C callback provider. This should be called during board startup
170/// before concurrent router/relay work begins.
171pub fn register_c_cryptography_provider(provider: CCryptographyProvider) {
172    unsafe {
173        core::ptr::addr_of_mut!(C_SHIM).write(provider);
174    }
175}
176
177pub fn clear_c_cryptography_provider() {
178    register_c_cryptography_provider(CCryptographyProvider {
179        seal: None,
180        open: None,
181        user: core::ptr::null_mut(),
182    });
183}
184
185pub fn c_cryptography_provider_registered() -> bool {
186    let provider = unsafe { core::ptr::addr_of!(C_SHIM).read() };
187    provider.seal.is_some() && provider.open.is_some()
188}
189
190/// Register a Rust cryptography provider for process-wide router/relay E2E traffic.
191///
192/// Registered providers are preferred over the software fallback. In std builds the
193/// provider can wrap OS crypto APIs; in embedded builds it can wrap hardware crypto
194/// or a secure element. Call during startup before concurrent router work.
195pub fn register_rust_cryptography_provider(provider: &'static dyn CryptographyProvider) {
196    unsafe {
197        core::ptr::addr_of_mut!(RUST_SHIM).write(Some(provider));
198    }
199}
200
201pub fn clear_rust_cryptography_provider() {
202    unsafe {
203        core::ptr::addr_of_mut!(RUST_SHIM).write(None);
204    }
205}
206
207pub fn rust_cryptography_provider_registered() -> bool {
208    unsafe { core::ptr::addr_of!(RUST_SHIM).read().is_some() }
209}
210
211/// Register a software fallback key for `key_id`.
212///
213/// The fallback uses HMAC-SHA256 to derive a stream and authenticate the
214/// ciphertext. Prefer OS/hardware providers when they are available; this path keeps
215/// encrypted traffic functional when no accelerator or OS provider is present.
216pub fn register_software_key(key_id: u32, key: &[u8]) -> TelemetryResult<()> {
217    let mut keyring = unsafe { core::ptr::addr_of!(SOFTWARE_KEYS).read() };
218    keyring.register(key_id, key)?;
219    unsafe {
220        core::ptr::addr_of_mut!(SOFTWARE_KEYS).write(keyring);
221    }
222    Ok(())
223}
224
225pub fn clear_software_keys() {
226    unsafe {
227        core::ptr::addr_of_mut!(SOFTWARE_KEYS).write(SoftwareKeyring::new());
228    }
229}
230
231pub fn software_crypto_available() -> bool {
232    unsafe { core::ptr::addr_of!(SOFTWARE_KEYS).read().is_registered() }
233}
234
235pub fn registered_crypto_available() -> bool {
236    c_cryptography_provider_registered()
237        || rust_cryptography_provider_registered()
238        || software_crypto_available()
239}
240
241#[derive(Debug, Clone, Copy, PartialEq, Eq)]
242pub struct ManagedCredential {
243    pub subject_id: u64,
244    pub key_id: u32,
245    pub epoch: u64,
246    pub not_before_ms: u64,
247    pub not_after_ms: u64,
248    pub permissions: u32,
249}
250
251/// Issue a compact master-signed credential.
252///
253/// This is a low-bandwidth alternative to user-managed cert files. Devices still
254/// need a provisioned root key or pinned master identity to verify credentials.
255pub fn issue_managed_credential(
256    root_key: &[u8],
257    credential: ManagedCredential,
258    out: &mut [u8],
259) -> TelemetryResult<usize> {
260    if root_key.len() < SOFTWARE_KEY_MIN_LEN || out.len() < MANAGED_CREDENTIAL_LEN {
261        return Err(TelemetryError::BadArg);
262    }
263    write_managed_credential_body(credential, &mut out[..MANAGED_CREDENTIAL_BODY_LEN]);
264    let key = normalize_software_key(root_key);
265    let tag = hmac_sha256(
266        &key,
267        &[
268            b"SEDS-MASTER-CREDENTIAL",
269            &out[..MANAGED_CREDENTIAL_BODY_LEN],
270        ],
271    );
272    out[MANAGED_CREDENTIAL_BODY_LEN..MANAGED_CREDENTIAL_LEN].copy_from_slice(&tag);
273    Ok(MANAGED_CREDENTIAL_LEN)
274}
275
276/// Verify a compact master-signed credential and check its validity window.
277pub fn verify_managed_credential(
278    root_key: &[u8],
279    bytes: &[u8],
280    now_ms: u64,
281) -> TelemetryResult<ManagedCredential> {
282    if root_key.len() < SOFTWARE_KEY_MIN_LEN || bytes.len() != MANAGED_CREDENTIAL_LEN {
283        return Err(TelemetryError::BadArg);
284    }
285    let credential = read_managed_credential_body(&bytes[..MANAGED_CREDENTIAL_BODY_LEN])?;
286    if now_ms < credential.not_before_ms || now_ms > credential.not_after_ms {
287        return Err(TelemetryError::HandlerError("credential expired"));
288    }
289    let key = normalize_software_key(root_key);
290    let expected = hmac_sha256(
291        &key,
292        &[
293            b"SEDS-MASTER-CREDENTIAL",
294            &bytes[..MANAGED_CREDENTIAL_BODY_LEN],
295        ],
296    );
297    if !constant_time_eq(
298        &bytes[MANAGED_CREDENTIAL_BODY_LEN..MANAGED_CREDENTIAL_LEN],
299        &expected,
300    ) {
301        return Err(TelemetryError::HandlerError("credential signature"));
302    }
303    Ok(credential)
304}
305
306pub fn seal_with_registered_c_provider(
307    key_id: u32,
308    nonce: &[u8],
309    aad: &[u8],
310    plaintext: &[u8],
311    ciphertext_out: &mut [u8],
312    tag_out: &mut [u8],
313) -> TelemetryResult<(usize, usize)> {
314    let provider = unsafe { core::ptr::addr_of!(C_SHIM).read() };
315    let Some(seal) = provider.seal else {
316        return Err(TelemetryError::BadArg);
317    };
318    let mut ciphertext_len = 0usize;
319    let mut tag_len = 0usize;
320    let status = unsafe {
321        seal(
322            key_id,
323            nonce.as_ptr(),
324            nonce.len(),
325            aad.as_ptr(),
326            aad.len(),
327            plaintext.as_ptr(),
328            plaintext.len(),
329            ciphertext_out.as_mut_ptr(),
330            ciphertext_out.len(),
331            &mut ciphertext_len,
332            tag_out.as_mut_ptr(),
333            tag_out.len(),
334            &mut tag_len,
335            provider.user,
336        )
337    };
338    if status != 0 {
339        return Err(TelemetryError::HandlerError("crypto seal"));
340    }
341    if ciphertext_len > ciphertext_out.len() || tag_len > tag_out.len() {
342        return Err(TelemetryError::SizeMismatchError);
343    }
344    Ok((ciphertext_len, tag_len))
345}
346
347pub fn open_with_registered_c_provider(
348    key_id: u32,
349    nonce: &[u8],
350    aad: &[u8],
351    ciphertext: &[u8],
352    tag: &[u8],
353    plaintext_out: &mut [u8],
354) -> TelemetryResult<usize> {
355    let provider = unsafe { core::ptr::addr_of!(C_SHIM).read() };
356    let Some(open) = provider.open else {
357        return Err(TelemetryError::BadArg);
358    };
359    let mut plaintext_len = 0usize;
360    let status = unsafe {
361        open(
362            key_id,
363            nonce.as_ptr(),
364            nonce.len(),
365            aad.as_ptr(),
366            aad.len(),
367            ciphertext.as_ptr(),
368            ciphertext.len(),
369            tag.as_ptr(),
370            tag.len(),
371            plaintext_out.as_mut_ptr(),
372            plaintext_out.len(),
373            &mut plaintext_len,
374            provider.user,
375        )
376    };
377    if status != 0 {
378        return Err(TelemetryError::HandlerError("crypto open"));
379    }
380    if plaintext_len > plaintext_out.len() {
381        return Err(TelemetryError::SizeMismatchError);
382    }
383    Ok(plaintext_len)
384}
385
386pub fn seal_with_registered_crypto(
387    key_id: u32,
388    nonce: &[u8],
389    aad: &[u8],
390    plaintext: &[u8],
391    ciphertext_out: &mut [u8],
392    tag_out: &mut [u8],
393) -> TelemetryResult<(usize, usize)> {
394    if c_cryptography_provider_registered() {
395        return seal_with_registered_c_provider(
396            key_id,
397            nonce,
398            aad,
399            plaintext,
400            ciphertext_out,
401            tag_out,
402        );
403    }
404    if let Some(provider) = unsafe { core::ptr::addr_of!(RUST_SHIM).read() } {
405        return seal_with(
406            provider,
407            key_id,
408            nonce,
409            aad,
410            plaintext,
411            ciphertext_out,
412            tag_out,
413        );
414    }
415    seal_with_software_key(key_id, nonce, aad, plaintext, ciphertext_out, tag_out)
416}
417
418pub fn open_with_registered_crypto(
419    key_id: u32,
420    nonce: &[u8],
421    aad: &[u8],
422    ciphertext: &[u8],
423    tag: &[u8],
424    plaintext_out: &mut [u8],
425) -> TelemetryResult<usize> {
426    if c_cryptography_provider_registered() {
427        return open_with_registered_c_provider(key_id, nonce, aad, ciphertext, tag, plaintext_out);
428    }
429    if let Some(provider) = unsafe { core::ptr::addr_of!(RUST_SHIM).read() } {
430        return open_with(provider, key_id, nonce, aad, ciphertext, tag, plaintext_out);
431    }
432    open_with_software_key(key_id, nonce, aad, ciphertext, tag, plaintext_out)
433}
434
435fn seal_with_software_key(
436    key_id: u32,
437    nonce: &[u8],
438    aad: &[u8],
439    plaintext: &[u8],
440    ciphertext_out: &mut [u8],
441    tag_out: &mut [u8],
442) -> TelemetryResult<(usize, usize)> {
443    if ciphertext_out.len() < plaintext.len() || tag_out.len() < SOFTWARE_TAG_LEN {
444        return Err(TelemetryError::SizeMismatchError);
445    }
446    let key = software_key_for(key_id)?;
447    apply_hmac_stream(&key, key_id, nonce, aad, plaintext, ciphertext_out);
448    let tag = software_tag(&key, key_id, nonce, aad, &ciphertext_out[..plaintext.len()]);
449    tag_out[..SOFTWARE_TAG_LEN].copy_from_slice(&tag[..SOFTWARE_TAG_LEN]);
450    Ok((plaintext.len(), SOFTWARE_TAG_LEN))
451}
452
453fn open_with_software_key(
454    key_id: u32,
455    nonce: &[u8],
456    aad: &[u8],
457    ciphertext: &[u8],
458    tag: &[u8],
459    plaintext_out: &mut [u8],
460) -> TelemetryResult<usize> {
461    if plaintext_out.len() < ciphertext.len() || tag.len() != SOFTWARE_TAG_LEN {
462        return Err(TelemetryError::SizeMismatchError);
463    }
464    let key = software_key_for(key_id)?;
465    let expected = software_tag(&key, key_id, nonce, aad, ciphertext);
466    if !constant_time_eq(tag, &expected[..SOFTWARE_TAG_LEN]) {
467        return Err(TelemetryError::HandlerError("crypto open"));
468    }
469    apply_hmac_stream(&key, key_id, nonce, aad, ciphertext, plaintext_out);
470    Ok(ciphertext.len())
471}
472
473fn software_key_for(key_id: u32) -> TelemetryResult<[u8; 32]> {
474    unsafe { core::ptr::addr_of!(SOFTWARE_KEYS).read() }
475        .get(key_id)
476        .ok_or(TelemetryError::BadArg)
477}
478
479fn write_managed_credential_body(credential: ManagedCredential, out: &mut [u8]) {
480    out[..8].copy_from_slice(MANAGED_CREDENTIAL_MAGIC);
481    out[8..16].copy_from_slice(&credential.subject_id.to_le_bytes());
482    out[16..20].copy_from_slice(&credential.key_id.to_le_bytes());
483    out[20..28].copy_from_slice(&credential.epoch.to_le_bytes());
484    out[28..36].copy_from_slice(&credential.not_before_ms.to_le_bytes());
485    out[36..44].copy_from_slice(&credential.not_after_ms.to_le_bytes());
486    out[44..48].copy_from_slice(&credential.permissions.to_le_bytes());
487}
488
489fn read_managed_credential_body(bytes: &[u8]) -> TelemetryResult<ManagedCredential> {
490    if bytes.len() != MANAGED_CREDENTIAL_BODY_LEN || &bytes[..8] != MANAGED_CREDENTIAL_MAGIC {
491        return Err(TelemetryError::BadArg);
492    }
493    Ok(ManagedCredential {
494        subject_id: u64::from_le_bytes(bytes[8..16].try_into().unwrap()),
495        key_id: u32::from_le_bytes(bytes[16..20].try_into().unwrap()),
496        epoch: u64::from_le_bytes(bytes[20..28].try_into().unwrap()),
497        not_before_ms: u64::from_le_bytes(bytes[28..36].try_into().unwrap()),
498        not_after_ms: u64::from_le_bytes(bytes[36..44].try_into().unwrap()),
499        permissions: u32::from_le_bytes(bytes[44..48].try_into().unwrap()),
500    })
501}
502
503fn normalize_software_key(raw_key: &[u8]) -> [u8; 32] {
504    if raw_key.len() == 32 {
505        let mut key = [0u8; 32];
506        key.copy_from_slice(raw_key);
507        key
508    } else {
509        Sha256::digest(raw_key)
510    }
511}
512
513fn apply_hmac_stream(
514    key: &[u8; 32],
515    key_id: u32,
516    nonce: &[u8],
517    aad: &[u8],
518    input: &[u8],
519    output: &mut [u8],
520) {
521    let key_id_bytes = key_id.to_le_bytes();
522    let mut counter = 0u64;
523    let mut offset = 0usize;
524    while offset < input.len() {
525        let counter_bytes = counter.to_le_bytes();
526        let block = hmac_sha256(
527            key,
528            &[
529                b"SEDS-HMAC-STREAM",
530                &key_id_bytes,
531                nonce,
532                aad,
533                &counter_bytes,
534            ],
535        );
536        let remaining = input.len() - offset;
537        let take = remaining.min(block.len());
538        for idx in 0..take {
539            output[offset + idx] = input[offset + idx] ^ block[idx];
540        }
541        offset += take;
542        counter = counter.wrapping_add(1);
543    }
544}
545
546fn software_tag(
547    key: &[u8; 32],
548    key_id: u32,
549    nonce: &[u8],
550    aad: &[u8],
551    ciphertext: &[u8],
552) -> [u8; 32] {
553    let key_id_bytes = key_id.to_le_bytes();
554    hmac_sha256(
555        key,
556        &[b"SEDS-HMAC-TAG", &key_id_bytes, nonce, aad, ciphertext],
557    )
558}
559
560fn constant_time_eq(a: &[u8], b: &[u8]) -> bool {
561    if a.len() != b.len() {
562        return false;
563    }
564    let mut diff = 0u8;
565    for idx in 0..a.len() {
566        diff |= a[idx] ^ b[idx];
567    }
568    diff == 0
569}
570
571fn hmac_sha256(key: &[u8; 32], chunks: &[&[u8]]) -> [u8; 32] {
572    let mut ipad = [0x36u8; 64];
573    let mut opad = [0x5cu8; 64];
574    for idx in 0..key.len() {
575        ipad[idx] ^= key[idx];
576        opad[idx] ^= key[idx];
577    }
578
579    let mut inner = Sha256::new();
580    inner.update(&ipad);
581    for chunk in chunks {
582        inner.update(chunk);
583    }
584    let inner_hash = inner.finalize();
585
586    let mut outer = Sha256::new();
587    outer.update(&opad);
588    outer.update(&inner_hash);
589    outer.finalize()
590}
591
592#[derive(Clone)]
593struct Sha256 {
594    state: [u32; 8],
595    buffer: [u8; 64],
596    buffer_len: usize,
597    len_bits: u64,
598}
599
600impl Sha256 {
601    fn new() -> Self {
602        Self {
603            state: [
604                0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab,
605                0x5be0cd19,
606            ],
607            buffer: [0; 64],
608            buffer_len: 0,
609            len_bits: 0,
610        }
611    }
612
613    fn digest(input: &[u8]) -> [u8; 32] {
614        let mut hasher = Self::new();
615        hasher.update(input);
616        hasher.finalize()
617    }
618
619    fn update(&mut self, mut input: &[u8]) {
620        self.len_bits = self
621            .len_bits
622            .wrapping_add((input.len() as u64).wrapping_mul(8));
623        if self.buffer_len > 0 {
624            let take = (64 - self.buffer_len).min(input.len());
625            self.buffer[self.buffer_len..self.buffer_len + take].copy_from_slice(&input[..take]);
626            self.buffer_len += take;
627            input = &input[take..];
628            if self.buffer_len == 64 {
629                let block = self.buffer;
630                self.compress(&block);
631                self.buffer_len = 0;
632            }
633        }
634        while input.len() >= 64 {
635            let mut block = [0u8; 64];
636            block.copy_from_slice(&input[..64]);
637            self.compress(&block);
638            input = &input[64..];
639        }
640        if !input.is_empty() {
641            self.buffer[..input.len()].copy_from_slice(input);
642            self.buffer_len = input.len();
643        }
644    }
645
646    fn finalize(mut self) -> [u8; 32] {
647        self.buffer[self.buffer_len] = 0x80;
648        self.buffer_len += 1;
649        if self.buffer_len > 56 {
650            self.buffer[self.buffer_len..].fill(0);
651            let block = self.buffer;
652            self.compress(&block);
653            self.buffer_len = 0;
654        }
655        self.buffer[self.buffer_len..56].fill(0);
656        self.buffer[56..].copy_from_slice(&self.len_bits.to_be_bytes());
657        let block = self.buffer;
658        self.compress(&block);
659
660        let mut out = [0u8; 32];
661        for (idx, word) in self.state.iter().enumerate() {
662            out[idx * 4..idx * 4 + 4].copy_from_slice(&word.to_be_bytes());
663        }
664        out
665    }
666
667    fn compress(&mut self, block: &[u8; 64]) {
668        const K: [u32; 64] = [
669            0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4,
670            0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe,
671            0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f,
672            0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
673            0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc,
674            0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b,
675            0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, 0x19a4c116,
676            0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
677            0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7,
678            0xc67178f2,
679        ];
680        let mut w = [0u32; 64];
681        for idx in 0..16 {
682            w[idx] = u32::from_be_bytes([
683                block[idx * 4],
684                block[idx * 4 + 1],
685                block[idx * 4 + 2],
686                block[idx * 4 + 3],
687            ]);
688        }
689        for idx in 16..64 {
690            let s0 =
691                w[idx - 15].rotate_right(7) ^ w[idx - 15].rotate_right(18) ^ (w[idx - 15] >> 3);
692            let s1 = w[idx - 2].rotate_right(17) ^ w[idx - 2].rotate_right(19) ^ (w[idx - 2] >> 10);
693            w[idx] = w[idx - 16]
694                .wrapping_add(s0)
695                .wrapping_add(w[idx - 7])
696                .wrapping_add(s1);
697        }
698
699        let mut a = self.state[0];
700        let mut b = self.state[1];
701        let mut c = self.state[2];
702        let mut d = self.state[3];
703        let mut e = self.state[4];
704        let mut f = self.state[5];
705        let mut g = self.state[6];
706        let mut h = self.state[7];
707
708        for idx in 0..64 {
709            let s1 = e.rotate_right(6) ^ e.rotate_right(11) ^ e.rotate_right(25);
710            let ch = (e & f) ^ ((!e) & g);
711            let temp1 = h
712                .wrapping_add(s1)
713                .wrapping_add(ch)
714                .wrapping_add(K[idx])
715                .wrapping_add(w[idx]);
716            let s0 = a.rotate_right(2) ^ a.rotate_right(13) ^ a.rotate_right(22);
717            let maj = (a & b) ^ (a & c) ^ (b & c);
718            let temp2 = s0.wrapping_add(maj);
719
720            h = g;
721            g = f;
722            f = e;
723            e = d.wrapping_add(temp1);
724            d = c;
725            c = b;
726            b = a;
727            a = temp1.wrapping_add(temp2);
728        }
729
730        self.state[0] = self.state[0].wrapping_add(a);
731        self.state[1] = self.state[1].wrapping_add(b);
732        self.state[2] = self.state[2].wrapping_add(c);
733        self.state[3] = self.state[3].wrapping_add(d);
734        self.state[4] = self.state[4].wrapping_add(e);
735        self.state[5] = self.state[5].wrapping_add(f);
736        self.state[6] = self.state[6].wrapping_add(g);
737        self.state[7] = self.state[7].wrapping_add(h);
738    }
739}
740
741#[cfg(test)]
742mod tests {
743    use super::*;
744
745    struct XorShim;
746
747    impl CryptographyProvider for XorShim {
748        fn seal(
749            &self,
750            key_id: u32,
751            _nonce: &[u8],
752            _aad: &[u8],
753            plaintext: &[u8],
754            ciphertext_out: &mut [u8],
755            tag_out: &mut [u8],
756        ) -> TelemetryResult<(usize, usize)> {
757            if ciphertext_out.len() < plaintext.len() || tag_out.len() < 4 {
758                return Err(TelemetryError::SizeMismatchError);
759            }
760            for (idx, byte) in plaintext.iter().enumerate() {
761                ciphertext_out[idx] = *byte ^ (key_id as u8);
762            }
763            tag_out[..4].copy_from_slice(b"SEDS");
764            Ok((plaintext.len(), 4))
765        }
766
767        fn open(
768            &self,
769            key_id: u32,
770            _nonce: &[u8],
771            _aad: &[u8],
772            ciphertext: &[u8],
773            tag: &[u8],
774            plaintext_out: &mut [u8],
775        ) -> TelemetryResult<usize> {
776            if plaintext_out.len() < ciphertext.len() || tag != b"SEDS" {
777                return Err(TelemetryError::SizeMismatchError);
778            }
779            for (idx, byte) in ciphertext.iter().enumerate() {
780                plaintext_out[idx] = *byte ^ (key_id as u8);
781            }
782            Ok(ciphertext.len())
783        }
784    }
785
786    #[test]
787    fn rust_encryption_roundtrips_without_c_callbacks() {
788        let provider = XorShim;
789        let plaintext = [1_u8, 2, 3, 4];
790        let mut ciphertext = [0_u8; 8];
791        let mut tag = [0_u8; 8];
792        let (ciphertext_len, tag_len) = seal_with(
793            &provider,
794            9,
795            &[0; 12],
796            b"aad",
797            &plaintext,
798            &mut ciphertext,
799            &mut tag,
800        )
801        .unwrap();
802        assert_eq!(ciphertext_len, plaintext.len());
803        assert_eq!(tag_len, 4);
804        let mut opened = [0_u8; 8];
805        let opened_len = open_with(
806            &provider,
807            9,
808            &[0; 12],
809            b"aad",
810            &ciphertext[..ciphertext_len],
811            &tag[..tag_len],
812            &mut opened,
813        )
814        .unwrap();
815        assert_eq!(opened_len, plaintext.len());
816        assert_eq!(&opened[..opened_len], plaintext);
817    }
818
819    #[test]
820    fn managed_credentials_verify_and_reject_tamper_or_expiry() {
821        let root = b"root key material with at least 32 bytes";
822        let credential = ManagedCredential {
823            subject_id: 0xAABB,
824            key_id: 7,
825            epoch: 11,
826            not_before_ms: 100,
827            not_after_ms: 1_000,
828            permissions: 0x05,
829        };
830        let mut bytes = [0u8; MANAGED_CREDENTIAL_LEN];
831        let len = issue_managed_credential(root, credential, &mut bytes).unwrap();
832        assert_eq!(len, MANAGED_CREDENTIAL_LEN);
833        assert_eq!(
834            verify_managed_credential(root, &bytes, 500).unwrap(),
835            credential
836        );
837
838        let mut tampered = bytes;
839        tampered[20] ^= 0x01;
840        assert!(verify_managed_credential(root, &tampered, 500).is_err());
841        assert!(verify_managed_credential(root, &bytes, 1_001).is_err());
842    }
843}