mirror of
https://github.com/openssl/openssl.git
synced 2026-05-07 20:12:39 +00:00
7952bc4b8a
Reviewed-by: Matt Caswell <matt@openssl.foundation> Reviewed-by: Tomas Mraz <tomas@openssl.foundation> MergeDate: Wed Apr 8 08:59:19 2026 (Merged from https://github.com/openssl/openssl/pull/30419)
1898 lines
68 KiB
C
1898 lines
68 KiB
C
/*
|
|
* Copyright 2025-2026 The OpenSSL Project Authors. All Rights Reserved.
|
|
*
|
|
* Licensed under the Apache License 2.0 (the "License"). You may not use
|
|
* this file except in compliance with the License. You can obtain a copy
|
|
* in the file LICENSE in the source distribution or at
|
|
* https://www.openssl.org/source/license.html
|
|
*/
|
|
|
|
#include <string.h>
|
|
#include "helpers/ssltestlib.h"
|
|
#include "testutil.h"
|
|
#include <openssl/ech.h>
|
|
#include <internal/ech_helpers.h>
|
|
#include <internal/packet.h>
|
|
|
|
#define OSSL_ECH_MAX_LINELEN 1000 /* for a sanity check */
|
|
#define DEF_CERTS_DIR "test/certs"
|
|
|
|
/* the testcase numbers */
|
|
#define TESTCASE_CH 1
|
|
#define TESTCASE_SH 2
|
|
#define TESTCASE_ECH 3
|
|
|
|
static OSSL_LIB_CTX *libctx = NULL;
|
|
static char *propq = NULL;
|
|
static OSSL_ECHSTORE *es = NULL;
|
|
static OSSL_HPKE_SUITE hpke_suite = OSSL_HPKE_SUITE_DEFAULT;
|
|
static int verbose = 0;
|
|
static int testcase = 0;
|
|
static int testiter = 0;
|
|
static char *certsdir = NULL;
|
|
static char *cert = NULL;
|
|
static char *privkey = NULL;
|
|
static unsigned char *hpke_info = NULL;
|
|
static size_t hpke_infolen = 0;
|
|
static int short_test = 0;
|
|
|
|
/*
|
|
* An x25519 ech key and ECHConfigList with public name example.com
|
|
* and the associated base64 encoded and binary forms of that
|
|
* ECHConfigList - hardcoding here is ok as we're testing for
|
|
* effects of corrupted CH/SH and not for ECHConfig badness.
|
|
*/
|
|
static const char pem_kp1[] = "-----BEGIN PRIVATE KEY-----\n"
|
|
"MC4CAQAwBQYDK2VuBCIEILDIeo9Eqc4K9/uQ0PNAyMaP60qrxiSHT2tNZL3ksIZS\n"
|
|
"-----END PRIVATE KEY-----\n"
|
|
"-----BEGIN ECHCONFIG-----\n"
|
|
"AD7+DQA6bAAgACCY7B0f/3KvHIFdoqFaObdU8YYU+MdBf4vzbLhAAL2QCwAEAAEA\n"
|
|
"AQALZXhhbXBsZS5jb20AAA==\n"
|
|
"-----END ECHCONFIG-----\n";
|
|
static const char echconfig[] = "AD7+DQA6bAAgACCY7B0f/3KvHIFdoqFaObdU8YYU+MdBf4vzbLhAAL2QCwAEAAEA"
|
|
"AQALZXhhbXBsZS5jb20AAA==";
|
|
static size_t echconfiglen = sizeof(echconfig) - 1;
|
|
|
|
/* a second ECHConfig for when we want to use the wrong one */
|
|
static const char ec_kp2[] = "AEf+DQBDvQAgACCr9pErR7E/gNeoni+0YpDZaMd7XN+hFnCN+H0Xnm1EHQAEAAEAAQAUZnJvbnQuc2VydmVyLmV4YW1wbGUAAA==";
|
|
static size_t ec_kp2len = sizeof(ec_kp2) - 1;
|
|
|
|
static unsigned char bin_echconfig[] = {
|
|
0x00, 0x3e, 0xfe, 0x0d, 0x00, 0x3a, 0x6c, 0x00,
|
|
0x20, 0x00, 0x20, 0x98, 0xec, 0x1d, 0x1f, 0xff,
|
|
0x72, 0xaf, 0x1c, 0x81, 0x5d, 0xa2, 0xa1, 0x5a,
|
|
0x39, 0xb7, 0x54, 0xf1, 0x86, 0x14, 0xf8, 0xc7,
|
|
0x41, 0x7f, 0x8b, 0xf3, 0x6c, 0xb8, 0x40, 0x00,
|
|
0xbd, 0x90, 0x0b, 0x00, 0x04, 0x00, 0x01, 0x00,
|
|
0x01, 0x00, 0x0b, 0x65, 0x78, 0x61, 0x6d, 0x70,
|
|
0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x00, 0x00
|
|
};
|
|
static size_t bin_echconfiglen = sizeof(bin_echconfig);
|
|
|
|
/*
|
|
* We can grab the CH and SH and manipulate those to check good
|
|
* behaviour in the face of various errors. The most important
|
|
* thing to test is the server processing of the new combinations
|
|
* that result from the EncodedInnerClientHello (basically the raw
|
|
* output of ECH decryption). We test that via test vectors for
|
|
* those various borked values that we encrypt (via HPKE) and
|
|
* inject into the CH. The SH is much simpler since there are
|
|
* far fewer things to test with the magic encoding of the ECH
|
|
* accept signal into the SH.random or HRR.extension, but we
|
|
* can also test with borked versions of those.
|
|
*
|
|
* We'd like to, but so far cannot, do similarly for the ECH
|
|
* retry-config in EncryptedExtensions. Seems like there's no
|
|
* good way to get at the plaintext there and replace it with
|
|
* a borked value. (QUIC tests seem to have a way to do that
|
|
* but I've yet to figure how to replicate that here for the
|
|
* retry-config.)
|
|
*/
|
|
|
|
/*
|
|
* For client hello, we use a set of test vectors for each test:
|
|
* - encoded inner CH prefix
|
|
* - encoded inner CH for borking (esp. outer extensions)
|
|
* - encoded inner CH postfix
|
|
* - expected result (1 for good, 0 for bad)
|
|
* - expected error reason in the case of bad
|
|
*
|
|
* For each test, we replace the ECH ciphertext with a value
|
|
* that's the HPKE seal/enc of an encoded inner-CH made up of
|
|
* the three parts above and then see if we get the expected
|
|
* error (reason).
|
|
*
|
|
* Whenever we re-seal we will get an error due to using the
|
|
* wrong inner client random, which we don't know. But that
|
|
* differs from errors in handling decoding after decryption.
|
|
*
|
|
* The inner CH is split in 3 variables so we can re-use pre
|
|
* and post values, making it easier to understand/manipulate
|
|
* a corrupted-or-not value.
|
|
*
|
|
* Note that the overall length of the encoded inner needs to
|
|
* be maintained as otherwise outer length fields that are not
|
|
* re-computed will be wrong. (We include a test of that as
|
|
* well.) A radical change in the content of encoded inner
|
|
* values (e.g. eliminating compression entirely) could break
|
|
* these tests, but minor changes should have no effect due to
|
|
* padding. (Such a radical change showing up as a fail of
|
|
* these tests is arguably a good outcome.)
|
|
*/
|
|
typedef struct {
|
|
const unsigned char *pre;
|
|
size_t prelen;
|
|
const unsigned char *forbork;
|
|
size_t fblen;
|
|
const unsigned char *post;
|
|
size_t postlen;
|
|
int rv_expected; /* expected result */
|
|
int err_expected; /* expected error */
|
|
} TEST_ECHINNER;
|
|
|
|
/* a full padded, encoded inner client hello */
|
|
static const unsigned char entire_encoded_inner[] = {
|
|
0x03, 0x03, 0x7b, 0xe8, 0xc1, 0x18, 0xd7, 0xd1,
|
|
0x9c, 0x39, 0xa4, 0xfa, 0xce, 0x75, 0x72, 0x40,
|
|
0xcf, 0x37, 0xbb, 0x4c, 0xcd, 0xa7, 0x62, 0xda,
|
|
0x04, 0xd2, 0xdb, 0xe2, 0x89, 0x33, 0x36, 0x15,
|
|
0x96, 0xc9, 0x00, 0x00, 0x08, 0x13, 0x02, 0x13,
|
|
0x03, 0x13, 0x01, 0x00, 0xff, 0x01, 0x00, 0x00,
|
|
0x32, 0xfd, 0x00, 0x00, 0x11, 0x10,
|
|
0x00, 0x0a, 0x00, 0x23, 0x00, 0x16, 0x00, 0x17,
|
|
0x00, 0x0d, 0x00, 0x2b, 0x00, 0x2d, 0x00, 0x33,
|
|
0x00, 0x00, 0x00, 0x14, 0x00, 0x12, 0x00, 0x00,
|
|
0x0f, 0x66, 0x6f, 0x6f, 0x2e, 0x65, 0x78, 0x61,
|
|
0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d,
|
|
0xfe, 0x0d, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00
|
|
};
|
|
|
|
/* a full padded, encoded inner client hello with no extensions */
|
|
static const unsigned char no_ext_encoded_inner[] = {
|
|
0x03, 0x03, 0x7b, 0xe8, 0xc1, 0x18, 0xd7, 0xd1,
|
|
0x9c, 0x39, 0xa4, 0xfa, 0xce, 0x75, 0x72, 0x40,
|
|
0xcf, 0x37, 0xbb, 0x4c, 0xcd, 0xa7, 0x62, 0xda,
|
|
0x04, 0xd2, 0xdb, 0xe2, 0x89, 0x33, 0x36, 0x15,
|
|
0x96, 0xc9, 0x00, 0x00, 0x08, 0x13, 0x02, 0x13,
|
|
0x03, 0x13, 0x01, 0x00, 0xff, 0x01, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
|
|
};
|
|
|
|
/* a too-short, encoded inner client hello */
|
|
static const unsigned char outer_short_encoded_inner[] = {
|
|
0x03, 0x03, 0x7b, 0xe8, 0xc1, 0x18, 0xd7, 0xd1,
|
|
0x9c, 0x39, 0xa4, 0xfa, 0xce, 0x75, 0x72, 0x40,
|
|
0xcf, 0x37, 0xbb, 0x4c, 0xcd, 0xa7, 0x62, 0xda,
|
|
0x04, 0xd2, 0xdb, 0xe2, 0x89, 0x33, 0x36, 0x15,
|
|
0x96, 0xc9, 0x00, 0x00, 0x08, 0x13, 0x02, 0x13,
|
|
0x03, 0x13, 0x01, 0x00, 0xff, 0x01, 0x00, 0x00,
|
|
0x32, 0xfd, 0x00, 0x00, 0x11, 0x10,
|
|
0x00, 0x0a, 0x00, 0x23, 0x00, 0x16, 0x00, 0x17,
|
|
0x00, 0x0d, 0x00, 0x2b, 0x00, 0x2d, 0x00, 0x33,
|
|
0x00, 0x00, 0x00, 0x14, 0x00, 0x12, 0x00, 0x00,
|
|
0x0f, 0x66, 0x6f, 0x6f, 0x2e, 0x65, 0x78, 0x61,
|
|
0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d,
|
|
0xfe, 0x0d, 0x00, 0x01, 0x01, 0x00, 0x00
|
|
};
|
|
|
|
/* inner prefix up as far as outer_exts */
|
|
static const unsigned char encoded_inner_pre[] = {
|
|
0x03, 0x03, 0x7b, 0xe8, 0xc1, 0x18, 0xd7, 0xd1,
|
|
0x9c, 0x39, 0xa4, 0xfa, 0xce, 0x75, 0x72, 0x40,
|
|
0xcf, 0x37, 0xbb, 0x4c, 0xcd, 0xa7, 0x62, 0xda,
|
|
0x04, 0xd2, 0xdb, 0xe2, 0x89, 0x33, 0x36, 0x15,
|
|
0x96, 0xc9, 0x00, 0x00, 0x08, 0x13, 0x02, 0x13,
|
|
0x03, 0x13, 0x01, 0x00, 0xff, 0x01, 0x00, 0x00,
|
|
0x32
|
|
};
|
|
|
|
/* inner prefix with mad length of suites (0xDDDD) */
|
|
static const unsigned char badsuites_inner_pre[] = {
|
|
0x03, 0x03, 0x7b, 0xe8, 0xc1, 0x18, 0xd7, 0xd1,
|
|
0x9c, 0x39, 0xa4, 0xfa, 0xce, 0x75, 0x72, 0x40,
|
|
0xcf, 0x37, 0xbb, 0x4c, 0xcd, 0xa7, 0x62, 0xda,
|
|
0x04, 0xd2, 0xdb, 0xe2, 0x89, 0x33, 0x36, 0x15,
|
|
0x96, 0xc9, 0x00, 0xDD, 0xDD, 0x13, 0x02, 0x13,
|
|
0x03, 0x13, 0x01, 0x00, 0xff, 0x01, 0x00, 0x00,
|
|
0x32
|
|
};
|
|
|
|
/* outer extensions - we play with variations of this */
|
|
static const unsigned char encoded_inner_outers[] = {
|
|
0xfd, 0x00, 0x00, 0x11, 0x10,
|
|
0x00, 0x0a, 0x00, 0x23, 0x00, 0x16, 0x00, 0x17,
|
|
0x00, 0x0d, 0x00, 0x2b, 0x00, 0x2d, 0x00, 0x33
|
|
};
|
|
|
|
/* outers with repetition of one extension (0x23) */
|
|
static const unsigned char borked_outer1[] = {
|
|
0xfd, 0x00, 0x00, 0x11, 0x10,
|
|
0x00, 0x23, 0x00, 0x23, 0x00, 0x16, 0x00, 0x17,
|
|
0x00, 0x0d, 0x00, 0x2b, 0x00, 0x2d, 0x00, 0x33
|
|
};
|
|
|
|
/* outers including a non-used extension (0xFFAB) */
|
|
static const unsigned char borked_outer2[] = {
|
|
0xfd, 0x00, 0x00, 0x11, 0x10,
|
|
0x00, 0x0a, 0x00, 0x23, 0x00, 0x16, 0x00, 0x17,
|
|
0xFF, 0xAB, 0x00, 0x2b, 0x00, 0x2d, 0x00, 0x33
|
|
};
|
|
|
|
/* refer to SNI in outers! 2nd-last is 0x0000 */
|
|
static const unsigned char borked_outer3[] = {
|
|
0xfd, 0x00, 0x00, 0x11, 0x10,
|
|
0x00, 0x0a, 0x00, 0x23, 0x00, 0x16, 0x00, 0x17,
|
|
0x00, 0x0d, 0x00, 0x2b, 0x00, 0x00, 0x00, 0x33
|
|
};
|
|
|
|
/* refer to ECH (0xfe0d) within outers */
|
|
static const unsigned char borked_outer4[] = {
|
|
0xfd, 0x00, 0x00, 0x11, 0x10,
|
|
0x00, 0x0a, 0x00, 0x23, 0x00, 0x16, 0x00, 0x17,
|
|
0xFE, 0x0D, 0x00, 0x2b, 0x00, 0x2d, 0x00, 0x33
|
|
};
|
|
|
|
/* refer to outers (0xfd00) within outers */
|
|
static const unsigned char borked_outer5[] = {
|
|
0xfd, 0x00, 0x00, 0x11, 0x10,
|
|
0x00, 0x0a, 0x00, 0x23, 0x00, 0x16, 0x00, 0x17,
|
|
0xFD, 0x00, 0x00, 0x2b, 0x00, 0x2d, 0x00, 0x33
|
|
};
|
|
|
|
/* no outers at all! include unknown ext 0xFF99 instead */
|
|
static const unsigned char borked_outer6[] = {
|
|
0xFF, 0x99, 0x00, 0x11, 0x10,
|
|
0x00, 0x0a, 0x00, 0x23, 0x00, 0x16, 0x00, 0x17,
|
|
0x00, 0x0d, 0x00, 0x2b, 0x00, 0x2d, 0x00, 0x33
|
|
};
|
|
|
|
/*
|
|
* outer with bad length (even number of octets)
|
|
* we add a short bogus extension (0xFFFF) after
|
|
* to ensure overall decode succeeds
|
|
*/
|
|
static const unsigned char borked_outer7[] = {
|
|
0xfd, 0x00, 0x00, 0x0E, 0x10,
|
|
0x00, 0x0a, 0x00, 0x23, 0x00, 0x16, 0x00, 0x17,
|
|
0x00, 0x0d, 0x00, 0xFF, 0xFF, 0x00, 0x01, 0x00
|
|
};
|
|
|
|
/* outer with bad inner length (odd number of octets) */
|
|
static const unsigned char borked_outer8[] = {
|
|
0xfd, 0x00, 0x00, 0x11, 0x11,
|
|
0x00, 0x0a, 0x00, 0x23, 0x00, 0x16, 0x00, 0x17,
|
|
0x00, 0x0d, 0x00, 0x2b, 0x00, 0x2d, 0x00, 0x33
|
|
};
|
|
|
|
/* outer with HUGE length (0xFF11) */
|
|
static const unsigned char borked_outer9[] = {
|
|
0xfd, 0x00, 0xFF, 0x11, 0x10,
|
|
0x00, 0x0a, 0x00, 0x23, 0x00, 0x16, 0x00, 0x17,
|
|
0x00, 0x0d, 0x00, 0x2b, 0x00, 0x2d, 0x00, 0x33
|
|
};
|
|
|
|
/* outer with zero length, followed by bogus ext */
|
|
static const unsigned char borked_outer10[] = {
|
|
0xfd, 0x00, 0x00, 0x00, 0xFF,
|
|
0x0F, 0x00, 0x0D, 0x23, 0x00, 0x16, 0x00, 0x17,
|
|
0x00, 0x0d, 0x00, 0x2b, 0x00, 0x2d, 0x00, 0x33
|
|
};
|
|
|
|
/* refer to key-share 0x00 0x33 (51) twice within outers */
|
|
static const unsigned char borked_outer11[] = {
|
|
0xfd, 0x00, 0x00, 0x11, 0x10,
|
|
0x00, 0x0a, 0x00, 0x23, 0x00, 0x16, 0x00, 0x17,
|
|
0x00, 0x33, 0x00, 0x2b, 0x00, 0x2d, 0x00, 0x33
|
|
};
|
|
|
|
/* refer to psk kex mode (0x00 0x2D/45) within outers */
|
|
static const unsigned char borked_outer12[] = {
|
|
0xfd, 0x00, 0x00, 0x11, 0x10,
|
|
0x00, 0x0a, 0x00, 0x23, 0x00, 0x16, 0x00, 0x17,
|
|
0x00, 0x2D, 0x00, 0x2b, 0x00, 0x2d, 0x00, 0x33
|
|
};
|
|
|
|
static const unsigned char encoded_inner_post[] = {
|
|
0x00, 0x00, 0x00, 0x14, 0x00, 0x12, 0x00, 0x00,
|
|
0x0f, 0x66, 0x6f, 0x6f, 0x2e, 0x65, 0x78, 0x61,
|
|
0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d,
|
|
0xfe, 0x0d, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00
|
|
};
|
|
|
|
/* muck up the padding by including non-zero stuff */
|
|
static const unsigned char bad_pad_encoded_inner_post[] = {
|
|
0x00, 0x00, 0x00, 0x14, 0x00, 0x12, 0x00, 0x00,
|
|
0x0f, 0x66, 0x6f, 0x6f, 0x2e, 0x65, 0x78, 0x61,
|
|
0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d,
|
|
0xfe, 0x0d, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00
|
|
};
|
|
|
|
/* an encoded inner that's just too short */
|
|
static const unsigned char short_encoded_inner[] = {
|
|
0x03, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
|
|
};
|
|
|
|
/*
|
|
* too many outer extensions - max is 20 (decimal)
|
|
* defined as OSSL_ECH_OUTERS_MAX
|
|
*/
|
|
static const unsigned char too_many_outers[] = {
|
|
0x03, 0x03, 0x7b, 0xe8, 0xc1, 0x18, 0xd7, 0xd1,
|
|
0x9c, 0x39, 0xa4, 0xfa, 0xce, 0x75, 0x72, 0x40,
|
|
0xcf, 0x37, 0xbb, 0x4c, 0xcd, 0xa7, 0x62, 0xda,
|
|
0x04, 0xd2, 0xdb, 0xe2, 0x89, 0x33, 0x36, 0x15,
|
|
0x96, 0xc9, 0x00, 0x00, 0x08, 0x13, 0x02, 0x13,
|
|
0x03, 0x13, 0x01, 0x00, 0xff, 0x01, 0x00,
|
|
0x00, 0x4c, /* extslen, incl. our added outers */
|
|
0xfd, 0x00, /* outers */
|
|
0x00, 0x2b, /* len of outers */
|
|
0x2a, /* above minus one (42) 21 outers */
|
|
0x00, 0x0a, /* the 8 'normal' outers */
|
|
0x00, 0x23,
|
|
0x00, 0x16,
|
|
0x00, 0x17,
|
|
0x00, 0x0d,
|
|
0x00, 0x2b,
|
|
0x00, 0x2d,
|
|
0x00, 0x33,
|
|
0x00, 0x0b, /* point encoding, not actually in outer */
|
|
/* 12 more outers, set 'em all to ALPN (16, 0x10) */
|
|
0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10,
|
|
0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10,
|
|
0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10,
|
|
/* and now the inner SNI, inner ECH and 3 padding octets */
|
|
0x00, 0x00, 0x00, 0x14, 0x00, 0x12, 0x00, 0x00,
|
|
0x0f, 0x66, 0x6f, 0x6f, 0x2e, 0x65, 0x78, 0x61,
|
|
0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d,
|
|
0xfe, 0x0d, 0x00, 0x01, 0x01,
|
|
0x00, 0x00, 0x00
|
|
};
|
|
|
|
static const unsigned char tlsv12_like_inner[] = {
|
|
0x03, 0x03, /* version, then client-random */
|
|
0x23, 0xc3, 0xa0, 0x49, 0xea, 0x17, 0x9e, 0x30,
|
|
0x6f, 0x0e, 0xc9, 0x79, 0xd0, 0xd1, 0xfd, 0xea,
|
|
0x63, 0xfd, 0x20, 0x04, 0xaa, 0xb3, 0x2a, 0x29,
|
|
0xf5, 0x96, 0x60, 0x29, 0x42, 0x7e, 0x5c, 0x7b,
|
|
0x00, /* zero'd session ID */
|
|
0x00, 0x02, /* ciphersuite len, just one */
|
|
0xc0, 0x2c, /* a TLSv1.2 ciphersuite */
|
|
0x01, 0x00, /* no compression */
|
|
0x00, 0x2c, /* extslen */
|
|
0xfd, 0x00, /* outers */
|
|
0x00, 0x0b, /* len of outers */
|
|
0x0a, /* above minus one (10) 5 outers */
|
|
0x00, 0x0a, /* the 'normal' outers, minus supported_versions */
|
|
0x00, 0x23,
|
|
0x00, 0x16,
|
|
0x00, 0x17,
|
|
0x00, 0x0d,
|
|
/* and now the inner SNI, inner ECH and padding octets */
|
|
0x00, 0x00, 0x00, 0x14, 0x00, 0x12, 0x00, 0x00,
|
|
0x0f, 0x66, 0x6f, 0x6f, 0x2e, 0x65, 0x78, 0x61,
|
|
0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d,
|
|
0xfe, 0x0d, 0x00, 0x01, 0x01,
|
|
0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00
|
|
};
|
|
|
|
/*
|
|
* a full padded, encoded inner client hello, but
|
|
* without an inner supported extensions, (take
|
|
* out the 0x00 0x2b and add some padding zeros,
|
|
* adjusting lengths) and hence meaning TLSv1.2
|
|
*/
|
|
static const unsigned char no_supported_exts[] = {
|
|
0x03, 0x03, 0x7b, 0xe8, 0xc1, 0x18, 0xd7, 0xd1,
|
|
0x9c, 0x39, 0xa4, 0xfa, 0xce, 0x75, 0x72, 0x40,
|
|
0xcf, 0x37, 0xbb, 0x4c, 0xcd, 0xa7, 0x62, 0xda,
|
|
0x04, 0xd2, 0xdb, 0xe2, 0x89, 0x33, 0x36, 0x15,
|
|
0x96, 0xc9, 0x00, 0x00, 0x08, 0x13, 0x02, 0x13,
|
|
0x03, 0x13, 0x01, 0x00, 0xff, 0x01, 0x00, 0x00,
|
|
0x30, 0xfd, 0x00, 0x00, 0x0f, 0x0e,
|
|
0x00, 0x0a, 0x00, 0x23, 0x00, 0x16, 0x00, 0x17,
|
|
0x00, 0x0d, 0x00, 0x2d, 0x00, 0x33,
|
|
0x00, 0x00, 0x00, 0x14, 0x00, 0x12, 0x00, 0x00,
|
|
0x0f, 0x66, 0x6f, 0x6f, 0x2e, 0x65, 0x78, 0x61,
|
|
0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d,
|
|
0xfe, 0x0d, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00
|
|
};
|
|
|
|
static const unsigned char tlsv12_inner[] = {
|
|
0x03, 0x03, /* version, then client-random */
|
|
0x23, 0xc3, 0xa0, 0x49, 0xea, 0x17, 0x9e, 0x30,
|
|
0x6f, 0x0e, 0xc9, 0x79, 0xd0, 0xd1, 0xfd, 0xea,
|
|
0x63, 0xfd, 0x20, 0x04, 0xaa, 0xb3, 0x2a, 0x29,
|
|
0xf5, 0x96, 0x60, 0x29, 0x42, 0x7e, 0x5c, 0x7b,
|
|
0x00, /* zero'd session ID */
|
|
0x00, 0x02, /* ciphersuite len, just one */
|
|
0xc0, 0x2c, /* a TLSv1.2 ciphersuite */
|
|
0x01, 0x00, /* no compression */
|
|
0x00, 0x32, /* extslen */
|
|
0xfd, 0x00, /* outers */
|
|
0x00, 0x10, /* len of outers */
|
|
0x0e, /* above minus one (16) 8 outers */
|
|
0x00, 0x0a, /* the 'normal' outers, minus supported_versions */
|
|
0x00, 0x23,
|
|
0x00, 0x16,
|
|
0x00, 0x17,
|
|
0x00, 0x0d,
|
|
0x00, 0x2d,
|
|
0x00, 0x33,
|
|
/* and now the inner SNI, inner ECH and padding octets */
|
|
0x00, 0x00, 0x00, 0x14, 0x00, 0x12, 0x00, 0x00,
|
|
0x0f, 0x66, 0x6f, 0x6f, 0x2e, 0x65, 0x78, 0x61,
|
|
0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d,
|
|
0xfe, 0x0d, 0x00, 0x01, 0x01,
|
|
0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
|
|
};
|
|
|
|
/*
|
|
* a full padded, encoded inner with no inner ECH
|
|
* we change 0xfe 0x0d to 0xFF 0xFD in the ext type
|
|
*/
|
|
static const unsigned char encoded_inner_no_ech[] = {
|
|
0x03, 0x03, 0x7b, 0xe8, 0xc1, 0x18, 0xd7, 0xd1,
|
|
0x9c, 0x39, 0xa4, 0xfa, 0xce, 0x75, 0x72, 0x40,
|
|
0xcf, 0x37, 0xbb, 0x4c, 0xcd, 0xa7, 0x62, 0xda,
|
|
0x04, 0xd2, 0xdb, 0xe2, 0x89, 0x33, 0x36, 0x15,
|
|
0x96, 0xc9, 0x00, 0x00, 0x08, 0x13, 0x02, 0x13,
|
|
0x03, 0x13, 0x01, 0x00, 0xff, 0x01, 0x00, 0x00,
|
|
0x32, 0xfd, 0x00, 0x00, 0x11, 0x10,
|
|
0x00, 0x0a, 0x00, 0x23, 0x00, 0x16, 0x00, 0x17,
|
|
0x00, 0x0d, 0x00, 0x2b, 0x00, 0x2d, 0x00, 0x33,
|
|
0x00, 0x00, 0x00, 0x14, 0x00, 0x12, 0x00, 0x00,
|
|
0x0f, 0x66, 0x6f, 0x6f, 0x2e, 0x65, 0x78, 0x61,
|
|
0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d,
|
|
0xFF, 0xFD, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00
|
|
};
|
|
|
|
/* A set of test vectors */
|
|
static TEST_ECHINNER test_inners[] = {
|
|
/* 1. basic case - copy to show test code works with no change */
|
|
{ NULL, 0, NULL, 0, NULL, 0, 1, SSL_ERROR_NONE },
|
|
|
|
/* 2. too-short encoded inner */
|
|
{ NULL, 0,
|
|
outer_short_encoded_inner, sizeof(outer_short_encoded_inner),
|
|
NULL, 0,
|
|
0, /* expected result */
|
|
SSL_R_DECRYPTION_FAILED_OR_BAD_RECORD_MAC },
|
|
/* 3. otherwise-correct case that fails only due to client random */
|
|
{ NULL, 0,
|
|
entire_encoded_inner, sizeof(entire_encoded_inner),
|
|
NULL, 0,
|
|
0, /* expected result */
|
|
SSL_R_DECRYPTION_FAILED_OR_BAD_RECORD_MAC },
|
|
/* 4. otherwise-correct case that fails only due to client random */
|
|
{ encoded_inner_pre, sizeof(encoded_inner_pre),
|
|
encoded_inner_outers, sizeof(encoded_inner_outers),
|
|
encoded_inner_post, sizeof(encoded_inner_post),
|
|
0, /* expected result */
|
|
SSL_R_DECRYPTION_FAILED_OR_BAD_RECORD_MAC },
|
|
/* 5. fails HPKE decryption due to bad padding so treated as GREASE */
|
|
{ encoded_inner_pre, sizeof(encoded_inner_pre),
|
|
encoded_inner_outers, sizeof(encoded_inner_outers),
|
|
bad_pad_encoded_inner_post, sizeof(bad_pad_encoded_inner_post),
|
|
0, /* expected result */
|
|
SSL_R_TLS_ALERT_ILLEGAL_PARAMETER },
|
|
/*
|
|
* 6. unsupported extension instead of outers - resulting decoded
|
|
* inner missing so much it seems to be the wrong protocol
|
|
*/
|
|
{ encoded_inner_pre, sizeof(encoded_inner_pre),
|
|
borked_outer6, sizeof(borked_outer6),
|
|
encoded_inner_post, sizeof(encoded_inner_post),
|
|
0, /* expected result - error is different with -notls1_2 */
|
|
#ifdef OPENSSL_NO_TLS1_2
|
|
SSL_R_VERSION_TOO_LOW
|
|
#else
|
|
SSL_R_UNSUPPORTED_PROTOCOL
|
|
#endif
|
|
},
|
|
|
|
/* 7. madly long ciphersuites in inner */
|
|
{ badsuites_inner_pre, sizeof(badsuites_inner_pre),
|
|
encoded_inner_outers, sizeof(encoded_inner_outers),
|
|
encoded_inner_post, sizeof(bad_pad_encoded_inner_post),
|
|
0, /* expected result */
|
|
SSL_R_TLSV1_ALERT_DECODE_ERROR },
|
|
/* 8. so many padding bytes recovered clear is short */
|
|
{ NULL, 0,
|
|
short_encoded_inner, sizeof(short_encoded_inner),
|
|
NULL, 0,
|
|
0, /* expected result */
|
|
SSL_R_BAD_EXTENSION },
|
|
|
|
/* 9. repeated codepoint inside outers */
|
|
{ encoded_inner_pre, sizeof(encoded_inner_pre),
|
|
borked_outer1, sizeof(borked_outer1),
|
|
encoded_inner_post, sizeof(encoded_inner_post),
|
|
0, /* expected result */
|
|
SSL_R_BAD_EXTENSION },
|
|
/* 10. non-existent codepoint inside outers */
|
|
{ encoded_inner_pre, sizeof(encoded_inner_pre),
|
|
borked_outer2, sizeof(borked_outer2),
|
|
encoded_inner_post, sizeof(encoded_inner_post),
|
|
0, /* expected result */
|
|
SSL_R_BAD_EXTENSION },
|
|
/* 11. include SNI in outers as well as both inner and outer */
|
|
{ encoded_inner_pre, sizeof(encoded_inner_pre),
|
|
borked_outer3, sizeof(borked_outer3),
|
|
encoded_inner_post, sizeof(encoded_inner_post),
|
|
0, /* expected result */
|
|
SSL_R_BAD_EXTENSION },
|
|
/* 12. refer to ECH within outers */
|
|
{ encoded_inner_pre, sizeof(encoded_inner_pre),
|
|
borked_outer4, sizeof(borked_outer4),
|
|
encoded_inner_post, sizeof(encoded_inner_post),
|
|
0, /* expected result */
|
|
SSL_R_BAD_EXTENSION },
|
|
/* 13. refer to outers within outers */
|
|
{ encoded_inner_pre, sizeof(encoded_inner_pre),
|
|
borked_outer5, sizeof(borked_outer5),
|
|
encoded_inner_post, sizeof(encoded_inner_post),
|
|
0, /* expected result */
|
|
SSL_R_BAD_EXTENSION },
|
|
/* 14. bad length of outers */
|
|
{ encoded_inner_pre, sizeof(encoded_inner_pre),
|
|
borked_outer7, sizeof(borked_outer7),
|
|
encoded_inner_post, sizeof(encoded_inner_post),
|
|
0, /* expected result */
|
|
SSL_R_BAD_EXTENSION },
|
|
/* 15. bad inner length in outers */
|
|
{ encoded_inner_pre, sizeof(encoded_inner_pre),
|
|
borked_outer8, sizeof(borked_outer8),
|
|
encoded_inner_post, sizeof(encoded_inner_post),
|
|
0, /* expected result */
|
|
SSL_R_BAD_EXTENSION },
|
|
/* 16. HUGE length in outers */
|
|
{ encoded_inner_pre, sizeof(encoded_inner_pre),
|
|
borked_outer9, sizeof(borked_outer9),
|
|
encoded_inner_post, sizeof(encoded_inner_post),
|
|
0, /* expected result */
|
|
SSL_R_BAD_EXTENSION },
|
|
/* 17. zero length in outers */
|
|
{ encoded_inner_pre, sizeof(encoded_inner_pre),
|
|
borked_outer10, sizeof(borked_outer10),
|
|
encoded_inner_post, sizeof(encoded_inner_post),
|
|
0, /* expected result */
|
|
SSL_R_BAD_EXTENSION },
|
|
/* 18. case with no extensions at all */
|
|
{ NULL, 0,
|
|
no_ext_encoded_inner, sizeof(no_ext_encoded_inner),
|
|
NULL, 0,
|
|
0, /* expected result */
|
|
SSL_R_BAD_EXTENSION },
|
|
/*
|
|
* 19. include key-share twice in outers as well as both inner and outer.
|
|
* There was a change with this one recently that can/does cause a
|
|
* different error message (used to be SSL_R_BAD_EXTENSION, but now
|
|
* mostly ERR_R_INTERNAL_ERROR). The issue is that this test repeats the
|
|
* key_share in the compressed exts and with PQ kybrid KEMs those are
|
|
* so large that instead of detecting the duplicate extension we see
|
|
* an earlier error where the inner CH is bigger than the outer.
|
|
*/
|
|
{ encoded_inner_pre, sizeof(encoded_inner_pre),
|
|
borked_outer11, sizeof(borked_outer11),
|
|
encoded_inner_post, sizeof(encoded_inner_post),
|
|
0, /* expected result */
|
|
#ifdef OPENSSL_NO_ML_KEM
|
|
SSL_R_BAD_EXTENSION
|
|
#else
|
|
ERR_R_INTERNAL_ERROR
|
|
#endif
|
|
},
|
|
/* 20. include psk key mode ext in outers as well as both inner and outer */
|
|
{ encoded_inner_pre, sizeof(encoded_inner_pre),
|
|
borked_outer12, sizeof(borked_outer12),
|
|
encoded_inner_post, sizeof(encoded_inner_post),
|
|
0, /* expected result */
|
|
SSL_R_BAD_EXTENSION },
|
|
/* 21. too many outers */
|
|
{ NULL, 0,
|
|
too_many_outers, sizeof(too_many_outers),
|
|
NULL, 0,
|
|
0, /* expected result */
|
|
SSL_R_BAD_EXTENSION },
|
|
/*
|
|
* 22. no supported_versions hence TLSv1.2, with server set to
|
|
* allow max tlsv1.3
|
|
*/
|
|
{ NULL, 0,
|
|
tlsv12_like_inner, sizeof(tlsv12_like_inner),
|
|
NULL, 0,
|
|
0, /* expected result */ SSL_R_UNSUPPORTED_PROTOCOL },
|
|
/*
|
|
* 23. no supported_versions hence TLSv1.2, with server set to
|
|
* allow max tlsv1.2
|
|
*/
|
|
{ NULL, 0,
|
|
no_supported_exts, sizeof(no_supported_exts),
|
|
NULL, 0,
|
|
0, /* expected result */
|
|
SSL_R_NO_PROTOCOLS_AVAILABLE },
|
|
/*
|
|
* 24. no supported_versions hence TLSv1.2, with server set to
|
|
* allow min tlsv1.2
|
|
*/
|
|
{ NULL, 0,
|
|
tlsv12_like_inner, sizeof(tlsv12_like_inner),
|
|
NULL, 0,
|
|
0, /* expected result */ SSL_R_UNSUPPORTED_PROTOCOL },
|
|
/* 25. smuggled TLSv1.2 CH */
|
|
{ NULL, 0,
|
|
tlsv12_inner, sizeof(tlsv12_inner),
|
|
NULL, 0,
|
|
0, /* expected result */ SSL_R_BAD_EXTENSION },
|
|
/* 26. otherwise-correct case that fails due to lack of inner ECH */
|
|
{ NULL, 0,
|
|
encoded_inner_no_ech, sizeof(encoded_inner_no_ech),
|
|
NULL, 0,
|
|
0, /* expected result */ SSL_R_ECH_REQUIRED },
|
|
};
|
|
|
|
/*
|
|
* For server hello/HRR, we use a set of test vectors for each test:
|
|
*
|
|
* - borkage encodes what we're breaking and is the OR
|
|
* of some #define'd OSSL_ECH_BORK_* flags
|
|
* - bork is the value to use instead of the real one (or NULL)
|
|
* - blen is the size of bork
|
|
* - rv_expected is the return value expected for the connection
|
|
* - err_expected is the reason code we expect to see
|
|
*/
|
|
typedef struct {
|
|
int borkage; /* type of borkage */
|
|
unsigned char *bork; /* borked value */
|
|
size_t blen; /* len(bork) */
|
|
int rv_expected; /* expected result */
|
|
int err_expected; /* expected error */
|
|
} TEST_SH;
|
|
|
|
#define OSSL_ECH_BORK_NONE 0
|
|
#define OSSL_ECH_BORK_FLIP 1
|
|
#define OSSL_ECH_BORK_HRR (1 << 1)
|
|
#define OSSL_ECH_BORK_SHORT_HRR_CONFIRM (1 << 2)
|
|
#define OSSL_ECH_BORK_LONG_HRR_CONFIRM (1 << 3)
|
|
#define OSSL_ECH_BORK_GREASE (1 << 4)
|
|
#define OSSL_ECH_BORK_REPLACE (1 << 5)
|
|
|
|
/* a truncated ECH, with another bogus ext to match overall length */
|
|
static unsigned char shortech[] = {
|
|
0xfe, 0x0d, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00,
|
|
0xdd, 0xdd, 0x00, 0x00
|
|
};
|
|
|
|
/* a too-long ECH internal length */
|
|
static unsigned char longech[] = {
|
|
0xfe, 0x0d, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00,
|
|
0xdd, 0xdd, 0x00, 0x00
|
|
};
|
|
|
|
static TEST_SH test_shs[] = {
|
|
/* 1. no messing about, should succeed */
|
|
{ OSSL_ECH_BORK_NONE, NULL, 0, 1, SSL_ERROR_NONE },
|
|
/* 2. trigger HRR but no other borkage */
|
|
{ OSSL_ECH_BORK_HRR, NULL, 0, 1, SSL_ERROR_NONE },
|
|
|
|
/* 3. GREASE and trigger HRR */
|
|
{ OSSL_ECH_BORK_HRR | OSSL_ECH_BORK_GREASE,
|
|
NULL, 0, 1, SSL_ERROR_NONE },
|
|
|
|
/* 4. flip bits in SH.random ECH confirmation value */
|
|
{ OSSL_ECH_BORK_FLIP, NULL, 0, 0,
|
|
SSL_R_DECRYPTION_FAILED_OR_BAD_RECORD_MAC },
|
|
/* 5. flip bits in HRR.exts ECH confirmation value */
|
|
{ OSSL_ECH_BORK_HRR | OSSL_ECH_BORK_FLIP,
|
|
NULL, 0, 0, SSL_R_ECH_REQUIRED },
|
|
/* 6. truncate HRR.exts ECH confirmation value */
|
|
{ OSSL_ECH_BORK_HRR | OSSL_ECH_BORK_REPLACE,
|
|
shortech, sizeof(shortech), 0, SSL_R_LENGTH_MISMATCH },
|
|
/* 7. too-long HRR.exts ECH confirmation value */
|
|
{ OSSL_ECH_BORK_HRR | OSSL_ECH_BORK_REPLACE,
|
|
longech, sizeof(longech), 0, SSL_R_BAD_EXTENSION },
|
|
|
|
};
|
|
|
|
/*
|
|
* Test vectors for badly encoded ECH extension values for
|
|
* the outer ClientHelllo. We grab the outbound ClientHello
|
|
* and overwrite these values in the appropriate place. That
|
|
* will always break the TLS connection, even with a correct
|
|
* encoding, as we're breaking the transcript, but we expect
|
|
* decoding to catch these and to get 'bad extension' errors
|
|
* in most cases.
|
|
*
|
|
* Note that the code for these tests could be more terse as
|
|
* declaring a separate buffer for each bad value is quite
|
|
* repetitive, but doing it this way is more readable and more
|
|
* easily varied/extended.
|
|
*/
|
|
|
|
/* an entire correctly encoded ECH (len = 190) */
|
|
static unsigned char entire_encoded_ech[] = {
|
|
0xfe, 0x0d, 0x00, 0xba, /* ext type & length */
|
|
0x00, /* outer ECH */
|
|
0x00, 0x01, 0x00, 0x01, /* cipher suite KDF, AEAD */
|
|
0x7c, /* config id */
|
|
0x00, 0x20, /* encap len then encap val */
|
|
0x59, 0x87, 0xbe, 0x13, 0xd0, 0xf1, 0x0e, 0x23,
|
|
0xcb, 0x28, 0x26, 0xc2, 0x88, 0xd0, 0x8f, 0xac,
|
|
0x04, 0x99, 0x54, 0x30, 0xa2, 0x0f, 0xfe, 0x53,
|
|
0xf5, 0xa5, 0x92, 0x01, 0xb1, 0x56, 0xd2, 0x3f,
|
|
0x00, 0x90, /* payload len then payload */
|
|
0x9e, 0xe6, 0xed, 0x1d, 0xe2, 0xef, 0x30, 0xb0,
|
|
0x91, 0x00, 0xdc, 0x90, 0x21, 0x9e, 0x5e, 0x6f,
|
|
0xcb, 0xb9, 0xb3, 0x05, 0xdd, 0xac, 0x97, 0x71,
|
|
0xf0, 0x2d, 0x48, 0xf7, 0x01, 0xf4, 0x68, 0x0c,
|
|
0xb4, 0xbe, 0x78, 0x3c, 0xa3, 0xcb, 0x6a, 0x16,
|
|
0x7a, 0xfc, 0x33, 0xcd, 0x12, 0xf3, 0x00, 0x2f,
|
|
0x3e, 0xaa, 0xef, 0x7c, 0x26, 0xd3, 0x6f, 0x46,
|
|
0x8e, 0xb8, 0x54, 0x4c, 0x6a, 0xc3, 0x85, 0x92,
|
|
0x44, 0xc1, 0xe2, 0x03, 0xfe, 0xfc, 0xca, 0xff,
|
|
0x3b, 0x03, 0x9a, 0xf0, 0xd8, 0xe7, 0x2d, 0xb0,
|
|
0xe3, 0x64, 0x9f, 0xb9, 0x78, 0xd3, 0xca, 0x4c,
|
|
0xa2, 0xdd, 0x1f, 0x68, 0x9a, 0x9b, 0xcc, 0xb9,
|
|
0x79, 0x59, 0xb4, 0xac, 0x4e, 0x7d, 0xce, 0xa3,
|
|
0xc7, 0x23, 0xe6, 0x1c, 0xcd, 0x8d, 0xaa, 0xaa,
|
|
0xdb, 0x21, 0xa1, 0xec, 0xb8, 0xbe, 0x53, 0x60,
|
|
0x4f, 0xf4, 0x0b, 0xef, 0xad, 0x1d, 0x45, 0x62,
|
|
0x65, 0x88, 0xfe, 0x15, 0x47, 0x25, 0x61, 0xa5,
|
|
0x65, 0x7a, 0x17, 0xaa, 0x08, 0x3f, 0xe8, 0xf2
|
|
};
|
|
|
|
/* overall length too much */
|
|
static unsigned char too_long_ech[] = {
|
|
0xfe, 0x0d, 0xFF, 0xba /* ext type & length */
|
|
};
|
|
|
|
/* overall length too short */
|
|
static unsigned char too_short_ech[] = {
|
|
0xfe, 0x0d, 0x00, 0x00 /* ext type & length */
|
|
};
|
|
|
|
/* no inner/outer value */
|
|
static unsigned char no_innerouter_ech[] = {
|
|
0xfe, 0x0d, 0x00, 0x00, /* ext type & length */
|
|
0x00
|
|
};
|
|
|
|
/* ECH inner/outer bad value */
|
|
static unsigned char bad_innerouter_ech[] = {
|
|
0xfe, 0x0d, 0x00, 0xba, /* ext type & length */
|
|
0xFF
|
|
};
|
|
|
|
/* too short to get to KDF */
|
|
static unsigned char too_short_kdf[] = {
|
|
0xfe, 0x0d, 0x00, 0x02, /* ext type & length */
|
|
0x00,
|
|
0x00, 0x01, 0x00, 0x01 /* cipher suite KDF, AEAD */
|
|
};
|
|
|
|
/* too short to get to AEAD */
|
|
static unsigned char too_short_aead[] = {
|
|
0xfe, 0x0d, 0x00, 0x04, /* ext type & length */
|
|
0x00,
|
|
0x00, 0x01, 0x00, 0x01 /* cipher suite KDF, AEAD */
|
|
};
|
|
|
|
/* too short to get to config_id */
|
|
static unsigned char too_short_cid[] = {
|
|
0xfe, 0x0d, 0x00, 0x05, /* ext type & length */
|
|
0x00,
|
|
0x00, 0x01, 0x00, 0x01, /* cipher suite KDF, AEAD */
|
|
0x7c
|
|
};
|
|
|
|
/* zero length encap (only ok in HRR) */
|
|
static unsigned char zero_encap_len[] = {
|
|
0xfe, 0x0d, 0x00, 0xba, /* ext type & length */
|
|
0x00,
|
|
0x00, 0x01, 0x00, 0x01, /* cipher suite KDF, AEAD */
|
|
0x7c,
|
|
0x00, 0x00
|
|
};
|
|
|
|
/* too short to get to encap_len */
|
|
static unsigned char too_short_encap_len[] = {
|
|
0xfe, 0x0d, 0x00, 0x07, /* ext type & length */
|
|
0x00,
|
|
0x00, 0x01, 0x00, 0x01, /* cipher suite KDF, AEAD */
|
|
0x7c,
|
|
0x00
|
|
};
|
|
|
|
/* too long encap len */
|
|
static unsigned char too_long_encap_len[] = {
|
|
0xfe, 0x0d, 0x00, 0xba, /* ext type & length */
|
|
0x00,
|
|
0x00, 0x01, 0x00, 0x01, /* cipher suite KDF, AEAD */
|
|
0x7c,
|
|
0xFF, 0xFF
|
|
};
|
|
|
|
/* bit long encap len (more than extension) */
|
|
static unsigned char bit_long_encap_len[] = {
|
|
0xfe, 0x0d, 0x00, 0xba, /* ext type & length */
|
|
0x00,
|
|
0x00, 0x01, 0x00, 0x01, /* cipher suite KDF, AEAD */
|
|
0x7c,
|
|
0x00, 0xFF
|
|
};
|
|
|
|
/* too short to get to payload_len */
|
|
static unsigned char too_short_payload_len[] = {
|
|
0xfe, 0x0d, 0x00, 0x29, /* ext type & length */
|
|
0x00,
|
|
0x00, 0x01, 0x00, 0x01, /* cipher suite KDF, AEAD */
|
|
0x7c,
|
|
0x00, 0x20, /* encap len then encap val */
|
|
0x59, 0x87, 0xbe, 0x13, 0xd0, 0xf1, 0x0e, 0x23,
|
|
0xcb, 0x28, 0x26, 0xc2, 0x88, 0xd0, 0x8f, 0xac,
|
|
0x04, 0x99, 0x54, 0x30, 0xa2, 0x0f, 0xfe, 0x53,
|
|
0xf5, 0xa5, 0x92, 0x01, 0xb1, 0x56, 0xd2, 0x3f,
|
|
0x00, 0x90 /* payload len then payload */
|
|
};
|
|
|
|
/* bit long payload_len */
|
|
static unsigned char bit_long_payload_len[] = {
|
|
0xfe, 0x0d, 0x00, 0xba, /* ext type & length */
|
|
0x00,
|
|
0x00, 0x01, 0x00, 0x01, /* cipher suite KDF, AEAD */
|
|
0x7c,
|
|
0x00, 0x20, /* encap len then encap val */
|
|
0x59, 0x87, 0xbe, 0x13, 0xd0, 0xf1, 0x0e, 0x23,
|
|
0xcb, 0x28, 0x26, 0xc2, 0x88, 0xd0, 0x8f, 0xac,
|
|
0x04, 0x99, 0x54, 0x30, 0xa2, 0x0f, 0xfe, 0x53,
|
|
0xf5, 0xa5, 0x92, 0x01, 0xb1, 0x56, 0xd2, 0x3f,
|
|
0x00, 0xba /* payload len then payload */
|
|
};
|
|
|
|
/* zero payload_len */
|
|
static unsigned char zero_payload_len[] = {
|
|
0xfe, 0x0d, 0x00, 0xba, /* ext type & length */
|
|
0x00,
|
|
0x00, 0x01, 0x00, 0x01, /* cipher suite KDF, AEAD */
|
|
0x7c,
|
|
0x00, 0x20, /* encap len then encap val */
|
|
0x59, 0x87, 0xbe, 0x13, 0xd0, 0xf1, 0x0e, 0x23,
|
|
0xcb, 0x28, 0x26, 0xc2, 0x88, 0xd0, 0x8f, 0xac,
|
|
0x04, 0x99, 0x54, 0x30, 0xa2, 0x0f, 0xfe, 0x53,
|
|
0xf5, 0xa5, 0x92, 0x01, 0xb1, 0x56, 0xd2, 0x3f,
|
|
0x00, 0x00 /* payload len then payload */
|
|
};
|
|
|
|
/*
|
|
* Structure for test vectors for ECH in the outer CH
|
|
* - value to use to overwrite encoded ECH
|
|
* - expected result (1 for good, 0 for bad)
|
|
* - expected error reason in the case of bad
|
|
*
|
|
* For each test, we replace the first |len| octets of the
|
|
* ECH extension in the outer CH with the associated |val|.
|
|
*
|
|
* Note that the overall length of the outer CH needs to
|
|
* be maintained as otherwise outer length fields that are not
|
|
* re-computed will be wrong. (We include a test of that as
|
|
* well.) A radical change in the content of encoded inner
|
|
* values (e.g. eliminating compression entirely) could break
|
|
* these tests, but minor changes should have no effect due to
|
|
* padding. (Such a radical change showing up as a fail of
|
|
* these tests is arguably a good outcome.)
|
|
*/
|
|
typedef struct {
|
|
const unsigned char *val;
|
|
size_t len;
|
|
int rv_expected; /* expected result */
|
|
int err_expected; /* expected error */
|
|
} TEST_ECHOUTER;
|
|
|
|
static TEST_ECHOUTER test_echs[] = {
|
|
/* 1. basic case - copy to show test code works with no change */
|
|
{ NULL, 0, 1, SSL_ERROR_NONE },
|
|
|
|
/* 2. good encoding/length but breaks TLS session integrity */
|
|
{ entire_encoded_ech, sizeof(entire_encoded_ech),
|
|
0, /* expected result */
|
|
SSL_R_DECRYPTION_FAILED_OR_BAD_RECORD_MAC },
|
|
/* 3. ECH length too long */
|
|
{ too_long_ech, sizeof(too_long_ech),
|
|
0, /* expected result */ SSL_R_BAD_EXTENSION },
|
|
/* 4. ECH length too short */
|
|
{ too_short_ech, sizeof(too_short_ech),
|
|
0, /* expected result */ SSL_R_BAD_EXTENSION },
|
|
/* 5. no inner/outer value */
|
|
{ no_innerouter_ech, sizeof(no_innerouter_ech),
|
|
0, /* expected result */ SSL_R_BAD_EXTENSION },
|
|
/* 6. inner/outer bad value */
|
|
{ bad_innerouter_ech, sizeof(bad_innerouter_ech),
|
|
0, /* expected result */ SSL_R_BAD_EXTENSION },
|
|
/* 7. too_short_kdf value */
|
|
{ too_short_kdf, sizeof(too_short_kdf),
|
|
0, /* expected result */ SSL_R_BAD_EXTENSION },
|
|
/* 8. too_short_aead value */
|
|
{ too_short_aead, sizeof(too_short_aead),
|
|
0, /* expected result */ SSL_R_BAD_EXTENSION },
|
|
/* 9. too_short_cid value */
|
|
{ too_short_cid, sizeof(too_short_cid),
|
|
0, /* expected result */ SSL_R_BAD_EXTENSION },
|
|
/* 10. zero_encap_len value */
|
|
{ zero_encap_len, sizeof(zero_encap_len),
|
|
0, /* expected result */ SSL_R_BAD_EXTENSION },
|
|
/* 11. too_short_encap_len value */
|
|
{ too_short_encap_len, sizeof(too_short_encap_len),
|
|
0, /* expected result */ SSL_R_BAD_EXTENSION },
|
|
/* 12. too_long_encap_len value */
|
|
{ too_long_encap_len, sizeof(too_long_encap_len),
|
|
0, /* expected result */ SSL_R_BAD_EXTENSION },
|
|
/* 13. bit_long_encap_len value */
|
|
{ bit_long_encap_len, sizeof(bit_long_encap_len),
|
|
0, /* expected result */ SSL_R_BAD_EXTENSION },
|
|
/* 14. too_short_payload_len value */
|
|
{ too_short_payload_len, sizeof(too_short_payload_len),
|
|
0, /* expected result */ SSL_R_BAD_EXTENSION },
|
|
/* 15. bit_long_payload_len value */
|
|
{ bit_long_payload_len, sizeof(bit_long_payload_len),
|
|
0, /* expected result */ SSL_R_BAD_EXTENSION },
|
|
/* 16. zero_payload_len value */
|
|
{ zero_payload_len, sizeof(zero_payload_len),
|
|
0, /* expected result */ SSL_R_BAD_EXTENSION },
|
|
};
|
|
|
|
/*
|
|
* Given a SH (or HRR) find the offsets of the ECH (if any)
|
|
* sh is the SH buffer
|
|
* sh_len is the length of the SH
|
|
* exts points to offset of extensions
|
|
* echoffset points to offset of ECH
|
|
* echtype points to the ext type of the ECH
|
|
* for success, other otherwise
|
|
*
|
|
* Offsets are returned to the type or length field in question.
|
|
* Offsets are set to zero if relevant thing not found.
|
|
*
|
|
* Note: input here is untrusted!
|
|
*/
|
|
static int ech_get_sh_offsets(const unsigned char *sh,
|
|
size_t sh_len, size_t *exts,
|
|
size_t *echoffset, uint16_t *echtype)
|
|
{
|
|
unsigned int elen = 0, etype = 0, pi_tmp = 0;
|
|
const unsigned char *pp_tmp = NULL, *shstart = NULL, *estart = NULL;
|
|
PACKET pkt;
|
|
size_t extlens = 0;
|
|
int done = 0;
|
|
#ifdef OSSL_ECH_SUPERVERBOSE
|
|
size_t echlen = 0; /* length of ECH, including type & ECH-internal length */
|
|
size_t sessid_offset = 0;
|
|
size_t sessid_len = 0;
|
|
#endif
|
|
|
|
if (sh == NULL || sh_len == 0 || exts == NULL || echoffset == NULL
|
|
|| echtype == NULL)
|
|
return 0;
|
|
*exts = *echoffset = *echtype = 0;
|
|
if (!PACKET_buf_init(&pkt, sh, sh_len))
|
|
return 0;
|
|
shstart = PACKET_data(&pkt);
|
|
if (!PACKET_get_net_2(&pkt, &pi_tmp))
|
|
return 0;
|
|
/* if we're not TLSv1.2+ then we can bail, but it's not an error */
|
|
if (pi_tmp != TLS1_2_VERSION)
|
|
return 1;
|
|
if (!PACKET_get_bytes(&pkt, &pp_tmp, SSL3_RANDOM_SIZE)
|
|
#ifdef OSSL_ECH_SUPERVERBOSE
|
|
|| (sessid_offset = PACKET_data(&pkt) - shstart) == 0
|
|
#endif
|
|
|| !PACKET_get_1(&pkt, &pi_tmp) /* sessid len */
|
|
#ifdef OSSL_ECH_SUPERVERBOSE
|
|
|| (sessid_len = (size_t)pi_tmp) == 0
|
|
#endif
|
|
|| !PACKET_get_bytes(&pkt, &pp_tmp, pi_tmp) /* sessid */
|
|
|| !PACKET_get_net_2(&pkt, &pi_tmp) /* ciphersuite */
|
|
|| !PACKET_get_1(&pkt, &pi_tmp) /* compression */
|
|
|| (*exts = PACKET_data(&pkt) - shstart) == 0
|
|
|| !PACKET_get_net_2(&pkt, &pi_tmp)) /* len(extensions) */
|
|
return 0;
|
|
extlens = (size_t)pi_tmp;
|
|
if (extlens == 0) /* not an error, in theory */
|
|
return 1;
|
|
estart = PACKET_data(&pkt);
|
|
while (PACKET_remaining(&pkt) > 0
|
|
&& (size_t)(PACKET_data(&pkt) - estart) < extlens
|
|
&& done < 1) {
|
|
if (!PACKET_get_net_2(&pkt, &etype)
|
|
|| !PACKET_get_net_2(&pkt, &elen))
|
|
return 0;
|
|
if (etype == TLSEXT_TYPE_ech) {
|
|
if (elen == 0)
|
|
return 0;
|
|
*echoffset = PACKET_data(&pkt) - shstart - 4;
|
|
*echtype = etype;
|
|
#ifdef OSSL_ECH_SUPERVERBOSE
|
|
echlen = elen + 4; /* type and length included */
|
|
#endif
|
|
done++;
|
|
}
|
|
if (!PACKET_get_bytes(&pkt, &pp_tmp, elen))
|
|
return 0;
|
|
}
|
|
#ifdef OSSL_ECH_SUPERVERBOSE
|
|
OSSL_TRACE_BEGIN(TLS)
|
|
{
|
|
BIO_printf(trc_out, "orig SH/ECH type: %4x\n", *echtype);
|
|
}
|
|
OSSL_TRACE_END(TLS);
|
|
ossl_ech_pbuf("orig SH", (unsigned char *)sh, sh_len);
|
|
ossl_ech_pbuf("orig SH session_id", (unsigned char *)sh + sessid_offset,
|
|
sessid_len);
|
|
ossl_ech_pbuf("orig SH exts", (unsigned char *)sh + *exts, extlens);
|
|
ossl_ech_pbuf("orig SH/ECH ", (unsigned char *)sh + *echoffset, echlen);
|
|
#endif
|
|
return 1;
|
|
}
|
|
|
|
/* Do a HPKE seal of a padded encoded inner */
|
|
static int seal_encoded_inner(char **out, int *outlen,
|
|
unsigned char *ei, size_t eilen,
|
|
const char *ch, int chlen,
|
|
size_t echoffset, size_t echlen)
|
|
{
|
|
int res = 0;
|
|
OSSL_HPKE_CTX *hctx = NULL;
|
|
unsigned char *mypub = NULL;
|
|
static size_t mypublen = 0;
|
|
unsigned char *theirpub = NULL;
|
|
size_t theirpublen = 0;
|
|
unsigned char *ct = NULL;
|
|
size_t ctlen = 0;
|
|
unsigned char *aad = NULL;
|
|
size_t aadlen = 0;
|
|
unsigned char *chout = NULL;
|
|
size_t choutlen = 0;
|
|
|
|
hctx = OSSL_HPKE_CTX_new(OSSL_HPKE_MODE_BASE, hpke_suite,
|
|
OSSL_HPKE_ROLE_SENDER, NULL, NULL);
|
|
if (!TEST_ptr(hctx))
|
|
goto err;
|
|
mypublen = OSSL_HPKE_get_public_encap_size(hpke_suite);
|
|
if (!TEST_ptr(mypub = OPENSSL_malloc(mypublen)))
|
|
goto err;
|
|
theirpub = bin_echconfig + 11;
|
|
theirpublen = 0x20;
|
|
if (!TEST_true(OSSL_HPKE_encap(hctx, mypub, &mypublen,
|
|
theirpub, theirpublen,
|
|
hpke_info, hpke_infolen)))
|
|
goto err;
|
|
/* form up aad which is entire outer CH: zero's instead of ECH ciphertext */
|
|
choutlen = chlen;
|
|
if (!TEST_ptr(chout = OPENSSL_malloc(choutlen)))
|
|
goto err;
|
|
memcpy(chout, ch, chlen);
|
|
memcpy(chout + echoffset + 12, mypub, mypublen);
|
|
ct = chout + echoffset + 12 + mypublen + 2;
|
|
ctlen = OSSL_HPKE_get_ciphertext_size(hpke_suite, eilen);
|
|
chout[echoffset + 12 + mypublen] = (ctlen >> 8) & 0xff;
|
|
chout[echoffset + 12 + mypublen + 1] = ctlen & 0xff;
|
|
/* the 9 skips the record layer header */
|
|
aad = chout + SSL3_RT_HEADER_LENGTH + SSL3_HM_HEADER_LENGTH;
|
|
aadlen = chlen - (SSL3_RT_HEADER_LENGTH + SSL3_HM_HEADER_LENGTH);
|
|
if (short_test == 0 && ct + ctlen != aad + aadlen) {
|
|
TEST_info("length oddity");
|
|
goto err;
|
|
}
|
|
memset(ct, 0, ctlen);
|
|
if (!TEST_true(OSSL_HPKE_seal(hctx, ct, &ctlen, aad, aadlen, ei, eilen)))
|
|
goto err;
|
|
*out = (char *)chout;
|
|
*outlen = (int)choutlen;
|
|
res = 1;
|
|
err:
|
|
OPENSSL_free(mypub);
|
|
OSSL_HPKE_CTX_free(hctx);
|
|
return res;
|
|
}
|
|
|
|
/* We'll either corrupt or copy the message based on the test index */
|
|
static int corrupt_or_copy(const char *msg, const int msglen,
|
|
char **msgout, int *msgoutlen)
|
|
{
|
|
TEST_ECHINNER *ti = NULL;
|
|
TEST_SH *ts = NULL;
|
|
TEST_ECHOUTER *to = NULL;
|
|
int is_ch = 0, is_sh = 0;
|
|
unsigned char *encoded_inner = NULL;
|
|
size_t prelen, fblen, postlen;
|
|
size_t encoded_innerlen = 0;
|
|
size_t sessid = 0, exts = 0, extlens = 0, echoffset = 0, echlen = 0;
|
|
size_t snioffset = 0, snilen = 0;
|
|
uint16_t echtype;
|
|
int inner, rv = 0;
|
|
|
|
/* is it a ClientHello or not? */
|
|
if (testcase == TESTCASE_CH && msglen > 10 && msg[0] == SSL3_RT_HANDSHAKE
|
|
&& msg[5] == SSL3_MT_CLIENT_HELLO)
|
|
is_ch = 1;
|
|
/* is it a ServerHello or not? */
|
|
if (testcase == TESTCASE_SH && msglen > 10 && msg[0] == SSL3_RT_HANDSHAKE
|
|
&& msg[5] == SSL3_MT_SERVER_HELLO)
|
|
is_sh = 1;
|
|
if (testcase == TESTCASE_ECH && msglen > 10 && msg[0] == SSL3_RT_HANDSHAKE
|
|
&& msg[5] == SSL3_MT_CLIENT_HELLO)
|
|
is_ch = 1;
|
|
|
|
if (testcase == TESTCASE_CH && is_ch == 1) {
|
|
if (testiter >= (int)OSSL_NELEM(test_inners))
|
|
return 0;
|
|
ti = &test_inners[testiter];
|
|
prelen = ti->pre == NULL ? 0 : ti->prelen;
|
|
fblen = ti->forbork == NULL ? 0 : ti->fblen;
|
|
postlen = ti->post == NULL ? 0 : ti->postlen;
|
|
/* check for editing errors */
|
|
if (testiter != 0 && testiter != 1
|
|
&& prelen + fblen + postlen != sizeof(entire_encoded_inner)) {
|
|
TEST_info("manual sizing error");
|
|
return 0;
|
|
}
|
|
if (testiter == 1) /* the only case with a short ciphertext for now */
|
|
short_test = 1;
|
|
if (!TEST_true(ossl_ech_helper_get_ch_offsets((const unsigned char *)msg
|
|
+ SSL3_RT_HEADER_LENGTH
|
|
+ SSL3_HM_HEADER_LENGTH,
|
|
msglen
|
|
- SSL3_RT_HEADER_LENGTH
|
|
- SSL3_HM_HEADER_LENGTH,
|
|
&sessid, &exts, &extlens,
|
|
&echoffset, &echtype, &echlen,
|
|
&snioffset, &snilen, &inner)))
|
|
return 0;
|
|
/* that better be an outer ECH :-) */
|
|
if (echoffset > 0 && !TEST_int_eq(inner, 0)) {
|
|
TEST_info("better send outer");
|
|
return 0;
|
|
}
|
|
/* bump offsets by 9 */
|
|
echoffset += 9;
|
|
snioffset += 9;
|
|
/*
|
|
* if it doesn't have an ECH, or if the forbork value in our test
|
|
* array is NULL, just copy the entire input to output
|
|
*/
|
|
if (echoffset == 9 || ti->forbork == NULL) {
|
|
if (!TEST_ptr(*msgout = OPENSSL_memdup(msg, msglen)))
|
|
return 0;
|
|
*msgoutlen = msglen;
|
|
return 1;
|
|
}
|
|
/* in this case, construct the encoded inner, then seal that */
|
|
encoded_innerlen = prelen + fblen + postlen;
|
|
if (!TEST_ptr(encoded_inner = OPENSSL_malloc(encoded_innerlen)))
|
|
return 0;
|
|
if (ti->pre != NULL) /* keep fuzz checker happy */
|
|
memcpy(encoded_inner, ti->pre, prelen);
|
|
if (ti->forbork != NULL)
|
|
memcpy(encoded_inner + prelen, ti->forbork, fblen);
|
|
if (ti->post != NULL)
|
|
memcpy(encoded_inner + prelen + fblen, ti->post, postlen);
|
|
if (!TEST_true(seal_encoded_inner(msgout, msgoutlen,
|
|
encoded_inner, encoded_innerlen,
|
|
msg, msglen, echoffset, echlen)))
|
|
return 0;
|
|
OPENSSL_free(encoded_inner);
|
|
return 1;
|
|
}
|
|
|
|
if (testcase == TESTCASE_ECH && is_ch == 1) {
|
|
if (testiter >= (int)OSSL_NELEM(test_echs))
|
|
return 0;
|
|
to = &test_echs[testiter];
|
|
if (!TEST_true(ossl_ech_helper_get_ch_offsets((const unsigned char *)msg
|
|
+ SSL3_RT_HEADER_LENGTH
|
|
+ SSL3_HM_HEADER_LENGTH,
|
|
msglen
|
|
- SSL3_RT_HEADER_LENGTH
|
|
- SSL3_HM_HEADER_LENGTH,
|
|
&sessid, &exts, &extlens,
|
|
&echoffset, &echtype, &echlen,
|
|
&snioffset, &snilen, &inner)))
|
|
return 0;
|
|
/* if it doesn't have an ECH just copy the entire input to output */
|
|
if (echoffset == 0) {
|
|
if (!TEST_ptr(*msgout = OPENSSL_memdup(msg, msglen)))
|
|
return 0;
|
|
*msgoutlen = msglen;
|
|
return 1;
|
|
}
|
|
/* check for editing errors, the +4 is for ext type + len */
|
|
if (to->len > (echlen + 4)) {
|
|
TEST_info("manual sizing error");
|
|
return 0;
|
|
}
|
|
if (!TEST_ptr(*msgout = OPENSSL_memdup(msg, msglen)))
|
|
return 0;
|
|
*msgoutlen = msglen;
|
|
/*
|
|
* overwrite (some of) the outer ECH, in contrast to
|
|
* the above case, here we're overwriting the ECH
|
|
* ext type and length as well, the +9 is for record
|
|
* layer framing as before
|
|
*/
|
|
if (to->val != NULL) /* keep fuzz checker happy */
|
|
memcpy(*msgout + echoffset + 9, to->val, to->len);
|
|
return 1;
|
|
}
|
|
|
|
if (testcase == TESTCASE_SH && is_sh == 1) {
|
|
if (testiter >= (int)OSSL_NELEM(test_shs))
|
|
return 0;
|
|
ts = &test_shs[testiter];
|
|
if (ts->borkage == 0) {
|
|
if (!TEST_ptr(*msgout = OPENSSL_memdup(msg, msglen)))
|
|
return 0;
|
|
*msgoutlen = msglen;
|
|
return 1;
|
|
}
|
|
/* flip bits in ECH confirmation */
|
|
if ((ts->borkage & OSSL_ECH_BORK_FLIP) != 0) {
|
|
if (!TEST_ptr(*msgout = OPENSSL_memdup(msg, msglen)))
|
|
return 0;
|
|
if ((ts->borkage & OSSL_ECH_BORK_HRR) != 0) {
|
|
rv = ech_get_sh_offsets((unsigned char *)msg + 9,
|
|
msglen - 9,
|
|
&exts, &echoffset,
|
|
&echtype);
|
|
if (!TEST_int_eq(rv, 1))
|
|
return 0;
|
|
if (echoffset > 0) {
|
|
(*msgout)[9 + echoffset + 4] = (*msgout)[9 + echoffset + 4] ^ 0xaa;
|
|
}
|
|
} else {
|
|
(*msgout)[9 + 2 + SSL3_RANDOM_SIZE - 4] = (*msgout)[9 + 2 + SSL3_RANDOM_SIZE - 4] ^ 0xaa;
|
|
}
|
|
*msgoutlen = msglen;
|
|
return 1;
|
|
}
|
|
if ((ts->borkage & OSSL_ECH_BORK_REPLACE) != 0 && (ts->borkage & OSSL_ECH_BORK_HRR) != 0) {
|
|
if (!TEST_ptr(*msgout = OPENSSL_memdup(msg, msglen)))
|
|
return 0;
|
|
rv = ech_get_sh_offsets((unsigned char *)msg + 9,
|
|
msglen - 9,
|
|
&exts, &echoffset, &echtype);
|
|
if (!TEST_int_eq(rv, 1))
|
|
return 0;
|
|
if (echoffset > 0)
|
|
memcpy(&((*msgout)[9 + echoffset]), ts->bork, ts->blen);
|
|
*msgoutlen = msglen;
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
/* if doing nothing, do that... */
|
|
if (!TEST_ptr(*msgout = OPENSSL_memdup(msg, msglen)))
|
|
return 0;
|
|
*msgoutlen = msglen;
|
|
return 1;
|
|
}
|
|
|
|
static void copy_flags(BIO *bio)
|
|
{
|
|
int flags;
|
|
BIO *next = BIO_next(bio);
|
|
|
|
flags = BIO_test_flags(next, BIO_FLAGS_SHOULD_RETRY | BIO_FLAGS_RWS);
|
|
BIO_clear_flags(bio, BIO_FLAGS_SHOULD_RETRY | BIO_FLAGS_RWS);
|
|
BIO_set_flags(bio, flags);
|
|
}
|
|
|
|
/*
|
|
* filter to corrupt or copy messages - this is basically copied
|
|
* from the setup in test/sslcorrupttest.c
|
|
*/
|
|
static int tls_corrupt_write(BIO *bio, const char *in, int inl)
|
|
{
|
|
int ret;
|
|
BIO *next = BIO_next(bio);
|
|
char *copy = NULL;
|
|
int copylen = 0;
|
|
|
|
ret = corrupt_or_copy(in, inl, ©, ©len);
|
|
if (ret == 0)
|
|
goto out;
|
|
ret = BIO_write(next, copy, inl);
|
|
copy_flags(bio);
|
|
out:
|
|
OPENSSL_free(copy);
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* This and others below are NOOP filters as we only mess
|
|
* with things via the write filter method
|
|
*/
|
|
static int tls_noop_read(BIO *bio, char *out, int outl)
|
|
{
|
|
int ret;
|
|
BIO *next = BIO_next(bio);
|
|
|
|
ret = BIO_read(next, out, outl);
|
|
copy_flags(bio);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static long tls_noop_ctrl(BIO *bio, int cmd, long num, void *ptr)
|
|
{
|
|
long ret;
|
|
BIO *next = BIO_next(bio);
|
|
|
|
if (next == NULL)
|
|
return 0;
|
|
|
|
switch (cmd) {
|
|
case BIO_CTRL_DUP:
|
|
ret = 0L;
|
|
break;
|
|
default:
|
|
ret = BIO_ctrl(next, cmd, num, ptr);
|
|
break;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static int tls_noop_gets(BIO *bio, char *buf, int size)
|
|
{
|
|
/* We don't support this - not needed anyway */
|
|
return -1;
|
|
}
|
|
|
|
static int tls_noop_puts(BIO *bio, const char *str)
|
|
{
|
|
/* We don't support this - not needed anyway */
|
|
return -1;
|
|
}
|
|
|
|
static int tls_noop_new(BIO *bio)
|
|
{
|
|
BIO_set_init(bio, 1);
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int tls_noop_free(BIO *bio)
|
|
{
|
|
BIO_set_init(bio, 0);
|
|
|
|
return 1;
|
|
}
|
|
|
|
#define BIO_TYPE_CUSTOM_CORRUPT (0x80 | BIO_TYPE_FILTER)
|
|
#define BIO_TYPE_CUSTOM_SPLIT (0x81 | BIO_TYPE_FILTER)
|
|
|
|
static BIO_METHOD *method_tls_corrupt = NULL;
|
|
|
|
/* Note: Not thread safe! */
|
|
static const BIO_METHOD *bio_f_tls_corrupt_filter(void)
|
|
{
|
|
if (method_tls_corrupt == NULL) {
|
|
method_tls_corrupt = BIO_meth_new(BIO_TYPE_CUSTOM_CORRUPT,
|
|
"TLS corrupt filter");
|
|
if (method_tls_corrupt == NULL
|
|
|| !BIO_meth_set_write(method_tls_corrupt, tls_corrupt_write)
|
|
|| !BIO_meth_set_read(method_tls_corrupt, tls_noop_read)
|
|
|| !BIO_meth_set_puts(method_tls_corrupt, tls_noop_puts)
|
|
|| !BIO_meth_set_gets(method_tls_corrupt, tls_noop_gets)
|
|
|| !BIO_meth_set_ctrl(method_tls_corrupt, tls_noop_ctrl)
|
|
|| !BIO_meth_set_create(method_tls_corrupt, tls_noop_new)
|
|
|| !BIO_meth_set_destroy(method_tls_corrupt, tls_noop_free))
|
|
return NULL;
|
|
}
|
|
return method_tls_corrupt;
|
|
}
|
|
|
|
static void bio_f_tls_corrupt_filter_free(void)
|
|
{
|
|
BIO_meth_free(method_tls_corrupt);
|
|
}
|
|
|
|
static int test_ch_corrupt(int testidx)
|
|
{
|
|
SSL_CTX *sctx = NULL, *cctx = NULL;
|
|
SSL *server = NULL, *client = NULL;
|
|
BIO *c_to_s_fbio;
|
|
int testresult = 0, err = 0, connrv = 0, err_reason = 0;
|
|
int exp_err = SSL_ERROR_NONE;
|
|
TEST_ECHINNER *ti = NULL;
|
|
const char *err_str = NULL;
|
|
|
|
testcase = TESTCASE_CH;
|
|
testiter = testidx;
|
|
ti = &test_inners[testidx];
|
|
if (verbose)
|
|
TEST_info("Starting #%d", testidx + 1);
|
|
if (!TEST_true(create_ssl_ctx_pair(NULL, TLS_server_method(),
|
|
TLS_client_method(),
|
|
TLS1_3_VERSION, TLS1_3_VERSION,
|
|
&sctx, &cctx, cert, privkey)))
|
|
return 0;
|
|
/* set server to be willing to only accept TLSv1.2 for test case 23 */
|
|
if (testidx == 22
|
|
&& !TEST_true(SSL_CTX_set_max_proto_version(sctx, TLS1_2_VERSION)))
|
|
goto end;
|
|
/* set server to be willing to accept TLSv1.2 for test case 24 */
|
|
if (testidx == 23
|
|
&& !TEST_true(SSL_CTX_set_min_proto_version(sctx, TLS1_2_VERSION)))
|
|
goto end;
|
|
/* set client/server to be willing to accept TLSv1.2 for test case 25 */
|
|
if (testidx == 24
|
|
&& !TEST_true(SSL_CTX_set_min_proto_version(sctx, TLS1_2_VERSION))
|
|
&& !TEST_true(SSL_CTX_set_min_proto_version(cctx, TLS1_2_VERSION)))
|
|
goto end;
|
|
if (!TEST_true(SSL_CTX_set1_echstore(sctx, es)))
|
|
goto end;
|
|
if (!TEST_ptr(c_to_s_fbio = BIO_new(bio_f_tls_corrupt_filter())))
|
|
goto end;
|
|
/* BIO is freed by create_ssl_connection on error */
|
|
if (!TEST_true(create_ssl_objects(sctx, cctx, &server, &client, NULL,
|
|
c_to_s_fbio)))
|
|
goto end;
|
|
if (!TEST_true(SSL_set1_ech_config_list(client, (unsigned char *)echconfig,
|
|
echconfiglen)))
|
|
goto end;
|
|
if (!TEST_true(SSL_set_tlsext_host_name(client, "foo.example.com")))
|
|
goto end;
|
|
exp_err = SSL_ERROR_SSL;
|
|
if (ti->err_expected == 0)
|
|
exp_err = SSL_ERROR_NONE;
|
|
connrv = create_ssl_connection(server, client, exp_err);
|
|
if (!TEST_int_eq(connrv, ti->rv_expected))
|
|
goto end;
|
|
if (verbose) {
|
|
err_str = ERR_reason_error_string(ti->err_expected);
|
|
err_reason = ERR_GET_REASON(ti->err_expected);
|
|
TEST_info("Expected error: %d/%s", err_reason, err_str);
|
|
}
|
|
if (connrv == 0) {
|
|
do {
|
|
err = ERR_get_error();
|
|
if (err == 0) {
|
|
TEST_error("ECH corruption: Unexpected error");
|
|
goto end;
|
|
}
|
|
err_reason = ERR_GET_REASON(err);
|
|
err_str = ERR_reason_error_string(err);
|
|
if (verbose)
|
|
TEST_info("Error reason: %d/%s", err_reason, err_str);
|
|
} while (err_reason != ti->err_expected);
|
|
}
|
|
testresult = 1;
|
|
end:
|
|
SSL_free(server);
|
|
SSL_free(client);
|
|
SSL_CTX_free(sctx);
|
|
SSL_CTX_free(cctx);
|
|
return testresult;
|
|
}
|
|
|
|
static int test_sh_corrupt(int testidx)
|
|
{
|
|
SSL_CTX *sctx = NULL, *cctx = NULL;
|
|
SSL *server = NULL, *client = NULL;
|
|
BIO *s_to_c_fbio;
|
|
TEST_SH *ts = NULL;
|
|
int testresult = 0, err = 0, connrv = 0, err_reason = 0;
|
|
int exp_err = SSL_ERROR_NONE;
|
|
unsigned char *retryconfig = NULL;
|
|
size_t retryconfiglen = 0;
|
|
const char *err_str = NULL;
|
|
|
|
testcase = TESTCASE_SH;
|
|
testiter = testidx;
|
|
ts = &test_shs[testidx];
|
|
if (verbose)
|
|
TEST_info("Starting #%d", testidx + 1);
|
|
if (!TEST_true(create_ssl_ctx_pair(NULL, TLS_server_method(),
|
|
TLS_client_method(),
|
|
TLS1_3_VERSION, TLS1_3_VERSION,
|
|
&sctx, &cctx, cert, privkey)))
|
|
return 0;
|
|
if (!TEST_true(SSL_CTX_set1_echstore(sctx, es)))
|
|
goto end;
|
|
if (!TEST_ptr(s_to_c_fbio = BIO_new(bio_f_tls_corrupt_filter())))
|
|
goto end;
|
|
/* BIO is freed by create_ssl_connection on error */
|
|
if (!TEST_true(create_ssl_objects(sctx, cctx, &server, &client,
|
|
s_to_c_fbio, NULL)))
|
|
goto end;
|
|
if ((ts->borkage & OSSL_ECH_BORK_GREASE) != 0) {
|
|
if (!TEST_true(SSL_set_options(client, SSL_OP_ECH_GREASE)))
|
|
goto end;
|
|
} else {
|
|
if (!TEST_true(SSL_set1_ech_config_list(client,
|
|
(unsigned char *)echconfig,
|
|
echconfiglen)))
|
|
goto end;
|
|
}
|
|
if (!TEST_true(SSL_set_tlsext_host_name(client, "foo.example.com")))
|
|
goto end;
|
|
if ((ts->borkage & OSSL_ECH_BORK_HRR) != 0
|
|
&& !TEST_true(SSL_set1_groups_list(server, "P-384")))
|
|
goto end;
|
|
exp_err = SSL_ERROR_SSL;
|
|
if (ts->err_expected == 0)
|
|
exp_err = SSL_ERROR_NONE;
|
|
connrv = create_ssl_connection(server, client, exp_err);
|
|
if (!TEST_int_eq(connrv, ts->rv_expected))
|
|
goto end;
|
|
if (connrv == 1 && (ts->borkage & OSSL_ECH_BORK_GREASE) != 0) {
|
|
if (!TEST_true(SSL_ech_get1_retry_config(client, &retryconfig,
|
|
&retryconfiglen))
|
|
|| !TEST_ptr(retryconfig)
|
|
|| !TEST_int_ne((int)retryconfiglen, 0))
|
|
goto end;
|
|
}
|
|
if (verbose) {
|
|
err_str = ERR_reason_error_string(ts->err_expected);
|
|
err_reason = ERR_GET_REASON(ts->err_expected);
|
|
TEST_info("Expected error: %d/%s", err_reason, err_str);
|
|
}
|
|
if (connrv == 0) {
|
|
do {
|
|
err = ERR_get_error();
|
|
if (err == 0) {
|
|
TEST_error("ECH corruption: Unexpected error");
|
|
goto end;
|
|
}
|
|
err_reason = ERR_GET_REASON(err);
|
|
err_str = ERR_reason_error_string(err);
|
|
if (verbose)
|
|
TEST_info("Error reason: %d/%s", err_reason, err_str);
|
|
} while (err_reason != ts->err_expected);
|
|
}
|
|
testresult = 1;
|
|
end:
|
|
OPENSSL_free(retryconfig);
|
|
SSL_free(server);
|
|
SSL_free(client);
|
|
SSL_CTX_free(sctx);
|
|
SSL_CTX_free(cctx);
|
|
return testresult;
|
|
}
|
|
|
|
typedef enum OPTION_choice {
|
|
OPT_ERR = -1,
|
|
OPT_EOF = 0,
|
|
OPT_VERBOSE,
|
|
OPT_TEST_ENUM
|
|
} OPTION_CHOICE;
|
|
|
|
static int test_ech_corrupt(int testidx)
|
|
{
|
|
SSL_CTX *sctx = NULL, *cctx = NULL;
|
|
SSL *server = NULL, *client = NULL;
|
|
BIO *c_to_s_fbio;
|
|
int testresult = 0, err = 0, connrv = 0, err_reason = 0;
|
|
int exp_err = SSL_ERROR_NONE;
|
|
TEST_ECHOUTER *to = NULL;
|
|
const char *err_str = NULL;
|
|
|
|
testcase = TESTCASE_ECH;
|
|
testiter = testidx;
|
|
to = &test_echs[testidx];
|
|
if (verbose)
|
|
TEST_info("Starting #%d", testidx + 1);
|
|
if (!TEST_true(create_ssl_ctx_pair(NULL, TLS_server_method(),
|
|
TLS_client_method(),
|
|
TLS1_3_VERSION, TLS1_3_VERSION,
|
|
&sctx, &cctx, cert, privkey)))
|
|
return 0;
|
|
if (!TEST_true(SSL_CTX_set1_echstore(sctx, es)))
|
|
goto end;
|
|
if (!TEST_ptr(c_to_s_fbio = BIO_new(bio_f_tls_corrupt_filter())))
|
|
goto end;
|
|
/* BIO is freed by create_ssl_connection on error */
|
|
if (!TEST_true(create_ssl_objects(sctx, cctx, &server, &client, NULL,
|
|
c_to_s_fbio)))
|
|
goto end;
|
|
if (!TEST_true(SSL_set1_ech_config_list(client, (unsigned char *)echconfig,
|
|
echconfiglen)))
|
|
goto end;
|
|
if (!TEST_true(SSL_set_tlsext_host_name(client, "foo.example.com")))
|
|
goto end;
|
|
exp_err = SSL_ERROR_SSL;
|
|
if (to->err_expected == 0)
|
|
exp_err = SSL_ERROR_NONE;
|
|
connrv = create_ssl_connection(server, client, exp_err);
|
|
if (!TEST_int_eq(connrv, to->rv_expected))
|
|
goto end;
|
|
if (verbose) {
|
|
err_str = ERR_reason_error_string(to->err_expected);
|
|
err_reason = ERR_GET_REASON(to->err_expected);
|
|
TEST_info("Expected error: %d/%s", err_reason, err_str);
|
|
}
|
|
if (connrv == 0) {
|
|
do {
|
|
err = ERR_get_error();
|
|
if (err == 0) {
|
|
TEST_error("ECH corruption: Unexpected error");
|
|
goto end;
|
|
}
|
|
err_reason = ERR_GET_REASON(err);
|
|
err_str = ERR_reason_error_string(err);
|
|
if (verbose)
|
|
TEST_info("Error reason: %d/%s", err_reason, err_str);
|
|
} while (err_reason != to->err_expected);
|
|
}
|
|
testresult = 1;
|
|
end:
|
|
SSL_free(server);
|
|
SSL_free(client);
|
|
SSL_CTX_free(sctx);
|
|
SSL_CTX_free(cctx);
|
|
return testresult;
|
|
}
|
|
|
|
/*
|
|
* Callback that corrupts a server Finished message.
|
|
* This doesn't seem to be documented, but the buffer here is the actual
|
|
* message and not a copy just for the callback, so we can corrupt it by
|
|
* flipping bits in the last octet of the server Finished. Presumably changing
|
|
* lengths would cause other breakage, but the below currently causes the
|
|
* `memcmp()` to fail in the client's call of `tls_process_finished()` which
|
|
* produces the desired effect of causing the TLS session to fail both because
|
|
* of ECH-required and subsequently because of a later handshake failure
|
|
* resulting in us not making the retry-configs available to the client.
|
|
*/
|
|
static void corrupt_server_finished(int write_p, int version, int content_type,
|
|
const void *buf, size_t msglen, SSL *ssl, void *arg)
|
|
{
|
|
unsigned char *msg = (unsigned char *)buf;
|
|
|
|
if (write_p == 0 && content_type == SSL3_RT_HANDSHAKE
|
|
&& msg[0] == SSL3_MT_FINISHED)
|
|
msg[msglen - 1] ^= 0xAA;
|
|
}
|
|
|
|
/*
|
|
* Test roundtrip with wrong ECHConfig, with and without corrupting
|
|
* the server Finished to check that retry-configs are not made
|
|
* available to the client in the latter case.
|
|
*/
|
|
static int ech_retry_config_test(int idx)
|
|
{
|
|
int res = 0, clientstatus, serverstatus;
|
|
SSL_CTX *cctx = NULL, *sctx = NULL;
|
|
SSL *clientssl = NULL, *serverssl = NULL;
|
|
char *cinner = NULL, *couter = NULL, *sinner = NULL, *souter = NULL;
|
|
unsigned char *retryconfig = NULL;
|
|
size_t retryconfiglen = 0;
|
|
int err = 0, err_reason = 0, exp_err = ERR_R_OSSL_STORE_LIB;
|
|
const char *err_str = NULL;
|
|
|
|
if (!TEST_true(create_ssl_ctx_pair(libctx, TLS_server_method(),
|
|
TLS_client_method(),
|
|
TLS1_3_VERSION, TLS1_3_VERSION,
|
|
&sctx, &cctx, cert, privkey)))
|
|
goto end;
|
|
if (!TEST_true(SSL_CTX_set1_echstore(sctx, es)))
|
|
goto end;
|
|
if (!TEST_true(create_ssl_objects(sctx, cctx, &serverssl,
|
|
&clientssl, NULL, NULL)))
|
|
goto end;
|
|
if (!TEST_true(SSL_set_tlsext_host_name(clientssl, "foo.example.com")))
|
|
goto end;
|
|
/* set a real but wrong ECHConfig */
|
|
if (!TEST_true(SSL_set1_ech_config_list(clientssl, (unsigned char *)ec_kp2,
|
|
ec_kp2len)))
|
|
goto end;
|
|
if (idx == 1) /* corrupt as desired */
|
|
SSL_set_msg_callback(clientssl, corrupt_server_finished);
|
|
/* real but wrong => failure, due to ECH */
|
|
if (!TEST_false(create_ssl_connection(serverssl, clientssl,
|
|
SSL_R_ECH_REQUIRED)))
|
|
goto end;
|
|
serverstatus = SSL_ech_get1_status(serverssl, &sinner, &souter);
|
|
if (verbose)
|
|
TEST_info("ech_retry_config_test: server status %d, %s, %s",
|
|
serverstatus, sinner, souter);
|
|
if (!TEST_int_eq(serverstatus, SSL_ECH_STATUS_GREASE))
|
|
goto end;
|
|
/* override cert verification */
|
|
SSL_set_verify_result(clientssl, X509_V_OK);
|
|
clientstatus = SSL_ech_get1_status(clientssl, &cinner, &couter);
|
|
if (verbose)
|
|
TEST_info("ech_retry_config_test: client status %d, %s, %s",
|
|
clientstatus, cinner, couter);
|
|
if (!TEST_int_eq(clientstatus, SSL_ECH_STATUS_FAILED_ECH))
|
|
goto end;
|
|
if (idx == 0) { /* no corruption, retry-configs made available */
|
|
if (!TEST_true(SSL_ech_get1_retry_config(clientssl, &retryconfig,
|
|
&retryconfiglen)))
|
|
goto end;
|
|
if (!TEST_ptr(retryconfig))
|
|
goto end;
|
|
if (!TEST_int_ne((int)retryconfiglen, 0))
|
|
goto end;
|
|
if (verbose)
|
|
TEST_info("ech_retry_config_test: retryconfglen: %d\n", (int)retryconfiglen);
|
|
/* we kow the size to expect as the configs are hard-coded above */
|
|
if (!TEST_size_t_eq(retryconfiglen, 64))
|
|
goto end;
|
|
} else { /* corruption, retry-configs NOT made available */
|
|
if (!TEST_false(SSL_ech_get1_retry_config(clientssl, &retryconfig,
|
|
&retryconfiglen)))
|
|
goto end;
|
|
/* check we got the specific error expected */
|
|
err_str = ERR_reason_error_string(exp_err);
|
|
err_reason = ERR_GET_REASON(exp_err);
|
|
TEST_info("ech_retry_config_test Expected error: %d/%s",
|
|
err_reason, err_str);
|
|
do {
|
|
err = ERR_get_error();
|
|
if (err == 0) {
|
|
TEST_error("ech_retry_config_test: Unexpected error");
|
|
goto end;
|
|
}
|
|
err_reason = ERR_GET_REASON(err);
|
|
err_str = ERR_reason_error_string(err);
|
|
if (verbose)
|
|
TEST_info("ech_retry_config_test Actual error: %d/%s",
|
|
err_reason, err_str);
|
|
} while (err_reason != exp_err);
|
|
if (verbose)
|
|
TEST_info("ech_retry_config_test: retry configs withheld\n");
|
|
}
|
|
res = 1;
|
|
end:
|
|
OPENSSL_free(sinner);
|
|
OPENSSL_free(souter);
|
|
OPENSSL_free(cinner);
|
|
OPENSSL_free(couter);
|
|
OPENSSL_free(retryconfig);
|
|
SSL_free(clientssl);
|
|
SSL_free(serverssl);
|
|
SSL_CTX_free(cctx);
|
|
SSL_CTX_free(sctx);
|
|
return res;
|
|
}
|
|
|
|
const OPTIONS *test_get_options(void)
|
|
{
|
|
static const OPTIONS test_options[] = {
|
|
OPT_TEST_OPTIONS_DEFAULT_USAGE,
|
|
{ "v", OPT_VERBOSE, '-', "Enable verbose mode" },
|
|
{ OPT_HELP_STR, 1, '-', "Run ECH Corruption tests\n" },
|
|
{ NULL }
|
|
};
|
|
return test_options;
|
|
}
|
|
|
|
int setup_tests(void)
|
|
{
|
|
OPTION_CHOICE o;
|
|
BIO *in = NULL;
|
|
|
|
while ((o = opt_next()) != OPT_EOF) {
|
|
switch (o) {
|
|
case OPT_VERBOSE:
|
|
verbose = 1;
|
|
break;
|
|
case OPT_TEST_CASES:
|
|
break;
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
if (!test_skip_common_options()) {
|
|
TEST_error("Error parsing test options\n");
|
|
return 0;
|
|
}
|
|
certsdir = test_get_argument(0);
|
|
if (certsdir == NULL)
|
|
certsdir = DEF_CERTS_DIR;
|
|
cert = test_mk_file_path(certsdir, "servercert.pem");
|
|
if (cert == NULL)
|
|
goto err;
|
|
privkey = test_mk_file_path(certsdir, "serverkey.pem");
|
|
if (privkey == NULL)
|
|
goto err;
|
|
|
|
/* make an OSSL_ECHSTORE for pem_kp1 */
|
|
if ((in = BIO_new(BIO_s_mem())) == NULL
|
|
|| BIO_write(in, pem_kp1, (int)strlen(pem_kp1)) <= 0
|
|
|| !TEST_ptr(es = OSSL_ECHSTORE_new(libctx, propq))
|
|
|| !TEST_true(OSSL_ECHSTORE_read_pem(es, in, OSSL_ECH_FOR_RETRY)))
|
|
goto err;
|
|
BIO_free_all(in);
|
|
in = NULL;
|
|
hpke_infolen = bin_echconfiglen + 200;
|
|
if (!TEST_ptr(hpke_info = OPENSSL_malloc(hpke_infolen)))
|
|
goto err;
|
|
/* +/- 2 is to drop the ECHConfigList length at the start */
|
|
if (!TEST_true(ossl_ech_make_enc_info((unsigned char *)bin_echconfig + 2,
|
|
bin_echconfiglen - 2,
|
|
hpke_info, &hpke_infolen)))
|
|
goto err;
|
|
ADD_ALL_TESTS(test_ch_corrupt, OSSL_NELEM(test_inners));
|
|
ADD_ALL_TESTS(test_sh_corrupt, OSSL_NELEM(test_shs));
|
|
ADD_ALL_TESTS(test_ech_corrupt, OSSL_NELEM(test_echs));
|
|
#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
|
|
ADD_ALL_TESTS(ech_retry_config_test, 2);
|
|
#else
|
|
/*
|
|
* It seems fuzz tests cause our corruption to not work so we'll skip doing
|
|
* that. There's an ifdef'd code fragment in `tls_process_finished()` for
|
|
* when fuzzing that I guess causes that, but it's ok that we only do the
|
|
* corruption test when not fuzzing. We still do the (first) non-corrupt
|
|
* test to avoid a warning that `ech_retry_config_test()` isn't called.
|
|
*/
|
|
ADD_ALL_TESTS(ech_retry_config_test, 1);
|
|
#endif
|
|
return 1;
|
|
err:
|
|
BIO_free_all(in);
|
|
return 0;
|
|
}
|
|
|
|
void cleanup_tests(void)
|
|
{
|
|
bio_f_tls_corrupt_filter_free();
|
|
OPENSSL_free(cert);
|
|
OPENSSL_free(privkey);
|
|
OPENSSL_free(hpke_info);
|
|
OSSL_ECHSTORE_free(es);
|
|
}
|