Preliminary support for compressed certificates.
This change adds server-side support for compressed certificates. (Although some definitions for client-side support are included in the headers, there's no code behind them yet.) Change-Id: I0f98abf0b782b7337ddd014c58e19e6b8cc5a3c2 Reviewed-on: https://boringssl-review.googlesource.com/27964 Reviewed-by: David Benjamin <davidben@google.com>
This commit is contained in:
parent
c1e4f338b1
commit
a307cb7d58
@ -4565,6 +4565,50 @@ BORINGSSL_MAKE_DELETER(SSL, SSL_free)
|
|||||||
BORINGSSL_MAKE_DELETER(SSL_CTX, SSL_CTX_free)
|
BORINGSSL_MAKE_DELETER(SSL_CTX, SSL_CTX_free)
|
||||||
BORINGSSL_MAKE_DELETER(SSL_SESSION, SSL_SESSION_free)
|
BORINGSSL_MAKE_DELETER(SSL_SESSION, SSL_SESSION_free)
|
||||||
|
|
||||||
|
// Certificate compression.
|
||||||
|
//
|
||||||
|
// Certificates in TLS 1.3 can be compressed[1]. BoringSSL supports this as both
|
||||||
|
// a client and a server, but does not link against any specific compression
|
||||||
|
// libraries in order to keep dependencies to a minimum. Instead, hooks for
|
||||||
|
// compression and decompression can be installed in an |SSL_CTX| to enable
|
||||||
|
// support.
|
||||||
|
//
|
||||||
|
// [1] https://tools.ietf.org/html/draft-ietf-tls-certificate-compression-03.
|
||||||
|
|
||||||
|
// CertCompressFunc is a pointer to a function that performs compression. It
|
||||||
|
// must write the compressed representation of |in| to |out|, returning one on
|
||||||
|
// success and zero on error. The results of compressing certificates are not
|
||||||
|
// cached internally. Implementations may wish to implement their own cache if
|
||||||
|
// they expect it to be useful given the certificates that they serve.
|
||||||
|
typedef bool (*CertCompressFunc)(SSL *ssl, CBB *out, Span<const uint8_t> in);
|
||||||
|
|
||||||
|
// CertDecompressFunc is a pointer to a function that performs decompression.
|
||||||
|
// The compressed data from the peer is passed as |in| and the decompressed
|
||||||
|
// result must be exactly |uncompressed_len| bytes long. It returns one on
|
||||||
|
// success, in which case |*out| must be set to the results of decompressing
|
||||||
|
// |in|, or zero on error. The results of decompression are not cached
|
||||||
|
// internally. Implementations may wish to implement their own cache if they
|
||||||
|
// expect it to be useful.
|
||||||
|
typedef bool (*CertDecompressFunc)(SSL *ssl,
|
||||||
|
bssl::UniquePtr<CRYPTO_BUFFER> *out,
|
||||||
|
size_t uncompressed_len,
|
||||||
|
Span<const uint8_t> in);
|
||||||
|
|
||||||
|
// SSL_CTX_add_cert_compression_alg registers a certificate compression
|
||||||
|
// algorithm on |ctx| with ID |alg_id|. (The value of |alg_id| should be an IANA
|
||||||
|
// assigned value and each can only be registered once.)
|
||||||
|
//
|
||||||
|
// One of the function pointers may be nullptr to avoid having to implement both
|
||||||
|
// sides of a compression algorithm if you're only going to use it in one
|
||||||
|
// direction. In this case, the unimplemented direction acts like it was never
|
||||||
|
// configured.
|
||||||
|
//
|
||||||
|
// For a server, algorithms are registered in preference order with the most
|
||||||
|
// preferable first. It returns one on success or zero on error.
|
||||||
|
OPENSSL_EXPORT int SSL_CTX_add_cert_compression_alg(
|
||||||
|
SSL_CTX *ctx, uint16_t alg_id, CertCompressFunc compress,
|
||||||
|
CertDecompressFunc decompress);
|
||||||
|
|
||||||
enum class OpenRecordResult {
|
enum class OpenRecordResult {
|
||||||
kOK,
|
kOK,
|
||||||
kDiscard,
|
kDiscard,
|
||||||
|
@ -311,6 +311,7 @@ OPENSSL_COMPILE_ASSERT(
|
|||||||
#define SSL3_MT_CERTIFICATE_STATUS 22
|
#define SSL3_MT_CERTIFICATE_STATUS 22
|
||||||
#define SSL3_MT_SUPPLEMENTAL_DATA 23
|
#define SSL3_MT_SUPPLEMENTAL_DATA 23
|
||||||
#define SSL3_MT_KEY_UPDATE 24
|
#define SSL3_MT_KEY_UPDATE 24
|
||||||
|
#define SSL3_MT_COMPRESSED_CERTIFICATE 25
|
||||||
#define SSL3_MT_NEXT_PROTO 67
|
#define SSL3_MT_NEXT_PROTO 67
|
||||||
#define SSL3_MT_CHANNEL_ID 203
|
#define SSL3_MT_CHANNEL_ID 203
|
||||||
#define SSL3_MT_MESSAGE_HASH 254
|
#define SSL3_MT_MESSAGE_HASH 254
|
||||||
|
@ -205,9 +205,15 @@ extern "C" {
|
|||||||
// ExtensionType value from draft-ietf-tokbind-negotiation-10
|
// ExtensionType value from draft-ietf-tokbind-negotiation-10
|
||||||
#define TLSEXT_TYPE_token_binding 24
|
#define TLSEXT_TYPE_token_binding 24
|
||||||
|
|
||||||
// ExtensionType value from draft-ietf-quic-tls
|
// ExtensionType value from draft-ietf-quic-tls. Note that this collides with
|
||||||
|
// TLS-LTS and, based on scans, something else too. Since it's QUIC-only, that
|
||||||
|
// shouldn't be a problem in practice.
|
||||||
#define TLSEXT_TYPE_quic_transport_parameters 26
|
#define TLSEXT_TYPE_quic_transport_parameters 26
|
||||||
|
|
||||||
|
// ExtensionType value assigned to
|
||||||
|
// https://tools.ietf.org/html/draft-ietf-tls-certificate-compression-03
|
||||||
|
#define TLSEXT_TYPE_cert_compression 27
|
||||||
|
|
||||||
// ExtensionType value from RFC4507
|
// ExtensionType value from RFC4507
|
||||||
#define TLSEXT_TYPE_session_ticket 35
|
#define TLSEXT_TYPE_session_ticket 35
|
||||||
|
|
||||||
|
@ -147,7 +147,8 @@ SSL_HANDSHAKE::SSL_HANDSHAKE(SSL *ssl_arg)
|
|||||||
extended_master_secret(false),
|
extended_master_secret(false),
|
||||||
pending_private_key_op(false),
|
pending_private_key_op(false),
|
||||||
grease_seeded(false),
|
grease_seeded(false),
|
||||||
handback(false) {
|
handback(false),
|
||||||
|
cert_compression_negotiated(false) {
|
||||||
assert(ssl);
|
assert(ssl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1481,6 +1481,11 @@ struct SSL_HANDSHAKE {
|
|||||||
// sent.
|
// sent.
|
||||||
uint16_t negotiated_token_binding_version;
|
uint16_t negotiated_token_binding_version;
|
||||||
|
|
||||||
|
// cert_compression_alg_id, for a server, contains the negotiated certificate
|
||||||
|
// compression algorithm for this client. It is only valid if
|
||||||
|
// |cert_compression_negotiated| is true.
|
||||||
|
uint16_t cert_compression_alg_id;
|
||||||
|
|
||||||
// server_params, in a TLS 1.2 server, stores the ServerKeyExchange
|
// server_params, in a TLS 1.2 server, stores the ServerKeyExchange
|
||||||
// parameters. It has client and server randoms prepended for signing
|
// parameters. It has client and server randoms prepended for signing
|
||||||
// convenience.
|
// convenience.
|
||||||
@ -1595,6 +1600,14 @@ struct SSL_HANDSHAKE {
|
|||||||
// grease_seeded is true if |grease_seed| has been initialized.
|
// grease_seeded is true if |grease_seed| has been initialized.
|
||||||
bool grease_seeded:1;
|
bool grease_seeded:1;
|
||||||
|
|
||||||
|
// handback indicates that a server should pause the handshake after
|
||||||
|
// finishing operations that require private key material, in such a way that
|
||||||
|
// |SSL_get_error| returns |SSL_HANDBACK|. It is set by |SSL_apply_handoff|.
|
||||||
|
bool handback:1;
|
||||||
|
|
||||||
|
// cert_compression_negotiated is true iff |cert_compression_alg_id| is valid.
|
||||||
|
bool cert_compression_negotiated:1;
|
||||||
|
|
||||||
// client_version is the value sent or received in the ClientHello version.
|
// client_version is the value sent or received in the ClientHello version.
|
||||||
uint16_t client_version = 0;
|
uint16_t client_version = 0;
|
||||||
|
|
||||||
@ -1619,11 +1632,6 @@ struct SSL_HANDSHAKE {
|
|||||||
// should be echoed in a ServerHello, or zero if no extension should be
|
// should be echoed in a ServerHello, or zero if no extension should be
|
||||||
// echoed.
|
// echoed.
|
||||||
uint16_t dummy_pq_padding_len = 0;
|
uint16_t dummy_pq_padding_len = 0;
|
||||||
|
|
||||||
// handback indicates that a server should pause the handshake after
|
|
||||||
// finishing operations that require private key material, in such a way that
|
|
||||||
// |SSL_get_error| returns |SSL_HANDBACK|. It is set by |SSL_apply_handoff|.
|
|
||||||
bool handback : 1;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
UniquePtr<SSL_HANDSHAKE> ssl_handshake_new(SSL *ssl);
|
UniquePtr<SSL_HANDSHAKE> ssl_handshake_new(SSL *ssl);
|
||||||
@ -1987,6 +1995,14 @@ struct tlsext_ticket_key {
|
|||||||
|
|
||||||
DECLARE_LHASH_OF(SSL_SESSION)
|
DECLARE_LHASH_OF(SSL_SESSION)
|
||||||
|
|
||||||
|
struct CertCompressionAlg {
|
||||||
|
bssl::CertCompressFunc compress;
|
||||||
|
bssl::CertDecompressFunc decompress;
|
||||||
|
uint16_t alg_id;
|
||||||
|
};
|
||||||
|
|
||||||
|
DEFINE_STACK_OF(CertCompressionAlg);
|
||||||
|
|
||||||
namespace bssl {
|
namespace bssl {
|
||||||
|
|
||||||
// SSLContext backs the public |SSL_CTX| type. Due to compatibility constraints,
|
// SSLContext backs the public |SSL_CTX| type. Due to compatibility constraints,
|
||||||
@ -2192,6 +2208,9 @@ struct SSLContext {
|
|||||||
// SRTP profiles we are willing to do from RFC 5764
|
// SRTP profiles we are willing to do from RFC 5764
|
||||||
STACK_OF(SRTP_PROTECTION_PROFILE) *srtp_profiles;
|
STACK_OF(SRTP_PROTECTION_PROFILE) *srtp_profiles;
|
||||||
|
|
||||||
|
// Defined compression algorithms for certificates.
|
||||||
|
STACK_OF(CertCompressionAlg) *cert_compression_algs;
|
||||||
|
|
||||||
// Supported group values inherited by SSL structure
|
// Supported group values inherited by SSL structure
|
||||||
size_t supported_group_list_len;
|
size_t supported_group_list_len;
|
||||||
uint16_t *supported_group_list;
|
uint16_t *supported_group_list;
|
||||||
|
@ -510,6 +510,51 @@ void SSL_set_handoff_mode(SSL *ssl, bool on) {
|
|||||||
ssl->config->handoff = on;
|
ssl->config->handoff = on;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int SSL_CTX_add_cert_compression_alg(SSL_CTX *ctx, uint16_t alg_id,
|
||||||
|
bssl::CertCompressFunc compress,
|
||||||
|
bssl::CertDecompressFunc decompress) {
|
||||||
|
assert(compress != nullptr || decompress != nullptr);
|
||||||
|
|
||||||
|
for (CertCompressionAlg *alg : ctx->cert_compression_algs) {
|
||||||
|
if (alg->alg_id == alg_id) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CertCompressionAlg *alg = reinterpret_cast<CertCompressionAlg *>(
|
||||||
|
OPENSSL_malloc(sizeof(CertCompressionAlg)));
|
||||||
|
if (alg == nullptr) {
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
|
||||||
|
OPENSSL_memset(alg, 0, sizeof(CertCompressionAlg));
|
||||||
|
alg->alg_id = alg_id;
|
||||||
|
alg->compress = compress;
|
||||||
|
alg->decompress = decompress;
|
||||||
|
|
||||||
|
if (ctx->cert_compression_algs == nullptr) {
|
||||||
|
ctx->cert_compression_algs = sk_CertCompressionAlg_new_null();
|
||||||
|
if (ctx->cert_compression_algs == nullptr) {
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!sk_CertCompressionAlg_push(ctx->cert_compression_algs, alg)) {
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
err:
|
||||||
|
OPENSSL_free(alg);
|
||||||
|
if (ctx->cert_compression_algs != nullptr &&
|
||||||
|
sk_CertCompressionAlg_num(ctx->cert_compression_algs) == 0) {
|
||||||
|
sk_CertCompressionAlg_free(ctx->cert_compression_algs);
|
||||||
|
ctx->cert_compression_algs = nullptr;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace bssl
|
} // namespace bssl
|
||||||
|
|
||||||
using namespace bssl;
|
using namespace bssl;
|
||||||
@ -672,6 +717,8 @@ void SSL_CTX_free(SSL_CTX *ctx) {
|
|||||||
sk_CRYPTO_BUFFER_pop_free(ctx->client_CA, CRYPTO_BUFFER_free);
|
sk_CRYPTO_BUFFER_pop_free(ctx->client_CA, CRYPTO_BUFFER_free);
|
||||||
ctx->x509_method->ssl_ctx_free(ctx);
|
ctx->x509_method->ssl_ctx_free(ctx);
|
||||||
sk_SRTP_PROTECTION_PROFILE_free(ctx->srtp_profiles);
|
sk_SRTP_PROTECTION_PROFILE_free(ctx->srtp_profiles);
|
||||||
|
sk_CertCompressionAlg_pop_free(ctx->cert_compression_algs,
|
||||||
|
Delete<CertCompressionAlg>);
|
||||||
OPENSSL_free(ctx->psk_identity_hint);
|
OPENSSL_free(ctx->psk_identity_hint);
|
||||||
OPENSSL_free(ctx->supported_group_list);
|
OPENSSL_free(ctx->supported_group_list);
|
||||||
OPENSSL_free(ctx->alpn_client_proto_list);
|
OPENSSL_free(ctx->alpn_client_proto_list);
|
||||||
|
@ -2761,6 +2761,93 @@ static bool ext_quic_transport_params_add_serverhello(SSL_HANDSHAKE *hs,
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Certificate compression
|
||||||
|
|
||||||
|
static bool cert_compression_add_clienthello(SSL_HANDSHAKE *hs, CBB *out) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool cert_compression_parse_serverhello(SSL_HANDSHAKE *hs,
|
||||||
|
uint8_t *out_alert,
|
||||||
|
CBS *contents) {
|
||||||
|
if (contents == nullptr) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The server may not echo this extension. Any server to client negotiation is
|
||||||
|
// advertised in the CertificateRequest message.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool cert_compression_parse_clienthello(SSL_HANDSHAKE *hs,
|
||||||
|
uint8_t *out_alert,
|
||||||
|
CBS *contents) {
|
||||||
|
if (contents == nullptr) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const size_t num_algs =
|
||||||
|
sk_CertCompressionAlg_num(hs->ssl->ctx->cert_compression_algs);
|
||||||
|
|
||||||
|
CBS alg_ids;
|
||||||
|
if (!CBS_get_u8_length_prefixed(contents, &alg_ids) ||
|
||||||
|
CBS_len(contents) != 0 ||
|
||||||
|
CBS_len(&alg_ids) == 0 ||
|
||||||
|
CBS_len(&alg_ids) % 2 == 1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const size_t num_given_alg_ids = CBS_len(&alg_ids) / 2;
|
||||||
|
Array<uint16_t> given_alg_ids;
|
||||||
|
if (!given_alg_ids.Init(num_given_alg_ids)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t best_index = num_algs;
|
||||||
|
size_t given_alg_idx = 0;
|
||||||
|
|
||||||
|
while (CBS_len(&alg_ids) > 0) {
|
||||||
|
uint16_t alg_id;
|
||||||
|
if (!CBS_get_u16(&alg_ids, &alg_id)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
given_alg_ids[given_alg_idx++] = alg_id;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < num_algs; i++) {
|
||||||
|
const auto *alg =
|
||||||
|
sk_CertCompressionAlg_value(hs->ssl->ctx->cert_compression_algs, i);
|
||||||
|
if (alg->alg_id == alg_id && alg->compress != nullptr) {
|
||||||
|
if (i < best_index) {
|
||||||
|
best_index = i;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
qsort(given_alg_ids.data(), given_alg_ids.size(), sizeof(uint16_t),
|
||||||
|
compare_uint16_t);
|
||||||
|
for (size_t i = 1; i < num_given_alg_ids; i++) {
|
||||||
|
if (given_alg_ids[i - 1] == given_alg_ids[i]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (best_index < num_algs &&
|
||||||
|
ssl_protocol_version(hs->ssl) >= TLS1_3_VERSION) {
|
||||||
|
hs->cert_compression_negotiated = true;
|
||||||
|
hs->cert_compression_alg_id =
|
||||||
|
sk_CertCompressionAlg_value(hs->ssl->ctx->cert_compression_algs,
|
||||||
|
best_index)->alg_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool cert_compression_add_serverhello(SSL_HANDSHAKE *hs, CBB *out) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// kExtensions contains all the supported extensions.
|
// kExtensions contains all the supported extensions.
|
||||||
static const struct tls_extension kExtensions[] = {
|
static const struct tls_extension kExtensions[] = {
|
||||||
@ -2945,6 +3032,14 @@ static const struct tls_extension kExtensions[] = {
|
|||||||
ext_token_binding_parse_clienthello,
|
ext_token_binding_parse_clienthello,
|
||||||
ext_token_binding_add_serverhello,
|
ext_token_binding_add_serverhello,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
TLSEXT_TYPE_cert_compression,
|
||||||
|
NULL,
|
||||||
|
cert_compression_add_clienthello,
|
||||||
|
cert_compression_parse_serverhello,
|
||||||
|
cert_compression_parse_clienthello,
|
||||||
|
cert_compression_add_serverhello,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
#define kNumExtensions (sizeof(kExtensions) / sizeof(struct tls_extension))
|
#define kNumExtensions (sizeof(kExtensions) / sizeof(struct tls_extension))
|
||||||
|
@ -1346,6 +1346,54 @@ static bssl::UniquePtr<SSL_CTX> SetupCtx(SSL_CTX *old_ctx,
|
|||||||
ssl_ctx.get());
|
ssl_ctx.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (config->install_cert_compression_algs &&
|
||||||
|
(!SSL_CTX_add_cert_compression_alg(
|
||||||
|
ssl_ctx.get(), 0xff02,
|
||||||
|
[](SSL *ssl, CBB *out, bssl::Span<const uint8_t> in) -> bool {
|
||||||
|
if (!CBB_add_u8(out, 1) ||
|
||||||
|
!CBB_add_u8(out, 2) ||
|
||||||
|
!CBB_add_u8(out, 3) ||
|
||||||
|
!CBB_add_u8(out, 4) ||
|
||||||
|
!CBB_add_bytes(out, in.data(), in.size())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
[](SSL *ssl, bssl::UniquePtr<CRYPTO_BUFFER> *out,
|
||||||
|
size_t uncompressed_len, bssl::Span<const uint8_t> in) -> bool {
|
||||||
|
if (in.size() < 4 || in[0] != 1 || in[1] != 2 || in[2] != 3 ||
|
||||||
|
in[3] != 4 || uncompressed_len != in.size() - 4) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const bssl::Span<const uint8_t> uncompressed(in.subspan(4));
|
||||||
|
out->reset(CRYPTO_BUFFER_new(uncompressed.data(),
|
||||||
|
uncompressed.size(), nullptr));
|
||||||
|
return true;
|
||||||
|
}) ||
|
||||||
|
!SSL_CTX_add_cert_compression_alg(
|
||||||
|
ssl_ctx.get(), 0xff01,
|
||||||
|
[](SSL *ssl, CBB *out, bssl::Span<const uint8_t> in) -> bool {
|
||||||
|
if (in.size() < 2 || in[0] != 0 || in[1] != 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return CBB_add_bytes(out, in.data() + 2, in.size() - 2);
|
||||||
|
},
|
||||||
|
[](SSL *ssl, bssl::UniquePtr<CRYPTO_BUFFER> *out,
|
||||||
|
size_t uncompressed_len, bssl::Span<const uint8_t> in) -> bool {
|
||||||
|
if (uncompressed_len != 2 + in.size()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
std::unique_ptr<uint8_t[]> buf(new uint8_t[2 + in.size()]);
|
||||||
|
buf[0] = 0;
|
||||||
|
buf[1] = 0;
|
||||||
|
OPENSSL_memcpy(&buf[2], in.data(), in.size());
|
||||||
|
out->reset(CRYPTO_BUFFER_new(buf.get(), 2 + in.size(), nullptr));
|
||||||
|
return true;
|
||||||
|
}))) {
|
||||||
|
fprintf(stderr, "SSL_CTX_add_cert_compression_alg failed.\n");
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
|
||||||
return ssl_ctx;
|
return ssl_ctx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,6 +99,7 @@ const (
|
|||||||
typeFinished uint8 = 20
|
typeFinished uint8 = 20
|
||||||
typeCertificateStatus uint8 = 22
|
typeCertificateStatus uint8 = 22
|
||||||
typeKeyUpdate uint8 = 24 // draft-ietf-tls-tls13-16
|
typeKeyUpdate uint8 = 24 // draft-ietf-tls-tls13-16
|
||||||
|
typeCompressedCertificate uint8 = 25 // Not IANA assigned
|
||||||
typeNextProtocol uint8 = 67 // Not IANA assigned
|
typeNextProtocol uint8 = 67 // Not IANA assigned
|
||||||
typeChannelID uint8 = 203 // Not IANA assigned
|
typeChannelID uint8 = 203 // Not IANA assigned
|
||||||
typeMessageHash uint8 = 254 // draft-ietf-tls-tls13-21
|
typeMessageHash uint8 = 254 // draft-ietf-tls-tls13-21
|
||||||
@ -122,7 +123,8 @@ const (
|
|||||||
extensionPadding uint16 = 21
|
extensionPadding uint16 = 21
|
||||||
extensionExtendedMasterSecret uint16 = 23
|
extensionExtendedMasterSecret uint16 = 23
|
||||||
extensionTokenBinding uint16 = 24
|
extensionTokenBinding uint16 = 24
|
||||||
extensionQUICTransportParams uint16 = 26
|
extensionQUICTransportParams uint16 = 26 // conflicts with TLS-LTS
|
||||||
|
extensionCompressedCertAlgs uint16 = 27
|
||||||
extensionSessionTicket uint16 = 35
|
extensionSessionTicket uint16 = 35
|
||||||
extensionPreSharedKey uint16 = 41 // draft-ietf-tls-tls13-23
|
extensionPreSharedKey uint16 = 41 // draft-ietf-tls-tls13-23
|
||||||
extensionEarlyData uint16 = 42 // draft-ietf-tls-tls13-23
|
extensionEarlyData uint16 = 42 // draft-ietf-tls-tls13-23
|
||||||
@ -330,6 +332,16 @@ type ServerSessionCache interface {
|
|||||||
Put(sessionId string, session *sessionState)
|
Put(sessionId string, session *sessionState)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CertCompressionAlg is a certificate compression algorithm, specified as a
|
||||||
|
// pair of functions for compressing and decompressing certificates.
|
||||||
|
type CertCompressionAlg struct {
|
||||||
|
// Compress returns a compressed representation of the input.
|
||||||
|
Compress func([]byte) []byte
|
||||||
|
// Decompress depresses the contents of in and writes the result to out, which
|
||||||
|
// will be the correct size. It returns true on success and false otherwise.
|
||||||
|
Decompress func(out, in []byte) bool
|
||||||
|
}
|
||||||
|
|
||||||
// A Config structure is used to configure a TLS client or server.
|
// A Config structure is used to configure a TLS client or server.
|
||||||
// After one has been passed to a TLS function it must not be
|
// After one has been passed to a TLS function it must not be
|
||||||
// modified. A Config may be reused; the tls package will also not
|
// modified. A Config may be reused; the tls package will also not
|
||||||
@ -500,6 +512,8 @@ type Config struct {
|
|||||||
// transport parameters extension.
|
// transport parameters extension.
|
||||||
QUICTransportParams []byte
|
QUICTransportParams []byte
|
||||||
|
|
||||||
|
CertCompressionAlgs map[uint16]CertCompressionAlg
|
||||||
|
|
||||||
// Bugs specifies optional misbehaviour to be used for testing other
|
// Bugs specifies optional misbehaviour to be used for testing other
|
||||||
// implementations.
|
// implementations.
|
||||||
Bugs ProtocolBugs
|
Bugs ProtocolBugs
|
||||||
@ -1596,6 +1610,14 @@ type ProtocolBugs struct {
|
|||||||
// SetX25519HighBit, if true, causes X25519 key shares to set their
|
// SetX25519HighBit, if true, causes X25519 key shares to set their
|
||||||
// high-order bit.
|
// high-order bit.
|
||||||
SetX25519HighBit bool
|
SetX25519HighBit bool
|
||||||
|
|
||||||
|
// DuplicateCompressedCertAlgs, if true, causes two, equal, certificate
|
||||||
|
// compression algorithm IDs to be sent.
|
||||||
|
DuplicateCompressedCertAlgs bool
|
||||||
|
|
||||||
|
// ExpectedCompressedCert specifies the compression algorithm ID that must be
|
||||||
|
// used on this connection, or zero if there are no special requirements.
|
||||||
|
ExpectedCompressedCert uint16
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Config) serverInit() {
|
func (c *Config) serverInit() {
|
||||||
|
@ -1368,6 +1368,8 @@ func (c *Conn) readHandshake() (interface{}, error) {
|
|||||||
m = &certificateMsg{
|
m = &certificateMsg{
|
||||||
hasRequestContext: c.vers >= VersionTLS13,
|
hasRequestContext: c.vers >= VersionTLS13,
|
||||||
}
|
}
|
||||||
|
case typeCompressedCertificate:
|
||||||
|
m = new(compressedCertificateMsg)
|
||||||
case typeCertificateRequest:
|
case typeCertificateRequest:
|
||||||
m = &certificateRequestMsg{
|
m = &certificateRequestMsg{
|
||||||
vers: c.wireVersion,
|
vers: c.wireVersion,
|
||||||
|
@ -155,6 +155,15 @@ func (c *Conn) clientHandshake() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if c.config.Bugs.DuplicateCompressedCertAlgs {
|
||||||
|
hello.compressedCertAlgs = []uint16{1, 1}
|
||||||
|
} else if len(c.config.CertCompressionAlgs) > 0 {
|
||||||
|
hello.compressedCertAlgs = make([]uint16, 0, len(c.config.CertCompressionAlgs))
|
||||||
|
for id, _ := range c.config.CertCompressionAlgs {
|
||||||
|
hello.compressedCertAlgs = append(hello.compressedCertAlgs, uint16(id))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if c.noRenegotiationInfo() {
|
if c.noRenegotiationInfo() {
|
||||||
hello.secureRenegotiation = nil
|
hello.secureRenegotiation = nil
|
||||||
}
|
}
|
||||||
@ -864,13 +873,47 @@ func (hs *clientHandshakeState) doTLS13Handshake() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
certMsg, ok := msg.(*certificateMsg)
|
var certMsg *certificateMsg
|
||||||
|
|
||||||
|
if compressedCertMsg, ok := msg.(*compressedCertificateMsg); ok {
|
||||||
|
hs.writeServerHash(compressedCertMsg.marshal())
|
||||||
|
|
||||||
|
alg, ok := c.config.CertCompressionAlgs[compressedCertMsg.algID]
|
||||||
if !ok {
|
if !ok {
|
||||||
|
c.sendAlert(alertBadCertificate)
|
||||||
|
return fmt.Errorf("tls: received certificate compressed with unknown algorithm %x", compressedCertMsg.algID)
|
||||||
|
}
|
||||||
|
|
||||||
|
decompressed := make([]byte, 4+int(compressedCertMsg.uncompressedLength))
|
||||||
|
if !alg.Decompress(decompressed[4:], compressedCertMsg.compressed) {
|
||||||
|
c.sendAlert(alertBadCertificate)
|
||||||
|
return fmt.Errorf("tls: failed to decompress certificate with algorithm %x", compressedCertMsg.algID)
|
||||||
|
}
|
||||||
|
|
||||||
|
certMsg = &certificateMsg{
|
||||||
|
hasRequestContext: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
if !certMsg.unmarshal(decompressed) {
|
||||||
|
c.sendAlert(alertBadCertificate)
|
||||||
|
return errors.New("tls: failed to parse decompressed certificate")
|
||||||
|
}
|
||||||
|
|
||||||
|
if expected := c.config.Bugs.ExpectedCompressedCert; expected != 0 && expected != compressedCertMsg.algID {
|
||||||
|
return fmt.Errorf("tls: expected certificate compressed with algorithm %x, but message used %x", expected, compressedCertMsg.algID)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if certMsg, ok = msg.(*certificateMsg); !ok {
|
||||||
c.sendAlert(alertUnexpectedMessage)
|
c.sendAlert(alertUnexpectedMessage)
|
||||||
return unexpectedMessageError(certMsg, msg)
|
return unexpectedMessageError(certMsg, msg)
|
||||||
}
|
}
|
||||||
hs.writeServerHash(certMsg.marshal())
|
hs.writeServerHash(certMsg.marshal())
|
||||||
|
|
||||||
|
if c.config.Bugs.ExpectedCompressedCert != 0 {
|
||||||
|
return errors.New("tls: uncompressed certificate received")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Check for unsolicited extensions.
|
// Check for unsolicited extensions.
|
||||||
for i, cert := range certMsg.certificates {
|
for i, cert := range certMsg.certificates {
|
||||||
if c.config.Bugs.NoOCSPStapling && cert.ocspResponse != nil {
|
if c.config.Bugs.NoOCSPStapling && cert.ocspResponse != nil {
|
||||||
|
@ -296,6 +296,7 @@ type clientHelloMsg struct {
|
|||||||
emptyExtensions bool
|
emptyExtensions bool
|
||||||
pad int
|
pad int
|
||||||
dummyPQPaddingLen int
|
dummyPQPaddingLen int
|
||||||
|
compressedCertAlgs []uint16
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *clientHelloMsg) equal(i interface{}) bool {
|
func (m *clientHelloMsg) equal(i interface{}) bool {
|
||||||
@ -349,7 +350,8 @@ func (m *clientHelloMsg) equal(i interface{}) bool {
|
|||||||
m.omitExtensions == m1.omitExtensions &&
|
m.omitExtensions == m1.omitExtensions &&
|
||||||
m.emptyExtensions == m1.emptyExtensions &&
|
m.emptyExtensions == m1.emptyExtensions &&
|
||||||
m.pad == m1.pad &&
|
m.pad == m1.pad &&
|
||||||
m.dummyPQPaddingLen == m1.dummyPQPaddingLen
|
m.dummyPQPaddingLen == m1.dummyPQPaddingLen &&
|
||||||
|
eqUint16s(m.compressedCertAlgs, m1.compressedCertAlgs)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *clientHelloMsg) marshal() []byte {
|
func (m *clientHelloMsg) marshal() []byte {
|
||||||
@ -586,6 +588,14 @@ func (m *clientHelloMsg) marshal() []byte {
|
|||||||
body := extensions.addU16LengthPrefixed()
|
body := extensions.addU16LengthPrefixed()
|
||||||
body.addBytes(make([]byte, l))
|
body.addBytes(make([]byte, l))
|
||||||
}
|
}
|
||||||
|
if len(m.compressedCertAlgs) > 0 {
|
||||||
|
extensions.addU16(extensionCompressedCertAlgs)
|
||||||
|
body := extensions.addU16LengthPrefixed()
|
||||||
|
algIDs := body.addU8LengthPrefixed()
|
||||||
|
for _, v := range m.compressedCertAlgs {
|
||||||
|
algIDs.addU16(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
// The PSK extension must be last (draft-ietf-tls-tls13-18 section 4.2.6).
|
// The PSK extension must be last (draft-ietf-tls-tls13-18 section 4.2.6).
|
||||||
if len(m.pskIdentities) > 0 && !m.pskBinderFirst {
|
if len(m.pskIdentities) > 0 && !m.pskBinderFirst {
|
||||||
extensions.addU16(extensionPreSharedKey)
|
extensions.addU16(extensionPreSharedKey)
|
||||||
@ -903,6 +913,24 @@ func (m *clientHelloMsg) unmarshal(data []byte) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
m.dummyPQPaddingLen = len(body)
|
m.dummyPQPaddingLen = len(body)
|
||||||
|
case extensionCompressedCertAlgs:
|
||||||
|
var algIDs byteReader
|
||||||
|
if !body.readU8LengthPrefixed(&algIDs) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
seen := make(map[uint16]struct{})
|
||||||
|
for len(algIDs) > 0 {
|
||||||
|
var algID uint16
|
||||||
|
if !algIDs.readU16(&algID) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if _, ok := seen[algID]; ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
seen[algID] = struct{}{}
|
||||||
|
m.compressedCertAlgs = append(m.compressedCertAlgs, algID)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if isGREASEValue(extension) {
|
if isGREASEValue(extension) {
|
||||||
@ -1671,6 +1699,48 @@ func (m *certificateMsg) unmarshal(data []byte) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type compressedCertificateMsg struct {
|
||||||
|
raw []byte
|
||||||
|
algID uint16
|
||||||
|
uncompressedLength uint32
|
||||||
|
compressed []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *compressedCertificateMsg) marshal() (x []byte) {
|
||||||
|
if m.raw != nil {
|
||||||
|
return m.raw
|
||||||
|
}
|
||||||
|
|
||||||
|
certMsg := newByteBuilder()
|
||||||
|
certMsg.addU8(typeCertificate)
|
||||||
|
certificate := certMsg.addU24LengthPrefixed()
|
||||||
|
certificate.addU16(m.algID)
|
||||||
|
certificate.addU24(int(m.uncompressedLength))
|
||||||
|
compressed := certificate.addU24LengthPrefixed()
|
||||||
|
compressed.addBytes(m.compressed)
|
||||||
|
|
||||||
|
m.raw = certMsg.finish()
|
||||||
|
return m.raw
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *compressedCertificateMsg) unmarshal(data []byte) bool {
|
||||||
|
m.raw = data
|
||||||
|
reader := byteReader(data[4:])
|
||||||
|
|
||||||
|
if !reader.readU16(&m.algID) ||
|
||||||
|
!reader.readU24(&m.uncompressedLength) ||
|
||||||
|
!reader.readU24LengthPrefixedBytes(&m.compressed) ||
|
||||||
|
len(reader) != 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if m.uncompressedLength >= 1<<17 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
type serverKeyExchangeMsg struct {
|
type serverKeyExchangeMsg struct {
|
||||||
raw []byte
|
raw []byte
|
||||||
key []byte
|
key []byte
|
||||||
|
@ -14396,6 +14396,158 @@ func addOmitExtensionsTests() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func addCertCompressionTests() {
|
||||||
|
// shrinkingPrefix is the first two bytes of a Certificate message.
|
||||||
|
shrinkingPrefix := []byte{0, 0}
|
||||||
|
// expandingPrefix is just some arbitrary byte string. This has to match the
|
||||||
|
// value in the shim.
|
||||||
|
expandingPrefix := []byte{1, 2, 3, 4}
|
||||||
|
|
||||||
|
shinking := CertCompressionAlg{
|
||||||
|
Compress: func(uncompressed []byte) []byte {
|
||||||
|
if !bytes.HasPrefix(uncompressed, shrinkingPrefix) {
|
||||||
|
panic(fmt.Sprintf("cannot compress certificate message %x", uncompressed))
|
||||||
|
}
|
||||||
|
return uncompressed[len(shrinkingPrefix):]
|
||||||
|
},
|
||||||
|
Decompress: func(out []byte, compressed []byte) bool {
|
||||||
|
if len(out) != len(shrinkingPrefix)+len(compressed) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
copy(out, shrinkingPrefix)
|
||||||
|
copy(out[len(shrinkingPrefix):], compressed)
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
expanding := CertCompressionAlg{
|
||||||
|
Compress: func(uncompressed []byte) []byte {
|
||||||
|
ret := make([]byte, 0, len(expandingPrefix)+len(uncompressed))
|
||||||
|
ret = append(ret, expandingPrefix...)
|
||||||
|
return append(ret, uncompressed...)
|
||||||
|
},
|
||||||
|
Decompress: func(out []byte, compressed []byte) bool {
|
||||||
|
if !bytes.HasPrefix(compressed, expandingPrefix) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
copy(out, compressed[len(expandingPrefix):])
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
shrinkingAlgId = 0xff01
|
||||||
|
expandingAlgId = 0xff02
|
||||||
|
)
|
||||||
|
|
||||||
|
for _, ver := range tlsVersions {
|
||||||
|
if ver.version < VersionTLS12 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Duplicate compression algorithms is an error, even if nothing is
|
||||||
|
// configured.
|
||||||
|
testCases = append(testCases, testCase{
|
||||||
|
testType: serverTest,
|
||||||
|
name: "DuplicateCertCompressionExt-" + ver.name,
|
||||||
|
tls13Variant: ver.tls13Variant,
|
||||||
|
config: Config{
|
||||||
|
MinVersion: ver.version,
|
||||||
|
MaxVersion: ver.version,
|
||||||
|
Bugs: ProtocolBugs{
|
||||||
|
DuplicateCompressedCertAlgs: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
shouldFail: true,
|
||||||
|
expectedError: ":ERROR_PARSING_EXTENSION:",
|
||||||
|
})
|
||||||
|
|
||||||
|
// With compression algorithms configured, an duplicate values should still
|
||||||
|
// be an error.
|
||||||
|
testCases = append(testCases, testCase{
|
||||||
|
testType: serverTest,
|
||||||
|
name: "DuplicateCertCompressionExt2-" + ver.name,
|
||||||
|
tls13Variant: ver.tls13Variant,
|
||||||
|
flags: []string{"-install-cert-compression-algs"},
|
||||||
|
config: Config{
|
||||||
|
MinVersion: ver.version,
|
||||||
|
MaxVersion: ver.version,
|
||||||
|
Bugs: ProtocolBugs{
|
||||||
|
DuplicateCompressedCertAlgs: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
shouldFail: true,
|
||||||
|
expectedError: ":ERROR_PARSING_EXTENSION:",
|
||||||
|
})
|
||||||
|
|
||||||
|
if ver.version < VersionTLS13 {
|
||||||
|
testCases = append(testCases, testCase{
|
||||||
|
testType: serverTest,
|
||||||
|
name: "CertCompressionIgnoredBefore13-" + ver.name,
|
||||||
|
flags: []string{"-install-cert-compression-algs"},
|
||||||
|
config: Config{
|
||||||
|
MinVersion: ver.version,
|
||||||
|
MaxVersion: ver.version,
|
||||||
|
CertCompressionAlgs: map[uint16]CertCompressionAlg{expandingAlgId: expanding},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
testCases = append(testCases, testCase{
|
||||||
|
testType: serverTest,
|
||||||
|
name: "CertCompressionExpands-" + ver.name,
|
||||||
|
tls13Variant: ver.tls13Variant,
|
||||||
|
flags: []string{"-install-cert-compression-algs"},
|
||||||
|
config: Config{
|
||||||
|
MinVersion: ver.version,
|
||||||
|
MaxVersion: ver.version,
|
||||||
|
CertCompressionAlgs: map[uint16]CertCompressionAlg{expandingAlgId: expanding},
|
||||||
|
Bugs: ProtocolBugs{
|
||||||
|
ExpectedCompressedCert: expandingAlgId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
testCases = append(testCases, testCase{
|
||||||
|
testType: serverTest,
|
||||||
|
name: "CertCompressionShrinks-" + ver.name,
|
||||||
|
tls13Variant: ver.tls13Variant,
|
||||||
|
flags: []string{"-install-cert-compression-algs"},
|
||||||
|
config: Config{
|
||||||
|
MinVersion: ver.version,
|
||||||
|
MaxVersion: ver.version,
|
||||||
|
CertCompressionAlgs: map[uint16]CertCompressionAlg{shrinkingAlgId: shinking},
|
||||||
|
Bugs: ProtocolBugs{
|
||||||
|
ExpectedCompressedCert: shrinkingAlgId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// With both algorithms configured, the server should pick its most
|
||||||
|
// preferable. (Which is expandingAlgId.)
|
||||||
|
testCases = append(testCases, testCase{
|
||||||
|
testType: serverTest,
|
||||||
|
name: "CertCompressionPriority-" + ver.name,
|
||||||
|
tls13Variant: ver.tls13Variant,
|
||||||
|
flags: []string{"-install-cert-compression-algs"},
|
||||||
|
config: Config{
|
||||||
|
MinVersion: ver.version,
|
||||||
|
MaxVersion: ver.version,
|
||||||
|
CertCompressionAlgs: map[uint16]CertCompressionAlg{
|
||||||
|
shrinkingAlgId: shinking,
|
||||||
|
expandingAlgId: expanding,
|
||||||
|
},
|
||||||
|
Bugs: ProtocolBugs{
|
||||||
|
ExpectedCompressedCert: expandingAlgId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func worker(statusChan chan statusMsg, c chan *testCase, shimPath string, wg *sync.WaitGroup) {
|
func worker(statusChan chan statusMsg, c chan *testCase, shimPath string, wg *sync.WaitGroup) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
|
|
||||||
@ -14524,6 +14676,7 @@ func main() {
|
|||||||
addECDSAKeyUsageTests()
|
addECDSAKeyUsageTests()
|
||||||
addExtraHandshakeTests()
|
addExtraHandshakeTests()
|
||||||
addOmitExtensionsTests()
|
addOmitExtensionsTests()
|
||||||
|
addCertCompressionTests()
|
||||||
|
|
||||||
testCases = append(testCases, convertToSplitHandshakeTests(testCases)...)
|
testCases = append(testCases, convertToSplitHandshakeTests(testCases)...)
|
||||||
|
|
||||||
|
@ -141,6 +141,8 @@ const Flag<bool> kBoolFlags[] = {
|
|||||||
{ "-set-ocsp-in-callback", &TestConfig::set_ocsp_in_callback },
|
{ "-set-ocsp-in-callback", &TestConfig::set_ocsp_in_callback },
|
||||||
{ "-decline-ocsp-callback", &TestConfig::decline_ocsp_callback },
|
{ "-decline-ocsp-callback", &TestConfig::decline_ocsp_callback },
|
||||||
{ "-fail-ocsp-callback", &TestConfig::fail_ocsp_callback },
|
{ "-fail-ocsp-callback", &TestConfig::fail_ocsp_callback },
|
||||||
|
{ "-install-cert-compression-algs",
|
||||||
|
&TestConfig::install_cert_compression_algs },
|
||||||
};
|
};
|
||||||
|
|
||||||
const Flag<std::string> kStringFlags[] = {
|
const Flag<std::string> kStringFlags[] = {
|
||||||
|
@ -161,6 +161,7 @@ struct TestConfig {
|
|||||||
bool set_ocsp_in_callback = false;
|
bool set_ocsp_in_callback = false;
|
||||||
bool decline_ocsp_callback = false;
|
bool decline_ocsp_callback = false;
|
||||||
bool fail_ocsp_callback = false;
|
bool fail_ocsp_callback = false;
|
||||||
|
bool install_cert_compression_algs = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
bool ParseConfig(int argc, char **argv, TestConfig *out_initial,
|
bool ParseConfig(int argc, char **argv, TestConfig *out_initial,
|
||||||
|
@ -353,12 +353,26 @@ int tls13_process_finished(SSL_HANDSHAKE *hs, const SSLMessage &msg,
|
|||||||
|
|
||||||
int tls13_add_certificate(SSL_HANDSHAKE *hs) {
|
int tls13_add_certificate(SSL_HANDSHAKE *hs) {
|
||||||
SSL *const ssl = hs->ssl;
|
SSL *const ssl = hs->ssl;
|
||||||
|
CERT *const cert = hs->config->cert;
|
||||||
|
|
||||||
ScopedCBB cbb;
|
ScopedCBB cbb;
|
||||||
CBB body, certificate_list;
|
CBB *body, body_storage, certificate_list;
|
||||||
if (!ssl->method->init_message(ssl, cbb.get(), &body, SSL3_MT_CERTIFICATE) ||
|
|
||||||
// The request context is always empty in the handshake.
|
if (hs->cert_compression_negotiated) {
|
||||||
!CBB_add_u8(&body, 0) ||
|
if (!CBB_init(cbb.get(), 1024)) {
|
||||||
!CBB_add_u24_length_prefixed(&body, &certificate_list)) {
|
return false;
|
||||||
|
}
|
||||||
|
body = cbb.get();
|
||||||
|
} else {
|
||||||
|
body = &body_storage;
|
||||||
|
if (!ssl->method->init_message(ssl, cbb.get(), body, SSL3_MT_CERTIFICATE)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (// The request context is always empty in the handshake.
|
||||||
|
!CBB_add_u8(body, 0) ||
|
||||||
|
!CBB_add_u24_length_prefixed(body, &certificate_list)) {
|
||||||
OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
|
OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -367,7 +381,6 @@ int tls13_add_certificate(SSL_HANDSHAKE *hs) {
|
|||||||
return ssl_add_message_cbb(ssl, cbb.get());
|
return ssl_add_message_cbb(ssl, cbb.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
CERT *cert = hs->config->cert;
|
|
||||||
CRYPTO_BUFFER *leaf_buf = sk_CRYPTO_BUFFER_value(cert->chain.get(), 0);
|
CRYPTO_BUFFER *leaf_buf = sk_CRYPTO_BUFFER_value(cert->chain.get(), 0);
|
||||||
CBB leaf, extensions;
|
CBB leaf, extensions;
|
||||||
if (!CBB_add_u24_length_prefixed(&certificate_list, &leaf) ||
|
if (!CBB_add_u24_length_prefixed(&certificate_list, &leaf) ||
|
||||||
@ -378,33 +391,29 @@ int tls13_add_certificate(SSL_HANDSHAKE *hs) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hs->scts_requested &&
|
if (hs->scts_requested && cert->signed_cert_timestamp_list != nullptr) {
|
||||||
hs->config->cert->signed_cert_timestamp_list != nullptr) {
|
|
||||||
CBB contents;
|
CBB contents;
|
||||||
if (!CBB_add_u16(&extensions, TLSEXT_TYPE_certificate_timestamp) ||
|
if (!CBB_add_u16(&extensions, TLSEXT_TYPE_certificate_timestamp) ||
|
||||||
!CBB_add_u16_length_prefixed(&extensions, &contents) ||
|
!CBB_add_u16_length_prefixed(&extensions, &contents) ||
|
||||||
!CBB_add_bytes(
|
!CBB_add_bytes(
|
||||||
&contents,
|
&contents,
|
||||||
CRYPTO_BUFFER_data(
|
CRYPTO_BUFFER_data(cert->signed_cert_timestamp_list.get()),
|
||||||
hs->config->cert->signed_cert_timestamp_list.get()),
|
CRYPTO_BUFFER_len(cert->signed_cert_timestamp_list.get())) ||
|
||||||
CRYPTO_BUFFER_len(
|
|
||||||
hs->config->cert->signed_cert_timestamp_list.get())) ||
|
|
||||||
!CBB_flush(&extensions)) {
|
!CBB_flush(&extensions)) {
|
||||||
OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
|
OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hs->ocsp_stapling_requested && hs->config->cert->ocsp_response != NULL) {
|
if (hs->ocsp_stapling_requested && cert->ocsp_response != NULL) {
|
||||||
CBB contents, ocsp_response;
|
CBB contents, ocsp_response;
|
||||||
if (!CBB_add_u16(&extensions, TLSEXT_TYPE_status_request) ||
|
if (!CBB_add_u16(&extensions, TLSEXT_TYPE_status_request) ||
|
||||||
!CBB_add_u16_length_prefixed(&extensions, &contents) ||
|
!CBB_add_u16_length_prefixed(&extensions, &contents) ||
|
||||||
!CBB_add_u8(&contents, TLSEXT_STATUSTYPE_ocsp) ||
|
!CBB_add_u8(&contents, TLSEXT_STATUSTYPE_ocsp) ||
|
||||||
!CBB_add_u24_length_prefixed(&contents, &ocsp_response) ||
|
!CBB_add_u24_length_prefixed(&contents, &ocsp_response) ||
|
||||||
!CBB_add_bytes(
|
!CBB_add_bytes(&ocsp_response,
|
||||||
&ocsp_response,
|
CRYPTO_BUFFER_data(cert->ocsp_response.get()),
|
||||||
CRYPTO_BUFFER_data(hs->config->cert->ocsp_response.get()),
|
CRYPTO_BUFFER_len(cert->ocsp_response.get())) ||
|
||||||
CRYPTO_BUFFER_len(hs->config->cert->ocsp_response.get())) ||
|
|
||||||
!CBB_flush(&extensions)) {
|
!CBB_flush(&extensions)) {
|
||||||
OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
|
OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
|
||||||
return 0;
|
return 0;
|
||||||
@ -423,9 +432,45 @@ int tls13_add_certificate(SSL_HANDSHAKE *hs) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!hs->cert_compression_negotiated) {
|
||||||
return ssl_add_message_cbb(ssl, cbb.get());
|
return ssl_add_message_cbb(ssl, cbb.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Array<uint8_t> msg;
|
||||||
|
if (!CBBFinishArray(cbb.get(), &msg)) {
|
||||||
|
OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const CertCompressionAlg *alg = nullptr;
|
||||||
|
for (CertCompressionAlg *candidate : ssl->ctx->cert_compression_algs) {
|
||||||
|
if (candidate->alg_id == hs->cert_compression_alg_id) {
|
||||||
|
alg = candidate;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (alg == nullptr || alg->compress == nullptr) {
|
||||||
|
OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
CBB compressed;
|
||||||
|
body = &body_storage;
|
||||||
|
if (!ssl->method->init_message(ssl, cbb.get(), body,
|
||||||
|
SSL3_MT_COMPRESSED_CERTIFICATE) ||
|
||||||
|
!CBB_add_u16(body, hs->cert_compression_alg_id) ||
|
||||||
|
!CBB_add_u24(body, msg.size()) ||
|
||||||
|
!CBB_add_u24_length_prefixed(body, &compressed) ||
|
||||||
|
!alg->compress(ssl, &compressed, msg) ||
|
||||||
|
!ssl_add_message_cbb(ssl, cbb.get())) {
|
||||||
|
OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
enum ssl_private_key_result_t tls13_add_certificate_verify(SSL_HANDSHAKE *hs) {
|
enum ssl_private_key_result_t tls13_add_certificate_verify(SSL_HANDSHAKE *hs) {
|
||||||
SSL *const ssl = hs->ssl;
|
SSL *const ssl = hs->ssl;
|
||||||
uint16_t signature_algorithm;
|
uint16_t signature_algorithm;
|
||||||
|
Loading…
Reference in New Issue
Block a user