objective categorical abstract machine language personal data server
65
fork

Configure Feed

Select the types of activity you want to include in your feed.

Fix dpop nonce rotation

futurGH 7a7e753c 4c76d5e1

+44 -48
-1
pegasus/lib/api/oauth_/token.ml
··· 128 128 Dream.json 129 129 ~headers: 130 130 [ ("DPoP-Nonce", nonce) 131 - ; ("Access-Control-Expose-Headers", "DPoP-Nonce") 132 131 ; ("Cache-Control", "no-store") ] 133 132 @@ Yojson.Safe.to_string 134 133 @@ `Assoc
+3 -3
pegasus/lib/oauth/constants.ml
··· 1 - let max_dpop_age_s = 60 1 + let max_dpop_age_s = 180 2 2 3 - let dpop_rotation_interval_ms = 60_000L 3 + let dpop_rotation_interval_ms = 60_000 4 4 5 - let jti_ttl_s = 3600 5 + let jti_ttl_s = 24 * 60 * 60 6 6 7 7 let jti_cache_size = 10_000 8 8
+23 -30
pegasus/lib/oauth/dpop.ml
··· 1 1 type nonce_state = 2 2 { secret: bytes 3 - ; mutable counter: int64 3 + ; mutable counter: int 4 4 ; mutable prev: string 5 5 ; mutable curr: string 6 - ; mutable next: string 7 - ; rotation_interval_ms: int64 } 6 + ; mutable next: string } 8 7 9 8 type ec_jwk = {crv: string; kty: string; x: string; y: string} 10 9 [@@deriving yojson {strict= false}] ··· 16 15 Hashtbl.create Constants.jti_cache_size 17 16 18 17 let cleanup_jti_cache () = 19 - let now = int_of_float (Unix.gettimeofday ()) in 18 + let now = Util.now_ms () in 20 19 Hashtbl.filter_map_inplace 21 20 (fun _ expires_at -> if expires_at > now then Some expires_at else None) 22 21 jti_cache 23 22 24 23 let compute_nonce secret counter = 25 24 let data = Bytes.create 8 in 26 - Bytes.set_int64_be data 0 counter ; 25 + Bytes.set_int64_be data 0 (Int64.of_int counter) ; 27 26 Digestif.SHA256.( 28 27 hmac_bytes ~key:(Bytes.to_string secret) data 29 28 |> to_raw_string |> Jwt.b64_encode ) 30 29 31 30 let create_nonce_state secret = 32 - let counter = 33 - Int64.div 34 - (Int64.of_float (Unix.gettimeofday () *. 1000.)) 35 - Constants.dpop_rotation_interval_ms 36 - in 31 + let counter = Util.now_ms () / Constants.dpop_rotation_interval_ms in 37 32 { secret 38 33 ; counter 39 - ; prev= compute_nonce secret (Int64.pred counter) 34 + ; prev= compute_nonce secret (pred counter) 40 35 ; curr= compute_nonce secret counter 41 - ; next= compute_nonce secret (Int64.succ counter) 42 - ; rotation_interval_ms= Constants.dpop_rotation_interval_ms } 36 + ; next= compute_nonce secret (succ counter) } 43 37 44 38 let nonce_state = ref (create_nonce_state Env.dpop_nonce_secret) 45 39 46 40 let next_nonce () = 47 - let now_counter = 48 - Int64.div 49 - (Int64.of_float (Unix.gettimeofday () *. 1000.)) 50 - !nonce_state.rotation_interval_ms 51 - in 52 - let diff = Int64.sub now_counter !nonce_state.counter in 41 + let now_counter = Util.now_ms () / Constants.dpop_rotation_interval_ms in 42 + let diff = now_counter - !nonce_state.counter in 53 43 ( match diff with 54 - | 0L -> 44 + | 0 -> 55 45 () 56 - | 1L -> 46 + | 1 -> 57 47 !nonce_state.prev <- !nonce_state.curr ; 58 48 !nonce_state.curr <- !nonce_state.next ; 59 - !nonce_state.next <- 60 - compute_nonce !nonce_state.secret (Int64.succ now_counter) 61 - | 2L -> 49 + !nonce_state.next <- compute_nonce !nonce_state.secret (succ now_counter) 50 + | 2 -> 62 51 !nonce_state.prev <- !nonce_state.next ; 63 52 !nonce_state.curr <- compute_nonce !nonce_state.secret now_counter ; 64 - !nonce_state.next <- 65 - compute_nonce !nonce_state.secret (Int64.succ now_counter) 53 + !nonce_state.next <- compute_nonce !nonce_state.secret (succ now_counter) 66 54 | _ -> 67 - !nonce_state.prev <- 68 - compute_nonce !nonce_state.secret (Int64.pred now_counter) ; 55 + !nonce_state.prev <- compute_nonce !nonce_state.secret (pred now_counter) ; 69 56 !nonce_state.curr <- compute_nonce !nonce_state.secret now_counter ; 70 - !nonce_state.next <- 71 - compute_nonce !nonce_state.secret (Int64.succ now_counter) ) ; 57 + !nonce_state.next <- compute_nonce !nonce_state.secret (succ now_counter) 58 + ) ; 72 59 !nonce_state.counter <- now_counter ; 73 60 !nonce_state.next 74 61 ··· 189 176 | None -> 190 177 Error "use_dpop_nonce" 191 178 | Some n when not (verify_nonce n) -> 179 + Log.debug (fun log -> 180 + let state = !nonce_state in 181 + log 182 + "given nonce %s, failed to match any of: %s, %s, \ 183 + %s" 184 + n state.prev state.curr state.next ) ; 192 185 Error "use_dpop_nonce" 193 186 | Some _ -> ( 194 187 if htm <> mthd then Error "htm mismatch"
+18 -14
pegasus/lib/xrpc.ml
··· 99 99 let extract_nsid req = (Dream.path [@warning "-3"]) req |> List.rev |> List.hd 100 100 101 101 let add_dpop_nonce_if_needed res = 102 - let nonce = Oauth.Dpop.next_nonce () in 103 - Dream.set_header res "DPoP-Nonce" nonce ; 104 - let expose_header = Dream.header res "Access-Control-Expose-Headers" in 105 - Dream.add_header res "Access-Control-Expose-Headers" 106 - ( match expose_header with 107 - | Some headers when not @@ Util.str_contains ~affix:"DPoP-Nonce" headers -> 108 - headers ^ ", DPoP-Nonce" 102 + let () = 103 + match Dream.header res "DPoP-Nonce" with 104 + | Some _ -> 105 + () 106 + | None -> 107 + Dream.set_header res "DPoP-Nonce" (Oauth.Dpop.next_nonce ()) 108 + in 109 + let () = 110 + match Dream.header res "Access-Control-Expose-Headers" with 111 + | Some header when Util.str_contains ~affix:"DPoP-Nonce" header -> 112 + () 113 + | Some header -> 114 + Dream.set_header res "Access-Control-Expose-Headers" 115 + (header ^ ", DPoP-Nonce") 109 116 | _ -> 110 - "DPoP-Nonce" ) ; 117 + Dream.set_header res "Access-Control-Expose-Headers" "DPoP-Nonce" 118 + in 111 119 res 112 120 113 121 let handler ?(auth : Auth.Verifiers.t = Any) ··· 337 345 Option.is_some dpop 338 346 || Option.is_some www_auth 339 347 && Option.get www_auth |> Util.str_contains ~affix:"DPoP" 340 - then begin 341 - Dream.set_header res "DPoP-Nonce" (Oauth.Dpop.next_nonce ()) ; 342 - Dream.add_header res "Access-Control-Expose-Headers" 343 - "DPoP-Nonce, WWW-Authenticate" 344 - end ; 345 - Lwt.return res 348 + then Lwt.return @@ add_dpop_nonce_if_needed res 349 + else Lwt.return res 346 350 347 351 let cors_middleware inner_handler req = 348 352 let%lwt res = inner_handler req in