···120 let exp = now_s + Defaults.service_token_exp in
121 let payload = service_jwt_to_yojson {iss= did; aud; lxm; exp} in
122 sign_jwt payload ~signing_key
0000000000000000000000000000000000000000000000000000000000000000000000000000000000000
···120 let exp = now_s + Defaults.service_token_exp in
121 let payload = service_jwt_to_yojson {iss= did; aud; lxm; exp} in
122 sign_jwt payload ~signing_key
123+124+type verify_jwt_error =
125+ | AuthRequired of string
126+ | ExpiredToken of string
127+ | InvalidToken of string
128+ | InternalError of string
129+130+(* if no did is provided, iss did will be assumed to be correct *)
131+let verify_service_jwt ~nsid ?did ~(verify_sig : string -> string -> 'a) token =
132+ match decode_jwt token with
133+ | Error e ->
134+ Lwt.return_error @@ AuthRequired e
135+ | Ok (_header, payload) -> (
136+ try
137+ let open Yojson.Safe.Util in
138+ let iss = payload |> member "iss" |> to_string in
139+ let aud = payload |> member "aud" |> to_string in
140+ let exp = payload |> member "exp" |> to_int in
141+ let lxm = payload |> member "lxm" |> to_string_option in
142+ let now = int_of_float (Unix.gettimeofday ()) in
143+ if exp < now then Lwt.return_error @@ ExpiredToken "token expired"
144+ else if aud <> Env.did then
145+ Lwt.return_error
146+ @@ InvalidToken "jwt audience does not match service did"
147+ else
148+ let iss_did =
149+ match String.split_on_char '#' iss with did :: _ -> did | [] -> iss
150+ in
151+ if did <> None && Some iss_did <> did then
152+ Lwt.return_error @@ InvalidToken "jwt issuer does not match did"
153+ else
154+ match lxm with
155+ | Some l when l <> nsid && l <> "*" ->
156+ Lwt.return_error
157+ @@ InvalidToken ("jwt lxm " ^ l ^ " does not match " ^ nsid)
158+ | _ -> (
159+ let did = Option.value did ~default:iss_did in
160+ match%lwt Id_resolver.Did.resolve did with
161+ | Error e ->
162+ Dream.debug (fun log ->
163+ log "failed to resolve did %s: %s" did e ) ;
164+ Lwt.return_error
165+ @@ InternalError "could not resolve jwt issuer did"
166+ | Ok did_doc -> (
167+ match
168+ Id_resolver.Did.Document.get_verification_key did_doc
169+ "#atproto"
170+ with
171+ | None ->
172+ Lwt.return_error
173+ @@ InternalError "missing or bad key in issuer did doc"
174+ | Some pubkey_multibase -> (
175+ match%lwt verify_sig did pubkey_multibase with
176+ | Ok creds ->
177+ Lwt.return_ok creds
178+ | Error _ -> (
179+ (* try again, skipping cache in case of key rotation *)
180+ match%lwt
181+ Id_resolver.Did.resolve ~skip_cache:true did
182+ with
183+ | Error _ ->
184+ Lwt.return_error
185+ @@ InvalidToken
186+ "jwt signature does not match jwt issuer"
187+ | Ok fresh_doc -> (
188+ match
189+ Id_resolver.Did.Document.get_verification_key fresh_doc
190+ "#atproto"
191+ with
192+ | None ->
193+ Lwt.return_error
194+ @@ InvalidToken
195+ "jwt signature does not match jwt issuer"
196+ | Some fresh_pubkey_multibase
197+ when fresh_pubkey_multibase = pubkey_multibase ->
198+ Lwt.return_error
199+ @@ InvalidToken
200+ "jwt signature does not match jwt issuer"
201+ | Some fresh_pubkey_multibase -> (
202+ match%lwt verify_sig did fresh_pubkey_multibase with
203+ | Ok creds ->
204+ Lwt.return_ok creds
205+ | Error e ->
206+ Lwt.return_error @@ InternalError e ) ) ) ) ) )
207+ with _ -> Lwt.return_error @@ InvalidToken "malformed service jwt" )
+32
pegasus/lib/plc.ml
···165 let%lwt body_str = Body.to_string body in
166 Lwt.return_error (Http.Status.to_int res.status, body_str)
16700000000000000000000000000000000168let did_of_operation operation : string =
169 let cbor = signed_operation_to_yojson operation |> Dag_cbor.encode_yojson in
170 let digest = Digestif.SHA256.(cbor |> digest_bytes |> to_raw_string) in
···165 let%lwt body_str = Body.to_string body in
166 Lwt.return_error (Http.Status.to_int res.status, body_str)
167168+let validate_operation ~handle ?signing_key (op : signed_operation) =
169+ let pds_pubkey =
170+ Env.rotation_key |> Kleidos.derive_pubkey |> Kleidos.pubkey_to_did_key
171+ in
172+ match op with
173+ | Operation op -> (
174+ if not (List.mem pds_pubkey op.rotation_keys) then
175+ Error "rotation keys must include the PDS public key"
176+ else
177+ match List.assoc_opt "atproto_pds" op.services with
178+ | Some {type'; endpoint}
179+ when type' <> "AtprotoPersonalDataServer"
180+ || endpoint <> Env.host_endpoint ->
181+ Error "invalid atproto_pds service"
182+ | _ ->
183+ let actor_pubkey =
184+ signing_key
185+ |> Option.map (fun sk ->
186+ sk |> Kleidos.parse_multikey_str |> Kleidos.derive_pubkey
187+ |> Kleidos.pubkey_to_did_key )
188+ in
189+ if
190+ actor_pubkey <> None
191+ && List.assoc_opt "atproto" op.verification_methods
192+ <> actor_pubkey
193+ then Error "incorrect atproto signing key"
194+ else if List.hd op.also_known_as <> "at://" ^ handle then
195+ Error "incorrect handle"
196+ else Ok () )
197+ | Tombstone _ ->
198+ Ok ()
199+200let did_of_operation operation : string =
201 let cbor = signed_operation_to_yojson operation |> Dag_cbor.encode_yojson in
202 let digest = Digestif.SHA256.(cbor |> digest_bytes |> to_raw_string) in
+4-3
pegasus/lib/repository.ml
···146type t =
147 { key: Kleidos.key
148 ; did: string
0149 ; db: User_store.t
150 ; mutable commit: (Cid.t * signed_commit) option }
151···422 Errors.invalid_request ~name:"RepoNotFound"
423 "your princess is in another castle"
424 in
425- let%lwt {signing_key; _} =
426 match%lwt Data_store.get_actor_by_identifier did ds_conn with
427 | Some actor when ensure_active = false || actor.deactivated_at = None ->
428 Lwt.return actor
···432 | None ->
433 failwith ("failed to retrieve actor for " ^ did)
434 in
435- let key = Kleidos.parse_multikey_str signing_key in
436 let%lwt commit = User_store.get_commit user_db in
437- Lwt.return {key; did; db= user_db; commit}
438439let export_car t : Car.stream Lwt.t =
440 let%lwt root, commit =
···146type t =
147 { key: Kleidos.key
148 ; did: string
149+ ; actor: Data_store.Types.actor
150 ; db: User_store.t
151 ; mutable commit: (Cid.t * signed_commit) option }
152···423 Errors.invalid_request ~name:"RepoNotFound"
424 "your princess is in another castle"
425 in
426+ let%lwt actor =
427 match%lwt Data_store.get_actor_by_identifier did ds_conn with
428 | Some actor when ensure_active = false || actor.deactivated_at = None ->
429 Lwt.return actor
···433 | None ->
434 failwith ("failed to retrieve actor for " ^ did)
435 in
436+ let key = Kleidos.parse_multikey_str actor.signing_key in
437 let%lwt commit = User_store.get_commit user_db in
438+ Lwt.return {key; did; actor; db= user_db; commit}
439440let export_car t : Car.stream Lwt.t =
441 let%lwt root, commit =
+7
pegasus/lib/util.ml
···460 headers )
461 (Http.Header.init ()) headers
4620000000463module type Template = sig
464 type props
465
···460 headers )
461 (Http.Header.init ()) headers
462463+let str_contains ~affix str =
464+ let re = Str.regexp_string affix in
465+ try
466+ ignore (Str.search_forward re str 0) ;
467+ true
468+ with Not_found -> false
469+470module type Template = sig
471 type props
472