[Git][NTPsec/ntpsec][master] 4 commits: Add CMAC authentication
Hal Murray
gitlab at mg.gitlab.com
Wed Jun 13 09:09:58 UTC 2018
Hal Murray pushed to branch master at NTPsec / ntpsec
Commits:
d4691e60 by Hal Murray at 2018-06-13T07:56:52Z
Add CMAC authentication
Also includes significant cleanups scattered around.
This does not include any #ifdefs for /usr/include/openssl/cmac.h
It works on all my test systems but might fail on some really
old/conservative systems. If so, we get to decide whether we
should add the #ifdefs or drop support.
- - - - -
a7a72bb7 by Hal Murray at 2018-06-13T07:56:52Z
Disable debugging logging.
- - - - -
41ec5a08 by Hal Murray at 2018-06-13T07:56:52Z
Check that keys for servers will work. Issue #487
- - - - -
a04100c3 by Hal Murray at 2018-06-13T07:57:30Z
Check to be sure openssl/cmac.h exists
This gives an error message from waf configure
rather than waf build
- - - - -
25 changed files:
- attic/digest-timing.c
- docs/authentic.txt
- docs/includes/auth-commands.txt
- docs/mode6.txt
- + include/ntp_auth.h
- include/ntp_stdlib.h
- include/ntp_types.h
- include/ntpd.h
- libntp/authkeys.c
- libntp/authreadkeys.c
- libntp/macencrypt.c
- libntp/ssl_init.c
- ntpclients/ntpkeygen.py
- ntpclients/ntpq.py
- ntpd/ntp_config.c
- ntpd/ntp_control.c
- ntpd/ntp_peer.c
- ntpd/ntp_proto.c
- ntpd/ntp_util.c
- ntpd/ntpd.c
- tests/common/tests_main.c
- tests/common/tests_main.h
- tests/libntp/authkeys.c
- tests/libntp/macencrypt.c
- wscript
Changes:
=====================================
attic/digest-timing.c
=====================================
--- a/attic/digest-timing.c
+++ b/attic/digest-timing.c
@@ -21,6 +21,7 @@
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
+#include <string.h>
#include <time.h>
#include <openssl/opensslv.h>
@@ -67,6 +68,7 @@ static void ssl_init(void)
{
ERR_load_crypto_strings();
OpenSSL_add_all_digests();
+ OpenSSL_add_all_ciphers();
ctx = EVP_MD_CTX_new();
#if OPENSSL_VERSION_NUMBER > CMAC_VERSION_CUTOFF
cmac = CMAC_CTX_new();
@@ -93,7 +95,7 @@ static unsigned int SSL_Digest(
static unsigned int SSL_DigestSlow(
int type, /* hash algorithm */
uint8_t *key, /* key pointer */
- int keylength, /* key size */
+ int keylength, /* key size */
uint8_t *pkt, /* packet pointer */
int pktlength /* packet length */
) {
=====================================
docs/authentic.txt
=====================================
--- a/docs/authentic.txt
+++ b/docs/authentic.txt
@@ -154,9 +154,16 @@ special message called a _crypto-NAK_. Since the crypto-NAK is protected
by the loopback test, an intruder cannot disrupt the protocol by sending
a bogus crypto-NAK.
-MD5 digests are 16 bytes. SHA1 digests are 20 bytes.
-Longer digests don't work yet. 2018-Jan-07
-FIXME: long digests
++ntpd+'s digest mode can use any digest supported by libcrypto
+from the OpenSSL project. MD5 digests are 16 bytes. SHA1 digests are 20 bytes.
+Longer digests are truncated.
+
++ntpd+'s mode can use any cypher with a CBC mode that is supported by
+libcrypto from the OpenSSL project. AES is short for AES-128.
+AES needs a 16 byte key. Longer keys are truncated. Shorter
+keys are padded with 0s. AES MACs are 16 bytes long. MACs longer
+than 20 bytes will be truncated.
+
Keys and related information are specified in a keys file, which must be
distributed and stored using secure means beyond the scope of the NTP
=====================================
docs/includes/auth-commands.txt
=====================================
--- a/docs/includes/auth-commands.txt
+++ b/docs/includes/auth-commands.txt
@@ -9,7 +9,7 @@
[[keys]]
+keys+ _keyfile_::
- Specifies the complete path and location of the MD5/SHA1 key file
+ Specifies the complete path and location of the key file
containing the keys and key identifiers used by {ntpdman},
and {ntpqman} when operating with symmetric-key cryptography.
This is the same operation as the +-k+ command line option.
=====================================
docs/mode6.txt
=====================================
--- a/docs/mode6.txt
+++ b/docs/mode6.txt
@@ -508,13 +508,18 @@ The contents of the MAC trailer consists of:
1. The 32-bit identifier of the signing key in network byte order.
-2. A cryptographic hash of the following octet spans, in order.
-First, the password entered to use the signing key, then the request
-header fields, then the payload.
+2a. In digest mode, a cryptographic hash of the following octet spans,
+in order. First, the password entered to use the signing key, then the
+request header fields, then the payload.
-The cryptographic hash is 16 octets for MD5 and 20 octets for SHA1.
-Longer digests don't work yet. 2018-Jan-06
-FIXME: long digests
+2b. In CMAC mode, a cryptographic hash of the packet header and
+payload with the crypto algorithim using the key.
+
+The cryptographic hash is 16 octets for MD5 and AES and 20 octets for SHA1.
+Longer digests are truncated.
+
+The key length for AES is 16 bytes. Longer keys are truncated. Shorter
+keys are padded with 0s. MD5 and SHA1 can use any key length.
== Compatibility Notes ==
=====================================
include/ntp_auth.h
=====================================
--- /dev/null
+++ b/include/ntp_auth.h
@@ -0,0 +1,75 @@
+#ifndef GUARD_AUTH_H
+#define GUARD_AUTH_H
+
+#include "ntp_types.h"
+#include "ntp_lists.h"
+
+#include <openssl/evp.h>
+#include <openssl/cmac.h>
+
+typedef enum {AUTH_NONE, AUTH_CMAC, AUTH_DIGEST} AUTH_Type;
+
+/*
+ * Structure to store auth data in the hash table.
+ */
+typedef struct auth_data auth_info;
+
+struct auth_data {
+ auth_info * hlink; /* next in hash bucket */
+ DECL_DLIST_LINK(auth_info, llink); /* for overall & free lists */
+ keyid_t keyid; /* key identifier */
+ AUTH_Type type; /* CMAC or old digest */
+ unsigned short flags; /* KEY_ flags that wave */
+ uint8_t * key; /* shared secret */
+ unsigned short key_size; /* secret length */
+ const EVP_MD * digest; /* Digest mode only */
+ const EVP_CIPHER *cipher; /* CMAC mode only */
+};
+
+extern void auth_init (void);
+extern void auth_prealloc (int);
+extern void auth_reset_stats(uptime_t reset_time);
+
+
+extern void auth_setkey (keyid_t, AUTH_Type, const char *, const uint8_t *, size_t);
+extern void auth_delkeys (void);
+extern bool authreadkeys (const char *);
+extern void authtrust (keyid_t, bool);
+
+extern auth_info * authlookup (keyid_t, bool);
+
+extern bool authdecrypt (auth_info*, uint32_t *, int, int);
+extern int authencrypt (auth_info*, uint32_t *, int);
+
+extern bool digest_decrypt (auth_info*, uint32_t *, int, int);
+extern int digest_encrypt (auth_info*, uint32_t *, int);
+
+extern bool cmac_decrypt (auth_info*, uint32_t *, int, int);
+extern int cmac_encrypt (auth_info*, uint32_t *, int);
+
+
+extern unsigned int authnumkeys; /* number of active keys */
+extern unsigned int authnumfreekeys; /* number of free keys */
+extern unsigned long authkeylookups; /* calls to lookup keys */
+extern unsigned long authkeynotfound; /* keys not found */
+extern unsigned long authencryptions; /* calls to authencrypt */
+extern unsigned long authdigestencrypt;/* calls to digest_encrypt */
+extern unsigned long authcmacencrypt; /* calls to cmac_encrypt */
+extern unsigned long authdecryptions; /* calls to authdecrypt */
+extern unsigned long authdigestdecrypt;/* calls to digest_decrypt */
+extern unsigned long authdigestfail; /* fails from digest_decrypt */
+extern unsigned long authcmacdecrypt; /* calls to cmac_decrypt*/
+extern unsigned long authcmacfail; /* fails from cmac_decrypt*/
+extern uptime_t auth_timereset; /* current_time when stats reset */
+
+
+/* EVP_get_cipherbyname() is broken on
+ * old CentOS, NetBSD, FreeBSD, and maybe others.
+ */
+extern const EVP_CIPHER * MY_get_cipherbyname(const char * name);
+
+
+/* Not in CMAC API */
+#define CMAC_MAX_MAC_LENGTH 64
+
+#endif /* GUARD_AUTH_H */
=====================================
include/ntp_stdlib.h
=====================================
--- a/include/ntp_stdlib.h
+++ b/include/ntp_stdlib.h
@@ -38,20 +38,8 @@ extern int change_logfile (const char *, bool);
extern void reopen_logfile (void);
extern void setup_logfile (const char *);
-/* authkeys.c */
-extern void auth_delkeys (void);
-extern bool authdecrypt (keyid_t, uint32_t *, int, int);
-extern int authencrypt (keyid_t, uint32_t *, int);
-extern bool authhavekey (keyid_t);
-extern bool authistrusted (keyid_t);
-extern bool authreadkeys (const char *);
-extern void authtrust (keyid_t, bool);
-extern bool authusekey (keyid_t, int, const uint8_t *);
-
extern int clocktime (int, int, int, int, int, time_t, uint32_t, uint32_t *, uint32_t *);
-extern void init_auth (void);
extern void init_network (void);
-extern void auth_prealloc_symkeys(int);
extern int ymd2yd (int, int, int);
/* getopt.c */
@@ -68,9 +56,6 @@ int ntp_getopt_long(int argc, char* const argv[], const char *optstring,
const struct option *longopts, int *longindex);
/* mac_md5encrypt.c */
-extern bool mac_authdecrypt (int, uint8_t *, int, uint32_t *, int, int);
-extern int mac_authencrypt (int, uint8_t *, int, uint32_t *, int);
-extern void mac_setkey (keyid_t, int, const uint8_t *, size_t);
extern uint32_t addr2refid (sockaddr_u *);
/* emalloc.c */
@@ -133,16 +118,6 @@ extern void getauthkeys (const char *);
* Variable declarations for libntp.
*/
-/* authkeys.c */
-extern unsigned int authkeynotfound; /* keys not found */
-extern unsigned int authkeylookups; /* calls to lookup keys */
-extern unsigned int authnumkeys; /* number of active keys */
-extern unsigned int authkeyuncached; /* cache misses */
-extern unsigned int authencryptions; /* calls to encrypt */
-extern unsigned int authdecryptions; /* calls to decrypt */
-
-extern int authnumfreekeys;
-
/* getopt.c */
extern char * ntp_optarg; /* global argument pointer */
extern int ntp_optind; /* global argv index */
=====================================
include/ntp_types.h
=====================================
--- a/include/ntp_types.h
+++ b/include/ntp_types.h
@@ -68,8 +68,8 @@ typedef uint16_t associd_t; /* association ID */
typedef uint32_t keyid_t; /* cryptographic key ID */
#define NTP_MAXKEY 0xffff /* max authentication key number */
-/* Max digest length in non-extension MACs, add 4 for keyID */
-#define MAX_BARE_DIGEST_LENGTH 20
+/* Max MAC length in non-extension MACs, add 4 for keyID */
+#define MAX_BARE_MAC_LENGTH 20
/*
* Ordinary double has only 53 bits of precision in IEEE754. But l_fp
=====================================
include/ntpd.h
=====================================
--- a/include/ntpd.h
+++ b/include/ntpd.h
@@ -226,7 +226,6 @@ extern char *ntp_signd_socket;
/* ntp_control.c */
extern keyid_t ctl_auth_keyid; /* keyid used for authenticating write requests */
-extern void reset_auth_stats(void);
/*
* Other statistics of possible interest
=====================================
libntp/authkeys.c
=====================================
--- a/libntp/authkeys.c
+++ b/libntp/authkeys.c
@@ -12,49 +12,37 @@
#include "ntp_lists.h"
#include "ntp_malloc.h"
#include "ntp_stdlib.h"
+#include "ntp_auth.h"
-/*
- * Structure to store keys in in the hash table.
- */
-typedef struct savekey symkey;
-
-struct savekey {
- symkey * hlink; /* next in hash bucket */
- DECL_DLIST_LINK(symkey, llink); /* for overall & free lists */
- uint8_t * secret; /* shared secret */
- keyid_t keyid; /* key identifier */
- unsigned short type; /* OpenSSL digest NID */
- unsigned short secretsize; /* secret octets */
- unsigned short flags; /* KEY_ flags that wave */
-};
-/* define the payload region of symkey beyond the list pointers */
-#define symkey_payload secret
+/* define the payload region of auth_data beyond the list pointers */
+#define auth_info_payload keyid
#define KEY_TRUSTED 0x001 /* this key is trusted */
#ifdef DEBUG
-typedef struct symkey_alloc_tag symkey_alloc;
+typedef struct auth_alloc_tag auth_alloc;
-struct symkey_alloc_tag {
- symkey_alloc * link;
+struct auth_alloc_tag {
+ auth_alloc * link;
void * mem; /* enable free() atexit */
};
-symkey_alloc * authallocs;
+auth_alloc * auth_allocs;
#endif /* DEBUG */
static inline unsigned short auth_log2(double x);
static void auth_moremem (int);
-static void auth_resize_hashtable(void);
-static void allocsymkey(symkey **, keyid_t, unsigned short,
+static void auth_resize_hashtable(void);
+static void alloc_auth_info(auth_info **, keyid_t, AUTH_Type,
+ const char *,
unsigned short, unsigned short, uint8_t *);
-static void freesymkey(symkey *, symkey **);
+static void free_auth_info(auth_info *, auth_info **);
#ifdef DEBUG
-static void free_auth_mem(void);
+static void free_auth_mem(void);
#endif
-static symkey key_listhead; /* list of all in-use keys */
+static auth_info key_listhead; /* list of all in-use keys */
/*
* The hash table. This is indexed by the low order bits of the
* keyid. This gets updated in auth_resize_hashtable
@@ -63,40 +51,46 @@ static symkey key_listhead; /* list of all in-use keys */
#define INIT_AUTHHASHSIZE 64
static unsigned short authhashbuckets = INIT_AUTHHASHSIZE;
static unsigned short authhashmask = INIT_AUTHHASHSIZE - 1;
-static symkey **key_hash;
-
-unsigned int authkeynotfound; /* keys not found */
-unsigned int authkeylookups; /* calls to lookup keys */
-unsigned int authnumkeys; /* number of active keys */
-unsigned int authkeyuncached; /* cache misses */
-static unsigned int authnokey; /* calls to encrypt with no key */
-unsigned int authencryptions; /* calls to encrypt */
-unsigned int authdecryptions; /* calls to decrypt */
+static auth_info **key_hash;
+
+unsigned int authnumkeys; /* number of active keys */
+unsigned int authnumfreekeys; /* number of free keys */
+unsigned long authkeylookups; /* calls to lookup keys */
+unsigned long authkeynotfound; /* keys not found */
+unsigned long authencryptions; /* calls to authencrypt */
+unsigned long authdigestencrypt;/* calls to digest_encrypt */
+unsigned long authcmacencrypt; /* calls to cmac_encrypt */
+unsigned long authdecryptions; /* calls to authdecrypt */
+unsigned long authdigestdecrypt;/* calls to digest_decrypt */
+unsigned long authdigestfail; /* fails from digest_decrypt */
+unsigned long authcmacdecrypt; /* calls to cmac_decrypt*/
+unsigned long authcmacfail; /* fails from cmac_decrypt*/
+uptime_t auth_timereset; /* current_time when stats reset */
/*
- * Storage for free symkey structures. We malloc() such things but
+ * Storage for free auth_info structures. We malloc() such things but
* never free them.
*/
-static symkey *authfreekeys;
-int authnumfreekeys;
+static auth_info *authfreekeys;
#define MEMINC 16 /* number of new free ones to get */
/*
- * The key cache. We cache the last key we looked at here.
+ * There used to be a cache for the last key we used.
+ * It was also a kludge to pass arguments.
+ * We now pass a pointer to auth_data.
+ * A cache isn't all that useful:
+ * On a busy server, we can use the same auth_data for the reply.
+ * On a client, where the reply might hit the cache from the request,
+ * the extra cost of a lookup isn't significant.
*/
-static keyid_t cache_keyid; /* key identifier */
-static uint8_t *cache_secret; /* secret */
-static unsigned short cache_secretsize; /* secret length */
-static int cache_type; /* OpenSSL digest NID */
-static unsigned short cache_flags; /* flags that wave */
/*
- * init_auth - initialize internal data
+ * auth_init - initialize internal data
*/
void
-init_auth(void)
+auth_init(void)
{
size_t newalloc;
@@ -117,6 +111,28 @@ init_auth(void)
/*
+ * auth_reset_stats - reset the authentication stat counters.
+ * can't use global current_time - not in library.
+ */
+void
+auth_reset_stats(uptime_t reset_time)
+{
+ authkeylookups = 0;
+ authkeynotfound = 0;
+ authencryptions = 0;
+ authdigestencrypt = 0;
+ authcmacencrypt = 0;
+ authdecryptions = 0;
+ authdigestdecrypt = 0;
+ authdigestfail = 0;
+ authcmacdecrypt = 0;
+ authcmacfail = 0;
+ auth_timereset = reset_time;
+}
+
+
+
+/*
* free_auth_mem - assist in leak detection by freeing all dynamic
* allocations from this module.
*/
@@ -124,18 +140,16 @@ init_auth(void)
static void
free_auth_mem(void)
{
- symkey * sk;
- symkey_alloc * alloc;
- symkey_alloc * next_alloc;
+ auth_info * sk;
+ auth_alloc * alloc;
+ auth_alloc * next_alloc;
while (NULL != (sk = HEAD_DLIST(key_listhead, llink))) {
- freesymkey(sk, &key_hash[KEYHASH(sk->keyid)]);
+ free_auth_info(sk, &key_hash[KEYHASH(sk->keyid)]);
}
free(key_hash);
key_hash = NULL;
- cache_keyid = 0;
- cache_flags = 0;
- for (alloc = authallocs; alloc != NULL; alloc = next_alloc) {
+ for (alloc = auth_allocs; NULL != alloc; alloc = next_alloc) {
next_alloc = alloc->link;
free(alloc->mem);
}
@@ -153,11 +167,11 @@ auth_moremem(
int keycount
)
{
- symkey * sk;
+ auth_info * auth;
int i;
#ifdef DEBUG
void * base;
- symkey_alloc * allocrec;
+ auth_alloc * allocrec;
# define MOREMEM_EXTRA_ALLOC (sizeof(*allocrec))
#else
# define MOREMEM_EXTRA_ALLOC (0)
@@ -166,29 +180,29 @@ auth_moremem(
i = (keycount > 0)
? keycount
: MEMINC;
- sk = emalloc_zero((unsigned int)i * sizeof(*sk) + MOREMEM_EXTRA_ALLOC);
+ auth = emalloc_zero((unsigned int)i * sizeof(*auth) + MOREMEM_EXTRA_ALLOC);
#ifdef DEBUG
- base = sk;
+ base = auth;
#endif
authnumfreekeys += i;
- for (; i > 0; i--, sk++) {
- LINK_SLIST(authfreekeys, sk, llink.f);
+ for (; i > 0; i--, auth++) {
+ LINK_SLIST(authfreekeys, auth, llink.f);
}
#ifdef DEBUG
- allocrec = (void *)sk;
+ allocrec = (void *)auth;
allocrec->mem = base;
- LINK_SLIST(authallocs, allocrec, link);
+ LINK_SLIST(auth_allocs, allocrec, link);
#endif
}
/*
- * auth_prealloc_symkeys
+ * auth_prealloc
*/
void
-auth_prealloc_symkeys(
+auth_prealloc(
int keycount
)
{
@@ -224,7 +238,7 @@ auth_resize_hashtable(void)
unsigned short hashbits;
unsigned short hash;
size_t newalloc;
- symkey * sk;
+ auth_info * auth;
totalkeys = authnumkeys + (unsigned int)authnumfreekeys;
hashbits = auth_log2(totalkeys / 4.0) + 1;
@@ -238,135 +252,92 @@ auth_resize_hashtable(void)
key_hash = erealloc(key_hash, newalloc);
memset(key_hash, '\0', newalloc);
- ITER_DLIST_BEGIN(key_listhead, sk, llink, symkey)
- hash = KEYHASH(sk->keyid);
- LINK_SLIST(key_hash[hash], sk, hlink);
+ ITER_DLIST_BEGIN(key_listhead, auth, llink, auth_info)
+ hash = KEYHASH(auth->keyid);
+ LINK_SLIST(key_hash[hash], auth, hlink);
ITER_DLIST_END()
}
/*
- * allocsymkey - common code to allocate and link in symkey
- *
- * secret must be allocated with a free-compatible allocator. It is
- * owned by the referring symkey structure, and will be free()d by
- * freesymkey().
+ * alloc_auth_info - allocate and link in an auth_info slot.
+ * secret must be allocated with a free-compatible allocator.
+ * It is owned by the the new auth_info and will be free()d by
+ * free_auth_info().
*/
static void
-allocsymkey(
- symkey ** bucket,
- keyid_t id,
+alloc_auth_info(
+ auth_info ** bucket,
+ keyid_t keyid,
+ AUTH_Type type,
+ const char * name,
unsigned short flags,
- unsigned short type,
- unsigned short secretsize,
- uint8_t * secret
+ unsigned short key_size,
+ uint8_t * key
)
{
- symkey * sk;
+ auth_info * auth;
if (authnumfreekeys < 1)
auth_moremem(-1);
- UNLINK_HEAD_SLIST(sk, authfreekeys, llink.f);
+ UNLINK_HEAD_SLIST(auth, authfreekeys, llink.f);
//ENSURE(sk != NULL);
- sk->keyid = id;
- sk->flags = flags;
- sk->type = type;
- sk->secretsize = secretsize;
- sk->secret = secret;
- LINK_SLIST(*bucket, sk, hlink);
- LINK_TAIL_DLIST(key_listhead, sk, llink);
+ auth->keyid = keyid;
+ auth->type = type;
+ auth->flags = flags;
+ auth->key_size = key_size;
+ auth->key = key;
+ switch (type) {
+ case AUTH_NONE:
+ auth->digest = NULL;
+ auth->cipher = NULL;
+ break;
+ case AUTH_DIGEST:
+ auth->digest = EVP_get_digestbyname(name);
+ auth->cipher = NULL;
+ break;
+ case AUTH_CMAC:
+ auth->digest = NULL;
+ auth->cipher = EVP_get_cipherbyname(name);
+ break;
+ }
+ LINK_SLIST(*bucket, auth, hlink);
+ LINK_TAIL_DLIST(key_listhead, auth, llink);
authnumfreekeys--;
authnumkeys++;
}
/*
- * freesymkey - common code to remove a symkey and recycle its entry.
+ * free_auth_info - common code to remove a auth_info and recycle its entry.
*/
static void
-freesymkey(
- symkey * sk,
- symkey ** bucket
+free_auth_info(
+ auth_info * auth,
+ auth_info ** bucket
)
{
- symkey * unlinked;
+ auth_info * unlinked;
- if (sk->secret != NULL) {
- memset(sk->secret, '\0', sk->secretsize);
- free(sk->secret);
- sk->secret = NULL;
+ if (NULL != auth->key) {
+ memset(auth->key, '\0', auth->key_size);
+ free(auth->key);
+ auth->key = NULL;
}
- UNLINK_SLIST(unlinked, *bucket, sk, hlink, symkey);
+ UNLINK_SLIST(unlinked, *bucket, auth, hlink, auth_info);
//ENSURE(sk == unlinked);
- UNLINK_DLIST(sk, llink);
- memset((char *)sk + offsetof(symkey, symkey_payload), '\0',
- sizeof(*sk) - offsetof(symkey, symkey_payload));
- LINK_SLIST(authfreekeys, sk, llink.f);
+ UNLINK_DLIST(auth, llink);
+ memset((char *)auth + offsetof(auth_info, auth_info_payload), '\0',
+ sizeof(*auth) - offsetof(auth_info, auth_info_payload));
+ LINK_SLIST(authfreekeys, auth, llink.f);
authnumkeys--;
authnumfreekeys++;
}
/*
- * authhavekey - return true and cache the key, if zero or both known
- * and trusted.
- */
-bool
-authhavekey(
- keyid_t id
- )
-{
- symkey * sk;
-
- authkeylookups++;
- if (0 == id || cache_keyid == id) {
- return true;
- }
-
- /*
- * Search the bin for the key. If found and the key type
- * is zero, somebody marked it trusted without specifying
- * a key or key type. In this case consider the key missing.
- */
- authkeyuncached++;
- for (sk = key_hash[KEYHASH(id)]; sk != NULL; sk = sk->hlink) {
- if (id == sk->keyid) {
- if (0 == sk->type) {
- authkeynotfound++;
- return false;
- }
- break;
- }
- }
-
- /*
- * If the key is not found, or if it is found but not trusted,
- * the key is not considered found.
- */
- if (NULL == sk) {
- authkeynotfound++;
- return false;
- }
- if (!(KEY_TRUSTED & sk->flags)) {
- authnokey++;
- return false;
- }
-
- /*
- * The key is found and trusted. Initialize the key cache.
- */
- cache_keyid = sk->keyid;
- cache_type = sk->type;
- cache_flags = sk->flags;
- cache_secret = sk->secret;
- cache_secretsize = sk->secretsize;
-
- return true;
-}
-
-
-/*
* authtrust - declare a key to be trusted/untrusted
+ * untrusted case not used (except for test code) 2018-Jun
*/
void
authtrust(
@@ -374,19 +345,19 @@ authtrust(
bool trust
)
{
- symkey ** bucket;
- symkey * sk;
+ auth_info ** bucket;
+ auth_info * auth;
/*
* Search bin for key; if it does not exist and is untrusted,
* forget it.
*/
bucket = &key_hash[KEYHASH(id)];
- for (sk = *bucket; sk != NULL; sk = sk->hlink) {
- if (id == sk->keyid)
+ for (auth = *bucket; NULL != auth; auth = auth->hlink) {
+ if (id == auth->keyid)
break;
}
- if (!trust && NULL == sk)
+ if (!trust && NULL == auth)
return;
/*
@@ -394,88 +365,97 @@ authtrust(
* exist and is to be trusted or it does exist and is or is
* not to be trusted.
*/
- if (sk != NULL) {
- if (cache_keyid == id) {
- cache_flags = 0;
- cache_keyid = 0;
- }
-
- /*
- * Key exists. If it is to be trusted, say so.
- */
+ if (NULL != auth) {
+ /* Key exists. Leave it around so we can trust it again. */
if (trust) {
- sk->flags |= KEY_TRUSTED;
- return;
+ auth->flags |= KEY_TRUSTED;
+ } else {
+ auth->flags &= ~KEY_TRUSTED;
}
-
- /* No longer trusted, return it to the free list. */
- freesymkey(sk, bucket);
return;
+
}
- allocsymkey(bucket, id, KEY_TRUSTED, 0, 0, NULL);
+ /* Create empty slot to hold trusted flag. No key. */
+ alloc_auth_info(bucket, id, AUTH_NONE, 0, KEY_TRUSTED, 0, NULL);
}
-
/*
- * authistrusted - determine whether a key is trusted
+ * authlookup - find key, check trust
*/
-bool
-authistrusted(
- keyid_t keyno
- )
+auth_info *
+authlookup(
+ keyid_t keyno,
+ bool needtrust
+ )
{
- symkey * sk;
- symkey ** bucket;
-
- if (keyno == cache_keyid)
- return !!(KEY_TRUSTED & cache_flags);
+ auth_info * auth;
+ auth_info ** bucket;
- authkeyuncached++;
- bucket = &key_hash[KEYHASH(keyno)];
- for (sk = *bucket; sk != NULL; sk = sk->hlink) {
- if (keyno == sk->keyid)
- break;
- }
- if (NULL == sk || !(KEY_TRUSTED & sk->flags)) {
- authkeynotfound++;
- return false;
- }
- return true;
+ authkeylookups++;
+ bucket = &key_hash[KEYHASH(keyno)];
+ for (auth = *bucket; NULL != auth; auth = auth->hlink) {
+ if (keyno == auth->keyid)
+ break;
+ }
+ if (NULL == auth ||
+ (AUTH_NONE == auth->type) ||
+ (needtrust && !(KEY_TRUSTED & auth->flags))) {
+ if (0) msyslog(LOG_INFO, "DEBUG: authlookup fail: key %u, %s, auth: %s",
+ keyno, needtrust? "T" : "F",
+ (NULL == auth)? "NULL:" : "Ok");
+ authkeynotfound++;
+ return NULL;
+ }
+ return auth;
}
-
void
-mac_setkey(
+auth_setkey(
keyid_t keyno,
- int keytype,
+ AUTH_Type type,
+ const char * name,
const uint8_t *key,
- size_t len
+ size_t key_size
)
{
- symkey ** bucket;
- uint8_t * secret;
- size_t secretsize;
-
- //ENSURE(keytype <= USHRT_MAX);
+ auth_info ** bucket;
+ uint8_t * newkey;
+
+ if (0) msyslog(LOG_INFO, "DEBUG: auth_setkey: key %u, %s, length %zu",
+ keyno, name, key_size);
+
+ //ENSURE(??? <= USHRT_MAX);
//ENSURE(len < 4 * 1024);
/*
* See if we already have the key. If so just stick in the
* new value.
*/
bucket = &key_hash[KEYHASH(keyno)];
- for (symkey * sk = *bucket; sk != NULL; sk = sk->hlink) {
- if (keyno == sk->keyid) {
- sk->type = (unsigned short)keytype;
- secretsize = len;
- sk->secretsize = (unsigned short)secretsize;
- free(sk->secret);
- sk->secret = emalloc(secretsize);
- memcpy(sk->secret, key, secretsize);
- if (cache_keyid == keyno) {
- cache_flags = 0;
- cache_keyid = 0;
+ for (auth_info * auth = *bucket; NULL != auth; auth = auth->hlink) {
+ if (keyno == auth->keyid) {
+ auth->type = type;
+ switch (type) {
+ case AUTH_NONE:
+ auth->digest = NULL;
+ auth->cipher = NULL;
+ break;
+ case AUTH_DIGEST:
+ auth->digest = EVP_get_digestbyname(name);
+ auth->cipher = NULL;
+ break;
+ case AUTH_CMAC:
+ auth->digest = NULL;
+ auth->cipher = EVP_get_cipherbyname(name);
+ break;
+ }
+ if (NULL != auth->key) {
+ memset(auth->key, '\0', auth->key_size);
+ free(auth->key);
}
+ auth->key_size = (unsigned short)key_size;
+ auth->key = emalloc(key_size);
+ memcpy(auth->key, key, key_size);
return;
}
}
@@ -483,17 +463,16 @@ mac_setkey(
/*
* Need to allocate new structure. Do it.
*/
- secretsize = len;
- secret = emalloc(secretsize);
- memcpy(secret, key, secretsize);
- allocsymkey(bucket, keyno, 0, (unsigned short)keytype,
- (unsigned short)secretsize, secret);
+ newkey = emalloc(key_size);
+ memcpy(newkey, key, key_size);
+ alloc_auth_info(bucket, keyno, type, name, 0,
+ (unsigned short)key_size, newkey);
#ifdef DEBUG
if (debug >= 4) { /* SPECIAL DEBUG */
- printf("auth_setkey: key %d type %d len %d ", (int)keyno,
- keytype, (int)secretsize);
- for (size_t j = 0; j < secretsize; j++)
- printf("%02x", secret[j]);
+ printf("auth_setkey: key %d type %s len %d ", (int)keyno,
+ name, (int)key_size);
+ for (size_t j = 0; j < key_size; j++)
+ printf("%02x", key[j]);
printf("\n");
}
#endif
@@ -508,21 +487,24 @@ mac_setkey(
void
auth_delkeys(void)
{
- symkey * sk;
+ auth_info * auth;
- ITER_DLIST_BEGIN(key_listhead, sk, llink, symkey)
+ ITER_DLIST_BEGIN(key_listhead, auth, llink, auth_info)
/*
* Don't lose info as to which keys are trusted.
*/
- if (KEY_TRUSTED & sk->flags) {
- if (sk->secret != NULL) {
- memset(sk->secret, '\0', sk->secretsize);
- free(sk->secret);
- sk->secret = NULL;
+ if (KEY_TRUSTED & auth->flags) {
+ if (NULL != auth->key) {
+ memset(auth->key, '\0', auth->key_size);
+ free(auth->key);
+ auth->key = NULL;
}
- sk->secretsize = 0;
+ auth->key_size = 0;
+ auth->type = AUTH_NONE;
+ auth->digest = NULL;
+ auth->cipher = NULL;
} else {
- freesymkey(sk, &key_hash[KEYHASH(sk->keyid)]);
+ free_auth_info(auth, &key_hash[KEYHASH(auth->keyid)]);
}
ITER_DLIST_END()
}
@@ -530,60 +512,70 @@ auth_delkeys(void)
/*
* authencrypt - generate message authenticator
- *
- * Returns length of authenticator field, zero if key not found.
+ * fills in keyid in packet
+ * Returns length of authenticator field
*/
int
authencrypt(
- keyid_t keyno,
+ auth_info * auth, /* assumed setup correctly */
uint32_t * pkt,
int length
)
-{\
- /*
- * A zero key identifier means the sender has not verified
- * the last message was correctly authenticated. The MAC
- * consists of a single word with value zero.
+{
+
+ /* Pre-CMAC versions of this code had checking here.
+ * That logic has been pushed up a layer. 2018-June
*/
+
authencryptions++;
- pkt[length / 4] = htonl(keyno);
- if (0 == keyno) {
- return 4;
- }
- if (!authhavekey(keyno)) {
+ pkt[length / 4] = htonl(auth->keyid);
+ switch (auth->type) {
+ case AUTH_DIGEST:
+ authdigestencrypt++;
+ return digest_encrypt(auth, pkt, length);
+ case AUTH_CMAC:
+ authcmacencrypt++;
+ return cmac_encrypt(auth, pkt, length);
+ case AUTH_NONE:
return 0;
}
-
- return mac_authencrypt(cache_type,
- cache_secret, cache_secretsize,
- pkt, length);
+ return 0;
}
/*
* authdecrypt - verify message authenticator
*
- * Returns true if authenticator valid, false if invalid or not found.
+ * Returns true if authenticator valid.
*/
bool
authdecrypt(
- keyid_t keyno,
+ auth_info * auth, /* assumed setup correctly */
uint32_t * pkt,
int length,
int size
)
{
- /*
- * A zero key identifier means the sender has not verified
- * the last message was correctly authenticated. For our
- * purpose this is an invalid authenticator.
+ bool answer;
+
+ /* Pre-CMAC versions of this code had checking here.
+ * That logic has been pushed up a layer. 2018-June
*/
+
authdecryptions++;
- if (0 == keyno || !authhavekey(keyno) || size < 4) {
+ switch (auth->type) {
+ case AUTH_DIGEST:
+ authdigestdecrypt++;
+ answer = digest_decrypt(auth, pkt, length, size);
+ if (!answer) authdigestfail++;
+ return answer;
+ case AUTH_CMAC:
+ authcmacdecrypt++;
+ answer = cmac_decrypt(auth, pkt, length, size);
+ if (!answer) authcmacfail++;
+ return answer;
+ case AUTH_NONE:
return false;
}
-
- return mac_authdecrypt(cache_type,
- cache_secret, cache_secretsize,
- pkt, length, size);
+ return false;
}
=====================================
libntp/authreadkeys.c
=====================================
--- a/libntp/authreadkeys.c
+++ b/libntp/authreadkeys.c
@@ -9,10 +9,15 @@
#include "ntp_syslog.h"
#include "ntp_stdlib.h"
#include "lib_strbuf.h"
+#include "ntp_auth.h"
#include <openssl/objects.h>
#include <openssl/evp.h>
+#include <openssl/cmac.h>
+
+#define NAMEBUFSIZE 100
+
/* Forwards */
static char *nexttok (char **);
@@ -59,28 +64,155 @@ nexttok(
return starttok;
}
+static char*
+try_cmac(const char *upcased, char* namebuf) {
+ strncpy(namebuf, upcased, NAMEBUFSIZE);
+ if ((strcmp(namebuf, "AES") == 0) || (strcmp(namebuf, "AES128CMAC") == 0))
+ strncpy(namebuf, "AES-128", NAMEBUFSIZE);
+ strlcat(namebuf, "-CBC", NAMEBUFSIZE);
+ namebuf[NAMEBUFSIZE-1] = '\0';
+ if (0) msyslog(LOG_INFO, "DEBUG try_cmac: %s=>%s", upcased, namebuf);
+ if (EVP_get_cipherbyname(namebuf) == NULL)
+ return NULL;
+ return namebuf;
+}
+
+static char*
+try_digest(char *upcased, char *namebuf) {
+ strncpy(namebuf, upcased, NAMEBUFSIZE);
+ if (EVP_get_digestbyname(namebuf) != NULL)
+ return namebuf;
+ if ('M' == upcased[0]) {
+ /* hack for backward compatibility */
+ strncpy(namebuf, "MD5", NAMEBUFSIZE);
+ if (EVP_get_digestbyname(namebuf) != NULL)
+ return namebuf;
+ }
+ return NULL;
+}
static void
-check_digest_length(
+check_digest_mac_length(
keyid_t keyno,
- int keytype,
char *name) {
unsigned char digest[EVP_MAX_MD_SIZE];
unsigned int length = 0;
EVP_MD_CTX *ctx;
const EVP_MD *md;
- md = EVP_get_digestbynid(keytype);
+ md = EVP_get_digestbyname(name);
ctx = EVP_MD_CTX_create();
EVP_DigestInit_ex(ctx, md, NULL);
EVP_DigestFinal_ex(ctx, digest, &length);
EVP_MD_CTX_destroy(ctx);
- if (MAX_BARE_DIGEST_LENGTH < length) {
+ if (MAX_BARE_MAC_LENGTH < length) {
msyslog(LOG_ERR, "AUTH: authreadkeys: digest for key %u, %s will be truncated.", keyno, name);
}
}
+static void
+check_cmac_mac_length(
+ keyid_t keyno,
+ char *name) {
+ unsigned char mac[CMAC_MAX_MAC_LENGTH+1024];
+ size_t length = 0;
+ char key[EVP_MAX_KEY_LENGTH]; /* garbage is OK */
+ CMAC_CTX *ctx;
+ const EVP_CIPHER *cipher = EVP_get_cipherbyname(name);
+
+ ctx = CMAC_CTX_new();
+ CMAC_Init(ctx, key, EVP_CIPHER_key_length(cipher), cipher, NULL);
+ CMAC_Final(ctx, mac, &length);
+ CMAC_CTX_free(ctx);
+
+ /* CMAC_MAX_MAC_LENGTH isn't in API
+ * Check here to avoid buffer overrun in cmac_decrypt and cmac_encrypt
+ */
+ if (CMAC_MAX_MAC_LENGTH < length) {
+ msyslog(LOG_ERR,
+ "AUTH: authreadkeys: CMAC for key %u, %s is too big: %lu",
+ keyno, name, (long unsigned int)length);
+ exit(1);
+ }
+
+ if (MAX_BARE_MAC_LENGTH < length) {
+ msyslog(LOG_ERR, "AUTH: authreadkeys: CMAC for key %u, %s will be truncated.", keyno, name);
+ }
+}
+
+/* check_mac_length - Check for CMAC/digest too long.
+ * maybe should check for too short.
+ */
+static void
+check_mac_length(
+ keyid_t keyno,
+ AUTH_Type type,
+ char * name,
+ char *upcased) {
+ switch (type) {
+ case AUTH_CMAC:
+ check_cmac_mac_length(keyno, name);
+ break;
+ case AUTH_DIGEST:
+ check_digest_mac_length(keyno, name);
+ break;
+ case AUTH_NONE:
+ msyslog(LOG_ERR, "BUG: authreadkeys: unknown AUTH type for key %u, %s", keyno, upcased);
+ }
+}
+
+/* check_cmac_key_length: check and fix CMAC key length
+ * Ciphers require a specific key length.
+ * Truncate or pad if necessary.
+ * This may modify the key string - assumes enough storage.
+ * AES-128 is 128 bits or 16 bytes.
+ */
+static int
+check_cmac_key_length(
+ keyid_t keyno,
+ char *name,
+ char *key,
+ int keylength) {
+ const EVP_CIPHER *cipher = EVP_get_cipherbyname(name);
+ int len = EVP_CIPHER_key_length(cipher);
+ int i;
+
+ if (len < keylength) {
+ msyslog(LOG_ERR, "AUTH: CMAC key %u will be truncated %d=>%d",
+ keyno, keylength, len);
+ } else if ( len > keylength) {
+ msyslog(LOG_ERR, "AUTH: CMAC key %u will be padded %d=>%d",
+ keyno, keylength, len);
+ for (i=keylength; i<len; i++) key[i] = 0;
+ } else {
+ if (0) msyslog(LOG_ERR, "AUTH: CMAC key %u is right size", keyno);
+ }
+
+ return len;
+}
+
+static int
+check_key_length(
+ keyid_t keyno,
+ AUTH_Type type,
+ char *name,
+ char *key,
+ int keylength) {
+ int length = keylength;
+ switch (type) {
+ case AUTH_CMAC:
+ length = check_cmac_key_length(keyno, name, key, keylength);
+ break;
+ case AUTH_DIGEST:
+ /* any length key works */
+ break;
+ case AUTH_NONE:
+ msyslog(LOG_ERR, "BUG: authreadkeys: unknown AUTH type for key %u", keyno);
+ }
+ return length;
+}
+
/*
* authreadkeys - (re)read keys from a file.
@@ -93,9 +225,11 @@ authreadkeys(
FILE *fp;
char *line;
keyid_t keyno;
- int keytype;
+ AUTH_Type type;
char buf[512]; /* lots of room for line */
uint8_t keystr[32]; /* Bug 2537 */
+ char * name;
+ char namebuf[NAMEBUFSIZE];
size_t len;
size_t j;
int keys = 0;
@@ -120,7 +254,7 @@ msyslog(LOG_ERR, "AUTH: authreadkeys: reading %s", file);
/*
* Now read lines from the file, looking for key entries
*/
- while ((line = fgets(buf, sizeof buf, fp)) != NULL) {
+ while ((line = fgets(buf, sizeof(buf), fp)) != NULL) {
char *token = nexttok(&line);
if (token == NULL)
continue;
@@ -143,7 +277,7 @@ msyslog(LOG_ERR, "AUTH: authreadkeys: reading %s", file);
}
/*
- * Next is keytype. See if that is all right.
+ * Next is CMAC or Digest type. See if that is all right.
*/
token = nexttok(&line);
if (token == NULL) {
@@ -152,15 +286,24 @@ msyslog(LOG_ERR, "AUTH: authreadkeys: reading %s", file);
continue;
}
/*
- * The key type is the NID used by the message digest
- * algorithm. There are a number of inconsistencies in
- * the OpenSSL database. We attempt to discover them
- * here and prevent use of inconsistent data later.
+ * This area used to pass around nids.
+ * There is a bug in older versions of libcrypt
+ * where some nid routine doesn't work so AES
+ * didn't work on old CentOS, NetBSD, or FreeBSD.
+ * 2018-Jun-08
+ *
+ * OpenSSL names are capitalized.
+ * So uppercase the name before passing to
+ * EVP_get_digestbyname() or EVP_get_cipherbyname().
+ *
+ * AES is short for AES-128.
+ * CMAC names get "-CBC" appended.
+ * ntp classic uses AES128CMAC, so we support that too.
*
- * OpenSSL digest short names are capitalized, so uppercase the
- * digest name before passing to OBJ_sn2nid(). If it is not
- * recognized but begins with 'M' use NID_md5 to be consistent
- * with past behavior.
+ * If it is not recognized but begins with 'M' use
+ * NID_md5 digest to be consistent with past behavior.
+ * Try CMAC names first to dodge this hack in case future
+ * cipher names begin with M.
*/
char *upcased;
char *pch;
@@ -169,27 +312,31 @@ msyslog(LOG_ERR, "AUTH: authreadkeys: reading %s", file);
for (pch = upcased; '\0' != *pch; pch++)
*pch = (char)toupper((unsigned char)*pch);
- keytype = OBJ_sn2nid(upcased);
- if ((NID_undef == keytype) && ('M' == upcased[0]))
- keytype = NID_md5;
- if (NID_undef == keytype) {
- msyslog(LOG_ERR,
- "AUTH: authreadkeys: invalid type for key %u, %s",
- keyno, token);
- continue;
+ name = NULL;
+ if (NULL == name) {
+ name = try_cmac(upcased, namebuf);
+ if (NULL != name)
+ type = AUTH_CMAC;
}
- if (EVP_get_digestbynid(keytype) == NULL) {
+ if (NULL == name) {
+ name = try_digest(upcased, namebuf);
+ if (NULL != name)
+ type = AUTH_DIGEST;
+ }
+ if (NULL == name) {
msyslog(LOG_ERR,
- "AUTH: authreadkeys: no algorithm for key %u, %s",
+ "AUTH: authreadkeys: unknown auth type %u, %s",
keyno, token);
- continue;
- }
+ continue;
+ }
+
+
/*
- * Finally, get key and insert it. If it is longer than 20
- * characters, it is a binary string encoded in hex;
- * otherwise, it is a text string of printable ASCII
- * characters.
+ * Finally, get key and insert it.
+ * If it is longer than 20 characters, it is a binary
+ * string encoded in hex; otherwise, it is a text string
+ * of printable ASCII characters.
*/
token = nexttok(&line);
if (token == NULL) {
@@ -199,8 +346,9 @@ msyslog(LOG_ERR, "AUTH: authreadkeys: reading %s", file);
}
len = strlen(token);
if (len <= 20) { /* Bug 2537 */
- check_digest_length(keyno, keytype, upcased);
- mac_setkey(keyno, keytype, (uint8_t *)token, len);
+ len = check_key_length(keyno, type, name, upcased, len);
+ check_mac_length(keyno, type, name, upcased);
+ auth_setkey(keyno, type, name, (uint8_t *)token, len);
keys++;
} else {
char hex[] = "0123456789abcdef";
@@ -230,8 +378,10 @@ msyslog(LOG_ERR, "AUTH: authreadkeys: reading %s", file);
keyno);
continue;
}
- check_digest_length(keyno, keytype, upcased);
- mac_setkey(keyno, keytype, keystr, jlim / 2);
+ len = jlim / 2;
+ len = check_key_length(keyno, type, name, upcased, len);
+ check_mac_length(keyno, type, name, upcased);
+ auth_setkey(keyno, type, name, keystr, len);
keys++;
}
}
=====================================
libntp/macencrypt.c
=====================================
--- a/libntp/macencrypt.c
+++ b/libntp/macencrypt.c
@@ -1,5 +1,5 @@
/*
- * digest support for NTP
+ * CMAC and digest support for NTP
*/
#include "config.h"
@@ -13,6 +13,7 @@
#include "ntp_fp.h"
#include "ntp_stdlib.h"
+#include "ntp_auth.h"
#include "ntp.h"
#ifndef EVP_MD_CTX_reset
@@ -23,6 +24,7 @@
/* Need one per thread. */
extern EVP_MD_CTX *digest_ctx;
+extern CMAC_CTX *cmac_ctx;
/* ctmemeq - test two blocks memory for equality without leaking
* timing information.
@@ -47,15 +49,88 @@ static bool ctmemeq(const void *s1, const void *s2, size_t n) {
}
/*
- * mac_authencrypt - generate message digest
+ * cmac_encrypt - generate CMAC authenticator
*
* Returns length of MAC including key ID and digest.
*/
int
-mac_authencrypt(
- int type, /* hash algorithm */
- uint8_t *key, /* key pointer */
- int key_size, /* key size */
+cmac_encrypt(
+ auth_info* auth,
+ uint32_t *pkt, /* packet pointer */
+ int length /* packet length */
+ )
+{
+ uint8_t mac[CMAC_MAX_MAC_LENGTH];
+ size_t len;
+ CMAC_CTX *ctx = cmac_ctx;
+
+ CMAC_resume(ctx);
+ if (!CMAC_Init(ctx, auth->key, auth->key_size, auth->cipher, NULL)) {
+ /* Shouldn't happen. Does if wrong key_size. */
+ msyslog(LOG_ERR,
+ "CMAC: encrypt: CMAC init failed, %u, %u",
+ auth->keyid, auth->key_size);
+ return (0);
+ }
+ CMAC_Update(ctx, (uint8_t *)pkt, (unsigned int)length);
+ CMAC_Final(ctx, mac, &len);
+ if (MAX_BARE_MAC_LENGTH < len)
+ len = MAX_BARE_MAC_LENGTH;
+ memmove((uint8_t *)pkt + length + 4, mac, len);
+ return (int)(len + 4);
+}
+
+
+/*
+ * cmac_decrypt - verify CMAC authenticator
+ *
+ * Returns true if valid, false if invalid.
+ */
+bool
+cmac_decrypt(
+ auth_info* auth,
+ uint32_t *pkt, /* packet pointer */
+ int length, /* packet length */
+ int size /* MAC size */
+ )
+{
+ uint8_t mac[CMAC_MAX_MAC_LENGTH];
+ size_t len;
+ CMAC_CTX *ctx = cmac_ctx;
+
+ CMAC_resume(ctx);
+ if (!CMAC_Init(ctx, auth->key, auth->key_size, auth->cipher, NULL)) {
+ /* Shouldn't happen. Does if wrong key_size. */
+ msyslog(LOG_ERR,
+ "CMAC: decrypt: CMAC init failed, %u, %u",
+ auth->keyid, auth->key_size);
+ return false;
+ }
+ CMAC_Update(ctx, (uint8_t *)pkt, (unsigned int)length);
+ CMAC_Final(ctx, mac, &len);
+ if (MAX_BARE_MAC_LENGTH < len)
+ len = MAX_BARE_MAC_LENGTH;
+ if ((unsigned int)size != len + 4) {
+ /* Beware of DoS attack.
+ * This indicates either the sender is broken
+ * or some admin fatfingered things.
+ * Similar code at digest_decrypt.
+ */
+ if (0) msyslog(LOG_ERR,
+ "CMAC: decrypt: MAC length error");
+ return false;
+ }
+ return ctmemeq(mac, (char *)pkt + length + 4, len);
+}
+
+/*
+ * digest_encrypt - generate message digest
+ *
+ * Returns length of MAC including key ID and digest.
+ */
+int
+digest_encrypt(
+ auth_info* auth,
uint32_t *pkt, /* packet pointer */
int length /* packet length */
)
@@ -70,31 +145,29 @@ mac_authencrypt(
* was created.
*/
EVP_MD_CTX_reset(ctx);
- if (!EVP_DigestInit_ex(ctx, EVP_get_digestbynid(type), NULL)) {
+ if (!EVP_DigestInit_ex(ctx, auth->digest, NULL)) {
msyslog(LOG_ERR,
"MAC: encrypt: digest init failed");
return (0);
}
- EVP_DigestUpdate(ctx, key, key_size);
+ EVP_DigestUpdate(ctx, auth->key, auth->key_size);
EVP_DigestUpdate(ctx, (uint8_t *)pkt, (unsigned int)length);
EVP_DigestFinal_ex(ctx, digest, &len);
- if (MAX_BARE_DIGEST_LENGTH < len)
- len = MAX_BARE_DIGEST_LENGTH;
+ if (MAX_BARE_MAC_LENGTH < len)
+ len = MAX_BARE_MAC_LENGTH;
memmove((uint8_t *)pkt + length + 4, digest, len);
return (int)(len + 4);
}
/*
- * mac_authdecrypt - verify message authenticator
+ * digest_decrypt - verify message authenticator
*
* Returns true if digest valid, false if invalid.
*/
bool
-mac_authdecrypt(
- int type, /* hash algorithm */
- uint8_t *key, /* key pointer */
- int key_size, /* key size */
+digest_decrypt(
+ auth_info* auth,
uint32_t *pkt, /* packet pointer */
int length, /* packet length */
int size /* MAC size */
@@ -110,19 +183,24 @@ mac_authdecrypt(
* was created.
*/
EVP_MD_CTX_reset(ctx);
- if (!EVP_DigestInit_ex(ctx, EVP_get_digestbynid(type), NULL)) {
+ if (!EVP_DigestInit_ex(ctx, auth->digest, NULL)) {
msyslog(LOG_ERR,
"MAC: decrypt: digest init failed");
return false;
}
- EVP_DigestUpdate(ctx, key, key_size);
+ EVP_DigestUpdate(ctx, auth->key, auth->key_size);
EVP_DigestUpdate(ctx, (uint8_t *)pkt, (unsigned int)length);
EVP_DigestFinal_ex(ctx, digest, &len);
- if (MAX_BARE_DIGEST_LENGTH < len)
- len = MAX_BARE_DIGEST_LENGTH;
+ if (MAX_BARE_MAC_LENGTH < len)
+ len = MAX_BARE_MAC_LENGTH;
if ((unsigned int)size != len + 4) {
- msyslog(LOG_ERR,
- "MAC: decrypt: MAC length error");
+ /* Beware of DoS attack.
+ * This indicates either the sender is broken
+ * or some admin fatfingered things.
+ * Similar code at cmac_decrypt.
+ */
+ if (0) msyslog(LOG_ERR,
+ "ERR: decrypt: digest length error");
return false;
}
return ctmemeq(digest, (char *)pkt + length + 4, len);
=====================================
libntp/ssl_init.c
=====================================
--- a/libntp/ssl_init.c
+++ b/libntp/ssl_init.c
@@ -10,6 +10,7 @@
#include <stdbool.h>
#include <openssl/evp.h>
+#include <openssl/cmac.h>
#ifndef EVP_MD_CTX_new
/* Slightly older version of OpenSSL */
@@ -23,6 +24,7 @@ static void atexit_ssl_cleanup(void);
static bool ssl_init_done;
EVP_MD_CTX *digest_ctx;
+CMAC_CTX *cmac_ctx;
void
ssl_init(void)
@@ -32,10 +34,12 @@ ssl_init(void)
#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER)
OpenSSL_add_all_digests();
+ OpenSSL_add_all_ciphers();
atexit(&atexit_ssl_cleanup);
#endif
digest_ctx = EVP_MD_CTX_new();
+ cmac_ctx = CMAC_CTX_new();
ssl_init_done = true;
}
=====================================
ntpclients/ntpkeygen.py
=====================================
--- a/ntpclients/ntpkeygen.py
+++ b/ntpclients/ntpkeygen.py
@@ -10,9 +10,12 @@
# association maintained by soft links. Following is a list of file
# types.
#
-# ntpkey_MD5key_<hostname>.<filestamp>
-# MD5 (128-bit) keys used to compute message digests in symmetric
-# key cryptography
+# ntpkey_AES_<hostname>.<filestamp>
+# AES (128-bit) keys used to compute CMAC mode authentcation
+# using shared key cryptography
+
+# The file can be edited by hand to support MD5 and SHA1 for
+# old digest mode authentcation.
from __future__ import print_function
@@ -27,27 +30,27 @@ import stat
#
# Cryptodefines
#
-MD5KEYS = 10 # number of keys generated of each type
-MD5SIZE = 20 # maximum key size
+NUMKEYS = 10 # number of keys generated of each type
+KEYSIZE = 16 # maximum key size
-def gen_md5(id, groupname):
- "Generate semi-random MD5 and SHA1 keys compatible with NTPv3 and NTPv4."
- with fheader("MD5key", id, groupname) as wp:
- for i in range(1, MD5KEYS+1):
- md5key = ""
- for j in range(MD5SIZE):
+def gen_keys(id, groupname):
+ "Generate semi-random AES keys for versions of ntpd with CMAC support."
+ with fheader("AES", id, groupname) as wp:
+ for i in range(1, NUMKEYS+1):
+ key = ""
+ for j in range(KEYSIZE):
while True:
r = randomizer.randint(0x21, 0x7e)
if r != ord('#'):
break
- md5key += chr(r)
- wp.write("%2d MD5 %s\n" % (i, md5key))
- for i in range(1, MD5KEYS+1):
- sha1key = ""
- for j in range(MD5SIZE):
- sha1key += "%02x" % randomizer.randint(0x00, 0xff)
- wp.write("%2d SHA1 %s\n" % (i + MD5KEYS, sha1key))
+ key += chr(r)
+ wp.write("%2d AES %s\n" % (i, key))
+ for i in range(1, NUMKEYS+1):
+ key = ""
+ for j in range(KEYSIZE):
+ key += "%02x" % randomizer.randint(0x00, 0xff)
+ wp.write("%2d AES %s\n" % (i + NUMKEYS, key))
#
@@ -85,16 +88,16 @@ if __name__ == '__main__':
for (switch, val) in options:
if switch == '-M':
- # dummy MD5 option for backwards compatibility
+ # dummy MD5 option for backwards compatibility, ignored
pass
elif switch in ("-h", "--help"):
- print("usage: ntpkeygen [-M]")
+ print("usage: ntpkeygen")
raise SystemExit(0)
# The seed is ignored by random.SystemRandom,
# even though the docs do not say so.
randomizer = random.SystemRandom()
- gen_md5("md5", socket.gethostname())
+ gen_keys("AES", socket.gethostname())
raise SystemExit(0)
# end
=====================================
ntpclients/ntpq.py
=====================================
--- a/ntpclients/ntpq.py
+++ b/ntpclients/ntpq.py
@@ -1405,6 +1405,8 @@ function: show ntpd access control list
usage: reslist
""")
+# FIXME: This table should move to ntpd
+# so the answers track when ntpd is updated
def do_sysinfo(self, _line):
"display system summary"
sysinfo = (
@@ -1431,6 +1433,8 @@ function: display system summary
usage: sysinfo
""")
+# FIXME: This table should move to ntpd
+# so the answers track when ntpd is updated
def do_kerninfo(self, _line):
"display kernel loop and PPS statistics"
kerninfo = (
@@ -1459,6 +1463,8 @@ function: display kernel loop and PPS statistics
usage: kerninfo
""")
+# FIXME: This table should move to ntpd
+# so the answers track when ntpd is updated
def do_sysstats(self, _line):
"display system uptime and packet counts"
sysstats = (
@@ -1484,6 +1490,8 @@ function: display system uptime and packet counts
usage: sysstats
""")
+# FIXME: This table should move to ntpd
+# so the answers track when ntpd is updated
def do_monstats(self, _line):
"display monitor (mrulist) counters and limits"
monstats = (
@@ -1511,18 +1519,28 @@ function: display monitor (mrulist) counters and limits
usage: monstats
""")
+# FIXME: This table should move to ntpd
+# so the answers track when ntpd is updated
def do_authinfo(self, _line):
"display symmetric authentication counters"
authinfo = (
- ("authreset", "time since reset:", NTP_INT),
- ("authkeys", "stored keys: ", NTP_INT),
- ("authfreek", "free keys: ", NTP_INT),
- ("authklookups", "key lookups: ", NTP_INT),
- ("authknotfound", "keys not found: ", NTP_INT),
- ("authkuncached", "uncached keys: ", NTP_INT),
- ("authkexpired", "expired keys: ", NTP_INT),
- ("authencrypts", "encryptions: ", NTP_INT),
- ("authdecrypts", "decryptions: ", NTP_INT),
+ ("authreset", "time since reset: ", NTP_INT),
+ ("authkeys", "stored keys: ", NTP_INT),
+ ("authfreek", "free keys: ", NTP_INT),
+ ("authklookups", "key lookups: ", NTP_INT),
+ ("authknotfound", "keys not found: ", NTP_INT),
+ ("authencrypts", "encryptions: ", NTP_INT),
+ ("authdigestencrypts", "digest encryptions: ", NTP_INT),
+ ("authcmacencrypts", "CMAC encryptions: ", NTP_INT),
+ ("authdecrypts", "decryptions: ", NTP_INT),
+ ("authdigestdecrypts", "digest decryptions: ", NTP_INT),
+ ("authdigestfails", "digest failures: ", NTP_INT),
+ ("authcmacdecrypts", "CMAC decryptions: ", NTP_INT),
+ ("authcmacfails", "CMAC failures: ", NTP_INT),
+ # Old variables no longer supported.
+ # Interesting if looking at an old system.
+ ("authkuncached", "uncached keys: ", NTP_INT),
+ ("authkexpired", "expired keys: ", NTP_INT),
)
self.collect_display(associd=0, variables=authinfo, decodestatus=False)
@@ -1532,6 +1550,8 @@ function: display symmetric authentication counters
usage: authinfo
""")
+# FIXME: This table should move to ntpd
+# so the answers track when ntpd is updated
def do_iostats(self, _line):
"display network input and output counters"
iostats = (
@@ -1556,6 +1576,8 @@ function: display network input and output counters
usage: iostats
""")
+# FIXME: This table should move to ntpd
+# so the answers track when ntpd is updated
def do_timerstats(self, line):
"display interval timer counters"
timerstats = (
=====================================
ntpd/ntp_config.c
=====================================
--- a/ntpd/ntp_config.c
+++ b/ntpd/ntp_config.c
@@ -33,6 +33,7 @@
#include "lib_strbuf.h"
#include "ntp_assert.h"
#include "ntp_dns.h"
+#include "ntp_auth.h"
/*
* [Classic Bug 467]: Some linux headers collide with CONFIG_PHONE and
@@ -1135,7 +1136,7 @@ config_auth(
}
if (0 < count)
msyslog(LOG_INFO, "Found %d trusted keys.", count);
- auth_prealloc_symkeys(count);
+ auth_prealloc(count);
/* Keys Command */
if (ptree->auth.keys)
@@ -2897,7 +2898,7 @@ config_reset_counters(
break;
case T_Auth:
- reset_auth_stats();
+ auth_reset_stats(current_time);
break;
case T_Ctl:
=====================================
ntpd/ntp_control.c
=====================================
--- a/ntpd/ntp_control.c
+++ b/ntpd/ntp_control.c
@@ -25,6 +25,7 @@
#include "ntp_leapsec.h"
#include "lib_strbuf.h"
#include "ntp_syscall.h"
+#include "ntp_auth.h"
#include "timespecops.h"
/* undefine to suppress random tags and get fixed emission order */
@@ -174,9 +175,9 @@ static const struct ctl_proc control_codes[] = {
#define CS_AUTHFREEK 49
#define CS_AUTHKLOOKUPS 50
#define CS_AUTHKNOTFOUND 51
-#define CS_AUTHKUNCACHED 52
-#define CS_AUTHKEXPIRED 53
-#define CS_AUTHENCRYPTS 54
+#define CS_AUTHENCRYPTS 52
+#define CS_AUTHDIGESTENCRYPT 53
+#define CS_AUTHCMACENCRYPT 54
#define CS_AUTHDECRYPTS 55
#define CS_AUTHRESET 56
#define CS_K_OFFSET 57
@@ -225,7 +226,11 @@ static const struct ctl_proc control_codes[] = {
#define CS_TICK 98
#define CS_NUMCTLREQ 99
#define CS_ROOTDISTANCE 100
-#define CS_MAXCODE CS_ROOTDISTANCE
+#define CS_AUTHDIGESTDECRYPT 101
+#define CS_AUTHDIGESTFAIL 102
+#define CS_AUTHCMACDECRYPT 103
+#define CS_AUTHCMACFAIL 104
+#define CS_MAXCODE CS_AUTHCMACFAIL
/*
* Peer variables we understand
@@ -357,9 +362,9 @@ static const struct ctl_var sys_var[] = {
{ CS_AUTHFREEK, RO, "authfreek" }, /* 49 */
{ CS_AUTHKLOOKUPS, RO, "authklookups" }, /* 50 */
{ CS_AUTHKNOTFOUND, RO, "authknotfound" }, /* 51 */
- { CS_AUTHKUNCACHED, RO, "authkuncached" }, /* 52 */
- { CS_AUTHKEXPIRED, RO, "authkexpired" }, /* 53 */
- { CS_AUTHENCRYPTS, RO, "authencrypts" }, /* 54 */
+ { CS_AUTHENCRYPTS, RO, "authencrypts" }, /* 52 */
+ { CS_AUTHDIGESTENCRYPT, RO, "authdigestencrypts" }, /* 53 */
+ { CS_AUTHCMACENCRYPT, RO, "authcmacencrypts" }, /* 54 */
{ CS_AUTHDECRYPTS, RO, "authdecrypts" }, /* 55 */
{ CS_AUTHRESET, RO, "authreset" }, /* 56 */
{ CS_K_OFFSET, RO, "koffset" }, /* 57 */
@@ -407,6 +412,10 @@ static const struct ctl_var sys_var[] = {
/* new in NTPsec */
{ CS_NUMCTLREQ, RO, "ss_numctlreq" }, /* 99 */
{ CS_ROOTDISTANCE, RO, "rootdist" }, /* 100 */
+ { CS_AUTHDIGESTDECRYPT, RO, "authdigestdecrypts" }, /* 101 */
+ { CS_AUTHDIGESTFAIL, RO, "authdigestfails" }, /* 102 */
+ { CS_AUTHCMACDECRYPT, RO, "authcmacdecrypts" }, /* 103 */
+ { CS_AUTHCMACFAIL, RO, "authcmacfails" }, /* 104 */
{ 0, EOV, "" }
};
@@ -611,13 +620,6 @@ static struct utsname utsnamebuf;
keyid_t ctl_auth_keyid;
/*
- * * A hack. To keep the authentication module clear of ntp-ism's, we
- * * include a time reset variable for its stats here.
- * */
-static unsigned long auth_timereset;
-
-
-/*
* We keep track of the last error reported by the system internally
*/
static uint8_t ctl_sys_last_event;
@@ -663,9 +665,7 @@ static bool datanotbinflag;
static sockaddr_u *rmt_addr;
static endpt *lcl_inter;
-static bool res_authenticate;
-static bool res_authokay;
-static keyid_t res_keyid;
+static auth_info* res_auth; /* !NULL => authenticate */
#define MAXDATALINELEN (72)
@@ -754,8 +754,8 @@ ctl_error(
/*
* send packet and bump counters
*/
- if (res_authenticate) {
- maclen = authencrypt(res_keyid, (uint32_t *)&rpkt,
+ if (NULL != res_auth) {
+ maclen = authencrypt(res_auth, (uint32_t *)&rpkt,
CTL_HEADER_LEN);
sendpkt(rmt_addr, lcl_inter, &rpkt,
(int)CTL_HEADER_LEN + maclen);
@@ -833,9 +833,7 @@ process_control(
res_frags = 1;
res_offset = 0;
res_associd = ntohs(pkt->associd);
- res_authenticate = false;
- res_keyid = 0;
- res_authokay = false;
+ res_auth = NULL;
req_count = (int)ntohs(pkt->count);
datanotbinflag = false;
datalinelen = 0;
@@ -864,22 +862,22 @@ process_control(
maclen = rbufp->recv_length - (size_t)properlen;
if ((rbufp->recv_length & 3) == 0 &&
maclen >= MIN_MAC_LEN && maclen <= MAX_MAC_LEN) {
- res_authenticate = true;
+ keyid_t keyid;
pkid = (void *)((char *)pkt + properlen);
- res_keyid = ntohl(*pkid);
+ keyid = ntohl(*pkid);
DPRINT(3, ("recv_len %zu, properlen %d, wants auth with keyid %08x, MAC length=%zu\n",
- rbufp->recv_length, properlen, res_keyid,
+ rbufp->recv_length, properlen, keyid,
maclen));
- if (!authistrusted(res_keyid))
- DPRINT(3, ("invalid keyid %08x\n", res_keyid));
- else if (authdecrypt(res_keyid, (uint32_t *)pkt,
+ res_auth = authlookup(keyid, true); // FIXME
+ if (NULL == res_auth)
+ DPRINT(3, ("invalid keyid %08x\n", keyid));
+ else if (authdecrypt(res_auth, (uint32_t *)pkt,
(int)rbufp->recv_length - (int)maclen,
(int)maclen)) {
- res_authokay = true;
DPRINT(3, ("authenticated okay\n"));
} else {
- res_keyid = 0;
+ res_auth = NULL;
DPRINT(3, ("authentication failed\n"));
}
}
@@ -898,8 +896,8 @@ process_control(
DPRINT(3, ("opcode %d, found command handler\n",
res_opcode));
if (cc->flags == AUTH
- && (!res_authokay
- || res_keyid != ctl_auth_keyid)) {
+ && (NULL == res_auth
+ || res_auth->keyid != ctl_auth_keyid)) {
ctl_error(CERR_PERMISSION);
return;
}
@@ -993,7 +991,6 @@ ctl_flushpkt(
int sendlen;
int maclen;
int totlen;
- keyid_t keyid;
dlen = datapt - rpkt.data;
if (!more && datanotbinflag && dlen + 2 < CTL_MAX_DATA_LEN) {
@@ -1029,7 +1026,8 @@ ctl_flushpkt(
(res_opcode & CTL_OP_MASK);
rpkt.count = htons((unsigned short)dlen);
rpkt.offset = htons((unsigned short)res_offset);
- if (res_authenticate) {
+ if (NULL != res_auth) {
+ keyid_t keyid;
totlen = sendlen;
/*
* If we are going to authenticate, then there
@@ -1039,9 +1037,9 @@ ctl_flushpkt(
while (totlen & 7) {
totlen++;
}
- keyid = htonl(res_keyid);
+ keyid = htonl(res_auth->keyid);
memcpy(datapt, &keyid, sizeof(keyid));
- maclen = authencrypt(res_keyid,
+ maclen = authencrypt(res_auth,
(uint32_t *)&rpkt, totlen);
sendpkt(rmt_addr, lcl_inter, &rpkt, totlen + maclen);
} else {
@@ -1833,23 +1831,38 @@ ctl_putsys(
ctl_putuint(sys_var[varid].text, authkeynotfound);
break;
- case CS_AUTHKUNCACHED:
- ctl_putuint(sys_var[varid].text, authkeyuncached);
+ case CS_AUTHENCRYPTS:
+ ctl_putuint(sys_var[varid].text, authencryptions);
break;
- case CS_AUTHKEXPIRED:
- /* historical relic - autokey used to expire keys */
- ctl_putuint(sys_var[varid].text, 0);
+ case CS_AUTHDIGESTENCRYPT:
+ ctl_putuint(sys_var[varid].text, authdigestencrypt);
break;
- case CS_AUTHENCRYPTS:
- ctl_putuint(sys_var[varid].text, authencryptions);
+ case CS_AUTHCMACENCRYPT:
+ ctl_putuint(sys_var[varid].text, authcmacencrypt);
break;
case CS_AUTHDECRYPTS:
ctl_putuint(sys_var[varid].text, authdecryptions);
break;
+ case CS_AUTHDIGESTDECRYPT:
+ ctl_putuint(sys_var[varid].text, authdigestdecrypt);
+ break;
+
+ case CS_AUTHDIGESTFAIL:
+ ctl_putuint(sys_var[varid].text, authdigestfail);
+ break;
+
+ case CS_AUTHCMACDECRYPT:
+ ctl_putuint(sys_var[varid].text, authcmacdecrypt);
+ break;
+
+ case CS_AUTHCMACFAIL:
+ ctl_putuint(sys_var[varid].text, authcmacfail);
+ break;
+
case CS_AUTHRESET:
ctl_putuint(sys_var[varid].text,
current_time - auth_timereset);
@@ -2698,7 +2711,7 @@ read_status(
return;
}
rpkt.status = htons(ctlpeerstatus(peer));
- if (res_authokay)
+ if (NULL != res_auth) /* FIXME: what's this for? */
peer->num_events = 0;
/*
* For now, output everything we know about the
@@ -2751,7 +2764,7 @@ read_peervars(void)
return;
}
rpkt.status = htons(ctlpeerstatus(peer));
- if (res_authokay)
+ if (NULL != res_auth) /* FIXME: What's this for?? */
peer->num_events = 0;
ZERO(wants);
gotvar = false;
@@ -2796,7 +2809,7 @@ read_sysvars(void)
* and give them to him.
*/
rpkt.status = htons(ctlsysstatus());
- if (res_authokay)
+ if (NULL != res_auth) /* FIXME: what's this for?? */
ctl_sys_num_events = 0;
wants_count = CS_MAXCODE + 1 + count_var(ext_sys_var);
wants = emalloc_zero(wants_count);
@@ -4460,21 +4473,3 @@ free_varlist(
}
}
-
-/* from ntp_request.c when ntpdc was nuked */
-
-/*
- * * reset_auth_stats - reset the authentication stat counters. Done here
- * * to keep ntp-isms out of the authentication module
- * */
-void
-reset_auth_stats(void)
-{
- authkeylookups = 0;
- authkeynotfound = 0;
- authencryptions = 0;
- authdecryptions = 0;
- authkeyuncached = 0;
- auth_timereset = current_time;
-}
-
=====================================
ntpd/ntp_peer.c
=====================================
--- a/ntpd/ntp_peer.c
+++ b/ntpd/ntp_peer.c
@@ -9,6 +9,7 @@
#include "ntpd.h"
#include "ntp_lists.h"
#include "ntp_stdlib.h"
+#include "ntp_auth.h"
/*
@@ -553,7 +554,13 @@ newpeer(
)
{
struct peer * peer;
- unsigned int hash;
+ unsigned int hash;
+ const char * name; /* for error messages */
+
+ if (NULL != hostname)
+ name = hostname;
+ else
+ name = socktoa(srcadr);
/*
* First search from the beginning for an association with given
@@ -589,10 +596,7 @@ newpeer(
* associations.
*/
if (peer != NULL) {
- DPRINT(2, ("newpeer(%s) found existing association\n",
- (hostname)
- ? hostname
- : socktoa(srcadr)));
+ DPRINT(2, ("newpeer(%s) found existing association\n", name));
return NULL;
}
@@ -656,6 +660,16 @@ newpeer(
if ((MDF_BCAST & cast_flags) && peer->dstadr != NULL)
enable_broadcast(peer->dstadr, srcadr);
+ /* if a key specified, verify that it will work */
+ if (0 != peer->cfg.peerkey) {
+ if (NULL == authlookup(peer->cfg.peerkey, false))
+ msyslog(LOG_ERR, "ERR: key %u not found for server %s",
+ peer->cfg.peerkey, name);
+ else if (NULL == authlookup(peer->cfg.peerkey, true))
+ msyslog(LOG_ERR, "ERR: key %u found but not trusted for server %s",
+ peer->cfg.peerkey, name);
+ }
+
peer->precision = sys_vars.sys_precision;
peer->hpoll = peer->cfg.minpoll;
if (cast_flags & MDF_POOL)
=====================================
ntpd/ntp_proto.c
=====================================
--- a/ntpd/ntp_proto.c
+++ b/ntpd/ntp_proto.c
@@ -9,6 +9,7 @@
#include "ntp_stdlib.h"
#include "ntp_leapsec.h"
#include "ntp_dns.h"
+#include "ntp_auth.h"
#include "timespecops.h"
#include <string.h>
@@ -154,7 +155,7 @@ double measured_tick; /* non-overridable sys_tick (s) */
static void clock_combine (peer_select *, int, int);
static void clock_select (void);
static void clock_update (struct peer *);
-static void fast_xmit (struct recvbuf *, int, keyid_t, int);
+static void fast_xmit (struct recvbuf *, int, auth_info*, int);
static int local_refid (struct peer *);
static void measure_precision(const bool);
static double measure_tick_fuzz(void);
@@ -344,7 +345,7 @@ parse_packet(
rbufp->mac_len = 0;
break;
case 20:
- /* MD5 authenticator */
+ /* AES-128 CMAC, MD5 digest */
if(PKT_VERSION(pkt->li_vn_mode) < 3) {
/* Only allowed as of NTPv3 */
goto fail;
@@ -354,7 +355,7 @@ parse_packet(
rbufp->mac_len = 16;
break;
case 24:
- /* SHA-1 authenticator */
+ /* SHA-1 digest */
if(PKT_VERSION(pkt->li_vn_mode) < 3) {
/* Only allowed as of NTPv3 */
goto fail;
@@ -407,20 +408,13 @@ parse_packet(
static bool
i_require_authentication(
struct peer const* peer,
- struct recvbuf const* rbufp,
unsigned short restrict_mask
)
{
bool restrict_notrust = restrict_mask & RES_DONTTRUST;
bool peer_has_key = peer != NULL && peer->cfg.peerkey != 0;
- bool wants_association =
- PKT_MODE(rbufp->pkt.li_vn_mode) == MODE_BROADCAST ||
- (peer == NULL && PKT_MODE(rbufp->pkt.li_vn_mode == MODE_ACTIVE));
- bool restrict_nopeer =
- (restrict_mask & RES_NOPEER) &&
- wants_association;
-
- return restrict_notrust || peer_has_key || restrict_nopeer;
+
+ return restrict_notrust || peer_has_key;
}
static bool
@@ -462,32 +456,12 @@ static void
handle_fastxmit(
struct recvbuf *rbufp,
unsigned short restrict_mask,
- bool request_already_authenticated
+ auth_info* auth
)
{
- uint32_t xkeyid;
-
- if (rbufp->dstadr->flags & INT_MCASTOPEN) {
- stat_count.sys_restricted++;
- }
-
- /* To prevent exposing an authentication oracle, only MAC
- the response if the request passed authentication.
- */
- if(request_already_authenticated ||
- (rbufp->keyid_present &&
- authdecrypt(rbufp->keyid,
- (uint32_t*)rbufp->recv_space.X_recv_buffer,
- (int)(rbufp->recv_length - (rbufp->mac_len + 4)),
- (int)(rbufp->mac_len + 4)))) {
- xkeyid = rbufp->keyid;
- } else {
- xkeyid = 0;
- }
-
int xmode =
PKT_MODE(rbufp->pkt.li_vn_mode) == MODE_ACTIVE ? MODE_PASSIVE : MODE_SERVER;
- fast_xmit(rbufp, xmode, xkeyid, restrict_mask);
+ fast_xmit(rbufp, xmode, auth, restrict_mask);
}
static void
@@ -668,7 +642,7 @@ receive(
{
struct peer *peer = NULL;
unsigned short restrict_mask;
- bool authenticated = false;
+ auth_info* auth = NULL; /* !NULL if authenticated */
stat_count.sys_received++;
@@ -738,7 +712,16 @@ receive(
}
}
- if(i_require_authentication(peer, rbufp, restrict_mask)) {
+ if(i_require_authentication(peer, restrict_mask) ||
+ /* He wants authentication */
+ rbufp->keyid_present) {
+ auth = authlookup(rbufp->keyid, true);
+ if (0) msyslog(LOG_INFO, "DEBUG: receive: key %u %s%s, length %d, %s",
+ rbufp->keyid,
+ (NULL == auth)? "N" : "A",
+ (NULL == peer)? "N" : "P",
+ rbufp->mac_len, socktoa(&rbufp->recv_srcadr) );
+ // FIXME: crypto-NAK?
if(
/* Check whether an authenticator is even present. */
!rbufp->keyid_present || is_crypto_nak(rbufp) ||
@@ -746,12 +729,13 @@ receive(
check that it matches. */
(peer != NULL && peer->cfg.peerkey != 0 &&
peer->cfg.peerkey != rbufp->keyid) ||
+ (auth == NULL) ||
/* Verify the MAC.
TODO: rewrite authdecrypt() to give it a
better name and a saner interface so we don't
have to do this screwy buffer-length
arithmetic in order to call it. */
- !authdecrypt(rbufp->keyid,
+ !authdecrypt(auth,
(uint32_t*)rbufp->recv_space.X_recv_buffer,
(int)(rbufp->recv_length - (rbufp->mac_len + 4)),
(int)(rbufp->mac_len + 4))) {
@@ -762,8 +746,6 @@ receive(
peer->flash |= BOGON5;
}
goto done;
- } else {
- authenticated = true;
}
}
@@ -775,7 +757,7 @@ receive(
switch (PKT_MODE(rbufp->pkt.li_vn_mode)) {
case MODE_ACTIVE: /* remote site using "peer" in config file */
case MODE_CLIENT: /* Request for us as a server. */
- handle_fastxmit(rbufp, restrict_mask, authenticated);
+ handle_fastxmit(rbufp, restrict_mask, auth);
stat_count.sys_processed++;
break;
case MODE_SERVER: /* Reply to our request. */
@@ -2088,7 +2070,7 @@ peer_xmit(
{
struct pkt xpkt; /* transmit packet */
size_t sendlen, authlen;
- keyid_t xkeyid = 0; /* transmit key ID */
+ auth_info *auth; /* !NULL for authentication */
l_fp xmt_tx;
if (!peer->dstadr) /* drop peers without interface */
@@ -2107,9 +2089,8 @@ peer_xmit(
xpkt.rec = htonl_fp(peer->dst);
/*
- * If the received packet contains a MAC, the transmitted packet
- * is authenticated and contains a MAC. If not, the transmitted
- * packet is not authenticated.
+ * If the peer (aka server) was configured with a key authenticate
+ * the packet. Else, the packet is not authenticated.
*/
sendlen = LEN_PKT_NOMAC;
if (peer->cfg.peerkey == 0) {
@@ -2144,14 +2125,14 @@ peer_xmit(
get_systime(&xmt_tx);
peer->org = xmt_tx;
xpkt.xmt = htonl_fp(xmt_tx);
- xkeyid = peer->cfg.peerkey;
- authlen = (size_t)authencrypt(xkeyid, (uint32_t *)&xpkt, (int)sendlen);
- if (authlen == 0) {
+ auth = authlookup(peer->cfg.peerkey, true);
+ if (NULL == auth) {
report_event(PEVNT_AUTH, peer, "no key");
peer->flash |= BOGON5; /* auth error */
peer->badauth++;
return;
}
+ authlen = (size_t)authencrypt(auth, (uint32_t *)&xpkt, (int)sendlen);
sendlen += authlen;
if (sendlen > sizeof(xpkt)) {
msyslog(LOG_ERR, "PROTO: buffer overflow %zu", sendlen);
@@ -2165,7 +2146,8 @@ peer_xmit(
DPRINT(1, ("transmit: at %u %s->%s mode %d keyid %08x len %zu\n",
current_time, peer->dstadr ?
socktoa(&peer->dstadr->sin) : "-",
- socktoa(&peer->srcadr), peer->hmode, xkeyid, sendlen));
+ socktoa(&peer->srcadr), peer->hmode,
+ peer->cfg.peerkey, sendlen));
}
@@ -2187,7 +2169,7 @@ static void
fast_xmit(
struct recvbuf *rbufp, /* receive packet pointer */
int xmode, /* receive mode */
- keyid_t xkeyid, /* transmit key ID */
+ auth_info *auth, /* !NULL for authentication */
int flags /* restrict mask */
)
{
@@ -2295,7 +2277,9 @@ fast_xmit(
#ifdef ENABLE_MSSNTP
if (flags & RES_MSSNTP) {
- send_via_ntp_signd(rbufp, xmode, xkeyid, flags, &xpkt);
+ keyid_t keyid = 0;
+ if (NULL != auth) keyid = auth->keyid;
+ send_via_ntp_signd(rbufp, xmode, keyid, flags, &xpkt);
return;
}
#endif /* ENABLE_MSSNTP */
@@ -2306,7 +2290,7 @@ fast_xmit(
* packet is not authenticated.
*/
sendlen = LEN_PKT_NOMAC;
- if (rbufp->recv_length == sendlen) {
+ if (NULL == auth) {
sendpkt(&rbufp->recv_srcadr, rbufp->dstadr, &xpkt, (int)sendlen);
DPRINT(1, ("transmit: at %u %s->%s mode %d len %zu\n",
current_time, socktoa(&rbufp->dstadr->sin),
@@ -2321,14 +2305,14 @@ fast_xmit(
* cryptosum.
*/
get_systime(&xmt_tx);
- sendlen += (size_t)authencrypt(xkeyid, (uint32_t *)&xpkt, (int)sendlen);
+ sendlen += (size_t)authencrypt(auth, (uint32_t *)&xpkt, (int)sendlen);
sendpkt(&rbufp->recv_srcadr, rbufp->dstadr, &xpkt, (int)sendlen);
get_systime(&xmt_ty);
xmt_ty -= xmt_tx;
sys_authdelay = xmt_ty;
DPRINT(1, ("transmit: at %u %s->%s mode %d keyid %08x len %zu\n",
current_time, socktoa(&rbufp->dstadr->sin),
- socktoa(&rbufp->recv_srcadr), xmode, xkeyid, sendlen));
+ socktoa(&rbufp->recv_srcadr), xmode, auth->keyid, sendlen));
}
=====================================
ntpd/ntp_util.c
=====================================
--- a/ntpd/ntp_util.c
+++ b/ntpd/ntp_util.c
@@ -11,6 +11,7 @@
#include "ntp_leapsec.h"
#include "ntp_stdlib.h"
#include "ntp_stdlib.h"
+#include "ntp_auth.h"
#include "ntpd.h"
#include "timespecops.h"
=====================================
ntpd/ntpd.c
=====================================
--- a/ntpd/ntpd.c
+++ b/ntpd/ntpd.c
@@ -12,6 +12,7 @@
#include "ntp_config.h"
#include "ntp_syslog.h"
#include "ntp_assert.h"
+#include "ntp_auth.h"
#ifdef ENABLE_DNS_LOOKUP
#include "ntp_dns.h"
#endif
@@ -663,7 +664,7 @@ ntpdmain(
* Exactly what command-line options are we expecting here?
*/
ssl_init();
- init_auth();
+ auth_init();
init_util();
init_restrict();
init_mon();
=====================================
tests/common/tests_main.c
=====================================
--- a/tests/common/tests_main.c
+++ b/tests/common/tests_main.c
@@ -78,7 +78,7 @@ static void RunAllTests(void)
int main(int argc, const char * argv[]) {
ssl_init();
- init_auth();
+ auth_init();
init_network();
args_argc = argc;
=====================================
tests/common/tests_main.h
=====================================
--- a/tests/common/tests_main.h
+++ b/tests/common/tests_main.h
@@ -7,6 +7,7 @@
#include "unity_fixture.h"
#include "ntp_stdlib.h"
+#include "ntp_auth.h"
const char* tests_main_args(int arg);
=====================================
tests/libntp/authkeys.c
=====================================
--- a/tests/libntp/authkeys.c
+++ b/tests/libntp/authkeys.c
@@ -1,5 +1,6 @@
#include "config.h"
#include "ntp_stdlib.h"
+#include "ntp_auth.h"
#include "unity.h"
#include "unity_fixture.h"
@@ -26,19 +27,15 @@ TEST_TEAR_DOWN(authkeys) {}
-static const int KEYTYPE = NID_md5;
-
-
static void AddTrustedKey(keyid_t);
static void AddUntrustedKey(keyid_t);
static void AddTrustedKey(keyid_t keyno) {
/*
- * We need to add a MD5-key in addition to setting the
- * trust, because authhavekey() requires type != 0.
+ * We need to add a type and key in addition to setting the
+ * trust, because authlookup() requires type != AUTH_NONE.
*/
- mac_setkey(keyno, KEYTYPE, NULL, 0);
-
+ auth_setkey(keyno, AUTH_DIGEST, "MD5", NULL, 0);
authtrust(keyno, true);
}
@@ -54,16 +51,24 @@ TEST(authkeys, AddTrustedKeys) {
AddTrustedKey(KEYNO1);
AddTrustedKey(KEYNO2);
- TEST_ASSERT_TRUE(authistrusted(KEYNO1));
- TEST_ASSERT_TRUE(authistrusted(KEYNO2));
+ TEST_ASSERT_NOT_NULL(authlookup(KEYNO1, true));
+ TEST_ASSERT_NOT_NULL(authlookup(KEYNO1, false));
+ TEST_ASSERT_NOT_NULL(authlookup(KEYNO2, true));
+ TEST_ASSERT_NOT_NULL(authlookup(KEYNO2, false));
}
TEST(authkeys, AddUntrustedKey) {
const keyid_t KEYNO = 3;
- AddUntrustedKey(KEYNO);
+ AddUntrustedKey(KEYNO); /* gets type of AUTH_NULL */
+
+ TEST_ASSERT_NULL(authlookup(KEYNO, true));
+ TEST_ASSERT_NULL(authlookup(KEYNO, false));
+
+ auth_setkey(KEYNO, AUTH_DIGEST, "MD5", NULL, 0);
- TEST_ASSERT_FALSE(authistrusted(KEYNO));
+ TEST_ASSERT_NULL(authlookup(KEYNO, true));
+ TEST_ASSERT_NOT_NULL(authlookup(KEYNO, false));
}
TEST(authkeys, HaveKeyCorrect) {
@@ -71,13 +76,15 @@ TEST(authkeys, HaveKeyCorrect) {
AddTrustedKey(KEYNO);
- TEST_ASSERT_TRUE(authhavekey(KEYNO));
+ TEST_ASSERT_NOT_NULL(authlookup(KEYNO, true));
+ TEST_ASSERT_NOT_NULL(authlookup(KEYNO, false));
}
TEST(authkeys, HaveKeyIncorrect) {
const keyid_t KEYNO = 2;
- TEST_ASSERT_FALSE(authhavekey(KEYNO));
+ TEST_ASSERT_NULL(authlookup(KEYNO, true));
+ TEST_ASSERT_NULL(authlookup(KEYNO, false));
}
TEST_GROUP_RUNNER(authkeys) {
=====================================
tests/libntp/macencrypt.c
=====================================
--- a/tests/libntp/macencrypt.c
+++ b/tests/libntp/macencrypt.c
@@ -1,5 +1,6 @@
#include "config.h"
#include "ntp_stdlib.h"
+#include "ntp_auth.h"
#include "unity.h"
#include "unity_fixture.h"
@@ -17,45 +18,112 @@ TEST_TEAR_DOWN(macencrypt) {}
/*
* Example packet with MD5 hash calculated manually.
*/
-const int keytype = NID_md5;
-char key[] = "abcdefgh";
-const unsigned short keyLength = 8;
+char MD5key[] = "abcdefgh";
+char CMACkey[] = "0123456789abcdef"; /* AES-128 needs 16 bytes */
const char *packet = "ijklmnopqrstuvwx";
const int packetLength = 16;
const int keyIdLength = 4;
const int digestLength = 16;
const int totalLength = 36; //error: initializer element is not constant packetLength + keyIdLength + digestLength;
-char expectedPacket[] = "ijklmnopqrstuvwx\0\0\0\0\x0c\x0e\x84\xcf\x0b\xb7\xa8\x68\x8e\x52\x38\xdb\xbc\x1c\x39\x53";
+char expectedMD5Packet[] = "ijklmnopqrstuvwx\0\0\0\0\x0c\x0e\x84\xcf\x0b\xb7\xa8\x68\x8e\x52\x38\xdb\xbc\x1c\x39\x53";
+char expectedCMACPacket[] = "ijklmnopqrstuvwx\0\0\0\0\xb0\xa1\xcf\xd2\x7f\x69\x0c\x43\xa7\x5d\x6c\x55\x91\x4b\x15\x14";
+
+auth_info auth;
TEST(macencrypt, Encrypt) {
- char *packetPtr[totalLength];
+ char packetPtr[totalLength];
+ memset(packetPtr+packetLength, 0, (size_t)keyIdLength);
+ memcpy(packetPtr, packet, (size_t)packetLength);
+
+ /* FIXME: Initialization stuff. Needed by other digest tests. */
+ auth.keyid = 123;
+ auth.type = AUTH_DIGEST;
+ auth.digest = EVP_get_digestbyname("MD5");
+ auth.cipher = NULL;
+ auth.key = (uint8_t *)MD5key;
+ auth.key_size = (unsigned short)strlen(MD5key);
+
+ TEST_ASSERT_NOT_NULL(auth.digest);
+
+ int length = digest_encrypt(&auth,
+ (uint32_t*)packetPtr, packetLength);
+
+ TEST_ASSERT_EQUAL(4+16, length);
+
+ TEST_ASSERT_TRUE(digest_decrypt(&auth,
+ (uint32_t*)packetPtr, packetLength, length));
+
+if (0) {
+ int i;
+ printf("\n");
+ for (i = 0; i< totalLength; i++)
+ printf("%02x ", (unsigned int)expectedMD5Packet[i] & 0xff);
+ printf("\n");
+ for (i = 0; i< totalLength; i++)
+ printf("%02x ", (unsigned int)packetPtr[i] & 0xff);
+ printf("\n");
+}
+ TEST_ASSERT_TRUE(memcmp(expectedMD5Packet, packetPtr, totalLength) == 0);
+
+}
+TEST(macencrypt, CMAC_Encrypt) {
+ char packetPtr[totalLength];
memset(packetPtr+packetLength, 0, (size_t)keyIdLength);
memcpy(packetPtr, packet, (size_t)packetLength);
- int length = mac_authencrypt(keytype,
- (unsigned char*)key, keyLength,
+ /* FIXME: Initialization stuff. Needed by other CMAC tests. */
+ auth.keyid = 1234;
+ auth.type = AUTH_CMAC;
+ auth.digest = NULL;
+ auth.cipher = EVP_get_cipherbyname("AES-128-CBC");
+ auth.key = (uint8_t *)CMACkey;
+ auth.key_size = (unsigned short)strlen(CMACkey);
+
+ TEST_ASSERT_NOT_NULL(auth.cipher);
+
+ int length = cmac_encrypt(&auth,
(uint32_t*)packetPtr, packetLength);
- TEST_ASSERT_TRUE(mac_authdecrypt(keytype,
- (unsigned char*)key, keyLength,
+ TEST_ASSERT_EQUAL(4+16, length); /* AES-128 */
+
+ TEST_ASSERT_TRUE(cmac_decrypt(&auth,
(uint32_t*)packetPtr, packetLength, length));
- TEST_ASSERT_EQUAL(20, length);
-//XXX TEST_ASSERT_TRUE(memcmp(expectedPacket, packetPtr, totalLength) == 0); Does not pass
+if (0) {
+ int i;
+ printf("\n");
+ for (i = 0; i< totalLength; i++)
+ printf("%02x ", (unsigned int)expectedCMACPacket[i] & 0xff);
+ printf("\n");
+ for (i = 0; i< totalLength; i++)
+ printf("%02x ", (unsigned int)packetPtr[i] & 0xff);
+ printf("\n");
+}
+ TEST_ASSERT_TRUE(memcmp(expectedCMACPacket, packetPtr, totalLength) == 0);
}
TEST(macencrypt, DecryptValid) {
- TEST_ASSERT_TRUE(mac_authdecrypt(keytype,
- (unsigned char*)key, keyLength,
- (uint32_t*)expectedPacket, packetLength, 20));
+ TEST_ASSERT_TRUE(digest_decrypt(&auth,
+ (uint32_t*)expectedMD5Packet, packetLength, 20));
+}
+
+TEST(macencrypt, DecryptValidCMAC) {
+ TEST_ASSERT_TRUE(cmac_decrypt(&auth,
+ (uint32_t*)expectedCMACPacket, packetLength, 20));
}
TEST(macencrypt, DecryptInvalid) {
char invalidPacket[] = "ijklmnopqrstuvwx\0\0\0\0\x0c\x0e\x84\xcf\x0b\xb7\xa8\x68\x8e\x52\x38\xdb\xbc\x1c\x39\x54";
- TEST_ASSERT_FALSE(mac_authdecrypt(keytype,
- (unsigned char*)key, keyLength,
+ TEST_ASSERT_FALSE(digest_decrypt(&auth,
+ (uint32_t*)invalidPacket, packetLength, 20));
+}
+
+TEST(macencrypt, DecryptInvalidCMAC) {
+ char invalidPacket[] = "ijklmnopqrstuvwx\0\0\0\0\x0c\x0e\x84\xcf\x0b\xb7\xa8\x68\x8e\x52\x38\xdb\xbc\x1c\x39\x54";
+
+ TEST_ASSERT_FALSE(cmac_decrypt(&auth,
(uint32_t*)invalidPacket, packetLength, 20));
}
@@ -72,9 +140,9 @@ TEST(macencrypt, IPv4AddressToRefId) {
TEST(macencrypt, IPv6AddressToRefId) {
const struct in6_addr address = {{{
0x20, 0x01, 0x0d, 0xb8,
- 0x85, 0xa3, 0x08, 0xd3,
- 0x13, 0x19, 0x8a, 0x2e,
- 0x03, 0x70, 0x73, 0x34
+ 0x85, 0xa3, 0x08, 0xd3,
+ 0x13, 0x19, 0x8a, 0x2e,
+ 0x03, 0x70, 0x73, 0x34
}}};
@@ -87,10 +155,17 @@ TEST(macencrypt, IPv6AddressToRefId) {
TEST_ASSERT_EQUAL(expected, addr2refid(&addr));
}
+/* Both digest and CMAC tests share some global variables
+ * that get setup by Encrypt or CMAC_Encrypt
+ * Thus the tests must be run in the right order.
+ */
TEST_GROUP_RUNNER(macencrypt) {
RUN_TEST_CASE(macencrypt, Encrypt);
RUN_TEST_CASE(macencrypt, DecryptValid);
RUN_TEST_CASE(macencrypt, DecryptInvalid);
+ RUN_TEST_CASE(macencrypt, CMAC_Encrypt);
+ RUN_TEST_CASE(macencrypt, DecryptValidCMAC);
+ RUN_TEST_CASE(macencrypt, DecryptInvalidCMAC);
RUN_TEST_CASE(macencrypt, IPv4AddressToRefId);
RUN_TEST_CASE(macencrypt, IPv6AddressToRefId);
}
=====================================
wscript
=====================================
--- a/wscript
+++ b/wscript
@@ -609,8 +609,9 @@ int main(int argc, char **argv) {
):
openssl_headers = (
"openssl/evp.h",
- "openssl/rand.h",
+ "openssl/cmac.h",
"openssl/objects.h",
+ "openssl/rand.h",
)
for hdr in openssl_headers:
ctx.check_cc(header_name=hdr, includes=ctx.env.PLATFORM_INCLUDES)
@@ -661,6 +662,13 @@ int main(int argc, char **argv) {
ctx.check_cc(header_name="stdbool.h", mandatory=True,
comment="Sanity check.")
+ # Very old versions of OpenSSL don't have cmac support.
+ # This gives a sane(er) error message.
+ # It would be possible to make CMAC support optional by adding
+ # appropriate #ifdefs to the code.
+ ctx.check_cc(header_name="openssl/cmac.h", mandatory=True,
+ comment="Sanity check.")
+
# This is a list of every optional include header in the
# codebase that is guarded by a directly corresponding HAVE_*_H symbol.
#
View it on GitLab: https://gitlab.com/NTPsec/ntpsec/compare/84660a737666758363bb536396e6eff90ea529d5...a04100c3f004e643ba4911fce3cf7a031531a2c9
--
View it on GitLab: https://gitlab.com/NTPsec/ntpsec/compare/84660a737666758363bb536396e6eff90ea529d5...a04100c3f004e643ba4911fce3cf7a031531a2c9
You're receiving this email because of your account on gitlab.com.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.ntpsec.org/pipermail/vc/attachments/20180613/ad6ea0e6/attachment.html>
More information about the vc
mailing list