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
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 */
|
||||||
|
|
|
@ -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
130
keys.c
|
@ -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);
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue