Add TLSv1.3 post-handshake authentication (PHA) Add SSL_verify_client_post_handshake() for servers to initiate PHA Add SSL_force_post_handshake_auth() for clients that don't have certificates initially configured, but use a certificate callback. Update SSL_CTX_set_verify()/SSL_set_verify() mode: * Add SSL_VERIFY_POST_HANDSHAKE to postpone client authentication until after the initial handshake. * Update SSL_VERIFY_CLIENT_ONCE now only sends out one CertRequest regardless of when the certificate authentication takes place; either initial handshake, re-negotiation, or post-handshake authentication. Add 'RequestPostHandshake' and 'RequirePostHandshake' SSL_CONF options that add the SSL_VERIFY_POST_HANDSHAKE to the 'Request' and 'Require' options Add support to s_client: * Enabled automatically when cert is configured * Can be forced enabled via -force_pha Add support to s_server: * Use 'c' to invoke PHA in s_server * Remove some dead code Update documentation Update unit tests: * Illegal use of PHA extension * TLSv1.3 certificate tests DTLS and TLS behave ever-so-slightly differently. So, when DTLS1.3 is implemented, it's PHA support state machine may need to be different. Add a TODO and a #error Update handshake context to deal with PHA. The handshake context for TLSv1.3 post-handshake auth is up through the ClientFinish message, plus the CertificateRequest message. Subsequent Certificate, CertificateVerify, and Finish messages are based on this handshake context (not the Certificate message per se, but it's included after the hash). KeyUpdate, NewSessionTicket, and prior Certificate Request messages are not included in post-handshake authentication. After the ClientFinished message is processed, save off the digest state for future post-handshake authentication. When post-handshake auth occurs, copy over the saved handshake context into the "main" handshake digest. This effectively discards the any KeyUpdate or NewSessionTicket messages and any prior post-handshake authentication. This, of course, assumes that the ID-22 did not mean to include any previous post-handshake authentication into the new handshake transcript. This is implied by section 4.4.1 that lists messages only up to the first ClientFinished. Reviewed-by: Ben Kaduk <kaduk@mit.edu> Reviewed-by: Matt Caswell <matt@openssl.org> (Merged from https://github.com/openssl/openssl/pull/4964)
diff --git a/ssl/ssl_conf.c b/ssl/ssl_conf.c index 0cd8ace..cb4ff8d 100644 --- a/ssl/ssl_conf.c +++ b/ssl/ssl_conf.c
@@ -386,7 +386,12 @@ SSL_FLAG_VFY_SRV("Request", SSL_VERIFY_PEER), SSL_FLAG_VFY_SRV("Require", SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT), - SSL_FLAG_VFY_SRV("Once", SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE) + SSL_FLAG_VFY_SRV("Once", SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE), + SSL_FLAG_VFY_SRV("RequestPostHandshake", + SSL_VERIFY_PEER | SSL_VERIFY_POST_HANDSHAKE), + SSL_FLAG_VFY_SRV("RequirePostHandshake", + SSL_VERIFY_PEER | SSL_VERIFY_POST_HANDSHAKE | + SSL_VERIFY_FAIL_IF_NO_PEER_CERT), }; if (value == NULL) return -3;
diff --git a/ssl/ssl_err.c b/ssl/ssl_err.c index 746678c..f0bde60 100644 --- a/ssl/ssl_err.c +++ b/ssl/ssl_err.c
@@ -351,6 +351,8 @@ {ERR_PACK(ERR_LIB_SSL, SSL_F_SSL_VALIDATE_CT, 0), "ssl_validate_ct"}, {ERR_PACK(ERR_LIB_SSL, SSL_F_SSL_VERIFY_CERT_CHAIN, 0), "ssl_verify_cert_chain"}, + {ERR_PACK(ERR_LIB_SSL, SSL_F_SSL_VERIFY_CLIENT_POST_HANDSHAKE, 0), + "SSL_verify_client_post_handshake"}, {ERR_PACK(ERR_LIB_SSL, SSL_F_SSL_WRITE, 0), "SSL_write"}, {ERR_PACK(ERR_LIB_SSL, SSL_F_SSL_WRITE_EARLY_DATA, 0), "SSL_write_early_data"}, @@ -369,6 +371,10 @@ {ERR_PACK(ERR_LIB_SSL, SSL_F_TLS13_GENERATE_SECRET, 0), "tls13_generate_secret"}, {ERR_PACK(ERR_LIB_SSL, SSL_F_TLS13_HKDF_EXPAND, 0), "tls13_hkdf_expand"}, + {ERR_PACK(ERR_LIB_SSL, SSL_F_TLS13_RESTORE_HANDSHAKE_DIGEST_FOR_PHA, 0), + "tls13_restore_handshake_digest_for_pha"}, + {ERR_PACK(ERR_LIB_SSL, SSL_F_TLS13_SAVE_HANDSHAKE_DIGEST_FOR_PHA, 0), + "tls13_save_handshake_digest_for_pha"}, {ERR_PACK(ERR_LIB_SSL, SSL_F_TLS13_SETUP_KEY_BLOCK, 0), "tls13_setup_key_block"}, {ERR_PACK(ERR_LIB_SSL, SSL_F_TLS1_CHANGE_CIPHER_STATE, 0), @@ -441,6 +447,8 @@ "tls_construct_ctos_npn"}, {ERR_PACK(ERR_LIB_SSL, SSL_F_TLS_CONSTRUCT_CTOS_PADDING, 0), "tls_construct_ctos_padding"}, + {ERR_PACK(ERR_LIB_SSL, SSL_F_TLS_CONSTRUCT_CTOS_POST_HANDSHAKE_AUTH, 0), + "tls_construct_ctos_post_handshake_auth"}, {ERR_PACK(ERR_LIB_SSL, SSL_F_TLS_CONSTRUCT_CTOS_PSK, 0), "tls_construct_ctos_psk"}, {ERR_PACK(ERR_LIB_SSL, SSL_F_TLS_CONSTRUCT_CTOS_PSK_KEX_MODES, 0), @@ -557,6 +565,8 @@ "tls_parse_ctos_key_share"}, {ERR_PACK(ERR_LIB_SSL, SSL_F_TLS_PARSE_CTOS_MAXFRAGMENTLEN, 0), "tls_parse_ctos_maxfragmentlen"}, + {ERR_PACK(ERR_LIB_SSL, SSL_F_TLS_PARSE_CTOS_POST_HANDSHAKE_AUTH, 0), + "tls_parse_ctos_post_handshake_auth"}, {ERR_PACK(ERR_LIB_SSL, SSL_F_TLS_PARSE_CTOS_PSK, 0), "tls_parse_ctos_psk"}, {ERR_PACK(ERR_LIB_SSL, SSL_F_TLS_PARSE_CTOS_PSK_KEX_MODES, 0), "tls_parse_ctos_psk_kex_modes"}, @@ -832,6 +842,8 @@ "exceeds max fragment size"}, {ERR_PACK(ERR_LIB_SSL, 0, SSL_R_EXCESSIVE_MESSAGE_SIZE), "excessive message size"}, + {ERR_PACK(ERR_LIB_SSL, 0, SSL_R_EXTENSION_NOT_RECEIVED), + "extension not received"}, {ERR_PACK(ERR_LIB_SSL, 0, SSL_R_EXTRA_DATA_IN_MESSAGE), "extra data in message"}, {ERR_PACK(ERR_LIB_SSL, 0, SSL_R_EXT_LENGTH_MISMATCH), @@ -868,8 +880,10 @@ {ERR_PACK(ERR_LIB_SSL, 0, SSL_R_INVALID_COMMAND), "invalid command"}, {ERR_PACK(ERR_LIB_SSL, 0, SSL_R_INVALID_COMPRESSION_ALGORITHM), "invalid compression algorithm"}, + {ERR_PACK(ERR_LIB_SSL, 0, SSL_R_INVALID_CONFIG), "invalid config"}, {ERR_PACK(ERR_LIB_SSL, 0, SSL_R_INVALID_CONFIGURATION_NAME), "invalid configuration name"}, + {ERR_PACK(ERR_LIB_SSL, 0, SSL_R_INVALID_CONTEXT), "invalid context"}, {ERR_PACK(ERR_LIB_SSL, 0, SSL_R_INVALID_CT_VALIDATION_TYPE), "invalid ct validation type"}, {ERR_PACK(ERR_LIB_SSL, 0, SSL_R_INVALID_KEY_UPDATE_TYPE), @@ -919,6 +933,7 @@ "missing tmp ecdh key"}, {ERR_PACK(ERR_LIB_SSL, 0, SSL_R_NOT_ON_RECORD_BOUNDARY), "not on record boundary"}, + {ERR_PACK(ERR_LIB_SSL, 0, SSL_R_NOT_SERVER), "not server"}, {ERR_PACK(ERR_LIB_SSL, 0, SSL_R_NO_APPLICATION_PROTOCOL), "no application protocol"}, {ERR_PACK(ERR_LIB_SSL, 0, SSL_R_NO_CERTIFICATES_RETURNED), @@ -978,6 +993,8 @@ "pem name bad prefix"}, {ERR_PACK(ERR_LIB_SSL, 0, SSL_R_PEM_NAME_TOO_SHORT), "pem name too short"}, {ERR_PACK(ERR_LIB_SSL, 0, SSL_R_PIPELINE_FAILURE), "pipeline failure"}, + {ERR_PACK(ERR_LIB_SSL, 0, SSL_R_POST_HANDSHAKE_AUTH_ENCODING_ERR), + "post handshake auth encoding err"}, {ERR_PACK(ERR_LIB_SSL, 0, SSL_R_PROTOCOL_IS_SHUTDOWN), "protocol is shutdown"}, {ERR_PACK(ERR_LIB_SSL, 0, SSL_R_PSK_IDENTITY_NOT_FOUND), @@ -996,6 +1013,8 @@ "renegotiation encoding err"}, {ERR_PACK(ERR_LIB_SSL, 0, SSL_R_RENEGOTIATION_MISMATCH), "renegotiation mismatch"}, + {ERR_PACK(ERR_LIB_SSL, 0, SSL_R_REQUEST_PENDING), "request pending"}, + {ERR_PACK(ERR_LIB_SSL, 0, SSL_R_REQUEST_SENT), "request sent"}, {ERR_PACK(ERR_LIB_SSL, 0, SSL_R_REQUIRED_CIPHER_MISSING), "required cipher missing"}, {ERR_PACK(ERR_LIB_SSL, 0, SSL_R_REQUIRED_COMPRESSION_ALGORITHM_MISSING),
diff --git a/ssl/ssl_lib.c b/ssl/ssl_lib.c index 270d4de..5a5fbad 100644 --- a/ssl/ssl_lib.c +++ b/ssl/ssl_lib.c
@@ -1186,6 +1186,8 @@ OPENSSL_free(s->ext.alpn); OPENSSL_free(s->ext.tls13_cookie); OPENSSL_free(s->clienthello); + OPENSSL_free(s->pha_context); + EVP_MD_CTX_free(s->pha_dgst); sk_X509_NAME_pop_free(s->ca_names, X509_NAME_free); @@ -5318,3 +5320,55 @@ return 0; } + +void SSL_force_post_handshake_auth(SSL *ssl) +{ + ssl->pha_forced = 1; +} + +int SSL_verify_client_post_handshake(SSL *ssl) +{ + if (!SSL_IS_TLS13(ssl)) { + SSLerr(SSL_F_SSL_VERIFY_CLIENT_POST_HANDSHAKE, SSL_R_WRONG_SSL_VERSION); + return 0; + } + if (!ssl->server) { + SSLerr(SSL_F_SSL_VERIFY_CLIENT_POST_HANDSHAKE, SSL_R_NOT_SERVER); + return 0; + } + + if (!SSL_is_init_finished(ssl)) { + SSLerr(SSL_F_SSL_VERIFY_CLIENT_POST_HANDSHAKE, SSL_R_STILL_IN_INIT); + return 0; + } + + switch (ssl->post_handshake_auth) { + case SSL_PHA_NONE: + SSLerr(SSL_F_SSL_VERIFY_CLIENT_POST_HANDSHAKE, SSL_R_EXTENSION_NOT_RECEIVED); + return 0; + default: + case SSL_PHA_EXT_SENT: + SSLerr(SSL_F_SSL_VERIFY_CLIENT_POST_HANDSHAKE, ERR_R_INTERNAL_ERROR); + return 0; + case SSL_PHA_EXT_RECEIVED: + break; + case SSL_PHA_REQUEST_PENDING: + SSLerr(SSL_F_SSL_VERIFY_CLIENT_POST_HANDSHAKE, SSL_R_REQUEST_PENDING); + return 0; + case SSL_PHA_REQUESTED: + SSLerr(SSL_F_SSL_VERIFY_CLIENT_POST_HANDSHAKE, SSL_R_REQUEST_SENT); + return 0; + } + + ssl->post_handshake_auth = SSL_PHA_REQUEST_PENDING; + + /* checks verify_mode and algorithm_auth */ + if (!send_certificate_request(ssl)) { + ssl->post_handshake_auth = SSL_PHA_EXT_RECEIVED; /* restore on error */ + SSLerr(SSL_F_SSL_VERIFY_CLIENT_POST_HANDSHAKE, SSL_R_INVALID_CONFIG); + return 0; + } + + ossl_statem_set_in_init(ssl, 1); + return 1; +}
diff --git a/ssl/ssl_locl.h b/ssl/ssl_locl.h index 6afd009..221d5b9 100644 --- a/ssl/ssl_locl.h +++ b/ssl/ssl_locl.h
@@ -402,6 +402,15 @@ #define CERT_PRIVATE_KEY 2 */ +/* Post-Handshake Authentication state */ +typedef enum { + SSL_PHA_NONE = 0, + SSL_PHA_EXT_SENT, /* client-side only: extension sent */ + SSL_PHA_EXT_RECEIVED, /* server-side only: extension received */ + SSL_PHA_REQUEST_PENDING, /* server-side only: request pending */ + SSL_PHA_REQUESTED /* request received by client, or sent by server */ +} SSL_PHA_STATE; + /* CipherSuite length. SSLv3 and all TLS versions. */ # define TLS_CIPHER_LEN 2 /* used to hold info on the particular ciphers used */ @@ -702,6 +711,7 @@ TLSEXT_IDX_signed_certificate_timestamp, TLSEXT_IDX_extended_master_secret, TLSEXT_IDX_signature_algorithms_cert, + TLSEXT_IDX_post_handshake_auth, TLSEXT_IDX_signature_algorithms, TLSEXT_IDX_supported_versions, TLSEXT_IDX_psk_kex_modes, @@ -1334,6 +1344,14 @@ int renegotiate; /* If sending a KeyUpdate is pending */ int key_update; + /* Post-handshake authentication state */ + SSL_PHA_STATE post_handshake_auth; + int pha_forced; + uint8_t* pha_context; + size_t pha_context_len; + int certreqs_sent; + EVP_MD_CTX *pha_dgst; /* this is just the digest through ClientFinished */ + # ifndef OPENSSL_NO_SRP /* ctx for SRP authentication */ SRP_CTX srp_ctx; @@ -2535,6 +2553,10 @@ __owur int srp_generate_client_master_secret(SSL *s); __owur int srp_verify_server_param(SSL *s); +/* statem/statem_srvr.c */ + +__owur int send_certificate_request(SSL *s); + /* statem/extensions_cust.c */ custom_ext_method *custom_ext_find(const custom_ext_methods *exts,
diff --git a/ssl/statem/extensions.c b/ssl/statem/extensions.c index 5ad86f2..2faba13 100644 --- a/ssl/statem/extensions.c +++ b/ssl/statem/extensions.c
@@ -56,6 +56,8 @@ static int final_early_data(SSL *s, unsigned int context, int sent); static int final_maxfragmentlen(SSL *s, unsigned int context, int sent); +static int init_post_handshake_auth(SSL *s, unsigned int context); + /* Structure to define a built-in extension */ typedef struct extensions_definition_st { /* The defined type for the extension */ @@ -290,6 +292,14 @@ NULL, NULL, NULL }, { + TLSEXT_TYPE_post_handshake_auth, + SSL_EXT_CLIENT_HELLO | SSL_EXT_TLS1_3_ONLY, + init_post_handshake_auth, + tls_parse_ctos_post_handshake_auth, NULL, + NULL, tls_construct_ctos_post_handshake_auth, + NULL, + }, + { TLSEXT_TYPE_signature_algorithms, SSL_EXT_CLIENT_HELLO | SSL_EXT_TLS1_3_CERTIFICATE_REQUEST, init_sig_algs, tls_parse_ctos_sig_algs, @@ -1653,3 +1663,10 @@ return 1; } + +static int init_post_handshake_auth(SSL *s, unsigned int context) +{ + s->post_handshake_auth = SSL_PHA_NONE; + + return 1; +}
diff --git a/ssl/statem/extensions_clnt.c b/ssl/statem/extensions_clnt.c index 5441e98..6286242 100644 --- a/ssl/statem/extensions_clnt.c +++ b/ssl/statem/extensions_clnt.c
@@ -1133,6 +1133,48 @@ #endif } +EXT_RETURN tls_construct_ctos_post_handshake_auth(SSL *s, WPACKET *pkt, + unsigned int context, + X509 *x, size_t chainidx) +{ +#ifndef OPENSSL_NO_TLS1_3 + if (!s->pha_forced) { + int i, n = 0; + + /* check for cert, if present, we can do post-handshake auth */ + if (s->cert == NULL) + return EXT_RETURN_NOT_SENT; + + for (i = 0; i < SSL_PKEY_NUM; i++) { + if (s->cert->pkeys[i].x509 != NULL + && s->cert->pkeys[i].privatekey != NULL) + n++; + } + + /* no identity certificates, so no extension */ + if (n == 0) + return EXT_RETURN_NOT_SENT; + } + + /* construct extension - 0 length, no contents */ + if (!WPACKET_put_bytes_u16(pkt, TLSEXT_TYPE_post_handshake_auth) + || !WPACKET_start_sub_packet_u16(pkt) + || !WPACKET_close(pkt)) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, + SSL_F_TLS_CONSTRUCT_CTOS_POST_HANDSHAKE_AUTH, + ERR_R_INTERNAL_ERROR); + return EXT_RETURN_FAIL; + } + + s->post_handshake_auth = SSL_PHA_EXT_SENT; + + return EXT_RETURN_SENT; +#else + return EXT_RETURN_NOT_SENT; +#endif +} + + /* * Parse the server's renegotiation binding and abort if it's not right */
diff --git a/ssl/statem/extensions_cust.c b/ssl/statem/extensions_cust.c index 0a23630..60a7c37 100644 --- a/ssl/statem/extensions_cust.c +++ b/ssl/statem/extensions_cust.c
@@ -525,6 +525,7 @@ case TLSEXT_TYPE_early_data: case TLSEXT_TYPE_certificate_authorities: case TLSEXT_TYPE_psk: + case TLSEXT_TYPE_post_handshake_auth: return 1; default: return 0;
diff --git a/ssl/statem/extensions_srvr.c b/ssl/statem/extensions_srvr.c index 0a7bac4..27ff5a5 100644 --- a/ssl/statem/extensions_srvr.c +++ b/ssl/statem/extensions_srvr.c
@@ -1161,6 +1161,20 @@ return 0; } +int tls_parse_ctos_post_handshake_auth(SSL *s, PACKET *pkt, unsigned int context, + X509 *x, size_t chainidx) +{ + if (PACKET_remaining(pkt) != 0) { + SSLfatal(s, SSL_AD_DECODE_ERROR, SSL_F_TLS_PARSE_CTOS_POST_HANDSHAKE_AUTH, + SSL_R_POST_HANDSHAKE_AUTH_ENCODING_ERR); + return 0; + } + + s->post_handshake_auth = SSL_PHA_EXT_RECEIVED; + + return 1; +} + /* * Add the server's renegotiation binding */
diff --git a/ssl/statem/statem_clnt.c b/ssl/statem/statem_clnt.c index e79bd7b..5050233 100644 --- a/ssl/statem/statem_clnt.c +++ b/ssl/statem/statem_clnt.c
@@ -1,5 +1,5 @@ /* - * Copyright 1995-2016 The OpenSSL Project Authors. All Rights Reserved. + * Copyright 1995-2018 The OpenSSL Project Authors. All Rights Reserved. * Copyright (c) 2002, Oracle and/or its affiliates. All rights reserved * Copyright 2005 Nokia. All rights reserved. * @@ -160,6 +160,26 @@ st->hand_state = TLS_ST_CR_KEY_UPDATE; return 1; } + if (mt == SSL3_MT_CERTIFICATE_REQUEST) { +#if DTLS_MAX_VERSION != DTLS1_2_VERSION +# error TODO(DTLS1.3): Restore digest for PHA before adding message. +#endif + if (!SSL_IS_DTLS(s) && s->post_handshake_auth == SSL_PHA_EXT_SENT) { + s->post_handshake_auth = SSL_PHA_REQUESTED; + /* + * In TLS, this is called before the message is added to the + * digest. In DTLS, this is expected to be called after adding + * to the digest. Either move the digest restore, or add the + * message here after the swap, or do it after the clientFinished? + */ + if (!tls13_restore_handshake_digest_for_pha(s)) { + /* SSLfatal() already called */ + return 0; + } + st->hand_state = TLS_ST_CR_CERT_REQ; + return 1; + } + } break; } @@ -375,6 +395,13 @@ * ossl_statem_client_write_transition(). */ switch (st->hand_state) { + case TLS_ST_CR_CERT_REQ: + if (s->post_handshake_auth == SSL_PHA_REQUESTED) { + st->hand_state = TLS_ST_CW_CERT; + return WRITE_TRAN_CONTINUE; + } + /* Fall through */ + default: /* Shouldn't happen */ SSLfatal(s, SSL_AD_INTERNAL_ERROR, @@ -798,11 +825,17 @@ return WORK_MORE_B; if (SSL_IS_TLS13(s)) { - if (!s->method->ssl3_enc->change_cipher_state(s, - SSL3_CC_APPLICATION | SSL3_CHANGE_CIPHER_CLIENT_WRITE)) { + if (!tls13_save_handshake_digest_for_pha(s)) { /* SSLfatal() already called */ return WORK_ERROR; } + if (s->post_handshake_auth != SSL_PHA_REQUESTED) { + if (!s->method->ssl3_enc->change_cipher_state(s, + SSL3_CC_APPLICATION | SSL3_CHANGE_CIPHER_CLIENT_WRITE)) { + /* SSLfatal() already called */ + return WORK_ERROR; + } + } } break; @@ -2399,9 +2432,11 @@ OPENSSL_free(s->s3->tmp.ctype); s->s3->tmp.ctype = NULL; s->s3->tmp.ctype_len = 0; + OPENSSL_free(s->pha_context); + s->pha_context = NULL; - /* TODO(TLS1.3) need to process request context, for now ignore */ - if (!PACKET_get_length_prefixed_1(pkt, &reqctx)) { + if (!PACKET_get_length_prefixed_1(pkt, &reqctx) || + !PACKET_memdup(&reqctx, &s->pha_context, &s->pha_context_len)) { SSLfatal(s, SSL_AD_DECODE_ERROR, SSL_F_TLS_PROCESS_CERTIFICATE_REQUEST, SSL_R_LENGTH_MISMATCH); @@ -3332,6 +3367,7 @@ if (s->cert->cert_flags & SSL_CERT_FLAGS_CHECK_TLS_STRICT && !tls1_check_chain(s, NULL, NULL, NULL, -2)) return 0; + return 1; } @@ -3357,8 +3393,12 @@ } s->rwstate = SSL_NOTHING; } - if (ssl3_check_client_certificate(s)) + if (ssl3_check_client_certificate(s)) { + if (s->post_handshake_auth == SSL_PHA_REQUESTED) { + return WORK_FINISHED_STOP; + } return WORK_FINISHED_CONTINUE; + } /* Fall through to WORK_MORE_B */ wst = WORK_MORE_B; @@ -3403,6 +3443,8 @@ } } + if (s->post_handshake_auth == SSL_PHA_REQUESTED) + return WORK_FINISHED_STOP; return WORK_FINISHED_CONTINUE; } @@ -3414,14 +3456,19 @@ int tls_construct_client_certificate(SSL *s, WPACKET *pkt) { - /* - * TODO(TLS1.3): For now we must put an empty context. Needs to be filled in - * later - */ - if (SSL_IS_TLS13(s) && !WPACKET_put_bytes_u8(pkt, 0)) { - SSLfatal(s, SSL_AD_INTERNAL_ERROR, - SSL_F_TLS_CONSTRUCT_CLIENT_CERTIFICATE, ERR_R_INTERNAL_ERROR); - return 0; + if (SSL_IS_TLS13(s)) { + if (s->pha_context == NULL) { + /* no context available, add 0-length context */ + if (!WPACKET_put_bytes_u8(pkt, 0)) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, + SSL_F_TLS_CONSTRUCT_CLIENT_CERTIFICATE, ERR_R_INTERNAL_ERROR); + return 0; + } + } else if (!WPACKET_sub_memcpy_u8(pkt, s->pha_context, s->pha_context_len)) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, + SSL_F_TLS_CONSTRUCT_CLIENT_CERTIFICATE, ERR_R_INTERNAL_ERROR); + return 0; + } } if (!ssl3_output_cert_chain(s, pkt, (s->s3->tmp.cert_req == 2) ? NULL
diff --git a/ssl/statem/statem_lib.c b/ssl/statem/statem_lib.c index 87ce280..f57f33c 100644 --- a/ssl/statem/statem_lib.c +++ b/ssl/statem/statem_lib.c
@@ -1,5 +1,5 @@ /* - * Copyright 1995-2016 The OpenSSL Project Authors. All Rights Reserved. + * Copyright 1995-2018 The OpenSSL Project Authors. All Rights Reserved. * Copyright (c) 2002, Oracle and/or its affiliates. All rights reserved * * Licensed under the OpenSSL license (the "License"). You may not use @@ -43,12 +43,15 @@ /* * should not be done for 'Hello Request's, but in that case we'll * ignore the result anyway + * TLS1.3 KeyUpdate and NewSessionTicket do not need to be added */ - if (!ssl3_finish_mac(s, - (unsigned char *)&s->init_buf->data[s->init_off], - written)) - return -1; - + if (!SSL_IS_TLS13(s) || (s->statem.hand_state != TLS_ST_SW_SESSION_TICKET + && s->statem.hand_state != TLS_ST_CW_KEY_UPDATE + && s->statem.hand_state != TLS_ST_SW_KEY_UPDATE)) + if (!ssl3_finish_mac(s, + (unsigned char *)&s->init_buf->data[s->init_off], + written)) + return -1; if (written == s->init_num) { if (s->msg_callback) s->msg_callback(1, s->version, type, s->init_buf->data, @@ -504,7 +507,7 @@ size_t slen; /* This is a real handshake so make sure we clean it up at the end */ - if (!s->server) + if (!s->server && s->post_handshake_auth != SSL_PHA_REQUESTED) s->statem.cleanuphand = 1; /* @@ -741,8 +744,14 @@ /* This is a real handshake so make sure we clean it up at the end */ - if (s->server) - s->statem.cleanuphand = 1; + if (s->server) { + if (s->post_handshake_auth != SSL_PHA_REQUESTED) + s->statem.cleanuphand = 1; + if (SSL_IS_TLS13(s) && !tls13_save_handshake_digest_for_pha(s)) { + /* SSLfatal() already called */ + return MSG_PROCESS_ERROR; + } + } /* * In TLSv1.3 a Finished message signals a key change so the end of the @@ -801,7 +810,8 @@ */ if (SSL_IS_TLS13(s)) { if (s->server) { - if (!s->method->ssl3_enc->change_cipher_state(s, + if (s->post_handshake_auth != SSL_PHA_REQUESTED && + !s->method->ssl3_enc->change_cipher_state(s, SSL3_CC_APPLICATION | SSL3_CHANGE_CIPHER_SERVER_READ)) { /* SSLfatal() already called */ return MSG_PROCESS_ERROR; @@ -1021,6 +1031,10 @@ s->init_num = 0; } + if (SSL_IS_TLS13(s) && !s->server + && s->post_handshake_auth == SSL_PHA_REQUESTED) + s->post_handshake_auth = SSL_PHA_EXT_SENT; + if (s->statem.cleanuphand) { /* skipped if we just sent a HelloRequest */ s->renegotiate = 0; @@ -1237,18 +1251,24 @@ /* * We defer feeding in the HRR until later. We'll do it as part of * processing the message + * The TLsv1.3 handshake transcript stops at the ClientFinished + * message. */ #define SERVER_HELLO_RANDOM_OFFSET (SSL3_HM_HEADER_LENGTH + 2) - if (s->s3->tmp.message_type != SSL3_MT_SERVER_HELLO - || s->init_num < SERVER_HELLO_RANDOM_OFFSET + SSL3_RANDOM_SIZE - || memcmp(hrrrandom, - s->init_buf->data + SERVER_HELLO_RANDOM_OFFSET, - SSL3_RANDOM_SIZE) != 0) { - if (!ssl3_finish_mac(s, (unsigned char *)s->init_buf->data, - s->init_num + SSL3_HM_HEADER_LENGTH)) { - /* SSLfatal() already called */ - *len = 0; - return 0; + /* KeyUpdate and NewSessionTicket do not need to be added */ + if (!SSL_IS_TLS13(s) || (s->s3->tmp.message_type != SSL3_MT_NEWSESSION_TICKET + && s->s3->tmp.message_type != SSL3_MT_KEY_UPDATE)) { + if (s->s3->tmp.message_type != SSL3_MT_SERVER_HELLO + || s->init_num < SERVER_HELLO_RANDOM_OFFSET + SSL3_RANDOM_SIZE + || memcmp(hrrrandom, + s->init_buf->data + SERVER_HELLO_RANDOM_OFFSET, + SSL3_RANDOM_SIZE) != 0) { + if (!ssl3_finish_mac(s, (unsigned char *)s->init_buf->data, + s->init_num + SSL3_HM_HEADER_LENGTH)) { + /* SSLfatal() already called */ + *len = 0; + return 0; + } } } if (s->msg_callback) @@ -2208,3 +2228,54 @@ *ptbs = tbs; return tbslen; } + +/* + * Saves the current handshake digest for Post-Handshake Auth, + * Done after ClientFinished is processed, done exactly once + */ +int tls13_save_handshake_digest_for_pha(SSL *s) +{ + if (s->pha_dgst == NULL) { + if (!ssl3_digest_cached_records(s, 1)) + /* SSLfatal() already called */ + return 0; + + s->pha_dgst = EVP_MD_CTX_new(); + if (s->pha_dgst == NULL) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, + SSL_F_TLS13_SAVE_HANDSHAKE_DIGEST_FOR_PHA, + ERR_R_INTERNAL_ERROR); + return 0; + } + if (!EVP_MD_CTX_copy_ex(s->pha_dgst, + s->s3->handshake_dgst)) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, + SSL_F_TLS13_SAVE_HANDSHAKE_DIGEST_FOR_PHA, + ERR_R_INTERNAL_ERROR); + return 0; + } + } + return 1; +} + +/* + * Restores the Post-Handshake Auth handshake digest + * Done just before sending/processing the Cert Request + */ +int tls13_restore_handshake_digest_for_pha(SSL *s) +{ + if (s->pha_dgst == NULL) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, + SSL_F_TLS13_RESTORE_HANDSHAKE_DIGEST_FOR_PHA, + ERR_R_INTERNAL_ERROR); + return 0; + } + if (!EVP_MD_CTX_copy_ex(s->s3->handshake_dgst, + s->pha_dgst)) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, + SSL_F_TLS13_RESTORE_HANDSHAKE_DIGEST_FOR_PHA, + ERR_R_INTERNAL_ERROR); + return 0; + } + return 1; +}
diff --git a/ssl/statem/statem_locl.h b/ssl/statem/statem_locl.h index f16d3cb..af081eb 100644 --- a/ssl/statem/statem_locl.h +++ b/ssl/statem/statem_locl.h
@@ -1,5 +1,5 @@ /* - * Copyright 2015-2017 The OpenSSL Project Authors. All Rights Reserved. + * Copyright 2015-2018 The OpenSSL Project Authors. All Rights Reserved. * * Licensed under the OpenSSL license (the "License"). You may not use * this file except in compliance with the License. You can obtain a copy @@ -235,6 +235,8 @@ X509 *x, size_t chainidx); int tls_parse_ctos_psk(SSL *s, PACKET *pkt, unsigned int context, X509 *x, size_t chainidx); +int tls_parse_ctos_post_handshake_auth(SSL *, PACKET *pkt, unsigned int context, + X509 *x, size_t chainidx); EXT_RETURN tls_construct_stoc_renegotiate(SSL *s, WPACKET *pkt, unsigned int context, X509 *x, @@ -365,6 +367,9 @@ size_t chainidx); EXT_RETURN tls_construct_ctos_psk(SSL *s, WPACKET *pkt, unsigned int context, X509 *x, size_t chainidx); +EXT_RETURN tls_construct_ctos_post_handshake_auth(SSL *s, WPACKET *pkt, unsigned int context, + X509 *x, size_t chainidx); + int tls_parse_stoc_renegotiate(SSL *s, PACKET *pkt, unsigned int context, X509 *x, size_t chainidx); int tls_parse_stoc_server_name(SSL *s, PACKET *pkt, unsigned int context, @@ -411,3 +416,6 @@ size_t chainidx); int tls_handle_alpn(SSL *s); + +int tls13_save_handshake_digest_for_pha(SSL *s); +int tls13_restore_handshake_digest_for_pha(SSL *s);
diff --git a/ssl/statem/statem_srvr.c b/ssl/statem/statem_srvr.c index 51b6ce9..812780a 100644 --- a/ssl/statem/statem_srvr.c +++ b/ssl/statem/statem_srvr.c
@@ -1,5 +1,5 @@ /* - * Copyright 1995-2016 The OpenSSL Project Authors. All Rights Reserved. + * Copyright 1995-2018 The OpenSSL Project Authors. All Rights Reserved. * Copyright (c) 2002, Oracle and/or its affiliates. All rights reserved * Copyright 2005 Nokia. All rights reserved. * @@ -107,6 +107,13 @@ */ if (s->early_data_state == SSL_EARLY_DATA_READING) break; + + if (mt == SSL3_MT_CERTIFICATE + && s->post_handshake_auth == SSL_PHA_REQUESTED) { + st->hand_state = TLS_ST_SR_CERT; + return 1; + } + if (mt == SSL3_MT_KEY_UPDATE) { st->hand_state = TLS_ST_SR_KEY_UPDATE; return 1; @@ -325,16 +332,22 @@ * 1: Yes * 0: No */ -static int send_certificate_request(SSL *s) +int send_certificate_request(SSL *s) { if ( /* don't request cert unless asked for it: */ s->verify_mode & SSL_VERIFY_PEER /* - * if SSL_VERIFY_CLIENT_ONCE is set, don't request cert - * during re-negotiation: + * don't request if post-handshake-only unless doing + * post-handshake in TLSv1.3: */ - && (s->s3->tmp.finish_md_len == 0 || + && (!SSL_IS_TLS13(s) || !(s->verify_mode & SSL_VERIFY_POST_HANDSHAKE) + || s->post_handshake_auth == SSL_PHA_REQUEST_PENDING) + /* + * if SSL_VERIFY_CLIENT_ONCE is set, don't request cert + * a second time: + */ + && (s->certreqs_sent < 1 || !(s->verify_mode & SSL_VERIFY_CLIENT_ONCE)) /* * never request cert in anonymous ciphersuites (see @@ -388,6 +401,10 @@ st->hand_state = TLS_ST_SW_KEY_UPDATE; return WRITE_TRAN_CONTINUE; } + if (s->post_handshake_auth == SSL_PHA_REQUEST_PENDING) { + st->hand_state = TLS_ST_SW_CERT_REQ; + return WRITE_TRAN_CONTINUE; + } /* Try to read from the client instead */ return WRITE_TRAN_FINISHED; @@ -423,7 +440,12 @@ return WRITE_TRAN_CONTINUE; case TLS_ST_SW_CERT_REQ: - st->hand_state = TLS_ST_SW_CERT; + if (s->post_handshake_auth == SSL_PHA_REQUEST_PENDING) { + s->post_handshake_auth = SSL_PHA_REQUESTED; + st->hand_state = TLS_ST_OK; + } else { + st->hand_state = TLS_ST_SW_CERT; + } return WRITE_TRAN_CONTINUE; case TLS_ST_SW_CERT: @@ -450,6 +472,8 @@ * and give the application the opportunity to delay sending the * session ticket? */ + if (s->post_handshake_auth == SSL_PHA_REQUESTED) + s->post_handshake_auth = SSL_PHA_EXT_RECEIVED; st->hand_state = TLS_ST_SW_SESSION_TICKET; return WRITE_TRAN_CONTINUE; @@ -863,6 +887,13 @@ } break; + case TLS_ST_SW_CERT_REQ: + if (s->post_handshake_auth == SSL_PHA_REQUEST_PENDING) { + if (statem_flush(s) != 1) + return WORK_MORE_A; + } + break; + case TLS_ST_SW_KEY_UPDATE: if (statem_flush(s) != 1) return WORK_MORE_A; @@ -2702,12 +2733,30 @@ int tls_construct_certificate_request(SSL *s, WPACKET *pkt) { if (SSL_IS_TLS13(s)) { - /* TODO(TLS1.3) for now send empty request context */ - if (!WPACKET_put_bytes_u8(pkt, 0)) { - SSLfatal(s, SSL_AD_INTERNAL_ERROR, - SSL_F_TLS_CONSTRUCT_CERTIFICATE_REQUEST, - ERR_R_INTERNAL_ERROR); - return 0; + /* Send random context when doing post-handshake auth */ + if (s->post_handshake_auth == SSL_PHA_REQUEST_PENDING) { + OPENSSL_free(s->pha_context); + s->pha_context_len = 32; + if ((s->pha_context = OPENSSL_malloc(s->pha_context_len)) == NULL + || ssl_randbytes(s, s->pha_context, s->pha_context_len) <= 0 + || !WPACKET_sub_memcpy_u8(pkt, s->pha_context, s->pha_context_len)) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, + SSL_F_TLS_CONSTRUCT_CERTIFICATE_REQUEST, + ERR_R_INTERNAL_ERROR); + return 0; + } + /* reset the handshake hash back to just after the ClientFinished */ + if (!tls13_restore_handshake_digest_for_pha(s)) { + /* SSLfatal() already called */ + return 0; + } + } else { + if (!WPACKET_put_bytes_u8(pkt, 0)) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, + SSL_F_TLS_CONSTRUCT_CERTIFICATE_REQUEST, + ERR_R_INTERNAL_ERROR); + return 0; + } } if (!tls_construct_extensions(s, pkt, @@ -2748,6 +2797,7 @@ } done: + s->certreqs_sent++; s->s3->tmp.cert_request = 1; return 1; } @@ -3396,11 +3446,12 @@ int i; MSG_PROCESS_RETURN ret = MSG_PROCESS_ERROR; X509 *x = NULL; - unsigned long l, llen; + unsigned long l; const unsigned char *certstart, *certbytes; STACK_OF(X509) *sk = NULL; PACKET spkt, context; size_t chainidx; + SSL_SESSION *new_sess = NULL; if ((sk = sk_X509_new_null()) == NULL) { SSLfatal(s, SSL_AD_INTERNAL_ERROR, SSL_F_TLS_PROCESS_CLIENT_CERTIFICATE, @@ -3408,10 +3459,16 @@ goto err; } - /* TODO(TLS1.3): For now we ignore the context. We need to verify this */ - if ((SSL_IS_TLS13(s) && !PACKET_get_length_prefixed_1(pkt, &context)) - || !PACKET_get_net_3(pkt, &llen) - || !PACKET_get_sub_packet(pkt, &spkt, llen) + if (SSL_IS_TLS13(s) && (!PACKET_get_length_prefixed_1(pkt, &context) + || (s->pha_context == NULL && PACKET_remaining(&context) != 0) + || (s->pha_context != NULL && + !PACKET_equal(&context, s->pha_context, s->pha_context_len)))) { + SSLfatal(s, SSL_AD_DECODE_ERROR, SSL_F_TLS_PROCESS_CLIENT_CERTIFICATE, + SSL_R_INVALID_CONTEXT); + goto err; + } + + if (!PACKET_get_length_prefixed_3(pkt, &spkt) || PACKET_remaining(pkt) != 0) { SSLfatal(s, SSL_AD_DECODE_ERROR, SSL_F_TLS_PROCESS_CLIENT_CERTIFICATE, SSL_R_LENGTH_MISMATCH); @@ -3516,6 +3573,35 @@ } } + /* + * Sessions must be immutable once they go into the session cache. Otherwise + * we can get multi-thread problems. Therefore we don't "update" sessions, + * we replace them with a duplicate. Here, we need to do this every time + * a new certificate is received via post-handshake authentication, as the + * session may have already gone into the session cache. + */ + + if (s->post_handshake_auth == SSL_PHA_REQUESTED) { + int m = s->session_ctx->session_cache_mode; + + if ((new_sess = ssl_session_dup(s->session, 0)) == 0) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, + SSL_F_TLS_PROCESS_CLIENT_CERTIFICATE, + ERR_R_MALLOC_FAILURE); + goto err; + } + + if (m & SSL_SESS_CACHE_SERVER) { + /* + * Remove the old session from the cache. We carry on if this fails + */ + SSL_CTX_remove_session(s->session_ctx, s->session); + } + + SSL_SESSION_free(s->session); + s->session = new_sess; + } + X509_free(s->session->peer); s->session->peer = sk_X509_shift(sk); s->session->verify_result = s->verify_result; @@ -3523,6 +3609,9 @@ sk_X509_pop_free(s->session->peer_chain, X509_free); s->session->peer_chain = sk; + if (new_sess != NULL) + ssl_update_cache(s, SSL_SESS_CACHE_SERVER); + /* * Freeze the handshake buffer. For <TLS1.3 we do this after the CKE * message
diff --git a/ssl/t1_trce.c b/ssl/t1_trce.c index b998765..fa975be 100644 --- a/ssl/t1_trce.c +++ b/ssl/t1_trce.c
@@ -481,7 +481,8 @@ {TLSEXT_TYPE_padding, "padding"}, {TLSEXT_TYPE_encrypt_then_mac, "encrypt_then_mac"}, {TLSEXT_TYPE_extended_master_secret, "extended_master_secret"}, - {TLSEXT_TYPE_early_data, "early_data"} + {TLSEXT_TYPE_early_data, "early_data"}, + {TLSEXT_TYPE_post_handshake_auth, "post_handshake_auth"} }; static const ssl_trace_tbl ssl_groups_tbl[] = {