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 : };
|