···120120 let exp = now_s + Defaults.service_token_exp in
121121 let payload = service_jwt_to_yojson {iss= did; aud; lxm; exp} in
122122 sign_jwt payload ~signing_key
123123+124124+type verify_jwt_error =
125125+ | AuthRequired of string
126126+ | ExpiredToken of string
127127+ | InvalidToken of string
128128+ | InternalError of string
129129+130130+(* if no did is provided, iss did will be assumed to be correct *)
131131+let verify_service_jwt ~nsid ?did ~(verify_sig : string -> string -> 'a) token =
132132+ match decode_jwt token with
133133+ | Error e ->
134134+ Lwt.return_error @@ AuthRequired e
135135+ | Ok (_header, payload) -> (
136136+ try
137137+ let open Yojson.Safe.Util in
138138+ let iss = payload |> member "iss" |> to_string in
139139+ let aud = payload |> member "aud" |> to_string in
140140+ let exp = payload |> member "exp" |> to_int in
141141+ let lxm = payload |> member "lxm" |> to_string_option in
142142+ let now = int_of_float (Unix.gettimeofday ()) in
143143+ if exp < now then Lwt.return_error @@ ExpiredToken "token expired"
144144+ else if aud <> Env.did then
145145+ Lwt.return_error
146146+ @@ InvalidToken "jwt audience does not match service did"
147147+ else
148148+ let iss_did =
149149+ match String.split_on_char '#' iss with did :: _ -> did | [] -> iss
150150+ in
151151+ if did <> None && Some iss_did <> did then
152152+ Lwt.return_error @@ InvalidToken "jwt issuer does not match did"
153153+ else
154154+ match lxm with
155155+ | Some l when l <> nsid && l <> "*" ->
156156+ Lwt.return_error
157157+ @@ InvalidToken ("jwt lxm " ^ l ^ " does not match " ^ nsid)
158158+ | _ -> (
159159+ let did = Option.value did ~default:iss_did in
160160+ match%lwt Id_resolver.Did.resolve did with
161161+ | Error e ->
162162+ Dream.debug (fun log ->
163163+ log "failed to resolve did %s: %s" did e ) ;
164164+ Lwt.return_error
165165+ @@ InternalError "could not resolve jwt issuer did"
166166+ | Ok did_doc -> (
167167+ match
168168+ Id_resolver.Did.Document.get_verification_key did_doc
169169+ "#atproto"
170170+ with
171171+ | None ->
172172+ Lwt.return_error
173173+ @@ InternalError "missing or bad key in issuer did doc"
174174+ | Some pubkey_multibase -> (
175175+ match%lwt verify_sig did pubkey_multibase with
176176+ | Ok creds ->
177177+ Lwt.return_ok creds
178178+ | Error _ -> (
179179+ (* try again, skipping cache in case of key rotation *)
180180+ match%lwt
181181+ Id_resolver.Did.resolve ~skip_cache:true did
182182+ with
183183+ | Error _ ->
184184+ Lwt.return_error
185185+ @@ InvalidToken
186186+ "jwt signature does not match jwt issuer"
187187+ | Ok fresh_doc -> (
188188+ match
189189+ Id_resolver.Did.Document.get_verification_key fresh_doc
190190+ "#atproto"
191191+ with
192192+ | None ->
193193+ Lwt.return_error
194194+ @@ InvalidToken
195195+ "jwt signature does not match jwt issuer"
196196+ | Some fresh_pubkey_multibase
197197+ when fresh_pubkey_multibase = pubkey_multibase ->
198198+ Lwt.return_error
199199+ @@ InvalidToken
200200+ "jwt signature does not match jwt issuer"
201201+ | Some fresh_pubkey_multibase -> (
202202+ match%lwt verify_sig did fresh_pubkey_multibase with
203203+ | Ok creds ->
204204+ Lwt.return_ok creds
205205+ | Error e ->
206206+ Lwt.return_error @@ InternalError e ) ) ) ) ) )
207207+ with _ -> Lwt.return_error @@ InvalidToken "malformed service jwt" )
+32
pegasus/lib/plc.ml
···165165 let%lwt body_str = Body.to_string body in
166166 Lwt.return_error (Http.Status.to_int res.status, body_str)
167167168168+let validate_operation ~handle ?signing_key (op : signed_operation) =
169169+ let pds_pubkey =
170170+ Env.rotation_key |> Kleidos.derive_pubkey |> Kleidos.pubkey_to_did_key
171171+ in
172172+ match op with
173173+ | Operation op -> (
174174+ if not (List.mem pds_pubkey op.rotation_keys) then
175175+ Error "rotation keys must include the PDS public key"
176176+ else
177177+ match List.assoc_opt "atproto_pds" op.services with
178178+ | Some {type'; endpoint}
179179+ when type' <> "AtprotoPersonalDataServer"
180180+ || endpoint <> Env.host_endpoint ->
181181+ Error "invalid atproto_pds service"
182182+ | _ ->
183183+ let actor_pubkey =
184184+ signing_key
185185+ |> Option.map (fun sk ->
186186+ sk |> Kleidos.parse_multikey_str |> Kleidos.derive_pubkey
187187+ |> Kleidos.pubkey_to_did_key )
188188+ in
189189+ if
190190+ actor_pubkey <> None
191191+ && List.assoc_opt "atproto" op.verification_methods
192192+ <> actor_pubkey
193193+ then Error "incorrect atproto signing key"
194194+ else if List.hd op.also_known_as <> "at://" ^ handle then
195195+ Error "incorrect handle"
196196+ else Ok () )
197197+ | Tombstone _ ->
198198+ Ok ()
199199+168200let did_of_operation operation : string =
169201 let cbor = signed_operation_to_yojson operation |> Dag_cbor.encode_yojson in
170202 let digest = Digestif.SHA256.(cbor |> digest_bytes |> to_raw_string) in
+4-3
pegasus/lib/repository.ml
···146146type t =
147147 { key: Kleidos.key
148148 ; did: string
149149+ ; actor: Data_store.Types.actor
149150 ; db: User_store.t
150151 ; mutable commit: (Cid.t * signed_commit) option }
151152···422423 Errors.invalid_request ~name:"RepoNotFound"
423424 "your princess is in another castle"
424425 in
425425- let%lwt {signing_key; _} =
426426+ let%lwt actor =
426427 match%lwt Data_store.get_actor_by_identifier did ds_conn with
427428 | Some actor when ensure_active = false || actor.deactivated_at = None ->
428429 Lwt.return actor
···432433 | None ->
433434 failwith ("failed to retrieve actor for " ^ did)
434435 in
435435- let key = Kleidos.parse_multikey_str signing_key in
436436+ let key = Kleidos.parse_multikey_str actor.signing_key in
436437 let%lwt commit = User_store.get_commit user_db in
437437- Lwt.return {key; did; db= user_db; commit}
438438+ Lwt.return {key; did; actor; db= user_db; commit}
438439439440let export_car t : Car.stream Lwt.t =
440441 let%lwt root, commit =
+7
pegasus/lib/util.ml
···460460 headers )
461461 (Http.Header.init ()) headers
462462463463+let str_contains ~affix str =
464464+ let re = Str.regexp_string affix in
465465+ try
466466+ ignore (Str.search_forward re str 0) ;
467467+ true
468468+ with Not_found -> false
469469+463470module type Template = sig
464471 type props
465472