Skip to main content

rustls_symcrypt/
ecdh.rs

1//! EcDh functions. For further documentation please refer to rust_symcrypt::ecdh
2use rustls::crypto::{ActiveKeyExchange, SharedSecret, SupportedKxGroup};
3use rustls::{Error, NamedGroup};
4
5use symcrypt::ecc::{CurveType, EcKey, EcKeyUsage};
6
7/// KxGroup is a struct that easily ties `rustls::NamedGroup` to the `symcrypt_sys::ecurve::CurveType`.
8///
9/// This outlines the supported key exchange groups that are exposed by Rustls and implemented by SymCrypt.
10/// Currently the only supported key exchange groups are `X25519`, `NistP38`, and `Nist256`.
11#[derive(Debug)]
12pub struct KxGroup {
13    name: NamedGroup,
14    curve_type: CurveType,
15}
16
17/// KeyExchange is a struct that defines the state for EcDh operations
18///
19/// the `state` field is tied to the [`EcDh`] struct from symcrypt_sys.
20///
21/// the `name` and `curve_type` provide access to the `rustls::NamedGroup`
22/// and `symcrypt_sys::ecurve::CurveType` respectively
23///
24/// `pub_key` is a `Vec<u8>` that represents the public key that is tied to the [`EcDh`] state.
25/// The `private_key` is not exposed.
26pub struct KeyExchange {
27    state: EcKey,
28    name: NamedGroup,
29    curve_type: CurveType,
30    pub_key: Vec<u8>,
31}
32
33/// All supported KeyExchange groups.
34/// ```ignore
35/// SECP384R1
36/// SECP256R1
37/// X25519 // Enabled with the `x25519` feature
38/// ```
39pub const ALL_KX_GROUPS: &[&dyn SupportedKxGroup] = &[
40    SECP384R1,
41    SECP256R1,
42    #[cfg(feature = "x25519")]
43    X25519,
44];
45
46// Since the type trait size cannot be determined at compile time, we must use trait objects, hence the `&dyn SupportedKxGroup`
47// annotation. Similarly, `KxGroup` must then also be taken as a reference.
48#[cfg(feature = "x25519")]
49pub const X25519: &dyn SupportedKxGroup = &KxGroup {
50    name: NamedGroup::X25519,
51    curve_type: CurveType::Curve25519,
52};
53
54pub const SECP256R1: &dyn SupportedKxGroup = &KxGroup {
55    name: NamedGroup::secp256r1,
56    curve_type: CurveType::NistP256,
57};
58
59pub const SECP384R1: &dyn SupportedKxGroup = &KxGroup {
60    name: NamedGroup::secp384r1,
61    curve_type: CurveType::NistP384,
62};
63
64/// Impl for the trait SupportedKxGroup
65///
66/// `start()` creates a new `symcrypt::ecc::EcKey` struct and subsequently, a new [`KeyExchange`] struct.
67///
68/// `name()` returns the `NamedGroup` of the current [`KeyExchange`] group.
69impl SupportedKxGroup for KxGroup {
70    fn start(&self) -> Result<Box<dyn ActiveKeyExchange>, Error> {
71        let ec_key = EcKey::generate_key_pair(self.curve_type, EcKeyUsage::EcDh)
72            .map_err(|e| Error::General(format!("SymCrypt key generation failed: {:?}", e)))?;
73        let mut pub_key = ec_key
74            .export_public_key()
75            .map_err(|e| Error::General(format!("SymCrypt public key export failed: {:?}", e)))?;
76
77        // Based on RFC 8446 https://www.rfc-editor.org/rfc/rfc8446#section-4.2.8.2.
78        // struct {
79        //     uint8 legacy_form = 4;
80        //     opaque X[coordinate_length];
81        //     opaque Y[coordinate_length];
82        // } UncompressedPointRepresentation;
83
84        // Have to pre-append 0x04 to the first element of the vec since SymCrypt expects the caller to do so,
85        // and Rustls expects the crypto library to append the 0x04.
86        // X25519 does not have the legacy form requirement.
87        match ec_key.get_curve_type() {
88            CurveType::NistP256 | CurveType::NistP384 => {
89                pub_key.insert(0, 0x04); // Prepend legacy byte to public key
90            }
91
92            CurveType::Curve25519 => {
93                // Curve25519 curve does not require public key prepending
94            }
95
96            // Not possible to reach this branch since NistP521 struct is not implemented for key exchange
97            CurveType::NistP521 => {
98                return Err(Error::General(
99                    "NistP521 is not supported for key exchange".to_string(),
100                ));
101            }
102        }
103
104        Ok(Box::new(KeyExchange {
105            state: ec_key,
106            name: self.name,
107            curve_type: self.curve_type,
108            pub_key,
109        }))
110    }
111
112    fn name(&self) -> NamedGroup {
113        self.name
114    }
115}
116
117/// Impl for the trait for `ActiveKeyExchange` in order to do stateful operations on the [`EcDh`] state.
118///
119/// `complete()` takes in a `peer_pub_key` and creates a secondary [`EcDh`] struct in order to generate the secret agreement.
120///
121/// Errors from SymCrypt will be propagated back to the user as a `rustls::Error::GeneralError`.
122///
123/// `pub_key()` will return a ref to the `Vec<u8>` that holds the public key.
124///
125/// `group()` will return the [`NamedGroup`] of the [`KeyExchange`]
126impl ActiveKeyExchange for KeyExchange {
127    fn complete(self: Box<Self>, peer_pub_key: &[u8]) -> Result<SharedSecret, Error> {
128        let new_peer_pub_key = match self.curve_type {
129            CurveType::NistP256 | CurveType::NistP384 => {
130                // If curve type is NistP256 or NistP384 or NistP521 remove the first byte
131                // Based on RFC 8446 https://www.rfc-editor.org/rfc/rfc8446#section-4.2.8.2.
132                // struct {
133                //     uint8 legacy_form = 4;
134                //     opaque X[coordinate_length];
135                //     opaque Y[coordinate_length];
136                // } UncompressedPointRepresentation;
137
138                // Have to remove the legacy_form 0x04. Rustls does not do this for us, and SymCrypt
139                // only expects the X and Y coordinates.
140
141                if peer_pub_key.starts_with(&[0x04]) {
142                    &peer_pub_key[1..] // Return a slice starting from the second byte
143                } else {
144                    return Err(Error::General("Invalid public key".to_string()));
145                }
146            }
147
148            CurveType::Curve25519 => {
149                // Do not remove first byte for Curve22519, since Curve25519 only has the x and y coordinates.
150                peer_pub_key
151            }
152
153            CurveType::NistP521 => {
154                return Err(Error::General(
155                    "NistP521 is not supported for key exchange".to_string(),
156                ));
157            }
158        };
159
160        let peer_ecdh =
161            match EcKey::set_public_key(self.curve_type, new_peer_pub_key, EcKeyUsage::EcDh) {
162                Ok(peer_ecdh) => peer_ecdh,
163                Err(symcrypt_error) => {
164                    let custom_error_message = format!(
165                        "SymCryptError: {}",
166                        symcrypt_error // Using general error to propagate the SymCrypt error back to the caller
167                    );
168                    return Err(Error::General(custom_error_message));
169                }
170            };
171
172        let secret_agreement = match EcKey::ecdh_secret_agreement(&self.state, peer_ecdh) {
173            Ok(secret_agreement) => secret_agreement,
174            Err(symcrypt_error) => {
175                let custom_error_message = format!(
176                    "SymCryptError: {}",
177                    symcrypt_error // Using general error to propagate the SymCrypt error back to the caller
178                );
179                return Err(Error::General(custom_error_message));
180            }
181        };
182        Ok(SharedSecret::from(secret_agreement.as_slice()))
183    }
184
185    fn pub_key(&self) -> &[u8] {
186        self.pub_key.as_ref()
187    }
188
189    fn group(&self) -> NamedGroup {
190        self.name
191    }
192}