summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBrandon P. Enochs2018-10-27 18:55:59 +0200
committerDenys Vlasenko2018-10-27 18:57:37 +0200
commita541314b1f5e7392608cdef91e9098330823ed31 (patch)
treea1c57bb25beac34dbfa5f8a24bc0c796befd2982
parent4329116b6d036b19a92cabf51dc388fab4787b53 (diff)
downloadbusybox-a541314b1f5e7392608cdef91e9098330823ed31.zip
busybox-a541314b1f5e7392608cdef91e9098330823ed31.tar.gz
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>
-rw-r--r--networking/ntpd.c266
1 files changed, 241 insertions, 25 deletions
diff --git a/networking/ntpd.c b/networking/ntpd.c
index 1ebdc34..354bff8 100644
--- a/networking/ntpd.c
+++ b/networking/ntpd.c
@@ -62,13 +62,19 @@
//config: help
//config: Make ntpd look in /etc/ntp.conf for peers. Only "server address"
//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))
//kbuild:lib-$(CONFIG_NTPD) += ntpd.o
//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: "NTP client/server\n"
//usage: "\n -d Verbose (may be repeated)"
@@ -76,8 +82,16 @@
//usage: "\n -q Quit after clock is set"
//usage: "\n -N Run at high priority"
//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: )
+//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: "\n If -p is not given, 'server HOST' lines"
//usage: "\n from /etc/ntp.conf are used"
@@ -228,14 +242,18 @@
/* Parameter averaging constant */
#define AVG 4
+#define MAX_KEY_NUMBER 65535
+#define KEYID_SIZE sizeof(uint32_t)
enum {
NTP_VERSION = 4,
NTP_MAXSTRATUM = 15,
- NTP_DIGESTSIZE = 16,
- NTP_MSGSIZE_NOAUTH = 48,
- NTP_MSGSIZE = (NTP_MSGSIZE_NOAUTH + 4 + NTP_DIGESTSIZE),
+ NTP_MD5_DIGESTSIZE = 16,
+ NTP_MSGSIZE_NOAUTH = 48,
+ 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 */
MODE_MASK = (7 << 0),
@@ -288,7 +306,7 @@ typedef struct {
l_fixedpt_t m_rectime;
l_fixedpt_t m_xmttime;
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;
typedef struct {
@@ -297,9 +315,26 @@ typedef struct {
double d_dispersion;
} 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 {
len_and_sockaddr *p_lsa;
char *p_dotted;
+#if ENABLE_FEATURE_NTP_AUTH
+ key_entry_t *key_entry;
+#endif
int p_fd;
int datapoint_idx;
uint32_t lastpkt_refid;
@@ -337,13 +372,14 @@ enum {
OPT_q = (1 << 1),
OPT_N = (1 << 2),
OPT_x = (1 << 3),
+ OPT_k = (1 << 4) * ENABLE_FEATURE_NTP_AUTH,
/* Insert new options above this line. */
/* Non-compat options: */
- OPT_w = (1 << 4),
- OPT_p = (1 << 5),
- OPT_S = (1 << 6),
- OPT_l = (1 << 7) * ENABLE_FEATURE_NTPD_SERVER,
- OPT_I = (1 << 8) * ENABLE_FEATURE_NTPD_SERVER,
+ OPT_w = (1 << (4+ENABLE_FEATURE_NTP_AUTH)),
+ OPT_p = (1 << (5+ENABLE_FEATURE_NTP_AUTH)),
+ OPT_S = (1 << (6+ENABLE_FEATURE_NTP_AUTH)),
+ OPT_l = (1 << (7+ENABLE_FEATURE_NTP_AUTH)) * ENABLE_FEATURE_NTPD_SERVER,
+ OPT_I = (1 << (8+ENABLE_FEATURE_NTP_AUTH)) * ENABLE_FEATURE_NTPD_SERVER,
/* We hijack some bits for other purposes */
OPT_qq = (1 << 31),
};
@@ -816,8 +852,12 @@ resolve_peer_hostname(peer_t *p)
return lsa;
}
+#if !ENABLE_FEATURE_NTP_AUTH
+#define add_peers(s, key_entry) \
+ add_peers(s)
+#endif
static void
-add_peers(const char *s)
+add_peers(const char *s, key_entry_t *key_entry)
{
llist_t *item;
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);
G.peer_cnt++;
}
@@ -870,6 +911,48 @@ do_sendto(int fd,
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
send_query_to_peer(peer_t *p)
{
@@ -946,9 +1029,18 @@ send_query_to_peer(peer_t *p)
*/
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,
- &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);
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);
}
- 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);
+ 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
|| 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);
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;
if (size < 0) {
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.
* 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
* in main loop.
@@ -2286,6 +2407,10 @@ static NOINLINE void ntp_init(char **argv)
{
unsigned opts;
llist_t *peers;
+#if ENABLE_FEATURE_NTP_AUTH
+ llist_t *key_entries;
+ char *key_file_path;
+#endif
srand(getpid());
@@ -2302,8 +2427,10 @@ static NOINLINE void ntp_init(char **argv)
/* Parse options */
peers = NULL;
+ IF_FEATURE_NTP_AUTH(key_entries = NULL;)
opts = getopt32(argv, "^"
"nqNx" /* compat */
+ IF_FEATURE_NTP_AUTH("k:") /* compat */
"wp:*S:"IF_FEATURE_NTPD_SERVER("l") /* NOT compat */
IF_FEATURE_NTPD_SERVER("I:") /* compat */
"d" /* compat */
@@ -2311,11 +2438,11 @@ static NOINLINE void ntp_init(char **argv)
"\0"
"dd:wn" /* -d: counter; -p: list; -w implies -n */
IF_FEATURE_NTPD_SERVER(":Il") /* -I implies -l */
- , &peers, &G.script_name,
-#if ENABLE_FEATURE_NTPD_SERVER
- &G.if_name,
-#endif
- &G.verbose);
+ IF_FEATURE_NTP_AUTH(, &key_file_path)
+ , &peers, &G.script_name
+ IF_FEATURE_NTPD_SERVER(, &G.if_name)
+ , &G.verbose
+ );
// if (opts & OPT_x) /* disable stepping, only slew is allowed */
// G.time_was_stepped = 1;
@@ -2341,19 +2468,107 @@ static NOINLINE void ntp_init(char **argv)
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 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)
- add_peers(llist_pop(&peers));
+ add_peers(llist_pop(&peers), NULL);
+#endif
}
#if ENABLE_FEATURE_NTPD_CONF
else {
parser_t *parser;
- char *token[3];
+ char *token[3 + 2*ENABLE_FEATURE_NTP_AUTH];
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]) {
- 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;
}
bb_error_msg("skipping %s:%u: unimplemented command '%s'",
@@ -2394,6 +2609,7 @@ static NOINLINE void ntp_init(char **argv)
| (1 << SIGCHLD)
, SIG_IGN
);
+//TODO: free unused elements of key_entries?
}
int ntpd_main(int argc UNUSED_PARAM, char **argv) MAIN_EXTERNALLY_VISIBLE;