LCOV - code coverage report
Current view: top level - ballet/x509 - fd_x509.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 0 815 0.0 %
Date: 2026-10-22 18:10:43 Functions: 0 33 0.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           0 : fd_x509_dn_string_width( int tag ) {
      20           0 :   switch( tag ) {
      21           0 :   case FD_DER_TAG_UTF8_STRING:
      22           0 :   case FD_DER_TAG_PRINTABLE_STR:
      23           0 :   case FD_DER_TAG_TELETEX_STRING:
      24           0 :   case FD_DER_TAG_IA5_STRING:       return 1UL;
      25           0 :   case FD_DER_TAG_BMP_STRING:       return 2UL;
      26           0 :   case FD_DER_TAG_UNIVERSAL_STRING: return 4UL;
      27           0 :   default:                          return 0UL;
      28           0 :   }
      29           0 : }
      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           0 :                        ulong         oid_len ) {
      97           0 :   if( alg_len!=oid_len && alg_len!=oid_len+sizeof(der_null) ) return 0;
      98           0 :   if( memcmp( alg, oid, oid_len ) ) return 0;
      99           0 :   return alg_len==oid_len || !memcmp( alg+oid_len, der_null, sizeof(der_null) );
     100           0 : }
     101             : 
     102             : static uchar
     103           0 : fd_x509_parse_sig_alg( uchar const * alg, ulong alg_len ) {
     104           0 :   if( fd_der_oid_match( alg, alg_len, oid_ed25519, sizeof(oid_ed25519) ) )
     105           0 :     return FD_X509_SIG_ED25519;
     106           0 :   if( fd_der_oid_match( alg, alg_len, oid_ecdsa_sha256, sizeof(oid_ecdsa_sha256) ) )
     107           0 :     return FD_X509_SIG_ECDSA_SHA256;
     108           0 :   if( fd_der_oid_match( alg, alg_len, oid_ecdsa_sha384, sizeof(oid_ecdsa_sha384) ) )
     109           0 :     return FD_X509_SIG_ECDSA_SHA384;
     110           0 :   if( fd_x509_rsa_alg_match( alg, alg_len, oid_rsa_sha256, sizeof(oid_rsa_sha256) ) )
     111           0 :     return FD_X509_SIG_RSA_SHA256;
     112           0 :   if( fd_x509_rsa_alg_match( alg, alg_len, oid_rsa_sha384, sizeof(oid_rsa_sha384) ) )
     113           0 :     return FD_X509_SIG_RSA_SHA384;
     114           0 :   if( fd_x509_rsa_alg_match( alg, alg_len, oid_rsa_sha512, sizeof(oid_rsa_sha512) ) )
     115           0 :     return FD_X509_SIG_RSA_SHA512;
     116           0 :   return FD_X509_SIG_UNKNOWN;
     117           0 : }
     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           0 :                           ulong         len ) {
     126           0 :   if( FD_UNLIKELY( !len || (p[0] & 0x80U) ) ) return 0;
     127           0 :   if( p[0]==0x00 ) {
     128           0 :     if( FD_UNLIKELY( len==1UL || !(p[1] & 0x80U) ) ) return 0;
     129           0 :   }
     130           0 :   return 1;
     131           0 : }
     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           0 :                           fd_rsa_pubkey_t * key ) {
     141           0 :   fd_der_cursor_t c = { .p=p, .end=p+len };
     142           0 :   uchar const * n; ulong n_len;
     143           0 :   uchar const * e; ulong e_len;
     144           0 :   FD_DER_ENTER( c, FD_DER_TAG_SEQUENCE );
     145           0 :     FD_DER_READ( c, FD_DER_TAG_INTEGER, n, n_len );
     146           0 :     FD_DER_READ( c, FD_DER_TAG_INTEGER, e, e_len );
     147           0 :     if( FD_UNLIKELY( !fd_x509_positive_integer( n, n_len ) ||
     148           0 :                      !fd_x509_positive_integer( e, e_len ) ) ) return -1;
     149           0 :   FD_DER_LEAVE( c );
     150           0 :   if( FD_UNLIKELY( FD_DER_HAS_MORE( c ) ) ) return -1;
     151             : 
     152           0 : #if FD_HAS_INT128
     153           0 :   fd_rsa_pubkey_t tmp[1];
     154           0 :   if( !key ) key = tmp;
     155           0 :   if( FD_UNLIKELY( !fd_rsa_pubkey_init( key, n, n_len, e, e_len ) ) ) return 1;
     156           0 :   return 0;
     157             : #else
     158             :   (void)key;
     159             :   return 1;
     160             : #endif
     161           0 : }
     162             : 
     163             : int
     164             : fd_x509_decode_rsa_pubkey( uchar const *     pubkey,
     165             :                            ulong             pubkey_len,
     166           0 :                            fd_rsa_pubkey_t * key ) {
     167           0 :   if( FD_UNLIKELY( !pubkey || !key ) ) return -1;
     168           0 :   return fd_x509_parse_rsa_pubkey( pubkey, pubkey_len, key ) ? -1 : 0;
     169           0 : }
     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           0 :                     uchar *           out_type ) {
     176             : 
     177             :   /* algorithm AlgorithmIdentifier ::= SEQUENCE {
     178             :        algorithm  OBJECT IDENTIFIER,
     179             :        parameters ANY DEFINED BY algorithm OPTIONAL } */
     180           0 :   uchar const * alg_ptr; ulong alg_len;
     181           0 :   FD_DER_READ( *c, FD_DER_TAG_SEQUENCE, alg_ptr, alg_len );
     182           0 :   fd_der_cursor_t alg = { .p = alg_ptr, .end = alg_ptr+alg_len };
     183             : 
     184           0 :   uchar const * oid; ulong oid_len;
     185           0 :   FD_DER_READ_RAW( alg, FD_DER_TAG_OID, oid, oid_len );
     186             : 
     187           0 :   uchar type = FD_X509_KEY_UNKNOWN;
     188           0 :   if( fd_der_oid_match( oid, oid_len, oid_ed25519, sizeof(oid_ed25519) ) ) {
     189             :     /* RFC 8410 Section 3: parameters MUST be absent */
     190           0 :     type = FD_X509_KEY_ED25519;
     191           0 :   } 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           0 :     uchar const * curve; ulong curve_len;
     194           0 :     FD_DER_READ_RAW( alg, FD_DER_TAG_OID, curve, curve_len );
     195           0 :     if(      fd_der_oid_match( curve, curve_len, oid_prime256v1, sizeof(oid_prime256v1) ) ) type = FD_X509_KEY_ECDSA_P256;
     196           0 :     else if( fd_der_oid_match( curve, curve_len, oid_secp384r1,  sizeof(oid_secp384r1)  ) ) type = FD_X509_KEY_ECDSA_P384;
     197           0 :   } 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           0 :     if( FD_DER_HAS_MORE( alg ) ) {
     201           0 :       uchar const * params; ulong params_len;
     202           0 :       FD_DER_READ( alg, FD_DER_TAG_NULL, params, params_len );
     203           0 :       (void)params;
     204           0 :       if( FD_UNLIKELY( params_len ) ) return -1;
     205           0 :     }
     206           0 :     type = FD_X509_KEY_RSA;
     207           0 :   } else if( FD_DER_HAS_MORE( alg ) ) {
     208           0 :     FD_DER_SKIP( alg );
     209           0 :   }
     210           0 :   if( FD_UNLIKELY( FD_DER_HAS_MORE( alg ) ) ) return -1;
     211             : 
     212             :   /* subjectPublicKey BIT STRING */
     213           0 :   uchar const * bits; ulong bits_len;
     214           0 :   FD_DER_READ_BITS( *c, bits, bits_len );
     215             : 
     216           0 :   switch( type ) {
     217           0 :   case FD_X509_KEY_ED25519:
     218           0 :     if( FD_UNLIKELY( bits_len != 32 ) ) return -1;
     219           0 :     break;
     220           0 :   case FD_X509_KEY_ECDSA_P256: {
     221           0 :     if( FD_UNLIKELY( bits_len != 65 ) ) return -1;
     222           0 :     uchar compressed[ 33 ];
     223           0 :     if( FD_UNLIKELY( fd_secp256r1_public_key_compress( compressed, bits )
     224           0 :                      !=FD_SECP256R1_SUCCESS ) ) return -1;
     225           0 :     break;
     226           0 :   }
     227           0 :   case FD_X509_KEY_ECDSA_P384: {
     228           0 :     if( FD_UNLIKELY( bits_len != 97 ) ) return -1;
     229           0 :     uchar compressed[ 49 ];
     230           0 :     if( FD_UNLIKELY( fd_secp384r1_public_key_compress( compressed, bits )
     231           0 :                      !=FD_SECP384R1_SUCCESS ) ) return -1;
     232           0 :     break;
     233           0 :   }
     234           0 :   case FD_X509_KEY_RSA: {
     235           0 :     int err = fd_x509_parse_rsa_pubkey( bits, bits_len, NULL );
     236           0 :     if( FD_UNLIKELY( err<0 ) ) return -1;
     237           0 :     if( err ) type = FD_X509_KEY_UNKNOWN;  /* e.g. RSA-1024 */
     238           0 :     break;
     239           0 :   }
     240           0 :   default: /* well-formed but unsupported */
     241           0 :     break;
     242           0 :   }
     243             : 
     244           0 :   *out_pk     = bits;
     245           0 :   *out_pk_len = bits_len;
     246           0 :   *out_type   = type;
     247           0 :   return 0;
     248           0 : }
     249             : 
     250             : static int
     251             : fd_x509_parse_path_len( uchar const * p,
     252             :                         ulong         len,
     253           0 :                         ulong *       out ) {
     254             :   /* INTEGER is signed.  pathLenConstraint is non-negative, and DER
     255             :      requires the shortest possible two's-complement encoding. */
     256           0 :   if( FD_UNLIKELY( !len || (p[0] & 0x80U) ) ) return -1;
     257           0 :   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           0 :   ulong path_len = 0UL;
     263           0 :   for( ulong i=0UL; i<len; i++ ) {
     264           0 :     if( FD_UNLIKELY( path_len>(~0UL >> 8) ) ) {
     265           0 :       path_len = ~0UL;
     266           0 :       break;
     267           0 :     }
     268           0 :     path_len = (path_len << 8) | (ulong)p[i];
     269           0 :   }
     270           0 :   *out = path_len;
     271           0 :   return 0;
     272           0 : }
     273             : 
     274             : static int
     275             : fd_x509_serial_valid( uchar const * p,
     276           0 :                       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           0 :   if( FD_UNLIKELY( !len || len>20UL || (p[0] & 0x80U) ) ) return 0;
     284           0 :   if( p[0]==0x00 ) {
     285           0 :     if( FD_UNLIKELY( len>1UL && !(p[1] & 0x80U) ) ) return 0;
     286           0 :   }
     287           0 :   return 1;
     288           0 : }
     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           0 :                     ulong         len ) {
     311           0 :   fd_der_cursor_t c = { .p=p, .end=p+len };
     312           0 :   int tag; ulong seq_len;
     313           0 :   if( FD_UNLIKELY( fd_der_read_tl( &c, &tag, &seq_len ) ||
     314           0 :                    tag!=(int)FD_DER_TAG_SEQUENCE || c.p+seq_len!=c.end ) ) return 0;
     315           0 :   while( FD_DER_HAS_MORE( c ) ) {
     316           0 :     ulong set_len;
     317           0 :     if( FD_UNLIKELY( fd_der_read_tl( &c, &tag, &set_len ) ||
     318           0 :                      tag!=(int)FD_DER_TAG_SET || !set_len ) ) return 0;
     319           0 :     fd_der_cursor_t set = { .p=c.p, .end=c.p+set_len };
     320           0 :     c.p += set_len;
     321           0 :     ulong atv_cnt = 0UL;
     322           0 :     while( FD_DER_HAS_MORE( set ) ) {
     323           0 :       if( FD_UNLIKELY( ++atv_cnt>FD_X509_RDN_ATV_MAX ) ) return 0;
     324           0 :       ulong atv_len;
     325           0 :       if( FD_UNLIKELY( fd_der_read_tl( &set, &tag, &atv_len ) ||
     326           0 :                        tag!=(int)FD_DER_TAG_SEQUENCE ) ) return 0;
     327           0 :       fd_der_cursor_t atv = { .p=set.p, .end=set.p+atv_len };
     328           0 :       set.p += atv_len;
     329           0 :       ulong oid_len;
     330           0 :       if( FD_UNLIKELY( fd_der_read_tl( &atv, &tag, &oid_len ) ||
     331           0 :                        tag!=(int)FD_DER_TAG_OID || !fd_der_oid_valid( atv.p, oid_len ) ) ) return 0;
     332           0 :       atv.p += oid_len;
     333           0 :       ulong val_len;
     334           0 :       if( FD_UNLIKELY( fd_der_read_tl( &atv, &tag, &val_len ) ) ) return 0;
     335           0 :       ulong width = fd_x509_dn_string_width( tag );
     336           0 :       if( width && FD_UNLIKELY( val_len%width || val_len/width>FD_X509_DN_VALUE_MAX ) ) return 0;
     337           0 :       if( tag==(int)FD_DER_TAG_UTF8_STRING &&
     338           0 :           FD_UNLIKELY( !fd_utf8_verify( (char const *)atv.p, val_len ) ) ) return 0;
     339           0 :       for( ulong i=0UL; width && i<val_len; i+=width ) {
     340           0 :         uint cp = 0U;
     341           0 :         for( ulong k=0UL; k<width; k++ ) cp = (cp<<8) | atv.p[i+k];
     342           0 :         if( width>1UL && FD_UNLIKELY( cp>0x10ffffU || (cp>=0xd800U && cp<=0xdfffU) ) ) return 0;
     343           0 :         if( tag==(int)FD_DER_TAG_IA5_STRING && FD_UNLIKELY( cp>0x7fU ) ) return 0;
     344           0 :         if( tag==(int)FD_DER_TAG_PRINTABLE_STR &&
     345           0 :             FD_UNLIKELY( !( (cp>='a' && cp<='z') || (cp>='A' && cp<='Z') ||
     346           0 :                             (cp>='0' && cp<='9') || (cp && strchr( " '()+,-./:=?", (int)cp )) ) ) ) return 0;
     347           0 :       }
     348           0 :       atv.p += val_len;
     349           0 :       if( FD_UNLIKELY( atv.p!=atv.end ) ) return 0;
     350           0 :     }
     351           0 :   }
     352           0 :   return 1;
     353           0 : }
     354             : 
     355             : static int
     356             : fd_x509_general_name_valid( int           tag,
     357             :                             uchar const * p,
     358           0 :                             ulong         len ) {
     359           0 :   switch( tag ) {
     360           0 :   case FD_DER_TAG_CONTEXT(0): /* otherName */
     361           0 :   case FD_DER_TAG_CONTEXT(3): /* x400Address */
     362           0 :   case FD_DER_TAG_CONTEXT(5): /* ediPartyName */
     363             :     /* not worth validating, we don't look at these */
     364           0 :     return !!len;
     365           0 :   case FD_DER_TAG_CONTEXT(4): /* directoryName: a Name SEQUENCE */
     366           0 :     return fd_x509_name_valid( p, len );
     367           0 :   case FD_DER_TAG_CONTEXT_PRIM(1): /* rfc822Name */
     368           0 :   case FD_DER_TAG_CONTEXT_PRIM(6): /* uniformResourceIdentifier */
     369           0 :     if( FD_UNLIKELY( !len ) ) return 0;
     370           0 :     for( ulong i=0UL; i<len; i++ )
     371           0 :       if( FD_UNLIKELY( p[i] & 0x80U ) ) return 0;
     372           0 :     return 1;
     373           0 :   case FD_DER_TAG_CONTEXT_PRIM(2): { /* dNSName */
     374           0 :     ulong off = len>2UL && p[0]=='*' && p[1]=='.' ? 2UL : 0UL;
     375           0 :     return fd_x509_dns_name_valid( (char const *)p+off, len-off );
     376           0 :   }
     377           0 :   case FD_DER_TAG_CONTEXT_PRIM(7): /* iPAddress */
     378           0 :     return len==4UL || len==16UL;
     379           0 :   case FD_DER_TAG_CONTEXT_PRIM(8): /* registeredID */
     380           0 :     return fd_der_oid_valid( p, len );
     381           0 :   default:
     382           0 :     return 0;
     383           0 :   }
     384           0 : }
     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           0 :                         ulong         len ) {
     401           0 :   fd_der_cursor_t trees = { .p=p, .end=p+len };
     402           0 :   if( FD_UNLIKELY( !len ) ) return -1;
     403             : 
     404           0 :   while( FD_DER_HAS_MORE( trees ) ) {
     405           0 :     uchar const * tree_ptr; ulong tree_len;
     406           0 :     FD_DER_READ( trees, FD_DER_TAG_SEQUENCE, tree_ptr, tree_len );
     407           0 :     fd_der_cursor_t tree = { .p=tree_ptr, .end=tree_ptr+tree_len };
     408             : 
     409           0 :     int tag; ulong base_len;
     410           0 :     if( FD_UNLIKELY( fd_der_read_tl( &tree, &tag, &base_len ) ) ) return -1;
     411           0 :     uchar const * base = tree.p;
     412           0 :     tree.p += base_len;
     413           0 :     if( FD_UNLIKELY( FD_DER_HAS_MORE( tree ) ) ) return -1;
     414             : 
     415           0 :     switch( tag ) {
     416           0 :     case FD_DER_TAG_CONTEXT_PRIM(2): { /* dNSName */
     417           0 :       if( FD_UNLIKELY( !base_len ) ) return -1;
     418           0 :       ulong off = base[0]=='.';
     419           0 :       if( FD_UNLIKELY( !fd_x509_dns_name_valid( (char const *)base+off, base_len-off ) ) ) return -1;
     420           0 :       break;
     421           0 :     }
     422           0 :     case FD_DER_TAG_CONTEXT_PRIM(7): /* iPAddress: address || mask */
     423           0 :       if( FD_UNLIKELY( base_len!=8UL && base_len!=32UL ) ) return -1;
     424           0 :       break;
     425           0 :     case FD_DER_TAG_CONTEXT_PRIM(1): /* rfc822Name */
     426           0 :       return -1;
     427           0 :     default:
     428           0 :       if( FD_UNLIKELY( !fd_x509_general_name_valid( tag, base, base_len ) ) ) return -1;
     429           0 :       break;
     430           0 :     }
     431           0 :   }
     432           0 :   return 0;
     433           0 : }
     434             : 
     435             : static int
     436             : fd_x509_parse_extensions( fd_der_cursor_t *     c,
     437           0 :                           fd_x509_cert_info_t * out ) {
     438             : 
     439           0 :   uchar const * seen_oid[ FD_X509_EXT_MAX ];
     440           0 :   ulong         seen_len[ FD_X509_EXT_MAX ];
     441           0 :   ulong         seen_cnt = 0UL;
     442             : 
     443           0 :   while( FD_DER_HAS_MORE( *c ) ) {
     444             :     /* Each Extension is a SEQUENCE { OID, BOOLEAN?, OCTET STRING } */
     445           0 :     uchar const * ext_ptr; ulong ext_len;
     446           0 :     FD_DER_READ( *c, FD_DER_TAG_SEQUENCE, ext_ptr, ext_len );
     447             : 
     448           0 :     fd_der_cursor_t ext = { .p = ext_ptr, .end = ext_ptr + ext_len };
     449             : 
     450           0 :     uchar const * oid_raw; ulong oid_raw_len;
     451           0 :     FD_DER_READ_RAW( ext, FD_DER_TAG_OID, oid_raw, oid_raw_len );
     452             : 
     453           0 :     if( FD_UNLIKELY( seen_cnt==FD_X509_EXT_MAX ) ) return -1;
     454           0 :     for( ulong i=0UL; i<seen_cnt; i++ ) {
     455           0 :       if( FD_UNLIKELY( fd_der_oid_match( oid_raw, oid_raw_len, seen_oid[i], seen_len[i] ) ) ) return -1;
     456           0 :     }
     457           0 :     seen_oid[ seen_cnt ] = oid_raw;
     458           0 :     seen_len[ seen_cnt++ ] = oid_raw_len;
     459             : 
     460           0 :     int critical = 0;
     461           0 :     if( ext.p<ext.end && *ext.p==FD_DER_TAG_BOOLEAN ) {
     462           0 :       uchar const * critical_ptr; ulong critical_len;
     463           0 :       FD_DER_READ( ext, FD_DER_TAG_BOOLEAN, critical_ptr, critical_len );
     464             :       /* critical is DEFAULT FALSE, so DER permits only explicit TRUE. */
     465           0 :       if( FD_UNLIKELY( critical_len!=1UL || critical_ptr[0]!=0xFF ) ) return -1;
     466           0 :       critical = 1;
     467           0 :     }
     468             : 
     469             :     /* OCTET STRING wrapping the extension value */
     470           0 :     uchar const * val_ptr; ulong val_len;
     471           0 :     FD_DER_READ( ext, FD_DER_TAG_OCTET_STRING, val_ptr, val_len );
     472           0 :     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           0 :     if( fd_der_oid_match( oid_raw, oid_raw_len,
     479           0 :                           oid_basic_constraints, sizeof(oid_basic_constraints) ) ) {
     480             :       /* RFC 5280 Section 4.2 permits at most one instance of an extension. */
     481           0 :       if( FD_UNLIKELY( out->has_basic_constraints ) ) return -1;
     482             : 
     483           0 :       fd_der_cursor_t val = { .p = val_ptr, .end = val_ptr + val_len };
     484           0 :       FD_DER_ENTER( val, FD_DER_TAG_SEQUENCE );
     485           0 :         int bc_tag; FD_DER_PEEK_TAG_OR( val, bc_tag, 0 );
     486           0 :         if( bc_tag == (int)FD_DER_TAG_BOOLEAN ) {
     487           0 :           uchar const * ca_ptr; ulong ca_len;
     488           0 :           FD_DER_READ( val, FD_DER_TAG_BOOLEAN, ca_ptr, ca_len );
     489             :           /* cA is DEFAULT FALSE, so DER permits only explicit TRUE. */
     490           0 :           if( FD_UNLIKELY( ca_len!=1UL || ca_ptr[0]!=0xFF ) ) return -1;
     491           0 :           if( FD_UNLIKELY( !critical ) ) return -1;
     492           0 :           out->is_ca = 1;
     493           0 :         }
     494           0 :         int path_len_tag; FD_DER_PEEK_TAG_OR( val, path_len_tag, 0 );
     495           0 :         if( path_len_tag == (int)FD_DER_TAG_INTEGER ) {
     496           0 :           if( FD_UNLIKELY( !out->is_ca ) ) return -1;
     497           0 :           uchar const * path_len_ptr; ulong path_len_len;
     498           0 :           FD_DER_READ( val, FD_DER_TAG_INTEGER, path_len_ptr, path_len_len );
     499           0 :           if( FD_UNLIKELY( fd_x509_parse_path_len( path_len_ptr, path_len_len,
     500           0 :                                                    &out->path_len_constraint ) ) ) return -1;
     501           0 :           out->has_path_len_constraint = 1;
     502           0 :         }
     503           0 :       FD_DER_LEAVE( val );  /* rejects unconsumed SEQUENCE content */
     504             :       /* Reject trailing bytes in the extension's OCTET STRING */
     505           0 :       if( FD_UNLIKELY( FD_DER_HAS_MORE( val ) ) ) return -1;
     506           0 :       out->has_basic_constraints = 1;
     507           0 :       continue;
     508           0 :     }
     509             : 
     510             :     /* keyUsage (2.5.29.15)
     511             :          KeyUsage ::= BIT STRING { digitalSignature(0) ... decipherOnly(8) } */
     512           0 :     if( fd_der_oid_match( oid_raw, oid_raw_len,
     513           0 :                           oid_key_usage, sizeof(oid_key_usage) ) ) {
     514             :       /* RFC 5280 Section 4.2 permits at most one instance of an extension */
     515           0 :       if( FD_UNLIKELY( out->has_key_usage ) ) return -1;
     516             : 
     517           0 :       fd_der_cursor_t val = { .p = val_ptr, .end = val_ptr + val_len };
     518           0 :       uchar const * bs; ulong bs_len;
     519           0 :       FD_DER_READ( val, FD_DER_TAG_BIT_STRING, bs, bs_len );
     520             :       /* Reject trailing bytes in the extension's OCTET STRING */
     521           0 :       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           0 :       if( FD_UNLIKELY( bs_len<2UL || bs_len>3UL ) ) return -1;
     526           0 :       uint unused = bs[0];
     527           0 :       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           0 :       uchar last = bs[ bs_len-1UL ];
     539           0 :       if( FD_UNLIKELY( last & (uchar)( ( 1U<<unused ) - 1U ) ) ) return -1;
     540           0 :       if( FD_LIKELY( last ) ) {
     541           0 :         uint canonical_unused = 0U;
     542           0 :         while( !(last & (uchar)(1U<<canonical_unused)) ) canonical_unused++;
     543           0 :         if( FD_UNLIKELY( unused!=canonical_unused ) ) return -1;
     544           0 :       } else if( FD_UNLIKELY( bs_len<3UL || !bs[1] ) ) {
     545           0 :         return -1;
     546           0 :       }
     547           0 :       out->key_usage = (ushort)( ( (uint)bs[1] << 8 ) |
     548           0 :                                  ( bs_len>2UL ? (uint)bs[2] : 0U ) );
     549           0 :       out->has_key_usage = 1;
     550           0 :       continue;
     551           0 :     }
     552             : 
     553             :     /* extKeyUsage (2.5.29.37)
     554             :          ExtKeyUsageSyntax ::= SEQUENCE SIZE (1..MAX) OF KeyPurposeId */
     555           0 :     if( fd_der_oid_match( oid_raw, oid_raw_len,
     556           0 :                           oid_ext_key_usage, sizeof(oid_ext_key_usage) ) ) {
     557           0 :       if( FD_UNLIKELY( out->has_ext_key_usage ) ) return -1;
     558             : 
     559           0 :       fd_der_cursor_t val = { .p = val_ptr, .end = val_ptr + val_len };
     560           0 :       ulong kp_cnt = 0UL;
     561           0 :       FD_DER_ENTER( val, FD_DER_TAG_SEQUENCE );
     562           0 :         while( FD_DER_HAS_MORE( val ) ) {
     563           0 :           uchar const * kp; ulong kp_len;
     564           0 :           FD_DER_READ_RAW( val, FD_DER_TAG_OID, kp, kp_len );
     565           0 :           kp_cnt++;
     566           0 :           if(      fd_der_oid_match( kp, kp_len, oid_kp_server_auth, sizeof(oid_kp_server_auth) ) )
     567           0 :             out->ext_key_usage = (ushort)( out->ext_key_usage | FD_X509_EKU_SERVER_AUTH );
     568           0 :           else if( fd_der_oid_match( kp, kp_len, oid_kp_any, sizeof(oid_kp_any) ) )
     569           0 :             out->ext_key_usage = (ushort)( out->ext_key_usage | FD_X509_EKU_ANY );
     570             :           /* Other key purposes are ignored */
     571           0 :         }
     572           0 :       FD_DER_LEAVE( val );
     573           0 :       if( FD_UNLIKELY( !kp_cnt ) ) return -1;  /* SIZE (1..MAX) */
     574           0 :       if( FD_UNLIKELY( FD_DER_HAS_MORE( val ) ) ) return -1;
     575           0 :       out->has_ext_key_usage = 1;
     576           0 :       continue;
     577           0 :     }
     578             : 
     579             :     /* subjectAltName (2.5.29.17) */
     580           0 :     if( fd_der_oid_match( oid_raw, oid_raw_len,
     581           0 :                           oid_san, sizeof(oid_san) ) ) {
     582           0 :       if( FD_UNLIKELY( out->has_subject_alt_name ) ) return -1;
     583           0 :       if( FD_UNLIKELY( out->subject_len==2UL && !critical ) ) return -1;
     584             : 
     585           0 :       fd_der_cursor_t val = { .p = val_ptr, .end = val_ptr + val_len };
     586             : 
     587             :       /* SEQUENCE OF GeneralName */
     588           0 :       uchar const * san_ptr; ulong san_len;
     589           0 :       FD_DER_READ( val, FD_DER_TAG_SEQUENCE, san_ptr, san_len );
     590           0 :       if( FD_UNLIKELY( !san_len ) ) return -1;  /* SIZE (1..MAX) */
     591           0 :       if( FD_UNLIKELY( FD_DER_HAS_MORE( val ) ) ) return -1;
     592             : 
     593           0 :       out->san_general_names     = san_ptr;
     594           0 :       out->san_general_names_len = san_len;
     595           0 :       out->has_subject_alt_name  = 1;
     596             : 
     597           0 :       fd_der_cursor_t san = { .p = san_ptr, .end = san_ptr + san_len };
     598           0 :       while( FD_DER_HAS_MORE( san ) ) {
     599           0 :         int gn_tag; ulong gn_len;
     600           0 :         if( FD_UNLIKELY( fd_der_read_tl( &san, &gn_tag, &gn_len ) ) ) return -1;
     601           0 :         if( FD_UNLIKELY( !fd_x509_general_name_valid( gn_tag, san.p, gn_len ) ) )
     602           0 :           return -1;
     603           0 :         san.p += gn_len;
     604           0 :       }
     605           0 :       continue;
     606           0 :     }
     607             : 
     608             :     /* nameConstraints (2.5.29.30) */
     609           0 :     if( fd_der_oid_match( oid_raw, oid_raw_len,
     610           0 :                           oid_name_constraints, sizeof(oid_name_constraints) ) ) {
     611           0 :       if( FD_UNLIKELY( out->has_name_constraints || !critical ) ) return -1;
     612             : 
     613           0 :       fd_der_cursor_t val = { .p=val_ptr, .end=val_ptr+val_len };
     614           0 :       FD_DER_ENTER( val, FD_DER_TAG_SEQUENCE );
     615           0 :         int last_tag = -1;
     616           0 :         while( FD_DER_HAS_MORE( val ) ) {
     617           0 :           int tag; ulong subtrees_len;
     618           0 :           if( FD_UNLIKELY( fd_der_read_tl( &val, &tag, &subtrees_len ) ) ) return -1;
     619           0 :           if( FD_UNLIKELY( (tag!=(int)FD_DER_TAG_CONTEXT(0) &&
     620           0 :                             tag!=(int)FD_DER_TAG_CONTEXT(1)) || tag<=last_tag ) ) return -1;
     621           0 :           uchar const * subtrees = val.p;
     622           0 :           val.p += subtrees_len;
     623           0 :           if( FD_UNLIKELY( fd_x509_subtrees_valid( subtrees, subtrees_len ) ) ) return -1;
     624           0 :           if( tag==(int)FD_DER_TAG_CONTEXT(0) ) {
     625           0 :             out->name_constraints_permitted     = subtrees;
     626           0 :             out->name_constraints_permitted_len = subtrees_len;
     627           0 :           } else {
     628           0 :             out->name_constraints_excluded     = subtrees;
     629           0 :             out->name_constraints_excluded_len = subtrees_len;
     630           0 :           }
     631           0 :           last_tag = tag;
     632           0 :         }
     633           0 :         if( FD_UNLIKELY( last_tag<0 ) ) return -1;
     634           0 :       FD_DER_LEAVE( val );
     635           0 :       if( FD_UNLIKELY( FD_DER_HAS_MORE( val ) ) ) return -1;
     636           0 :       out->has_name_constraints = 1;
     637           0 :       continue;
     638           0 :     }
     639             : 
     640             :     /* Unknown extension */
     641           0 :     if( FD_UNLIKELY( critical ) ) return -1;
     642           0 :   }
     643             : 
     644           0 :   return 0;
     645           0 : }
     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           0 : dec2( uchar const * s ) {
     652           0 :   if( FD_UNLIKELY( s[0]<'0' || s[0]>'9' || s[1]<'0' || s[1]>'9' ) ) return -1;
     653           0 :   return (s[0]-'0')*10 + (s[1]-'0');
     654           0 : }
     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           0 :                  long d ) {
     664           0 :   y -= m<=2;
     665           0 :   long era = (y>=0 ? y : y-399) / 400;
     666           0 :   long yoe = y - era*400;                                     /* [0, 399] */
     667           0 :   long doy = (153*(m + (m>2 ? -3 : 9)) + 2)/5 + d-1;          /* [0, 365] */
     668           0 :   long doe = yoe*365 + yoe/4 - yoe/100 + doy;                 /* [0, 146096] */
     669           0 :   return era*146097L + doe - 719468L;
     670           0 : }
     671             : 
     672             : long
     673             : fd_x509_time_parse( uchar         tag,
     674             :                     uchar const * s,
     675           0 :                     ulong         s_len ) {
     676           0 :   long year;
     677             : 
     678           0 :   if( tag==FD_DER_TAG_UTC_TIME ) {
     679           0 :     if( FD_UNLIKELY( s_len!=13UL ) ) return FD_X509_TIME_INVALID;
     680           0 :     int yy = dec2( s );
     681           0 :     if( FD_UNLIKELY( yy<0 ) ) return FD_X509_TIME_INVALID;
     682             :     /* RFC 5280 Section 4.1.2.5.1 */
     683           0 :     year = yy>=50 ? 1900L+yy : 2000L+yy;
     684           0 :     s += 2;
     685           0 :   } else if( tag==FD_DER_TAG_GENERALIZED_TIME ) {
     686           0 :     if( FD_UNLIKELY( s_len!=15UL ) ) return FD_X509_TIME_INVALID;
     687           0 :     int hi = dec2( s );
     688           0 :     int lo = dec2( s+2 );
     689           0 :     if( FD_UNLIKELY( hi<0 || lo<0 ) ) return FD_X509_TIME_INVALID;
     690           0 :     year = hi*100L + lo;
     691           0 :     s += 4;
     692           0 :   } else {
     693           0 :     return FD_X509_TIME_INVALID;
     694           0 :   }
     695             : 
     696           0 :   int mon = dec2( s   );
     697           0 :   int day = dec2( s+2 );
     698           0 :   int hh  = dec2( s+4 );
     699           0 :   int mm  = dec2( s+6 );
     700           0 :   int ss  = dec2( s+8 );
     701           0 :   if( FD_UNLIKELY( mon<0 || day<0 || hh<0 || mm<0 || ss<0 ) ) return FD_X509_TIME_INVALID;
     702           0 :   if( FD_UNLIKELY( s[10]!='Z' ) ) return FD_X509_TIME_INVALID;
     703             : 
     704           0 :   if( FD_UNLIKELY( mon<1 || mon>12 ) ) return FD_X509_TIME_INVALID;
     705             : 
     706           0 :   static uchar const mon_days[ 12 ] = { 31,28,31,30,31,30,31,31,30,31,30,31 };
     707           0 :   int leap = ( !(year%4) && (year%100) ) || !(year%400);
     708           0 :   int day_max = mon_days[ mon-1 ] + ( mon==2 && leap );
     709           0 :   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           0 :   if( FD_UNLIKELY( hh>23 || mm>59 || ss>59 ) ) return FD_X509_TIME_INVALID;
     713             : 
     714           0 :   return days_from_civil( year, mon, day )*86400L + hh*3600L + mm*60L + ss;
     715           0 : }
     716             : 
     717             : int
     718             : fd_x509_cert_parse( uchar const *         cert,
     719             :                     ulong                 cert_sz,
     720           0 :                     fd_x509_cert_info_t * out ) {
     721             : 
     722           0 :   if( FD_UNLIKELY( !cert || !out ) ) return -1;
     723             : 
     724           0 :   fd_memset( out, 0, sizeof(fd_x509_cert_info_t) );
     725           0 :   out->key_type = FD_X509_KEY_UNKNOWN;
     726           0 :   out->sig_alg  = FD_X509_SIG_UNKNOWN;
     727             : 
     728           0 :   uchar const * tbs_sig_alg     = NULL;
     729           0 :   ulong         tbs_sig_alg_len = 0UL;
     730             : 
     731           0 :   FD_DER_CURSOR_FROM_BUF( c, cert, cert_sz );
     732             : 
     733           0 :   FD_DER_ENTER( c, FD_DER_TAG_SEQUENCE );
     734             : 
     735             :     /* tbsCertificate */
     736           0 :     uchar const * tbs_start = c.p;
     737           0 :     uchar const * tbs_ptr; ulong tbs_content_len;
     738           0 :     FD_DER_READ( c, FD_DER_TAG_SEQUENCE, tbs_ptr, tbs_content_len );
     739           0 :     out->tbs     = tbs_start;
     740           0 :     out->tbs_len = (ulong)( c.p - tbs_start );
     741             : 
     742           0 :     {
     743           0 :       fd_der_cursor_t tbs = { .p = tbs_ptr, .end = tbs_ptr + tbs_content_len };
     744             : 
     745             :       /* version [0] EXPLICIT Version DEFAULT v1 */
     746           0 :       if( FD_DER_HAS_MORE( tbs ) && *tbs.p==FD_DER_TAG_CONTEXT(0) ) {
     747           0 :         FD_DER_ENTER( tbs, FD_DER_TAG_CONTEXT(0) );
     748           0 :           uchar const * version; ulong version_len;
     749           0 :           FD_DER_READ( tbs, FD_DER_TAG_INTEGER, version, version_len );
     750             :           /* Explicit v1 violates DEFAULT encoding.  Only v1/v2/v3 exist. */
     751           0 :           if( FD_UNLIKELY( version_len!=1UL || version[0]<1U || version[0]>2U ) ) return -1;
     752           0 :           out->version = version[0];
     753           0 :         FD_DER_LEAVE( tbs );
     754           0 :       }
     755             : 
     756             :       /* serialNumber CertificateSerialNumber */
     757           0 :       uchar const * serial; ulong serial_len;
     758           0 :       FD_DER_READ( tbs, FD_DER_TAG_INTEGER, serial, serial_len );
     759           0 :       if( FD_UNLIKELY( !fd_x509_serial_valid( serial, serial_len ) ) ) return -1;
     760             : 
     761             :       /* signature AlgorithmIdentifier SEQUENCE */
     762           0 :       FD_DER_READ( tbs, FD_DER_TAG_SEQUENCE, tbs_sig_alg, tbs_sig_alg_len );
     763           0 :       out->sig_alg = fd_x509_parse_sig_alg( tbs_sig_alg, tbs_sig_alg_len );
     764             : 
     765             :       /* issuer Name SEQUENCE */
     766           0 :       FD_DER_READ_RAW( tbs, FD_DER_TAG_SEQUENCE, out->issuer, out->issuer_len );
     767           0 :       if( FD_UNLIKELY( out->issuer_len==2UL ||
     768           0 :                        !fd_x509_name_valid( out->issuer, out->issuer_len ) ) ) return -1;
     769             : 
     770             :       /* validity SEQUENCE { notBefore, notAfter } */
     771           0 :       FD_DER_ENTER( tbs, FD_DER_TAG_SEQUENCE );
     772           0 :         FD_DER_READ_TIME( tbs, out->not_before_tag, out->not_before, out->not_before_len );
     773           0 :         FD_DER_READ_TIME( tbs, out->not_after_tag,  out->not_after,  out->not_after_len  );
     774           0 :         out->not_before_unix = fd_x509_time_parse( out->not_before_tag, out->not_before, out->not_before_len );
     775           0 :         out->not_after_unix  = fd_x509_time_parse( out->not_after_tag,  out->not_after,  out->not_after_len  );
     776           0 :       FD_DER_LEAVE( tbs );
     777             : 
     778             :       /* subject Name SEQUENCE */
     779           0 :       FD_DER_READ_RAW( tbs, FD_DER_TAG_SEQUENCE, out->subject, out->subject_len );
     780           0 :       if( FD_UNLIKELY( !fd_x509_name_valid( out->subject, out->subject_len ) ) ) return -1;
     781             : 
     782             :       /* subjectPublicKeyInfo SEQUENCE */
     783           0 :       FD_DER_ENTER( tbs, FD_DER_TAG_SEQUENCE );
     784           0 :         if( FD_UNLIKELY( fd_x509_parse_spki( &tbs, &out->pubkey,
     785           0 :                                               &out->pubkey_len,
     786           0 :                                               &out->key_type ) ) )
     787           0 :           return -1;
     788           0 :       FD_DER_LEAVE( tbs );
     789             : 
     790             :       /* issuerUniqueID [1]
     791             :          subjectUniqueID [2]
     792             :          extensions [3] */
     793           0 :       int last_optional = 0;
     794           0 :       while( FD_DER_HAS_MORE( tbs ) ) {
     795           0 :         int next_tag;
     796           0 :         FD_DER_PEEK_TAG( tbs, next_tag );
     797             : 
     798           0 :         if( next_tag==(int)FD_DER_TAG_CONTEXT_PRIM(1) ||
     799           0 :             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           0 :         } else if( next_tag == (int)FD_DER_TAG_CONTEXT(3) ) {
     808           0 :           if( FD_UNLIKELY( out->version!=2U || last_optional>=3 ) ) return -1;
     809           0 :           last_optional = 3;
     810           0 :           FD_DER_ENTER( tbs, FD_DER_TAG_CONTEXT(3) );
     811           0 :             uchar const * ext_seq_ptr; ulong ext_seq_len;
     812           0 :             FD_DER_READ( tbs, FD_DER_TAG_SEQUENCE, ext_seq_ptr, ext_seq_len );
     813           0 :             if( FD_UNLIKELY( !ext_seq_len ) ) return -1;
     814           0 :             fd_der_cursor_t ext = { .p = ext_seq_ptr, .end = ext_seq_ptr + ext_seq_len };
     815           0 :             if( FD_UNLIKELY( fd_x509_parse_extensions( &ext, out ) ) ) return -1;
     816           0 :           FD_DER_LEAVE( tbs );
     817           0 :         } else {
     818           0 :           return -1;
     819           0 :         }
     820           0 :       }
     821             : 
     822           0 :       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           0 :       if( FD_UNLIKELY( !out->is_ca &&
     826           0 :                        (out->has_name_constraints || (out->key_usage & FD_X509_KU_KEY_CERT_SIGN)) ) ) return -1;
     827           0 :       if( FD_UNLIKELY( out->has_path_len_constraint && out->has_key_usage &&
     828           0 :                        !(out->key_usage & FD_X509_KU_KEY_CERT_SIGN) ) ) return -1;
     829           0 :     }
     830             : 
     831             :     /* signatureAlgorithm must match the TBSCertificate field exactly. */
     832           0 :     uchar const * outer_sig_alg; ulong outer_sig_alg_len;
     833           0 :     FD_DER_READ( c, FD_DER_TAG_SEQUENCE, outer_sig_alg, outer_sig_alg_len );
     834           0 :     if( FD_UNLIKELY( outer_sig_alg_len!=tbs_sig_alg_len ||
     835           0 :                      memcmp( outer_sig_alg, tbs_sig_alg, tbs_sig_alg_len ) ) ) return -1;
     836             : 
     837             :     /* signatureValue BIT STRING */
     838           0 :     FD_DER_READ_BITS( c, out->sig, out->sig_len );
     839             : 
     840           0 :   FD_DER_LEAVE( c );
     841             : 
     842             :   /* The supplied buffer is one DER Certificate, not a DER prefix. */
     843           0 :   if( FD_UNLIKELY( FD_DER_HAS_MORE( c ) ) ) return -1;
     844             : 
     845           0 :   return 0;
     846           0 : }
     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           0 :                   ulong *           content_len ) {
     860           0 :   int tag;
     861           0 :   if( FD_UNLIKELY( fd_der_read_tl( c, &tag, content_len ) || tag!=expected_tag ) ) return -1;
     862           0 :   *content = c->p;
     863           0 :   c->p += *content_len;
     864           0 :   return 0;
     865           0 : }
     866             : 
     867             : static int
     868             : fd_x509_dn_case_ignore_oid( uchar const * oid,
     869           0 :                             ulong         oid_len ) {
     870           0 :   if( oid_len==3UL && oid[0]==0x55U && oid[1]==0x04U ) { /* 2.5.4 */
     871           0 :     switch( oid[2] ) {
     872           0 :     case  3U: /* commonName */
     873           0 :     case  4U: /* surname */
     874           0 :     case  5U: /* serialNumber */
     875           0 :     case  6U: /* countryName */
     876           0 :     case  7U: /* localityName */
     877           0 :     case  8U: /* stateOrProvinceName */
     878           0 :     case  9U: /* streetAddress */
     879           0 :     case 10U: /* organizationName */
     880           0 :     case 11U: /* organizationalUnitName */
     881           0 :     case 12U: /* title */
     882           0 :     case 13U: /* description */
     883           0 :     case 15U: /* businessCategory */
     884           0 :     case 17U: /* postalCode */
     885           0 :     case 18U: /* postOfficeBox */
     886           0 :     case 19U: /* physicalDeliveryOfficeName */
     887           0 :     case 27U: /* destinationIndicator */
     888           0 :     case 41U: /* name */
     889           0 :     case 42U: /* givenName */
     890           0 :     case 43U: /* initials */
     891           0 :     case 44U: /* generationQualifier */
     892           0 :     case 46U: /* dnQualifier */
     893           0 :     case 51U: /* houseIdentifier */
     894           0 :     case 54U: /* dmdName */
     895           0 :     case 65U: /* pseudonym */
     896           0 :     case 72U: /* role */
     897           0 :     case 97U: /* organizationIdentifier */
     898           0 :       return 1;
     899           0 :     default:
     900           0 :       return 0;
     901           0 :     }
     902           0 :   }
     903             : 
     904             :   /* userId 0.9.2342.19200300.100.1.1 and domainComponent ...100.1.25,
     905             :      both IA5String (RFC 4519: caseIgnoreIA5Match) */
     906           0 :   static uchar const oid_user_id[] = { 0x09,0x92,0x26,0x89,0x93,0xf2,0x2c,0x64,0x01,0x01 };
     907           0 :   static uchar const oid_dc[]      = { 0x09,0x92,0x26,0x89,0x93,0xf2,0x2c,0x64,0x01,0x19 };
     908           0 :   return oid_len==sizeof(oid_user_id) &&
     909           0 :          ( !memcmp( oid, oid_user_id, sizeof(oid_user_id) ) || !memcmp( oid, oid_dc, sizeof(oid_dc) ) );
     910           0 : }
     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           0 :                              ulong *       out_len ) {
     928           0 :   ulong width = fd_x509_dn_string_width( tag );
     929           0 :   if( FD_UNLIKELY( !width || len%width || len/width>FD_X509_DN_VALUE_MAX ) ) return -1;
     930           0 :   int utf8_in = tag==(int)FD_DER_TAG_UTF8_STRING;
     931             : 
     932           0 :   ulong j = 0UL;
     933           0 :   int pending_space = 0;
     934           0 :   for( ulong i=0UL; i<len; i+=width ) {
     935           0 :     uint c = 0U;
     936           0 :     for( ulong k=0UL; k<width; k++ ) c = (c<<8) | (uint)p[i+k];
     937           0 :     if( c==' ' ) {
     938           0 :       if( j ) pending_space = 1;
     939           0 :       continue;
     940           0 :     }
     941           0 :     if( pending_space ) out[j++] = ' ';
     942           0 :     pending_space = 0;
     943           0 :     if( c>='A' && c<='Z' ) c += (uint)('a'-'A');
     944           0 :     if( utf8_in || c<0x80U ) {
     945           0 :       out[j++] = (uchar)c;
     946           0 :     } else if( c<0x800U ) {
     947           0 :       out[j++] = (uchar)( 0xC0U | (c>>6) );
     948           0 :       out[j++] = (uchar)( 0x80U | (c & 0x3FU) );
     949           0 :     } 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           0 :   }
     962           0 :   *out_len = j;
     963           0 :   return 0;
     964           0 : }
     965             : 
     966             : static int
     967             : fd_x509_atv_equal( fd_x509_der_slice_t a,
     968           0 :                    fd_x509_der_slice_t b ) {
     969           0 :   fd_der_cursor_t ac = { .p=a.p, .end=a.p+a.len };
     970           0 :   fd_der_cursor_t bc = { .p=b.p, .end=b.p+b.len };
     971             : 
     972           0 :   uchar const * aoid; ulong aoid_len;
     973           0 :   uchar const * boid; ulong boid_len;
     974           0 :   if( FD_UNLIKELY( fd_x509_der_read( &ac, FD_DER_TAG_OID, &aoid, &aoid_len ) ||
     975           0 :                    fd_x509_der_read( &bc, FD_DER_TAG_OID, &boid, &boid_len ) ) ) return 0;
     976           0 :   if( aoid_len!=boid_len || memcmp( aoid, boid, aoid_len ) ) return 0;
     977             : 
     978           0 :   int atag; ulong aval_len;
     979           0 :   int btag; ulong bval_len;
     980           0 :   if( FD_UNLIKELY( fd_der_read_tl( &ac, &atag, &aval_len ) ||
     981           0 :                    fd_der_read_tl( &bc, &btag, &bval_len ) ) ) return 0;
     982           0 :   uchar const * aval = ac.p; ac.p += aval_len;
     983           0 :   uchar const * bval = bc.p; bc.p += bval_len;
     984           0 :   if( FD_UNLIKELY( ac.p!=ac.end || bc.p!=bc.end ) ) return 0;
     985             : 
     986           0 :   if( fd_x509_dn_case_ignore_oid( aoid, aoid_len ) &&
     987           0 :       fd_x509_dn_string_width( atag ) && fd_x509_dn_string_width( btag ) ) {
     988           0 :     uchar anorm[ FD_X509_DN_NORM_MAX ]; ulong anorm_len;
     989           0 :     uchar bnorm[ FD_X509_DN_NORM_MAX ]; ulong bnorm_len;
     990           0 :     if( !fd_x509_dn_string_normalize( atag, aval, aval_len, anorm, &anorm_len ) &&
     991           0 :         !fd_x509_dn_string_normalize( btag, bval, bval_len, bnorm, &bnorm_len ) )
     992           0 :       return anorm_len==bnorm_len && !memcmp( anorm, bnorm, anorm_len );
     993           0 :   }
     994             : 
     995           0 :   return atag==btag && aval_len==bval_len && !memcmp( aval, bval, aval_len );
     996           0 : }
     997             : 
     998             : static int
     999             : fd_x509_rdn_equal( uchar const * a,
    1000             :                    ulong         a_len,
    1001             :                    uchar const * b,
    1002           0 :                    ulong         b_len ) {
    1003           0 :   fd_x509_der_slice_t aa[ FD_X509_RDN_ATV_MAX ]; ulong aa_cnt = 0UL;
    1004           0 :   fd_x509_der_slice_t ba[ FD_X509_RDN_ATV_MAX ]; ulong ba_cnt = 0UL;
    1005           0 :   fd_der_cursor_t ac = { .p=a, .end=a+a_len };
    1006           0 :   fd_der_cursor_t bc = { .p=b, .end=b+b_len };
    1007             : 
    1008           0 :   while( FD_DER_HAS_MORE( ac ) ) {
    1009           0 :     if( FD_UNLIKELY( aa_cnt==FD_X509_RDN_ATV_MAX ||
    1010           0 :                      fd_x509_der_read( &ac, FD_DER_TAG_SEQUENCE,
    1011           0 :                                        &aa[aa_cnt].p, &aa[aa_cnt].len ) ) ) return 0;
    1012           0 :     aa_cnt++;
    1013           0 :   }
    1014           0 :   while( FD_DER_HAS_MORE( bc ) ) {
    1015           0 :     if( FD_UNLIKELY( ba_cnt==FD_X509_RDN_ATV_MAX ||
    1016           0 :                      fd_x509_der_read( &bc, FD_DER_TAG_SEQUENCE,
    1017           0 :                                        &ba[ba_cnt].p, &ba[ba_cnt].len ) ) ) return 0;
    1018           0 :     ba_cnt++;
    1019           0 :   }
    1020           0 :   if( aa_cnt!=ba_cnt ) return 0;
    1021             : 
    1022           0 :   uchar matched[ FD_X509_RDN_ATV_MAX ] = {0};
    1023           0 :   for( ulong i=0UL; i<aa_cnt; i++ ) {
    1024           0 :     ulong j=0UL;
    1025           0 :     for( ; j<ba_cnt; j++ )
    1026           0 :       if( !matched[j] && fd_x509_atv_equal( aa[i], ba[j] ) ) break;
    1027           0 :     if( j==ba_cnt ) return 0;
    1028           0 :     matched[j] = 1U;
    1029           0 :   }
    1030           0 :   return 1;
    1031           0 : }
    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           0 :                        int           full ) {
    1043           0 :   if( FD_UNLIKELY( (!a && a_len) || (!b && b_len) ) ) return 0;
    1044             : 
    1045           0 :   FD_DER_CURSOR_FROM_BUF( ac, a, a_len );
    1046           0 :   FD_DER_CURSOR_FROM_BUF( bc, b, b_len );
    1047           0 :   uchar const * ap; ulong alen;
    1048           0 :   uchar const * bp; ulong blen;
    1049           0 :   if( FD_UNLIKELY( fd_x509_der_read( &ac, FD_DER_TAG_SEQUENCE, &ap, &alen ) || ac.p!=ac.end ||
    1050           0 :                    fd_x509_der_read( &bc, FD_DER_TAG_SEQUENCE, &bp, &blen ) || bc.p!=bc.end ) ) return 0;
    1051             : 
    1052           0 :   if( a_len==b_len && !memcmp( a, b, a_len ) ) return 1;
    1053             : 
    1054           0 :   ac.p=ap; ac.end=ap+alen;
    1055           0 :   bc.p=bp; bc.end=bp+blen;
    1056           0 :   while( FD_DER_HAS_MORE( ac ) && FD_DER_HAS_MORE( bc ) ) {
    1057           0 :     uchar const * ardn; ulong ardn_len;
    1058           0 :     uchar const * brdn; ulong brdn_len;
    1059           0 :     if( FD_UNLIKELY( fd_x509_der_read( &ac, FD_DER_TAG_SET, &ardn, &ardn_len ) ||
    1060           0 :                      fd_x509_der_read( &bc, FD_DER_TAG_SET, &brdn, &brdn_len ) ||
    1061           0 :                      !fd_x509_rdn_equal( ardn, ardn_len, brdn, brdn_len ) ) ) return 0;
    1062           0 :   }
    1063           0 :   return ac.p==ac.end && ( !full || bc.p==bc.end );
    1064           0 : }
    1065             : 
    1066             : int
    1067             : fd_x509_name_equal( uchar const * a,
    1068             :                     ulong         a_len,
    1069             :                     uchar const * b,
    1070           0 :                     ulong         b_len ) {
    1071           0 :   return fd_x509_name_cmp_rdns( a, a_len, b, b_len, 1 );
    1072           0 : }
    1073             : 
    1074             : int
    1075             : fd_x509_name_prefix( uchar const * prefix,
    1076             :                      ulong         prefix_len,
    1077             :                      uchar const * name,
    1078           0 :                      ulong         name_len ) {
    1079           0 :   return fd_x509_name_cmp_rdns( prefix, prefix_len, name, name_len, 0 );
    1080           0 : }
    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           0 :                         uchar *        out_key_type ) {
    1088           0 :   if( FD_UNLIKELY( !cert || !out_pubkey || !out_pubkey_len || !out_key_type ) ) return -1;
    1089           0 :   fd_x509_cert_info_t info;
    1090           0 :   if( FD_UNLIKELY( fd_x509_cert_parse( cert, cert_sz, &info ) ) ) return -1;
    1091           0 :   if( FD_UNLIKELY( info.key_type==FD_X509_KEY_UNKNOWN ) ) return -1;
    1092           0 :   *out_pubkey     = info.pubkey;
    1093           0 :   *out_pubkey_len = info.pubkey_len;
    1094           0 :   *out_key_type   = info.key_type;
    1095           0 :   return 0;
    1096           0 : }
    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           0 :                    ulong        len ) {
    1104           0 :   for( ulong i=0UL; i<len; i++ ) {
    1105           0 :     char x = a[i]; if( x>='A' && x<='Z' ) x = (char)( x + ('a'-'A') );
    1106           0 :     char y = b[i]; if( y>='A' && y<='Z' ) y = (char)( y + ('a'-'A') );
    1107           0 :     if( x!=y ) return 0;
    1108           0 :   }
    1109           0 :   return 1;
    1110           0 : }
    1111             : 
    1112             : int
    1113             : fd_x509_dns_name_valid( char const * name,
    1114           0 :                         ulong        len ) {
    1115           0 :   if( len<1UL || len>253UL ) return 0;
    1116             : 
    1117           0 :   ulong label_len = 0UL;
    1118           0 :   for( ulong i=0UL; i<len; i++ ) {
    1119           0 :     uchar c = (uchar)name[i];
    1120             : 
    1121           0 :     if( c=='.' ) {
    1122           0 :       if( label_len<1UL || label_len>63UL ) return 0;
    1123           0 :       if( name[i-1UL]=='-' ) return 0;
    1124           0 :       label_len = 0UL;
    1125           0 :       continue;
    1126           0 :     }
    1127             : 
    1128           0 :     int alnum = ( c>='a' && c<='z' ) || ( c>='A' && c<='Z' ) || ( c>='0' && c<='9' );
    1129           0 :     if( !( alnum || c=='-' || c=='_' ) ) return 0;
    1130           0 :     if( c=='-' && label_len==0UL ) return 0;
    1131           0 :     label_len++;
    1132           0 :   }
    1133             : 
    1134           0 :   if( label_len<1UL || label_len>63UL ) return 0;
    1135           0 :   if( name[len-1UL]=='-' ) return 0;
    1136             : 
    1137           0 :   return 1;
    1138           0 : }
    1139             : 
    1140             : static int
    1141             : dns_pattern_matches( char const * pattern,
    1142             :                      ulong        pattern_len,
    1143             :                      char const * hostname,
    1144           0 :                      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           0 :   if( pattern_len>=2UL && pattern[0]=='*' && pattern[1]=='.' ) {
    1151           0 :     char const * pattern_tail     = pattern + 2;
    1152           0 :     ulong        pattern_tail_len = pattern_len - 2UL;
    1153             : 
    1154           0 :     if( !fd_x509_dns_name_valid( pattern_tail, pattern_tail_len ) ) return 0;
    1155           0 :     if( !memchr( pattern_tail, '.', pattern_tail_len ) ) return 0;
    1156             : 
    1157           0 :     ulong dot_pos = 0UL;
    1158           0 :     for( ; dot_pos<hostname_len; dot_pos++ ) {
    1159           0 :       if( hostname[dot_pos]=='.' ) break;
    1160           0 :     }
    1161             : 
    1162             :     /* The wildcard must consume a non-empty label. */
    1163             : 
    1164           0 :     if( dot_pos==0UL || dot_pos>=hostname_len ) return 0;
    1165             : 
    1166           0 :     char const * host_tail     = hostname + dot_pos + 1UL;
    1167           0 :     ulong        host_tail_len = hostname_len - dot_pos - 1UL;
    1168             : 
    1169           0 :     return host_tail_len==pattern_tail_len &&
    1170           0 :            fd_x509_dns_eq_ci( pattern_tail, host_tail, pattern_tail_len );
    1171           0 :   }
    1172             : 
    1173             :   /* Exact match (case-insensitive) */
    1174             : 
    1175           0 :   return pattern_len==hostname_len &&
    1176           0 :          fd_x509_dns_name_valid( pattern, pattern_len ) &&
    1177           0 :          fd_x509_dns_eq_ci( pattern, hostname, pattern_len );
    1178           0 : }
    1179             : 
    1180             : int
    1181             : fd_x509_san_matches( fd_x509_cert_info_t const * info,
    1182             :                      char const *                hostname,
    1183           0 :                      ulong                       hostname_len ) {
    1184             : 
    1185           0 :   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           0 :   if( hostname_len && hostname[ hostname_len-1UL ]=='.' ) hostname_len--;
    1191             : 
    1192           0 :   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           0 :   int   is_ip4 = 0;
    1200           0 :   uchar ip4[ 4 ];
    1201           0 :   if( hostname_len<=15UL ) {
    1202           0 :     char ip4_cstr[ 16 ];
    1203           0 :     memcpy( ip4_cstr, hostname, hostname_len );
    1204           0 :     ip4_cstr[ hostname_len ] = '\0';
    1205           0 :     uint addr;
    1206           0 :     if( fd_cstr_to_ip4_addr( ip4_cstr, &addr ) ) {
    1207           0 :       is_ip4 = 1;
    1208             :       /* opposite endianness of FD_IP4_ADDR */
    1209           0 :       ip4[0] = (uchar)( addr       );
    1210           0 :       ip4[1] = (uchar)( addr >>  8 );
    1211           0 :       ip4[2] = (uchar)( addr >> 16 );
    1212           0 :       ip4[3] = (uchar)( addr >> 24 );
    1213           0 :     }
    1214           0 :   }
    1215           0 :   if( FD_UNLIKELY( !info->has_subject_alt_name ) ) return 0;
    1216             : 
    1217           0 :   fd_der_cursor_t san = { .p   = info->san_general_names,
    1218           0 :                           .end = info->san_general_names + info->san_general_names_len };
    1219           0 :   int matched = 0;
    1220           0 :   while( FD_DER_HAS_MORE( san ) ) {
    1221           0 :     int gn_tag; ulong gn_len;
    1222           0 :     if( FD_UNLIKELY( fd_der_read_tl( &san, &gn_tag, &gn_len ) ) ) return 0;
    1223           0 :     if( FD_UNLIKELY( !fd_x509_general_name_valid( gn_tag, san.p, gn_len ) ) )
    1224           0 :       return 0;
    1225             : 
    1226           0 :     if( is_ip4 ) {
    1227           0 :       if( gn_tag==(int)FD_DER_TAG_CONTEXT_PRIM(7) && gn_len==4UL &&
    1228           0 :           !memcmp( san.p, ip4, 4UL ) ) matched = 1;
    1229           0 :     } else {
    1230           0 :       if( gn_tag==(int)FD_DER_TAG_CONTEXT_PRIM(2) &&
    1231           0 :           dns_pattern_matches( (char const *)san.p, gn_len, hostname, hostname_len ) ) matched = 1;
    1232           0 :     }
    1233           0 :     san.p += gn_len;
    1234           0 :   }
    1235             : 
    1236           0 :   return matched;
    1237           0 : }
    1238             : 
    1239             : int
    1240             : fd_x509_decode_ecdsa_sig( uchar const * der,
    1241             :                           ulong         der_len,
    1242             :                           uchar *       raw_sig,
    1243           0 :                           ulong         scalar_sz ) {
    1244             : 
    1245           0 :   if( FD_UNLIKELY( !der || !raw_sig ) ) return -1;
    1246             : 
    1247           0 :   FD_DER_CURSOR_FROM_BUF( c, der, der_len );
    1248             : 
    1249           0 :   uchar const * r_ptr; ulong r_len;
    1250           0 :   uchar const * s_ptr; ulong s_len;
    1251             : 
    1252           0 :   FD_DER_ENTER( c, FD_DER_TAG_SEQUENCE );
    1253           0 :     FD_DER_READ( c, FD_DER_TAG_INTEGER, r_ptr, r_len );
    1254           0 :     FD_DER_READ( c, FD_DER_TAG_INTEGER, s_ptr, s_len );
    1255           0 :   FD_DER_LEAVE( c );
    1256             : 
    1257           0 :   if( FD_UNLIKELY( FD_DER_HAS_MORE( c ) ) ) return -1;
    1258             : 
    1259           0 :   if( FD_UNLIKELY( fd_der_int_to_fixed( r_ptr, r_len, raw_sig,             scalar_sz ) ) ) return -1;
    1260           0 :   if( FD_UNLIKELY( fd_der_int_to_fixed( s_ptr, s_len, raw_sig + scalar_sz, scalar_sz ) ) ) return -1;
    1261             : 
    1262           0 :   return 0;
    1263           0 : }
    1264             : 
    1265             : int
    1266             : fd_x509_ec_point_compress( uchar const * uncompressed,
    1267             :                            ulong         coord_sz,
    1268           0 :                            uchar *       compressed ) {
    1269           0 :   if( FD_UNLIKELY( !uncompressed || !compressed ) ) return -1;
    1270           0 :   if( FD_UNLIKELY( coord_sz!=32UL && coord_sz!=48UL ) ) return -1;
    1271           0 :   if( coord_sz==32UL )
    1272           0 :     return fd_secp256r1_public_key_compress( compressed, uncompressed )
    1273           0 :            ==FD_SECP256R1_SUCCESS ? 0 : -1;
    1274           0 :   else
    1275           0 :     return fd_secp384r1_public_key_compress( compressed, uncompressed )
    1276           0 :          ==FD_SECP384R1_SUCCESS ? 0 : -1;
    1277           0 : }

Generated by: LCOV version 1.14