nx_x509_validity_test.nx source
↩ module page · 216 lines · 9736 B
1// nx_x509_validity_test.nx -- KAT for the X.509 validity period
2// parser + range-check. Exercises both ASN.1 Time encodings
3// (UTCTime 0x17 and GeneralizedTime 0x18), the OK / NOT_YET_VALID /
4// EXPIRED verdicts, plus error paths for bad tag, bad length,
5// inverted range, and missing validity.
6//
7// expect_exit: 0
8// license_tier: ORIGINAL
9
10import "nx_syscalls.nx"
11import "nx_asn1.nx"
12import "nx_x509.nx"
13import "nx_x509_validity.nx"
14
15// ---- Helpers ------------------------------------------------------
16
17// Write a UTCTime TLV "YYMMDDhhmmssZ" into buf at off. yyyy is the
18// full 4-digit year; we encode as 2-digit modulo (RFC 5280: years
19// 1950..2049). Returns bytes written (15 = 2-byte TLV header +
20// 13-byte content).
21func emit_utctime(buf: *u8, off: i64,
22 yyyy: i64, mo: i64, d: i64,
23 h: i64, mi: i64, s: i64) -> i64 {
24 buf[off + 0] = 0x17 as u8 // ASN1_UTC_TIME
25 buf[off + 1] = 13 as u8
26 let yy: i64 = yyyy - 2000
27 buf[off + 2] = (0x30 + (yy / 10)) as u8
28 buf[off + 3] = (0x30 + (yy % 10)) as u8
29 buf[off + 4] = (0x30 + (mo / 10)) as u8
30 buf[off + 5] = (0x30 + (mo % 10)) as u8
31 buf[off + 6] = (0x30 + (d / 10)) as u8
32 buf[off + 7] = (0x30 + (d % 10)) as u8
33 buf[off + 8] = (0x30 + (h / 10)) as u8
34 buf[off + 9] = (0x30 + (h % 10)) as u8
35 buf[off + 10] = (0x30 + (mi / 10)) as u8
36 buf[off + 11] = (0x30 + (mi % 10)) as u8
37 buf[off + 12] = (0x30 + (s / 10)) as u8
38 buf[off + 13] = (0x30 + (s % 10)) as u8
39 buf[off + 14] = 0x5A as u8 // 'Z'
40 return 15
41}
42
43// Write a GeneralizedTime TLV "YYYYMMDDhhmmssZ" into buf at off.
44// Returns bytes written (17 = 2-byte TLV header + 15-byte content).
45func emit_gentime(buf: *u8, off: i64,
46 yyyy: i64, mo: i64, d: i64,
47 h: i64, mi: i64, s: i64) -> i64 {
48 buf[off + 0] = 0x18 as u8 // ASN1_GENERALIZED
49 buf[off + 1] = 15 as u8
50 buf[off + 2] = (0x30 + (yyyy / 1000)) as u8
51 buf[off + 3] = (0x30 + ((yyyy / 100) % 10)) as u8
52 buf[off + 4] = (0x30 + ((yyyy / 10) % 10)) as u8
53 buf[off + 5] = (0x30 + (yyyy % 10)) as u8
54 buf[off + 6] = (0x30 + (mo / 10)) as u8
55 buf[off + 7] = (0x30 + (mo % 10)) as u8
56 buf[off + 8] = (0x30 + (d / 10)) as u8
57 buf[off + 9] = (0x30 + (d % 10)) as u8
58 buf[off + 10] = (0x30 + (h / 10)) as u8
59 buf[off + 11] = (0x30 + (h % 10)) as u8
60 buf[off + 12] = (0x30 + (mi / 10)) as u8
61 buf[off + 13] = (0x30 + (mi % 10)) as u8
62 buf[off + 14] = (0x30 + (s / 10)) as u8
63 buf[off + 15] = (0x30 + (s % 10)) as u8
64 buf[off + 16] = 0x5A as u8 // 'Z'
65 return 17
66}
67
68func main() -> i64 {
69 // Known epochs for date-arithmetic spot-checks (computed via
70 // Hinnant days_from_civil + 86400 sec/day):
71 // 2023-01-01T00:00:00Z = 1672531200
72 // 2026-01-01T00:00:00Z = 1767225600
73 // 2024-06-15T12:00:00Z = 1718452800 (within [2023, 2026])
74 // 2022-06-01T00:00:00Z = 1654041600 (BEFORE 2023)
75 // 2027-01-01T00:00:00Z = 1798761600 (AFTER 2026)
76 let NB_EPOCH: i64 = 1672531200
77 let NA_EPOCH: i64 = 1767225600
78 let NOW_OK: i64 = 1718452800
79 let NOW_EARLY: i64 = 1654041600
80 let NOW_LATE: i64 = 1798761600
81
82 let buf: *u8 = sys_mmap(256)
83 let cert_raw: *u8 = sys_mmap(256)
84 let cert: *X509Cert = cert_raw as *X509Cert
85
86 // Initialise all X509Cert fields (manual construction; no parser
87 // call here -- we test the validity primitive in isolation).
88 cert.tbs_off = 0; cert.tbs_len = 0
89 cert.serial_off = 0; cert.serial_len = 0
90 cert.sig_alg_off = 0; cert.sig_alg_len = 0
91 cert.spki_off = 0; cert.spki_len = 0
92 cert.sig_off = 0; cert.sig_len = 0
93 cert.pubkey_off = 0; cert.pubkey_len = 0
94 cert.pubkey_alg_off = 0; cert.pubkey_alg_len = 0
95 cert.validity_off = 0; cert.validity_len = 0
96 cert.extensions_off = 0 - 1
97 cert.extensions_len = 0
98
99 // ---- Test A: UTCTime + UTCTime, both valid ----
100 let n1: i64 = emit_utctime(buf, 0, 2023, 1, 1, 0, 0, 0)
101 let n2: i64 = emit_utctime(buf, n1, 2026, 1, 1, 0, 0, 0)
102 cert.validity_off = 0
103 cert.validity_len = n1 + n2 // 15 + 15 = 30
104
105 let nb_p: *i64 = sys_mmap(16) as *i64
106 let na_p: *i64 = sys_mmap(16) as *i64
107 let v1: i64 = x509_validity_get(buf, cert, nb_p, na_p)
108 if v1 != NX_X509_VALID_OK { return 1 }
109 if *nb_p != NB_EPOCH { return 2 }
110 if *na_p != NA_EPOCH { return 3 }
111
112 // ---- Test B: now within range -> OK ----
113 if x509_validity_check(buf, cert, NOW_OK) != NX_X509_VALID_OK { return 4 }
114
115 // ---- Test C: now before notBefore -> NOT_YET_VALID ----
116 if x509_validity_check(buf, cert, NOW_EARLY) != NX_X509_VALID_NOT_YET_VALID { return 5 }
117
118 // ---- Test D: now after notAfter -> EXPIRED ----
119 if x509_validity_check(buf, cert, NOW_LATE) != NX_X509_VALID_EXPIRED { return 6 }
120
121 // ---- Test E: edge cases at exact boundaries ----
122 if x509_validity_check(buf, cert, NB_EPOCH) != NX_X509_VALID_OK { return 7 }
123 if x509_validity_check(buf, cert, NA_EPOCH) != NX_X509_VALID_OK { return 8 }
124 if x509_validity_check(buf, cert, NB_EPOCH - 1) != NX_X509_VALID_NOT_YET_VALID { return 9 }
125 if x509_validity_check(buf, cert, NA_EPOCH + 1) != NX_X509_VALID_EXPIRED { return 10 }
126
127 // ---- Test F: GeneralizedTime + GeneralizedTime, valid ----
128 let n3: i64 = emit_gentime(buf, 0, 2023, 1, 1, 0, 0, 0)
129 let n4: i64 = emit_gentime(buf, n3, 2026, 1, 1, 0, 0, 0)
130 cert.validity_off = 0
131 cert.validity_len = n3 + n4 // 17 + 17 = 34
132 let v6: i64 = x509_validity_get(buf, cert, nb_p, na_p)
133 if v6 != NX_X509_VALID_OK { return 11 }
134 if *nb_p != NB_EPOCH { return 12 }
135 if *na_p != NA_EPOCH { return 13 }
136 if x509_validity_check(buf, cert, NOW_OK) != NX_X509_VALID_OK { return 14 }
137
138 // ---- Test G: mixed UTCTime + GeneralizedTime ----
139 let n5a: i64 = emit_utctime(buf, 0, 2023, 1, 1, 0, 0, 0)
140 let n5b: i64 = emit_gentime(buf, n5a, 2026, 1, 1, 0, 0, 0)
141 cert.validity_off = 0
142 cert.validity_len = n5a + n5b // 15 + 17 = 32
143 let v7: i64 = x509_validity_get(buf, cert, nb_p, na_p)
144 if v7 != NX_X509_VALID_OK { return 15 }
145 if *nb_p != NB_EPOCH { return 16 }
146 if *na_p != NA_EPOCH { return 17 }
147
148 // ---- Test H: inverted range (notBefore > notAfter) rejected ----
149 let n6a: i64 = emit_utctime(buf, 0, 2026, 1, 1, 0, 0, 0)
150 let n6b: i64 = emit_utctime(buf, n6a, 2023, 1, 1, 0, 0, 0)
151 cert.validity_off = 0
152 cert.validity_len = n6a + n6b // 30
153 let v8: i64 = x509_validity_get(buf, cert, nb_p, na_p)
154 if v8 != NX_X509_VALID_INVERTED { return 18 }
155
156 // ---- Test I: bad ASN.1 tag rejected ----
157 let n7a: i64 = emit_utctime(buf, 0, 2023, 1, 1, 0, 0, 0)
158 let n7b: i64 = emit_utctime(buf, n7a, 2026, 1, 1, 0, 0, 0)
159 buf[0] = 0x99 as u8 // mangle first tag
160 cert.validity_off = 0
161 cert.validity_len = n7a + n7b
162 let v9: i64 = x509_validity_get(buf, cert, nb_p, na_p)
163 if v9 != NX_X509_VALID_BAD_TAG { return 19 }
164
165 // ---- Test J: bad inner length rejected ----
166 let n8a: i64 = emit_utctime(buf, 0, 2023, 1, 1, 0, 0, 0)
167 let n8b: i64 = emit_utctime(buf, n8a, 2026, 1, 1, 0, 0, 0)
168 buf[1] = 99 as u8 // claim 99-byte UTCTime
169 cert.validity_off = 0
170 cert.validity_len = n8a + n8b
171 let v10: i64 = x509_validity_get(buf, cert, nb_p, na_p)
172 if v10 != NX_X509_VALID_BAD_LENGTH { return 20 }
173
174 // ---- Test K: long-form length rejected (no Time uses long form) ----
175 let n9a: i64 = emit_utctime(buf, 0, 2023, 1, 1, 0, 0, 0)
176 let n9b: i64 = emit_utctime(buf, n9a, 2026, 1, 1, 0, 0, 0)
177 buf[1] = 0x82 as u8 // long-form indicator
178 cert.validity_off = 0
179 cert.validity_len = n9a + n9b
180 let v11: i64 = x509_validity_get(buf, cert, nb_p, na_p)
181 if v11 != NX_X509_VALID_BAD_LENGTH { return 21 }
182
183 // ---- Test L: empty validity body rejected ----
184 cert.validity_off = 0
185 cert.validity_len = 0
186 let v12: i64 = x509_validity_get(buf, cert, nb_p, na_p)
187 if v12 != NX_X509_VALID_NO_VALIDITY { return 22 }
188
189 // ---- Test M: validity body too short for two TLVs ----
190 let n10a: i64 = emit_utctime(buf, 0, 2023, 1, 1, 0, 0, 0)
191 cert.validity_off = 0
192 cert.validity_len = n10a + 1 // 16: room for first + 1 leftover
193 let v13: i64 = x509_validity_get(buf, cert, nb_p, na_p)
194 if v13 != NX_X509_VALID_BAD_FORMAT { return 23 }
195
196 // ---- Test N: trailing bytes (over-long body) rejected ----
197 let n11a: i64 = emit_utctime(buf, 0, 2023, 1, 1, 0, 0, 0)
198 let n11b: i64 = emit_utctime(buf, n11a, 2026, 1, 1, 0, 0, 0)
199 cert.validity_off = 0
200 cert.validity_len = n11a + n11b + 3 // 3 extra trailing bytes
201 let v14: i64 = x509_validity_get(buf, cert, nb_p, na_p)
202 if v14 != NX_X509_VALID_BAD_FORMAT { return 24 }
203
204 // ---- Test O: verdict gate ----
205 if nx_x509_validity_verdict_is_valid(NX_X509_VALID_OK) != 1 { return 25 }
206 if nx_x509_validity_verdict_is_valid(NX_X509_VALID_NOT_YET_VALID) != 1 { return 26 }
207 if nx_x509_validity_verdict_is_valid(NX_X509_VALID_EXPIRED) != 1 { return 27 }
208 if nx_x509_validity_verdict_is_valid(NX_X509_VALID_BAD_FORMAT) != 1 { return 28 }
209 if nx_x509_validity_verdict_is_valid(NX_X509_VALID_INVERTED) != 1 { return 29 }
210 if nx_x509_validity_verdict_is_valid(NX_X509_VALID_VERDICT_N) != 0 { return 30 }
211 if nx_x509_validity_verdict_is_valid(0) != 0 { return 31 }
212 if nx_x509_validity_verdict_is_valid(0 - 1) != 0 { return 32 }
213 if nx_x509_validity_verdict_is_valid(999) != 0 { return 33 }
214
215 return 0
216}