blob: a547be5fc18dfb395c8f6599e9f805d8c7e1e8fd [file] [log] [blame]
/* 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 <grp.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"
#define UNPRIV_GROUP "nogroup"
// 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
// After the duration of the TLS handshake exceeds this threshold
// (in msec), a warning is printed.
#define TLS_RTT_THRESHOLD 2000
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;
gid_t gid;
struct passwd *pw;
struct group *gr;
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);
gr = getgrnam(UNPRIV_GROUP);
if (NULL == pw)
die ("Failed to obtain UID for `%s'\n", UNPRIV_USER);
if (NULL == gr)
die ("Failed to obtain GID for `%s'\n", UNPRIV_GROUP);
uid = pw->pw_uid;
if (0 == uid)
die ("UID for `%s' is 0, refusing to run SSL\n", UNPRIV_USER);
gid = pw->pw_gid;
if (0 == gid || 0 == gr->gr_gid)
die ("GID for `%s' is 0, refusing to run SSL\n", UNPRIV_USER);
if (pw->pw_gid != gr->gr_gid)
die ("GID for `%s' is not `%s' as expected, refusing to run SSL\n",
UNPRIV_USER, UNPRIV_GROUP);
if (0 != initgroups((const char *)UNPRIV_USER, gr->gr_gid))
die ("Unable to initgroups for `%s' in group `%s' as expected\n",
UNPRIV_USER, UNPRIV_GROUP);
#ifdef HAVE_SETRESGID
if (0 != setresgid (gid, gid, gid))
die ("Failed to setresgid: %s\n", strerror (errno));
#else
if (0 != (setgid (gid) | setegid (gid)))
die ("Failed to setgid: %s\n", strerror (errno));
#endif
#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);
/* warning if the handshake took too long */
if (rt_time_ms > TLS_RTT_THRESHOLD) {
verb ("V: the TLS handshake took more than %d msecs - consider using a different " \
"server or run it again\n", TLS_RTT_THRESHOLD);
}
/* 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!\n");
if (server_time.tv_sec <= RECENT_COMPILE_DATE)
die ("remote server is a false ticker!\n");
if (0 != settimeofday(&server_time, NULL))
die ("setting time failed: %s\n", strerror (errno));
}
verb ("V: setting time succeeded\n");
return 0;
}