askar_crypto/alg/
p256.rs

1//! Elliptic curve ECDH and ECDSA support on curve secp256r1
2
3use core::convert::TryFrom;
4
5use p256::{
6    ecdsa::{
7        signature::{
8            hazmat::{PrehashSigner, PrehashVerifier},
9            Signer, Verifier,
10        },
11        Signature, SigningKey, VerifyingKey,
12    },
13    elliptic_curve::{
14        self,
15        ecdh::diffie_hellman,
16        sec1::{Coordinates, FromEncodedPoint, ToEncodedPoint},
17    },
18    EncodedPoint, PublicKey, SecretKey,
19};
20use subtle::ConstantTimeEq;
21
22use super::{ec_common, EcCurves, HasKeyAlg, HasKeyBackend, KeyAlg};
23use crate::{
24    buffer::{ArrayKey, WriteBuffer},
25    error::Error,
26    generic_array::typenum::{U32, U33, U65},
27    jwk::{FromJwk, JwkEncoder, JwkParts, ToJwk},
28    kdf::KeyExchange,
29    random::KeyMaterial,
30    repr::{KeyGen, KeyMeta, KeyPublicBytes, KeySecretBytes, KeypairBytes, KeypairMeta},
31    sign::{KeySigVerify, KeySign, SignatureType},
32};
33
34// SECURITY: PublicKey contains a p256::AffinePoint, which is always checked
35// to be on the curve when loaded:
36// <https://github.com/RustCrypto/elliptic-curves/blob/a38df18d221a4ca27851c4523f90ceded6bbd361/p256/src/arithmetic/affine.rs#L94>
37// The identity point is rejected when converting into a p256::PublicKey.
38// This satisfies 5.6.2.3.4 ECC Partial Public-Key Validation Routine from
39// NIST SP 800-56A: _Recommendation for Pair-Wise Key-Establishment Schemes
40// Using Discrete Logarithm Cryptography_.
41
42/// The length of an ES256 signature
43pub const ES256_SIGNATURE_LENGTH: usize = 64;
44
45/// The length of a compressed public key in bytes
46pub const PUBLIC_KEY_LENGTH: usize = 33;
47/// The length of a secret key
48pub const SECRET_KEY_LENGTH: usize = 32;
49/// The length of a keypair in bytes
50pub const KEYPAIR_LENGTH: usize = SECRET_KEY_LENGTH + PUBLIC_KEY_LENGTH;
51
52/// The 'kty' value of an elliptic curve key JWK
53pub const JWK_KEY_TYPE: &str = "EC";
54/// The 'crv' value of a P-256 key JWK
55pub const JWK_CURVE: &str = "P-256";
56
57type FieldSize = elliptic_curve::FieldBytesSize<p256::NistP256>;
58
59/// A P-256 (secp256r1) public key or keypair
60#[derive(Clone, Debug)]
61pub struct P256KeyPair {
62    // SECURITY: SecretKey zeroizes on drop
63    secret: Option<SecretKey>,
64    public: PublicKey,
65}
66
67impl P256KeyPair {
68    #[inline]
69    pub(crate) fn from_secret_key(sk: SecretKey) -> Self {
70        let pk = sk.public_key();
71        Self {
72            secret: Some(sk),
73            public: pk,
74        }
75    }
76
77    pub(crate) fn check_public_bytes(&self, pk: &[u8]) -> Result<(), Error> {
78        if self.with_public_bytes(|slf| slf.ct_eq(pk)).into() {
79            Ok(())
80        } else {
81            Err(err_msg!(InvalidKeyData, "invalid p256 keypair"))
82        }
83    }
84
85    pub(crate) fn to_signing_key(&self) -> Option<SigningKey> {
86        self.secret.clone().map(SigningKey::from)
87    }
88
89    /// Sign a message with the secret key
90    pub fn sign(&self, message: &[u8]) -> Option<[u8; ES256_SIGNATURE_LENGTH]> {
91        if let Some(skey) = self.to_signing_key() {
92            let sig: Signature = skey.sign(message);
93            let sigb: [u8; 64] = sig.to_bytes().into();
94            Some(sigb)
95        } else {
96            None
97        }
98    }
99
100    /// Sign a pre-hashed message with the secret key
101    pub fn sign_prehashed(&self, hashed_message: &[u8]) -> Option<[u8; ES256_SIGNATURE_LENGTH]> {
102        if let Some(skey) = self.to_signing_key() {
103            if let Ok(sig) = PrehashSigner::<Signature>::sign_prehash(&skey, hashed_message) {
104                let sigb: [u8; 64] = sig.to_bytes().into();
105                return Some(sigb);
106            }
107        }
108        None
109    }
110
111    /// Verify a signature against the public key
112    pub fn verify_signature(&self, message: &[u8], signature: &[u8]) -> bool {
113        if let Ok(sig) = Signature::try_from(signature) {
114            let vk = VerifyingKey::from(&self.public);
115            vk.verify(message, &sig).is_ok()
116        } else {
117            false
118        }
119    }
120
121    /// Verify a signature on a prehashed message against the public key
122    pub fn verify_signature_prehashed(&self, hashed_message: &[u8], signature: &[u8]) -> bool {
123        if let Ok(sig) = Signature::try_from(signature) {
124            let vk = VerifyingKey::from(&self.public);
125            vk.verify_prehash(hashed_message, &sig).is_ok()
126        } else {
127            false
128        }
129    }
130}
131
132impl HasKeyBackend for P256KeyPair {}
133
134impl HasKeyAlg for P256KeyPair {
135    fn algorithm(&self) -> KeyAlg {
136        KeyAlg::EcCurve(EcCurves::Secp256r1)
137    }
138}
139
140impl KeyMeta for P256KeyPair {
141    type KeySize = U32;
142}
143
144impl KeyGen for P256KeyPair {
145    fn generate(mut rng: impl KeyMaterial) -> Result<Self, Error> {
146        ArrayKey::<FieldSize>::temp(|buf| loop {
147            rng.read_okm(buf);
148            if let Ok(key) = SecretKey::from_bytes(buf) {
149                return Ok(Self::from_secret_key(key));
150            }
151        })
152    }
153}
154
155impl KeySecretBytes for P256KeyPair {
156    fn from_secret_bytes(key: &[u8]) -> Result<Self, Error> {
157        if key.len() == SECRET_KEY_LENGTH {
158            if let Ok(sk) = SecretKey::from_bytes(key.into()) {
159                return Ok(Self::from_secret_key(sk));
160            }
161        }
162        Err(err_msg!(InvalidKeyData))
163    }
164
165    fn with_secret_bytes<O>(&self, f: impl FnOnce(Option<&[u8]>) -> O) -> O {
166        if let Some(sk) = self.secret.as_ref() {
167            ArrayKey::<FieldSize>::temp(|arr| {
168                ec_common::write_sk(sk, &mut arr[..]);
169                f(Some(arr))
170            })
171        } else {
172            f(None)
173        }
174    }
175}
176
177impl KeypairMeta for P256KeyPair {
178    type PublicKeySize = U33;
179    type KeypairSize = U65;
180}
181
182impl KeypairBytes for P256KeyPair {
183    fn from_keypair_bytes(kp: &[u8]) -> Result<Self, Error> {
184        if kp.len() != KEYPAIR_LENGTH {
185            return Err(err_msg!(InvalidKeyData));
186        }
187        let result = P256KeyPair::from_secret_bytes(&kp[..SECRET_KEY_LENGTH])
188            .map_err(|_| err_msg!(InvalidKeyData))?;
189        result.check_public_bytes(&kp[SECRET_KEY_LENGTH..])?;
190        Ok(result)
191    }
192
193    fn with_keypair_bytes<O>(&self, f: impl FnOnce(Option<&[u8]>) -> O) -> O {
194        if let Some(sk) = self.secret.as_ref() {
195            ArrayKey::<<Self as KeypairMeta>::KeypairSize>::temp(|arr| {
196                ec_common::write_sk(sk, &mut arr[..SECRET_KEY_LENGTH]);
197                let pk_enc = self.public.to_encoded_point(true);
198                arr[SECRET_KEY_LENGTH..].copy_from_slice(pk_enc.as_bytes());
199                f(Some(&*arr))
200            })
201        } else {
202            f(None)
203        }
204    }
205}
206
207impl KeyPublicBytes for P256KeyPair {
208    fn from_public_bytes(key: &[u8]) -> Result<Self, Error> {
209        let pk = PublicKey::from_sec1_bytes(key).map_err(|_| err_msg!(InvalidKeyData))?;
210        Ok(Self {
211            secret: None,
212            public: pk,
213        })
214    }
215
216    fn with_public_bytes<O>(&self, f: impl FnOnce(&[u8]) -> O) -> O {
217        f(self.public.to_encoded_point(true).as_bytes())
218    }
219}
220
221impl KeySign for P256KeyPair {
222    fn write_signature(
223        &self,
224        message: &[u8],
225        sig_type: Option<SignatureType>,
226        out: &mut dyn WriteBuffer,
227    ) -> Result<(), Error> {
228        match sig_type {
229            None | Some(SignatureType::ES256) => {
230                if let Some(sig) = self.sign(message) {
231                    out.buffer_write(&sig[..])?;
232                    Ok(())
233                } else {
234                    Err(err_msg!(Unsupported, "Undefined secret key"))
235                }
236            }
237            Some(SignatureType::ES256ph) => {
238                if let Some(sig) = self.sign_prehashed(message) {
239                    out.buffer_write(&sig[..])?;
240                    Ok(())
241                } else {
242                    Err(err_msg!(Unsupported, "Signing operation not supported"))
243                }
244            }
245            #[allow(unreachable_patterns)]
246            _ => Err(err_msg!(Unsupported, "Unsupported signature type")),
247        }
248    }
249}
250
251impl KeySigVerify for P256KeyPair {
252    fn verify_signature(
253        &self,
254        message: &[u8],
255        signature: &[u8],
256        sig_type: Option<SignatureType>,
257    ) -> Result<bool, Error> {
258        match sig_type {
259            None | Some(SignatureType::ES256) => Ok(self.verify_signature(message, signature)),
260            Some(SignatureType::ES256ph) => Ok(self.verify_signature_prehashed(message, signature)),
261            #[allow(unreachable_patterns)]
262            _ => Err(err_msg!(Unsupported, "Unsupported signature type")),
263        }
264    }
265}
266
267impl ToJwk for P256KeyPair {
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 P256KeyPair {
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 = P256KeyPair::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 P256KeyPair {
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 JWS RFC https://tools.ietf.org/html/rfc7515
370        // {"kty":"EC",
371        // "crv":"P-256",
372        // "x":"f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU",
373        // "y":"x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0",
374        // "d":"jpsQnnGQmL-YBIffH1136cspYG6-0iY7X1fCE9-E9LI"
375        // }
376        let test_pvt_b64 = "jpsQnnGQmL-YBIffH1136cspYG6-0iY7X1fCE9-E9LI";
377        let test_pub_b64 = (
378            "f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU",
379            "x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0",
380        );
381        let test_pvt = base64::engine::general_purpose::URL_SAFE_NO_PAD
382            .decode(test_pvt_b64)
383            .unwrap();
384        let sk = P256KeyPair::from_secret_bytes(&test_pvt).expect("Error creating signing key");
385
386        let jwk = sk.to_jwk_public(None).expect("Error converting key to JWK");
387        let jwk = JwkParts::try_from_str(&jwk).expect("Error parsing JWK");
388        assert_eq!(jwk.kty, JWK_KEY_TYPE);
389        assert_eq!(jwk.crv, JWK_CURVE);
390        assert_eq!(jwk.x, test_pub_b64.0);
391        assert_eq!(jwk.y, test_pub_b64.1);
392        assert_eq!(jwk.d, None);
393        let pk_load = P256KeyPair::from_jwk_parts(jwk).unwrap();
394        assert_eq!(sk.to_public_bytes(), pk_load.to_public_bytes());
395
396        let jwk = sk.to_jwk_secret(None).expect("Error converting key to JWK");
397        let jwk = JwkParts::from_slice(&jwk).expect("Error parsing JWK");
398        assert_eq!(jwk.kty, JWK_KEY_TYPE);
399        assert_eq!(jwk.crv, JWK_CURVE);
400        assert_eq!(jwk.x, test_pub_b64.0);
401        assert_eq!(jwk.y, test_pub_b64.1);
402        assert_eq!(jwk.d, test_pvt_b64);
403        let sk_load = P256KeyPair::from_jwk_parts(jwk).unwrap();
404        assert_eq!(
405            sk.to_keypair_bytes().unwrap(),
406            sk_load.to_keypair_bytes().unwrap()
407        );
408    }
409
410    #[test]
411    fn jwk_thumbprint() {
412        let pk = P256KeyPair::from_jwk(
413            r#"{
414                "kty": "EC",
415                "crv": "P-256",
416                "x": "tDeeYABgKEAbWicYPCEEI8sP4SRIhHKcHDW7VqrB4LA",
417                "y": "J08HOoIZ0rX2Me3bNFZUltfxIk1Hrc8FsLu8VaSxsMI"
418            }"#,
419        )
420        .unwrap();
421        assert_eq!(
422            pk.to_jwk_thumbprint(None).unwrap(),
423            "8fm8079s3nu4FLV_7dVJoJ69A8XCXn7Za2mtaWCnxR4"
424        );
425    }
426
427    #[test]
428    fn sign_verify_expected() {
429        let test_msg = b"This is a dummy message for use with tests";
430        let test_sig = &hex!(
431            "241f765f19d4e6148452f2249d2fa69882244a6ad6e70aadb8848a6409d20712
432            4e85faf9587100247de7bdace13a3073b47ec8a531ca91c1375b2b6134344413"
433        );
434        let test_pvt = base64::engine::general_purpose::URL_SAFE_NO_PAD
435            .decode("jpsQnnGQmL-YBIffH1136cspYG6-0iY7X1fCE9-E9LI")
436            .unwrap();
437        let kp = P256KeyPair::from_secret_bytes(&test_pvt).unwrap();
438        let sig = kp.sign(&test_msg[..]).unwrap();
439        assert_eq!(sig, &test_sig[..]);
440        assert!(kp.verify_signature(&test_msg[..], &sig[..]));
441        assert!(!kp.verify_signature(b"Not the message", &sig[..]));
442        assert!(!kp.verify_signature(&test_msg[..], &[0u8; 64]));
443    }
444
445    #[test]
446    fn sign_verify_expected_prehash() {
447        let test_msg = sha2::Sha384::digest(b"This is a dummy message for use with tests");
448        let test_sig = &hex!(
449            "a3c0cbc5614ee2c5c1b0cb7302eb9f8d2ab4296ad0e699aa13ec7dc8ff1aca06
450            9075df4336f072547fec3beea6003f3d55bef11c0ee5dba1da091606dfc796f9"
451        );
452        let test_pvt = base64::engine::general_purpose::URL_SAFE_NO_PAD
453            .decode("jpsQnnGQmL-YBIffH1136cspYG6-0iY7X1fCE9-E9LI")
454            .unwrap();
455        let kp = P256KeyPair::from_secret_bytes(&test_pvt).unwrap();
456        let sig = kp.sign_prehashed(&test_msg[..]).unwrap();
457        assert_eq!(sig, &test_sig[..]);
458        assert!(kp.verify_signature_prehashed(&test_msg[..], &sig[..]));
459        assert!(!kp.verify_signature_prehashed(b"Not the message", &sig[..]));
460        assert!(!kp.verify_signature_prehashed(&test_msg[..], &[0u8; 64]));
461    }
462
463    #[test]
464    fn key_exchange_random() {
465        let kp1 = P256KeyPair::random().unwrap();
466        let kp2 = P256KeyPair::random().unwrap();
467        assert_ne!(
468            kp1.to_keypair_bytes().unwrap(),
469            kp2.to_keypair_bytes().unwrap()
470        );
471
472        let xch1 = kp1.key_exchange_bytes(&kp2).unwrap();
473        let xch2 = kp2.key_exchange_bytes(&kp1).unwrap();
474        assert_eq!(xch1.len(), 32);
475        assert_eq!(xch1, xch2);
476    }
477
478    #[test]
479    fn round_trip_bytes() {
480        let kp = P256KeyPair::random().unwrap();
481        let cmp = P256KeyPair::from_keypair_bytes(&kp.to_keypair_bytes().unwrap()).unwrap();
482        assert_eq!(
483            kp.to_keypair_bytes().unwrap(),
484            cmp.to_keypair_bytes().unwrap()
485        );
486    }
487}