forked from ioerror/tlsdate
/
tlsdate-helper.c
356 lines (295 loc) · 10.9 KB
/
tlsdate-helper.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
/* Copyright (c) 2012, Jacob Appelbaum.
* Copyright (c) 2012, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/*
This file contains the license for tlsdate,
a free software project to set your system clock securely.
It also lists the licenses for other components used by tlsdate.
For more information about tlsdate, see https://github.com/ioerror/tlsdate
If you got this file as a part of a larger bundle,
there may be other license terms that you should be aware of.
===============================================================================
tlsdate is distributed under this license:
Copyright (c) 2011-2012, Jacob Appelbaum <jacob@appelbaum.net>
Copyright (c) 2011-2012, The Tor Project, Inc.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the names of the copyright owners nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
===============================================================================
If you got tlsdate as a static binary with OpenSSL included, then you should
know:
"This product includes software developed by the OpenSSL Project for use in
the OpenSSL Toolkit (http://www.openssl.org/)"
===============================================================================
*/
/**
* \file tlsdate-helper.c
* \brief Helper program that does the actual work of setting the system clock.
**/
/*
* tlsdate is a tool for setting the system clock by hand or by communication
* with the network. It does not set the RTC. It is designed to be as secure as
* TLS (RFC 2246) but of course the security of TLS is often reduced to
* whichever CA racket you believe is trustworthy. By default, tlsdate trusts
* your local CA root store - so any of these companies could assist in a MITM
* attack against you and you'd be screwed.
* This tool is designed to be run by hand or as a system daemon. It must be
* run as root or otherwise have the proper caps; it will not be able to set
* the system time without running as root or another privileged user.
*/
#include "tlsdate-config.h"
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/mman.h>
#include <time.h>
#include <pwd.h>
#include <arpa/inet.h>
#include <openssl/bio.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <openssl/evp.h>
/** Name of user that we feel safe to run SSL handshake with. */
#define UNPRIV_USER "nobody"
// We should never accept a time before we were compiled
// We measure in seconds since the epoch - eg: echo `date '+%s'`
// We set this manually to ensure others can reproduce a build;
// automation of this will make every build different!
#define RECENT_COMPILE_DATE (uint32_t) 1328610583
#define MAX_REASONABLE_TIME (uint32_t) 1999991337
static int verbose;
static int ca_racket;
static const char *host;
static const char *port;
static const char *protocol;
/** helper function to print message and die */
static void
die(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
exit(1);
}
/** helper function for 'verbose' output */
static void
verb (const char *fmt, ...)
{
va_list ap;
if (! verbose) return;
va_start(ap, fmt);
// FIXME: stdout or stderr for verbose messages?
vfprintf(stderr, fmt, ap);
va_end(ap);
}
/**
* Run SSL handshake and store the resulting time value in the
* 'time_map'.
*
* @param time_map where to store the current time
*/
static void
run_ssl (uint32_t *time_map)
{
BIO *s_bio;
BIO *c_bio;
SSL_CTX *ctx;
SSL *ssl;
SSL_load_error_strings();
SSL_library_init();
ctx = NULL;
if (0 == strcmp("sslv23", protocol))
{
verb ("V: using SSLv23_client_method()\n");
ctx = SSL_CTX_new(SSLv23_client_method());
} else if (0 == strcmp("sslv3", protocol))
{
verb ("V: using SSLv3_client_method()\n");
ctx = SSL_CTX_new(SSLv3_client_method());
} else if (0 == strcmp("tlsv1", protocol))
{
verb ("V: using TLSv1_client_method()\n");
ctx = SSL_CTX_new(TLSv1_client_method());
} else
die("Unsupported protocol `%s'\n", protocol);
if (ctx == NULL)
die("OpenSSL failed to support protocol `%s'\n", protocol);
if (ca_racket)
{
// For google specifically:
// SSL_CTX_load_verify_locations(ctx, "/etc/ssl/certs/Equifax_Secure_CA.pem", NULL);
if (1 != SSL_CTX_load_verify_locations(ctx, NULL, "/etc/ssl/certs/"))
fprintf(stderr, "SSL_CTX_load_verify_locations failed\n");
}
if (NULL == (s_bio = BIO_new_ssl_connect(ctx)))
die ("SSL BIO setup failed\n");
BIO_get_ssl(s_bio, &ssl);
if (NULL == ssl)
die ("SSL setup failed\n");
SSL_set_mode(ssl, SSL_MODE_AUTO_RETRY);
if ( (1 != BIO_set_conn_hostname(s_bio, host)) ||
(1 != BIO_set_conn_port(s_bio, port)) )
die ("Failed to initialize connection to `%s:%s'\n", host, port);
if (NULL == (c_bio = BIO_new_fp(stdout, BIO_NOCLOSE)))
die ("FIXME: error message");
// This should run in seccomp
// eg: prctl(PR_SET_SECCOMP, 1);
if (1 != BIO_do_connect(s_bio)) // XXX TODO: BIO_should_retry() later?
die ("SSL connection failed\n");
if (1 != BIO_do_handshake(s_bio))
die ("SSL handshake failed\n");
// Verify the peer certificate against the CA certs on the local system
if (ca_racket) {
X509 *x509;
long ssl_verify_result;
if (NULL == (x509 = SSL_get_peer_certificate(ssl)) )
die ("Getting SSL certificate failed\n");
// In theory, we verify that the cert is valid
ssl_verify_result = SSL_get_verify_result(ssl);
switch (ssl_verify_result)
{
case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT:
case X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN:
die ("SSL certificate is self signed\n");
case X509_V_OK:
verb ("V: SSL certificate verification passed\n");
break;
default:
die ("SSL certification verification error: %ld\n",
ssl_verify_result);
}
} else {
verb ("V: Certificate verification skipped!\n");
}
// from /usr/include/openssl/ssl3.h
// ssl->s3->server_random is an unsigned char of 32 bytes
memcpy(time_map, ssl->s3->server_random, sizeof (uint32_t));
}
/** drop root rights and become 'nobody' */
static void
become_nobody ()
{
uid_t uid;
struct passwd *pw;
if (0 != getuid ())
return; /* not running as root to begin with; should (!) be harmless to continue
without dropping to 'nobody' (setting time will fail in the end) */
pw = getpwnam(UNPRIV_USER);
if (NULL == pw)
die ("Failed to obtain UID for `%s'\n", UNPRIV_USER);
uid = pw->pw_uid;
if (0 == uid)
die ("UID for `%s' is 0, refusing to run SSL\n", UNPRIV_USER);
#ifdef HAVE_SETRESUID
if (0 != setresuid (uid, uid, uid))
die ("Failed to setresuid: %s\n", strerror (errno));
#else
if (0 != (setuid (uid) | seteuid (uid)))
die ("Failed to setuid: %s\n", strerror (errno));
#endif
}
int
main(int argc, char **argv)
{
uint32_t *time_map;
struct timeval start_timeval;
struct timeval end_timeval;
int status;
pid_t ssl_child;
long long rt_time_ms;
uint32_t server_time_s;
if (argc != 6)
return 1;
host = argv[1];
port = argv[2];
protocol = argv[3];
ca_racket = (0 != strcmp ("unchecked", argv[4]));
verbose = (0 != strcmp ("quiet", argv[5]));
time_map = mmap (NULL, sizeof (uint32_t),
PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_ANONYMOUS, -1, 0);
if (MAP_FAILED == time_map)
{
fprintf (stderr, "mmap failed: %s\n",
strerror (errno));
return 1;
}
/* Get the current time from the system clock. */
if (0 != gettimeofday(&start_timeval, NULL))
die ("Failed to read current time of day: %s\n", strerror (errno));
verb ("V: time is currently %lu.%06lu\n",
(unsigned long)start_timeval.tv_sec,
(unsigned long)start_timeval.tv_usec);
/* initialize to bogus value, just to be on the safe side */
*time_map = 0;
/* Run SSL interaction in separate process (and not as 'root') */
ssl_child = fork ();
if (-1 == ssl_child)
die ("fork failed: %s\n", strerror (errno));
if (0 == ssl_child)
{
become_nobody ();
run_ssl (time_map);
(void) munmap (time_map, sizeof (uint32_t));
_exit (0);
}
if (ssl_child != waitpid (ssl_child, &status, 0))
die ("waitpid failed: %s\n", strerror (errno));
if (! (WIFEXITED (status) && (0 == WEXITSTATUS (status)) ))
die ("child process failed in SSL handshake\n");
if (0 != gettimeofday(&end_timeval, NULL))
die ("Failed to read current time of day: %s\n", strerror (errno));
/* calculate RTT */
rt_time_ms = (end_timeval.tv_sec - start_timeval.tv_sec) * 1000 + (end_timeval.tv_usec - start_timeval.tv_usec) / 1000;
if (rt_time_ms < 0)
rt_time_ms = 0; /* non-linear time... */
server_time_s = ntohl (*time_map);
munmap (time_map, sizeof (uint32_t));
verb ("V: server time %u (difference is about %d s) was fetched in %lld ms\n",
(unsigned int) server_time_s,
start_timeval.tv_sec - server_time_s,
rt_time_ms);
/* finally, actually set the time */
{
struct timeval server_time;
/* correct server time by half of RTT */
server_time.tv_sec = server_time_s + (rt_time_ms / 2 / 1000);
server_time.tv_usec = (rt_time_ms / 2) % 1000;
// We should never receive a time that is before the time we were last
// compiled; we subscribe to the linear theory of time for this program
// and this program alone!
if (server_time.tv_sec >= MAX_REASONABLE_TIME)
die("remote server is a false ticker from the future!");
if (server_time.tv_sec <= RECENT_COMPILE_DATE)
die ("remote server is a false ticker!");
if (0 != settimeofday(&server_time, NULL))
die ("setting time failed: %s\n", strerror (errno));
}
verb ("V: setting time succeeded\n");
return 0;
}