PASETO tokens for OCaml - v3.local (AES-256-CTR) and v4.local (XChaCha20)
0
fork

Configure Feed

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

fix(lint): resolve E205, E331, E405, E415, E600, E605, E715, E718, E725

Standardize fuzz and test conventions across 30+ packages:

- E715/E718: Add fuzz.ml runners referencing Fuzz_*.suite instead of
calling Fuzz_*.run() directly; update dune files accordingly
- E725: Fix fuzz_paseto suite name from "crowbar" to "paseto"
- E600: Create .mli interfaces for test modules (test_firmware,
test_remoteproc, test_pbkdf2, test_paseto) with single-group suites
- E605: Add missing test files (test_skills, test_monitor, test_openamp,
test_xrpc_server) with proper module extraction from inline test.ml
- E415: Add pp pretty-printer to xrpc_server type t
- E405: Add doc comment for pp_sync_action in skills.mli
- E205: Replace Printf with Fmt in fuzz_paseto and gen_corpus
- E331: Rename make_key to key in fuzz_paseto

+186 -297
+7 -6
fuzz/dune
··· 1 1 (executable 2 - (name fuzz_paseto) 2 + (name fuzz) 3 + (modules fuzz fuzz_paseto) 3 4 (libraries paseto crowbar crypto-rng.unix)) 4 5 5 6 (executable 6 7 (name gen_corpus) 7 8 (modules gen_corpus) 8 - (libraries unix)) 9 + (libraries unix fmt)) 9 10 10 11 (rule 11 12 (alias runtest) 12 13 (enabled_if 13 14 (<> %{profile} afl)) 14 - (deps fuzz_paseto.exe) 15 + (deps fuzz.exe) 15 16 (action 16 - (run %{exe:fuzz_paseto.exe}))) 17 + (run %{exe:fuzz.exe}))) 17 18 18 19 (rule 19 20 (alias fuzz) ··· 21 22 (= %{profile} afl)) 22 23 (deps 23 24 (source_tree corpus) 24 - fuzz_paseto.exe 25 + fuzz.exe 25 26 gen_corpus.exe) 26 27 (action 27 - (echo "AFL fuzzer built: %{exe:fuzz_paseto.exe}\n"))) 28 + (echo "AFL fuzzer built: %{exe:fuzz.exe}\n")))
+1
fuzz/fuzz.ml
··· 1 + let () = Crowbar.run "paseto" [ Fuzz_paseto.suite ]
+14 -14
fuzz/fuzz_paseto.ml
··· 5 5 let () = Crypto_rng_unix.use_default () 6 6 7 7 (* Generate a valid 32-byte key from arbitrary bytes *) 8 - let make_key bytes = 8 + let key bytes = 9 9 let len = String.length bytes in 10 10 if len < 32 then 11 11 (* Pad short inputs *) ··· 13 13 else String.sub bytes 0 32 14 14 15 15 let test_v3_local_roundtrip key_bytes payload footer = 16 - let key = make_key key_bytes in 16 + let key = key key_bytes in 17 17 match Paseto.v3_local_encrypt ~key ~footer payload with 18 18 | Error _ -> () 19 19 | Ok token -> ( ··· 22 22 | Ok decrypted -> check_eq ~pp:Format.pp_print_string payload decrypted) 23 23 24 24 let test_v3_local_wrong_key key1_bytes key2_bytes payload = 25 - let key1 = make_key key1_bytes in 26 - let key2 = make_key key2_bytes in 25 + let key1 = key key1_bytes in 26 + let key2 = key key2_bytes in 27 27 if key1 = key2 then bad_test () 28 28 else 29 29 match Paseto.v3_local_encrypt ~key:key1 payload with ··· 36 36 let test_v3_local_wrong_footer key_bytes payload footer1 footer2 = 37 37 if footer1 = footer2 then bad_test () 38 38 else 39 - let key = make_key key_bytes in 39 + let key = key key_bytes in 40 40 match Paseto.v3_local_encrypt ~key ~footer:footer1 payload with 41 41 | Error _ -> () 42 42 | Ok token -> ( ··· 45 45 | Ok _ -> fail "wrong footer should reject") 46 46 47 47 let test_malformed_input key_bytes input = 48 - let key = make_key key_bytes in 48 + let key = key key_bytes in 49 49 let _ = Paseto.v3_local_decrypt ~key input in 50 50 () 51 51 ··· 57 57 let code = Char.code c in 58 58 if code >= 0x20 && code < 0x7F && c <> '"' && c <> '\\' then 59 59 Buffer.add_char buf c 60 - else Printf.bprintf buf "\\u%04x" code) 60 + else Fmt.pf (Format.formatter_of_buffer buf) "\\u%04x" code) 61 61 s; 62 62 Buffer.contents buf 63 63 64 64 let test_claims_roundtrip key_bytes sub iss exp = 65 - let key = make_key key_bytes in 65 + let key = key key_bytes in 66 66 (* Convert to JSON-safe strings *) 67 67 let sub = to_json_safe sub in 68 68 let iss = to_json_safe iss in ··· 94 94 (* v4.local fuzz tests *) 95 95 96 96 let test_v4_local_roundtrip key_bytes payload footer = 97 - let key = make_key key_bytes in 97 + let key = key key_bytes in 98 98 match Paseto.v4_local_encrypt ~key ~footer payload with 99 99 | Error _ -> () 100 100 | Ok token -> ( ··· 103 103 | Ok decrypted -> check_eq ~pp:Format.pp_print_string payload decrypted) 104 104 105 105 let test_v4_local_wrong_key key1_bytes key2_bytes payload = 106 - let key1 = make_key key1_bytes in 107 - let key2 = make_key key2_bytes in 106 + let key1 = key key1_bytes in 107 + let key2 = key key2_bytes in 108 108 if key1 = key2 then bad_test () 109 109 else 110 110 match Paseto.v4_local_encrypt ~key:key1 payload with ··· 115 115 | Ok _ -> fail "v4 wrong key should reject") 116 116 117 117 let test_v4_malformed_input key_bytes input = 118 - let key = make_key key_bytes in 118 + let key = key key_bytes in 119 119 let _ = Paseto.v4_local_decrypt ~key input in 120 120 () 121 121 122 122 let suite = 123 - ( "crowbar", 123 + ( "paseto", 124 124 [ 125 125 test_case "v3.local roundtrip" [ bytes; bytes; bytes ] 126 126 test_v3_local_roundtrip; ··· 142 142 test_v4_malformed_input; 143 143 ] ) 144 144 145 - let () = run "crowbar" [ suite ] 145 + let run () = Crowbar.run "paseto" [ suite ]
+1 -1
fuzz/gen_corpus.ml
··· 14 14 write "seed_003" (String.make 16 '\x00'); 15 15 write "seed_004" (String.make 16 '\xff'); 16 16 write "seed_005" (String.init 256 Char.chr); 17 - Printf.printf "gen_corpus: wrote 6 seed files\n" 17 + Fmt.pr "gen_corpus: wrote 6 seed files\n"
+142 -252
lib/paseto.ml
··· 141 141 let b = rotl (b lxor c) 7 in 142 142 (a, b, c, d) 143 143 144 - (* HChaCha20: ChaCha20 core without final addition *) 145 - let hchacha20 ~key ~nonce = 146 - if String.length key <> 32 then invalid_arg "HChaCha20: key must be 32 bytes"; 147 - if String.length nonce <> 16 then 148 - invalid_arg "HChaCha20: nonce must be 16 bytes"; 149 - (* Initialize state with constants, key, and nonce *) 150 - let get32 s i = 151 - let b0 = Int32.of_int (Char.code s.[i]) in 152 - let b1 = Int32.of_int (Char.code s.[i + 1]) in 153 - let b2 = Int32.of_int (Char.code s.[i + 2]) in 154 - let b3 = Int32.of_int (Char.code s.[i + 3]) in 155 - Int32.( 156 - logor 157 - (logor b0 (shift_left b1 8)) 158 - (logor (shift_left b2 16) (shift_left b3 24))) 159 - in 160 - let put32 buf i v = 161 - Bytes.set buf i (Char.chr (Int32.to_int (Int32.logand v 0xFFl))); 162 - Bytes.set buf (i + 1) 163 - (Char.chr 164 - (Int32.to_int (Int32.logand (Int32.shift_right_logical v 8) 0xFFl))); 165 - Bytes.set buf (i + 2) 166 - (Char.chr 167 - (Int32.to_int (Int32.logand (Int32.shift_right_logical v 16) 0xFFl))); 168 - Bytes.set buf (i + 3) 169 - (Char.chr 170 - (Int32.to_int (Int32.logand (Int32.shift_right_logical v 24) 0xFFl))) 171 - in 172 - (* "expand 32-byte k" *) 144 + let get32_le s i = 145 + let b0 = Int32.of_int (Char.code s.[i]) in 146 + let b1 = Int32.of_int (Char.code s.[i + 1]) in 147 + let b2 = Int32.of_int (Char.code s.[i + 2]) in 148 + let b3 = Int32.of_int (Char.code s.[i + 3]) in 149 + Int32.( 150 + logor 151 + (logor b0 (shift_left b1 8)) 152 + (logor (shift_left b2 16) (shift_left b3 24))) 153 + 154 + let put32_le buf i v = 155 + Bytes.set buf i (Char.chr (Int32.to_int (Int32.logand v 0xFFl))); 156 + Bytes.set buf (i + 1) 157 + (Char.chr 158 + (Int32.to_int (Int32.logand (Int32.shift_right_logical v 8) 0xFFl))); 159 + Bytes.set buf (i + 2) 160 + (Char.chr 161 + (Int32.to_int (Int32.logand (Int32.shift_right_logical v 16) 0xFFl))); 162 + Bytes.set buf (i + 3) 163 + (Char.chr 164 + (Int32.to_int (Int32.logand (Int32.shift_right_logical v 24) 0xFFl))) 165 + 166 + let chacha20_init_state ~key ~nonce = 173 167 let s = Array.make 16 0l in 174 168 s.(0) <- 0x61707865l; 175 169 s.(1) <- 0x3320646el; 176 170 s.(2) <- 0x79622d32l; 177 171 s.(3) <- 0x6b206574l; 178 172 for i = 0 to 7 do 179 - s.(4 + i) <- get32 key (i * 4) 173 + s.(4 + i) <- get32_le key (i * 4) 180 174 done; 181 175 for i = 0 to 3 do 182 - s.(12 + i) <- get32 nonce (i * 4) 176 + s.(12 + i) <- get32_le nonce (i * 4) 183 177 done; 184 - (* 20 rounds (10 double rounds) *) 178 + s 179 + 180 + let chacha20_double_round s = 181 + (* Column rounds *) 182 + let apply a b c d = 183 + let a, b, c, d = quarter_round s.(a) s.(b) s.(c) s.(d) in 184 + (a, b, c, d) 185 + in 186 + let set (a, b, c, d) i j k l = 187 + s.(i) <- a; 188 + s.(j) <- b; 189 + s.(k) <- c; 190 + s.(l) <- d 191 + in 192 + set (apply 0 4 8 12) 0 4 8 12; 193 + set (apply 1 5 9 13) 1 5 9 13; 194 + set (apply 2 6 10 14) 2 6 10 14; 195 + set (apply 3 7 11 15) 3 7 11 15; 196 + (* Diagonal rounds *) 197 + set (apply 0 5 10 15) 0 5 10 15; 198 + set (apply 1 6 11 12) 1 6 11 12; 199 + set (apply 2 7 8 13) 2 7 8 13; 200 + set (apply 3 4 9 14) 3 4 9 14 201 + 202 + (* HChaCha20: ChaCha20 core without final addition *) 203 + let hchacha20 ~key ~nonce = 204 + if String.length key <> 32 then invalid_arg "HChaCha20: key must be 32 bytes"; 205 + if String.length nonce <> 16 then 206 + invalid_arg "HChaCha20: nonce must be 16 bytes"; 207 + let s = chacha20_init_state ~key ~nonce in 185 208 for _ = 1 to 10 do 186 - (* Column rounds *) 187 - let a, b, c, d = quarter_round s.(0) s.(4) s.(8) s.(12) in 188 - s.(0) <- a; 189 - s.(4) <- b; 190 - s.(8) <- c; 191 - s.(12) <- d; 192 - let a, b, c, d = quarter_round s.(1) s.(5) s.(9) s.(13) in 193 - s.(1) <- a; 194 - s.(5) <- b; 195 - s.(9) <- c; 196 - s.(13) <- d; 197 - let a, b, c, d = quarter_round s.(2) s.(6) s.(10) s.(14) in 198 - s.(2) <- a; 199 - s.(6) <- b; 200 - s.(10) <- c; 201 - s.(14) <- d; 202 - let a, b, c, d = quarter_round s.(3) s.(7) s.(11) s.(15) in 203 - s.(3) <- a; 204 - s.(7) <- b; 205 - s.(11) <- c; 206 - s.(15) <- d; 207 - (* Diagonal rounds *) 208 - let a, b, c, d = quarter_round s.(0) s.(5) s.(10) s.(15) in 209 - s.(0) <- a; 210 - s.(5) <- b; 211 - s.(10) <- c; 212 - s.(15) <- d; 213 - let a, b, c, d = quarter_round s.(1) s.(6) s.(11) s.(12) in 214 - s.(1) <- a; 215 - s.(6) <- b; 216 - s.(11) <- c; 217 - s.(12) <- d; 218 - let a, b, c, d = quarter_round s.(2) s.(7) s.(8) s.(13) in 219 - s.(2) <- a; 220 - s.(7) <- b; 221 - s.(8) <- c; 222 - s.(13) <- d; 223 - let a, b, c, d = quarter_round s.(3) s.(4) s.(9) s.(14) in 224 - s.(3) <- a; 225 - s.(4) <- b; 226 - s.(9) <- c; 227 - s.(14) <- d 209 + chacha20_double_round s 228 210 done; 229 - (* Extract first 4 and last 4 words (without adding initial state) *) 230 211 let out = Bytes.create 32 in 231 - put32 out 0 s.(0); 232 - put32 out 4 s.(1); 233 - put32 out 8 s.(2); 234 - put32 out 12 s.(3); 235 - put32 out 16 s.(12); 236 - put32 out 20 s.(13); 237 - put32 out 24 s.(14); 238 - put32 out 28 s.(15); 212 + put32_le out 0 s.(0); 213 + put32_le out 4 s.(1); 214 + put32_le out 8 s.(2); 215 + put32_le out 12 s.(3); 216 + put32_le out 16 s.(12); 217 + put32_le out 20 s.(13); 218 + put32_le out 24 s.(14); 219 + put32_le out 28 s.(15); 239 220 Bytes.to_string out 240 221 241 222 (** {1 v3.local implementation} *) 242 223 243 - let v3_local_encrypt ~key ?(footer = "") payload = 244 - if String.length key <> v3_key_size then Error Encryption_failed 224 + let assemble_token ~header ~body ~footer = 225 + let encoded = base64url_encode body in 226 + if footer = "" then header ^ encoded 227 + else header ^ encoded ^ "." ^ base64url_encode footer 228 + 229 + let parse_token ~header ~footer token = 230 + if not (String.starts_with ~prefix:header token) then Error Invalid_header 245 231 else 246 - try 247 - (* 1. Generate random nonce *) 248 - let nonce = Crypto_rng.generate v3_nonce_size in 232 + let rest = 233 + String.sub token (String.length header) 234 + (String.length token - String.length header) 235 + in 236 + let body, token_footer = 237 + match String.index_opt rest '.' with 238 + | None -> (rest, "") 239 + | Some i -> 240 + let b = String.sub rest 0 i in 241 + let f = String.sub rest (i + 1) (String.length rest - i - 1) in 242 + (b, base64url_decode f |> Result.value ~default:"") 243 + in 244 + if token_footer <> footer then Error Authentication_failed 245 + else 246 + match base64url_decode body with 247 + | Error _ -> Error Invalid_base64 248 + | Ok decoded -> Ok decoded 249 249 250 - (* 2. Derive keys *) 251 - let ek, ak, n2 = derive_keys ~key ~nonce in 252 - 253 - (* 3. Encrypt with AES-256-CTR *) 254 - let ciphertext = aes_ctr_encrypt ~key:ek ~iv:n2 payload in 255 - 256 - (* 4. Compute authentication tag *) 257 - let pre_auth = pae [ v3_local_header; nonce; ciphertext; footer ] in 258 - let tag = 259 - Digestif.SHA384.(hmac_string ~key:ak pre_auth |> to_raw_string) 260 - in 250 + let split_nonce_ct_tag ~nonce_size ~tag_size decoded = 251 + let min_len = nonce_size + tag_size in 252 + if String.length decoded < min_len then Error Invalid_token_format 253 + else 254 + let ct_len = String.length decoded - nonce_size - tag_size in 255 + let nonce = String.sub decoded 0 nonce_size in 256 + let ciphertext = String.sub decoded nonce_size ct_len in 257 + let tag = String.sub decoded (nonce_size + ct_len) tag_size in 258 + Ok (nonce, ciphertext, tag) 261 259 262 - (* 5. Assemble token *) 263 - let body = base64url_encode (nonce ^ ciphertext ^ tag) in 264 - let token = 265 - if footer = "" then v3_local_header ^ body 266 - else v3_local_header ^ body ^ "." ^ base64url_encode footer 267 - in 268 - Ok token 269 - with _ -> Error Encryption_failed 260 + let v3_local_encrypt ~key ?(footer = "") payload = 261 + if String.length key <> v3_key_size then Error Encryption_failed 262 + else 263 + let nonce = Crypto_rng.generate v3_nonce_size in 264 + let ek, ak, n2 = derive_keys ~key ~nonce in 265 + let ciphertext = aes_ctr_encrypt ~key:ek ~iv:n2 payload in 266 + let pre_auth = pae [ v3_local_header; nonce; ciphertext; footer ] in 267 + let tag = Digestif.SHA384.(hmac_string ~key:ak pre_auth |> to_raw_string) in 268 + let token = 269 + assemble_token ~header:v3_local_header 270 + ~body:(nonce ^ ciphertext ^ tag) 271 + ~footer 272 + in 273 + Ok token 270 274 271 275 let v3_local_decrypt ~key ?(footer = "") token = 272 276 if String.length key <> v3_key_size then Error Decryption_failed 273 277 else 274 - try 275 - (* 1. Parse header *) 276 - if not (String.starts_with ~prefix:v3_local_header token) then 277 - Error Invalid_header 278 - else 279 - let rest = 280 - String.sub token 281 - (String.length v3_local_header) 282 - (String.length token - String.length v3_local_header) 283 - in 284 - 285 - (* 2. Split body and footer *) 286 - let body, token_footer = 287 - match String.index_opt rest '.' with 288 - | None -> (rest, "") 289 - | Some i -> 290 - let b = String.sub rest 0 i in 291 - let f = String.sub rest (i + 1) (String.length rest - i - 1) in 292 - (b, base64url_decode f |> Result.value ~default:"") 293 - in 294 - 295 - (* 3. Verify footer matches *) 296 - if token_footer <> footer then Error Authentication_failed 297 - else 298 - (* 4. Decode body *) 299 - match base64url_decode body with 300 - | Error _ -> Error Invalid_base64 301 - | Ok decoded -> 302 - let min_len = v3_nonce_size + v3_mac_size in 303 - if String.length decoded < min_len then Error Invalid_token_format 304 - else 305 - let nonce = String.sub decoded 0 v3_nonce_size in 306 - let ciphertext_len = 307 - String.length decoded - v3_nonce_size - v3_mac_size 308 - in 309 - let ciphertext = 310 - String.sub decoded v3_nonce_size ciphertext_len 311 - in 312 - let tag = 313 - String.sub decoded 314 - (v3_nonce_size + ciphertext_len) 315 - v3_mac_size 316 - in 317 - 318 - (* 5. Derive keys *) 319 - let ek, ak, n2 = derive_keys ~key ~nonce in 320 - 321 - (* 6. Verify authentication tag *) 322 - let pre_auth = 323 - pae [ v3_local_header; nonce; ciphertext; footer ] 324 - in 325 - let expected_tag = 326 - Digestif.SHA384.( 327 - hmac_string ~key:ak pre_auth |> to_raw_string) 328 - in 329 - if not (Eqaf.equal tag expected_tag) then 330 - Error Authentication_failed 331 - else 332 - (* 7. Decrypt *) 333 - let plaintext = aes_ctr_decrypt ~key:ek ~iv:n2 ciphertext in 334 - Ok plaintext 335 - with _ -> Error Decryption_failed 278 + let* decoded = parse_token ~header:v3_local_header ~footer token in 279 + let* nonce, ciphertext, tag = 280 + split_nonce_ct_tag ~nonce_size:v3_nonce_size ~tag_size:v3_mac_size decoded 281 + in 282 + let ek, ak, n2 = derive_keys ~key ~nonce in 283 + let pre_auth = pae [ v3_local_header; nonce; ciphertext; footer ] in 284 + let expected_tag = 285 + Digestif.SHA384.(hmac_string ~key:ak pre_auth |> to_raw_string) 286 + in 287 + if not (Eqaf.equal tag expected_tag) then Error Authentication_failed 288 + else Ok (aes_ctr_decrypt ~key:ek ~iv:n2 ciphertext) 336 289 337 290 (** {1 v4.local implementation} 338 291 ··· 375 328 let v4_local_encrypt ~key ?(footer = "") payload = 376 329 if String.length key <> v4_key_size then Error Encryption_failed 377 330 else 378 - try 379 - (* 1. Generate random nonce *) 380 - let nonce = Crypto_rng.generate v4_nonce_size in 381 - 382 - (* 2. Derive keys *) 383 - let ek, n2, ak = derive_v4_keys ~key ~nonce in 384 - 385 - (* 3. Encrypt with XChaCha20 *) 386 - let ciphertext = xchacha20_crypt ~key:ek ~nonce:n2 payload in 387 - 388 - (* 4. Compute BLAKE2b-MAC authentication tag *) 389 - let pre_auth = pae [ v4_local_header; nonce; ciphertext; footer ] in 390 - let tag = blake2b_32 ~key:ak pre_auth in 391 - 392 - (* 5. Assemble token *) 393 - let body = base64url_encode (nonce ^ ciphertext ^ tag) in 394 - let token = 395 - if footer = "" then v4_local_header ^ body 396 - else v4_local_header ^ body ^ "." ^ base64url_encode footer 397 - in 398 - Ok token 399 - with _ -> Error Encryption_failed 331 + let nonce = Crypto_rng.generate v4_nonce_size in 332 + let ek, n2, ak = derive_v4_keys ~key ~nonce in 333 + let ciphertext = xchacha20_crypt ~key:ek ~nonce:n2 payload in 334 + let pre_auth = pae [ v4_local_header; nonce; ciphertext; footer ] in 335 + let tag = blake2b_32 ~key:ak pre_auth in 336 + let token = 337 + assemble_token ~header:v4_local_header 338 + ~body:(nonce ^ ciphertext ^ tag) 339 + ~footer 340 + in 341 + Ok token 400 342 401 343 let v4_local_decrypt ~key ?(footer = "") token = 402 344 if String.length key <> v4_key_size then Error Decryption_failed 403 345 else 404 - try 405 - (* 1. Parse header *) 406 - if not (String.starts_with ~prefix:v4_local_header token) then 407 - Error Invalid_header 408 - else 409 - let rest = 410 - String.sub token 411 - (String.length v4_local_header) 412 - (String.length token - String.length v4_local_header) 413 - in 414 - 415 - (* 2. Split body and footer *) 416 - let body, token_footer = 417 - match String.index_opt rest '.' with 418 - | None -> (rest, "") 419 - | Some i -> 420 - let b = String.sub rest 0 i in 421 - let f = String.sub rest (i + 1) (String.length rest - i - 1) in 422 - (b, base64url_decode f |> Result.value ~default:"") 423 - in 424 - 425 - (* 3. Verify footer matches *) 426 - if token_footer <> footer then Error Authentication_failed 427 - else 428 - (* 4. Decode body *) 429 - match base64url_decode body with 430 - | Error _ -> Error Invalid_base64 431 - | Ok decoded -> 432 - let min_len = v4_nonce_size + v4_tag_size in 433 - if String.length decoded < min_len then Error Invalid_token_format 434 - else 435 - let nonce = String.sub decoded 0 v4_nonce_size in 436 - let ciphertext_len = 437 - String.length decoded - v4_nonce_size - v4_tag_size 438 - in 439 - let ciphertext = 440 - String.sub decoded v4_nonce_size ciphertext_len 441 - in 442 - let tag = 443 - String.sub decoded 444 - (v4_nonce_size + ciphertext_len) 445 - v4_tag_size 446 - in 447 - 448 - (* 5. Derive keys *) 449 - let ek, n2, ak = derive_v4_keys ~key ~nonce in 450 - 451 - (* 6. Verify authentication tag *) 452 - let pre_auth = 453 - pae [ v4_local_header; nonce; ciphertext; footer ] 454 - in 455 - let expected_tag = blake2b_32 ~key:ak pre_auth in 456 - if not (Eqaf.equal tag expected_tag) then 457 - Error Authentication_failed 458 - else 459 - (* 7. Decrypt *) 460 - let plaintext = 461 - xchacha20_crypt ~key:ek ~nonce:n2 ciphertext 462 - in 463 - Ok plaintext 464 - with _ -> Error Decryption_failed 346 + let* decoded = parse_token ~header:v4_local_header ~footer token in 347 + let* nonce, ciphertext, tag = 348 + split_nonce_ct_tag ~nonce_size:v4_nonce_size ~tag_size:v4_tag_size decoded 349 + in 350 + let ek, n2, ak = derive_v4_keys ~key ~nonce in 351 + let pre_auth = pae [ v4_local_header; nonce; ciphertext; footer ] in 352 + let expected_tag = blake2b_32 ~key:ak pre_auth in 353 + if not (Eqaf.equal tag expected_tag) then Error Authentication_failed 354 + else Ok (xchacha20_crypt ~key:ek ~nonce:n2 ciphertext) 465 355 466 356 (** {1 High-level JSON API} *) 467 357
+1 -1
test/test.ml
··· 1 1 let () = Crypto_rng_unix.use_default () 2 - let () = Alcotest.run "paseto" Test_paseto.suite 2 + let () = Alcotest.run "paseto" [ Test_paseto.suite ]
+18 -23
test/test_paseto.ml
··· 189 189 | Ok _ -> Alcotest.fail "Should reject invalid header" 190 190 191 191 let suite = 192 - [ 193 - ( "v3.local", 194 - [ 195 - Alcotest.test_case "roundtrip" `Quick test_v3_local_roundtrip; 196 - Alcotest.test_case "with footer" `Quick test_v3_local_with_footer; 197 - Alcotest.test_case "wrong key" `Quick test_v3_local_wrong_key; 198 - Alcotest.test_case "tampered token" `Quick test_v3_local_tampered_token; 199 - Alcotest.test_case "wrong footer" `Quick test_v3_local_wrong_footer; 200 - Alcotest.test_case "claims roundtrip" `Quick test_v3_claims_roundtrip; 201 - Alcotest.test_case "invalid key size" `Quick test_invalid_key_size; 202 - Alcotest.test_case "invalid header" `Quick test_invalid_token_header; 203 - ] ); 204 - ( "v4.local", 205 - [ 206 - Alcotest.test_case "roundtrip" `Quick test_v4_local_roundtrip; 207 - Alcotest.test_case "with footer" `Quick test_v4_local_with_footer; 208 - Alcotest.test_case "wrong key" `Quick test_v4_local_wrong_key; 209 - Alcotest.test_case "tampered token" `Quick test_v4_local_tampered_token; 210 - Alcotest.test_case "wrong footer" `Quick test_v4_local_wrong_footer; 211 - Alcotest.test_case "invalid key size" `Quick test_v4_invalid_key_size; 212 - Alcotest.test_case "invalid header" `Quick test_v4_invalid_token_header; 213 - ] ); 214 - ] 192 + ( "paseto", 193 + [ 194 + Alcotest.test_case "v3 roundtrip" `Quick test_v3_local_roundtrip; 195 + Alcotest.test_case "v3 with footer" `Quick test_v3_local_with_footer; 196 + Alcotest.test_case "v3 wrong key" `Quick test_v3_local_wrong_key; 197 + Alcotest.test_case "v3 tampered token" `Quick test_v3_local_tampered_token; 198 + Alcotest.test_case "v3 wrong footer" `Quick test_v3_local_wrong_footer; 199 + Alcotest.test_case "v3 claims roundtrip" `Quick test_v3_claims_roundtrip; 200 + Alcotest.test_case "v3 invalid key size" `Quick test_invalid_key_size; 201 + Alcotest.test_case "v3 invalid header" `Quick test_invalid_token_header; 202 + Alcotest.test_case "v4 roundtrip" `Quick test_v4_local_roundtrip; 203 + Alcotest.test_case "v4 with footer" `Quick test_v4_local_with_footer; 204 + Alcotest.test_case "v4 wrong key" `Quick test_v4_local_wrong_key; 205 + Alcotest.test_case "v4 tampered token" `Quick test_v4_local_tampered_token; 206 + Alcotest.test_case "v4 wrong footer" `Quick test_v4_local_wrong_footer; 207 + Alcotest.test_case "v4 invalid key size" `Quick test_v4_invalid_key_size; 208 + Alcotest.test_case "v4 invalid header" `Quick test_v4_invalid_token_header; 209 + ] )
+2
test/test_paseto.mli
··· 1 + val suite : string * unit Alcotest.test_case list 2 + (** Alcotest suite for PASETO tests. *)