LCOV - code coverage report
Current view: top level - app/shared/commands - keys.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 0 102 0.0 %
Date: 2026-06-29 05:51:35 Functions: 0 6 0.0 %

          Line data    Source code
       1             : #include "../fd_config.h"
       2             : #include "../fd_action.h"
       3             : 
       4             : #include "../../platform/fd_file_util.h"
       5             : #include "../../../disco/keyguard/fd_keyload.h"
       6             : 
       7             : #include <errno.h>
       8             : #include <fcntl.h>
       9             : #include <unistd.h>
      10             : #include <sys/stat.h>
      11             : #include <sys/random.h>
      12             : 
      13             : typedef enum {
      14             :   CMD_NEW_KEY,
      15             :   CMD_PUBKEY,
      16             : } cmd_type_t;
      17             : 
      18             : static int
      19             : cstr_ncpy_check( char *       dst,
      20             :                  char const * src,
      21           0 :                  ulong        dst_sz ) {
      22           0 :   if( FD_UNLIKELY( fd_cstr_nlen( src, dst_sz )>=dst_sz ) ) return 0;
      23             : 
      24           0 :   fd_cstr_ncpy( dst, src, dst_sz );
      25           0 :   return 1;
      26           0 : }
      27             : 
      28             : void
      29             : keys_cmd_args( int *    pargc,
      30             :                char *** pargv,
      31           0 :                args_t * args ) {
      32           0 :   if( FD_UNLIKELY( *pargc < 2 ) ) goto err;
      33             : 
      34           0 :   if( FD_LIKELY( !strcmp( *pargv[ 0 ], "new" ) ) ) {
      35           0 :     (*pargc)--;
      36           0 :     (*pargv)++;
      37           0 :     if( FD_UNLIKELY( *pargc < 1 ) ) goto err;
      38           0 :     args->keys.cmd = CMD_NEW_KEY;
      39           0 :     if( FD_UNLIKELY( !cstr_ncpy_check( args->keys.file_path, *pargv[ 0 ], sizeof( args->keys.file_path ) ) ) ) {
      40           0 :       FD_LOG_ERR(( "key file path too long" ));
      41           0 :     }
      42           0 :   }
      43           0 :   else if( FD_LIKELY( !strcmp( *pargv[ 0 ], "pubkey"  ) ) ) {
      44           0 :     (*pargc)--;
      45           0 :     (*pargv)++;
      46           0 :     if( FD_UNLIKELY( *pargc < 1 ) ) goto err;
      47           0 :     args->keys.cmd = CMD_PUBKEY;
      48           0 :     if( FD_UNLIKELY( !cstr_ncpy_check( args->keys.file_path, *pargv[ 0 ], sizeof( args->keys.file_path ) ) ) ) {
      49           0 :       FD_LOG_ERR(( "key file path too long" ));
      50           0 :     }
      51           0 :   }
      52           0 :   else goto err;
      53             : 
      54           0 :   (*pargc)--;
      55           0 :   (*pargv)++;
      56             : 
      57           0 :   return;
      58             : 
      59           0 : err:
      60           0 :     FD_LOG_ERR(( "unrecognized subcommand `%s`\nusage:\n"
      61           0 :                  "  keys new <path-to-keyfile>\n"
      62           0 :                  "  keys pubkey <path-to-keyfile>\n",
      63           0 :                  *pargv[0] ));
      64           0 : }
      65             : 
      66             : void FD_FN_SENSITIVE
      67             : generate_keypair( char const *     keyfile,
      68             :                   uint             target_uid,
      69             :                   uint             target_gid,
      70           0 :                   int              use_grnd_random ) {
      71           0 :   uint flags = use_grnd_random ? GRND_RANDOM : 0U;
      72             : 
      73           0 :   uchar keypair[ 64 ];
      74           0 :   long bytes_produced = 0L;
      75           0 :   while( FD_LIKELY( bytes_produced<32L ) ) {
      76           0 :     long n = getrandom( keypair+bytes_produced, (ulong)(32-bytes_produced), flags );
      77           0 :     if( FD_UNLIKELY( -1==n ) ) FD_LOG_ERR(( "could not create keypair, getrandom() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
      78           0 :     bytes_produced += n;
      79           0 :   }
      80             : 
      81           0 :   fd_sha512_t _sha[ 1 ];
      82           0 :   fd_sha512_t * sha = fd_sha512_join( fd_sha512_new( _sha ) );
      83           0 :   if( FD_UNLIKELY( !sha ) ) FD_LOG_ERR(( "could not create keypair, fd_sha512 join failed" ));
      84           0 :   fd_ed25519_public_from_private( keypair+32UL, keypair, sha );
      85             : 
      86             :   /* Switch to non-root uid/gid for file creation.  Permissions checks
      87             :      are still done as root. */
      88           0 :   gid_t gid = getgid();
      89           0 :   uid_t uid = getuid();
      90           0 :   if( FD_LIKELY( !gid && setegid( target_gid ) ) ) FD_LOG_ERR(( "setegid() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
      91           0 :   if( FD_LIKELY( !uid && seteuid( target_uid ) ) ) FD_LOG_ERR(( "seteuid() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
      92             : 
      93             :   /* find last `/` in keyfile and zero it */
      94           0 :   char keyfile_copy[ PATH_MAX ] = {0};
      95           0 :   strncpy( keyfile_copy, keyfile, sizeof( keyfile_copy )-1UL );
      96           0 :   char * last_slash = strrchr( keyfile_copy, '/' );
      97           0 :   if( FD_LIKELY( last_slash ) ) {
      98           0 :     *last_slash = '\0';
      99           0 :     if( FD_UNLIKELY( -1==fd_file_util_mkdir_all( keyfile_copy, target_uid, target_gid, 1 ) ) ) {
     100           0 :       FD_LOG_ERR(( "could not create keypair, `mkdir -p %s` failed (%i-%s)", keyfile_copy, errno, fd_io_strerror( errno ) ));
     101           0 :     }
     102           0 :   }
     103             : 
     104           0 :   int fd = open( keyfile, O_WRONLY|O_CREAT|O_EXCL, S_IRUSR|S_IWUSR );
     105           0 :   if( FD_UNLIKELY( -1==fd ) ) {
     106           0 :     if( FD_LIKELY( errno==EEXIST ) ) FD_LOG_ERR(( "could not create keypair as the keyfile `%s` already exists", keyfile ));
     107           0 :     else                             FD_LOG_ERR(( "could not create keypair, open(%s) failed (%i-%s)", keyfile, errno, fd_io_strerror( errno ) ));
     108           0 :   }
     109             : 
     110           0 :   if( FD_UNLIKELY( write( fd, "[", 1 )!=1L ) ) FD_LOG_ERR(( "could not create keypair, write() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     111             : 
     112           0 :   for( ulong i=0UL; i<64UL; i++ ) {
     113           0 :     if( FD_LIKELY( i ) ) {
     114           0 :       if( FD_UNLIKELY( write( fd, ",", 1 )!=1L ) ) FD_LOG_ERR(( "could not create keypair, write() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     115           0 :     }
     116             : 
     117           0 :     char digits[ 4 ];
     118           0 :     ulong digits_len;
     119           0 :     FD_TEST( fd_cstr_printf_check( digits, sizeof( digits ), &digits_len, "%d", keypair[ i ] ) );
     120           0 :     if( FD_UNLIKELY( write( fd, digits, digits_len )!=(long)digits_len ) ) FD_LOG_ERR(( "could not create keypair, write() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     121           0 :   }
     122             : 
     123           0 :   if( FD_UNLIKELY( write( fd, "]", 1 )!=1L ) ) FD_LOG_ERR(( "could not create keypair, write() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     124           0 :   if( FD_UNLIKELY( close( fd ) ) ) FD_LOG_ERR(( "could not create keypair, close() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     125             : 
     126           0 :   FD_LOG_NOTICE(( "successfully created keypair in `%s`", keyfile ));
     127             : 
     128           0 :   if( FD_UNLIKELY( seteuid( uid ) ) ) FD_LOG_ERR(( "seteuid() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     129           0 :   if( FD_UNLIKELY( setegid( gid ) ) ) FD_LOG_ERR(( "setegid() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     130             : 
     131           0 :   fd_memzero_explicit( keypair, 64UL );
     132           0 : }
     133             : 
     134             : static void
     135           0 : keys_pubkey( const char * file_path ) {
     136           0 :   uchar const * pubkey = fd_keyload_load( file_path, 1 );
     137           0 :   char pubkey_str[ FD_BASE58_ENCODED_32_SZ ];
     138           0 :   fd_base58_encode_32( pubkey, NULL, pubkey_str );
     139           0 :   FD_LOG_STDOUT(( "%s\n", pubkey_str ));
     140           0 : }
     141             : 
     142             : void
     143             : keys_cmd_fn( args_t *   args,
     144           0 :              config_t * config ) {
     145           0 :   if( FD_LIKELY( args->keys.cmd == CMD_NEW_KEY ) ) {
     146           0 :     generate_keypair( args->keys.file_path, config->uid, config->gid, 1 );
     147           0 :   } else if( FD_LIKELY( args->keys.cmd==CMD_PUBKEY ) ) {
     148           0 :     keys_pubkey( args->keys.file_path );
     149           0 :   } else {
     150           0 :     FD_LOG_ERR(( "unknown key type `%lu`", args->keys.cmd ));
     151           0 :   }
     152           0 : }
     153             : 
     154             : static void
     155           0 : keys_args_help( fd_action_help_t * help ) {
     156           0 :   fd_action_help_arg( help, "new <path>",    NULL, "Generate a new random ED25519 keypair and write it to <path> as a\n"
     157           0 :                                                    "Solana keypair file (a JSON array of 64 bytes)" );
     158             :   fd_action_help_arg( help, "pubkey <path>", NULL, "Read the Solana keypair file at <path> and print its base58 public key" );
     159           0 : }
     160             : 
     161             : action_t fd_action_keys = {
     162             :   .name        = "keys",
     163             :   .args        = keys_cmd_args,
     164             :   .fn          = keys_cmd_fn,
     165             :   .perm        = NULL,
     166             :   .description = "Generate new keypairs for use with the validator or print a public key",
     167             :   .detail      = "Local key file utility.  `new` creates a fresh validator or vote account\n"
     168             :                  "keypair; `pubkey` prints the public key of an existing keypair file.  This\n"
     169             :                  "command does not require a running validator.",
     170             :   .usage       = "keys <new|pubkey> <path-to-keyfile>",
     171             :   .args_help   = keys_args_help,
     172             : };

Generated by: LCOV version 1.14