LCOV - code coverage report
Current view: top level - ballet/x509 - fd_der.h (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 160 160 100.0 %
Date: 2026-09-17 04:28:31 Functions: 10 20 50.0 %

          Line data    Source code
       1             : #ifndef HEADER_fd_src_ballet_x509_fd_der_h
       2             : #define HEADER_fd_src_ballet_x509_fd_der_h
       3             : 
       4             : /* fd_der.h provides a parser for DER (Distinguished Encoding Rules)
       5             :    parsing for ASN.1 structures.
       6             : 
       7             :    All reads are bounded by the cursor's [p, end) window.
       8             :    ENTER narrows end to the content of the current TLV.  LEAVE checks
       9             :    that exactly the right number of bytes were consumed and restores the
      10             :    outer window.
      11             : 
      12             :    All macros assume the enclosing function returns int, with the semantics
      13             :    success=0 & failure=nonzero.  On any parse error, the macro evaluates
      14             :    to "return -1".
      15             : 
      16             :    An example of parsing an ECDSA DER signature:
      17             : 
      18             :      FD_DER_CURSOR_FROM_BUF( c, der, der_len );
      19             :      FD_DER_ENTER( c, FD_DER_TAG_SEQUENCE );
      20             :        FD_DER_READ( c, FD_DER_TAG_INTEGER, r_ptr, r_len );
      21             :        FD_DER_READ( c, FD_DER_TAG_INTEGER, s_ptr, s_len );
      22             :      FD_DER_LEAVE( c );
      23             : 
      24             :   */
      25             : 
      26             : #include "../../util/fd_util_base.h"
      27             : 
      28             : /* DER tags */
      29             : 
      30      959838 : #define FD_DER_TAG_BOOLEAN          ((uchar)0x01)
      31      316437 : #define FD_DER_TAG_INTEGER          ((uchar)0x02)
      32        2127 : #define FD_DER_TAG_BIT_STRING       ((uchar)0x03)
      33        1161 : #define FD_DER_TAG_OCTET_STRING     ((uchar)0x04)
      34          15 : #define FD_DER_TAG_NULL             ((uchar)0x05)
      35           3 : #define FD_DER_TAG_OID              ((uchar)0x06)
      36     1020714 : #define FD_DER_TAG_UTF8_STRING      ((uchar)0x0c)
      37      348399 : #define FD_DER_TAG_TELETEX_STRING   ((uchar)0x14)
      38       10509 : #define FD_DER_TAG_SEQUENCE         ((uchar)0x30)
      39         216 : #define FD_DER_TAG_SET              ((uchar)0x31)
      40     7781352 : #define FD_DER_TAG_PRINTABLE_STR    ((uchar)0x13)
      41     7781367 : #define FD_DER_TAG_IA5_STRING       ((uchar)0x16)
      42      645561 : #define FD_DER_TAG_UTC_TIME         ((uchar)0x17)
      43      316524 : #define FD_DER_TAG_GENERALIZED_TIME ((uchar)0x18)
      44          18 : #define FD_DER_TAG_UNIVERSAL_STRING ((uchar)0x1c)
      45          48 : #define FD_DER_TAG_BMP_STRING       ((uchar)0x1e)
      46             : 
      47             : /* Context-specific tags [0]..[7] (EXPLICIT, constructed) */
      48             : 
      49      646926 : #define FD_DER_TAG_CONTEXT(n)      ((uchar)(0xA0 | (n)))
      50             : 
      51             : /* Context-specific tags [0]..[7] (IMPLICIT, primitive) */
      52             : 
      53     1297449 : #define FD_DER_TAG_CONTEXT_PRIM(n) ((uchar)(0x80 | (n)))
      54             : 
      55             : /* fd_der_cursor_t is the read cursor into a DER buffer.
      56             :    p points to the next byte to read.
      57             :    end points one past the last readable byte.
      58             :    Always: p <= end. */
      59             : 
      60             : typedef struct {
      61             :   uchar const * p;
      62             :   uchar const * end;
      63             : } fd_der_cursor_t;
      64             : 
      65             : FD_PROTOTYPES_BEGIN
      66             : 
      67             : /* fd_der_read_tl reads a DER tag-length prefix from cursor c.
      68             : 
      69             :    On success, out_tag is the tag byte, out_len is the content length,
      70             :    c->p is advanced past the tag-length bytes, returns 0.
      71             : 
      72             :    On failure, c is in an undefined state, returns -1. */
      73             : 
      74             : static inline int
      75             : fd_der_read_tl( fd_der_cursor_t * c,
      76             :                 int *             out_tag,
      77    11686179 :                 ulong *           out_len ) {
      78             : 
      79    11686179 :   uchar const * p   = c->p;
      80    11686179 :   uchar const * end = c->end;
      81             : 
      82             :   /* Tag byte */
      83    11686179 :   if( FD_UNLIKELY( p == end ) ) return -1;
      84    11686146 :   if( FD_UNLIKELY( (*p & 0x1fU)==0x1fU ) ) return -1; /* unsupported high-tag-number form */
      85    11686137 :   *out_tag = (int)*p++;
      86             : 
      87             :   /* Length byte */
      88    11686137 :   if( FD_UNLIKELY( p == end ) ) return -1;
      89    11686125 :   ulong len = *p++;
      90             : 
      91    11686125 :   if( len & 0x80 ) {
      92      649980 :     uint n_bytes = (uint)( len & 0x7F );
      93      649980 :     if( FD_UNLIKELY( n_bytes > 4 || n_bytes == 0 ) ) return -1;
      94      649971 :     if( FD_UNLIKELY( p == end || !p[0] ) ) return -1; /* no leading zero length octet */
      95      649962 :     len = 0;
      96     1313610 :     for( uint i = 0; i < n_bytes; i++ ) {
      97      663651 :       if( FD_UNLIKELY( p == end ) ) return -1;
      98      663648 :       len = ( len << 8 ) | *p++;
      99      663648 :     }
     100      649959 :     if( FD_UNLIKELY( len<128UL ) ) return -1; /* long form must be necessary */
     101      649959 :   }
     102             : 
     103    11686098 :   if( FD_UNLIKELY( (ulong)( end - p ) < len ) ) return -1;
     104             : 
     105    11685339 :   *out_len = len;
     106    11685339 :   c->p     = p;
     107    11685339 :   return 0;
     108    11686098 : }
     109             : 
     110             : /* fd_der_int_to_fixed strips DER INTEGER padding and right-justifies
     111             :    into a fixed-size output buffer.
     112             : 
     113             :    p points to the INTEGER content bytes, len is the content length.
     114             :    out is zero-filled and receives the value right-justified into out_sz
     115             :    bytes.
     116             : 
     117             :    Returns 0 on success, -1 on failure. */
     118             : 
     119             : static inline int
     120             : fd_der_int_to_fixed( uchar const * p,
     121             :                      ulong         len,
     122             :                      uchar *       out,
     123        3894 :                      ulong         out_sz ) {
     124             : 
     125        3894 :   if( FD_UNLIKELY( !len ) ) return -1;
     126             : 
     127             :   /* ECDSA scalars are positive INTEGERs.  DER uses a leading zero only
     128             :      when it is needed to keep the value positive. */
     129        3891 :   if( p[0]==0x00 ) {
     130        1936 :     if( FD_UNLIKELY( len==1UL || !(p[1] & 0x80U) ) ) return -1;
     131        1927 :     p++;
     132        1927 :     len--;
     133        2085 :   } else if( FD_UNLIKELY( p[0] & 0x80U ) ) return -1;
     134             : 
     135        3876 :   if( FD_UNLIKELY( len>out_sz ) ) return -1;
     136             : 
     137        3870 :   fd_memset( out, 0, out_sz );
     138        3870 :   fd_memcpy( out + ( out_sz - len ), p, len );
     139        3870 :   return 0;
     140        3876 : }
     141             : 
     142             : /* fd_der_oid_valid checks that p,len is a canonical DER OBJECT IDENTIFIER
     143             :    content encoding.  Each subidentifier uses base 128, with bit 7 marking
     144             :    continuation.  DER forbids leading zero base-128 digits. */
     145             : 
     146             : static inline int
     147             : fd_der_oid_valid( uchar const * p,
     148     1303785 :                   ulong         len ) {
     149     1303785 :   if( FD_UNLIKELY( !len ) ) return 0;
     150             : 
     151     1303776 :   int at_subidentifier_start = 1;
     152     5257272 :   for( ulong i=0UL; i<len; i++ ) {
     153     3953517 :     uchar octet = p[i];
     154     3953517 :     if( FD_UNLIKELY( at_subidentifier_start && octet==0x80U ) ) return 0;
     155     3953496 :     at_subidentifier_start = !(octet & 0x80U);
     156     3953496 :   }
     157     1303755 :   return at_subidentifier_start;
     158     1303776 : }
     159             : 
     160             : FD_PROTOTYPES_END
     161             : 
     162             : /* Initialize a cursor from a buffer pointer and length. */
     163             : 
     164             : #define FD_DER_CURSOR_FROM_BUF( c, buf, sz )  \
     165      337698 :   fd_der_cursor_t c = { .p   = (uchar const *)(buf),                         \
     166      337698 :                         .end = (sz) ? (uchar const *)(buf) + (sz)            \
     167      337698 :                                     : (uchar const *)(buf) }
     168             : 
     169             : /* Enter a constructed type (SEQUENCE, SET, etc.).
     170             :    Must be paired with FD_DER_LEAVE. */
     171             : 
     172             : #define FD_DER_ENTER( c, expected_tag )                                   \
     173     1932651 :   do {                                                                    \
     174     1932651 :     uchar const * _fd_der_outer_end_ = (c).end;                           \
     175     1932651 :     do {                                                                  \
     176     1932651 :       int _fd_der_tag_; ulong _fd_der_len_;                               \
     177     1932651 :       if( FD_UNLIKELY( fd_der_read_tl( &(c), &_fd_der_tag_,               \
     178     1932651 :                                        &_fd_der_len_ ) ) )                \
     179     1932651 :         return -1;                                                        \
     180     1932651 :       if( FD_UNLIKELY( _fd_der_tag_ != (int)(expected_tag) ) )            \
     181     1931874 :         return -1;                                                        \
     182     1931874 :       (c).end = (c).p + _fd_der_len_;                                     \
     183     1931871 :     } while(0)
     184             : 
     185             : /* Leave a constructed type.  Checks that all content was consumed
     186             :    and restores the outer end pointer. */
     187             : 
     188             : #define FD_DER_LEAVE( c )                                                 \
     189     1931289 :     if( FD_UNLIKELY( (c).p != (c).end ) ) return -1;                      \
     190     1931289 :     (c).end = _fd_der_outer_end_;                                         \
     191     1931283 :   } while(0)
     192             : 
     193             : /* Leave a constructed type without requiring all content consumed.
     194             :    Advances past any remaining content. */
     195             : 
     196             : #define FD_DER_LEAVE_RELAXED( c )                                         \
     197           3 :     (c).p   = (c).end;                                                    \
     198           3 :     (c).end = _fd_der_outer_end_;                                         \
     199           3 :   } while(0)
     200             : 
     201             : /* Read a primitive TLV. Verifies the tag and sets ptr/len to the
     202             :    content bytes. Advances the cursor past the TLV.  OBJECT IDENTIFIER
     203             :    content is additionally checked for canonical DER encoding. */
     204             : 
     205             : #define FD_DER_READ( c, expected_tag, out_ptr, out_len )                  \
     206     5146893 :   do {                                                                    \
     207     5146893 :     int _fd_der_tag_; ulong _fd_der_len_;                                 \
     208     5146893 :     if( FD_UNLIKELY( fd_der_read_tl( &(c), &_fd_der_tag_,                 \
     209     5146893 :                                      &_fd_der_len_ ) ) )                  \
     210     5146893 :       return -1;                                                          \
     211     5146893 :     if( FD_UNLIKELY( _fd_der_tag_ != (int)(expected_tag) ) )              \
     212     5146884 :       return -1;                                                          \
     213     5146884 :     if( FD_UNLIKELY( _fd_der_tag_ == (int)FD_DER_TAG_OID &&               \
     214     5146869 :                      !fd_der_oid_valid( (c).p, _fd_der_len_ ) ) )         \
     215     5146869 :       return -1;                                                          \
     216     5146869 :     (out_ptr) = (c).p;                                                    \
     217     5146869 :     (out_len) = _fd_der_len_;                                             \
     218     5146869 :     (c).p += _fd_der_len_;                                                \
     219     5146869 :   } while(0)
     220             : 
     221             : /* Read a TLV including its tag+length prefix.  Sets raw_ptr to the
     222             :    start of the tag byte and raw_len to tag+length+content.  OBJECT
     223             :    IDENTIFIER content is additionally checked for canonical DER encoding. */
     224             : 
     225             : #define FD_DER_READ_RAW( c, expected_tag, raw_ptr, raw_len )              \
     226     1616034 :   do {                                                                    \
     227     1616034 :     (raw_ptr) = (c).p;                                                    \
     228     1616034 :     int _fd_der_tag_; ulong _fd_der_len_;                                 \
     229     1616034 :     if( FD_UNLIKELY( fd_der_read_tl( &(c), &_fd_der_tag_,                 \
     230     1616034 :                                      &_fd_der_len_ ) ) )                  \
     231     1616034 :       return -1;                                                          \
     232     1616034 :     if( FD_UNLIKELY( _fd_der_tag_ != (int)(expected_tag) ) )              \
     233     1616031 :       return -1;                                                          \
     234     1616031 :     if( FD_UNLIKELY( _fd_der_tag_ == (int)FD_DER_TAG_OID &&               \
     235     1616025 :                      !fd_der_oid_valid( (c).p, _fd_der_len_ ) ) )         \
     236     1616025 :       return -1;                                                          \
     237     1616025 :     (c).p += _fd_der_len_;                                                \
     238     1616001 :     (raw_len) = (ulong)( (c).p - (raw_ptr) );                             \
     239     1616001 :   } while(0)
     240             : 
     241             : /* Peek at the next tag byte without consuming it.
     242             :    Sets out_tag to the tag value. Returns -1 if cursor is exhausted. */
     243             : 
     244             : #define FD_DER_PEEK_TAG( c, out_tag )                                     \
     245      321900 :   do {                                                                    \
     246      321900 :     if( FD_UNLIKELY( (c).p == (c).end ) ) return -1;                      \
     247      321900 :     (out_tag) = (int)*(c).p;                                              \
     248      321897 :   } while(0)
     249             : 
     250             : /* Peek at the next tag byte, returning a default if cursor exhausted. */
     251             : 
     252             : #define FD_DER_PEEK_TAG_OR( c, out_tag, default_tag )                     \
     253      630756 :   do {                                                                    \
     254      630756 :     (out_tag) = ( (c).p != (c).end ) ? (int)*(c).p : (int)(default_tag);  \
     255      630756 :   } while(0)
     256             : 
     257             : /* Skip one TLV. */
     258             : 
     259             : #define FD_DER_SKIP( c )                                                  \
     260           3 :   do {                                                                    \
     261           3 :     int _fd_der_tag_; ulong _fd_der_len_;                                 \
     262           3 :     if( FD_UNLIKELY( fd_der_read_tl( &(c), &_fd_der_tag_,                 \
     263           3 :                                      &_fd_der_len_ ) ) )                  \
     264           3 :       return -1;                                                          \
     265           3 :     (void)_fd_der_tag_;                                                   \
     266           3 :     (c).p += _fd_der_len_;                                                \
     267           3 :   } while(0)
     268             : 
     269             : /* Skip one TLV iff the next tag matches expected_tag.
     270             :    If the tag doesn't match or cursor is exhausted, does nothing. */
     271             : 
     272             : #define FD_DER_SKIP_IF( c, expected_tag )                                 \
     273           6 :   do {                                                                    \
     274           6 :     if( (c).p != (c).end && (int)*(c).p == (int)(expected_tag) ) {        \
     275           3 :       FD_DER_SKIP( c );                                                   \
     276           3 :     }                                                                     \
     277           6 :   } while(0)
     278             : 
     279             : /* Read a BIT STRING and strip the "unused bits" byte.
     280             :    DER BIT STRING content is represented as, unused_bits || actual_bits.
     281             :    For X.509 signatures and pubkeys, unused_bits is always 0x00.
     282             :    Sets ptr & len to the actual bits after the unused-bits byte. */
     283             : 
     284             : #define FD_DER_READ_BITS( c, out_ptr, out_len )                           \
     285      644046 :   do {                                                                    \
     286      644046 :     uchar const * _fd_der_bits_raw_; ulong _fd_der_bits_raw_len_;         \
     287      644046 :     FD_DER_READ( c, FD_DER_TAG_BIT_STRING,                                \
     288      644046 :                  _fd_der_bits_raw_, _fd_der_bits_raw_len_ );              \
     289      644046 :     if( FD_UNLIKELY( _fd_der_bits_raw_len_ < 1 ||                         \
     290      644046 :                      _fd_der_bits_raw_[0] != 0x00 ) )                     \
     291      644046 :       return -1;                                                          \
     292      644046 :     (out_ptr) = _fd_der_bits_raw_ + 1;                                    \
     293      644040 :     (out_len) = _fd_der_bits_raw_len_ - 1;                                \
     294      644040 :   } while(0)
     295             : 
     296             : /* Read a time field, either UTCTime or GeneralizedTime. */
     297             : 
     298             : #define FD_DER_READ_TIME( c, out_tag, out_ptr, out_len )                   \
     299      644367 :   do {                                                                    \
     300      644367 :     int _fd_der_tag_; ulong _fd_der_len_;                                 \
     301      644367 :     if( FD_UNLIKELY( fd_der_read_tl( &(c), &_fd_der_tag_,                 \
     302      644367 :                                      &_fd_der_len_ ) ) )                  \
     303      644367 :       return -1;                                                          \
     304      644367 :     if( FD_UNLIKELY( _fd_der_tag_ != (int)FD_DER_TAG_UTC_TIME &&          \
     305      644367 :                      _fd_der_tag_ != (int)FD_DER_TAG_GENERALIZED_TIME ) ) \
     306      644367 :       return -1;                                                          \
     307      644367 :     (out_tag) = (uchar)_fd_der_tag_;                                      \
     308      644364 :     (out_ptr) = (c).p;                                                    \
     309      644364 :     (out_len) = _fd_der_len_;                                             \
     310      644364 :     (c).p += _fd_der_len_;                                                \
     311      644364 :   } while(0)
     312             : 
     313             : /* Check if cursor has more content to read. */
     314             : 
     315     4577322 : #define FD_DER_HAS_MORE( c ) ( (c).p != (c).end )
     316             : 
     317             : static inline int
     318             : fd_der_oid_match( uchar const * tlv,
     319             :                   ulong         tlv_len,
     320             :                   uchar const * expected_oid,
     321     2744013 :                   ulong         expected_oid_len ) {
     322     2744013 :   return ( tlv_len == expected_oid_len &&
     323     2744013 :            0 == memcmp( tlv, expected_oid, expected_oid_len ) );
     324     2744013 : }
     325             : 
     326             : #endif /* HEADER_fd_src_ballet_x509_fd_der_h */

Generated by: LCOV version 1.14