tink_hybrid/subtle/
elliptic_curves.rs

1// Copyright 2020-2021 The Tink-Rust Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//      http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14//
15////////////////////////////////////////////////////////////////////////////////
16
17use p256::{
18    elliptic_curve,
19    elliptic_curve::{
20        ecdh,
21        generic_array::typenum::Unsigned,
22        sec1::{EncodedPoint, FromEncodedPoint},
23        AffinePoint,
24    },
25};
26use tink_core::{utils::wrap_err, TinkError};
27use tink_proto::{EcPointFormat, EllipticCurveType};
28
29// See SEC 1 section 2.3.3.
30/// Prefix byte indicating uncompressed format (x || y)
31const EC_FORMAT_PREFIX_UNCOMPRESSED: u8 = 4;
32/// Prefix byte indicating compressed format (x, with y having 1 final bit).
33const EC_FORMAT_PREFIX_COMPRESSED_ODD: u8 = 3;
34/// Prefix byte indicating compressed format (x, with y having 0 final bit).
35const EC_FORMAT_PREFIX_COMPRESSED_EVEN: u8 = 2;
36
37/// An elliptic curve public key.
38#[derive(Debug, Clone)]
39pub enum EcPublicKey {
40    NistP256(AffinePoint<p256::NistP256>),
41}
42
43impl EcPublicKey {
44    pub fn new(curve: EllipticCurveType, x: &[u8], y: &[u8]) -> Result<Self, TinkError> {
45        match curve {
46            EllipticCurveType::NistP256 => {
47                let x = element_from_padded_slice::<p256::NistP256>(x)?;
48                let y = element_from_padded_slice::<p256::NistP256>(y)?;
49                let encoded_pt = EncodedPoint::<p256::NistP256>::from_affine_coordinates(
50                    &x, &y, /* compress= */ false,
51                );
52                let affine_pt = Option::<_>::from(
53                    AffinePoint::<p256::NistP256>::from_encoded_point(&encoded_pt),
54                )
55                .ok_or_else(|| TinkError::new("invalid point"))?;
56                Ok(EcPublicKey::NistP256(affine_pt))
57            }
58            _ => Err(format!("unsupported curve {curve:?}").into()),
59        }
60    }
61
62    pub fn curve(&self) -> EllipticCurveType {
63        match self {
64            EcPublicKey::NistP256(_) => EllipticCurveType::NistP256,
65        }
66    }
67
68    pub fn x_y_bytes(&self) -> Result<(Vec<u8>, Vec<u8>), TinkError> {
69        match self {
70            EcPublicKey::NistP256(affine_pt) => {
71                // Check that the public key data is in the expected uncompressed format:
72                //  - 1 byte uncompressed prefix (0x04)
73                //  - P bytes of X coordinate
74                //  - P bytes of Y coordinate
75                // where P is the field element size.
76                let encoded_pt: EncodedPoint<p256::NistP256> =
77                    EncodedPoint::<p256::NistP256>::from(*affine_pt);
78                let pub_key_data = encoded_pt.as_bytes().to_vec();
79                let point_len =
80                    <p256::NistP256 as elliptic_curve::Curve>::FieldBytesSize::to_usize();
81                if pub_key_data.len() != 2 * point_len + 1
82                    || pub_key_data[0] != EC_FORMAT_PREFIX_UNCOMPRESSED
83                {
84                    Err("unexpected public key data format".into())
85                } else {
86                    Ok((
87                        pub_key_data[1..point_len + 1].to_vec(),
88                        pub_key_data[point_len + 1..].to_vec(),
89                    ))
90                }
91            }
92        }
93    }
94}
95
96/// An elliptic curve private key.
97#[derive(Clone)]
98pub enum EcPrivateKey {
99    NistP256(p256::NonZeroScalar),
100}
101
102impl EcPrivateKey {
103    pub fn public_key(&self) -> EcPublicKey {
104        match self {
105            EcPrivateKey::NistP256(d) => {
106                let pub_key = p256::PublicKey::from_secret_scalar(d);
107                EcPublicKey::NistP256(*pub_key.as_affine())
108            }
109        }
110    }
111    pub fn d_bytes(&self) -> Vec<u8> {
112        match self {
113            EcPrivateKey::NistP256(d) => d.to_bytes().to_vec(),
114        }
115    }
116}
117
118impl EcPrivateKey {
119    /// Convert a stored private key to an `EcPrivateKey`.
120    pub fn new(curve: EllipticCurveType, d: &[u8]) -> Result<EcPrivateKey, TinkError> {
121        match curve {
122            EllipticCurveType::NistP256 => {
123                let d_elt = element_from_padded_slice::<p256::NistP256>(d)?;
124                let d_scalar = Option::<_>::from(p256::NonZeroScalar::from_repr(d_elt))
125                    .ok_or_else(|| TinkError::new("failed to parse D value"))?;
126                Ok(EcPrivateKey::NistP256(d_scalar))
127            }
128            _ => Err(format!("unsupported curve {curve:?}").into()),
129        }
130    }
131}
132
133fn field_size_in_bytes(c: EllipticCurveType) -> Result<usize, TinkError> {
134    match c {
135        EllipticCurveType::NistP256 => {
136            Ok(<p256::NistP256 as elliptic_curve::Curve>::FieldBytesSize::to_usize())
137        }
138        _ => Err(format!("unsupported curve {c:?}").into()),
139    }
140}
141
142pub fn encoding_size_in_bytes(c: EllipticCurveType, p: EcPointFormat) -> Result<usize, TinkError> {
143    let c_size = field_size_in_bytes(c)?;
144    match p {
145        EcPointFormat::Uncompressed => Ok(2 * c_size + 1), // 04 || x || y
146        EcPointFormat::DoNotUseCrunchyUncompressed => Ok(2 * c_size), // x || y
147        EcPointFormat::Compressed => Ok(c_size + 1),       // {02,03} || x
148        _ => Err(format!("invalid point format {p:?}").into()),
149    }
150}
151
152/// Encode a point into the format specified.
153pub fn point_encode(
154    c: EllipticCurveType,
155    p_format: EcPointFormat,
156    pub_key: &EcPublicKey,
157) -> Result<Vec<u8>, TinkError> {
158    let c_size = field_size_in_bytes(c)?;
159    let (x, y) = pub_key.x_y_bytes()?;
160    match p_format {
161        EcPointFormat::Uncompressed => {
162            let mut encoded = vec![0; 2 * c_size + 1];
163            encoded[1 + 2 * c_size - y.len()..].copy_from_slice(&y);
164            encoded[1 + c_size - x.len()..1 + c_size].copy_from_slice(&x);
165            encoded[0] = EC_FORMAT_PREFIX_UNCOMPRESSED;
166            Ok(encoded)
167        }
168        EcPointFormat::DoNotUseCrunchyUncompressed => {
169            let mut encoded = vec![0; 2 * c_size];
170            encoded[2 * c_size - y.len()..].copy_from_slice(&y);
171            encoded[c_size - x.len()..c_size].copy_from_slice(&x);
172            Ok(encoded)
173        }
174        EcPointFormat::Compressed => {
175            let mut encoded = vec![0; c_size + 1];
176            encoded[0] = if y[y.len() - 1] & 0x01 == 1 {
177                EC_FORMAT_PREFIX_COMPRESSED_ODD
178            } else {
179                EC_FORMAT_PREFIX_COMPRESSED_EVEN
180            };
181            encoded[1 + c_size - x.len()..].copy_from_slice(&x);
182            Ok(encoded)
183        }
184        _ => Err("invalid point format".into()),
185    }
186}
187
188// Decode an encoded point to return an [`EcPubKey`].
189pub fn point_decode(
190    c: EllipticCurveType,
191    p_format: EcPointFormat,
192    e: &[u8],
193) -> Result<EcPublicKey, TinkError> {
194    let c_size = field_size_in_bytes(c)?;
195    match p_format {
196        EcPointFormat::Uncompressed => {
197            if e.len() != (2 * c_size + 1) {
198                return Err("invalid point size".into());
199            }
200            if e[0] != EC_FORMAT_PREFIX_UNCOMPRESSED {
201                return Err("invalid point format".into());
202            }
203            match c {
204                EllipticCurveType::NistP256 => {
205                    let pub_key = p256::PublicKey::from_sec1_bytes(e)
206                        .map_err(|e| wrap_err("invalid point", e))?;
207                    Ok(EcPublicKey::NistP256(*pub_key.as_affine()))
208                }
209                _ => Err(format!("unsupported curve {c:?}").into()),
210            }
211        }
212        EcPointFormat::DoNotUseCrunchyUncompressed => {
213            if e.len() != 2 * c_size {
214                return Err("invalid point size".into());
215            }
216            let mut e_prefixed = Vec::with_capacity(1 + e.len());
217            e_prefixed.push(EC_FORMAT_PREFIX_UNCOMPRESSED);
218            e_prefixed.extend_from_slice(e);
219            point_decode(c, EcPointFormat::Uncompressed, &e_prefixed)
220        }
221        EcPointFormat::Compressed => {
222            if e.len() != c_size + 1 {
223                return Err("compressed point has wrong length".into());
224            }
225            let _lsb = match e[0] {
226                EC_FORMAT_PREFIX_COMPRESSED_EVEN => false,
227                EC_FORMAT_PREFIX_COMPRESSED_ODD => true,
228                _ => return Err("invalid format".into()),
229            };
230            match c {
231                EllipticCurveType::NistP256 => {
232                    let pub_key = p256::PublicKey::from_sec1_bytes(e)
233                        .map_err(|e| wrap_err("invalid point", e))?;
234                    Ok(EcPublicKey::NistP256(*pub_key.as_affine()))
235                }
236                _ => Err(format!("unsupported curve {c:?}").into()),
237            }
238        }
239        _ => Err(format!("invalid point format: {p_format:?}").into()),
240    }
241}
242
243/// Compute a shared secret using given private key and peer public key.
244pub fn compute_shared_secret(
245    peer_pub_key: &EcPublicKey,
246    priv_key: &EcPrivateKey,
247) -> Result<Vec<u8>, TinkError> {
248    let shared_secret = match (peer_pub_key, priv_key) {
249        (EcPublicKey::NistP256(peer_pub_key), EcPrivateKey::NistP256(priv_key)) => {
250            ecdh::diffie_hellman(priv_key, peer_pub_key)
251                .raw_secret_bytes()
252                .to_vec()
253        }
254    };
255    Ok(shared_secret)
256}
257
258/// Create a new private key for a given curve.
259pub fn generate_ecdh_key_pair(c: EllipticCurveType) -> Result<EcPrivateKey, TinkError> {
260    let mut csprng = elliptic_curve::rand_core::OsRng {};
261    match c {
262        EllipticCurveType::NistP256 => Ok(EcPrivateKey::NistP256(p256::NonZeroScalar::random(
263            &mut csprng,
264        ))),
265        _ => Err(format!("unsupported curve {c:?}").into()),
266    }
267}
268
269/// Produce an elliptic field element from a byte slice, allowing for padding
270pub(crate) fn element_from_padded_slice<C: elliptic_curve::Curve>(
271    data: &[u8],
272) -> Result<elliptic_curve::FieldBytes<C>, TinkError> {
273    let point_len = C::FieldBytesSize::to_usize();
274    if data.len() >= point_len {
275        let offset = data.len() - point_len;
276        for v in data.iter().take(offset) {
277            // Check that any excess bytes on the left over and above
278            // the field size are all zeroes.
279            if *v != 0 {
280                return Err("point too large".into());
281            }
282        }
283        Ok(elliptic_curve::FieldBytes::<C>::clone_from_slice(
284            &data[offset..],
285        ))
286    } else {
287        // We have been given data that is too short for the field size.
288        // Left-pad it with zero bytes up to the field size.
289        let mut data_copy = vec![0; point_len];
290        data_copy[(point_len - data.len())..].copy_from_slice(data);
291        Ok(elliptic_curve::FieldBytes::<C>::clone_from_slice(
292            &data_copy,
293        ))
294    }
295}