code wiki / (root) / nx_pem.nx

nx_pem.nx source

↩ module page · 190 lines · 6559 B

1// pem.nx -- RFC 7468 PEM (Privacy-Enhanced Mail) container parser. 2// 3// PEM is the ASCII-armor wrapper around binary DER content in X.509 4// certs, PKCS#8 private keys, SSH keys, cryptographic signatures. 5// 6// Format: 7// -----BEGIN <LABEL>----- 8// <base64-encoded DER, line-wrapped at 64 chars> 9// -----END <LABEL>----- 10// 11// Common labels: 12// CERTIFICATE (X.509 public cert) 13// PRIVATE KEY (PKCS#8 envelope, algorithm in ASN.1 inside) 14// PUBLIC KEY (SubjectPublicKeyInfo DER) 15// RSA PRIVATE KEY (PKCS#1; legacy, still used) 16// ENCRYPTED PRIVATE KEY (PKCS#8 with PBKDF2) 17// 18// API: 19// pem_decode(buf, len, out_der, out_cap) -> (label_off, label_len, 20// der_len) 21// Returns DER bytes written to out_der, or negative on error. 22// Caller supplies out_der sized >= 3/4 of base64 input. 23// 24// Invariants: 25// PEM1 Finds the FIRST BEGIN/END block in the input; callers 26// needing multiple blocks (cert chains) iterate by 27// advancing past the previous END line. 28// PEM2 Label must match between BEGIN and END (RFC 7468 ยง3); 29// mismatch returns a negative error. 30// PEM3 Whitespace inside the base64 body (newlines, spaces) 31// is tolerated and stripped before base64_decode. Other 32// non-alphabet bytes return an error. 33// PEM4 Output buffer bounds-checked; overrun returns negative. 34 35// nx_safety_envelope: 36// intended_use: AUTO_APPLIED -- primitive-specific tuning queued 37// sil_target: SIL1 38// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail] 39// verdict: NOT_YET_EVALUATED 40 41import "nx_syscalls.nx" 42import "nx_base64.nx" 43 44const PEM_ERR_NO_BEGIN: i64 = -1 45const PEM_ERR_NO_END: i64 = -2 46const PEM_ERR_LABEL_MISMATCH: i64 = -3 47const PEM_ERR_OVERFLOW: i64 = -4 48const PEM_ERR_BAD_BODY: i64 = -5 49 50// Find the literal "-----" pattern starting at pos. Returns 51// offset of the first '-', or -1 if not found. 52func pem_find_dashes(buf: *u8, len: i64, pos: i64) -> i64 { 53 var p: i64 = pos 54 while p + 4 < len { 55 if buf[p] == 0x2D { 56 if buf[p+1] == 0x2D { 57 if buf[p+2] == 0x2D { 58 if buf[p+3] == 0x2D { 59 if buf[p+4] == 0x2D { return p } 60 } 61 } 62 } 63 } 64 p = p + 1 65 } 66 return -1 67} 68 69// Match literal at buf[off..off+n] against cstring lit. 70func pem_match_lit(buf: *u8, off: i64, lit: *u8) -> i64 { 71 var i: i64 = 0 72 while lit[i] != 0 { 73 if buf[off + i] != lit[i] { return 0 } 74 i = i + 1 75 } 76 return i 77} 78 79// Result holder (avoids the &struct.field limitation). 80struct PemResult { 81 label_off: i64, 82 label_len: i64, 83 body_off: i64, 84 body_end: i64, 85} 86 87// Locate the first BEGIN/END block. Writes label + body offsets. 88// Does NOT decode base64 yet. Returns 0 on success or negative. 89func pem_locate(buf: *u8, len: i64, out: *PemResult) -> i64 { 90 let begin_pos: i64 = pem_find_dashes(buf, len, 0) 91 if begin_pos < 0 { return PEM_ERR_NO_BEGIN } 92 // Check for "-----BEGIN ". 93 let begin_lit: *u8 = "-----BEGIN " 94 if pem_match_lit(buf, begin_pos, begin_lit) == 0 { 95 return PEM_ERR_NO_BEGIN 96 } 97 // Label runs from after "-----BEGIN " to the next "-----". 98 let label_start: i64 = begin_pos + 11 99 let label_end_dashes: i64 = pem_find_dashes(buf, len, label_start) 100 if label_end_dashes < 0 { return PEM_ERR_NO_BEGIN } 101 out.label_off = label_start 102 out.label_len = label_end_dashes - label_start 103 104 // Body starts after the closing "-----" + any trailing CR/LF. 105 var body_start: i64 = label_end_dashes + 5 106 while body_start < len { 107 let b: i64 = buf[body_start] 108 if b == 0x0A { body_start = body_start + 1 } 109 else { 110 if b == 0x0D { body_start = body_start + 1 } 111 else { break } 112 } 113 } 114 // Body ends at "-----END <LABEL>-----". 115 let end_dashes: i64 = pem_find_dashes(buf, len, body_start) 116 if end_dashes < 0 { return PEM_ERR_NO_END } 117 // Verify "-----END ". 118 let end_lit: *u8 = "-----END " 119 if pem_match_lit(buf, end_dashes, end_lit) == 0 { return PEM_ERR_NO_END } 120 // Verify label matches. 121 var k: i64 = 0 122 while k < out.label_len { 123 if buf[end_dashes + 9 + k] != buf[out.label_off + k] { 124 return PEM_ERR_LABEL_MISMATCH 125 } 126 k = k + 1 127 } 128 out.body_off = body_start 129 out.body_end = end_dashes 130 return 0 131} 132 133// Full decode: locate + base64-decode the body into caller's 134// out_der buffer. Returns number of DER bytes produced, or 135// negative. Writes label info to label_off / label_len slots. 136func pem_decode(buf: *u8, len: i64, 137 out_der: *u8, out_cap: i64, 138 label_off_out: *i64, label_len_out: *i64) -> i64 { 139 let res_raw: *u8 = sys_mmap(64) 140 let res: *PemResult = res_raw as *PemResult 141 let rc: i64 = pem_locate(buf, len, res) 142 if rc < 0 { return rc } 143 *label_off_out = res.label_off 144 *label_len_out = res.label_len 145 146 // Copy body to a clean scratch with whitespace stripped, then 147 // base64-decode. Max output = body_len * 3 / 4. 148 let body_len: i64 = res.body_end - res.body_off 149 if body_len > out_cap * 2 { return PEM_ERR_OVERFLOW } // guard 150 let scratch: *u8 = sys_mmap(body_len + 16) 151 var si: i64 = 0 152 var bi: i64 = res.body_off 153 while bi < res.body_end { 154 let b: i64 = buf[bi] 155 if b != 0x0A { 156 if b != 0x0D { 157 if b != 0x20 { 158 if b != 0x09 { 159 scratch[si] = b 160 si = si + 1 161 } 162 } 163 } 164 } 165 bi = bi + 1 166 } 167 let n_der: i64 = b64_decode(scratch, si, out_der) 168 if n_der < 0 { return PEM_ERR_BAD_BODY } 169 return n_der 170} 171 172// Compile-only smoke: parse a stub PEM block. Input is: 173// -----BEGIN TEST----- 174// Zm9vYmFy 175// -----END TEST----- 176// (base64 of "foobar" = Zm9vYmFy, 6 byte output). 177func main() -> i64 { 178 let pem: *u8 = "-----BEGIN TEST-----\nZm9vYmFy\n-----END TEST-----\n" 179 var n: i64 = 0 180 while pem[n] != 0 { n = n + 1 } 181 182 let der: *u8 = sys_mmap(32) 183 let lo: *i64 = sys_mmap(16) as *i64 184 let ll: *i64 = sys_mmap(16) as *i64 185 let result: i64 = pem_decode(pem, n, der, 32, lo, ll) 186 if result != 6 { return 1 } 187 if der[0] != 0x66 { return 2 } // 'f' of "foobar" 188 if *ll != 4 { return 3 } // "TEST" 189 return 0 190}