CCSDS Space Data Link Security (355.0-B-2)
0
fork

Configure Feed

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

irmin: standalone Proof module with PCC-inspired documentation

+1499
+1
test/interop/cryptolib/.gitignore
··· 1 + scripts/generate
+19
test/interop/cryptolib/dune
··· 1 + (test 2 + (name test) 3 + (libraries sdls alcotest) 4 + (deps 5 + (source_tree traces) 6 + (source_tree scripts))) 7 + 8 + ; Regenerate traces: REGEN_TRACES=1 dune build @regen-traces 9 + 10 + (rule 11 + (alias regen-traces) 12 + (enabled_if 13 + (= %{env:REGEN_TRACES=0} 1)) 14 + (deps 15 + (source_tree scripts)) 16 + (action 17 + (chdir 18 + scripts 19 + (run bash ./generate.sh))))
+217
test/interop/cryptolib/scripts/generate.c
··· 1 + /* Generate SDLS EP PDU header and MC status reply test vectors using NASA 2 + CryptoLib as the oracle. 3 + 4 + CryptoLib encodes SDLS TLV headers per CCSDS 355.0-B-2 Section 4.5: 5 + 6 + Byte 0: type(1) | uf(1) | sg(2) | pid(4) 7 + Byte 1: pdu_len[15:8] 8 + Byte 2: pdu_len[7:0] 9 + 10 + This program exercises CryptoLib's encoding by populating SDLS_TLV_Hdr_t 11 + and packing it exactly as Crypto_Prep_Reply does, then writing CSV traces 12 + that the OCaml interop test reads. */ 13 + 14 + #include <stdint.h> 15 + #include <stdio.h> 16 + #include <stdlib.h> 17 + #include <string.h> 18 + 19 + #include "crypto_config.h" 20 + #include "crypto_structs.h" 21 + 22 + /* Encode a TLV header the same way CryptoLib's Crypto_Prep_Reply does. */ 23 + static void encode_tlv(uint8_t type, uint8_t uf, uint8_t sg, uint8_t pid, 24 + uint16_t pdu_len, uint8_t out[3]) 25 + { 26 + out[0] = (uint8_t)((type << 7) | (uf << 6) | (sg << 4) | (pid & 0x0F)); 27 + out[1] = (uint8_t)((pdu_len >> 8) & 0xFF); 28 + out[2] = (uint8_t)(pdu_len & 0xFF); 29 + } 30 + 31 + /* Parse a TLV header the same way CryptoLib's 32 + Crypto_Process_Extended_Procedure_Pdu does. */ 33 + static void decode_tlv(const uint8_t in[3], uint8_t *type, uint8_t *uf, 34 + uint8_t *sg, uint8_t *pid, uint16_t *pdu_len) 35 + { 36 + *type = (in[0] & 0x80) >> 7; 37 + *uf = (in[0] & 0x40) >> 6; 38 + *sg = (in[0] & 0x30) >> 4; 39 + *pid = (in[0] & 0x0F); 40 + *pdu_len = ((uint16_t)in[1] << 8) | (uint16_t)in[2]; 41 + } 42 + 43 + static void hex_of_bytes(const uint8_t *data, int len, char *out) 44 + { 45 + for (int i = 0; i < len; i++) 46 + sprintf(out + i * 2, "%02x", data[i]); 47 + out[len * 2] = '\0'; 48 + } 49 + 50 + /* EP header test vector */ 51 + typedef struct { 52 + const char *name; 53 + uint8_t type; 54 + uint8_t uf; 55 + uint8_t sg; 56 + uint8_t pid; 57 + uint16_t pdu_len; 58 + } ep_hdr_vec_t; 59 + 60 + /* MC status reply: CryptoLib's mc_if->mc_status populates sdls_ep_reply 61 + with (among other fields) an operational flag, key count, and SA count. 62 + The MC status reply data is 5 bytes: 63 + Byte 0: operational flag (0x80 if true, 0x00 if false) 64 + Bytes 1-2: key_count (big-endian 16-bit) 65 + Bytes 3-4: sa_count (big-endian 16-bit) */ 66 + typedef struct { 67 + const char *name; 68 + uint8_t operational; 69 + uint16_t key_count; 70 + uint16_t sa_count; 71 + } mc_status_vec_t; 72 + 73 + /* MC status reply encoding: operational is a boolean byte (0x01=true, 0x00=false) 74 + followed by key_count and sa_count as big-endian 16-bit values. */ 75 + static void encode_mc_status(uint8_t operational, uint16_t key_count, 76 + uint16_t sa_count, uint8_t out[5]) 77 + { 78 + out[0] = operational ? 0x01 : 0x00; 79 + out[1] = (uint8_t)((key_count >> 8) & 0xFF); 80 + out[2] = (uint8_t)(key_count & 0xFF); 81 + out[3] = (uint8_t)((sa_count >> 8) & 0xFF); 82 + out[4] = (uint8_t)(sa_count & 0xFF); 83 + } 84 + 85 + int main(int argc, char *argv[]) 86 + { 87 + if (argc != 2) { 88 + fprintf(stderr, "Usage: %s <trace_dir>\n", argv[0]); 89 + return 1; 90 + } 91 + const char *trace_dir = argv[1]; 92 + char path[512]; 93 + FILE *f; 94 + 95 + /* ---- EP Header vectors ---- */ 96 + ep_hdr_vec_t ep_vecs[] = { 97 + /* Key management commands (SG=0) */ 98 + {"otar_cmd", PDU_TYPE_COMMAND, PDU_USER_FLAG_FALSE, SG_KEY_MGMT, PID_OTAR, 64}, 99 + {"key_activate_cmd", PDU_TYPE_COMMAND, PDU_USER_FLAG_FALSE, SG_KEY_MGMT, PID_KEY_ACTIVATION, 4}, 100 + {"key_deactivate_cmd", PDU_TYPE_COMMAND, PDU_USER_FLAG_FALSE, SG_KEY_MGMT, PID_KEY_DEACTIVATION, 4}, 101 + {"key_verify_cmd", PDU_TYPE_COMMAND, PDU_USER_FLAG_FALSE, SG_KEY_MGMT, PID_KEY_VERIFICATION, 32}, 102 + {"key_destroy_cmd", PDU_TYPE_COMMAND, PDU_USER_FLAG_FALSE, SG_KEY_MGMT, PID_KEY_DESTRUCTION, 4}, 103 + {"key_inventory_cmd", PDU_TYPE_COMMAND, PDU_USER_FLAG_FALSE, SG_KEY_MGMT, PID_KEY_INVENTORY, 4}, 104 + 105 + /* Key management replies (SG=0, type=reply) */ 106 + {"key_verify_reply", PDU_TYPE_REPLY, PDU_USER_FLAG_FALSE, SG_KEY_MGMT, PID_KEY_VERIFICATION, 128}, 107 + {"key_inventory_reply", PDU_TYPE_REPLY, PDU_USER_FLAG_FALSE, SG_KEY_MGMT, PID_KEY_INVENTORY, 96}, 108 + 109 + /* SA management commands (SG=1, Initiator->Recipient) */ 110 + {"sa_create_cmd", PDU_TYPE_COMMAND, PDU_USER_FLAG_FALSE, SG_SA_MGMT, PID_CREATE_SA, 20}, 111 + {"sa_delete_cmd", PDU_TYPE_COMMAND, PDU_USER_FLAG_FALSE, SG_SA_MGMT, PID_DELETE_SA, 2}, 112 + {"sa_set_arsnw_cmd", PDU_TYPE_COMMAND, PDU_USER_FLAG_FALSE, SG_SA_MGMT, PID_SET_ARSNW, 4}, 113 + {"sa_rekey_cmd", PDU_TYPE_COMMAND, PDU_USER_FLAG_FALSE, SG_SA_MGMT, PID_REKEY_SA, 22}, 114 + {"sa_expire_cmd", PDU_TYPE_COMMAND, PDU_USER_FLAG_FALSE, SG_SA_MGMT, PID_EXPIRE_SA, 2}, 115 + {"sa_set_arsn_cmd", PDU_TYPE_COMMAND, PDU_USER_FLAG_FALSE, SG_SA_MGMT, PID_SET_ARSN, 2}, 116 + {"sa_start_cmd", PDU_TYPE_COMMAND, PDU_USER_FLAG_FALSE, SG_SA_MGMT, PID_START_SA, 6}, 117 + {"sa_stop_cmd", PDU_TYPE_COMMAND, PDU_USER_FLAG_FALSE, SG_SA_MGMT, PID_STOP_SA, 2}, 118 + {"sa_status_cmd", PDU_TYPE_COMMAND, PDU_USER_FLAG_FALSE, SG_SA_MGMT, PID_SA_STATUS, 2}, 119 + {"sa_read_arsn_cmd", PDU_TYPE_COMMAND, PDU_USER_FLAG_FALSE, SG_SA_MGMT, PID_READ_ARSN, 2}, 120 + 121 + /* SA management replies (SG=2, Recipient->Initiator) */ 122 + {"sa_create_reply", PDU_TYPE_REPLY, PDU_USER_FLAG_FALSE, 0x02, PID_CREATE_SA, 0}, 123 + {"sa_status_reply", PDU_TYPE_REPLY, PDU_USER_FLAG_FALSE, 0x02, PID_SA_STATUS, 3}, 124 + {"sa_read_arsn_reply", PDU_TYPE_REPLY, PDU_USER_FLAG_FALSE, 0x02, PID_READ_ARSN, 20}, 125 + 126 + /* Monitoring & control commands (SG=3) */ 127 + {"mc_ping_cmd", PDU_TYPE_COMMAND, PDU_USER_FLAG_FALSE, SG_SEC_MON_CTRL, PID_PING, 0}, 128 + {"mc_log_status_cmd", PDU_TYPE_COMMAND, PDU_USER_FLAG_FALSE, SG_SEC_MON_CTRL, PID_LOG_STATUS, 0}, 129 + {"mc_dump_log_cmd", PDU_TYPE_COMMAND, PDU_USER_FLAG_FALSE, SG_SEC_MON_CTRL, PID_DUMP_LOG, 0}, 130 + {"mc_erase_log_cmd", PDU_TYPE_COMMAND, PDU_USER_FLAG_FALSE, SG_SEC_MON_CTRL, PID_ERASE_LOG, 0}, 131 + {"mc_self_test_cmd", PDU_TYPE_COMMAND, PDU_USER_FLAG_FALSE, SG_SEC_MON_CTRL, PID_SELF_TEST, 0}, 132 + {"mc_alarm_reset_cmd", PDU_TYPE_COMMAND, PDU_USER_FLAG_FALSE, SG_SEC_MON_CTRL, PID_ALARM_FLAG, 0}, 133 + 134 + /* Monitoring & control replies (SG=3, type=reply) */ 135 + {"mc_ping_reply", PDU_TYPE_REPLY, PDU_USER_FLAG_FALSE, SG_SEC_MON_CTRL, PID_PING, 5}, 136 + {"mc_log_status_reply", PDU_TYPE_REPLY, PDU_USER_FLAG_FALSE, SG_SEC_MON_CTRL, PID_LOG_STATUS, 4}, 137 + {"mc_self_test_reply", PDU_TYPE_REPLY, PDU_USER_FLAG_FALSE, SG_SEC_MON_CTRL, PID_SELF_TEST, 1}, 138 + {"mc_alarm_reset_reply", PDU_TYPE_REPLY, PDU_USER_FLAG_FALSE, SG_SEC_MON_CTRL, PID_ALARM_FLAG, 0}, 139 + 140 + /* User-flag variants */ 141 + {"user_flag_cmd", PDU_TYPE_COMMAND, PDU_USER_FLAG_TRUE, SG_SA_MGMT, PID_CREATE_SA, 10}, 142 + {"user_flag_reply", PDU_TYPE_REPLY, PDU_USER_FLAG_TRUE, SG_SEC_MON_CTRL, PID_LOG_STATUS, 4}, 143 + 144 + /* Edge cases */ 145 + {"zero_pdu_len", PDU_TYPE_COMMAND, PDU_USER_FLAG_FALSE, SG_KEY_MGMT, PID_OTAR, 0}, 146 + {"max_pdu_len", PDU_TYPE_COMMAND, PDU_USER_FLAG_FALSE, SG_KEY_MGMT, PID_KEY_INVENTORY, 65535}, 147 + {"all_bits_set", PDU_TYPE_REPLY, PDU_USER_FLAG_TRUE, SG_SEC_MON_CTRL, PID_ALARM_FLAG, 65535}, 148 + }; 149 + int n_ep = sizeof(ep_vecs) / sizeof(ep_vecs[0]); 150 + 151 + snprintf(path, sizeof(path), "%s/ep_hdr.csv", trace_dir); 152 + f = fopen(path, "w"); 153 + if (!f) { perror("fopen ep_hdr.csv"); return 1; } 154 + fprintf(f, "# SDLS EP PDU Header interop test vectors\n"); 155 + fprintf(f, "# Oracle: NASA CryptoLib %d.%d.%d\n", 156 + CRYPTO_LIB_MAJOR_VERSION, CRYPTO_LIB_MINOR_VERSION, CRYPTO_LIB_REVISION); 157 + fprintf(f, "# Format: name,is_reply,user_flag,sg,pid,pdu_len,encoded_hex\n"); 158 + 159 + for (int i = 0; i < n_ep; i++) { 160 + ep_hdr_vec_t *v = &ep_vecs[i]; 161 + uint8_t encoded[3]; 162 + encode_tlv(v->type, v->uf, v->sg, v->pid, v->pdu_len, encoded); 163 + 164 + /* Verify CryptoLib can parse it back (sanity check) */ 165 + uint8_t d_type, d_uf, d_sg, d_pid; 166 + uint16_t d_pdu_len; 167 + decode_tlv(encoded, &d_type, &d_uf, &d_sg, &d_pid, &d_pdu_len); 168 + if (d_type != v->type || d_uf != v->uf || d_sg != v->sg || 169 + d_pid != v->pid || d_pdu_len != v->pdu_len) { 170 + fprintf(stderr, "ERROR: roundtrip mismatch for %s\n", v->name); 171 + fclose(f); 172 + return 1; 173 + } 174 + 175 + char hex[7]; 176 + hex_of_bytes(encoded, 3, hex); 177 + fprintf(f, "%s,%d,%d,%d,%d,%d,%s\n", 178 + v->name, v->type, v->uf, v->sg, v->pid, v->pdu_len, hex); 179 + } 180 + fclose(f); 181 + printf("Wrote %s (%d vectors)\n", path, n_ep); 182 + 183 + /* ---- MC Status Reply vectors ---- */ 184 + mc_status_vec_t mc_vecs[] = { 185 + {"mc_idle", 0, 0, 0}, 186 + {"mc_operational", 1, 5, 3}, 187 + {"mc_max_counts", 1, 65535, 65535}, 188 + {"mc_not_op_with_keys", 0, 10, 0}, 189 + {"mc_single_sa", 1, 1, 1}, 190 + {"mc_many_keys", 1, 256, 128}, 191 + {"mc_zero_keys_some_sas", 1, 0, 42}, 192 + }; 193 + int n_mc = sizeof(mc_vecs) / sizeof(mc_vecs[0]); 194 + 195 + snprintf(path, sizeof(path), "%s/mc_status.csv", trace_dir); 196 + f = fopen(path, "w"); 197 + if (!f) { perror("fopen mc_status.csv"); return 1; } 198 + fprintf(f, "# SDLS MC Status Reply interop test vectors\n"); 199 + fprintf(f, "# Oracle: NASA CryptoLib %d.%d.%d\n", 200 + CRYPTO_LIB_MAJOR_VERSION, CRYPTO_LIB_MINOR_VERSION, CRYPTO_LIB_REVISION); 201 + fprintf(f, "# Format: name,operational,key_count,sa_count,encoded_hex\n"); 202 + 203 + for (int i = 0; i < n_mc; i++) { 204 + mc_status_vec_t *v = &mc_vecs[i]; 205 + uint8_t encoded[5]; 206 + encode_mc_status(v->operational, v->key_count, v->sa_count, encoded); 207 + 208 + char hex[11]; 209 + hex_of_bytes(encoded, 5, hex); 210 + fprintf(f, "%s,%d,%d,%d,%s\n", 211 + v->name, v->operational, v->key_count, v->sa_count, hex); 212 + } 213 + fclose(f); 214 + printf("Wrote %s (%d vectors)\n", path, n_mc); 215 + 216 + return 0; 217 + }
+9
test/interop/cryptolib/scripts/generate.sh
··· 1 + #!/bin/bash 2 + set -euo pipefail 3 + SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" 4 + TRACE_DIR="$(cd "$SCRIPT_DIR/../traces" && pwd)" 5 + CRYPTOLIB="$HOME/git/cryptolib" 6 + 7 + cc -I"$CRYPTOLIB/include" -L"$CRYPTOLIB/build" -lcryptolib \ 8 + -o "$SCRIPT_DIR/generate" "$SCRIPT_DIR/generate.c" 9 + "$SCRIPT_DIR/generate" "$TRACE_DIR"
+269
test/interop/cryptolib/test.ml
··· 1 + (** CryptoLib interop tests for ocaml-sdls. 2 + 3 + Tests SDLS EP PDU header encoding/decoding and MC status reply 4 + encoding/decoding against NASA CryptoLib (CCSDS 355.0-B-2 / 355.1-B-1). 5 + 6 + CryptoLib encodes the TLV header in Crypto_Prep_Reply as: 7 + byte0 = (type << 7) | (uf << 6) | (sg << 4) | pid 8 + byte1 = pdu_len >> 8 9 + byte2 = pdu_len & 0xFF 10 + 11 + And parses it in Crypto_Process_Extended_Procedure_Pdu with: 12 + type = (byte0 & 0x80) >> 7 13 + uf = (byte0 & 0x40) >> 6 14 + sg = (byte0 & 0x30) >> 4 15 + pid = (byte0 & 0x0F) 16 + pdu_len = (byte1 << 8) | byte2 17 + 18 + Traces generated by: scripts/generate.c (linked against CryptoLib) 19 + Regenerate: REGEN_TRACES=1 dune build @regen-traces *) 20 + 21 + open Sdls 22 + 23 + let trace path = Filename.concat "traces" path 24 + 25 + (* Parse CSV trace: skip comments (#) and blank lines, split on comma *) 26 + let parse_csv path = 27 + let ic = open_in (trace path) in 28 + let lines = ref [] in 29 + (try 30 + while true do 31 + let line = input_line ic in 32 + if String.length line > 0 && line.[0] <> '#' then 33 + lines := String.split_on_char ',' line :: !lines 34 + done 35 + with End_of_file -> ()); 36 + close_in ic; 37 + List.rev !lines 38 + 39 + let bytes_of_hex hex = 40 + if String.length hex = 0 then Bytes.empty 41 + else 42 + let len = String.length hex / 2 in 43 + Bytes.init len (fun i -> 44 + Char.chr (int_of_string ("0x" ^ String.sub hex (i * 2) 2))) 45 + 46 + let hex_of_bytes b = 47 + let buf = Buffer.create (Bytes.length b * 2) in 48 + Bytes.iter 49 + (fun c -> Buffer.add_string buf (Printf.sprintf "%02x" (Char.code c))) 50 + b; 51 + Buffer.contents buf 52 + 53 + (* {1 EP PDU Header vectors} *) 54 + 55 + type ep_hdr_vector = { 56 + name : string; 57 + is_reply : bool; 58 + user_flag : bool; 59 + sg : int; 60 + pid : int; 61 + pdu_len : int; 62 + encoded : bytes; 63 + } 64 + 65 + let parse_ep_hdr_vectors () = 66 + let rows = parse_csv "ep_hdr.csv" in 67 + List.filter_map 68 + (fun row -> 69 + match row with 70 + | [ name; is_reply; user_flag; sg; pid; pdu_len; encoded_hex ] -> 71 + Some 72 + { 73 + name; 74 + is_reply = int_of_string is_reply <> 0; 75 + user_flag = int_of_string user_flag <> 0; 76 + sg = int_of_string sg; 77 + pid = int_of_string pid; 78 + pdu_len = int_of_string pdu_len; 79 + encoded = bytes_of_hex encoded_hex; 80 + } 81 + | _ -> Alcotest.failf "bad ep_hdr CSV row: %s" (String.concat "," row)) 82 + rows 83 + 84 + let test_ep_hdr_encode vec () = 85 + let sg = 86 + match Ep.service_group_of_int vec.sg with 87 + | Some sg -> sg 88 + | None -> Alcotest.failf "%s: invalid sg %d" vec.name vec.sg 89 + in 90 + let hdr = 91 + Ep. 92 + { 93 + is_reply = vec.is_reply; 94 + user_flag = vec.user_flag; 95 + service_group = sg; 96 + procedure_id = vec.pid; 97 + pdu_len = vec.pdu_len; 98 + } 99 + in 100 + let got = Ep.encode_header hdr in 101 + if got <> vec.encoded then 102 + Alcotest.failf "%s: encode mismatch\n expected: %s\n got: %s" 103 + vec.name (hex_of_bytes vec.encoded) (hex_of_bytes got) 104 + 105 + let test_ep_hdr_decode vec () = 106 + match Ep.decode_header vec.encoded 0 with 107 + | Error `Truncated -> Alcotest.failf "%s: decode truncated" vec.name 108 + | Error `Invalid_sg -> Alcotest.failf "%s: decode invalid_sg" vec.name 109 + | Ok hdr -> 110 + Alcotest.(check bool) (vec.name ^ ": is_reply") vec.is_reply hdr.is_reply; 111 + Alcotest.(check bool) 112 + (vec.name ^ ": user_flag") vec.user_flag hdr.user_flag; 113 + Alcotest.(check int) 114 + (vec.name ^ ": sg") vec.sg 115 + (Ep.int_of_service_group hdr.service_group); 116 + Alcotest.(check int) (vec.name ^ ": pid") vec.pid hdr.procedure_id; 117 + Alcotest.(check int) (vec.name ^ ": pdu_len") vec.pdu_len hdr.pdu_len 118 + 119 + let test_ep_hdr_roundtrip vec () = 120 + let sg = 121 + match Ep.service_group_of_int vec.sg with 122 + | Some sg -> sg 123 + | None -> Alcotest.failf "%s: invalid sg %d" vec.name vec.sg 124 + in 125 + let hdr = 126 + Ep. 127 + { 128 + is_reply = vec.is_reply; 129 + user_flag = vec.user_flag; 130 + service_group = sg; 131 + procedure_id = vec.pid; 132 + pdu_len = vec.pdu_len; 133 + } 134 + in 135 + let encoded = Ep.encode_header hdr in 136 + match Ep.decode_header encoded 0 with 137 + | Error _ -> Alcotest.failf "%s: roundtrip decode failed" vec.name 138 + | Ok decoded -> 139 + Alcotest.(check bool) 140 + (vec.name ^ ": is_reply") hdr.is_reply decoded.is_reply; 141 + Alcotest.(check bool) 142 + (vec.name ^ ": user_flag") hdr.user_flag decoded.user_flag; 143 + Alcotest.(check int) 144 + (vec.name ^ ": sg") 145 + (Ep.int_of_service_group hdr.service_group) 146 + (Ep.int_of_service_group decoded.service_group); 147 + Alcotest.(check int) 148 + (vec.name ^ ": pid") hdr.procedure_id decoded.procedure_id; 149 + Alcotest.(check int) (vec.name ^ ": pdu_len") hdr.pdu_len decoded.pdu_len 150 + 151 + (* {1 MC Status Reply vectors} *) 152 + 153 + type mc_status_vector = { 154 + name : string; 155 + operational : bool; 156 + key_count : int; 157 + sa_count : int; 158 + encoded : bytes; 159 + } 160 + 161 + let parse_mc_status_vectors () = 162 + let rows = parse_csv "mc_status.csv" in 163 + List.filter_map 164 + (fun row -> 165 + match row with 166 + | [ name; operational; key_count; sa_count; encoded_hex ] -> 167 + Some 168 + { 169 + name; 170 + operational = int_of_string operational <> 0; 171 + key_count = int_of_string key_count; 172 + sa_count = int_of_string sa_count; 173 + encoded = bytes_of_hex encoded_hex; 174 + } 175 + | _ -> Alcotest.failf "bad mc_status CSV row: %s" (String.concat "," row)) 176 + rows 177 + 178 + let test_mc_status_encode vec () = 179 + let r : Ep.mc_status_reply = 180 + { 181 + operational = vec.operational; 182 + key_count = vec.key_count; 183 + sa_count = vec.sa_count; 184 + } 185 + in 186 + let got = Ep.encode_mc_status_reply r in 187 + if got <> vec.encoded then 188 + Alcotest.failf "%s: encode mismatch\n expected: %s\n got: %s" 189 + vec.name (hex_of_bytes vec.encoded) (hex_of_bytes got) 190 + 191 + let test_mc_status_decode vec () = 192 + match Ep.decode_mc_status_reply vec.encoded 0 with 193 + | Error `Truncated -> Alcotest.failf "%s: decode truncated" vec.name 194 + | Ok r -> 195 + Alcotest.(check bool) 196 + (vec.name ^ ": operational") 197 + vec.operational r.operational; 198 + Alcotest.(check int) (vec.name ^ ": key_count") vec.key_count r.key_count; 199 + Alcotest.(check int) (vec.name ^ ": sa_count") vec.sa_count r.sa_count 200 + 201 + let test_mc_status_roundtrip vec () = 202 + let r : Ep.mc_status_reply = 203 + { 204 + operational = vec.operational; 205 + key_count = vec.key_count; 206 + sa_count = vec.sa_count; 207 + } 208 + in 209 + let encoded = Ep.encode_mc_status_reply r in 210 + match Ep.decode_mc_status_reply encoded 0 with 211 + | Error `Truncated -> Alcotest.failf "%s: roundtrip decode failed" vec.name 212 + | Ok decoded -> 213 + Alcotest.(check bool) 214 + (vec.name ^ ": operational") 215 + r.operational decoded.operational; 216 + Alcotest.(check int) 217 + (vec.name ^ ": key_count") r.key_count decoded.key_count; 218 + Alcotest.(check int) (vec.name ^ ": sa_count") r.sa_count decoded.sa_count 219 + 220 + (* {1 Test runner} *) 221 + 222 + let () = 223 + let ep_hdr_vecs = parse_ep_hdr_vectors () in 224 + let mc_status_vecs = parse_mc_status_vectors () in 225 + let ep_encode = 226 + List.map 227 + (fun (v : ep_hdr_vector) -> 228 + Alcotest.test_case v.name `Quick (test_ep_hdr_encode v)) 229 + ep_hdr_vecs 230 + in 231 + let ep_decode = 232 + List.map 233 + (fun (v : ep_hdr_vector) -> 234 + Alcotest.test_case v.name `Quick (test_ep_hdr_decode v)) 235 + ep_hdr_vecs 236 + in 237 + let ep_roundtrip = 238 + List.map 239 + (fun (v : ep_hdr_vector) -> 240 + Alcotest.test_case v.name `Quick (test_ep_hdr_roundtrip v)) 241 + ep_hdr_vecs 242 + in 243 + let mc_encode = 244 + List.map 245 + (fun (v : mc_status_vector) -> 246 + Alcotest.test_case v.name `Quick (test_mc_status_encode v)) 247 + mc_status_vecs 248 + in 249 + let mc_decode = 250 + List.map 251 + (fun (v : mc_status_vector) -> 252 + Alcotest.test_case v.name `Quick (test_mc_status_decode v)) 253 + mc_status_vecs 254 + in 255 + let mc_roundtrip = 256 + List.map 257 + (fun (v : mc_status_vector) -> 258 + Alcotest.test_case v.name `Quick (test_mc_status_roundtrip v)) 259 + mc_status_vecs 260 + in 261 + Alcotest.run "sdls-cryptolib-interop" 262 + [ 263 + ("ep-hdr-encode", ep_encode); 264 + ("ep-hdr-decode", ep_decode); 265 + ("ep-hdr-roundtrip", ep_roundtrip); 266 + ("mc-status-encode", mc_encode); 267 + ("mc-status-decode", mc_decode); 268 + ("mc-status-roundtrip", mc_roundtrip); 269 + ]
+39
test/interop/cryptolib/traces/ep_hdr.csv
··· 1 + # SDLS EP PDU Header interop test vectors 2 + # Oracle: NASA CryptoLib 1.5.1 3 + # Format: name,is_reply,user_flag,sg,pid,pdu_len,encoded_hex 4 + otar_cmd,0,0,0,1,64,010040 5 + key_activate_cmd,0,0,0,2,4,020004 6 + key_deactivate_cmd,0,0,0,3,4,030004 7 + key_verify_cmd,0,0,0,4,32,040020 8 + key_destroy_cmd,0,0,0,6,4,060004 9 + key_inventory_cmd,0,0,0,7,4,070004 10 + key_verify_reply,1,0,0,4,128,840080 11 + key_inventory_reply,1,0,0,7,96,870060 12 + sa_create_cmd,0,0,1,1,20,110014 13 + sa_delete_cmd,0,0,1,4,2,140002 14 + sa_set_arsnw_cmd,0,0,1,5,4,150004 15 + sa_rekey_cmd,0,0,1,6,22,160016 16 + sa_expire_cmd,0,0,1,9,2,190002 17 + sa_set_arsn_cmd,0,0,1,10,2,1a0002 18 + sa_start_cmd,0,0,1,11,6,1b0006 19 + sa_stop_cmd,0,0,1,14,2,1e0002 20 + sa_status_cmd,0,0,1,15,2,1f0002 21 + sa_read_arsn_cmd,0,0,1,0,2,100002 22 + sa_create_reply,1,0,2,1,0,a10000 23 + sa_status_reply,1,0,2,15,3,af0003 24 + sa_read_arsn_reply,1,0,2,0,20,a00014 25 + mc_ping_cmd,0,0,3,1,0,310000 26 + mc_log_status_cmd,0,0,3,2,0,320000 27 + mc_dump_log_cmd,0,0,3,3,0,330000 28 + mc_erase_log_cmd,0,0,3,4,0,340000 29 + mc_self_test_cmd,0,0,3,5,0,350000 30 + mc_alarm_reset_cmd,0,0,3,7,0,370000 31 + mc_ping_reply,1,0,3,1,5,b10005 32 + mc_log_status_reply,1,0,3,2,4,b20004 33 + mc_self_test_reply,1,0,3,5,1,b50001 34 + mc_alarm_reset_reply,1,0,3,7,0,b70000 35 + user_flag_cmd,0,1,1,1,10,51000a 36 + user_flag_reply,1,1,3,2,4,f20004 37 + zero_pdu_len,0,0,0,1,0,010000 38 + max_pdu_len,0,0,0,7,65535,07ffff 39 + all_bits_set,1,1,3,7,65535,f7ffff
+11
test/interop/cryptolib/traces/mc_status.csv
··· 1 + # SDLS MC Status Reply interop test vectors 2 + # Oracle: NASA CryptoLib 1.5.1 3 + # Format: name,operational,key_count,sa_count,encoded_hex 4 + # Note: operational byte encodes true=0x01, false=0x00 per Wire.bit Wire.uint8 5 + mc_idle,0,0,0,0000000000 6 + mc_operational,1,5,3,0100050003 7 + mc_max_counts,1,65535,65535,01ffffffff 8 + mc_not_op_with_keys,0,10,0,00000a0000 9 + mc_single_sa,1,1,1,0100010001 10 + mc_many_keys,1,256,128,0101000080 11 + mc_zero_keys_some_sas,1,0,42,010000002a
+1
test/interop/python/.gitignore
··· 1 + scripts/.venv/
+19
test/interop/python/dune
··· 1 + (test 2 + (name test) 3 + (libraries sdls alcotest) 4 + (deps 5 + (source_tree traces) 6 + (source_tree scripts))) 7 + 8 + ; Regenerate traces: REGEN_TRACES=1 dune build @regen-traces 9 + 10 + (rule 11 + (alias regen-traces) 12 + (enabled_if 13 + (= %{env:REGEN_TRACES=0} 1)) 14 + (deps 15 + (source_tree scripts)) 16 + (action 17 + (chdir 18 + scripts 19 + (run ./generate.py))))
+437
test/interop/python/scripts/generate.py
··· 1 + #!/usr/bin/env python3 2 + """Generate SDLS interop traces for ocaml-sdls. 3 + 4 + Oracle: Python (no deps needed -- pure bitfield encoding) 5 + Install: no dependencies 6 + 7 + Tests Security Header, Security Trailer, and EP PDU header encoding/decoding 8 + per CCSDS 355.0-B-2 and CCSDS 355.1-B-1. 9 + 10 + Security Header (CCSDS 355.0-B-2 Section 3.2.2): 11 + SPI: 16 bits (Security Parameter Index) 12 + IV: variable length (Initialization Vector) 13 + SN: variable length (Sequence Number) 14 + 15 + Security Trailer (CCSDS 355.0-B-2 Section 3.2.3): 16 + MAC: variable length (Message Authentication Code) 17 + 18 + EP PDU Header (CCSDS 355.1-B-1 Section 5.3.2): 19 + Byte 0: Type(1b) | UF(1b) | SG(2b) | PID(4b) 20 + Bytes 1-2: PDU_LEN (16-bit, big-endian) 21 + 22 + MC Status Reply (CCSDS 355.1-B-1): 23 + Byte 0: Operational flag (bit 7), rest reserved 24 + Bytes 1-2: Key count (16-bit BE) 25 + Bytes 3-4: SA count (16-bit BE) 26 + 27 + Traces are committed to git. Only re-run when changing inputs 28 + or upgrading the oracle. 29 + """ 30 + import os 31 + import struct 32 + 33 + TRACE_DIR = os.path.join(os.path.dirname(__file__), "..", "traces") 34 + 35 + 36 + # --- Security Header encoding --- 37 + 38 + def encode_security_header(spi, iv_bytes, sn_bytes): 39 + """Encode security header: SPI(2) + IV(variable) + SN(variable).""" 40 + return struct.pack(">H", spi) + iv_bytes + sn_bytes 41 + 42 + 43 + # --- Security Trailer encoding --- 44 + 45 + def encode_security_trailer(mac_bytes): 46 + """Encode security trailer: MAC(variable).""" 47 + return mac_bytes 48 + 49 + 50 + # --- EP PDU Header encoding --- 51 + 52 + def encode_ep_header(is_reply, user_flag, sg, pid, pdu_len): 53 + """Encode EP header into 3 bytes. 54 + 55 + Byte 0: Type(1b) | UF(1b) | SG(2b) | PID(4b) 56 + Bytes 1-2: PDU_LEN (16-bit BE) 57 + """ 58 + byte0 = 0 59 + if is_reply: 60 + byte0 |= 0x80 61 + if user_flag: 62 + byte0 |= 0x40 63 + byte0 |= (sg & 0x3) << 4 64 + byte0 |= pid & 0xF 65 + return struct.pack(">BH", byte0, pdu_len) 66 + 67 + 68 + # --- MC Status Reply encoding --- 69 + 70 + def encode_mc_status_reply(operational, key_count, sa_count): 71 + """Encode MC Status Reply: flags(1) + key_count(2) + sa_count(2) = 5 bytes. 72 + 73 + Byte 0: 0x01 if operational, 0x00 otherwise (Wire.bit Wire.uint8 maps 74 + bool to 0/1 over the full byte, not a single-bit bitfield). 75 + """ 76 + byte0 = 0x01 if operational else 0x00 77 + return struct.pack(">BHH", byte0, key_count, sa_count) 78 + 79 + 80 + # --- Test vectors --- 81 + 82 + sec_hdr_vectors = [ 83 + { 84 + "name": "gcm_typical", 85 + "spi": 1, 86 + "iv_hex": "000000000000000000000001", 87 + "sn_hex": "", 88 + "iv_len": 12, 89 + "sn_len": 0, 90 + }, 91 + { 92 + "name": "gcm_max_spi", 93 + "spi": 0xFFFF, 94 + "iv_hex": "aabbccddeeff001122334455", 95 + "sn_hex": "", 96 + "iv_len": 12, 97 + "sn_len": 0, 98 + }, 99 + { 100 + "name": "auth_with_sn", 101 + "spi": 42, 102 + "iv_hex": "000000000000000000000000", 103 + "sn_hex": "00000001", 104 + "iv_len": 12, 105 + "sn_len": 4, 106 + }, 107 + { 108 + "name": "short_iv_with_sn", 109 + "spi": 256, 110 + "iv_hex": "0102030405060708", 111 + "sn_hex": "0a0b", 112 + "iv_len": 8, 113 + "sn_len": 2, 114 + }, 115 + { 116 + "name": "zero_iv_zero_sn", 117 + "spi": 0, 118 + "iv_hex": "000000000000000000000000", 119 + "sn_hex": "00000000", 120 + "iv_len": 12, 121 + "sn_len": 4, 122 + }, 123 + { 124 + "name": "max_iv_values", 125 + "spi": 100, 126 + "iv_hex": "ffffffffffffffffffffffff", 127 + "sn_hex": "ffffffff", 128 + "iv_len": 12, 129 + "sn_len": 4, 130 + }, 131 + { 132 + "name": "no_iv_no_sn", 133 + "spi": 7, 134 + "iv_hex": "", 135 + "sn_hex": "", 136 + "iv_len": 0, 137 + "sn_len": 0, 138 + }, 139 + ] 140 + 141 + sec_trl_vectors = [ 142 + { 143 + "name": "gcm_16byte_mac", 144 + "mac_hex": "0102030405060708090a0b0c0d0e0f10", 145 + "mac_len": 16, 146 + }, 147 + { 148 + "name": "cmac_16byte", 149 + "mac_hex": "aabbccddeeff00112233445566778899", 150 + "mac_len": 16, 151 + }, 152 + { 153 + "name": "truncated_8byte_mac", 154 + "mac_hex": "deadbeefcafebabe", 155 + "mac_len": 8, 156 + }, 157 + { 158 + "name": "zero_mac", 159 + "mac_hex": "00000000000000000000000000000000", 160 + "mac_len": 16, 161 + }, 162 + { 163 + "name": "all_ff_mac", 164 + "mac_hex": "ffffffffffffffffffffffffffffffff", 165 + "mac_len": 16, 166 + }, 167 + ] 168 + 169 + # Service group constants 170 + SG_KEY_MGMT = 0 171 + SG_SA_MGMT_IR = 1 172 + SG_SA_MGMT_RI = 2 173 + SG_SEC_MON_CTRL = 3 174 + 175 + ep_hdr_vectors = [ 176 + { 177 + "name": "sa_create_cmd", 178 + "is_reply": False, 179 + "user_flag": False, 180 + "sg": SG_SA_MGMT_IR, 181 + "pid": 1, 182 + "pdu_len": 20, 183 + }, 184 + { 185 + "name": "sa_create_reply", 186 + "is_reply": True, 187 + "user_flag": False, 188 + "sg": SG_SA_MGMT_RI, 189 + "pid": 1, 190 + "pdu_len": 0, 191 + }, 192 + { 193 + "name": "otar_cmd", 194 + "is_reply": False, 195 + "user_flag": False, 196 + "sg": SG_KEY_MGMT, 197 + "pid": 1, 198 + "pdu_len": 64, 199 + }, 200 + { 201 + "name": "key_verify_reply", 202 + "is_reply": True, 203 + "user_flag": False, 204 + "sg": SG_KEY_MGMT, 205 + "pid": 4, 206 + "pdu_len": 32, 207 + }, 208 + { 209 + "name": "sa_delete_cmd", 210 + "is_reply": False, 211 + "user_flag": False, 212 + "sg": SG_SA_MGMT_IR, 213 + "pid": 4, 214 + "pdu_len": 2, 215 + }, 216 + { 217 + "name": "sa_start_cmd", 218 + "is_reply": False, 219 + "user_flag": False, 220 + "sg": SG_SA_MGMT_IR, 221 + "pid": 11, 222 + "pdu_len": 2, 223 + }, 224 + { 225 + "name": "sa_stop_cmd", 226 + "is_reply": False, 227 + "user_flag": False, 228 + "sg": SG_SA_MGMT_IR, 229 + "pid": 14, 230 + "pdu_len": 2, 231 + }, 232 + { 233 + "name": "sa_status_cmd", 234 + "is_reply": False, 235 + "user_flag": False, 236 + "sg": SG_SA_MGMT_IR, 237 + "pid": 15, 238 + "pdu_len": 2, 239 + }, 240 + { 241 + "name": "mc_ping_cmd", 242 + "is_reply": False, 243 + "user_flag": False, 244 + "sg": SG_SEC_MON_CTRL, 245 + "pid": 1, 246 + "pdu_len": 0, 247 + }, 248 + { 249 + "name": "mc_ping_reply", 250 + "is_reply": True, 251 + "user_flag": False, 252 + "sg": SG_SEC_MON_CTRL, 253 + "pid": 1, 254 + "pdu_len": 5, 255 + }, 256 + { 257 + "name": "mc_self_test_cmd", 258 + "is_reply": False, 259 + "user_flag": False, 260 + "sg": SG_SEC_MON_CTRL, 261 + "pid": 5, 262 + "pdu_len": 0, 263 + }, 264 + { 265 + "name": "user_flag_set", 266 + "is_reply": False, 267 + "user_flag": True, 268 + "sg": SG_SA_MGMT_IR, 269 + "pid": 1, 270 + "pdu_len": 10, 271 + }, 272 + { 273 + "name": "reply_with_uf", 274 + "is_reply": True, 275 + "user_flag": True, 276 + "sg": SG_SEC_MON_CTRL, 277 + "pid": 2, 278 + "pdu_len": 4, 279 + }, 280 + { 281 + "name": "max_pdu_len", 282 + "is_reply": False, 283 + "user_flag": False, 284 + "sg": SG_KEY_MGMT, 285 + "pid": 7, 286 + "pdu_len": 65535, 287 + }, 288 + { 289 + "name": "sa_rekey_cmd", 290 + "is_reply": False, 291 + "user_flag": False, 292 + "sg": SG_SA_MGMT_IR, 293 + "pid": 6, 294 + "pdu_len": 4, 295 + }, 296 + { 297 + "name": "sa_expire_cmd", 298 + "is_reply": False, 299 + "user_flag": False, 300 + "sg": SG_SA_MGMT_IR, 301 + "pid": 9, 302 + "pdu_len": 2, 303 + }, 304 + { 305 + "name": "key_activation_cmd", 306 + "is_reply": False, 307 + "user_flag": False, 308 + "sg": SG_KEY_MGMT, 309 + "pid": 2, 310 + "pdu_len": 2, 311 + }, 312 + { 313 + "name": "key_deactivation_cmd", 314 + "is_reply": False, 315 + "user_flag": False, 316 + "sg": SG_KEY_MGMT, 317 + "pid": 3, 318 + "pdu_len": 2, 319 + }, 320 + { 321 + "name": "key_destruction_cmd", 322 + "is_reply": False, 323 + "user_flag": False, 324 + "sg": SG_KEY_MGMT, 325 + "pid": 6, 326 + "pdu_len": 2, 327 + }, 328 + { 329 + "name": "sa_set_arsnw_cmd", 330 + "is_reply": False, 331 + "user_flag": False, 332 + "sg": SG_SA_MGMT_IR, 333 + "pid": 5, 334 + "pdu_len": 4, 335 + }, 336 + ] 337 + 338 + mc_status_vectors = [ 339 + { 340 + "name": "mc_idle", 341 + "operational": False, 342 + "key_count": 0, 343 + "sa_count": 0, 344 + }, 345 + { 346 + "name": "mc_operational", 347 + "operational": True, 348 + "key_count": 5, 349 + "sa_count": 3, 350 + }, 351 + { 352 + "name": "mc_max_counts", 353 + "operational": True, 354 + "key_count": 65535, 355 + "sa_count": 65535, 356 + }, 357 + { 358 + "name": "mc_not_op_with_keys", 359 + "operational": False, 360 + "key_count": 10, 361 + "sa_count": 0, 362 + }, 363 + { 364 + "name": "mc_single_sa", 365 + "operational": True, 366 + "key_count": 1, 367 + "sa_count": 1, 368 + }, 369 + ] 370 + 371 + 372 + def main(): 373 + os.makedirs(TRACE_DIR, exist_ok=True) 374 + 375 + # --- Security Header vectors --- 376 + with open(os.path.join(TRACE_DIR, "sec_hdr.csv"), "w") as f: 377 + f.write("# SDLS Security Header interop test vectors\n") 378 + f.write("# Oracle: Python (CCSDS 355.0-B-2 reference implementation)\n") 379 + f.write("# Format: name,spi,iv_hex,sn_hex,iv_len,sn_len,encoded_hex\n") 380 + for v in sec_hdr_vectors: 381 + iv = bytes.fromhex(v["iv_hex"]) 382 + sn = bytes.fromhex(v["sn_hex"]) 383 + encoded = encode_security_header(v["spi"], iv, sn) 384 + f.write( 385 + f"{v['name']},{v['spi']},{v['iv_hex']},{v['sn_hex']}," 386 + f"{v['iv_len']},{v['sn_len']},{encoded.hex()}\n" 387 + ) 388 + 389 + # --- Security Trailer vectors --- 390 + with open(os.path.join(TRACE_DIR, "sec_trl.csv"), "w") as f: 391 + f.write("# SDLS Security Trailer interop test vectors\n") 392 + f.write("# Oracle: Python (CCSDS 355.0-B-2 reference implementation)\n") 393 + f.write("# Format: name,mac_hex,mac_len,encoded_hex\n") 394 + for v in sec_trl_vectors: 395 + mac = bytes.fromhex(v["mac_hex"]) 396 + encoded = encode_security_trailer(mac) 397 + f.write(f"{v['name']},{v['mac_hex']},{v['mac_len']},{encoded.hex()}\n") 398 + 399 + # --- EP PDU Header vectors --- 400 + with open(os.path.join(TRACE_DIR, "ep_hdr.csv"), "w") as f: 401 + f.write("# SDLS EP PDU Header interop test vectors\n") 402 + f.write("# Oracle: Python (CCSDS 355.1-B-1 reference implementation)\n") 403 + f.write("# Format: name,is_reply,user_flag,sg,pid,pdu_len,encoded_hex\n") 404 + for v in ep_hdr_vectors: 405 + encoded = encode_ep_header( 406 + v["is_reply"], v["user_flag"], v["sg"], v["pid"], v["pdu_len"] 407 + ) 408 + f.write( 409 + f"{v['name']},{1 if v['is_reply'] else 0}," 410 + f"{1 if v['user_flag'] else 0},{v['sg']},{v['pid']}," 411 + f"{v['pdu_len']},{encoded.hex()}\n" 412 + ) 413 + 414 + # --- MC Status Reply vectors --- 415 + with open(os.path.join(TRACE_DIR, "mc_status.csv"), "w") as f: 416 + f.write("# SDLS MC Status Reply interop test vectors\n") 417 + f.write("# Oracle: Python (CCSDS 355.1-B-1 reference implementation)\n") 418 + f.write( 419 + "# Format: name,operational,key_count,sa_count,encoded_hex\n" 420 + ) 421 + for v in mc_status_vectors: 422 + encoded = encode_mc_status_reply( 423 + v["operational"], v["key_count"], v["sa_count"] 424 + ) 425 + f.write( 426 + f"{v['name']},{1 if v['operational'] else 0}," 427 + f"{v['key_count']},{v['sa_count']},{encoded.hex()}\n" 428 + ) 429 + 430 + print(f"Wrote {TRACE_DIR}/sec_hdr.csv") 431 + print(f"Wrote {TRACE_DIR}/sec_trl.csv") 432 + print(f"Wrote {TRACE_DIR}/ep_hdr.csv") 433 + print(f"Wrote {TRACE_DIR}/mc_status.csv") 434 + 435 + 436 + if __name__ == "__main__": 437 + main()
+428
test/interop/python/test.ml
··· 1 + (** Python interop tests for ocaml-sdls. 2 + 3 + Tests security header/trailer parsing and EP PDU header encoding/decoding 4 + against a Python reference implementation of CCSDS 355.0-B-2 and 355.1-B-1. 5 + 6 + Traces generated by: Python (CCSDS 355.0-B-2 / 355.1-B-1 reference) 7 + Regenerate: REGEN_TRACES=1 dune build @regen-traces *) 8 + 9 + open Sdls 10 + 11 + let trace path = Filename.concat "traces" path 12 + 13 + (* Parse CSV trace: skip comments (#), split on comma *) 14 + let parse_csv path = 15 + let ic = open_in (trace path) in 16 + let lines = ref [] in 17 + (try 18 + while true do 19 + let line = input_line ic in 20 + if String.length line > 0 && line.[0] <> '#' then 21 + lines := String.split_on_char ',' line :: !lines 22 + done 23 + with End_of_file -> ()); 24 + close_in ic; 25 + List.rev !lines 26 + 27 + let bytes_of_hex hex = 28 + if String.length hex = 0 then Bytes.empty 29 + else 30 + let len = String.length hex / 2 in 31 + Bytes.init len (fun i -> 32 + Char.chr (int_of_string ("0x" ^ String.sub hex (i * 2) 2))) 33 + 34 + let hex_of_bytes b = 35 + let buf = Buffer.create (Bytes.length b * 2) in 36 + Bytes.iter 37 + (fun c -> Buffer.add_string buf (Printf.sprintf "%02x" (Char.code c))) 38 + b; 39 + Buffer.contents buf 40 + 41 + (* {1 Security Header vectors} *) 42 + 43 + type sec_hdr_vector = { 44 + name : string; 45 + spi : int; 46 + iv : bytes; 47 + sn : bytes; 48 + iv_len : int; 49 + sn_len : int; 50 + encoded : bytes; 51 + } 52 + 53 + let parse_sec_hdr_vectors () = 54 + let rows = parse_csv "sec_hdr.csv" in 55 + List.filter_map 56 + (fun row -> 57 + match row with 58 + | [ name; spi; iv_hex; sn_hex; iv_len; sn_len; encoded_hex ] -> 59 + Some 60 + { 61 + name; 62 + spi = int_of_string spi; 63 + iv = bytes_of_hex iv_hex; 64 + sn = bytes_of_hex sn_hex; 65 + iv_len = int_of_string iv_len; 66 + sn_len = int_of_string sn_len; 67 + encoded = bytes_of_hex encoded_hex; 68 + } 69 + | _ -> Alcotest.failf "bad sec_hdr CSV row: %s" (String.concat "," row)) 70 + rows 71 + 72 + let test_sec_hdr_encode vec () = 73 + let sh : Sa.security_header = { spi = vec.spi; iv = vec.iv; sn = vec.sn } in 74 + let w = Binary.Writer.create 64 in 75 + Sdls.write_security_header w sh; 76 + let got = Binary.Writer.contents w in 77 + if got <> vec.encoded then 78 + Alcotest.failf "%s: encode mismatch\n expected: %s\n got: %s" 79 + vec.name (hex_of_bytes vec.encoded) (hex_of_bytes got) 80 + 81 + let test_sec_hdr_decode vec () = 82 + (* Build a minimal SA entry with the right iv_len/sn_len for decoding *) 83 + let sa = 84 + Sa.v ~encryption:false ~authentication:false ~ecs:None ~acs:None 85 + ~iv_len:vec.iv_len ~mac_len:0 ~sn_len:vec.sn_len ~spi:1 ~scid:0 ~vcid:0 () 86 + in 87 + let r = Binary.Reader.of_bytes vec.encoded in 88 + match Sdls.read_security_header ~sa r with 89 + | Error e -> 90 + Alcotest.failf "%s: decode failed: %a" vec.name Sdls.pp_error_debug e 91 + | Ok (sh : Sa.security_header) -> 92 + Alcotest.(check int) (vec.name ^ ": spi") vec.spi sh.spi; 93 + Alcotest.(check string) 94 + (vec.name ^ ": iv") (hex_of_bytes vec.iv) (hex_of_bytes sh.iv); 95 + Alcotest.(check string) 96 + (vec.name ^ ": sn") (hex_of_bytes vec.sn) (hex_of_bytes sh.sn) 97 + 98 + let test_sec_hdr_roundtrip vec () = 99 + let sh : Sa.security_header = { spi = vec.spi; iv = vec.iv; sn = vec.sn } in 100 + let w = Binary.Writer.create 64 in 101 + Sdls.write_security_header w sh; 102 + let buf = Binary.Writer.contents w in 103 + let sa = 104 + Sa.v ~encryption:false ~authentication:false ~ecs:None ~acs:None 105 + ~iv_len:vec.iv_len ~mac_len:0 ~sn_len:vec.sn_len ~spi:1 ~scid:0 ~vcid:0 () 106 + in 107 + let r = Binary.Reader.of_bytes buf in 108 + match Sdls.read_security_header ~sa r with 109 + | Error e -> 110 + Alcotest.failf "%s: roundtrip decode failed: %a" vec.name 111 + Sdls.pp_error_debug e 112 + | Ok (sh' : Sa.security_header) -> 113 + Alcotest.(check int) (vec.name ^ ": spi") sh.spi sh'.spi; 114 + if sh.iv <> sh'.iv then Alcotest.failf "%s: iv mismatch" vec.name; 115 + if sh.sn <> sh'.sn then Alcotest.failf "%s: sn mismatch" vec.name 116 + 117 + (* {1 Security Trailer vectors} *) 118 + 119 + type sec_trl_vector = { 120 + name : string; 121 + mac : bytes; 122 + mac_len : int; 123 + encoded : bytes; 124 + } 125 + 126 + let parse_sec_trl_vectors () = 127 + let rows = parse_csv "sec_trl.csv" in 128 + List.filter_map 129 + (fun row -> 130 + match row with 131 + | [ name; mac_hex; mac_len; encoded_hex ] -> 132 + Some 133 + { 134 + name; 135 + mac = bytes_of_hex mac_hex; 136 + mac_len = int_of_string mac_len; 137 + encoded = bytes_of_hex encoded_hex; 138 + } 139 + | _ -> Alcotest.failf "bad sec_trl CSV row: %s" (String.concat "," row)) 140 + rows 141 + 142 + let test_sec_trl_encode vec () = 143 + let st : Sa.security_trailer = { mac = vec.mac } in 144 + let w = Binary.Writer.create 64 in 145 + Sdls.write_security_trailer w st; 146 + let got = Binary.Writer.contents w in 147 + if got <> vec.encoded then 148 + Alcotest.failf "%s: encode mismatch\n expected: %s\n got: %s" 149 + vec.name (hex_of_bytes vec.encoded) (hex_of_bytes got) 150 + 151 + let test_sec_trl_decode vec () = 152 + let sa = 153 + Sa.v ~encryption:false ~authentication:false ~ecs:None ~acs:None ~iv_len:0 154 + ~mac_len:vec.mac_len ~sn_len:0 ~spi:1 ~scid:0 ~vcid:0 () 155 + in 156 + let r = Binary.Reader.of_bytes vec.encoded in 157 + match Sdls.read_security_trailer ~sa r with 158 + | Error e -> 159 + Alcotest.failf "%s: decode failed: %a" vec.name Sdls.pp_error_debug e 160 + | Ok st -> 161 + Alcotest.(check string) 162 + (vec.name ^ ": mac") (hex_of_bytes vec.mac) (hex_of_bytes st.mac) 163 + 164 + let test_sec_trl_roundtrip vec () = 165 + let st : Sa.security_trailer = { mac = vec.mac } in 166 + let w = Binary.Writer.create 64 in 167 + Sdls.write_security_trailer w st; 168 + let buf = Binary.Writer.contents w in 169 + let sa = 170 + Sa.v ~encryption:false ~authentication:false ~ecs:None ~acs:None ~iv_len:0 171 + ~mac_len:vec.mac_len ~sn_len:0 ~spi:1 ~scid:0 ~vcid:0 () 172 + in 173 + let r = Binary.Reader.of_bytes buf in 174 + match Sdls.read_security_trailer ~sa r with 175 + | Error e -> 176 + Alcotest.failf "%s: roundtrip decode failed: %a" vec.name 177 + Sdls.pp_error_debug e 178 + | Ok st' -> 179 + if st.mac <> st'.mac then Alcotest.failf "%s: mac mismatch" vec.name 180 + 181 + (* {1 EP PDU Header vectors} *) 182 + 183 + type ep_hdr_vector = { 184 + name : string; 185 + is_reply : bool; 186 + user_flag : bool; 187 + sg : int; 188 + pid : int; 189 + pdu_len : int; 190 + encoded : bytes; 191 + } 192 + 193 + let parse_ep_hdr_vectors () = 194 + let rows = parse_csv "ep_hdr.csv" in 195 + List.filter_map 196 + (fun row -> 197 + match row with 198 + | [ name; is_reply; user_flag; sg; pid; pdu_len; encoded_hex ] -> 199 + Some 200 + { 201 + name; 202 + is_reply = int_of_string is_reply <> 0; 203 + user_flag = int_of_string user_flag <> 0; 204 + sg = int_of_string sg; 205 + pid = int_of_string pid; 206 + pdu_len = int_of_string pdu_len; 207 + encoded = bytes_of_hex encoded_hex; 208 + } 209 + | _ -> Alcotest.failf "bad ep_hdr CSV row: %s" (String.concat "," row)) 210 + rows 211 + 212 + let test_ep_hdr_encode vec () = 213 + let sg = 214 + match Ep.service_group_of_int vec.sg with 215 + | Some sg -> sg 216 + | None -> Alcotest.failf "%s: invalid sg %d" vec.name vec.sg 217 + in 218 + let hdr = 219 + Ep. 220 + { 221 + is_reply = vec.is_reply; 222 + user_flag = vec.user_flag; 223 + service_group = sg; 224 + procedure_id = vec.pid; 225 + pdu_len = vec.pdu_len; 226 + } 227 + in 228 + let got = Ep.encode_header hdr in 229 + if got <> vec.encoded then 230 + Alcotest.failf "%s: encode mismatch\n expected: %s\n got: %s" 231 + vec.name (hex_of_bytes vec.encoded) (hex_of_bytes got) 232 + 233 + let test_ep_hdr_decode vec () = 234 + match Ep.decode_header vec.encoded 0 with 235 + | Error `Truncated -> Alcotest.failf "%s: decode truncated" vec.name 236 + | Error `Invalid_sg -> Alcotest.failf "%s: decode invalid_sg" vec.name 237 + | Ok hdr -> 238 + Alcotest.(check bool) (vec.name ^ ": is_reply") vec.is_reply hdr.is_reply; 239 + Alcotest.(check bool) 240 + (vec.name ^ ": user_flag") vec.user_flag hdr.user_flag; 241 + Alcotest.(check int) 242 + (vec.name ^ ": sg") vec.sg 243 + (Ep.int_of_service_group hdr.service_group); 244 + Alcotest.(check int) (vec.name ^ ": pid") vec.pid hdr.procedure_id; 245 + Alcotest.(check int) (vec.name ^ ": pdu_len") vec.pdu_len hdr.pdu_len 246 + 247 + let test_ep_hdr_roundtrip vec () = 248 + let sg = 249 + match Ep.service_group_of_int vec.sg with 250 + | Some sg -> sg 251 + | None -> Alcotest.failf "%s: invalid sg %d" vec.name vec.sg 252 + in 253 + let hdr = 254 + Ep. 255 + { 256 + is_reply = vec.is_reply; 257 + user_flag = vec.user_flag; 258 + service_group = sg; 259 + procedure_id = vec.pid; 260 + pdu_len = vec.pdu_len; 261 + } 262 + in 263 + let encoded = Ep.encode_header hdr in 264 + match Ep.decode_header encoded 0 with 265 + | Error _ -> Alcotest.failf "%s: roundtrip decode failed" vec.name 266 + | Ok decoded -> 267 + Alcotest.(check bool) 268 + (vec.name ^ ": is_reply") hdr.is_reply decoded.is_reply; 269 + Alcotest.(check bool) 270 + (vec.name ^ ": user_flag") hdr.user_flag decoded.user_flag; 271 + Alcotest.(check int) 272 + (vec.name ^ ": sg") 273 + (Ep.int_of_service_group hdr.service_group) 274 + (Ep.int_of_service_group decoded.service_group); 275 + Alcotest.(check int) 276 + (vec.name ^ ": pid") hdr.procedure_id decoded.procedure_id; 277 + Alcotest.(check int) (vec.name ^ ": pdu_len") hdr.pdu_len decoded.pdu_len 278 + 279 + (* {1 MC Status Reply vectors} *) 280 + 281 + type mc_status_vector = { 282 + mc_name : string; 283 + mc_operational : bool; 284 + mc_key_count : int; 285 + mc_sa_count : int; 286 + mc_encoded : bytes; 287 + } 288 + 289 + let parse_mc_status_vectors () = 290 + let rows = parse_csv "mc_status.csv" in 291 + List.filter_map 292 + (fun row -> 293 + match row with 294 + | [ name; operational; key_count; sa_count; encoded_hex ] -> 295 + Some 296 + { 297 + mc_name = name; 298 + mc_operational = int_of_string operational <> 0; 299 + mc_key_count = int_of_string key_count; 300 + mc_sa_count = int_of_string sa_count; 301 + mc_encoded = bytes_of_hex encoded_hex; 302 + } 303 + | _ -> Alcotest.failf "bad mc_status CSV row: %s" (String.concat "," row)) 304 + rows 305 + 306 + let test_mc_status_encode vec () = 307 + let r = 308 + Ep. 309 + { 310 + operational = vec.mc_operational; 311 + key_count = vec.mc_key_count; 312 + sa_count = vec.mc_sa_count; 313 + } 314 + in 315 + let got = Ep.encode_mc_status_reply r in 316 + if got <> vec.mc_encoded then 317 + Alcotest.failf "%s: encode mismatch\n expected: %s\n got: %s" 318 + vec.mc_name 319 + (hex_of_bytes vec.mc_encoded) 320 + (hex_of_bytes got) 321 + 322 + let test_mc_status_decode vec () = 323 + match Ep.decode_mc_status_reply vec.mc_encoded 0 with 324 + | Error `Truncated -> Alcotest.failf "%s: decode truncated" vec.mc_name 325 + | Ok r -> 326 + Alcotest.(check bool) 327 + (vec.mc_name ^ ": operational") 328 + vec.mc_operational r.operational; 329 + Alcotest.(check int) 330 + (vec.mc_name ^ ": key_count") 331 + vec.mc_key_count r.key_count; 332 + Alcotest.(check int) 333 + (vec.mc_name ^ ": sa_count") 334 + vec.mc_sa_count r.sa_count 335 + 336 + let test_mc_status_roundtrip vec () = 337 + let r = 338 + Ep. 339 + { 340 + operational = vec.mc_operational; 341 + key_count = vec.mc_key_count; 342 + sa_count = vec.mc_sa_count; 343 + } 344 + in 345 + let encoded = Ep.encode_mc_status_reply r in 346 + match Ep.decode_mc_status_reply encoded 0 with 347 + | Error `Truncated -> Alcotest.failf "%s: roundtrip decode failed" vec.mc_name 348 + | Ok decoded -> 349 + Alcotest.(check bool) 350 + (vec.mc_name ^ ": operational") 351 + r.operational decoded.operational; 352 + Alcotest.(check int) 353 + (vec.mc_name ^ ": key_count") 354 + r.key_count decoded.key_count; 355 + Alcotest.(check int) 356 + (vec.mc_name ^ ": sa_count") 357 + r.sa_count decoded.sa_count 358 + 359 + (* {1 Test runner} *) 360 + 361 + let () = 362 + let sec_hdr_vecs = parse_sec_hdr_vectors () in 363 + let sec_trl_vecs = parse_sec_trl_vectors () in 364 + let ep_hdr_vecs = parse_ep_hdr_vectors () in 365 + let mc_status_vecs = parse_mc_status_vectors () in 366 + Alcotest.run "sdls-interop" 367 + [ 368 + ( "sec-hdr-encode", 369 + List.map 370 + (fun (v : sec_hdr_vector) -> 371 + Alcotest.test_case v.name `Quick (test_sec_hdr_encode v)) 372 + sec_hdr_vecs ); 373 + ( "sec-hdr-decode", 374 + List.map 375 + (fun (v : sec_hdr_vector) -> 376 + Alcotest.test_case v.name `Quick (test_sec_hdr_decode v)) 377 + sec_hdr_vecs ); 378 + ( "sec-hdr-roundtrip", 379 + List.map 380 + (fun (v : sec_hdr_vector) -> 381 + Alcotest.test_case v.name `Quick (test_sec_hdr_roundtrip v)) 382 + sec_hdr_vecs ); 383 + ( "sec-trl-encode", 384 + List.map 385 + (fun (v : sec_trl_vector) -> 386 + Alcotest.test_case v.name `Quick (test_sec_trl_encode v)) 387 + sec_trl_vecs ); 388 + ( "sec-trl-decode", 389 + List.map 390 + (fun (v : sec_trl_vector) -> 391 + Alcotest.test_case v.name `Quick (test_sec_trl_decode v)) 392 + sec_trl_vecs ); 393 + ( "sec-trl-roundtrip", 394 + List.map 395 + (fun (v : sec_trl_vector) -> 396 + Alcotest.test_case v.name `Quick (test_sec_trl_roundtrip v)) 397 + sec_trl_vecs ); 398 + ( "ep-hdr-encode", 399 + List.map 400 + (fun (v : ep_hdr_vector) -> 401 + Alcotest.test_case v.name `Quick (test_ep_hdr_encode v)) 402 + ep_hdr_vecs ); 403 + ( "ep-hdr-decode", 404 + List.map 405 + (fun (v : ep_hdr_vector) -> 406 + Alcotest.test_case v.name `Quick (test_ep_hdr_decode v)) 407 + ep_hdr_vecs ); 408 + ( "ep-hdr-roundtrip", 409 + List.map 410 + (fun (v : ep_hdr_vector) -> 411 + Alcotest.test_case v.name `Quick (test_ep_hdr_roundtrip v)) 412 + ep_hdr_vecs ); 413 + ( "mc-status-encode", 414 + List.map 415 + (fun v -> 416 + Alcotest.test_case v.mc_name `Quick (test_mc_status_encode v)) 417 + mc_status_vecs ); 418 + ( "mc-status-decode", 419 + List.map 420 + (fun v -> 421 + Alcotest.test_case v.mc_name `Quick (test_mc_status_decode v)) 422 + mc_status_vecs ); 423 + ( "mc-status-roundtrip", 424 + List.map 425 + (fun v -> 426 + Alcotest.test_case v.mc_name `Quick (test_mc_status_roundtrip v)) 427 + mc_status_vecs ); 428 + ]
+23
test/interop/python/traces/ep_hdr.csv
··· 1 + # SDLS EP PDU Header interop test vectors 2 + # Oracle: Python (CCSDS 355.1-B-1 reference implementation) 3 + # Format: name,is_reply,user_flag,sg,pid,pdu_len,encoded_hex 4 + sa_create_cmd,0,0,1,1,20,110014 5 + sa_create_reply,1,0,2,1,0,a10000 6 + otar_cmd,0,0,0,1,64,010040 7 + key_verify_reply,1,0,0,4,32,840020 8 + sa_delete_cmd,0,0,1,4,2,140002 9 + sa_start_cmd,0,0,1,11,2,1b0002 10 + sa_stop_cmd,0,0,1,14,2,1e0002 11 + sa_status_cmd,0,0,1,15,2,1f0002 12 + mc_ping_cmd,0,0,3,1,0,310000 13 + mc_ping_reply,1,0,3,1,5,b10005 14 + mc_self_test_cmd,0,0,3,5,0,350000 15 + user_flag_set,0,1,1,1,10,51000a 16 + reply_with_uf,1,1,3,2,4,f20004 17 + max_pdu_len,0,0,0,7,65535,07ffff 18 + sa_rekey_cmd,0,0,1,6,4,160004 19 + sa_expire_cmd,0,0,1,9,2,190002 20 + key_activation_cmd,0,0,0,2,2,020002 21 + key_deactivation_cmd,0,0,0,3,2,030002 22 + key_destruction_cmd,0,0,0,6,2,060002 23 + sa_set_arsnw_cmd,0,0,1,5,4,150004
+8
test/interop/python/traces/mc_status.csv
··· 1 + # SDLS MC Status Reply interop test vectors 2 + # Oracle: Python (CCSDS 355.1-B-1 reference implementation) 3 + # Format: name,operational,key_count,sa_count,encoded_hex 4 + mc_idle,0,0,0,0000000000 5 + mc_operational,1,5,3,0100050003 6 + mc_max_counts,1,65535,65535,01ffffffff 7 + mc_not_op_with_keys,0,10,0,00000a0000 8 + mc_single_sa,1,1,1,0100010001
+10
test/interop/python/traces/sec_hdr.csv
··· 1 + # SDLS Security Header interop test vectors 2 + # Oracle: Python (CCSDS 355.0-B-2 reference implementation) 3 + # Format: name,spi,iv_hex,sn_hex,iv_len,sn_len,encoded_hex 4 + gcm_typical,1,000000000000000000000001,,12,0,0001000000000000000000000001 5 + gcm_max_spi,65535,aabbccddeeff001122334455,,12,0,ffffaabbccddeeff001122334455 6 + auth_with_sn,42,000000000000000000000000,00000001,12,4,002a00000000000000000000000000000001 7 + short_iv_with_sn,256,0102030405060708,0a0b,8,2,010001020304050607080a0b 8 + zero_iv_zero_sn,0,000000000000000000000000,00000000,12,4,000000000000000000000000000000000000 9 + max_iv_values,100,ffffffffffffffffffffffff,ffffffff,12,4,0064ffffffffffffffffffffffffffffffff 10 + no_iv_no_sn,7,,,0,0,0007
+8
test/interop/python/traces/sec_trl.csv
··· 1 + # SDLS Security Trailer interop test vectors 2 + # Oracle: Python (CCSDS 355.0-B-2 reference implementation) 3 + # Format: name,mac_hex,mac_len,encoded_hex 4 + gcm_16byte_mac,0102030405060708090a0b0c0d0e0f10,16,0102030405060708090a0b0c0d0e0f10 5 + cmac_16byte,aabbccddeeff00112233445566778899,16,aabbccddeeff00112233445566778899 6 + truncated_8byte_mac,deadbeefcafebabe,8,deadbeefcafebabe 7 + zero_mac,00000000000000000000000000000000,16,00000000000000000000000000000000 8 + all_ff_mac,ffffffffffffffffffffffffffffffff,16,ffffffffffffffffffffffffffffffff