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:
parent
e8069a0179
commit
57957ab6cf
4 changed files with 127 additions and 56 deletions
|
@ -261,7 +261,7 @@ CPS_SplitWord(char *line)
|
|||
/* ================================================== */
|
||||
|
||||
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;
|
||||
|
||||
|
@ -278,10 +278,10 @@ CPS_ParseKey(char *line, uint32_t *id, const char **hash, char **key)
|
|||
return 0;
|
||||
|
||||
if (*s3) {
|
||||
*hash = s2;
|
||||
*type = s2;
|
||||
*key = s3;
|
||||
} else {
|
||||
*hash = "MD5";
|
||||
*type = "MD5";
|
||||
*key = s2;
|
||||
}
|
||||
|
||||
|
|
|
@ -49,6 +49,6 @@ extern void CPS_NormalizeLine(char *line);
|
|||
extern char *CPS_SplitWord(char *line);
|
||||
|
||||
/* 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 */
|
||||
|
|
|
@ -96,7 +96,7 @@ interval in order to allow a burst with two requests.
|
|||
*key* _ID_:::
|
||||
The NTP protocol supports a message authentication code (MAC) to prevent
|
||||
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.
|
||||
+
|
||||
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_::
|
||||
This directive is used to specify the location of the file containing ID-key
|
||||
pairs for authentication of NTP packets.
|
||||
This directive is used to specify the location of the file containing symmetric
|
||||
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:
|
||||
+
|
||||
|
@ -2033,30 +2035,41 @@ format of the file is shown below:
|
|||
10 tulip
|
||||
11 hyacinth
|
||||
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),
|
||||
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.
|
||||
Each line consists of an ID, optional type, and key.
|
||||
+
|
||||
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
|
||||
(nettle, nss, or libtomcrypt), the following functions are available: *MD5*,
|
||||
*SHA1*, *SHA256*, *SHA384*, *SHA512*. Depending on which library and version is
|
||||
*chronyd* using, some or all of the following functions may also be available:
|
||||
*SHA3-224*, *SHA3-256*, *SHA3-384*, *SHA3-512*, *TIGER*, *WHIRLPOOL*.
|
||||
*chronyd* using, some of the following hash functions and ciphers may
|
||||
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
|
||||
*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
|
||||
authentication code (MAC) in NTP packets. It is recommended to use SHA1, or
|
||||
stronger, hash function with random passwords specified in the hexadecimal
|
||||
format that have at least 128 bits. *chronyd* will log a warning to
|
||||
syslog on start if a source is specified in the configuration file with a key
|
||||
that has password shorter than 80 bits.
|
||||
It is recommended to use randomly generated keys, specified in the hexadecimal
|
||||
format, which are at least 128 bits long (i.e. they have at least 32 characters
|
||||
after the *HEX:* prefix). *chronyd* will log a warning to syslog on start if a
|
||||
source is specified in the configuration file with a key 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
|
||||
generate random keys for the key file. By default, it generates 160-bit MD5 or
|
||||
|
|
122
keys.c
122
keys.c
|
@ -32,6 +32,7 @@
|
|||
|
||||
#include "array.h"
|
||||
#include "keys.h"
|
||||
#include "cmac.h"
|
||||
#include "cmdparse.h"
|
||||
#include "conf.h"
|
||||
#include "memory.h"
|
||||
|
@ -42,11 +43,22 @@
|
|||
/* Consider 80 bits as the absolute minimum for a secure key */
|
||||
#define MIN_SECURE_KEY_LENGTH 10
|
||||
|
||||
typedef enum {
|
||||
NTP_MAC,
|
||||
CMAC,
|
||||
} KeyClass;
|
||||
|
||||
typedef struct {
|
||||
uint32_t id;
|
||||
char *val;
|
||||
int len;
|
||||
KeyClass class;
|
||||
union {
|
||||
struct {
|
||||
unsigned char *value;
|
||||
int length;
|
||||
int hash_id;
|
||||
} ntp_mac;
|
||||
CMC_Instance cmac;
|
||||
} data;
|
||||
int auth_delay;
|
||||
} Key;
|
||||
|
||||
|
@ -62,9 +74,21 @@ static void
|
|||
free_keys(void)
|
||||
{
|
||||
unsigned int i;
|
||||
Key *key;
|
||||
|
||||
for (i = 0; i < ARR_GetSize(keys); i++)
|
||||
Free(((Key *)ARR_GetElement(keys, i))->val);
|
||||
for (i = 0; i < ARR_GetSize(keys); i++) {
|
||||
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);
|
||||
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
|
||||
decode_password(char *key)
|
||||
decode_key(char *key)
|
||||
{
|
||||
int i, j, len = strlen(key);
|
||||
char buf[3], *p;
|
||||
|
@ -184,11 +208,11 @@ compare_keys_by_id(const void *a, const void *b)
|
|||
void
|
||||
KEY_Reload(void)
|
||||
{
|
||||
unsigned int i, line_number;
|
||||
unsigned int i, line_number, key_length, cmac_key_length;
|
||||
FILE *in;
|
||||
uint32_t key_id;
|
||||
char line[2048], *keyval, *key_file;
|
||||
const char *hashname;
|
||||
char line[2048], *key_file, *key_value;
|
||||
const char *key_type;
|
||||
int hash_id;
|
||||
Key key;
|
||||
|
||||
free_keys();
|
||||
|
@ -212,26 +236,43 @@ KEY_Reload(void)
|
|||
if (!*line)
|
||||
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);
|
||||
continue;
|
||||
}
|
||||
|
||||
key.hash_id = HSH_GetHashId(hashname);
|
||||
if (key.hash_id < 0) {
|
||||
LOG(LOGS_WARN, "Unknown hash function in key %"PRIu32, key_id);
|
||||
key_length = decode_key(key_value);
|
||||
if (key_length == 0) {
|
||||
LOG(LOGS_WARN, "Could not decode key %"PRIu32, key.id);
|
||||
continue;
|
||||
}
|
||||
|
||||
key.len = decode_password(keyval);
|
||||
if (!key.len) {
|
||||
LOG(LOGS_WARN, "Could not decode password in key %"PRIu32, key_id);
|
||||
hash_id = HSH_GetHashId(key_type);
|
||||
cmac_key_length = CMC_GetKeyLength(key_type);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
key.id = key_id;
|
||||
key.val = MallocArray(char, key.len);
|
||||
memcpy(key.val, keyval, key.len);
|
||||
ARR_AppendElement(keys, &key);
|
||||
}
|
||||
|
||||
|
@ -334,7 +375,15 @@ KEY_GetAuthLength(uint32_t key_id)
|
|||
if (!key)
|
||||
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)
|
||||
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
|
||||
generate_ntp_auth(int hash_id, const unsigned char *key, int key_len,
|
||||
const unsigned char *data, int data_len,
|
||||
generate_auth(Key *key, const unsigned char *data, int data_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
|
||||
check_ntp_auth(int hash_id, const unsigned char *key, int key_len,
|
||||
const unsigned char *data, int data_len,
|
||||
check_auth(Key *key, const unsigned char *data, int data_len,
|
||||
const unsigned char *auth, int auth_len, int trunc_len)
|
||||
{
|
||||
unsigned char buf[MAX_HASH_LENGTH];
|
||||
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);
|
||||
}
|
||||
|
@ -390,8 +450,7 @@ KEY_GenerateAuth(uint32_t key_id, const unsigned char *data, int data_len,
|
|||
if (!key)
|
||||
return 0;
|
||||
|
||||
return generate_ntp_auth(key->hash_id, (unsigned char *)key->val, key->len,
|
||||
data, data_len, auth, auth_len);
|
||||
return generate_auth(key, 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)
|
||||
return 0;
|
||||
|
||||
return check_ntp_auth(key->hash_id, (unsigned char *)key->val, key->len,
|
||||
data, data_len, auth, auth_len, trunc_len);
|
||||
return check_auth(key, data, data_len, auth, auth_len, trunc_len);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue