LCOV - code coverage report
Current view: top level - ballet/x509 - fd_x509.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 781 815 95.8 %
Date: 2026-09-17 04:28:31 Functions: 32 33 97.0 %

          Line data    Source code
       1             : #include "fd_x509.h"
       2             : #include "fd_der.h"
       3             : #include "../../util/net/fd_ip4.h"
       4             : #include "../secp256r1/fd_secp256r1.h"
       5             : #include "../secp384r1/fd_secp384r1.h"
       6             : #include "../utf8/fd_utf8.h"
       7             : #include <string.h>
       8             : 
       9             : /* Bounds on distinguished names this parser accepts: attributes per
      10             :    RDN, and characters per string-typed attribute value. */
      11             : 
      12             : #define FD_X509_RDN_ATV_MAX  (16UL)
      13             : #define FD_X509_DN_VALUE_MAX (512UL)
      14             : 
      15             : /* fd_x509_dn_string_width returns the code unit width of a string type
      16             :    the name comparator normalizes, or 0 for any other tag. */
      17             : 
      18             : static ulong
      19      348456 : fd_x509_dn_string_width( int tag ) {
      20      348456 :   switch( tag ) {
      21      348171 :   case FD_DER_TAG_UTF8_STRING:
      22      348399 :   case FD_DER_TAG_PRINTABLE_STR:
      23      348399 :   case FD_DER_TAG_TELETEX_STRING:
      24      348414 :   case FD_DER_TAG_IA5_STRING:       return 1UL;
      25          33 :   case FD_DER_TAG_BMP_STRING:       return 2UL;
      26           9 :   case FD_DER_TAG_UNIVERSAL_STRING: return 4UL;
      27           0 :   default:                          return 0UL;
      28      348456 :   }
      29      348456 : }
      30             : 
      31             : /* OID for algorithm IDs. */
      32             : 
      33             : /* Ed25519: 1.3.101.112 */
      34             : static uchar const oid_ed25519[] = { 0x06, 0x03, 0x2b, 0x65, 0x70 };
      35             : 
      36             : /* ecPublicKey: 1.2.840.10045.2.1 */
      37             : static uchar const oid_ec_pubkey[] = { 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01 };
      38             : 
      39             : /* prime256v1 (P-256): 1.2.840.10045.3.1.7 */
      40             : static uchar const oid_prime256v1[] = { 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07 };
      41             : 
      42             : /* secp384r1 (P-384): 1.3.132.0.34 */
      43             : static uchar const oid_secp384r1[] = { 0x06, 0x05, 0x2b, 0x81, 0x04, 0x00, 0x22 };
      44             : 
      45             : /* ecdsa-with-SHA256: 1.2.840.10045.4.3.2 */
      46             : static uchar const oid_ecdsa_sha256[] = { 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02 };
      47             : 
      48             : /* ecdsa-with-SHA384: 1.2.840.10045.4.3.3 */
      49             : static uchar const oid_ecdsa_sha384[] = { 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x03 };
      50             : 
      51             : /* rsaEncryption: 1.2.840.113549.1.1.1 */
      52             : static uchar const oid_rsa_encryption[] = { 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01 };
      53             : 
      54             : /* sha256WithRSAEncryption: 1.2.840.113549.1.1.11 */
      55             : static uchar const oid_rsa_sha256[] = { 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b };
      56             : 
      57             : /* sha384WithRSAEncryption: 1.2.840.113549.1.1.12 */
      58             : static uchar const oid_rsa_sha384[] = { 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0c };
      59             : 
      60             : /* sha512WithRSAEncryption: 1.2.840.113549.1.1.13 */
      61             : static uchar const oid_rsa_sha512[] = { 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0d };
      62             : 
      63             : /* DER NULL, the parameters of every RSA AlgorithmIdentifier */
      64             : static uchar const der_null[] = { 0x05, 0x00 };
      65             : 
      66             : /* subjectAltName: 2.5.29.17 */
      67             : static uchar const oid_san[] = { 0x06, 0x03, 0x55, 0x1d, 0x11 };
      68             : 
      69             : /* nameConstraints: 2.5.29.30 */
      70             : static uchar const oid_name_constraints[] = { 0x06, 0x03, 0x55, 0x1d, 0x1e };
      71             : 
      72             : /* basicConstraints: 2.5.29.19 */
      73             : static uchar const oid_basic_constraints[] = { 0x06, 0x03, 0x55, 0x1d, 0x13 };
      74             : 
      75             : /* OID 2.5.29.15 (keyUsage) */
      76             : static uchar const oid_key_usage[] = { 0x06, 0x03, 0x55, 0x1d, 0x0f };
      77             : 
      78             : /* OID 2.5.29.37 (extKeyUsage) */
      79             : static uchar const oid_ext_key_usage[] = { 0x06, 0x03, 0x55, 0x1d, 0x25 };
      80             : 
      81             : /* OID 1.3.6.1.5.5.7.3.1 (id-kp-serverAuth) */
      82             : static uchar const oid_kp_server_auth[] = { 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x01 };
      83             : 
      84             : /* OID 2.5.29.37.0 (anyExtendedKeyUsage) */
      85             : static uchar const oid_kp_any[] = { 0x06, 0x04, 0x55, 0x1d, 0x25, 0x00 };
      86             : 
      87             : 
      88             : /* fd_x509_rsa_alg_match matches an AlgorithmIdentifier content against
      89             :    an RSA OID.  RFC 4055 Section 5 requires the parameters to be NULL
      90             :    but tells implementations to accept them absent as well. */
      91             : 
      92             : static int
      93             : fd_x509_rsa_alg_match( uchar const * alg,
      94             :                        ulong         alg_len,
      95             :                        uchar const * oid,
      96         162 :                        ulong         oid_len ) {
      97         162 :   if( alg_len!=oid_len && alg_len!=oid_len+sizeof(der_null) ) return 0;
      98         162 :   if( memcmp( alg, oid, oid_len ) ) return 0;
      99         102 :   return alg_len==oid_len || !memcmp( alg+oid_len, der_null, sizeof(der_null) );
     100         162 : }
     101             : 
     102             : static uchar
     103      322194 : fd_x509_parse_sig_alg( uchar const * alg, ulong alg_len ) {
     104      322194 :   if( fd_der_oid_match( alg, alg_len, oid_ed25519, sizeof(oid_ed25519) ) )
     105      319614 :     return FD_X509_SIG_ED25519;
     106        2580 :   if( fd_der_oid_match( alg, alg_len, oid_ecdsa_sha256, sizeof(oid_ecdsa_sha256) ) )
     107        2274 :     return FD_X509_SIG_ECDSA_SHA256;
     108         306 :   if( fd_der_oid_match( alg, alg_len, oid_ecdsa_sha384, sizeof(oid_ecdsa_sha384) ) )
     109         204 :     return FD_X509_SIG_ECDSA_SHA384;
     110         102 :   if( fd_x509_rsa_alg_match( alg, alg_len, oid_rsa_sha256, sizeof(oid_rsa_sha256) ) )
     111          63 :     return FD_X509_SIG_RSA_SHA256;
     112          39 :   if( fd_x509_rsa_alg_match( alg, alg_len, oid_rsa_sha384, sizeof(oid_rsa_sha384) ) )
     113          18 :     return FD_X509_SIG_RSA_SHA384;
     114          21 :   if( fd_x509_rsa_alg_match( alg, alg_len, oid_rsa_sha512, sizeof(oid_rsa_sha512) ) )
     115          21 :     return FD_X509_SIG_RSA_SHA512;
     116           0 :   return FD_X509_SIG_UNKNOWN;
     117          21 : }
     118             : 
     119             : /* fd_x509_positive_integer checks that a DER INTEGER content is a
     120             :    positive value in minimal two's complement encoding (RFC 8017 Appendix
     121             :    A.1.1 requires positive modulus and exponent). */
     122             : 
     123             : static int
     124             : fd_x509_positive_integer( uchar const * p,
     125        2766 :                           ulong         len ) {
     126        2766 :   if( FD_UNLIKELY( !len || (p[0] & 0x80U) ) ) return 0;
     127        2763 :   if( p[0]==0x00 ) {
     128        1380 :     if( FD_UNLIKELY( len==1UL || !(p[1] & 0x80U) ) ) return 0;
     129        1380 :   }
     130        2760 :   return 1;
     131        2763 : }
     132             : 
     133             : /* fd_x509_parse_rsa_pubkey parses a DER RSAPublicKey.  Returns 0 on
     134             :    success, -1 if malformed, and 1 if well-formed but outside the range
     135             :    fd_rsa supports.  key may be NULL to only validate. */
     136             : 
     137             : static int
     138             : fd_x509_parse_rsa_pubkey( uchar const *     p,
     139             :                           ulong             len,
     140        1389 :                           fd_rsa_pubkey_t * key ) {
     141        1389 :   fd_der_cursor_t c = { .p=p, .end=p+len };
     142        1389 :   uchar const * n; ulong n_len;
     143        1389 :   uchar const * e; ulong e_len;
     144        1389 :   FD_DER_ENTER( c, FD_DER_TAG_SEQUENCE );
     145        1386 :     FD_DER_READ( c, FD_DER_TAG_INTEGER, n, n_len );
     146        1386 :     FD_DER_READ( c, FD_DER_TAG_INTEGER, e, e_len );
     147        1386 :     if( FD_UNLIKELY( !fd_x509_positive_integer( n, n_len ) ||
     148        1386 :                      !fd_x509_positive_integer( e, e_len ) ) ) return -1;
     149        1380 :   FD_DER_LEAVE( c );
     150        1380 :   if( FD_UNLIKELY( FD_DER_HAS_MORE( c ) ) ) return -1;
     151             : 
     152        1377 : #if FD_HAS_INT128
     153        1377 :   fd_rsa_pubkey_t tmp[1];
     154        1377 :   if( !key ) key = tmp;
     155        1377 :   if( FD_UNLIKELY( !fd_rsa_pubkey_init( key, n, n_len, e, e_len ) ) ) return 1;
     156        1371 :   return 0;
     157             : #else
     158             :   (void)key;
     159             :   return 1;
     160             : #endif
     161        1377 : }
     162             : 
     163             : int
     164             : fd_x509_decode_rsa_pubkey( uchar const *     pubkey,
     165             :                            ulong             pubkey_len,
     166         573 :                            fd_rsa_pubkey_t * key ) {
     167         573 :   if( FD_UNLIKELY( !pubkey || !key ) ) return -1;
     168         567 :   return fd_x509_parse_rsa_pubkey( pubkey, pubkey_len, key ) ? -1 : 0;
     169         573 : }
     170             : 
     171             : static int
     172             : fd_x509_parse_spki( fd_der_cursor_t * c,
     173             :                     uchar const **    out_pk,
     174             :                     ulong *           out_pk_len,
     175      322140 :                     uchar *           out_type ) {
     176             : 
     177             :   /* algorithm AlgorithmIdentifier ::= SEQUENCE {
     178             :        algorithm  OBJECT IDENTIFIER,
     179             :        parameters ANY DEFINED BY algorithm OPTIONAL } */
     180      322140 :   uchar const * alg_ptr; ulong alg_len;
     181      322140 :   FD_DER_READ( *c, FD_DER_TAG_SEQUENCE, alg_ptr, alg_len );
     182      322140 :   fd_der_cursor_t alg = { .p = alg_ptr, .end = alg_ptr+alg_len };
     183             : 
     184      322140 :   uchar const * oid; ulong oid_len;
     185      322140 :   FD_DER_READ_RAW( alg, FD_DER_TAG_OID, oid, oid_len );
     186             : 
     187      322137 :   uchar type = FD_X509_KEY_UNKNOWN;
     188      322137 :   if( fd_der_oid_match( oid, oid_len, oid_ed25519, sizeof(oid_ed25519) ) ) {
     189             :     /* RFC 8410 Section 3: parameters MUST be absent */
     190      318831 :     type = FD_X509_KEY_ED25519;
     191      318831 :   } else if( fd_der_oid_match( oid, oid_len, oid_ec_pubkey, sizeof(oid_ec_pubkey) ) ) {
     192             :     /* RFC 5480 Section 2.1.1: parameters is a namedCurve OID */
     193        2481 :     uchar const * curve; ulong curve_len;
     194        2481 :     FD_DER_READ_RAW( alg, FD_DER_TAG_OID, curve, curve_len );
     195        2478 :     if(      fd_der_oid_match( curve, curve_len, oid_prime256v1, sizeof(oid_prime256v1) ) ) type = FD_X509_KEY_ECDSA_P256;
     196          39 :     else if( fd_der_oid_match( curve, curve_len, oid_secp384r1,  sizeof(oid_secp384r1)  ) ) type = FD_X509_KEY_ECDSA_P384;
     197        2478 :   } else if( fd_der_oid_match( oid, oid_len, oid_rsa_encryption, sizeof(oid_rsa_encryption) ) ) {
     198             :     /* RFC 3279 Section 2.3.1: parameters MUST be NULL.  Absent is
     199             :        tolerated, matching the RFC 4055 rule for the signature OIDs. */
     200         822 :     if( FD_DER_HAS_MORE( alg ) ) {
     201         822 :       uchar const * params; ulong params_len;
     202         822 :       FD_DER_READ( alg, FD_DER_TAG_NULL, params, params_len );
     203         822 :       (void)params;
     204         822 :       if( FD_UNLIKELY( params_len ) ) return -1;
     205         822 :     }
     206         822 :     type = FD_X509_KEY_RSA;
     207         822 :   } else if( FD_DER_HAS_MORE( alg ) ) {
     208           0 :     FD_DER_SKIP( alg );
     209           0 :   }
     210      322134 :   if( FD_UNLIKELY( FD_DER_HAS_MORE( alg ) ) ) return -1;
     211             : 
     212             :   /* subjectPublicKey BIT STRING */
     213      322131 :   uchar const * bits; ulong bits_len;
     214      322131 :   FD_DER_READ_BITS( *c, bits, bits_len );
     215             : 
     216      322131 :   switch( type ) {
     217      318828 :   case FD_X509_KEY_ED25519:
     218      318828 :     if( FD_UNLIKELY( bits_len != 32 ) ) return -1;
     219      318828 :     break;
     220      318828 :   case FD_X509_KEY_ECDSA_P256: {
     221        2439 :     if( FD_UNLIKELY( bits_len != 65 ) ) return -1;
     222        2439 :     uchar compressed[ 33 ];
     223        2439 :     if( FD_UNLIKELY( fd_secp256r1_public_key_compress( compressed, bits )
     224        2439 :                      !=FD_SECP256R1_SUCCESS ) ) return -1;
     225        2439 :     break;
     226        2439 :   }
     227        2439 :   case FD_X509_KEY_ECDSA_P384: {
     228          33 :     if( FD_UNLIKELY( bits_len != 97 ) ) return -1;
     229          33 :     uchar compressed[ 49 ];
     230          33 :     if( FD_UNLIKELY( fd_secp384r1_public_key_compress( compressed, bits )
     231          33 :                      !=FD_SECP384R1_SUCCESS ) ) return -1;
     232          30 :     break;
     233          33 :   }
     234         822 :   case FD_X509_KEY_RSA: {
     235         822 :     int err = fd_x509_parse_rsa_pubkey( bits, bits_len, NULL );
     236         822 :     if( FD_UNLIKELY( err<0 ) ) return -1;
     237         822 :     if( err ) type = FD_X509_KEY_UNKNOWN;  /* e.g. RSA-1024 */
     238         822 :     break;
     239         822 :   }
     240           9 :   default: /* well-formed but unsupported */
     241           9 :     break;
     242      322131 :   }
     243             : 
     244      322128 :   *out_pk     = bits;
     245      322128 :   *out_pk_len = bits_len;
     246      322128 :   *out_type   = type;
     247      322128 :   return 0;
     248      322131 : }
     249             : 
     250             : static int
     251             : fd_x509_parse_path_len( uchar const * p,
     252             :                         ulong         len,
     253         546 :                         ulong *       out ) {
     254             :   /* INTEGER is signed.  pathLenConstraint is non-negative, and DER
     255             :      requires the shortest possible two's-complement encoding. */
     256         546 :   if( FD_UNLIKELY( !len || (p[0] & 0x80U) ) ) return -1;
     257         540 :   if( FD_UNLIKELY( len>1UL && p[0]==0x00 && !(p[1] & 0x80U) ) ) return -1;
     258             : 
     259             :   /* Values larger than ulong are valid ASN.1 (MAX is unbounded).  Such a
     260             :      value cannot constrain a path capped at FD_X509_CHAIN_MAX, so saturate
     261             :      it instead of rejecting an otherwise valid certificate. */
     262         537 :   ulong path_len = 0UL;
     263        1077 :   for( ulong i=0UL; i<len; i++ ) {
     264         540 :     if( FD_UNLIKELY( path_len>(~0UL >> 8) ) ) {
     265           0 :       path_len = ~0UL;
     266           0 :       break;
     267           0 :     }
     268         540 :     path_len = (path_len << 8) | (ulong)p[i];
     269         540 :   }
     270         537 :   *out = path_len;
     271         537 :   return 0;
     272         540 : }
     273             : 
     274             : static int
     275             : fd_x509_serial_valid( uchar const * p,
     276      322197 :                       ulong         len ) {
     277             :   /* RFC 5280 requires a positive serial number no longer than 20 octets.
     278             :      DER INTEGERs are signed and minimally encoded.
     279             : 
     280             :      Exception: serial 0 is allowed.  It is in the Mozilla root store:
     281             :        Hellenic Academic and Research Institutions ECC RootCA 2015
     282             :        sha256 44b545aa8a25e65a73ca15dc27fc36d24c1cb9953a066539b11582dc487b4833 */
     283      322197 :   if( FD_UNLIKELY( !len || len>20UL || (p[0] & 0x80U) ) ) return 0;
     284      322194 :   if( p[0]==0x00 ) {
     285           6 :     if( FD_UNLIKELY( len>1UL && !(p[1] & 0x80U) ) ) return 0;
     286           6 :   }
     287      322194 :   return 1;
     288      322194 : }
     289             : 
     290             : static int
     291             : fd_x509_unique_id_valid( uchar const * p,
     292           0 :                          ulong         len ) {
     293             :   /* UniqueIdentifier is an IMPLICIT BIT STRING. */
     294           0 :   if( FD_UNLIKELY( !len || p[0]>7U ) ) return 0;
     295           0 :   if( len==1UL ) return p[0]==0U;
     296           0 :   return !( p[len-1UL] & (uchar)( (1U<<p[0])-1U ) );
     297           0 : }
     298             : 
     299             : /* fd_x509_name_valid checks that [p,p+len) is one Name TLV in the shape
     300             :    fd_x509_name_equal and fd_x509_name_prefix compare: a SEQUENCE of
     301             :    non-empty SETs of SEQUENCE { OID, value }, at most FD_X509_RDN_ATV_MAX
     302             :    attributes per SET, string values whole in their code unit and at
     303             :    most FD_X509_DN_VALUE_MAX characters.  Parsing rejects any other Name
     304             :    so that a comparison can only fail because two names differ, never
     305             :    because one could not be read: an excluded-subtree check that fails
     306             :    to compare would otherwise pass the name. */
     307             : 
     308             : static int
     309             : fd_x509_name_valid( uchar const * p,
     310      644484 :                     ulong         len ) {
     311      644484 :   fd_der_cursor_t c = { .p=p, .end=p+len };
     312      644484 :   int tag; ulong seq_len;
     313      644484 :   if( FD_UNLIKELY( fd_der_read_tl( &c, &tag, &seq_len ) ||
     314      644484 :                    tag!=(int)FD_DER_TAG_SEQUENCE || c.p+seq_len!=c.end ) ) return 0;
     315      976464 :   while( FD_DER_HAS_MORE( c ) ) {
     316      332031 :     ulong set_len;
     317      332031 :     if( FD_UNLIKELY( fd_der_read_tl( &c, &tag, &set_len ) ||
     318      332031 :                      tag!=(int)FD_DER_TAG_SET || !set_len ) ) return 0;
     319      332022 :     fd_der_cursor_t set = { .p=c.p, .end=c.p+set_len };
     320      332022 :     c.p += set_len;
     321      332022 :     ulong atv_cnt = 0UL;
     322      664098 :     while( FD_DER_HAS_MORE( set ) ) {
     323      332118 :       if( FD_UNLIKELY( ++atv_cnt>FD_X509_RDN_ATV_MAX ) ) return 0;
     324      332112 :       ulong atv_len;
     325      332112 :       if( FD_UNLIKELY( fd_der_read_tl( &set, &tag, &atv_len ) ||
     326      332112 :                        tag!=(int)FD_DER_TAG_SEQUENCE ) ) return 0;
     327      332112 :       fd_der_cursor_t atv = { .p=set.p, .end=set.p+atv_len };
     328      332112 :       set.p += atv_len;
     329      332112 :       ulong oid_len;
     330      332112 :       if( FD_UNLIKELY( fd_der_read_tl( &atv, &tag, &oid_len ) ||
     331      332112 :                        tag!=(int)FD_DER_TAG_OID || !fd_der_oid_valid( atv.p, oid_len ) ) ) return 0;
     332      332112 :       atv.p += oid_len;
     333      332112 :       ulong val_len;
     334      332112 :       if( FD_UNLIKELY( fd_der_read_tl( &atv, &tag, &val_len ) ) ) return 0;
     335      332112 :       ulong width = fd_x509_dn_string_width( tag );
     336      332112 :       if( width && FD_UNLIKELY( val_len%width || val_len/width>FD_X509_DN_VALUE_MAX ) ) return 0;
     337      332106 :       if( tag==(int)FD_DER_TAG_UTF8_STRING &&
     338      332106 :           FD_UNLIKELY( !fd_utf8_verify( (char const *)atv.p, val_len ) ) ) return 0;
     339     4048563 :       for( ulong i=0UL; width && i<val_len; i+=width ) {
     340     3716481 :         uint cp = 0U;
     341     7433010 :         for( ulong k=0UL; k<width; k++ ) cp = (cp<<8) | atv.p[i+k];
     342     3716481 :         if( width>1UL && FD_UNLIKELY( cp>0x10ffffU || (cp>=0xd800U && cp<=0xdfffU) ) ) return 0;
     343     3716472 :         if( tag==(int)FD_DER_TAG_IA5_STRING && FD_UNLIKELY( cp>0x7fU ) ) return 0;
     344     3716469 :         if( tag==(int)FD_DER_TAG_PRINTABLE_STR &&
     345     3716469 :             FD_UNLIKELY( !( (cp>='a' && cp<='z') || (cp>='A' && cp<='Z') ||
     346     3716469 :                             (cp>='0' && cp<='9') || (cp && strchr( " '()+,-./:=?", (int)cp )) ) ) ) return 0;
     347     3716469 :       }
     348      332082 :       atv.p += val_len;
     349      332082 :       if( FD_UNLIKELY( atv.p!=atv.end ) ) return 0;
     350      332082 :     }
     351      332022 :   }
     352      644433 :   return 1;
     353      644484 : }
     354             : 
     355             : static int
     356             : fd_x509_general_name_valid( int           tag,
     357             :                             uchar const * p,
     358      326145 :                             ulong         len ) {
     359      326145 :   switch( tag ) {
     360          30 :   case FD_DER_TAG_CONTEXT(0): /* otherName */
     361          30 :   case FD_DER_TAG_CONTEXT(3): /* x400Address */
     362          30 :   case FD_DER_TAG_CONTEXT(5): /* ediPartyName */
     363             :     /* not worth validating, we don't look at these */
     364          30 :     return !!len;
     365         114 :   case FD_DER_TAG_CONTEXT(4): /* directoryName: a Name SEQUENCE */
     366         114 :     return fd_x509_name_valid( p, len );
     367           9 :   case FD_DER_TAG_CONTEXT_PRIM(1): /* rfc822Name */
     368          15 :   case FD_DER_TAG_CONTEXT_PRIM(6): /* uniformResourceIdentifier */
     369          15 :     if( FD_UNLIKELY( !len ) ) return 0;
     370          27 :     for( ulong i=0UL; i<len; i++ )
     371          24 :       if( FD_UNLIKELY( p[i] & 0x80U ) ) return 0;
     372           3 :     return 1;
     373      325827 :   case FD_DER_TAG_CONTEXT_PRIM(2): { /* dNSName */
     374      325827 :     ulong off = len>2UL && p[0]=='*' && p[1]=='.' ? 2UL : 0UL;
     375      325827 :     return fd_x509_dns_name_valid( (char const *)p+off, len-off );
     376           3 :   }
     377         135 :   case FD_DER_TAG_CONTEXT_PRIM(7): /* iPAddress */
     378         135 :     return len==4UL || len==16UL;
     379           6 :   case FD_DER_TAG_CONTEXT_PRIM(8): /* registeredID */
     380           6 :     return fd_der_oid_valid( p, len );
     381          18 :   default:
     382          18 :     return 0;
     383      326145 :   }
     384      326145 : }
     385             : 
     386             : /* Validate GeneralSubtrees.  The verifier enforces dNSName, iPAddress
     387             :    and directoryName bases.  Other name forms are accepted here since a
     388             :    CA may constrain forms a cert never uses (real CA/B Forum
     389             :    intermediates permit a dNSName and exclude iPAddress 0.0.0.0/0 and
     390             :    ::/0 in the same extension); the verifier rejects any cert carrying
     391             :    a SAN of such a constrained form.  rfc822Name is the exception: RFC
     392             :    5280 Section 4.2.1.10 also applies it to subject emailAddress
     393             :    attributes, which this verifier does not match, so a CA constraining
     394             :    mail addresses is rejected outright rather than half-enforced.  Web
     395             :    PKI issuing CAs do not carry such constraints.  minimum and maximum
     396             :    are unsupported; minimum's DEFAULT is zero and omitted in DER. */
     397             : 
     398             : static int
     399             : fd_x509_subtrees_valid( uchar const * p,
     400         315 :                         ulong         len ) {
     401         315 :   fd_der_cursor_t trees = { .p=p, .end=p+len };
     402         315 :   if( FD_UNLIKELY( !len ) ) return -1;
     403             : 
     404         702 :   while( FD_DER_HAS_MORE( trees ) ) {
     405         399 :     uchar const * tree_ptr; ulong tree_len;
     406         399 :     FD_DER_READ( trees, FD_DER_TAG_SEQUENCE, tree_ptr, tree_len );
     407         399 :     fd_der_cursor_t tree = { .p=tree_ptr, .end=tree_ptr+tree_len };
     408             : 
     409         399 :     int tag; ulong base_len;
     410         399 :     if( FD_UNLIKELY( fd_der_read_tl( &tree, &tag, &base_len ) ) ) return -1;
     411         399 :     uchar const * base = tree.p;
     412         399 :     tree.p += base_len;
     413         399 :     if( FD_UNLIKELY( FD_DER_HAS_MORE( tree ) ) ) return -1;
     414             : 
     415         399 :     switch( tag ) {
     416         159 :     case FD_DER_TAG_CONTEXT_PRIM(2): { /* dNSName */
     417         159 :       if( FD_UNLIKELY( !base_len ) ) return -1;
     418         159 :       ulong off = base[0]=='.';
     419         159 :       if( FD_UNLIKELY( !fd_x509_dns_name_valid( (char const *)base+off, base_len-off ) ) ) return -1;
     420         159 :       break;
     421         159 :     }
     422         159 :     case FD_DER_TAG_CONTEXT_PRIM(7): /* iPAddress: address || mask */
     423          90 :       if( FD_UNLIKELY( base_len!=8UL && base_len!=32UL ) ) return -1;
     424          90 :       break;
     425          90 :     case FD_DER_TAG_CONTEXT_PRIM(1): /* rfc822Name */
     426          12 :       return -1;
     427         138 :     default:
     428         138 :       if( FD_UNLIKELY( !fd_x509_general_name_valid( tag, base, base_len ) ) ) return -1;
     429         138 :       break;
     430         399 :     }
     431         399 :   }
     432         303 :   return 0;
     433         315 : }
     434             : 
     435             : static int
     436             : fd_x509_parse_extensions( fd_der_cursor_t *     c,
     437      321891 :                           fd_x509_cert_info_t * out ) {
     438             : 
     439      321891 :   uchar const * seen_oid[ FD_X509_EXT_MAX ];
     440      321891 :   ulong         seen_len[ FD_X509_EXT_MAX ];
     441      321891 :   ulong         seen_cnt = 0UL;
     442             : 
     443      965691 :   while( FD_DER_HAS_MORE( *c ) ) {
     444             :     /* Each Extension is a SEQUENCE { OID, BOOLEAN?, OCTET STRING } */
     445      643992 :     uchar const * ext_ptr; ulong ext_len;
     446      643992 :     FD_DER_READ( *c, FD_DER_TAG_SEQUENCE, ext_ptr, ext_len );
     447             : 
     448      643992 :     fd_der_cursor_t ext = { .p = ext_ptr, .end = ext_ptr + ext_len };
     449             : 
     450      643992 :     uchar const * oid_raw; ulong oid_raw_len;
     451      643992 :     FD_DER_READ_RAW( ext, FD_DER_TAG_OID, oid_raw, oid_raw_len );
     452             : 
     453      643989 :     if( FD_UNLIKELY( seen_cnt==FD_X509_EXT_MAX ) ) return -1;
     454     1098087 :     for( ulong i=0UL; i<seen_cnt; i++ ) {
     455      454119 :       if( FD_UNLIKELY( fd_der_oid_match( oid_raw, oid_raw_len, seen_oid[i], seen_len[i] ) ) ) return -1;
     456      454119 :     }
     457      643968 :     seen_oid[ seen_cnt ] = oid_raw;
     458      643968 :     seen_len[ seen_cnt++ ] = oid_raw_len;
     459             : 
     460      643968 :     int critical = 0;
     461      643968 :     if( ext.p<ext.end && *ext.p==FD_DER_TAG_BOOLEAN ) {
     462      629373 :       uchar const * critical_ptr; ulong critical_len;
     463      629373 :       FD_DER_READ( ext, FD_DER_TAG_BOOLEAN, critical_ptr, critical_len );
     464             :       /* critical is DEFAULT FALSE, so DER permits only explicit TRUE. */
     465      629373 :       if( FD_UNLIKELY( critical_len!=1UL || critical_ptr[0]!=0xFF ) ) return -1;
     466      629370 :       critical = 1;
     467      629370 :     }
     468             : 
     469             :     /* OCTET STRING wrapping the extension value */
     470      643965 :     uchar const * val_ptr; ulong val_len;
     471      643965 :     FD_DER_READ( ext, FD_DER_TAG_OCTET_STRING, val_ptr, val_len );
     472      643956 :     if( FD_UNLIKELY( FD_DER_HAS_MORE( ext ) ) ) return -1;
     473             : 
     474             :     /* basicConstraints (2.5.29.19)
     475             :          BasicConstraints ::= SEQUENCE {
     476             :            cA                BOOLEAN DEFAULT FALSE,
     477             :            pathLenConstraint INTEGER (0..MAX) OPTIONAL } */
     478      643953 :     if( fd_der_oid_match( oid_raw, oid_raw_len,
     479      643953 :                           oid_basic_constraints, sizeof(oid_basic_constraints) ) ) {
     480             :       /* RFC 5280 Section 4.2 permits at most one instance of an extension. */
     481      315384 :       if( FD_UNLIKELY( out->has_basic_constraints ) ) return -1;
     482             : 
     483      315384 :       fd_der_cursor_t val = { .p = val_ptr, .end = val_ptr + val_len };
     484      315384 :       FD_DER_ENTER( val, FD_DER_TAG_SEQUENCE );
     485      315381 :         int bc_tag; FD_DER_PEEK_TAG_OR( val, bc_tag, 0 );
     486      315381 :         if( bc_tag == (int)FD_DER_TAG_BOOLEAN ) {
     487        2835 :           uchar const * ca_ptr; ulong ca_len;
     488        2835 :           FD_DER_READ( val, FD_DER_TAG_BOOLEAN, ca_ptr, ca_len );
     489             :           /* cA is DEFAULT FALSE, so DER permits only explicit TRUE. */
     490        2835 :           if( FD_UNLIKELY( ca_len!=1UL || ca_ptr[0]!=0xFF ) ) return -1;
     491        2826 :           if( FD_UNLIKELY( !critical ) ) return -1;
     492        2823 :           out->is_ca = 1;
     493        2823 :         }
     494      315369 :         int path_len_tag; FD_DER_PEEK_TAG_OR( val, path_len_tag, 0 );
     495      315369 :         if( path_len_tag == (int)FD_DER_TAG_INTEGER ) {
     496         549 :           if( FD_UNLIKELY( !out->is_ca ) ) return -1;
     497         546 :           uchar const * path_len_ptr; ulong path_len_len;
     498         546 :           FD_DER_READ( val, FD_DER_TAG_INTEGER, path_len_ptr, path_len_len );
     499         546 :           if( FD_UNLIKELY( fd_x509_parse_path_len( path_len_ptr, path_len_len,
     500         546 :                                                    &out->path_len_constraint ) ) ) return -1;
     501         537 :           out->has_path_len_constraint = 1;
     502         537 :         }
     503      315357 :       FD_DER_LEAVE( val );  /* rejects unconsumed SEQUENCE content */
     504             :       /* Reject trailing bytes in the extension's OCTET STRING */
     505      315357 :       if( FD_UNLIKELY( FD_DER_HAS_MORE( val ) ) ) return -1;
     506      315354 :       out->has_basic_constraints = 1;
     507      315354 :       continue;
     508      315357 :     }
     509             : 
     510             :     /* keyUsage (2.5.29.15)
     511             :          KeyUsage ::= BIT STRING { digitalSignature(0) ... decipherOnly(8) } */
     512      328569 :     if( fd_der_oid_match( oid_raw, oid_raw_len,
     513      328569 :                           oid_key_usage, sizeof(oid_key_usage) ) ) {
     514             :       /* RFC 5280 Section 4.2 permits at most one instance of an extension */
     515        1341 :       if( FD_UNLIKELY( out->has_key_usage ) ) return -1;
     516             : 
     517        1341 :       fd_der_cursor_t val = { .p = val_ptr, .end = val_ptr + val_len };
     518        1341 :       uchar const * bs; ulong bs_len;
     519        1341 :       FD_DER_READ( val, FD_DER_TAG_BIT_STRING, bs, bs_len );
     520             :       /* Reject trailing bytes in the extension's OCTET STRING */
     521        1338 :       if( FD_UNLIKELY( FD_DER_HAS_MORE( val ) ) ) return -1;
     522             : 
     523             :       /* Content is unused_bits || bits.  A 9 bit NamedBitList needs at
     524             :          most two octets of bits. */
     525        1335 :       if( FD_UNLIKELY( bs_len<2UL || bs_len>3UL ) ) return -1;
     526        1320 :       uint unused = bs[0];
     527        1320 :       if( FD_UNLIKELY( unused>7U ) ) return -1;
     528             : 
     529             :       /* DER zeroes the unused bits and trims trailing zero bits, so the
     530             :          final octet has no unused bits set and is itself nonzero.
     531             : 
     532             :          Exception: a trailing zero octet is allowed.  Two Mozilla roots
     533             :          encode keyUsage as 07 06 00 instead of 01 06:
     534             :            Trustwave Global ECC P256 Certification Authority
     535             :            sha256 945bbc825ea554f489d1fd51a73ddf2ea624ac7019a05205225c22a78ccfa8b4
     536             :            Trustwave Global ECC P384 Certification Authority
     537             :            sha256 55903859c8c0c3ebb8759ece4e2557225ff5758bbd38ebd48276601e1bd58097 */
     538        1317 :       uchar last = bs[ bs_len-1UL ];
     539        1317 :       if( FD_UNLIKELY( last & (uchar)( ( 1U<<unused ) - 1U ) ) ) return -1;
     540        1314 :       if( FD_LIKELY( last ) ) {
     541        1302 :         uint canonical_unused = 0U;
     542        8205 :         while( !(last & (uchar)(1U<<canonical_unused)) ) canonical_unused++;
     543        1302 :         if( FD_UNLIKELY( unused!=canonical_unused ) ) return -1;
     544        1302 :       } else if( FD_UNLIKELY( bs_len<3UL || !bs[1] ) ) {
     545           6 :         return -1;
     546           6 :       }
     547        1305 :       out->key_usage = (ushort)( ( (uint)bs[1] << 8 ) |
     548        1305 :                                  ( bs_len>2UL ? (uint)bs[2] : 0U ) );
     549        1305 :       out->has_key_usage = 1;
     550        1305 :       continue;
     551        1314 :     }
     552             : 
     553             :     /* extKeyUsage (2.5.29.37)
     554             :          ExtKeyUsageSyntax ::= SEQUENCE SIZE (1..MAX) OF KeyPurposeId */
     555      327228 :     if( fd_der_oid_match( oid_raw, oid_raw_len,
     556      327228 :                           oid_ext_key_usage, sizeof(oid_ext_key_usage) ) ) {
     557        2238 :       if( FD_UNLIKELY( out->has_ext_key_usage ) ) return -1;
     558             : 
     559        2238 :       fd_der_cursor_t val = { .p = val_ptr, .end = val_ptr + val_len };
     560        2238 :       ulong kp_cnt = 0UL;
     561        2238 :       FD_DER_ENTER( val, FD_DER_TAG_SEQUENCE );
     562        5253 :         while( FD_DER_HAS_MORE( val ) ) {
     563        3030 :           uchar const * kp; ulong kp_len;
     564        3030 :           FD_DER_READ_RAW( val, FD_DER_TAG_OID, kp, kp_len );
     565        3015 :           kp_cnt++;
     566        3015 :           if(      fd_der_oid_match( kp, kp_len, oid_kp_server_auth, sizeof(oid_kp_server_auth) ) )
     567        1584 :             out->ext_key_usage = (ushort)( out->ext_key_usage | FD_X509_EKU_SERVER_AUTH );
     568        1431 :           else if( fd_der_oid_match( kp, kp_len, oid_kp_any, sizeof(oid_kp_any) ) )
     569          15 :             out->ext_key_usage = (ushort)( out->ext_key_usage | FD_X509_EKU_ANY );
     570             :           /* Other key purposes are ignored */
     571        3015 :         }
     572        2223 :       FD_DER_LEAVE( val );
     573        2223 :       if( FD_UNLIKELY( !kp_cnt ) ) return -1;  /* SIZE (1..MAX) */
     574        2220 :       if( FD_UNLIKELY( FD_DER_HAS_MORE( val ) ) ) return -1;
     575        2217 :       out->has_ext_key_usage = 1;
     576        2217 :       continue;
     577        2220 :     }
     578             : 
     579             :     /* subjectAltName (2.5.29.17) */
     580      324990 :     if( fd_der_oid_match( oid_raw, oid_raw_len,
     581      324990 :                           oid_san, sizeof(oid_san) ) ) {
     582      318147 :       if( FD_UNLIKELY( out->has_subject_alt_name ) ) return -1;
     583      318147 :       if( FD_UNLIKELY( out->subject_len==2UL && !critical ) ) return -1;
     584             : 
     585      318141 :       fd_der_cursor_t val = { .p = val_ptr, .end = val_ptr + val_len };
     586             : 
     587             :       /* SEQUENCE OF GeneralName */
     588      318141 :       uchar const * san_ptr; ulong san_len;
     589      318141 :       FD_DER_READ( val, FD_DER_TAG_SEQUENCE, san_ptr, san_len );
     590      318141 :       if( FD_UNLIKELY( !san_len ) ) return -1;  /* SIZE (1..MAX) */
     591      318138 :       if( FD_UNLIKELY( FD_DER_HAS_MORE( val ) ) ) return -1;
     592             : 
     593      318138 :       out->san_general_names     = san_ptr;
     594      318138 :       out->san_general_names_len = san_len;
     595      318138 :       out->has_subject_alt_name  = 1;
     596             : 
     597      318138 :       fd_der_cursor_t san = { .p = san_ptr, .end = san_ptr + san_len };
     598      641586 :       while( FD_DER_HAS_MORE( san ) ) {
     599      323484 :         int gn_tag; ulong gn_len;
     600      323484 :         if( FD_UNLIKELY( fd_der_read_tl( &san, &gn_tag, &gn_len ) ) ) return -1;
     601      323481 :         if( FD_UNLIKELY( !fd_x509_general_name_valid( gn_tag, san.p, gn_len ) ) )
     602          33 :           return -1;
     603      323448 :         san.p += gn_len;
     604      323448 :       }
     605      318102 :       continue;
     606      318138 :     }
     607             : 
     608             :     /* nameConstraints (2.5.29.30) */
     609        6843 :     if( fd_der_oid_match( oid_raw, oid_raw_len,
     610        6843 :                           oid_name_constraints, sizeof(oid_name_constraints) ) ) {
     611         279 :       if( FD_UNLIKELY( out->has_name_constraints || !critical ) ) return -1;
     612             : 
     613         273 :       fd_der_cursor_t val = { .p=val_ptr, .end=val_ptr+val_len };
     614         273 :       FD_DER_ENTER( val, FD_DER_TAG_SEQUENCE );
     615         273 :         int last_tag = -1;
     616         576 :         while( FD_DER_HAS_MORE( val ) ) {
     617         315 :           int tag; ulong subtrees_len;
     618         315 :           if( FD_UNLIKELY( fd_der_read_tl( &val, &tag, &subtrees_len ) ) ) return -1;
     619         315 :           if( FD_UNLIKELY( (tag!=(int)FD_DER_TAG_CONTEXT(0) &&
     620         315 :                             tag!=(int)FD_DER_TAG_CONTEXT(1)) || tag<=last_tag ) ) return -1;
     621         315 :           uchar const * subtrees = val.p;
     622         315 :           val.p += subtrees_len;
     623         315 :           if( FD_UNLIKELY( fd_x509_subtrees_valid( subtrees, subtrees_len ) ) ) return -1;
     624         303 :           if( tag==(int)FD_DER_TAG_CONTEXT(0) ) {
     625         195 :             out->name_constraints_permitted     = subtrees;
     626         195 :             out->name_constraints_permitted_len = subtrees_len;
     627         195 :           } else {
     628         108 :             out->name_constraints_excluded     = subtrees;
     629         108 :             out->name_constraints_excluded_len = subtrees_len;
     630         108 :           }
     631         303 :           last_tag = tag;
     632         303 :         }
     633         261 :         if( FD_UNLIKELY( last_tag<0 ) ) return -1;
     634         258 :       FD_DER_LEAVE( val );
     635         258 :       if( FD_UNLIKELY( FD_DER_HAS_MORE( val ) ) ) return -1;
     636         258 :       out->has_name_constraints = 1;
     637         258 :       continue;
     638         258 :     }
     639             : 
     640             :     /* Unknown extension */
     641        6564 :     if( FD_UNLIKELY( critical ) ) return -1;
     642        6564 :   }
     643             : 
     644      321699 :   return 0;
     645      321891 : }
     646             : 
     647             : /* dec2 reads a two digit decimal number, or -1 if either character is
     648             :    not an ASCII digit. */
     649             : 
     650             : static int
     651     4181886 : dec2( uchar const * s ) {
     652     4181886 :   if( FD_UNLIKELY( s[0]<'0' || s[0]>'9' || s[1]<'0' || s[1]>'9' ) ) return -1;
     653     4181880 :   return (s[0]-'0')*10 + (s[1]-'0');
     654     4181886 : }
     655             : 
     656             : /* days_from_civil returns the number of days between 1970-01-01 and
     657             :    y-m-d (proleptic Gregorian).  Howard Hinnant's algorithm, valid for
     658             :    any year representable in a long. */
     659             : 
     660             : static long
     661             : days_from_civil( long y,
     662             :                  long m,
     663      644373 :                  long d ) {
     664      644373 :   y -= m<=2;
     665      644373 :   long era = (y>=0 ? y : y-399) / 400;
     666      644373 :   long yoe = y - era*400;                                     /* [0, 399] */
     667      644373 :   long doy = (153*(m + (m>2 ? -3 : 9)) + 2)/5 + d-1;          /* [0, 365] */
     668      644373 :   long doe = yoe*365 + yoe/4 - yoe/100 + doy;                 /* [0, 146096] */
     669      644373 :   return era*146097L + doe - 719468L;
     670      644373 : }
     671             : 
     672             : long
     673             : fd_x509_time_parse( uchar         tag,
     674             :                     uchar const * s,
     675      644424 :                     ulong         s_len ) {
     676      644424 :   long year;
     677             : 
     678      644424 :   if( tag==FD_DER_TAG_UTC_TIME ) {
     679      328950 :     if( FD_UNLIKELY( s_len!=13UL ) ) return FD_X509_TIME_INVALID;
     680      328935 :     int yy = dec2( s );
     681      328935 :     if( FD_UNLIKELY( yy<0 ) ) return FD_X509_TIME_INVALID;
     682             :     /* RFC 5280 Section 4.1.2.5.1 */
     683      328935 :     year = yy>=50 ? 1900L+yy : 2000L+yy;
     684      328935 :     s += 2;
     685      328935 :   } else if( tag==FD_DER_TAG_GENERALIZED_TIME ) {
     686      315471 :     if( FD_UNLIKELY( s_len!=15UL ) ) return FD_X509_TIME_INVALID;
     687      315468 :     int hi = dec2( s );
     688      315468 :     int lo = dec2( s+2 );
     689      315468 :     if( FD_UNLIKELY( hi<0 || lo<0 ) ) return FD_X509_TIME_INVALID;
     690      315468 :     year = hi*100L + lo;
     691      315468 :     s += 4;
     692      315468 :   } else {
     693           3 :     return FD_X509_TIME_INVALID;
     694           3 :   }
     695             : 
     696      644403 :   int mon = dec2( s   );
     697      644403 :   int day = dec2( s+2 );
     698      644403 :   int hh  = dec2( s+4 );
     699      644403 :   int mm  = dec2( s+6 );
     700      644403 :   int ss  = dec2( s+8 );
     701      644403 :   if( FD_UNLIKELY( mon<0 || day<0 || hh<0 || mm<0 || ss<0 ) ) return FD_X509_TIME_INVALID;
     702      644397 :   if( FD_UNLIKELY( s[10]!='Z' ) ) return FD_X509_TIME_INVALID;
     703             : 
     704      644397 :   if( FD_UNLIKELY( mon<1 || mon>12 ) ) return FD_X509_TIME_INVALID;
     705             : 
     706      644391 :   static uchar const mon_days[ 12 ] = { 31,28,31,30,31,30,31,31,30,31,30,31 };
     707      644391 :   int leap = ( !(year%4) && (year%100) ) || !(year%400);
     708      644391 :   int day_max = mon_days[ mon-1 ] + ( mon==2 && leap );
     709      644391 :   if( FD_UNLIKELY( day<1 || day>day_max ) ) return FD_X509_TIME_INVALID;
     710             : 
     711             :   /* Leap seconds (ss==60) are rejected: RFC 5280 does not require them */
     712      644382 :   if( FD_UNLIKELY( hh>23 || mm>59 || ss>59 ) ) return FD_X509_TIME_INVALID;
     713             : 
     714      644373 :   return days_from_civil( year, mon, day )*86400L + hh*3600L + mm*60L + ss;
     715      644382 : }
     716             : 
     717             : int
     718             : fd_x509_cert_parse( uchar const *         cert,
     719             :                     ulong                 cert_sz,
     720      322980 :                     fd_x509_cert_info_t * out ) {
     721             : 
     722      322980 :   if( FD_UNLIKELY( !cert || !out ) ) return -1;
     723             : 
     724      322974 :   fd_memset( out, 0, sizeof(fd_x509_cert_info_t) );
     725      322974 :   out->key_type = FD_X509_KEY_UNKNOWN;
     726      322974 :   out->sig_alg  = FD_X509_SIG_UNKNOWN;
     727             : 
     728      322974 :   uchar const * tbs_sig_alg     = NULL;
     729      322974 :   ulong         tbs_sig_alg_len = 0UL;
     730             : 
     731      322974 :   FD_DER_CURSOR_FROM_BUF( c, cert, cert_sz );
     732             : 
     733      322974 :   FD_DER_ENTER( c, FD_DER_TAG_SEQUENCE );
     734             : 
     735             :     /* tbsCertificate */
     736      322212 :     uchar const * tbs_start = c.p;
     737      322212 :     uchar const * tbs_ptr; ulong tbs_content_len;
     738      322212 :     FD_DER_READ( c, FD_DER_TAG_SEQUENCE, tbs_ptr, tbs_content_len );
     739      322206 :     out->tbs     = tbs_start;
     740      322206 :     out->tbs_len = (ulong)( c.p - tbs_start );
     741             : 
     742      322206 :     {
     743      322206 :       fd_der_cursor_t tbs = { .p = tbs_ptr, .end = tbs_ptr + tbs_content_len };
     744             : 
     745             :       /* version [0] EXPLICIT Version DEFAULT v1 */
     746      322206 :       if( FD_DER_HAS_MORE( tbs ) && *tbs.p==FD_DER_TAG_CONTEXT(0) ) {
     747      322206 :         FD_DER_ENTER( tbs, FD_DER_TAG_CONTEXT(0) );
     748      322206 :           uchar const * version; ulong version_len;
     749      322206 :           FD_DER_READ( tbs, FD_DER_TAG_INTEGER, version, version_len );
     750             :           /* Explicit v1 violates DEFAULT encoding.  Only v1/v2/v3 exist. */
     751      322203 :           if( FD_UNLIKELY( version_len!=1UL || version[0]<1U || version[0]>2U ) ) return -1;
     752      322200 :           out->version = version[0];
     753      322200 :         FD_DER_LEAVE( tbs );
     754      322206 :       }
     755             : 
     756             :       /* serialNumber CertificateSerialNumber */
     757      322200 :       uchar const * serial; ulong serial_len;
     758      322200 :       FD_DER_READ( tbs, FD_DER_TAG_INTEGER, serial, serial_len );
     759      322197 :       if( FD_UNLIKELY( !fd_x509_serial_valid( serial, serial_len ) ) ) return -1;
     760             : 
     761             :       /* signature AlgorithmIdentifier SEQUENCE */
     762      322194 :       FD_DER_READ( tbs, FD_DER_TAG_SEQUENCE, tbs_sig_alg, tbs_sig_alg_len );
     763      322194 :       out->sig_alg = fd_x509_parse_sig_alg( tbs_sig_alg, tbs_sig_alg_len );
     764             : 
     765             :       /* issuer Name SEQUENCE */
     766      322194 :       FD_DER_READ_RAW( tbs, FD_DER_TAG_SEQUENCE, out->issuer, out->issuer_len );
     767      322194 :       if( FD_UNLIKELY( out->issuer_len==2UL ||
     768      322194 :                        !fd_x509_name_valid( out->issuer, out->issuer_len ) ) ) return -1;
     769             : 
     770             :       /* validity SEQUENCE { notBefore, notAfter } */
     771      322179 :       FD_DER_ENTER( tbs, FD_DER_TAG_SEQUENCE );
     772      322179 :         FD_DER_READ_TIME( tbs, out->not_before_tag, out->not_before, out->not_before_len );
     773      322179 :         FD_DER_READ_TIME( tbs, out->not_after_tag,  out->not_after,  out->not_after_len  );
     774      322179 :         out->not_before_unix = fd_x509_time_parse( out->not_before_tag, out->not_before, out->not_before_len );
     775      322179 :         out->not_after_unix  = fd_x509_time_parse( out->not_after_tag,  out->not_after,  out->not_after_len  );
     776      322179 :       FD_DER_LEAVE( tbs );
     777             : 
     778             :       /* subject Name SEQUENCE */
     779      322179 :       FD_DER_READ_RAW( tbs, FD_DER_TAG_SEQUENCE, out->subject, out->subject_len );
     780      322179 :       if( FD_UNLIKELY( !fd_x509_name_valid( out->subject, out->subject_len ) ) ) return -1;
     781             : 
     782             :       /* subjectPublicKeyInfo SEQUENCE */
     783      322140 :       FD_DER_ENTER( tbs, FD_DER_TAG_SEQUENCE );
     784      322140 :         if( FD_UNLIKELY( fd_x509_parse_spki( &tbs, &out->pubkey,
     785      322140 :                                               &out->pubkey_len,
     786      322140 :                                               &out->key_type ) ) )
     787          12 :           return -1;
     788      322128 :       FD_DER_LEAVE( tbs );
     789             : 
     790             :       /* issuerUniqueID [1]
     791             :          subjectUniqueID [2]
     792             :          extensions [3] */
     793      322128 :       int last_optional = 0;
     794      643827 :       while( FD_DER_HAS_MORE( tbs ) ) {
     795      321894 :         int next_tag;
     796      321894 :         FD_DER_PEEK_TAG( tbs, next_tag );
     797             : 
     798      321894 :         if( next_tag==(int)FD_DER_TAG_CONTEXT_PRIM(1) ||
     799      321894 :             next_tag==(int)FD_DER_TAG_CONTEXT_PRIM(2) ) {
     800           0 :           int field = next_tag & 7;
     801           0 :           if( FD_UNLIKELY( out->version<1U || field<=last_optional ) ) return -1;
     802           0 :           int tag; ulong unique_id_len;
     803           0 :           if( FD_UNLIKELY( fd_der_read_tl( &tbs, &tag, &unique_id_len ) ) ) return -1;
     804           0 :           if( FD_UNLIKELY( !fd_x509_unique_id_valid( tbs.p, unique_id_len ) ) ) return -1;
     805           0 :           tbs.p += unique_id_len;
     806           0 :           last_optional = field;
     807      321894 :         } else if( next_tag == (int)FD_DER_TAG_CONTEXT(3) ) {
     808      321894 :           if( FD_UNLIKELY( out->version!=2U || last_optional>=3 ) ) return -1;
     809      321891 :           last_optional = 3;
     810      321891 :           FD_DER_ENTER( tbs, FD_DER_TAG_CONTEXT(3) );
     811      321891 :             uchar const * ext_seq_ptr; ulong ext_seq_len;
     812      321891 :             FD_DER_READ( tbs, FD_DER_TAG_SEQUENCE, ext_seq_ptr, ext_seq_len );
     813      321891 :             if( FD_UNLIKELY( !ext_seq_len ) ) return -1;
     814      321891 :             fd_der_cursor_t ext = { .p = ext_seq_ptr, .end = ext_seq_ptr + ext_seq_len };
     815      321891 :             if( FD_UNLIKELY( fd_x509_parse_extensions( &ext, out ) ) ) return -1;
     816      321699 :           FD_DER_LEAVE( tbs );
     817      321891 :         } else {
     818           0 :           return -1;
     819           0 :         }
     820      321894 :       }
     821             : 
     822      321933 :       if( FD_UNLIKELY( out->subject_len==2UL && (out->is_ca || !out->has_subject_alt_name) ) ) return -1;
     823             :       /* RFC 5280 Sections 4.2.1.3, 4.2.1.9 and 4.2.1.10.  Check after
     824             :          parsing every extension so their order cannot affect the result. */
     825      321927 :       if( FD_UNLIKELY( !out->is_ca &&
     826      321927 :                        (out->has_name_constraints || (out->key_usage & FD_X509_KU_KEY_CERT_SIGN)) ) ) return -1;
     827      321912 :       if( FD_UNLIKELY( out->has_path_len_constraint && out->has_key_usage &&
     828      321912 :                        !(out->key_usage & FD_X509_KU_KEY_CERT_SIGN) ) ) return -1;
     829      321912 :     }
     830             : 
     831             :     /* signatureAlgorithm must match the TBSCertificate field exactly. */
     832      321906 :     uchar const * outer_sig_alg; ulong outer_sig_alg_len;
     833      321906 :     FD_DER_READ( c, FD_DER_TAG_SEQUENCE, outer_sig_alg, outer_sig_alg_len );
     834      321906 :     if( FD_UNLIKELY( outer_sig_alg_len!=tbs_sig_alg_len ||
     835      321906 :                      memcmp( outer_sig_alg, tbs_sig_alg, tbs_sig_alg_len ) ) ) return -1;
     836             : 
     837             :     /* signatureValue BIT STRING */
     838      321903 :     FD_DER_READ_BITS( c, out->sig, out->sig_len );
     839             : 
     840      321903 :   FD_DER_LEAVE( c );
     841             : 
     842             :   /* The supplied buffer is one DER Certificate, not a DER prefix. */
     843      321903 :   if( FD_UNLIKELY( FD_DER_HAS_MORE( c ) ) ) return -1;
     844             : 
     845      321900 :   return 0;
     846      321903 : }
     847             : 
     848             : /* Distinguished-name matching **********************************************/
     849             : 
     850             : typedef struct {
     851             :   uchar const * p;
     852             :   ulong         len;
     853             : } fd_x509_der_slice_t;
     854             : 
     855             : static int
     856             : fd_x509_der_read( fd_der_cursor_t * c,
     857             :                   int               expected_tag,
     858             :                   uchar const **    content,
     859       37341 :                   ulong *           content_len ) {
     860       37341 :   int tag;
     861       37341 :   if( FD_UNLIKELY( fd_der_read_tl( c, &tag, content_len ) || tag!=expected_tag ) ) return -1;
     862       37320 :   *content = c->p;
     863       37320 :   c->p += *content_len;
     864       37320 :   return 0;
     865       37341 : }
     866             : 
     867             : static int
     868             : fd_x509_dn_case_ignore_oid( uchar const * oid,
     869        4098 :                             ulong         oid_len ) {
     870        4098 :   if( oid_len==3UL && oid[0]==0x55U && oid[1]==0x04U ) { /* 2.5.4 */
     871        4083 :     switch( oid[2] ) {
     872        4038 :     case  3U: /* commonName */
     873        4038 :     case  4U: /* surname */
     874        4038 :     case  5U: /* serialNumber */
     875        4038 :     case  6U: /* countryName */
     876        4038 :     case  7U: /* localityName */
     877        4038 :     case  8U: /* stateOrProvinceName */
     878        4038 :     case  9U: /* streetAddress */
     879        4083 :     case 10U: /* organizationName */
     880        4083 :     case 11U: /* organizationalUnitName */
     881        4083 :     case 12U: /* title */
     882        4083 :     case 13U: /* description */
     883        4083 :     case 15U: /* businessCategory */
     884        4083 :     case 17U: /* postalCode */
     885        4083 :     case 18U: /* postOfficeBox */
     886        4083 :     case 19U: /* physicalDeliveryOfficeName */
     887        4083 :     case 27U: /* destinationIndicator */
     888        4083 :     case 41U: /* name */
     889        4083 :     case 42U: /* givenName */
     890        4083 :     case 43U: /* initials */
     891        4083 :     case 44U: /* generationQualifier */
     892        4083 :     case 46U: /* dnQualifier */
     893        4083 :     case 51U: /* houseIdentifier */
     894        4083 :     case 54U: /* dmdName */
     895        4083 :     case 65U: /* pseudonym */
     896        4083 :     case 72U: /* role */
     897        4083 :     case 97U: /* organizationIdentifier */
     898        4083 :       return 1;
     899           0 :     default:
     900           0 :       return 0;
     901        4083 :     }
     902        4083 :   }
     903             : 
     904             :   /* userId 0.9.2342.19200300.100.1.1 and domainComponent ...100.1.25,
     905             :      both IA5String (RFC 4519: caseIgnoreIA5Match) */
     906          15 :   static uchar const oid_user_id[] = { 0x09,0x92,0x26,0x89,0x93,0xf2,0x2c,0x64,0x01,0x01 };
     907          15 :   static uchar const oid_dc[]      = { 0x09,0x92,0x26,0x89,0x93,0xf2,0x2c,0x64,0x01,0x19 };
     908          15 :   return oid_len==sizeof(oid_user_id) &&
     909          15 :          ( !memcmp( oid, oid_user_id, sizeof(oid_user_id) ) || !memcmp( oid, oid_dc, sizeof(oid_dc) ) );
     910        4098 : }
     911             : 
     912             : /* Normalize a string value to UTF-8 with ASCII case folded, leading and
     913             :    trailing spaces trimmed, and internal runs of spaces collapsed (the
     914             :    RFC 5280 Section 7.1 profile of caseIgnoreMatch, as OpenSSL does it).
     915             :    UTF8String bytes pass through; BMPString and UniversalString code
     916             :    points are re-encoded, and TeletexString is read as Latin-1.  out
     917             :    must hold FD_X509_DN_NORM_MAX bytes.  Fails only on a value
     918             :    fd_x509_name_valid would have rejected. */
     919             : 
     920             : #define FD_X509_DN_NORM_MAX (4UL*FD_X509_DN_VALUE_MAX)
     921             : 
     922             : static int
     923             : fd_x509_dn_string_normalize( int           tag,
     924             :                              uchar const * p,
     925             :                              ulong         len,
     926             :                              uchar *       out,
     927        8172 :                              ulong *       out_len ) {
     928        8172 :   ulong width = fd_x509_dn_string_width( tag );
     929        8172 :   if( FD_UNLIKELY( !width || len%width || len/width>FD_X509_DN_VALUE_MAX ) ) return -1;
     930        8172 :   int utf8_in = tag==(int)FD_DER_TAG_UTF8_STRING;
     931             : 
     932        8172 :   ulong j = 0UL;
     933        8172 :   int pending_space = 0;
     934      136503 :   for( ulong i=0UL; i<len; i+=width ) {
     935      128331 :     uint c = 0U;
     936      256698 :     for( ulong k=0UL; k<width; k++ ) c = (c<<8) | (uint)p[i+k];
     937      128331 :     if( c==' ' ) {
     938       20634 :       if( j ) pending_space = 1;
     939       20634 :       continue;
     940       20634 :     }
     941      107697 :     if( pending_space ) out[j++] = ' ';
     942      107697 :     pending_space = 0;
     943      107697 :     if( c>='A' && c<='Z' ) c += (uint)('a'-'A');
     944      107697 :     if( utf8_in || c<0x80U ) {
     945      107688 :       out[j++] = (uchar)c;
     946      107688 :     } else if( c<0x800U ) {
     947           9 :       out[j++] = (uchar)( 0xC0U | (c>>6) );
     948           9 :       out[j++] = (uchar)( 0x80U | (c & 0x3FU) );
     949           9 :     } else if( c<0x10000U ) {
     950           0 :       out[j++] = (uchar)( 0xE0U | (c>>12) );
     951           0 :       out[j++] = (uchar)( 0x80U | ((c>>6) & 0x3FU) );
     952           0 :       out[j++] = (uchar)( 0x80U | (c & 0x3FU) );
     953           0 :     } else if( c<0x110000U ) {
     954           0 :       out[j++] = (uchar)( 0xF0U | (c>>18) );
     955           0 :       out[j++] = (uchar)( 0x80U | ((c>>12) & 0x3FU) );
     956           0 :       out[j++] = (uchar)( 0x80U | ((c>>6) & 0x3FU) );
     957           0 :       out[j++] = (uchar)( 0x80U | (c & 0x3FU) );
     958           0 :     } else {
     959           0 :       return -1;
     960           0 :     }
     961      107697 :   }
     962        8172 :   *out_len = j;
     963        8172 :   return 0;
     964        8172 : }
     965             : 
     966             : static int
     967             : fd_x509_atv_equal( fd_x509_der_slice_t a,
     968        4107 :                    fd_x509_der_slice_t b ) {
     969        4107 :   fd_der_cursor_t ac = { .p=a.p, .end=a.p+a.len };
     970        4107 :   fd_der_cursor_t bc = { .p=b.p, .end=b.p+b.len };
     971             : 
     972        4107 :   uchar const * aoid; ulong aoid_len;
     973        4107 :   uchar const * boid; ulong boid_len;
     974        4107 :   if( FD_UNLIKELY( fd_x509_der_read( &ac, FD_DER_TAG_OID, &aoid, &aoid_len ) ||
     975        4107 :                    fd_x509_der_read( &bc, FD_DER_TAG_OID, &boid, &boid_len ) ) ) return 0;
     976        4107 :   if( aoid_len!=boid_len || memcmp( aoid, boid, aoid_len ) ) return 0;
     977             : 
     978        4098 :   int atag; ulong aval_len;
     979        4098 :   int btag; ulong bval_len;
     980        4098 :   if( FD_UNLIKELY( fd_der_read_tl( &ac, &atag, &aval_len ) ||
     981        4098 :                    fd_der_read_tl( &bc, &btag, &bval_len ) ) ) return 0;
     982        4098 :   uchar const * aval = ac.p; ac.p += aval_len;
     983        4098 :   uchar const * bval = bc.p; bc.p += bval_len;
     984        4098 :   if( FD_UNLIKELY( ac.p!=ac.end || bc.p!=bc.end ) ) return 0;
     985             : 
     986        4098 :   if( fd_x509_dn_case_ignore_oid( aoid, aoid_len ) &&
     987        4098 :       fd_x509_dn_string_width( atag ) && fd_x509_dn_string_width( btag ) ) {
     988        4086 :     uchar anorm[ FD_X509_DN_NORM_MAX ]; ulong anorm_len;
     989        4086 :     uchar bnorm[ FD_X509_DN_NORM_MAX ]; ulong bnorm_len;
     990        4086 :     if( !fd_x509_dn_string_normalize( atag, aval, aval_len, anorm, &anorm_len ) &&
     991        4086 :         !fd_x509_dn_string_normalize( btag, bval, bval_len, bnorm, &bnorm_len ) )
     992        4086 :       return anorm_len==bnorm_len && !memcmp( anorm, bnorm, anorm_len );
     993        4086 :   }
     994             : 
     995          12 :   return atag==btag && aval_len==bval_len && !memcmp( aval, bval, aval_len );
     996        4098 : }
     997             : 
     998             : static int
     999             : fd_x509_rdn_equal( uchar const * a,
    1000             :                    ulong         a_len,
    1001             :                    uchar const * b,
    1002        4107 :                    ulong         b_len ) {
    1003        4107 :   fd_x509_der_slice_t aa[ FD_X509_RDN_ATV_MAX ]; ulong aa_cnt = 0UL;
    1004        4107 :   fd_x509_der_slice_t ba[ FD_X509_RDN_ATV_MAX ]; ulong ba_cnt = 0UL;
    1005        4107 :   fd_der_cursor_t ac = { .p=a, .end=a+a_len };
    1006        4107 :   fd_der_cursor_t bc = { .p=b, .end=b+b_len };
    1007             : 
    1008        8214 :   while( FD_DER_HAS_MORE( ac ) ) {
    1009        4107 :     if( FD_UNLIKELY( aa_cnt==FD_X509_RDN_ATV_MAX ||
    1010        4107 :                      fd_x509_der_read( &ac, FD_DER_TAG_SEQUENCE,
    1011        4107 :                                        &aa[aa_cnt].p, &aa[aa_cnt].len ) ) ) return 0;
    1012        4107 :     aa_cnt++;
    1013        4107 :   }
    1014        8214 :   while( FD_DER_HAS_MORE( bc ) ) {
    1015        4107 :     if( FD_UNLIKELY( ba_cnt==FD_X509_RDN_ATV_MAX ||
    1016        4107 :                      fd_x509_der_read( &bc, FD_DER_TAG_SEQUENCE,
    1017        4107 :                                        &ba[ba_cnt].p, &ba[ba_cnt].len ) ) ) return 0;
    1018        4107 :     ba_cnt++;
    1019        4107 :   }
    1020        4107 :   if( aa_cnt!=ba_cnt ) return 0;
    1021             : 
    1022        4107 :   uchar matched[ FD_X509_RDN_ATV_MAX ] = {0};
    1023        4212 :   for( ulong i=0UL; i<aa_cnt; i++ ) {
    1024        4107 :     ulong j=0UL;
    1025        8109 :     for( ; j<ba_cnt; j++ )
    1026        4107 :       if( !matched[j] && fd_x509_atv_equal( aa[i], ba[j] ) ) break;
    1027        4107 :     if( j==ba_cnt ) return 0;
    1028         105 :     matched[j] = 1U;
    1029         105 :   }
    1030         105 :   return 1;
    1031        4107 : }
    1032             : 
    1033             : /* fd_x509_name_cmp_rdns walks both Names RDN by RDN.  Returns 1 if a
    1034             :    is exhausted (a's RDNs are a prefix of b's; with full=1 b must be
    1035             :    exhausted as well), 0 on a mismatch or malformed input. */
    1036             : 
    1037             : static int
    1038             : fd_x509_name_cmp_rdns( uchar const * a,
    1039             :                        ulong         a_len,
    1040             :                        uchar const * b,
    1041             :                        ulong         b_len,
    1042        6360 :                        int           full ) {
    1043        6360 :   if( FD_UNLIKELY( (!a && a_len) || (!b && b_len) ) ) return 0;
    1044             : 
    1045        6354 :   FD_DER_CURSOR_FROM_BUF( ac, a, a_len );
    1046        6354 :   FD_DER_CURSOR_FROM_BUF( bc, b, b_len );
    1047        6354 :   uchar const * ap; ulong alen;
    1048        6354 :   uchar const * bp; ulong blen;
    1049        6354 :   if( FD_UNLIKELY( fd_x509_der_read( &ac, FD_DER_TAG_SEQUENCE, &ap, &alen ) || ac.p!=ac.end ||
    1050        6354 :                    fd_x509_der_read( &bc, FD_DER_TAG_SEQUENCE, &bp, &blen ) || bc.p!=bc.end ) ) return 0;
    1051             : 
    1052        6336 :   if( a_len==b_len && !memcmp( a, b, a_len ) ) return 1;
    1053             : 
    1054        4113 :   ac.p=ap; ac.end=ap+alen;
    1055        4113 :   bc.p=bp; bc.end=bp+blen;
    1056        4218 :   while( FD_DER_HAS_MORE( ac ) && FD_DER_HAS_MORE( bc ) ) {
    1057        4110 :     uchar const * ardn; ulong ardn_len;
    1058        4110 :     uchar const * brdn; ulong brdn_len;
    1059        4110 :     if( FD_UNLIKELY( fd_x509_der_read( &ac, FD_DER_TAG_SET, &ardn, &ardn_len ) ||
    1060        4110 :                      fd_x509_der_read( &bc, FD_DER_TAG_SET, &brdn, &brdn_len ) ||
    1061        4110 :                      !fd_x509_rdn_equal( ardn, ardn_len, brdn, brdn_len ) ) ) return 0;
    1062        4110 :   }
    1063         108 :   return ac.p==ac.end && ( !full || bc.p==bc.end );
    1064        4113 : }
    1065             : 
    1066             : int
    1067             : fd_x509_name_equal( uchar const * a,
    1068             :                     ulong         a_len,
    1069             :                     uchar const * b,
    1070        6306 :                     ulong         b_len ) {
    1071        6306 :   return fd_x509_name_cmp_rdns( a, a_len, b, b_len, 1 );
    1072        6306 : }
    1073             : 
    1074             : int
    1075             : fd_x509_name_prefix( uchar const * prefix,
    1076             :                      ulong         prefix_len,
    1077             :                      uchar const * name,
    1078          54 :                      ulong         name_len ) {
    1079          54 :   return fd_x509_name_cmp_rdns( prefix, prefix_len, name, name_len, 0 );
    1080          54 : }
    1081             : 
    1082             : int
    1083             : fd_x509_extract_pubkey( uchar const *  cert,
    1084             :                         ulong          cert_sz,
    1085             :                         uchar const ** out_pubkey,
    1086             :                         ulong *        out_pubkey_len,
    1087      317988 :                         uchar *        out_key_type ) {
    1088      317988 :   if( FD_UNLIKELY( !cert || !out_pubkey || !out_pubkey_len || !out_key_type ) ) return -1;
    1089      317988 :   fd_x509_cert_info_t info;
    1090      317988 :   if( FD_UNLIKELY( fd_x509_cert_parse( cert, cert_sz, &info ) ) ) return -1;
    1091      317238 :   if( FD_UNLIKELY( info.key_type==FD_X509_KEY_UNKNOWN ) ) return -1;
    1092      317238 :   *out_pubkey     = info.pubkey;
    1093      317238 :   *out_pubkey_len = info.pubkey_len;
    1094      317238 :   *out_key_type   = info.key_type;
    1095      317238 :   return 0;
    1096      317238 : }
    1097             : 
    1098             : /* Hostname matching (RFC 6125 Section 6.4.3) */
    1099             : 
    1100             : int
    1101             : fd_x509_dns_eq_ci( char const * a,
    1102             :                    char const * b,
    1103        1311 :                    ulong        len ) {
    1104       20193 :   for( ulong i=0UL; i<len; i++ ) {
    1105       18891 :     char x = a[i]; if( x>='A' && x<='Z' ) x = (char)( x + ('a'-'A') );
    1106       18891 :     char y = b[i]; if( y>='A' && y<='Z' ) y = (char)( y + ('a'-'A') );
    1107       18891 :     if( x!=y ) return 0;
    1108       18891 :   }
    1109        1302 :   return 1;
    1110        1311 : }
    1111             : 
    1112             : int
    1113             : fd_x509_dns_name_valid( char const * name,
    1114      328731 :                         ulong        len ) {
    1115      328731 :   if( len<1UL || len>253UL ) return 0;
    1116             : 
    1117      328716 :   ulong label_len = 0UL;
    1118     3355116 :   for( ulong i=0UL; i<len; i++ ) {
    1119     3026439 :     uchar c = (uchar)name[i];
    1120             : 
    1121     3026439 :     if( c=='.' ) {
    1122       25227 :       if( label_len<1UL || label_len>63UL ) return 0;
    1123       25212 :       if( name[i-1UL]=='-' ) return 0;
    1124       25212 :       label_len = 0UL;
    1125       25212 :       continue;
    1126       25212 :     }
    1127             : 
    1128     3001212 :     int alnum = ( c>='a' && c<='z' ) || ( c>='A' && c<='Z' ) || ( c>='0' && c<='9' );
    1129     3001212 :     if( !( alnum || c=='-' || c=='_' ) ) return 0;
    1130     3001188 :     if( c=='-' && label_len==0UL ) return 0;
    1131     3001188 :     label_len++;
    1132     3001188 :   }
    1133             : 
    1134      328677 :   if( label_len<1UL || label_len>63UL ) return 0;
    1135      328671 :   if( name[len-1UL]=='-' ) return 0;
    1136             : 
    1137      328671 :   return 1;
    1138      328671 : }
    1139             : 
    1140             : static int
    1141             : dns_pattern_matches( char const * pattern,
    1142             :                      ulong        pattern_len,
    1143             :                      char const * hostname,
    1144        2406 :                      ulong        hostname_len ) {
    1145             : 
    1146             :   /* Wildcard: *.example.com matches foo.example.com.  The wildcard is
    1147             :      only recognized as the whole leftmost label, and the remainder
    1148             :      must span at least two labels, so *.com matches nothing. */
    1149             : 
    1150        2406 :   if( pattern_len>=2UL && pattern[0]=='*' && pattern[1]=='.' ) {
    1151          27 :     char const * pattern_tail     = pattern + 2;
    1152          27 :     ulong        pattern_tail_len = pattern_len - 2UL;
    1153             : 
    1154          27 :     if( !fd_x509_dns_name_valid( pattern_tail, pattern_tail_len ) ) return 0;
    1155          27 :     if( !memchr( pattern_tail, '.', pattern_tail_len ) ) return 0;
    1156             : 
    1157          24 :     ulong dot_pos = 0UL;
    1158         102 :     for( ; dot_pos<hostname_len; dot_pos++ ) {
    1159         102 :       if( hostname[dot_pos]=='.' ) break;
    1160         102 :     }
    1161             : 
    1162             :     /* The wildcard must consume a non-empty label. */
    1163             : 
    1164          24 :     if( dot_pos==0UL || dot_pos>=hostname_len ) return 0;
    1165             : 
    1166          24 :     char const * host_tail     = hostname + dot_pos + 1UL;
    1167          24 :     ulong        host_tail_len = hostname_len - dot_pos - 1UL;
    1168             : 
    1169          24 :     return host_tail_len==pattern_tail_len &&
    1170          24 :            fd_x509_dns_eq_ci( pattern_tail, host_tail, pattern_tail_len );
    1171          24 :   }
    1172             : 
    1173             :   /* Exact match (case-insensitive) */
    1174             : 
    1175        2379 :   return pattern_len==hostname_len &&
    1176        2379 :          fd_x509_dns_name_valid( pattern, pattern_len ) &&
    1177        2379 :          fd_x509_dns_eq_ci( pattern, hostname, pattern_len );
    1178        2406 : }
    1179             : 
    1180             : int
    1181             : fd_x509_san_matches( fd_x509_cert_info_t const * info,
    1182             :                      char const *                hostname,
    1183        1389 :                      ulong                       hostname_len ) {
    1184             : 
    1185        1389 :   if( FD_UNLIKELY( !info || !hostname ) ) return 0;
    1186             : 
    1187             :   /* Treat an absolute DNS reference name as equivalent to its relative
    1188             :      spelling.  The root label is not part of the dNSName SAN value. */
    1189             : 
    1190        1383 :   if( hostname_len && hostname[ hostname_len-1UL ]=='.' ) hostname_len--;
    1191             : 
    1192        1383 :   if( FD_UNLIKELY( !fd_x509_dns_name_valid( hostname, hostname_len ) ) ) return 0;
    1193             : 
    1194             :   /* An IPv4 literal is matched byte-for-byte against iPAddress SANs
    1195             :      (RFC 6125 Section 1.7.2, RFC 5280 Section 4.2.1.6) and never
    1196             :      against dNSName SANs.  IPv6 literals contain ':' and are rejected
    1197             :      by fd_x509_dns_name_valid above; the HTTP clients are IPv4-only. */
    1198             : 
    1199        1353 :   int   is_ip4 = 0;
    1200        1353 :   uchar ip4[ 4 ];
    1201        1353 :   if( hostname_len<=15UL ) {
    1202        1344 :     char ip4_cstr[ 16 ];
    1203        1344 :     memcpy( ip4_cstr, hostname, hostname_len );
    1204        1344 :     ip4_cstr[ hostname_len ] = '\0';
    1205        1344 :     uint addr;
    1206        1344 :     if( fd_cstr_to_ip4_addr( ip4_cstr, &addr ) ) {
    1207          36 :       is_ip4 = 1;
    1208             :       /* opposite endianness of FD_IP4_ADDR */
    1209          36 :       ip4[0] = (uchar)( addr       );
    1210          36 :       ip4[1] = (uchar)( addr >>  8 );
    1211          36 :       ip4[2] = (uchar)( addr >> 16 );
    1212          36 :       ip4[3] = (uchar)( addr >> 24 );
    1213          36 :     }
    1214        1344 :   }
    1215        1353 :   if( FD_UNLIKELY( !info->has_subject_alt_name ) ) return 0;
    1216             : 
    1217        1350 :   fd_der_cursor_t san = { .p   = info->san_general_names,
    1218        1350 :                           .end = info->san_general_names + info->san_general_names_len };
    1219        1350 :   int matched = 0;
    1220        3834 :   while( FD_DER_HAS_MORE( san ) ) {
    1221        2526 :     int gn_tag; ulong gn_len;
    1222        2526 :     if( FD_UNLIKELY( fd_der_read_tl( &san, &gn_tag, &gn_len ) ) ) return 0;
    1223        2526 :     if( FD_UNLIKELY( !fd_x509_general_name_valid( gn_tag, san.p, gn_len ) ) )
    1224          42 :       return 0;
    1225             : 
    1226        2484 :     if( is_ip4 ) {
    1227          51 :       if( gn_tag==(int)FD_DER_TAG_CONTEXT_PRIM(7) && gn_len==4UL &&
    1228          51 :           !memcmp( san.p, ip4, 4UL ) ) matched = 1;
    1229        2433 :     } else {
    1230        2433 :       if( gn_tag==(int)FD_DER_TAG_CONTEXT_PRIM(2) &&
    1231        2433 :           dns_pattern_matches( (char const *)san.p, gn_len, hostname, hostname_len ) ) matched = 1;
    1232        2433 :     }
    1233        2484 :     san.p += gn_len;
    1234        2484 :   }
    1235             : 
    1236        1308 :   return matched;
    1237        1350 : }
    1238             : 
    1239             : int
    1240             : fd_x509_decode_ecdsa_sig( uchar const * der,
    1241             :                           ulong         der_len,
    1242             :                           uchar *       raw_sig,
    1243        1950 :                           ulong         scalar_sz ) {
    1244             : 
    1245        1950 :   if( FD_UNLIKELY( !der || !raw_sig ) ) return -1;
    1246             : 
    1247        1944 :   FD_DER_CURSOR_FROM_BUF( c, der, der_len );
    1248             : 
    1249        1944 :   uchar const * r_ptr; ulong r_len;
    1250        1944 :   uchar const * s_ptr; ulong s_len;
    1251             : 
    1252        1944 :   FD_DER_ENTER( c, FD_DER_TAG_SEQUENCE );
    1253        1941 :     FD_DER_READ( c, FD_DER_TAG_INTEGER, r_ptr, r_len );
    1254        1941 :     FD_DER_READ( c, FD_DER_TAG_INTEGER, s_ptr, s_len );
    1255        1941 :   FD_DER_LEAVE( c );
    1256             : 
    1257        1938 :   if( FD_UNLIKELY( FD_DER_HAS_MORE( c ) ) ) return -1;
    1258             : 
    1259        1935 :   if( FD_UNLIKELY( fd_der_int_to_fixed( r_ptr, r_len, raw_sig,             scalar_sz ) ) ) return -1;
    1260        1929 :   if( FD_UNLIKELY( fd_der_int_to_fixed( s_ptr, s_len, raw_sig + scalar_sz, scalar_sz ) ) ) return -1;
    1261             : 
    1262        1929 :   return 0;
    1263        1929 : }
    1264             : 
    1265             : int
    1266             : fd_x509_ec_point_compress( uchar const * uncompressed,
    1267             :                            ulong         coord_sz,
    1268        1959 :                            uchar *       compressed ) {
    1269        1959 :   if( FD_UNLIKELY( !uncompressed || !compressed ) ) return -1;
    1270        1953 :   if( FD_UNLIKELY( coord_sz!=32UL && coord_sz!=48UL ) ) return -1;
    1271        1941 :   if( coord_sz==32UL )
    1272        1770 :     return fd_secp256r1_public_key_compress( compressed, uncompressed )
    1273        1770 :            ==FD_SECP256R1_SUCCESS ? 0 : -1;
    1274         171 :   else
    1275         171 :     return fd_secp384r1_public_key_compress( compressed, uncompressed )
    1276         171 :          ==FD_SECP384R1_SUCCESS ? 0 : -1;
    1277        1941 : }

Generated by: LCOV version 1.14