LCOV - code coverage report
Current view: top level - ballet/rsa - fd_rsa.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 142 142 100.0 %
Date: 2026-09-17 04:28:31 Functions: 8 8 100.0 %

          Line data    Source code
       1             : #include "fd_rsa.h"
       2             : #include "../sha256/fd_sha256.h"
       3             : #include "../sha512/fd_sha512.h"
       4             : #include <string.h>
       5             : 
       6             : #ifdef FD_HAS_S2NBIGNUM
       7             : #include <stdint.h>
       8             : #include "../../third_party/s2n-bignum/include/s2n-bignum.h"
       9             : #else
      10             : #if !FD_HAS_INT128
      11             : #error "fd_rsa requires FD_HAS_INT128"
      12             : #endif
      13             : 
      14             : /* Portable fallback for the three s2n-bignum routines fd_rsa_public_op
      15             :    uses. */
      16             : 
      17             : /* Returns -n0^{-1} mod 2^64 (n0 odd) */
      18             : 
      19             : static ulong
      20             : fd_rsa_ref_n0inv( ulong n0 ) {
      21             :   ulong x = n0;  /* correct to 3 bits, Newton doubles per step */
      22             :   for( int i=0; i<6; i++ ) x *= 2UL - n0*x;
      23             :   return 0UL-x;
      24             : }
      25             : 
      26             : /* z := x - n if x >= n (both k limbs) */
      27             : 
      28             : static void
      29             : fd_rsa_ref_reduce( ulong         k,
      30             :                    ulong *       x,
      31             :                    ulong const * n ) {
      32             :   ulong i = k;
      33             :   do { i--; } while( i && x[i]==n[i] );
      34             :   if( x[i]<n[i] ) return;
      35             :   ulong borrow = 0UL;
      36             :   for( ulong j=0UL; j<k; j++ ) {
      37             :     uint128 d = (uint128)x[j] - n[j] - borrow;
      38             :     x[j]   = (ulong)d;
      39             :     borrow = (ulong)( d>>64 ) & 1UL;
      40             :   }
      41             : }
      42             : 
      43             : /* z := x*y/2^{64k} mod n (CIOS).  z must not alias x or y. */
      44             : 
      45             : static void
      46             : bignum_montmul( ulong         k,
      47             :                 ulong *       z,
      48             :                 ulong const * x,
      49             :                 ulong const * y,
      50             :                 ulong const * n ) {
      51             :   ulong n0inv = fd_rsa_ref_n0inv( n[0] );
      52             :   ulong t[ FD_RSA_LIMB_CNT_MAX+2UL ];
      53             :   memset( t, 0, (k+2UL)*sizeof(ulong) );
      54             :   for( ulong i=0UL; i<k; i++ ) {
      55             :     uint128 c = 0;
      56             :     for( ulong j=0UL; j<k; j++ ) {
      57             :       c += (uint128)t[j] + (uint128)x[j]*y[i];
      58             :       t[j] = (ulong)c; c >>= 64;
      59             :     }
      60             :     c += t[k]; t[k] = (ulong)c; t[k+1UL] = (ulong)( c>>64 );
      61             : 
      62             :     ulong m = t[0]*n0inv;
      63             :     c = (uint128)t[0] + (uint128)m*n[0];
      64             :     c >>= 64;
      65             :     for( ulong j=1UL; j<k; j++ ) {
      66             :       c += (uint128)t[j] + (uint128)m*n[j];
      67             :       t[j-1UL] = (ulong)c; c >>= 64;
      68             :     }
      69             :     c += t[k]; t[k-1UL] = (ulong)c; t[k] = t[k+1UL] + (ulong)( c>>64 );
      70             :   }
      71             :   /* t < 2n here, and t[k] is 0 or 1 */
      72             :   if( t[k] ) {
      73             :     ulong borrow = 0UL;
      74             :     for( ulong j=0UL; j<k; j++ ) {
      75             :       uint128 d = (uint128)t[j] - n[j] - borrow;
      76             :       t[j]   = (ulong)d;
      77             :       borrow = (ulong)( d>>64 ) & 1UL;
      78             :     }
      79             :   } else {
      80             :     fd_rsa_ref_reduce( k, t, n );
      81             :   }
      82             :   memcpy( z, t, k*sizeof(ulong) );
      83             : }
      84             : 
      85             : /* z := x/2^{64k} mod n */
      86             : 
      87             : static void
      88             : bignum_demont( ulong         k,
      89             :                ulong *       z,
      90             :                ulong const * x,
      91             :                ulong const * n ) {
      92             :   ulong one[ FD_RSA_LIMB_CNT_MAX ];
      93             :   memset( one, 0, k*sizeof(ulong) );
      94             :   one[0] = 1UL;
      95             :   bignum_montmul( k, z, x, one, n );
      96             : }
      97             : 
      98             : /* z := 2^{128k} mod n, by repeated doubling from 2^{b-1} < n where b
      99             :    is the bit length of n. */
     100             : 
     101             : static void
     102             : bignum_montifier( ulong         k,
     103             :                   ulong *       z,
     104             :                   ulong const * n,
     105             :                   ulong *       t ) {
     106             :   (void)t;
     107             :   ulong bits = 64UL*k;
     108             :   while( !( n[ (bits-1UL)>>6 ] >> ( (bits-1UL)&63UL ) & 1UL ) ) bits--;
     109             :   memset( z, 0, k*sizeof(ulong) );
     110             :   z[ (bits-1UL)>>6 ] = 1UL << ( (bits-1UL)&63UL );
     111             :   for( ulong i=bits-1UL; i<128UL*k; i++ ) {
     112             :     ulong carry = 0UL;
     113             :     for( ulong j=0UL; j<k; j++ ) {
     114             :       ulong next = z[j]>>63;
     115             :       z[j] = (z[j]<<1) | carry;
     116             :       carry = next;
     117             :     }
     118             :     if( carry ) {
     119             :       ulong borrow = 0UL;
     120             :       for( ulong j=0UL; j<k; j++ ) {
     121             :         uint128 d = (uint128)z[j] - n[j] - borrow;
     122             :         z[j]   = (ulong)d;
     123             :         borrow = (ulong)( d>>64 ) & 1UL;
     124             :       }
     125             :     } else {
     126             :       fd_rsa_ref_reduce( k, z, n );
     127             :     }
     128             :   }
     129             : }
     130             : 
     131             : #endif /* FD_HAS_S2NBIGNUM */
     132             : 
     133             : static ulong
     134        5340 : fd_rsa_hash_sz( int hash ) {
     135        5340 :   switch( hash ) {
     136        3333 :   case FD_RSA_HASH_SHA256: return FD_SHA256_HASH_SZ;
     137         957 :   case FD_RSA_HASH_SHA384: return FD_SHA384_HASH_SZ;
     138         960 :   case FD_RSA_HASH_SHA512: return FD_SHA512_HASH_SZ;
     139          90 :   default:                 return 0UL;
     140        5340 :   }
     141        5340 : }
     142             : 
     143             : static void
     144             : fd_rsa_hash( int          hash,
     145             :              void const * data,
     146             :              ulong        sz,
     147       30423 :              uchar *      out ) {
     148       30423 :   switch( hash ) {
     149       28608 :   case FD_RSA_HASH_SHA256: fd_sha256_hash( data, sz, out ); break;
     150         984 :   case FD_RSA_HASH_SHA384: fd_sha384_hash( data, sz, out ); break;
     151         831 :   case FD_RSA_HASH_SHA512: fd_sha512_hash( data, sz, out ); break;
     152       30423 :   }
     153       30423 : }
     154             : 
     155             : /* Big endian bytes <-> little endian 64-bit limbs */
     156             : 
     157             : static void
     158             : fd_rsa_be_to_limbs( ulong *       limbs,
     159             :                     ulong         limb_cnt,
     160             :                     uchar const * be,
     161        6486 :                     ulong         be_sz ) {
     162        6486 :   memset( limbs, 0, limb_cnt*sizeof(ulong) );
     163     2142078 :   for( ulong i=0UL; i<be_sz; i++ )
     164     2135592 :     limbs[ i>>3 ] |= (ulong)be[ be_sz-1UL-i ] << ( 8UL*(i&7UL) );
     165        6486 : }
     166             : 
     167             : FD_FN_UNUSED static void
     168             : fd_rsa_limbs_to_be( uchar *       be,
     169             :                     ulong         be_sz,
     170        4989 :                     ulong const * limbs ) {
     171     1728135 :   for( ulong i=0UL; i<be_sz; i++ )
     172     1723146 :     be[ be_sz-1UL-i ] = (uchar)( limbs[ i>>3 ] >> ( 8UL*(i&7UL) ) );
     173        4989 : }
     174             : 
     175             : fd_rsa_pubkey_t *
     176             : fd_rsa_pubkey_init( fd_rsa_pubkey_t * key,
     177             :                     uchar const *     n,
     178             :                     ulong             n_sz,
     179             :                     uchar const *     e,
     180        1470 :                     ulong             e_sz ) {
     181        1470 :   if( FD_UNLIKELY( !key || !n || !e ) ) return NULL;
     182             : 
     183        4410 :   while( n_sz && !n[0] ) { n++; n_sz--; }
     184        1488 :   while( e_sz && !e[0] ) { e++; e_sz--; }
     185        1461 :   if( FD_UNLIKELY( !n_sz || n_sz>FD_RSA_MOD_SZ_MAX ) ) return NULL;
     186        1452 :   if( FD_UNLIKELY( !e_sz || e_sz>sizeof(ulong)   ) ) return NULL;
     187             : 
     188        1443 :   ulong mod_bits = 8UL*(n_sz-1UL) + (ulong)fd_uchar_find_msb( n[0] ) + 1UL;
     189        1443 :   if( FD_UNLIKELY( mod_bits<FD_RSA_MOD_BITS_MIN || mod_bits>FD_RSA_MOD_BITS_MAX ) ) return NULL;
     190        1437 :   if( FD_UNLIKELY( !(n[ n_sz-1UL ] & 1U) ) ) return NULL;
     191             : 
     192        1434 :   ulong ev = 0UL;
     193        5736 :   for( ulong i=0UL; i<e_sz; i++ ) ev = (ev<<8) | (ulong)e[i];
     194        1434 :   if( FD_UNLIKELY( ev<3UL || !(ev & 1UL) ) ) return NULL;
     195             : 
     196        1425 :   memset( key, 0, sizeof(fd_rsa_pubkey_t) );
     197        1425 :   key->e        = ev;
     198        1425 :   key->limb_cnt = (n_sz+7UL)>>3;
     199        1425 :   key->n_sz     = n_sz;
     200        1425 :   key->mod_bits = mod_bits;
     201        1425 :   fd_rsa_be_to_limbs( key->n, key->limb_cnt, n, n_sz );
     202        1425 :   return key;
     203        1434 : }
     204             : 
     205             : /* fd_rsa_public_op computes em = sig^e mod n (RSAVP1, RFC 8017 Section
     206             :    5.2.2) and writes it big endian to em[key->n_sz].  Returns 0 if sig
     207             :    is not a valid signature representative (wrong length or not below
     208             :    the modulus). */
     209             : 
     210             : static int
     211             : fd_rsa_public_op( fd_rsa_pubkey_t const * key,
     212             :                   uchar const *           sig,
     213             :                   ulong                   sig_sz,
     214        5250 :                   uchar *                 em ) {
     215        5250 :   if( FD_UNLIKELY( sig_sz!=key->n_sz ) ) return 0;
     216        5061 :   ulong const k = key->limb_cnt;
     217             : 
     218        5061 :   ulong s[ FD_RSA_LIMB_CNT_MAX ];
     219        5061 :   fd_rsa_be_to_limbs( s, k, sig, sig_sz );
     220             : 
     221             :   /* Require s < n */
     222        5061 :   ulong i = k;
     223        7077 :   do { i--; } while( i && s[i]==key->n[i] );
     224        5061 :   if( FD_UNLIKELY( s[i]>=key->n[i] ) ) return 0;
     225             : 
     226             :   /* Left-to-right binary exponentiation in the Montgomery domain
     227             :      (bignum_montmul does not support aliased operands) */
     228        4989 :   ulong r2  [ FD_RSA_LIMB_CNT_MAX ];
     229        4989 :   ulong tmp [ FD_RSA_LIMB_CNT_MAX ];
     230        4989 :   ulong base[ FD_RSA_LIMB_CNT_MAX ];
     231        4989 :   ulong acc0[ FD_RSA_LIMB_CNT_MAX ];
     232        4989 :   ulong acc1[ FD_RSA_LIMB_CNT_MAX ];
     233        4989 :   bignum_montifier( k, r2, key->n, tmp );
     234        4989 :   bignum_montmul( k, base, s, r2, key->n );
     235        4989 :   ulong * acc = acc0;
     236        4989 :   ulong * nxt = acc1;
     237        4989 :   memcpy( acc, base, k*sizeof(ulong) );
     238       76713 :   for( int bit=fd_ulong_find_msb( key->e )-1; bit>=0; bit-- ) {
     239       71724 :     bignum_montmul( k, nxt, acc, acc, key->n );
     240       71724 :     if( (key->e>>bit) & 1UL ) {
     241        4989 :       bignum_montmul( k, acc, nxt, base, key->n );
     242       66735 :     } else {
     243       66735 :       ulong * t = acc; acc = nxt; nxt = t;
     244       66735 :     }
     245       71724 :   }
     246        4989 :   bignum_demont( k, tmp, acc, key->n );
     247        4989 :   fd_rsa_limbs_to_be( em, key->n_sz, tmp );
     248        4989 :   return 1;
     249        5061 : }
     250             : 
     251             : /* DER DigestInfo prefixes (RFC 8017 Section 9.2 Note 1) */
     252             : 
     253             : static uchar const fd_rsa_digest_info_sha256[ 19 ] = {
     254             :   0x30,0x31,0x30,0x0d,0x06,0x09,0x60,0x86,0x48,0x01,0x65,0x03,0x04,0x02,0x01,0x05,0x00,0x04,0x20 };
     255             : static uchar const fd_rsa_digest_info_sha384[ 19 ] = {
     256             :   0x30,0x41,0x30,0x0d,0x06,0x09,0x60,0x86,0x48,0x01,0x65,0x03,0x04,0x02,0x02,0x05,0x00,0x04,0x30 };
     257             : static uchar const fd_rsa_digest_info_sha512[ 19 ] = {
     258             :   0x30,0x51,0x30,0x0d,0x06,0x09,0x60,0x86,0x48,0x01,0x65,0x03,0x04,0x02,0x03,0x05,0x00,0x04,0x40 };
     259             : 
     260             : int
     261             : fd_rsa_verify_pkcs1_v15( fd_rsa_pubkey_t const * key,
     262             :                          uchar const *           sig,
     263             :                          ulong                   sig_sz,
     264             :                          uchar const *           msg,
     265             :                          ulong                   msg_sz,
     266        1542 :                          int                     hash ) {
     267        1542 :   ulong h_sz = fd_rsa_hash_sz( hash );
     268        1542 :   if( FD_UNLIKELY( !h_sz ) ) return FD_RSA_FAILURE;
     269        1497 :   uchar const * prefix;
     270        1497 :   switch( hash ) {
     271         546 :   case FD_RSA_HASH_SHA256: prefix = fd_rsa_digest_info_sha256; break;
     272         471 :   case FD_RSA_HASH_SHA384: prefix = fd_rsa_digest_info_sha384; break;
     273         480 :   default:                 prefix = fd_rsa_digest_info_sha512; break;
     274        1497 :   }
     275             : 
     276        1497 :   uchar em[ FD_RSA_MOD_SZ_MAX ];
     277        1497 :   if( FD_UNLIKELY( !fd_rsa_public_op( key, sig, sig_sz, em ) ) ) return FD_RSA_FAILURE;
     278             : 
     279             :   /* Rebuild the one valid encoding for this modulus size and compare
     280             :      it whole (RFC 8017 Section 8.2.2 step 4), so no partial parse of
     281             :      em can accept an alternative padding. */
     282        1371 :   ulong const em_sz = key->n_sz;
     283        1371 :   ulong const t_sz  = 19UL + h_sz;
     284        1371 :   if( FD_UNLIKELY( em_sz < t_sz+11UL ) ) return FD_RSA_FAILURE;
     285             : 
     286        1371 :   uchar expected[ FD_RSA_MOD_SZ_MAX ];
     287        1371 :   ulong ps_sz = em_sz - t_sz - 3UL;
     288        1371 :   expected[0] = 0x00;
     289        1371 :   expected[1] = 0x01;
     290        1371 :   memset( expected+2UL, 0xff, ps_sz );
     291        1371 :   expected[ 2UL+ps_sz ] = 0x00;
     292        1371 :   memcpy( expected+3UL+ps_sz, prefix, 19UL );
     293        1371 :   fd_rsa_hash( hash, msg, msg_sz, expected+em_sz-h_sz );
     294             : 
     295        1371 :   return fd_memeq( em, expected, em_sz ) ? FD_RSA_SUCCESS : FD_RSA_FAILURE;
     296        1371 : }
     297             : 
     298             : int
     299             : fd_rsa_verify_pss( fd_rsa_pubkey_t const * key,
     300             :                    uchar const *           sig,
     301             :                    ulong                   sig_sz,
     302             :                    uchar const *           msg,
     303             :                    ulong                   msg_sz,
     304        3798 :                    int                     hash ) {
     305        3798 :   ulong h_sz = fd_rsa_hash_sz( hash );
     306        3798 :   if( FD_UNLIKELY( !h_sz ) ) return FD_RSA_FAILURE;
     307        3753 :   ulong const s_sz = h_sz;
     308             : 
     309        3753 :   uchar em_buf[ FD_RSA_MOD_SZ_MAX ];
     310        3753 :   if( FD_UNLIKELY( !fd_rsa_public_op( key, sig, sig_sz, em_buf ) ) ) return FD_RSA_FAILURE;
     311             : 
     312             :   /* EMSA-PSS-VERIFY (RFC 8017 Section 9.1.2) with emBits = modBits-1.
     313             :      When modBits-1 is a multiple of 8, em is one byte shorter than the
     314             :      modulus and the public op result must have a zero leading byte. */
     315        3618 :   ulong const em_bits = key->mod_bits-1UL;
     316        3618 :   ulong const em_sz   = (em_bits+7UL)>>3;
     317        3618 :   uchar const * em = em_buf;
     318        3618 :   if( em_sz<key->n_sz ) {
     319         261 :     if( FD_UNLIKELY( em_buf[0] ) ) return FD_RSA_FAILURE;
     320         246 :     em++;
     321         246 :   }
     322             : 
     323        3603 :   if( FD_UNLIKELY( em_sz < h_sz+s_sz+2UL ) ) return FD_RSA_FAILURE;
     324        3603 :   if( FD_UNLIKELY( em[ em_sz-1UL ]!=0xbc ) ) return FD_RSA_FAILURE;
     325             : 
     326        2454 :   ulong const   db_sz     = em_sz - h_sz - 1UL;
     327        2454 :   uchar const * masked_db = em;
     328        2454 :   uchar const * h         = em + db_sz;
     329             : 
     330             :   /* The leftmost 8*emLen-emBits bits of maskedDB must be zero */
     331        2454 :   ulong const top_bits = 8UL*em_sz - em_bits;
     332        2454 :   if( FD_UNLIKELY( masked_db[0] & (uchar)( 0xff00U>>top_bits ) ) ) return FD_RSA_FAILURE;
     333             : 
     334             :   /* DB = maskedDB xor MGF1(H, dbLen) */
     335        2454 :   uchar db[ FD_RSA_MOD_SZ_MAX ];
     336        2454 :   uchar mgf_in[ FD_SHA512_HASH_SZ+4UL ];
     337        2454 :   memcpy( mgf_in, h, h_sz );
     338       26826 :   for( ulong off=0UL, ctr=0UL; off<db_sz; off+=h_sz, ctr++ ) {
     339       24372 :     FD_STORE( uint, mgf_in+h_sz, fd_uint_bswap( (uint)ctr ) );
     340       24372 :     uchar mask[ FD_SHA512_HASH_SZ ];
     341       24372 :     fd_rsa_hash( hash, mgf_in, h_sz+4UL, mask );
     342       24372 :     ulong n = fd_ulong_min( h_sz, db_sz-off );
     343      817758 :     for( ulong i=0UL; i<n; i++ ) db[ off+i ] = (uchar)( masked_db[ off+i ] ^ mask[ i ] );
     344       24372 :   }
     345        2454 :   db[0] = (uchar)( db[0] & (uchar)( 0xffU>>top_bits ) );
     346             : 
     347             :   /* DB = PS (zeros) || 0x01 || salt */
     348        2454 :   ulong const ps_sz = em_sz - h_sz - s_sz - 2UL;
     349        2454 :   uchar acc = 0;
     350      711402 :   for( ulong i=0UL; i<ps_sz; i++ ) acc |= db[i];
     351        2454 :   if( FD_UNLIKELY( acc || db[ ps_sz ]!=0x01 ) ) return FD_RSA_FAILURE;
     352        2340 :   uchar const * salt = db + ps_sz + 1UL;
     353             : 
     354             :   /* H' = Hash( 0x00 x8 || mHash || salt ) */
     355        2340 :   uchar m_prime[ 8UL + 2UL*FD_SHA512_HASH_SZ ];
     356        2340 :   memset( m_prime, 0, 8UL );
     357        2340 :   fd_rsa_hash( hash, msg, msg_sz, m_prime+8UL );
     358        2340 :   memcpy( m_prime+8UL+h_sz, salt, s_sz );
     359        2340 :   uchar h_prime[ FD_SHA512_HASH_SZ ];
     360        2340 :   fd_rsa_hash( hash, m_prime, 8UL+h_sz+s_sz, h_prime );
     361             : 
     362        2340 :   return fd_memeq( h_prime, h, h_sz ) ? FD_RSA_SUCCESS : FD_RSA_FAILURE;
     363        2454 : }

Generated by: LCOV version 1.14