MINOR: ssl: add support of aes256 bits ticket keys on file and cli.

Openssl switched from aes128 to aes256 since may 2016  to compute
tls ticket secrets used by default. But Haproxy still handled only
128 bits keys for both tls key file and CLI.

This patch permit the user to set aes256 keys throught CLI or
the key file (80 bytes encoded in base64) in the same way that
aes128 keys were handled (48 bytes encoded in base64):
- first 16 bytes for the key name
- next 16/32 bytes for aes 128/256 key bits key
- last 16/32 bytes for hmac 128/256 bits

Both sizes are now supported (but keys from same file must be
of the same size and can but updated via CLI only using a key of
the same size).

Note: This feature need the fix "dec func ignores padding for output
size checking."
This commit is contained in:
Emeric Brun 2019-01-10 17:51:55 +01:00 committed by Willy Tarreau
parent 09852f70e0
commit 9e7547740c
5 changed files with 130 additions and 33 deletions

View File

@ -11337,13 +11337,16 @@ tfo
tls-ticket-keys <keyfile>
Sets the TLS ticket keys file to load the keys from. The keys need to be 48
bytes long, encoded with base64 (ex. openssl rand -base64 48). Number of keys
is specified by the TLS_TICKETS_NO build option (default 3) and at least as
many keys need to be present in the file. Last TLS_TICKETS_NO keys will be
used for decryption and the penultimate one for encryption. This enables easy
key rotation by just appending new key to the file and reloading the process.
Keys must be periodically rotated (ex. every 12h) or Perfect Forward Secrecy
is compromised. It is also a good idea to keep the keys off any permanent
or 80 bytes long, depending if aes128 or aes256 is used, encoded with base64
with one line per key (ex. openssl rand 80 | openssl base64 -A | xargs echo).
The first key determines the key length used for next keys: you can't mix
aes128 and aes256 keys. Number of keys is specified by the TLS_TICKETS_NO
build option (default 3) and at least as many keys need to be present in
the file. Last TLS_TICKETS_NO keys will be used for decryption and the
penultimate one for encryption. This enables easy key rotation by just
appending new key to the file and reloading the process. Keys must be
periodically rotated (ex. every 12h) or Perfect Forward Secrecy is
compromised. It is also a good idea to keep the keys off any permanent
storage such as hard drives (hint: use tmpfs and don't swap those files).
Lifetime hint can be changed using tune.ssl.timeout.

View File

@ -1761,7 +1761,7 @@ set ssl tls-key <id> <tlskey>
ultimate key, while the penultimate one is used for encryption (others just
decrypt). The oldest TLS key present is overwritten. <id> is either a numeric
#<id> or <file> returned by "show tls-keys". <tlskey> is a base64 encoded 48
bit TLS ticket key (ex. openssl rand -base64 48).
or 80 bits TLS ticket key (ex. openssl rand 80 | openssl base64 -A).
set table <table> key <key> [data.<data_type> <value>]*
Create or update a stick-table entry in the table. If the key is not present,

View File

@ -67,7 +67,7 @@ unsigned int ssl_sock_get_verify_result(struct connection *conn);
int ssl_sock_update_ocsp_response(struct buffer *ocsp_response, char **err);
#endif
#if (defined SSL_CTRL_SET_TLSEXT_TICKET_KEY_CB && TLS_TICKETS_NO > 0)
void ssl_sock_update_tlskey_ref(struct tls_keys_ref *ref,
int ssl_sock_update_tlskey_ref(struct tls_keys_ref *ref,
struct buffer *tlskey);
int ssl_sock_update_tlskey(char *filename, struct buffer *tlskey, char **err);
struct tls_keys_ref *tlskeys_ref_lookup(const char *filename);

View File

@ -49,19 +49,32 @@ struct tls_version_filter {
extern struct list tlskeys_reference;
struct tls_sess_key {
struct tls_sess_key_128 {
unsigned char name[16];
unsigned char aes_key[16];
unsigned char hmac_key[16];
} __attribute__((packed));
struct tls_sess_key_256 {
unsigned char name[16];
unsigned char aes_key[32];
unsigned char hmac_key[32];
} __attribute__((packed));
union tls_sess_key{
unsigned char name[16];
struct tls_sess_key_256 key_128;
struct tls_sess_key_256 key_256;
} __attribute__((packed));
struct tls_keys_ref {
struct list list; /* Used to chain refs. */
char *filename;
int unique_id; /* Each pattern reference have unique id. */
int refcount; /* number of users of this tls_keys_ref. */
struct tls_sess_key *tlskeys;
union tls_sess_key *tlskeys;
int tls_ticket_enc_index;
int key_size_bits;
__decl_hathreads(HA_RWLOCK_T lock); /* lock used to protect the ref */
};

View File

@ -849,7 +849,7 @@ end:
static int ssl_tlsext_ticket_key_cb(SSL *s, unsigned char key_name[16], unsigned char *iv, EVP_CIPHER_CTX *ectx, HMAC_CTX *hctx, int enc)
{
struct tls_keys_ref *ref;
struct tls_sess_key *keys;
union tls_sess_key *keys;
struct connection *conn;
int head;
int i;
@ -868,11 +868,22 @@ static int ssl_tlsext_ticket_key_cb(SSL *s, unsigned char key_name[16], unsigned
if(!RAND_pseudo_bytes(iv, EVP_MAX_IV_LENGTH))
goto end;
if(!EVP_EncryptInit_ex(ectx, EVP_aes_128_cbc(), NULL, keys[head].aes_key, iv))
goto end;
if (ref->key_size_bits == 128) {
HMAC_Init_ex(hctx, keys[head].hmac_key, 16, HASH_FUNCT(), NULL);
ret = 1;
if(!EVP_EncryptInit_ex(ectx, EVP_aes_128_cbc(), NULL, keys[head].key_128.aes_key, iv))
goto end;
HMAC_Init_ex(hctx, keys[head].key_128.hmac_key, 16, HASH_FUNCT(), NULL);
ret = 1;
}
else if (ref->key_size_bits == 256 ) {
if(!EVP_EncryptInit_ex(ectx, EVP_aes_256_cbc(), NULL, keys[head].key_256.aes_key, iv))
goto end;
HMAC_Init_ex(hctx, keys[head].key_256.hmac_key, 32, HASH_FUNCT(), NULL);
ret = 1;
}
} else {
for (i = 0; i < TLS_TICKETS_NO; i++) {
if (!memcmp(key_name, keys[(head + i) % TLS_TICKETS_NO].name, 16))
@ -882,13 +893,22 @@ static int ssl_tlsext_ticket_key_cb(SSL *s, unsigned char key_name[16], unsigned
goto end;
found:
HMAC_Init_ex(hctx, keys[(head + i) % TLS_TICKETS_NO].hmac_key, 16, HASH_FUNCT(), NULL);
if(!EVP_DecryptInit_ex(ectx, EVP_aes_128_cbc(), NULL, keys[(head + i) % TLS_TICKETS_NO].aes_key, iv))
goto end;
/* 2 for key renewal, 1 if current key is still valid */
ret = i ? 2 : 1;
if (ref->key_size_bits == 128) {
HMAC_Init_ex(hctx, keys[(head + i) % TLS_TICKETS_NO].key_128.hmac_key, 16, HASH_FUNCT(), NULL);
if(!EVP_DecryptInit_ex(ectx, EVP_aes_128_cbc(), NULL, keys[(head + i) % TLS_TICKETS_NO].key_128.aes_key, iv))
goto end;
/* 2 for key renewal, 1 if current key is still valid */
ret = i ? 2 : 1;
}
else if (ref->key_size_bits == 256) {
HMAC_Init_ex(hctx, keys[(head + i) % TLS_TICKETS_NO].key_256.hmac_key, 32, HASH_FUNCT(), NULL);
if(!EVP_DecryptInit_ex(ectx, EVP_aes_256_cbc(), NULL, keys[(head + i) % TLS_TICKETS_NO].key_256.aes_key, iv))
goto end;
/* 2 for key renewal, 1 if current key is still valid */
ret = i ? 2 : 1;
}
}
end:
HA_RWLOCK_RDUNLOCK(TLSKEYS_REF_LOCK, &ref->lock);
return ret;
@ -914,14 +934,31 @@ struct tls_keys_ref *tlskeys_ref_lookupid(int unique_id)
return NULL;
}
void ssl_sock_update_tlskey_ref(struct tls_keys_ref *ref,
/* Update the key into ref: if keysize doesnt
* match existing ones, this function returns -1
* else it returns 0 on success.
*/
int ssl_sock_update_tlskey_ref(struct tls_keys_ref *ref,
struct buffer *tlskey)
{
if (ref->key_size_bits == 128) {
if (tlskey->data != sizeof(struct tls_sess_key_128))
return -1;
}
else if (ref->key_size_bits == 256) {
if (tlskey->data != sizeof(struct tls_sess_key_256))
return -1;
}
else
return -1;
HA_RWLOCK_WRLOCK(TLSKEYS_REF_LOCK, &ref->lock);
memcpy((char *) (ref->tlskeys + ((ref->tls_ticket_enc_index + 2) % TLS_TICKETS_NO)),
tlskey->area, tlskey->data);
ref->tls_ticket_enc_index = (ref->tls_ticket_enc_index + 1) % TLS_TICKETS_NO;
HA_RWLOCK_WRUNLOCK(TLSKEYS_REF_LOCK, &ref->lock);
return 0;
}
int ssl_sock_update_tlskey(char *filename, struct buffer *tlskey, char **err)
@ -932,7 +969,11 @@ int ssl_sock_update_tlskey(char *filename, struct buffer *tlskey, char **err)
memprintf(err, "Unable to locate the referenced filename: %s", filename);
return 1;
}
ssl_sock_update_tlskey_ref(ref, tlskey);
if (ssl_sock_update_tlskey_ref(ref, tlskey) < 0) {
memprintf(err, "Invalid key size");
return 1;
}
return 0;
}
@ -7826,7 +7867,7 @@ static int bind_parse_tls_ticket_keys(char **args, int cur_arg, struct proxy *px
return ERR_ALERT | ERR_FATAL;
}
keys_ref->tlskeys = malloc(TLS_TICKETS_NO * sizeof(struct tls_sess_key));
keys_ref->tlskeys = malloc(TLS_TICKETS_NO * sizeof(union tls_sess_key));
if (!keys_ref->tlskeys) {
free(keys_ref);
if (err)
@ -7851,8 +7892,11 @@ static int bind_parse_tls_ticket_keys(char **args, int cur_arg, struct proxy *px
return ERR_ALERT | ERR_FATAL;
}
keys_ref->key_size_bits = 0;
while (fgets(thisline, sizeof(thisline), f) != NULL) {
int len = strlen(thisline);
int dec_size;
/* Strip newline characters from the end */
if(thisline[len - 1] == '\n')
thisline[--len] = 0;
@ -7860,7 +7904,8 @@ static int bind_parse_tls_ticket_keys(char **args, int cur_arg, struct proxy *px
if(thisline[len - 1] == '\r')
thisline[--len] = 0;
if (base64dec(thisline, len, (char *) (keys_ref->tlskeys + i % TLS_TICKETS_NO), sizeof(struct tls_sess_key)) != sizeof(struct tls_sess_key)) {
dec_size = base64dec(thisline, len, (char *) (keys_ref->tlskeys + i % TLS_TICKETS_NO), sizeof(union tls_sess_key));
if (dec_size < 0) {
free(keys_ref->filename);
free(keys_ref->tlskeys);
free(keys_ref);
@ -7869,6 +7914,23 @@ static int bind_parse_tls_ticket_keys(char **args, int cur_arg, struct proxy *px
fclose(f);
return ERR_ALERT | ERR_FATAL;
}
else if (!keys_ref->key_size_bits && (dec_size == sizeof(struct tls_sess_key_128))) {
keys_ref->key_size_bits = 128;
}
else if (!keys_ref->key_size_bits && (dec_size == sizeof(struct tls_sess_key_256))) {
keys_ref->key_size_bits = 256;
}
else if (((dec_size != sizeof(struct tls_sess_key_128)) && (dec_size != sizeof(struct tls_sess_key_256)))
|| ((dec_size == sizeof(struct tls_sess_key_128) && (keys_ref->key_size_bits != 128)))
|| ((dec_size == sizeof(struct tls_sess_key_256) && (keys_ref->key_size_bits != 256)))) {
free(keys_ref->filename);
free(keys_ref->tlskeys);
free(keys_ref);
if (err)
memprintf(err, "'%s' : wrong sized key on line %d", args[cur_arg+1], i + 1);
fclose(f);
return ERR_ALERT | ERR_FATAL;
}
i++;
}
@ -8814,11 +8876,24 @@ static int cli_io_handler_tlskeys_files(struct appctx *appctx) {
chunk_reset(t2);
/* should never fail here because we dump only a key in the t2 buffer */
t2->data = a2base64((char *)(ref->tlskeys + (head + 2 + appctx->ctx.cli.i1) % TLS_TICKETS_NO),
sizeof(struct tls_sess_key),
t2->area, t2->size);
chunk_appendf(&trash, "%d.%d %s\n", ref->unique_id, appctx->ctx.cli.i1,
t2->area);
if (ref->key_size_bits == 128) {
t2->data = a2base64((char *)(ref->tlskeys + (head + 2 + appctx->ctx.cli.i1) % TLS_TICKETS_NO),
sizeof(struct tls_sess_key_128),
t2->area, t2->size);
chunk_appendf(&trash, "%d.%d %s\n", ref->unique_id, appctx->ctx.cli.i1,
t2->area);
}
else if (ref->key_size_bits == 256) {
t2->data = a2base64((char *)(ref->tlskeys + (head + 2 + appctx->ctx.cli.i1) % TLS_TICKETS_NO),
sizeof(struct tls_sess_key_256),
t2->area, t2->size);
chunk_appendf(&trash, "%d.%d %s\n", ref->unique_id, appctx->ctx.cli.i1,
t2->area);
}
else {
/* This case should never happen */
chunk_appendf(&trash, "%d.%d <unknown>\n", ref->unique_id, appctx->ctx.cli.i1);
}
if (ci_putchk(si_ic(si), &trash) == -1) {
/* let's try again later from this stream. We add ourselves into
@ -8906,14 +8981,20 @@ static int cli_parse_set_tlskeys(char **args, char *payload, struct appctx *appc
}
ret = base64dec(args[4], strlen(args[4]), trash.area, trash.size);
if (ret != sizeof(struct tls_sess_key)) {
if (ret < 0) {
appctx->ctx.cli.severity = LOG_ERR;
appctx->ctx.cli.msg = "'set ssl tls-key' received invalid base64 encoded TLS key.\n";
appctx->st0 = CLI_ST_PRINT;
return 1;
}
trash.data = ret;
ssl_sock_update_tlskey_ref(ref, &trash);
if (ssl_sock_update_tlskey_ref(ref, &trash) < 0) {
appctx->ctx.cli.severity = LOG_ERR;
appctx->ctx.cli.msg = "'set ssl tls-key' received a key of wrong size.\n";
appctx->st0 = CLI_ST_PRINT;
return 1;
}
appctx->ctx.cli.severity = LOG_INFO;
appctx->ctx.cli.msg = "TLS ticket key updated!\n";
appctx->st0 = CLI_ST_PRINT;