bssl_crypto/ecdsa.rs
1// Copyright 2024 The BoringSSL 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// https://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//! Elliptic Curve Digital Signature Algorithm.
16//!
17//! The module implements ECDSA for the NIST curves P-256 and P-384.
18//!
19//! ```
20//! use bssl_crypto::{ecdsa, ec::P256};
21//!
22//! let key = ecdsa::PrivateKey::<P256>::generate();
23//! // Publish your public key.
24//! let public_key_bytes = key.to_der_subject_public_key_info();
25//!
26//! // Sign and publish some message.
27//! let signed_message = b"hello world";
28//! let mut sig = key.sign(signed_message);
29//!
30//! // Anyone with the public key can verify it.
31//! let public_key = ecdsa::PublicKey::<P256>::from_der_subject_public_key_info(
32//! public_key_bytes.as_ref()).unwrap();
33//! assert!(public_key.verify(signed_message, sig.as_slice()).is_ok());
34//! ```
35
36use crate::{
37 ec::{self, Group},
38 with_output_vec, Buffer, FfiSlice, InvalidSignatureError,
39};
40use alloc::vec::Vec;
41use core::marker::PhantomData;
42
43/// An ECDSA public key over the given curve.
44pub struct PublicKey<C: ec::Curve> {
45 point: ec::Point,
46 marker: PhantomData<C>,
47}
48
49impl<C: ec::Curve> PublicKey<C> {
50 /// Parse a public key in uncompressed X9.62 format. (This is the common
51 /// format for elliptic curve points beginning with an 0x04 byte.)
52 pub fn from_x962_uncompressed(x962: &[u8]) -> Option<Self> {
53 let point = ec::Point::from_x962_uncompressed(C::group(), x962)?;
54 Some(Self {
55 point,
56 marker: PhantomData,
57 })
58 }
59
60 /// Serialize this key as uncompressed X9.62 format.
61 pub fn to_x962_uncompressed(&self) -> Buffer {
62 self.point.to_x962_uncompressed()
63 }
64
65 /// Parse a public key in compressed X9.62 point format.
66 pub fn from_x962_compressed(x962: &[u8]) -> Option<Self> {
67 let point = ec::Point::from_x962_compressed(C::group(), x962)?;
68 Some(Self {
69 point,
70 marker: PhantomData,
71 })
72 }
73
74 /// Serialize this key as compressed X9.62 format.
75 ///
76 /// WARNING: compressed form is rarely used and is not as well supported as
77 /// the uncompressed form.
78 pub fn to_x962_compressed(&self) -> Buffer {
79 self.point.to_x962_compressed()
80 }
81
82 /// Parse a public key in SubjectPublicKeyInfo format.
83 /// (This is found in, e.g., X.509 certificates.)
84 pub fn from_der_subject_public_key_info(spki: &[u8]) -> Option<Self> {
85 let point = ec::Point::from_der_subject_public_key_info(C::group(), spki)?;
86 Some(Self {
87 point,
88 marker: PhantomData,
89 })
90 }
91
92 /// Serialize this key in SubjectPublicKeyInfo format.
93 pub fn to_der_subject_public_key_info(&self) -> Buffer {
94 self.point.to_der_subject_public_key_info()
95 }
96
97 /// Verify `signature` as a valid ASN.1-based signature of a digest of
98 /// `signed_msg` with this public key. SHA-256 will be used to produce the
99 /// digest if the curve of this public key is P-256. SHA-384 will be used to
100 /// produce the digest if the curve of this public key is P-384.
101 pub fn verify(&self, signed_msg: &[u8], signature: &[u8]) -> Result<(), InvalidSignatureError> {
102 let digest = C::hash(signed_msg);
103 let result = self.point.with_point_as_ec_key(|ec_key| unsafe {
104 // Safety: `ec_key` is valid per `with_point_as_ec_key`.
105 bssl_sys::ECDSA_verify(
106 /*type=*/ 0,
107 digest.as_slice().as_ffi_ptr(),
108 digest.len(),
109 signature.as_ffi_ptr(),
110 signature.len(),
111 ec_key,
112 )
113 });
114 if result == 1 {
115 Ok(())
116 } else {
117 Err(InvalidSignatureError)
118 }
119 }
120
121 /// Verify `signature` as a valid P1363-based signature of a digest of
122 /// `signed_msg` with this public key. SHA-256 will be used to produce the
123 /// digest if the curve of this public key is P-256. SHA-384 will be used to
124 /// produce the digest if the curve of this public key is P-384.
125 pub fn verify_p1363(
126 &self,
127 signed_msg: &[u8],
128 signature: &[u8],
129 ) -> Result<(), InvalidSignatureError> {
130 let digest = C::hash(signed_msg);
131 let result = self.point.with_point_as_ec_key(|ec_key| unsafe {
132 // Safety: `ec_key` is valid per `with_point_as_ec_key`.
133 bssl_sys::ECDSA_verify_p1363(
134 digest.as_slice().as_ffi_ptr(),
135 digest.len(),
136 signature.as_ffi_ptr(),
137 signature.len(),
138 ec_key,
139 )
140 });
141 if result == 1 {
142 Ok(())
143 } else {
144 Err(InvalidSignatureError)
145 }
146 }
147}
148
149/// An ECDSA private key over the given curve.
150pub struct PrivateKey<C: ec::Curve> {
151 key: ec::Key,
152 marker: PhantomData<C>,
153}
154
155impl<C: ec::Curve> Clone for PrivateKey<C> {
156 fn clone(&self) -> Self {
157 Self {
158 key: self.key.clone(),
159 marker: PhantomData,
160 }
161 }
162}
163
164/// Parsed `ECPrivateKey` dispatched into the corresponding curve types.
165pub enum ParsedPrivateKey {
166 /// A P-256 private key.
167 P256(PrivateKey<ec::P256>),
168 /// A P-384 private key.
169 P384(PrivateKey<ec::P384>),
170}
171
172impl ParsedPrivateKey {
173 /// Parses an ECPrivateKey structure from a DER encoded structure per [RFC 5915],
174 /// whose curve is specified by the `ECParameters`.
175 ///
176 /// Unless the curve group is one of the variants of [`Group`], this method returns [`None`].
177 ///
178 /// [RFC 5915]: <https://datatracker.ietf.org/doc/html/rfc5915>
179 pub fn from_der(der: &[u8]) -> Option<Self> {
180 let key = ec::Key::from_der_ec_private_key_with_curve_names(der)?;
181 match key.get_group()? {
182 Group::P256 => Some(ParsedPrivateKey::P256(PrivateKey {
183 key,
184 marker: PhantomData,
185 })),
186 Group::P384 => Some(ParsedPrivateKey::P384(PrivateKey {
187 key,
188 marker: PhantomData,
189 })),
190 }
191 }
192}
193
194impl<C: ec::Curve> PrivateKey<C> {
195 /// Generate a random private key.
196 pub fn generate() -> Self {
197 Self {
198 key: ec::Key::generate(C::group()),
199 marker: PhantomData,
200 }
201 }
202
203 /// Parse a `PrivateKey` from a zero-padded, big-endian representation of the secret scalar.
204 pub fn from_big_endian(scalar: &[u8]) -> Option<Self> {
205 let key = ec::Key::from_big_endian(C::group(), scalar)?;
206 Some(Self {
207 key,
208 marker: PhantomData,
209 })
210 }
211
212 /// Return the private key as zero-padded, big-endian bytes.
213 pub fn to_big_endian(&self) -> Buffer {
214 self.key.to_big_endian()
215 }
216
217 /// Parse an ECPrivateKey structure (from RFC 5915). The key must be on the
218 /// specified curve.
219 pub fn from_der_ec_private_key(der: &[u8]) -> Option<Self> {
220 let key = ec::Key::from_der_ec_private_key(C::group(), der)?;
221 Some(Self {
222 key,
223 marker: PhantomData,
224 })
225 }
226
227 /// Serialize this private key as an ECPrivateKey structure (from RFC 5915).
228 pub fn to_der_ec_private_key(&self) -> Buffer {
229 self.key.to_der_ec_private_key()
230 }
231
232 /// Parse a PrivateKeyInfo structure (from RFC 5208), commonly called
233 /// "PKCS#8 format". The key must be on the specified curve.
234 pub fn from_der_private_key_info(der: &[u8]) -> Option<Self> {
235 let key = ec::Key::from_der_private_key_info(C::group(), der)?;
236 Some(Self {
237 key,
238 marker: PhantomData,
239 })
240 }
241
242 // Caller must make sure that the group of the key matches `C`,
243 // or else it panics.
244 pub(crate) fn from_ec_key(key: ec::Key) -> Self {
245 assert_eq!(key.get_group(), Some(C::group()));
246 Self {
247 key,
248 marker: PhantomData,
249 }
250 }
251
252 /// Serialize this private key as a PrivateKeyInfo structure (from RFC 5208),
253 /// commonly called "PKCS#8 format".
254 pub fn to_der_private_key_info(&self) -> Buffer {
255 self.key.to_der_private_key_info()
256 }
257
258 /// Serialize the _public_ part of this key in uncompressed X9.62 format.
259 pub fn to_x962_uncompressed(&self) -> Buffer {
260 self.key.to_x962_uncompressed()
261 }
262
263 /// Serialize the _public_ part of this key in compressed X9.62 format.
264 ///
265 /// WARNING: compressed form is rarely used and is not as well supported as
266 /// the uncompressed form.
267 pub fn to_x962_compressed(&self) -> Buffer {
268 self.key.to_x962_compressed()
269 }
270
271 /// Serialize this key in SubjectPublicKeyInfo format.
272 pub fn to_der_subject_public_key_info(&self) -> Buffer {
273 self.key.to_der_subject_public_key_info()
274 }
275
276 /// Return the public key corresponding to this private key.
277 pub fn to_public_key(&self) -> PublicKey<C> {
278 PublicKey {
279 point: self.key.to_point(),
280 marker: PhantomData,
281 }
282 }
283
284 /// Sign a digest of `to_be_signed` using this key and return the
285 /// ASN.1-based signature. SHA-256 will be used to produce the digest if the
286 /// curve of this public key is P-256. SHA-384 will be used to produce the
287 /// digest if the curve of this public key is P-384.
288 pub fn sign(&self, to_be_signed: &[u8]) -> Vec<u8> {
289 // Safety: `self.key` is valid by construction.
290 let max_size = unsafe { bssl_sys::ECDSA_size(self.key.as_ffi_ptr()) };
291 // No curve can be empty.
292 assert_ne!(max_size, 0);
293
294 let digest = C::hash(to_be_signed);
295
296 unsafe {
297 with_output_vec(max_size, |out_buf| {
298 let mut out_len: core::ffi::c_uint = 0;
299 // Safety: `out_buf` points to at least `max_size` bytes,
300 // as required.
301 let result = {
302 bssl_sys::ECDSA_sign(
303 /*type=*/ 0,
304 digest.as_slice().as_ffi_ptr(),
305 digest.len(),
306 out_buf,
307 &mut out_len,
308 self.key.as_ffi_ptr(),
309 )
310 };
311 // Signing should never fail unless we're out of memory,
312 // which this crate doesn't handle.
313 assert_eq!(result, 1);
314 let out_len = out_len as usize;
315 assert!(out_len <= max_size);
316 // Safety: `out_len` bytes have been written.
317 out_len
318 })
319 }
320 }
321
322 /// Sign a digest of `to_be_signed` using this key and return the
323 /// P1363-based signature. SHA-256 will be used to produce the digest if
324 /// the curve of this public key is P-256. SHA-384 will be used to produce
325 /// the digest if the curve of this public key is P-384.
326 pub fn sign_p1363(&self, to_be_signed: &[u8]) -> Vec<u8> {
327 // Safety: `self.key` is valid by construction.
328 let max_size = unsafe { bssl_sys::ECDSA_size_p1363(self.key.as_ffi_ptr()) };
329 // No curve can be empty.
330 assert_ne!(max_size, 0);
331
332 let digest = C::hash(to_be_signed);
333
334 unsafe {
335 with_output_vec(max_size, |out_buf| {
336 let mut out_len = 0usize;
337 // Safety: `out_buf` points to at least `size` bytes, as
338 // required.
339 let result = {
340 bssl_sys::ECDSA_sign_p1363(
341 digest.as_slice().as_ffi_ptr(),
342 digest.len(),
343 out_buf,
344 &mut out_len,
345 max_size,
346 self.key.as_ffi_ptr(),
347 )
348 };
349 // Signing should never fail unless we're out of memory,
350 // which this crate doesn't handle.
351 assert_eq!(result, 1);
352 assert!(out_len <= max_size);
353 // Safety: `out_len` bytes have been written.
354 out_len
355 })
356 }
357 }
358}
359
360#[cfg(test)]
361mod test {
362 use super::*;
363 use crate::ec::{P256, P384};
364
365 fn check_curve<C: ec::Curve>() {
366 let signed_message = b"hello world";
367 let key = PrivateKey::<C>::generate();
368 let mut sig = key.sign(signed_message);
369 let mut sig_p1363 = key.sign_p1363(signed_message);
370
371 let public_key = PublicKey::<C>::from_der_subject_public_key_info(
372 key.to_der_subject_public_key_info().as_ref(),
373 )
374 .unwrap();
375 assert!(public_key.verify(signed_message, sig.as_slice()).is_ok());
376 assert!(public_key
377 .verify_p1363(signed_message, sig_p1363.as_slice())
378 .is_ok());
379
380 sig[10] ^= 1;
381 assert!(public_key.verify(signed_message, sig.as_slice()).is_err());
382 sig_p1363[10] ^= 1;
383 assert!(public_key
384 .verify_p1363(signed_message, sig_p1363.as_slice())
385 .is_err());
386 }
387
388 fn check_compressed<C: ec::Curve>() {
389 let signed_message = b"hello world";
390 let key = PrivateKey::<C>::generate();
391 let mut sig = key.sign(signed_message);
392 let mut sig_p1363 = key.sign_p1363(signed_message);
393
394 let public_key =
395 PublicKey::<C>::from_x962_compressed(key.to_x962_compressed().as_ref()).unwrap();
396 assert!(public_key.verify(signed_message, sig.as_slice()).is_ok());
397 assert!(public_key
398 .verify_p1363(signed_message, sig_p1363.as_slice())
399 .is_ok());
400
401 sig[10] ^= 1;
402 assert!(public_key.verify(signed_message, sig.as_slice()).is_err());
403 sig_p1363[10] ^= 1;
404 assert!(public_key
405 .verify_p1363(signed_message, sig_p1363.as_slice())
406 .is_err());
407 }
408
409 fn check_parsing<C: ec::Curve>() {
410 let key = PrivateKey::<C>::generate();
411 let der = key.to_der_ec_private_key();
412 let parsed = ParsedPrivateKey::from_der(der.as_ref()).unwrap();
413 match parsed {
414 ParsedPrivateKey::P256(_) => assert!(matches!(C::group(), Group::P256)),
415 ParsedPrivateKey::P384(_) => assert!(matches!(C::group(), Group::P384)),
416 }
417 }
418
419 #[test]
420 fn p256() {
421 check_curve::<P256>();
422 check_compressed::<P256>();
423 check_parsing::<P256>();
424 }
425
426 #[test]
427 fn p384() {
428 check_curve::<P384>();
429 check_compressed::<P384>();
430 check_parsing::<P384>();
431 }
432}