1 #ifdef HAVE_CONFIG_H
2 #include "config.h"
3 #elif defined(_MSC_VER)
4 #include "config-msvc.h"
5 #endif
6
7 #include "syshead.h"
8
9 #include "base64.h"
10 #include "buffer.h"
11 #include "crypto.h"
12 #include "openvpn.h"
13 #include "ssl_common.h"
14 #include "auth_token.h"
15 #include "push.h"
16 #include "integer.h"
17 #include "ssl.h"
18 #include "ssl_verify.h"
19 #include <inttypes.h>
20
21 const char *auth_token_pem_name = "OpenVPN auth-token server key";
22
23 #define AUTH_TOKEN_SESSION_ID_LEN 12
24 #if AUTH_TOKEN_SESSION_ID_LEN % 3
25 #error AUTH_TOKEN_SESSION_ID_LEN needs to be multiple a 3
26 #endif
27
28 /* Size of the data of the token (not b64 encoded and without prefix) */
29 #define TOKEN_DATA_LEN (2 * sizeof(int64_t) + AUTH_TOKEN_SESSION_ID_LEN + 32)
30
31 static struct key_type
auth_token_kt(void)32 auth_token_kt(void)
33 {
34 struct key_type kt = { 0 };
35 /* We do not encrypt our session tokens */
36 kt.cipher = NULL;
37 kt.digest = md_kt_get("SHA256");
38
39 if (!kt.digest)
40 {
41 msg(M_WARN, "ERROR: --tls-crypt requires HMAC-SHA-256 support.");
42 return (struct key_type) { 0 };
43 }
44
45 kt.hmac_length = md_kt_size(kt.digest);
46
47 return kt;
48 }
49
50
51 void
add_session_token_env(struct tls_session * session,struct tls_multi * multi,const struct user_pass * up)52 add_session_token_env(struct tls_session *session, struct tls_multi *multi,
53 const struct user_pass *up)
54 {
55 if (!multi->opt.auth_token_generate)
56 {
57 return;
58 }
59
60
61 const char *state;
62
63 if (!is_auth_token(up->password))
64 {
65 state = "Initial";
66 }
67 else if (multi->auth_token_state_flags & AUTH_TOKEN_HMAC_OK)
68 {
69 switch (multi->auth_token_state_flags & (AUTH_TOKEN_VALID_EMPTYUSER|AUTH_TOKEN_EXPIRED))
70 {
71 case 0:
72 state = "Authenticated";
73 break;
74
75 case AUTH_TOKEN_EXPIRED:
76 state = "Expired";
77 break;
78
79 case AUTH_TOKEN_VALID_EMPTYUSER:
80 state = "AuthenticatedEmptyUser";
81 break;
82
83 case AUTH_TOKEN_VALID_EMPTYUSER | AUTH_TOKEN_EXPIRED:
84 state = "ExpiredEmptyUser";
85 break;
86
87 default:
88 /* Silence compiler warning, all four possible combinations are covered */
89 ASSERT(0);
90 }
91 }
92 else
93 {
94 state = "Invalid";
95 }
96
97 setenv_str(session->opt->es, "session_state", state);
98
99 /* We had a valid session id before */
100 const char *session_id_source;
101 if (multi->auth_token_state_flags & AUTH_TOKEN_HMAC_OK
102 && !(multi->auth_token_state_flags & AUTH_TOKEN_EXPIRED))
103 {
104 session_id_source = up->password;
105 }
106 else
107 {
108 /*
109 * No session before, generate a new session token for the new session
110 */
111 if (!multi->auth_token)
112 {
113 generate_auth_token(up, multi);
114 }
115 session_id_source = multi->auth_token;
116 }
117 /*
118 * In the auth-token the auth token is already base64 encoded
119 * and being a multiple of 4 ensure that it a multiple of bytes
120 * in the encoding
121 */
122
123 char session_id[AUTH_TOKEN_SESSION_ID_LEN*2] = {0};
124 memcpy(session_id, session_id_source + strlen(SESSION_ID_PREFIX),
125 AUTH_TOKEN_SESSION_ID_LEN*8/6);
126
127 setenv_str(session->opt->es, "session_id", session_id);
128 }
129
130 void
auth_token_write_server_key_file(const char * filename)131 auth_token_write_server_key_file(const char *filename)
132 {
133 write_pem_key_file(filename, auth_token_pem_name);
134 }
135
136 void
auth_token_init_secret(struct key_ctx * key_ctx,const char * key_file,bool key_inline)137 auth_token_init_secret(struct key_ctx *key_ctx, const char *key_file,
138 bool key_inline)
139 {
140 struct key_type kt = auth_token_kt();
141
142 struct buffer server_secret_key = alloc_buf(2048);
143
144 bool key_loaded = false;
145 if (key_file)
146 {
147 key_loaded = read_pem_key_file(&server_secret_key,
148 auth_token_pem_name,
149 key_file, key_inline);
150 }
151 else
152 {
153 key_loaded = generate_ephemeral_key(&server_secret_key,
154 auth_token_pem_name);
155 }
156
157 if (!key_loaded)
158 {
159 msg(M_FATAL, "ERROR: Cannot load auth-token secret");
160 }
161
162 struct key key;
163
164 if (!buf_read(&server_secret_key, &key, sizeof(key)))
165 {
166 msg(M_FATAL, "ERROR: not enough data in auth-token secret");
167 }
168 init_key_ctx(key_ctx, &key, &kt, false, "auth-token secret");
169
170 free_buf(&server_secret_key);
171 }
172
173 void
generate_auth_token(const struct user_pass * up,struct tls_multi * multi)174 generate_auth_token(const struct user_pass *up, struct tls_multi *multi)
175 {
176 struct gc_arena gc = gc_new();
177
178 int64_t timestamp = htonll((uint64_t)now);
179 int64_t initial_timestamp = timestamp;
180
181 hmac_ctx_t *ctx = multi->opt.auth_token_key.hmac;
182 ASSERT(hmac_ctx_size(ctx) == 256/8);
183
184 uint8_t sessid[AUTH_TOKEN_SESSION_ID_LEN];
185
186 if (multi->auth_token)
187 {
188 /* Just enough space to fit 8 bytes+ 1 extra to decode a non padded
189 * base64 string (multiple of 3 bytes). 9 bytes => 12 bytes base64
190 * bytes
191 */
192 char old_tstamp_decode[9];
193
194 /*
195 * reuse the same session id and timestamp and null terminate it at
196 * for base64 decode it only decodes the session id part of it
197 */
198 char *old_sessid = multi->auth_token + strlen(SESSION_ID_PREFIX);
199 char *old_tsamp_initial = old_sessid + AUTH_TOKEN_SESSION_ID_LEN*8/6;
200
201 old_tsamp_initial[12] = '\0';
202 ASSERT(openvpn_base64_decode(old_tsamp_initial, old_tstamp_decode, 9) == 9);
203
204 /*
205 * Avoid old gcc (4.8.x) complaining about strict aliasing
206 * by using a temporary variable instead of doing it in one
207 * line
208 */
209 uint64_t *tstamp_ptr = (uint64_t *) old_tstamp_decode;
210 initial_timestamp = *tstamp_ptr;
211
212 old_tsamp_initial[0] = '\0';
213 ASSERT(openvpn_base64_decode(old_sessid, sessid, AUTH_TOKEN_SESSION_ID_LEN)==AUTH_TOKEN_SESSION_ID_LEN);
214
215
216 /* free the auth-token, we will replace it with a new one */
217 free(multi->auth_token);
218 }
219 else if (!rand_bytes(sessid, AUTH_TOKEN_SESSION_ID_LEN))
220 {
221 msg( M_FATAL, "Failed to get enough randomness for "
222 "authentication token");
223 }
224
225 /* Calculate the HMAC */
226 /* We enforce up->username to be \0 terminated in ssl.c.. Allowing username
227 * with \0 in them is asking for troubles in so many ways anyway that we
228 * ignore that corner case here
229 */
230 uint8_t hmac_output[256/8];
231
232 hmac_ctx_reset(ctx);
233
234 /*
235 * If the token was only valid for the empty user, also generate
236 * a new token with the empty username since we do not want to loose
237 * the information that the username cannot be trusted
238 */
239 if (multi->auth_token_state_flags & AUTH_TOKEN_VALID_EMPTYUSER)
240 {
241 hmac_ctx_update(ctx, (const uint8_t *) "", 0);
242 }
243 else
244 {
245 hmac_ctx_update(ctx, (uint8_t *) up->username, (int) strlen(up->username));
246 }
247 hmac_ctx_update(ctx, sessid, AUTH_TOKEN_SESSION_ID_LEN);
248 hmac_ctx_update(ctx, (uint8_t *) &initial_timestamp, sizeof(initial_timestamp));
249 hmac_ctx_update(ctx, (uint8_t *) ×tamp, sizeof(timestamp));
250 hmac_ctx_final(ctx, hmac_output);
251
252 /* Construct the unencoded session token */
253 struct buffer token = alloc_buf_gc(
254 2*sizeof(uint64_t) + AUTH_TOKEN_SESSION_ID_LEN + 256/8, &gc);
255
256 ASSERT(buf_write(&token, sessid, sizeof(sessid)));
257 ASSERT(buf_write(&token, &initial_timestamp, sizeof(initial_timestamp)));
258 ASSERT(buf_write(&token, ×tamp, sizeof(timestamp)));
259 ASSERT(buf_write(&token, hmac_output, sizeof(hmac_output)));
260
261 char *b64output;
262 openvpn_base64_encode(BPTR(&token), BLEN(&token), &b64output);
263
264 struct buffer session_token = alloc_buf_gc(
265 strlen(SESSION_ID_PREFIX) + strlen(b64output) + 1, &gc);
266
267 ASSERT(buf_write(&session_token, SESSION_ID_PREFIX, strlen(SESSION_ID_PREFIX)));
268 ASSERT(buf_write(&session_token, b64output, (int)strlen(b64output)));
269 ASSERT(buf_write_u8(&session_token, 0));
270
271 free(b64output);
272
273 multi->auth_token = strdup((char *)BPTR(&session_token));
274
275 dmsg(D_SHOW_KEYS, "Generated token for client: %s (%s)",
276 multi->auth_token, up->username);
277
278 gc_free(&gc);
279 }
280
281
282 static bool
check_hmac_token(hmac_ctx_t * ctx,const uint8_t * b64decoded,const char * username)283 check_hmac_token(hmac_ctx_t *ctx, const uint8_t *b64decoded, const char *username)
284 {
285 ASSERT(hmac_ctx_size(ctx) == 256/8);
286
287 uint8_t hmac_output[256/8];
288
289 hmac_ctx_reset(ctx);
290 hmac_ctx_update(ctx, (uint8_t *) username, (int)strlen(username));
291 hmac_ctx_update(ctx, b64decoded, TOKEN_DATA_LEN - 256/8);
292 hmac_ctx_final(ctx, hmac_output);
293
294 const uint8_t *hmac = b64decoded + TOKEN_DATA_LEN - 256/8;
295 return memcmp_constant_time(&hmac_output, hmac, 32) == 0;
296 }
297
298 unsigned int
verify_auth_token(struct user_pass * up,struct tls_multi * multi,struct tls_session * session)299 verify_auth_token(struct user_pass *up, struct tls_multi *multi,
300 struct tls_session *session)
301 {
302 /*
303 * Base64 is <= input and input is < USER_PASS_LEN, so using USER_PASS_LEN
304 * is safe here but a bit overkill
305 */
306 uint8_t b64decoded[USER_PASS_LEN];
307 int decoded_len = openvpn_base64_decode(up->password + strlen(SESSION_ID_PREFIX),
308 b64decoded, USER_PASS_LEN);
309
310 /*
311 * Ensure that the decoded data is the size of the
312 * timestamp + hmac + session id
313 */
314 if (decoded_len != TOKEN_DATA_LEN)
315 {
316 msg(M_WARN, "ERROR: --auth-token wrong size (%d!=%d)",
317 decoded_len, (int) TOKEN_DATA_LEN);
318 return 0;
319 }
320
321 unsigned int ret = 0;
322
323 const uint8_t *sessid = b64decoded;
324 const uint8_t *tstamp_initial = sessid + AUTH_TOKEN_SESSION_ID_LEN;
325 const uint8_t *tstamp = tstamp_initial + sizeof(int64_t);
326
327 uint64_t timestamp = ntohll(*((uint64_t *) (tstamp)));
328 uint64_t timestamp_initial = ntohll(*((uint64_t *) (tstamp_initial)));
329
330 hmac_ctx_t *ctx = multi->opt.auth_token_key.hmac;
331 if (check_hmac_token(ctx, b64decoded, up->username))
332 {
333 ret |= AUTH_TOKEN_HMAC_OK;
334 }
335 else if (check_hmac_token(ctx, b64decoded, ""))
336 {
337 ret |= AUTH_TOKEN_HMAC_OK;
338 ret |= AUTH_TOKEN_VALID_EMPTYUSER;
339 /* overwrite the username of the client with the empty one */
340 strcpy(up->username, "");
341 }
342 else
343 {
344 msg(M_WARN, "--auth-token-gen: HMAC on token from client failed (%s)",
345 up->username);
346 return 0;
347 }
348
349 /* Accept session tokens that not expired are in the acceptable range
350 * for renogiations */
351 bool in_renog_time = now >= timestamp
352 && now < timestamp + 2 * session->opt->renegotiate_seconds;
353
354 /* We could still have a client that does not update
355 * its auth-token, so also allow the initial auth-token */
356 bool initialtoken = multi->auth_token_initial
357 && memcmp_constant_time(up->password, multi->auth_token_initial,
358 strlen(multi->auth_token_initial)) == 0;
359
360 if (!in_renog_time && !initialtoken)
361 {
362 ret |= AUTH_TOKEN_EXPIRED;
363 }
364
365 /* Sanity check the initial timestamp */
366 if (timestamp < timestamp_initial)
367 {
368 msg(M_WARN, "Initial timestamp (%" PRIu64 " in token from client earlier than "
369 "current timestamp %" PRIu64 ". Broken/unsynchronised clock?",
370 timestamp_initial, timestamp);
371 ret |= AUTH_TOKEN_EXPIRED;
372 }
373
374 if (multi->opt.auth_token_lifetime
375 && now > timestamp_initial + multi->opt.auth_token_lifetime)
376 {
377 ret |= AUTH_TOKEN_EXPIRED;
378 }
379
380 if (ret & AUTH_TOKEN_EXPIRED)
381 {
382 /* Tell client that the session token is expired */
383 auth_set_client_reason(multi, "SESSION: token expired");
384 msg(M_INFO, "--auth-token-gen: auth-token from client expired");
385 }
386 return ret;
387 }
388
389 void
wipe_auth_token(struct tls_multi * multi)390 wipe_auth_token(struct tls_multi *multi)
391 {
392 if (multi)
393 {
394 if (multi->auth_token)
395 {
396 secure_memzero(multi->auth_token, strlen(multi->auth_token));
397 free(multi->auth_token);
398 }
399 if (multi->auth_token_initial)
400 {
401 secure_memzero(multi->auth_token_initial,
402 strlen(multi->auth_token_initial));
403 free(multi->auth_token_initial);
404 }
405 multi->auth_token = NULL;
406 multi->auth_token_initial = NULL;
407 }
408 }
409