askar_crypto/alg/
k256.rs

1//! Elliptic curve ECDH and ECDSA support on curve secp256k1
2
3use k256::{
4    ecdsa::{
5        signature::{
6            hazmat::{PrehashSigner, PrehashVerifier},
7            Signer, Verifier,
8        },
9        Signature, SigningKey, VerifyingKey,
10    },
11    elliptic_curve::{
12        self,
13        ecdh::diffie_hellman,
14        sec1::{Coordinates, FromEncodedPoint, ToEncodedPoint},
15    },
16    EncodedPoint, PublicKey, SecretKey,
17};
18use subtle::ConstantTimeEq;
19
20use super::{ec_common, EcCurves, HasKeyAlg, HasKeyBackend, KeyAlg};
21use crate::{
22    buffer::{ArrayKey, WriteBuffer},
23    error::Error,
24    generic_array::typenum::{U32, U33, U65},
25    jwk::{FromJwk, JwkEncoder, JwkParts, ToJwk},
26    kdf::KeyExchange,
27    random::KeyMaterial,
28    repr::{KeyGen, KeyMeta, KeyPublicBytes, KeySecretBytes, KeypairBytes, KeypairMeta},
29    sign::{KeySigVerify, KeySign, SignatureType},
30};
31
32// SECURITY: PublicKey contains a k256::AffinePoint, which is always checked
33// to be on the curve when loaded:
34// <https://github.com/RustCrypto/elliptic-curves/blob/a38df18d221a4ca27851c4523f90ceded6bbd361/k256/src/arithmetic/affine.rs#L96>
35// The identity point is rejected when converting into a k256::PublicKey.
36// This satisfies 5.6.2.3.4 ECC Partial Public-Key Validation Routine from
37// NIST SP 800-56A: _Recommendation for Pair-Wise Key-Establishment Schemes
38// Using Discrete Logarithm Cryptography_.
39
40/// The length of an ES256K signature
41pub const ES256K_SIGNATURE_LENGTH: usize = 64;
42
43/// The length of a compressed public key in bytes
44pub const PUBLIC_KEY_LENGTH: usize = 33;
45/// The length of a secret key
46pub const SECRET_KEY_LENGTH: usize = 32;
47/// The length of a keypair in bytes
48pub const KEYPAIR_LENGTH: usize = SECRET_KEY_LENGTH + PUBLIC_KEY_LENGTH;
49
50/// The 'kty' value of an elliptic curve key JWK
51pub const JWK_KEY_TYPE: &str = "EC";
52/// The 'crv' value of a K-256 key JWK
53pub const JWK_CURVE: &str = "secp256k1";
54
55type FieldSize = elliptic_curve::FieldBytesSize<k256::Secp256k1>;
56
57/// A K-256 (secp256k1) public key or keypair
58#[derive(Clone, Debug)]
59pub struct K256KeyPair {
60    // SECURITY: SecretKey zeroizes on drop
61    secret: Option<SecretKey>,
62    public: PublicKey,
63}
64
65impl K256KeyPair {
66    #[inline]
67    pub(crate) fn from_secret_key(sk: SecretKey) -> Self {
68        let pk = sk.public_key();
69        Self {
70            secret: Some(sk),
71            public: pk,
72        }
73    }
74
75    pub(crate) fn check_public_bytes(&self, pk: &[u8]) -> Result<(), Error> {
76        if self.with_public_bytes(|slf| slf.ct_eq(pk)).into() {
77            Ok(())
78        } else {
79            Err(err_msg!(InvalidKeyData, "invalid k256 keypair"))
80        }
81    }
82
83    pub(crate) fn to_signing_key(&self) -> Option<SigningKey> {
84        self.secret.as_ref().map(SigningKey::from)
85    }
86
87    /// Sign a message with the secret key
88    pub fn sign(&self, message: &[u8]) -> Option<[u8; ES256K_SIGNATURE_LENGTH]> {
89        if let Some(skey) = self.to_signing_key() {
90            let sig: Signature = skey.sign(message);
91            let sigb: [u8; 64] = sig.to_bytes().into();
92            Some(sigb)
93        } else {
94            None
95        }
96    }
97
98    /// Sign a pre-hashed message with the secret key
99    pub fn sign_prehashed(&self, hashed_message: &[u8]) -> Option<[u8; ES256K_SIGNATURE_LENGTH]> {
100        if let Some(skey) = self.to_signing_key() {
101            if let Ok(sig) = PrehashSigner::<Signature>::sign_prehash(&skey, hashed_message) {
102                let sigb: [u8; 64] = sig.to_bytes().into();
103                return Some(sigb);
104            }
105        }
106        None
107    }
108
109    /// Verify a signature against the public key
110    pub fn verify_signature(&self, message: &[u8], signature: &[u8]) -> bool {
111        if let Ok(sig) = Signature::try_from(signature) {
112            let vk = VerifyingKey::from(&self.public);
113            vk.verify(message, &sig).is_ok()
114        } else {
115            false
116        }
117    }
118
119    /// Verify a signature on a prehashed message against the public key
120    pub fn verify_signature_prehashed(&self, hashed_message: &[u8], signature: &[u8]) -> bool {
121        if let Ok(sig) = Signature::try_from(signature) {
122            let vk = VerifyingKey::from(&self.public);
123            vk.verify_prehash(hashed_message, &sig).is_ok()
124        } else {
125            false
126        }
127    }
128}
129
130impl HasKeyBackend for K256KeyPair {}
131
132impl HasKeyAlg for K256KeyPair {
133    fn algorithm(&self) -> KeyAlg {
134        KeyAlg::EcCurve(EcCurves::Secp256k1)
135    }
136}
137
138impl KeyMeta for K256KeyPair {
139    type KeySize = U32;
140}
141
142impl KeyGen for K256KeyPair {
143    fn generate(mut rng: impl KeyMaterial) -> Result<Self, Error> {
144        ArrayKey::<FieldSize>::temp(|buf| loop {
145            rng.read_okm(buf);
146            if let Ok(key) = SecretKey::from_bytes(buf) {
147                return Ok(Self::from_secret_key(key));
148            }
149        })
150    }
151}
152
153impl KeySecretBytes for K256KeyPair {
154    fn from_secret_bytes(key: &[u8]) -> Result<Self, Error> {
155        if key.len() == SECRET_KEY_LENGTH {
156            if let Ok(sk) = SecretKey::from_bytes(key.into()) {
157                return Ok(Self::from_secret_key(sk));
158            }
159        }
160        Err(err_msg!(InvalidKeyData))
161    }
162
163    fn with_secret_bytes<O>(&self, f: impl FnOnce(Option<&[u8]>) -> O) -> O {
164        if let Some(sk) = self.secret.as_ref() {
165            ArrayKey::<FieldSize>::temp(|arr| {
166                ec_common::write_sk(sk, &mut arr[..]);
167                f(Some(arr))
168            })
169        } else {
170            f(None)
171        }
172    }
173}
174
175impl KeypairMeta for K256KeyPair {
176    type PublicKeySize = U33;
177    type KeypairSize = U65;
178}
179
180impl KeypairBytes for K256KeyPair {
181    fn from_keypair_bytes(kp: &[u8]) -> Result<Self, Error> {
182        if kp.len() != KEYPAIR_LENGTH {
183            return Err(err_msg!(InvalidKeyData));
184        }
185        let result = K256KeyPair::from_secret_bytes(&kp[..SECRET_KEY_LENGTH])
186            .map_err(|_| err_msg!(InvalidKeyData))?;
187        result.check_public_bytes(&kp[SECRET_KEY_LENGTH..])?;
188        Ok(result)
189    }
190
191    fn with_keypair_bytes<O>(&self, f: impl FnOnce(Option<&[u8]>) -> O) -> O {
192        if let Some(sk) = self.secret.as_ref() {
193            ArrayKey::<<Self as KeypairMeta>::KeypairSize>::temp(|arr| {
194                ec_common::write_sk(sk, &mut arr[..SECRET_KEY_LENGTH]);
195                let pk_enc = self.public.to_encoded_point(true);
196                arr[SECRET_KEY_LENGTH..].copy_from_slice(pk_enc.as_bytes());
197                f(Some(&*arr))
198            })
199        } else {
200            f(None)
201        }
202    }
203}
204
205impl KeyPublicBytes for K256KeyPair {
206    fn from_public_bytes(key: &[u8]) -> Result<Self, Error> {
207        let pk = PublicKey::from_sec1_bytes(key).map_err(|_| err_msg!(InvalidKeyData))?;
208        Ok(Self {
209            secret: None,
210            public: pk,
211        })
212    }
213
214    fn with_public_bytes<O>(&self, f: impl FnOnce(&[u8]) -> O) -> O {
215        f(self.public.to_encoded_point(true).as_bytes())
216    }
217}
218
219impl KeySign for K256KeyPair {
220    fn write_signature(
221        &self,
222        message: &[u8],
223        sig_type: Option<SignatureType>,
224        out: &mut dyn WriteBuffer,
225    ) -> Result<(), Error> {
226        match sig_type {
227            None | Some(SignatureType::ES256K) => {
228                if let Some(sig) = self.sign(message) {
229                    out.buffer_write(&sig[..])?;
230                    Ok(())
231                } else {
232                    Err(err_msg!(Unsupported, "Signing operation not supported"))
233                }
234            }
235            Some(SignatureType::ES256Kph) => {
236                if let Some(sig) = self.sign_prehashed(message) {
237                    out.buffer_write(&sig[..])?;
238                    Ok(())
239                } else {
240                    Err(err_msg!(Unsupported, "Signing operation not supported"))
241                }
242            }
243            #[allow(unreachable_patterns)]
244            _ => Err(err_msg!(Unsupported, "Unsupported signature type")),
245        }
246    }
247}
248
249impl KeySigVerify for K256KeyPair {
250    fn verify_signature(
251        &self,
252        message: &[u8],
253        signature: &[u8],
254        sig_type: Option<SignatureType>,
255    ) -> Result<bool, Error> {
256        match sig_type {
257            None | Some(SignatureType::ES256K) => Ok(self.verify_signature(message, signature)),
258            Some(SignatureType::ES256Kph) => {
259                Ok(self.verify_signature_prehashed(message, signature))
260            }
261            #[allow(unreachable_patterns)]
262            _ => Err(err_msg!(Unsupported, "Unsupported signature type")),
263        }
264    }
265}
266
267impl ToJwk for K256KeyPair {
268    fn encode_jwk(&self, enc: &mut dyn JwkEncoder) -> Result<(), Error> {
269        let pk_enc = self.public.to_encoded_point(false);
270        let (x, y) = match pk_enc.coordinates() {
271            Coordinates::Identity => {
272                return Err(err_msg!(
273                    Unsupported,
274                    "Cannot convert identity point to JWK"
275                ))
276            }
277            Coordinates::Uncompressed { x, y } => (x, y),
278            Coordinates::Compressed { .. } | Coordinates::Compact { .. } => unreachable!(),
279        };
280
281        enc.add_str("crv", JWK_CURVE)?;
282        enc.add_str("kty", JWK_KEY_TYPE)?;
283        enc.add_as_base64("x", &x[..])?;
284        enc.add_as_base64("y", &y[..])?;
285        if enc.is_secret() {
286            self.with_secret_bytes(|buf| {
287                if let Some(sk) = buf {
288                    enc.add_as_base64("d", sk)
289                } else {
290                    Ok(())
291                }
292            })?;
293        }
294        Ok(())
295    }
296}
297
298impl FromJwk for K256KeyPair {
299    fn from_jwk_parts(jwk: JwkParts<'_>) -> Result<Self, Error> {
300        if jwk.kty != JWK_KEY_TYPE {
301            return Err(err_msg!(InvalidKeyData, "Unsupported key type"));
302        }
303        if jwk.crv != JWK_CURVE {
304            return Err(err_msg!(InvalidKeyData, "Unsupported key algorithm"));
305        }
306        let pk_x = ArrayKey::<FieldSize>::try_new_with(|arr| {
307            if jwk.x.decode_base64(arr)? != arr.len() {
308                Err(err_msg!(InvalidKeyData))
309            } else {
310                Ok(())
311            }
312        })?;
313        let pk_y = ArrayKey::<FieldSize>::try_new_with(|arr| {
314            if jwk.y.decode_base64(arr)? != arr.len() {
315                Err(err_msg!(InvalidKeyData))
316            } else {
317                Ok(())
318            }
319        })?;
320        let pk = Option::from(PublicKey::from_encoded_point(
321            &EncodedPoint::from_affine_coordinates(pk_x.as_ref(), pk_y.as_ref(), false),
322        ))
323        .ok_or_else(|| err_msg!(InvalidKeyData))?;
324        if jwk.d.is_some() {
325            ArrayKey::<FieldSize>::temp(|arr| {
326                if jwk.d.decode_base64(arr)? != arr.len() {
327                    Err(err_msg!(InvalidKeyData))
328                } else {
329                    let kp = K256KeyPair::from_secret_bytes(arr)?;
330                    if kp.public != pk {
331                        Err(err_msg!(InvalidKeyData))
332                    } else {
333                        Ok(kp)
334                    }
335                }
336            })
337        } else {
338            Ok(Self {
339                secret: None,
340                public: pk,
341            })
342        }
343    }
344}
345
346impl KeyExchange for K256KeyPair {
347    fn write_key_exchange(&self, other: &Self, out: &mut dyn WriteBuffer) -> Result<(), Error> {
348        match self.secret.as_ref() {
349            Some(sk) => {
350                let xk = diffie_hellman(sk.to_nonzero_scalar(), other.public.as_affine());
351                out.buffer_write(xk.raw_secret_bytes().as_ref())?;
352                Ok(())
353            }
354            None => Err(err_msg!(MissingSecretKey)),
355        }
356    }
357}
358
359#[cfg(test)]
360mod tests {
361    use base64::Engine;
362    use sha2::Digest;
363
364    use super::*;
365    use crate::repr::ToPublicBytes;
366
367    #[test]
368    fn jwk_expected() {
369        // from https://identity.foundation/EcdsaSecp256k1RecoverySignature2020/
370        // {"kty":"EC",
371        // "crv":"secp256k1",
372        // "d": "rhYFsBPF9q3-uZThy7B3c4LDF_8wnozFUAEm5LLC4Zw",
373        // "kid": "JUvpllMEYUZ2joO59UNui_XYDqxVqiFLLAJ8klWuPBw",
374        // "kty": "EC",
375        // "x": "dWCvM4fTdeM0KmloF57zxtBPXTOythHPMm1HCLrdd3A",
376        // "y": "36uMVGM7hnw-N6GnjFcihWE3SkrhMLzzLCdPMXPEXlA"
377        // }
378        let test_pvt_b64 = "rhYFsBPF9q3-uZThy7B3c4LDF_8wnozFUAEm5LLC4Zw";
379        let test_pub_b64 = (
380            "dWCvM4fTdeM0KmloF57zxtBPXTOythHPMm1HCLrdd3A",
381            "36uMVGM7hnw-N6GnjFcihWE3SkrhMLzzLCdPMXPEXlA",
382        );
383        let test_pvt = base64::engine::general_purpose::URL_SAFE_NO_PAD
384            .decode(test_pvt_b64)
385            .unwrap();
386        let sk = K256KeyPair::from_secret_bytes(&test_pvt).expect("Error creating signing key");
387
388        let jwk = sk.to_jwk_public(None).expect("Error converting key to JWK");
389        let jwk = JwkParts::try_from_str(&jwk).expect("Error parsing JWK");
390        assert_eq!(jwk.kty, JWK_KEY_TYPE);
391        assert_eq!(jwk.crv, JWK_CURVE);
392        assert_eq!(jwk.x, test_pub_b64.0);
393        assert_eq!(jwk.y, test_pub_b64.1);
394        assert_eq!(jwk.d, None);
395        let pk_load = K256KeyPair::from_jwk_parts(jwk).unwrap();
396        assert_eq!(sk.to_public_bytes(), pk_load.to_public_bytes());
397
398        let jwk = sk.to_jwk_secret(None).expect("Error converting key to JWK");
399        let jwk = JwkParts::from_slice(&jwk).expect("Error parsing JWK");
400        assert_eq!(jwk.kty, JWK_KEY_TYPE);
401        assert_eq!(jwk.crv, JWK_CURVE);
402        assert_eq!(jwk.x, test_pub_b64.0);
403        assert_eq!(jwk.y, test_pub_b64.1);
404        assert_eq!(jwk.d, test_pvt_b64);
405        let sk_load = K256KeyPair::from_jwk_parts(jwk).unwrap();
406        assert_eq!(
407            sk.to_keypair_bytes().unwrap(),
408            sk_load.to_keypair_bytes().unwrap()
409        );
410    }
411
412    #[test]
413    fn sign_verify_expected() {
414        let test_msg = b"This is a dummy message for use with tests";
415        let test_sig = &hex!(
416            "a2a3affbe18cda8c5a7b6375f05b304c2303ab8beb21428709a43a519f8f946f
417            6ffa7966afdb337e9b1f70bb575282e71d4fe5bbe6bfa97b229d6bd7e97df1e5"
418        );
419        let test_pvt = base64::engine::general_purpose::URL_SAFE_NO_PAD
420            .decode("jv_VrhPomm6_WOzb74xF4eMI0hu9p0W1Zlxi0nz8AFs")
421            .unwrap();
422        let kp = K256KeyPair::from_secret_bytes(&test_pvt).unwrap();
423        let sig = kp.sign(&test_msg[..]).unwrap();
424        assert_eq!(sig, &test_sig[..]);
425        assert!(kp.verify_signature(&test_msg[..], &sig[..]));
426        assert!(!kp.verify_signature(b"Not the message", &sig[..]));
427        assert!(!kp.verify_signature(&test_msg[..], &[0u8; 64]));
428    }
429
430    #[test]
431    fn sign_verify_expected_prehash() {
432        let test_msg = sha2::Sha256::digest(b"This is a dummy message for use with tests");
433        let test_sig = &hex!(
434            "a2a3affbe18cda8c5a7b6375f05b304c2303ab8beb21428709a43a519f8f946f
435            6ffa7966afdb337e9b1f70bb575282e71d4fe5bbe6bfa97b229d6bd7e97df1e5"
436        );
437        let test_pvt = base64::engine::general_purpose::URL_SAFE_NO_PAD
438            .decode("jv_VrhPomm6_WOzb74xF4eMI0hu9p0W1Zlxi0nz8AFs")
439            .unwrap();
440        let kp = K256KeyPair::from_secret_bytes(&test_pvt).unwrap();
441        let sig = kp.sign_prehashed(&test_msg[..]).unwrap();
442        assert_eq!(sig, &test_sig[..]);
443        assert!(kp.verify_signature_prehashed(&test_msg[..], &sig[..]));
444        assert!(!kp.verify_signature_prehashed(b"Not the message", &sig[..]));
445        assert!(!kp.verify_signature_prehashed(&test_msg[..], &[0u8; 64]));
446    }
447
448    #[test]
449    fn key_exchange_random() {
450        let kp1 = K256KeyPair::random().unwrap();
451        let kp2 = K256KeyPair::random().unwrap();
452        assert_ne!(
453            kp1.to_keypair_bytes().unwrap(),
454            kp2.to_keypair_bytes().unwrap()
455        );
456
457        let xch1 = kp1.key_exchange_bytes(&kp2).unwrap();
458        let xch2 = kp2.key_exchange_bytes(&kp1).unwrap();
459        assert_eq!(xch1.len(), 32);
460        assert_eq!(xch1, xch2);
461    }
462
463    #[test]
464    fn round_trip_bytes() {
465        let kp = K256KeyPair::random().unwrap();
466        let cmp = K256KeyPair::from_keypair_bytes(&kp.to_keypair_bytes().unwrap()).unwrap();
467        assert_eq!(
468            kp.to_keypair_bytes().unwrap(),
469            cmp.to_keypair_bytes().unwrap()
470        );
471    }
472}