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}