1use anyhow::{bail, ensure, Context, Result};
4use base64::engine::general_purpose::URL_SAFE_NO_PAD;
5use base64::Engine;
6use ring::signature::{self, KeyPair};
7use serde_json::{json, Map, Value};
8
9pub fn b64(bytes: &[u8]) -> String {
10 URL_SAFE_NO_PAD.encode(bytes)
11}
12
13pub fn unb64(s: &str) -> Result<Vec<u8>> {
14 ensure!(
15 s.bytes()
16 .all(|b| b.is_ascii_alphanumeric() || b == b'-' || b == b'_'),
17 "not base64url without padding"
18 );
19 Ok(URL_SAFE_NO_PAD.decode(s)?)
20}
21
22pub fn sha256(bytes: &[u8]) -> Vec<u8> {
23 ring::digest::digest(&ring::digest::SHA256, bytes)
24 .as_ref()
25 .to_vec()
26}
27
28#[derive(Debug, Clone)]
30pub struct Jws {
31 pub alg: String,
32 pub nonce: String,
33 pub url: String,
34 pub jwk: Option<Map<String, Value>>,
36 pub kid: Option<String>,
37 pub payload: Vec<u8>,
39 signing_input: Vec<u8>,
40 signature: Vec<u8>,
41}
42
43impl Jws {
44 pub fn parse(body: &[u8]) -> Result<Self> {
45 let v: Value = serde_json::from_slice(body).context("body is not JSON")?;
46 let o = v.as_object().context("JWS is a JSON object")?;
47 ensure!(
48 !o.contains_key("signatures") && !o.contains_key("header"),
49 "only the flattened JWS serialization with a protected header is accepted"
50 );
51 let field = |k: &str| {
52 o.get(k)
53 .and_then(Value::as_str)
54 .with_context(|| format!("JWS {k} missing"))
55 };
56 let (protected_b64, payload_b64, sig_b64) =
57 (field("protected")?, field("payload")?, field("signature")?);
58 let protected: Value = serde_json::from_slice(&unb64(protected_b64)?)
59 .context("protected header is not JSON")?;
60 let p = protected
61 .as_object()
62 .context("protected header is an object")?;
63 let s = |k: &str| p.get(k).and_then(Value::as_str).map(str::to_owned);
64 let alg = s("alg").context("protected header has no alg")?;
65 let nonce = s("nonce").context("protected header has no nonce")?;
66 let url = s("url").context("protected header has no url")?;
67 let jwk = p
68 .get("jwk")
69 .map(|j| j.as_object().cloned().context("jwk is an object"))
70 .transpose()?;
71 let kid = s("kid");
72 ensure!(jwk.is_some() != kid.is_some(), "exactly one of jwk and kid");
73 Ok(Self {
74 alg,
75 nonce,
76 url,
77 jwk,
78 kid,
79 payload: unb64(payload_b64)?,
80 signing_input: format!("{protected_b64}.{payload_b64}").into_bytes(),
81 signature: unb64(sig_b64)?,
82 })
83 }
84
85 pub fn payload_json(&self) -> Result<Value> {
86 ensure!(!self.payload.is_empty(), "payload is empty");
87 serde_json::from_slice(&self.payload).context("payload is not JSON")
88 }
89
90 pub fn verify(&self, jwk: &Map<String, Value>) -> Result<()> {
92 let key = PublicKey::from_jwk(jwk)?;
93 ensure!(
94 key.alg_ok(&self.alg),
95 "alg {} does not match the {} key",
96 self.alg,
97 key.kind()
98 );
99 key.verify(&self.alg, &self.signing_input, &self.signature)
100 }
101}
102
103pub enum PublicKey {
105 Ec { crv: &'static str, point: Vec<u8> },
106 Rsa { n: Vec<u8>, e: Vec<u8> },
107 Ed25519(Vec<u8>),
108}
109
110impl PublicKey {
111 pub fn from_jwk(jwk: &Map<String, Value>) -> Result<Self> {
112 let s = |k: &str| {
113 jwk.get(k)
114 .and_then(Value::as_str)
115 .with_context(|| format!("jwk {k} missing"))
116 };
117 match s("kty")? {
118 "EC" => {
119 let (crv, len) = match s("crv")? {
120 "P-256" => ("P-256", 32),
121 "P-384" => ("P-384", 48),
122 other => bail!("unsupported EC curve {other}"),
123 };
124 let (x, y) = (unb64(s("x")?)?, unb64(s("y")?)?);
125 ensure!(
126 x.len() == len && y.len() == len,
127 "EC coordinates have the wrong length"
128 );
129 let mut point = vec![4u8];
130 point.extend(x);
131 point.extend(y);
132 Ok(Self::Ec { crv, point })
133 }
134 "RSA" => {
135 let (n, e) = (unb64(s("n")?)?, unb64(s("e")?)?);
136 ensure!(
137 (256..=1024).contains(&n.len()) && !e.is_empty() && e.len() <= 8,
138 "RSA key must be 2048 to 8192 bits"
139 );
140 Ok(Self::Rsa { n, e })
141 }
142 "OKP" => {
143 ensure!(s("crv")? == "Ed25519", "only Ed25519 OKP keys");
144 let x = unb64(s("x")?)?;
145 ensure!(x.len() == 32, "Ed25519 key is 32 bytes");
146 Ok(Self::Ed25519(x))
147 }
148 other => bail!("unsupported key type {other}"),
149 }
150 }
151
152 pub fn kind(&self) -> &'static str {
153 match self {
154 Self::Ec { crv, .. } => crv,
155 Self::Rsa { .. } => "RSA",
156 Self::Ed25519(_) => "Ed25519",
157 }
158 }
159
160 fn alg_ok(&self, alg: &str) -> bool {
161 matches!(
162 (self, alg),
163 (Self::Ec { crv: "P-256", .. }, "ES256")
164 | (Self::Ec { crv: "P-384", .. }, "ES384")
165 | (Self::Rsa { .. }, "RS256")
166 | (Self::Ed25519(_), "EdDSA")
167 )
168 }
169
170 fn verify(&self, alg: &str, msg: &[u8], sig: &[u8]) -> Result<()> {
171 let ok = match (self, alg) {
172 (Self::Ec { point, .. }, "ES256") => {
173 signature::UnparsedPublicKey::new(&signature::ECDSA_P256_SHA256_FIXED, point)
174 .verify(msg, sig)
175 .is_ok()
176 }
177 (Self::Ec { point, .. }, "ES384") => {
178 signature::UnparsedPublicKey::new(&signature::ECDSA_P384_SHA384_FIXED, point)
179 .verify(msg, sig)
180 .is_ok()
181 }
182 (Self::Rsa { n, e }, "RS256") => signature::RsaPublicKeyComponents { n, e }
183 .verify(&signature::RSA_PKCS1_2048_8192_SHA256, msg, sig)
184 .is_ok(),
185 (Self::Ed25519(x), "EdDSA") => {
186 signature::UnparsedPublicKey::new(&signature::ED25519, x)
187 .verify(msg, sig)
188 .is_ok()
189 }
190 _ => false,
191 };
192 ensure!(ok, "JWS signature does not verify");
193 Ok(())
194 }
195}
196
197pub fn thumbprint(jwk: &Map<String, Value>) -> Result<String> {
199 let s = |k: &str| {
200 jwk.get(k)
201 .and_then(Value::as_str)
202 .with_context(|| format!("jwk {k} missing"))
203 };
204 let canonical = match s("kty")? {
205 "EC" => json!({"crv": s("crv")?, "kty": "EC", "x": s("x")?, "y": s("y")?}),
206 "RSA" => json!({"e": s("e")?, "kty": "RSA", "n": s("n")?}),
207 "OKP" => json!({"crv": s("crv")?, "kty": "OKP", "x": s("x")?}),
208 other => bail!("unsupported key type {other}"),
209 };
210 Ok(b64(&sha256(canonical.to_string().as_bytes())))
212}
213
214pub struct AccountKey {
216 pair: signature::EcdsaKeyPair,
217 rng: ring::rand::SystemRandom,
218}
219
220impl AccountKey {
221 pub fn generate() -> Result<Self> {
222 let rng = ring::rand::SystemRandom::new();
223 let pkcs8 = signature::EcdsaKeyPair::generate_pkcs8(
224 &signature::ECDSA_P256_SHA256_FIXED_SIGNING,
225 &rng,
226 )
227 .map_err(|_| anyhow::anyhow!("key generation failed"))?;
228 let pair = signature::EcdsaKeyPair::from_pkcs8(
229 &signature::ECDSA_P256_SHA256_FIXED_SIGNING,
230 pkcs8.as_ref(),
231 &rng,
232 )
233 .map_err(|_| anyhow::anyhow!("key generation failed"))?;
234 Ok(Self { pair, rng })
235 }
236
237 pub fn jwk(&self) -> Map<String, Value> {
238 let p = self.pair.public_key().as_ref();
239 json!({"crv": "P-256", "kty": "EC", "x": b64(&p[1..33]), "y": b64(&p[33..65])})
240 .as_object()
241 .cloned()
242 .unwrap_or_default()
243 }
244
245 pub fn sign(
248 &self,
249 url: &str,
250 nonce: &str,
251 kid: Option<&str>,
252 payload: Option<&Value>,
253 ) -> Result<Value> {
254 let mut protected = json!({"alg": "ES256", "nonce": nonce, "url": url});
255 match kid {
256 Some(k) => protected["kid"] = json!(k),
257 None => protected["jwk"] = Value::Object(self.jwk()),
258 }
259 let protected_b64 = b64(protected.to_string().as_bytes());
260 let payload_b64 = payload
261 .map(|p| b64(p.to_string().as_bytes()))
262 .unwrap_or_default();
263 let sig = self
264 .pair
265 .sign(
266 &self.rng,
267 format!("{protected_b64}.{payload_b64}").as_bytes(),
268 )
269 .map_err(|_| anyhow::anyhow!("signing failed"))?;
270 Ok(
271 json!({"protected": protected_b64, "payload": payload_b64, "signature": b64(sig.as_ref())}),
272 )
273 }
274}