rubble/ecdh/
mod.rs

1//! Elliptic Curve Diffie-Hellman (ECDH) on P-256.
2//!
3//! BLE uses ECDH on P-256 for pairing. This module provides an interface for plugging in different
4//! implementations of the P-256 operations. The main consumer of this module is the [`security`]
5//! module; refer to that for more info about pairing and encryption in BLE.
6//!
7//! The primary trait in this module is [`EcdhProvider`]. Rubble comes with 2 built-in
8//! implementations of that trait:
9//!
10//! * [`P256Provider`] and [`P256SecretKey`]: These use the pure-Rust [`p256`] crate and are always
11//!   available.
12//! * [`RingProvider`] and [`RingSecretKey`] (behind the **`ring`** Cargo feature): These use the
13//!   [*ring*][ring] library to provide the operations. Note that *ring* does not support
14//!   `#![no_std]` operation, so this is mostly useful for tests and other non-embedded usage.
15//!
16//! [`security`]: ../security/index.html
17//! [`EcdhProvider`]: trait.EcdhProvider.html
18//! [`P256Provider`]: struct.P256Provider.html
19//! [`P256SecretKey`]: struct.P256SecretKey.html
20//! [`RingProvider`]: struct.RingProvider.html
21//! [`RingSecretKey`]: struct.RingSecretKey.html
22//! [ring]: https://github.com/briansmith/ring
23//! [`p256`]: https://docs.rs/p256
24
25mod p256;
26
27pub use self::p256::*;
28
29#[cfg(feature = "ring")]
30mod ring;
31
32#[cfg(feature = "ring")]
33pub use self::ring::*;
34
35use core::fmt;
36use rand_core::{CryptoRng, RngCore};
37
38/// A P-256 public key (point on the curve) in uncompressed format.
39///
40/// The encoding is as specified in *[SEC 1: Elliptic Curve Cryptography]*, but without the leading
41/// `0x04` byte: The first 32 Bytes are the big-endian encoding of the point's X coordinate, and the
42/// remaining 32 Bytes are the Y coordinate, encoded the same way.
43///
44/// Note that this type does not provide any validity guarantees (unlike [`PrivateKey`]
45/// implementors): It is possible to represent invalid public P-256 keys, such as the point at
46/// infinity, with this type. The other APIs in this module are designed to take that into account.
47///
48/// [SEC 1: Elliptic Curve Cryptography]: http://www.secg.org/sec1-v2.pdf
49/// [`PrivateKey`]: trait.PrivateKey.html
50pub struct PublicKey(pub [u8; 64]);
51
52/// A shared secret resulting from an ECDH key agreement.
53///
54/// This is returned by implementations of [`SecretKey::agree`].
55///
56/// [`SecretKey::agree`]: trait.SecretKey.html#tymethod.agree
57pub struct SharedSecret(pub [u8; 32]);
58
59/// Error returned by [`SecretKey::agree`] when the public key of the other party is invalid.
60///
61/// [`SecretKey::agree`]: trait.SecretKey.html#tymethod.agree
62#[derive(Debug)]
63pub struct InvalidPublicKey {}
64
65impl InvalidPublicKey {
66    /// Creates a new `InvalidPublicKey` error.
67    pub fn new() -> Self {
68        Self {}
69    }
70}
71
72impl fmt::Display for InvalidPublicKey {
73    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
74        f.write_str("invalid public key")
75    }
76}
77
78/// Trait for ECDH providers.
79pub trait EcdhProvider {
80    /// Provider-defined secret key type.
81    type SecretKey: SecretKey;
82
83    /// Generates a P-256 key pair using cryptographically strong randomness.
84    ///
85    /// Implementors must ensure that they only return valid private/public key pairs from this
86    /// method.
87    ///
88    /// Rubble will pass a cryptographically secure random number generator `rng` to this function
89    /// that may be used to obtain entropy for key generation. Implementations may also use their
90    /// own RNG if they so choose.
91    fn generate_keypair<R>(&mut self, rng: &mut R) -> (Self::SecretKey, PublicKey)
92    where
93        R: RngCore + CryptoRng;
94}
95
96/// Secret key operations required by Rubble.
97///
98/// This API imposes no requirements on the representation or location of secret keys. This means
99/// that it should be possible to implement this trait even for keys stored in some secure key
100/// storage like a smartcard.
101pub trait SecretKey: Sized {
102    /// Performs ECDH key agreement using an ephemeral secret key `self` and the public key of the
103    /// other party.
104    ///
105    /// Here, "ephemeral" just means that this method takes `self` by value. This allows
106    /// implementing `SecretKey` for providers that enforce single-use keys using Rust ownership
107    /// (like *ring*).
108    ///
109    /// # Errors
110    ///
111    /// If `foreign_key` is an invalid public key, implementors must return an error.
112    fn agree(self, foreign_key: &PublicKey) -> Result<SharedSecret, InvalidPublicKey>;
113}
114
115/// Runs Rubble's P-256 provider testsuite against `provider`.
116///
117/// Note that this is just a quick smoke test that does not provide any assurance about security
118/// properties. The P-256 provider should have a dedicated test suite.
119pub fn run_tests(mut provider: impl EcdhProvider) {
120    static RNG: &[u8] = &[
121        0x1e, 0x66, 0x81, 0xb6, 0xa3, 0x4e, 0x06, 0x97, 0x75, 0xbe, 0xd4, 0x5c, 0xf9, 0x52, 0x3f,
122        0xf1, 0x5b, 0x6a, 0x72, 0xe2, 0xb8, 0x35, 0xb3, 0x29, 0x5e, 0xe0, 0xbb, 0x92, 0x35, 0xa5,
123        0xb9, 0x60, 0xc9, 0xaf, 0xe2, 0x72, 0x12, 0xf1, 0xc4, 0xfc, 0x10, 0x2d, 0x63, 0x2f, 0x05,
124        0xd6, 0xe5, 0x0a, 0xbf, 0x2c, 0xb9, 0x02, 0x3a, 0x67, 0x23, 0x63, 0x36, 0x7a, 0x62, 0xe6,
125        0x63, 0xce, 0x28, 0x98,
126    ];
127
128    // Pretend-RNG that returns a fixed sequence of pregenerated numbers. Do not do this outside of
129    // tests.
130    struct Rng(&'static [u8]);
131
132    impl RngCore for Rng {
133        fn next_u32(&mut self) -> u32 {
134            rand_core::impls::next_u32_via_fill(self)
135        }
136        fn next_u64(&mut self) -> u64 {
137            rand_core::impls::next_u64_via_fill(self)
138        }
139        fn fill_bytes(&mut self, dest: &mut [u8]) {
140            if self.0.len() < dest.len() {
141                panic!("p256::run_tests: ran out of pregenerated entropy");
142            }
143
144            for chunk in dest.chunks_mut(self.0.len()) {
145                chunk.copy_from_slice(&self.0[..chunk.len()]);
146                self.0 = &self.0[chunk.len()..];
147            }
148        }
149        fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand_core::Error> {
150            self.fill_bytes(dest);
151            Ok(())
152        }
153    }
154
155    impl CryptoRng for Rng {}
156
157    // Test that different key pairs will be generated:
158    let mut rng = Rng(RNG);
159    let (secret1, public1) = provider.generate_keypair(&mut rng);
160    let (secret2, public2) = provider.generate_keypair(&mut rng);
161    assert_ne!(&public1.0[..], &public2.0[..]);
162
163    // Test that ECDH agreement results in the same shared secret:
164    let shared1 = secret1.agree(&public2).unwrap();
165    let shared2 = secret2.agree(&public1).unwrap();
166    assert_eq!(shared1.0, shared2.0);
167
168    // Now, test that ECDH agreement with invalid public keys fails correctly.
169
170    // Point at infinity is an invalid public key:
171    let infty = PublicKey([0; 64]);
172    let (secret, _) = provider.generate_keypair(&mut Rng(RNG));
173    assert!(secret.agree(&infty).is_err());
174
175    // Malicious public key not on the curve:
176    // (taken from https://web-in-security.blogspot.com/2015/09/practical-invalid-curve-attacks.html)
177    let x = [
178        0xb7, 0x0b, 0xf0, 0x43, 0xc1, 0x44, 0x93, 0x57, 0x56, 0xf8, 0xf4, 0x57, 0x8c, 0x36, 0x9c,
179        0xf9, 0x60, 0xee, 0x51, 0x0a, 0x5a, 0x0f, 0x90, 0xe9, 0x3a, 0x37, 0x3a, 0x21, 0xf0, 0xd1,
180        0x39, 0x7f,
181    ];
182    let y = [
183        0x4a, 0x2e, 0x0d, 0xed, 0x57, 0xa5, 0x15, 0x6b, 0xb8, 0x2e, 0xb4, 0x31, 0x4c, 0x37, 0xfd,
184        0x41, 0x55, 0x39, 0x5a, 0x7e, 0x51, 0x98, 0x8a, 0xf2, 0x89, 0xcc, 0xe5, 0x31, 0xb9, 0xc1,
185        0x71, 0x92,
186    ];
187    let mut key = [0; 64];
188    key[..32].copy_from_slice(&x);
189    key[32..].copy_from_slice(&y);
190
191    let (secret, _) = provider.generate_keypair(&mut Rng(RNG));
192    assert!(secret.agree(&PublicKey(key)).is_err());
193}