Skip to content
Snippets Groups Projects
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
ecdh.rs 4.86 KiB
// Copyright 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use super::ec::exponent256::NonZeroExponentP256;
use super::ec::int256;
use super::ec::int256::Int256;
use super::ec::point::PointP256;
use super::rng256::Rng256;
use super::sha256::Sha256;
use super::Hash256;

pub const NBYTES: usize = int256::NBYTES;

pub struct SecKey {
    a: NonZeroExponentP256,
}

#[cfg_attr(feature = "derive_debug", derive(Clone, PartialEq, Debug))]
pub struct PubKey {
    p: PointP256,
}

impl SecKey {
    pub fn gensk<R>(rng: &mut R) -> SecKey
    where
        R: Rng256,
    {
        SecKey {
            a: NonZeroExponentP256::gen_uniform(rng),
        }
    }

    pub fn genpk(&self) -> PubKey {
        PubKey {
            p: PointP256::base_point_mul(self.a.as_exponent()),
        }
    }

    fn exchange_raw(&self, other: &PubKey) -> PointP256 {
        // At this point, the PubKey type guarantees that other.p is a valid point on the curve.
        // It's the responsibility of the caller to handle errors when converting serialized bytes
        // to a PubKey.
        other.p.mul(self.a.as_exponent())
        // TODO: Do we need to check that the exchanged point is not infinite, and if yes handle
        // the error? The following argument should be reviewed:
        //
        // In principle this isn't needed on the P-256 curve, which has a prime order and a
        // cofactor of 1.
        //
        // Some pointers on this:
        // - https://www.secg.org/sec1-v2.pdf
    }

    // DH key agreement method defined in the FIDO2 specification, Section 5.5.4. "Getting
    // sharedSecret from Authenticator"
    pub fn exchange_x_sha256(&self, other: &PubKey) -> [u8; 32] {
        let p = self.exchange_raw(other);
        let mut x: [u8; 32] = [Default::default(); 32];
        p.getx().to_int().to_bin(&mut x);
        Sha256::hash(&x)
    }
}

impl PubKey {
    #[cfg(test)]
    fn from_bytes_uncompressed(bytes: &[u8]) -> Option<PubKey> {
        PointP256::from_bytes_uncompressed_vartime(bytes).map(|p| PubKey { p })
    }

    #[cfg(test)]
    fn to_bytes_uncompressed(&self, bytes: &mut [u8; 65]) {
        self.p.to_bytes_uncompressed(bytes);
    }

    pub fn from_coordinates(x: &[u8; NBYTES], y: &[u8; NBYTES]) -> Option<PubKey> {
        PointP256::new_checked_vartime(Int256::from_bin(x), Int256::from_bin(y))
            .map(|p| PubKey { p })
    }

    pub fn to_coordinates(&self, x: &mut [u8; NBYTES], y: &mut [u8; NBYTES]) {
        self.p.getx().to_int().to_bin(x);
        self.p.gety().to_int().to_bin(y);
    }
}

#[cfg(test)]
mod test {
    use super::super::rng256::ThreadRng256;
    use super::*;

    // Run more test iterations in release mode, as the code should be faster.
    #[cfg(not(debug_assertions))]
    const ITERATIONS: u32 = 10000;
    #[cfg(debug_assertions)]
    const ITERATIONS: u32 = 500;

    /** Test that key generation creates valid keys **/
    #[test]
    fn test_gen_pub_is_valid_random() {
        let mut rng = ThreadRng256 {};

        for _ in 0..ITERATIONS {
            let sk = SecKey::gensk(&mut rng);
            let pk = sk.genpk();
            assert!(pk.p.is_valid_vartime());
        }
    }

    /** Test that the exchanged key is the same on both sides **/
    #[test]
    fn test_exchange_x_sha256_is_symmetric() {
        let mut rng = ThreadRng256 {};

        for _ in 0..ITERATIONS {
            let sk_a = SecKey::gensk(&mut rng);
            let pk_a = sk_a.genpk();
            let sk_b = SecKey::gensk(&mut rng);
            let pk_b = sk_b.genpk();
            assert_eq!(sk_a.exchange_x_sha256(&pk_b), sk_b.exchange_x_sha256(&pk_a));
        }
    }

    #[test]
    fn test_exchange_x_sha256_bytes_is_symmetric() {
        let mut rng = ThreadRng256 {};

        for _ in 0..ITERATIONS {
            let sk_a = SecKey::gensk(&mut rng);
            let mut pk_bytes_a = [Default::default(); 65];
            sk_a.genpk().to_bytes_uncompressed(&mut pk_bytes_a);

            let sk_b = SecKey::gensk(&mut rng);
            let mut pk_bytes_b = [Default::default(); 65];
            sk_b.genpk().to_bytes_uncompressed(&mut pk_bytes_b);

            let pk_a = PubKey::from_bytes_uncompressed(&pk_bytes_a).unwrap();
            let pk_b = PubKey::from_bytes_uncompressed(&pk_bytes_b).unwrap();
            assert_eq!(sk_a.exchange_x_sha256(&pk_b), sk_b.exchange_x_sha256(&pk_a));
        }
    }

    // TODO: tests with invalid public shares.
}