Line data Source code
1 : #include "fd_keyguard.h"
2 : #include "fd_keyguard_client.h"
3 : #include "../bundle/fd_bundle_crank_constants.h"
4 : #include "../../flamenco/runtime/fd_system_ids.h"
5 : #include "../../flamenco/gossip/fd_gossip_value.h"
6 : #include "../../ballet/txn/fd_compact_u16.h"
7 : #include "../../waltz/tls/fd_tls.h"
8 : /* manually include just fd_features_generated.h so we can get
9 : FD_FEATURE_SET_ID without anything else that we don't need. */
10 : #define HEADER_fd_src_flamenco_features_fd_features_h
11 : #include "../../flamenco/features/fd_features_generated.h"
12 : #undef HEADER_fd_src_flamenco_features_fd_features_h
13 :
14 : struct fd_keyguard_sign_req {
15 : fd_keyguard_authority_t * authority;
16 : };
17 :
18 : typedef struct fd_keyguard_sign_req fd_keyguard_sign_req_t;
19 :
20 : static int
21 : fd_keyguard_authorize_vote_txn( fd_keyguard_authority_t const * authority,
22 : uchar const * data,
23 : ulong sz,
24 0 : int sign_type ) {
25 0 : if( sign_type != FD_KEYGUARD_SIGN_TYPE_ED25519 ) return 0;
26 0 : if( sz > FD_TXN_MTU ) return 0;
27 : /* Each vote transaction may have 1 or 2 signers. The first byte in
28 : the transaction message is the number of signers. */
29 0 : ulong off = 0UL;
30 0 : uchar signer_cnt = data[off];
31 0 : if( signer_cnt!=1 && signer_cnt!=2 ) return 0;
32 0 : if( signer_cnt==1 && sz<=140 ) return 0;
33 0 : if( signer_cnt==2 && sz<=172 ) return 0;
34 : /* The authority's public key will be the first listed account in the
35 : transaction message. */
36 :
37 : /* r/o signers = 1 when there are 2 signers and 1 otherwise. */
38 0 : off++;
39 0 : if( data[off]!=signer_cnt-1 ) return 0;
40 :
41 : /* There will always be 1 r/o unsigned account. */
42 0 : off++;
43 0 : if( data[off]!=1 ) return 0;
44 :
45 : /* The only accounts should be the 1 or 2 signers, the vote account,
46 : and the vote program. The number of accounts is represented as a
47 : compact u16. */
48 0 : off++;
49 0 : ulong bytes = fd_cu16_dec_sz( data+off, 3UL );
50 0 : if( bytes!=1UL ) return 0;
51 0 : ulong acc_cnt = 2+signer_cnt;
52 0 : if( data[off]!=acc_cnt ) return 0;
53 :
54 : /* The first account should always be the authority's public key. */
55 0 : off++;
56 0 : ulong acct_off = off;
57 0 : if( memcmp( authority->identity_pubkey, data + acct_off, 32 ) ) return 0;
58 :
59 : /* Each transaction account key is listed out and is followed by a 32
60 : byte blockhash. The instruction count is after this. */
61 0 : off += (acc_cnt+1) * 32;
62 0 : bytes = fd_cu16_dec_sz( data+off, 3UL );
63 0 : uchar instr_cnt = data[ off ];
64 0 : if( bytes!=1UL ) return 0;
65 0 : if( instr_cnt!=1 ) return 0;
66 :
67 : /* The program id will be the first byte of the instruction payload
68 : and should be the vote program. */
69 0 : off++;
70 0 : uchar program_id = data[ off ];
71 0 : if( program_id != acc_cnt-1 ) return 0;
72 0 : ulong program_acct_off = 4UL + (program_id * 32UL);
73 0 : if( memcmp( &fd_solana_vote_program_id, data+program_acct_off, 32 ) ) return 0;
74 :
75 0 : off++;
76 0 : bytes = fd_cu16_dec_sz( data+off, 3UL );
77 0 : if( bytes!=1UL ) return 0;
78 :
79 : /* Vote account count will always be 2. One byte is used to list the
80 : account count for the transaction and 1 byte for each account. */
81 0 : if( data[ off ]!=2 ) return 0;
82 0 : off += 3UL;
83 :
84 : /* Move the cursor forward by the instruction data size. The first
85 : byte of the instruction data will be the discriminant. Only allow
86 : tower sync vote instructions (14). */
87 0 : bytes = fd_cu16_dec_sz( data+off, 3UL );
88 0 : off += bytes;
89 0 : if( data[off]!=14 ) return 0;
90 :
91 0 : return 1;
92 0 : }
93 :
94 : static int
95 : fd_keyguard_authorize_gossip( fd_keyguard_authority_t const * authority,
96 : uchar const * data,
97 : ulong sz,
98 0 : int sign_type ) {
99 0 : if( sign_type != FD_KEYGUARD_SIGN_TYPE_ED25519 ) return 0;
100 :
101 : /* Every gossip message contains a 4 byte enum variant tag (at the
102 : beginning of the message) and a 32 byte public key (at an arbitrary
103 : location). */
104 0 : if( sz<36UL ) return 0;
105 0 : if( sz>1188UL-64UL ) return 0;
106 :
107 0 : uint tag = FD_LOAD( uint, data );
108 0 : ulong origin_off = ULONG_MAX;
109 0 : switch( tag ) {
110 0 : case FD_GOSSIP_VALUE_VOTE:
111 0 : origin_off = 1UL;
112 0 : if( sz<4UL+1UL+32UL+FD_TXN_MIN_SERIALIZED_SZ+8UL ) return 0;
113 0 : ulong sig_cnt = data[ 4UL+1UL+32UL ];
114 0 : if( (sig_cnt==0UL) | (sig_cnt>2UL) ) return 0;
115 0 : ulong vote_off = 4UL+1UL+32UL+1UL+64UL*sig_cnt;
116 0 : if( !fd_keyguard_authorize_vote_txn( authority, data+vote_off, sz-(vote_off+8UL), FD_KEYGUARD_SIGN_TYPE_ED25519 ) )
117 0 : return 0;
118 0 : break;
119 0 : case FD_GOSSIP_VALUE_CONTACT_INFO:
120 0 : origin_off = 0UL;
121 : /* Contact info is pretty tough to parse. The best we can do is
122 : check the feature set and client ID.
123 : min sz
124 : 4B tag
125 : 32B origin
126 : 1B-10B wallclock
127 : 8B outset
128 : 2B shred version
129 : 1B-3B major version
130 : 1B-3B minor version
131 : 1B-3B patch version
132 : 4B commit
133 : 4B feature set
134 : 1B-3B client ID
135 : 1B-3B unique addr cnt
136 : ??? IP addresses
137 : 1B-3B socket cnt
138 : ??? ports
139 : 1B-3B extension len
140 : ...
141 :
142 : Total: 62B+
143 : */
144 0 : if( sz<62UL ) return 0;
145 0 : ulong off = 4UL+32UL;
146 0 : for( ulong i=0UL; i<10UL; i++ ) if( !(data[ off++ ]&0x80) ) break;
147 0 : off += 8UL+2UL;
148 : /* off<56, so we're still safe here */
149 0 : if( sz<off+15UL ) return 0;
150 0 : for( ulong i=0UL; i<3UL; i++ ) if( !(data[ off++ ]&0x80) ) break;
151 0 : for( ulong i=0UL; i<3UL; i++ ) if( !(data[ off++ ]&0x80) ) break;
152 0 : for( ulong i=0UL; i<3UL; i++ ) if( !(data[ off++ ]&0x80) ) break;
153 0 : if( sz<off+12UL ) return 0;
154 0 : uint commit = FD_LOAD( uint, data+off ); off += 4UL;
155 0 : uint feature_set = FD_LOAD( uint, data+off ); off += 4UL;
156 0 : uchar client_id = data[ off ];
157 0 : (void)commit; /* Checking commit introduces a circular dependency between disco and app :'( */
158 0 : if( feature_set!=FD_FEATURE_SET_ID ) return 0;
159 0 : if( client_id !=5 ) return 0; /* FD_GOSSIP_CONTACT_INFO_CLIENT_FIREDANCER */
160 :
161 0 : break;
162 0 : case FD_GOSSIP_VALUE_DUPLICATE_SHRED:
163 0 : origin_off = 2UL;
164 0 : if( sz< 4UL+65UL ) return 0;
165 0 : ulong chunk_len = FD_LOAD( ulong, data+4UL+57UL );
166 0 : if( sz!=4UL+65UL+chunk_len ) return 0;
167 0 : break;
168 :
169 : /* We don't sign these yet. */
170 0 : case FD_GOSSIP_VALUE_NODE_INSTANCE: /* origin_off = 0UL; break; */ return 0;
171 0 : case FD_GOSSIP_VALUE_SNAPSHOT_HASHES: /* origin_off = 0UL; break; */ return 0;
172 :
173 : /* We refuse to serialize these */
174 0 : case FD_GOSSIP_VALUE_LEGACY_CONTACT_INFO:
175 0 : case FD_GOSSIP_VALUE_LOWEST_SLOT:
176 0 : case FD_GOSSIP_VALUE_LEGACY_SNAPSHOT_HASHES:
177 0 : case FD_GOSSIP_VALUE_ACCOUNT_HASHES:
178 0 : case FD_GOSSIP_VALUE_EPOCH_SLOTS:
179 0 : case FD_GOSSIP_VALUE_LEGACY_VERSION:
180 0 : case FD_GOSSIP_VALUE_VERSION:
181 0 : case FD_GOSSIP_VALUE_RESTART_LAST_VOTED_FORK_SLOTS:
182 0 : case FD_GOSSIP_VALUE_RESTART_HEAVIEST_FORK:
183 0 : default:
184 0 : return 0;
185 0 : }
186 0 : if( sz<sizeof(uint)+origin_off+32UL ) return 0;
187 :
188 0 : return fd_memeq( authority->identity_pubkey, data+sizeof(uint)+origin_off, 32UL );
189 0 : }
190 :
191 : static int
192 : fd_keyguard_authorize_bundle_crank_txn( fd_keyguard_authority_t const * authority,
193 : uchar const * data,
194 : ulong sz,
195 0 : int sign_type ) {
196 0 : static const uchar disc1[ 8 ] = { FD_BUNDLE_CRANK_DISC_INIT_TIP_DISTR };
197 0 : static const uchar disc2[ 8 ] = { FD_BUNDLE_CRANK_DISC_CHANGE_TIP_RCV };
198 0 : static const uchar disc3[ 8 ] = { FD_BUNDLE_CRANK_DISC_CHANGE_BLK_BLD };
199 :
200 0 : if( sign_type != FD_KEYGUARD_SIGN_TYPE_ED25519 ) return 0;
201 :
202 0 : (void)authority;
203 : /* TODO: we can check a lot more bytes */
204 0 : switch( sz ) {
205 0 : case (FD_BUNDLE_CRANK_2_SZ-65UL):
206 0 : return fd_memeq( data+FD_BUNDLE_CRANK_2_IX1_DISC_OFF-65UL, disc2, 8UL ) &&
207 0 : fd_memeq( data+FD_BUNDLE_CRANK_2_IX2_DISC_OFF-65UL, disc3, 8UL );
208 0 : case (FD_BUNDLE_CRANK_3_SZ-65UL):
209 0 : return fd_memeq( data+FD_BUNDLE_CRANK_3_IX1_DISC_OFF-65UL, disc1, 8UL ) &&
210 0 : fd_memeq( data+FD_BUNDLE_CRANK_3_IX2_DISC_OFF-65UL, disc2, 8UL ) &&
211 0 : fd_memeq( data+FD_BUNDLE_CRANK_3_IX3_DISC_OFF-65UL, disc3, 8UL );
212 0 : default:
213 0 : return 0;
214 0 : }
215 0 : }
216 :
217 : static int
218 : fd_keyguard_authorize_ping( fd_keyguard_authority_t const * authority,
219 : uchar const * data,
220 : ulong sz,
221 0 : int sign_type ) {
222 0 : (void)authority;
223 0 : if( sign_type != FD_KEYGUARD_SIGN_TYPE_ED25519 ) return 0;
224 0 : if( sz != 32 ) return 0;
225 0 : if( 0!=memcmp( data, "SOLANA_PING_PONG", 16 ) ) return 0;
226 0 : return 1;
227 0 : }
228 :
229 : static int
230 : fd_keyguard_authorize_pong( fd_keyguard_authority_t const * authority,
231 : uchar const * data,
232 : ulong sz,
233 0 : int sign_type ) {
234 0 : (void)authority;
235 0 : if( sign_type != FD_KEYGUARD_SIGN_TYPE_SHA256_ED25519 ) return 0;
236 0 : if( sz != 48 ) return 0;
237 0 : if( 0!=memcmp( data, "SOLANA_PING_PONG", 16 ) ) return 0;
238 0 : return 1;
239 0 : }
240 :
241 : static int
242 : fd_keyguard_authorize_gossip_prune( fd_keyguard_authority_t const * authority,
243 : uchar const * data,
244 : ulong sz,
245 0 : int sign_type ) {
246 0 : if( FD_UNLIKELY( sign_type != FD_KEYGUARD_SIGN_TYPE_ED25519 ) ) return 0;
247 : /* Prune messages always start with the prefix followed by the pubkey. */
248 0 : if( sz<66UL ) return 0;
249 0 : if( FD_LOAD( ulong, data )!=18UL ) return 0;
250 0 : if( 0!=memcmp( data+8UL, "\xffSOLANA_PRUNE_DATA", 18 ) ) return 0;
251 0 : if( 0!=memcmp( authority->identity_pubkey, data+26UL, 32 ) ) return 0;
252 0 : return 1;
253 0 : }
254 :
255 : static int
256 : fd_keyguard_authorize_repair( fd_keyguard_authority_t const * authority,
257 : uchar const * data,
258 : ulong sz,
259 0 : int sign_type ) {
260 :
261 0 : if( sign_type != FD_KEYGUARD_SIGN_TYPE_ED25519 ) return 0;
262 0 : if( sz<80 ) return 0;
263 :
264 0 : uint discriminant = fd_uint_load_4( data );
265 0 : uchar const * sender = data+4;
266 :
267 0 : if( discriminant< 8 ) return 0; /* window_index is min ID */
268 0 : if( discriminant>11 ) return 0; /* ancestor_hashes is max ID */
269 :
270 0 : if( 0!=memcmp( authority->identity_pubkey, sender, 32 ) ) return 0;
271 :
272 0 : return 1;
273 0 : }
274 :
275 : static int
276 : fd_keyguard_authorize_tls_cv( fd_keyguard_authority_t const * authority FD_PARAM_UNUSED,
277 : uchar const * data,
278 : ulong sz,
279 0 : int sign_type ) {
280 0 : if( FD_UNLIKELY( sign_type != FD_KEYGUARD_SIGN_TYPE_ED25519 ) ) return 0;
281 0 : if( FD_UNLIKELY( sz != 130 ) ) return 0;
282 :
283 : /* validate client prefix against fd_tls */
284 0 : return fd_memeq( fd_tls13_cli_sign_prefix, data, sizeof(fd_tls13_cli_sign_prefix) );
285 0 : }
286 :
287 : int
288 : fd_keyguard_payload_authorize( fd_keyguard_authority_t const * authority,
289 : uchar const * data,
290 : ulong sz,
291 : int role,
292 0 : int sign_type ) {
293 :
294 0 : if( sz > FD_KEYGUARD_SIGN_REQ_MTU ) {
295 0 : FD_LOG_WARNING(( "oversz signing request (role=%d sz=%lu)", role, sz ));
296 0 : return 0;
297 0 : }
298 :
299 : /* Identify payload type */
300 :
301 0 : ulong payload_mask = fd_keyguard_payload_match( data, sz, sign_type );
302 0 : int match_cnt = fd_ulong_popcnt( payload_mask );
303 0 : if( FD_UNLIKELY( payload_mask==0UL ) ) {
304 0 : FD_LOG_WARNING(( "unrecognized payload type (role=%#x)", (uint)role ));
305 0 : }
306 :
307 0 : int is_ambiguous = match_cnt != 1;
308 :
309 : /* We know that gossip, gossip prune, and repair messages are
310 : ambiguous, so allow mismatches here. */
311 0 : int is_gossip_repair =
312 0 : 0==( payload_mask &
313 0 : (~( FD_KEYGUARD_PAYLOAD_GOSSIP |
314 0 : FD_KEYGUARD_PAYLOAD_REPAIR ) ) );
315 : /* Also allow ambiguities between shred and gossip ping messages
316 : until shred sign type is fixed... */
317 0 : int is_shred_ping =
318 0 : 0==( payload_mask &
319 0 : (~( FD_KEYGUARD_PAYLOAD_SHRED |
320 0 : FD_KEYGUARD_PAYLOAD_PING ) ) );
321 :
322 0 : if( FD_UNLIKELY( is_ambiguous && !is_gossip_repair && !is_shred_ping ) ) {
323 0 : FD_LOG_WARNING(( "ambiguous payload type (role=%#x mask=%#lx)", (uint)role, payload_mask ));
324 0 : }
325 :
326 : /* Authorize each role */
327 :
328 0 : switch( role ) {
329 :
330 0 : case FD_KEYGUARD_ROLE_TXSEND: {
331 0 : int txn_ok = (!!( payload_mask & FD_KEYGUARD_PAYLOAD_TXN )) &&
332 0 : fd_keyguard_authorize_vote_txn( authority, data, sz, sign_type );
333 0 : int tls_ok = (!!( payload_mask & FD_KEYGUARD_PAYLOAD_TLS_CV )) &&
334 0 : fd_keyguard_authorize_tls_cv( authority, data, sz, sign_type );
335 0 : if( FD_UNLIKELY( !txn_ok && !tls_ok ) ) {
336 0 : FD_LOG_WARNING(( "unauthorized payload type for send (mask=%#lx)", payload_mask ));
337 0 : return 0;
338 0 : }
339 0 : return 1;
340 0 : }
341 :
342 0 : case FD_KEYGUARD_ROLE_GOSSIP: {
343 0 : int ping_ok = (!!( payload_mask & FD_KEYGUARD_PAYLOAD_PING )) &&
344 0 : fd_keyguard_authorize_ping( authority, data, sz, sign_type );
345 0 : int pong_ok = (!!( payload_mask & FD_KEYGUARD_PAYLOAD_PONG )) &&
346 0 : fd_keyguard_authorize_pong( authority, data, sz, sign_type );
347 0 : int prune_ok = (!!( payload_mask & FD_KEYGUARD_PAYLOAD_PRUNE )) &&
348 0 : fd_keyguard_authorize_gossip_prune( authority, data, sz, sign_type );
349 0 : int gossip_ok = (!!( payload_mask & FD_KEYGUARD_PAYLOAD_GOSSIP )) &&
350 0 : fd_keyguard_authorize_gossip( authority, data, sz, sign_type );
351 0 : if( FD_UNLIKELY( !ping_ok && !pong_ok && !prune_ok && !gossip_ok ) ) {
352 0 : FD_LOG_WARNING(( "unauthorized payload type for gossip (mask=%#lx)", payload_mask ));
353 0 : return 0;
354 0 : }
355 0 : return 1;
356 0 : }
357 :
358 0 : case FD_KEYGUARD_ROLE_REPAIR: {
359 0 : int ping_ok = (!!( payload_mask & FD_KEYGUARD_PAYLOAD_PING )) &&
360 0 : fd_keyguard_authorize_ping( authority, data, sz, sign_type );
361 0 : int pong_ok = (!!( payload_mask & FD_KEYGUARD_PAYLOAD_PONG )) &&
362 0 : fd_keyguard_authorize_pong( authority, data, sz, sign_type );
363 0 : int repair_ok = (!!( payload_mask & FD_KEYGUARD_PAYLOAD_REPAIR )) &&
364 0 : fd_keyguard_authorize_repair( authority, data, sz, sign_type );
365 0 : if( FD_UNLIKELY( !ping_ok && !pong_ok && !repair_ok ) ) {
366 0 : FD_LOG_WARNING(( "unauthorized payload type for repair (mask=%#lx)", payload_mask ));
367 0 : return 0;
368 0 : }
369 0 : return 1;
370 0 : }
371 :
372 0 : case FD_KEYGUARD_ROLE_LEADER:
373 0 : if( FD_UNLIKELY( payload_mask != FD_KEYGUARD_PAYLOAD_SHRED ) ) {
374 0 : FD_LOG_WARNING(( "unauthorized payload type for leader (mask=%#lx)", payload_mask ));
375 0 : return 0;
376 0 : }
377 : /* no further restrictions on shred */
378 0 : return 1;
379 :
380 0 : case FD_KEYGUARD_ROLE_BUNDLE:
381 0 : if( FD_UNLIKELY( payload_mask != FD_KEYGUARD_PAYLOAD_BUNDLE ) ) {
382 0 : FD_LOG_WARNING(( "unauthorized payload type for bundle (mask=%#lx)", payload_mask ));
383 0 : return 0;
384 0 : }
385 : /* no further restrictions on bundle */
386 0 : return 1;
387 :
388 0 : case FD_KEYGUARD_ROLE_EVENT:
389 0 : if( FD_UNLIKELY( payload_mask != FD_KEYGUARD_PAYLOAD_EVENT ) ) {
390 0 : FD_LOG_WARNING(( "unauthorized payload type for event (mask=%#lx)", payload_mask ));
391 0 : return 0;
392 0 : }
393 : /* no further restrictions on event */
394 0 : return 1;
395 :
396 0 : case FD_KEYGUARD_ROLE_BUNDLE_CRANK:
397 0 : if( FD_UNLIKELY( payload_mask != FD_KEYGUARD_PAYLOAD_TXN ) ) {
398 0 : FD_LOG_WARNING(( "unauthorized payload type for event (mask=%#lx)", payload_mask ));
399 0 : return 0;
400 0 : }
401 0 : return fd_keyguard_authorize_bundle_crank_txn( authority, data, sz, sign_type );
402 :
403 0 : default:
404 0 : FD_LOG_WARNING(( "unsupported role=%#x", (uint)role ));
405 0 : return 0;
406 0 : }
407 0 : }
|