LCOV - code coverage report
Current view: top level - ballet/x509 - fd_x509_ca_store.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 120 146 82.2 %
Date: 2026-09-17 04:28:31 Functions: 4 5 80.0 %

          Line data    Source code
       1             : #include "fd_x509_ca_store.h"
       2             : 
       3             : #if FD_HAS_HOSTED
       4             : 
       5             : #include "../base64/fd_base64.h"
       6             : #include "../../util/io/fd_io.h"
       7             : #include "../../util/log/fd_log.h"
       8             : #include <fcntl.h>
       9             : #include <stdlib.h>
      10             : #include <string.h>
      11             : #include <sys/stat.h>
      12             : #include <unistd.h>
      13             : 
      14        9117 : #define PEM_BEGIN "-----BEGIN CERTIFICATE-----"
      15        5244 : #define PEM_END   "-----END CERTIFICATE-----"
      16        5433 : #define PEM_BEGIN_SZ (sizeof(PEM_BEGIN)-1)
      17        3495 : #define PEM_END_SZ   (sizeof(PEM_END)-1)
      18             : 
      19             : /* Scan [p, end) for needle.  Returns pointer to first match, or NULL. */
      20             : static char const *
      21        5433 : find_line( char const * p, char const * end, char const * needle, ulong needle_sz ) {
      22     1239000 :   for( ; (ulong)(end-p)>=needle_sz; p++ ) {
      23     1238622 :     if( p[0]==needle[0] && !memcmp( p, needle, needle_sz ) ) return p;
      24     1238622 :   }
      25         378 :   return NULL;
      26        5433 : }
      27             : 
      28             : #define PEM_B64_STRIPPED_MAX (8192UL)
      29             : 
      30             : /* Sanity bound on the bundle file.  Real CA bundles are 200-400 KB;
      31             :    this only exists so that pointing pem_path at something absurd fails
      32             :    instead of allocating without end. */
      33             : 
      34             : #define CA_BUNDLE_SZ_MAX (64UL<<20)
      35             : 
      36             : static long
      37             : pem_b64_decode( uchar * out,      ulong out_max,
      38        1746 :                 char const * b64, ulong b64_sz ) {
      39        1746 :   char stripped[ PEM_B64_STRIPPED_MAX ];
      40        1746 :   ulong j = 0;
      41      597501 :   for( ulong i=0; i<b64_sz; i++ ) {
      42      595755 :     char c = b64[i];
      43      595755 :     if( c=='\n' || c=='\r' || c==' ' || c=='\t' ) continue;
      44      591372 :     if( FD_UNLIKELY( j >= sizeof(stripped) ) ) return -1;
      45      591372 :     stripped[j++] = c;
      46      591372 :   }
      47        1746 :   while( j & 3 ) {
      48           0 :     if( FD_UNLIKELY( j >= sizeof(stripped) ) ) return -1;
      49           0 :     stripped[j++] = '=';
      50           0 :   }
      51        1746 :   if( !j ) return -1;
      52        1746 :   if( FD_UNLIKELY( FD_BASE64_DEC_SZ(j) > out_max ) ) return -1;
      53        1746 :   return fd_base64_decode( out, stripped, j );
      54        1746 : }
      55             : 
      56             : long
      57             : fd_x509_ca_store_load( fd_x509_ca_store_t * store,
      58         192 :                        char const *         pem_path ) {
      59         192 :   store->cnt = 0;
      60             : 
      61         192 :   int fd = open( pem_path, O_RDONLY|O_CLOEXEC );
      62         192 :   if( FD_UNLIKELY( fd<0 ) ) return -1;
      63             : 
      64         192 :   struct stat st;
      65         192 :   if( FD_UNLIKELY( fstat( fd, &st )<0 ) ) { close( fd ); return -1; }
      66         192 :   ulong file_sz = (ulong)st.st_size;
      67         192 :   if( FD_UNLIKELY( !file_sz ) ) { close( fd ); return -1; }
      68         192 :   if( FD_UNLIKELY( file_sz>CA_BUNDLE_SZ_MAX ) ) {
      69           3 :     FD_LOG_WARNING(( "CA bundle %s is %lu bytes, larger than the %lu byte limit",
      70           3 :                      pem_path, file_sz, CA_BUNDLE_SZ_MAX ));
      71           3 :     close( fd ); return -1;
      72           3 :   }
      73             : 
      74         189 :   uchar * file_buf = malloc( file_sz );
      75         189 :   if( FD_UNLIKELY( !file_buf ) ) { close( fd ); return -1; }
      76             : 
      77         189 :   ulong read_sz;
      78         189 :   int err = fd_io_read( fd, file_buf, file_sz, file_sz, &read_sz );
      79         189 :   (void)close( fd );
      80         189 :   if( FD_UNLIKELY( err ) ) {
      81           0 :     FD_LOG_WARNING(( "read(%s) failed (%i-%s)", pem_path, err, fd_io_strerror( err ) ));
      82           0 :     free( file_buf ); return -1;
      83           0 :   }
      84             : 
      85         189 :   char const * p   = (char const *)file_buf;
      86         189 :   char const * end = p + file_sz;
      87         189 :   ulong loaded          = 0;
      88         189 :   ulong unsupported_key = 0;
      89             : 
      90        1935 :   while( p < end ) {
      91             :     /* Find next PEM certificate block */
      92        1935 :     char const * begin = find_line( p, end, PEM_BEGIN, PEM_BEGIN_SZ );
      93        1935 :     if( !begin ) break;
      94        1749 :     char const * b64 = begin + PEM_BEGIN_SZ;
      95             : 
      96        1749 :     char const * next_begin = find_line( b64, end, PEM_BEGIN, PEM_BEGIN_SZ );
      97        1749 :     char const * finish = find_line( b64, next_begin ? next_begin : end, PEM_END, PEM_END_SZ );
      98        1749 :     if( !finish && next_begin ) {
      99           3 :       p = next_begin;
     100           3 :       continue;
     101           3 :     }
     102        1746 :     if( !finish ) break;
     103        1746 :     p = finish + PEM_END_SZ;
     104             : 
     105        1746 :     uchar der[ FD_BASE64_DEC_SZ(PEM_B64_STRIPPED_MAX) ];
     106        1746 :     long der_sz = pem_b64_decode( der, sizeof(der), b64, (ulong)(finish - b64) );
     107        1746 :     if( FD_UNLIKELY( -1L==der_sz ) ) continue;
     108             : 
     109        1743 :     fd_x509_cert_info_t info;
     110        1743 :     char const * reason = NULL;
     111        1743 :     if(      fd_x509_cert_parse( der, (ulong)der_sz, &info ) )    reason = "parse failed";
     112        1737 :     else if( info.key_type == FD_X509_KEY_UNKNOWN )               { unsupported_key++; continue; }
     113        1734 :     else if( info.subject_len > FD_X509_CA_SUBJECT_MAX )          reason = "subject too long";
     114        1734 :     else if( info.pubkey_len > sizeof(store->entries[0].pubkey) ) reason = "public key too long";
     115        1734 :     else if( !info.is_ca )                                        reason = "not a CA";
     116        1731 :     else if( info.has_key_usage &&
     117        1731 :              !( info.key_usage & FD_X509_KU_KEY_CERT_SIGN ) )     reason = "keyUsage lacks keyCertSign";
     118        1731 :     else if( info.has_ext_key_usage &&
     119        1731 :              !( info.ext_key_usage &
     120           6 :                 ( FD_X509_EKU_SERVER_AUTH|FD_X509_EKU_ANY ) ) )  reason = "extKeyUsage lacks serverAuth";
     121        1728 :     else if( info.name_constraints_permitted_len+
     122        1728 :              info.name_constraints_excluded_len >
     123        1728 :              FD_X509_CA_NAME_CONSTRAINTS_MAX )                    reason = "name constraints too long";
     124        1740 :     if( reason ) {
     125          12 :       FD_LOG_INFO(( "ignoring CA cert at %s offset %ld: %s",
     126          12 :                     pem_path, (long)(begin-(char const *)file_buf), reason ));
     127          12 :       continue;
     128          12 :     }
     129             : 
     130        1728 :     if( FD_UNLIKELY( store->cnt >= FD_X509_CA_STORE_MAX ) ) {
     131           3 :       FD_LOG_WARNING(( "CA bundle %s holds more than %lu trust anchors; the rest were dropped",
     132           3 :                        pem_path, FD_X509_CA_STORE_MAX ));
     133           3 :       break;
     134           3 :     }
     135             : 
     136        1725 :     fd_x509_ca_entry_t * e = &store->entries[ store->cnt++ ];
     137        1725 :     fd_memcpy( e->subject, info.subject, info.subject_len );
     138        1725 :     e->subject_len = info.subject_len;
     139        1725 :     fd_memcpy( e->pubkey, info.pubkey, info.pubkey_len );
     140        1725 :     e->pubkey_len = info.pubkey_len;
     141        1725 :     e->key_type   = info.key_type;
     142        1725 :     e->path_len_constraint     = info.path_len_constraint;
     143        1725 :     e->has_path_len_constraint = info.has_path_len_constraint;
     144        1725 :     fd_memcpy( e->name_constraints,
     145        1725 :                info.name_constraints_permitted, info.name_constraints_permitted_len );
     146        1725 :     fd_memcpy( e->name_constraints+info.name_constraints_permitted_len,
     147        1725 :                info.name_constraints_excluded,  info.name_constraints_excluded_len );
     148        1725 :     e->name_constraints_permitted_len = info.name_constraints_permitted_len;
     149        1725 :     e->name_constraints_excluded_len  = info.name_constraints_excluded_len;
     150        1725 :     e->has_name_constraints           = info.has_name_constraints;
     151        1725 :     loaded++;
     152        1725 :   }
     153             : 
     154         189 :   free( file_buf );
     155             : 
     156         189 :   if( unsupported_key )
     157           3 :     FD_LOG_INFO(( "ignored %lu CA certificates in %s with unsupported public key types",
     158         189 :                   unsupported_key, pem_path ));
     159             : 
     160         189 :   return (long)loaded;
     161         189 : }
     162             : 
     163             : long
     164           0 : fd_x509_ca_store_load_system( fd_x509_ca_store_t * store ) {
     165           0 :   static char const * const ca_paths[] = {
     166           0 :     "/etc/ssl/certs/ca-certificates.crt", /* Debian/Ubuntu */
     167           0 :     "/etc/pki/tls/certs/ca-bundle.crt",   /* RHEL/Fedora */
     168           0 :     "/etc/ssl/ca-bundle.pem",             /* openSUSE/SLES */
     169           0 :     "/etc/ssl/cert.pem",                  /* Alpine */
     170           0 :     NULL
     171           0 :   };
     172             : 
     173           0 :   for( ulong i=0UL; ca_paths[i]; i++ ) {
     174           0 :     long loaded = fd_x509_ca_store_load( store, ca_paths[i] );
     175           0 :     if( loaded<0L ) continue;
     176           0 :     if( FD_UNLIKELY( !loaded ) ) {
     177           0 :       FD_LOG_WARNING(( "CA bundle %s holds no usable trust anchor; ignoring it", ca_paths[i] ));
     178           0 :       continue;
     179           0 :     }
     180           0 :     FD_LOG_INFO(( "Loaded %ld CA certificates from %s", loaded, ca_paths[i] ));
     181           0 :     return loaded;
     182           0 :   }
     183             : 
     184           0 :   return -1L;
     185           0 : }
     186             : 
     187             : #endif /* FD_HAS_HOSTED */
     188             : 
     189             : fd_x509_ca_entry_t const *
     190             : fd_x509_ca_store_find_next( fd_x509_ca_store_t const * store,
     191             :                             uchar const *              subject,
     192             :                             ulong                      subject_len,
     193        2238 :                             ulong *                    idx ) {
     194        4617 :   for( ulong i=*idx; i < store->cnt; i++ ) {
     195        3669 :     fd_x509_ca_entry_t const * e = &store->entries[i];
     196        3669 :     if( fd_x509_name_equal( e->subject, e->subject_len, subject, subject_len ) ) {
     197        1290 :       *idx = i+1UL;
     198        1290 :       return e;
     199        1290 :     }
     200        3669 :   }
     201         948 :   *idx = store->cnt;
     202             :   return NULL;
     203        2238 : }

Generated by: LCOV version 1.14