diff options
Diffstat (limited to 'src/ssl')
42 files changed, 4629 insertions, 1117 deletions
diff --git a/src/ssl/CMakeLists.txt b/src/ssl/CMakeLists.txt index dc89dcaa..0fb532ea 100644 --- a/src/ssl/CMakeLists.txt +++ b/src/ssl/CMakeLists.txt @@ -50,6 +50,7 @@ add_executable( span_test.cc ssl_test.cc + ssl_c_test.c $<TARGET_OBJECTS:boringssl_gtest_main> ) diff --git a/src/ssl/d1_pkt.cc b/src/ssl/d1_pkt.cc index be595b07..dfb8a67a 100644 --- a/src/ssl/d1_pkt.cc +++ b/src/ssl/d1_pkt.cc @@ -256,7 +256,7 @@ int dtls1_dispatch_alert(SSL *ssl) { if (ret <= 0) { return ret; } - ssl->s3->alert_dispatch = 0; + ssl->s3->alert_dispatch = false; // If the alert is fatal, flush the BIO now. if (ssl->s3->send_alert[0] == SSL3_AL_FATAL) { diff --git a/src/ssl/handoff.cc b/src/ssl/handoff.cc index 0928015d..db5886a6 100644 --- a/src/ssl/handoff.cc +++ b/src/ssl/handoff.cc @@ -450,6 +450,10 @@ bool SSL_apply_handback(SSL *ssl, Span<const uint8_t> handback) { s3->aead_write_ctx->SetVersionIfNullCipher(ssl->version); s3->hs->cert_request = cert_request; + // TODO(davidben): When handoff for TLS 1.3 is added, serialize + // |early_data_reason| and stabilize the constants. + s3->early_data_reason = ssl_early_data_protocol_version; + Array<uint8_t> key_block; if ((type == handback_after_session_resumption || type == handback_after_handshake) && diff --git a/src/ssl/handshake.cc b/src/ssl/handshake.cc index 89be48f8..b8e00704 100644 --- a/src/ssl/handshake.cc +++ b/src/ssl/handshake.cc @@ -648,6 +648,7 @@ int ssl_run_handshake(SSL_HANDSHAKE *hs, bool *out_early_return) { return -1; case ssl_hs_early_data_rejected: + assert(ssl->s3->early_data_reason != ssl_early_data_unknown); ssl->s3->rwstate = SSL_EARLY_DATA_REJECTED; // Cause |SSL_write| to start failing immediately. hs->can_early_write = false; diff --git a/src/ssl/handshake_client.cc b/src/ssl/handshake_client.cc index b0de6708..a53e4303 100644 --- a/src/ssl/handshake_client.cc +++ b/src/ssl/handshake_client.cc @@ -1071,13 +1071,8 @@ static enum ssl_hs_wait_t do_read_server_key_exchange(SSL_HANDSHAKE *hs) { return ssl_hs_error; } - bool sig_ok = ssl_public_key_verify(ssl, signature, signature_algorithm, - hs->peer_pubkey.get(), transcript_data); -#if defined(BORINGSSL_UNSAFE_FUZZER_MODE) - sig_ok = true; - ERR_clear_error(); -#endif - if (!sig_ok) { + if (!ssl_public_key_verify(ssl, signature, signature_algorithm, + hs->peer_pubkey.get(), transcript_data)) { // bad signature OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_SIGNATURE); ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECRYPT_ERROR); diff --git a/src/ssl/handshake_server.cc b/src/ssl/handshake_server.cc index 4622ad08..36aa5606 100644 --- a/src/ssl/handshake_server.cc +++ b/src/ssl/handshake_server.cc @@ -503,6 +503,54 @@ static bool is_probably_jdk11_with_tls13(const SSL_CLIENT_HELLO *client_hello) { return true; } +static bool extract_sni(SSL_HANDSHAKE *hs, uint8_t *out_alert, + const SSL_CLIENT_HELLO *client_hello) { + SSL *const ssl = hs->ssl; + CBS sni; + if (!ssl_client_hello_get_extension(client_hello, &sni, + TLSEXT_TYPE_server_name)) { + // No SNI extension to parse. + return true; + } + + CBS server_name_list, host_name; + uint8_t name_type; + if (!CBS_get_u16_length_prefixed(&sni, &server_name_list) || + !CBS_get_u8(&server_name_list, &name_type) || + // Although the server_name extension was intended to be extensible to + // new name types and multiple names, OpenSSL 1.0.x had a bug which meant + // different name types will cause an error. Further, RFC 4366 originally + // defined syntax inextensibly. RFC 6066 corrected this mistake, but + // adding new name types is no longer feasible. + // + // Act as if the extensibility does not exist to simplify parsing. + !CBS_get_u16_length_prefixed(&server_name_list, &host_name) || + CBS_len(&server_name_list) != 0 || + CBS_len(&sni) != 0) { + *out_alert = SSL_AD_DECODE_ERROR; + return false; + } + + if (name_type != TLSEXT_NAMETYPE_host_name || + CBS_len(&host_name) == 0 || + CBS_len(&host_name) > TLSEXT_MAXLEN_host_name || + CBS_contains_zero_byte(&host_name)) { + *out_alert = SSL_AD_UNRECOGNIZED_NAME; + return false; + } + + // Copy the hostname as a string. + char *raw = nullptr; + if (!CBS_strdup(&host_name, &raw)) { + *out_alert = SSL_AD_INTERNAL_ERROR; + return false; + } + ssl->s3->hostname.reset(raw); + + hs->should_ack_sni = true; + return true; +} + static enum ssl_hs_wait_t do_read_client_hello(SSL_HANDSHAKE *hs) { SSL *const ssl = hs->ssl; @@ -526,6 +574,12 @@ static enum ssl_hs_wait_t do_read_client_hello(SSL_HANDSHAKE *hs) { return ssl_hs_handoff; } + uint8_t alert = SSL_AD_DECODE_ERROR; + if (!extract_sni(hs, &alert, &client_hello)) { + ssl_send_alert(ssl, SSL3_AL_FATAL, alert); + return ssl_hs_error; + } + // Run the early callback. if (ssl->ctx->select_certificate_cb != NULL) { switch (ssl->ctx->select_certificate_cb(&client_hello)) { @@ -553,7 +607,6 @@ static enum ssl_hs_wait_t do_read_client_hello(SSL_HANDSHAKE *hs) { hs->apply_jdk11_workaround = true; } - uint8_t alert = SSL_AD_DECODE_ERROR; if (!negotiate_version(hs, &alert, &client_hello)) { ssl_send_alert(ssl, SSL3_AL_FATAL, alert); return ssl_hs_error; @@ -635,6 +688,8 @@ static enum ssl_hs_wait_t do_select_certificate(SSL_HANDSHAKE *hs) { return ssl_hs_ok; } + ssl->s3->early_data_reason = ssl_early_data_protocol_version; + SSL_CLIENT_HELLO client_hello; if (!ssl_client_hello_init(ssl, &client_hello, msg)) { return ssl_hs_error; @@ -1408,14 +1463,8 @@ static enum ssl_hs_wait_t do_read_client_certificate_verify(SSL_HANDSHAKE *hs) { return ssl_hs_error; } - bool sig_ok = - ssl_public_key_verify(ssl, signature, signature_algorithm, - hs->peer_pubkey.get(), hs->transcript.buffer()); -#if defined(BORINGSSL_UNSAFE_FUZZER_MODE) - sig_ok = true; - ERR_clear_error(); -#endif - if (!sig_ok) { + if (!ssl_public_key_verify(ssl, signature, signature_algorithm, + hs->peer_pubkey.get(), hs->transcript.buffer())) { OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_SIGNATURE); ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECRYPT_ERROR); return ssl_hs_error; diff --git a/src/ssl/internal.h b/src/ssl/internal.h index ee2952a4..b355c7f1 100644 --- a/src/ssl/internal.h +++ b/src/ssl/internal.h @@ -465,6 +465,9 @@ BSSL_NAMESPACE_BEGIN #define SSL_HANDSHAKE_MAC_SHA256 0x2 #define SSL_HANDSHAKE_MAC_SHA384 0x4 +// SSL_MAX_MD_SIZE is size of the largest hash function used in TLS, SHA-384. +#define SSL_MAX_MD_SIZE 48 + // An SSLCipherPreferenceList contains a list of SSL_CIPHERs with equal- // preference groups. For TLS clients, the groups are moot because the server // picks the cipher and groups cannot be expressed on the wire. However, for @@ -560,6 +563,12 @@ bool ssl_cipher_requires_server_key_exchange(const SSL_CIPHER *cipher); // it returns zero. size_t ssl_cipher_get_record_split_len(const SSL_CIPHER *cipher); +// ssl_choose_tls13_cipher returns an |SSL_CIPHER| corresponding with the best +// available from |cipher_suites| compatible with |version| and |group_id|. It +// returns NULL if there isn't a compatible cipher. +const SSL_CIPHER *ssl_choose_tls13_cipher(CBS cipher_suites, uint16_t version, + uint16_t group_id); + // Transcript layer. @@ -1446,13 +1455,13 @@ struct SSL_HANDSHAKE { uint16_t max_version = 0; size_t hash_len = 0; - uint8_t secret[EVP_MAX_MD_SIZE] = {0}; - uint8_t early_traffic_secret[EVP_MAX_MD_SIZE] = {0}; - uint8_t client_handshake_secret[EVP_MAX_MD_SIZE] = {0}; - uint8_t server_handshake_secret[EVP_MAX_MD_SIZE] = {0}; - uint8_t client_traffic_secret_0[EVP_MAX_MD_SIZE] = {0}; - uint8_t server_traffic_secret_0[EVP_MAX_MD_SIZE] = {0}; - uint8_t expected_client_finished[EVP_MAX_MD_SIZE] = {0}; + uint8_t secret[SSL_MAX_MD_SIZE] = {0}; + uint8_t early_traffic_secret[SSL_MAX_MD_SIZE] = {0}; + uint8_t client_handshake_secret[SSL_MAX_MD_SIZE] = {0}; + uint8_t server_handshake_secret[SSL_MAX_MD_SIZE] = {0}; + uint8_t client_traffic_secret_0[SSL_MAX_MD_SIZE] = {0}; + uint8_t server_traffic_secret_0[SSL_MAX_MD_SIZE] = {0}; + uint8_t expected_client_finished[SSL_MAX_MD_SIZE] = {0}; union { // sent is a bitset where the bits correspond to elements of kExtensions @@ -2029,7 +2038,7 @@ struct SSL_X509_METHOD { // check_client_CA_list returns one if |names| is a good list of X.509 // distinguished names and zero otherwise. This is used to ensure that we can // reject unparsable values at handshake time when using crypto/x509. - int (*check_client_CA_list)(STACK_OF(CRYPTO_BUFFER) *names); + bool (*check_client_CA_list)(STACK_OF(CRYPTO_BUFFER) *names); // cert_clear frees and NULLs all X509 certificate-related state. void (*cert_clear)(CERT *cert); @@ -2046,35 +2055,35 @@ struct SSL_X509_METHOD { // session_cache_objects fills out |sess->x509_peer| and |sess->x509_chain| // from |sess->certs| and erases |sess->x509_chain_without_leaf|. It returns - // one on success or zero on error. - int (*session_cache_objects)(SSL_SESSION *session); + // true on success or false on error. + bool (*session_cache_objects)(SSL_SESSION *session); // session_dup duplicates any needed fields from |session| to |new_session|. - // It returns one on success or zero on error. - int (*session_dup)(SSL_SESSION *new_session, const SSL_SESSION *session); + // It returns true on success or false on error. + bool (*session_dup)(SSL_SESSION *new_session, const SSL_SESSION *session); // session_clear frees any X509-related state from |session|. void (*session_clear)(SSL_SESSION *session); // session_verify_cert_chain verifies the certificate chain in |session|, - // sets |session->verify_result| and returns one on success or zero on + // sets |session->verify_result| and returns true on success or false on // error. - int (*session_verify_cert_chain)(SSL_SESSION *session, SSL_HANDSHAKE *ssl, - uint8_t *out_alert); + bool (*session_verify_cert_chain)(SSL_SESSION *session, SSL_HANDSHAKE *ssl, + uint8_t *out_alert); // hs_flush_cached_ca_names drops any cached |X509_NAME|s from |hs|. void (*hs_flush_cached_ca_names)(SSL_HANDSHAKE *hs); - // ssl_new does any neccessary initialisation of |hs|. It returns one on - // success or zero on error. - int (*ssl_new)(SSL_HANDSHAKE *hs); + // ssl_new does any necessary initialisation of |hs|. It returns true on + // success or false on error. + bool (*ssl_new)(SSL_HANDSHAKE *hs); // ssl_free frees anything created by |ssl_new|. void (*ssl_config_free)(SSL_CONFIG *cfg); // ssl_flush_cached_client_CA drops any cached |X509_NAME|s from |ssl|. void (*ssl_flush_cached_client_CA)(SSL_CONFIG *cfg); // ssl_auto_chain_if_needed runs the deprecated auto-chaining logic if // necessary. On success, it updates |ssl|'s certificate configuration as - // needed and returns one. Otherwise, it returns zero. - int (*ssl_auto_chain_if_needed)(SSL_HANDSHAKE *hs); - // ssl_ctx_new does any neccessary initialisation of |ctx|. It returns one on - // success or zero on error. - int (*ssl_ctx_new)(SSL_CTX *ctx); + // needed and returns true. Otherwise, it returns false. + bool (*ssl_auto_chain_if_needed)(SSL_HANDSHAKE *hs); + // ssl_ctx_new does any necessary initialisation of |ctx|. It returns true on + // success or false on error. + bool (*ssl_ctx_new)(SSL_CTX *ctx); // ssl_ctx_free frees anything created by |ssl_ctx_new|. void (*ssl_ctx_free)(SSL_CTX *ctx); // ssl_ctx_flush_cached_client_CA drops any cached |X509_NAME|s from |ctx|. @@ -2164,8 +2173,6 @@ struct SSL3_STATE { // the receive half of the connection. UniquePtr<ERR_SAVE_STATE> read_error; - int alert_dispatch = 0; - int total_renegotiations = 0; // This holds a variable that indicates what we were doing when a 0 or -1 is @@ -2221,6 +2228,10 @@ struct SSL3_STATE { // session_reused indicates whether a session was resumed. bool session_reused : 1; + // delegated_credential_used is whether we presented a delegated credential to + // the peer. + bool delegated_credential_used : 1; + bool send_connection_binding : 1; // In a client, this means that the server supported Channel ID and that a @@ -2244,6 +2255,13 @@ struct SSL3_STATE { // token_binding_negotiated is set if Token Binding was negotiated. bool token_binding_negotiated : 1; + // pq_experimental_signal_seen is true if the peer was observed + // sending/echoing the post-quantum experiment signal. + bool pq_experiment_signal_seen : 1; + + // alert_dispatch is true there is an alert in |send_alert| to be sent. + bool alert_dispatch : 1; + // hs_buf is the buffer of handshake data to process. UniquePtr<BUF_MEM> hs_buf; @@ -2266,6 +2284,9 @@ struct SSL3_STATE { // which resumed a session. int32_t ticket_age_skew = 0; + // ssl_early_data_reason stores details on why 0-RTT was accepted or rejected. + enum ssl_early_data_reason_t early_data_reason = ssl_early_data_unknown; + // aead_read_ctx is the current read cipher state. UniquePtr<SSLAEADContext> aead_read_ctx; @@ -2276,14 +2297,12 @@ struct SSL3_STATE { // one. UniquePtr<SSL_HANDSHAKE> hs; - uint8_t write_traffic_secret[EVP_MAX_MD_SIZE] = {0}; - uint8_t read_traffic_secret[EVP_MAX_MD_SIZE] = {0}; - uint8_t exporter_secret[EVP_MAX_MD_SIZE] = {0}; - uint8_t early_exporter_secret[EVP_MAX_MD_SIZE] = {0}; + uint8_t write_traffic_secret[SSL_MAX_MD_SIZE] = {0}; + uint8_t read_traffic_secret[SSL_MAX_MD_SIZE] = {0}; + uint8_t exporter_secret[SSL_MAX_MD_SIZE] = {0}; uint8_t write_traffic_secret_len = 0; uint8_t read_traffic_secret_len = 0; uint8_t exporter_secret_len = 0; - uint8_t early_exporter_secret_len = 0; // Connection binding to prevent renegotiation attacks uint8_t previous_client_finished[12] = {0}; @@ -2674,7 +2693,8 @@ void ssl_session_renew_timeout(SSL *ssl, SSL_SESSION *session, void ssl_update_cache(SSL_HANDSHAKE *hs, int mode); -int ssl_send_alert(SSL *ssl, int level, int desc); +void ssl_send_alert(SSL *ssl, int level, int desc); +int ssl_send_alert_impl(SSL *ssl, int level, int desc); bool ssl3_get_message(const SSL *ssl, SSLMessage *out); ssl_open_record_t ssl3_open_handshake(SSL *ssl, size_t *out_consumed, uint8_t *out_alert, Span<uint8_t> in); @@ -3170,6 +3190,11 @@ struct ssl_ctx_st { // If enable_early_data is true, early data can be sent and accepted. bool enable_early_data : 1; + // pq_experiment_signal indicates that an empty extension should be sent + // (for clients) or echoed (for servers) to indicate participation in an + // experiment of post-quantum key exchanges. + bool pq_experiment_signal : 1; + private: ~ssl_ctx_st(); friend void SSL_CTX_free(SSL_CTX *); diff --git a/src/ssl/s3_both.cc b/src/ssl/s3_both.cc index 27e9454f..842ec676 100644 --- a/src/ssl/s3_both.cc +++ b/src/ssl/s3_both.cc @@ -116,6 +116,8 @@ #include <limits.h> #include <string.h> +#include <tuple> + #include <openssl/buf.h> #include <openssl/bytestring.h> #include <openssl/err.h> @@ -652,4 +654,72 @@ void ssl3_next_message(SSL *ssl) { } } +// CipherScorer produces a "score" for each possible cipher suite offered by +// the client. +class CipherScorer { + public: + CipherScorer(uint16_t group_id) + : aes_is_fine_(EVP_has_aes_hardware()), + security_128_is_fine_(group_id != SSL_CURVE_CECPQ2 && + group_id != SSL_CURVE_CECPQ2b) {} + + typedef std::tuple<bool, bool, bool> Score; + + // MinScore returns a |Score| that will compare less than the score of all + // cipher suites. + Score MinScore() const { + return Score(false, false, false); + } + + Score Evaluate(const SSL_CIPHER *a) const { + return Score( + // Something is always preferable to nothing. + true, + // Either 128-bit is fine, or 256-bit is preferred. + security_128_is_fine_ || a->algorithm_enc != SSL_AES128GCM, + // Either AES is fine, or else ChaCha20 is preferred. + aes_is_fine_ || a->algorithm_enc == SSL_CHACHA20POLY1305); + } + + private: + const bool aes_is_fine_; + const bool security_128_is_fine_; +}; + +const SSL_CIPHER *ssl_choose_tls13_cipher(CBS cipher_suites, uint16_t version, + uint16_t group_id) { + if (CBS_len(&cipher_suites) % 2 != 0) { + return nullptr; + } + + const SSL_CIPHER *best = nullptr; + CipherScorer scorer(group_id); + CipherScorer::Score best_score = scorer.MinScore(); + + while (CBS_len(&cipher_suites) > 0) { + uint16_t cipher_suite; + if (!CBS_get_u16(&cipher_suites, &cipher_suite)) { + return nullptr; + } + + // Limit to TLS 1.3 ciphers we know about. + const SSL_CIPHER *candidate = SSL_get_cipher_by_value(cipher_suite); + if (candidate == nullptr || + SSL_CIPHER_get_min_version(candidate) > version || + SSL_CIPHER_get_max_version(candidate) < version) { + continue; + } + + const CipherScorer::Score candidate_score = scorer.Evaluate(candidate); + // |candidate_score| must be larger to displace the current choice. That way + // the client's order controls between ciphers with an equal score. + if (candidate_score > best_score) { + best = candidate; + best_score = candidate_score; + } + } + + return best; +} + BSSL_NAMESPACE_END diff --git a/src/ssl/s3_lib.cc b/src/ssl/s3_lib.cc index 0e0770c1..41dd5889 100644 --- a/src/ssl/s3_lib.cc +++ b/src/ssl/s3_lib.cc @@ -172,13 +172,16 @@ SSL3_STATE::SSL3_STATE() has_message(false), initial_handshake_complete(false), session_reused(false), + delegated_credential_used(false), send_connection_binding(false), channel_id_valid(false), key_update_pending(false), wpend_pending(false), early_data_accepted(false), tls13_downgrade(false), - token_binding_negotiated(false) {} + token_binding_negotiated(false), + pq_experiment_signal_seen(false), + alert_dispatch(false) {} SSL3_STATE::~SSL3_STATE() {} diff --git a/src/ssl/s3_pkt.cc b/src/ssl/s3_pkt.cc index abc6798e..a54bb001 100644 --- a/src/ssl/s3_pkt.cc +++ b/src/ssl/s3_pkt.cc @@ -118,6 +118,7 @@ #include <openssl/mem.h> #include <openssl/rand.h> +#include "../crypto/err/internal.h" #include "../crypto/internal.h" #include "internal.h" @@ -381,7 +382,24 @@ ssl_open_record_t ssl3_open_change_cipher_spec(SSL *ssl, size_t *out_consumed, return ssl_open_record_success; } -int ssl_send_alert(SSL *ssl, int level, int desc) { +void ssl_send_alert(SSL *ssl, int level, int desc) { + // This function is called in response to a fatal error from the peer. Ignore + // any failures writing the alert and report only the original error. In + // particular, if the transport uses |SSL_write|, our existing error will be + // clobbered so we must save and restore the error queue. See + // https://crbug.com/959305. + // + // TODO(davidben): Return the alert out of the handshake, rather than calling + // this function internally everywhere. + // + // TODO(davidben): This does not allow retrying if the alert hit EAGAIN. See + // https://crbug.com/boringssl/130. + UniquePtr<ERR_SAVE_STATE> err_state(ERR_save_state()); + ssl_send_alert_impl(ssl, level, desc); + ERR_restore_state(err_state.get()); +} + +int ssl_send_alert_impl(SSL *ssl, int level, int desc) { // It is illegal to send an alert when we've already sent a closing one. if (ssl->s3->write_shutdown != ssl_shutdown_none) { OPENSSL_PUT_ERROR(SSL, SSL_R_PROTOCOL_IS_SHUTDOWN); @@ -396,7 +414,7 @@ int ssl_send_alert(SSL *ssl, int level, int desc) { ssl->s3->write_shutdown = ssl_shutdown_error; } - ssl->s3->alert_dispatch = 1; + ssl->s3->alert_dispatch = true; ssl->s3->send_alert[0] = level; ssl->s3->send_alert[1] = desc; if (ssl->s3->write_buffer.empty()) { @@ -423,7 +441,7 @@ int ssl3_dispatch_alert(SSL *ssl) { } } - ssl->s3->alert_dispatch = 0; + ssl->s3->alert_dispatch = false; // If the alert is fatal, flush the BIO now. if (ssl->s3->send_alert[0] == SSL3_AL_FATAL) { diff --git a/src/ssl/ssl_c_test.c b/src/ssl/ssl_c_test.c new file mode 100644 index 00000000..02f8655d --- /dev/null +++ b/src/ssl/ssl_c_test.c @@ -0,0 +1,15 @@ +#include <openssl/ssl.h> + +int BORINGSSL_enum_c_type_test(void); + +int BORINGSSL_enum_c_type_test(void) { +#if defined(__cplusplus) +#error "This is testing the behaviour of the C compiler." +#error "It's pointless to build it in C++ mode." +#endif + + // In C++, the enums in ssl.h are explicitly typed as ints to allow them to + // be predeclared. This function confirms that the C compiler believes them + // to be the same size as ints. They may differ in signedness, however. + return sizeof(enum ssl_private_key_result_t) == sizeof(int); +} diff --git a/src/ssl/ssl_cert.cc b/src/ssl/ssl_cert.cc index 54df38f7..b565a356 100644 --- a/src/ssl/ssl_cert.cc +++ b/src/ssl/ssl_cert.cc @@ -1010,3 +1010,7 @@ int SSL_set1_delegated_credential(SSL *ssl, CRYPTO_BUFFER *dc, EVP_PKEY *pkey, return cert_set_dc(ssl->config->cert.get(), dc, pkey, key_method); } + +int SSL_delegated_credential_used(const SSL *ssl) { + return ssl->s3->delegated_credential_used; +} diff --git a/src/ssl/ssl_key_share.cc b/src/ssl/ssl_key_share.cc index 78d2aa16..826fb1ac 100644 --- a/src/ssl/ssl_key_share.cc +++ b/src/ssl/ssl_key_share.cc @@ -31,7 +31,7 @@ #include "internal.h" #include "../crypto/internal.h" - +#include "../third_party/sike/sike.h" BSSL_NAMESPACE_BEGIN @@ -300,6 +300,87 @@ class CECPQ2KeyShare : public SSLKeyShare { HRSS_private_key hrss_private_key_; }; +class CECPQ2bKeyShare : public SSLKeyShare { + public: + uint16_t GroupID() const override { return SSL_CURVE_CECPQ2b; } + + bool Offer(CBB *out) override { + uint8_t public_x25519[32] = {0}; + X25519_keypair(public_x25519, private_x25519_); + if (!SIKE_keypair(private_sike_, public_sike_)) { + return false; + } + + return CBB_add_bytes(out, public_x25519, sizeof(public_x25519)) && + CBB_add_bytes(out, public_sike_, sizeof(public_sike_)); + } + + bool Accept(CBB *out_public_key, Array<uint8_t> *out_secret, + uint8_t *out_alert, Span<const uint8_t> peer_key) override { + uint8_t public_x25519[32]; + uint8_t private_x25519[32]; + uint8_t sike_ciphertext[SIKE_CT_BYTESZ] = {0}; + + *out_alert = SSL_AD_INTERNAL_ERROR; + + if (peer_key.size() != sizeof(public_x25519) + SIKE_PUB_BYTESZ) { + *out_alert = SSL_AD_DECODE_ERROR; + OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_ECPOINT); + return false; + } + + Array<uint8_t> secret; + if (!secret.Init(sizeof(private_x25519_) + SIKE_SS_BYTESZ)) { + OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE); + return false; + } + + X25519_keypair(public_x25519, private_x25519); + if (!X25519(secret.data(), private_x25519, peer_key.data())) { + *out_alert = SSL_AD_DECODE_ERROR; + OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_ECPOINT); + return false; + } + + SIKE_encaps(secret.data() + sizeof(private_x25519_), sike_ciphertext, + peer_key.data() + sizeof(public_x25519)); + *out_secret = std::move(secret); + + return CBB_add_bytes(out_public_key, public_x25519, + sizeof(public_x25519)) && + CBB_add_bytes(out_public_key, sike_ciphertext, + sizeof(sike_ciphertext)); + } + + bool Finish(Array<uint8_t> *out_secret, uint8_t *out_alert, + Span<const uint8_t> peer_key) override { + *out_alert = SSL_AD_INTERNAL_ERROR; + + Array<uint8_t> secret; + if (!secret.Init(sizeof(private_x25519_) + SIKE_SS_BYTESZ)) { + OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE); + return false; + } + + if (peer_key.size() != 32 + SIKE_CT_BYTESZ || + !X25519(secret.data(), private_x25519_, peer_key.data())) { + *out_alert = SSL_AD_DECODE_ERROR; + OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_ECPOINT); + return false; + } + + SIKE_decaps(secret.data() + sizeof(private_x25519_), peer_key.data() + 32, + public_sike_, private_sike_); + *out_secret = std::move(secret); + return true; + } + + private: + uint8_t private_x25519_[32]; + uint8_t private_sike_[SIKE_PRV_BYTESZ]; + uint8_t public_sike_[SIKE_PUB_BYTESZ]; +}; + CONSTEXPR_ARRAY NamedGroup kNamedGroups[] = { {NID_secp224r1, SSL_CURVE_SECP224R1, "P-224", "secp224r1"}, {NID_X9_62_prime256v1, SSL_CURVE_SECP256R1, "P-256", "prime256v1"}, @@ -307,6 +388,7 @@ CONSTEXPR_ARRAY NamedGroup kNamedGroups[] = { {NID_secp521r1, SSL_CURVE_SECP521R1, "P-521", "secp521r1"}, {NID_X25519, SSL_CURVE_X25519, "X25519", "x25519"}, {NID_CECPQ2, SSL_CURVE_CECPQ2, "CECPQ2", "CECPQ2"}, + {NID_CECPQ2b, SSL_CURVE_CECPQ2b, "CECPQ2b", "CECPQ2b"}, }; } // namespace @@ -333,6 +415,8 @@ UniquePtr<SSLKeyShare> SSLKeyShare::Create(uint16_t group_id) { return UniquePtr<SSLKeyShare>(New<X25519KeyShare>()); case SSL_CURVE_CECPQ2: return UniquePtr<SSLKeyShare>(New<CECPQ2KeyShare>()); + case SSL_CURVE_CECPQ2b: + return UniquePtr<SSLKeyShare>(New<CECPQ2bKeyShare>()); default: return nullptr; } diff --git a/src/ssl/ssl_lib.cc b/src/ssl/ssl_lib.cc index f9910f70..00ee7da2 100644 --- a/src/ssl/ssl_lib.cc +++ b/src/ssl/ssl_lib.cc @@ -569,7 +569,8 @@ ssl_ctx_st::ssl_ctx_st(const SSL_METHOD *ssl_method) false_start_allowed_without_alpn(false), ignore_tls13_downgrade(false), handoff(false), - enable_early_data(false) { + enable_early_data(false), + pq_experiment_signal(false) { CRYPTO_MUTEX_init(&lock); CRYPTO_new_ex_data(&ex_data); } @@ -1195,7 +1196,7 @@ int SSL_shutdown(SSL *ssl) { if (ssl->s3->write_shutdown != ssl_shutdown_close_notify) { // Send a close_notify. - if (ssl_send_alert(ssl, SSL3_AL_WARNING, SSL_AD_CLOSE_NOTIFY) <= 0) { + if (ssl_send_alert_impl(ssl, SSL3_AL_WARNING, SSL_AD_CLOSE_NOTIFY) <= 0) { return -1; } } else if (ssl->s3->alert_dispatch) { @@ -1242,7 +1243,15 @@ int SSL_send_fatal_alert(SSL *ssl, uint8_t alert) { return ssl->method->dispatch_alert(ssl); } - return ssl_send_alert(ssl, SSL3_AL_FATAL, alert); + return ssl_send_alert_impl(ssl, SSL3_AL_FATAL, alert); +} + +void SSL_CTX_enable_pq_experiment_signal(SSL_CTX *ctx) { + ctx->pq_experiment_signal = true; +} + +int SSL_pq_experiment_signal_seen(const SSL *ssl) { + return ssl->s3->pq_experiment_signal_seen; } int SSL_set_quic_transport_params(SSL *ssl, const uint8_t *params, @@ -1294,6 +1303,10 @@ void SSL_reset_early_data_reject(SSL *ssl) { ssl->s3->wpend_pending = false; } +enum ssl_early_data_reason_t SSL_get_early_data_reason(const SSL *ssl) { + return ssl->s3->early_data_reason; +} + static int bio_retry_reason_to_error(int reason) { switch (reason) { case BIO_RR_CONNECT: diff --git a/src/ssl/ssl_privkey.cc b/src/ssl/ssl_privkey.cc index 1ddb1b16..23f8d124 100644 --- a/src/ssl/ssl_privkey.cc +++ b/src/ssl/ssl_privkey.cc @@ -236,9 +236,16 @@ bool ssl_public_key_verify(SSL *ssl, Span<const uint8_t> signature, uint16_t sigalg, EVP_PKEY *pkey, Span<const uint8_t> in) { ScopedEVP_MD_CTX ctx; - return setup_ctx(ssl, ctx.get(), pkey, sigalg, true /* verify */) && - EVP_DigestVerify(ctx.get(), signature.data(), signature.size(), - in.data(), in.size()); + if (!setup_ctx(ssl, ctx.get(), pkey, sigalg, true /* verify */)) { + return false; + } + bool ok = EVP_DigestVerify(ctx.get(), signature.data(), signature.size(), + in.data(), in.size()); +#if defined(BORINGSSL_UNSAFE_FUZZER_MODE) + ok = true; + ERR_clear_error(); +#endif + return ok; } enum ssl_private_key_result_t ssl_private_key_decrypt(SSL_HANDSHAKE *hs, diff --git a/src/ssl/ssl_session.cc b/src/ssl/ssl_session.cc index 927dd1ba..bb04b1ad 100644 --- a/src/ssl/ssl_session.cc +++ b/src/ssl/ssl_session.cc @@ -1044,6 +1044,11 @@ void SSL_SESSION_get0_peer_sha256(const SSL_SESSION *session, } } +int SSL_SESSION_early_data_capable(const SSL_SESSION *session) { + return ssl_session_protocol_version(session) >= TLS1_3_VERSION && + session->ticket_max_early_data != 0; +} + SSL_SESSION *SSL_magic_pending_session_ptr(void) { return (SSL_SESSION *)&g_pending_session_magic; } diff --git a/src/ssl/ssl_test.cc b/src/ssl/ssl_test.cc index d01b6495..6f180c74 100644 --- a/src/ssl/ssl_test.cc +++ b/src/ssl/ssl_test.cc @@ -737,103 +737,77 @@ static bool DecodeBase64(std::vector<uint8_t> *out, const char *in) { return true; } -static bool TestSSL_SESSIONEncoding(const char *input_b64) { - const uint8_t *cptr; - uint8_t *ptr; - - // Decode the input. - std::vector<uint8_t> input; - if (!DecodeBase64(&input, input_b64)) { - return false; - } - - // Verify the SSL_SESSION decodes. - bssl::UniquePtr<SSL_CTX> ssl_ctx(SSL_CTX_new(TLS_method())); - if (!ssl_ctx) { - return false; - } - bssl::UniquePtr<SSL_SESSION> session( - SSL_SESSION_from_bytes(input.data(), input.size(), ssl_ctx.get())); - if (!session) { - fprintf(stderr, "SSL_SESSION_from_bytes failed\n"); - return false; - } - - // Verify the SSL_SESSION encoding round-trips. - size_t encoded_len; - bssl::UniquePtr<uint8_t> encoded; - uint8_t *encoded_raw; - if (!SSL_SESSION_to_bytes(session.get(), &encoded_raw, &encoded_len)) { - fprintf(stderr, "SSL_SESSION_to_bytes failed\n"); - return false; - } - encoded.reset(encoded_raw); - if (encoded_len != input.size() || - OPENSSL_memcmp(input.data(), encoded.get(), input.size()) != 0) { - fprintf(stderr, "SSL_SESSION_to_bytes did not round-trip\n"); - hexdump(stderr, "Before: ", input.data(), input.size()); - hexdump(stderr, "After: ", encoded_raw, encoded_len); - return false; - } - - // Verify the SSL_SESSION also decodes with the legacy API. - cptr = input.data(); - session.reset(d2i_SSL_SESSION(NULL, &cptr, input.size())); - if (!session || cptr != input.data() + input.size()) { - fprintf(stderr, "d2i_SSL_SESSION failed\n"); - return false; - } - - // Verify the SSL_SESSION encoding round-trips via the legacy API. - int len = i2d_SSL_SESSION(session.get(), NULL); - if (len < 0 || (size_t)len != input.size()) { - fprintf(stderr, "i2d_SSL_SESSION(NULL) returned invalid length\n"); - return false; - } - - encoded.reset((uint8_t *)OPENSSL_malloc(input.size())); - if (!encoded) { - fprintf(stderr, "malloc failed\n"); - return false; - } - - ptr = encoded.get(); - len = i2d_SSL_SESSION(session.get(), &ptr); - if (len < 0 || (size_t)len != input.size()) { - fprintf(stderr, "i2d_SSL_SESSION returned invalid length\n"); - return false; - } - if (ptr != encoded.get() + input.size()) { - fprintf(stderr, "i2d_SSL_SESSION did not advance ptr correctly\n"); - return false; - } - if (OPENSSL_memcmp(input.data(), encoded.get(), input.size()) != 0) { - fprintf(stderr, "i2d_SSL_SESSION did not round-trip\n"); - return false; - } - - return true; -} - -static bool TestBadSSL_SESSIONEncoding(const char *input_b64) { - std::vector<uint8_t> input; - if (!DecodeBase64(&input, input_b64)) { - return false; - } - - // Verify that the SSL_SESSION fails to decode. - bssl::UniquePtr<SSL_CTX> ssl_ctx(SSL_CTX_new(TLS_method())); - if (!ssl_ctx) { - return false; - } - bssl::UniquePtr<SSL_SESSION> session( - SSL_SESSION_from_bytes(input.data(), input.size(), ssl_ctx.get())); - if (session) { - fprintf(stderr, "SSL_SESSION_from_bytes unexpectedly succeeded\n"); - return false; +TEST(SSLTest, SessionEncoding) { + for (const char *input_b64 : { + kOpenSSLSession, + kCustomSession, + kBoringSSLSession, + }) { + SCOPED_TRACE(std::string(input_b64)); + // Decode the input. + std::vector<uint8_t> input; + ASSERT_TRUE(DecodeBase64(&input, input_b64)); + + // Verify the SSL_SESSION decodes. + bssl::UniquePtr<SSL_CTX> ssl_ctx(SSL_CTX_new(TLS_method())); + ASSERT_TRUE(ssl_ctx); + bssl::UniquePtr<SSL_SESSION> session( + SSL_SESSION_from_bytes(input.data(), input.size(), ssl_ctx.get())); + ASSERT_TRUE(session) << "SSL_SESSION_from_bytes failed"; + + // Verify the SSL_SESSION encoding round-trips. + size_t encoded_len; + bssl::UniquePtr<uint8_t> encoded; + uint8_t *encoded_raw; + ASSERT_TRUE(SSL_SESSION_to_bytes(session.get(), &encoded_raw, &encoded_len)) + << "SSL_SESSION_to_bytes failed"; + encoded.reset(encoded_raw); + EXPECT_EQ(Bytes(encoded.get(), encoded_len), Bytes(input)) + << "SSL_SESSION_to_bytes did not round-trip"; + + // Verify the SSL_SESSION also decodes with the legacy API. + const uint8_t *cptr = input.data(); + session.reset(d2i_SSL_SESSION(NULL, &cptr, input.size())); + ASSERT_TRUE(session) << "d2i_SSL_SESSION failed"; + EXPECT_EQ(cptr, input.data() + input.size()); + + // Verify the SSL_SESSION encoding round-trips via the legacy API. + int len = i2d_SSL_SESSION(session.get(), NULL); + ASSERT_GT(len, 0) << "i2d_SSL_SESSION failed"; + ASSERT_EQ(static_cast<size_t>(len), input.size()) + << "i2d_SSL_SESSION(NULL) returned invalid length"; + + encoded.reset((uint8_t *)OPENSSL_malloc(input.size())); + ASSERT_TRUE(encoded); + + uint8_t *ptr = encoded.get(); + len = i2d_SSL_SESSION(session.get(), &ptr); + ASSERT_GT(len, 0) << "i2d_SSL_SESSION failed"; + ASSERT_EQ(static_cast<size_t>(len), input.size()) + << "i2d_SSL_SESSION(NULL) returned invalid length"; + ASSERT_EQ(ptr, encoded.get() + input.size()) + << "i2d_SSL_SESSION did not advance ptr correctly"; + EXPECT_EQ(Bytes(encoded.get(), encoded_len), Bytes(input)) + << "SSL_SESSION_to_bytes did not round-trip"; + } + + for (const char *input_b64 : { + kBadSessionExtraField, + kBadSessionVersion, + kBadSessionTrailingData, + }) { + SCOPED_TRACE(std::string(input_b64)); + std::vector<uint8_t> input; + ASSERT_TRUE(DecodeBase64(&input, input_b64)); + + // Verify that the SSL_SESSION fails to decode. + bssl::UniquePtr<SSL_CTX> ssl_ctx(SSL_CTX_new(TLS_method())); + ASSERT_TRUE(ssl_ctx); + bssl::UniquePtr<SSL_SESSION> session( + SSL_SESSION_from_bytes(input.data(), input.size(), ssl_ctx.get())); + EXPECT_FALSE(session) << "SSL_SESSION_from_bytes unexpectedly succeeded"; + ERR_clear_error(); } - ERR_clear_error(); - return true; } static void ExpectDefaultVersion(uint16_t min_version, uint16_t max_version, @@ -1087,63 +1061,67 @@ static size_t GetClientHelloLen(uint16_t max_version, uint16_t session_version, return client_hello.size() - SSL3_RT_HEADER_LENGTH; } -struct PaddingTest { - size_t input_len, padded_len; -}; +TEST(SSLTest, Padding) { + struct PaddingVersions { + uint16_t max_version, session_version; + }; + static const PaddingVersions kPaddingVersions[] = { + // Test the padding extension at TLS 1.2. + {TLS1_2_VERSION, TLS1_2_VERSION}, + // Test the padding extension at TLS 1.3 with a TLS 1.2 session, so there + // will be no PSK binder after the padding extension. + {TLS1_3_VERSION, TLS1_2_VERSION}, + // Test the padding extension at TLS 1.3 with a TLS 1.3 session, so there + // will be a PSK binder after the padding extension. + {TLS1_3_VERSION, TLS1_3_VERSION}, -static const PaddingTest kPaddingTests[] = { - // ClientHellos of length below 0x100 do not require padding. - {0xfe, 0xfe}, - {0xff, 0xff}, - // ClientHellos of length 0x100 through 0x1fb are padded up to 0x200. - {0x100, 0x200}, - {0x123, 0x200}, - {0x1fb, 0x200}, - // ClientHellos of length 0x1fc through 0x1ff get padded beyond 0x200. The - // padding extension takes a minimum of four bytes plus one required content - // byte. (To work around yet more server bugs, we avoid empty final - // extensions.) - {0x1fc, 0x201}, - {0x1fd, 0x202}, - {0x1fe, 0x203}, - {0x1ff, 0x204}, - // Finally, larger ClientHellos need no padding. - {0x200, 0x200}, - {0x201, 0x201}, -}; + }; -static bool TestPaddingExtension(uint16_t max_version, - uint16_t session_version) { - // Sample a baseline length. - size_t base_len = GetClientHelloLen(max_version, session_version, 1); - if (base_len == 0) { - return false; - } + struct PaddingTest { + size_t input_len, padded_len; + }; + static const PaddingTest kPaddingTests[] = { + // ClientHellos of length below 0x100 do not require padding. + {0xfe, 0xfe}, + {0xff, 0xff}, + // ClientHellos of length 0x100 through 0x1fb are padded up to 0x200. + {0x100, 0x200}, + {0x123, 0x200}, + {0x1fb, 0x200}, + // ClientHellos of length 0x1fc through 0x1ff get padded beyond 0x200. The + // padding extension takes a minimum of four bytes plus one required + // content + // byte. (To work around yet more server bugs, we avoid empty final + // extensions.) + {0x1fc, 0x201}, + {0x1fd, 0x202}, + {0x1fe, 0x203}, + {0x1ff, 0x204}, + // Finally, larger ClientHellos need no padding. + {0x200, 0x200}, + {0x201, 0x201}, + }; - for (const PaddingTest &test : kPaddingTests) { - if (base_len > test.input_len) { - fprintf(stderr, - "Baseline ClientHello too long (max_version = %04x, " - "session_version = %04x).\n", - max_version, session_version); - return false; - } + for (const PaddingVersions &versions : kPaddingVersions) { + SCOPED_TRACE(versions.max_version); + SCOPED_TRACE(versions.session_version); - size_t padded_len = GetClientHelloLen(max_version, session_version, - 1 + test.input_len - base_len); - if (padded_len != test.padded_len) { - fprintf(stderr, - "%u-byte ClientHello padded to %u bytes, not %u (max_version = " - "%04x, session_version = %04x).\n", - static_cast<unsigned>(test.input_len), - static_cast<unsigned>(padded_len), - static_cast<unsigned>(test.padded_len), max_version, - session_version); - return false; + // Sample a baseline length. + size_t base_len = + GetClientHelloLen(versions.max_version, versions.session_version, 1); + ASSERT_NE(base_len, 0u) << "Baseline length could not be sampled"; + + for (const PaddingTest &test : kPaddingTests) { + SCOPED_TRACE(test.input_len); + ASSERT_LE(base_len, test.input_len) << "Baseline ClientHello too long"; + + size_t padded_len = + GetClientHelloLen(versions.max_version, versions.session_version, + 1 + test.input_len - base_len); + EXPECT_EQ(padded_len, test.padded_len) + << "ClientHello was not padded to expected length"; } } - - return true; } static bssl::UniquePtr<X509> GetTestCertificate() { @@ -1550,6 +1528,37 @@ static bool CompleteHandshakes(SSL *client, SSL *server) { return true; } +static bool FlushNewSessionTickets(SSL *client, SSL *server) { + // NewSessionTickets are deferred on the server to |SSL_write|, and clients do + // not pick them up until |SSL_read|. + for (;;) { + int server_ret = SSL_write(server, nullptr, 0); + int server_err = SSL_get_error(server, server_ret); + // The server may either succeed (|server_ret| is zero) or block on write + // (|server_ret| is -1 and |server_err| is |SSL_ERROR_WANT_WRITE|). + if (server_ret > 0 || + (server_ret < 0 && server_err != SSL_ERROR_WANT_WRITE)) { + fprintf(stderr, "Unexpected server result: %d %d\n", server_ret, + server_err); + return false; + } + + int client_ret = SSL_read(client, nullptr, 0); + int client_err = SSL_get_error(client, client_ret); + // The client must always block on read. + if (client_ret != -1 || client_err != SSL_ERROR_WANT_READ) { + fprintf(stderr, "Unexpected client result: %d %d\n", client_ret, + client_err); + return false; + } + + // The server flushed everything it had to write. + if (server_ret == 0) { + return true; + } + } +} + struct ClientConfig { SSL_SESSION *session = nullptr; std::string servername; @@ -1661,9 +1670,7 @@ TEST_P(SSLVersionTest, SequenceNumber) { // Drain any post-handshake messages to ensure there are no unread records // on either end. - uint8_t byte = 0; - ASSERT_LE(SSL_read(client_.get(), &byte, 1), 0); - ASSERT_LE(SSL_read(server_.get(), &byte, 1), 0); + ASSERT_TRUE(FlushNewSessionTickets(client_.get(), server_.get())); uint64_t client_read_seq = SSL_get_read_sequence(client_.get()); uint64_t client_write_seq = SSL_get_write_sequence(client_.get()); @@ -1687,6 +1694,7 @@ TEST_P(SSLVersionTest, SequenceNumber) { } // Send a record from client to server. + uint8_t byte = 0; EXPECT_EQ(SSL_write(client_.get(), &byte, 1), 1); EXPECT_EQ(SSL_read(server_.get(), &byte, 1), 1); @@ -2084,14 +2092,12 @@ static bssl::UniquePtr<SSL_SESSION> CreateClientSession( // Connect client and server to get a session. bssl::UniquePtr<SSL> client, server; if (!ConnectClientAndServer(&client, &server, client_ctx, server_ctx, - config)) { + config) || + !FlushNewSessionTickets(client.get(), server.get())) { fprintf(stderr, "Failed to connect client and server.\n"); return nullptr; } - // Run the read loop to account for post-handshake tickets in TLS 1.3. - SSL_read(client.get(), nullptr, 0); - SSL_CTX_sess_set_new_cb(client_ctx, nullptr); if (!g_last_session) { @@ -2125,7 +2131,8 @@ static bssl::UniquePtr<SSL_SESSION> ExpectSessionRenewed(SSL_CTX *client_ctx, ClientConfig config; config.session = session; if (!ConnectClientAndServer(&client, &server, client_ctx, server_ctx, - config)) { + config) || + !FlushNewSessionTickets(client.get(), server.get())) { fprintf(stderr, "Failed to connect client and server.\n"); return nullptr; } @@ -2140,9 +2147,6 @@ static bssl::UniquePtr<SSL_SESSION> ExpectSessionRenewed(SSL_CTX *client_ctx, return nullptr; } - // Run the read loop to account for post-handshake tickets in TLS 1.3. - SSL_read(client.get(), nullptr, 0); - SSL_CTX_sess_set_new_cb(client_ctx, nullptr); if (!g_last_session) { @@ -3055,6 +3059,82 @@ TEST_P(SSLVersionTest, ClientSessionCacheMode) { EXPECT_FALSE(CreateClientSession(client_ctx_.get(), server_ctx_.get())); } +// Test that all versions survive tiny write buffers. In particular, TLS 1.3 +// NewSessionTickets are written post-handshake. Servers that block +// |SSL_do_handshake| on writing them will deadlock if clients are not draining +// the buffer. Test that we do not do this. +TEST_P(SSLVersionTest, SmallBuffer) { + // DTLS is a datagram protocol and requires packet-sized buffers. + if (is_dtls()) { + return; + } + + // Test both flushing NewSessionTickets with a zero-sized write and + // non-zero-sized write. + for (bool use_zero_write : {false, true}) { + SCOPED_TRACE(use_zero_write); + + g_last_session = nullptr; + SSL_CTX_set_session_cache_mode(client_ctx_.get(), SSL_SESS_CACHE_BOTH); + SSL_CTX_sess_set_new_cb(client_ctx_.get(), SaveLastSession); + + bssl::UniquePtr<SSL> client(SSL_new(client_ctx_.get())), + server(SSL_new(server_ctx_.get())); + ASSERT_TRUE(client); + ASSERT_TRUE(server); + SSL_set_connect_state(client.get()); + SSL_set_accept_state(server.get()); + + // Use a tiny buffer. + BIO *bio1, *bio2; + ASSERT_TRUE(BIO_new_bio_pair(&bio1, 1, &bio2, 1)); + + // SSL_set_bio takes ownership. + SSL_set_bio(client.get(), bio1, bio1); + SSL_set_bio(server.get(), bio2, bio2); + + ASSERT_TRUE(CompleteHandshakes(client.get(), server.get())); + if (version() >= TLS1_3_VERSION) { + // The post-handshake ticket should not have been processed yet. + EXPECT_FALSE(g_last_session); + } + + if (use_zero_write) { + ASSERT_TRUE(FlushNewSessionTickets(client.get(), server.get())); + EXPECT_TRUE(g_last_session); + } + + // Send some data from server to client. If |use_zero_write| is false, this + // will also flush the NewSessionTickets. + static const char kMessage[] = "hello world"; + char buf[sizeof(kMessage)]; + for (;;) { + int server_ret = SSL_write(server.get(), kMessage, sizeof(kMessage)); + int server_err = SSL_get_error(server.get(), server_ret); + int client_ret = SSL_read(client.get(), buf, sizeof(buf)); + int client_err = SSL_get_error(client.get(), client_ret); + + // The server will write a single record, so every iteration should see + // |SSL_ERROR_WANT_WRITE| and |SSL_ERROR_WANT_READ|, until the final + // iteration, where both will complete. + if (server_ret > 0) { + EXPECT_EQ(server_ret, static_cast<int>(sizeof(kMessage))); + EXPECT_EQ(client_ret, static_cast<int>(sizeof(kMessage))); + EXPECT_EQ(Bytes(buf), Bytes(kMessage)); + break; + } + + ASSERT_EQ(server_ret, -1); + ASSERT_EQ(server_err, SSL_ERROR_WANT_WRITE); + ASSERT_EQ(client_ret, -1); + ASSERT_EQ(client_err, SSL_ERROR_WANT_READ); + } + + // The NewSessionTickets should have been flushed and processed. + EXPECT_TRUE(g_last_session); + } +} + TEST(SSLTest, AddChainCertHack) { // Ensure that we don't accidently break the hack that we have in place to // keep curl and serf happy when they use an |X509| even after transfering @@ -3514,9 +3594,7 @@ TEST_P(TicketAEADMethodTest, Resume) { EXPECT_FALSE(SSL_session_reused(client.get())); EXPECT_FALSE(SSL_session_reused(server.get())); - // Run the read loop to account for post-handshake tickets in TLS 1.3. - SSL_read(client.get(), nullptr, 0); - + ASSERT_TRUE(FlushNewSessionTickets(client.get(), server.get())); bssl::UniquePtr<SSL_SESSION> session = std::move(g_last_session); ConnectClientAndServerWithTicketMethod(&client, &server, client_ctx.get(), server_ctx.get(), retry_count, @@ -4606,7 +4684,7 @@ TEST_P(SSLVersionTest, SessionPropertiesThreads) { thread.join(); } } -#endif +#endif // OPENSSL_THREADS constexpr size_t kNumQUICLevels = 4; static_assert(ssl_encryption_initial < kNumQUICLevels, @@ -5263,23 +5341,101 @@ TEST_F(QUICMethodTest, BadPostHandshake) { EXPECT_EQ(SSL_process_quic_post_handshake(client_.get()), 0); } -// TODO(davidben): Convert this file to GTest properly. -TEST(SSLTest, AllTests) { - if (!TestSSL_SESSIONEncoding(kOpenSSLSession) || - !TestSSL_SESSIONEncoding(kCustomSession) || - !TestSSL_SESSIONEncoding(kBoringSSLSession) || - !TestBadSSL_SESSIONEncoding(kBadSessionExtraField) || - !TestBadSSL_SESSIONEncoding(kBadSessionVersion) || - !TestBadSSL_SESSIONEncoding(kBadSessionTrailingData) || - // Test the padding extension at TLS 1.2. - !TestPaddingExtension(TLS1_2_VERSION, TLS1_2_VERSION) || - // Test the padding extension at TLS 1.3 with a TLS 1.2 session, so there - // will be no PSK binder after the padding extension. - !TestPaddingExtension(TLS1_3_VERSION, TLS1_2_VERSION) || - // Test the padding extension at TLS 1.3 with a TLS 1.3 session, so there - // will be a PSK binder after the padding extension. - !TestPaddingExtension(TLS1_3_VERSION, TLS1_3_VERSION)) { - ADD_FAILURE() << "Tests failed"; +extern "C" { +int BORINGSSL_enum_c_type_test(void); +} + +TEST(SSLTest, EnumTypes) { + EXPECT_EQ(sizeof(int), sizeof(ssl_private_key_result_t)); + EXPECT_EQ(1, BORINGSSL_enum_c_type_test()); +} + +TEST_P(SSLVersionTest, DoubleSSLError) { + // Connect the inner SSL connections. + ASSERT_TRUE(Connect()); + + // Make a pair of |BIO|s which wrap |client_| and |server_|. + UniquePtr<BIO_METHOD> bio_method(BIO_meth_new(0, nullptr)); + ASSERT_TRUE(bio_method); + ASSERT_TRUE(BIO_meth_set_read( + bio_method.get(), [](BIO *bio, char *out, int len) -> int { + SSL *ssl = static_cast<SSL *>(BIO_get_data(bio)); + int ret = SSL_read(ssl, out, len); + int ssl_ret = SSL_get_error(ssl, ret); + if (ssl_ret == SSL_ERROR_WANT_READ) { + BIO_set_retry_read(bio); + } + return ret; + })); + ASSERT_TRUE(BIO_meth_set_write( + bio_method.get(), [](BIO *bio, const char *in, int len) -> int { + SSL *ssl = static_cast<SSL *>(BIO_get_data(bio)); + int ret = SSL_write(ssl, in, len); + int ssl_ret = SSL_get_error(ssl, ret); + if (ssl_ret == SSL_ERROR_WANT_WRITE) { + BIO_set_retry_write(bio); + } + return ret; + })); + ASSERT_TRUE(BIO_meth_set_ctrl( + bio_method.get(), [](BIO *bio, int cmd, long larg, void *parg) -> long { + // |SSL| objects require |BIO_flush| support. + if (cmd == BIO_CTRL_FLUSH) { + return 1; + } + return 0; + })); + + UniquePtr<BIO> client_bio(BIO_new(bio_method.get())); + ASSERT_TRUE(client_bio); + BIO_set_data(client_bio.get(), client_.get()); + BIO_set_init(client_bio.get(), 1); + + UniquePtr<BIO> server_bio(BIO_new(bio_method.get())); + ASSERT_TRUE(server_bio); + BIO_set_data(server_bio.get(), server_.get()); + BIO_set_init(server_bio.get(), 1); + + // Wrap the inner connections in another layer of SSL. + UniquePtr<SSL> client_outer(SSL_new(client_ctx_.get())); + ASSERT_TRUE(client_outer); + SSL_set_connect_state(client_outer.get()); + SSL_set_bio(client_outer.get(), client_bio.get(), client_bio.get()); + client_bio.release(); // |SSL_set_bio| takes ownership. + + UniquePtr<SSL> server_outer(SSL_new(server_ctx_.get())); + ASSERT_TRUE(server_outer); + SSL_set_accept_state(server_outer.get()); + SSL_set_bio(server_outer.get(), server_bio.get(), server_bio.get()); + server_bio.release(); // |SSL_set_bio| takes ownership. + + // Configure |client_outer| to reject the server certificate. + SSL_set_custom_verify( + client_outer.get(), SSL_VERIFY_PEER, + [](SSL *ssl, uint8_t *out_alert) -> ssl_verify_result_t { + return ssl_verify_invalid; + }); + + for (;;) { + int client_ret = SSL_do_handshake(client_outer.get()); + int client_err = SSL_get_error(client_outer.get(), client_ret); + if (client_err != SSL_ERROR_WANT_READ && + client_err != SSL_ERROR_WANT_WRITE) { + // The client handshake should terminate on a certificate verification + // error. + EXPECT_EQ(SSL_ERROR_SSL, client_err); + uint32_t err = ERR_peek_error(); + EXPECT_EQ(ERR_LIB_SSL, ERR_GET_LIB(err)); + EXPECT_EQ(SSL_R_CERTIFICATE_VERIFY_FAILED, ERR_GET_REASON(err)); + break; + } + + // Run the server handshake and continue. + int server_ret = SSL_do_handshake(server_outer.get()); + int server_err = SSL_get_error(server_outer.get(), server_ret); + ASSERT_TRUE(server_err == SSL_ERROR_NONE || + server_err == SSL_ERROR_WANT_READ || + server_err == SSL_ERROR_WANT_WRITE); } } diff --git a/src/ssl/ssl_x509.cc b/src/ssl/ssl_x509.cc index 841482f8..cda76117 100644 --- a/src/ssl/ssl_x509.cc +++ b/src/ssl/ssl_x509.cc @@ -200,19 +200,19 @@ static UniquePtr<STACK_OF(CRYPTO_BUFFER)> new_leafless_chain(void) { // forms of elements of |chain|. It returns one on success or zero on error, in // which case no change to |cert->chain| is made. It preverses the existing // leaf from |cert->chain|, if any. -static int ssl_cert_set_chain(CERT *cert, STACK_OF(X509) *chain) { +static bool ssl_cert_set_chain(CERT *cert, STACK_OF(X509) *chain) { UniquePtr<STACK_OF(CRYPTO_BUFFER)> new_chain; if (cert->chain != nullptr) { new_chain.reset(sk_CRYPTO_BUFFER_new_null()); if (!new_chain) { - return 0; + return false; } // |leaf| might be NULL if it's a “leafless” chain. CRYPTO_BUFFER *leaf = sk_CRYPTO_BUFFER_value(cert->chain.get(), 0); if (!PushToStack(new_chain.get(), UpRef(leaf))) { - return 0; + return false; } } @@ -220,32 +220,32 @@ static int ssl_cert_set_chain(CERT *cert, STACK_OF(X509) *chain) { if (!new_chain) { new_chain = new_leafless_chain(); if (!new_chain) { - return 0; + return false; } } UniquePtr<CRYPTO_BUFFER> buffer = x509_to_buffer(x509); if (!buffer || !PushToStack(new_chain.get(), std::move(buffer))) { - return 0; + return false; } } cert->chain = std::move(new_chain); - return 1; + return true; } static void ssl_crypto_x509_cert_flush_cached_leaf(CERT *cert) { X509_free(cert->x509_leaf); - cert->x509_leaf = NULL; + cert->x509_leaf = nullptr; } static void ssl_crypto_x509_cert_flush_cached_chain(CERT *cert) { sk_X509_pop_free(cert->x509_chain, X509_free); - cert->x509_chain = NULL; + cert->x509_chain = nullptr; } -static int ssl_crypto_x509_check_client_CA_list( +static bool ssl_crypto_x509_check_client_CA_list( STACK_OF(CRYPTO_BUFFER) *names) { for (const CRYPTO_BUFFER *buffer : names) { const uint8_t *inp = CRYPTO_BUFFER_data(buffer); @@ -253,11 +253,11 @@ static int ssl_crypto_x509_check_client_CA_list( d2i_X509_NAME(nullptr, &inp, CRYPTO_BUFFER_len(buffer))); if (name == nullptr || inp != CRYPTO_BUFFER_data(buffer) + CRYPTO_BUFFER_len(buffer)) { - return 0; + return false; } } - return 1; + return true; } static void ssl_crypto_x509_cert_clear(CERT *cert) { @@ -265,7 +265,7 @@ static void ssl_crypto_x509_cert_clear(CERT *cert) { ssl_crypto_x509_cert_flush_cached_chain(cert); X509_free(cert->x509_stash); - cert->x509_stash = NULL; + cert->x509_stash = nullptr; } static void ssl_crypto_x509_cert_free(CERT *cert) { @@ -274,19 +274,19 @@ static void ssl_crypto_x509_cert_free(CERT *cert) { } static void ssl_crypto_x509_cert_dup(CERT *new_cert, const CERT *cert) { - if (cert->verify_store != NULL) { + if (cert->verify_store != nullptr) { X509_STORE_up_ref(cert->verify_store); new_cert->verify_store = cert->verify_store; } } -static int ssl_crypto_x509_session_cache_objects(SSL_SESSION *sess) { +static bool ssl_crypto_x509_session_cache_objects(SSL_SESSION *sess) { bssl::UniquePtr<STACK_OF(X509)> chain, chain_without_leaf; if (sk_CRYPTO_BUFFER_num(sess->certs.get()) > 0) { chain.reset(sk_X509_new_null()); if (!chain) { OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE); - return 0; + return false; } if (sess->is_server) { // chain_without_leaf is only needed for server sessions. See @@ -294,7 +294,7 @@ static int ssl_crypto_x509_session_cache_objects(SSL_SESSION *sess) { chain_without_leaf.reset(sk_X509_new_null()); if (!chain_without_leaf) { OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE); - return 0; + return false; } } } @@ -304,18 +304,18 @@ static int ssl_crypto_x509_session_cache_objects(SSL_SESSION *sess) { UniquePtr<X509> x509(X509_parse_from_buffer(cert)); if (!x509) { OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR); - return 0; + return false; } if (leaf == nullptr) { leaf = UpRef(x509); } else if (chain_without_leaf && !PushToStack(chain_without_leaf.get(), UpRef(x509))) { OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE); - return 0; + return false; } if (!PushToStack(chain.get(), std::move(x509))) { OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE); - return 0; + return false; } } @@ -327,80 +327,76 @@ static int ssl_crypto_x509_session_cache_objects(SSL_SESSION *sess) { X509_free(sess->x509_peer); sess->x509_peer = leaf.release(); - return 1; + return true; } -static int ssl_crypto_x509_session_dup(SSL_SESSION *new_session, - const SSL_SESSION *session) { +static bool ssl_crypto_x509_session_dup(SSL_SESSION *new_session, + const SSL_SESSION *session) { new_session->x509_peer = UpRef(session->x509_peer).release(); if (session->x509_chain != nullptr) { new_session->x509_chain = X509_chain_up_ref(session->x509_chain); if (new_session->x509_chain == nullptr) { - return 0; + return false; } } if (session->x509_chain_without_leaf != nullptr) { new_session->x509_chain_without_leaf = X509_chain_up_ref(session->x509_chain_without_leaf); if (new_session->x509_chain_without_leaf == nullptr) { - return 0; + return false; } } - return 1; + return true; } static void ssl_crypto_x509_session_clear(SSL_SESSION *session) { X509_free(session->x509_peer); - session->x509_peer = NULL; + session->x509_peer = nullptr; sk_X509_pop_free(session->x509_chain, X509_free); - session->x509_chain = NULL; + session->x509_chain = nullptr; sk_X509_pop_free(session->x509_chain_without_leaf, X509_free); - session->x509_chain_without_leaf = NULL; + session->x509_chain_without_leaf = nullptr; } -static int ssl_crypto_x509_session_verify_cert_chain(SSL_SESSION *session, - SSL_HANDSHAKE *hs, - uint8_t *out_alert) { +static bool ssl_crypto_x509_session_verify_cert_chain(SSL_SESSION *session, + SSL_HANDSHAKE *hs, + uint8_t *out_alert) { *out_alert = SSL_AD_INTERNAL_ERROR; STACK_OF(X509) *const cert_chain = session->x509_chain; - if (cert_chain == NULL || sk_X509_num(cert_chain) == 0) { - return 0; + if (cert_chain == nullptr || sk_X509_num(cert_chain) == 0) { + return false; } SSL_CTX *ssl_ctx = hs->ssl->ctx.get(); X509_STORE *verify_store = ssl_ctx->cert_store; - if (hs->config->cert->verify_store != NULL) { + if (hs->config->cert->verify_store != nullptr) { verify_store = hs->config->cert->verify_store; } X509 *leaf = sk_X509_value(cert_chain, 0); ScopedX509_STORE_CTX ctx; - if (!X509_STORE_CTX_init(ctx.get(), verify_store, leaf, cert_chain)) { + if (!X509_STORE_CTX_init(ctx.get(), verify_store, leaf, cert_chain) || + !X509_STORE_CTX_set_ex_data( + ctx.get(), SSL_get_ex_data_X509_STORE_CTX_idx(), hs->ssl) || + // We need to inherit the verify parameters. These can be determined by + // the context: if its a server it will verify SSL client certificates or + // vice versa. + !X509_STORE_CTX_set_default( + ctx.get(), hs->ssl->server ? "ssl_client" : "ssl_server") || + // Anything non-default in "param" should overwrite anything in the ctx. + !X509_VERIFY_PARAM_set1(X509_STORE_CTX_get0_param(ctx.get()), + hs->config->param)) { OPENSSL_PUT_ERROR(SSL, ERR_R_X509_LIB); - return 0; - } - if (!X509_STORE_CTX_set_ex_data( - ctx.get(), SSL_get_ex_data_X509_STORE_CTX_idx(), hs->ssl)) { - return 0; + return false; } - // We need to inherit the verify parameters. These can be determined by the - // context: if its a server it will verify SSL client certificates or vice - // versa. - X509_STORE_CTX_set_default(ctx.get(), - hs->ssl->server ? "ssl_client" : "ssl_server"); - - // Anything non-default in "param" should overwrite anything in the ctx. - X509_VERIFY_PARAM_set1(X509_STORE_CTX_get0_param(ctx.get()), - hs->config->param); - if (hs->config->verify_callback) { X509_STORE_CTX_set_verify_cb(ctx.get(), hs->config->verify_callback); } int verify_ret; - if (ssl_ctx->app_verify_callback != NULL) { + if (ssl_ctx->app_verify_callback != nullptr) { verify_ret = ssl_ctx->app_verify_callback(ctx.get(), ssl_ctx->app_verify_arg); } else { @@ -412,59 +408,59 @@ static int ssl_crypto_x509_session_verify_cert_chain(SSL_SESSION *session, // If |SSL_VERIFY_NONE|, the error is non-fatal, but we keep the result. if (verify_ret <= 0 && hs->config->verify_mode != SSL_VERIFY_NONE) { *out_alert = SSL_alert_from_verify_result(ctx->error); - return 0; + return false; } ERR_clear_error(); - return 1; + return true; } static void ssl_crypto_x509_hs_flush_cached_ca_names(SSL_HANDSHAKE *hs) { sk_X509_NAME_pop_free(hs->cached_x509_ca_names, X509_NAME_free); - hs->cached_x509_ca_names = NULL; + hs->cached_x509_ca_names = nullptr; } -static int ssl_crypto_x509_ssl_new(SSL_HANDSHAKE *hs) { +static bool ssl_crypto_x509_ssl_new(SSL_HANDSHAKE *hs) { hs->config->param = X509_VERIFY_PARAM_new(); - if (hs->config->param == NULL) { - return 0; + if (hs->config->param == nullptr) { + return false; } X509_VERIFY_PARAM_inherit(hs->config->param, hs->ssl->ctx->param); - return 1; + return true; } static void ssl_crypto_x509_ssl_flush_cached_client_CA(SSL_CONFIG *cfg) { sk_X509_NAME_pop_free(cfg->cached_x509_client_CA, X509_NAME_free); - cfg->cached_x509_client_CA = NULL; + cfg->cached_x509_client_CA = nullptr; } static void ssl_crypto_x509_ssl_config_free(SSL_CONFIG *cfg) { sk_X509_NAME_pop_free(cfg->cached_x509_client_CA, X509_NAME_free); - cfg->cached_x509_client_CA = NULL; + cfg->cached_x509_client_CA = nullptr; X509_VERIFY_PARAM_free(cfg->param); } -static int ssl_crypto_x509_ssl_auto_chain_if_needed(SSL_HANDSHAKE *hs) { +static bool ssl_crypto_x509_ssl_auto_chain_if_needed(SSL_HANDSHAKE *hs) { // Only build a chain if there are no intermediates configured and the feature // isn't disabled. if ((hs->ssl->mode & SSL_MODE_NO_AUTO_CHAIN) || !ssl_has_certificate(hs) || hs->config->cert->chain == NULL || sk_CRYPTO_BUFFER_num(hs->config->cert->chain.get()) > 1) { - return 1; + return true; } UniquePtr<X509> leaf(X509_parse_from_buffer( sk_CRYPTO_BUFFER_value(hs->config->cert->chain.get(), 0))); if (!leaf) { OPENSSL_PUT_ERROR(SSL, ERR_R_X509_LIB); - return 0; + return false; } ScopedX509_STORE_CTX ctx; if (!X509_STORE_CTX_init(ctx.get(), hs->ssl->ctx->cert_store, leaf.get(), NULL)) { OPENSSL_PUT_ERROR(SSL, ERR_R_X509_LIB); - return 0; + return false; } // Attempt to build a chain, ignoring the result. @@ -475,23 +471,23 @@ static int ssl_crypto_x509_ssl_auto_chain_if_needed(SSL_HANDSHAKE *hs) { X509_free(sk_X509_shift(ctx->chain)); if (!ssl_cert_set_chain(hs->config->cert.get(), ctx->chain)) { - return 0; + return false; } ssl_crypto_x509_cert_flush_cached_chain(hs->config->cert.get()); - return 1; + return true; } static void ssl_crypto_x509_ssl_ctx_flush_cached_client_CA(SSL_CTX *ctx) { sk_X509_NAME_pop_free(ctx->cached_x509_client_CA, X509_NAME_free); - ctx->cached_x509_client_CA = NULL; + ctx->cached_x509_client_CA = nullptr; } -static int ssl_crypto_x509_ssl_ctx_new(SSL_CTX *ctx) { +static bool ssl_crypto_x509_ssl_ctx_new(SSL_CTX *ctx) { ctx->cert_store = X509_STORE_new(); ctx->param = X509_VERIFY_PARAM_new(); - return (ctx->cert_store != NULL && ctx->param != NULL); + return (ctx->cert_store != nullptr && ctx->param != nullptr); } static void ssl_crypto_x509_ssl_ctx_free(SSL_CTX *ctx) { diff --git a/src/ssl/t1_enc.cc b/src/ssl/t1_enc.cc index c6b2844d..4c2fffb3 100644 --- a/src/ssl/t1_enc.cc +++ b/src/ssl/t1_enc.cc @@ -359,27 +359,3 @@ int SSL_export_keying_material(SSL *ssl, uint8_t *out, size_t out_len, MakeConstSpan(session->master_key, session->master_key_length), MakeConstSpan(label, label_len), seed, {}); } - -int SSL_export_early_keying_material( - SSL *ssl, uint8_t *out, size_t out_len, const char *label, size_t label_len, - const uint8_t *context, size_t context_len) { - if (!SSL_in_early_data(ssl) && - (!ssl->s3->have_version || - ssl_protocol_version(ssl) < TLS1_3_VERSION)) { - OPENSSL_PUT_ERROR(SSL, SSL_R_WRONG_SSL_VERSION); - return 0; - } - - // The early exporter only exists if we accepted early data or offered it as - // a client. - if (!SSL_in_early_data(ssl) && !SSL_early_data_accepted(ssl)) { - OPENSSL_PUT_ERROR(SSL, SSL_R_EARLY_DATA_NOT_IN_USE); - return 0; - } - - return tls13_export_keying_material( - ssl, MakeSpan(out, out_len), - MakeConstSpan(ssl->s3->early_exporter_secret, - ssl->s3->early_exporter_secret_len), - MakeConstSpan(label, label_len), MakeConstSpan(context, context_len)); -} diff --git a/src/ssl/t1_lib.cc b/src/ssl/t1_lib.cc index 87f18889..c1c41a8a 100644 --- a/src/ssl/t1_lib.cc +++ b/src/ssl/t1_lib.cc @@ -199,6 +199,10 @@ static bool tls1_check_duplicate_extensions(const CBS *cbs) { return true; } +static bool is_post_quantum_group(uint16_t id) { + return id == SSL_CURVE_CECPQ2 || id == SSL_CURVE_CECPQ2b; +} + bool ssl_client_hello_init(const SSL *ssl, SSL_CLIENT_HELLO *out, const SSLMessage &msg) { OPENSSL_memset(out, 0, sizeof(*out)); @@ -325,10 +329,10 @@ bool tls1_get_shared_group(SSL_HANDSHAKE *hs, uint16_t *out_group_id) { for (uint16_t pref_group : pref) { for (uint16_t supp_group : supp) { if (pref_group == supp_group && - // CECPQ2 doesn't fit in the u8-length-prefixed ECPoint field in TLS - // 1.2 and below. + // CECPQ2(b) doesn't fit in the u8-length-prefixed ECPoint field in + // TLS 1.2 and below. (ssl_protocol_version(ssl) >= TLS1_3_VERSION || - pref_group != SSL_CURVE_CECPQ2)) { + !is_post_quantum_group(pref_group))) { *out_group_id = pref_group; return true; } @@ -390,9 +394,9 @@ bool tls1_set_curves_list(Array<uint16_t> *out_group_ids, const char *curves) { } bool tls1_check_group_id(const SSL_HANDSHAKE *hs, uint16_t group_id) { - if (group_id == SSL_CURVE_CECPQ2 && + if (is_post_quantum_group(group_id) && ssl_protocol_version(hs->ssl) < TLS1_3_VERSION) { - // CECPQ2 requires TLS 1.3. + // CECPQ2(b) requires TLS 1.3. return false; } @@ -629,45 +633,7 @@ static bool ext_sni_parse_serverhello(SSL_HANDSHAKE *hs, uint8_t *out_alert, static bool ext_sni_parse_clienthello(SSL_HANDSHAKE *hs, uint8_t *out_alert, CBS *contents) { - SSL *const ssl = hs->ssl; - if (contents == NULL) { - return true; - } - - CBS server_name_list, host_name; - uint8_t name_type; - if (!CBS_get_u16_length_prefixed(contents, &server_name_list) || - !CBS_get_u8(&server_name_list, &name_type) || - // Although the server_name extension was intended to be extensible to - // new name types and multiple names, OpenSSL 1.0.x had a bug which meant - // different name types will cause an error. Further, RFC 4366 originally - // defined syntax inextensibly. RFC 6066 corrected this mistake, but - // adding new name types is no longer feasible. - // - // Act as if the extensibility does not exist to simplify parsing. - !CBS_get_u16_length_prefixed(&server_name_list, &host_name) || - CBS_len(&server_name_list) != 0 || - CBS_len(contents) != 0) { - return false; - } - - if (name_type != TLSEXT_NAMETYPE_host_name || - CBS_len(&host_name) == 0 || - CBS_len(&host_name) > TLSEXT_MAXLEN_host_name || - CBS_contains_zero_byte(&host_name)) { - *out_alert = SSL_AD_UNRECOGNIZED_NAME; - return false; - } - - // Copy the hostname as a string. - char *raw = nullptr; - if (!CBS_strdup(&host_name, &raw)) { - *out_alert = SSL_AD_INTERNAL_ERROR; - return false; - } - ssl->s3->hostname.reset(raw); - - hs->should_ack_sni = true; + // SNI has already been parsed earlier in the handshake. See |extract_sni|. return true; } @@ -1790,7 +1756,7 @@ static bool ext_ec_point_add_extension(SSL_HANDSHAKE *hs, CBB *out) { } static bool ext_ec_point_add_clienthello(SSL_HANDSHAKE *hs, CBB *out) { - // The point format extension is unneccessary in TLS 1.3. + // The point format extension is unnecessary in TLS 1.3. if (hs->min_version >= TLS1_3_VERSION) { return true; } @@ -2057,20 +2023,46 @@ static bool ext_psk_key_exchange_modes_parse_clienthello(SSL_HANDSHAKE *hs, static bool ext_early_data_add_clienthello(SSL_HANDSHAKE *hs, CBB *out) { SSL *const ssl = hs->ssl; - if (!ssl->enable_early_data || - // Session must be 0-RTT capable. - ssl->session == nullptr || - ssl_session_protocol_version(ssl->session.get()) < TLS1_3_VERSION || - ssl->session->ticket_max_early_data == 0 || - // The second ClientHello never offers early data. - hs->received_hello_retry_request || - // In case ALPN preferences changed since this session was established, - // avoid reporting a confusing value in |SSL_get0_alpn_selected|. - (!ssl->session->early_alpn.empty() && - !ssl_is_alpn_protocol_allowed(hs, ssl->session->early_alpn))) { + // The second ClientHello never offers early data, and we must have already + // filled in |early_data_reason| by this point. + if (hs->received_hello_retry_request) { + assert(ssl->s3->early_data_reason != ssl_early_data_unknown); + return true; + } + + if (!ssl->enable_early_data) { + ssl->s3->early_data_reason = ssl_early_data_disabled; return true; } + if (hs->max_version < TLS1_3_VERSION) { + // We discard inapplicable sessions, so this is redundant with the session + // checks below, but we check give a more useful reason. + ssl->s3->early_data_reason = ssl_early_data_protocol_version; + return true; + } + + if (ssl->session == nullptr) { + ssl->s3->early_data_reason = ssl_early_data_no_session_offered; + return true; + } + + if (ssl_session_protocol_version(ssl->session.get()) < TLS1_3_VERSION || + ssl->session->ticket_max_early_data == 0) { + ssl->s3->early_data_reason = ssl_early_data_unsupported_for_session; + return true; + } + + // In case ALPN preferences changed since this session was established, avoid + // reporting a confusing value in |SSL_get0_alpn_selected| and sending early + // data we know will be rejected. + if (!ssl->session->early_alpn.empty() && + !ssl_is_alpn_protocol_allowed(hs, ssl->session->early_alpn)) { + ssl->s3->early_data_reason = ssl_early_data_alpn_mismatch; + return true; + } + + // |early_data_reason| will be filled in later when the server responds. hs->early_data_offered = true; if (!CBB_add_u16(out, TLSEXT_TYPE_early_data) || @@ -2083,12 +2075,27 @@ static bool ext_early_data_add_clienthello(SSL_HANDSHAKE *hs, CBB *out) { } static bool ext_early_data_parse_serverhello(SSL_HANDSHAKE *hs, - uint8_t *out_alert, CBS *contents) { + uint8_t *out_alert, + CBS *contents) { SSL *const ssl = hs->ssl; if (contents == NULL) { + if (hs->early_data_offered && !hs->received_hello_retry_request) { + ssl->s3->early_data_reason = ssl->s3->session_reused + ? ssl_early_data_peer_declined + : ssl_early_data_session_not_resumed; + } else { + // We already filled in |early_data_reason| when declining to offer 0-RTT + // or handling the implicit HelloRetryRequest reject. + assert(ssl->s3->early_data_reason != ssl_early_data_unknown); + } return true; } + // If we received an HRR, the second ClientHello never offers early data, so + // the extensions logic will automatically reject early data extensions as + // unsolicited. This covered by the ServerAcceptsEarlyDataOnHRR test. + assert(!hs->received_hello_retry_request); + if (CBS_len(contents) != 0) { *out_alert = SSL_AD_DECODE_ERROR; return false; @@ -2100,6 +2107,7 @@ static bool ext_early_data_parse_serverhello(SSL_HANDSHAKE *hs, return false; } + ssl->s3->early_data_reason = ssl_early_data_accepted; ssl->s3->early_data_accepted = true; return true; } @@ -2186,8 +2194,8 @@ static bool ext_key_share_add_clienthello(SSL_HANDSHAKE *hs, CBB *out) { group_id = groups[0]; - if (group_id == SSL_CURVE_CECPQ2 && groups.size() >= 2) { - // CECPQ2 is not sent as the only initial key share. We'll include the + if (is_post_quantum_group(group_id) && groups.size() >= 2) { + // CECPQ2(b) is not sent as the only initial key share. We'll include the // 2nd preference group too to avoid round-trips. second_group_id = groups[1]; assert(second_group_id != group_id); @@ -2424,7 +2432,7 @@ static bool ext_supported_groups_add_clienthello(SSL_HANDSHAKE *hs, CBB *out) { } for (uint16_t group : tls1_get_grouplist(hs)) { - if (group == SSL_CURVE_CECPQ2 && + if (is_post_quantum_group(group) && hs->max_version < TLS1_3_VERSION) { continue; } @@ -2840,6 +2848,67 @@ static bool cert_compression_add_serverhello(SSL_HANDSHAKE *hs, CBB *out) { return true; } + +// Post-quantum experiment signal +// +// This extension may be used in order to identify a control group for +// experimenting with post-quantum key exchange algorithms. + +static bool ext_pq_experiment_signal_add_clienthello(SSL_HANDSHAKE *hs, + CBB *out) { + if (hs->ssl->ctx->pq_experiment_signal && + (!CBB_add_u16(out, TLSEXT_TYPE_pq_experiment_signal) || + !CBB_add_u16(out, 0))) { + return false; + } + + return true; +} + +static bool ext_pq_experiment_signal_parse_serverhello(SSL_HANDSHAKE *hs, + uint8_t *out_alert, + CBS *contents) { + if (contents == nullptr) { + return true; + } + + if (!hs->ssl->ctx->pq_experiment_signal || CBS_len(contents) != 0) { + return false; + } + + hs->ssl->s3->pq_experiment_signal_seen = true; + return true; +} + +static bool ext_pq_experiment_signal_parse_clienthello(SSL_HANDSHAKE *hs, + uint8_t *out_alert, + CBS *contents) { + if (contents == nullptr) { + return true; + } + + if (CBS_len(contents) != 0) { + return false; + } + + if (hs->ssl->ctx->pq_experiment_signal) { + hs->ssl->s3->pq_experiment_signal_seen = true; + } + + return true; +} + +static bool ext_pq_experiment_signal_add_serverhello(SSL_HANDSHAKE *hs, + CBB *out) { + if (hs->ssl->s3->pq_experiment_signal_seen && + (!CBB_add_u16(out, TLSEXT_TYPE_pq_experiment_signal) || + !CBB_add_u16(out, 0))) { + return false; + } + + return true; +} + // kExtensions contains all the supported extensions. static const struct tls_extension kExtensions[] = { { @@ -3028,6 +3097,14 @@ static const struct tls_extension kExtensions[] = { ext_delegated_credential_parse_clienthello, dont_add_serverhello, }, + { + TLSEXT_TYPE_pq_experiment_signal, + NULL, + ext_pq_experiment_signal_add_clienthello, + ext_pq_experiment_signal_parse_serverhello, + ext_pq_experiment_signal_parse_clienthello, + ext_pq_experiment_signal_add_serverhello, + }, }; #define kNumExtensions (sizeof(kExtensions) / sizeof(struct tls_extension)) @@ -3061,6 +3138,9 @@ bool ssl_add_clienthello_tlsext(SSL_HANDSHAKE *hs, CBB *out, return false; } + // Note we may send multiple ClientHellos for DTLS HelloVerifyRequest and TLS + // 1.3 HelloRetryRequest. For the latter, the extensions may change, so it is + // important to reset this value. hs->extensions.sent = 0; for (size_t i = 0; i < kNumExtensions; i++) { diff --git a/src/ssl/test/bssl_shim.cc b/src/ssl/test/bssl_shim.cc index 62db0767..f58c1510 100644 --- a/src/ssl/test/bssl_shim.cc +++ b/src/ssl/test/bssl_shim.cc @@ -279,23 +279,23 @@ static uint16_t GetProtocolVersion(const SSL *ssl) { // after a renegotiation, that authentication-related properties match |config|. static bool CheckAuthProperties(SSL *ssl, bool is_resume, const TestConfig *config) { - if (!config->expected_ocsp_response.empty()) { + if (!config->expect_ocsp_response.empty()) { const uint8_t *data; size_t len; SSL_get0_ocsp_response(ssl, &data, &len); - if (config->expected_ocsp_response.size() != len || - OPENSSL_memcmp(config->expected_ocsp_response.data(), data, len) != 0) { + if (config->expect_ocsp_response.size() != len || + OPENSSL_memcmp(config->expect_ocsp_response.data(), data, len) != 0) { fprintf(stderr, "OCSP response mismatch\n"); return false; } } - if (!config->expected_signed_cert_timestamps.empty()) { + if (!config->expect_signed_cert_timestamps.empty()) { const uint8_t *data; size_t len; SSL_get0_signed_cert_timestamp_list(ssl, &data, &len); - if (config->expected_signed_cert_timestamps.size() != len || - OPENSSL_memcmp(config->expected_signed_cert_timestamps.data(), data, + if (config->expect_signed_cert_timestamps.size() != len || + OPENSSL_memcmp(config->expect_signed_cert_timestamps.data(), data, len) != 0) { fprintf(stderr, "SCT list mismatch\n"); return false; @@ -389,6 +389,39 @@ static bool CheckAuthProperties(SSL *ssl, bool is_resume, return true; } +static const char *EarlyDataReasonToString(ssl_early_data_reason_t reason) { + switch (reason) { + case ssl_early_data_unknown: + return "unknown"; + case ssl_early_data_disabled: + return "disabled"; + case ssl_early_data_accepted: + return "accepted"; + case ssl_early_data_protocol_version: + return "protocol_version"; + case ssl_early_data_peer_declined: + return "peer_declined"; + case ssl_early_data_no_session_offered: + return "no_session_offered"; + case ssl_early_data_session_not_resumed: + return "session_not_resumed"; + case ssl_early_data_unsupported_for_session: + return "unsupported_for_session"; + case ssl_early_data_hello_retry_request: + return "hello_retry_request"; + case ssl_early_data_alpn_mismatch: + return "alpn_mismatch"; + case ssl_early_data_channel_id: + return "channel_id"; + case ssl_early_data_token_binding: + return "token_binding"; + case ssl_early_data_ticket_age_skew: + return "ticket_age_skew"; + } + + abort(); +} + // CheckHandshakeProperties checks, immediately after |ssl| completes its // initial handshake (or False Starts), whether all the properties are // consistent with the test configuration and invariants. @@ -459,23 +492,23 @@ static bool CheckHandshakeProperties(SSL *ssl, bool is_resume, return false; } - if (!config->expected_server_name.empty()) { + if (!config->expect_server_name.empty()) { const char *server_name = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name); if (server_name == nullptr || - server_name != config->expected_server_name) { + server_name != config->expect_server_name) { fprintf(stderr, "servername mismatch (got %s; want %s)\n", - server_name, config->expected_server_name.c_str()); + server_name, config->expect_server_name.c_str()); return false; } } - if (!config->expected_next_proto.empty()) { + if (!config->expect_next_proto.empty()) { const uint8_t *next_proto; unsigned next_proto_len; SSL_get0_next_proto_negotiated(ssl, &next_proto, &next_proto_len); - if (next_proto_len != config->expected_next_proto.size() || - OPENSSL_memcmp(next_proto, config->expected_next_proto.data(), + if (next_proto_len != config->expect_next_proto.size() || + OPENSSL_memcmp(next_proto, config->expect_next_proto.data(), next_proto_len) != 0) { fprintf(stderr, "negotiated next proto mismatch\n"); return false; @@ -486,48 +519,48 @@ static bool CheckHandshakeProperties(SSL *ssl, bool is_resume, const uint8_t *alpn_proto; unsigned alpn_proto_len; SSL_get0_alpn_selected(ssl, &alpn_proto, &alpn_proto_len); - if (alpn_proto_len != config->expected_alpn.size() || - OPENSSL_memcmp(alpn_proto, config->expected_alpn.data(), + if (alpn_proto_len != config->expect_alpn.size() || + OPENSSL_memcmp(alpn_proto, config->expect_alpn.data(), alpn_proto_len) != 0) { fprintf(stderr, "negotiated alpn proto mismatch\n"); return false; } } - if (!config->expected_quic_transport_params.empty()) { + if (!config->expect_quic_transport_params.empty()) { const uint8_t *peer_params; size_t peer_params_len; SSL_get_peer_quic_transport_params(ssl, &peer_params, &peer_params_len); - if (peer_params_len != config->expected_quic_transport_params.size() || + if (peer_params_len != config->expect_quic_transport_params.size() || OPENSSL_memcmp(peer_params, - config->expected_quic_transport_params.data(), + config->expect_quic_transport_params.data(), peer_params_len) != 0) { fprintf(stderr, "QUIC transport params mismatch\n"); return false; } } - if (!config->expected_channel_id.empty()) { + if (!config->expect_channel_id.empty()) { uint8_t channel_id[64]; if (!SSL_get_tls_channel_id(ssl, channel_id, sizeof(channel_id))) { fprintf(stderr, "no channel id negotiated\n"); return false; } - if (config->expected_channel_id.size() != 64 || - OPENSSL_memcmp(config->expected_channel_id.data(), channel_id, 64) != + if (config->expect_channel_id.size() != 64 || + OPENSSL_memcmp(config->expect_channel_id.data(), channel_id, 64) != 0) { fprintf(stderr, "channel id mismatch\n"); return false; } } - if (config->expected_token_binding_param != -1) { + if (config->expect_token_binding_param != -1) { if (!SSL_is_token_binding_negotiated(ssl)) { fprintf(stderr, "no Token Binding negotiated\n"); return false; } if (SSL_get_negotiated_token_binding_param(ssl) != - static_cast<uint8_t>(config->expected_token_binding_param)) { + static_cast<uint8_t>(config->expect_token_binding_param)) { fprintf(stderr, "Token Binding param mismatch\n"); return false; } @@ -587,7 +620,8 @@ static bool CheckHandshakeProperties(SSL *ssl, bool is_resume, return false; } - if (is_resume && !SSL_in_early_data(ssl)) { + // The early data status is only applicable after the handshake is confirmed. + if (!SSL_in_early_data(ssl)) { if ((config->expect_accept_early_data && !SSL_early_data_accepted(ssl)) || (config->expect_reject_early_data && SSL_early_data_accepted(ssl))) { fprintf(stderr, @@ -595,6 +629,15 @@ static bool CheckHandshakeProperties(SSL *ssl, bool is_resume, SSL_early_data_accepted(ssl) ? "" : " not"); return false; } + + const char *early_data_reason = + EarlyDataReasonToString(SSL_get_early_data_reason(ssl)); + if (!config->expect_early_data_reason.empty() && + config->expect_early_data_reason != early_data_reason) { + fprintf(stderr, "Early data reason was \"%s\", expected \"%s\"\n", + early_data_reason, config->expect_early_data_reason.c_str()); + return false; + } } if (!config->psk.empty()) { @@ -622,6 +665,21 @@ static bool CheckHandshakeProperties(SSL *ssl, bool is_resume, return false; } + if (config->expect_delegated_credential_used != + !!SSL_delegated_credential_used(ssl)) { + fprintf(stderr, + "Got %s delegated credential usage, but wanted opposite. \n", + SSL_delegated_credential_used(ssl) ? "" : "no"); + return false; + } + + if (config->expect_pq_experiment_signal != + !!SSL_pq_experiment_signal_seen(ssl)) { + fprintf(stderr, "Got %sPQ experiment signal, but wanted opposite. \n", + SSL_pq_experiment_signal_seen(ssl) ? "" : "no "); + return false; + } + return true; } @@ -701,6 +759,16 @@ static bool DoConnection(bssl::UniquePtr<SSL_SESSION> *out_session, return false; } + // Client pre- and post-0-RTT reject states are considered logically + // different connections with different test expections. Check that the test + // did not mistakenly configure reason expectations on the wrong one. + if (!config->expect_early_data_reason.empty()) { + fprintf(stderr, + "Test error: client reject -expect-early-data-reason flags " + "should be configured with -on-retry, not -on-resume.\n"); + return false; + } + // Reset the connection and try again at 1-RTT. SSL_reset_early_data_reject(ssl.get()); GetTestState(ssl.get())->cert_verified = false; @@ -804,22 +872,6 @@ static bool DoExchange(bssl::UniquePtr<SSL_SESSION> *out_session, GetTestState(ssl)->got_new_session = false; } - if (config->export_early_keying_material > 0) { - std::vector<uint8_t> result( - static_cast<size_t>(config->export_early_keying_material)); - if (!SSL_export_early_keying_material( - ssl, result.data(), result.size(), config->export_label.data(), - config->export_label.size(), - reinterpret_cast<const uint8_t *>(config->export_context.data()), - config->export_context.size())) { - fprintf(stderr, "failed to export keying material\n"); - return false; - } - if (WriteAll(ssl, result.data(), result.size()) < 0) { - return false; - } - } - if (config->export_keying_material > 0) { std::vector<uint8_t> result( static_cast<size_t>(config->export_keying_material)); diff --git a/src/ssl/test/runner/common.go b/src/ssl/test/runner/common.go index bbcacf59..b56b9b35 100644 --- a/src/ssl/test/runner/common.go +++ b/src/ssl/test/runner/common.go @@ -126,6 +126,7 @@ const ( extensionQUICTransportParams uint16 = 0xffa5 // draft-ietf-quic-tls-13 extensionChannelID uint16 = 30032 // not IANA assigned extensionDelegatedCredentials uint16 = 0xff02 // not IANA assigned + extensionPQExperimentSignal uint16 = 54538 ) // TLS signaling cipher suite values @@ -144,12 +145,13 @@ var tls13HelloRetryRequest = []uint8{ type CurveID uint16 const ( - CurveP224 CurveID = 21 - CurveP256 CurveID = 23 - CurveP384 CurveID = 24 - CurveP521 CurveID = 25 - CurveX25519 CurveID = 29 - CurveCECPQ2 CurveID = 16696 + CurveP224 CurveID = 21 + CurveP256 CurveID = 23 + CurveP384 CurveID = 24 + CurveP521 CurveID = 25 + CurveX25519 CurveID = 29 + CurveCECPQ2 CurveID = 16696 + CurveCECPQ2b CurveID = 65074 ) // TLS Elliptic Curve Point Formats @@ -499,6 +501,11 @@ type Config struct { CertCompressionAlgs map[uint16]CertCompressionAlg + // PQExperimentSignal instructs a client to send a non-IANA defined extension + // that signals participation in an experiment of post-quantum key exchange + // methods. + PQExperimentSignal bool + // Bugs specifies optional misbehaviour to be used for testing other // implementations. Bugs ProtocolBugs @@ -1319,21 +1326,6 @@ type ProtocolBugs struct { // it was accepted. SendEarlyDataExtension bool - // ExpectEarlyKeyingMaterial, if non-zero, causes a TLS 1.3 server to - // read an application data record after the ClientHello before it sends - // a ServerHello. The record's contents have the specified length and - // match the corresponding early exporter value. This is used to test - // the client using the early exporter in the 0-RTT state. - ExpectEarlyKeyingMaterial int - - // ExpectEarlyKeyingLabel is the label to use with - // ExpectEarlyKeyingMaterial. - ExpectEarlyKeyingLabel string - - // ExpectEarlyKeyingContext is the context string to use with - // ExpectEarlyKeyingMaterial - ExpectEarlyKeyingContext string - // ExpectEarlyData causes a TLS 1.3 server to read application // data after the ClientHello (assuming the server is able to // derive the key under which the data is encrypted) before it @@ -1649,6 +1641,10 @@ type ProtocolBugs struct { // DisableDelegatedCredentials, if true, disables client support for delegated // credentials. DisableDelegatedCredentials bool + + // ExpectPQExperimentSignal specifies whether or not the post-quantum + // experiment signal should be received by a client or server. + ExpectPQExperimentSignal bool } func (c *Config) serverInit() { @@ -1728,7 +1724,7 @@ func (c *Config) maxVersion(isDTLS bool) uint16 { return ret } -var defaultCurvePreferences = []CurveID{CurveCECPQ2, CurveX25519, CurveP256, CurveP384, CurveP521} +var defaultCurvePreferences = []CurveID{CurveCECPQ2b, CurveCECPQ2, CurveX25519, CurveP256, CurveP384, CurveP521} func (c *Config) curvePreferences() []CurveID { if c == nil || len(c.CurvePreferences) == 0 { diff --git a/src/ssl/test/runner/ecdsa_p224_key.pem b/src/ssl/test/runner/ecdsa_p224_key.pem index cfe411b4..d62594bb 100644 --- a/src/ssl/test/runner/ecdsa_p224_key.pem +++ b/src/ssl/test/runner/ecdsa_p224_key.pem @@ -1,5 +1,5 @@ ------BEGIN EC PRIVATE KEY----- -MGgCAQEEHGi+rNLi+gHJqmRRtdlLBOw1WYv7H/VnlYGAZ0+gBwYFK4EEACGhPAM6 -AATp26Xp0vT4LKigWIorhX4Rg1g9sxmgtPFFyNGCvDd1vpDMEC+INLEoANNW7JGZ -pULvgx/pugEcOQ== ------END EC PRIVATE KEY----- +-----BEGIN PRIVATE KEY----- +MHgCAQAwEAYHKoZIzj0CAQYFK4EEACEEYTBfAgEBBBxovqzS4voByapkUbXZSwTs +NVmL+x/1Z5WBgGdPoTwDOgAE6dul6dL0+CyooFiKK4V+EYNYPbMZoLTxRcjRgrw3 +db6QzBAviDSxKADTVuyRmaVC74Mf6boBHDk= +-----END PRIVATE KEY----- diff --git a/src/ssl/test/runner/fuzzer_mode.json b/src/ssl/test/runner/fuzzer_mode.json index 1a154c2c..0a507227 100644 --- a/src/ssl/test/runner/fuzzer_mode.json +++ b/src/ssl/test/runner/fuzzer_mode.json @@ -18,9 +18,6 @@ "BadECDSA-*": "Fuzzer mode always accepts a signature.", "*-InvalidSignature-*": "Fuzzer mode always accepts a signature.", - "*Auth-Verify-RSA-PKCS1-*-TLS13*": "Fuzzer mode always accepts a signature.", - "*Auth-Verify-ECDSA-SHA1-TLS13*": "Fuzzer mode always accepts a signature.", - "*Auth-Verify-ECDSA-P224-*-TLS13*": "Fuzzer mode always accepts a signature.", "Verify-*Auth-SignatureType*": "Fuzzer mode always accepts a signature.", "ECDSACurveMismatch-Verify-TLS13*": "Fuzzer mode always accepts a signature.", "InvalidChannelIDSignature-*": "Fuzzer mode always accepts a signature.", @@ -30,6 +27,7 @@ "Resume-Server-DeclineCrossVersion*": "Fuzzer mode does not encrypt tickets.", "TicketCallback-SingleCall-*": "Fuzzer mode does not encrypt tickets.", "CorruptTicket-*": "Fuzzer mode does not encrypt tickets.", + "*RejectTicket-Server-*": "Fuzzer mode does not encrypt tickets.", "ShimTicketRewritable*": "Fuzzer mode does not encrypt tickets.", "Resume-Server-*Binder*": "Fuzzer mode does not check binders.", @@ -46,6 +44,7 @@ "*-EarlyData-RejectUnfinishedWrite-Client-*": "Trial decryption does not work with the NULL cipher.", "EarlyData-Reject*-Client-*": "Trial decryption does not work with the NULL cipher.", "CustomExtensions-Server-EarlyDataOffered": "Trial decryption does not work with the NULL cipher.", + "*-TicketAgeSkew-*-Reject": "Trial decryption does not work with the NULL cipher.", "Renegotiate-Client-BadExt*": "Fuzzer mode does not check renegotiation_info.", diff --git a/src/ssl/test/runner/handshake_client.go b/src/ssl/test/runner/handshake_client.go index 45dc75d1..2574ec3f 100644 --- a/src/ssl/test/runner/handshake_client.go +++ b/src/ssl/test/runner/handshake_client.go @@ -129,6 +129,7 @@ func (c *Conn) clientHandshake() error { omitExtensions: c.config.Bugs.OmitExtensions, emptyExtensions: c.config.Bugs.EmptyExtensions, delegatedCredentials: !c.config.Bugs.DisableDelegatedCredentials, + pqExperimentSignal: c.config.PQExperimentSignal, } if maxVersion >= VersionTLS13 { @@ -1666,6 +1667,10 @@ func (hs *clientHandshakeState) processServerExtensions(serverExtensions *server c.quicTransportParams = serverExtensions.quicTransportParams } + if c.config.Bugs.ExpectPQExperimentSignal != serverExtensions.pqExperimentSignal { + return fmt.Errorf("tls: PQ experiment signal presence (%t) was not what was expected", serverExtensions.pqExperimentSignal) + } + return nil } diff --git a/src/ssl/test/runner/handshake_messages.go b/src/ssl/test/runner/handshake_messages.go index f12ca1ac..ac52eed4 100644 --- a/src/ssl/test/runner/handshake_messages.go +++ b/src/ssl/test/runner/handshake_messages.go @@ -298,6 +298,7 @@ type clientHelloMsg struct { pad int compressedCertAlgs []uint16 delegatedCredentials bool + pqExperimentSignal bool } func (m *clientHelloMsg) equal(i interface{}) bool { @@ -352,7 +353,8 @@ func (m *clientHelloMsg) equal(i interface{}) bool { m.emptyExtensions == m1.emptyExtensions && m.pad == m1.pad && eqUint16s(m.compressedCertAlgs, m1.compressedCertAlgs) && - m.delegatedCredentials == m1.delegatedCredentials + m.delegatedCredentials == m1.delegatedCredentials && + m.pqExperimentSignal == m1.pqExperimentSignal } func (m *clientHelloMsg) marshalKeyShares(bb *byteBuilder) { @@ -598,6 +600,11 @@ func (m *clientHelloMsg) marshal() []byte { extensions.addU16(extensionDelegatedCredentials) extensions.addU16(0) // Length is always 0 } + if m.pqExperimentSignal { + extensions.addU16(extensionPQExperimentSignal) + extensions.addU16(0) // Length is always 0 + } + // The PSK extension must be last. See https://tools.ietf.org/html/rfc8446#section-4.2.11 if len(m.pskIdentities) > 0 && !m.pskBinderFirst { extensions.addU16(extensionPreSharedKey) @@ -724,6 +731,7 @@ func (m *clientHelloMsg) unmarshal(data []byte) bool { m.extendedMasterSecret = false m.customExtension = "" m.delegatedCredentials = false + m.pqExperimentSignal = false if len(reader) == 0 { // ClientHello is optionally followed by extension data @@ -959,6 +967,11 @@ func (m *clientHelloMsg) unmarshal(data []byte) bool { return false } m.delegatedCredentials = true + case extensionPQExperimentSignal: + if len(body) != 0 { + return false + } + m.pqExperimentSignal = true } if isGREASEValue(extension) { @@ -1226,6 +1239,7 @@ type serverExtensions struct { supportedCurves []CurveID quicTransportParams []byte serverNameAck bool + pqExperimentSignal bool } func (m *serverExtensions) marshal(extensions *byteBuilder) { @@ -1360,6 +1374,10 @@ func (m *serverExtensions) marshal(extensions *byteBuilder) { extensions.addU16(extensionServerName) extensions.addU16(0) // zero length } + if m.pqExperimentSignal { + extensions.addU16(extensionPQExperimentSignal) + extensions.addU16(0) // zero length + } } func (m *serverExtensions) unmarshal(data byteReader, version uint16) bool { @@ -1468,6 +1486,11 @@ func (m *serverExtensions) unmarshal(data byteReader, version uint16) bool { return false } m.hasEarlyData = true + case extensionPQExperimentSignal: + if len(body) != 0 { + return false + } + m.pqExperimentSignal = true default: // Unknown extensions are illegal from the server. return false diff --git a/src/ssl/test/runner/handshake_server.go b/src/ssl/test/runner/handshake_server.go index d2ef9b42..3a6c8107 100644 --- a/src/ssl/test/runner/handshake_server.go +++ b/src/ssl/test/runner/handshake_server.go @@ -210,8 +210,8 @@ func (hs *serverHandshakeState) readClientHello() error { if config.Bugs.FailIfCECPQ2Offered { for _, offeredCurve := range hs.clientHello.supportedCurves { - if offeredCurve == CurveCECPQ2 { - return errors.New("tls: CECPQ2 was offered") + if isPqGroup(offeredCurve) { + return errors.New("tls: CECPQ2 or CECPQ2b was offered") } } } @@ -228,6 +228,10 @@ func (hs *serverHandshakeState) readClientHello() error { } } + if c.config.Bugs.ExpectPQExperimentSignal != hs.clientHello.pqExperimentSignal { + return fmt.Errorf("tls: PQ experiment signal presence (%t) was not what was expected", hs.clientHello.pqExperimentSignal) + } + c.clientVersion = hs.clientHello.vers // Use the versions extension if supplied, otherwise use the legacy ClientHello version. @@ -722,16 +726,7 @@ ResendHelloRetryRequest: } c.earlyCipherSuite = hs.suite - expectEarlyData := config.Bugs.ExpectEarlyData - if n := config.Bugs.ExpectEarlyKeyingMaterial; n > 0 { - exporter, err := c.ExportEarlyKeyingMaterial(n, []byte(config.Bugs.ExpectEarlyKeyingLabel), []byte(config.Bugs.ExpectEarlyKeyingContext)) - if err != nil { - return err - } - expectEarlyData = append([][]byte{exporter}, expectEarlyData...) - } - - for _, expectedMsg := range expectEarlyData { + for _, expectedMsg := range config.Bugs.ExpectEarlyData { if err := c.readRecord(recordTypeApplicationData); err != nil { return err } @@ -1232,8 +1227,8 @@ func (hs *serverHandshakeState) processClientHello() (isResume bool, err error) preferredCurves := config.curvePreferences() Curves: for _, curve := range hs.clientHello.supportedCurves { - if curve == CurveCECPQ2 && c.vers < VersionTLS13 { - // CECPQ2 is TLS 1.3-only. + if isPqGroup(curve) && c.vers < VersionTLS13 { + // CECPQ2 and CECPQ2b is TLS 1.3-only. continue } @@ -1456,6 +1451,7 @@ func (hs *serverHandshakeState) processClientExtensions(serverExtensions *server } serverExtensions.serverNameAck = c.config.Bugs.SendServerNameAck + serverExtensions.pqExperimentSignal = hs.clientHello.pqExperimentSignal return nil } diff --git a/src/ssl/test/runner/key_agreement.go b/src/ssl/test/runner/key_agreement.go index 13e78bc4..f4789b66 100644 --- a/src/ssl/test/runner/key_agreement.go +++ b/src/ssl/test/runner/key_agreement.go @@ -19,6 +19,7 @@ import ( "boringssl.googlesource.com/boringssl/ssl/test/runner/curve25519" "boringssl.googlesource.com/boringssl/ssl/test/runner/ed25519" "boringssl.googlesource.com/boringssl/ssl/test/runner/hrss" + "boringssl.googlesource.com/boringssl/ssl/test/runner/sike" ) type keyType int @@ -433,6 +434,98 @@ func (e *cecpq2Curve) finish(peerKey []byte) (preMasterSecret []byte, err error) return preMasterSecret, nil } +// cecpq2BCurve implements CECPQ2b, which is SIKE combined with X25519. +type cecpq2BCurve struct { + // Both public key and shared secret size + x25519PrivateKey [32]byte + sikePrivateKey *sike.PrivateKey +} + +func (e *cecpq2BCurve) offer(rand io.Reader) (publicKey []byte, err error) { + if _, err = io.ReadFull(rand, e.x25519PrivateKey[:]); err != nil { + return nil, err + } + + var x25519Public [32]byte + curve25519.ScalarBaseMult(&x25519Public, &e.x25519PrivateKey) + + e.sikePrivateKey = sike.NewPrivateKey(sike.KeyVariant_SIKE) + if err = e.sikePrivateKey.Generate(rand); err != nil { + return nil, err + } + + sikePublic := e.sikePrivateKey.GeneratePublicKey().Export() + var ret []byte + ret = append(ret, x25519Public[:]...) + ret = append(ret, sikePublic...) + return ret, nil +} + +func (e *cecpq2BCurve) accept(rand io.Reader, peerKey []byte) (publicKey []byte, preMasterSecret []byte, err error) { + if len(peerKey) != 32+sike.Params.PublicKeySize { + return nil, nil, errors.New("tls: bad length CECPQ2b offer") + } + + if _, err = io.ReadFull(rand, e.x25519PrivateKey[:]); err != nil { + return nil, nil, err + } + + var x25519Shared, x25519PeerKey, x25519Public [32]byte + copy(x25519PeerKey[:], peerKey) + curve25519.ScalarBaseMult(&x25519Public, &e.x25519PrivateKey) + curve25519.ScalarMult(&x25519Shared, &e.x25519PrivateKey, &x25519PeerKey) + + // Per RFC 7748, reject the all-zero value in constant time. + var zeros [32]byte + if subtle.ConstantTimeCompare(zeros[:], x25519Shared[:]) == 1 { + return nil, nil, errors.New("tls: X25519 value with wrong order") + } + + var sikePubKey = sike.NewPublicKey(sike.KeyVariant_SIKE) + if err = sikePubKey.Import(peerKey[32:]); err != nil { + // should never happen as size was already checked + return nil, nil, errors.New("tls: implementation error") + } + sikeCiphertext, sikeShared, err := sike.Encapsulate(rand, sikePubKey) + if err != nil { + return nil, nil, err + } + + publicKey = append(publicKey, x25519Public[:]...) + publicKey = append(publicKey, sikeCiphertext...) + preMasterSecret = append(preMasterSecret, x25519Shared[:]...) + preMasterSecret = append(preMasterSecret, sikeShared...) + + return publicKey, preMasterSecret, nil +} + +func (e *cecpq2BCurve) finish(peerKey []byte) (preMasterSecret []byte, err error) { + if len(peerKey) != 32+(sike.Params.PublicKeySize+sike.Params.MsgLen) { + return nil, errors.New("tls: bad length CECPQ2b reply") + } + + var x25519Shared, x25519PeerKey [32]byte + copy(x25519PeerKey[:], peerKey) + curve25519.ScalarMult(&x25519Shared, &e.x25519PrivateKey, &x25519PeerKey) + + // Per RFC 7748, reject the all-zero value in constant time. + var zeros [32]byte + if subtle.ConstantTimeCompare(zeros[:], x25519Shared[:]) == 1 { + return nil, errors.New("tls: X25519 value with wrong order") + } + + var sikePubKey = e.sikePrivateKey.GeneratePublicKey() + sikeShared, err := sike.Decapsulate(e.sikePrivateKey, sikePubKey, peerKey[32:]) + if err != nil { + return nil, errors.New("tls: invalid SIKE ciphertext") + } + + preMasterSecret = append(preMasterSecret, x25519Shared[:]...) + preMasterSecret = append(preMasterSecret, sikeShared...) + + return preMasterSecret, nil +} + func curveForCurveID(id CurveID, config *Config) (ecdhCurve, bool) { switch id { case CurveP224: @@ -447,6 +540,8 @@ func curveForCurveID(id CurveID, config *Config) (ecdhCurve, bool) { return &x25519ECDHCurve{setHighBit: config.Bugs.SetX25519HighBit}, true case CurveCECPQ2: return &cecpq2Curve{}, true + case CurveCECPQ2b: + return &cecpq2BCurve{}, true default: return nil, false } @@ -594,8 +689,8 @@ func (ka *ecdheKeyAgreement) generateServerKeyExchange(config *Config, cert *Cer NextCandidate: for _, candidate := range preferredCurves { - if candidate == CurveCECPQ2 && version < VersionTLS13 { - // CECPQ2 is TLS 1.3-only. + if isPqGroup(candidate) && version < VersionTLS13 { + // CECPQ2 and CECPQ2b is TLS 1.3-only. continue } diff --git a/src/ssl/test/runner/runner.go b/src/ssl/test/runner/runner.go index 8461bd86..877a239c 100644 --- a/src/ssl/test/runner/runner.go +++ b/src/ssl/test/runner/runner.go @@ -589,9 +589,6 @@ type testCase struct { exportLabel string exportContext string useExportContext bool - // exportEarlyKeyingMaterial, if non-zero, behaves like - // exportKeyingMaterial, but for the early exporter. - exportEarlyKeyingMaterial int // flags, if not empty, contains a list of command-line flags that will // be passed to the shim program. flags []string @@ -881,20 +878,6 @@ func doExchange(test *testCase, config *Config, conn net.Conn, isResume bool, tr } } - if isResume && test.exportEarlyKeyingMaterial > 0 { - actual := make([]byte, test.exportEarlyKeyingMaterial) - if _, err := io.ReadFull(tlsConn, actual); err != nil { - return err - } - expected, err := tlsConn.ExportEarlyKeyingMaterial(test.exportEarlyKeyingMaterial, []byte(test.exportLabel), []byte(test.exportContext)) - if err != nil { - return err - } - if !bytes.Equal(actual, expected) { - return fmt.Errorf("early keying material mismatch; got %x, wanted %x", actual, expected) - } - } - if test.exportKeyingMaterial > 0 { actual := make([]byte, test.exportKeyingMaterial) if _, err := io.ReadFull(tlsConn, actual); err != nil { @@ -1272,10 +1255,7 @@ func runTest(test *testCase, shimPath string, mallocNumToFail int64) error { flags = append(flags, "-use-export-context") } } - if test.exportEarlyKeyingMaterial > 0 { - flags = append(flags, "-on-resume-export-early-keying-material", strconv.Itoa(test.exportEarlyKeyingMaterial)) - } - if test.exportKeyingMaterial > 0 || test.exportEarlyKeyingMaterial > 0 { + if test.exportKeyingMaterial > 0 { flags = append(flags, "-export-label", test.exportLabel) flags = append(flags, "-export-context", test.exportContext) } @@ -1569,34 +1549,34 @@ type testCipherSuite struct { } var testCipherSuites = []testCipherSuite{ - {"3DES-SHA", TLS_RSA_WITH_3DES_EDE_CBC_SHA}, - {"AES128-GCM", TLS_RSA_WITH_AES_128_GCM_SHA256}, - {"AES128-SHA", TLS_RSA_WITH_AES_128_CBC_SHA}, - {"AES256-GCM", TLS_RSA_WITH_AES_256_GCM_SHA384}, - {"AES256-SHA", TLS_RSA_WITH_AES_256_CBC_SHA}, - {"ECDHE-ECDSA-AES128-GCM", TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256}, - {"ECDHE-ECDSA-AES128-SHA", TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA}, - {"ECDHE-ECDSA-AES256-GCM", TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384}, - {"ECDHE-ECDSA-AES256-SHA", TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA}, - {"ECDHE-ECDSA-CHACHA20-POLY1305", TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256}, - {"ECDHE-RSA-AES128-GCM", TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}, - {"ECDHE-RSA-AES128-SHA", TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA}, - {"ECDHE-RSA-AES256-GCM", TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384}, - {"ECDHE-RSA-AES256-SHA", TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA}, - {"ECDHE-RSA-CHACHA20-POLY1305", TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256}, - {"PSK-AES128-CBC-SHA", TLS_PSK_WITH_AES_128_CBC_SHA}, - {"PSK-AES256-CBC-SHA", TLS_PSK_WITH_AES_256_CBC_SHA}, - {"ECDHE-PSK-AES128-CBC-SHA", TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA}, - {"ECDHE-PSK-AES256-CBC-SHA", TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA}, - {"ECDHE-PSK-CHACHA20-POLY1305", TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256}, - {"AEAD-CHACHA20-POLY1305", TLS_CHACHA20_POLY1305_SHA256}, - {"AEAD-AES128-GCM-SHA256", TLS_AES_128_GCM_SHA256}, - {"AEAD-AES256-GCM-SHA384", TLS_AES_256_GCM_SHA384}, - {"NULL-SHA", TLS_RSA_WITH_NULL_SHA}, + {"RSA_WITH_3DES_EDE_CBC_SHA", TLS_RSA_WITH_3DES_EDE_CBC_SHA}, + {"RSA_WITH_AES_128_GCM_SHA256", TLS_RSA_WITH_AES_128_GCM_SHA256}, + {"RSA_WITH_AES_128_CBC_SHA", TLS_RSA_WITH_AES_128_CBC_SHA}, + {"RSA_WITH_AES_256_GCM_SHA384", TLS_RSA_WITH_AES_256_GCM_SHA384}, + {"RSA_WITH_AES_256_CBC_SHA", TLS_RSA_WITH_AES_256_CBC_SHA}, + {"ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256}, + {"ECDHE_ECDSA_WITH_AES_128_CBC_SHA", TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA}, + {"ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384}, + {"ECDHE_ECDSA_WITH_AES_256_CBC_SHA", TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA}, + {"ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256", TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256}, + {"ECDHE_RSA_WITH_AES_128_GCM_SHA256", TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}, + {"ECDHE_RSA_WITH_AES_128_CBC_SHA", TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA}, + {"ECDHE_RSA_WITH_AES_256_GCM_SHA384", TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384}, + {"ECDHE_RSA_WITH_AES_256_CBC_SHA", TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA}, + {"ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256", TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256}, + {"PSK_WITH_AES_128_CBC_SHA", TLS_PSK_WITH_AES_128_CBC_SHA}, + {"PSK_WITH_AES_256_CBC_SHA", TLS_PSK_WITH_AES_256_CBC_SHA}, + {"ECDHE_PSK_WITH_AES_128_CBC_SHA", TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA}, + {"ECDHE_PSK_WITH_AES_256_CBC_SHA", TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA}, + {"ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256", TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256}, + {"CHACHA20_POLY1305_SHA256", TLS_CHACHA20_POLY1305_SHA256}, + {"AES_128_GCM_SHA256", TLS_AES_128_GCM_SHA256}, + {"AES_256_GCM_SHA384", TLS_AES_256_GCM_SHA384}, + {"RSA_WITH_NULL_SHA", TLS_RSA_WITH_NULL_SHA}, } func hasComponent(suiteName, component string) bool { - return strings.Contains("-"+suiteName+"-", "-"+component+"-") + return strings.Contains("_"+suiteName+"_", "_"+component+"_") } func isTLS12Only(suiteName string) bool { @@ -1607,7 +1587,7 @@ func isTLS12Only(suiteName string) bool { } func isTLS13Suite(suiteName string) bool { - return strings.HasPrefix(suiteName, "AEAD-") + return !hasComponent(suiteName, "WITH") } func bigFromHex(hex string) *big.Int { @@ -4466,6 +4446,8 @@ func addStateMachineCoverageTests(config stateMachineTestConfig) { }, resumeSession: true, resumeRenewedSession: true, + // 0-RTT being disabled overrides all other 0-RTT reasons. + flags: []string{"-expect-early-data-reason", "disabled"}, }) tests = append(tests, testCase{ @@ -4477,9 +4459,13 @@ func addStateMachineCoverageTests(config stateMachineTestConfig) { }, resumeSession: true, resumeRenewedSession: true, - // TLS 1.3 uses tickets, so the session should not be - // cached statefully. - flags: []string{"-expect-no-session-id"}, + flags: []string{ + // TLS 1.3 uses tickets, so the session should not be + // cached statefully. + "-expect-no-session-id", + // 0-RTT being disabled overrides all other 0-RTT reasons. + "-expect-early-data-reason", "disabled", + }, }) tests = append(tests, testCase{ @@ -4532,7 +4518,7 @@ func addStateMachineCoverageTests(config stateMachineTestConfig) { flags: []string{ "-enable-early-data", "-expect-ticket-supports-early-data", - "-expect-accept-early-data", + "-on-resume-expect-accept-early-data", "-on-resume-shim-writes-first", }, }) @@ -4561,7 +4547,7 @@ func addStateMachineCoverageTests(config stateMachineTestConfig) { flags: []string{ "-enable-early-data", "-expect-ticket-supports-early-data", - "-expect-accept-early-data", + "-on-resume-expect-accept-early-data", "-on-resume-read-with-unfinished-write", "-on-resume-shim-writes-first", }, @@ -4612,7 +4598,7 @@ func addStateMachineCoverageTests(config stateMachineTestConfig) { resumeSession: true, flags: []string{ "-enable-early-data", - "-expect-accept-early-data", + "-on-resume-expect-accept-early-data", }, shouldFail: true, expectedError: ":TOO_MUCH_READ_EARLY_DATA:", @@ -6663,7 +6649,7 @@ func addExtensionTests() { flags: []string{ "-token-binding-params", base64.StdEncoding.EncodeToString([]byte{2, 1, 0}), - "-expected-token-binding-param", + "-expect-token-binding-param", "2", }, }) @@ -6712,7 +6698,7 @@ func addExtensionTests() { flags: []string{ "-token-binding-params", base64.StdEncoding.EncodeToString([]byte{2, 1, 0}), - "-expected-token-binding-param", + "-expect-token-binding-param", "2", }, }) @@ -6748,7 +6734,7 @@ func addExtensionTests() { flags: []string{ "-token-binding-params", base64.StdEncoding.EncodeToString([]byte{2, 1, 0}), - "-expected-token-binding-param", + "-expect-token-binding-param", "2", }, }) @@ -6766,7 +6752,7 @@ func addExtensionTests() { flags: []string{ "-token-binding-params", base64.StdEncoding.EncodeToString([]byte{0, 1, 2}), - "-expected-token-binding-param", + "-expect-token-binding-param", "2", }, }) @@ -6797,7 +6783,7 @@ func addExtensionTests() { flags: []string{ "-token-binding-params", base64.StdEncoding.EncodeToString([]byte{0, 1, 2}), - "-expected-token-binding-param", + "-expect-token-binding-param", "2", }, shouldFail: true, @@ -6817,7 +6803,7 @@ func addExtensionTests() { flags: []string{ "-token-binding-params", base64.StdEncoding.EncodeToString([]byte{0, 1, 2}), - "-expected-token-binding-param", + "-expect-token-binding-param", "2", }, shouldFail: true, @@ -6837,7 +6823,7 @@ func addExtensionTests() { flags: []string{ "-token-binding-params", base64.StdEncoding.EncodeToString([]byte{0, 1, 2}), - "-expected-token-binding-param", + "-expect-token-binding-param", "2", }, shouldFail: true, @@ -6873,7 +6859,7 @@ func addExtensionTests() { flags: []string{ "-token-binding-params", base64.StdEncoding.EncodeToString([]byte{0, 1, 2}), - "-expected-token-binding-param", + "-expect-token-binding-param", "2", }, }) @@ -7018,6 +7004,8 @@ func addExtensionTests() { "-expect-ticket-supports-early-data", "-token-binding-params", base64.StdEncoding.EncodeToString([]byte{2, 1, 0}), + "-expect-reject-early-data", + "-on-retry-expect-early-data-reason", "token_binding", }, }) } @@ -7036,7 +7024,7 @@ func addExtensionTests() { flags: []string{ "-quic-transport-params", base64.StdEncoding.EncodeToString([]byte{3, 4}), - "-expected-quic-transport-params", + "-expect-quic-transport-params", base64.StdEncoding.EncodeToString([]byte{1, 2}), }, expectedQUICTransportParams: []byte{3, 4}, @@ -7053,7 +7041,7 @@ func addExtensionTests() { flags: []string{ "-quic-transport-params", base64.StdEncoding.EncodeToString([]byte{3, 4}), - "-expected-quic-transport-params", + "-expect-quic-transport-params", base64.StdEncoding.EncodeToString([]byte{1, 2}), }, expectedQUICTransportParams: []byte{3, 4}, @@ -7097,7 +7085,7 @@ func addExtensionTests() { QUICTransportParams: []byte{1, 2}, }, flags: []string{ - "-expected-quic-transport-params", + "-expect-quic-transport-params", base64.StdEncoding.EncodeToString([]byte{1, 2}), }, shouldFail: true, @@ -8712,21 +8700,21 @@ var testSignatureAlgorithms = []struct { id signatureAlgorithm cert testCert }{ - {"RSA-PKCS1-SHA1", signatureRSAPKCS1WithSHA1, testCertRSA}, - {"RSA-PKCS1-SHA256", signatureRSAPKCS1WithSHA256, testCertRSA}, - {"RSA-PKCS1-SHA384", signatureRSAPKCS1WithSHA384, testCertRSA}, - {"RSA-PKCS1-SHA512", signatureRSAPKCS1WithSHA512, testCertRSA}, - {"ECDSA-SHA1", signatureECDSAWithSHA1, testCertECDSAP256}, + {"RSA_PKCS1_SHA1", signatureRSAPKCS1WithSHA1, testCertRSA}, + {"RSA_PKCS1_SHA256", signatureRSAPKCS1WithSHA256, testCertRSA}, + {"RSA_PKCS1_SHA384", signatureRSAPKCS1WithSHA384, testCertRSA}, + {"RSA_PKCS1_SHA512", signatureRSAPKCS1WithSHA512, testCertRSA}, + {"ECDSA_SHA1", signatureECDSAWithSHA1, testCertECDSAP256}, // The “P256” in the following line is not a mistake. In TLS 1.2 the // hash function doesn't have to match the curve and so the same // signature algorithm works with P-224. - {"ECDSA-P224-SHA256", signatureECDSAWithP256AndSHA256, testCertECDSAP224}, - {"ECDSA-P256-SHA256", signatureECDSAWithP256AndSHA256, testCertECDSAP256}, - {"ECDSA-P384-SHA384", signatureECDSAWithP384AndSHA384, testCertECDSAP384}, - {"ECDSA-P521-SHA512", signatureECDSAWithP521AndSHA512, testCertECDSAP521}, - {"RSA-PSS-SHA256", signatureRSAPSSWithSHA256, testCertRSA}, - {"RSA-PSS-SHA384", signatureRSAPSSWithSHA384, testCertRSA}, - {"RSA-PSS-SHA512", signatureRSAPSSWithSHA512, testCertRSA}, + {"ECDSA_P224_SHA256", signatureECDSAWithP256AndSHA256, testCertECDSAP224}, + {"ECDSA_P256_SHA256", signatureECDSAWithP256AndSHA256, testCertECDSAP256}, + {"ECDSA_P384_SHA384", signatureECDSAWithP384AndSHA384, testCertECDSAP384}, + {"ECDSA_P521_SHA512", signatureECDSAWithP521AndSHA512, testCertECDSAP521}, + {"RSA_PSS_SHA256", signatureRSAPSSWithSHA256, testCertRSA}, + {"RSA_PSS_SHA384", signatureRSAPSSWithSHA384, testCertRSA}, + {"RSA_PSS_SHA512", signatureRSAPSSWithSHA512, testCertRSA}, {"Ed25519", signatureEd25519, testCertEd25519}, // Tests for key types prior to TLS 1.2. {"RSA", 0, testCertRSA}, @@ -10129,7 +10117,7 @@ func addExportKeyingMaterialTests() { flags: []string{ "-enable-early-data", "-expect-ticket-supports-early-data", - "-expect-accept-early-data", + "-on-resume-expect-accept-early-data", "-on-resume-export-keying-material", "1024", "-on-resume-export-label", "label", "-on-resume-export-context", "context", @@ -10138,106 +10126,6 @@ func addExportKeyingMaterialTests() { expectedError: ":HANDSHAKE_NOT_COMPLETE:", }) - // Test the early exporter works while the client is - // sending 0-RTT data. This data arrives during the - // server handshake, so we test it with ProtocolBugs. - testCases = append(testCases, testCase{ - name: "ExportEarlyKeyingMaterial-Client-InEarlyData-" + vers.name, - config: Config{ - MaxVersion: vers.version, - MaxEarlyDataSize: 16384, - }, - resumeConfig: &Config{ - MaxVersion: vers.version, - MaxEarlyDataSize: 16384, - Bugs: ProtocolBugs{ - ExpectEarlyKeyingMaterial: 1024, - ExpectEarlyKeyingLabel: "label", - ExpectEarlyKeyingContext: "context", - }, - }, - resumeSession: true, - flags: []string{ - "-enable-early-data", - "-expect-ticket-supports-early-data", - "-expect-accept-early-data", - "-on-resume-export-early-keying-material", "1024", - "-on-resume-export-label", "label", - "-on-resume-export-context", "context", - }, - }) - - // Test the early exporter still works on the client - // after the handshake is confirmed. This arrives after - // the server handshake, so the normal hooks work. - testCases = append(testCases, testCase{ - name: "ExportEarlyKeyingMaterial-Client-EarlyDataAccept-" + vers.name, - config: Config{ - MaxVersion: vers.version, - MaxEarlyDataSize: 16384, - }, - resumeConfig: &Config{ - MaxVersion: vers.version, - MaxEarlyDataSize: 16384, - }, - resumeSession: true, - exportEarlyKeyingMaterial: 1024, - exportLabel: "label", - exportContext: "context", - flags: []string{ - "-enable-early-data", - "-expect-ticket-supports-early-data", - "-expect-accept-early-data", - // Handshake twice on the client to force - // handshake confirmation. - "-handshake-twice", - }, - }) - - // Test the early exporter does not work on the client - // if 0-RTT was not offered. - testCases = append(testCases, testCase{ - name: "NoExportEarlyKeyingMaterial-Client-Initial-" + vers.name, - config: Config{ - MaxVersion: vers.version, - }, - flags: []string{"-export-early-keying-material", "1024"}, - shouldFail: true, - expectedError: ":EARLY_DATA_NOT_IN_USE:", - }) - testCases = append(testCases, testCase{ - name: "NoExportEarlyKeyingMaterial-Client-Resume-" + vers.name, - config: Config{ - MaxVersion: vers.version, - }, - resumeSession: true, - flags: []string{"-on-resume-export-early-keying-material", "1024"}, - shouldFail: true, - expectedError: ":EARLY_DATA_NOT_IN_USE:", - }) - - // Test the early exporter does not work on the client - // after a 0-RTT reject. - testCases = append(testCases, testCase{ - name: "NoExportEarlyKeyingMaterial-Client-EarlyDataReject-" + vers.name, - config: Config{ - MaxVersion: vers.version, - MaxEarlyDataSize: 16384, - Bugs: ProtocolBugs{ - AlwaysRejectEarlyData: true, - }, - }, - resumeSession: true, - flags: []string{ - "-enable-early-data", - "-expect-ticket-supports-early-data", - "-expect-reject-early-data", - "-on-retry-export-early-keying-material", "1024", - }, - shouldFail: true, - expectedError: ":EARLY_DATA_NOT_IN_USE:", - }) - // Test the normal exporter on the server in half-RTT. testCases = append(testCases, testCase{ testType: serverTest, @@ -10256,75 +10144,6 @@ func addExportKeyingMaterialTests() { useExportContext: true, flags: []string{"-enable-early-data"}, }) - - // Test the early exporter works on the server in half-RTT. - testCases = append(testCases, testCase{ - testType: serverTest, - name: "ExportEarlyKeyingMaterial-Server-HalfRTT-" + vers.name, - config: Config{ - MaxVersion: vers.version, - Bugs: ProtocolBugs{ - SendEarlyData: [][]byte{}, - ExpectEarlyDataAccepted: true, - }, - }, - resumeSession: true, - exportEarlyKeyingMaterial: 1024, - exportLabel: "label", - exportContext: "context", - flags: []string{"-enable-early-data"}, - }) - - // Test the early exporter does not work on the server - // if 0-RTT was not offered. - testCases = append(testCases, testCase{ - testType: serverTest, - name: "NoExportEarlyKeyingMaterial-Server-Initial-" + vers.name, - config: Config{ - MaxVersion: vers.version, - }, - flags: []string{"-export-early-keying-material", "1024"}, - shouldFail: true, - expectedError: ":EARLY_DATA_NOT_IN_USE:", - }) - testCases = append(testCases, testCase{ - testType: serverTest, - name: "NoExportEarlyKeyingMaterial-Server-Resume-" + vers.name, - config: Config{ - MaxVersion: vers.version, - }, - resumeSession: true, - flags: []string{"-on-resume-export-early-keying-material", "1024"}, - shouldFail: true, - expectedError: ":EARLY_DATA_NOT_IN_USE:", - }) - } else { - // Test the early exporter fails before TLS 1.3. - testCases = append(testCases, testCase{ - name: "NoExportEarlyKeyingMaterial-Client-" + vers.name, - config: Config{ - MaxVersion: vers.version, - }, - resumeSession: true, - exportEarlyKeyingMaterial: 1024, - exportLabel: "label", - exportContext: "context", - shouldFail: true, - expectedError: ":WRONG_SSL_VERSION:", - }) - testCases = append(testCases, testCase{ - testType: serverTest, - name: "NoExportEarlyKeyingMaterial-Server-" + vers.name, - config: Config{ - MaxVersion: vers.version, - }, - resumeSession: true, - exportEarlyKeyingMaterial: 1024, - exportLabel: "label", - exportContext: "context", - shouldFail: true, - expectedError: ":WRONG_SSL_VERSION:", - }) } } @@ -10580,14 +10399,19 @@ var testCurves = []struct { {"P-521", CurveP521}, {"X25519", CurveX25519}, {"CECPQ2", CurveCECPQ2}, + {"CECPQ2b", CurveCECPQ2b}, } const bogusCurve = 0x1234 +func isPqGroup(r CurveID) bool { + return r == CurveCECPQ2 || r == CurveCECPQ2b +} + func addCurveTests() { for _, curve := range testCurves { for _, ver := range tlsVersions { - if curve.id == CurveCECPQ2 && ver.version < VersionTLS13 { + if isPqGroup(curve.id) && ver.version < VersionTLS13 { continue } @@ -10629,7 +10453,7 @@ func addCurveTests() { expectedCurveID: curve.id, }) - if curve.id != CurveX25519 && curve.id != CurveCECPQ2 { + if curve.id != CurveX25519 && !isPqGroup(curve.id) { testCases = append(testCases, testCase{ name: "CurveTest-Client-Compressed-" + suffix, config: Config{ @@ -11054,6 +10878,21 @@ func addCurveTests() { }, }) + // CECPQ2b should not be offered by a TLS < 1.3 client. + testCases = append(testCases, testCase{ + name: "CECPQ2bNotInTLS12", + config: Config{ + Bugs: ProtocolBugs{ + FailIfCECPQ2Offered: true, + }, + }, + flags: []string{ + "-max-version", strconv.Itoa(VersionTLS12), + "-curves", strconv.Itoa(int(CurveCECPQ2b)), + "-curves", strconv.Itoa(int(CurveX25519)), + }, + }) + // CECPQ2 should not crash a TLS < 1.3 client if the server mistakenly // selects it. testCases = append(testCases, testCase{ @@ -11072,6 +10911,24 @@ func addCurveTests() { expectedError: ":WRONG_CURVE:", }) + // CECPQ2b should not crash a TLS < 1.3 client if the server mistakenly + // selects it. + testCases = append(testCases, testCase{ + name: "CECPQ2bNotAcceptedByTLS12Client", + config: Config{ + Bugs: ProtocolBugs{ + SendCurve: CurveCECPQ2b, + }, + }, + flags: []string{ + "-max-version", strconv.Itoa(VersionTLS12), + "-curves", strconv.Itoa(int(CurveCECPQ2b)), + "-curves", strconv.Itoa(int(CurveX25519)), + }, + shouldFail: true, + expectedError: ":WRONG_CURVE:", + }) + // CECPQ2 should not be offered by default as a client. testCases = append(testCases, testCase{ name: "CECPQ2NotEnabledByDefaultInClients", @@ -11083,6 +10940,17 @@ func addCurveTests() { }, }) + // CECPQ2b should not be offered by default as a client. + testCases = append(testCases, testCase{ + name: "CECPQ2bNotEnabledByDefaultInClients", + config: Config{ + MinVersion: VersionTLS13, + Bugs: ProtocolBugs{ + FailIfCECPQ2Offered: true, + }, + }, + }) + // If CECPQ2 is offered, both X25519 and CECPQ2 should have a key-share. testCases = append(testCases, testCase{ name: "NotJustCECPQ2KeyShare", @@ -11115,6 +10983,38 @@ func addCurveTests() { }, }) + // If CECPQ2b is offered, both X25519 and CECPQ2b should have a key-share. + testCases = append(testCases, testCase{ + name: "NotJustCECPQ2bKeyShare", + config: Config{ + MinVersion: VersionTLS13, + Bugs: ProtocolBugs{ + ExpectedKeyShares: []CurveID{CurveCECPQ2b, CurveX25519}, + }, + }, + flags: []string{ + "-curves", strconv.Itoa(int(CurveCECPQ2b)), + "-curves", strconv.Itoa(int(CurveX25519)), + "-expect-curve-id", strconv.Itoa(int(CurveCECPQ2b)), + }, + }) + + // ... but only if CECPQ2b is listed first. + testCases = append(testCases, testCase{ + name: "CECPQ2bKeyShareNotIncludedSecond", + config: Config{ + MinVersion: VersionTLS13, + Bugs: ProtocolBugs{ + ExpectedKeyShares: []CurveID{CurveX25519}, + }, + }, + flags: []string{ + "-curves", strconv.Itoa(int(CurveX25519)), + "-curves", strconv.Itoa(int(CurveCECPQ2b)), + "-expect-curve-id", strconv.Itoa(int(CurveX25519)), + }, + }) + // If CECPQ2 is the only configured curve, the key share is sent. testCases = append(testCases, testCase{ name: "JustConfiguringCECPQ2Works", @@ -11130,6 +11030,21 @@ func addCurveTests() { }, }) + // If CECPQ2b is the only configured curve, the key share is sent. + testCases = append(testCases, testCase{ + name: "JustConfiguringCECPQ2bWorks", + config: Config{ + MinVersion: VersionTLS13, + Bugs: ProtocolBugs{ + ExpectedKeyShares: []CurveID{CurveCECPQ2b}, + }, + }, + flags: []string{ + "-curves", strconv.Itoa(int(CurveCECPQ2b)), + "-expect-curve-id", strconv.Itoa(int(CurveCECPQ2b)), + }, + }) + // As a server, CECPQ2 is not yet supported by default. testCases = append(testCases, testCase{ testType: serverTest, @@ -11144,6 +11059,21 @@ func addCurveTests() { "-expect-curve-id", strconv.Itoa(int(CurveX25519)), }, }) + + // As a server, CECPQ2b is not yet supported by default. + testCases = append(testCases, testCase{ + testType: serverTest, + name: "CECPQ2bNotEnabledByDefaultForAServer", + config: Config{ + MinVersion: VersionTLS13, + CurvePreferences: []CurveID{CurveCECPQ2b, CurveX25519}, + DefaultCurves: []CurveID{CurveCECPQ2b}, + }, + flags: []string{ + "-server-preference", + "-expect-curve-id", strconv.Itoa(int(CurveX25519)), + }, + }) } func addTLS13RecordTests() { @@ -11322,9 +11252,99 @@ func addSessionTicketTests() { }, }) + // Test that ticket age skew up to 60 seconds in either direction is accepted. + testCases = append(testCases, testCase{ + testType: serverTest, + name: "TLS13-TicketAgeSkew-Forward-60-Accept", + config: Config{ + MaxVersion: VersionTLS13, + Bugs: ProtocolBugs{ + SendTicketAge: 70 * time.Second, + SendEarlyData: [][]byte{{1, 2, 3, 4}}, + ExpectEarlyDataAccepted: true, + ExpectHalfRTTData: [][]byte{{254, 253, 252, 251}}, + }, + }, + resumeSession: true, + flags: []string{ + "-resumption-delay", "10", + "-expect-ticket-age-skew", "60", + // 0-RTT is accepted. + "-enable-early-data", + "-on-resume-expect-accept-early-data", + "-on-resume-expect-early-data-reason", "accept", + }, + }) + testCases = append(testCases, testCase{ + testType: serverTest, + name: "TLS13-TicketAgeSkew-Backward-60-Accept", + config: Config{ + MaxVersion: VersionTLS13, + Bugs: ProtocolBugs{ + SendTicketAge: 10 * time.Second, + SendEarlyData: [][]byte{{1, 2, 3, 4}}, + ExpectEarlyDataAccepted: true, + ExpectHalfRTTData: [][]byte{{254, 253, 252, 251}}, + }, + }, + resumeSession: true, + flags: []string{ + "-resumption-delay", "70", + "-expect-ticket-age-skew", "-60", + // 0-RTT is accepted. + "-enable-early-data", + "-on-resume-expect-accept-early-data", + "-on-resume-expect-early-data-reason", "accept", + }, + }) + + // Test that ticket age skew beyond 60 seconds in either direction is rejected. + testCases = append(testCases, testCase{ + testType: serverTest, + name: "TLS13-TicketAgeSkew-Forward-61-Reject", + config: Config{ + MaxVersion: VersionTLS13, + Bugs: ProtocolBugs{ + SendTicketAge: 71 * time.Second, + SendEarlyData: [][]byte{{1, 2, 3, 4}}, + ExpectEarlyDataAccepted: false, + }, + }, + resumeSession: true, + flags: []string{ + "-resumption-delay", "10", + "-expect-ticket-age-skew", "61", + // 0-RTT is rejected. + "-enable-early-data", + "-expect-reject-early-data", + "-on-resume-expect-early-data-reason", "ticket_age_skew", + }, + }) + testCases = append(testCases, testCase{ + testType: serverTest, + name: "TLS13-TicketAgeSkew-Backward-61-Reject", + config: Config{ + MaxVersion: VersionTLS13, + Bugs: ProtocolBugs{ + SendTicketAge: 10 * time.Second, + SendEarlyData: [][]byte{{1, 2, 3, 4}}, + ExpectEarlyDataAccepted: false, + }, + }, + resumeSession: true, + flags: []string{ + "-resumption-delay", "71", + "-expect-ticket-age-skew", "-61", + // 0-RTT is rejected. + "-enable-early-data", + "-expect-reject-early-data", + "-on-resume-expect-early-data-reason", "ticket_age_skew", + }, + }) + testCases = append(testCases, testCase{ testType: clientTest, - name: "TLS13-SendTicketEarlyDataInfo", + name: "TLS13-SendTicketEarlyDataSupport", config: Config{ MaxVersion: VersionTLS13, MaxEarlyDataSize: 16384, @@ -11335,19 +11355,22 @@ func addSessionTicketTests() { }, }) - // Test that 0-RTT tickets are ignored in clients unless opted in. + // Test that 0-RTT tickets are still recorded as such when early data is disabled overall. testCases = append(testCases, testCase{ testType: clientTest, - name: "TLS13-SendTicketEarlyDataInfo-Disabled", + name: "TLS13-SendTicketEarlyDataSupport-Disabled", config: Config{ MaxVersion: VersionTLS13, MaxEarlyDataSize: 16384, }, + flags: []string{ + "-expect-ticket-supports-early-data", + }, }) testCases = append(testCases, testCase{ testType: clientTest, - name: "TLS13-DuplicateTicketEarlyDataInfo", + name: "TLS13-DuplicateTicketEarlyDataSupport", config: Config{ MaxVersion: VersionTLS13, MaxEarlyDataSize: 16384, @@ -11362,7 +11385,7 @@ func addSessionTicketTests() { testCases = append(testCases, testCase{ testType: serverTest, - name: "TLS13-ExpectTicketEarlyDataInfo", + name: "TLS13-ExpectTicketEarlyDataSupport", config: Config{ MaxVersion: VersionTLS13, Bugs: ProtocolBugs{ @@ -12329,7 +12352,9 @@ func addTLS13HandshakeTests() { flags: []string{ "-enable-early-data", "-expect-ticket-supports-early-data", - "-expect-accept-early-data", + "-on-initial-expect-early-data-reason", "no_session_offered", + "-on-resume-expect-accept-early-data", + "-on-resume-expect-early-data-reason", "accept", "-on-resume-shim-writes-first", }, }) @@ -12354,6 +12379,7 @@ func addTLS13HandshakeTests() { "-expect-ticket-supports-early-data", "-expect-reject-early-data", "-on-resume-shim-writes-first", + "-on-retry-expect-early-data-reason", "peer_declined", }, }) @@ -12373,10 +12399,14 @@ func addTLS13HandshakeTests() { resumeSession: true, flags: []string{ "-enable-early-data", - "-expect-accept-early-data", + "-on-initial-expect-early-data-reason", "no_session_offered", + "-on-resume-expect-accept-early-data", + "-on-resume-expect-early-data-reason", "accept", }, }) + // The above tests the most recent ticket. Additionally test that 0-RTT + // works on the first ticket issued by the server. testCases = append(testCases, testCase{ testType: serverTest, name: "EarlyData-FirstTicket-Server-TLS13", @@ -12394,7 +12424,8 @@ func addTLS13HandshakeTests() { resumeSession: true, flags: []string{ "-enable-early-data", - "-expect-accept-early-data", + "-on-resume-expect-accept-early-data", + "-on-resume-expect-early-data-reason", "accept", }, }) @@ -12463,6 +12494,9 @@ func addTLS13HandshakeTests() { }, DefaultCurves: []CurveID{}, }, + // Though the session is not resumed and we send HelloRetryRequest, + // early data being disabled takes priority as the reject reason. + flags: []string{"-expect-early-data-reason", "disabled"}, }) testCases = append(testCases, testCase{ @@ -12979,6 +13013,8 @@ func addTLS13HandshakeTests() { }, }, }) + + // Test the client handles 0-RTT being rejected by a full handshake. testCases = append(testCases, testCase{ testType: clientTest, name: "EarlyData-RejectTicket-Client-TLS13", @@ -13000,6 +13036,9 @@ func addTLS13HandshakeTests() { "-expect-ticket-supports-early-data", "-expect-reject-early-data", "-on-resume-shim-writes-first", + "-on-retry-expect-early-data-reason", "session_not_resumed", + // Test the peer certificate is reported correctly in each of the + // three logical connections. "-on-initial-expect-peer-cert-file", path.Join(*resourceDir, rsaCertificateFile), "-on-resume-expect-peer-cert-file", path.Join(*resourceDir, rsaCertificateFile), "-on-retry-expect-peer-cert-file", path.Join(*resourceDir, ecdsaP256CertificateFile), @@ -13008,6 +13047,34 @@ func addTLS13HandshakeTests() { }, }) + // Test the server rejects 0-RTT if it does not recognize the ticket. + testCases = append(testCases, testCase{ + testType: serverTest, + name: "EarlyData-RejectTicket-Server-TLS13", + config: Config{ + MaxVersion: VersionTLS13, + MinVersion: VersionTLS13, + Bugs: ProtocolBugs{ + SendEarlyData: [][]byte{{1, 2, 3, 4}}, + ExpectEarlyDataAccepted: false, + // Corrupt the ticket. + FilterTicket: func(in []byte) ([]byte, error) { + in[len(in)-1] ^= 1 + return in, nil + }, + }, + }, + messageCount: 2, + resumeSession: true, + expectResumeRejected: true, + flags: []string{ + "-enable-early-data", + "-on-resume-expect-reject-early-data", + "-on-resume-expect-early-data-reason", "session_not_resumed", + }, + }) + + // Test the client handles 0-RTT being rejected via a HelloRetryRequest. testCases = append(testCases, testCase{ testType: clientTest, name: "EarlyData-HRR-Client-TLS13", @@ -13027,6 +13094,99 @@ func addTLS13HandshakeTests() { "-enable-early-data", "-expect-ticket-supports-early-data", "-expect-reject-early-data", + "-on-retry-expect-early-data-reason", "hello_retry_request", + }, + }) + + // Test the server rejects 0-RTT if it needs to send a HelloRetryRequest. + testCases = append(testCases, testCase{ + testType: serverTest, + name: "EarlyData-HRR-Server-TLS13", + config: Config{ + MaxVersion: VersionTLS13, + MinVersion: VersionTLS13, + // Require a HelloRetryRequest for every curve. + DefaultCurves: []CurveID{}, + Bugs: ProtocolBugs{ + SendEarlyData: [][]byte{{1, 2, 3, 4}}, + ExpectEarlyDataAccepted: false, + }, + }, + messageCount: 2, + resumeSession: true, + flags: []string{ + "-enable-early-data", + "-on-resume-expect-reject-early-data", + "-on-resume-expect-early-data-reason", "hello_retry_request", + }, + }) + + // Test the client handles a 0-RTT reject from both ticket rejection and + // HelloRetryRequest. + testCases = append(testCases, testCase{ + testType: clientTest, + name: "EarlyData-HRR-RejectTicket-Client-TLS13", + config: Config{ + MaxVersion: VersionTLS13, + MaxEarlyDataSize: 16384, + Certificates: []Certificate{rsaCertificate}, + }, + resumeConfig: &Config{ + MaxVersion: VersionTLS13, + MaxEarlyDataSize: 16384, + Certificates: []Certificate{ecdsaP256Certificate}, + SessionTicketsDisabled: true, + Bugs: ProtocolBugs{ + SendHelloRetryRequestCookie: []byte{1, 2, 3, 4}, + }, + }, + resumeSession: true, + expectResumeRejected: true, + flags: []string{ + "-enable-early-data", + "-expect-ticket-supports-early-data", + "-expect-reject-early-data", + // The client sees HelloRetryRequest before the resumption result, + // though neither value is inherently preferable. + "-on-retry-expect-early-data-reason", "hello_retry_request", + // Test the peer certificate is reported correctly in each of the + // three logical connections. + "-on-initial-expect-peer-cert-file", path.Join(*resourceDir, rsaCertificateFile), + "-on-resume-expect-peer-cert-file", path.Join(*resourceDir, rsaCertificateFile), + "-on-retry-expect-peer-cert-file", path.Join(*resourceDir, ecdsaP256CertificateFile), + // Session tickets are disabled, so the runner will not send a ticket. + "-on-retry-expect-no-session", + }, + }) + + // Test the server rejects 0-RTT if it needs to send a HelloRetryRequest. + testCases = append(testCases, testCase{ + testType: serverTest, + name: "EarlyData-HRR-RejectTicket-Server-TLS13", + config: Config{ + MaxVersion: VersionTLS13, + MinVersion: VersionTLS13, + // Require a HelloRetryRequest for every curve. + DefaultCurves: []CurveID{}, + Bugs: ProtocolBugs{ + SendEarlyData: [][]byte{{1, 2, 3, 4}}, + ExpectEarlyDataAccepted: false, + // Corrupt the ticket. + FilterTicket: func(in []byte) ([]byte, error) { + in[len(in)-1] ^= 1 + return in, nil + }, + }, + }, + messageCount: 2, + resumeSession: true, + expectResumeRejected: true, + flags: []string{ + "-enable-early-data", + "-on-resume-expect-reject-early-data", + // The server sees the missed resumption before HelloRetryRequest, + // though neither value is inherently preferable. + "-on-resume-expect-early-data-reason", "session_not_resumed", }, }) @@ -13192,6 +13352,10 @@ func addTLS13HandshakeTests() { "-enable-early-data", "-expect-ticket-supports-early-data", "-expect-reject-early-data", + // The client does not learn ALPN was the cause. + "-on-retry-expect-early-data-reason", "peer_declined", + // In the 0-RTT state, we surface the predicted ALPN. After + // processing the reject, we surface the real one. "-on-initial-expect-alpn", "foo", "-on-resume-expect-alpn", "foo", "-on-retry-expect-alpn", "bar", @@ -13218,6 +13382,10 @@ func addTLS13HandshakeTests() { "-enable-early-data", "-expect-ticket-supports-early-data", "-expect-reject-early-data", + // The client does not learn ALPN was the cause. + "-on-retry-expect-early-data-reason", "peer_declined", + // In the 0-RTT state, we surface the predicted ALPN. After + // processing the reject, we surface the real one. "-on-initial-expect-alpn", "", "-on-resume-expect-alpn", "", "-on-retry-expect-alpn", "foo", @@ -13245,6 +13413,10 @@ func addTLS13HandshakeTests() { "-enable-early-data", "-expect-ticket-supports-early-data", "-expect-reject-early-data", + // The client does not learn ALPN was the cause. + "-on-retry-expect-early-data-reason", "peer_declined", + // In the 0-RTT state, we surface the predicted ALPN. After + // processing the reject, we surface the real one. "-on-initial-expect-alpn", "foo", "-on-resume-expect-alpn", "foo", "-on-retry-expect-alpn", "", @@ -13299,10 +13471,31 @@ func addTLS13HandshakeTests() { "-enable-early-data", "-expect-ticket-supports-early-data", "-expect-no-offer-early-data", + // Offer different ALPN values in the initial and resumption. "-on-initial-advertise-alpn", "\x03foo", - "-on-resume-advertise-alpn", "\x03bar", "-on-initial-expect-alpn", "foo", + "-on-resume-advertise-alpn", "\x03bar", "-on-resume-expect-alpn", "bar", + // The ALPN mismatch comes from the client, so it reports it as the + // reason. + "-on-resume-expect-early-data-reason", "alpn_mismatch", + }, + }) + + // Test that the client does not offer 0-RTT to servers which never + // advertise it. + testCases = append(testCases, testCase{ + testType: clientTest, + name: "EarlyData-NonZeroRTTSession-Client-TLS13", + config: Config{ + MaxVersion: VersionTLS13, + }, + resumeSession: true, + flags: []string{ + "-enable-early-data", + "-on-resume-expect-no-offer-early-data", + // The client declines to offer 0-RTT because of the session. + "-on-resume-expect-early-data-reason", "unsupported_for_session", }, }) @@ -13325,6 +13518,8 @@ func addTLS13HandshakeTests() { flags: []string{ "-on-resume-enable-early-data", "-expect-reject-early-data", + // The server rejects 0-RTT because of the session. + "-on-resume-expect-early-data-reason", "unsupported_for_session", }, }) @@ -13350,6 +13545,7 @@ func addTLS13HandshakeTests() { "-enable-early-data", "-on-initial-select-alpn", "", "-on-resume-select-alpn", "foo", + "-on-resume-expect-early-data-reason", "alpn_mismatch", }, }) @@ -13375,6 +13571,7 @@ func addTLS13HandshakeTests() { "-enable-early-data", "-on-initial-select-alpn", "foo", "-on-resume-select-alpn", "", + "-on-resume-expect-early-data-reason", "alpn_mismatch", }, }) @@ -13399,6 +13596,7 @@ func addTLS13HandshakeTests() { "-enable-early-data", "-on-initial-select-alpn", "foo", "-on-resume-select-alpn", "bar", + "-on-resume-expect-early-data-reason", "alpn_mismatch", }, }) @@ -13443,6 +13641,8 @@ func addTLS13HandshakeTests() { "-expect-ticket-supports-early-data", "-send-channel-id", path.Join(*resourceDir, channelIDKeyFile), "-expect-reject-early-data", + // The client never learns the reason was Channel ID. + "-on-retry-expect-early-data-reason", "peer_declined", }, }) @@ -13460,7 +13660,8 @@ func addTLS13HandshakeTests() { "-enable-early-data", "-expect-ticket-supports-early-data", "-send-channel-id", path.Join(*resourceDir, channelIDKeyFile), - "-expect-accept-early-data", + "-on-resume-expect-accept-early-data", + "-on-resume-expect-early-data-reason", "accept", }, }) @@ -13484,6 +13685,7 @@ func addTLS13HandshakeTests() { "-expect-reject-early-data", "-expect-channel-id", base64.StdEncoding.EncodeToString(channelIDBytes), + "-on-resume-expect-early-data-reason", "channel_id", }, }) @@ -13504,8 +13706,9 @@ func addTLS13HandshakeTests() { expectChannelID: false, flags: []string{ "-enable-early-data", - "-expect-accept-early-data", + "-on-resume-expect-accept-early-data", "-enable-channel-id", + "-on-resume-expect-early-data-reason", "accept", }, }) @@ -13565,7 +13768,7 @@ func addTLS13HandshakeTests() { flags: []string{ "-enable-early-data", "-expect-ticket-supports-early-data", - "-expect-accept-early-data", + "-on-resume-expect-accept-early-data", "-expect-version", strconv.Itoa(VersionTLS13), }, }) @@ -13590,7 +13793,7 @@ func addTLS13HandshakeTests() { flags: []string{ "-enable-early-data", "-expect-ticket-supports-early-data", - "-expect-accept-early-data", + "-on-resume-expect-accept-early-data", }, shouldFail: true, expectedError: ":DIGEST_CHECK_FAILED:", @@ -13614,7 +13817,7 @@ func addTLS13HandshakeTests() { resumeSession: true, flags: []string{ "-enable-early-data", - "-expect-accept-early-data", + "-on-resume-expect-accept-early-data", }, shouldFail: true, expectedError: ":DIGEST_CHECK_FAILED:", @@ -13639,7 +13842,7 @@ func addTLS13HandshakeTests() { flags: []string{ "-enable-early-data", "-expect-ticket-supports-early-data", - "-expect-accept-early-data", + "-on-resume-expect-accept-early-data", }, shouldFail: true, expectedError: ":DECODE_ERROR:", @@ -13687,6 +13890,45 @@ func addTLS13HandshakeTests() { expectedLocalError: "remote error: unexpected message", }) + // If the client or server has 0-RTT enabled but disabled TLS 1.3, it should + // report a reason of protocol_version. + testCases = append(testCases, testCase{ + testType: clientTest, + name: "EarlyDataEnabled-Client-MaxTLS12", + expectedVersion: VersionTLS12, + flags: []string{ + "-enable-early-data", + "-max-version", strconv.Itoa(VersionTLS12), + "-expect-early-data-reason", "protocol_version", + }, + }) + testCases = append(testCases, testCase{ + testType: serverTest, + name: "EarlyDataEnabled-Server-MaxTLS12", + expectedVersion: VersionTLS12, + flags: []string{ + "-enable-early-data", + "-max-version", strconv.Itoa(VersionTLS12), + "-expect-early-data-reason", "protocol_version", + }, + }) + + // The server additionally reports protocol_version if it enabled TLS 1.3, + // but the peer negotiated TLS 1.2. (The corresponding situation does not + // exist on the client because negotiating TLS 1.2 with a 0-RTT ClientHello + // is a fatal error.) + testCases = append(testCases, testCase{ + testType: serverTest, + name: "EarlyDataEnabled-Server-NegotiateTLS12", + config: Config{ + MaxVersion: VersionTLS12, + }, + expectedVersion: VersionTLS12, + flags: []string{ + "-enable-early-data", + "-expect-early-data-reason", "protocol_version", + }, + }) } func addTLS13CipherPreferenceTests() { @@ -13756,6 +13998,22 @@ func addTLS13CipherPreferenceTests() { "-curves", strconv.Itoa(int(CurveCECPQ2)), }, }) + + // CECPQ2b prefers 256-bit ciphers but will use AES-128 if there's nothing else. + testCases = append(testCases, testCase{ + testType: serverTest, + name: "TLS13-CipherPreference-CECPQ2b-AES128Only", + config: Config{ + MaxVersion: VersionTLS13, + CipherSuites: []uint16{ + TLS_AES_128_GCM_SHA256, + }, + }, + flags: []string{ + "-curves", strconv.Itoa(int(CurveCECPQ2b)), + }, + }) + // When a 256-bit cipher is offered, even if not in first place, it should be // picked. testCases = append(testCases, testCase{ @@ -13790,6 +14048,40 @@ func addTLS13CipherPreferenceTests() { expectedCipher: TLS_AES_128_GCM_SHA256, }) + // When a 256-bit cipher is offered, even if not in first place, it should be + // picked. + testCases = append(testCases, testCase{ + testType: serverTest, + name: "TLS13-CipherPreference-CECPQ2b-AES256Preferred", + config: Config{ + MaxVersion: VersionTLS13, + CipherSuites: []uint16{ + TLS_AES_128_GCM_SHA256, + TLS_AES_256_GCM_SHA384, + }, + }, + flags: []string{ + "-curves", strconv.Itoa(int(CurveCECPQ2b)), + }, + expectedCipher: TLS_AES_256_GCM_SHA384, + }) + // ... but when CECPQ2b isn't being used, the client's preference controls. + testCases = append(testCases, testCase{ + testType: serverTest, + name: "TLS13-CipherPreference-CECPQ2b-AES128PreferredOtherwise", + config: Config{ + MaxVersion: VersionTLS13, + CipherSuites: []uint16{ + TLS_AES_128_GCM_SHA256, + TLS_AES_256_GCM_SHA384, + }, + }, + flags: []string{ + "-curves", strconv.Itoa(int(CurveX25519)), + }, + expectedCipher: TLS_AES_128_GCM_SHA256, + }) + // Test that CECPQ2 continues to honor AES vs ChaCha20 logic. testCases = append(testCases, testCase{ testType: serverTest, @@ -13825,6 +14117,42 @@ func addTLS13CipherPreferenceTests() { "-expect-cipher-no-aes", strconv.Itoa(int(TLS_CHACHA20_POLY1305_SHA256)), }, }) + + // Test that CECPQ2b continues to honor AES vs ChaCha20 logic. + testCases = append(testCases, testCase{ + testType: serverTest, + name: "TLS13-CipherPreference-CECPQ2b-AES128-ChaCha20-AES256", + config: Config{ + MaxVersion: VersionTLS13, + CipherSuites: []uint16{ + TLS_AES_128_GCM_SHA256, + TLS_CHACHA20_POLY1305_SHA256, + TLS_AES_256_GCM_SHA384, + }, + }, + flags: []string{ + "-curves", strconv.Itoa(int(CurveCECPQ2b)), + "-expect-cipher-aes", strconv.Itoa(int(TLS_CHACHA20_POLY1305_SHA256)), + "-expect-cipher-no-aes", strconv.Itoa(int(TLS_CHACHA20_POLY1305_SHA256)), + }, + }) + testCases = append(testCases, testCase{ + testType: serverTest, + name: "TLS13-CipherPreference-CECPQ2b-AES128-AES256-ChaCha20", + config: Config{ + MaxVersion: VersionTLS13, + CipherSuites: []uint16{ + TLS_AES_128_GCM_SHA256, + TLS_AES_256_GCM_SHA384, + TLS_CHACHA20_POLY1305_SHA256, + }, + }, + flags: []string{ + "-curves", strconv.Itoa(int(CurveCECPQ2b)), + "-expect-cipher-aes", strconv.Itoa(int(TLS_AES_256_GCM_SHA384)), + "-expect-cipher-no-aes", strconv.Itoa(int(TLS_CHACHA20_POLY1305_SHA256)), + }, + }) } func addPeekTests() { @@ -14390,7 +14718,7 @@ func addExtraHandshakeTests() { flags: []string{ "-async", "-enable-early-data", - "-expect-accept-early-data", + "-on-resume-expect-accept-early-data", "-no-op-extra-handshake", }, }) @@ -14963,6 +15291,7 @@ func addDelegatedCredentialTests() { }, flags: []string{ "-delegated-credential", ecdsaFlagValue, + "-expect-delegated-credential-used", }, }) @@ -15013,6 +15342,67 @@ func addDelegatedCredentialTests() { }) } +func addPQExperimentSignalTests() { + testCases = append(testCases, testCase{ + testType: serverTest, + name: "PQExperimentSignal-Server-NoEchoIfNotConfigured", + config: Config{ + MinVersion: VersionTLS13, + MaxVersion: VersionTLS13, + Bugs: ProtocolBugs{ + ExpectPQExperimentSignal: false, + }, + PQExperimentSignal: true, + }, + }) + + testCases = append(testCases, testCase{ + testType: serverTest, + name: "PQExperimentSignal-Server-Echo", + config: Config{ + MinVersion: VersionTLS13, + MaxVersion: VersionTLS13, + Bugs: ProtocolBugs{ + ExpectPQExperimentSignal: true, + }, + PQExperimentSignal: true, + }, + flags: []string{ + "-enable-pq-experiment-signal", + "-expect-pq-experiment-signal", + }, + }) + + testCases = append(testCases, testCase{ + testType: clientTest, + name: "PQExperimentSignal-Client-NotDefault", + config: Config{ + MinVersion: VersionTLS13, + MaxVersion: VersionTLS13, + Bugs: ProtocolBugs{ + ExpectPQExperimentSignal: false, + }, + PQExperimentSignal: true, + }, + }) + + testCases = append(testCases, testCase{ + testType: clientTest, + name: "PQExperimentSignal-Client", + config: Config{ + MinVersion: VersionTLS13, + MaxVersion: VersionTLS13, + Bugs: ProtocolBugs{ + ExpectPQExperimentSignal: true, + }, + }, + flags: []string{ + "-enable-pq-experiment-signal", + "-expect-pq-experiment-signal", + }, + }) +} + func worker(statusChan chan statusMsg, c chan *testCase, shimPath string, wg *sync.WaitGroup) { defer wg.Done() @@ -15150,6 +15540,7 @@ func main() { addCertCompressionTests() addJDK11WorkaroundTests() addDelegatedCredentialTests() + addPQExperimentSignalTests() testCases = append(testCases, convertToSplitHandshakeTests(testCases)...) diff --git a/src/ssl/test/runner/sike/arith.go b/src/ssl/test/runner/sike/arith.go new file mode 100644 index 00000000..10a2ca63 --- /dev/null +++ b/src/ssl/test/runner/sike/arith.go @@ -0,0 +1,374 @@ +// Copyright (c) 2019, Cloudflare Inc. +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY +// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION +// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN +// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +package sike + +import ( + "math/bits" +) + +// Compute z = x + y (mod 2*p). +func fpAddRdc(z, x, y *Fp) { + var carry uint64 + + // z=x+y % p + for i := 0; i < FP_WORDS; i++ { + z[i], carry = bits.Add64(x[i], y[i], carry) + } + + // z = z - pX2 + carry = 0 + for i := 0; i < FP_WORDS; i++ { + z[i], carry = bits.Sub64(z[i], pX2[i], carry) + } + + // if z<0 add pX2 back + mask := uint64(0 - carry) + carry = 0 + for i := 0; i < FP_WORDS; i++ { + z[i], carry = bits.Add64(z[i], pX2[i]&mask, carry) + } +} + +// Compute z = x - y (mod 2*p). +func fpSubRdc(z, x, y *Fp) { + var borrow uint64 + + // z = z - pX2 + for i := 0; i < FP_WORDS; i++ { + z[i], borrow = bits.Sub64(x[i], y[i], borrow) + } + + // if z<0 add pX2 back + mask := uint64(0 - borrow) + borrow = 0 + for i := 0; i < FP_WORDS; i++ { + z[i], borrow = bits.Add64(z[i], pX2[i]&mask, borrow) + } +} + +// Reduce a field element in [0, 2*p) to one in [0,p). +func fpRdcP(x *Fp) { + var borrow, mask uint64 + for i := 0; i < FP_WORDS; i++ { + x[i], borrow = bits.Sub64(x[i], p[i], borrow) + } + + // Sets all bits if borrow = 1 + mask = 0 - borrow + borrow = 0 + for i := 0; i < FP_WORDS; i++ { + x[i], borrow = bits.Add64(x[i], p[i]&mask, borrow) + } +} + +// Implementation doesn't actually depend on a prime field. +func fpSwapCond(x, y *Fp, mask uint8) { + if mask != 0 { + var tmp Fp + copy(tmp[:], y[:]) + copy(y[:], x[:]) + copy(x[:], tmp[:]) + } +} + +// Compute z = x * y. +func fpMul(z *FpX2, x, y *Fp) { + var carry, t, u, v uint64 + var hi, lo uint64 + + for i := uint64(0); i < FP_WORDS; i++ { + for j := uint64(0); j <= i; j++ { + hi, lo = bits.Mul64(x[j], y[i-j]) + v, carry = bits.Add64(lo, v, 0) + u, carry = bits.Add64(hi, u, carry) + t += carry + } + z[i] = v + v = u + u = t + t = 0 + } + + for i := FP_WORDS; i < (2*FP_WORDS)-1; i++ { + for j := i - FP_WORDS + 1; j < FP_WORDS; j++ { + hi, lo = bits.Mul64(x[j], y[i-j]) + v, carry = bits.Add64(lo, v, 0) + u, carry = bits.Add64(hi, u, carry) + t += carry + } + z[i] = v + v = u + u = t + t = 0 + } + z[2*FP_WORDS-1] = v +} + +// Perform Montgomery reduction: set z = x R^{-1} (mod 2*p) +// with R=2^512. Destroys the input value. +func fpMontRdc(z *Fp, x *FpX2) { + var carry, t, u, v uint64 + var hi, lo uint64 + var count int + + count = 3 // number of 0 digits in the least significat part of p + 1 + + for i := 0; i < FP_WORDS; i++ { + for j := 0; j < i; j++ { + if j < (i - count + 1) { + hi, lo = bits.Mul64(z[j], p1[i-j]) + v, carry = bits.Add64(lo, v, 0) + u, carry = bits.Add64(hi, u, carry) + t += carry + } + } + v, carry = bits.Add64(v, x[i], 0) + u, carry = bits.Add64(u, 0, carry) + t += carry + + z[i] = v + v = u + u = t + t = 0 + } + + for i := FP_WORDS; i < 2*FP_WORDS-1; i++ { + if count > 0 { + count-- + } + for j := i - FP_WORDS + 1; j < FP_WORDS; j++ { + if j < (FP_WORDS - count) { + hi, lo = bits.Mul64(z[j], p1[i-j]) + v, carry = bits.Add64(lo, v, 0) + u, carry = bits.Add64(hi, u, carry) + t += carry + } + } + v, carry = bits.Add64(v, x[i], 0) + u, carry = bits.Add64(u, 0, carry) + + t += carry + z[i-FP_WORDS] = v + v = u + u = t + t = 0 + } + v, carry = bits.Add64(v, x[2*FP_WORDS-1], 0) + z[FP_WORDS-1] = v +} + +// Compute z = x + y, without reducing mod p. +func fp2Add(z, x, y *FpX2) { + var carry uint64 + for i := 0; i < 2*FP_WORDS; i++ { + z[i], carry = bits.Add64(x[i], y[i], carry) + } +} + +// Compute z = x - y, without reducing mod p. +func fp2Sub(z, x, y *FpX2) { + var borrow, mask uint64 + for i := 0; i < 2*FP_WORDS; i++ { + z[i], borrow = bits.Sub64(x[i], y[i], borrow) + } + + // Sets all bits if borrow = 1 + mask = 0 - borrow + borrow = 0 + for i := FP_WORDS; i < 2*FP_WORDS; i++ { + z[i], borrow = bits.Add64(z[i], p[i-FP_WORDS]&mask, borrow) + } +} + +// Montgomery multiplication. Input values must be already +// in Montgomery domain. +func fpMulRdc(dest, lhs, rhs *Fp) { + a := lhs // = a*R + b := rhs // = b*R + + var ab FpX2 + fpMul(&ab, a, b) // = a*b*R*R + fpMontRdc(dest, &ab) // = a*b*R mod p +} + +// Set dest = x^((p-3)/4). If x is square, this is 1/sqrt(x). +// Uses variation of sliding-window algorithm from with window size +// of 5 and least to most significant bit sliding (left-to-right) +// See HAC 14.85 for general description. +// +// Allowed to overlap x with dest. +// All values in Montgomery domains +// Set dest = x^(2^k), for k >= 1, by repeated squarings. +func p34(dest, x *Fp) { + var lookup [16]Fp + + // This performs sum(powStrategy) + 1 squarings and len(lookup) + len(mulStrategy) + // multiplications. + powStrategy := []uint8{ + 0x03, 0x0A, 0x07, 0x05, 0x06, 0x05, 0x03, 0x08, 0x04, 0x07, + 0x05, 0x06, 0x04, 0x05, 0x09, 0x06, 0x03, 0x0B, 0x05, 0x05, + 0x02, 0x08, 0x04, 0x07, 0x07, 0x08, 0x05, 0x06, 0x04, 0x08, + 0x05, 0x02, 0x0A, 0x06, 0x05, 0x04, 0x08, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x01} + mulStrategy := []uint8{ + 0x02, 0x0F, 0x09, 0x08, 0x0E, 0x0C, 0x02, 0x08, 0x05, 0x0F, + 0x08, 0x0F, 0x06, 0x06, 0x03, 0x02, 0x00, 0x0A, 0x09, 0x0D, + 0x01, 0x0C, 0x03, 0x07, 0x01, 0x0A, 0x08, 0x0B, 0x02, 0x0F, + 0x0E, 0x01, 0x0B, 0x0C, 0x0E, 0x03, 0x0B, 0x0F, 0x0F, 0x0F, + 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, + 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, + 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, + 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x00} + initialMul := uint8(8) + + // Precompute lookup table of odd multiples of x for window + // size k=5. + var xx Fp + fpMulRdc(&xx, x, x) + lookup[0] = *x + for i := 1; i < 16; i++ { + fpMulRdc(&lookup[i], &lookup[i-1], &xx) + } + + // Now lookup = {x, x^3, x^5, ... } + // so that lookup[i] = x^{2*i + 1} + // so that lookup[k/2] = x^k, for odd k + *dest = lookup[initialMul] + for i := uint8(0); i < uint8(len(powStrategy)); i++ { + fpMulRdc(dest, dest, dest) + for j := uint8(1); j < powStrategy[i]; j++ { + fpMulRdc(dest, dest, dest) + } + fpMulRdc(dest, dest, &lookup[mulStrategy[i]]) + } +} + +func add(dest, lhs, rhs *Fp2) { + fpAddRdc(&dest.A, &lhs.A, &rhs.A) + fpAddRdc(&dest.B, &lhs.B, &rhs.B) +} + +func sub(dest, lhs, rhs *Fp2) { + fpSubRdc(&dest.A, &lhs.A, &rhs.A) + fpSubRdc(&dest.B, &lhs.B, &rhs.B) +} + +func mul(dest, lhs, rhs *Fp2) { + // Let (a,b,c,d) = (lhs.a,lhs.b,rhs.a,rhs.b). + a := &lhs.A + b := &lhs.B + c := &rhs.A + d := &rhs.B + + // We want to compute + // + // (a + bi)*(c + di) = (a*c - b*d) + (a*d + b*c)i + // + // Use Karatsuba's trick: note that + // + // (b - a)*(c - d) = (b*c + a*d) - a*c - b*d + // + // so (a*d + b*c) = (b-a)*(c-d) + a*c + b*d. + + var ac, bd FpX2 + fpMul(&ac, a, c) // = a*c*R*R + fpMul(&bd, b, d) // = b*d*R*R + + var b_minus_a, c_minus_d Fp + fpSubRdc(&b_minus_a, b, a) // = (b-a)*R + fpSubRdc(&c_minus_d, c, d) // = (c-d)*R + + var ad_plus_bc FpX2 + fpMul(&ad_plus_bc, &b_minus_a, &c_minus_d) // = (b-a)*(c-d)*R*R + fp2Add(&ad_plus_bc, &ad_plus_bc, &ac) // = ((b-a)*(c-d) + a*c)*R*R + fp2Add(&ad_plus_bc, &ad_plus_bc, &bd) // = ((b-a)*(c-d) + a*c + b*d)*R*R + + fpMontRdc(&dest.B, &ad_plus_bc) // = (a*d + b*c)*R mod p + + var ac_minus_bd FpX2 + fp2Sub(&ac_minus_bd, &ac, &bd) // = (a*c - b*d)*R*R + fpMontRdc(&dest.A, &ac_minus_bd) // = (a*c - b*d)*R mod p +} + +func inv(dest, x *Fp2) { + var a2PlusB2 Fp + var asq, bsq FpX2 + var ac FpX2 + var minusB Fp + var minusBC FpX2 + + a := &x.A + b := &x.B + + // We want to compute + // + // 1 1 (a - bi) (a - bi) + // -------- = -------- -------- = ----------- + // (a + bi) (a + bi) (a - bi) (a^2 + b^2) + // + // Letting c = 1/(a^2 + b^2), this is + // + // 1/(a+bi) = a*c - b*ci. + + fpMul(&asq, a, a) // = a*a*R*R + fpMul(&bsq, b, b) // = b*b*R*R + fp2Add(&asq, &asq, &bsq) // = (a^2 + b^2)*R*R + fpMontRdc(&a2PlusB2, &asq) // = (a^2 + b^2)*R mod p + // Now a2PlusB2 = a^2 + b^2 + + inv := a2PlusB2 + fpMulRdc(&inv, &a2PlusB2, &a2PlusB2) + p34(&inv, &inv) + fpMulRdc(&inv, &inv, &inv) + fpMulRdc(&inv, &inv, &a2PlusB2) + + fpMul(&ac, a, &inv) + fpMontRdc(&dest.A, &ac) + + fpSubRdc(&minusB, &minusB, b) + fpMul(&minusBC, &minusB, &inv) + fpMontRdc(&dest.B, &minusBC) +} + +func sqr(dest, x *Fp2) { + var a2, aPlusB, aMinusB Fp + var a2MinB2, ab2 FpX2 + + a := &x.A + b := &x.B + + // (a + bi)*(a + bi) = (a^2 - b^2) + 2abi. + fpAddRdc(&a2, a, a) // = a*R + a*R = 2*a*R + fpAddRdc(&aPlusB, a, b) // = a*R + b*R = (a+b)*R + fpSubRdc(&aMinusB, a, b) // = a*R - b*R = (a-b)*R + fpMul(&a2MinB2, &aPlusB, &aMinusB) // = (a+b)*(a-b)*R*R = (a^2 - b^2)*R*R + fpMul(&ab2, &a2, b) // = 2*a*b*R*R + fpMontRdc(&dest.A, &a2MinB2) // = (a^2 - b^2)*R mod p + fpMontRdc(&dest.B, &ab2) // = 2*a*b*R mod p +} + +// In case choice == 1, performs following swap in constant time: +// xPx <-> xQx +// xPz <-> xQz +// Otherwise returns xPx, xPz, xQx, xQz unchanged +func condSwap(xPx, xPz, xQx, xQz *Fp2, choice uint8) { + fpSwapCond(&xPx.A, &xQx.A, choice) + fpSwapCond(&xPx.B, &xQx.B, choice) + fpSwapCond(&xPz.A, &xQz.A, choice) + fpSwapCond(&xPz.B, &xQz.B, choice) +} diff --git a/src/ssl/test/runner/sike/consts.go b/src/ssl/test/runner/sike/consts.go new file mode 100644 index 00000000..9d68a4fd --- /dev/null +++ b/src/ssl/test/runner/sike/consts.go @@ -0,0 +1,317 @@ +// Copyright (c) 2019, Cloudflare Inc. +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY +// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION +// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN +// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +package sike + +// I keep it bool in order to be able to apply logical NOT +type KeyVariant uint + +// Representation of an element of the base field F_p. +// +// No particular meaning is assigned to the representation -- it could represent +// an element in Montgomery form, or not. Tracking the meaning of the field +// element is left to higher types. +type Fp [FP_WORDS]uint64 + +// Represents an intermediate product of two elements of the base field F_p. +type FpX2 [2 * FP_WORDS]uint64 + +// Represents an element of the extended field Fp^2 = Fp(x+i) +type Fp2 struct { + A Fp + B Fp +} + +type DomainParams struct { + // P, Q and R=P-Q base points + Affine_P, Affine_Q, Affine_R Fp2 + // Size of a compuatation strategy for x-torsion group + IsogenyStrategy []uint32 + // Max size of secret key for x-torsion group + SecretBitLen uint + // Max size of secret key for x-torsion group + SecretByteLen uint +} + +type SidhParams struct { + Id uint8 + // Bytelen of P + Bytelen int + // The public key size, in bytes. + PublicKeySize int + // The shared secret size, in bytes. + SharedSecretSize int + // Defines A,C constant for starting curve Cy^2 = x^3 + Ax^2 + x + InitCurve ProjectiveCurveParameters + // 2- and 3-torsion group parameter definitions + A, B DomainParams + // Precomputed 1/2 in the Fp2 in Montgomery domain + HalfFp2 Fp2 + // Precomputed identity element in the Fp2 in Montgomery domain + OneFp2 Fp2 + // Length of SIKE secret message. Must be one of {24,32,40}, + // depending on size of prime field used (see [SIKE], 1.4 and 5.1) + MsgLen int + // Length of SIKE ephemeral KEM key (see [SIKE], 1.4 and 5.1) + KemSize int + // Size of a ciphertext returned by encapsulation in bytes + CiphertextSize int +} + +// Stores curve projective parameters equivalent to A/C. Meaning of the +// values depends on the context. When working with isogenies over +// subgroup that are powers of: +// * three then (A:C) ~ (A+2C:A-2C) +// * four then (A:C) ~ (A+2C: 4C) +// See Appendix A of SIKE for more details +type CurveCoefficientsEquiv struct { + A Fp2 + C Fp2 +} + +// A point on the projective line P^1(F_{p^2}). +// +// This represents a point on the Kummer line of a Montgomery curve. The +// curve is specified by a ProjectiveCurveParameters struct. +type ProjectivePoint struct { + X Fp2 + Z Fp2 +} + +// Base type for public and private key. Used mainly to carry domain +// parameters. +type key struct { + // Domain parameters of the algorithm to be used with a key + params *SidhParams + // Flag indicates whether corresponds to 2-, 3-torsion group or SIKE + keyVariant KeyVariant +} + +// Defines operations on private key +type PrivateKey struct { + key + // Secret key + Scalar []byte + // Used only by KEM + S []byte +} + +// Defines operations on public key +type PublicKey struct { + key + affine_xP Fp2 + affine_xQ Fp2 + affine_xQmP Fp2 +} + +// A point on the projective line P^1(F_{p^2}). +// +// This is used to work projectively with the curve coefficients. +type ProjectiveCurveParameters struct { + A Fp2 + C Fp2 +} + +const ( + // First 2 bits identify SIDH variant third bit indicates + // whether key is a SIKE variant (set) or SIDH (not set) + + // 001 - SIDH: corresponds to 2-torsion group + KeyVariant_SIDH_A KeyVariant = 1 << 0 + // 010 - SIDH: corresponds to 3-torsion group + KeyVariant_SIDH_B = 1 << 1 + // 110 - SIKE + KeyVariant_SIKE = 1<<2 | KeyVariant_SIDH_B + // Number of uint64 limbs used to store field element + FP_WORDS = 7 +) + +// Used internally by this package +// ------------------------------- + +var ( + p = Fp{ + 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0xFDC1767AE2FFFFFF, + 0x7BC65C783158AEA3, 0x6CFC5FD681C52056, 0x2341F27177344, + } + + // 2*p434 + pX2 = Fp{ + 0xFFFFFFFFFFFFFFFE, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0xFB82ECF5C5FFFFFF, + 0xF78CB8F062B15D47, 0xD9F8BFAD038A40AC, 0x4683E4E2EE688, + } + + // p434 + 1 + p1 = Fp{ + 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0xFDC1767AE3000000, + 0x7BC65C783158AEA3, 0x6CFC5FD681C52056, 0x0002341F27177344, + } + + // R^2=(2^448)^2 mod p + R2 = Fp{ + 0x28E55B65DCD69B30, 0xACEC7367768798C2, 0xAB27973F8311688D, 0x175CC6AF8D6C7C0B, + 0xABCD92BF2DDE347E, 0x69E16A61C7686D9A, 0x000025A89BCDD12A, + } + + // 1/2 * R mod p + half = Fp2{ + A: Fp{ + 0x0000000000003A16, 0x0000000000000000, 0x0000000000000000, 0x5C87FA027E000000, + 0x6C00D27DAACFD66A, 0x74992A2A2FBBA086, 0x0000767753DE976D}, + } + + // 1*R mod p + one = Fp2{ + A: Fp{ + 0x000000000000742C, 0x0000000000000000, 0x0000000000000000, 0xB90FF404FC000000, + 0xD801A4FB559FACD4, 0xE93254545F77410C, 0x0000ECEEA7BD2EDA}, + } + + // 6*R mod p + six = Fp2{ + A: Fp{ + 0x000000000002B90A, 0x0000000000000000, 0x0000000000000000, 0x5ADCCB2822000000, + 0x187D24F39F0CAFB4, 0x9D353A4D394145A0, 0x00012559A0403298}, + } + + Params SidhParams +) + +func init() { + Params = SidhParams{ + // SIDH public key byte size. + PublicKeySize: 330, + // SIDH shared secret byte size. + SharedSecretSize: 110, + InitCurve: ProjectiveCurveParameters{ + A: six, + C: one, + }, + A: DomainParams{ + // The x-coordinate of PA + Affine_P: Fp2{ + A: Fp{ + 0x05ADF455C5C345BF, 0x91935C5CC767AC2B, 0xAFE4E879951F0257, 0x70E792DC89FA27B1, + 0xF797F526BB48C8CD, 0x2181DB6131AF621F, 0x00000A1C08B1ECC4, + }, + B: Fp{ + 0x74840EB87CDA7788, 0x2971AA0ECF9F9D0B, 0xCB5732BDF41715D5, 0x8CD8E51F7AACFFAA, + 0xA7F424730D7E419F, 0xD671EB919A179E8C, 0x0000FFA26C5A924A, + }, + }, + // The x-coordinate of QA + Affine_Q: Fp2{ + A: Fp{ + 0xFEC6E64588B7273B, 0xD2A626D74CBBF1C6, 0xF8F58F07A78098C7, 0xE23941F470841B03, + 0x1B63EDA2045538DD, 0x735CFEB0FFD49215, 0x0001C4CB77542876, + }, + B: Fp{ + 0xADB0F733C17FFDD6, 0x6AFFBD037DA0A050, 0x680EC43DB144E02F, 0x1E2E5D5FF524E374, + 0xE2DDA115260E2995, 0xA6E4B552E2EDE508, 0x00018ECCDDF4B53E, + }, + }, + // The x-coordinate of RA = PA-QA + Affine_R: Fp2{ + A: Fp{ + 0x01BA4DB518CD6C7D, 0x2CB0251FE3CC0611, 0x259B0C6949A9121B, 0x60E17AC16D2F82AD, + 0x3AA41F1CE175D92D, 0x413FBE6A9B9BC4F3, 0x00022A81D8D55643, + }, + B: Fp{ + 0xB8ADBC70FC82E54A, 0xEF9CDDB0D5FADDED, 0x5820C734C80096A0, 0x7799994BAA96E0E4, + 0x044961599E379AF8, 0xDB2B94FBF09F27E2, 0x0000B87FC716C0C6, + }, + }, + // Max size of secret key for 2-torsion group, corresponds to 2^e2 - 1 + SecretBitLen: 216, + // SecretBitLen in bytes. + SecretByteLen: 27, + // 2-torsion group computation strategy + IsogenyStrategy: []uint32{ + 0x30, 0x1C, 0x10, 0x08, 0x04, 0x02, 0x01, 0x01, 0x02, 0x01, + 0x01, 0x04, 0x02, 0x01, 0x01, 0x02, 0x01, 0x01, 0x08, 0x04, + 0x02, 0x01, 0x01, 0x02, 0x01, 0x01, 0x04, 0x02, 0x01, 0x01, + 0x02, 0x01, 0x01, 0x0D, 0x07, 0x04, 0x02, 0x01, 0x01, 0x02, + 0x01, 0x01, 0x03, 0x02, 0x01, 0x01, 0x01, 0x01, 0x05, 0x04, + 0x02, 0x01, 0x01, 0x02, 0x01, 0x01, 0x02, 0x01, 0x01, 0x01, + 0x15, 0x0C, 0x07, 0x04, 0x02, 0x01, 0x01, 0x02, 0x01, 0x01, + 0x03, 0x02, 0x01, 0x01, 0x01, 0x01, 0x05, 0x03, 0x02, 0x01, + 0x01, 0x01, 0x01, 0x02, 0x01, 0x01, 0x01, 0x09, 0x05, 0x03, + 0x02, 0x01, 0x01, 0x01, 0x01, 0x02, 0x01, 0x01, 0x01, 0x04, + 0x02, 0x01, 0x01, 0x01, 0x02, 0x01, 0x01}, + }, + B: DomainParams{ + // The x-coordinate of PB + Affine_P: Fp2{ + A: Fp{ + 0x6E5497556EDD48A3, 0x2A61B501546F1C05, 0xEB919446D049887D, 0x5864A4A69D450C4F, + 0xB883F276A6490D2B, 0x22CC287022D5F5B9, 0x0001BED4772E551F, + }, + B: Fp{ + 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, + 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, + }, + }, + // The x-coordinate of QB + Affine_Q: Fp2{ + A: Fp{ + 0xFAE2A3F93D8B6B8E, 0x494871F51700FE1C, 0xEF1A94228413C27C, 0x498FF4A4AF60BD62, + 0xB00AD2A708267E8A, 0xF4328294E017837F, 0x000034080181D8AE, + }, + B: Fp{ + 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, + 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, + }, + }, + // The x-coordinate of RB = PB - QB + Affine_R: Fp2{ + A: Fp{ + 0x283B34FAFEFDC8E4, 0x9208F44977C3E647, 0x7DEAE962816F4E9A, 0x68A2BA8AA262EC9D, + 0x8176F112EA43F45B, 0x02106D022634F504, 0x00007E8A50F02E37, + }, + B: Fp{ + 0xB378B7C1DA22CCB1, 0x6D089C99AD1D9230, 0xEBE15711813E2369, 0x2B35A68239D48A53, + 0x445F6FD138407C93, 0xBEF93B29A3F6B54B, 0x000173FA910377D3, + }, + }, + // Size of secret key for 3-torsion group, corresponds to log_2(3^e3) - 1. + SecretBitLen: 217, + // SecretBitLen in bytes. + SecretByteLen: 28, + // 3-torsion group computation strategy + IsogenyStrategy: []uint32{ + 0x42, 0x21, 0x11, 0x09, 0x05, 0x03, 0x02, 0x01, 0x01, 0x01, + 0x01, 0x02, 0x01, 0x01, 0x01, 0x04, 0x02, 0x01, 0x01, 0x01, + 0x02, 0x01, 0x01, 0x08, 0x04, 0x02, 0x01, 0x01, 0x01, 0x02, + 0x01, 0x01, 0x04, 0x02, 0x01, 0x01, 0x02, 0x01, 0x01, 0x10, + 0x08, 0x04, 0x02, 0x01, 0x01, 0x01, 0x02, 0x01, 0x01, 0x04, + 0x02, 0x01, 0x01, 0x02, 0x01, 0x01, 0x08, 0x04, 0x02, 0x01, + 0x01, 0x02, 0x01, 0x01, 0x04, 0x02, 0x01, 0x01, 0x02, 0x01, + 0x01, 0x20, 0x10, 0x08, 0x04, 0x03, 0x01, 0x01, 0x01, 0x01, + 0x02, 0x01, 0x01, 0x04, 0x02, 0x01, 0x01, 0x02, 0x01, 0x01, + 0x08, 0x04, 0x02, 0x01, 0x01, 0x02, 0x01, 0x01, 0x04, 0x02, + 0x01, 0x01, 0x02, 0x01, 0x01, 0x10, 0x08, 0x04, 0x02, 0x01, + 0x01, 0x02, 0x01, 0x01, 0x04, 0x02, 0x01, 0x01, 0x02, 0x01, + 0x01, 0x08, 0x04, 0x02, 0x01, 0x01, 0x02, 0x01, 0x01, 0x04, + 0x02, 0x01, 0x01, 0x02, 0x01, 0x01}, + }, + OneFp2: one, + HalfFp2: half, + MsgLen: 16, + // SIKEp434 provides 128 bit of classical security ([SIKE], 5.1) + KemSize: 16, + // ceil(434+7/8) + Bytelen: 55, + CiphertextSize: 16 + 330, + } +} diff --git a/src/ssl/test/runner/sike/curve.go b/src/ssl/test/runner/sike/curve.go new file mode 100644 index 00000000..81725462 --- /dev/null +++ b/src/ssl/test/runner/sike/curve.go @@ -0,0 +1,422 @@ +// Copyright (c) 2019, Cloudflare Inc. +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY +// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION +// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN +// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +package sike + +// Interface for working with isogenies. +type isogeny interface { + // Given a torsion point on a curve computes isogenous curve. + // Returns curve coefficients (A:C), so that E_(A/C) = E_(A/C)/<P>, + // where P is a provided projective point. Sets also isogeny constants + // that are needed for isogeny evaluation. + GenerateCurve(*ProjectivePoint) CurveCoefficientsEquiv + // Evaluates isogeny at caller provided point. Requires isogeny curve constants + // to be earlier computed by GenerateCurve. + EvaluatePoint(*ProjectivePoint) ProjectivePoint +} + +// Stores isogeny 3 curve constants +type isogeny3 struct { + K1 Fp2 + K2 Fp2 +} + +// Stores isogeny 4 curve constants +type isogeny4 struct { + isogeny3 + K3 Fp2 +} + +// Constructs isogeny3 objects +func NewIsogeny3() isogeny { + return &isogeny3{} +} + +// Constructs isogeny4 objects +func NewIsogeny4() isogeny { + return &isogeny4{} +} + +// Helper function for RightToLeftLadder(). Returns A+2C / 4. +func calcAplus2Over4(cparams *ProjectiveCurveParameters) (ret Fp2) { + var tmp Fp2 + + // 2C + add(&tmp, &cparams.C, &cparams.C) + // A+2C + add(&ret, &cparams.A, &tmp) + // 1/4C + add(&tmp, &tmp, &tmp) + inv(&tmp, &tmp) + // A+2C/4C + mul(&ret, &ret, &tmp) + return +} + +// Converts values in x.A and x.B to Montgomery domain +// x.A = x.A * R mod p +// x.B = x.B * R mod p +// Performs v = v*R^2*R^(-1) mod p, for both x.A and x.B +func toMontDomain(x *Fp2) { + var aRR FpX2 + + // convert to montgomery domain + fpMul(&aRR, &x.A, &R2) // = a*R*R + fpMontRdc(&x.A, &aRR) // = a*R mod p + fpMul(&aRR, &x.B, &R2) + fpMontRdc(&x.B, &aRR) +} + +// Converts values in x.A and x.B from Montgomery domain +// a = x.A mod p +// b = x.B mod p +// +// After returning from the call x is not modified. +func fromMontDomain(x *Fp2, out *Fp2) { + var aR FpX2 + + // convert from montgomery domain + copy(aR[:], x.A[:]) + fpMontRdc(&out.A, &aR) // = a mod p in [0, 2p) + fpRdcP(&out.A) // = a mod p in [0, p) + for i := range aR { + aR[i] = 0 + } + copy(aR[:], x.B[:]) + fpMontRdc(&out.B, &aR) + fpRdcP(&out.B) +} + +// Computes j-invariant for a curve y2=x3+A/Cx+x with A,C in F_(p^2). Result +// is returned in 'j'. Implementation corresponds to Algorithm 9 from SIKE. +func Jinvariant(cparams *ProjectiveCurveParameters, j *Fp2) { + var t0, t1 Fp2 + + sqr(j, &cparams.A) // j = A^2 + sqr(&t1, &cparams.C) // t1 = C^2 + add(&t0, &t1, &t1) // t0 = t1 + t1 + sub(&t0, j, &t0) // t0 = j - t0 + sub(&t0, &t0, &t1) // t0 = t0 - t1 + sub(j, &t0, &t1) // t0 = t0 - t1 + sqr(&t1, &t1) // t1 = t1^2 + mul(j, j, &t1) // j = j * t1 + add(&t0, &t0, &t0) // t0 = t0 + t0 + add(&t0, &t0, &t0) // t0 = t0 + t0 + sqr(&t1, &t0) // t1 = t0^2 + mul(&t0, &t0, &t1) // t0 = t0 * t1 + add(&t0, &t0, &t0) // t0 = t0 + t0 + add(&t0, &t0, &t0) // t0 = t0 + t0 + inv(j, j) // j = 1/j + mul(j, &t0, j) // j = t0 * j +} + +// Given affine points x(P), x(Q) and x(Q-P) in a extension field F_{p^2}, function +// recorvers projective coordinate A of a curve. This is Algorithm 10 from SIKE. +func RecoverCoordinateA(curve *ProjectiveCurveParameters, xp, xq, xr *Fp2) { + var t0, t1 Fp2 + + add(&t1, xp, xq) // t1 = Xp + Xq + mul(&t0, xp, xq) // t0 = Xp * Xq + mul(&curve.A, xr, &t1) // A = X(q-p) * t1 + add(&curve.A, &curve.A, &t0) // A = A + t0 + mul(&t0, &t0, xr) // t0 = t0 * X(q-p) + sub(&curve.A, &curve.A, &Params.OneFp2) // A = A - 1 + add(&t0, &t0, &t0) // t0 = t0 + t0 + add(&t1, &t1, xr) // t1 = t1 + X(q-p) + add(&t0, &t0, &t0) // t0 = t0 + t0 + sqr(&curve.A, &curve.A) // A = A^2 + inv(&t0, &t0) // t0 = 1/t0 + mul(&curve.A, &curve.A, &t0) // A = A * t0 + sub(&curve.A, &curve.A, &t1) // A = A - t1 +} + +// Computes equivalence (A:C) ~ (A+2C : A-2C) +func CalcCurveParamsEquiv3(cparams *ProjectiveCurveParameters) CurveCoefficientsEquiv { + var coef CurveCoefficientsEquiv + var c2 Fp2 + + add(&c2, &cparams.C, &cparams.C) + // A24p = A+2*C + add(&coef.A, &cparams.A, &c2) + // A24m = A-2*C + sub(&coef.C, &cparams.A, &c2) + return coef +} + +// Computes equivalence (A:C) ~ (A+2C : 4C) +func CalcCurveParamsEquiv4(cparams *ProjectiveCurveParameters) CurveCoefficientsEquiv { + var coefEq CurveCoefficientsEquiv + + add(&coefEq.C, &cparams.C, &cparams.C) + // A24p = A+2C + add(&coefEq.A, &cparams.A, &coefEq.C) + // C24 = 4*C + add(&coefEq.C, &coefEq.C, &coefEq.C) + return coefEq +} + +// Recovers (A:C) curve parameters from projectively equivalent (A+2C:A-2C). +func RecoverCurveCoefficients3(cparams *ProjectiveCurveParameters, coefEq *CurveCoefficientsEquiv) { + add(&cparams.A, &coefEq.A, &coefEq.C) + // cparams.A = 2*(A+2C+A-2C) = 4A + add(&cparams.A, &cparams.A, &cparams.A) + // cparams.C = (A+2C-A+2C) = 4C + sub(&cparams.C, &coefEq.A, &coefEq.C) + return +} + +// Recovers (A:C) curve parameters from projectively equivalent (A+2C:4C). +func RecoverCurveCoefficients4(cparams *ProjectiveCurveParameters, coefEq *CurveCoefficientsEquiv) { + // cparams.C = (4C)*1/2=2C + mul(&cparams.C, &coefEq.C, &Params.HalfFp2) + // cparams.A = A+2C - 2C = A + sub(&cparams.A, &coefEq.A, &cparams.C) + // cparams.C = 2C * 1/2 = C + mul(&cparams.C, &cparams.C, &Params.HalfFp2) + return +} + +// Combined coordinate doubling and differential addition. Takes projective points +// P,Q,Q-P and (A+2C)/4C curve E coefficient. Returns 2*P and P+Q calculated on E. +// Function is used only by RightToLeftLadder. Corresponds to Algorithm 5 of SIKE +func xDbladd(P, Q, QmP *ProjectivePoint, a24 *Fp2) (dblP, PaQ ProjectivePoint) { + var t0, t1, t2 Fp2 + xQmP, zQmP := &QmP.X, &QmP.Z + xPaQ, zPaQ := &PaQ.X, &PaQ.Z + x2P, z2P := &dblP.X, &dblP.Z + xP, zP := &P.X, &P.Z + xQ, zQ := &Q.X, &Q.Z + + add(&t0, xP, zP) // t0 = Xp+Zp + sub(&t1, xP, zP) // t1 = Xp-Zp + sqr(x2P, &t0) // 2P.X = t0^2 + sub(&t2, xQ, zQ) // t2 = Xq-Zq + add(xPaQ, xQ, zQ) // Xp+q = Xq+Zq + mul(&t0, &t0, &t2) // t0 = t0 * t2 + mul(z2P, &t1, &t1) // 2P.Z = t1 * t1 + mul(&t1, &t1, xPaQ) // t1 = t1 * Xp+q + sub(&t2, x2P, z2P) // t2 = 2P.X - 2P.Z + mul(x2P, x2P, z2P) // 2P.X = 2P.X * 2P.Z + mul(xPaQ, a24, &t2) // Xp+q = A24 * t2 + sub(zPaQ, &t0, &t1) // Zp+q = t0 - t1 + add(z2P, xPaQ, z2P) // 2P.Z = Xp+q + 2P.Z + add(xPaQ, &t0, &t1) // Xp+q = t0 + t1 + mul(z2P, z2P, &t2) // 2P.Z = 2P.Z * t2 + sqr(zPaQ, zPaQ) // Zp+q = Zp+q ^ 2 + sqr(xPaQ, xPaQ) // Xp+q = Xp+q ^ 2 + mul(zPaQ, xQmP, zPaQ) // Zp+q = Xq-p * Zp+q + mul(xPaQ, zQmP, xPaQ) // Xp+q = Zq-p * Xp+q + return +} + +// Given the curve parameters, xP = x(P), computes xP = x([2^k]P) +// Safe to overlap xP, x2P. +func Pow2k(xP *ProjectivePoint, params *CurveCoefficientsEquiv, k uint32) { + var t0, t1 Fp2 + + x, z := &xP.X, &xP.Z + for i := uint32(0); i < k; i++ { + sub(&t0, x, z) // t0 = Xp - Zp + add(&t1, x, z) // t1 = Xp + Zp + sqr(&t0, &t0) // t0 = t0 ^ 2 + sqr(&t1, &t1) // t1 = t1 ^ 2 + mul(z, ¶ms.C, &t0) // Z2p = C24 * t0 + mul(x, z, &t1) // X2p = Z2p * t1 + sub(&t1, &t1, &t0) // t1 = t1 - t0 + mul(&t0, ¶ms.A, &t1) // t0 = A24+ * t1 + add(z, z, &t0) // Z2p = Z2p + t0 + mul(z, z, &t1) // Zp = Z2p * t1 + } +} + +// Given the curve parameters, xP = x(P), and k >= 0, compute xP = x([3^k]P). +// +// Safe to overlap xP, xR. +func Pow3k(xP *ProjectivePoint, params *CurveCoefficientsEquiv, k uint32) { + var t0, t1, t2, t3, t4, t5, t6 Fp2 + + x, z := &xP.X, &xP.Z + for i := uint32(0); i < k; i++ { + sub(&t0, x, z) // t0 = Xp - Zp + sqr(&t2, &t0) // t2 = t0^2 + add(&t1, x, z) // t1 = Xp + Zp + sqr(&t3, &t1) // t3 = t1^2 + add(&t4, &t1, &t0) // t4 = t1 + t0 + sub(&t0, &t1, &t0) // t0 = t1 - t0 + sqr(&t1, &t4) // t1 = t4^2 + sub(&t1, &t1, &t3) // t1 = t1 - t3 + sub(&t1, &t1, &t2) // t1 = t1 - t2 + mul(&t5, &t3, ¶ms.A) // t5 = t3 * A24+ + mul(&t3, &t3, &t5) // t3 = t5 * t3 + mul(&t6, &t2, ¶ms.C) // t6 = t2 * A24- + mul(&t2, &t2, &t6) // t2 = t2 * t6 + sub(&t3, &t2, &t3) // t3 = t2 - t3 + sub(&t2, &t5, &t6) // t2 = t5 - t6 + mul(&t1, &t2, &t1) // t1 = t2 * t1 + add(&t2, &t3, &t1) // t2 = t3 + t1 + sqr(&t2, &t2) // t2 = t2^2 + mul(x, &t2, &t4) // X3p = t2 * t4 + sub(&t1, &t3, &t1) // t1 = t3 - t1 + sqr(&t1, &t1) // t1 = t1^2 + mul(z, &t1, &t0) // Z3p = t1 * t0 + } +} + +// Set (y1, y2, y3) = (1/x1, 1/x2, 1/x3). +// +// All xi, yi must be distinct. +func Fp2Batch3Inv(x1, x2, x3, y1, y2, y3 *Fp2) { + var x1x2, t Fp2 + + mul(&x1x2, x1, x2) // x1*x2 + mul(&t, &x1x2, x3) // 1/(x1*x2*x3) + inv(&t, &t) + mul(y1, &t, x2) // 1/x1 + mul(y1, y1, x3) + mul(y2, &t, x1) // 1/x2 + mul(y2, y2, x3) + mul(y3, &t, &x1x2) // 1/x3 +} + +// ScalarMul3Pt is a right-to-left point multiplication that given the +// x-coordinate of P, Q and P-Q calculates the x-coordinate of R=Q+[scalar]P. +// nbits must be smaller or equal to len(scalar). +func ScalarMul3Pt(cparams *ProjectiveCurveParameters, P, Q, PmQ *ProjectivePoint, nbits uint, scalar []uint8) ProjectivePoint { + var R0, R2, R1 ProjectivePoint + aPlus2Over4 := calcAplus2Over4(cparams) + R1 = *P + R2 = *PmQ + R0 = *Q + + // Iterate over the bits of the scalar, bottom to top + prevBit := uint8(0) + for i := uint(0); i < nbits; i++ { + bit := (scalar[i>>3] >> (i & 7) & 1) + swap := prevBit ^ bit + prevBit = bit + condSwap(&R1.X, &R1.Z, &R2.X, &R2.Z, swap) + R0, R2 = xDbladd(&R0, &R2, &R1, &aPlus2Over4) + } + condSwap(&R1.X, &R1.Z, &R2.X, &R2.Z, prevBit) + return R1 +} + +// Given a three-torsion point p = x(PB) on the curve E_(A:C), construct the +// three-isogeny phi : E_(A:C) -> E_(A:C)/<P_3> = E_(A':C'). +// +// Input: (XP_3: ZP_3), where P_3 has exact order 3 on E_A/C +// Output: * Curve coordinates (A' + 2C', A' - 2C') corresponding to E_A'/C' = A_E/C/<P3> +// * isogeny phi with constants in F_p^2 +func (phi *isogeny3) GenerateCurve(p *ProjectivePoint) CurveCoefficientsEquiv { + var t0, t1, t2, t3, t4 Fp2 + var coefEq CurveCoefficientsEquiv + var K1, K2 = &phi.K1, &phi.K2 + + sub(K1, &p.X, &p.Z) // K1 = XP3 - ZP3 + sqr(&t0, K1) // t0 = K1^2 + add(K2, &p.X, &p.Z) // K2 = XP3 + ZP3 + sqr(&t1, K2) // t1 = K2^2 + add(&t2, &t0, &t1) // t2 = t0 + t1 + add(&t3, K1, K2) // t3 = K1 + K2 + sqr(&t3, &t3) // t3 = t3^2 + sub(&t3, &t3, &t2) // t3 = t3 - t2 + add(&t2, &t1, &t3) // t2 = t1 + t3 + add(&t3, &t3, &t0) // t3 = t3 + t0 + add(&t4, &t3, &t0) // t4 = t3 + t0 + add(&t4, &t4, &t4) // t4 = t4 + t4 + add(&t4, &t1, &t4) // t4 = t1 + t4 + mul(&coefEq.C, &t2, &t4) // A24m = t2 * t4 + add(&t4, &t1, &t2) // t4 = t1 + t2 + add(&t4, &t4, &t4) // t4 = t4 + t4 + add(&t4, &t0, &t4) // t4 = t0 + t4 + mul(&t4, &t3, &t4) // t4 = t3 * t4 + sub(&t0, &t4, &coefEq.C) // t0 = t4 - A24m + add(&coefEq.A, &coefEq.C, &t0) // A24p = A24m + t0 + return coefEq +} + +// Given a 3-isogeny phi and a point pB = x(PB), compute x(QB), the x-coordinate +// of the image QB = phi(PB) of PB under phi : E_(A:C) -> E_(A':C'). +// +// The output xQ = x(Q) is then a point on the curve E_(A':C'); the curve +// parameters are returned by the GenerateCurve function used to construct phi. +func (phi *isogeny3) EvaluatePoint(p *ProjectivePoint) ProjectivePoint { + var t0, t1, t2 Fp2 + var q ProjectivePoint + var K1, K2 = &phi.K1, &phi.K2 + var px, pz = &p.X, &p.Z + + add(&t0, px, pz) // t0 = XQ + ZQ + sub(&t1, px, pz) // t1 = XQ - ZQ + mul(&t0, K1, &t0) // t2 = K1 * t0 + mul(&t1, K2, &t1) // t1 = K2 * t1 + add(&t2, &t0, &t1) // t2 = t0 + t1 + sub(&t0, &t1, &t0) // t0 = t1 - t0 + sqr(&t2, &t2) // t2 = t2 ^ 2 + sqr(&t0, &t0) // t0 = t0 ^ 2 + mul(&q.X, px, &t2) // XQ'= XQ * t2 + mul(&q.Z, pz, &t0) // ZQ'= ZQ * t0 + return q +} + +// Given a four-torsion point p = x(PB) on the curve E_(A:C), construct the +// four-isogeny phi : E_(A:C) -> E_(A:C)/<P_4> = E_(A':C'). +// +// Input: (XP_4: ZP_4), where P_4 has exact order 4 on E_A/C +// Output: * Curve coordinates (A' + 2C', 4C') corresponding to E_A'/C' = A_E/C/<P4> +// * isogeny phi with constants in F_p^2 +func (phi *isogeny4) GenerateCurve(p *ProjectivePoint) CurveCoefficientsEquiv { + var coefEq CurveCoefficientsEquiv + var xp4, zp4 = &p.X, &p.Z + var K1, K2, K3 = &phi.K1, &phi.K2, &phi.K3 + + sub(K2, xp4, zp4) + add(K3, xp4, zp4) + sqr(K1, zp4) + add(K1, K1, K1) + sqr(&coefEq.C, K1) + add(K1, K1, K1) + sqr(&coefEq.A, xp4) + add(&coefEq.A, &coefEq.A, &coefEq.A) + sqr(&coefEq.A, &coefEq.A) + return coefEq +} + +// Given a 4-isogeny phi and a point xP = x(P), compute x(Q), the x-coordinate +// of the image Q = phi(P) of P under phi : E_(A:C) -> E_(A':C'). +// +// Input: isogeny returned by GenerateCurve and point q=(Qx,Qz) from E0_A/C +// Output: Corresponding point q from E1_A'/C', where E1 is 4-isogenous to E0 +func (phi *isogeny4) EvaluatePoint(p *ProjectivePoint) ProjectivePoint { + var t0, t1 Fp2 + var q = *p + var xq, zq = &q.X, &q.Z + var K1, K2, K3 = &phi.K1, &phi.K2, &phi.K3 + + add(&t0, xq, zq) + sub(&t1, xq, zq) + mul(xq, &t0, K2) + mul(zq, &t1, K3) + mul(&t0, &t0, &t1) + mul(&t0, &t0, K1) + add(&t1, xq, zq) + sub(zq, xq, zq) + sqr(&t1, &t1) + sqr(zq, zq) + add(xq, &t0, &t1) + sub(&t0, zq, &t0) + mul(xq, xq, &t1) + mul(zq, zq, &t0) + return q +} diff --git a/src/ssl/test/runner/sike/sike.go b/src/ssl/test/runner/sike/sike.go new file mode 100644 index 00000000..dcd6cfc4 --- /dev/null +++ b/src/ssl/test/runner/sike/sike.go @@ -0,0 +1,683 @@ +// Copyright (c) 2019, Cloudflare Inc. +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY +// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION +// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN +// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +package sike + +import ( + "crypto/sha256" + "crypto/subtle" + "errors" + "io" +) + +// Zeroize Fp2 +func zeroize(fp *Fp2) { + // Zeroizing in 2 separated loops tells compiler to + // use fast runtime.memclr() + for i := range fp.A { + fp.A[i] = 0 + } + for i := range fp.B { + fp.B[i] = 0 + } +} + +// Convert the input to wire format. +// +// The output byte slice must be at least 2*bytelen(p) bytes long. +func convFp2ToBytes(output []byte, fp2 *Fp2) { + if len(output) < 2*Params.Bytelen { + panic("output byte slice too short") + } + var a Fp2 + fromMontDomain(fp2, &a) + + // convert to bytes in little endian form + for i := 0; i < Params.Bytelen; i++ { + // set i = j*8 + k + tmp := i / 8 + k := uint64(i % 8) + output[i] = byte(a.A[tmp] >> (8 * k)) + output[i+Params.Bytelen] = byte(a.B[tmp] >> (8 * k)) + } +} + +// Read 2*bytelen(p) bytes into the given ExtensionFieldElement. +// +// It is an error to call this function if the input byte slice is less than 2*bytelen(p) bytes long. +func convBytesToFp2(fp2 *Fp2, input []byte) { + if len(input) < 2*Params.Bytelen { + panic("input byte slice too short") + } + + for i := 0; i < Params.Bytelen; i++ { + j := i / 8 + k := uint64(i % 8) + fp2.A[j] |= uint64(input[i]) << (8 * k) + fp2.B[j] |= uint64(input[i+Params.Bytelen]) << (8 * k) + } + toMontDomain(fp2) +} + +// ----------------------------------------------------------------------------- +// Functions for traversing isogeny trees acoording to strategy. Key type 'A' is +// + +// Traverses isogeny tree in order to compute xR, xP, xQ and xQmP needed +// for public key generation. +func traverseTreePublicKeyA(curve *ProjectiveCurveParameters, xR, phiP, phiQ, phiR *ProjectivePoint, pub *PublicKey) { + var points = make([]ProjectivePoint, 0, 8) + var indices = make([]int, 0, 8) + var i, sidx int + + cparam := CalcCurveParamsEquiv4(curve) + phi := NewIsogeny4() + strat := pub.params.A.IsogenyStrategy + stratSz := len(strat) + + for j := 1; j <= stratSz; j++ { + for i <= stratSz-j { + points = append(points, *xR) + indices = append(indices, i) + + k := strat[sidx] + sidx++ + Pow2k(xR, &cparam, 2*k) + i += int(k) + } + + cparam = phi.GenerateCurve(xR) + for k := 0; k < len(points); k++ { + points[k] = phi.EvaluatePoint(&points[k]) + } + + *phiP = phi.EvaluatePoint(phiP) + *phiQ = phi.EvaluatePoint(phiQ) + *phiR = phi.EvaluatePoint(phiR) + + // pop xR from points + *xR, points = points[len(points)-1], points[:len(points)-1] + i, indices = int(indices[len(indices)-1]), indices[:len(indices)-1] + } +} + +// Traverses isogeny tree in order to compute xR needed +// for public key generation. +func traverseTreeSharedKeyA(curve *ProjectiveCurveParameters, xR *ProjectivePoint, pub *PublicKey) { + var points = make([]ProjectivePoint, 0, 8) + var indices = make([]int, 0, 8) + var i, sidx int + + cparam := CalcCurveParamsEquiv4(curve) + phi := NewIsogeny4() + strat := pub.params.A.IsogenyStrategy + stratSz := len(strat) + + for j := 1; j <= stratSz; j++ { + for i <= stratSz-j { + points = append(points, *xR) + indices = append(indices, i) + + k := strat[sidx] + sidx++ + Pow2k(xR, &cparam, 2*k) + i += int(k) + } + + cparam = phi.GenerateCurve(xR) + for k := 0; k < len(points); k++ { + points[k] = phi.EvaluatePoint(&points[k]) + } + + // pop xR from points + *xR, points = points[len(points)-1], points[:len(points)-1] + i, indices = int(indices[len(indices)-1]), indices[:len(indices)-1] + } +} + +// Traverses isogeny tree in order to compute xR, xP, xQ and xQmP needed +// for public key generation. +func traverseTreePublicKeyB(curve *ProjectiveCurveParameters, xR, phiP, phiQ, phiR *ProjectivePoint, pub *PublicKey) { + var points = make([]ProjectivePoint, 0, 8) + var indices = make([]int, 0, 8) + var i, sidx int + + cparam := CalcCurveParamsEquiv3(curve) + phi := NewIsogeny3() + strat := pub.params.B.IsogenyStrategy + stratSz := len(strat) + + for j := 1; j <= stratSz; j++ { + for i <= stratSz-j { + points = append(points, *xR) + indices = append(indices, i) + + k := strat[sidx] + sidx++ + Pow3k(xR, &cparam, k) + i += int(k) + } + + cparam = phi.GenerateCurve(xR) + for k := 0; k < len(points); k++ { + points[k] = phi.EvaluatePoint(&points[k]) + } + + *phiP = phi.EvaluatePoint(phiP) + *phiQ = phi.EvaluatePoint(phiQ) + *phiR = phi.EvaluatePoint(phiR) + + // pop xR from points + *xR, points = points[len(points)-1], points[:len(points)-1] + i, indices = int(indices[len(indices)-1]), indices[:len(indices)-1] + } +} + +// Traverses isogeny tree in order to compute xR, xP, xQ and xQmP needed +// for public key generation. +func traverseTreeSharedKeyB(curve *ProjectiveCurveParameters, xR *ProjectivePoint, pub *PublicKey) { + var points = make([]ProjectivePoint, 0, 8) + var indices = make([]int, 0, 8) + var i, sidx int + + cparam := CalcCurveParamsEquiv3(curve) + phi := NewIsogeny3() + strat := pub.params.B.IsogenyStrategy + stratSz := len(strat) + + for j := 1; j <= stratSz; j++ { + for i <= stratSz-j { + points = append(points, *xR) + indices = append(indices, i) + + k := strat[sidx] + sidx++ + Pow3k(xR, &cparam, k) + i += int(k) + } + + cparam = phi.GenerateCurve(xR) + for k := 0; k < len(points); k++ { + points[k] = phi.EvaluatePoint(&points[k]) + } + + // pop xR from points + *xR, points = points[len(points)-1], points[:len(points)-1] + i, indices = int(indices[len(indices)-1]), indices[:len(indices)-1] + } +} + +// Generate a public key in the 2-torsion group +func publicKeyGenA(prv *PrivateKey) (pub *PublicKey) { + var xPA, xQA, xRA ProjectivePoint + var xPB, xQB, xRB, xK ProjectivePoint + var invZP, invZQ, invZR Fp2 + + pub = NewPublicKey(KeyVariant_SIDH_A) + var phi = NewIsogeny4() + + // Load points for A + xPA = ProjectivePoint{X: prv.params.A.Affine_P, Z: prv.params.OneFp2} + xQA = ProjectivePoint{X: prv.params.A.Affine_Q, Z: prv.params.OneFp2} + xRA = ProjectivePoint{X: prv.params.A.Affine_R, Z: prv.params.OneFp2} + + // Load points for B + xRB = ProjectivePoint{X: prv.params.B.Affine_R, Z: prv.params.OneFp2} + xQB = ProjectivePoint{X: prv.params.B.Affine_Q, Z: prv.params.OneFp2} + xPB = ProjectivePoint{X: prv.params.B.Affine_P, Z: prv.params.OneFp2} + + // Find isogeny kernel + xK = ScalarMul3Pt(&pub.params.InitCurve, &xPA, &xQA, &xRA, prv.params.A.SecretBitLen, prv.Scalar) + traverseTreePublicKeyA(&pub.params.InitCurve, &xK, &xPB, &xQB, &xRB, pub) + + // Secret isogeny + phi.GenerateCurve(&xK) + xPA = phi.EvaluatePoint(&xPB) + xQA = phi.EvaluatePoint(&xQB) + xRA = phi.EvaluatePoint(&xRB) + Fp2Batch3Inv(&xPA.Z, &xQA.Z, &xRA.Z, &invZP, &invZQ, &invZR) + + mul(&pub.affine_xP, &xPA.X, &invZP) + mul(&pub.affine_xQ, &xQA.X, &invZQ) + mul(&pub.affine_xQmP, &xRA.X, &invZR) + return +} + +// Generate a public key in the 3-torsion group +func publicKeyGenB(prv *PrivateKey) (pub *PublicKey) { + var xPB, xQB, xRB, xK ProjectivePoint + var xPA, xQA, xRA ProjectivePoint + var invZP, invZQ, invZR Fp2 + + pub = NewPublicKey(prv.keyVariant) + var phi = NewIsogeny3() + + // Load points for B + xRB = ProjectivePoint{X: prv.params.B.Affine_R, Z: prv.params.OneFp2} + xQB = ProjectivePoint{X: prv.params.B.Affine_Q, Z: prv.params.OneFp2} + xPB = ProjectivePoint{X: prv.params.B.Affine_P, Z: prv.params.OneFp2} + + // Load points for A + xPA = ProjectivePoint{X: prv.params.A.Affine_P, Z: prv.params.OneFp2} + xQA = ProjectivePoint{X: prv.params.A.Affine_Q, Z: prv.params.OneFp2} + xRA = ProjectivePoint{X: prv.params.A.Affine_R, Z: prv.params.OneFp2} + + xK = ScalarMul3Pt(&pub.params.InitCurve, &xPB, &xQB, &xRB, prv.params.B.SecretBitLen, prv.Scalar) + traverseTreePublicKeyB(&pub.params.InitCurve, &xK, &xPA, &xQA, &xRA, pub) + + phi.GenerateCurve(&xK) + xPB = phi.EvaluatePoint(&xPA) + xQB = phi.EvaluatePoint(&xQA) + xRB = phi.EvaluatePoint(&xRA) + Fp2Batch3Inv(&xPB.Z, &xQB.Z, &xRB.Z, &invZP, &invZQ, &invZR) + + mul(&pub.affine_xP, &xPB.X, &invZP) + mul(&pub.affine_xQ, &xQB.X, &invZQ) + mul(&pub.affine_xQmP, &xRB.X, &invZR) + return +} + +// ----------------------------------------------------------------------------- +// Key agreement functions +// + +// Establishing shared keys in in 2-torsion group +func deriveSecretA(prv *PrivateKey, pub *PublicKey) []byte { + var sharedSecret = make([]byte, pub.params.SharedSecretSize) + var xP, xQ, xQmP ProjectivePoint + var xK ProjectivePoint + var cparam ProjectiveCurveParameters + var phi = NewIsogeny4() + var jInv Fp2 + + // Recover curve coefficients + RecoverCoordinateA(&cparam, &pub.affine_xP, &pub.affine_xQ, &pub.affine_xQmP) + // C=1 + cparam.C = Params.OneFp2 + + // Find kernel of the morphism + xP = ProjectivePoint{X: pub.affine_xP, Z: pub.params.OneFp2} + xQ = ProjectivePoint{X: pub.affine_xQ, Z: pub.params.OneFp2} + xQmP = ProjectivePoint{X: pub.affine_xQmP, Z: pub.params.OneFp2} + xK = ScalarMul3Pt(&cparam, &xP, &xQ, &xQmP, pub.params.A.SecretBitLen, prv.Scalar) + + // Traverse isogeny tree + traverseTreeSharedKeyA(&cparam, &xK, pub) + + // Calculate j-invariant on isogeneus curve + c := phi.GenerateCurve(&xK) + RecoverCurveCoefficients4(&cparam, &c) + Jinvariant(&cparam, &jInv) + convFp2ToBytes(sharedSecret, &jInv) + return sharedSecret +} + +// Establishing shared keys in in 3-torsion group +func deriveSecretB(prv *PrivateKey, pub *PublicKey) []byte { + var sharedSecret = make([]byte, pub.params.SharedSecretSize) + var xP, xQ, xQmP ProjectivePoint + var xK ProjectivePoint + var cparam ProjectiveCurveParameters + var phi = NewIsogeny3() + var jInv Fp2 + + // Recover curve A coefficient + RecoverCoordinateA(&cparam, &pub.affine_xP, &pub.affine_xQ, &pub.affine_xQmP) + // C=1 + cparam.C = Params.OneFp2 + + // Find kernel of the morphism + xP = ProjectivePoint{X: pub.affine_xP, Z: pub.params.OneFp2} + xQ = ProjectivePoint{X: pub.affine_xQ, Z: pub.params.OneFp2} + xQmP = ProjectivePoint{X: pub.affine_xQmP, Z: pub.params.OneFp2} + xK = ScalarMul3Pt(&cparam, &xP, &xQ, &xQmP, pub.params.B.SecretBitLen, prv.Scalar) + + // Traverse isogeny tree + traverseTreeSharedKeyB(&cparam, &xK, pub) + + // Calculate j-invariant on isogeneus curve + c := phi.GenerateCurve(&xK) + RecoverCurveCoefficients3(&cparam, &c) + Jinvariant(&cparam, &jInv) + convFp2ToBytes(sharedSecret, &jInv) + return sharedSecret +} + +func encrypt(skA *PrivateKey, pkA, pkB *PublicKey, ptext []byte) ([]byte, error) { + if pkB.keyVariant != KeyVariant_SIKE { + return nil, errors.New("wrong key type") + } + + j, err := DeriveSecret(skA, pkB) + if err != nil { + return nil, err + } + + if len(ptext) != pkA.params.KemSize { + panic("Implementation error") + } + + digest := sha256.Sum256(j) + // Uses truncated digest (first 16-bytes) + for i, _ := range ptext { + digest[i] ^= ptext[i] + } + + ret := make([]byte, pkA.Size()+len(ptext)) + copy(ret, pkA.Export()) + copy(ret[pkA.Size():], digest[:pkA.params.KemSize]) + return ret, nil +} + +// NewPrivateKey initializes private key. +// Usage of this function guarantees that the object is correctly initialized. +func NewPrivateKey(v KeyVariant) *PrivateKey { + prv := &PrivateKey{key: key{params: &Params, keyVariant: v}} + if (v & KeyVariant_SIDH_A) == KeyVariant_SIDH_A { + prv.Scalar = make([]byte, prv.params.A.SecretByteLen) + } else { + prv.Scalar = make([]byte, prv.params.B.SecretByteLen) + } + if v == KeyVariant_SIKE { + prv.S = make([]byte, prv.params.MsgLen) + } + return prv +} + +// NewPublicKey initializes public key. +// Usage of this function guarantees that the object is correctly initialized. +func NewPublicKey(v KeyVariant) *PublicKey { + return &PublicKey{key: key{params: &Params, keyVariant: v}} +} + +// Import clears content of the public key currently stored in the structure +// and imports key stored in the byte string. Returns error in case byte string +// size is wrong. Doesn't perform any validation. +func (pub *PublicKey) Import(input []byte) error { + if len(input) != pub.Size() { + return errors.New("sidh: input to short") + } + ssSz := pub.params.SharedSecretSize + convBytesToFp2(&pub.affine_xP, input[0:ssSz]) + convBytesToFp2(&pub.affine_xQ, input[ssSz:2*ssSz]) + convBytesToFp2(&pub.affine_xQmP, input[2*ssSz:3*ssSz]) + return nil +} + +// Exports currently stored key. In case structure hasn't been filled with key data +// returned byte string is filled with zeros. +func (pub *PublicKey) Export() []byte { + output := make([]byte, pub.params.PublicKeySize) + ssSz := pub.params.SharedSecretSize + convFp2ToBytes(output[0:ssSz], &pub.affine_xP) + convFp2ToBytes(output[ssSz:2*ssSz], &pub.affine_xQ) + convFp2ToBytes(output[2*ssSz:3*ssSz], &pub.affine_xQmP) + return output +} + +// Size returns size of the public key in bytes +func (pub *PublicKey) Size() int { + return pub.params.PublicKeySize +} + +// Exports currently stored key. In case structure hasn't been filled with key data +// returned byte string is filled with zeros. +func (prv *PrivateKey) Export() []byte { + ret := make([]byte, len(prv.Scalar)+len(prv.S)) + copy(ret, prv.S) + copy(ret[len(prv.S):], prv.Scalar) + return ret +} + +// Size returns size of the private key in bytes +func (prv *PrivateKey) Size() int { + tmp := len(prv.Scalar) + if prv.keyVariant == KeyVariant_SIKE { + tmp += int(prv.params.MsgLen) + } + return tmp +} + +// Import clears content of the private key currently stored in the structure +// and imports key from octet string. In case of SIKE, the random value 'S' +// must be prepended to the value of actual private key (see SIKE spec for details). +// Function doesn't import public key value to PrivateKey object. +func (prv *PrivateKey) Import(input []byte) error { + if len(input) != prv.Size() { + return errors.New("sidh: input to short") + } + copy(prv.S, input[:len(prv.S)]) + copy(prv.Scalar, input[len(prv.S):]) + return nil +} + +// Generates random private key for SIDH or SIKE. Generated value is +// formed as little-endian integer from key-space <2^(e2-1)..2^e2 - 1> +// for KeyVariant_A or <2^(s-1)..2^s - 1>, where s = floor(log_2(3^e3)), +// for KeyVariant_B. +// +// Returns error in case user provided RNG fails. +func (prv *PrivateKey) Generate(rand io.Reader) error { + var err error + var dp *DomainParams + + if (prv.keyVariant & KeyVariant_SIDH_A) == KeyVariant_SIDH_A { + dp = &prv.params.A + } else { + dp = &prv.params.B + } + + if prv.keyVariant == KeyVariant_SIKE { + _, err = io.ReadFull(rand, prv.S) + } + + // Private key generation takes advantage of the fact that keyspace for secret + // key is (0, 2^x - 1), for some possitivite value of 'x' (see SIKE, 1.3.8). + // It means that all bytes in the secret key, but the last one, can take any + // value between <0x00,0xFF>. Similarily for the last byte, but generation + // needs to chop off some bits, to make sure generated value is an element of + // a key-space. + _, err = io.ReadFull(rand, prv.Scalar) + if err != nil { + return err + } + prv.Scalar[len(prv.Scalar)-1] &= (1 << (dp.SecretBitLen % 8)) - 1 + // Make sure scalar is SecretBitLen long. SIKE spec says that key + // space starts from 0, but I'm not confortable with having low + // value scalars used for private keys. It is still secrure as per + // table 5.1 in [SIKE]. + prv.Scalar[len(prv.Scalar)-1] |= 1 << ((dp.SecretBitLen % 8) - 1) + return err +} + +// Generates public key. +// +// Constant time. +func (prv *PrivateKey) GeneratePublicKey() *PublicKey { + if (prv.keyVariant & KeyVariant_SIDH_A) == KeyVariant_SIDH_A { + return publicKeyGenA(prv) + } + return publicKeyGenB(prv) +} + +// Computes a shared secret which is a j-invariant. Function requires that pub has +// different KeyVariant than prv. Length of returned output is 2*ceil(log_2 P)/8), +// where P is a prime defining finite field. +// +// It's important to notice that each keypair must not be used more than once +// to calculate shared secret. +// +// Function may return error. This happens only in case provided input is invalid. +// Constant time for properly initialized private and public key. +func DeriveSecret(prv *PrivateKey, pub *PublicKey) ([]byte, error) { + + if (pub == nil) || (prv == nil) { + return nil, errors.New("sidh: invalid arguments") + } + + if (pub.keyVariant == prv.keyVariant) || (pub.params.Id != prv.params.Id) { + return nil, errors.New("sidh: public and private are incompatbile") + } + + if (prv.keyVariant & KeyVariant_SIDH_A) == KeyVariant_SIDH_A { + return deriveSecretA(prv, pub), nil + } else { + return deriveSecretB(prv, pub), nil + } +} + +// Uses SIKE public key to encrypt plaintext. Requires cryptographically secure PRNG +// Returns ciphertext in case encryption succeeds. Returns error in case PRNG fails +// or wrongly formatted input was provided. +func Encrypt(rng io.Reader, pub *PublicKey, ptext []byte) ([]byte, error) { + var ptextLen = len(ptext) + // c1 must be security level + 64 bits (see [SIKE] 1.4 and 4.3.3) + if ptextLen != pub.params.KemSize { + return nil, errors.New("Unsupported message length") + } + + skA := NewPrivateKey(KeyVariant_SIDH_A) + err := skA.Generate(rng) + if err != nil { + return nil, err + } + + pkA := skA.GeneratePublicKey() + return encrypt(skA, pkA, pub, ptext) +} + +// Uses SIKE private key to decrypt ciphertext. Returns plaintext in case +// decryption succeeds or error in case unexptected input was provided. +// Constant time +func Decrypt(prv *PrivateKey, ctext []byte) ([]byte, error) { + var c1_len int + n := make([]byte, prv.params.KemSize) + pk_len := prv.params.PublicKeySize + + if prv.keyVariant != KeyVariant_SIKE { + return nil, errors.New("wrong key type") + } + + // ctext is a concatenation of (pubkey_A || c1=ciphertext) + // it must be security level + 64 bits (see [SIKE] 1.4 and 4.3.3) + c1_len = len(ctext) - pk_len + if c1_len != int(prv.params.KemSize) { + return nil, errors.New("wrong size of cipher text") + } + + c0 := NewPublicKey(KeyVariant_SIDH_A) + err := c0.Import(ctext[:pk_len]) + if err != nil { + return nil, err + } + j, err := DeriveSecret(prv, c0) + if err != nil { + return nil, err + } + + digest := sha256.Sum256(j) + copy(n, digest[:]) + + for i, _ := range n { + n[i] ^= ctext[pk_len+i] + } + return n[:c1_len], nil +} + +// Encapsulation receives the public key and generates SIKE ciphertext and shared secret. +// The generated ciphertext is used for authentication. +// The rng must be cryptographically secure PRNG. +// Error is returned in case PRNG fails or wrongly formatted input was provided. +func Encapsulate(rng io.Reader, pub *PublicKey) (ctext []byte, secret []byte, err error) { + // Buffer for random, secret message + ptext := make([]byte, pub.params.MsgLen) + // SHA256 hash context object + d := sha256.New() + + // Generate ephemeral value + _, err = io.ReadFull(rng, ptext) + if err != nil { + return nil, nil, err + } + + // Implementation uses first 28-bytes of secret + d.Write(ptext) + d.Write(pub.Export()) + digest := d.Sum(nil) + // r = G(ptext||pub) + r := digest[:pub.params.A.SecretByteLen] + + // (c0 || c1) = Enc(pkA, ptext; r) + skA := NewPrivateKey(KeyVariant_SIDH_A) + err = skA.Import(r) + if err != nil { + return nil, nil, err + } + + pkA := skA.GeneratePublicKey() + ctext, err = encrypt(skA, pkA, pub, ptext) + if err != nil { + return nil, nil, err + } + + // K = H(ptext||(c0||c1)) + d.Reset() + d.Write(ptext) + d.Write(ctext) + digest = d.Sum(digest[:0]) + return ctext, digest[:pub.params.KemSize], nil +} + +// Decapsulate given the keypair and ciphertext as inputs, Decapsulate outputs a shared +// secret if plaintext verifies correctly, otherwise function outputs random value. +// Decapsulation may fail in case input is wrongly formatted. +// Constant time for properly initialized input. +func Decapsulate(prv *PrivateKey, pub *PublicKey, ctext []byte) ([]byte, error) { + var skA = NewPrivateKey(KeyVariant_SIDH_A) + // SHA256 hash context object + d := sha256.New() + + m, err := Decrypt(prv, ctext) + if err != nil { + return nil, err + } + + // r' = G(m'||pub) + d.Write(m) + d.Write(pub.Export()) + digest := d.Sum(nil) + // Never fails + skA.Import(digest[:pub.params.A.SecretByteLen]) + + // Never fails + pkA := skA.GeneratePublicKey() + c0 := pkA.Export() + + d.Reset() + if subtle.ConstantTimeCompare(c0, ctext[:len(c0)]) == 1 { + d.Write(m) + } else { + // S is chosen at random when generating a key and is unknown to the other party. It + // may seem weird, but it's correct. It is important that S is unpredictable + // to other party. Without this check, it is possible to recover a secret, by + // providing series of invalid ciphertexts. It is also important that in case + // + // See more details in "On the security of supersingular isogeny cryptosystems" + // (S. Galbraith, et al., 2016, ePrint #859). + d.Write(prv.S) + } + d.Write(ctext) + digest = d.Sum(digest[:0]) + return digest[:pub.params.KemSize], nil +} diff --git a/src/ssl/test/runner/sike/sike_test.go b/src/ssl/test/runner/sike/sike_test.go new file mode 100644 index 00000000..2e146bc7 --- /dev/null +++ b/src/ssl/test/runner/sike/sike_test.go @@ -0,0 +1,698 @@ +// Copyright (c) 2019, Cloudflare Inc. +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY +// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION +// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN +// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +package sike + +import ( + "bufio" + "bytes" + "crypto/rand" + "encoding/hex" + "math/big" + "strings" + "testing" +) + +var tdata = struct { + name string + PrB_sidh string + PkB_sidh string + PrA_sidh string + PkA_sidh string + PkB_sike string + PrB_sike string +}{ + name: "P-434", + PrA_sidh: "3A727E04EA9B7E2A766A6F846489E7E7B915263BCEED308BB10FC9", + PkA_sidh: "9E668D1E6750ED4B91EE052C32839CA9DD2E56D52BC24DECC950AA" + + "AD24CEED3F9049C77FE80F0B9B01E7F8DAD7833EEC2286544D6380" + + "009C379CDD3E7517CEF5E20EB01F8231D52FC30DC61D2F63FB357F" + + "85DC6396E8A95DB9740BD3A972C8DB7901B31F074CD3E45345CA78" + + "F900817130E688A29A7CF0073B5C00FF2C65FBE776918EF9BD8E75" + + "B29EF7FAB791969B60B0C5B37A8992EDEF95FA7BAC40A95DAFE02E" + + "237301FEE9A7A43FD0B73477E8035DD12B73FAFEF18D39904DDE36" + + "53A754F36BE1888F6607C6A7951349A414352CF31A29F2C40302DB" + + "406C48018C905EB9DC46AFBF42A9187A9BB9E51B587622A2862DC7" + + "D5CC598BF38ED6320FB51D8697AD3D7A72ABCC32A393F0133DA8DF" + + "5E253D9E00B760B2DF342FCE974DCFE946CFE4727783531882800F" + + "9E5DD594D6D5A6275EEFEF9713ED838F4A06BB34D7B8D46E0B385A" + + "AEA1C7963601", + PrB_sidh: "E37BFE55B43B32448F375903D8D226EC94ADBFEA1D2B3536EB987001", + PkB_sidh: "C9F73E4497AAA3FDF9EB688135866A8A83934BA10E273B8CC3808C" + + "F0C1F5FAB3E9BB295885881B73DEBC875670C0F51C4BB40DF5FEDE" + + "01B8AF32D1BF10508B8C17B2734EB93B2B7F5D84A4A0F2F816E9E2" + + "C32AC253C0B6025B124D05A87A9E2A8567930F44BAA14219B941B6" + + "B400B4AED1D796DA12A5A9F0B8F3F5EE9DD43F64CB24A3B1719DF2" + + "78ADF56B5F3395187829DA2319DEABF6BBD6EDA244DE2B62CC5AC2" + + "50C1009DD1CD4712B0B37406612AD002B5E51A62B51AC9C0374D14" + + "3ABBBD58275FAFC4A5E959C54838C2D6D9FB43B7B2609061267B6A" + + "2E6C6D01D295C4223E0D3D7A4CDCFB28A7818A737935279751A6DD" + + "8290FD498D1F6AD5F4FFF6BDFA536713F509DCE8047252F1E7D0DD" + + "9FCC414C0070B5DCCE3665A21A032D7FBE749181032183AFAD240B" + + "7E671E87FBBEC3A8CA4C11AA7A9A23AC69AE2ACF54B664DECD2775" + + "3D63508F1B02", + PrB_sike: "4B622DE1350119C45A9F2E2EF3DC5DF56A27FCDFCDDAF58CD69B90" + + "3752D68C200934E160B234E49EDE247601", + PkB_sike: "1BD0A2E81307B6F96461317DDF535ACC0E59C742627BAE60D27605" + + "E10FAF722D22A73E184CB572A12E79DCD58C6B54FB01442114CBE9" + + "010B6CAEC25D04C16C5E42540C1524C545B8C67614ED4183C9FA5B" + + "D0BE45A7F89FBC770EE8E7E5E391C7EE6F35F74C29E6D9E35B1663" + + "DA01E48E9DEB2347512D366FDE505161677055E3EF23054D276E81" + + "7E2C57025DA1C10D2461F68617F2D11256EEE4E2D7DBDF6C8E34F3" + + "A0FD00C625428CB41857002159DAB94267ABE42D630C6AAA91AF83" + + "7C7A6740754EA6634C45454C51B0BB4D44C3CCCCE4B32C00901CF6" + + "9C008D013348379B2F9837F428A01B6173584691F2A6F3A3C4CF48" + + "7D20D261B36C8CDB1BC158E2A5162A9DA4F7A97AA0879B9897E2B6" + + "891B672201F9AEFBF799C27B2587120AC586A511360926FB7DA8EB" + + "F5CB5272F396AE06608422BE9792E2CE9BEF21BF55B7EFF8DC7EC8" + + "C99910D3F800", +} + +/* ------------------------------------------------------------------------- + Helpers + -------------------------------------------------------------------------*/ +// Fail if err !=nil. Display msg as an error message +func checkErr(t testing.TB, err error, msg string) { + t.Helper() + if err != nil { + t.Error(msg) + } +} + +// Utility used for running same test with all registered prime fields +type MultiIdTestingFunc func(testing.TB) + +// Converts string to private key +func convToPrv(s string, v KeyVariant) *PrivateKey { + key := NewPrivateKey(v) + hex, e := hex.DecodeString(s) + if e != nil { + panic("non-hex number provided") + } + e = key.Import(hex) + if e != nil { + panic("Can't import private key") + } + return key +} + +// Converts string to public key +func convToPub(s string, v KeyVariant) *PublicKey { + key := NewPublicKey(v) + hex, e := hex.DecodeString(s) + if e != nil { + panic("non-hex number provided") + } + e = key.Import(hex) + if e != nil { + panic("Can't import public key") + } + return key +} + +/* ------------------------------------------------------------------------- + Unit tests + -------------------------------------------------------------------------*/ +func TestKeygen(t *testing.T) { + alicePrivate := convToPrv(tdata.PrA_sidh, KeyVariant_SIDH_A) + bobPrivate := convToPrv(tdata.PrB_sidh, KeyVariant_SIDH_B) + expPubA := convToPub(tdata.PkA_sidh, KeyVariant_SIDH_A) + expPubB := convToPub(tdata.PkB_sidh, KeyVariant_SIDH_B) + + pubA := alicePrivate.GeneratePublicKey() + pubB := bobPrivate.GeneratePublicKey() + + if !bytes.Equal(pubA.Export(), expPubA.Export()) { + t.Fatalf("unexpected value of public key A") + } + if !bytes.Equal(pubB.Export(), expPubB.Export()) { + t.Fatalf("unexpected value of public key B") + } +} + +func TestImportExport(t *testing.T) { + var err error + a := NewPublicKey(KeyVariant_SIDH_A) + b := NewPublicKey(KeyVariant_SIDH_B) + + // Import keys + a_hex, err := hex.DecodeString(tdata.PkA_sidh) + checkErr(t, err, "invalid hex-number provided") + + err = a.Import(a_hex) + checkErr(t, err, "import failed") + + b_hex, err := hex.DecodeString(tdata.PkB_sike) + checkErr(t, err, "invalid hex-number provided") + + err = b.Import(b_hex) + checkErr(t, err, "import failed") + + // Export and check if same + if !bytes.Equal(b.Export(), b_hex) || !bytes.Equal(a.Export(), a_hex) { + t.Fatalf("export/import failed") + } + + if (len(b.Export()) != b.Size()) || (len(a.Export()) != a.Size()) { + t.Fatalf("wrong size of exported keys") + } +} + +func testPrivateKeyBelowMax(t testing.TB) { + for variant, keySz := range map[KeyVariant]*DomainParams{ + KeyVariant_SIDH_A: &Params.A, + KeyVariant_SIDH_B: &Params.B} { + + func(v KeyVariant, dp *DomainParams) { + var blen = int(dp.SecretByteLen) + var prv = NewPrivateKey(v) + + // Calculate either (2^e2 - 1) or (2^s - 1); where s=ceil(log_2(3^e3))) + maxSecertVal := big.NewInt(int64(dp.SecretBitLen)) + maxSecertVal.Exp(big.NewInt(int64(2)), maxSecertVal, nil) + maxSecertVal.Sub(maxSecertVal, big.NewInt(1)) + + // Do same test 1000 times + for i := 0; i < 1000; i++ { + err := prv.Generate(rand.Reader) + checkErr(t, err, "Private key generation") + + // Convert to big-endian, as that's what expected by (*Int)SetBytes() + secretBytes := prv.Export() + for i := 0; i < int(blen/2); i++ { + tmp := secretBytes[i] ^ secretBytes[blen-i-1] + secretBytes[i] = tmp ^ secretBytes[i] + secretBytes[blen-i-1] = tmp ^ secretBytes[blen-i-1] + } + prvBig := new(big.Int).SetBytes(secretBytes) + // Check if generated key is bigger than acceptable + if prvBig.Cmp(maxSecertVal) == 1 { + t.Error("Generated private key is wrong") + } + } + }(variant, keySz) + } +} + +func testKeyAgreement(t *testing.T, pkA, prA, pkB, prB string) { + var e error + + // KeyPairs + alicePublic := convToPub(pkA, KeyVariant_SIDH_A) + bobPublic := convToPub(pkB, KeyVariant_SIDH_B) + alicePrivate := convToPrv(prA, KeyVariant_SIDH_A) + bobPrivate := convToPrv(prB, KeyVariant_SIDH_B) + + // Do actual test + s1, e := DeriveSecret(bobPrivate, alicePublic) + checkErr(t, e, "derivation s1") + s2, e := DeriveSecret(alicePrivate, bobPublic) + checkErr(t, e, "derivation s1") + + if !bytes.Equal(s1[:], s2[:]) { + t.Fatalf("two shared keys: %d, %d do not match", s1, s2) + } + + // Negative case + dec, e := hex.DecodeString(tdata.PkA_sidh) + if e != nil { + t.FailNow() + } + dec[0] = ^dec[0] + e = alicePublic.Import(dec) + if e != nil { + t.FailNow() + } + + s1, e = DeriveSecret(bobPrivate, alicePublic) + checkErr(t, e, "derivation of s1 failed") + s2, e = DeriveSecret(alicePrivate, bobPublic) + checkErr(t, e, "derivation of s2 failed") + + if bytes.Equal(s1[:], s2[:]) { + t.Fatalf("The two shared keys: %d, %d match", s1, s2) + } +} + +func TestDerivationRoundTrip(t *testing.T) { + var err error + + prvA := NewPrivateKey(KeyVariant_SIDH_A) + prvB := NewPrivateKey(KeyVariant_SIDH_B) + + // Generate private keys + err = prvA.Generate(rand.Reader) + checkErr(t, err, "key generation failed") + err = prvB.Generate(rand.Reader) + checkErr(t, err, "key generation failed") + + // Generate public keys + pubA := prvA.GeneratePublicKey() + pubB := prvB.GeneratePublicKey() + + // Derive shared secret + s1, err := DeriveSecret(prvB, pubA) + checkErr(t, err, "") + + s2, err := DeriveSecret(prvA, pubB) + checkErr(t, err, "") + + if !bytes.Equal(s1[:], s2[:]) { + t.Fatalf("Two shared keys: \n%X, \n%X do not match", s1, s2) + } +} + +// Encrypt, Decrypt, check if input/output plaintext is the same +func testPKERoundTrip(t testing.TB, id uint8) { + // Message to be encrypted + var msg = make([]byte, Params.MsgLen) + for i, _ := range msg { + msg[i] = byte(i) + } + + // Import keys + pkB := NewPublicKey(KeyVariant_SIKE) + skB := NewPrivateKey(KeyVariant_SIKE) + pk_hex, err := hex.DecodeString(tdata.PkB_sike) + if err != nil { + t.Fatal(err) + } + sk_hex, err := hex.DecodeString(tdata.PrB_sike) + if err != nil { + t.Fatal(err) + } + if pkB.Import(pk_hex) != nil || skB.Import(sk_hex) != nil { + t.Error("Import") + } + + ct, err := Encrypt(rand.Reader, pkB, msg[:]) + if err != nil { + t.Fatal(err) + } + pt, err := Decrypt(skB, ct) + if err != nil { + t.Fatal(err) + } + if !bytes.Equal(pt[:], msg[:]) { + t.Errorf("Decryption failed \n got : %X\n exp : %X", pt, msg) + } +} + +// Generate key and check if can encrypt +func TestPKEKeyGeneration(t *testing.T) { + // Message to be encrypted + var msg = make([]byte, Params.MsgLen) + var err error + for i, _ := range msg { + msg[i] = byte(i) + } + + sk := NewPrivateKey(KeyVariant_SIKE) + err = sk.Generate(rand.Reader) + checkErr(t, err, "PEK key generation") + pk := sk.GeneratePublicKey() + + // Try to encrypt + ct, err := Encrypt(rand.Reader, pk, msg[:]) + checkErr(t, err, "PEK encryption") + pt, err := Decrypt(sk, ct) + checkErr(t, err, "PEK key decryption") + + if !bytes.Equal(pt[:], msg[:]) { + t.Fatalf("Decryption failed \n got : %X\n exp : %X", pt, msg) + } +} + +func TestNegativePKE(t *testing.T) { + var msg [40]byte + var err error + + // Generate key + sk := NewPrivateKey(KeyVariant_SIKE) + err = sk.Generate(rand.Reader) + checkErr(t, err, "key generation") + + pk := sk.GeneratePublicKey() + + // bytelen(msg) - 1 + ct, err := Encrypt(rand.Reader, pk, msg[:Params.KemSize+8-1]) + if err == nil { + t.Fatal("Error hasn't been returned") + } + if ct != nil { + t.Fatal("Ciphertext must be nil") + } + + // KemSize - 1 + pt, err := Decrypt(sk, msg[:Params.KemSize+8-1]) + if err == nil { + t.Fatal("Error hasn't been returned") + } + if pt != nil { + t.Fatal("Ciphertext must be nil") + } +} + +func testKEMRoundTrip(t *testing.T, pkB, skB []byte) { + // Import keys + pk := NewPublicKey(KeyVariant_SIKE) + sk := NewPrivateKey(KeyVariant_SIKE) + if pk.Import(pkB) != nil || sk.Import(skB) != nil { + t.Error("Import failed") + } + + ct, ss_e, err := Encapsulate(rand.Reader, pk) + if err != nil { + t.Error("Encapsulate failed") + } + + ss_d, err := Decapsulate(sk, pk, ct) + if err != nil { + t.Error("Decapsulate failed") + } + if !bytes.Equal(ss_e, ss_d) { + t.Error("Shared secrets from decapsulation and encapsulation differ") + } +} + +func TestKEMRoundTrip(t *testing.T) { + pk, err := hex.DecodeString(tdata.PkB_sike) + checkErr(t, err, "public key B not a number") + sk, err := hex.DecodeString(tdata.PrB_sike) + checkErr(t, err, "private key B not a number") + testKEMRoundTrip(t, pk, sk) +} + +func TestKEMKeyGeneration(t *testing.T) { + // Generate key + sk := NewPrivateKey(KeyVariant_SIKE) + checkErr(t, sk.Generate(rand.Reader), "error: key generation") + pk := sk.GeneratePublicKey() + + // calculated shared secret + ct, ss_e, err := Encapsulate(rand.Reader, pk) + + checkErr(t, err, "encapsulation failed") + ss_d, err := Decapsulate(sk, pk, ct) + checkErr(t, err, "decapsulation failed") + + if !bytes.Equal(ss_e, ss_d) { + t.Fatalf("KEM failed \n encapsulated: %X\n decapsulated: %X", ss_d, ss_e) + } +} + +func TestNegativeKEM(t *testing.T) { + sk := NewPrivateKey(KeyVariant_SIKE) + checkErr(t, sk.Generate(rand.Reader), "error: key generation") + pk := sk.GeneratePublicKey() + + ct, ss_e, err := Encapsulate(rand.Reader, pk) + checkErr(t, err, "pre-requisite for a test failed") + + ct[0] = ct[0] - 1 + ss_d, err := Decapsulate(sk, pk, ct) + checkErr(t, err, "decapsulation returns error when invalid ciphertext provided") + + if bytes.Equal(ss_e, ss_d) { + // no idea how this could ever happen, but it would be very bad + t.Error("critical error") + } + + // Try encapsulating with SIDH key + pkSidh := NewPublicKey(KeyVariant_SIDH_B) + prSidh := NewPrivateKey(KeyVariant_SIDH_B) + _, _, err = Encapsulate(rand.Reader, pkSidh) + if err == nil { + t.Error("encapsulation accepts SIDH public key") + } + // Try decapsulating with SIDH key + _, err = Decapsulate(prSidh, pk, ct) + if err == nil { + t.Error("decapsulation accepts SIDH private key key") + } +} + +// In case invalid ciphertext is provided, SIKE's decapsulation must +// return same (but unpredictable) result for a given key. +func TestNegativeKEMSameWrongResult(t *testing.T) { + sk := NewPrivateKey(KeyVariant_SIKE) + checkErr(t, sk.Generate(rand.Reader), "error: key generation") + pk := sk.GeneratePublicKey() + + ct, encSs, err := Encapsulate(rand.Reader, pk) + checkErr(t, err, "pre-requisite for a test failed") + + // make ciphertext wrong + ct[0] = ct[0] - 1 + decSs1, err := Decapsulate(sk, pk, ct) + checkErr(t, err, "pre-requisite for a test failed") + + // second decapsulation must be done with same, but imported private key + expSk := sk.Export() + + // creat new private key + sk = NewPrivateKey(KeyVariant_SIKE) + err = sk.Import(expSk) + checkErr(t, err, "import failed") + + // try decapsulating again. ss2 must be same as ss1 and different than + // original plaintext + decSs2, err := Decapsulate(sk, pk, ct) + checkErr(t, err, "pre-requisite for a test failed") + + if !bytes.Equal(decSs1, decSs2) { + t.Error("decapsulation is insecure") + } + + if bytes.Equal(encSs, decSs1) || bytes.Equal(encSs, decSs2) { + // this test requires that decapsulation returns wrong result + t.Errorf("test implementation error") + } +} + +func readAndCheckLine(r *bufio.Reader) []byte { + // Read next line from buffer + line, isPrefix, err := r.ReadLine() + if err != nil || isPrefix { + panic("Wrong format of input file") + } + + // Function expects that line is in format "KEY = HEX_VALUE". Get + // value, which should be a hex string + hexst := strings.Split(string(line), "=")[1] + hexst = strings.TrimSpace(hexst) + // Convert value to byte string + ret, err := hex.DecodeString(hexst) + if err != nil { + panic("Wrong format of input file") + } + return ret +} + +func testKeygenSIKE(pk, sk []byte, id uint8) bool { + // Import provided private key + var prvKey = NewPrivateKey(KeyVariant_SIKE) + if prvKey.Import(sk) != nil { + panic("sike test: can't load KAT") + } + + // Generate public key + pubKey := prvKey.GeneratePublicKey() + return bytes.Equal(pubKey.Export(), pk) +} + +func testDecapsulation(pk, sk, ct, ssExpected []byte, id uint8) bool { + var pubKey = NewPublicKey(KeyVariant_SIKE) + var prvKey = NewPrivateKey(KeyVariant_SIKE) + if pubKey.Import(pk) != nil || prvKey.Import(sk) != nil { + panic("sike test: can't load KAT") + } + + ssGot, err := Decapsulate(prvKey, pubKey, ct) + if err != nil { + panic("sike test: can't perform degcapsulation KAT") + } + + return bytes.Equal(ssGot, ssExpected) +} + +func TestKeyAgreement(t *testing.T) { + testKeyAgreement(t, tdata.PkA_sidh, tdata.PrA_sidh, tdata.PkB_sidh, tdata.PrB_sidh) +} + +// Same values as in sike_test.cc +func TestDecapsulation(t *testing.T) { + var sk = [16 + 28]byte{ + 0x04, 0x5E, 0x01, 0x42, 0xB8, 0x2F, 0xE1, 0x9A, 0x38, 0x25, + 0x92, 0xE7, 0xDC, 0xBA, 0xF7, 0x1B, 0xB1, 0xFD, 0x34, 0x42, + 0xDB, 0x02, 0xBC, 0x9D, 0x4C, 0xD0, 0x72, 0x34, 0x4D, 0xBD, + 0x06, 0xDF, 0x1C, 0x7D, 0x0A, 0x88, 0xB2, 0x50, 0xC4, 0xF6, + 0xAE, 0xE8, 0x25, 0x01, + } + + var pk = [330]byte{ + 0x6D, 0x8D, 0xF5, 0x7B, 0xCD, 0x47, 0xCA, 0xCB, 0x7A, 0x38, + 0xB7, 0xA6, 0x90, 0xB7, 0x37, 0x03, 0xD4, 0x6F, 0x27, 0x73, + 0x74, 0x17, 0x5A, 0xA4, 0x0D, 0xC6, 0x81, 0xAD, 0xDB, 0xF7, + 0x18, 0xB2, 0x3C, 0x30, 0xCF, 0xAA, 0x08, 0x11, 0x91, 0xCC, + 0x27, 0x4E, 0xF1, 0xA6, 0xB7, 0xDA, 0xD2, 0xCF, 0x99, 0x7F, + 0xF7, 0xE1, 0xD0, 0xCE, 0x00, 0xD2, 0x4B, 0xA4, 0x33, 0xB4, + 0x87, 0x01, 0x3F, 0x02, 0xF7, 0xF9, 0xDE, 0xC3, 0x60, 0x62, + 0xDA, 0x3F, 0x74, 0xA9, 0x44, 0xBE, 0x19, 0xD5, 0x03, 0x2A, + 0x79, 0x8C, 0xA7, 0xFF, 0xEA, 0xB3, 0xBB, 0xB5, 0xD4, 0x1D, + 0x8F, 0x92, 0xCE, 0x62, 0x6E, 0x99, 0x24, 0xD7, 0x57, 0xFA, + 0xCD, 0xB6, 0xE2, 0x8E, 0xFD, 0x22, 0x0E, 0x31, 0x21, 0x01, + 0x8D, 0x79, 0xF8, 0x3E, 0x27, 0xEC, 0x43, 0x40, 0xDB, 0x82, + 0xE5, 0xEB, 0x6C, 0x97, 0x66, 0x29, 0x15, 0x68, 0xB7, 0x4D, + 0x84, 0xD1, 0x8A, 0x0B, 0x12, 0x36, 0x2C, 0x0C, 0x0A, 0x6E, + 0x4E, 0xDE, 0xA5, 0x8A, 0xDE, 0x77, 0xDD, 0x70, 0x49, 0x73, + 0xAC, 0x27, 0x6D, 0x8D, 0x25, 0x9A, 0xE4, 0x25, 0xE8, 0x95, + 0x8F, 0xFE, 0x90, 0x3B, 0x00, 0x69, 0x20, 0xE8, 0x7C, 0xA5, + 0xF5, 0x79, 0xC0, 0x61, 0x51, 0x91, 0x35, 0x25, 0x3F, 0x17, + 0x2F, 0x70, 0x73, 0xF0, 0x89, 0xB5, 0xC8, 0x25, 0xB8, 0xE5, + 0x7E, 0x34, 0xDD, 0x11, 0xE5, 0xD6, 0xC3, 0xD5, 0x29, 0x89, + 0xC6, 0x2C, 0x99, 0x53, 0x1D, 0x2C, 0x77, 0xB0, 0xB6, 0xA1, + 0xBD, 0x79, 0xFB, 0x4A, 0xC2, 0x48, 0x4C, 0x62, 0x51, 0x00, + 0xE3, 0x91, 0x2A, 0xCB, 0x84, 0x03, 0x5D, 0x2D, 0xC8, 0x33, + 0xE9, 0x14, 0xBF, 0x74, 0x21, 0xBC, 0xF4, 0x76, 0xE5, 0x42, + 0xB8, 0xBD, 0xE2, 0xE7, 0x20, 0x95, 0x54, 0xF2, 0xED, 0xC0, + 0x79, 0x38, 0x1E, 0xD2, 0xEA, 0x1A, 0x63, 0x85, 0xE7, 0x3A, + 0xDA, 0xAD, 0xAB, 0x1B, 0x1E, 0x19, 0x9E, 0x73, 0xD0, 0x10, + 0x2E, 0x38, 0xAC, 0x8B, 0x00, 0x6A, 0x30, 0x2C, 0x3D, 0x70, + 0x8E, 0x39, 0x6D, 0xC0, 0x12, 0x61, 0x7D, 0x2A, 0x0A, 0x04, + 0x95, 0x8E, 0x09, 0x3C, 0x7B, 0xEC, 0x2E, 0xBC, 0xE8, 0xE8, + 0xE8, 0x37, 0x29, 0xC4, 0x7E, 0x76, 0x48, 0xB9, 0x3B, 0x72, + 0xE5, 0x99, 0x9B, 0xF9, 0xE3, 0x99, 0x72, 0x3F, 0x35, 0x29, + 0x85, 0xE0, 0xC8, 0xBF, 0xB1, 0x6B, 0xB1, 0x6E, 0x72, 0x00, + } + + var ct = [330 + 16]byte{ + 0xFF, 0xEB, 0xEF, 0x4A, 0xC0, 0x57, 0x0F, 0x26, 0xAC, 0x76, + 0xA8, 0xB0, 0xA3, 0x5D, 0x9C, 0xD9, 0x25, 0xD1, 0x7F, 0x92, + 0x5D, 0xF4, 0x23, 0x34, 0xC3, 0x03, 0x10, 0xE1, 0xB0, 0x24, + 0x9B, 0x44, 0x58, 0x26, 0x13, 0x56, 0x83, 0x43, 0x72, 0x69, + 0x28, 0x0D, 0x55, 0x07, 0x1F, 0xDB, 0xC0, 0x23, 0x34, 0x83, + 0x1A, 0x09, 0x9B, 0x80, 0x00, 0x64, 0x56, 0xDC, 0x79, 0x7A, + 0xD2, 0xCE, 0x23, 0xC9, 0x72, 0x27, 0xFC, 0x8D, 0xAB, 0xBF, + 0xD3, 0x17, 0xF6, 0x91, 0x7B, 0x15, 0x93, 0x83, 0x8A, 0x4F, + 0x6C, 0xCA, 0x4A, 0x94, 0xDA, 0xC7, 0x9D, 0xB6, 0xD6, 0xBA, + 0xBD, 0x81, 0x9A, 0x78, 0xE5, 0xE5, 0xBE, 0x17, 0xBC, 0xCB, + 0xC8, 0x23, 0x80, 0x5F, 0x75, 0xF8, 0xDB, 0x51, 0x55, 0x00, + 0x25, 0x33, 0x52, 0x64, 0xB2, 0xD6, 0xD8, 0x9A, 0x2A, 0x9E, + 0x29, 0x99, 0x13, 0x33, 0xE2, 0xA7, 0x98, 0xAC, 0xD7, 0x79, + 0x5C, 0x2F, 0xBA, 0x07, 0xC3, 0x03, 0x37, 0xD6, 0xE6, 0xB5, + 0xA1, 0xF5, 0x29, 0xB6, 0xF6, 0xC0, 0x5C, 0x44, 0x68, 0x2B, + 0x0B, 0xF5, 0x00, 0x01, 0x44, 0xD5, 0xCC, 0x23, 0xB5, 0x27, + 0x4F, 0xCA, 0xB4, 0x05, 0x01, 0xF9, 0xD4, 0x41, 0xE0, 0xE1, + 0x1E, 0xCF, 0xA9, 0xBC, 0x79, 0xD7, 0xD5, 0xF5, 0x3C, 0xE6, + 0x93, 0xF4, 0x6C, 0x84, 0x5A, 0x2C, 0x4B, 0xE4, 0x91, 0xB2, + 0xB2, 0xB8, 0xAD, 0x74, 0x9A, 0x69, 0x79, 0x4C, 0x84, 0xB7, + 0xBF, 0xF1, 0x68, 0x4B, 0xAE, 0x0F, 0x7F, 0x45, 0x3B, 0x18, + 0x3F, 0xFA, 0x00, 0x48, 0xE0, 0x3A, 0xE2, 0xC0, 0xAE, 0x00, + 0xCE, 0x90, 0x28, 0xA4, 0x1B, 0xBE, 0xCA, 0x0C, 0x21, 0x29, + 0x64, 0x30, 0x5E, 0x35, 0xAD, 0xFD, 0x83, 0x47, 0x40, 0x6D, + 0x15, 0x56, 0xFC, 0xF8, 0x5F, 0xAB, 0x81, 0xFE, 0x6B, 0xE9, + 0x6B, 0xED, 0x27, 0x35, 0x7C, 0xD8, 0x2C, 0xD4, 0xF2, 0x11, + 0xE6, 0xAF, 0xDF, 0xB8, 0x91, 0x96, 0xEB, 0xF7, 0x4C, 0x8D, + 0x70, 0x77, 0x90, 0x81, 0x00, 0x09, 0x19, 0x27, 0x8A, 0x9E, + 0xB6, 0x1A, 0xE9, 0xAC, 0x6C, 0xC9, 0xF8, 0xEA, 0xA2, 0x34, + 0xB8, 0xAC, 0xB3, 0xB3, 0x68, 0xA1, 0xB7, 0x29, 0x55, 0xCA, + 0x40, 0x23, 0x92, 0x5C, 0x0C, 0x79, 0x6B, 0xD6, 0x9F, 0x5B, + 0xD2, 0xE6, 0xAE, 0x04, 0xCB, 0xEC, 0xC7, 0x88, 0x18, 0xDB, + 0x7A, 0xE6, 0xD6, 0xC9, 0x39, 0xFD, 0x93, 0x9B, 0xC8, 0x01, + 0x6F, 0x3E, 0x6C, 0x90, 0x3E, 0x73, 0x76, 0x99, 0x7C, 0x48, + 0xDA, 0x68, 0x48, 0x80, 0x2B, 0x63, + } + var ssExp = [16]byte{ + 0xA1, 0xF9, 0x5A, 0x67, 0xB9, 0x3D, 0x1E, 0x72, 0xE8, 0xC5, + 0x71, 0xF1, 0x4C, 0xB2, 0xAA, 0x6D, + } + + var prvObj = NewPrivateKey(KeyVariant_SIKE) + var pubObj = NewPublicKey(KeyVariant_SIKE) + + if pubObj.Import(pk[:]) != nil || prvObj.Import(sk[:]) != nil { + t.Error("Can't import one of the keys") + } + + res, _ := Decapsulate(prvObj, pubObj, ct[:]) + if !bytes.Equal(ssExp[:], res) { + t.Error("Wrong decapsulation result") + } +} + +/* ------------------------------------------------------------------------- + Benchmarking + -------------------------------------------------------------------------*/ + +func BenchmarkSidhKeyAgreement(b *testing.B) { + // KeyPairs + alicePublic := convToPub(tdata.PkA_sidh, KeyVariant_SIDH_A) + alicePrivate := convToPrv(tdata.PrA_sidh, KeyVariant_SIDH_A) + bobPublic := convToPub(tdata.PkB_sidh, KeyVariant_SIDH_B) + bobPrivate := convToPrv(tdata.PrB_sidh, KeyVariant_SIDH_B) + + for i := 0; i < b.N; i++ { + // Derive shared secret + DeriveSecret(bobPrivate, alicePublic) + DeriveSecret(alicePrivate, bobPublic) + } +} + +func BenchmarkAliceKeyGenPrv(b *testing.B) { + prv := NewPrivateKey(KeyVariant_SIDH_A) + for n := 0; n < b.N; n++ { + prv.Generate(rand.Reader) + } +} + +func BenchmarkBobKeyGenPrv(b *testing.B) { + prv := NewPrivateKey(KeyVariant_SIDH_B) + for n := 0; n < b.N; n++ { + prv.Generate(rand.Reader) + } +} + +func BenchmarkAliceKeyGenPub(b *testing.B) { + prv := NewPrivateKey(KeyVariant_SIDH_A) + prv.Generate(rand.Reader) + for n := 0; n < b.N; n++ { + prv.GeneratePublicKey() + } +} + +func BenchmarkBobKeyGenPub(b *testing.B) { + prv := NewPrivateKey(KeyVariant_SIDH_B) + prv.Generate(rand.Reader) + for n := 0; n < b.N; n++ { + prv.GeneratePublicKey() + } +} + +func BenchmarkSharedSecretAlice(b *testing.B) { + aPr := convToPrv(tdata.PrA_sidh, KeyVariant_SIDH_A) + bPk := convToPub(tdata.PkB_sike, KeyVariant_SIDH_B) + for n := 0; n < b.N; n++ { + DeriveSecret(aPr, bPk) + } +} + +func BenchmarkSharedSecretBob(b *testing.B) { + // m_B = 3*randint(0,3^238) + aPk := convToPub(tdata.PkA_sidh, KeyVariant_SIDH_A) + bPr := convToPrv(tdata.PrB_sidh, KeyVariant_SIDH_B) + for n := 0; n < b.N; n++ { + DeriveSecret(bPr, aPk) + } +} diff --git a/src/ssl/test/runner/tls.go b/src/ssl/test/runner/tls.go index 128d22e4..1862b3d8 100644 --- a/src/ssl/test/runner/tls.go +++ b/src/ssl/test/runner/tls.go @@ -276,15 +276,13 @@ func isEd25519Certificate(cert *x509.Certificate) bool { } func getCertificatePublicKey(cert *x509.Certificate) crypto.PublicKey { - if cert.PublicKey != nil { - return cert.PublicKey - } - + // TODO(davidben): When Go 1.13 is released, use the Ed25519 support in + // the standard library. if isEd25519Certificate(cert) { return ed25519.PublicKey(cert.RawSubjectPublicKeyInfo[len(ed25519SPKIPrefix):]) } - return nil + return cert.PublicKey } var ed25519PKCS8Prefix = []byte{0x30, 0x2e, 0x02, 0x01, 0x00, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x70, @@ -294,6 +292,12 @@ var ed25519PKCS8Prefix = []byte{0x30, 0x2e, 0x02, 0x01, 0x00, 0x30, 0x05, 0x06, // PKCS#1 private keys by default, while OpenSSL 1.0.0 generates PKCS#8 keys. // OpenSSL ecparam generates SEC1 EC private keys for ECDSA. We try all three. func parsePrivateKey(der []byte) (crypto.PrivateKey, error) { + // TODO(davidben): When Go 1.13 is released, use the Ed25519 support in + // the standard library. + if bytes.HasPrefix(der, ed25519PKCS8Prefix) && len(der) == len(ed25519PKCS8Prefix)+32 { + seed := der[len(ed25519PKCS8Prefix):] + return ed25519.NewKeyFromSeed(seed), nil + } if key, err := x509.ParsePKCS1PrivateKey(der); err == nil { return key, nil } @@ -309,10 +313,5 @@ func parsePrivateKey(der []byte) (crypto.PrivateKey, error) { return key, nil } - if bytes.HasPrefix(der, ed25519PKCS8Prefix) && len(der) == len(ed25519PKCS8Prefix)+32 { - seed := der[len(ed25519PKCS8Prefix):] - return ed25519.NewKeyFromSeed(seed), nil - } - return nil, errors.New("crypto/tls: failed to parse private key") } diff --git a/src/ssl/test/test_config.cc b/src/ssl/test/test_config.cc index 70e061b0..bd32ce9d 100644 --- a/src/ssl/test/test_config.cc +++ b/src/ssl/test/test_config.cc @@ -51,181 +51,181 @@ T *FindField(TestConfig *config, const Flag<T> (&flags)[N], const char *flag) { } const Flag<bool> kBoolFlags[] = { - { "-server", &TestConfig::is_server }, - { "-dtls", &TestConfig::is_dtls }, - { "-fallback-scsv", &TestConfig::fallback_scsv }, - { "-require-any-client-certificate", - &TestConfig::require_any_client_certificate }, - { "-false-start", &TestConfig::false_start }, - { "-async", &TestConfig::async }, - { "-write-different-record-sizes", - &TestConfig::write_different_record_sizes }, - { "-cbc-record-splitting", &TestConfig::cbc_record_splitting }, - { "-partial-write", &TestConfig::partial_write }, - { "-no-tls13", &TestConfig::no_tls13 }, - { "-no-tls12", &TestConfig::no_tls12 }, - { "-no-tls11", &TestConfig::no_tls11 }, - { "-no-tls1", &TestConfig::no_tls1 }, - { "-no-ticket", &TestConfig::no_ticket }, - { "-enable-channel-id", &TestConfig::enable_channel_id }, - { "-shim-writes-first", &TestConfig::shim_writes_first }, - { "-expect-session-miss", &TestConfig::expect_session_miss }, - { "-decline-alpn", &TestConfig::decline_alpn }, - { "-select-empty-alpn", &TestConfig::select_empty_alpn }, - { "-expect-extended-master-secret", - &TestConfig::expect_extended_master_secret }, - { "-enable-ocsp-stapling", &TestConfig::enable_ocsp_stapling }, - { "-enable-signed-cert-timestamps", - &TestConfig::enable_signed_cert_timestamps }, - { "-implicit-handshake", &TestConfig::implicit_handshake }, - { "-use-early-callback", &TestConfig::use_early_callback }, - { "-fail-early-callback", &TestConfig::fail_early_callback }, - { "-install-ddos-callback", &TestConfig::install_ddos_callback }, - { "-fail-ddos-callback", &TestConfig::fail_ddos_callback }, - { "-fail-cert-callback", &TestConfig::fail_cert_callback }, - { "-handshake-never-done", &TestConfig::handshake_never_done }, - { "-use-export-context", &TestConfig::use_export_context }, - { "-tls-unique", &TestConfig::tls_unique }, - { "-expect-ticket-renewal", &TestConfig::expect_ticket_renewal }, - { "-expect-no-session", &TestConfig::expect_no_session }, - { "-expect-ticket-supports-early-data", - &TestConfig::expect_ticket_supports_early_data }, - { "-use-ticket-callback", &TestConfig::use_ticket_callback }, - { "-renew-ticket", &TestConfig::renew_ticket }, - { "-enable-early-data", &TestConfig::enable_early_data }, - { "-check-close-notify", &TestConfig::check_close_notify }, - { "-shim-shuts-down", &TestConfig::shim_shuts_down }, - { "-verify-fail", &TestConfig::verify_fail }, - { "-verify-peer", &TestConfig::verify_peer }, - { "-verify-peer-if-no-obc", &TestConfig::verify_peer_if_no_obc }, - { "-expect-verify-result", &TestConfig::expect_verify_result }, - { "-renegotiate-once", &TestConfig::renegotiate_once }, - { "-renegotiate-freely", &TestConfig::renegotiate_freely }, - { "-renegotiate-ignore", &TestConfig::renegotiate_ignore }, - { "-forbid-renegotiation-after-handshake", - &TestConfig::forbid_renegotiation_after_handshake }, - { "-enable-all-curves", &TestConfig::enable_all_curves }, - { "-use-old-client-cert-callback", - &TestConfig::use_old_client_cert_callback }, - { "-send-alert", &TestConfig::send_alert }, - { "-peek-then-read", &TestConfig::peek_then_read }, - { "-enable-grease", &TestConfig::enable_grease }, - { "-use-exporter-between-reads", &TestConfig::use_exporter_between_reads }, - { "-retain-only-sha256-client-cert", - &TestConfig::retain_only_sha256_client_cert }, - { "-expect-sha256-client-cert", - &TestConfig::expect_sha256_client_cert }, - { "-read-with-unfinished-write", &TestConfig::read_with_unfinished_write }, - { "-expect-secure-renegotiation", - &TestConfig::expect_secure_renegotiation }, - { "-expect-no-secure-renegotiation", - &TestConfig::expect_no_secure_renegotiation }, - { "-expect-session-id", &TestConfig::expect_session_id }, - { "-expect-no-session-id", &TestConfig::expect_no_session_id }, - { "-expect-accept-early-data", &TestConfig::expect_accept_early_data }, - { "-expect-reject-early-data", &TestConfig::expect_reject_early_data }, - { "-expect-no-offer-early-data", &TestConfig::expect_no_offer_early_data }, - { "-no-op-extra-handshake", &TestConfig::no_op_extra_handshake }, - { "-handshake-twice", &TestConfig::handshake_twice }, - { "-allow-unknown-alpn-protos", &TestConfig::allow_unknown_alpn_protos }, - { "-enable-ed25519", &TestConfig::enable_ed25519 }, - { "-use-custom-verify-callback", &TestConfig::use_custom_verify_callback }, - { "-allow-false-start-without-alpn", - &TestConfig::allow_false_start_without_alpn }, - { "-ignore-tls13-downgrade", &TestConfig::ignore_tls13_downgrade }, - { "-expect-tls13-downgrade", &TestConfig::expect_tls13_downgrade }, - { "-handoff", &TestConfig::handoff }, - { "-no-rsa-pss-rsae-certs", &TestConfig::no_rsa_pss_rsae_certs }, - { "-use-ocsp-callback", &TestConfig::use_ocsp_callback }, - { "-set-ocsp-in-callback", &TestConfig::set_ocsp_in_callback }, - { "-decline-ocsp-callback", &TestConfig::decline_ocsp_callback }, - { "-fail-ocsp-callback", &TestConfig::fail_ocsp_callback }, - { "-install-cert-compression-algs", - &TestConfig::install_cert_compression_algs }, - { "-is-handshaker-supported", &TestConfig::is_handshaker_supported }, - { "-handshaker-resume", &TestConfig::handshaker_resume }, - { "-reverify-on-resume", &TestConfig::reverify_on_resume }, - { "-enforce-rsa-key-usage", &TestConfig::enforce_rsa_key_usage }, - { "-jdk11-workaround", &TestConfig::jdk11_workaround }, - { "-server-preference", &TestConfig::server_preference }, - { "-export-traffic-secrets", &TestConfig::export_traffic_secrets }, - { "-key-update", &TestConfig::key_update }, + {"-server", &TestConfig::is_server}, + {"-dtls", &TestConfig::is_dtls}, + {"-fallback-scsv", &TestConfig::fallback_scsv}, + {"-require-any-client-certificate", + &TestConfig::require_any_client_certificate}, + {"-false-start", &TestConfig::false_start}, + {"-async", &TestConfig::async}, + {"-write-different-record-sizes", + &TestConfig::write_different_record_sizes}, + {"-cbc-record-splitting", &TestConfig::cbc_record_splitting}, + {"-partial-write", &TestConfig::partial_write}, + {"-no-tls13", &TestConfig::no_tls13}, + {"-no-tls12", &TestConfig::no_tls12}, + {"-no-tls11", &TestConfig::no_tls11}, + {"-no-tls1", &TestConfig::no_tls1}, + {"-no-ticket", &TestConfig::no_ticket}, + {"-enable-channel-id", &TestConfig::enable_channel_id}, + {"-shim-writes-first", &TestConfig::shim_writes_first}, + {"-expect-session-miss", &TestConfig::expect_session_miss}, + {"-decline-alpn", &TestConfig::decline_alpn}, + {"-select-empty-alpn", &TestConfig::select_empty_alpn}, + {"-expect-extended-master-secret", + &TestConfig::expect_extended_master_secret}, + {"-enable-ocsp-stapling", &TestConfig::enable_ocsp_stapling}, + {"-enable-signed-cert-timestamps", + &TestConfig::enable_signed_cert_timestamps}, + {"-implicit-handshake", &TestConfig::implicit_handshake}, + {"-use-early-callback", &TestConfig::use_early_callback}, + {"-fail-early-callback", &TestConfig::fail_early_callback}, + {"-install-ddos-callback", &TestConfig::install_ddos_callback}, + {"-fail-ddos-callback", &TestConfig::fail_ddos_callback}, + {"-fail-cert-callback", &TestConfig::fail_cert_callback}, + {"-handshake-never-done", &TestConfig::handshake_never_done}, + {"-use-export-context", &TestConfig::use_export_context}, + {"-tls-unique", &TestConfig::tls_unique}, + {"-expect-ticket-renewal", &TestConfig::expect_ticket_renewal}, + {"-expect-no-session", &TestConfig::expect_no_session}, + {"-expect-ticket-supports-early-data", + &TestConfig::expect_ticket_supports_early_data}, + {"-use-ticket-callback", &TestConfig::use_ticket_callback}, + {"-renew-ticket", &TestConfig::renew_ticket}, + {"-enable-early-data", &TestConfig::enable_early_data}, + {"-check-close-notify", &TestConfig::check_close_notify}, + {"-shim-shuts-down", &TestConfig::shim_shuts_down}, + {"-verify-fail", &TestConfig::verify_fail}, + {"-verify-peer", &TestConfig::verify_peer}, + {"-verify-peer-if-no-obc", &TestConfig::verify_peer_if_no_obc}, + {"-expect-verify-result", &TestConfig::expect_verify_result}, + {"-renegotiate-once", &TestConfig::renegotiate_once}, + {"-renegotiate-freely", &TestConfig::renegotiate_freely}, + {"-renegotiate-ignore", &TestConfig::renegotiate_ignore}, + {"-forbid-renegotiation-after-handshake", + &TestConfig::forbid_renegotiation_after_handshake}, + {"-enable-all-curves", &TestConfig::enable_all_curves}, + {"-use-old-client-cert-callback", + &TestConfig::use_old_client_cert_callback}, + {"-send-alert", &TestConfig::send_alert}, + {"-peek-then-read", &TestConfig::peek_then_read}, + {"-enable-grease", &TestConfig::enable_grease}, + {"-use-exporter-between-reads", &TestConfig::use_exporter_between_reads}, + {"-retain-only-sha256-client-cert", + &TestConfig::retain_only_sha256_client_cert}, + {"-expect-sha256-client-cert", &TestConfig::expect_sha256_client_cert}, + {"-read-with-unfinished-write", &TestConfig::read_with_unfinished_write}, + {"-expect-secure-renegotiation", &TestConfig::expect_secure_renegotiation}, + {"-expect-no-secure-renegotiation", + &TestConfig::expect_no_secure_renegotiation}, + {"-expect-session-id", &TestConfig::expect_session_id}, + {"-expect-no-session-id", &TestConfig::expect_no_session_id}, + {"-expect-accept-early-data", &TestConfig::expect_accept_early_data}, + {"-expect-reject-early-data", &TestConfig::expect_reject_early_data}, + {"-expect-no-offer-early-data", &TestConfig::expect_no_offer_early_data}, + {"-no-op-extra-handshake", &TestConfig::no_op_extra_handshake}, + {"-handshake-twice", &TestConfig::handshake_twice}, + {"-allow-unknown-alpn-protos", &TestConfig::allow_unknown_alpn_protos}, + {"-enable-ed25519", &TestConfig::enable_ed25519}, + {"-use-custom-verify-callback", &TestConfig::use_custom_verify_callback}, + {"-allow-false-start-without-alpn", + &TestConfig::allow_false_start_without_alpn}, + {"-ignore-tls13-downgrade", &TestConfig::ignore_tls13_downgrade}, + {"-expect-tls13-downgrade", &TestConfig::expect_tls13_downgrade}, + {"-handoff", &TestConfig::handoff}, + {"-no-rsa-pss-rsae-certs", &TestConfig::no_rsa_pss_rsae_certs}, + {"-use-ocsp-callback", &TestConfig::use_ocsp_callback}, + {"-set-ocsp-in-callback", &TestConfig::set_ocsp_in_callback}, + {"-decline-ocsp-callback", &TestConfig::decline_ocsp_callback}, + {"-fail-ocsp-callback", &TestConfig::fail_ocsp_callback}, + {"-install-cert-compression-algs", + &TestConfig::install_cert_compression_algs}, + {"-is-handshaker-supported", &TestConfig::is_handshaker_supported}, + {"-handshaker-resume", &TestConfig::handshaker_resume}, + {"-reverify-on-resume", &TestConfig::reverify_on_resume}, + {"-enforce-rsa-key-usage", &TestConfig::enforce_rsa_key_usage}, + {"-jdk11-workaround", &TestConfig::jdk11_workaround}, + {"-server-preference", &TestConfig::server_preference}, + {"-export-traffic-secrets", &TestConfig::export_traffic_secrets}, + {"-key-update", &TestConfig::key_update}, + {"-expect-delegated-credential-used", + &TestConfig::expect_delegated_credential_used}, + {"-enable-pq-experiment-signal", &TestConfig::enable_pq_experiment_signal}, + {"-expect-pq-experiment-signal", &TestConfig::expect_pq_experiment_signal}, }; const Flag<std::string> kStringFlags[] = { - { "-write-settings", &TestConfig::write_settings }, - { "-key-file", &TestConfig::key_file }, - { "-cert-file", &TestConfig::cert_file }, - { "-expect-server-name", &TestConfig::expected_server_name }, - { "-advertise-npn", &TestConfig::advertise_npn }, - { "-expect-next-proto", &TestConfig::expected_next_proto }, - { "-select-next-proto", &TestConfig::select_next_proto }, - { "-send-channel-id", &TestConfig::send_channel_id }, - { "-host-name", &TestConfig::host_name }, - { "-advertise-alpn", &TestConfig::advertise_alpn }, - { "-expect-alpn", &TestConfig::expected_alpn }, - { "-expect-late-alpn", &TestConfig::expected_late_alpn }, - { "-expect-advertised-alpn", &TestConfig::expected_advertised_alpn }, - { "-select-alpn", &TestConfig::select_alpn }, - { "-psk", &TestConfig::psk }, - { "-psk-identity", &TestConfig::psk_identity }, - { "-srtp-profiles", &TestConfig::srtp_profiles }, - { "-cipher", &TestConfig::cipher }, - { "-export-label", &TestConfig::export_label }, - { "-export-context", &TestConfig::export_context }, - { "-expect-peer-cert-file", &TestConfig::expect_peer_cert_file }, - { "-use-client-ca-list", &TestConfig::use_client_ca_list }, - { "-expect-client-ca-list", &TestConfig::expected_client_ca_list }, - { "-expect-msg-callback", &TestConfig::expect_msg_callback }, - { "-handshaker-path", &TestConfig::handshaker_path }, - { "-delegated-credential", &TestConfig::delegated_credential }, + {"-write-settings", &TestConfig::write_settings}, + {"-key-file", &TestConfig::key_file}, + {"-cert-file", &TestConfig::cert_file}, + {"-expect-server-name", &TestConfig::expect_server_name}, + {"-advertise-npn", &TestConfig::advertise_npn}, + {"-expect-next-proto", &TestConfig::expect_next_proto}, + {"-select-next-proto", &TestConfig::select_next_proto}, + {"-send-channel-id", &TestConfig::send_channel_id}, + {"-host-name", &TestConfig::host_name}, + {"-advertise-alpn", &TestConfig::advertise_alpn}, + {"-expect-alpn", &TestConfig::expect_alpn}, + {"-expect-late-alpn", &TestConfig::expect_late_alpn}, + {"-expect-advertised-alpn", &TestConfig::expect_advertised_alpn}, + {"-select-alpn", &TestConfig::select_alpn}, + {"-psk", &TestConfig::psk}, + {"-psk-identity", &TestConfig::psk_identity}, + {"-srtp-profiles", &TestConfig::srtp_profiles}, + {"-cipher", &TestConfig::cipher}, + {"-export-label", &TestConfig::export_label}, + {"-export-context", &TestConfig::export_context}, + {"-expect-peer-cert-file", &TestConfig::expect_peer_cert_file}, + {"-use-client-ca-list", &TestConfig::use_client_ca_list}, + {"-expect-client-ca-list", &TestConfig::expect_client_ca_list}, + {"-expect-msg-callback", &TestConfig::expect_msg_callback}, + {"-handshaker-path", &TestConfig::handshaker_path}, + {"-delegated-credential", &TestConfig::delegated_credential}, + {"-expect-early-data-reason", &TestConfig::expect_early_data_reason}, }; const Flag<std::string> kBase64Flags[] = { - { "-expect-certificate-types", &TestConfig::expected_certificate_types }, - { "-expect-channel-id", &TestConfig::expected_channel_id }, - { "-token-binding-params", &TestConfig::send_token_binding_params }, - { "-expect-ocsp-response", &TestConfig::expected_ocsp_response }, - { "-expect-signed-cert-timestamps", - &TestConfig::expected_signed_cert_timestamps }, - { "-ocsp-response", &TestConfig::ocsp_response }, - { "-signed-cert-timestamps", &TestConfig::signed_cert_timestamps }, - { "-ticket-key", &TestConfig::ticket_key }, - { "-quic-transport-params", &TestConfig::quic_transport_params }, - { "-expected-quic-transport-params", - &TestConfig::expected_quic_transport_params }, + {"-expect-certificate-types", &TestConfig::expect_certificate_types}, + {"-expect-channel-id", &TestConfig::expect_channel_id}, + {"-token-binding-params", &TestConfig::send_token_binding_params}, + {"-expect-ocsp-response", &TestConfig::expect_ocsp_response}, + {"-expect-signed-cert-timestamps", + &TestConfig::expect_signed_cert_timestamps}, + {"-ocsp-response", &TestConfig::ocsp_response}, + {"-signed-cert-timestamps", &TestConfig::signed_cert_timestamps}, + {"-ticket-key", &TestConfig::ticket_key}, + {"-quic-transport-params", &TestConfig::quic_transport_params}, + {"-expect-quic-transport-params", + &TestConfig::expect_quic_transport_params}, }; const Flag<int> kIntFlags[] = { - { "-port", &TestConfig::port }, - { "-resume-count", &TestConfig::resume_count }, - { "-expected-token-binding-param", - &TestConfig::expected_token_binding_param }, - { "-min-version", &TestConfig::min_version }, - { "-max-version", &TestConfig::max_version }, - { "-expect-version", &TestConfig::expect_version }, - { "-mtu", &TestConfig::mtu }, - { "-export-early-keying-material", - &TestConfig::export_early_keying_material }, - { "-export-keying-material", &TestConfig::export_keying_material }, - { "-expect-total-renegotiations", &TestConfig::expect_total_renegotiations }, - { "-expect-peer-signature-algorithm", - &TestConfig::expect_peer_signature_algorithm }, - { "-expect-curve-id", &TestConfig::expect_curve_id }, - { "-initial-timeout-duration-ms", &TestConfig::initial_timeout_duration_ms }, - { "-max-cert-list", &TestConfig::max_cert_list }, - { "-expect-cipher-aes", &TestConfig::expect_cipher_aes }, - { "-expect-cipher-no-aes", &TestConfig::expect_cipher_no_aes }, - { "-resumption-delay", &TestConfig::resumption_delay }, - { "-max-send-fragment", &TestConfig::max_send_fragment }, - { "-read-size", &TestConfig::read_size }, - { "-expect-ticket-age-skew", &TestConfig::expect_ticket_age_skew }, + {"-port", &TestConfig::port}, + {"-resume-count", &TestConfig::resume_count}, + {"-expect-token-binding-param", &TestConfig::expect_token_binding_param}, + {"-min-version", &TestConfig::min_version}, + {"-max-version", &TestConfig::max_version}, + {"-expect-version", &TestConfig::expect_version}, + {"-mtu", &TestConfig::mtu}, + {"-export-keying-material", &TestConfig::export_keying_material}, + {"-expect-total-renegotiations", &TestConfig::expect_total_renegotiations}, + {"-expect-peer-signature-algorithm", + &TestConfig::expect_peer_signature_algorithm}, + {"-expect-curve-id", &TestConfig::expect_curve_id}, + {"-initial-timeout-duration-ms", &TestConfig::initial_timeout_duration_ms}, + {"-max-cert-list", &TestConfig::max_cert_list}, + {"-expect-cipher-aes", &TestConfig::expect_cipher_aes}, + {"-expect-cipher-no-aes", &TestConfig::expect_cipher_no_aes}, + {"-resumption-delay", &TestConfig::resumption_delay}, + {"-max-send-fragment", &TestConfig::max_send_fragment}, + {"-read-size", &TestConfig::read_size}, + {"-expect-ticket-age-skew", &TestConfig::expect_ticket_age_skew}, }; const Flag<std::vector<int>> kIntVectorFlags[] = { {"-signing-prefs", &TestConfig::signing_prefs}, {"-verify-prefs", &TestConfig::verify_prefs}, - {"-expect-peer-verify-pref", &TestConfig::expected_peer_verify_prefs}, + {"-expect-peer-verify-pref", &TestConfig::expect_peer_verify_prefs}, {"-curves", &TestConfig::curves}, }; @@ -243,7 +243,7 @@ bool ParseFlag(char *flag, int argc, char **argv, int *i, if (string_field != NULL) { *i = *i + 1; if (*i >= argc) { - fprintf(stderr, "Missing parameter\n"); + fprintf(stderr, "Missing parameter.\n"); return false; } if (!skip) { @@ -256,19 +256,19 @@ bool ParseFlag(char *flag, int argc, char **argv, int *i, if (base64_field != NULL) { *i = *i + 1; if (*i >= argc) { - fprintf(stderr, "Missing parameter\n"); + fprintf(stderr, "Missing parameter.\n"); return false; } size_t len; if (!EVP_DecodedLength(&len, strlen(argv[*i]))) { - fprintf(stderr, "Invalid base64: %s\n", argv[*i]); + fprintf(stderr, "Invalid base64: %s.\n", argv[*i]); return false; } std::unique_ptr<uint8_t[]> decoded(new uint8_t[len]); if (!EVP_DecodeBase64(decoded.get(), &len, len, reinterpret_cast<const uint8_t *>(argv[*i]), strlen(argv[*i]))) { - fprintf(stderr, "Invalid base64: %s\n", argv[*i]); + fprintf(stderr, "Invalid base64: %s.\n", argv[*i]); return false; } if (!skip) { @@ -282,7 +282,7 @@ bool ParseFlag(char *flag, int argc, char **argv, int *i, if (int_field) { *i = *i + 1; if (*i >= argc) { - fprintf(stderr, "Missing parameter\n"); + fprintf(stderr, "Missing parameter.\n"); return false; } if (!skip) { @@ -296,7 +296,7 @@ bool ParseFlag(char *flag, int argc, char **argv, int *i, if (int_vector_field) { *i = *i + 1; if (*i >= argc) { - fprintf(stderr, "Missing parameter\n"); + fprintf(stderr, "Missing parameter.\n"); return false; } @@ -307,7 +307,7 @@ bool ParseFlag(char *flag, int argc, char **argv, int *i, return true; } - fprintf(stderr, "Unknown argument: %s\n", flag); + fprintf(stderr, "Unknown argument: %s.\n", flag); return false; } @@ -403,9 +403,9 @@ static int ServerNameCallback(SSL *ssl, int *out_alert, void *arg) { const TestConfig *config = GetTestConfig(ssl); const char *server_name = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name); if (server_name == nullptr || - std::string(server_name) != config->expected_server_name) { - fprintf(stderr, "servername mismatch (got %s; want %s)\n", server_name, - config->expected_server_name.c_str()); + std::string(server_name) != config->expect_server_name) { + fprintf(stderr, "servername mismatch (got %s; want %s).\n", server_name, + config->expect_server_name.c_str()); return SSL_TLSEXT_ERR_ALERT_FATAL; } @@ -449,7 +449,7 @@ static void MessageCallback(int is_write, int version, int content_type, if (content_type == SSL3_RT_HEADER) { if (len != (config->is_dtls ? DTLS1_RT_HEADER_LENGTH : SSL3_RT_HEADER_LENGTH)) { - fprintf(stderr, "Incorrect length for record header: %zu\n", len); + fprintf(stderr, "Incorrect length for record header: %zu.\n", len); state->msg_callback_ok = false; } return; @@ -459,7 +459,7 @@ static void MessageCallback(int is_write, int version, int content_type, switch (content_type) { case 0: if (version != SSL2_VERSION) { - fprintf(stderr, "Incorrect version for V2ClientHello: %x\n", version); + fprintf(stderr, "Incorrect version for V2ClientHello: %x.\n", version); state->msg_callback_ok = false; return; } @@ -509,7 +509,7 @@ static void MessageCallback(int is_write, int version, int content_type, return; default: - fprintf(stderr, "Invalid content_type: %d\n", content_type); + fprintf(stderr, "Invalid content_type: %d.\n", content_type); state->msg_callback_ok = false; } } @@ -618,11 +618,11 @@ static int AlpnSelectCallback(SSL *ssl, const uint8_t **out, uint8_t *outlen, return SSL_TLSEXT_ERR_NOACK; } - if (!config->expected_advertised_alpn.empty() && - (config->expected_advertised_alpn.size() != inlen || - OPENSSL_memcmp(config->expected_advertised_alpn.data(), in, inlen) != + if (!config->expect_advertised_alpn.empty() && + (config->expect_advertised_alpn.size() != inlen || + OPENSSL_memcmp(config->expect_advertised_alpn.data(), in, inlen) != 0)) { - fprintf(stderr, "bad ALPN select callback inputs\n"); + fprintf(stderr, "bad ALPN select callback inputs.\n"); exit(1); } @@ -634,12 +634,12 @@ static int AlpnSelectCallback(SSL *ssl, const uint8_t **out, uint8_t *outlen, static bool CheckVerifyCallback(SSL *ssl) { const TestConfig *config = GetTestConfig(ssl); - if (!config->expected_ocsp_response.empty()) { + if (!config->expect_ocsp_response.empty()) { const uint8_t *data; size_t len; SSL_get0_ocsp_response(ssl, &data, &len); if (len == 0) { - fprintf(stderr, "OCSP response not available in verify callback\n"); + fprintf(stderr, "OCSP response not available in verify callback.\n"); return false; } } @@ -808,7 +808,7 @@ static std::vector<std::string> DecodeHexStrings( for (const auto &part : parts) { std::string binary; if (!HexDecode(&binary, part)) { - fprintf(stderr, "Bad hex string: %s\n", part.c_str()); + fprintf(stderr, "Bad hex string: %s.\n", part.c_str()); return ret; } @@ -847,22 +847,22 @@ static bssl::UniquePtr<STACK_OF(X509_NAME)> DecodeHexX509Names( static bool CheckPeerVerifyPrefs(SSL *ssl) { const TestConfig *config = GetTestConfig(ssl); - if (!config->expected_peer_verify_prefs.empty()) { + if (!config->expect_peer_verify_prefs.empty()) { const uint16_t *peer_sigalgs; size_t num_peer_sigalgs = SSL_get0_peer_verify_algorithms(ssl, &peer_sigalgs); - if (config->expected_peer_verify_prefs.size() != num_peer_sigalgs) { + if (config->expect_peer_verify_prefs.size() != num_peer_sigalgs) { fprintf(stderr, "peer verify preferences length mismatch (got %zu, wanted %zu)\n", - num_peer_sigalgs, config->expected_peer_verify_prefs.size()); + num_peer_sigalgs, config->expect_peer_verify_prefs.size()); return false; } for (size_t i = 0; i < num_peer_sigalgs; i++) { if (static_cast<int>(peer_sigalgs[i]) != - config->expected_peer_verify_prefs[i]) { + config->expect_peer_verify_prefs[i]) { fprintf(stderr, "peer verify preference %zu mismatch (got %04x, wanted %04x\n", - i, peer_sigalgs[i], config->expected_peer_verify_prefs[i]); + i, peer_sigalgs[i], config->expect_peer_verify_prefs[i]); return false; } } @@ -877,29 +877,29 @@ static bool CheckCertificateRequest(SSL *ssl) { return false; } - if (!config->expected_certificate_types.empty()) { + if (!config->expect_certificate_types.empty()) { const uint8_t *certificate_types; size_t certificate_types_len = SSL_get0_certificate_types(ssl, &certificate_types); - if (certificate_types_len != config->expected_certificate_types.size() || + if (certificate_types_len != config->expect_certificate_types.size() || OPENSSL_memcmp(certificate_types, - config->expected_certificate_types.data(), + config->expect_certificate_types.data(), certificate_types_len) != 0) { - fprintf(stderr, "certificate types mismatch\n"); + fprintf(stderr, "certificate types mismatch.\n"); return false; } } - if (!config->expected_client_ca_list.empty()) { + if (!config->expect_client_ca_list.empty()) { bssl::UniquePtr<STACK_OF(X509_NAME)> expected = - DecodeHexX509Names(config->expected_client_ca_list); + DecodeHexX509Names(config->expect_client_ca_list); const size_t num_expected = sk_X509_NAME_num(expected.get()); const STACK_OF(X509_NAME) *received = SSL_get_client_CA_list(ssl); const size_t num_received = sk_X509_NAME_num(received); if (num_received != num_expected) { - fprintf(stderr, "expected %u names in CertificateRequest but got %u\n", + fprintf(stderr, "expected %u names in CertificateRequest but got %u.\n", static_cast<unsigned>(num_expected), static_cast<unsigned>(num_received)); return false; @@ -908,7 +908,7 @@ static bool CheckCertificateRequest(SSL *ssl) { for (size_t i = 0; i < num_received; i++) { if (X509_NAME_cmp(sk_X509_NAME_value(received, i), sk_X509_NAME_value(expected.get(), i)) != 0) { - fprintf(stderr, "names in CertificateRequest differ at index #%d\n", + fprintf(stderr, "names in CertificateRequest differ at index #%d.\n", static_cast<unsigned>(i)); return false; } @@ -1099,35 +1099,16 @@ static enum ssl_select_cert_result_t SelectCertificateCallback( const TestConfig *config = GetTestConfig(client_hello->ssl); GetTestState(client_hello->ssl)->early_callback_called = true; - if (!config->expected_server_name.empty()) { - const uint8_t *extension_data; - size_t extension_len; - CBS extension, server_name_list, host_name; - uint8_t name_type; - - if (!SSL_early_callback_ctx_extension_get( - client_hello, TLSEXT_TYPE_server_name, &extension_data, - &extension_len)) { - fprintf(stderr, "Could not find server_name extension.\n"); - return ssl_select_cert_error; - } - - CBS_init(&extension, extension_data, extension_len); - if (!CBS_get_u16_length_prefixed(&extension, &server_name_list) || - CBS_len(&extension) != 0 || - !CBS_get_u8(&server_name_list, &name_type) || - name_type != TLSEXT_NAMETYPE_host_name || - !CBS_get_u16_length_prefixed(&server_name_list, &host_name) || - CBS_len(&server_name_list) != 0) { - fprintf(stderr, "Could not decode server_name extension.\n"); + if (!config->expect_server_name.empty()) { + const char *server_name = + SSL_get_servername(client_hello->ssl, TLSEXT_NAMETYPE_host_name); + if (server_name == nullptr || + std::string(server_name) != config->expect_server_name) { + fprintf(stderr, + "Server name mismatch in early callback (got %s; want %s).\n", + server_name, config->expect_server_name.c_str()); return ssl_select_cert_error; } - - if (!CBS_mem_equal(&host_name, - (const uint8_t *)config->expected_server_name.data(), - config->expected_server_name.size())) { - fprintf(stderr, "Server name mismatch.\n"); - } } if (config->fail_early_callback) { @@ -1240,7 +1221,7 @@ bssl::UniquePtr<SSL_CTX> TestConfig::SetupCtx(SSL_CTX *old_ctx) const { SSL_CTX_set_grease_enabled(ssl_ctx.get(), 1); } - if (!expected_server_name.empty()) { + if (!expect_server_name.empty()) { SSL_CTX_set_tlsext_servername_callback(ssl_ctx.get(), ServerNameCallback); } @@ -1344,6 +1325,10 @@ bssl::UniquePtr<SSL_CTX> TestConfig::SetupCtx(SSL_CTX *old_ctx) const { SSL_CTX_set_options(ssl_ctx.get(), SSL_OP_CIPHER_SERVER_PREFERENCE); } + if (enable_pq_experiment_signal) { + SSL_CTX_enable_pq_experiment_signal(ssl_ctx.get()); + } + return ssl_ctx; } @@ -1371,7 +1356,7 @@ static unsigned PskClientCallback(SSL *ssl, const char *hint, // Account for the trailing '\0' for the identity. if (config->psk_identity.size() >= max_identity_len || config->psk.size() > max_psk_len) { - fprintf(stderr, "PSK buffers too small\n"); + fprintf(stderr, "PSK buffers too small.\n"); return 0; } @@ -1390,7 +1375,7 @@ static unsigned PskServerCallback(SSL *ssl, const char *identity, } if (config->psk.size() > max_psk_len) { - fprintf(stderr, "PSK buffers too small\n"); + fprintf(stderr, "PSK buffers too small.\n"); return 0; } @@ -1520,7 +1505,7 @@ bssl::UniquePtr<SSL> TestConfig::NewSSL( if (no_ticket) { SSL_set_options(ssl.get(), SSL_OP_NO_TICKET); } - if (!expected_channel_id.empty() || enable_channel_id) { + if (!expect_channel_id.empty() || enable_channel_id) { SSL_set_tls_channel_id_enabled(ssl.get(), 1); } if (!send_channel_id.empty()) { @@ -1622,6 +1607,9 @@ bssl::UniquePtr<SSL> TestConfig::NewSSL( case SSL_CURVE_CECPQ2: nids.push_back(NID_CECPQ2); break; + case SSL_CURVE_CECPQ2b: + nids.push_back(NID_CECPQ2b); + break; } if (!SSL_set1_curves(ssl.get(), &nids[0], nids.size())) { return nullptr; @@ -1630,8 +1618,8 @@ bssl::UniquePtr<SSL> TestConfig::NewSSL( } if (enable_all_curves) { static const int kAllCurves[] = { - NID_secp224r1, NID_X9_62_prime256v1, NID_secp384r1, - NID_secp521r1, NID_X25519, NID_CECPQ2, + NID_secp224r1, NID_X9_62_prime256v1, NID_secp384r1, NID_secp521r1, + NID_X25519, NID_CECPQ2, NID_CECPQ2b, }; if (!SSL_set1_curves(ssl.get(), kAllCurves, OPENSSL_ARRAY_SIZE(kAllCurves))) { @@ -1678,7 +1666,8 @@ bssl::UniquePtr<SSL> TestConfig::NewSSL( if (!delegated_credential.empty()) { std::string::size_type comma = delegated_credential.find(','); if (comma == std::string::npos) { - fprintf(stderr, "failed to find comma in delegated credential argument"); + fprintf(stderr, + "failed to find comma in delegated credential argument.\n"); return nullptr; } @@ -1686,7 +1675,7 @@ bssl::UniquePtr<SSL> TestConfig::NewSSL( const std::string pkcs8_hex = delegated_credential.substr(comma + 1); std::string dc, pkcs8; if (!HexDecode(&dc, dc_hex) || !HexDecode(&pkcs8, pkcs8_hex)) { - fprintf(stderr, "failed to hex decode delegated credential argument"); + fprintf(stderr, "failed to hex decode delegated credential argument.\n"); return nullptr; } @@ -1697,7 +1686,7 @@ bssl::UniquePtr<SSL> TestConfig::NewSSL( bssl::UniquePtr<EVP_PKEY> priv(EVP_parse_private_key(&pkcs8_cbs)); if (!priv) { - fprintf(stderr, "failed to parse delegated credential private key"); + fprintf(stderr, "failed to parse delegated credential private key.\n"); return nullptr; } diff --git a/src/ssl/test/test_config.h b/src/ssl/test/test_config.h index 9221d6f1..ce4b4164 100644 --- a/src/ssl/test/test_config.h +++ b/src/ssl/test/test_config.h @@ -32,15 +32,15 @@ struct TestConfig { bool fallback_scsv = false; std::vector<int> signing_prefs; std::vector<int> verify_prefs; - std::vector<int> expected_peer_verify_prefs; + std::vector<int> expect_peer_verify_prefs; std::vector<int> curves; std::string key_file; std::string cert_file; - std::string expected_server_name; - std::string expected_certificate_types; + std::string expect_server_name; + std::string expect_certificate_types; bool require_any_client_certificate = false; std::string advertise_npn; - std::string expected_next_proto; + std::string expect_next_proto; bool false_start = false; std::string select_next_proto; bool async = false; @@ -52,31 +52,31 @@ struct TestConfig { bool no_tls11 = false; bool no_tls1 = false; bool no_ticket = false; - std::string expected_channel_id; + std::string expect_channel_id; bool enable_channel_id = false; std::string send_channel_id; - int expected_token_binding_param = -1; + int expect_token_binding_param = -1; std::string send_token_binding_params; bool shim_writes_first = false; std::string host_name; std::string advertise_alpn; - std::string expected_alpn; - std::string expected_late_alpn; - std::string expected_advertised_alpn; + std::string expect_alpn; + std::string expect_late_alpn; + std::string expect_advertised_alpn; std::string select_alpn; bool decline_alpn = false; bool select_empty_alpn = false; std::string quic_transport_params; - std::string expected_quic_transport_params; + std::string expect_quic_transport_params; bool expect_session_miss = false; bool expect_extended_master_secret = false; std::string psk; std::string psk_identity; std::string srtp_profiles; bool enable_ocsp_stapling = false; - std::string expected_ocsp_response; + std::string expect_ocsp_response; bool enable_signed_cert_timestamps = false; - std::string expected_signed_cert_timestamps; + std::string expect_signed_cert_timestamps; int min_version = 0; int max_version = 0; int expect_version = 0; @@ -89,7 +89,6 @@ struct TestConfig { bool fail_cert_callback = false; std::string cipher; bool handshake_never_done = false; - int export_early_keying_material = 0; int export_keying_material = 0; std::string export_label; std::string export_context; @@ -127,7 +126,7 @@ struct TestConfig { bool use_old_client_cert_callback = false; int initial_timeout_duration_ms = 0; std::string use_client_ca_list; - std::string expected_client_ca_list; + std::string expect_client_ca_list; bool send_alert = false; bool peek_then_read = false; bool enable_grease = false; @@ -173,7 +172,11 @@ struct TestConfig { bool server_preference = false; bool export_traffic_secrets = false; bool key_update = false; + bool expect_delegated_credential_used = false; std::string delegated_credential; + std::string expect_early_data_reason; + bool enable_pq_experiment_signal = false; + bool expect_pq_experiment_signal = false; int argc; char **argv; diff --git a/src/ssl/tls13_both.cc b/src/ssl/tls13_both.cc index ba5719fd..1a49e4c2 100644 --- a/src/ssl/tls13_both.cc +++ b/src/ssl/tls13_both.cc @@ -370,13 +370,8 @@ bool tls13_process_certificate_verify(SSL_HANDSHAKE *hs, const SSLMessage &msg) return false; } - bool sig_ok = ssl_public_key_verify(ssl, signature, signature_algorithm, - hs->peer_pubkey.get(), input); -#if defined(BORINGSSL_UNSAFE_FUZZER_MODE) - sig_ok = true; - ERR_clear_error(); -#endif - if (!sig_ok) { + if (!ssl_public_key_verify(ssl, signature, signature_algorithm, + hs->peer_pubkey.get(), input)) { OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_SIGNATURE); ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECRYPT_ERROR); return false; @@ -488,15 +483,16 @@ bool tls13_add_certificate(SSL_HANDSHAKE *hs) { if (ssl_signing_with_dc(hs)) { const CRYPTO_BUFFER *raw = dc->raw.get(); + CBB child; if (!CBB_add_u16(&extensions, TLSEXT_TYPE_delegated_credential) || - !CBB_add_u16(&extensions, CRYPTO_BUFFER_len(raw)) || - !CBB_add_bytes(&extensions, - CRYPTO_BUFFER_data(raw), + !CBB_add_u16_length_prefixed(&extensions, &child) || + !CBB_add_bytes(&child, CRYPTO_BUFFER_data(raw), CRYPTO_BUFFER_len(raw)) || !CBB_flush(&extensions)) { OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR); return 0; } + ssl->s3->delegated_credential_used = true; } for (size_t i = 1; i < sk_CRYPTO_BUFFER_num(cert->chain.get()); i++) { diff --git a/src/ssl/tls13_client.cc b/src/ssl/tls13_client.cc index ac97165f..f411e194 100644 --- a/src/ssl/tls13_client.cc +++ b/src/ssl/tls13_client.cc @@ -188,6 +188,7 @@ static enum ssl_hs_wait_t do_read_hello_retry_request(SSL_HANDSHAKE *hs) { hs->tls13_state = state_send_second_client_hello; // 0-RTT is rejected if we receive a HelloRetryRequest. if (hs->in_early_data) { + ssl->s3->early_data_reason = ssl_early_data_hello_retry_request; return ssl_hs_early_data_rejected; } return ssl_hs_ok; @@ -900,7 +901,7 @@ bool tls13_process_new_session_ticket(SSL *ssl, const SSLMessage &msg) { return false; } - if (have_early_data_info && ssl->enable_early_data) { + if (have_early_data_info) { if (!CBS_get_u32(&early_data_info, &session->ticket_max_early_data) || CBS_len(&early_data_info) != 0) { ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR); diff --git a/src/ssl/tls13_enc.cc b/src/ssl/tls13_enc.cc index 73535612..b6a402f4 100644 --- a/src/ssl/tls13_enc.cc +++ b/src/ssl/tls13_enc.cc @@ -38,6 +38,7 @@ static bool init_key_schedule(SSL_HANDSHAKE *hs, uint16_t version, return false; } + assert(hs->transcript.DigestLen() <= SSL_MAX_MD_SIZE); hs->hash_len = hs->transcript.DigestLen(); // Initialize the secret to the zero key. @@ -215,7 +216,6 @@ bool tls13_set_traffic_key(SSL *ssl, enum ssl_encryption_level_t level, static const char kTLS13LabelExporter[] = "exp master"; -static const char kTLS13LabelEarlyExporter[] = "e exp master"; static const char kTLS13LabelClientEarlyTraffic[] = "c e traffic"; static const char kTLS13LabelClientHandshakeTraffic[] = "c hs traffic"; @@ -229,13 +229,9 @@ bool tls13_derive_early_secrets(SSL_HANDSHAKE *hs) { kTLS13LabelClientEarlyTraffic, strlen(kTLS13LabelClientEarlyTraffic)) || !ssl_log_secret(ssl, "CLIENT_EARLY_TRAFFIC_SECRET", - hs->early_traffic_secret, hs->hash_len) || - !derive_secret(hs, ssl->s3->early_exporter_secret, hs->hash_len, - kTLS13LabelEarlyExporter, - strlen(kTLS13LabelEarlyExporter))) { + hs->early_traffic_secret, hs->hash_len)) { return false; } - ssl->s3->early_exporter_secret_len = hs->hash_len; if (ssl->quic_method != nullptr) { if (ssl->server) { diff --git a/src/ssl/tls13_server.cc b/src/ssl/tls13_server.cc index caaf0c74..4ebea5bd 100644 --- a/src/ssl/tls13_server.cc +++ b/src/ssl/tls13_server.cc @@ -53,6 +53,12 @@ enum server_hs_state_t { static const uint8_t kZeroes[EVP_MAX_MD_SIZE] = {0}; +// Allow a minute of ticket age skew in either direction. This covers +// transmission delays in ClientHello and NewSessionTicket, as well as +// drift between client and server clock rate since the ticket was issued. +// See RFC 8446, section 8.3. +static const int32_t kMaxTicketAgeSkewSeconds = 60; + static int resolve_ecdhe_secret(SSL_HANDSHAKE *hs, bool *out_need_retry, SSL_CLIENT_HELLO *client_hello) { SSL *const ssl = hs->ssl; @@ -97,77 +103,15 @@ static int ssl_ext_supported_versions_add_serverhello(SSL_HANDSHAKE *hs, return 1; } -// CipherScorer produces a "score" for each possible cipher suite offered by -// the client. -class CipherScorer { - public: - CipherScorer(uint16_t group_id) - : aes_is_fine_(EVP_has_aes_hardware()), - security_128_is_fine_(group_id != SSL_CURVE_CECPQ2) {} - - typedef std::tuple<bool, bool, bool> Score; - - // MinScore returns a |Score| that will compare less than the score of all - // cipher suites. - Score MinScore() const { - return Score(false, false, false); - } - - Score Evaluate(const SSL_CIPHER *a) const { - return Score( - // Something is always preferable to nothing. - true, - // Either 128-bit is fine, or 256-bit is preferred. - security_128_is_fine_ || a->algorithm_enc != SSL_AES128GCM, - // Either AES is fine, or else ChaCha20 is preferred. - aes_is_fine_ || a->algorithm_enc == SSL_CHACHA20POLY1305); - } - - private: - const bool aes_is_fine_; - const bool security_128_is_fine_; -}; - static const SSL_CIPHER *choose_tls13_cipher( const SSL *ssl, const SSL_CLIENT_HELLO *client_hello, uint16_t group_id) { - if (client_hello->cipher_suites_len % 2 != 0) { - return nullptr; - } - CBS cipher_suites; CBS_init(&cipher_suites, client_hello->cipher_suites, client_hello->cipher_suites_len); const uint16_t version = ssl_protocol_version(ssl); - const SSL_CIPHER *best = nullptr; - CipherScorer scorer(group_id); - CipherScorer::Score best_score = scorer.MinScore(); - - while (CBS_len(&cipher_suites) > 0) { - uint16_t cipher_suite; - if (!CBS_get_u16(&cipher_suites, &cipher_suite)) { - return nullptr; - } - - // Limit to TLS 1.3 ciphers we know about. - const SSL_CIPHER *candidate = SSL_get_cipher_by_value(cipher_suite); - if (candidate == nullptr || - SSL_CIPHER_get_min_version(candidate) > version || - SSL_CIPHER_get_max_version(candidate) < version) { - continue; - } - - const CipherScorer::Score candidate_score = scorer.Evaluate(candidate); - // |candidate_score| must be larger to displace the current choice. That way - // the client's order controls between ciphers with an equal score. - if (candidate_score > best_score) { - best = candidate; - best_score = candidate_score; - } - } - - return best; + return ssl_choose_tls13_cipher(cipher_suites, version, group_id); } static bool add_new_session_tickets(SSL_HANDSHAKE *hs, bool *out_sent_tickets) { @@ -307,16 +251,15 @@ static enum ssl_hs_wait_t do_select_parameters(SSL_HANDSHAKE *hs) { static enum ssl_ticket_aead_result_t select_session( SSL_HANDSHAKE *hs, uint8_t *out_alert, UniquePtr<SSL_SESSION> *out_session, - int32_t *out_ticket_age_skew, const SSLMessage &msg, - const SSL_CLIENT_HELLO *client_hello) { + int32_t *out_ticket_age_skew, bool *out_offered_ticket, + const SSLMessage &msg, const SSL_CLIENT_HELLO *client_hello) { SSL *const ssl = hs->ssl; - *out_session = NULL; + *out_session = nullptr; - // Decode the ticket if we agreed on a PSK key exchange mode. CBS pre_shared_key; - if (!hs->accept_psk_mode || - !ssl_client_hello_get_extension(client_hello, &pre_shared_key, - TLSEXT_TYPE_pre_shared_key)) { + *out_offered_ticket = ssl_client_hello_get_extension( + client_hello, &pre_shared_key, TLSEXT_TYPE_pre_shared_key); + if (!*out_offered_ticket) { return ssl_ticket_aead_ignore_ticket; } @@ -337,6 +280,11 @@ static enum ssl_ticket_aead_result_t select_session( return ssl_ticket_aead_error; } + // If the peer did not offer psk_dhe, ignore the resumption. + if (!hs->accept_psk_mode) { + return ssl_ticket_aead_ignore_ticket; + } + // TLS 1.3 session tickets are renewed separately as part of the // NewSessionTicket. bool unused_renew; @@ -406,10 +354,18 @@ static enum ssl_hs_wait_t do_select_session(SSL_HANDSHAKE *hs) { uint8_t alert = SSL_AD_DECODE_ERROR; UniquePtr<SSL_SESSION> session; - switch (select_session(hs, &alert, &session, &ssl->s3->ticket_age_skew, msg, - &client_hello)) { + bool offered_ticket = false; + switch (select_session(hs, &alert, &session, &ssl->s3->ticket_age_skew, + &offered_ticket, msg, &client_hello)) { case ssl_ticket_aead_ignore_ticket: assert(!session); + if (!ssl->enable_early_data) { + ssl->s3->early_data_reason = ssl_early_data_disabled; + } else if (!offered_ticket) { + ssl->s3->early_data_reason = ssl_early_data_no_session_offered; + } else { + ssl->s3->early_data_reason = ssl_early_data_session_not_resumed; + } if (!ssl_get_new_session(hs, 1 /* server */)) { ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR); return ssl_hs_error; @@ -421,26 +377,34 @@ static enum ssl_hs_wait_t do_select_session(SSL_HANDSHAKE *hs) { // a fresh session. hs->new_session = SSL_SESSION_dup(session.get(), SSL_SESSION_DUP_AUTH_ONLY); + if (hs->new_session == nullptr) { + ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR); + return ssl_hs_error; + } - if (ssl->enable_early_data && - // Early data must be acceptable for this ticket. - session->ticket_max_early_data != 0 && - // The client must have offered early data. - hs->early_data_offered && + if (!ssl->enable_early_data) { + ssl->s3->early_data_reason = ssl_early_data_disabled; + } else if (session->ticket_max_early_data == 0) { + ssl->s3->early_data_reason = ssl_early_data_unsupported_for_session; + } else if (!hs->early_data_offered) { + ssl->s3->early_data_reason = ssl_early_data_peer_declined; + } else if (ssl->s3->channel_id_valid) { // Channel ID is incompatible with 0-RTT. - !ssl->s3->channel_id_valid && - // If Token Binding is negotiated, reject 0-RTT. - !ssl->s3->token_binding_negotiated && - // The negotiated ALPN must match the one in the ticket. - MakeConstSpan(ssl->s3->alpn_selected) == session->early_alpn) { + ssl->s3->early_data_reason = ssl_early_data_channel_id; + } else if (ssl->s3->token_binding_negotiated) { + // Token Binding is incompatible with 0-RTT. + ssl->s3->early_data_reason = ssl_early_data_token_binding; + } else if (MakeConstSpan(ssl->s3->alpn_selected) != session->early_alpn) { + // The negotiated ALPN must match the one in the ticket. + ssl->s3->early_data_reason = ssl_early_data_alpn_mismatch; + } else if (ssl->s3->ticket_age_skew < -kMaxTicketAgeSkewSeconds || + kMaxTicketAgeSkewSeconds < ssl->s3->ticket_age_skew) { + ssl->s3->early_data_reason = ssl_early_data_ticket_age_skew; + } else { + ssl->s3->early_data_reason = ssl_early_data_accepted; ssl->s3->early_data_accepted = true; } - if (hs->new_session == NULL) { - ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR); - return ssl_hs_error; - } - ssl->s3->session_reused = true; // Resumption incorporates fresh key material, so refresh the timeout. @@ -499,7 +463,10 @@ static enum ssl_hs_wait_t do_select_session(SSL_HANDSHAKE *hs) { bool need_retry; if (!resolve_ecdhe_secret(hs, &need_retry, &client_hello)) { if (need_retry) { - ssl->s3->early_data_accepted = false; + if (ssl->s3->early_data_accepted) { + ssl->s3->early_data_reason = ssl_early_data_hello_retry_request; + ssl->s3->early_data_accepted = false; + } ssl->s3->skip_early_data = true; ssl->method->next_message(ssl); if (!hs->transcript.UpdateForHelloRetryRequest()) { @@ -950,7 +917,15 @@ static enum ssl_hs_wait_t do_send_new_session_ticket(SSL_HANDSHAKE *hs) { } hs->tls13_state = state_done; - return sent_tickets ? ssl_hs_flush : ssl_hs_ok; + // In TLS 1.3, the NewSessionTicket isn't flushed until the server performs a + // write, to prevent a non-reading client from causing the server to hang in + // the case of a small server write buffer. Consumers which don't write data + // to the client will need to do a zero-byte write if they wish to flush the + // tickets. + if (hs->ssl->ctx->quic_method != nullptr && sent_tickets) { + return ssl_hs_flush; + } + return ssl_hs_ok; } enum ssl_hs_wait_t tls13_server_handshake(SSL_HANDSHAKE *hs) { diff --git a/src/ssl/tls_method.cc b/src/ssl/tls_method.cc index bc9410b3..95fac4d5 100644 --- a/src/ssl/tls_method.cc +++ b/src/ssl/tls_method.cc @@ -125,9 +125,9 @@ static const SSL_PROTOCOL_METHOD kTLSProtocolMethod = { ssl3_set_write_state, }; -static int ssl_noop_x509_check_client_CA_names( +static bool ssl_noop_x509_check_client_CA_names( STACK_OF(CRYPTO_BUFFER) *names) { - return 1; + return true; } static void ssl_noop_x509_clear(CERT *cert) {} @@ -135,29 +135,29 @@ static void ssl_noop_x509_free(CERT *cert) {} static void ssl_noop_x509_dup(CERT *new_cert, const CERT *cert) {} static void ssl_noop_x509_flush_cached_leaf(CERT *cert) {} static void ssl_noop_x509_flush_cached_chain(CERT *cert) {} -static int ssl_noop_x509_session_cache_objects(SSL_SESSION *sess) { - return 1; +static bool ssl_noop_x509_session_cache_objects(SSL_SESSION *sess) { + return true; } -static int ssl_noop_x509_session_dup(SSL_SESSION *new_session, - const SSL_SESSION *session) { - return 1; +static bool ssl_noop_x509_session_dup(SSL_SESSION *new_session, + const SSL_SESSION *session) { + return true; } static void ssl_noop_x509_session_clear(SSL_SESSION *session) {} -static int ssl_noop_x509_session_verify_cert_chain(SSL_SESSION *session, - SSL_HANDSHAKE *hs, - uint8_t *out_alert) { - return 0; +static bool ssl_noop_x509_session_verify_cert_chain(SSL_SESSION *session, + SSL_HANDSHAKE *hs, + uint8_t *out_alert) { + return false; } static void ssl_noop_x509_hs_flush_cached_ca_names(SSL_HANDSHAKE *hs) {} -static int ssl_noop_x509_ssl_new(SSL_HANDSHAKE *hs) { return 1; } +static bool ssl_noop_x509_ssl_new(SSL_HANDSHAKE *hs) { return true; } static void ssl_noop_x509_ssl_config_free(SSL_CONFIG *cfg) {} static void ssl_noop_x509_ssl_flush_cached_client_CA(SSL_CONFIG *cfg) {} -static int ssl_noop_x509_ssl_auto_chain_if_needed(SSL_HANDSHAKE *hs) { - return 1; +static bool ssl_noop_x509_ssl_auto_chain_if_needed(SSL_HANDSHAKE *hs) { + return true; } -static int ssl_noop_x509_ssl_ctx_new(SSL_CTX *ctx) { return 1; } -static void ssl_noop_x509_ssl_ctx_free(SSL_CTX *ctx) { } +static bool ssl_noop_x509_ssl_ctx_new(SSL_CTX *ctx) { return true; } +static void ssl_noop_x509_ssl_ctx_free(SSL_CTX *ctx) {} static void ssl_noop_x509_ssl_ctx_flush_cached_client_CA(SSL_CTX *ctx) {} const SSL_X509_METHOD ssl_noop_x509_method = { |