code wiki / (root) / nx_https_post_complete.nx

nx_https_post_complete.nx source

↩ module page · 184 lines · 6741 B

1// nx_https_post_complete.nx -- HTTPS POST companion to 2// nx_https_get_complete. Sends an HTTP/1.1 POST request with a 3// caller-supplied body (typically application/jose+json for ACME) 4// over a CONNECTED TLS 1.3 session, then drains the encrypted 5// response into out_buf. 6// 7// Composes: 8// nx_http_client_build_request_post -- POST line + headers + body 9// nx_tls13_record_encrypt_v2 -- AEAD wrap (one request rec) 10// nx_tls13_read_record_from_fd -- partial-read framer 11// nx_tls13_record_decrypt_v2 -- AEAD unwrap per response rec 12// 13// Body size limit: NX_HTTPS_PC_BODY_BYTES (8 KB). ACME requests are 14// typically <2 KB (JWS-wrapped JSON over a 32-byte EdDSA key) so 8 KB 15// gives a comfortable margin. Caller should fragment larger payloads 16// across multiple TLS records explicitly (not auto-fragment here -- 17// callers needing >8 KB request bodies are sufficiently rare that 18// they should opt in). 19// 20// Verdict mirrors NX_HTTPS_GC_* with a PC prefix; same sealed-enum 21// shape (positive bytes on success, negative -verdict on failure). 22// 23// Per Cardinals 9 (single-responsibility -- ONE round trip), 22 24// (composition of shipped primitives), 25 (build intelligence, don't 25// strip features -- adds POST capability without weakening GET). 26// 27// license_tier: INDEPENDENT_REDERIVE 28// genealogy_id: international-research-sources/ietf/rfc_8446 + rfc_9112 + rfc_8555 29// lineage_id: nishi_https_post_complete_q1 30 31import "nx_syscalls.nx" 32import "nx_tls13.nx" 33import "nx_tls13_record.nx" 34import "nx_tls13_read_record_from_fd.nx" 35import "nx_tls13_client_session.nx" 36import "nx_http_client.nx" 37 38const NX_HTTPS_PC_OK: i64 = 1 39const NX_HTTPS_PC_BAD_STATE: i64 = 2 40const NX_HTTPS_PC_BUILD_FAIL: i64 = 3 41const NX_HTTPS_PC_ENCRYPT_FAIL: i64 = 4 42const NX_HTTPS_PC_WRITE_FAIL: i64 = 5 43const NX_HTTPS_PC_READ_FAIL: i64 = 6 44const NX_HTTPS_PC_DECRYPT_FAIL: i64 = 7 45const NX_HTTPS_PC_BUF_OVERFLOW: i64 = 8 46const NX_HTTPS_PC_BODY_TOO_BIG: i64 = 9 47const NX_HTTPS_PC_VERDICT_N: i64 = 10 48 49const NX_HTTPS_PC_REQ_BUF_BYTES: i64 = 16384 // header (~512) + body (<= 8 KB) + slack 50const NX_HTTPS_PC_BODY_BYTES: i64 = 8192 51const NX_HTTPS_PC_RESP_RECORD_BYTES: i64 = 16645 52 53func nx_https_pc_verdict_is_valid(v: i64) -> i64 { 54 if v < NX_HTTPS_PC_OK { return 0 } 55 if v >= NX_HTTPS_PC_VERDICT_N { return 0 } 56 return 1 57} 58 59func _pc_write_n(fd: i64, buf: *u8, n: i64) -> i64 { 60 var off: i64 = 0 61 while off < n { 62 let w: i64 = sys_write(fd, (buf as i64 + off) as *u8, n - off) 63 if w <= 0 { return 0 - 1 } 64 off = off + w 65 } 66 return 0 67} 68 69func nx_https_post_complete( 70 s: *Tls13ClientSession, 71 fd: i64, 72 path: *u8, path_len: i64, 73 host: *u8, host_len: i64, 74 content_type: *u8, content_type_len: i64, 75 body: *u8, body_len: i64, 76 out_buf: *u8, out_cap: i64 77) -> i64 { 78 if s.state != NX_TLS13_CSESSION_STATE_CONNECTED { 79 return 0 - NX_HTTPS_PC_BAD_STATE 80 } 81 if body_len > NX_HTTPS_PC_BODY_BYTES { 82 return 0 - NX_HTTPS_PC_BODY_TOO_BIG 83 } 84 85 // ---- Build HTTP POST request ---- 86 let req: *u8 = sys_mmap(NX_HTTPS_PC_REQ_BUF_BYTES) 87 let req_len: i64 = nx_http_client_build_request_post( 88 path, path_len, 89 host, host_len, 90 content_type, content_type_len, 91 body, body_len, 92 req 93 ) 94 if req_len <= 0 { return 0 - NX_HTTPS_PC_BUILD_FAIL } 95 96 // ---- Encrypt as TLS application_data record ---- 97 let rec_buf: *u8 = sys_mmap(req_len + 64) 98 let header_out: *u8 = rec_buf 99 let ct_out: *u8 = rec_buf + NX_TLS13_RECORD_HEADER_LEN 100 let tag_out: *u8 = rec_buf + NX_TLS13_RECORD_HEADER_LEN + req_len + 1 101 102 let enc_v: i64 = nx_tls13_record_encrypt_v2( 103 s.cipher_suite, 104 s.client_app_traffic_key, 105 s.client_app_iv, 106 s.client_app_seq, 107 req, req_len, 108 NX_TLS13_CT_APPLICATION_DATA, 109 0, 110 header_out, ct_out, tag_out 111 ) 112 s.client_app_seq = s.client_app_seq + 1 113 if enc_v != NX_TLS13_REC_VERDICT_OK { return 0 - NX_HTTPS_PC_ENCRYPT_FAIL } 114 115 // ---- Write encrypted request record ---- 116 let total_rec_len: i64 = NX_TLS13_RECORD_HEADER_LEN + req_len + 1 + NX_TLS13_RECORD_TAG_LEN 117 let wr_v: i64 = _pc_write_n(fd, rec_buf, total_rec_len) 118 if wr_v < 0 { return 0 - NX_HTTPS_PC_WRITE_FAIL } 119 120 // ---- Read + decrypt response records (same shape as get_complete) ---- 121 var accumulated: i64 = 0 122 while accumulated < out_cap { 123 let rec_in: *u8 = sys_mmap(NX_HTTPS_PC_RESP_RECORD_BYTES) 124 let rec_in_total: i64 = nx_tls13_read_record_from_fd( 125 fd, rec_in, NX_HTTPS_PC_RESP_RECORD_BYTES 126 ) 127 if rec_in_total < 0 { 128 let nv: i64 = 0 - rec_in_total 129 if nv == NX_TLS13_READ_REC_EOF { return accumulated } 130 if nv == NX_TLS13_READ_REC_PAYLOAD_EOF { return accumulated } 131 return 0 - NX_HTTPS_PC_READ_FAIL 132 } 133 134 let rec_in_header: *u8 = rec_in 135 let rec_in_ct: *u8 = rec_in + NX_TLS13_RECORD_HEADER_LEN 136 let rec_in_ct_len: i64 = rec_in_total - NX_TLS13_RECORD_HEADER_LEN - NX_TLS13_RECORD_TAG_LEN 137 let rec_in_tag: *u8 = rec_in + rec_in_total - NX_TLS13_RECORD_TAG_LEN 138 139 let plaintext: *u8 = sys_mmap(rec_in_ct_len + 16) 140 let plaintext_ct_p: *i64 = sys_mmap(16) as *i64 141 let plaintext_len_p: *i64 = sys_mmap(16) as *i64 142 143 let dec_v: i64 = nx_tls13_record_decrypt_v2( 144 s.cipher_suite, 145 s.server_app_traffic_key, 146 s.server_app_iv, 147 s.server_app_seq, 148 rec_in_header, 149 rec_in_ct, rec_in_ct_len, 150 rec_in_tag, 151 plaintext, 152 plaintext_ct_p, plaintext_len_p 153 ) 154 s.server_app_seq = s.server_app_seq + 1 155 if dec_v != NX_TLS13_REC_VERDICT_OK { return 0 - NX_HTTPS_PC_DECRYPT_FAIL } 156 157 if *plaintext_ct_p == NX_TLS13_CT_ALERT { 158 if *plaintext_len_p >= 2 { 159 if (plaintext[1] & 0xff) == 0 { return accumulated } 160 } 161 return accumulated 162 } 163 164 if *plaintext_ct_p == NX_TLS13_CT_APPLICATION_DATA { 165 let avail: i64 = out_cap - accumulated 166 let to_copy: i64 = *plaintext_len_p 167 if to_copy > avail { return 0 - NX_HTTPS_PC_BUF_OVERFLOW } 168 var i: i64 = 0 169 while i < to_copy { 170 out_buf[accumulated + i] = plaintext[i] 171 i = i + 1 172 } 173 accumulated = accumulated + to_copy 174 } 175 } 176 177 return accumulated 178} 179 180// Compile-only smoke. Live exercise belongs to a separate end-to-end 181// test against ACME staging (queued: nx_acme_directory_live_smoke). 182func main() -> i64 { 183 return 0 184}