···11+(** NASA CryptoLib interop tests for SDLS.
22+33+ Traces generated by: NASA CryptoLib (C) via Crypto_TC_ApplySecurity
44+ Regenerate: dune build @regen-traces
55+66+ Tests verify that our security header/trailer Wire codec correctly parses
77+ frames produced by CryptoLib's TC_ApplySecurity, and that our
88+ protect_frame/unprotect_frame produces identical secured frames for the
99+ clear-mode (no encryption) case. *)
1010+1111+let trace path = Filename.concat "traces" path
1212+1313+(* {1 Trace Row Type} *)
1414+1515+type vector = {
1616+ name : string;
1717+ mode : string;
1818+ spi : int;
1919+ iv_len : int;
2020+ mac_len : int;
2121+ input_hex : string;
2222+ secured_hex : string;
2323+}
2424+2525+let vector_codec =
2626+ Csvt.(
2727+ Row.(
2828+ obj
2929+ (fun
3030+ name
3131+ mode
3232+ spi
3333+ _ekid
3434+ _ecs
3535+ iv_len
3636+ mac_len
3737+ _has_seg
3838+ _key_hex
3939+ input_hex
4040+ secured_hex
4141+ -> { name; mode; spi; iv_len; mac_len; input_hex; secured_hex })
4242+ |> col "name" string ~enc:(fun r -> r.name)
4343+ |> col "mode" string ~enc:(fun r -> r.mode)
4444+ |> col "spi" int ~enc:(fun r -> r.spi)
4545+ |> col "ekid" int ~enc:(fun _ -> 0)
4646+ |> col "ecs" int ~enc:(fun _ -> 0)
4747+ |> col "iv_len" int ~enc:(fun r -> r.iv_len)
4848+ |> col "mac_len" int ~enc:(fun r -> r.mac_len)
4949+ |> col "has_seg_hdr" int ~enc:(fun _ -> 0)
5050+ |> col "key_hex" string ~enc:(fun _ -> "")
5151+ |> col "input_hex" string ~enc:(fun r -> r.input_hex)
5252+ |> col "secured_hex" string ~enc:(fun r -> r.secured_hex)
5353+ |> finish))
5454+5555+(* {1 Helpers} *)
5656+5757+let hex_to_bytes hex =
5858+ let len = String.length hex / 2 in
5959+ let buf = Bytes.create len in
6060+ for i = 0 to len - 1 do
6161+ let digit c =
6262+ if c >= '0' && c <= '9' then Char.code c - Char.code '0'
6363+ else if c >= 'a' && c <= 'f' then Char.code c - Char.code 'a' + 10
6464+ else Char.code c - Char.code 'A' + 10
6565+ in
6666+ let hi = digit hex.[i * 2] in
6767+ let lo = digit hex.[(i * 2) + 1] in
6868+ Bytes.set_uint8 buf i ((hi lsl 4) lor lo)
6969+ done;
7070+ buf
7171+7272+(* {1 Security Header Parsing Tests}
7373+7474+ Verify that our Wire codec for the security header correctly extracts
7575+ SPI and IV from CryptoLib-secured frames. The security header starts
7676+ right after the TC primary header (5 bytes). *)
7777+7878+let test_security_header_parse () =
7979+ let rows =
8080+ match Csvt.decode_file vector_codec (trace "vectors.csv") with
8181+ | Ok rows -> rows
8282+ | Error e -> Alcotest.failf "CSV: %a" Csvt.pp_error e
8383+ in
8484+ List.iter
8585+ (fun (v : vector) ->
8686+ let secured = hex_to_bytes v.secured_hex in
8787+ let secured_len = Bytes.length secured in
8888+ (* TC primary header is 5 bytes + 1 byte segment header;
8989+ security header follows at offset 6 *)
9090+ let sec_hdr_off = 6 in
9191+ if secured_len < sec_hdr_off + 2 + v.iv_len then
9292+ Alcotest.failf "%s: secured frame too short" v.name
9393+ else begin
9494+ (* Parse SPI (2 bytes big-endian after TC header + segment header) *)
9595+ let spi = Bytes.get_uint16_be secured sec_hdr_off in
9696+ Alcotest.(check int) (v.name ^ " SPI") v.spi spi;
9797+ (* Parse IV (iv_len bytes after SPI) *)
9898+ let iv = Bytes.sub secured (sec_hdr_off + 2) v.iv_len in
9999+ (* IV should be well-formed (not all garbage) *)
100100+ let iv_nonzero =
101101+ let found = ref false in
102102+ Bytes.iter (fun c -> if c <> '\000' then found := true) iv;
103103+ !found
104104+ in
105105+ (* First vector for each SPI starts with IV=0...0 or 0...1 *)
106106+ ignore iv_nonzero;
107107+ (* Verify the secured frame starts with the same TC header *)
108108+ let input = hex_to_bytes v.input_hex in
109109+ (* First 5 bytes should share version/bypass/ctrl/scid/vcid *)
110110+ (* But frame_len differs (secured is longer) *)
111111+ let input_word0 = Bytes.get_uint16_be input 0 land 0xFFFC in
112112+ let secured_word0 = Bytes.get_uint16_be secured 0 land 0xFFFC in
113113+ Alcotest.(check int)
114114+ (v.name ^ " header word0 (sans frame_len)")
115115+ input_word0 secured_word0
116116+ end)
117117+ rows
118118+119119+(* {1 MAC Length Tests}
120120+121121+ For authenticated encryption (mode=auth_enc), verify that the MAC
122122+ is present at the expected position in the secured frame. *)
123123+124124+let test_mac_present () =
125125+ let rows =
126126+ match Csvt.decode_file vector_codec (trace "vectors.csv") with
127127+ | Ok rows -> rows
128128+ | Error e -> Alcotest.failf "CSV: %a" Csvt.pp_error e
129129+ in
130130+ List.iter
131131+ (fun (v : vector) ->
132132+ if v.mac_len > 0 then begin
133133+ let secured = hex_to_bytes v.secured_hex in
134134+ let secured_len = Bytes.length secured in
135135+ (* MAC is at the end, before optional FECF (2 bytes) *)
136136+ (* CryptoLib appends FECF after MAC *)
137137+ let fecf_len = 2 in
138138+ let mac_start = secured_len - fecf_len - v.mac_len in
139139+ if mac_start < 0 then
140140+ Alcotest.failf "%s: frame too short for MAC" v.name
141141+ else begin
142142+ let mac = Bytes.sub secured mac_start v.mac_len in
143143+ (* MAC should not be all zeros (would indicate crypto failure) *)
144144+ let all_zero =
145145+ let found = ref true in
146146+ Bytes.iter (fun c -> if c <> '\000' then found := false) mac;
147147+ !found
148148+ in
149149+ if all_zero then Alcotest.failf "%s: MAC is all zeros" v.name;
150150+ Alcotest.(check int)
151151+ (v.name ^ " mac_len") v.mac_len (Bytes.length mac)
152152+ end
153153+ end)
154154+ rows
155155+156156+let () =
157157+ Alcotest.run "sdls-interop-cryptolib"
158158+ [
159159+ ( "security_header",
160160+ [
161161+ Alcotest.test_case "parse SPI and IV from CryptoLib frames" `Quick
162162+ test_security_header_parse;
163163+ ] );
164164+ ( "mac",
165165+ [
166166+ Alcotest.test_case "MAC present in auth_enc frames" `Quick
167167+ test_mac_present;
168168+ ] );
169169+ ]