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}