Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TLSv1.3: add optional integrity-only ciphers [RFC 9150] #22903

Closed
Closed
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/run-checker-merge.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ jobs:
no-srp,
no-srtp,
no-ts,
no-integrity-only-ciphers,
DDvO marked this conversation as resolved.
Show resolved Hide resolved
enable-weak-ssl-ciphers,
enable-zlib,
]
Expand Down
7 changes: 7 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,13 @@ OpenSSL 3.4

*Tim Perry*

* Added support for integrity-only cipher suites TLS_SHA256_SHA256 and
TLS_SHA384_SHA384 in TLS 1.3, as defined in RFC 9150.

This work was sponsored by Siemens AG.

*Rajeev Ranjan*

* Added support for requesting CRL in CMP.

This work was sponsored by Siemens AG.
Expand Down
1 change: 1 addition & 0 deletions Configure
Original file line number Diff line number Diff line change
Expand Up @@ -528,6 +528,7 @@ my @disablables = (
"thread-pool",
"threads",
"tls",
"integrity-only-ciphers",
"trace",
"ts",
"ubsan",
Expand Down
11 changes: 11 additions & 0 deletions doc/man1/openssl-ciphers.pod.in
rajeev-0 marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -738,6 +738,15 @@ Note: the CBC modes mentioned in this RFC are not supported.
TLS_AES_128_CCM_SHA256 TLS_AES_128_CCM_SHA256
TLS_AES_128_CCM_8_SHA256 TLS_AES_128_CCM_8_SHA256

=head2 TLS v1.3 integrity-only cipher suites according to RFC 9150

TLS_SHA256_SHA256 TLS_SHA256_SHA256
TLS_SHA384_SHA384 TLS_SHA384_SHA384

DDvO marked this conversation as resolved.
Show resolved Hide resolved
Note: these ciphers are purely HMAC based and do not provide any confidentiality
and thus are disabled by default.
These ciphers are only available at security level 0.

=head2 Older names used by OpenSSL

The following names are accepted by older releases:
Expand Down Expand Up @@ -802,6 +811,8 @@ The B<-convert> option was added in OpenSSL 1.1.1.
Support for standard IANA names in cipher lists was added in
OpenSSL 3.2.0.

The support for TLS v1.3 integrity-only cipher suites was added in OpenSSL 3.4.

=head1 COPYRIGHT

Copyright 2000-2024 The OpenSSL Project Authors. All Rights Reserved.
Expand Down
4 changes: 4 additions & 0 deletions doc/man3/SSL_CTX_set_cipher_list.pod
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ ciphersuite names in order of preference. Valid TLSv1.3 ciphersuite names are:

=item TLS_AES_128_CCM_8_SHA256

=item TLS_SHA384_SHA384 - integrity-only

=item TLS_SHA256_SHA256 - integrity-only

=back

An empty list is permissible. The default value for the this setting is:
Expand Down
6 changes: 6 additions & 0 deletions include/openssl/tls1.h
Original file line number Diff line number Diff line change
Expand Up @@ -622,6 +622,10 @@ int SSL_CTX_set_tlsext_ticket_key_evp_cb
# define TLS1_3_CK_AES_128_CCM_SHA256 0x03001304
# define TLS1_3_CK_AES_128_CCM_8_SHA256 0x03001305

/* Integrity-only ciphersuites from RFC 9150 */
# define TLS1_3_CK_SHA256_SHA256 0x0300C0B4
# define TLS1_3_CK_SHA384_SHA384 0x0300C0B5

/* Aria ciphersuites from RFC6209 */
# define TLS1_CK_RSA_WITH_ARIA_128_GCM_SHA256 0x0300C050
# define TLS1_CK_RSA_WITH_ARIA_256_GCM_SHA384 0x0300C051
Expand Down Expand Up @@ -699,6 +703,8 @@ int SSL_CTX_set_tlsext_ticket_key_evp_cb
# define TLS1_3_RFC_AES_128_GCM_SHA256 "TLS_AES_128_GCM_SHA256"
# define TLS1_3_RFC_AES_256_GCM_SHA384 "TLS_AES_256_GCM_SHA384"
# define TLS1_3_RFC_CHACHA20_POLY1305_SHA256 "TLS_CHACHA20_POLY1305_SHA256"
# define TLS1_3_RFC_SHA256_SHA256 "TLS_SHA256_SHA256"
# define TLS1_3_RFC_SHA384_SHA384 "TLS_SHA384_SHA384"
# define TLS1_3_RFC_AES_128_CCM_SHA256 "TLS_AES_128_CCM_SHA256"
# define TLS1_3_RFC_AES_128_CCM_8_SHA256 "TLS_AES_128_CCM_8_SHA256"
# define TLS1_RFC_ECDHE_ECDSA_WITH_NULL_SHA "TLS_ECDHE_ECDSA_WITH_NULL_SHA"
Expand Down
2 changes: 1 addition & 1 deletion providers/implementations/ciphers/cipher_null.c
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ static int null_cipher(void *vctx, unsigned char *out, size_t *outl,
}
if (outsize < inl)
return 0;
if (in != out)
if (out != NULL && in != out)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, but is this an unrelated bugfix,
or maybe just a leftover of the earlier version?
If it is needed for the new integrity-only feature, maybe add a respective comment.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is a bugfix, as It lead to crash when NULL cipher is used with TLS v1.3.

t8m marked this conversation as resolved.
Show resolved Hide resolved
memcpy(out, in, inl);
*outl = inl;
return 1;
Expand Down
7 changes: 5 additions & 2 deletions ssl/record/methods/recmethod_local.h
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,9 @@ struct ossl_record_layer_st
/* cryptographic state */
EVP_CIPHER_CTX *enc_ctx;

/* TLSv1.3 MAC ctx, only used with integrity-only cipher */
EVP_MAC_CTX *mac_ctx;

/* Explicit IV length */
size_t eivlen;

Expand Down Expand Up @@ -333,8 +336,8 @@ struct ossl_record_layer_st
int tlstree;

/* TLSv1.3 fields */
/* static IV */
unsigned char iv[EVP_MAX_IV_LENGTH];
unsigned char *iv; /* static IV */
unsigned char *nonce; /* part of static IV followed by sequence number */
int allow_plain_alerts;

/* TLS "any" fields */
Expand Down
127 changes: 96 additions & 31 deletions ssl/record/methods/tls13_meth.c
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,42 @@ static int tls13_set_crypto_state(OSSL_RECORD_LAYER *rl, int level,
COMP_METHOD *comp)
{
EVP_CIPHER_CTX *ciph_ctx;
EVP_MAC_CTX *mac_ctx;
EVP_MAC *mac;
OSSL_PARAM params[2], *p = params;
int mode;
int enc = (rl->direction == OSSL_RECORD_DIRECTION_WRITE) ? 1 : 0;

if (ivlen > sizeof(rl->iv)) {
ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR);
rl->iv = OPENSSL_malloc(ivlen);
if (rl->iv == NULL)
return OSSL_RECORD_RETURN_FATAL;
}

rl->nonce = OPENSSL_malloc(ivlen);
if (rl->nonce == NULL)
return OSSL_RECORD_RETURN_FATAL;

memcpy(rl->iv, iv, ivlen);

/* Integrity only */
if (EVP_CIPHER_is_a(ciph, "NULL") && mactype == NID_hmac && md != NULL) {
mac = EVP_MAC_fetch(rl->libctx, "HMAC", rl->propq);
if (mac == NULL
|| (mac_ctx = rl->mac_ctx = EVP_MAC_CTX_new(mac)) == NULL) {
EVP_MAC_free(mac);
ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR);
return OSSL_RECORD_RETURN_FATAL;
}
EVP_MAC_free(mac);
*p++ = OSSL_PARAM_construct_utf8_string(OSSL_MAC_PARAM_DIGEST,
(char *)EVP_MD_name(md), 0);
*p = OSSL_PARAM_construct_end();
if (!EVP_MAC_init(mac_ctx, key, keylen, params)) {
ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR);
return OSSL_RECORD_RETURN_FATAL;
mattcaswell marked this conversation as resolved.
Show resolved Hide resolved
}
goto end;
}

