From fee8144c767e362cd92bbf06e86ea6bd59a400ed Mon Sep 17 00:00:00 2001 From: futurGH Date: Mon, 13 Oct 2025 16:59:24 -0400 Subject: [PATCH] use existing jwt logic --- pegasus/lib/jwt.ml | 56 ++++++++++++++++++++++----------------- pegasus/lib/oauth/dpop.ml | 26 +++++++----------- 2 files changed, 40 insertions(+), 42 deletions(-) diff --git a/pegasus/lib/jwt.ml b/pegasus/lib/jwt.ml index 26f4160..eef5869 100644 --- a/pegasus/lib/jwt.ml +++ b/pegasus/lib/jwt.ml @@ -19,9 +19,9 @@ let b64_encode str = let b64_decode str = match Base64.decode ~pad:false ~alphabet:Base64.uri_safe_alphabet str with | Ok s -> - Ok s + s | Error (`Msg e) -> - Error e + failwith e let extract_signature_components signature = if Bytes.length signature <> 64 then failwith "expected 64 byte jwt signature" @@ -30,7 +30,7 @@ let extract_signature_components signature = let s = Bytes.sub signature 32 32 in (r, s) -let sign_jwt payload signing_key = +let sign_jwt payload ?(typ = "JWT") signing_key = let _, (module Curve : Kleidos.CURVE) = signing_key in let alg = match Curve.name with @@ -51,7 +51,7 @@ let sign_jwt payload signing_key = failwith "invalid curve" in let header_json = - `Assoc [("alg", `String alg); ("crv", `String crv); ("typ", `String "JWT")] + `Assoc [("alg", `String alg); ("crv", `String crv); ("typ", `String typ)] in let encoded_header = header_json |> Yojson.Safe.to_string |> b64_encode in let encoded_payload = payload |> Yojson.Safe.to_string |> b64_encode in @@ -65,32 +65,24 @@ let sign_jwt payload signing_key = let decode_jwt jwt = match String.split_on_char '.' jwt with | [header_b64; payload_b64; _] -> ( - match (b64_decode header_b64, b64_decode payload_b64) with - | Ok header_str, Ok payload_str -> ( - try - let header = Yojson.Safe.from_string header_str in - let payload = Yojson.Safe.from_string payload_str in - Ok (header, payload) - with _ -> Error "invalid json in jwt" ) - | Error e, _ | _, Error e -> - Error e ) + try + let header = Yojson.Safe.from_string (b64_decode header_b64) in + let payload = Yojson.Safe.from_string (b64_decode payload_b64) in + Ok (header, payload) + with _ -> Error "invalid jwt" ) | _ -> Error "invalid jwt format" let verify_jwt jwt pubkey = match String.split_on_char '.' jwt with - | [header_b64; payload_b64; signature_b64] -> ( - match b64_decode signature_b64 with - | Error e -> - Error e - | Ok signature_str -> - let signature = Bytes.of_string signature_str in - let signing_input = header_b64 ^ "." ^ payload_b64 in - let verified = - Kleidos.verify ~pubkey ~msg:(Bytes.of_string signing_input) ~signature - in - if verified then decode_jwt jwt - else Error "jwt signature verification failed" ) + | [header_b64; payload_b64; signature_b64] -> + let signature = Bytes.of_string (b64_decode signature_b64) in + let signing_input = header_b64 ^ "." ^ payload_b64 in + let verified = + Kleidos.verify ~pubkey ~msg:(Bytes.of_string signing_input) ~signature + in + if verified then decode_jwt jwt + else Error "jwt signature verification failed" | _ -> Error "invalid jwt format" @@ -126,3 +118,17 @@ let generate_service_jwt ~did ~aud ~lxm ~signing_key = let exp = now_s + Defaults.service_token_exp in let payload = service_jwt_to_yojson {iss= did; aud; lxm; exp} in sign_jwt payload signing_key + +let extract_claim claims key = + try + let open Yojson.Safe.Util in + let rec find_nested json keys = + match keys with + | [] -> + Some json + | k :: rest -> + find_nested (json |> member k) rest + in + let keys = String.split_on_char '.' key in + find_nested claims keys + with _ -> None diff --git a/pegasus/lib/oauth/dpop.ml b/pegasus/lib/oauth/dpop.ml index 7500496..179d670 100644 --- a/pegasus/lib/oauth/dpop.ml +++ b/pegasus/lib/oauth/dpop.ml @@ -29,8 +29,7 @@ let compute_nonce secret counter = Bytes.set_int64_be data 0 counter ; Digestif.SHA256.( hmac_bytes ~key:(Bytes.to_string secret) data - |> to_raw_string - |> Base64.encode_exn ~pad:false ) + |> to_raw_string |> Jwt.b64_encode ) let create_nonce_state secret = let counter = @@ -79,9 +78,6 @@ let normalize_url url = ?port:(Uri.port uri) ~path:(Uri.path uri) () |> Uri.to_string -let b64url_decode s = - Base64.decode_exn ~alphabet:Base64.uri_safe_alphabet ~pad:false s - let compute_jwk_thumbprint jwk = let open Yojson.Safe.Util in let crv = jwk |> member "crv" |> to_string in @@ -92,8 +88,7 @@ let compute_jwk_thumbprint jwk = (* keys must be in lexicographic order *) Printf.sprintf {|{"crv":"%s","kty":"%s","x":"%s","y":"%s"}|} crv kty x y in - Digestif.SHA256.( - digest_string tp |> to_raw_string |> Base64.encode_exn ~pad:false ) + Digestif.SHA256.(digest_string tp |> to_raw_string |> Jwt.b64_encode) let verify_signature jwt jwk = let open Yojson.Safe.Util in @@ -101,15 +96,12 @@ let verify_signature jwt jwk = match parts with | [header_b64; payload_b64; sig_b64] -> let signing_input = header_b64 ^ "." ^ payload_b64 in - let msg = - Digestif.SHA256.(digest_string signing_input |> to_raw_string) - |> Bytes.of_string - in + let msg = Bytes.of_string signing_input in let x = - jwk |> member "x" |> to_string |> b64url_decode |> Bytes.of_string + jwk |> member "x" |> to_string |> Jwt.b64_decode |> Bytes.of_string in let y = - jwk |> member "y" |> to_string |> b64url_decode |> Bytes.of_string + jwk |> member "y" |> to_string |> Jwt.b64_decode |> Bytes.of_string in let crv = jwk |> member "crv" |> to_string in let pubkey = Bytes.cat (Bytes.of_string "\x04") (Bytes.cat x y) in @@ -123,7 +115,7 @@ let verify_signature jwt jwk = | _ -> failwith "unsupported algorithm" ) in - let sig_bytes = b64url_decode sig_b64 |> Bytes.of_string in + let sig_bytes = Jwt.b64_decode sig_b64 |> Bytes.of_string in let r = Bytes.sub sig_bytes 0 32 in let s = Bytes.sub sig_bytes 32 32 in let signature = Bytes.cat r s in @@ -139,8 +131,8 @@ let verify_dpop_proof ~nonce_state ~mthd ~url ~dpop_header ?access_token () = let open Yojson.Safe.Util in match String.split_on_char '.' jwt with | [header_b64; payload_b64; _] -> ( - let header = Yojson.Safe.from_string (b64url_decode header_b64) in - let payload = Yojson.Safe.from_string (b64url_decode payload_b64) in + let header = Yojson.Safe.from_string (Jwt.b64_decode header_b64) in + let payload = Yojson.Safe.from_string (Jwt.b64_decode payload_b64) in let typ = header |> member "typ" |> to_string in if typ <> "dpop+jwt" then Lwt.return_error "invalid typ in dpop proof" else @@ -202,7 +194,7 @@ let verify_dpop_proof ~nonce_state ~mthd ~url ~dpop_header ?access_token () = let expected_ath = Digestif.SHA256.( digest_string token |> to_raw_string - |> Base64.encode_exn ~pad:false ) + |> Jwt.b64_encode ) in if Some expected_ath <> ath_claim then Lwt.return_error "ath mismatch" -- 2.51.2