Skip to main content

embedded_cal/
dh.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2// SPDX-FileCopyrightText: Inria-AIO, Cryspen, and Christian Amsüss
3
4use crate::ImportError;
5
6/// Diffie-Hellman style key establishment.
7///
8/// This trait does not distinguish between prime factor DH and Elliptic Curve DH (ECDH); it
9/// describes the general interface, and many embedded systems likely only implement the latter.
10///
11/// This trait takes inspiration from the
12/// [`elliptic_curve`](https://docs.rs/elliptic-curve/latest/elliptic_curve/) crate, but does not
13/// use it directly because
14/// - `embedded-cal` passes around an exclusive reference to its engine,
15/// - its operation is cryptographically agile rather than monomorphized over algorithms,
16/// - only the user visible parts are modelled here (corresponding to
17///   [`SecretKey`](https://docs.rs/elliptic-curve/latest/elliptic_curve/struct.SecretKey.html),
18///   [`PublicKey`](https://docs.rs/elliptic-curve/latest/elliptic_curve/struct.PublicKey.html) and
19///   a [`SharedSecret`](https://docs.rs/elliptic-curve/latest/elliptic_curve/ecdh/struct.SharedSecret.html), and
20/// - it does not distinguish, on the type level, between an `EphemeralSecret` and a `SecretKey`,
21///   as some protocols such as [Group OSCORE](https://www.ietf.org/archive/id/draft-ietf-core-oscore-groupcomm-28.html)
22///   have legitimate use cases for static-static key derivations.
23///
24/// Importing and exporting public keys is a relatively verbose affair, as there is no consistent
25/// byte-like structure for all algorithms. Implementations are expected to provide imports and
26/// exports for every method that is defined for a given algorithm. For example, for P-256, both
27/// export and import in EC2-style format are expected, both with and without coordinate
28/// compression.
29pub trait DhProvider {
30    type Algorithm: DhAlgorithm;
31    /// A secret key that is intended to be exported.
32    ///
33    /// It is recommended (but not required) that this is not just [`SecretKey`][Self::SecretKey]
34    /// but at least a newtype around it; otherwise, users with knowledge of the concrete
35    /// [`Cal`][super::Cal] type (or who require that `C::SecretKey` is identical to or convertible
36    /// from a `C::VisibleSecretKey`) could just swap them around.
37    ///
38    /// ## Rationale
39    ///
40    /// Visible secret keys are a dedicated type here, compared to the AEAD and HMAC types where
41    /// keys are merely loadable because secret keys have more diverse forms of serialization (eg.
42    /// raw bytes, DER or COSE keys), and because key need generation (and not "just" a fixed
43    /// number of uniformly random bytes that is trivial to generate once and store as part of it).
44    type VisibleSecretKey: Sized + Into<Self::SecretKey>;
45    type SecretKey: Sized;
46    type PublicKey: Sized;
47    type SharedSecret: Sized;
48
49    /// Generates a secret key that is intended to be exported / shared (e.g. to be persisted
50    /// across program executions).
51    fn generate_visible(&mut self, alg: Self::Algorithm) -> Self::VisibleSecretKey;
52
53    /// Generates a secret key.
54    fn generate(&mut self, alg: Self::Algorithm) -> Self::SecretKey {
55        self.generate_visible(alg).into()
56    }
57
58    /// Exposes a visible secret key's secret.
59    ///
60    /// Data is stored in the algorithm's native format. For COSE ECDH, this is the `d` value.
61    ///
62    /// If any algorithms are later added for which there is no straightforward `[u8]`
63    /// representation, other methods may be added.
64    // FIXME: Does this need explicit words of warning?
65    // FIXME: Should we make this fallible on grounds of using the wrong format?
66    fn export_secretkey_bytes<'s>(
67        &mut self,
68        secretkey: &'s Self::VisibleSecretKey,
69    ) -> impl AsRef<[u8]> + use<'s, Self>;
70
71    /// Inverse operation of [`.export_secretkey_bytes()`][Self::export_secretkey_bytes()].
72    // FIXME: Should this go directly to SecretKey without being re-exportable?
73    fn import_secretkey_bytes(
74        &mut self,
75        alg: Self::Algorithm,
76        secret: &[u8],
77    ) -> Result<Self::VisibleSecretKey, ImportError>;
78
79    /// Exposes a public key's key data bytes.
80    ///
81    /// For ECDH keys, this is defined as the compact representation.
82    fn export_publickey_bytes<'p>(
83        &mut self,
84        public: &'p Self::PublicKey,
85    ) -> impl AsRef<[u8]> + use<'p, Self>;
86    /// Imports a public key in the inverse operation of
87    /// [`.export_publickey_bytes()`][Self::export_publickey_bytes()].
88    fn import_publickey_bytes(
89        &mut self,
90        alg: Self::Algorithm,
91        data: &[u8],
92    ) -> Result<Self::PublicKey, ImportError>;
93
94    /// Derives a shared secret from a public and a private key.
95    ///
96    /// # Errors
97    ///
98    /// … are produced only if the private and the public key are for different algorithms.
99    // FIXME: Is this really an error we should raise? People who don't check algorithms will also
100    // reach into nonexistent offsets in output material, and that too is punishable by panics.
101    fn shared_secret(
102        &mut self,
103        private: &Self::SecretKey,
104        public: &Self::PublicKey,
105    ) -> Result<Self::SharedSecret, IncompatibleKeys>;
106
107    /// Produces the public key corresponding to a private key.
108    fn public_key(&mut self, private: &Self::SecretKey) -> Self::PublicKey;
109
110    /// Produces the bytes of the shared secret, in the algorithm's
111    /// [`.output_length()`][DhAlgorithm::output_length()].
112    ///
113    /// The [`SharedSecret`][Self::SharedSecret] itself is *not* `AsRef` itself because the
114    /// implementation may want to not pass out this secret by default (expecting it to be used as
115    /// input to a KDF).
116    // Should we name the return type? That'd enable users to store it -- but *should* it be stored
117    // in the first place?
118    fn raw_secret_bytes<'s>(
119        &mut self,
120        secret: &'s Self::SharedSecret,
121    ) -> impl AsRef<[u8]> + use<'s, Self>;
122}
123
124/// Error indicating that the public and the private key are incompatible.
125#[derive(Debug)]
126pub struct IncompatibleKeys;
127
128impl core::fmt::Display for IncompatibleKeys {
129    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
130        f.write_str("keys are incompatible")
131    }
132}
133
134impl core::error::Error for IncompatibleKeys {}
135
136/// An algorithm for diffie-hellman style key establishment.
137///
138/// This not only encodes the cryptographic algorithm, but also the curve, but not post-processing
139/// such as the KDF.
140///
141/// Note that while JOSE and COSE have [not switched their identifiers](https://datatracker.ietf.org/doc/html/rfc9864#name-ecdh-key-agreement-algorith)
142/// to fully-specified for ECDH, it makes sense to group algorithm (practically always ECDH so far)
143/// and curve, as it helps making illegal states unrepresentable.
144///
145/// The current constructors do not cover the breadth of what the interface can do, as COSE does
146/// not have entries for non-EC DH (or ony other) key agreement.
147// FIXME: We *could* encode the KDF and then make the shared secret only available through that
148// KDF, but that'd make this overly COSE specific, and constraints like
149// <https://github.com/lake-rs/embedded-cal/issues/60> could be added later.
150pub trait DhAlgorithm: Sized + PartialEq + Eq + core::fmt::Debug + Clone {
151    /// Length of the shared secret produced by keys of this algorithm.
152    fn output_length(&self) -> usize;
153
154    /// Selects a DH algorithm from its COSE numbers.
155    ///
156    /// The curve number comes from the ["COSE Elliptic Curves"](https://www.iana.org/assignments/cose/cose.xhtml#elliptic-curves)
157    /// registry maintained by IANA.
158    #[inline]
159    #[allow(
160        unused_variables,
161        reason = "Argument names are part of the documentation"
162    )]
163    fn from_cose_ecdh(curve: impl Into<i128>) -> Option<Self> {
164        None
165    }
166}
167
168pub fn test_dh_algorithm_ecdh_p256<DP: DhProvider>() {
169    let cose_ecdh_1 = DP::Algorithm::from_cose_ecdh(1i8).expect(
170        "test for type claiming ECDH on P-256 compatibility did not recognize COSE curve 1",
171    );
172    assert_eq!(cose_ecdh_1.output_length(), 32)
173}
174
175pub fn test_dh_selftest<C: crate::Cal + rand_core::CryptoRng>(
176    cal: &mut C,
177    alg: <C::DhProvider as DhProvider>::Algorithm,
178) {
179    let cal = cal.dh();
180
181    let my_secret = cal.generate(alg.clone());
182    let peer_secret = cal.generate(alg);
183    let my_public = cal.public_key(&my_secret);
184    let peer_public = cal.public_key(&peer_secret);
185
186    let my_shared_secret = cal.shared_secret(&my_secret, &peer_public).unwrap();
187    let peer_shared_secret = cal.shared_secret(&peer_secret, &my_public).unwrap();
188
189    let my_raw_bytes = cal.raw_secret_bytes(&my_shared_secret);
190    let peer_raw_bytes = cal.raw_secret_bytes(&peer_shared_secret);
191    assert_eq!(my_raw_bytes.as_ref(), peer_raw_bytes.as_ref());
192}