LCOV - code coverage report
Current view: top level - ballet/x509 - fd_x509_ca_store.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 0 147 0.0 %
Date: 2026-10-22 18:10:43 Functions: 0 5 0.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           0 : #define PEM_BEGIN "-----BEGIN CERTIFICATE-----"
      15           0 : #define PEM_END   "-----END CERTIFICATE-----"
      16           0 : #define PEM_BEGIN_SZ (sizeof(PEM_BEGIN)-1)
      17           0 : #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           0 : find_line( char const * p, char const * end, char const * needle, ulong needle_sz ) {
      22           0 :   for( ; (ulong)(end-p)>=needle_sz; p++ ) {
      23           0 :     if( p[0]==needle[0] && !memcmp( p, needle, needle_sz ) ) return p;
      24           0 :   }
      25           0 :   return NULL;
      26           0 : }
      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           0 :                 char const * b64, ulong b64_sz ) {
      39           0 :   char stripped[ PEM_B64_STRIPPED_MAX ];
      40           0 :   ulong j = 0;
      41           0 :   for( ulong i=0; i<b64_sz; i++ ) {
      42           0 :     char c = b64[i];
      43           0 :     if( c=='\n' || c=='\r' || c==' ' || c=='\t' ) continue;
      44           0 :     if( FD_UNLIKELY( j >= sizeof(stripped) ) ) return -1;
      45           0 :     stripped[j++] = c;
      46           0 :   }
      47           0 :   while( j & 3 ) {
      48           0 :     if( FD_UNLIKELY( j >= sizeof(stripped) ) ) return -1;
      49           0 :     stripped[j++] = '=';
      50           0 :   }
      51           0 :   if( !j ) return -1;
      52           0 :   if( FD_UNLIKELY( FD_BASE64_DEC_SZ(j) > out_max ) ) return -1;
      53           0 :   return fd_base64_decode( out, stripped, j );
      54           0 : }
      55             : 
      56             : long
      57             : fd_x509_ca_store_load( fd_x509_ca_store_t * store,
      58           0 :                        char const *         pem_path ) {
      59           0 :   store->cnt = 0;
      60             : 
      61           0 :   int fd = open( pem_path, O_RDONLY|O_CLOEXEC );
      62           0 :   if( FD_UNLIKELY( fd<0 ) ) return -1;
      63             : 
      64           0 :   struct stat st;
      65           0 :   if( FD_UNLIKELY( fstat( fd, &st )<0 ) ) { close( fd ); return -1; }
      66           0 :   ulong file_sz = (ulong)st.st_size;
      67           0 :   if( FD_UNLIKELY( !file_sz ) ) { close( fd ); return -1; }
      68           0 :   if( FD_UNLIKELY( file_sz>CA_BUNDLE_SZ_MAX ) ) {
      69           0 :     FD_LOG_WARNING(( "CA bundle %s is %lu bytes, larger than the %lu byte limit",
      70           0 :                      pem_path, file_sz, CA_BUNDLE_SZ_MAX ));
      71           0 :     close( fd ); return -1;
      72           0 :   }
      73             : 
      74           0 :   uchar * file_buf = malloc( file_sz );
      75           0 :   if( FD_UNLIKELY( !file_buf ) ) { close( fd ); return -1; }
      76             : 
      77           0 :   ulong read_sz;
      78           0 :   int err = fd_io_read( fd, file_buf, file_sz, file_sz, &read_sz );
      79           0 :   (void)close( fd );
      80           0 :   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           0 :   char const * p   = (char const *)file_buf;
      86           0 :   char const * end = p + file_sz;
      87           0 :   ulong loaded          = 0;
      88           0 :   ulong unsupported_key = 0;
      89             : 
      90           0 :   while( p < end ) {
      91             :     /* Find next PEM certificate block */
      92           0 :     char const * begin = find_line( p, end, PEM_BEGIN, PEM_BEGIN_SZ );
      93           0 :     if( !begin ) break;
      94           0 :     char const * b64 = begin + PEM_BEGIN_SZ;
      95             : 
      96           0 :     char const * next_begin = find_line( b64, end, PEM_BEGIN, PEM_BEGIN_SZ );
      97           0 :     char const * finish = find_line( b64, next_begin ? next_begin : end, PEM_END, PEM_END_SZ );
      98           0 :     if( !finish && next_begin ) {
      99           0 :       p = next_begin;
     100           0 :       continue;
     101           0 :     }
     102           0 :     if( !finish ) break;
     103           0 :     p = finish + PEM_END_SZ;
     104             : 
     105           0 :     uchar der[ FD_BASE64_DEC_SZ(PEM_B64_STRIPPED_MAX) ];
     106           0 :     long der_sz = pem_b64_decode( der, sizeof(der), b64, (ulong)(finish - b64) );
     107           0 :     if( FD_UNLIKELY( -1L==der_sz ) ) continue;
     108             : 
     109           0 :     fd_x509_cert_info_t info;
     110           0 :     char const * reason = NULL;
     111           0 :     if(      fd_x509_cert_parse( der, (ulong)der_sz, &info ) )    reason = "parse failed";
     112           0 :     else if( info.key_type == FD_X509_KEY_UNKNOWN )               { unsupported_key++; continue; }
     113           0 :     else if( info.subject_len > FD_X509_CA_SUBJECT_MAX )          reason = "subject too long";
     114           0 :     else if( info.pubkey_len > sizeof(store->entries[0].pubkey) ) reason = "public key too long";
     115           0 :     else if( !info.is_ca )                                        reason = "not a CA";
     116           0 :     else if( info.has_key_usage &&
     117           0 :              !( info.key_usage & FD_X509_KU_KEY_CERT_SIGN ) )     reason = "keyUsage lacks keyCertSign";
     118           0 :     else if( info.has_ext_key_usage &&
     119           0 :              !( info.ext_key_usage &
     120           0 :                 ( FD_X509_EKU_SERVER_AUTH|FD_X509_EKU_ANY ) ) )  reason = "extKeyUsage lacks serverAuth";
     121           0 :     else if( info.name_constraints_permitted_len+
     122           0 :              info.name_constraints_excluded_len >
     123           0 :              FD_X509_CA_NAME_CONSTRAINTS_MAX )                    reason = "name constraints too long";
     124           0 :     if( reason ) {
     125           0 :       FD_LOG_INFO(( "ignoring CA cert at %s offset %ld: %s",
     126           0 :                     pem_path, (long)(begin-(char const *)file_buf), reason ));
     127           0 :       continue;
     128           0 :     }
     129             : 
     130           0 :     if( FD_UNLIKELY( store->cnt >= FD_X509_CA_STORE_MAX ) ) {
     131           0 :       FD_LOG_WARNING(( "CA bundle %s holds more than %lu trust anchors; the rest were dropped",
     132           0 :                        pem_path, FD_X509_CA_STORE_MAX ));
     133           0 :       break;
     134           0 :     }
     135             : 
     136           0 :     fd_x509_ca_entry_t * e = &store->entries[ store->cnt++ ];
     137           0 :     fd_memcpy( e->subject, info.subject, info.subject_len );
     138           0 :     e->subject_len = info.subject_len;
     139           0 :     fd_memcpy( e->pubkey, info.pubkey, info.pubkey_len );
     140           0 :     e->pubkey_len = info.pubkey_len;
     141           0 :     e->key_type   = info.key_type;
     142           0 :     e->path_len_constraint     = info.path_len_constraint;
     143           0 :     e->has_path_len_constraint = info.has_path_len_constraint;
     144           0 :     fd_memcpy( e->name_constraints,
     145           0 :                info.name_constraints_permitted, info.name_constraints_permitted_len );
     146           0 :     fd_memcpy( e->name_constraints+info.name_constraints_permitted_len,
     147           0 :                info.name_constraints_excluded,  info.name_constraints_excluded_len );
     148           0 :     e->name_constraints_permitted_len = info.name_constraints_permitted_len;
     149           0 :     e->name_constraints_excluded_len  = info.name_constraints_excluded_len;
     150           0 :     e->has_name_constraints           = info.has_name_constraints;
     151           0 :     loaded++;
     152           0 :   }
     153             : 
     154           0 :   free( file_buf );
     155             : 
     156           0 :   if( unsupported_key )
     157           0 :     FD_LOG_INFO(( "ignored %lu CA certificates in %s with unsupported public key types",
     158           0 :                   unsupported_key, pem_path ));
     159             : 
     160           0 :   return (long)loaded;
     161           0 : }
     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           0 :                             ulong *                    idx ) {
     194           0 :   for( ulong i=*idx; i < store->cnt; i++ ) {
     195           0 :     fd_x509_ca_entry_t const * e = &store->entries[i];
     196           0 :     if( fd_x509_name_equal( e->subject, e->subject_len, subject, subject_len ) ) {
     197           0 :       *idx = i+1UL;
     198           0 :       return e;
     199           0 :     }
     200           0 :   }
     201           0 :   *idx = store->cnt;
     202           0 :   return NULL;
     203           0 : }

Generated by: LCOV version 1.14