Line data Source code
1 : #define _GNU_SOURCE
2 : #include "fd_keyload.h"
3 : #include "../../ballet/ed25519/fd_ed25519.h"
4 :
5 : #include <errno.h>
6 : #include <string.h>
7 : #include <fcntl.h>
8 : #include <unistd.h>
9 : #include <string.h>
10 : #include <stdio.h>
11 : #include <sys/mman.h>
12 : #include <sys/stat.h>
13 :
14 : static FD_TL uchar junk_public_key[ 32 ];
15 : static FD_TL int junk_key_fd = -1;
16 :
17 : void
18 : fd_keyload_set_failover_junk( uchar const public_key[ 32 ],
19 0 : int key_fd ) {
20 0 : fd_memcpy( junk_public_key, public_key, 32UL );
21 0 : junk_key_fd = key_fd;
22 0 : }
23 :
24 : uchar * FD_FN_SENSITIVE
25 : fd_keyload_read( int key_fd,
26 : char const * key_path,
27 12 : uchar * keypair ) {
28 12 : #define KEY_PARSE_ERR( ... ) \
29 12 : FD_LOG_ERR(( "Error while parsing the validator identity key at path " \
30 780 : "`%s` specified by [consensus.{identity|vote_account}_path] in the " \
31 780 : "configuration TOML file. Solana key files are " \
32 780 : "formatted as a 64-element JSON array. " __VA_ARGS__ ))
33 72 : #define KEY_SZ 64UL
34 : /* at least one digit per byte, commas in between each byte, opening and closing brackets */
35 12 : #define MIN_KEY_FILE_SZ ((ssize_t)(KEY_SZ + KEY_SZ-1UL + 2UL))
36 24 : #define MAX_KEY_FILE_SZ 1023UL /* Unless it has extraneous whitespace, max is 64*4+1 */
37 :
38 :
39 12 : char * json_key_file = (char *)keypair+KEY_SZ;
40 12 : long bytes_read = read( key_fd, keypair+KEY_SZ, MAX_KEY_FILE_SZ );
41 12 : if( FD_UNLIKELY( bytes_read==-1 ) ) FD_LOG_ERR(( "reading key file (%s) failed (%i-%s)", key_path, errno, fd_io_strerror( errno ) ));
42 12 : if( FD_UNLIKELY( close( key_fd ) ) ) FD_LOG_ERR(( "closing key file (%s) failed (%i-%s)", key_path, errno, fd_io_strerror( errno ) ));
43 :
44 12 : if( bytes_read<MIN_KEY_FILE_SZ ) FD_LOG_ERR(( "the specified key file (%s) was too short", key_path ));
45 12 : json_key_file[ bytes_read ] = '\0';
46 :
47 :
48 : /* These pointers reveal information about the key, so store them in
49 : the protected page temporarily as well. */
50 12 : char ** tok = (char **)(keypair+KEY_SZ+1024UL);
51 12 : if( FD_UNLIKELY( fd_cstr_tokenize( tok, KEY_SZ, json_key_file, ',' ) != KEY_SZ ) ) KEY_PARSE_ERR( "", key_path );
52 :
53 12 : if( FD_UNLIKELY( 1!=sscanf( tok[ 0 ], "[ %hhu", &keypair[ 0 ] ) ) )
54 12 : KEY_PARSE_ERR( "The file should start with an opening `[` followed by a decimal integer.", key_path );
55 756 : for( ulong i=1UL; i<63UL; i++ ) {
56 744 : if( FD_UNLIKELY( 1!=sscanf( tok[ i ], "%hhu", &keypair[ i ] ) ) )
57 744 : KEY_PARSE_ERR( "Parsing failed near the %luth value.", key_path, i );
58 744 : }
59 12 : if( FD_UNLIKELY( 1!=sscanf( tok[ 63 ], "%hhu ]", &keypair[ 63 ] ) ) )
60 12 : KEY_PARSE_ERR( "Parsing failed near the 63rd value. Perhaps the file is missing a closing `]`", key_path );
61 :
62 :
63 : /* Clear out the buffer just in case it was actually used */
64 12 : fd_memzero_explicit( json_key_file, MAX_KEY_FILE_SZ );
65 12 : fd_memzero_explicit( tok, KEY_SZ*sizeof(char *) );
66 12 : #undef MAX_KEY_FILE_SZ
67 12 : #undef MIN_KEY_FILE_SZ
68 12 : #undef KEY_SZ
69 12 : #undef KEY_PARSE_ERR
70 :
71 12 : return keypair;
72 12 : }
73 :
74 : /* Expects that key[i] is writable for i in [0, 1600). */
75 : static inline uchar * FD_FN_SENSITIVE
76 : read_key( char const * key_path,
77 12 : uchar * key ) {
78 12 : int key_fd = open( key_path, O_RDONLY );
79 12 : if( FD_UNLIKELY( key_fd==-1 ) ) {
80 0 : if( FD_UNLIKELY( errno==ENOENT ) ) {
81 0 : FD_LOG_ERR((
82 0 : "The [consensus.identity_path] in your configuration expects a "
83 0 : "keyfile at `%s` but there is no such file. Either update the "
84 0 : "configuration file to point to your validator identity "
85 0 : "keypair, or generate a new validator identity key by running "
86 0 : "`fdctl keys new %s`", key_path, key_path ));
87 0 : } else
88 0 : FD_LOG_ERR(( "Opening key file (%s) failed (%i-%s)", key_path, errno, fd_io_strerror( errno ) ));
89 0 : }
90 :
91 12 : return fd_keyload_read( key_fd, key_path, key );
92 12 : }
93 :
94 : static void FD_FN_SENSITIVE
95 0 : read_junk_key( uchar * key ) {
96 0 : int key_fd = junk_key_fd;
97 0 : junk_key_fd = -1;
98 0 : struct stat st;
99 0 : if( FD_UNLIKELY( fstat( key_fd, &st ) || !S_ISFIFO( st.st_mode ) ) )
100 0 : FD_LOG_ERR(( "the failover junk key requires a startup pipe" ));
101 0 : for( ulong off=0UL; off<64UL; ) {
102 0 : long n = read( key_fd, key+off, 64UL-off );
103 0 : if( FD_UNLIKELY( n==-1L && errno==EINTR ) ) continue;
104 0 : if( FD_UNLIKELY( n<=0L ) ) FD_LOG_ERR(( "could not read the failover junk key" ));
105 0 : off += (ulong)n;
106 0 : }
107 0 : long n;
108 0 : do n = read( key_fd, key+64UL, 1UL ); while( FD_UNLIKELY( n==-1L && errno==EINTR ) );
109 0 : if( FD_UNLIKELY( n ) ) FD_LOG_ERR(( "unexpected data after the failover junk key" ));
110 0 : if( FD_UNLIKELY( close( key_fd ) ) ) FD_LOG_ERR(( "closing the failover junk key pipe failed (%i-%s)", errno, fd_io_strerror( errno ) ));
111 0 : fd_sha512_t * sha = fd_sha512_join( fd_sha512_new( key+FD_SHA512_ALIGN ) );
112 0 : FD_TEST( sha );
113 0 : fd_ed25519_public_from_private( key+64UL, key, sha );
114 0 : if( FD_UNLIKELY( !fd_memeq( key+32UL, key+64UL, 32UL ) ||
115 0 : !fd_memeq( key+32UL, junk_public_key, 32UL ) ) )
116 0 : FD_LOG_ERR(( "the failover junk key does not match its public key" ));
117 0 : fd_memzero_explicit( key+64UL, 4096UL-64UL );
118 0 : }
119 :
120 : uchar const * FD_FN_SENSITIVE
121 : fd_keyload_load( char const * key_path,
122 12 : int public_key_only ) {
123 12 : if( FD_UNLIKELY( !key_path || !key_path[0] ) ) {
124 0 : FD_LOG_ERR(( "Missing key_path" ));
125 0 : }
126 :
127 : /* Load the signing key. Since this is key material, we load it into
128 : its own page that's non-dumpable, readonly, and protected by guard
129 : pages. */
130 12 : uchar * key_page = fd_keyload_alloc_protected_pages( 1UL, 2UL );
131 :
132 12 : if( FD_UNLIKELY( !strcmp( key_path, FD_KEYLOAD_FAILOVER_JUNK ) ) ) {
133 0 : if( FD_UNLIKELY( fd_memeq( junk_public_key, (uchar[ 32 ]){0}, 32UL ) ) ) FD_LOG_ERR(( "missing failover junk key" ));
134 0 : if( public_key_only ) fd_memcpy( key_page+32UL, junk_public_key, 32UL );
135 0 : else read_junk_key( key_page );
136 12 : } else {
137 12 : read_key( key_path, key_page );
138 12 : }
139 :
140 12 : if( public_key_only ) fd_memzero_explicit( key_page, 32UL );
141 :
142 : /* Make the key page read-only now that the key has been loaded. */
143 12 : if( FD_UNLIKELY( mprotect( key_page, 4096UL, PROT_READ ) ) )
144 0 : FD_LOG_ERR(( "mprotect failed (%i-%s)", errno, fd_io_strerror( errno ) ));
145 :
146 12 : if( public_key_only ) return key_page+32UL;
147 6 : else return key_page;
148 12 : }
149 :
150 : uchar const * FD_FN_SENSITIVE
151 0 : fd_keyload_generate( void ) {
152 0 : uchar * key = fd_keyload_alloc_protected_pages( 1UL, 2UL );
153 0 : if( FD_UNLIKELY( !fd_rng_secure( key, 32UL ) ) ) FD_LOG_ERR(( "could not generate a keypair" ));
154 0 : fd_sha512_t * sha = fd_sha512_join( fd_sha512_new( key+FD_SHA512_ALIGN ) );
155 0 : FD_TEST( sha );
156 0 : fd_ed25519_public_from_private( key+32UL, key, sha );
157 0 : fd_memzero_explicit( key+FD_SHA512_ALIGN, sizeof(fd_sha512_t) );
158 0 : return fd_keyload_mprotect_ro( key, 0 );
159 0 : }
160 :
161 : uchar * FD_FN_SENSITIVE
162 : fd_keyload_mprotect_wr( uchar const * key,
163 0 : int public_key_only ) {
164 0 : uchar * key_mut = (uchar *)key;
165 0 : void * key_page = public_key_only ? key_mut-32UL : key_mut;
166 0 : if( FD_UNLIKELY( mprotect( key_page, 4096UL, PROT_READ | PROT_WRITE ) ) )
167 0 : FD_LOG_ERR(( "mprotect failed (%i-%s)", errno, fd_io_strerror( errno ) ));
168 0 : return key_mut;
169 0 : }
170 :
171 : uchar const * FD_FN_SENSITIVE
172 : fd_keyload_mprotect_ro( uchar * key,
173 0 : int public_key_only ) {
174 0 : void * key_page = public_key_only ? key-32UL : key;
175 0 : if( FD_UNLIKELY( mprotect( key_page, 4096UL, PROT_READ ) ) )
176 0 : FD_LOG_ERR(( "mprotect failed (%i-%s)", errno, fd_io_strerror( errno ) ));
177 0 : return key;
178 0 : }
179 :
180 : void FD_FN_SENSITIVE
181 : fd_keyload_unload( uchar const * key,
182 6 : int public_key_only ) {
183 6 : void * key_page = public_key_only ? (uchar *)key-32UL : (uchar *)key;
184 6 : ulong sz = (2UL*1UL+2UL)*4096UL;
185 :
186 6 : if( FD_UNLIKELY( mprotect( key_page, 4096UL, PROT_READ | PROT_WRITE ) ) )
187 0 : FD_LOG_ERR(( "mprotect failed (%i-%s)", errno, fd_io_strerror( errno ) ));
188 6 : fd_memzero_explicit( key_page, 4096UL );
189 :
190 6 : if( FD_UNLIKELY( -1==munmap( (uchar*)key_page - 2UL*4096UL, sz ) ) )
191 0 : FD_LOG_ERR(( "munmap failed (%i-%s)", errno, fd_io_strerror( errno ) ));
192 6 : }
193 :
194 : void * FD_FN_SENSITIVE
195 : fd_keyload_alloc_protected_pages( ulong page_cnt,
196 18 : ulong guard_page_cnt ) {
197 36 : #define PAGE_SZ (4096UL)
198 18 : void * pages = mmap( NULL, (2UL*guard_page_cnt+page_cnt)*PAGE_SZ, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0UL );
199 18 : if( FD_UNLIKELY( pages==MAP_FAILED ) ) FD_LOG_ERR(( "mmap failed (%i-%s)", errno, fd_io_strerror( errno ) ));
200 :
201 18 : uchar * middle_pages = (uchar *)( (ulong)pages + guard_page_cnt*PAGE_SZ );
202 :
203 : /* Make the guard pages untouchable */
204 18 : if( FD_UNLIKELY( mprotect( pages, guard_page_cnt*PAGE_SZ, PROT_NONE ) ) )
205 0 : FD_LOG_ERR(( "mprotect failed (%i-%s)", errno, fd_io_strerror( errno ) ));
206 :
207 18 : if( FD_UNLIKELY( mprotect( middle_pages+page_cnt*PAGE_SZ, guard_page_cnt*PAGE_SZ, PROT_NONE ) ) )
208 0 : FD_LOG_ERR(( "mprotect failed (%i-%s)", errno, fd_io_strerror( errno ) ));
209 :
210 : /* Lock the key page so that it doesn't page to disk */
211 18 : if( FD_UNLIKELY( mlock( middle_pages, page_cnt*PAGE_SZ ) ) )
212 0 : FD_LOG_ERR(( "mlock failed (%i-%s)", errno, fd_io_strerror( errno ) ));
213 :
214 : /* Prevent the key page from showing up in core dumps. It shouldn't be
215 : possible to fork this process typically, but we also prevent any
216 : forked child from having this page. */
217 : /* `madvise` supports only a single `advice` per call, so we cannot combine via bitwise or. */
218 18 : if( FD_UNLIKELY( madvise( middle_pages, page_cnt*PAGE_SZ, MADV_WIPEONFORK ) ) )
219 0 : FD_LOG_ERR(( "madvise failed (%i-%s)", errno, fd_io_strerror( errno ) ));
220 18 : if( FD_UNLIKELY( madvise( middle_pages, page_cnt*PAGE_SZ, MADV_DONTDUMP ) ) )
221 0 : FD_LOG_ERR(( "madvise failed (%i-%s)", errno, fd_io_strerror( errno ) ));
222 :
223 18 : return middle_pages;
224 18 : #undef PAGE_SZ
225 18 : }
|