Line data Source code
1 : #include "fd_x509_verify.h"
2 : #include "fd_der.h"
3 : #include "../ed25519/fd_ed25519.h"
4 : #include "../secp256r1/fd_secp256r1.h"
5 : #include "../secp384r1/fd_secp384r1.h"
6 : #include <string.h>
7 :
8 : /* fd_x509_verify_sig verifies a certificate's signature given the
9 : issuer's public key. Returns 0 on success, non-zero on failure. */
10 :
11 : static int
12 : fd_x509_verify_sig( fd_x509_cert_info_t const * cert,
13 : uchar const * issuer_pubkey,
14 : ulong issuer_pubkey_len,
15 0 : uchar issuer_key_type ) {
16 :
17 0 : switch( cert->sig_alg ) {
18 :
19 0 : case FD_X509_SIG_ED25519: {
20 0 : if( FD_UNLIKELY( issuer_key_type != FD_X509_KEY_ED25519 ) ) return -1;
21 0 : if( FD_UNLIKELY( issuer_pubkey_len != 32 ) ) return -1;
22 0 : if( FD_UNLIKELY( cert->sig_len != 64 ) ) return -1;
23 :
24 0 : fd_sha512_t sha512[1];
25 0 : int err = fd_ed25519_verify( cert->tbs, cert->tbs_len, cert->sig, issuer_pubkey, sha512 );
26 0 : return ( err == FD_ED25519_SUCCESS ) ? 0 : -1;
27 0 : }
28 :
29 0 : case FD_X509_SIG_ECDSA_SHA256: {
30 0 : if( FD_UNLIKELY( issuer_key_type != FD_X509_KEY_ECDSA_P256 ) ) return -1;
31 0 : if( FD_UNLIKELY( issuer_pubkey_len != 65 ) ) return -1;
32 :
33 0 : uchar raw_sig[64];
34 0 : if( FD_UNLIKELY( fd_x509_decode_ecdsa_sig( cert->sig, cert->sig_len, raw_sig, 32 ) ) )
35 0 : return -1;
36 :
37 0 : uchar compressed_pk[33];
38 0 : if( FD_UNLIKELY( fd_x509_ec_point_compress( issuer_pubkey, 32, compressed_pk ) ) )
39 0 : return -1;
40 :
41 0 : fd_sha256_t sha256[1];
42 0 : int err = fd_secp256r1_verify_allow_high_s( cert->tbs, cert->tbs_len, raw_sig, compressed_pk, sha256 );
43 0 : return ( err == FD_SECP256R1_SUCCESS ) ? 0 : -1;
44 0 : }
45 :
46 0 : case FD_X509_SIG_ECDSA_SHA384: {
47 0 : if( FD_UNLIKELY( issuer_key_type != FD_X509_KEY_ECDSA_P384 ) ) return -1;
48 0 : if( FD_UNLIKELY( issuer_pubkey_len != 97 ) ) return -1;
49 :
50 0 : uchar raw_sig[96];
51 0 : if( FD_UNLIKELY( fd_x509_decode_ecdsa_sig( cert->sig, cert->sig_len, raw_sig, 48 ) ) )
52 0 : return -1;
53 :
54 0 : uchar compressed_pk[49];
55 0 : if( FD_UNLIKELY( fd_x509_ec_point_compress( issuer_pubkey, 48, compressed_pk ) ) )
56 0 : return -1;
57 :
58 0 : fd_sha512_t sha384[1];
59 0 : int err = fd_secp384r1_verify_allow_high_s( cert->tbs, cert->tbs_len, raw_sig, compressed_pk, sha384 );
60 0 : return ( err == FD_SECP384R1_SUCCESS ) ? 0 : -1;
61 0 : }
62 :
63 0 : case FD_X509_SIG_RSA_SHA256:
64 0 : case FD_X509_SIG_RSA_SHA384:
65 0 : case FD_X509_SIG_RSA_SHA512: {
66 : #if !FD_HAS_INT128
67 : return 1; /* no fd_rsa */
68 : #else
69 0 : if( FD_UNLIKELY( issuer_key_type != FD_X509_KEY_RSA ) ) return -1;
70 :
71 0 : fd_rsa_pubkey_t key[1];
72 0 : if( FD_UNLIKELY( fd_x509_decode_rsa_pubkey( issuer_pubkey, issuer_pubkey_len, key ) ) )
73 0 : return -1;
74 :
75 0 : int hash = cert->sig_alg==FD_X509_SIG_RSA_SHA256 ? FD_RSA_HASH_SHA256 :
76 0 : cert->sig_alg==FD_X509_SIG_RSA_SHA384 ? FD_RSA_HASH_SHA384 :
77 0 : FD_RSA_HASH_SHA512;
78 0 : int err = fd_rsa_verify_pkcs1_v15( key, cert->sig, cert->sig_len, cert->tbs, cert->tbs_len, hash );
79 0 : return ( err == FD_RSA_SUCCESS ) ? 0 : -1;
80 0 : #endif
81 0 : }
82 :
83 0 : default:
84 0 : return 1; /* unsupported sig algorithm */
85 0 : }
86 0 : }
87 :
88 : /* fd_x509_check_validity returns FD_X509_VERIFY_OK if cert is within its
89 : validity period at unix_seconds, otherwise the error to report. */
90 :
91 : static int
92 : fd_x509_check_validity( fd_x509_cert_info_t const * cert,
93 0 : long unix_seconds ) {
94 0 : if( FD_UNLIKELY( cert->not_before_unix==FD_X509_TIME_INVALID ||
95 0 : cert->not_after_unix ==FD_X509_TIME_INVALID ) )
96 0 : return FD_X509_VERIFY_ERR_TIME_PARSE;
97 0 : if( FD_UNLIKELY( unix_seconds < cert->not_before_unix ) )
98 0 : return FD_X509_VERIFY_ERR_NOT_YET_VALID;
99 0 : if( FD_UNLIKELY( unix_seconds > cert->not_after_unix ) )
100 0 : return FD_X509_VERIFY_ERR_EXPIRED;
101 0 : return FD_X509_VERIFY_OK;
102 0 : }
103 :
104 : /* fd_x509_check_eku returns OK if cert may be used for TLS server
105 : authentication. An absent extKeyUsage is unconstrained. */
106 :
107 : static int
108 0 : fd_x509_check_eku( fd_x509_cert_info_t const * cert ) {
109 0 : if( FD_UNLIKELY(
110 0 : cert->has_ext_key_usage &&
111 0 : !( cert->ext_key_usage & ( FD_X509_EKU_SERVER_AUTH|FD_X509_EKU_ANY ) ) ) ) {
112 0 : return FD_X509_VERIFY_ERR_EXT_KEY_USAGE;
113 0 : }
114 0 : return FD_X509_VERIFY_OK;
115 0 : }
116 :
117 : /* fd_x509_check_leaf_usage enforces the TLS 1.3 server authentication
118 : usage policy on the leaf. */
119 :
120 : static int
121 0 : fd_x509_check_leaf_usage( fd_x509_cert_info_t const * leaf ) {
122 0 : if( FD_UNLIKELY(
123 0 : leaf->has_key_usage &&
124 0 : !( leaf->key_usage & FD_X509_KU_DIGITAL_SIGNATURE ) ) ) {
125 0 : return FD_X509_VERIFY_ERR_KEY_USAGE;
126 0 : }
127 0 : return fd_x509_check_eku( leaf );
128 0 : }
129 :
130 :
131 : static int
132 : fd_x509_dns_constraint_matches( uchar const * constraint,
133 : ulong constraint_len,
134 : uchar const * name,
135 : ulong name_len,
136 0 : int excluded ) {
137 : /* Excluded subtrees reject any overlapping wildcard expansion. */
138 0 : if( excluded && name_len>2UL && name[0]=='*' && name[1]=='.' ) {
139 0 : uchar const * dot = memchr( constraint, '.', constraint_len );
140 0 : if( dot ) {
141 0 : ulong tail_len = constraint_len-(ulong)( dot+1-constraint );
142 0 : if( tail_len==name_len-2UL &&
143 0 : fd_x509_dns_eq_ci( (char const *)name+2, (char const *)dot+1, tail_len ) ) return 1;
144 0 : }
145 0 : }
146 :
147 0 : int subdomains_only = constraint[0]=='.';
148 0 : if( subdomains_only ) {
149 0 : if( name_len<=constraint_len ) return 0;
150 0 : return fd_x509_dns_eq_ci( (char const *)name+name_len-constraint_len, (char const *)constraint, constraint_len );
151 0 : }
152 :
153 0 : if( name_len<constraint_len ) return 0;
154 0 : if( !fd_x509_dns_eq_ci( (char const *)name+name_len-constraint_len, (char const *)constraint, constraint_len ) ) return 0;
155 0 : return name_len==constraint_len || name[name_len-constraint_len-1UL]=='.';
156 0 : }
157 :
158 : /* iPAddress subtree base is address||mask (8 or 32 bytes). */
159 :
160 : static int
161 : fd_x509_ip_constraint_matches( uchar const * constraint,
162 : ulong constraint_len,
163 : uchar const * ip,
164 0 : ulong ip_len ) {
165 0 : if( constraint_len!=2UL*ip_len ) return 0;
166 0 : uchar const * mask = constraint+ip_len;
167 0 : for( ulong i=0UL; i<ip_len; i++ ) {
168 0 : if( (ip[i] & mask[i]) != (constraint[i] & mask[i]) ) return 0;
169 0 : }
170 0 : return 1;
171 0 : }
172 :
173 : /* fd_x509_subtrees_match matches name against the subtrees of the same
174 : name form (tag). Returns:
175 : 1 a subtree matches
176 : 0 subtrees of this form exist, none match
177 : -1 no subtree of this form (form is unconstrained, RFC 5280 4.2.1.10)
178 : -2 malformed, or a subtree of a form this verifier cannot match */
179 :
180 : static int
181 : fd_x509_subtrees_match( uchar const * trees,
182 : ulong trees_len,
183 : int tag,
184 : uchar const * name,
185 : ulong name_len,
186 0 : int excluded ) {
187 0 : int found = 0;
188 0 : fd_der_cursor_t c = { .p=trees, .end=trees+trees_len };
189 0 : while( FD_DER_HAS_MORE( c ) ) {
190 0 : int tree_tag; ulong tree_len;
191 0 : if( FD_UNLIKELY( fd_der_read_tl( &c, &tree_tag, &tree_len ) ||
192 0 : tree_tag!=(int)FD_DER_TAG_SEQUENCE ) ) return -2;
193 0 : fd_der_cursor_t t = { .p=c.p, .end=c.p+tree_len };
194 0 : c.p += tree_len;
195 0 : int base_tag; ulong base_len;
196 0 : if( FD_UNLIKELY( fd_der_read_tl( &t, &base_tag, &base_len ) ) ) return -2;
197 0 : if( base_tag!=tag ) continue;
198 0 : found = 1;
199 0 : int match;
200 0 : switch( tag ) {
201 0 : case FD_DER_TAG_CONTEXT_PRIM(2):
202 0 : match = fd_x509_dns_constraint_matches( t.p, base_len, name, name_len, excluded );
203 0 : break;
204 0 : case FD_DER_TAG_CONTEXT_PRIM(7):
205 0 : match = fd_x509_ip_constraint_matches( t.p, base_len, name, name_len );
206 0 : break;
207 0 : case FD_DER_TAG_CONTEXT(4):
208 0 : match = fd_x509_name_prefix( t.p, base_len, name, name_len );
209 0 : break;
210 0 : default:
211 : /* nameConstraints is critical: a form we cannot match must not
212 : be accepted (RFC 5280 Section 4.2). */
213 0 : return -2;
214 0 : }
215 0 : if( match ) return 1;
216 0 : }
217 0 : return found ? 0 : -1;
218 0 : }
219 :
220 : /* fd_x509_name_constrained checks one name against a CA's permitted
221 : and excluded GeneralSubtrees. */
222 :
223 : static int
224 : fd_x509_name_constrained( uchar const * permitted,
225 : ulong permitted_len,
226 : uchar const * excluded,
227 : ulong excluded_len,
228 : int tag,
229 : uchar const * name,
230 0 : ulong name_len ) {
231 0 : if( excluded_len ) {
232 0 : int match = fd_x509_subtrees_match( excluded, excluded_len, tag, name, name_len, 1 );
233 0 : if( match==1 || match==-2 ) return FD_X509_VERIFY_ERR_NAME_CONSTRAINT;
234 0 : }
235 0 : if( permitted_len ) {
236 0 : int match = fd_x509_subtrees_match( permitted, permitted_len, tag, name, name_len, 0 );
237 0 : if( match==0 || match==-2 ) return FD_X509_VERIFY_ERR_NAME_CONSTRAINT;
238 0 : }
239 0 : return FD_X509_VERIFY_OK;
240 0 : }
241 :
242 : /* fd_x509_check_name_constraints checks the subject DN (against
243 : directoryName subtrees, RFC 5280 Section 6.1.4 (g)) and each SAN of
244 : cert against a CA's permitted and excluded GeneralSubtrees. An
245 : empty subject is skipped, as in OpenSSL and BoringSSL. Subject
246 : emailAddress attributes are not checked against rfc822Name
247 : subtrees. */
248 :
249 : static int
250 : fd_x509_check_name_constraints( int has_name_constraints,
251 : uchar const * permitted,
252 : ulong permitted_len,
253 : uchar const * excluded,
254 : ulong excluded_len,
255 0 : fd_x509_cert_info_t const * cert ) {
256 0 : if( !has_name_constraints ) return FD_X509_VERIFY_OK;
257 :
258 0 : if( cert->subject_len>2UL ) {
259 0 : int err = fd_x509_name_constrained( permitted, permitted_len, excluded, excluded_len,
260 0 : FD_DER_TAG_CONTEXT(4), cert->subject, cert->subject_len );
261 0 : if( FD_UNLIKELY( err ) ) return err;
262 0 : }
263 :
264 0 : if( !cert->has_subject_alt_name ) return FD_X509_VERIFY_OK;
265 :
266 0 : fd_der_cursor_t san = { .p=cert->san_general_names,
267 0 : .end=cert->san_general_names+cert->san_general_names_len };
268 0 : while( FD_DER_HAS_MORE( san ) ) {
269 0 : int tag; ulong name_len;
270 0 : if( FD_UNLIKELY( fd_der_read_tl( &san, &tag, &name_len ) ) )
271 0 : return FD_X509_VERIFY_ERR_NAME_CONSTRAINT;
272 0 : uchar const * name = san.p;
273 0 : san.p += name_len;
274 :
275 0 : if( tag==(int)FD_DER_TAG_CONTEXT_PRIM(2) ) {
276 0 : int wildcard = name_len>2UL && name[0]=='*' && name[1]=='.';
277 0 : if( FD_UNLIKELY( wildcard ? !fd_x509_dns_name_valid( (char const *)name+2, name_len-2UL )
278 0 : : !fd_x509_dns_name_valid( (char const *)name, name_len ) ) )
279 0 : return FD_X509_VERIFY_ERR_NAME_CONSTRAINT;
280 0 : } else if( tag==(int)FD_DER_TAG_CONTEXT_PRIM(7) ) {
281 0 : if( FD_UNLIKELY( name_len!=4UL && name_len!=16UL ) )
282 0 : return FD_X509_VERIFY_ERR_NAME_CONSTRAINT;
283 0 : }
284 :
285 0 : int err = fd_x509_name_constrained( permitted, permitted_len, excluded, excluded_len,
286 0 : tag, name, name_len );
287 0 : if( FD_UNLIKELY( err ) ) return err;
288 0 : }
289 0 : return FD_X509_VERIFY_OK;
290 0 : }
291 :
292 : /* fd_x509_check_path_name_constraints applies a CA's name constraints
293 : to every cert in certs[0,cnt). Name constraints do not apply to
294 : non-final self-issued certs (RFC 5280 Section 6.1.4 (a)). */
295 :
296 : static int
297 : fd_x509_check_path_name_constraints( int has_name_constraints,
298 : uchar const * permitted,
299 : ulong permitted_len,
300 : uchar const * excluded,
301 : ulong excluded_len,
302 : fd_x509_cert_info_t const * certs,
303 0 : ulong cnt ) {
304 0 : for( ulong j=0UL; j<cnt; j++ ) {
305 0 : if( j && fd_x509_name_equal( certs[j].issuer, certs[j].issuer_len,
306 0 : certs[j].subject, certs[j].subject_len ) ) continue;
307 0 : int nc_err = fd_x509_check_name_constraints( has_name_constraints,
308 0 : permitted, permitted_len,
309 0 : excluded, excluded_len, &certs[j] );
310 0 : if( FD_UNLIKELY( nc_err ) ) return nc_err;
311 0 : }
312 0 : return FD_X509_VERIFY_OK;
313 0 : }
314 :
315 : /* fd_x509_check_anchor applies the trust anchor's own constraints to
316 : the path certs[0,cnt) that it terminates: its pathLenConstraint
317 : against the non_self_issued_ca_cnt intermediate CAs on the path, and
318 : its name constraints against every cert. fd_x509_ca_store_load
319 : already rejected anchors whose extKeyUsage excludes serverAuth. */
320 :
321 : static int
322 : fd_x509_check_anchor( fd_x509_ca_entry_t const * ca,
323 : fd_x509_cert_info_t const * certs,
324 : ulong cnt,
325 0 : ulong non_self_issued_ca_cnt ) {
326 0 : if( ca->has_path_len_constraint && non_self_issued_ca_cnt>ca->path_len_constraint )
327 0 : return FD_X509_VERIFY_ERR_PATH_LEN;
328 0 : return fd_x509_check_path_name_constraints(
329 0 : ca->has_name_constraints,
330 0 : ca->name_constraints, ca->name_constraints_permitted_len,
331 0 : ca->name_constraints+ca->name_constraints_permitted_len, ca->name_constraints_excluded_len,
332 0 : certs, cnt );
333 0 : }
334 :
335 : /* Implemented as specified by RFC 5280 Section 6.1.3. */
336 : int
337 : fd_x509_verify_chain( uchar const * const * chain_der,
338 : ulong const * chain_der_sz,
339 : ulong chain_cnt,
340 : fd_x509_ca_store_t const * ca_store,
341 : char const * hostname,
342 : ulong hostname_len,
343 0 : long unix_seconds ) {
344 :
345 0 : if( FD_UNLIKELY( chain_cnt == 0 ) ) return FD_X509_VERIFY_ERR_CHAIN_BREAK;
346 0 : if( FD_UNLIKELY( chain_cnt > FD_X509_CHAIN_MAX ) ) return FD_X509_VERIFY_ERR_CHAIN_TOO_LONG;
347 :
348 0 : for( ulong i=0UL; i<chain_cnt; i++ )
349 0 : if( FD_UNLIKELY( chain_der_sz[i] > FD_X509_CERT_SZ_MAX ) )
350 0 : return FD_X509_VERIFY_ERR_CERT_TOO_LARGE;
351 :
352 0 : fd_x509_cert_info_t certs[ FD_X509_CHAIN_MAX ] = {0};
353 :
354 0 : if( FD_UNLIKELY( fd_x509_cert_parse( chain_der[0], chain_der_sz[0], &certs[0] ) ) )
355 0 : return FD_X509_VERIFY_ERR_PARSE;
356 0 : if( FD_UNLIKELY( certs[0].key_type==FD_X509_KEY_UNKNOWN ) )
357 0 : return FD_X509_VERIFY_ERR_UNSUPPORTED;
358 :
359 0 : int time_err = fd_x509_check_validity( &certs[0], unix_seconds );
360 0 : if( FD_UNLIKELY( time_err ) ) return time_err;
361 :
362 0 : int usage_err = fd_x509_check_leaf_usage( &certs[0] );
363 0 : if( FD_UNLIKELY( usage_err ) ) return usage_err;
364 :
365 0 : if( hostname && hostname_len ) {
366 0 : if( FD_UNLIKELY( !fd_x509_san_matches( &certs[0], hostname, hostname_len ) ) )
367 0 : return FD_X509_VERIFY_ERR_HOSTNAME;
368 0 : }
369 :
370 : /* The presented list is leaf first; the issuers after it are in any
371 : order (RFC 8446 Section 4.4.2 tells clients to expect that). path
372 : is built by picking, at each step, an unused presented cert whose
373 : subject names the current issuer and whose key checks out. A
374 : branch that dead-ends short of a trust anchor is backed out of and
375 : the next candidate at that level tried (a cross-signed CA is
376 : presented twice under one subject). The first dead end's error is
377 : reported if no branch works out. Presented certs are parsed
378 : lazily. sig_budget bounds the work on a chain of mutually valid
379 : same-subject CAs; an honest chain needs one verify per cert. */
380 :
381 0 : FD_STATIC_ASSERT( FD_X509_CHAIN_MAX<=64UL, bitset );
382 0 : fd_x509_cert_info_t path[ FD_X509_CHAIN_MAX ];
383 0 : ulong parsed = 1UL; /* bit j: certs[j] is parsed */
384 0 : ulong used = 1UL; /* bit j: certs[j] is on path */
385 0 : path[0] = certs[0];
386 :
387 0 : ulong depth = 0UL;
388 0 : ulong j_start = 1UL;
389 0 : ulong non_self_issued_ca_cnt = 0UL;
390 0 : ulong sig_budget = 4UL*FD_X509_CHAIN_MAX;
391 0 : int first_err = FD_X509_VERIFY_OK;
392 0 : for(;;) {
393 0 : fd_x509_cert_info_t const * cur = &path[ depth ];
394 :
395 : /* A trust anchor for this cert's issuer completes the path. Peers
396 : routinely append cross-signatures leading up to some older root,
397 : so the certs beyond this point are not ours to walk: they chain to
398 : an anchor we do not need and may not even hold. Skipped when
399 : resuming after a backtrack: the anchors already failed here. */
400 :
401 0 : int anchored = 0;
402 0 : int anchor_err = FD_X509_VERIFY_OK;
403 0 : if( j_start==1UL ) {
404 0 : ulong idx = 0UL;
405 0 : for( fd_x509_ca_entry_t const * ca;
406 0 : !!( ca = fd_x509_ca_store_find_next( ca_store, cur->issuer, cur->issuer_len, &idx ) ); ) {
407 0 : anchored = 1;
408 :
409 : /* A name match is not a key match, so keep trying the remaining
410 : anchors sharing this subject. */
411 :
412 0 : if( FD_UNLIKELY( !sig_budget-- ) ) return first_err ? first_err : FD_X509_VERIFY_ERR_CHAIN_BREAK;
413 0 : int sig_rc = fd_x509_verify_sig( cur, ca->pubkey, ca->pubkey_len, ca->key_type );
414 0 : if( FD_UNLIKELY( sig_rc > 0 ) ) { anchor_err = FD_X509_VERIFY_ERR_UNSUPPORTED; continue; }
415 0 : if( sig_rc ) continue;
416 :
417 0 : anchor_err = fd_x509_check_anchor( ca, path, depth+1UL, non_self_issued_ca_cnt );
418 0 : if( !anchor_err ) return FD_X509_VERIFY_OK;
419 0 : }
420 0 : }
421 :
422 : /* Not anchored here, so find the issuer among the presented certs.
423 : Candidates that name-match but fail a check are passed over in
424 : favour of a later candidate (cross-signed CAs share a subject);
425 : the first such failure is reported if none of them work out. */
426 :
427 0 : int cand_err = FD_X509_VERIFY_OK;
428 0 : int any_left = 0;
429 0 : ulong pick = ULONG_MAX;
430 0 : for( ulong j = j_start; j < chain_cnt; j++ ) {
431 0 : if( used & (1UL<<j) ) continue;
432 0 : any_left = 1;
433 :
434 0 : if( !( parsed & (1UL<<j) ) ) {
435 0 : if( FD_UNLIKELY( fd_x509_cert_parse( chain_der[j], chain_der_sz[j], &certs[j] ) ) )
436 0 : return FD_X509_VERIFY_ERR_PARSE;
437 0 : parsed |= 1UL<<j;
438 0 : }
439 0 : fd_x509_cert_info_t const * cand = &certs[j];
440 :
441 0 : if( !fd_x509_name_equal( cur->issuer, cur->issuer_len, cand->subject, cand->subject_len ) )
442 0 : continue;
443 :
444 0 : int err = fd_x509_check_validity( cand, unix_seconds );
445 0 : if( !err && !cand->is_ca ) err = FD_X509_VERIFY_ERR_CA_FLAG;
446 :
447 : /* pathLenConstraint counts non-self-issued intermediate CA certs
448 : between this issuer and the leaf. The leaf itself never counts. */
449 0 : if( !err && cand->has_path_len_constraint &&
450 0 : non_self_issued_ca_cnt>cand->path_len_constraint )
451 0 : err = FD_X509_VERIFY_ERR_PATH_LEN;
452 :
453 0 : if( !err && cand->has_key_usage && !( cand->key_usage & FD_X509_KU_KEY_CERT_SIGN ) )
454 0 : err = FD_X509_VERIFY_ERR_KEY_USAGE;
455 :
456 0 : if( !err ) err = fd_x509_check_eku( cand );
457 :
458 0 : if( !err ) err = fd_x509_check_path_name_constraints(
459 0 : cand->has_name_constraints,
460 0 : cand->name_constraints_permitted, cand->name_constraints_permitted_len,
461 0 : cand->name_constraints_excluded, cand->name_constraints_excluded_len,
462 0 : path, depth+1UL );
463 :
464 0 : if( !err ) {
465 0 : if( FD_UNLIKELY( !sig_budget-- ) ) return first_err ? first_err : FD_X509_VERIFY_ERR_CHAIN_BREAK;
466 0 : int sig_rc = fd_x509_verify_sig( cur, cand->pubkey, cand->pubkey_len, cand->key_type );
467 0 : if( sig_rc < 0 ) err = FD_X509_VERIFY_ERR_SIG;
468 0 : if( sig_rc > 0 ) err = FD_X509_VERIFY_ERR_UNSUPPORTED;
469 0 : }
470 :
471 0 : if( !err ) { pick = j; break; }
472 0 : if( !cand_err ) cand_err = err;
473 0 : }
474 :
475 0 : if( pick != ULONG_MAX ) {
476 0 : used |= 1UL<<pick;
477 0 : path[ depth+1 ] = certs[ pick ];
478 :
479 : /* A self-issued rollover CA does not consume path length budget. */
480 0 : if( !fd_x509_name_equal( certs[pick].issuer, certs[pick].issuer_len,
481 0 : certs[pick].subject, certs[pick].subject_len ) )
482 0 : non_self_issued_ca_cnt++;
483 :
484 0 : depth++;
485 0 : j_start = 1UL;
486 0 : continue;
487 0 : }
488 :
489 0 : if( !first_err ) {
490 0 : if( cand_err ) first_err = cand_err;
491 0 : else if( anchor_err ) first_err = anchor_err;
492 0 : else if( anchored ) first_err = FD_X509_VERIFY_ERR_SIG;
493 0 : else first_err = any_left ? FD_X509_VERIFY_ERR_CHAIN_BREAK : FD_X509_VERIFY_ERR_NO_TRUST_ANCHOR;
494 0 : }
495 0 : if( !depth ) return first_err;
496 :
497 : /* Back up one level and release the cert picked there. path holds
498 : copies, so the pick is the used cert whose tbs pointer matches. */
499 :
500 0 : depth--;
501 0 : ulong prev = 1UL;
502 0 : while( !( used & (1UL<<prev) ) || certs[prev].tbs!=path[depth+1].tbs ) prev++;
503 0 : used &= ~(1UL<<prev);
504 0 : if( !fd_x509_name_equal( certs[prev].issuer, certs[prev].issuer_len,
505 0 : certs[prev].subject, certs[prev].subject_len ) )
506 0 : non_self_issued_ca_cnt--;
507 0 : j_start = prev+1UL;
508 0 : }
509 0 : }
510 :
511 : int
512 : fd_x509_verify_tls_cert_msg( uchar const * cert_msg,
513 : ulong cert_msg_sz,
514 : fd_x509_ca_store_t const * ca_store,
515 : char const * hostname,
516 : ulong hostname_len,
517 0 : long unix_seconds ) {
518 :
519 0 : if( FD_UNLIKELY( !cert_msg ) ) return FD_X509_VERIFY_ERR_PARSE;
520 :
521 0 : uchar const * p = cert_msg;
522 0 : uchar const * end = cert_msg + cert_msg_sz;
523 :
524 : /* A server Certificate sent for the main handshake must have an empty
525 : certificate_request_context (RFC 8446 Section 4.4.2). */
526 0 : if( FD_UNLIKELY( (ulong)(end-p)<1UL ) ) return FD_X509_VERIFY_ERR_PARSE;
527 0 : ulong ctx_len = *p++;
528 0 : if( FD_UNLIKELY( ctx_len ) ) return FD_X509_VERIFY_ERR_PARSE;
529 :
530 : /* certificate_list<0..2^24-1> */
531 0 : if( FD_UNLIKELY( (ulong)(end-p)<3UL ) ) return FD_X509_VERIFY_ERR_PARSE;
532 0 : ulong list_len = ( (ulong)p[0]<<16 ) | ( (ulong)p[1]<<8 ) | (ulong)p[2];
533 0 : p += 3;
534 0 : if( FD_UNLIKELY( list_len != (ulong)( end-p ) ) ) return FD_X509_VERIFY_ERR_PARSE;
535 0 : uchar const * list_end = p + list_len;
536 :
537 0 : uchar const * chain_der [ FD_X509_CHAIN_MAX ];
538 0 : ulong chain_der_sz[ FD_X509_CHAIN_MAX ];
539 0 : ulong chain_cnt = 0UL;
540 :
541 0 : while( p < list_end ) {
542 0 : if( FD_UNLIKELY( chain_cnt >= FD_X509_CHAIN_MAX ) ) return FD_X509_VERIFY_ERR_CHAIN_TOO_LONG;
543 :
544 : /* cert_data<1..2^24-1> */
545 0 : if( FD_UNLIKELY( (ulong)(list_end-p)<3UL ) ) return FD_X509_VERIFY_ERR_PARSE;
546 0 : ulong cert_len = ( (ulong)p[0]<<16 ) | ( (ulong)p[1]<<8 ) | (ulong)p[2];
547 0 : p += 3;
548 0 : if( FD_UNLIKELY( !cert_len ) )
549 0 : return FD_X509_VERIFY_ERR_PARSE;
550 0 : if( FD_UNLIKELY( cert_len > FD_X509_CERT_SZ_MAX ) )
551 0 : return FD_X509_VERIFY_ERR_CERT_TOO_LARGE;
552 0 : if( FD_UNLIKELY( (ulong)(list_end-p)<cert_len ) )
553 0 : return FD_X509_VERIFY_ERR_PARSE;
554 :
555 0 : chain_der [ chain_cnt ] = p;
556 0 : chain_der_sz[ chain_cnt ] = cert_len;
557 0 : chain_cnt++;
558 0 : p += cert_len;
559 :
560 : /* extensions<0..2^16-1> */
561 0 : if( FD_UNLIKELY( (ulong)(list_end-p)<2UL ) ) return FD_X509_VERIFY_ERR_PARSE;
562 0 : ulong ext_len = ( (ulong)p[0]<<8 ) | (ulong)p[1];
563 0 : p += 2;
564 0 : if( FD_UNLIKELY( (ulong)(list_end-p)<ext_len ) ) return FD_X509_VERIFY_ERR_PARSE;
565 0 : uchar const * ext_end = p + ext_len;
566 :
567 0 : while( p < ext_end ) {
568 0 : if( FD_UNLIKELY( (ulong)(ext_end-p)<4UL ) ) return FD_X509_VERIFY_ERR_PARSE;
569 0 : p += 2; /* ExtensionType */
570 0 : ulong ext_data_len = ( (ulong)p[0]<<8 ) | (ulong)p[1];
571 0 : p += 2;
572 0 : if( FD_UNLIKELY( (ulong)(ext_end-p)<ext_data_len ) ) return FD_X509_VERIFY_ERR_PARSE;
573 0 : p += ext_data_len;
574 0 : }
575 0 : }
576 :
577 0 : if( FD_UNLIKELY( !chain_cnt ) ) return FD_X509_VERIFY_ERR_PARSE;
578 :
579 0 : return fd_x509_verify_chain( chain_der, chain_der_sz, chain_cnt,
580 0 : ca_store, hostname, hostname_len, unix_seconds );
581 0 : }
|