ntpd: add support for MD5/SHA1 message authentication

Add support for MD5 message authentication as described in RFC 5905.
This patch also supports SHA1 authentication.

The key file format is the same file format as used by ntpd.
The configuration file format follows standard Unix conventions
(# comments) with lines consist of the following fields separated by whitespace:
<key identifier, [1,65535]> <SHA1|MD5> <an ASCII string of up to 20 characters|an octet string [a-zA-F0-9] of up to 40 characters>.

https://www.ietf.org/rfc/rfc5905.txt

function                                             old     new   delta
ntp_init                                             473     987    +514
hash                                                   -     125    +125
recv_and_process_peer_pkt                            889     961     +72
packed_usage                                       33066   33130     +64
ntpd_main                                           1226    1277     +51
find_key_entry                                         -      29     +29
add_peers                                            195     207     +12
recv_and_process_client_pkt                          509     514      +5
------------------------------------------------------------------------------
(add/remove: 2/0 grow/shrink: 6/0 up/down: 872/0)             Total: 872 bytes

Signed-off-by: Brandon P. Enochs <enochs.brandon@gmail.com>
Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
This commit is contained in:
Brandon P. Enochs 2018-10-27 18:55:59 +02:00 committed by Denys Vlasenko
parent 4329116b6d
commit a541314b1f

View File

@ -62,13 +62,19 @@
//config: help //config: help
//config: Make ntpd look in /etc/ntp.conf for peers. Only "server address" //config: Make ntpd look in /etc/ntp.conf for peers. Only "server address"
//config: is supported. //config: is supported.
//config:config FEATURE_NTP_AUTH
//config: bool "Support md5/sha1 message authentication codes"
//config: default n
//config: depends on NTPD
//applet:IF_NTPD(APPLET(ntpd, BB_DIR_USR_SBIN, BB_SUID_DROP)) //applet:IF_NTPD(APPLET(ntpd, BB_DIR_USR_SBIN, BB_SUID_DROP))
//kbuild:lib-$(CONFIG_NTPD) += ntpd.o //kbuild:lib-$(CONFIG_NTPD) += ntpd.o
//usage:#define ntpd_trivial_usage //usage:#define ntpd_trivial_usage
//usage: "[-dnqNw"IF_FEATURE_NTPD_SERVER("l -I IFACE")"] [-S PROG] [-p PEER]..." //usage: "[-dnqNw"IF_FEATURE_NTPD_SERVER("l] [-I IFACE")"] [-S PROG]"
//usage: IF_NOT_FEATURE_NTP_AUTH(" [-p PEER]...")
//usage: IF_FEATURE_NTP_AUTH(" [-k KEYFILE] [-p [keyno:N:]PEER]...")
//usage:#define ntpd_full_usage "\n\n" //usage:#define ntpd_full_usage "\n\n"
//usage: "NTP client/server\n" //usage: "NTP client/server\n"
//usage: "\n -d Verbose (may be repeated)" //usage: "\n -d Verbose (may be repeated)"
@ -76,8 +82,16 @@
//usage: "\n -q Quit after clock is set" //usage: "\n -q Quit after clock is set"
//usage: "\n -N Run at high priority" //usage: "\n -N Run at high priority"
//usage: "\n -w Do not set time (only query peers), implies -n" //usage: "\n -w Do not set time (only query peers), implies -n"
//usage: "\n -S PROG Run PROG after stepping time, stratum change, and every 11 mins" //usage: "\n -S PROG Run PROG after stepping time, stratum change, and every 11 min"
//usage: IF_NOT_FEATURE_NTP_AUTH(
//usage: "\n -p PEER Obtain time from PEER (may be repeated)" //usage: "\n -p PEER Obtain time from PEER (may be repeated)"
//usage: )
//usage: IF_FEATURE_NTP_AUTH(
//usage: "\n -k FILE Key file (ntp.keys compatible)"
//usage: "\n -p [keyno:NUM:]PEER"
//usage: "\n Obtain time from PEER (may be repeated)"
//usage: "\n Use key NUM for authentication"
//usage: )
//usage: IF_FEATURE_NTPD_CONF( //usage: IF_FEATURE_NTPD_CONF(
//usage: "\n If -p is not given, 'server HOST' lines" //usage: "\n If -p is not given, 'server HOST' lines"
//usage: "\n from /etc/ntp.conf are used" //usage: "\n from /etc/ntp.conf are used"
@ -228,14 +242,18 @@
/* Parameter averaging constant */ /* Parameter averaging constant */
#define AVG 4 #define AVG 4
#define MAX_KEY_NUMBER 65535
#define KEYID_SIZE sizeof(uint32_t)
enum { enum {
NTP_VERSION = 4, NTP_VERSION = 4,
NTP_MAXSTRATUM = 15, NTP_MAXSTRATUM = 15,
NTP_DIGESTSIZE = 16, NTP_MD5_DIGESTSIZE = 16,
NTP_MSGSIZE_NOAUTH = 48, NTP_MSGSIZE_NOAUTH = 48,
NTP_MSGSIZE = (NTP_MSGSIZE_NOAUTH + 4 + NTP_DIGESTSIZE), NTP_MSGSIZE_MD5_AUTH = NTP_MSGSIZE_NOAUTH + KEYID_SIZE + NTP_MD5_DIGESTSIZE,
NTP_SHA1_DIGESTSIZE = 20,
NTP_MSGSIZE_SHA1_AUTH = NTP_MSGSIZE_NOAUTH + KEYID_SIZE + NTP_SHA1_DIGESTSIZE,
/* Status Masks */ /* Status Masks */
MODE_MASK = (7 << 0), MODE_MASK = (7 << 0),
@ -288,7 +306,7 @@ typedef struct {
l_fixedpt_t m_rectime; l_fixedpt_t m_rectime;
l_fixedpt_t m_xmttime; l_fixedpt_t m_xmttime;
uint32_t m_keyid; uint32_t m_keyid;
uint8_t m_digest[NTP_DIGESTSIZE]; uint8_t m_digest[ENABLE_FEATURE_NTP_AUTH ? NTP_SHA1_DIGESTSIZE : NTP_MD5_DIGESTSIZE];
} msg_t; } msg_t;
typedef struct { typedef struct {
@ -297,9 +315,26 @@ typedef struct {
double d_dispersion; double d_dispersion;
} datapoint_t; } datapoint_t;
#if ENABLE_FEATURE_NTP_AUTH
enum {
HASH_MD5,
HASH_SHA1,
};
typedef struct {
unsigned id; //try uint16_t?
smalluint type;
smalluint msg_size;
smalluint key_length;
char key[0];
} key_entry_t;
#endif
typedef struct { typedef struct {
len_and_sockaddr *p_lsa; len_and_sockaddr *p_lsa;
char *p_dotted; char *p_dotted;
#if ENABLE_FEATURE_NTP_AUTH
key_entry_t *key_entry;
#endif
int p_fd; int p_fd;
int datapoint_idx; int datapoint_idx;
uint32_t lastpkt_refid; uint32_t lastpkt_refid;
@ -337,13 +372,14 @@ enum {
OPT_q = (1 << 1), OPT_q = (1 << 1),
OPT_N = (1 << 2), OPT_N = (1 << 2),
OPT_x = (1 << 3), OPT_x = (1 << 3),
OPT_k = (1 << 4) * ENABLE_FEATURE_NTP_AUTH,
/* Insert new options above this line. */ /* Insert new options above this line. */
/* Non-compat options: */ /* Non-compat options: */
OPT_w = (1 << 4), OPT_w = (1 << (4+ENABLE_FEATURE_NTP_AUTH)),
OPT_p = (1 << 5), OPT_p = (1 << (5+ENABLE_FEATURE_NTP_AUTH)),
OPT_S = (1 << 6), OPT_S = (1 << (6+ENABLE_FEATURE_NTP_AUTH)),
OPT_l = (1 << 7) * ENABLE_FEATURE_NTPD_SERVER, OPT_l = (1 << (7+ENABLE_FEATURE_NTP_AUTH)) * ENABLE_FEATURE_NTPD_SERVER,
OPT_I = (1 << 8) * ENABLE_FEATURE_NTPD_SERVER, OPT_I = (1 << (8+ENABLE_FEATURE_NTP_AUTH)) * ENABLE_FEATURE_NTPD_SERVER,
/* We hijack some bits for other purposes */ /* We hijack some bits for other purposes */
OPT_qq = (1 << 31), OPT_qq = (1 << 31),
}; };
@ -816,8 +852,12 @@ resolve_peer_hostname(peer_t *p)
return lsa; return lsa;
} }
#if !ENABLE_FEATURE_NTP_AUTH
#define add_peers(s, key_entry) \
add_peers(s)
#endif
static void static void
add_peers(const char *s) add_peers(const char *s, key_entry_t *key_entry)
{ {
llist_t *item; llist_t *item;
peer_t *p; peer_t *p;
@ -846,6 +886,7 @@ add_peers(const char *s)
} }
} }
IF_FEATURE_NTP_AUTH(p->key_entry = key_entry;)
llist_add_to(&G.ntp_peers, p); llist_add_to(&G.ntp_peers, p);
G.peer_cnt++; G.peer_cnt++;
} }
@ -870,6 +911,48 @@ do_sendto(int fd,
return 0; return 0;
} }
#if ENABLE_FEATURE_NTP_AUTH
static void
hash(key_entry_t *key_entry, const msg_t *msg, uint8_t *output)
{
union {
md5_ctx_t m;
sha1_ctx_t s;
} ctx;
unsigned hash_size = sizeof(*msg) - sizeof(msg->m_keyid) - sizeof(msg->m_digest);
switch (key_entry->type) {
case HASH_MD5:
md5_begin(&ctx.m);
md5_hash(&ctx.m, key_entry->key, key_entry->key_length);
md5_hash(&ctx.m, msg, hash_size);
md5_end(&ctx.m, output);
break;
default: /* it's HASH_SHA1 */
sha1_begin(&ctx.s);
sha1_hash(&ctx.s, key_entry->key, key_entry->key_length);
sha1_hash(&ctx.s, msg, hash_size);
sha1_end(&ctx.s, output);
break;
}
}
static void
hash_peer(peer_t *p)
{
p->p_xmt_msg.m_keyid = htonl(p->key_entry->id);
hash(p->key_entry, &p->p_xmt_msg, p->p_xmt_msg.m_digest);
}
static int
hashes_differ(peer_t *p, const msg_t *msg)
{
uint8_t digest[NTP_SHA1_DIGESTSIZE];
hash(p->key_entry, msg, digest);
return memcmp(digest, msg->m_digest, p->key_entry->msg_size - NTP_MSGSIZE_NOAUTH - KEYID_SIZE);
}
#endif
static void static void
send_query_to_peer(peer_t *p) send_query_to_peer(peer_t *p)
{ {
@ -946,9 +1029,18 @@ send_query_to_peer(peer_t *p)
*/ */
p->reachable_bits <<= 1; p->reachable_bits <<= 1;
#if ENABLE_FEATURE_NTP_AUTH
if (p->key_entry)
hash_peer(p);
if (do_sendto(p->p_fd, /*from:*/ NULL, /*to:*/ &p->p_lsa->u.sa, /*addrlen:*/ p->p_lsa->len, if (do_sendto(p->p_fd, /*from:*/ NULL, /*to:*/ &p->p_lsa->u.sa, /*addrlen:*/ p->p_lsa->len,
&p->p_xmt_msg, NTP_MSGSIZE_NOAUTH) == -1 &p->p_xmt_msg, !p->key_entry ? NTP_MSGSIZE_NOAUTH : p->key_entry->msg_size) == -1
) { )
#else
if (do_sendto(p->p_fd, /*from:*/ NULL, /*to:*/ &p->p_lsa->u.sa, /*addrlen:*/ p->p_lsa->len,
&p->p_xmt_msg, NTP_MSGSIZE_NOAUTH) == -1
)
#endif
{
close(p->p_fd); close(p->p_fd);
p->p_fd = -1; p->p_fd = -1;
/* /*
@ -1924,10 +2016,21 @@ recv_and_process_peer_pkt(peer_t *p)
bb_perror_msg_and_die("recv(%s) error", p->p_dotted); bb_perror_msg_and_die("recv(%s) error", p->p_dotted);
} }
if (size != NTP_MSGSIZE_NOAUTH && size != NTP_MSGSIZE) { #if ENABLE_FEATURE_NTP_AUTH
if (size != NTP_MSGSIZE_NOAUTH && size != NTP_MSGSIZE_MD5_AUTH && size != NTP_MSGSIZE_SHA1_AUTH) {
bb_error_msg("malformed packet received from %s", p->p_dotted); bb_error_msg("malformed packet received from %s", p->p_dotted);
return; return;
} }
if (p->key_entry && hashes_differ(p, &msg)) {
bb_error_msg("invalid cryptographic hash received from %s", p->p_dotted);
return;
}
#else
if (size != NTP_MSGSIZE_NOAUTH && size != NTP_MSGSIZE_MD5_AUTH) {
bb_error_msg("malformed packet received from %s", p->p_dotted);
return;
}
#endif
if (msg.m_orgtime.int_partl != p->p_xmt_msg.m_xmttime.int_partl if (msg.m_orgtime.int_partl != p->p_xmt_msg.m_xmttime.int_partl
|| msg.m_orgtime.fractionl != p->p_xmt_msg.m_xmttime.fractionl || msg.m_orgtime.fractionl != p->p_xmt_msg.m_xmttime.fractionl
@ -2135,7 +2238,12 @@ recv_and_process_client_pkt(void /*int fd*/)
from = xzalloc(to->len); from = xzalloc(to->len);
size = recv_from_to(G_listen_fd, &msg, sizeof(msg), MSG_DONTWAIT, from, &to->u.sa, to->len); size = recv_from_to(G_listen_fd, &msg, sizeof(msg), MSG_DONTWAIT, from, &to->u.sa, to->len);
if (size != NTP_MSGSIZE_NOAUTH && size != NTP_MSGSIZE) { #if ENABLE_FEATURE_NTP_AUTH
if (size != NTP_MSGSIZE_NOAUTH && size != NTP_MSGSIZE_MD5_AUTH && size != NTP_MSGSIZE_SHA1_AUTH)
#else
if (size != NTP_MSGSIZE_NOAUTH && size != NTP_MSGSIZE_MD5_AUTH)
#endif
{
char *addr; char *addr;
if (size < 0) { if (size < 0) {
if (errno == EAGAIN) if (errno == EAGAIN)
@ -2278,6 +2386,19 @@ recv_and_process_client_pkt(void /*int fd*/)
* with the -g and -q options. See the tinker command for other options. * with the -g and -q options. See the tinker command for other options.
* Note: The kernel time discipline is disabled with this option. * Note: The kernel time discipline is disabled with this option.
*/ */
#if ENABLE_FEATURE_NTP_AUTH
static key_entry_t *
find_key_entry(llist_t *key_entries, unsigned id)
{
while (key_entries) {
key_entry_t *cur = (key_entry_t*) key_entries->data;
if (cur->id == id)
return cur;
key_entries = key_entries->link;
}
bb_error_msg_and_die("key %u is not defined", id);
}
#endif
/* By doing init in a separate function we decrease stack usage /* By doing init in a separate function we decrease stack usage
* in main loop. * in main loop.
@ -2286,6 +2407,10 @@ static NOINLINE void ntp_init(char **argv)
{ {
unsigned opts; unsigned opts;
llist_t *peers; llist_t *peers;
#if ENABLE_FEATURE_NTP_AUTH
llist_t *key_entries;
char *key_file_path;
#endif
srand(getpid()); srand(getpid());
@ -2302,8 +2427,10 @@ static NOINLINE void ntp_init(char **argv)
/* Parse options */ /* Parse options */
peers = NULL; peers = NULL;
IF_FEATURE_NTP_AUTH(key_entries = NULL;)
opts = getopt32(argv, "^" opts = getopt32(argv, "^"
"nqNx" /* compat */ "nqNx" /* compat */
IF_FEATURE_NTP_AUTH("k:") /* compat */
"wp:*S:"IF_FEATURE_NTPD_SERVER("l") /* NOT compat */ "wp:*S:"IF_FEATURE_NTPD_SERVER("l") /* NOT compat */
IF_FEATURE_NTPD_SERVER("I:") /* compat */ IF_FEATURE_NTPD_SERVER("I:") /* compat */
"d" /* compat */ "d" /* compat */
@ -2311,11 +2438,11 @@ static NOINLINE void ntp_init(char **argv)
"\0" "\0"
"dd:wn" /* -d: counter; -p: list; -w implies -n */ "dd:wn" /* -d: counter; -p: list; -w implies -n */
IF_FEATURE_NTPD_SERVER(":Il") /* -I implies -l */ IF_FEATURE_NTPD_SERVER(":Il") /* -I implies -l */
, &peers, &G.script_name, IF_FEATURE_NTP_AUTH(, &key_file_path)
#if ENABLE_FEATURE_NTPD_SERVER , &peers, &G.script_name
&G.if_name, IF_FEATURE_NTPD_SERVER(, &G.if_name)
#endif , &G.verbose
&G.verbose); );
// if (opts & OPT_x) /* disable stepping, only slew is allowed */ // if (opts & OPT_x) /* disable stepping, only slew is allowed */
// G.time_was_stepped = 1; // G.time_was_stepped = 1;
@ -2341,19 +2468,107 @@ static NOINLINE void ntp_init(char **argv)
logmode = LOGMODE_NONE; logmode = LOGMODE_NONE;
} }
#if ENABLE_FEATURE_NTP_AUTH
if (opts & OPT_k) {
char *tokens[4];
parser_t *parser;
parser = config_open(key_file_path);
while (config_read(parser, tokens, 4, 3, "# \t", PARSE_NORMAL | PARSE_MIN_DIE) == 3) {
key_entry_t *key_entry;
char buffer[40];
smalluint hash_type;
smalluint msg_size;
smalluint key_length;
char *key;
if ((tokens[1][0] | 0x20) == 'm')
/* supports 'M' and 'md5' formats */
hash_type = HASH_MD5;
else
if (strncasecmp(tokens[1], "sha", 3) == 0)
/* supports 'sha' and 'sha1' formats */
hash_type = HASH_SHA1;
else
bb_error_msg_and_die("only MD5 and SHA1 keys supported");
/* man ntp.keys:
* MD5 The key is 1 to 16 printable characters terminated by an EOL,
* whitespace, or a # (which is the "start of comment" character).
* SHA
* SHA1
* RMD160 The key is a hex-encoded ASCII string of 40 characters, which
* is truncated as necessary.
*/
key_length = strnlen(tokens[2], sizeof(buffer)+1);
if (key_length >= sizeof(buffer)+1) {
err:
bb_error_msg_and_die("malformed key at line %u", parser->lineno);
}
if (hash_type == HASH_MD5) {
key = tokens[2];
msg_size = NTP_MSGSIZE_MD5_AUTH;
} else /* it's hash_type == HASH_SHA1 */
if (!(key_length & 1)) {
key_length >>= 1;
if (!hex2bin(buffer, tokens[2], key_length))
goto err;
key = buffer;
msg_size = NTP_MSGSIZE_SHA1_AUTH;
} else {
goto err;
}
key_entry = xzalloc(sizeof(*key_entry) + key_length);
key_entry->type = hash_type;
key_entry->msg_size = msg_size;
key_entry->key_length = key_length;
memcpy(key_entry->key, key, key_length);
key_entry->id = xatou_range(tokens[0], 1, MAX_KEY_NUMBER);
llist_add_to(&key_entries, key_entry);
}
config_close(parser);
}
#endif
if (peers) { if (peers) {
#if ENABLE_FEATURE_NTP_AUTH
while (peers) {
char *peer = llist_pop(&peers);
key_entry_t *key_entry = NULL;
if (strncmp(peer, "keyno:", 6) == 0) {
char *end;
int key_id;
peer += 6;
end = strchr(peer, ':');
*end = '\0';
key_id = xatou_range(peer, 1, MAX_KEY_NUMBER);
*end = ':';
key_entry = find_key_entry(key_entries, key_id);
peer = end + 1;
}
add_peers(peer, key_entry);
}
#else
while (peers) while (peers)
add_peers(llist_pop(&peers)); add_peers(llist_pop(&peers), NULL);
#endif
} }
#if ENABLE_FEATURE_NTPD_CONF #if ENABLE_FEATURE_NTPD_CONF
else { else {
parser_t *parser; parser_t *parser;
char *token[3]; char *token[3 + 2*ENABLE_FEATURE_NTP_AUTH];
parser = config_open("/etc/ntp.conf"); parser = config_open("/etc/ntp.conf");
while (config_read(parser, token, 3, 1, "# \t", PARSE_NORMAL)) { while (config_read(parser, token, 3 + 2*ENABLE_FEATURE_NTP_AUTH, 1, "# \t", PARSE_NORMAL)) {
if (strcmp(token[0], "server") == 0 && token[1]) { if (strcmp(token[0], "server") == 0 && token[1]) {
add_peers(token[1]); # if ENABLE_FEATURE_NTP_AUTH
key_entry_t *key_entry = NULL;
if (token[2] && token[3] && strcmp(token[2], "key") == 0) {
unsigned key_id = xatou_range(token[3], 1, MAX_KEY_NUMBER);
key_entry = find_key_entry(key_entries, key_id);
}
add_peers(token[1], key_entry);
# else
add_peers(token[1], NULL);
# endif
continue; continue;
} }
bb_error_msg("skipping %s:%u: unimplemented command '%s'", bb_error_msg("skipping %s:%u: unimplemented command '%s'",
@ -2394,6 +2609,7 @@ static NOINLINE void ntp_init(char **argv)
| (1 << SIGCHLD) | (1 << SIGCHLD)
, SIG_IGN , SIG_IGN
); );
//TODO: free unused elements of key_entries?
} }
int ntpd_main(int argc UNUSED_PARAM, char **argv) MAIN_EXTERNALLY_VISIBLE; int ntpd_main(int argc UNUSED_PARAM, char **argv) MAIN_EXTERNALLY_VISIBLE;