A better Rust ATProto crate
1use base64::Engine;
2use base64::engine::general_purpose::URL_SAFE_NO_PAD;
3use jacquard_common::bos::BosStr;
4use smol_str::SmolStr;
5
6use super::{Header, jwt::Claims};
7
8/// Builds the base64url-encoded `header.payload` signing input.
9fn signing_input<S: BosStr + serde::Serialize>(
10 header: &Header<S>,
11 claims: &Claims<&str>,
12) -> serde_json::Result<(String, String)> {
13 let h = URL_SAFE_NO_PAD.encode(serde_json::to_string(header)?);
14 let p = URL_SAFE_NO_PAD.encode(serde_json::to_string(claims)?);
15 Ok((h, p))
16}
17
18/// Assembles a compact JWS from pre-encoded parts and raw signature bytes.
19fn assemble(header: &str, payload: &str, sig: &[u8]) -> SmolStr {
20 smol_str::format_smolstr!("{header}.{payload}.{}", URL_SAFE_NO_PAD.encode(sig))
21}
22
23/// Creates a compact-serialized signed JWT using ES256 (P-256 ECDSA with SHA-256).
24pub fn create_signed_jwt_es256<S: BosStr + serde::Serialize>(
25 key: p256::ecdsa::SigningKey,
26 header: Header<S>,
27 claims: Claims<&str>,
28) -> serde_json::Result<SmolStr> {
29 use p256::ecdsa::signature::Signer;
30 let (h, p) = signing_input(&header, &claims)?;
31 let sig: p256::ecdsa::Signature = key.sign(format!("{h}.{p}").as_bytes());
32 Ok(assemble(&h, &p, &sig.to_bytes()))
33}
34
35/// Creates a compact-serialized signed JWT using ES384 (P-384 ECDSA with SHA-384).
36pub fn create_signed_jwt_es384<S: BosStr + serde::Serialize>(
37 key: p384::ecdsa::SigningKey,
38 header: Header<S>,
39 claims: Claims<&str>,
40) -> serde_json::Result<SmolStr> {
41 use p384::ecdsa::signature::Signer;
42 let (h, p) = signing_input(&header, &claims)?;
43 let sig: p384::ecdsa::Signature = key.sign(format!("{h}.{p}").as_bytes());
44 Ok(assemble(&h, &p, &sig.to_bytes()))
45}
46
47/// Creates a compact-serialized signed JWT using ES256K (secp256k1 ECDSA with SHA-256).
48pub fn create_signed_jwt_es256k<S: BosStr + serde::Serialize>(
49 key: k256::ecdsa::SigningKey,
50 header: Header<S>,
51 claims: Claims<&str>,
52) -> serde_json::Result<SmolStr> {
53 use k256::ecdsa::signature::Signer;
54 let (h, p) = signing_input(&header, &claims)?;
55 let sig: k256::ecdsa::Signature = key.sign(format!("{h}.{p}").as_bytes());
56 Ok(assemble(&h, &p, &sig.to_bytes()))
57}
58
59/// Creates a compact-serialized signed JWT using EdDSA (Ed25519).
60pub fn create_signed_jwt_eddsa<S: BosStr + serde::Serialize>(
61 key: ed25519_dalek::SigningKey,
62 header: Header<S>,
63 claims: Claims<&str>,
64) -> serde_json::Result<SmolStr> {
65 use ed25519_dalek::Signer;
66 let (h, p) = signing_input(&header, &claims)?;
67 let sig = key.sign(format!("{h}.{p}").as_bytes());
68 Ok(assemble(&h, &p, &sig.to_bytes()))
69}