atproto libraries implementation in ocaml
1(** Identity Verification for AT Protocol.
2
3 This module provides bidirectional verification of identities, ensuring that
4 DIDs and handles are properly linked. Verification confirms that:
5
6 1. A DID document includes the expected handle in alsoKnownAs 2. The handle
7 resolves back to the same DID
8
9 This is crucial for security as it prevents impersonation attacks. *)
10
11open Atproto_syntax
12
13(** {1 Types} *)
14
15type verified_identity = {
16 did : Did.t;
17 handle : Handle.t;
18 signing_key : string option; (** Multibase-encoded public key *)
19 pds_endpoint : Uri.t option;
20}
21(** A fully verified identity *)
22
23type verification_error =
24 | Did_resolution_failed of Did_resolver.error
25 | Handle_resolution_failed of Handle_resolver.error
26 | Handle_mismatch of { expected : Handle.t; found : Handle.t option }
27 | Did_mismatch of { expected : Did.t; found : Did.t }
28 | No_handle_in_document
29 | Invalid_did of string
30 | Invalid_handle of string
31
32let error_to_string = function
33 | Did_resolution_failed e ->
34 Printf.sprintf "DID resolution failed: %s"
35 (Did_resolver.error_to_string e)
36 | Handle_resolution_failed e ->
37 Printf.sprintf "Handle resolution failed: %s"
38 (Handle_resolver.error_to_string e)
39 | Handle_mismatch { expected; found } ->
40 let found_str =
41 match found with None -> "none" | Some h -> Handle.to_string h
42 in
43 Printf.sprintf "Handle mismatch: expected %s, found %s"
44 (Handle.to_string expected)
45 found_str
46 | Did_mismatch { expected; found } ->
47 Printf.sprintf "DID mismatch: expected %s, found %s"
48 (Did.to_string expected) (Did.to_string found)
49 | No_handle_in_document -> "No handle found in DID document"
50 | Invalid_did s -> Printf.sprintf "Invalid DID: %s" s
51 | Invalid_handle s -> Printf.sprintf "Invalid handle: %s" s
52
53(** {1 Verification Functions} *)
54
55(** Verify a DID by: 1. Resolving the DID to get the document 2. Extracting the
56 handle from alsoKnownAs 3. Resolving the handle to verify it points back to
57 the DID *)
58let verify_did did =
59 (* Step 1: Resolve DID *)
60 match Did_resolver.resolve_did did with
61 | Error e -> Error (Did_resolution_failed e)
62 | Ok doc -> (
63 (* Step 2: Extract handle from document *)
64 match Did_resolver.get_handle doc with
65 | None -> Error No_handle_in_document
66 | Some handle -> (
67 (* Step 3: Resolve handle back to DID *)
68 match Handle_resolver.resolve handle with
69 | Error e -> Error (Handle_resolution_failed e)
70 | Ok resolved_did ->
71 (* Step 4: Verify DIDs match *)
72 if Did.equal did resolved_did then
73 Ok
74 {
75 did;
76 handle;
77 signing_key = Did_resolver.get_signing_key doc;
78 pds_endpoint = Did_resolver.get_pds_endpoint doc;
79 }
80 else Error (Did_mismatch { expected = did; found = resolved_did })
81 ))
82
83(** Verify a DID string *)
84let verify_did_string did_str =
85 match Did.of_string did_str with
86 | Error _ -> Error (Invalid_did did_str)
87 | Ok did -> verify_did did
88
89(** Verify a handle by: 1. Resolving the handle to get the DID 2. Resolving the
90 DID to get the document 3. Verifying the handle is in alsoKnownAs *)
91let verify_handle handle =
92 (* Step 1: Resolve handle to DID *)
93 match Handle_resolver.resolve handle with
94 | Error e -> Error (Handle_resolution_failed e)
95 | Ok did -> (
96 (* Step 2: Resolve DID to document *)
97 match Did_resolver.resolve_did did with
98 | Error e -> Error (Did_resolution_failed e)
99 | Ok doc -> (
100 (* Step 3: Verify handle is in document *)
101 match Did_resolver.get_handle doc with
102 | None -> Error No_handle_in_document
103 | Some doc_handle ->
104 if Handle.equal handle doc_handle then
105 Ok
106 {
107 did;
108 handle;
109 signing_key = Did_resolver.get_signing_key doc;
110 pds_endpoint = Did_resolver.get_pds_endpoint doc;
111 }
112 else
113 Error
114 (Handle_mismatch
115 { expected = handle; found = Some doc_handle })))
116
117(** Verify a handle string *)
118let verify_handle_string handle_str =
119 match Handle.of_string handle_str with
120 | Error _ -> Error (Invalid_handle handle_str)
121 | Ok handle -> verify_handle handle
122
123(** Verify that a DID and handle are bidirectionally linked. Both must resolve
124 to each other. *)
125let verify_bidirectional did handle =
126 (* Verify from DID side *)
127 match Did_resolver.resolve_did did with
128 | Error e -> Error (Did_resolution_failed e)
129 | Ok doc -> (
130 (* Check handle is in document *)
131 match Did_resolver.get_handle doc with
132 | None -> Error No_handle_in_document
133 | Some doc_handle -> (
134 if not (Handle.equal handle doc_handle) then
135 Error
136 (Handle_mismatch { expected = handle; found = Some doc_handle })
137 else
138 (* Verify from handle side *)
139 match Handle_resolver.resolve handle with
140 | Error e -> Error (Handle_resolution_failed e)
141 | Ok resolved_did ->
142 if not (Did.equal did resolved_did) then
143 Error (Did_mismatch { expected = did; found = resolved_did })
144 else
145 Ok
146 {
147 did;
148 handle;
149 signing_key = Did_resolver.get_signing_key doc;
150 pds_endpoint = Did_resolver.get_pds_endpoint doc;
151 }))
152
153(** Verify bidirectional link from strings *)
154let verify_bidirectional_strings did_str handle_str =
155 match (Did.of_string did_str, Handle.of_string handle_str) with
156 | Error _, _ -> Error (Invalid_did did_str)
157 | _, Error _ -> Error (Invalid_handle handle_str)
158 | Ok did, Ok handle -> verify_bidirectional did handle
159
160(** {1 Quick Checks} *)
161
162(** Check if a DID has a valid handle (without full verification). Only checks
163 that the handle is present in the document. *)
164let did_has_handle did =
165 match Did_resolver.resolve_did did with
166 | Error _ -> false
167 | Ok doc -> Option.is_some (Did_resolver.get_handle doc)
168
169(** Check if a handle resolves to a valid DID. Does not verify the reverse
170 direction. *)
171let handle_resolves handle =
172 match Handle_resolver.resolve handle with Error _ -> false | Ok _ -> true
173
174(** Get identity info without full verification. Useful for display purposes
175 when verification is not critical. *)
176let get_identity_info did =
177 match Did_resolver.resolve_did did with
178 | Error e -> Error (Did_resolution_failed e)
179 | Ok doc ->
180 Ok
181 {
182 did;
183 handle =
184 (match Did_resolver.get_handle doc with
185 | Some h -> h
186 | None -> Handle.of_string_exn "unknown.invalid");
187 signing_key = Did_resolver.get_signing_key doc;
188 pds_endpoint = Did_resolver.get_pds_endpoint doc;
189 }