fix: use DecodingKey::from_ec_components for JWK EC keys from_ec_der expects DER-encoded SubjectPublicKeyInfo, not a raw uncompressed EC point — my handcrafted 04||x||y blob was rejected as an invalid signature. from_ec_components takes the base64url x and y directly and is the documented path for JWK EC keys.

Author: Aaron Steven White
Commit 3da89680e766b317e30ee6b3c650b10fd7fce87e
Parent: 6d191cee47
Structural diff unavailable

These commits were pushed via plain git push, so no pre-parsed schemas are available. Install git-remote-cospan and re-push via panproto:// to see scope-level changes, breaking change detection, and semantic diffs.

brew install panproto/tap/git-remote-cospan
1 file changed +4 -37
@@ -276,45 +276,12 @@ fn try_decoding_key_from_jwk(jwk: &serde_json::Value, algorithm: Algorithm) -> O
276276     let crv = jwk.get("crv").and_then(|v| v.as_str());
277277 
278278     match (algorithm, kty, crv) {
279-        (Algorithm::ES256, "EC", Some("P-256")) | (Algorithm::ES256, "EC", Some("secp256r1")) => {
279+        (Algorithm::ES256, "EC", Some("P-256"))
280+        | (Algorithm::ES256, "EC", Some("secp256r1"))
281+        | (Algorithm::ES384, "EC", Some("P-384")) => {
280282             let x = jwk.get("x")?.as_str()?;
281283             let y = jwk.get("y")?.as_str()?;
282-
283-            // Reconstruct the uncompressed EC point (04 || x || y).
284-            use base64::Engine;
285-            let x_bytes = base64::engine::general_purpose::URL_SAFE_NO_PAD
286-                .decode(x)
287-                .ok()?;
288-            let y_bytes = base64::engine::general_purpose::URL_SAFE_NO_PAD
289-                .decode(y)
290-                .ok()?;
291-
292-            let mut ec_point = Vec::with_capacity(1 + x_bytes.len() + y_bytes.len());
293-            ec_point.push(0x04); // uncompressed point prefix
294-            ec_point.extend_from_slice(&x_bytes);
295-            ec_point.extend_from_slice(&y_bytes);
296-
297-            Some(DecodingKey::from_ec_der(&ec_point))
298-        }
299-        // ES384 for P-384 keys (less common in ATProto)
300-        (Algorithm::ES384, "EC", Some("P-384")) => {
301-            let x = jwk.get("x")?.as_str()?;
302-            let y = jwk.get("y")?.as_str()?;
303-
304-            use base64::Engine;
305-            let x_bytes = base64::engine::general_purpose::URL_SAFE_NO_PAD
306-                .decode(x)
307-                .ok()?;
308-            let y_bytes = base64::engine::general_purpose::URL_SAFE_NO_PAD
309-                .decode(y)
310-                .ok()?;
311-
312-            let mut ec_point = Vec::with_capacity(1 + x_bytes.len() + y_bytes.len());
313-            ec_point.push(0x04);
314-            ec_point.extend_from_slice(&x_bytes);
315-            ec_point.extend_from_slice(&y_bytes);
316-
317-            Some(DecodingKey::from_ec_der(&ec_point))
284+            DecodingKey::from_ec_components(x, y).ok()
318285         }
319286         _ => None,
320287     }
cospan · schematic version control on atproto built on AT Protocol