keys: add support for CMAC keys

Allow a cipher (AES128 or AES256) to be specified as the type of a key
in the key file to authenticate NTP packets with a CMAC instead of the
NTPv4 (RFC 5905) MAC using a hash function. This follows RFC 8573.
This commit is contained in:
Miroslav Lichvar 2019-09-17 16:59:55 +02:00
parent e8069a0179
commit 57957ab6cf
4 changed files with 127 additions and 56 deletions

View file

@ -261,7 +261,7 @@ CPS_SplitWord(char *line)
/* ================================================== */ /* ================================================== */
int int
CPS_ParseKey(char *line, uint32_t *id, const char **hash, char **key) CPS_ParseKey(char *line, uint32_t *id, const char **type, char **key)
{ {
char *s1, *s2, *s3, *s4; char *s1, *s2, *s3, *s4;
@ -278,10 +278,10 @@ CPS_ParseKey(char *line, uint32_t *id, const char **hash, char **key)
return 0; return 0;
if (*s3) { if (*s3) {
*hash = s2; *type = s2;
*key = s3; *key = s3;
} else { } else {
*hash = "MD5"; *type = "MD5";
*key = s2; *key = s2;
} }

View file

@ -49,6 +49,6 @@ extern void CPS_NormalizeLine(char *line);
extern char *CPS_SplitWord(char *line); extern char *CPS_SplitWord(char *line);
/* Parse a key from keyfile */ /* Parse a key from keyfile */
extern int CPS_ParseKey(char *line, uint32_t *id, const char **hash, char **key); extern int CPS_ParseKey(char *line, uint32_t *id, const char **type, char **key);
#endif /* GOT_CMDPARSE_H */ #endif /* GOT_CMDPARSE_H */

View file

@ -96,7 +96,7 @@ interval in order to allow a burst with two requests.
*key* _ID_::: *key* _ID_:::
The NTP protocol supports a message authentication code (MAC) to prevent The NTP protocol supports a message authentication code (MAC) to prevent
computers having their system time upset by rogue packets being sent to them. computers having their system time upset by rogue packets being sent to them.
The MAC is generated as a function of a password specified in the key file, The MAC is generated as a function of a key specified in the key file,
which is specified by the <<keyfile,*keyfile*>> directive. which is specified by the <<keyfile,*keyfile*>> directive.
+ +
The *key* option specifies which key (with an ID in the range 1 through 2^32-1) The *key* option specifies which key (with an ID in the range 1 through 2^32-1)
@ -2017,8 +2017,10 @@ include @SYSCONFDIR@/chrony.d/*.conf
---- ----
[[keyfile]]*keyfile* _file_:: [[keyfile]]*keyfile* _file_::
This directive is used to specify the location of the file containing ID-key This directive is used to specify the location of the file containing symmetric
pairs for authentication of NTP packets. keys which are shared between NTP servers and clients, or peers, in order to
authenticate NTP packets with a message authentication code (MAC) using a
cryptographic hash function or cipher.
+ +
The format of the directive is shown in the example below: The format of the directive is shown in the example below:
+ +
@ -2033,30 +2035,41 @@ format of the file is shown below:
10 tulip 10 tulip
11 hyacinth 11 hyacinth
20 MD5 ASCII:crocus 20 MD5 ASCII:crocus
25 SHA1 HEX:1dc764e0791b11fa67efc7ecbc4b0d73f68a070c 25 SHA1 HEX:933F62BE1D604E68A81B557F18CFA200483F5B70
30 AES128 HEX:7EA62AE64D190114D46D5A082F948EC1
31 AES256 HEX:37DDCBC67BB902BCB8E995977FAB4D2B5642F5B32EBCEEE421921D97E5CBFE39
... ...
---- ----
+ +
Each line consists of an ID, name of an authentication hash function (optional), Each line consists of an ID, optional type, and key.
and a password. The ID can be any unsigned integer in the range 1 through
2^32-1. The default hash function is *MD5*, which is always supported.
+ +
The ID can be any positive integer in the range 1 through 2^32-1.
+
The type is a name of a cryptographic hash function or cipher which is used to
generate and verify the MAC. The default type is *MD5*, which is always
supported.
If *chronyd* was built with enabled support for hashing using a crypto library If *chronyd* was built with enabled support for hashing using a crypto library
(nettle, nss, or libtomcrypt), the following functions are available: *MD5*, (nettle, nss, or libtomcrypt), the following functions are available: *MD5*,
*SHA1*, *SHA256*, *SHA384*, *SHA512*. Depending on which library and version is *SHA1*, *SHA256*, *SHA384*, *SHA512*. Depending on which library and version is
*chronyd* using, some or all of the following functions may also be available: *chronyd* using, some of the following hash functions and ciphers may
*SHA3-224*, *SHA3-256*, *SHA3-384*, *SHA3-512*, *TIGER*, *WHIRLPOOL*. also be available:
*SHA3-224*, *SHA3-256*, *SHA3-384*, *SHA3-512*, *TIGER*, *WHIRLPOOL*, *AES128*,
*AES256*.
+ +
The password can be specified as a string of characters not containing white The key can be specified as a string of ASCII characters not containing white
space with an optional *ASCII:* prefix, or as a hexadecimal number with the space with an optional *ASCII:* prefix, or as a hexadecimal number with the
*HEX:* prefix. The maximum length of the line is 2047 characters. *HEX:* prefix. The maximum length of the line is 2047 characters.
If the type is a cipher, the length of the key must match the cipher (i.e. 128
bits for AES128 and 256 bits for AES256).
+ +
The password is used with the hash function to generate and verify a message It is recommended to use randomly generated keys, specified in the hexadecimal
authentication code (MAC) in NTP packets. It is recommended to use SHA1, or format, which are at least 128 bits long (i.e. they have at least 32 characters
stronger, hash function with random passwords specified in the hexadecimal after the *HEX:* prefix). *chronyd* will log a warning to syslog on start if a
format that have at least 128 bits. *chronyd* will log a warning to source is specified in the configuration file with a key shorter than 80 bits.
syslog on start if a source is specified in the configuration file with a key +
that has password shorter than 80 bits. The recommended key types are AES ciphers and SHA3 hash functions. MD5 should
be avoided unless no other type is supported on the server and client, or
peers.
+ +
The <<chronyc.adoc#keygen,*keygen*>> command of *chronyc* can be used to The <<chronyc.adoc#keygen,*keygen*>> command of *chronyc* can be used to
generate random keys for the key file. By default, it generates 160-bit MD5 or generate random keys for the key file. By default, it generates 160-bit MD5 or

130
keys.c
View file

@ -32,6 +32,7 @@
#include "array.h" #include "array.h"
#include "keys.h" #include "keys.h"
#include "cmac.h"
#include "cmdparse.h" #include "cmdparse.h"
#include "conf.h" #include "conf.h"
#include "memory.h" #include "memory.h"
@ -42,11 +43,22 @@
/* Consider 80 bits as the absolute minimum for a secure key */ /* Consider 80 bits as the absolute minimum for a secure key */
#define MIN_SECURE_KEY_LENGTH 10 #define MIN_SECURE_KEY_LENGTH 10
typedef enum {
NTP_MAC,
CMAC,
} KeyClass;
typedef struct { typedef struct {
uint32_t id; uint32_t id;
char *val; KeyClass class;
int len; union {
int hash_id; struct {
unsigned char *value;
int length;
int hash_id;
} ntp_mac;
CMC_Instance cmac;
} data;
int auth_delay; int auth_delay;
} Key; } Key;
@ -62,9 +74,21 @@ static void
free_keys(void) free_keys(void)
{ {
unsigned int i; unsigned int i;
Key *key;
for (i = 0; i < ARR_GetSize(keys); i++) for (i = 0; i < ARR_GetSize(keys); i++) {
Free(((Key *)ARR_GetElement(keys, i))->val); key = ARR_GetElement(keys, i);
switch (key->class) {
case NTP_MAC:
Free(key->data.ntp_mac.value);
break;
case CMAC:
CMC_DestroyInstance(key->data.cmac);
break;
default:
assert(0);
}
}
ARR_SetSize(keys, 0); ARR_SetSize(keys, 0);
cache_valid = 0; cache_valid = 0;
@ -129,10 +153,10 @@ determine_hash_delay(uint32_t key_id)
} }
/* ================================================== */ /* ================================================== */
/* Decode password encoded in ASCII or HEX */ /* Decode key encoded in ASCII or HEX */
static int static int
decode_password(char *key) decode_key(char *key)
{ {
int i, j, len = strlen(key); int i, j, len = strlen(key);
char buf[3], *p; char buf[3], *p;
@ -184,11 +208,11 @@ compare_keys_by_id(const void *a, const void *b)
void void
KEY_Reload(void) KEY_Reload(void)
{ {
unsigned int i, line_number; unsigned int i, line_number, key_length, cmac_key_length;
FILE *in; FILE *in;
uint32_t key_id; char line[2048], *key_file, *key_value;
char line[2048], *keyval, *key_file; const char *key_type;
const char *hashname; int hash_id;
Key key; Key key;
free_keys(); free_keys();
@ -212,26 +236,43 @@ KEY_Reload(void)
if (!*line) if (!*line)
continue; continue;
if (!CPS_ParseKey(line, &key_id, &hashname, &keyval)) { memset(&key, 0, sizeof (key));
if (!CPS_ParseKey(line, &key.id, &key_type, &key_value)) {
LOG(LOGS_WARN, "Could not parse key at line %u in file %s", line_number, key_file); LOG(LOGS_WARN, "Could not parse key at line %u in file %s", line_number, key_file);
continue; continue;
} }
key.hash_id = HSH_GetHashId(hashname); key_length = decode_key(key_value);
if (key.hash_id < 0) { if (key_length == 0) {
LOG(LOGS_WARN, "Unknown hash function in key %"PRIu32, key_id); LOG(LOGS_WARN, "Could not decode key %"PRIu32, key.id);
continue; continue;
} }
key.len = decode_password(keyval); hash_id = HSH_GetHashId(key_type);
if (!key.len) { cmac_key_length = CMC_GetKeyLength(key_type);
LOG(LOGS_WARN, "Could not decode password in key %"PRIu32, key_id);
if (hash_id >= 0) {
key.class = NTP_MAC;
key.data.ntp_mac.value = MallocArray(unsigned char, key_length);
memcpy(key.data.ntp_mac.value, key_value, key_length);
key.data.ntp_mac.length = key_length;
key.data.ntp_mac.hash_id = hash_id;
} else if (cmac_key_length > 0) {
if (cmac_key_length != key_length) {
LOG(LOGS_WARN, "Invalid length of %s key %"PRIu32" (expected %u bits)",
key_type, key.id, 8 * cmac_key_length);
continue;
}
key.class = CMAC;
key.data.cmac = CMC_CreateInstance(key_type, (unsigned char *)key_value, key_length);
assert(key.data.cmac);
} else {
LOG(LOGS_WARN, "Unknown hash function or cipher in key %"PRIu32, key.id);
continue; continue;
} }
key.id = key_id;
key.val = MallocArray(char, key.len);
memcpy(key.val, keyval, key.len);
ARR_AppendElement(keys, &key); ARR_AppendElement(keys, &key);
} }
@ -334,7 +375,15 @@ KEY_GetAuthLength(uint32_t key_id)
if (!key) if (!key)
return 0; return 0;
return HSH_Hash(key->hash_id, buf, 0, buf, 0, buf, sizeof (buf)); switch (key->class) {
case NTP_MAC:
return HSH_Hash(key->data.ntp_mac.hash_id, buf, 0, buf, 0, buf, sizeof (buf));
case CMAC:
return CMC_Hash(key->data.cmac, buf, 0, buf, sizeof (buf));
default:
assert(0);
return 0;
}
} }
/* ================================================== */ /* ================================================== */
@ -349,30 +398,41 @@ KEY_CheckKeyLength(uint32_t key_id)
if (!key) if (!key)
return 0; return 0;
return key->len >= MIN_SECURE_KEY_LENGTH; switch (key->class) {
case NTP_MAC:
return key->data.ntp_mac.length >= MIN_SECURE_KEY_LENGTH;
default:
return 1;
}
} }
/* ================================================== */ /* ================================================== */
static int static int
generate_ntp_auth(int hash_id, const unsigned char *key, int key_len, generate_auth(Key *key, const unsigned char *data, int data_len,
const unsigned char *data, int data_len, unsigned char *auth, int auth_len)
unsigned char *auth, int auth_len)
{ {
return HSH_Hash(hash_id, key, key_len, data, data_len, auth, auth_len); switch (key->class) {
case NTP_MAC:
return HSH_Hash(key->data.ntp_mac.hash_id, key->data.ntp_mac.value,
key->data.ntp_mac.length, data, data_len, auth, auth_len);
case CMAC:
return CMC_Hash(key->data.cmac, data, data_len, auth, auth_len);
default:
return 0;
}
} }
/* ================================================== */ /* ================================================== */
static int static int
check_ntp_auth(int hash_id, const unsigned char *key, int key_len, check_auth(Key *key, const unsigned char *data, int data_len,
const unsigned char *data, int data_len, const unsigned char *auth, int auth_len, int trunc_len)
const unsigned char *auth, int auth_len, int trunc_len)
{ {
unsigned char buf[MAX_HASH_LENGTH]; unsigned char buf[MAX_HASH_LENGTH];
int hash_len; int hash_len;
hash_len = generate_ntp_auth(hash_id, key, key_len, data, data_len, buf, sizeof (buf)); hash_len = generate_auth(key, data, data_len, buf, sizeof (buf));
return MIN(hash_len, trunc_len) == auth_len && !memcmp(buf, auth, auth_len); return MIN(hash_len, trunc_len) == auth_len && !memcmp(buf, auth, auth_len);
} }
@ -381,7 +441,7 @@ check_ntp_auth(int hash_id, const unsigned char *key, int key_len,
int int
KEY_GenerateAuth(uint32_t key_id, const unsigned char *data, int data_len, KEY_GenerateAuth(uint32_t key_id, const unsigned char *data, int data_len,
unsigned char *auth, int auth_len) unsigned char *auth, int auth_len)
{ {
Key *key; Key *key;
@ -390,8 +450,7 @@ KEY_GenerateAuth(uint32_t key_id, const unsigned char *data, int data_len,
if (!key) if (!key)
return 0; return 0;
return generate_ntp_auth(key->hash_id, (unsigned char *)key->val, key->len, return generate_auth(key, data, data_len, auth, auth_len);
data, data_len, auth, auth_len);
} }
/* ================================================== */ /* ================================================== */
@ -407,6 +466,5 @@ KEY_CheckAuth(uint32_t key_id, const unsigned char *data, int data_len,
if (!key) if (!key)
return 0; return 0;
return check_ntp_auth(key->hash_id, (unsigned char *)key->val, key->len, return check_auth(key, data, data_len, auth, auth_len, trunc_len);
data, data_len, auth, auth_len, trunc_len);
} }