aranya_crypto/
aranya.rs

1//! This file contains the various device keys.
2
3#![forbid(unsafe_code)]
4
5use core::{borrow::Borrow, fmt, marker::PhantomData, result::Result};
6
7use derive_where::derive_where;
8use serde::{Deserialize, Deserializer, Serialize, Serializer, de};
9use spideroak_crypto::{
10    aead::Tag,
11    csprng::Csprng,
12    import::{Import as _, ImportError},
13    kem::{DecapKey as _, Kem},
14    keys::PublicKey as _,
15    signer::{self, Signer, SigningKey as _, VerifyingKey as _},
16};
17use zerocopy::{ByteEq, Immutable, IntoBytes, KnownLayout, Unaligned};
18
19use crate::{
20    ciphersuite::{CipherSuite, CipherSuiteExt as _},
21    error::Error,
22    groupkey::{EncryptedGroupKey, GroupKey},
23    hpke::{self, Mode},
24    misc::{SigData, kem_key, signing_key},
25    policy::{self, Cmd, CmdId, GroupId},
26};
27
28/// A signature created by a signing key.
29#[derive_where(Clone, Debug)]
30pub struct Signature<CS: CipherSuite>(pub(crate) <CS::Signer as Signer>::Signature);
31
32impl<CS: CipherSuite> Signature<CS> {
33    /// Returns the raw signature.
34    ///
35    /// Should only be used in situations where contextual data
36    /// is being merged in. Otherwise, use [`Serialize`].
37    pub(crate) fn raw_sig(&self) -> SigData<CS> {
38        signer::Signature::export(&self.0)
39    }
40
41    /// Encodes itself as bytes.
42    pub fn to_bytes(&self) -> impl Borrow<[u8]> + use<CS> {
43        self.raw_sig()
44    }
45
46    /// Returns itself from its byte encoding.
47    pub fn from_bytes(data: &[u8]) -> Result<Self, ImportError> {
48        let sig = <CS::Signer as Signer>::Signature::import(data)?;
49        Ok(Self(sig))
50    }
51}
52
53impl<CS: CipherSuite> Serialize for Signature<CS> {
54    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
55    where
56        S: Serializer,
57    {
58        serializer.serialize_bytes(self.to_bytes().borrow())
59    }
60}
61
62impl<'de, CS: CipherSuite> Deserialize<'de> for Signature<CS> {
63    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
64    where
65        D: Deserializer<'de>,
66    {
67        struct SigVisitor<CS>(PhantomData<CS>);
68        impl<'de, G: CipherSuite> de::Visitor<'de> for SigVisitor<G> {
69            type Value = Signature<G>;
70
71            fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
72                write!(f, "a signature")
73            }
74
75            fn visit_borrowed_bytes<E>(self, v: &'de [u8]) -> Result<Self::Value, E>
76            where
77                E: de::Error,
78            {
79                Signature::<G>::from_bytes(v).map_err(de::Error::custom)
80            }
81
82            fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
83            where
84                E: de::Error,
85            {
86                Signature::<G>::from_bytes(v).map_err(de::Error::custom)
87            }
88        }
89        let sig = deserializer.deserialize_bytes(SigVisitor::<CS>(PhantomData))?;
90        Ok(sig)
91    }
92}
93
94signing_key! {
95    /// The Device Identity Key.
96    sk = IdentityKey,
97    pk = IdentityVerifyingKey,
98    id = DeviceId,
99    context = "Device Identity Key V1",
100}
101
102impl<CS: CipherSuite> IdentityKey<CS> {
103    /// Creates a signature over `msg` bound to some `context`.
104    ///
105    /// `msg` must NOT be pre-hashed.
106    ///
107    /// # Example
108    ///
109    /// ```rust
110    /// # #[cfg(all(feature = "alloc", not(feature = "trng")))]
111    /// # {
112    /// use aranya_crypto::{
113    ///     IdentityKey, Rng,
114    ///     default::{DefaultCipherSuite, DefaultEngine},
115    /// };
116    ///
117    /// let sk = IdentityKey::<DefaultCipherSuite>::new(&mut Rng);
118    ///
119    /// const MESSAGE: &[u8] = b"hello, world!";
120    /// const CONTEXT: &[u8] = b"doc test";
121    /// let sig = sk.sign(MESSAGE, CONTEXT).expect("should not fail");
122    ///
123    /// sk.public()
124    ///     .expect("identity key should be valid")
125    ///     .verify(MESSAGE, CONTEXT, &sig)
126    ///     .expect("should not fail");
127    ///
128    /// sk.public()
129    ///     .expect("identity key should be valid")
130    ///     .verify(MESSAGE, b"wrong context", &sig)
131    ///     .expect_err("should fail");
132    ///
133    /// let wrong_sig = sk
134    ///     .sign(b"different", b"signature")
135    ///     .expect("should not fail");
136    /// sk.public()
137    ///     .expect("identity key should be valid")
138    ///     .verify(MESSAGE, CONTEXT, &wrong_sig)
139    ///     .expect_err("should fail");
140    /// # }
141    /// ```
142    pub fn sign(&self, msg: &[u8], context: &[u8]) -> Result<Signature<CS>, Error> {
143        // digest = H(
144        //     "IdentityKey",
145        //     suites,
146        //     pk,
147        //     context,
148        //     msg,
149        // )
150        let sum = CS::tuple_hash(b"IdentityKey", [self.id()?.as_bytes(), context, msg]);
151        let sig = self.sk.sign(&sum)?;
152        Ok(Signature(sig))
153    }
154}
155
156impl<CS: CipherSuite> IdentityVerifyingKey<CS> {
157    /// Verifies the signature allegedly created over `msg` and
158    /// bound to some `context`.
159    ///
160    /// `msg` must NOT be pre-hashed.
161    pub fn verify(&self, msg: &[u8], context: &[u8], sig: &Signature<CS>) -> Result<(), Error> {
162        // digest = H(
163        //     "IdentityKey",
164        //     suites,
165        //     pk,
166        //     context,
167        //     msg,
168        // )
169        let sum = CS::tuple_hash(b"IdentityKey", [self.id()?.as_bytes(), context, msg]);
170        Ok(self.pk.verify(&sum, &sig.0)?)
171    }
172}
173
174signing_key! {
175    /// The Device Signing Key.
176    sk = SigningKey,
177    pk = VerifyingKey,
178    id = SigningKeyId,
179    context = "Device Signing Key V1",
180}
181
182impl<CS: CipherSuite> SigningKey<CS> {
183    /// Creates a signature over `msg` bound to some `context`.
184    ///
185    /// `msg` must NOT be pre-hashed.
186    ///
187    /// # Example
188    ///
189    /// ```rust
190    /// # #[cfg(all(feature = "alloc", not(feature = "trng")))]
191    /// # {
192    /// use aranya_crypto::{
193    ///     Rng, SigningKey,
194    ///     default::{DefaultCipherSuite, DefaultEngine},
195    /// };
196    ///
197    /// let sk = SigningKey::<DefaultCipherSuite>::new(&mut Rng);
198    ///
199    /// const MESSAGE: &[u8] = b"hello, world!";
200    /// const CONTEXT: &[u8] = b"doc test";
201    /// let sig = sk.sign(MESSAGE, CONTEXT).expect("should not fail");
202    ///
203    /// sk.public()
204    ///     .expect("signing key should be valid")
205    ///     .verify(MESSAGE, CONTEXT, &sig)
206    ///     .expect("should not fail");
207    ///
208    /// sk.public()
209    ///     .expect("signing key should be valid")
210    ///     .verify(MESSAGE, b"wrong context", &sig)
211    ///     .expect_err("should fail");
212    ///
213    /// let wrong_sig = sk
214    ///     .sign(b"different", b"signature")
215    ///     .expect("should not fail");
216    /// sk.public()
217    ///     .expect("signing key should be valid")
218    ///     .verify(MESSAGE, CONTEXT, &wrong_sig)
219    ///     .expect_err("should fail");
220    /// # }
221    /// ```
222    pub fn sign(&self, msg: &[u8], context: &[u8]) -> Result<Signature<CS>, Error> {
223        // digest = H(
224        //     "SigningKey",
225        //     suites,
226        //     pk,
227        //     context,
228        //     msg,
229        // )
230        let sum = CS::tuple_hash(b"SigningKey", [self.id()?.as_bytes(), context, msg]);
231        let sig = self.sk.sign(&sum)?;
232        Ok(Signature(sig))
233    }
234
235    /// Creates a signature over a named policy command.
236    ///
237    /// # Example
238    ///
239    /// ```rust
240    /// # #[cfg(all(feature = "alloc", not(feature = "trng")))]
241    /// # {
242    /// use aranya_crypto::{
243    ///     BaseId, Cmd, Rng, SigningKey,
244    ///     default::{DefaultCipherSuite, DefaultEngine},
245    ///     id::IdExt as _,
246    ///     policy::CmdId,
247    /// };
248    ///
249    /// let sk = SigningKey::<DefaultCipherSuite>::new(&mut Rng);
250    ///
251    /// let data = b"... some command data ...";
252    /// let name = "AddDevice";
253    /// let parent_id = &CmdId::random(&mut Rng);
254    ///
255    /// let good_cmd = Cmd {
256    ///     data,
257    ///     name,
258    ///     parent_id,
259    /// };
260    /// let (sig, _) = sk.sign_cmd(good_cmd).expect("should not fail");
261    /// sk.public()
262    ///     .expect("signing key should be valid")
263    ///     .verify_cmd(good_cmd, &sig)
264    ///     .expect("should not fail");
265    ///
266    /// let wrong_name_cmd = Cmd {
267    ///     data,
268    ///     name: "wrong name",
269    ///     parent_id,
270    /// };
271    /// sk.public()
272    ///     .expect("signing key should be valid")
273    ///     .verify_cmd(wrong_name_cmd, &sig)
274    ///     .expect_err("should fail");
275    ///
276    /// let wrong_id_cmd = Cmd {
277    ///     data,
278    ///     name,
279    ///     parent_id: &CmdId::random(&mut Rng),
280    /// };
281    /// sk.public()
282    ///     .expect("signing key should be valid")
283    ///     .verify_cmd(wrong_id_cmd, &sig)
284    ///     .expect_err("should fail");
285    ///
286    /// let wrong_sig_cmd = Cmd {
287    ///     data: b"different",
288    ///     name: "signature",
289    ///     parent_id: &CmdId::random(&mut Rng),
290    /// };
291    /// let (wrong_sig, _) = sk.sign_cmd(wrong_sig_cmd).expect("should not fail");
292    /// sk.public()
293    ///     .expect("signing key should be valid")
294    ///     .verify_cmd(good_cmd, &wrong_sig)
295    ///     .expect_err("should fail");
296    /// # }
297    /// ```
298    pub fn sign_cmd(&self, cmd: Cmd<'_>) -> Result<(Signature<CS>, CmdId), Error> {
299        let digest = cmd.digest::<CS>(self.id()?);
300        let sig = Signature(self.sk.sign(&digest)?);
301        let id = policy::cmd_id(&digest, &sig);
302        Ok((sig, id))
303    }
304}
305
306impl<CS: CipherSuite> VerifyingKey<CS> {
307    /// Verifies the signature allegedly created over `msg` and
308    /// bound to some `context`.
309    ///
310    /// `msg` must NOT be pre-hashed.
311    pub fn verify(&self, msg: &[u8], context: &[u8], sig: &Signature<CS>) -> Result<(), Error> {
312        // digest = H(
313        //     "SigningKey",
314        //     suites,
315        //     pk,
316        //     context,
317        //     msg,
318        // )
319        let sum = CS::tuple_hash(b"SigningKey", [self.id()?.as_bytes(), context, msg]);
320        Ok(self.pk.verify(&sum, &sig.0)?)
321    }
322
323    /// Verifies the signature allegedly created over a policy
324    /// command and returns its ID.
325    pub fn verify_cmd(&self, cmd: Cmd<'_>, sig: &Signature<CS>) -> Result<CmdId, Error> {
326        let digest = cmd.digest::<CS>(self.id()?);
327        self.pk.verify(&digest, &sig.0)?;
328        let id = policy::cmd_id(&digest, sig);
329        Ok(id)
330    }
331}
332
333kem_key! {
334    /// The Device Encryption Key.
335    sk = EncryptionKey,
336    pk = EncryptionPublicKey,
337    id = EncryptionKeyId,
338    context = "Device Encryption Key V1",
339}
340
341impl<CS: CipherSuite> EncryptionKey<CS> {
342    /// Decrypts and authenticates a [`GroupKey`] received from
343    /// a peer.
344    pub fn open_group_key(
345        &self,
346        enc: &Encap<CS>,
347        ciphertext: EncryptedGroupKey<CS>,
348        group: GroupId,
349    ) -> Result<GroupKey<CS>, Error> {
350        let EncryptedGroupKey {
351            mut ciphertext,
352            tag,
353        } = ciphertext;
354
355        // info = concat(
356        //     "GroupKey-v1",
357        //     group,
358        // )
359        let info = GroupKeyInfo {
360            domain: *b"GroupKey-v1",
361            group,
362        };
363        let mut ctx = hpke::setup_recv::<CS>(Mode::Base, &enc.0, &self.sk, [info.as_bytes()])?;
364        ctx.open_in_place(&mut ciphertext, &tag, info.as_bytes())?;
365        Ok(GroupKey::from_seed(ciphertext.into()))
366    }
367}
368
369#[repr(C)]
370#[derive(Copy, Clone, Debug, ByteEq, Immutable, IntoBytes, KnownLayout, Unaligned)]
371struct GroupKeyInfo {
372    /// Always "GroupKey-v1".
373    domain: [u8; 11],
374    group: GroupId,
375}
376
377impl<CS: CipherSuite> EncryptionPublicKey<CS> {
378    /// Encrypts and authenticates the [`GroupKey`] such that it
379    /// can only be decrypted by the holder of the private half
380    /// of the [`EncryptionPublicKey`].
381    pub fn seal_group_key<R: Csprng>(
382        &self,
383        rng: &mut R,
384        key: &GroupKey<CS>,
385        group: GroupId,
386    ) -> Result<(Encap<CS>, EncryptedGroupKey<CS>), Error> {
387        // info = concat(
388        //     "GroupKey-v1",
389        //     group,
390        // )
391        let info = GroupKeyInfo {
392            domain: *b"GroupKey-v1",
393            group,
394        };
395        let (enc, mut ctx) =
396            hpke::setup_send::<CS, _>(rng, Mode::Base, &self.pk, [info.as_bytes()])?;
397        let mut ciphertext = (*key.raw_seed()).into();
398        let mut tag = Tag::<CS::Aead>::default();
399        ctx.seal_in_place(&mut ciphertext, &mut tag, info.as_bytes())?;
400        Ok((Encap(enc), EncryptedGroupKey { ciphertext, tag }))
401    }
402}
403
404/// An encapsulated symmetric key.
405pub struct Encap<CS: CipherSuite>(pub(crate) <CS::Kem as Kem>::Encap);
406
407impl<CS: CipherSuite> Encap<CS> {
408    /// Encodes itself as bytes.
409    #[inline]
410    pub fn as_bytes(&self) -> &[u8] {
411        self.0.borrow()
412    }
413
414    /// Returns itself from its byte encoding.
415    pub fn from_bytes(data: &[u8]) -> Result<Self, ImportError> {
416        let enc = <CS::Kem as Kem>::Encap::import(data)?;
417        Ok(Self(enc))
418    }
419
420    #[cfg(feature = "afc")]
421    pub(crate) fn as_inner(&self) -> &<CS::Kem as Kem>::Encap {
422        &self.0
423    }
424}
425
426impl<CS: CipherSuite> fmt::Debug for Encap<CS> {
427    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
428        f.debug_tuple("Encap").field(&self.as_bytes()).finish()
429    }
430}
431
432impl<CS> Serialize for Encap<CS>
433where
434    CS: CipherSuite,
435{
436    fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
437    where
438        S: Serializer,
439    {
440        s.serialize_bytes(self.as_bytes())
441    }
442}
443
444impl<'de, CS> Deserialize<'de> for Encap<CS>
445where
446    CS: CipherSuite,
447{
448    fn deserialize<D>(d: D) -> Result<Self, D::Error>
449    where
450        D: Deserializer<'de>,
451    {
452        struct EncapVisitor<G: ?Sized>(PhantomData<G>);
453        impl<'de, G> de::Visitor<'de> for EncapVisitor<G>
454        where
455            G: CipherSuite,
456        {
457            type Value = Encap<G>;
458
459            fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
460                write!(f, "a valid encapsulation")
461            }
462
463            fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
464            where
465                E: de::Error,
466            {
467                Encap::<G>::from_bytes(v).map_err(E::custom)
468            }
469
470            fn visit_borrowed_bytes<E>(self, v: &'de [u8]) -> Result<Self::Value, E>
471            where
472                E: de::Error,
473            {
474                Encap::<G>::from_bytes(v).map_err(E::custom)
475            }
476        }
477        d.deserialize_bytes(EncapVisitor(PhantomData))
478    }
479}
480
481#[cfg(test)]
482mod tests {
483    use core::cell::OnceCell;
484
485    use spideroak_crypto::{ed25519::Ed25519, import::Import as _, kem::Kem, rust, signer::Signer};
486
487    use super::*;
488    use crate::{default::DhKemP256HkdfSha256, test_util::TestCs};
489
490    type CS = TestCs<
491        rust::Aes256Gcm,
492        rust::Sha256,
493        rust::HkdfSha512,
494        DhKemP256HkdfSha256,
495        rust::HmacSha512,
496        Ed25519,
497    >;
498
499    /// Golden test for [`IdentityKey`] IDs.
500    #[test]
501    fn test_identity_key_id() {
502        let tests = [(
503            // Fixed key bytes for reproducible test
504            [
505                0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
506                0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c,
507                0x1d, 0x1e, 0x1f, 0x20,
508            ],
509            "FzsznndyXSmwS8LjWbg2g7CGp1jAD8RMArG1BCdWYkRE",
510        )];
511
512        for (i, (key_bytes, expected_id)) in tests.iter().enumerate() {
513            let sk = <<CS as CipherSuite>::Signer as Signer>::SigningKey::import(key_bytes)
514                .expect("should import signing key");
515            let identity_key: IdentityKey<CS> = IdentityKey {
516                sk,
517                id: OnceCell::new(),
518            };
519
520            let got_id = identity_key.id().expect("should compute ID");
521            let expected = DeviceId::decode(expected_id).expect("should decode expected ID");
522
523            assert_eq!(got_id, expected, "test case #{i}");
524        }
525    }
526
527    /// Golden test for [`SigningKey`] IDs.
528    #[test]
529    fn test_signing_key_id() {
530        let tests = [(
531            // Fixed key bytes for reproducible test
532            [
533                0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
534                0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c,
535                0x1d, 0x1e, 0x1f, 0x20,
536            ],
537            "4NQYLfhYhMWDR7Rmu3ubH24NP3e4HUP4f6mcpBKdygWF",
538        )];
539
540        for (i, (key_bytes, expected_id)) in tests.iter().enumerate() {
541            let sk = <<CS as CipherSuite>::Signer as Signer>::SigningKey::import(key_bytes)
542                .expect("should import signing key");
543            let signing_key: SigningKey<CS> = SigningKey {
544                sk,
545                id: OnceCell::new(),
546            };
547
548            let got_id = signing_key.id().expect("should compute ID");
549            let expected = SigningKeyId::decode(expected_id).expect("should decode expected ID");
550
551            assert_eq!(got_id, expected, "test case #{i}");
552        }
553    }
554
555    /// Golden test for [`EncryptionKey`] IDs.
556    #[test]
557    fn test_encryption_key_id() {
558        let tests = [(
559            // Fixed key bytes for reproducible test
560            [
561                0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
562                0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c,
563                0x1d, 0x1e, 0x1f, 0x20,
564            ],
565            "GDi3zb242AU8zW6QQKUypadFffRaDWA5PhX2eQ1ANphz",
566        )];
567
568        for (i, (key_bytes, expected_id)) in tests.iter().enumerate() {
569            let sk = <<CS as CipherSuite>::Kem as Kem>::DecapKey::import(key_bytes)
570                .expect("should import decap key");
571            let encryption_key: EncryptionKey<CS> = EncryptionKey {
572                sk,
573                id: OnceCell::new(),
574            };
575
576            let got_id = encryption_key.id().expect("should compute ID");
577            let expected = EncryptionKeyId::decode(expected_id).expect("should decode expected ID");
578
579            assert_eq!(got_id, expected, "test case #{i}");
580        }
581    }
582}