ts_crypto/
ecdsa.rs

1//! Elliptic curve keys
2
3use const_oid::ObjectIdentifier;
4use der::{
5    Decode,
6    asn1::{SequenceOf, UintRef},
7};
8use digest::{
9    Digest,
10    array::{Array, ArraySize},
11};
12use ecdsa::{
13    EcdsaCurve, EncodedPoint, Signature, SignatureSize, SigningKey as EcSigningKey,
14    VerifyingKey as EcVerifyingKey,
15};
16use elliptic_curve::{
17    AffinePoint, CurveArithmetic, FieldBytes, FieldBytesSize,
18    point::AffineCoordinates,
19    sec1::{FromEncodedPoint, ToEncodedPoint},
20};
21use pkcs8::{AssociatedOid, DecodePrivateKey};
22use sec1::{EcPrivateKey, point::ModulusSize};
23use sha2::{Sha256, Sha384, Sha512};
24use signature::hazmat::{PrehashVerifier, RandomizedPrehashSigner};
25
26pub use const_oid::db::rfc5912::{SECP_256_R_1, SECP_384_R_1, SECP_521_R_1};
27pub use p256::ecdsa::{SigningKey as P256SigningKey, VerifyingKey as P256VerifyingKey};
28pub use p384::ecdsa::{SigningKey as P384SigningKey, VerifyingKey as P384VerifyingKey};
29pub use p521::ecdsa::{SigningKey as P521SigningKey, VerifyingKey as P521VerifyingKey};
30
31/// A verifying key using `ECDSA`
32pub trait VerifyingKey {
33    /// Verify the message using the SHA-256 digest
34    fn verifies_sha256(&self, signature: &[u8], message: &[u8]) -> bool;
35    /// Verify the message using the SHA-384 digest
36    fn verifies_sha384(&self, signature: &[u8], message: &[u8]) -> bool;
37    /// Verify the message using the SHA-512 digest
38    fn verifies_sha512(&self, signature: &[u8], message: &[u8]) -> bool;
39
40    /// Get the x coordinate bytes.
41    fn x(&self) -> Vec<u8>;
42
43    /// Get the y coordinate bytes.
44    fn y(&self) -> Vec<u8>;
45
46    /// Get the object identifier of the curve.
47    fn curve_oid(&self) -> ObjectIdentifier;
48
49    /// Create a verifying key from the coordinates.
50    fn from_coordinates(x: &[u8], y: &[u8]) -> Option<Self>
51    where
52        Self: Sized;
53
54    /// Create a verifying key from the sec1 encoded coordinates
55    fn from_encoded_coordinates(coordinates: &[u8]) -> Option<Self>
56    where
57        Self: Sized;
58
59    /// Create a new key from a subject public key info der.
60    fn from_spki_der(der: &[u8]) -> Option<Self>
61    where
62        Self: Sized;
63}
64
65/// A signing key using `ECDSA`.
66pub trait SigningKey {
67    /// Sign the message using the SHA-256 digest
68    fn sign_sha256(&self, message: &[u8]) -> Vec<u8>;
69    /// Sign the message using the SHA-384 digest
70    fn sign_sha384(&self, message: &[u8]) -> Vec<u8>;
71    /// Sign the message using the SHA-512 digest
72    fn sign_sha512(&self, message: &[u8]) -> Vec<u8>;
73
74    /// Create a verifying key from this key.
75    fn verifying_key(&self) -> Box<dyn VerifyingKey>;
76
77    /// Create a new key from a PKCS8 der.
78    fn from_pkcs8_der(der: &[u8]) -> Option<Self>
79    where
80        Self: Sized;
81
82    /// Create a new key from a sec1 EC private key der.
83    fn from_sec1_der(der: &[u8]) -> Option<Self>
84    where
85        Self: Sized;
86}
87
88impl<C> VerifyingKey for EcVerifyingKey<C>
89where
90    C: EcdsaCurve + CurveArithmetic + AssociatedOid,
91    AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>,
92    FieldBytesSize<C>: ModulusSize,
93    SignatureSize<C>: ArraySize,
94    EcSigningKey<C>: RandomizedPrehashSigner<Signature<C>> + DecodePrivateKey,
95    Self: pkcs8::DecodePublicKey,
96{
97    /// Verify the message using the SHA-256 digest
98    fn verifies_sha256(&self, signature: &[u8], message: &[u8]) -> bool {
99        let Some(signature) = get_signature(signature) else {
100            return false;
101        };
102
103        let hash = Sha256::digest(message);
104        self.verify_prehash(&hash, &signature).is_ok()
105    }
106
107    /// Verify the message using the SHA-384 digest
108    fn verifies_sha384(&self, signature: &[u8], message: &[u8]) -> bool {
109        let Some(signature) = get_signature(signature) else {
110            return false;
111        };
112        let hash = Sha384::digest(message);
113        self.verify_prehash(&hash, &signature).is_ok()
114    }
115
116    /// Verify the message using the SHA-512 digest
117    fn verifies_sha512(&self, signature: &[u8], message: &[u8]) -> bool {
118        let Some(signature) = get_signature(signature) else {
119            return false;
120        };
121        let hash = Sha512::digest(message);
122        self.verify_prehash(&hash, &signature).is_ok()
123    }
124
125    fn x(&self) -> Vec<u8> {
126        self.as_affine().x().to_vec()
127    }
128
129    fn y(&self) -> Vec<u8> {
130        self.as_affine().y().to_vec()
131    }
132
133    fn from_coordinates(x: &[u8], y: &[u8]) -> Option<Self>
134    where
135        Self: Sized,
136    {
137        let x = Array::try_from(x).ok()?;
138        let y = Array::try_from(y).ok()?;
139        let point = EncodedPoint::<C>::from_affine_coordinates(&x, &y, false);
140        let point = AffinePoint::<C>::from_encoded_point(&point).into_option()?;
141        Self::from_affine(point).ok()
142    }
143
144    fn from_encoded_coordinates(coordinates: &[u8]) -> Option<Self>
145    where
146        Self: Sized,
147    {
148        let point = EncodedPoint::<C>::from_bytes(coordinates).ok()?;
149        let point = AffinePoint::<C>::from_encoded_point(&point).into_option()?;
150        Self::from_affine(point).ok()
151    }
152
153    fn from_spki_der(der: &[u8]) -> Option<Self>
154    where
155        Self: Sized,
156    {
157        <Self as pkcs8::DecodePublicKey>::from_public_key_der(der).ok()
158    }
159
160    fn curve_oid(&self) -> ObjectIdentifier {
161        <C as AssociatedOid>::OID
162    }
163}
164
165impl<C> SigningKey for EcSigningKey<C>
166where
167    C: EcdsaCurve + CurveArithmetic + AssociatedOid,
168    AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>,
169    FieldBytesSize<C>: ModulusSize,
170    SignatureSize<C>: ArraySize,
171    Self: RandomizedPrehashSigner<Signature<C>> + DecodePrivateKey,
172    EcVerifyingKey<C>: pkcs8::DecodePublicKey,
173{
174    /// Sign the message using this key.
175    ///
176    /// ## Panics
177    /// * If the digest length is less than half the field size of the curve.
178    /// * Any other errors occur during signing.
179    fn sign_sha256(&self, message: &[u8]) -> Vec<u8> {
180        let hash = Sha256::digest(message);
181        self.sign_prehash_with_rng(&mut rand::rng(), &hash)
182            .expect("the digest algorithm should produce a hash that is valid for this curve")
183            .to_vec()
184    }
185
186    /// Sign the message using this key.
187    ///
188    /// ## Panics
189    /// * If the digest length is less than half the field size of the curve.
190    /// * Any other errors occur during signing.
191    fn sign_sha384(&self, message: &[u8]) -> Vec<u8> {
192        let hash = Sha384::digest(message);
193        self.sign_prehash_with_rng(&mut rand::rng(), &hash)
194            .expect("the digest algorithm should produce a hash that is valid for this curve")
195            .to_vec()
196    }
197
198    /// Sign the message using this key.
199    ///
200    /// ## Panics
201    /// * If the digest length is less than half the field size of the curve.
202    /// * Any other errors occur during signing.
203    fn sign_sha512(&self, message: &[u8]) -> Vec<u8> {
204        let hash = Sha512::digest(message);
205        self.sign_prehash_with_rng(&mut rand::rng(), &hash)
206            .expect("the digest algorithm should produce a hash that is valid for this curve")
207            .to_vec()
208    }
209
210    fn from_pkcs8_der(der: &[u8]) -> Option<Self>
211    where
212        Self: Sized,
213    {
214        <Self as DecodePrivateKey>::from_pkcs8_der(der).ok()
215    }
216
217    fn from_sec1_der(der: &[u8]) -> Option<Self>
218    where
219        Self: Sized,
220    {
221        let key = EcPrivateKey::from_der(der).ok()?;
222        Self::from_slice(key.private_key).ok()
223    }
224
225    fn verifying_key(&self) -> Box<dyn VerifyingKey> {
226        Box::new(*self.verifying_key())
227    }
228}
229
230/// Get the signature from some bytes.
231fn get_signature<C>(signature: &[u8]) -> Option<Signature<C>>
232where
233    C: EcdsaCurve + CurveArithmetic,
234    SignatureSize<C>: ArraySize,
235{
236    if let Ok(signature) = Signature::from_slice(signature) {
237        Some(signature)
238    } else if let Ok(sequence) = SequenceOf::<UintRef, 2>::from_der(signature) {
239        // Manual parsing of DER encoded signature due to strange type bounds.
240
241        // ```text
242        // ECDSA-Sig-Value ::= SEQUENCE {
243        //   r  INTEGER,
244        //   s  INTEGER
245        // }
246        // ```
247        let r = sequence.get(0)?;
248        let s = sequence.get(1)?;
249
250        // Pad `r` and `s` to the required length
251        let mut r_array = Array::<u8, FieldBytesSize<C>>::default();
252        if r.as_bytes().len() > r_array.len() {
253            return None;
254        }
255        let offset = r_array.len().saturating_sub(r.as_bytes().len());
256        #[allow(clippy::indexing_slicing, reason = "offest is always in bounds")]
257        r_array[offset..].copy_from_slice(r.as_bytes());
258
259        let mut s_array = Array::<u8, FieldBytesSize<C>>::default();
260        if s.as_bytes().len() > s_array.len() {
261            return None;
262        }
263        let offset = r_array.len().saturating_sub(s.as_bytes().len());
264        #[allow(clippy::indexing_slicing, reason = "offest is always in bounds")]
265        s_array[offset..].copy_from_slice(s.as_bytes());
266
267        let r = FieldBytes::<C>::try_from(r_array.as_slice()).ok()?;
268        let s = FieldBytes::<C>::try_from(s_array.as_slice()).ok()?;
269
270        Signature::<C>::from_scalars(r, s).ok()
271    } else {
272        None
273    }
274}