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_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 {
|
||||
kOK,
|
||||
kDiscard,
|
||||
|
@ -311,6 +311,7 @@ OPENSSL_COMPILE_ASSERT(
|
||||
#define SSL3_MT_CERTIFICATE_STATUS 22
|
||||
#define SSL3_MT_SUPPLEMENTAL_DATA 23
|
||||
#define SSL3_MT_KEY_UPDATE 24
|
||||
#define SSL3_MT_COMPRESSED_CERTIFICATE 25
|
||||
#define SSL3_MT_NEXT_PROTO 67
|
||||
#define SSL3_MT_CHANNEL_ID 203
|
||||
#define SSL3_MT_MESSAGE_HASH 254
|
||||
|
@ -205,9 +205,15 @@ extern "C" {
|
||||
// ExtensionType value from draft-ietf-tokbind-negotiation-10
|
||||
#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
|
||||
|
||||
// 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
|
||||
#define TLSEXT_TYPE_session_ticket 35
|
||||
|
||||
|
@ -147,7 +147,8 @@ SSL_HANDSHAKE::SSL_HANDSHAKE(SSL *ssl_arg)
|
||||
extended_master_secret(false),
|
||||
pending_private_key_op(false),
|
||||
grease_seeded(false),
|
||||
handback(false) {
|
||||
handback(false),
|
||||
cert_compression_negotiated(false) {
|
||||
assert(ssl);
|
||||
}
|
||||
|
||||
|
@ -1481,6 +1481,11 @@ struct SSL_HANDSHAKE {
|
||||
// sent.
|
||||
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
|
||||
// parameters. It has client and server randoms prepended for signing
|
||||
// convenience.
|
||||
@ -1595,6 +1600,14 @@ struct SSL_HANDSHAKE {
|
||||
// grease_seeded is true if |grease_seed| has been initialized.
|
||||
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.
|
||||
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
|
||||
// echoed.
|
||||
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);
|
||||
@ -1987,6 +1995,14 @@ struct tlsext_ticket_key {
|
||||
|
||||
DECLARE_LHASH_OF(SSL_SESSION)
|
||||
|
||||
struct CertCompressionAlg {
|
||||
bssl::CertCompressFunc compress;
|
||||
bssl::CertDecompressFunc decompress;
|
||||
uint16_t alg_id;
|
||||
};
|
||||
|
||||
DEFINE_STACK_OF(CertCompressionAlg);
|
||||
|
||||
namespace bssl {
|
||||
|
||||
// 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
|
||||
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
|
||||
size_t supported_group_list_len;
|
||||
uint16_t *supported_group_list;
|
||||
|
@ -510,6 +510,51 @@ void SSL_set_handoff_mode(SSL *ssl, bool 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
|
||||
|
||||
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);
|
||||
ctx->x509_method->ssl_ctx_free(ctx);
|
||||
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->supported_group_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;
|
||||
}
|
||||
|
||||
// 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.
|
||||
static const struct tls_extension kExtensions[] = {
|
||||
@ -2945,6 +3032,14 @@ static const struct tls_extension kExtensions[] = {
|
||||
ext_token_binding_parse_clienthello,
|
||||
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))
|
||||
|
@ -1346,6 +1346,54 @@ static bssl::UniquePtr<SSL_CTX> SetupCtx(SSL_CTX *old_ctx,
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -82,26 +82,27 @@ const (
|
||||
|
||||
// TLS handshake message types.
|
||||
const (
|
||||
typeHelloRequest uint8 = 0
|
||||
typeClientHello uint8 = 1
|
||||
typeServerHello uint8 = 2
|
||||
typeHelloVerifyRequest uint8 = 3
|
||||
typeNewSessionTicket uint8 = 4
|
||||
typeEndOfEarlyData uint8 = 5 // draft-ietf-tls-tls13-21
|
||||
typeHelloRetryRequest uint8 = 6 // draft-ietf-tls-tls13-16
|
||||
typeEncryptedExtensions uint8 = 8 // draft-ietf-tls-tls13-16
|
||||
typeCertificate uint8 = 11
|
||||
typeServerKeyExchange uint8 = 12
|
||||
typeCertificateRequest uint8 = 13
|
||||
typeServerHelloDone uint8 = 14
|
||||
typeCertificateVerify uint8 = 15
|
||||
typeClientKeyExchange uint8 = 16
|
||||
typeFinished uint8 = 20
|
||||
typeCertificateStatus uint8 = 22
|
||||
typeKeyUpdate uint8 = 24 // draft-ietf-tls-tls13-16
|
||||
typeNextProtocol uint8 = 67 // Not IANA assigned
|
||||
typeChannelID uint8 = 203 // Not IANA assigned
|
||||
typeMessageHash uint8 = 254 // draft-ietf-tls-tls13-21
|
||||
typeHelloRequest uint8 = 0
|
||||
typeClientHello uint8 = 1
|
||||
typeServerHello uint8 = 2
|
||||
typeHelloVerifyRequest uint8 = 3
|
||||
typeNewSessionTicket uint8 = 4
|
||||
typeEndOfEarlyData uint8 = 5 // draft-ietf-tls-tls13-21
|
||||
typeHelloRetryRequest uint8 = 6 // draft-ietf-tls-tls13-16
|
||||
typeEncryptedExtensions uint8 = 8 // draft-ietf-tls-tls13-16
|
||||
typeCertificate uint8 = 11
|
||||
typeServerKeyExchange uint8 = 12
|
||||
typeCertificateRequest uint8 = 13
|
||||
typeServerHelloDone uint8 = 14
|
||||
typeCertificateVerify uint8 = 15
|
||||
typeClientKeyExchange uint8 = 16
|
||||
typeFinished uint8 = 20
|
||||
typeCertificateStatus uint8 = 22
|
||||
typeKeyUpdate uint8 = 24 // draft-ietf-tls-tls13-16
|
||||
typeCompressedCertificate uint8 = 25 // Not IANA assigned
|
||||
typeNextProtocol uint8 = 67 // Not IANA assigned
|
||||
typeChannelID uint8 = 203 // Not IANA assigned
|
||||
typeMessageHash uint8 = 254 // draft-ietf-tls-tls13-21
|
||||
)
|
||||
|
||||
// TLS compression types.
|
||||
@ -122,7 +123,8 @@ const (
|
||||
extensionPadding uint16 = 21
|
||||
extensionExtendedMasterSecret uint16 = 23
|
||||
extensionTokenBinding uint16 = 24
|
||||
extensionQUICTransportParams uint16 = 26
|
||||
extensionQUICTransportParams uint16 = 26 // conflicts with TLS-LTS
|
||||
extensionCompressedCertAlgs uint16 = 27
|
||||
extensionSessionTicket uint16 = 35
|
||||
extensionPreSharedKey uint16 = 41 // 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)
|
||||
}
|
||||
|
||||
// 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.
|
||||
// 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
|
||||
@ -500,6 +512,8 @@ type Config struct {
|
||||
// transport parameters extension.
|
||||
QUICTransportParams []byte
|
||||
|
||||
CertCompressionAlgs map[uint16]CertCompressionAlg
|
||||
|
||||
// Bugs specifies optional misbehaviour to be used for testing other
|
||||
// implementations.
|
||||
Bugs ProtocolBugs
|
||||
@ -1596,6 +1610,14 @@ type ProtocolBugs struct {
|
||||
// SetX25519HighBit, if true, causes X25519 key shares to set their
|
||||
// high-order bit.
|
||||
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() {
|
||||
|
@ -1368,9 +1368,11 @@ func (c *Conn) readHandshake() (interface{}, error) {
|
||||
m = &certificateMsg{
|
||||
hasRequestContext: c.vers >= VersionTLS13,
|
||||
}
|
||||
case typeCompressedCertificate:
|
||||
m = new(compressedCertificateMsg)
|
||||
case typeCertificateRequest:
|
||||
m = &certificateRequestMsg{
|
||||
vers: c.wireVersion,
|
||||
vers: c.wireVersion,
|
||||
hasSignatureAlgorithm: c.vers >= VersionTLS12,
|
||||
hasRequestContext: c.vers >= VersionTLS13,
|
||||
}
|
||||
@ -1806,7 +1808,7 @@ func (c *Conn) Handshake() error {
|
||||
if c.isDTLS && c.config.Bugs.SendSplitAlert {
|
||||
c.conn.Write([]byte{
|
||||
byte(recordTypeAlert), // type
|
||||
0xfe, 0xff, // version
|
||||
0xfe, 0xff, // version
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // sequence
|
||||
0x0, 0x2, // length
|
||||
})
|
||||
|
@ -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() {
|
||||
hello.secureRenegotiation = nil
|
||||
}
|
||||
@ -864,12 +873,46 @@ func (hs *clientHandshakeState) doTLS13Handshake() error {
|
||||
}
|
||||
}
|
||||
|
||||
certMsg, ok := msg.(*certificateMsg)
|
||||
if !ok {
|
||||
c.sendAlert(alertUnexpectedMessage)
|
||||
return unexpectedMessageError(certMsg, msg)
|
||||
var certMsg *certificateMsg
|
||||
|
||||
if compressedCertMsg, ok := msg.(*compressedCertificateMsg); ok {
|
||||
hs.writeServerHash(compressedCertMsg.marshal())
|
||||
|
||||
alg, ok := c.config.CertCompressionAlgs[compressedCertMsg.algID]
|
||||
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)
|
||||
return unexpectedMessageError(certMsg, msg)
|
||||
}
|
||||
hs.writeServerHash(certMsg.marshal())
|
||||
|
||||
if c.config.Bugs.ExpectedCompressedCert != 0 {
|
||||
return errors.New("tls: uncompressed certificate received")
|
||||
}
|
||||
}
|
||||
hs.writeServerHash(certMsg.marshal())
|
||||
|
||||
// Check for unsolicited extensions.
|
||||
for i, cert := range certMsg.certificates {
|
||||
|
@ -296,6 +296,7 @@ type clientHelloMsg struct {
|
||||
emptyExtensions bool
|
||||
pad int
|
||||
dummyPQPaddingLen int
|
||||
compressedCertAlgs []uint16
|
||||
}
|
||||
|
||||
func (m *clientHelloMsg) equal(i interface{}) bool {
|
||||
@ -349,7 +350,8 @@ func (m *clientHelloMsg) equal(i interface{}) bool {
|
||||
m.omitExtensions == m1.omitExtensions &&
|
||||
m.emptyExtensions == m1.emptyExtensions &&
|
||||
m.pad == m1.pad &&
|
||||
m.dummyPQPaddingLen == m1.dummyPQPaddingLen
|
||||
m.dummyPQPaddingLen == m1.dummyPQPaddingLen &&
|
||||
eqUint16s(m.compressedCertAlgs, m1.compressedCertAlgs)
|
||||
}
|
||||
|
||||
func (m *clientHelloMsg) marshal() []byte {
|
||||
@ -586,6 +588,14 @@ func (m *clientHelloMsg) marshal() []byte {
|
||||
body := extensions.addU16LengthPrefixed()
|
||||
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).
|
||||
if len(m.pskIdentities) > 0 && !m.pskBinderFirst {
|
||||
extensions.addU16(extensionPreSharedKey)
|
||||
@ -903,6 +913,24 @@ func (m *clientHelloMsg) unmarshal(data []byte) bool {
|
||||
return false
|
||||
}
|
||||
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) {
|
||||
@ -1671,6 +1699,48 @@ func (m *certificateMsg) unmarshal(data []byte) bool {
|
||||
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 {
|
||||
raw []byte
|
||||
key []byte
|
||||
|
@ -834,7 +834,7 @@ ResendHelloRetryRequest:
|
||||
if config.ClientAuth >= RequestClientCert {
|
||||
// Request a client certificate
|
||||
certReq := &certificateRequestMsg{
|
||||
vers: c.wireVersion,
|
||||
vers: c.wireVersion,
|
||||
hasSignatureAlgorithm: !config.Bugs.OmitCertificateRequestAlgorithms,
|
||||
hasRequestContext: true,
|
||||
requestContext: config.Bugs.SendRequestContext,
|
||||
|
@ -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) {
|
||||
defer wg.Done()
|
||||
|
||||
@ -14524,6 +14676,7 @@ func main() {
|
||||
addECDSAKeyUsageTests()
|
||||
addExtraHandshakeTests()
|
||||
addOmitExtensionsTests()
|
||||
addCertCompressionTests()
|
||||
|
||||
testCases = append(testCases, convertToSplitHandshakeTests(testCases)...)
|
||||
|
||||
|
@ -141,6 +141,8 @@ const Flag<bool> kBoolFlags[] = {
|
||||
{ "-set-ocsp-in-callback", &TestConfig::set_ocsp_in_callback },
|
||||
{ "-decline-ocsp-callback", &TestConfig::decline_ocsp_callback },
|
||||
{ "-fail-ocsp-callback", &TestConfig::fail_ocsp_callback },
|
||||
{ "-install-cert-compression-algs",
|
||||
&TestConfig::install_cert_compression_algs },
|
||||
};
|
||||
|
||||
const Flag<std::string> kStringFlags[] = {
|
||||
|
@ -161,6 +161,7 @@ struct TestConfig {
|
||||
bool set_ocsp_in_callback = false;
|
||||
bool decline_ocsp_callback = false;
|
||||
bool fail_ocsp_callback = false;
|
||||
bool install_cert_compression_algs = false;
|
||||
};
|
||||
|
||||
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) {
|
||||
SSL *const ssl = hs->ssl;
|
||||
CERT *const cert = hs->config->cert;
|
||||
|
||||
ScopedCBB cbb;
|
||||
CBB body, certificate_list;
|
||||
if (!ssl->method->init_message(ssl, cbb.get(), &body, SSL3_MT_CERTIFICATE) ||
|
||||
// The request context is always empty in the handshake.
|
||||
!CBB_add_u8(&body, 0) ||
|
||||
!CBB_add_u24_length_prefixed(&body, &certificate_list)) {
|
||||
CBB *body, body_storage, certificate_list;
|
||||
|
||||
if (hs->cert_compression_negotiated) {
|
||||
if (!CBB_init(cbb.get(), 1024)) {
|
||||
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);
|
||||
return 0;
|
||||
}
|
||||
@ -367,7 +381,6 @@ int tls13_add_certificate(SSL_HANDSHAKE *hs) {
|
||||
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);
|
||||
CBB leaf, extensions;
|
||||
if (!CBB_add_u24_length_prefixed(&certificate_list, &leaf) ||
|
||||
@ -378,33 +391,29 @@ int tls13_add_certificate(SSL_HANDSHAKE *hs) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (hs->scts_requested &&
|
||||
hs->config->cert->signed_cert_timestamp_list != nullptr) {
|
||||
if (hs->scts_requested && cert->signed_cert_timestamp_list != nullptr) {
|
||||
CBB contents;
|
||||
if (!CBB_add_u16(&extensions, TLSEXT_TYPE_certificate_timestamp) ||
|
||||
!CBB_add_u16_length_prefixed(&extensions, &contents) ||
|
||||
!CBB_add_bytes(
|
||||
&contents,
|
||||
CRYPTO_BUFFER_data(
|
||||
hs->config->cert->signed_cert_timestamp_list.get()),
|
||||
CRYPTO_BUFFER_len(
|
||||
hs->config->cert->signed_cert_timestamp_list.get())) ||
|
||||
CRYPTO_BUFFER_data(cert->signed_cert_timestamp_list.get()),
|
||||
CRYPTO_BUFFER_len(cert->signed_cert_timestamp_list.get())) ||
|
||||
!CBB_flush(&extensions)) {
|
||||
OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
|
||||
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;
|
||||
if (!CBB_add_u16(&extensions, TLSEXT_TYPE_status_request) ||
|
||||
!CBB_add_u16_length_prefixed(&extensions, &contents) ||
|
||||
!CBB_add_u8(&contents, TLSEXT_STATUSTYPE_ocsp) ||
|
||||
!CBB_add_u24_length_prefixed(&contents, &ocsp_response) ||
|
||||
!CBB_add_bytes(
|
||||
&ocsp_response,
|
||||
CRYPTO_BUFFER_data(hs->config->cert->ocsp_response.get()),
|
||||
CRYPTO_BUFFER_len(hs->config->cert->ocsp_response.get())) ||
|
||||
!CBB_add_bytes(&ocsp_response,
|
||||
CRYPTO_BUFFER_data(cert->ocsp_response.get()),
|
||||
CRYPTO_BUFFER_len(cert->ocsp_response.get())) ||
|
||||
!CBB_flush(&extensions)) {
|
||||
OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
|
||||
return 0;
|
||||
@ -423,7 +432,43 @@ int tls13_add_certificate(SSL_HANDSHAKE *hs) {
|
||||
}
|
||||
}
|
||||
|
||||
return ssl_add_message_cbb(ssl, cbb.get());
|
||||
if (!hs->cert_compression_negotiated) {
|
||||
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) {
|
||||
|
Loading…
Reference in New Issue
Block a user