Line data Source code
1 : #include "fd_bundle_auth.h"
2 : #include "../../ballet/base58/fd_base58.h"
3 : #include "../../ballet/pb/fd_pb_encode.h"
4 : #include "../../ballet/pb/fd_pb_tokenize.h"
5 : #include "../../disco/keyguard/fd_keyguard.h"
6 : #include "../../disco/keyguard/fd_keyguard_client.h"
7 :
8 0 : #define FD_BUNDLE_AUTH_REQUEST_TIMEOUT ((long)5e9) /* 5 seconds */
9 :
10 : /* fd_bundle_auther_t generates auth tokens. */
11 :
12 : /* FIXME consider rewriting this with coroutines or light threads */
13 :
14 : fd_bundle_auther_t *
15 0 : fd_bundle_auther_init( fd_bundle_auther_t * auther ) {
16 0 : *auther = (fd_bundle_auther_t) {
17 0 : .state = FD_BUNDLE_AUTH_STATE_REQ_CHALLENGE,
18 0 : .needs_poll = 1
19 0 : };
20 0 : return auther;
21 0 : }
22 :
23 : void
24 0 : fd_bundle_auther_handle_request_fail( fd_bundle_auther_t * auther ) {
25 0 : switch( auther->state ) {
26 0 : case FD_BUNDLE_AUTH_STATE_WAIT_CHALLENGE:
27 0 : FD_LOG_DEBUG(( "Request for auth challenge failed, retrying" ));
28 0 : auther->state = FD_BUNDLE_AUTH_STATE_REQ_CHALLENGE;
29 0 : auther->needs_poll = 1;
30 0 : break;
31 0 : case FD_BUNDLE_AUTH_STATE_WAIT_TOKENS:
32 0 : FD_LOG_DEBUG(( "Request for auth tokens failed" ));
33 0 : auther->state = FD_BUNDLE_AUTH_STATE_REQ_CHALLENGE;
34 0 : auther->needs_poll = 1;
35 0 : break;
36 0 : }
37 0 : }
38 :
39 : static void
40 : fd_bundle_auther_req_challenge( fd_bundle_auther_t * auther,
41 0 : fd_grpc_client_t * client ) {
42 0 : if( FD_UNLIKELY( fd_grpc_client_request_is_blocked( client ) ) ) return;
43 :
44 0 : uchar req[ 64 ];
45 0 : fd_pb_encoder_t enc[1]; fd_pb_encoder_init( enc, req, sizeof(req) );
46 0 : FD_TEST( fd_pb_push_uint32( enc, 1U, 2U ) ); /* role = VALIDATOR */
47 0 : FD_TEST( fd_pb_push_bytes( enc, 2U, auther->pubkey, 32UL ) ); /* pubkey */
48 :
49 0 : static char const path[] = "/auth.AuthService/GenerateAuthChallenge";
50 0 : fd_grpc_h2_stream_t * request = fd_grpc_client_request_start(
51 0 : client,
52 0 : path, sizeof(path)-1,
53 0 : FD_BUNDLE_CLIENT_REQ_Auth_GenerateAuthChallenge,
54 0 : req, fd_pb_encoder_out_sz( enc ),
55 0 : NULL, 0,
56 0 : 0 /* is_streaming */
57 0 : );
58 0 : if( FD_UNLIKELY( !request ) ) return;
59 :
60 0 : auther->state = FD_BUNDLE_AUTH_STATE_WAIT_CHALLENGE;
61 0 : auther->needs_poll = 0;
62 0 : fd_grpc_client_deadline_set(
63 0 : request,
64 0 : FD_GRPC_DEADLINE_RX_END,
65 0 : fd_log_wallclock()+FD_BUNDLE_AUTH_REQUEST_TIMEOUT );
66 :
67 0 : char key_cstr[ FD_BASE58_ENCODED_32_SZ ];
68 0 : fd_base58_encode_32( auther->pubkey, NULL, key_cstr );
69 0 : FD_LOG_INFO(( "Requesting bundle auth challenge (identity=%s)", key_cstr ));
70 0 : }
71 :
72 : int
73 : fd_bundle_auther_handle_challenge_resp(
74 : fd_bundle_auther_t * auther,
75 : void const * data,
76 : ulong data_sz
77 0 : ) {
78 0 : auther->needs_poll = 1;
79 :
80 0 : fd_pb_inbuf_t in[1]; fd_pb_inbuf_init( in, data, data_sz );
81 0 : int found_challenge = 0;
82 0 : while( fd_pb_inbuf_sz( in ) ) {
83 0 : fd_pb_tlv_t tlv[1];
84 0 : if( FD_UNLIKELY( !fd_pb_tlv_read( in, tlv ) ) ) {
85 0 : FD_LOG_WARNING(( "Protobuf decode of (auth.GenerateAuthChallengeResponse) failed" ));
86 0 : goto fail;
87 0 : }
88 0 : if( tlv->field_id==1U ) { /* challenge */
89 0 : uchar const * chal = fd_pb_tlv_bytes( in, tlv );
90 0 : if( FD_UNLIKELY( !chal ) ) {
91 0 : FD_LOG_WARNING(( "Protobuf decode of (auth.GenerateAuthChallengeResponse) failed" ));
92 0 : goto fail;
93 0 : }
94 0 : if( FD_UNLIKELY( tlv->len!=9UL ) ) {
95 0 : FD_LOG_WARNING(( "Unexpected auth.GenerateAuthChallengeResponse challenge size: %lu bytes", tlv->len ));
96 0 : goto fail;
97 0 : }
98 0 : memcpy( auther->challenge, chal, 9UL );
99 0 : found_challenge = 1;
100 0 : }
101 0 : if( FD_UNLIKELY( !fd_pb_tlv_skip( in, tlv ) ) ) {
102 0 : FD_LOG_WARNING(( "Protobuf decode of (auth.GenerateAuthChallengeResponse) failed" ));
103 0 : goto fail;
104 0 : }
105 0 : }
106 0 : if( FD_UNLIKELY( !found_challenge ) ) {
107 0 : FD_LOG_WARNING(( "auth.GenerateAuthChallengeResponse: missing challenge" ));
108 0 : goto fail;
109 0 : }
110 :
111 0 : auther->state = FD_BUNDLE_AUTH_STATE_REQ_TOKENS;
112 0 : FD_LOG_DEBUG(( "Got auth challenge" ));
113 0 : return 1;
114 :
115 0 : fail:
116 0 : auther->state = FD_BUNDLE_AUTH_STATE_REQ_CHALLENGE;
117 0 : return 0;
118 0 : }
119 :
120 : static void
121 : fd_bundle_auther_req_tokens( fd_bundle_auther_t * auther,
122 : fd_grpc_client_t * client,
123 0 : fd_keyguard_client_t * keyguard ) {
124 0 : if( FD_UNLIKELY( fd_grpc_client_request_is_blocked( client ) ) ) return;
125 :
126 : /* Format challenge string as '{base58(pubkey)}-{challenge}' */
127 0 : char chal[ FD_BASE58_ENCODED_32_SZ+1UL+9UL ];
128 0 : ulong enc_len;
129 0 : char * p = chal;
130 0 : fd_base58_encode_32( auther->pubkey, &enc_len, p );
131 0 : p += enc_len;
132 0 : p = fd_cstr_append_char( p, '-' );
133 0 : p = fd_cstr_append_text( p, auther->challenge, 9UL );
134 0 : ulong chal_sz = (ulong)p - (ulong)chal;
135 :
136 0 : uchar signed_chal[ 64 ];
137 0 : fd_keyguard_client_sign( keyguard, signed_chal, (uchar const *)auther->challenge, 9UL, FD_KEYGUARD_SIGN_TYPE_PUBKEY_CONCAT_ED25519 );
138 :
139 0 : uchar req[ 256 ];
140 0 : fd_pb_encoder_t enc[1]; fd_pb_encoder_init( enc, req, sizeof(req) );
141 0 : FD_TEST( fd_pb_push_bytes( enc, 1U, chal, chal_sz ) ); /* challenge */
142 0 : FD_TEST( fd_pb_push_bytes( enc, 2U, auther->pubkey, 32UL ) ); /* client_pubkey */
143 0 : FD_TEST( fd_pb_push_bytes( enc, 3U, signed_chal, 64UL ) ); /* signed_challenge */
144 :
145 0 : static char const path[] = "/auth.AuthService/GenerateAuthTokens";
146 0 : fd_grpc_h2_stream_t * request = fd_grpc_client_request_start(
147 0 : client,
148 0 : path, sizeof(path)-1,
149 0 : FD_BUNDLE_CLIENT_REQ_Auth_GenerateAuthTokens,
150 0 : req, fd_pb_encoder_out_sz( enc ),
151 0 : NULL, 0,
152 0 : 0 /* is_streaming */
153 0 : );
154 0 : if( FD_UNLIKELY( !request ) ) return;
155 :
156 0 : auther->state = FD_BUNDLE_AUTH_STATE_WAIT_TOKENS;
157 0 : auther->needs_poll = 0;
158 0 : fd_grpc_client_deadline_set(
159 0 : request,
160 0 : FD_GRPC_DEADLINE_RX_END,
161 0 : fd_log_wallclock()+FD_BUNDLE_AUTH_REQUEST_TIMEOUT );
162 :
163 0 : FD_LOG_DEBUG(( "Requesting bundle auth tokens" ));
164 0 : }
165 :
166 : int
167 : fd_bundle_auther_handle_tokens_resp(
168 : fd_bundle_auther_t * auther,
169 : void const * data,
170 : ulong data_sz
171 0 : ) {
172 : /* GenerateAuthTokensResponse.access_token (Token) .value (bytes) */
173 0 : uchar const * tok = NULL;
174 0 : ulong tok_sz = 0UL;
175 0 : fd_pb_inbuf_t in[1]; fd_pb_inbuf_init( in, data, data_sz );
176 0 : while( fd_pb_inbuf_sz( in ) ) {
177 0 : fd_pb_tlv_t tlv[1];
178 0 : if( FD_UNLIKELY( !fd_pb_tlv_read( in, tlv ) ) ) goto decode_fail;
179 0 : if( tlv->field_id==1U ) { /* access_token */
180 0 : fd_pb_inbuf_t sub[1];
181 0 : if( FD_UNLIKELY( !fd_pb_tlv_submsg( in, tlv, sub ) ) ) goto decode_fail;
182 0 : while( fd_pb_inbuf_sz( sub ) ) {
183 0 : fd_pb_tlv_t tlv2[1];
184 0 : if( FD_UNLIKELY( !fd_pb_tlv_read( sub, tlv2 ) ) ) goto decode_fail;
185 0 : if( tlv2->field_id==1U ) { /* value */
186 0 : tok = fd_pb_tlv_bytes( sub, tlv2 );
187 0 : if( FD_UNLIKELY( !tok ) ) goto decode_fail;
188 0 : tok_sz = tlv2->len;
189 0 : }
190 0 : if( FD_UNLIKELY( !fd_pb_tlv_skip( sub, tlv2 ) ) ) goto decode_fail;
191 0 : }
192 0 : }
193 0 : if( FD_UNLIKELY( !fd_pb_tlv_skip( in, tlv ) ) ) goto decode_fail;
194 0 : }
195 0 : if( FD_UNLIKELY( !tok || !tok_sz ) ) {
196 0 : FD_LOG_WARNING(( "auth.GenerateAuthTokensResponse: missing access_token" ));
197 0 : goto fail;
198 0 : }
199 0 : if( FD_UNLIKELY( tok_sz > sizeof(auther->access_token) ) ) {
200 0 : FD_LOG_WARNING(( "auth.GenerateAuthTokensResponse: oversz access_token: %lu bytes", tok_sz ));
201 0 : goto fail;
202 0 : }
203 :
204 0 : fd_memcpy( auther->access_token, tok, tok_sz );
205 0 : auther->access_token_sz = (ushort)tok_sz;
206 0 : auther->state = FD_BUNDLE_AUTH_STATE_DONE_WAIT;
207 0 : FD_LOG_DEBUG(( "Got auth tokens" ));
208 0 : return 1;
209 :
210 0 : decode_fail:
211 0 : FD_LOG_WARNING(( "Protobuf decode of (auth.GenerateAuthTokensResponse) failed" ));
212 0 : fail:
213 0 : auther->state = FD_BUNDLE_AUTH_STATE_REQ_CHALLENGE;
214 0 : auther->needs_poll = 1;
215 0 : return 0;
216 0 : }
217 :
218 : void
219 : fd_bundle_auther_poll( fd_bundle_auther_t * auther,
220 : fd_grpc_client_t * client,
221 0 : fd_keyguard_client_t * keyguard ) {
222 0 : switch( auther->state ) {
223 0 : case FD_BUNDLE_AUTH_STATE_REQ_CHALLENGE:
224 0 : fd_bundle_auther_req_challenge( auther, client );
225 0 : break;
226 0 : case FD_BUNDLE_AUTH_STATE_REQ_TOKENS:
227 0 : fd_bundle_auther_req_tokens( auther, client, keyguard );
228 0 : break;
229 0 : default:
230 0 : break;
231 0 : }
232 0 : }
233 :
234 : void
235 0 : fd_bundle_auther_reset( fd_bundle_auther_t * auther ) {
236 0 : auther->state = FD_BUNDLE_AUTH_STATE_REQ_CHALLENGE;
237 0 : auther->needs_poll = 1;
238 0 : }
|