ciph_ctx = rl->enc_ctx = EVP_CIPHER_CTX_new();
if (ciph_ctx == NULL) {
ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR);
Expand All @@ -51,23 +78,26 @@ static int tls13_set_crypto_state(OSSL_RECORD_LAYER *rl, int level,
ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR);
return OSSL_RECORD_RETURN_FATAL;
}

end:
return OSSL_RECORD_RETURN_SUCCESS;
}

static int tls13_cipher(OSSL_RECORD_LAYER *rl, TLS_RL_RECORD *recs,
size_t n_recs, int sending, SSL_MAC_BUF *mac,
size_t macsize)
{
EVP_CIPHER_CTX *ctx;
unsigned char iv[EVP_MAX_IV_LENGTH], recheader[SSL3_RT_HEADER_LENGTH];
size_t ivlen, offset, loop, hdrlen;
EVP_CIPHER_CTX *enc_ctx;
unsigned char recheader[SSL3_RT_HEADER_LENGTH];
unsigned char tag[EVP_MAX_MD_SIZE];
size_t nonce_len, offset, loop, hdrlen, taglen;
unsigned char *staticiv;
unsigned char *nonce;
unsigned char *seq = rl->sequence;
int lenu, lenf;
TLS_RL_RECORD *rec = &recs[0];
WPACKET wpkt;
const EVP_CIPHER *cipher;
EVP_MAC_CTX *mac_ctx = NULL;
int mode;

if (n_recs != 1) {
Expand All @@ -76,29 +106,32 @@ static int tls13_cipher(OSSL_RECORD_LAYER *rl, TLS_RL_RECORD *recs,
return 0;
}

ctx = rl->enc_ctx;
enc_ctx = rl->enc_ctx; /* enc_ctx is ignored when rl->mac_ctx != NULL */
staticiv = rl->iv;
nonce = rl->nonce;

cipher = EVP_CIPHER_CTX_get0_cipher(ctx);
if (cipher == NULL) {
if (enc_ctx == NULL && rl->mac_ctx == NULL) {
RLAYERfatal(rl, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR);
return 0;
}
mode = EVP_CIPHER_get_mode(cipher);

/*
* If we're sending an alert and ctx != NULL then we must be forcing
* plaintext alerts. If we're reading and ctx != NULL then we allow
* plaintext alerts at certain points in the handshake. If we've got this
* far then we have already validated that a plaintext alert is ok here.
*/
if (ctx == NULL || rec->type == SSL3_RT_ALERT) {
if (rec->type == SSL3_RT_ALERT) {
DDvO marked this conversation as resolved.
Show resolved Hide resolved
memmove(rec->data, rec->input, rec->length);
rec->input = rec->data;
return 1;
}
DDvO marked this conversation as resolved.
Show resolved Hide resolved

ivlen = EVP_CIPHER_CTX_get_iv_length(ctx);
/* For integrity-only ciphers, nonce_len is same as MAC size */
if (rl->mac_ctx != NULL)
nonce_len = EVP_MAC_CTX_get_mac_size(rl->mac_ctx);
else
nonce_len = EVP_CIPHER_CTX_get_iv_length(enc_ctx);
DDvO marked this conversation as resolved.
Show resolved Hide resolved

if (!sending) {
/*
Expand All @@ -110,30 +143,22 @@ static int tls13_cipher(OSSL_RECORD_LAYER *rl, TLS_RL_RECORD *recs,
rec->length -= rl->taglen;
}

/* Set up IV */
if (ivlen < SEQ_NUM_SIZE) {
/* Set up nonce: part of static IV followed by sequence number */
if (nonce_len < SEQ_NUM_SIZE) {
/* Should not happen */
RLAYERfatal(rl, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR);
return 0;
}
offset = ivlen - SEQ_NUM_SIZE;
memcpy(iv, staticiv, offset);
offset = nonce_len - SEQ_NUM_SIZE;
memcpy(nonce, staticiv, offset);
for (loop = 0; loop < SEQ_NUM_SIZE; loop++)
iv[offset + loop] = staticiv[offset + loop] ^ seq[loop];
nonce[offset + loop] = staticiv[offset + loop] ^ seq[loop];

if (!tls_increment_sequence_ctr(rl)) {
/* RLAYERfatal already called */
return 0;
}

if (EVP_CipherInit_ex(ctx, NULL, NULL, NULL, iv, sending) <= 0
|| (!sending && EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG,
rl->taglen,
rec->data + rec->length) <= 0)) {
RLAYERfatal(rl, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR);
return 0;
}

/* Set up the AAD */
if (!WPACKET_init_static_len(&wpkt, recheader, sizeof(recheader), 0)
|| !WPACKET_put_bytes_u8(&wpkt, rec->type)
Expand All @@ -147,24 +172,64 @@ static int tls13_cipher(OSSL_RECORD_LAYER *rl, TLS_RL_RECORD *recs,
return 0;
}

if (rl->mac_ctx != NULL) {
int ret = 0;

if ((mac_ctx = EVP_MAC_CTX_dup(rl->mac_ctx)) == NULL
|| !EVP_MAC_update(mac_ctx, nonce, nonce_len)
|| !EVP_MAC_update(mac_ctx, recheader, sizeof(recheader))
|| !EVP_MAC_update(mac_ctx, rec->input, rec->length)
|| !EVP_MAC_final(mac_ctx, tag, &taglen, rl->taglen)) {
RLAYERfatal(rl, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR);
goto end_mac;
}

if (sending) {
memcpy(rec->data + rec->length, tag, rl->taglen);
rec->length += rl->taglen;
} else if (CRYPTO_memcmp(tag, rec->data + rec->length,
rl->taglen) != 0) {
goto end_mac;
}
ret = 1;
end_mac:
EVP_MAC_CTX_free(mac_ctx);
return ret;
}

cipher = EVP_CIPHER_CTX_get0_cipher(enc_ctx);
if (cipher == NULL) {
RLAYERfatal(rl, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR);
return 0;
DDvO marked this conversation as resolved.
Show resolved Hide resolved
}
mode = EVP_CIPHER_get_mode(cipher);

if (EVP_CipherInit_ex(enc_ctx, NULL, NULL, NULL, nonce, sending) <= 0
|| (!sending && EVP_CIPHER_CTX_ctrl(enc_ctx, EVP_CTRL_AEAD_SET_TAG,
rl->taglen,
rec->data + rec->length) <= 0)) {
RLAYERfatal(rl, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR);
return 0;
}

/*
* For CCM we must explicitly set the total plaintext length before we add
* any AAD.
*/
if ((mode == EVP_CIPH_CCM_MODE
&& EVP_CipherUpdate(ctx, NULL, &lenu, NULL,
&& EVP_CipherUpdate(enc_ctx, NULL, &lenu, NULL,
(unsigned int)rec->length) <= 0)
|| EVP_CipherUpdate(ctx, NULL, &lenu, recheader,
|| EVP_CipherUpdate(enc_ctx, NULL, &lenu, recheader,
sizeof(recheader)) <= 0
|| EVP_CipherUpdate(ctx, rec->data, &lenu, rec->input,
|| EVP_CipherUpdate(enc_ctx, rec->data, &lenu, rec->input,
(unsigned int)rec->length) <= 0
|| EVP_CipherFinal_ex(ctx, rec->data + lenu, &lenf) <= 0
|| EVP_CipherFinal_ex(enc_ctx, rec->data + lenu, &lenf) <= 0
|| (size_t)(lenu + lenf) != rec->length) {
return 0;
}
if (sending) {
/* Add the tag */
if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_GET_TAG, rl->taglen,
if (EVP_CIPHER_CTX_ctrl(enc_ctx, EVP_CTRL_AEAD_GET_TAG, rl->taglen,
rec->data + rec->length) <= 0) {
RLAYERfatal(rl, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR);
return 0;
Expand Down
4 changes: 3 additions & 1 deletion ssl/record/methods/tls_common.c
Original file line number Diff line number Diff line change
Expand Up @@ -1434,11 +1434,13 @@ static void tls_int_free(OSSL_RECORD_LAYER *rl)
tls_release_write_buffer(rl);

EVP_CIPHER_CTX_free(rl->enc_ctx);
EVP_MAC_CTX_free(rl->mac_ctx);
EVP_MD_CTX_free(rl->md_ctx);
#ifndef OPENSSL_NO_COMP
COMP_CTX_free(rl->compctx);
#endif

OPENSSL_free(rl->iv);
OPENSSL_free(rl->nonce);
if (rl->version == SSL3_VERSION)
OPENSSL_cleanse(rl->mac_secret, sizeof(rl->mac_secret));

Expand Down