code wiki / hub / nx_tls13_sig_algs_audit.nx

nx_tls13_sig_algs_audit.nx source

↩ module page · 193 lines · 8033 B

1// nx_tls13_sig_algs_audit.nx -- V-HOST-2+1-3 Path B: ClientHello 2// signature_algorithms extension parser. 3// 4// Per RFC 8446 §4.2.3: client offers a list of signature_scheme values 5// indicating which sig algs it can verify. This module parses that 6// list defensively + tells the caller which of the substrate's 7// supported sig algs (ecdsa_secp256r1_sha256, ed25519) the client 8// supports. 9// 10// COMPOSES (per "avoid duplicate primitives"): 11// nx_syscalls (no allocation; caller-supplied buffer reads) 12// 13// COMPOSED BY: 14// future TLS server handshake validation (V-HOST-2+1-4 smoke uses 15// this for handshake-failure prediction) 16// future client-capability probes 17// sovereign nx_pages_https_daemon vhost-pick logic V+1 18// 19// V-HOST-2+1-3 SCOPE: 20// - Parse extension type 0x000D (signature_algorithms) from 21// ClientHello extension block 22// - Walk the sig-alg list; flag whether ecdsa_secp256r1_sha256 (0x0403) 23// OR ed25519 (0x0807) appear 24// - Return verdict + boolean flags 25// 26// V-HOST-2+1-3 NON-SCOPE: 27// - Block the handshake (substrate-friendly: substrate-caller decides) 28// - Parse other sig schemes (rsa_pkcs1_*, ecdsa_secp384r1_*, etc.) 29// - PSK / cert_authorities extensions 30// 31// Status: V-HOST-2+1-3 Path B. 2026-05-27. 32 33import "nx_syscalls.nx" 34 35// ===== Sealed verdict surface (codes 3700-3709) ================================================= 36const NX_SAA_OK: i64 = 0 37const NX_SAA_BAD_INPUT: i64 = 3700 38const NX_SAA_TRUNCATED: i64 = 3701 39const NX_SAA_NOT_TLS13: i64 = 3702 40const NX_SAA_NO_SIG_ALGS_EXT: i64 = 3703 41const NX_SAA_LOOP_BUDGET: i64 = 3704 42 43// ===== Sealed sig_scheme values (per RFC 8446 §4.2.3) ================================================= 44 45const NX_SAA_SCHEME_RSA_PKCS1_SHA256: i64 = 0x0401 46const NX_SAA_SCHEME_ECDSA_SECP256R1_SHA256: i64 = 0x0403 47const NX_SAA_SCHEME_ECDSA_SECP384R1_SHA384: i64 = 0x0503 48const NX_SAA_SCHEME_RSA_PSS_RSAE_SHA256: i64 = 0x0804 49const NX_SAA_SCHEME_RSA_PSS_RSAE_SHA384: i64 = 0x0805 50const NX_SAA_SCHEME_RSA_PSS_RSAE_SHA512: i64 = 0x0806 51const NX_SAA_SCHEME_ED25519: i64 = 0x0807 52const NX_SAA_SCHEME_ED448: i64 = 0x0808 53const NX_SAA_SCHEME_RSA_PSS_PSS_SHA256: i64 = 0x0809 54 55const NX_SAA_EXT_TYPE_SIGNATURE_ALGORITHMS: i64 = 0x000D 56 57// ===== Named constants (M7) ================================================= 58const NX_SAA_MAX_EXT_DATA_LEN: i64 = 65535 // 16-bit length cap per spec 59const NX_SAA_LOOP_BUDGET_CAP: i64 = 100000 60 61// ===== Result struct ================================================= 62 63struct NxSigAlgsAuditResult { 64 found_sig_algs_ext: i64 // 1 if extension type 0x000D was present 65 supports_ecdsa_p256: i64 // 1 if 0x0403 in the list 66 supports_ed25519: i64 // 1 if 0x0807 in the list 67 total_schemes_offered: i64 // # of u16 entries in the list 68 valid: i64 69} 70 71func nx_saa_result_init(r: *NxSigAlgsAuditResult) -> i64 { 72 if (r as i64) == 0 { return 0 - NX_SAA_BAD_INPUT } 73 r.found_sig_algs_ext = 0 74 r.supports_ecdsa_p256 = 0 75 r.supports_ed25519 = 0 76 r.total_schemes_offered = 0 77 r.valid = 1 78 return NX_SAA_OK 79} 80 81// ===== Parse extensions block + find signature_algorithms ================================================= 82// 83// ClientHello extensions block layout (per RFC 8446 §4.1.2): 84// u16 extensions_length 85// per extension: 86// u16 extension_type 87// u16 extension_data_length 88// extension_data (extension_data_length bytes) 89// 90// signature_algorithms extension data layout (per RFC 8446 §4.2.3): 91// u16 supported_signature_algorithms_length 92// per sig_scheme: 93// u16 sig_scheme 94 95func nx_tls13_sig_algs_audit(ch_body: *u8, ch_body_n: i64, 96 ext_block_offset: i64, 97 result: *NxSigAlgsAuditResult) -> i64 { 98 if (ch_body as i64) == 0 { return 0 - NX_SAA_BAD_INPUT } 99 if ch_body_n < 0 { return 0 - NX_SAA_BAD_INPUT } 100 if ext_block_offset < 0 { return 0 - NX_SAA_BAD_INPUT } 101 if ext_block_offset + 2 > ch_body_n { return 0 - NX_SAA_TRUNCATED } 102 let rc_r: i64 = nx_saa_result_init(result) 103 if rc_r != NX_SAA_OK { return rc_r } 104 105 // Read u16 extensions_length 106 let exts_len: i64 = ((ch_body[ext_block_offset] as i64) << 8) 107 | (ch_body[ext_block_offset + 1] as i64) 108 if ext_block_offset + 2 + exts_len > ch_body_n { return 0 - NX_SAA_TRUNCATED } 109 if exts_len > NX_SAA_MAX_EXT_DATA_LEN { return 0 - NX_SAA_TRUNCATED } 110 111 var ext_off: i64 = ext_block_offset + 2 112 let ext_end: i64 = ext_off + exts_len 113 var iter: i64 = 0 114 115 while ext_off < ext_end { 116 if iter >= NX_SAA_LOOP_BUDGET_CAP { return 0 - NX_SAA_LOOP_BUDGET } 117 iter = iter + 1 118 if ext_off + 4 > ext_end { return 0 - NX_SAA_TRUNCATED } 119 120 let ext_type: i64 = ((ch_body[ext_off] as i64) << 8) 121 | (ch_body[ext_off + 1] as i64) 122 let ext_data_len: i64 = ((ch_body[ext_off + 2] as i64) << 8) 123 | (ch_body[ext_off + 3] as i64) 124 if ext_off + 4 + ext_data_len > ext_end { return 0 - NX_SAA_TRUNCATED } 125 126 if ext_type == NX_SAA_EXT_TYPE_SIGNATURE_ALGORITHMS { 127 result.found_sig_algs_ext = 1 128 // Parse the sig_alg list inside ext_data 129 let sa_off: i64 = ext_off + 4 130 if ext_data_len < 2 { return 0 - NX_SAA_TRUNCATED } 131 let list_len: i64 = ((ch_body[sa_off] as i64) << 8) 132 | (ch_body[sa_off + 1] as i64) 133 if sa_off + 2 + list_len > ext_off + 4 + ext_data_len { return 0 - NX_SAA_TRUNCATED } 134 if (list_len % 2) != 0 { return 0 - NX_SAA_TRUNCATED } 135 let n_schemes: i64 = list_len / 2 136 result.total_schemes_offered = n_schemes 137 138 var i: i64 = 0 139 while i < n_schemes { 140 if iter >= NX_SAA_LOOP_BUDGET_CAP { return 0 - NX_SAA_LOOP_BUDGET } 141 iter = iter + 1 142 let scheme: i64 = ((ch_body[sa_off + 2 + i * 2] as i64) << 8) 143 | (ch_body[sa_off + 2 + i * 2 + 1] as i64) 144 if scheme == NX_SAA_SCHEME_ECDSA_SECP256R1_SHA256 { 145 result.supports_ecdsa_p256 = 1 146 } 147 if scheme == NX_SAA_SCHEME_ED25519 { 148 result.supports_ed25519 = 1 149 } 150 i = i + 1 151 } 152 } 153 ext_off = ext_off + 4 + ext_data_len 154 } 155 156 if result.found_sig_algs_ext == 0 { return 0 - NX_SAA_NO_SIG_ALGS_EXT } 157 return NX_SAA_OK 158} 159 160// ===== Convenience: pick a sig alg from result ================================================= 161// 162// Returns the substrate-supported sig alg that BOTH: 163// 1. the client offered, AND 164// 2. the operator's cert key supports 165// 166// Returns NX_SAA_SCHEME_* on success; 0 if no overlap. 167// caller_cert_kind: 0 = ECDSA-only; 1 = Ed25519-only; 2 = either-OK 168// 169// Per RFC 8446 §4.4.3: if no overlap, server MUST send HANDSHAKE_FAILURE 170// alert. Caller handles that decision; this fn just reports. 171 172func nx_saa_pick_sig_alg(result: *NxSigAlgsAuditResult, 173 caller_cert_kind: i64) -> i64 { 174 if result.valid != 1 { return 0 } 175 if result.found_sig_algs_ext == 0 { return 0 } 176 // Cert kind 0 = ECDSA only 177 if caller_cert_kind == 0 { 178 if result.supports_ecdsa_p256 == 1 { return NX_SAA_SCHEME_ECDSA_SECP256R1_SHA256 } 179 return 0 180 } 181 // Cert kind 1 = Ed25519 only 182 if caller_cert_kind == 1 { 183 if result.supports_ed25519 == 1 { return NX_SAA_SCHEME_ED25519 } 184 return 0 185 } 186 // Cert kind 2 = either; prefer Ed25519 (modern; smaller; per RFC 8446 modernization) 187 if caller_cert_kind == 2 { 188 if result.supports_ed25519 == 1 { return NX_SAA_SCHEME_ED25519 } 189 if result.supports_ecdsa_p256 == 1 { return NX_SAA_SCHEME_ECDSA_SECP256R1_SHA256 } 190 return 0 191 } 192 return 0 193}