LCOV - code coverage report
Current view: top level - ballet/x509 - fd_x509_verify.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 341 356 95.8 %
Date: 2026-09-17 04:28:31 Functions: 13 13 100.0 %

          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        2010 :                     uchar                       issuer_key_type ) {
      16             : 
      17        2010 :   switch( cert->sig_alg ) {
      18             : 
      19        1344 :   case FD_X509_SIG_ED25519: {
      20        1344 :     if( FD_UNLIKELY( issuer_key_type != FD_X509_KEY_ED25519 ) ) return -1;
      21        1344 :     if( FD_UNLIKELY( issuer_pubkey_len != 32 ) ) return -1;
      22        1344 :     if( FD_UNLIKELY( cert->sig_len != 64 ) ) return -1;
      23             : 
      24        1344 :     fd_sha512_t sha512[1];
      25        1344 :     int err = fd_ed25519_verify( cert->tbs, cert->tbs_len, cert->sig, issuer_pubkey, sha512 );
      26        1344 :     return ( err == FD_ED25519_SUCCESS ) ? 0 : -1;
      27        1344 :   }
      28             : 
      29         465 :   case FD_X509_SIG_ECDSA_SHA256: {
      30         465 :     if( FD_UNLIKELY( issuer_key_type != FD_X509_KEY_ECDSA_P256 ) ) return -1;
      31         465 :     if( FD_UNLIKELY( issuer_pubkey_len != 65 ) ) return -1;
      32             : 
      33         465 :     uchar raw_sig[64];
      34         465 :     if( FD_UNLIKELY( fd_x509_decode_ecdsa_sig( cert->sig, cert->sig_len, raw_sig, 32 ) ) )
      35           0 :       return -1;
      36             : 
      37         465 :     uchar compressed_pk[33];
      38         465 :     if( FD_UNLIKELY( fd_x509_ec_point_compress( issuer_pubkey, 32, compressed_pk ) ) )
      39           0 :       return -1;
      40             : 
      41         465 :     fd_sha256_t sha256[1];
      42         465 :     int err = fd_secp256r1_verify_allow_high_s( cert->tbs, cert->tbs_len, raw_sig, compressed_pk, sha256 );
      43         465 :     return ( err == FD_SECP256R1_SUCCESS ) ? 0 : -1;
      44         465 :   }
      45             : 
      46         162 :   case FD_X509_SIG_ECDSA_SHA384: {
      47         162 :     if( FD_UNLIKELY( issuer_key_type != FD_X509_KEY_ECDSA_P384 ) ) return -1;
      48         162 :     if( FD_UNLIKELY( issuer_pubkey_len != 97 ) ) return -1;
      49             : 
      50         162 :     uchar raw_sig[96];
      51         162 :     if( FD_UNLIKELY( fd_x509_decode_ecdsa_sig( cert->sig, cert->sig_len, raw_sig, 48 ) ) )
      52           0 :       return -1;
      53             : 
      54         162 :     uchar compressed_pk[49];
      55         162 :     if( FD_UNLIKELY( fd_x509_ec_point_compress( issuer_pubkey, 48, compressed_pk ) ) )
      56           0 :       return -1;
      57             : 
      58         162 :     fd_sha512_t sha384[1];
      59         162 :     int err = fd_secp384r1_verify_allow_high_s( cert->tbs, cert->tbs_len, raw_sig, compressed_pk, sha384 );
      60         162 :     return ( err == FD_SECP384R1_SUCCESS ) ? 0 : -1;
      61         162 :   }
      62             : 
      63          18 :   case FD_X509_SIG_RSA_SHA256:
      64          24 :   case FD_X509_SIG_RSA_SHA384:
      65          39 :   case FD_X509_SIG_RSA_SHA512: {
      66             : #if !FD_HAS_INT128
      67             :     return 1;  /* no fd_rsa */
      68             : #else
      69          39 :     if( FD_UNLIKELY( issuer_key_type != FD_X509_KEY_RSA ) ) return -1;
      70             : 
      71          39 :     fd_rsa_pubkey_t key[1];
      72          39 :     if( FD_UNLIKELY( fd_x509_decode_rsa_pubkey( issuer_pubkey, issuer_pubkey_len, key ) ) )
      73           0 :       return -1;
      74             : 
      75          39 :     int hash = cert->sig_alg==FD_X509_SIG_RSA_SHA256 ? FD_RSA_HASH_SHA256 :
      76          39 :                cert->sig_alg==FD_X509_SIG_RSA_SHA384 ? FD_RSA_HASH_SHA384 :
      77          21 :                                                        FD_RSA_HASH_SHA512;
      78          39 :     int err = fd_rsa_verify_pkcs1_v15( key, cert->sig, cert->sig_len, cert->tbs, cert->tbs_len, hash );
      79          39 :     return ( err == FD_RSA_SUCCESS ) ? 0 : -1;
      80          39 : #endif
      81          39 :   }
      82             : 
      83           0 :   default:
      84           0 :     return 1;  /* unsupported sig algorithm */
      85        2010 :   }
      86        2010 : }
      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        2439 :                         long                        unix_seconds ) {
      94        2439 :   if( FD_UNLIKELY( cert->not_before_unix==FD_X509_TIME_INVALID ||
      95        2439 :                    cert->not_after_unix ==FD_X509_TIME_INVALID ) )
      96           6 :     return FD_X509_VERIFY_ERR_TIME_PARSE;
      97        2433 :   if( FD_UNLIKELY( unix_seconds < cert->not_before_unix ) )
      98          21 :     return FD_X509_VERIFY_ERR_NOT_YET_VALID;
      99        2412 :   if( FD_UNLIKELY( unix_seconds > cert->not_after_unix ) )
     100          33 :     return FD_X509_VERIFY_ERR_EXPIRED;
     101        2379 :   return FD_X509_VERIFY_OK;
     102        2412 : }
     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        2346 : fd_x509_check_eku( fd_x509_cert_info_t const * cert ) {
     109        2346 :   if( FD_UNLIKELY(
     110        2346 :         cert->has_ext_key_usage &&
     111        2346 :         !( cert->ext_key_usage & ( FD_X509_EKU_SERVER_AUTH|FD_X509_EKU_ANY ) ) ) ) {
     112          21 :     return FD_X509_VERIFY_ERR_EXT_KEY_USAGE;
     113          21 :   }
     114        2325 :   return FD_X509_VERIFY_OK;
     115        2346 : }
     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        1548 : fd_x509_check_leaf_usage( fd_x509_cert_info_t const * leaf ) {
     122        1548 :   if( FD_UNLIKELY(
     123        1548 :       leaf->has_key_usage &&
     124        1548 :       !( leaf->key_usage & FD_X509_KU_DIGITAL_SIGNATURE ) ) ) {
     125          15 :     return FD_X509_VERIFY_ERR_KEY_USAGE;
     126          15 :   }
     127        1533 :   return fd_x509_check_eku( leaf );
     128        1548 : }
     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          90 :                                 int           excluded ) {
     137             :   /* Excluded subtrees reject any overlapping wildcard expansion. */
     138          90 :   if( excluded && name_len>2UL && name[0]=='*' && name[1]=='.' ) {
     139          27 :     uchar const * dot = memchr( constraint, '.', constraint_len );
     140          27 :     if( dot ) {
     141          24 :       ulong tail_len = constraint_len-(ulong)( dot+1-constraint );
     142          24 :       if( tail_len==name_len-2UL &&
     143          24 :           fd_x509_dns_eq_ci( (char const *)name+2, (char const *)dot+1, tail_len ) ) return 1;
     144          24 :     }
     145          27 :   }
     146             : 
     147          81 :   int subdomains_only = constraint[0]=='.';
     148          81 :   if( subdomains_only ) {
     149          18 :     if( name_len<=constraint_len ) return 0;
     150           9 :     return fd_x509_dns_eq_ci( (char const *)name+name_len-constraint_len, (char const *)constraint, constraint_len );
     151          18 :   }
     152             : 
     153          63 :   if( name_len<constraint_len ) return 0;
     154          39 :   if( !fd_x509_dns_eq_ci( (char const *)name+name_len-constraint_len, (char const *)constraint, constraint_len ) ) return 0;
     155          33 :   return name_len==constraint_len || name[name_len-constraint_len-1UL]=='.';
     156          39 : }
     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          24 :                                ulong         ip_len ) {
     165          24 :   if( constraint_len!=2UL*ip_len ) return 0;
     166          18 :   uchar const * mask = constraint+ip_len;
     167         102 :   for( ulong i=0UL; i<ip_len; i++ ) {
     168          90 :     if( (ip[i] & mask[i]) != (constraint[i] & mask[i]) ) return 0;
     169          90 :   }
     170          12 :   return 1;
     171          18 : }
     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         333 :                         int           excluded ) {
     187         333 :   int found = 0;
     188         333 :   fd_der_cursor_t c = { .p=trees, .end=trees+trees_len };
     189         645 :   while( FD_DER_HAS_MORE( c ) ) {
     190         408 :     int tree_tag; ulong tree_len;
     191         408 :     if( FD_UNLIKELY( fd_der_read_tl( &c, &tree_tag, &tree_len ) ||
     192         408 :                      tree_tag!=(int)FD_DER_TAG_SEQUENCE ) ) return -2;
     193         408 :     fd_der_cursor_t t = { .p=c.p, .end=c.p+tree_len };
     194         408 :     c.p += tree_len;
     195         408 :     int base_tag; ulong base_len;
     196         408 :     if( FD_UNLIKELY( fd_der_read_tl( &t, &base_tag, &base_len ) ) ) return -2;
     197         408 :     if( base_tag!=tag ) continue;
     198         168 :     found = 1;
     199         168 :     int match;
     200         168 :     switch( tag ) {
     201          90 :     case FD_DER_TAG_CONTEXT_PRIM(2):
     202          90 :       match = fd_x509_dns_constraint_matches( t.p, base_len, name, name_len, excluded );
     203          90 :       break;
     204          24 :     case FD_DER_TAG_CONTEXT_PRIM(7):
     205          24 :       match = fd_x509_ip_constraint_matches( t.p, base_len, name, name_len );
     206          24 :       break;
     207          54 :     case FD_DER_TAG_CONTEXT(4):
     208          54 :       match = fd_x509_name_prefix( t.p, base_len, name, name_len );
     209          54 :       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         168 :     }
     215         168 :     if( match ) return 1;
     216         168 :   }
     217         237 :   return found ? 0 : -1;
     218         333 : }
     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         279 :                           ulong         name_len ) {
     231         279 :   if( excluded_len ) {
     232         144 :     int match = fd_x509_subtrees_match( excluded, excluded_len, tag, name, name_len, 1 );
     233         144 :     if( match==1 || match==-2 ) return FD_X509_VERIFY_ERR_NAME_CONSTRAINT;
     234         144 :   }
     235         243 :   if( permitted_len ) {
     236         189 :     int match = fd_x509_subtrees_match( permitted, permitted_len, tag, name, name_len, 0 );
     237         189 :     if( match==0 || match==-2 ) return FD_X509_VERIFY_ERR_NAME_CONSTRAINT;
     238         189 :   }
     239         198 :   return FD_X509_VERIFY_OK;
     240         243 : }
     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        2835 :                                 fd_x509_cert_info_t const * cert ) {
     256        2835 :   if( !has_name_constraints ) return FD_X509_VERIFY_OK;
     257             : 
     258         159 :   if( cert->subject_len>2UL ) {
     259         153 :     int err = fd_x509_name_constrained( permitted, permitted_len, excluded, excluded_len,
     260         153 :                                         FD_DER_TAG_CONTEXT(4), cert->subject, cert->subject_len );
     261         153 :     if( FD_UNLIKELY( err ) ) return err;
     262         153 :   }
     263             : 
     264         141 :   if( !cert->has_subject_alt_name ) return FD_X509_VERIFY_OK;
     265             : 
     266         126 :   fd_der_cursor_t san = { .p=cert->san_general_names,
     267         126 :                           .end=cert->san_general_names+cert->san_general_names_len };
     268         189 :   while( FD_DER_HAS_MORE( san ) ) {
     269         126 :     int tag; ulong name_len;
     270         126 :     if( FD_UNLIKELY( fd_der_read_tl( &san, &tag, &name_len ) ) )
     271           0 :       return FD_X509_VERIFY_ERR_NAME_CONSTRAINT;
     272         126 :     uchar const * name = san.p;
     273         126 :     san.p += name_len;
     274             : 
     275         126 :     if( tag==(int)FD_DER_TAG_CONTEXT_PRIM(2) ) {
     276          93 :       int wildcard = name_len>2UL && name[0]=='*' && name[1]=='.';
     277          93 :       if( FD_UNLIKELY( wildcard ? !fd_x509_dns_name_valid( (char const *)name+2, name_len-2UL )
     278          93 :                                : !fd_x509_dns_name_valid( (char const *)name,   name_len     ) ) )
     279           0 :         return FD_X509_VERIFY_ERR_NAME_CONSTRAINT;
     280          93 :     } else if( tag==(int)FD_DER_TAG_CONTEXT_PRIM(7) ) {
     281          24 :       if( FD_UNLIKELY( name_len!=4UL && name_len!=16UL ) )
     282           0 :         return FD_X509_VERIFY_ERR_NAME_CONSTRAINT;
     283          24 :     }
     284             : 
     285         126 :     int err = fd_x509_name_constrained( permitted, permitted_len, excluded, excluded_len,
     286         126 :                                         tag, name, name_len );
     287         126 :     if( FD_UNLIKELY( err ) ) return err;
     288         126 :   }
     289          63 :   return FD_X509_VERIFY_OK;
     290         126 : }
     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        2064 :                                      ulong                       cnt ) {
     304        4902 :   for( ulong j=0UL; j<cnt; j++ ) {
     305        2919 :     if( j && fd_x509_name_equal( certs[j].issuer, certs[j].issuer_len,
     306         855 :                                  certs[j].subject, certs[j].subject_len ) ) continue;
     307        2835 :     int nc_err = fd_x509_check_name_constraints( has_name_constraints,
     308        2835 :                                                  permitted, permitted_len,
     309        2835 :                                                  excluded,  excluded_len, &certs[j] );
     310        2835 :     if( FD_UNLIKELY( nc_err ) ) return nc_err;
     311        2835 :   }
     312        1983 :   return FD_X509_VERIFY_OK;
     313        2064 : }
     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        1266 :                       ulong                       non_self_issued_ca_cnt ) {
     326        1266 :   if( ca->has_path_len_constraint && non_self_issued_ca_cnt>ca->path_len_constraint )
     327          12 :     return FD_X509_VERIFY_ERR_PATH_LEN;
     328        1254 :   return fd_x509_check_path_name_constraints(
     329        1254 :       ca->has_name_constraints,
     330        1254 :       ca->name_constraints,                                   ca->name_constraints_permitted_len,
     331        1254 :       ca->name_constraints+ca->name_constraints_permitted_len, ca->name_constraints_excluded_len,
     332        1254 :       certs, cnt );
     333        1266 : }
     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        1620 :                       long                         unix_seconds ) {
     344             : 
     345        1620 :   if( FD_UNLIKELY( chain_cnt == 0 ) ) return FD_X509_VERIFY_ERR_CHAIN_BREAK;
     346        1617 :   if( FD_UNLIKELY( chain_cnt > FD_X509_CHAIN_MAX ) ) return FD_X509_VERIFY_ERR_CHAIN_TOO_LONG;
     347             : 
     348        4440 :   for( ulong i=0UL; i<chain_cnt; i++ )
     349        2832 :     if( FD_UNLIKELY( chain_der_sz[i] > FD_X509_CERT_SZ_MAX ) )
     350           3 :       return FD_X509_VERIFY_ERR_CERT_TOO_LARGE;
     351             : 
     352        1608 :   fd_x509_cert_info_t certs[ FD_X509_CHAIN_MAX ] = {0};
     353             : 
     354        1608 :   if( FD_UNLIKELY( fd_x509_cert_parse( chain_der[0], chain_der_sz[0], &certs[0] ) ) )
     355           9 :     return FD_X509_VERIFY_ERR_PARSE;
     356        1599 :   if( FD_UNLIKELY( certs[0].key_type==FD_X509_KEY_UNKNOWN ) )
     357           0 :     return FD_X509_VERIFY_ERR_UNSUPPORTED;
     358             : 
     359        1599 :   int time_err = fd_x509_check_validity( &certs[0], unix_seconds );
     360        1599 :   if( FD_UNLIKELY( time_err ) ) return time_err;
     361             : 
     362        1548 :   int usage_err = fd_x509_check_leaf_usage( &certs[0] );
     363        1548 :   if( FD_UNLIKELY( usage_err ) ) return usage_err;
     364             : 
     365        1515 :   if( hostname && hostname_len ) {
     366        1224 :     if( FD_UNLIKELY( !fd_x509_san_matches( &certs[0], hostname, hostname_len ) ) )
     367          48 :       return FD_X509_VERIFY_ERR_HOSTNAME;
     368        1224 :   }
     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        1467 :   FD_STATIC_ASSERT( FD_X509_CHAIN_MAX<=64UL, bitset );
     382        1467 :   fd_x509_cert_info_t path[ FD_X509_CHAIN_MAX ];
     383        1467 :   ulong parsed = 1UL;  /* bit j: certs[j] is parsed */
     384        1467 :   ulong used   = 1UL;  /* bit j: certs[j] is on path */
     385        1467 :   path[0] = certs[0];
     386             : 
     387        1467 :   ulong depth                  = 0UL;
     388        1467 :   ulong j_start                = 1UL;
     389        1467 :   ulong non_self_issued_ca_cnt = 0UL;
     390        1467 :   ulong sig_budget             = 4UL*FD_X509_CHAIN_MAX;
     391        1467 :   int   first_err              = FD_X509_VERIFY_OK;
     392        2277 :   for(;;) {
     393        2277 :     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        2277 :     int anchored   = 0;
     402        2277 :     int anchor_err = FD_X509_VERIFY_OK;
     403        2277 :     if( j_start==1UL ) {
     404        2190 :       ulong idx = 0UL;
     405        2190 :       for( fd_x509_ca_entry_t const * ca;
     406        2217 :            !!( ca = fd_x509_ca_store_find_next( ca_store, cur->issuer, cur->issuer_len, &idx ) ); ) {
     407        1278 :         anchored = 1;
     408             : 
     409             :         /* A name match is not a key match, so keep trying the remaining
     410             :            anchors sharing this subject. */
     411             : 
     412        1278 :         if( FD_UNLIKELY( !sig_budget-- ) ) return first_err ? first_err : FD_X509_VERIFY_ERR_CHAIN_BREAK;
     413        1278 :         int sig_rc = fd_x509_verify_sig( cur, ca->pubkey, ca->pubkey_len, ca->key_type );
     414        1278 :         if( FD_UNLIKELY( sig_rc > 0 ) ) { anchor_err = FD_X509_VERIFY_ERR_UNSUPPORTED; continue; }
     415        1278 :         if( sig_rc )                    continue;
     416             : 
     417        1266 :         anchor_err = fd_x509_check_anchor( ca, path, depth+1UL, non_self_issued_ca_cnt );
     418        1266 :         if( !anchor_err ) return FD_X509_VERIFY_OK;
     419        1266 :       }
     420        2190 :     }
     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        1026 :     int   cand_err  = FD_X509_VERIFY_OK;
     428        1026 :     int   any_left  = 0;
     429        1026 :     ulong pick      = ULONG_MAX;
     430        1569 :     for( ulong j = j_start; j < chain_cnt; j++ ) {
     431        1275 :       if( used & (1UL<<j) ) continue;
     432         939 :       any_left = 1;
     433             : 
     434         939 :       if( !( parsed & (1UL<<j) ) ) {
     435         864 :         if( FD_UNLIKELY( fd_x509_cert_parse( chain_der[j], chain_der_sz[j], &certs[j] ) ) )
     436           9 :           return FD_X509_VERIFY_ERR_PARSE;
     437         855 :         parsed |= 1UL<<j;
     438         855 :       }
     439         930 :       fd_x509_cert_info_t const * cand = &certs[j];
     440             : 
     441         930 :       if( !fd_x509_name_equal( cur->issuer, cur->issuer_len, cand->subject, cand->subject_len ) )
     442          90 :         continue;
     443             : 
     444         840 :       int err = fd_x509_check_validity( cand, unix_seconds );
     445         840 :       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         840 :       if( !err && cand->has_path_len_constraint &&
     450         840 :           non_self_issued_ca_cnt>cand->path_len_constraint )
     451           9 :         err = FD_X509_VERIFY_ERR_PATH_LEN;
     452             : 
     453         840 :       if( !err && cand->has_key_usage && !( cand->key_usage & FD_X509_KU_KEY_CERT_SIGN ) )
     454           6 :         err = FD_X509_VERIFY_ERR_KEY_USAGE;
     455             : 
     456         840 :       if( !err ) err = fd_x509_check_eku( cand );
     457             : 
     458         840 :       if( !err ) err = fd_x509_check_path_name_constraints(
     459         810 :           cand->has_name_constraints,
     460         810 :           cand->name_constraints_permitted, cand->name_constraints_permitted_len,
     461         810 :           cand->name_constraints_excluded,  cand->name_constraints_excluded_len,
     462         810 :           path, depth+1UL );
     463             : 
     464         840 :       if( !err ) {
     465         732 :         if( FD_UNLIKELY( !sig_budget-- ) ) return first_err ? first_err : FD_X509_VERIFY_ERR_CHAIN_BREAK;
     466         732 :         int sig_rc = fd_x509_verify_sig( cur, cand->pubkey, cand->pubkey_len, cand->key_type );
     467         732 :         if( sig_rc < 0 ) err = FD_X509_VERIFY_ERR_SIG;
     468         732 :         if( sig_rc > 0 ) err = FD_X509_VERIFY_ERR_UNSUPPORTED;
     469         732 :       }
     470             : 
     471         840 :       if( !err ) { pick = j; break; }
     472         117 :       if( !cand_err ) cand_err = err;
     473         117 :     }
     474             : 
     475        1017 :     if( pick != ULONG_MAX ) {
     476         723 :       used |= 1UL<<pick;
     477         723 :       path[ depth+1 ] = certs[ pick ];
     478             : 
     479             :       /* A self-issued rollover CA does not consume path length budget. */
     480         723 :       if( !fd_x509_name_equal( certs[pick].issuer,  certs[pick].issuer_len,
     481         723 :                                certs[pick].subject, certs[pick].subject_len ) )
     482         684 :         non_self_issued_ca_cnt++;
     483             : 
     484         723 :       depth++;
     485         723 :       j_start = 1UL;
     486         723 :       continue;
     487         723 :     }
     488             : 
     489         294 :     if( !first_err ) {
     490         213 :       if(      cand_err   ) first_err = cand_err;
     491         102 :       else if( anchor_err ) first_err = anchor_err;
     492          87 :       else if( anchored   ) first_err = FD_X509_VERIFY_ERR_SIG;
     493          78 :       else                  first_err = any_left ? FD_X509_VERIFY_ERR_CHAIN_BREAK : FD_X509_VERIFY_ERR_NO_TRUST_ANCHOR;
     494         213 :     }
     495         294 :     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          87 :     depth--;
     501          87 :     ulong prev = 1UL;
     502         168 :     while( !( used & (1UL<<prev) ) || certs[prev].tbs!=path[depth+1].tbs ) prev++;
     503          87 :     used &= ~(1UL<<prev);
     504          87 :     if( !fd_x509_name_equal( certs[prev].issuer,  certs[prev].issuer_len,
     505          87 :                              certs[prev].subject, certs[prev].subject_len ) )
     506          57 :       non_self_issued_ca_cnt--;
     507          87 :     j_start = prev+1UL;
     508          87 :   }
     509        1467 : }
     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        1137 :                              long                       unix_seconds ) {
     518             : 
     519        1137 :   if( FD_UNLIKELY( !cert_msg ) ) return FD_X509_VERIFY_ERR_PARSE;
     520             : 
     521        1137 :   uchar const * p   = cert_msg;
     522        1137 :   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        1137 :   if( FD_UNLIKELY( (ulong)(end-p)<1UL ) ) return FD_X509_VERIFY_ERR_PARSE;
     527        1134 :   ulong ctx_len = *p++;
     528        1134 :   if( FD_UNLIKELY( ctx_len ) ) return FD_X509_VERIFY_ERR_PARSE;
     529             : 
     530             :   /* certificate_list<0..2^24-1> */
     531        1128 :   if( FD_UNLIKELY( (ulong)(end-p)<3UL ) ) return FD_X509_VERIFY_ERR_PARSE;
     532        1119 :   ulong list_len = ( (ulong)p[0]<<16 ) | ( (ulong)p[1]<<8 ) | (ulong)p[2];
     533        1119 :   p += 3;
     534        1119 :   if( FD_UNLIKELY( list_len != (ulong)( end-p ) ) ) return FD_X509_VERIFY_ERR_PARSE;
     535        1116 :   uchar const * list_end = p + list_len;
     536             : 
     537        1116 :   uchar const * chain_der   [ FD_X509_CHAIN_MAX ];
     538        1116 :   ulong         chain_der_sz[ FD_X509_CHAIN_MAX ];
     539        1116 :   ulong         chain_cnt = 0UL;
     540             : 
     541        3006 :   while( p < list_end ) {
     542        1905 :     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        1902 :     if( FD_UNLIKELY( (ulong)(list_end-p)<3UL ) ) return FD_X509_VERIFY_ERR_PARSE;
     546        1902 :     ulong cert_len = ( (ulong)p[0]<<16 ) | ( (ulong)p[1]<<8 ) | (ulong)p[2];
     547        1902 :     p += 3;
     548        1902 :     if( FD_UNLIKELY( !cert_len ) )
     549           0 :       return FD_X509_VERIFY_ERR_PARSE;
     550        1902 :     if( FD_UNLIKELY( cert_len > FD_X509_CERT_SZ_MAX ) )
     551           3 :       return FD_X509_VERIFY_ERR_CERT_TOO_LARGE;
     552        1899 :     if( FD_UNLIKELY( (ulong)(list_end-p)<cert_len ) )
     553           0 :       return FD_X509_VERIFY_ERR_PARSE;
     554             : 
     555        1899 :     chain_der   [ chain_cnt ] = p;
     556        1899 :     chain_der_sz[ chain_cnt ] = cert_len;
     557        1899 :     chain_cnt++;
     558        1899 :     p += cert_len;
     559             : 
     560             :     /* extensions<0..2^16-1> */
     561        1899 :     if( FD_UNLIKELY( (ulong)(list_end-p)<2UL ) ) return FD_X509_VERIFY_ERR_PARSE;
     562        1896 :     ulong ext_len = ( (ulong)p[0]<<8 ) | (ulong)p[1];
     563        1896 :     p += 2;
     564        1896 :     if( FD_UNLIKELY( (ulong)(list_end-p)<ext_len ) ) return FD_X509_VERIFY_ERR_PARSE;
     565        1893 :     uchar const * ext_end = p + ext_len;
     566             : 
     567        1896 :     while( p < ext_end ) {
     568           6 :       if( FD_UNLIKELY( (ulong)(ext_end-p)<4UL ) ) return FD_X509_VERIFY_ERR_PARSE;
     569           3 :       p += 2; /* ExtensionType */
     570           3 :       ulong ext_data_len = ( (ulong)p[0]<<8 ) | (ulong)p[1];
     571           3 :       p += 2;
     572           3 :       if( FD_UNLIKELY( (ulong)(ext_end-p)<ext_data_len ) ) return FD_X509_VERIFY_ERR_PARSE;
     573           3 :       p += ext_data_len;
     574           3 :     }
     575        1893 :   }
     576             : 
     577        1101 :   if( FD_UNLIKELY( !chain_cnt ) ) return FD_X509_VERIFY_ERR_PARSE;
     578             : 
     579        1098 :   return fd_x509_verify_chain( chain_der, chain_der_sz, chain_cnt,
     580        1098 :                               ca_store, hostname, hostname_len, unix_seconds );
     581        1101 : }

Generated by: LCOV version 1.14