1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
#![allow(clippy::duplicate_mod)]

use alloc::boxed::Box;
use core::fmt;

use super::ring_like::agreement;
use super::ring_like::rand::SystemRandom;
use crate::crypto::{ActiveKeyExchange, FfdheGroup, SharedSecret, SupportedKxGroup};
use crate::error::{Error, PeerMisbehaved};
use crate::msgs::enums::NamedGroup;
use crate::rand::GetRandomFailed;

/// A key-exchange group supported by *ring*.
///
/// All possible instances of this class are provided by the library in
/// the [`ALL_KX_GROUPS`] array.
struct KxGroup {
    /// The IANA "TLS Supported Groups" name of the group
    name: NamedGroup,

    /// The corresponding ring agreement::Algorithm
    agreement_algorithm: &'static agreement::Algorithm,

    /// Whether the algorithm is allowed by FIPS
    ///
    /// `SupportedKxGroup::fips()` is true if and only if the algorithm is allowed,
    /// _and_ the implementation is FIPS-validated.
    fips_allowed: bool,

    /// aws-lc-rs 1.9 and later accepts more formats of public keys than
    /// just uncompressed.
    ///
    /// That is not compatible with TLS:
    /// - TLS1.3 outlaws other encodings,
    /// - TLS1.2 negotiates other encodings (we only offer uncompressed), and
    ///   defaults to uncompressed if negotiation is not done.
    ///
    /// This function should return `true` if the basic shape of its argument
    /// is consistent with an uncompressed point encoding.  It does not need
    /// to verify that the point is on the curve (if the curve requires that
    /// for security); aws-lc-rs/ring must do that.
    pub_key_validator: fn(&[u8]) -> bool,
}

impl SupportedKxGroup for KxGroup {
    fn start(&self) -> Result<Box<dyn ActiveKeyExchange>, Error> {
        let rng = SystemRandom::new();
        let priv_key = agreement::EphemeralPrivateKey::generate(self.agreement_algorithm, &rng)
            .map_err(|_| GetRandomFailed)?;

        let pub_key = priv_key
            .compute_public_key()
            .map_err(|_| GetRandomFailed)?;

        Ok(Box::new(KeyExchange {
            name: self.name,
            agreement_algorithm: self.agreement_algorithm,
            priv_key,
            pub_key,
            pub_key_validator: self.pub_key_validator,
        }))
    }

    fn ffdhe_group(&self) -> Option<FfdheGroup<'static>> {
        None
    }

    fn name(&self) -> NamedGroup {
        self.name
    }

    fn fips(&self) -> bool {
        self.fips_allowed && super::fips()
    }
}

impl fmt::Debug for KxGroup {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        self.name.fmt(f)
    }
}

/// Ephemeral ECDH on curve25519 (see RFC7748)
pub static X25519: &dyn SupportedKxGroup = &KxGroup {
    name: NamedGroup::X25519,
    agreement_algorithm: &agreement::X25519,

    // "Curves that are included in SP 800-186 but not included in SP 800-56Arev3 are
    //  not approved for key agreement. E.g., the ECDH X25519 and X448 key agreement
    //  schemes (defined in RFC 7748) that use Curve25519 and Curve448, respectively,
    //  are not compliant to SP 800-56Arev3."
    // -- <https://csrc.nist.gov/csrc/media/Projects/cryptographic-module-validation-program/documents/fips%20140-3/FIPS%20140-3%20IG.pdf>
    fips_allowed: false,

    pub_key_validator: |point: &[u8]| point.len() == 32,
};

/// Ephemeral ECDH on secp256r1 (aka NIST-P256)
pub static SECP256R1: &dyn SupportedKxGroup = &KxGroup {
    name: NamedGroup::secp256r1,
    agreement_algorithm: &agreement::ECDH_P256,
    fips_allowed: true,
    pub_key_validator: uncompressed_point,
};

/// Ephemeral ECDH on secp384r1 (aka NIST-P384)
pub static SECP384R1: &dyn SupportedKxGroup = &KxGroup {
    name: NamedGroup::secp384r1,
    agreement_algorithm: &agreement::ECDH_P384,
    fips_allowed: true,
    pub_key_validator: uncompressed_point,
};

fn uncompressed_point(point: &[u8]) -> bool {
    // See `UncompressedPointRepresentation`, which is a retelling of
    // SEC1 section 2.3.3 "Elliptic-Curve-Point-to-Octet-String Conversion"
    // <https://datatracker.ietf.org/doc/html/rfc8446#section-4.2.8.2>
    matches!(point.first(), Some(0x04))
}

/// A list of all the key exchange groups supported by rustls.
pub static ALL_KX_GROUPS: &[&dyn SupportedKxGroup] = &[X25519, SECP256R1, SECP384R1];

/// An in-progress key exchange.  This has the algorithm,
/// our private key, and our public key.
struct KeyExchange {
    name: NamedGroup,
    agreement_algorithm: &'static agreement::Algorithm,
    priv_key: agreement::EphemeralPrivateKey,
    pub_key: agreement::PublicKey,
    pub_key_validator: fn(&[u8]) -> bool,
}

impl ActiveKeyExchange for KeyExchange {
    /// Completes the key exchange, given the peer's public key.
    fn complete(self: Box<Self>, peer: &[u8]) -> Result<SharedSecret, Error> {
        if !(self.pub_key_validator)(peer) {
            return Err(PeerMisbehaved::InvalidKeyShare.into());
        }
        let peer_key = agreement::UnparsedPublicKey::new(self.agreement_algorithm, peer);
        super::ring_shim::agree_ephemeral(self.priv_key, &peer_key)
            .map_err(|_| PeerMisbehaved::InvalidKeyShare.into())
    }

    fn ffdhe_group(&self) -> Option<FfdheGroup<'static>> {
        None
    }

    /// Return the group being used.
    fn group(&self) -> NamedGroup {
        self.name
    }

    /// Return the public key being used.
    fn pub_key(&self) -> &[u8] {
        self.pub_key.as_ref()
    }
}

#[cfg(test)]
mod tests {
    use std::format;

    #[test]
    fn kxgroup_fmt_yields_name() {
        assert_eq!("X25519", format!("{:?}", super::X25519));
    }
}

#[cfg(bench)]
mod benchmarks {
    #[bench]
    fn bench_x25519(b: &mut test::Bencher) {
        bench_any(b, super::X25519);
    }

    #[bench]
    fn bench_ecdh_p256(b: &mut test::Bencher) {
        bench_any(b, super::SECP256R1);
    }

    #[bench]
    fn bench_ecdh_p384(b: &mut test::Bencher) {
        bench_any(b, super::SECP384R1);
    }

    fn bench_any(b: &mut test::Bencher, kxg: &dyn super::SupportedKxGroup) {
        b.iter(|| {
            let akx = kxg.start().unwrap();
            let pub_key = akx.pub_key().to_vec();
            test::black_box(akx.complete(&pub_key).unwrap());
        });
    }
}