jwt_compact_frame/alg/
es256k.rs

1//! `ES256K` algorithm implementation using the `secp256k1` crate.
2
3use lazy_static::lazy_static;
4use secp256k1::{
5	constants::{COMPACT_SIGNATURE_SIZE, FIELD_SIZE, SECRET_KEY_SIZE, UNCOMPRESSED_PUBLIC_KEY_SIZE},
6	ecdsa::Signature,
7	All, Message, PublicKey, Secp256k1, SecretKey,
8};
9use sha2::{
10	digest::{crypto_common::BlockSizeUser, generic_array::typenum::U32, FixedOutputReset, HashMarker},
11	Digest, Sha256,
12};
13
14use core::{marker::PhantomData, num::NonZeroUsize};
15
16use crate::{
17	alg::{SecretBytes, SigningKey, VerifyingKey},
18	alloc::Cow,
19	jwk::{JsonWebKey, JwkError, KeyType},
20	Algorithm, AlgorithmSignature,
21};
22
23/// Byte size of a serialized EC coordinate.
24const COORDINATE_SIZE: usize = FIELD_SIZE.len();
25
26impl AlgorithmSignature for Signature {
27	const LENGTH: Option<NonZeroUsize> = NonZeroUsize::new(COMPACT_SIGNATURE_SIZE);
28
29	fn try_from_slice(slice: &[u8]) -> anyhow::Result<Self> {
30		Signature::from_compact(slice).map_err(Into::into)
31	}
32
33	fn as_bytes(&self) -> Cow<'_, [u8]> {
34		Cow::Owned(self.serialize_compact()[..].to_vec())
35	}
36}
37
38/// Algorithm implementing elliptic curve digital signatures (ECDSA) on the secp256k1 curve.
39///
40/// The algorithm does not fix the choice of the message digest algorithm; instead,
41/// it is provided as a type parameter. SHA-256 is the default parameter value,
42/// but it can be set to any cryptographically secure hash function with 32-byte output
43/// (e.g., SHA3-256).
44#[derive(Debug)]
45#[cfg_attr(docsrs, doc(cfg(any(feature = "es256k", feature = "k256"))))]
46pub struct Es256k<D = Sha256> {
47	context: Secp256k1<All>,
48	_digest: PhantomData<D>,
49}
50
51impl<D> Default for Es256k<D>
52where
53	D: FixedOutputReset<OutputSize = U32> + BlockSizeUser + Clone + Default + HashMarker,
54{
55	fn default() -> Self {
56		Es256k { context: Secp256k1::new(), _digest: PhantomData }
57	}
58}
59
60impl<D> Es256k<D>
61where
62	D: FixedOutputReset<OutputSize = U32> + BlockSizeUser + Clone + Default + HashMarker,
63{
64	/// Creates a new algorithm instance.
65	/// This is a (moderately) expensive operation, so if necessary, the algorithm should
66	/// be `clone()`d rather than created anew.
67	#[cfg_attr(docsrs, doc(cfg(feature = "es256k")))]
68	pub fn new(context: Secp256k1<All>) -> Self {
69		Es256k { context, _digest: PhantomData }
70	}
71}
72
73impl<D> Algorithm for Es256k<D>
74where
75	D: FixedOutputReset<OutputSize = U32> + BlockSizeUser + Clone + Default + HashMarker,
76{
77	type Signature = Signature;
78	type SigningKey = SecretKey;
79	type VerifyingKey = PublicKey;
80
81	fn name(&self) -> Cow<'static, str> {
82		Cow::Borrowed("ES256K")
83	}
84
85	fn sign(&self, signing_key: &Self::SigningKey, message: &[u8]) -> Self::Signature {
86		let mut digest = D::default();
87		digest.update(message);
88		let message = Message::from_digest(digest.finalize().into());
89
90		self.context.sign_ecdsa(&message, signing_key)
91	}
92
93	fn verify_signature(
94		&self,
95		signature: &Self::Signature,
96		verifying_key: &Self::VerifyingKey,
97		message: &[u8],
98	) -> bool {
99		let mut digest = D::default();
100		digest.update(message);
101		let message = Message::from_digest(digest.finalize().into());
102
103		// Some implementations (e.g., OpenSSL) produce high-S signatures, which
104		// are considered invalid by this implementation. Hence, we perform normalization here.
105		//
106		// See also: https://github.com/bitcoin/bips/blob/master/bip-0062.mediawiki
107		let mut normalized_signature = *signature;
108		normalized_signature.normalize_s();
109
110		self.context.verify_ecdsa(&message, &normalized_signature, verifying_key).is_ok()
111	}
112}
113
114/// This implementation initializes a `libsecp256k1` context once on the first call to
115/// `to_verifying_key` if it was not initialized previously.
116impl SigningKey<Es256k> for SecretKey {
117	fn from_slice(raw: &[u8]) -> anyhow::Result<Self> {
118		Self::from_slice(raw).map_err(From::from)
119	}
120
121	fn to_verifying_key(&self) -> PublicKey {
122		lazy_static! {
123			static ref CONTEXT: Secp256k1<All> = Secp256k1::new();
124		}
125		PublicKey::from_secret_key(&CONTEXT, self)
126	}
127
128	fn as_bytes(&self) -> SecretBytes<'_> {
129		SecretBytes::borrowed(&self[..])
130	}
131}
132
133impl VerifyingKey<Es256k> for PublicKey {
134	fn from_slice(raw: &[u8]) -> anyhow::Result<Self> {
135		Self::from_slice(raw).map_err(From::from)
136	}
137
138	/// Serializes the key as a 33-byte compressed form, as per [`Self::serialize()`].
139	fn as_bytes(&self) -> Cow<'_, [u8]> {
140		Cow::Owned(self.serialize().to_vec())
141	}
142}
143
144fn create_jwk<'a>(pk: &PublicKey, sk: Option<&'a SecretKey>) -> JsonWebKey<'a> {
145	let uncompressed = pk.serialize_uncompressed();
146	JsonWebKey::EllipticCurve {
147		curve: "secp256k1".into(),
148		x: Cow::Owned(uncompressed[1..=COORDINATE_SIZE].to_vec()),
149		y: Cow::Owned(uncompressed[(1 + COORDINATE_SIZE)..].to_vec()),
150		secret: sk.map(|sk| SecretBytes::borrowed(&sk.as_ref()[..])),
151	}
152}
153
154impl<'a> From<&'a PublicKey> for JsonWebKey<'a> {
155	fn from(key: &'a PublicKey) -> JsonWebKey<'a> {
156		create_jwk(key, None)
157	}
158}
159
160impl TryFrom<&JsonWebKey<'_>> for PublicKey {
161	type Error = JwkError;
162
163	fn try_from(jwk: &JsonWebKey<'_>) -> Result<Self, Self::Error> {
164		let JsonWebKey::EllipticCurve { curve, x, y, .. } = jwk else {
165			return Err(JwkError::key_type(jwk, KeyType::EllipticCurve));
166		};
167		JsonWebKey::ensure_curve(curve, "secp256k1")?;
168		JsonWebKey::ensure_len("x", x, COORDINATE_SIZE)?;
169		JsonWebKey::ensure_len("y", y, COORDINATE_SIZE)?;
170
171		let mut key_bytes = [0_u8; UNCOMPRESSED_PUBLIC_KEY_SIZE];
172		key_bytes[0] = 4; // uncompressed key marker
173		key_bytes[1..=COORDINATE_SIZE].copy_from_slice(x);
174		key_bytes[(1 + COORDINATE_SIZE)..].copy_from_slice(y);
175		PublicKey::from_slice(&key_bytes[..]).map_err(JwkError::custom)
176	}
177}
178
179impl<'a> From<&'a SecretKey> for JsonWebKey<'a> {
180	fn from(key: &'a SecretKey) -> JsonWebKey<'a> {
181		create_jwk(&key.to_verifying_key(), Some(key))
182	}
183}
184
185impl TryFrom<&JsonWebKey<'_>> for SecretKey {
186	type Error = JwkError;
187
188	fn try_from(jwk: &JsonWebKey<'_>) -> Result<Self, Self::Error> {
189		let JsonWebKey::EllipticCurve { secret, .. } = jwk else {
190			return Err(JwkError::key_type(jwk, KeyType::EllipticCurve));
191		};
192		let sk_bytes = secret.as_deref();
193		let sk_bytes = sk_bytes.ok_or_else(|| JwkError::NoField("d".into()))?;
194		JsonWebKey::ensure_len("d", sk_bytes, SECRET_KEY_SIZE)?;
195
196		let sk = SecretKey::from_slice(sk_bytes).map_err(JwkError::custom)?;
197		jwk.ensure_key_match(sk)
198	}
199}