···157157 {sql| CREATE TRIGGER IF NOT EXISTS cleanup_expired_oauth_codes
158158 AFTER INSERT ON oauth_codes
159159 BEGIN
160160- DELETE FROM oauth_codes WHERE expires_at < unixepoch() * 1000;
160160+ DELETE FROM oauth_codes WHERE expires_at < unixepoch() * 1000 OR used = 1;
161161 END
162162 |sql}
163163 syntax_off]
+3-34
pegasus/lib/oauth/client.ml
···11-type metadata =
22- { client_id: string
33- ; client_name: string option
44- ; client_uri: string
55- ; redirect_uris: string list
66- ; grant_types: string list
77- ; response_types: string list
88- ; scope: string
99- ; token_endpoint_auth_method: string
1010- ; application_type: string
1111- ; dpop_bound_access_tokens: bool
1212- ; jwks_uri: string option
1313- ; jwks: Yojson.Safe.t option }
11+open Types
1421515-let fetch_client_metadata client_id =
33+let fetch_client_metadata client_id : client_metadata Lwt.t =
164 let%lwt {status; _}, res =
175 Cohttp_lwt_unix.Client.get (Uri.of_string client_id)
186 in
···2412 else
2513 let%lwt body = Cohttp_lwt.Body.to_string res in
2614 let json = Yojson.Safe.from_string body in
2727- let open Yojson.Safe.Util in
2828- let metadata =
2929- { client_id= json |> member "client_id" |> to_string
3030- ; client_name= json |> member "client_name" |> to_string_option
3131- ; client_uri= json |> member "client_uri" |> to_string
3232- ; redirect_uris=
3333- json |> member "redirect_uris" |> to_list |> List.map to_string
3434- ; grant_types=
3535- json |> member "grant_types" |> to_list |> List.map to_string
3636- ; response_types=
3737- json |> member "response_types" |> to_list |> List.map to_string
3838- ; scope= json |> member "scope" |> to_string
3939- ; token_endpoint_auth_method=
4040- json |> member "token_endpoint_auth_method" |> to_string
4141- ; application_type= json |> member "application_type" |> to_string
4242- ; dpop_bound_access_tokens=
4343- json |> member "dpop_bound_access_tokens" |> to_bool
4444- ; jwks_uri= json |> member "jwks_uri" |> to_string_option
4545- ; jwks= json |> member "jwks" |> to_option (fun j -> j) }
4646- in
1515+ let metadata = client_metadata_of_yojson json |> Result.get_ok in
4716 if metadata.client_id <> client_id then failwith "client_id mismatch"
4817 else
4918 let scopes = String.split_on_char ' ' metadata.scope in
···66 ; mutable next: string
77 ; rotation_interval_ms: int64 }
8899+type ec_jwk = {crv: string; kty: string; x: string; y: string}
1010+[@@deriving yojson]
1111+912type proof = {jti: string; jkt: string; htm: string; htu: string}
1313+[@@deriving yojson]
10141111-let max_age_s = 60
1212-1313-let rotation_interval_ms = 60_000L
1414-1515-let jti_ttl_s = 3600
1616-1717-let jti_cache_size = 10_000
1818-1919-let jti_cache : (string, int) Hashtbl.t = Hashtbl.create jti_cache_size
1515+let jti_cache : (string, int) Hashtbl.t =
1616+ Hashtbl.create Constants.jti_cache_size
20172118let cleanup_jti_cache () =
2219 let now = int_of_float (Unix.gettimeofday ()) in
···3532 let counter =
3633 Int64.div
3734 (Int64.of_float (Unix.gettimeofday () *. 1000.))
3838- rotation_interval_ms
3535+ Constants.dpop_rotation_interval_ms
3936 in
4037 { secret
4138 ; counter
4239 ; prev= compute_nonce secret (Int64.pred counter)
4340 ; curr= compute_nonce secret counter
4441 ; next= compute_nonce secret (Int64.succ counter)
4545- ; rotation_interval_ms }
4242+ ; rotation_interval_ms= Constants.dpop_rotation_interval_ms }
46434744let next_nonce state =
4845 let now_counter =
···6360 valid
64616562let add_jti jti =
6666- let expires_at = int_of_float (Unix.gettimeofday ()) + jti_ttl_s in
6363+ let expires_at = int_of_float (Unix.gettimeofday ()) + Constants.jti_ttl_s in
6764 if Hashtbl.mem jti_cache jti then false (* replay *)
6865 else (
6966 Hashtbl.add jti_cache jti expires_at ;
···7976 |> Uri.to_string
80778178let compute_jwk_thumbprint jwk =
8282- let open Yojson.Safe.Util in
8383- let crv = jwk |> member "crv" |> to_string in
8484- let kty = jwk |> member "kty" |> to_string in
8585- let x = jwk |> member "x" |> to_string in
8686- let y = jwk |> member "y" |> to_string in
7979+ let {crv; kty; x; y} = jwk in
8780 let tp =
8881 (* keys must be in lexicographic order *)
8982 Printf.sprintf {|{"crv":"%s","kty":"%s","x":"%s","y":"%s"}|} crv kty x y
···9184 Digestif.SHA256.(digest_string tp |> to_raw_string |> Jwt.b64_encode)
92859386let verify_signature jwt jwk =
9494- let open Yojson.Safe.Util in
9587 let parts = String.split_on_char '.' jwt in
9688 match parts with
9789 | [header_b64; payload_b64; sig_b64] ->
9890 let signing_input = header_b64 ^ "." ^ payload_b64 in
9991 let msg = Bytes.of_string signing_input in
100100- let x =
101101- jwk |> member "x" |> to_string |> Jwt.b64_decode |> Bytes.of_string
102102- in
103103- let y =
104104- jwk |> member "y" |> to_string |> Jwt.b64_decode |> Bytes.of_string
105105- in
106106- let crv = jwk |> member "crv" |> to_string in
9292+ let {x; y; crv; _} = jwk in
9393+ let x = x |> Jwt.b64_decode |> Bytes.of_string in
9494+ let y = y |> Jwt.b64_decode |> Bytes.of_string in
10795 let pubkey = Bytes.cat (Bytes.of_string "\x04") (Bytes.cat x y) in
10896 let pubkey =
10997 ( pubkey
···140128 if alg <> "ES256" && alg <> "ES256K" then
141129 Lwt.return_error "only es256 and es256k supported for dpop"
142130 else
143143- let jwk = header |> member "jwk" in
144144- let crv = jwk |> member "crv" |> to_string in
131131+ let jwk =
132132+ header |> member "jwk" |> ec_jwk_of_yojson |> Result.get_ok
133133+ in
145134 if
146135 not
147147- ( match (alg, crv) with
136136+ ( match (alg, jwk.crv) with
148137 | "ES256", "P-256" ->
149138 true
150139 | "ES256K", "secp256k1" ->
···153142 false )
154143 then
155144 Lwt.return_error
156156- (Printf.sprintf "algorithm %s doesn't match curve %s" alg crv)
145145+ (Printf.sprintf "algorithm %s doesn't match curve %s" alg
146146+ jwk.crv )
157147 else
158148 let jti = payload |> member "jti" |> to_string in
159149 let htm = payload |> member "htm" |> to_string in
···175165 then Lwt.return_error "htu mismatch"
176166 else
177167 let now = int_of_float (Unix.gettimeofday ()) in
178178- if now - iat > max_age_s then
168168+ if now - iat > Constants.max_dpop_age_s then
179169 Lwt.return_error "dpop proof too old"
180170 else if iat - now > 5 then
181171 Lwt.return_error "dpop proof in future"