rubble 0.0.4

An embedded BLE stack
Documentation
use super::*;
use ::p256::{
    elliptic_curve::{
        sec1::{FromEncodedPoint, ToEncodedPoint},
        Field,
    },
    ProjectivePoint, Scalar,
};
use rand_core::{CryptoRng, RngCore};

/// An ECDH provider using the pure-Rust `p256` crate.
pub struct P256Provider {}

impl P256Provider {
    /// Creates a new instance.
    pub fn new() -> Self {
        Self {}
    }
}

impl EcdhProvider for P256Provider {
    type SecretKey = P256SecretKey;

    fn generate_keypair<R>(&mut self, rng: &mut R) -> (Self::SecretKey, PublicKey)
    where
        R: RngCore + CryptoRng,
    {
        let scalar = Scalar::random(rng);
        let secret = P256SecretKey { inner: scalar };

        let public = ProjectivePoint::generator() * scalar;
        let public = public.to_affine();
        let public = public.to_encoded_point(false);

        // Remove the leading `0x04` tag bytes since we don't use that in Rubble.
        let mut public_bytes = [0; 64];
        public_bytes.copy_from_slice(&public.as_bytes()[1..]);
        let public = PublicKey(public_bytes);

        (secret, public)
    }
}

/// A secret key generated by a [`P256Provider`].
///
/// [`P256Provider`]: struct.P256Provider.html
pub struct P256SecretKey {
    // NOTE: We're not using `EphemeralSecret` here, because it (intentionally) cannot be
    // constructed from raw key material, but we want to do that in the tests below.
    inner: Scalar,
}

impl P256SecretKey {
    #[cfg(test)]
    fn from_test_data(bytes: [u8; 32]) -> Self {
        Self {
            inner: Scalar::from_bytes_reduced((&bytes).into()),
        }
    }
}

impl SecretKey for P256SecretKey {
    fn agree(self, foreign_key: &PublicKey) -> Result<SharedSecret, InvalidPublicKey> {
        // Convert the public key to SEC1 format.
        let mut encoded = [0; 65];
        encoded[0] = 0x04; // indicates uncompressed format (see RFC 5480)
        encoded[1..].copy_from_slice(&foreign_key.0);

        let foreign_key =
            ::p256::EncodedPoint::from_bytes(&encoded).map_err(|_| InvalidPublicKey::new())?;

        // ECDH is nothing but multiplying the foreign public key by the local secret key.
        let affine = ::p256::AffinePoint::from_encoded_point(&foreign_key);
        if affine.is_none() {
            return Err(InvalidPublicKey::new());
        }

        let mul_point = ProjectivePoint::from(affine.unwrap()) * self.inner;
        let uncomp_point = ::p256::EncodedPoint::from(mul_point.to_affine());

        // First byte is a `0x04` tag, next 32 Bytes are the shared secret.
        let mut secret = [0; 32];
        secret.copy_from_slice(&uncomp_point.as_bytes()[1..32 + 1]);

        Ok(SharedSecret(secret))
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::ecdh::SecretKey;

    #[test]
    fn testsuite() {
        crate::ecdh::run_tests(P256Provider::new());
    }

    /// Uses `p256` to verify the Bluetooth test vectors.
    ///
    /// See "7.1.2 P-256 sample data" in the spec.
    #[test]
    fn test_vectors() {
        fn parse_into(mut slice: &mut [u8], s: &str) {
            for s_word in s.split_whitespace() {
                assert_eq!(s_word.len(), 8);

                let target = &mut slice[..4];
                for i in 0..4 {
                    target[i] = u8::from_str_radix(&s_word[i * 2..i * 2 + 2], 16).unwrap();
                }
                slice = &mut slice[4..];
            }

            assert!(slice.is_empty());
        }

        // Strings copied straight from the spec
        const PRIV_A: &str =
            "3f49f6d4 a3c55f38 74c9b3e3 d2103f50 4aff607b eb40b799 5899b8a6 cd3c1abd";
        const PUB_A_X: &str =
            "20b003d2 f297be2c 5e2c83a7 e9f9a5b9 eff49111 acf4fddb cc030148 0e359de6";
        const PUB_A_Y: &str =
            "dc809c49 652aeb6d 63329abf 5a52155c 766345c2 8fed3024 741c8ed0 1589d28b";

        const PRIV_B: &str =
            "55188b3d 32f6bb9a 900afcfb eed4e72a 59cb9ac2 f19d7cfb 6b4fdd49 f47fc5fd";
        const PUB_B_X: &str =
            "1ea1f0f0 1faf1d96 09592284 f19e4c00 47b58afd 8615a69f 559077b2 2faaa190";
        const PUB_B_Y: &str =
            "4c55f33e 429dad37 7356703a 9ab85160 472d1130 e28e3676 5f89aff9 15b1214a";

        const DHKEY: &str =
            "ec0234a3 57c8ad05 341010a6 0a397d9b 99796b13 b4f866f1 868d34f3 73bfa698";

        let mut priv_a = [0; 32];
        parse_into(&mut priv_a, PRIV_A);
        let key_a = P256SecretKey::from_test_data(priv_a);

        let mut pub_a_bytes = [0; 64];
        parse_into(&mut pub_a_bytes[..32], PUB_A_X);
        parse_into(&mut pub_a_bytes[32..], PUB_A_Y);
        let pub_a = PublicKey(pub_a_bytes);

        let mut priv_b = [0; 32];
        parse_into(&mut priv_b, PRIV_B);
        let key_b = P256SecretKey::from_test_data(priv_b);

        let mut pub_b_bytes = [0; 64];
        parse_into(&mut pub_b_bytes[..32], PUB_B_X);
        parse_into(&mut pub_b_bytes[32..], PUB_B_Y);
        let pub_b = PublicKey(pub_b_bytes);

        let shared_a = key_a.agree(&pub_b).unwrap();
        let shared_b = key_b.agree(&pub_a).unwrap();
        let mut dhkey = [0; 32];
        parse_into(&mut dhkey, DHKEY);
        assert_eq!(&shared_a.0, &shared_b.0);
        assert_eq!(&shared_a.0, &dhkey);
    }
}