1
#include "source/common/tls/cert_compression.h"
2

            
3
#include "source/common/common/assert.h"
4
#include "source/common/common/macros.h"
5

            
6
#include "brotli/decode.h"
7
#include "brotli/encode.h"
8
#include "openssl/tls1.h"
9

            
10
#define ZLIB_CONST
11
#include "zlib.h"
12

            
13
namespace Envoy {
14
namespace Extensions {
15
namespace TransportSockets {
16
namespace Tls {
17

            
18
11060
void CertCompression::registerBrotli(SSL_CTX* ssl_ctx) {
19
11060
  auto ret = SSL_CTX_add_cert_compression_alg(ssl_ctx, TLSEXT_cert_compression_brotli,
20
11060
                                              compressBrotli, decompressBrotli);
21
11060
  ASSERT(ret == 1);
22
11060
}
23

            
24
11060
void CertCompression::registerZlib(SSL_CTX* ssl_ctx) {
25
11060
  auto ret = SSL_CTX_add_cert_compression_alg(ssl_ctx, TLSEXT_cert_compression_zlib, compressZlib,
26
11060
                                              decompressZlib);
27
11060
  ASSERT(ret == 1);
28
11060
}
29

            
30
1871
int CertCompression::compressBrotli(SSL*, CBB* out, const uint8_t* in, size_t in_len) {
31
1871
  size_t encoded_size = BrotliEncoderMaxCompressedSize(in_len);
32
1871
  if (encoded_size == 0) {
33
1
    IS_ENVOY_BUG("BrotliEncoderMaxCompressedSize returned 0");
34
1
    return FAILURE;
35
1
  }
36

            
37
1870
  uint8_t* out_buf = nullptr;
38
1870
  if (!CBB_reserve(out, &out_buf, encoded_size)) {
39
    IS_ENVOY_BUG(fmt::format("Cert compression failure in allocating output CBB buffer of size {}",
40
                             encoded_size));
41
    return FAILURE;
42
  }
43

            
44
1870
  if (BrotliEncoderCompress(BROTLI_DEFAULT_QUALITY, BROTLI_DEFAULT_WINDOW, BROTLI_MODE_GENERIC,
45
1870
                            in_len, in, &encoded_size, out_buf) != BROTLI_TRUE) {
46
    IS_ENVOY_BUG("Cert compression failure in BrotliEncoderCompress");
47
    return FAILURE;
48
  }
49

            
50
1870
  if (!CBB_did_write(out, encoded_size)) {
51
    IS_ENVOY_BUG("CBB_did_write failed");
52
    return FAILURE;
53
  }
54

            
55
1870
  ENVOY_LOG(trace, "Cert brotli compression successful");
56
1870
  return SUCCESS;
57
1870
}
58

            
59
int CertCompression::decompressBrotli(SSL*, CRYPTO_BUFFER** out, size_t uncompressed_len,
60
1850
                                      const uint8_t* in, size_t in_len) {
61
1850
  uint8_t* out_buf = nullptr;
62
1850
  bssl::UniquePtr<CRYPTO_BUFFER> decompressed_data(CRYPTO_BUFFER_alloc(&out_buf, uncompressed_len));
63
1850
  if (!decompressed_data) {
64
    IS_ENVOY_BUG("Failed to allocate CRYPTO_BUFFER for brotli decompression");
65
    return FAILURE;
66
  }
67

            
68
1850
  size_t decoded_size = uncompressed_len;
69
1850
  BrotliDecoderResult result = BrotliDecoderDecompress(in_len, in, &decoded_size, out_buf);
70

            
71
1850
  if (result != BROTLI_DECODER_RESULT_SUCCESS) {
72
1
    ENVOY_LOG_PERIODIC(error, std::chrono::seconds(10),
73
1
                       "Cert brotli decompression failure, possibly caused by invalid "
74
1
                       "compressed cert from peer: result={}, decoded_size={}, uncompressed_len={}",
75
1
                       static_cast<int>(result), decoded_size, uncompressed_len);
76
1
    return FAILURE;
77
1
  }
78

            
79
1849
  if (decoded_size != uncompressed_len) {
80
1
    ENVOY_LOG_PERIODIC(error, std::chrono::seconds(10),
81
1
                       "Brotli decompression length did not match peer provided uncompressed "
82
1
                       "length, caused by either invalid peer handshake data or decompression "
83
1
                       "error: decoded_size={}, uncompressed_len={}",
84
1
                       decoded_size, uncompressed_len);
85
1
    return FAILURE;
86
1
  }
87

            
88
1848
  ENVOY_LOG(trace, "Cert brotli decompression successful");
89
1848
  *out = decompressed_data.release();
90
1848
  return SUCCESS;
91
1849
}
92

            
93
namespace {
94

            
95
class ScopedZStream {
96
public:
97
  using CleanupFunc = int (*)(z_stream*);
98

            
99
8
  ScopedZStream(z_stream& z, CleanupFunc cleanup) : z_(z), cleanup_(cleanup) {}
100
8
  ~ScopedZStream() { cleanup_(&z_); }
101

            
102
private:
103
  z_stream& z_;
104
  CleanupFunc cleanup_;
105
};
106

            
107
} // namespace
108

            
109
3
int CertCompression::compressZlib(SSL*, CBB* out, const uint8_t* in, size_t in_len) {
110
3
  z_stream z = {};
111
  // The deflateInit macro from zlib.h contains an old-style cast, so we need to suppress the
112
  // warning for this call.
113
3
#pragma GCC diagnostic push
114
3
#pragma GCC diagnostic ignored "-Wold-style-cast"
115
3
  int rv = deflateInit(&z, Z_DEFAULT_COMPRESSION);
116
3
#pragma GCC diagnostic pop
117
3
  if (rv != Z_OK) {
118
    IS_ENVOY_BUG(fmt::format("Cert compression failure in deflateInit: {}", rv));
119
    return FAILURE;
120
  }
121

            
122
3
  ScopedZStream deleter(z, deflateEnd);
123

            
124
3
  const auto upper_bound = deflateBound(&z, in_len);
125

            
126
3
  uint8_t* out_buf = nullptr;
127
3
  if (!CBB_reserve(out, &out_buf, upper_bound)) {
128
    IS_ENVOY_BUG(fmt::format("Cert compression failure in allocating output CBB buffer of size {}",
129
                             upper_bound));
130
    return FAILURE;
131
  }
132

            
133
3
  z.next_in = in;
134
3
  z.avail_in = in_len;
135
3
  z.next_out = out_buf;
136
3
  z.avail_out = upper_bound;
137

            
138
3
  rv = deflate(&z, Z_FINISH);
139
3
  if (rv != Z_STREAM_END) {
140
    IS_ENVOY_BUG(fmt::format(
141
        "Cert compression failure in deflate: {}, z.total_out {}, in_len {}, z.avail_in {}", rv,
142
        z.avail_in, in_len, z.avail_in));
143
    return FAILURE;
144
  }
145

            
146
3
  if (!CBB_did_write(out, z.total_out)) {
147
    IS_ENVOY_BUG("CBB_did_write failed");
148
    return FAILURE;
149
  }
150

            
151
3
  ENVOY_LOG(trace, "Cert zlib compression successful");
152
3
  return SUCCESS;
153
3
}
154

            
155
int CertCompression::decompressZlib(SSL*, CRYPTO_BUFFER** out, size_t uncompressed_len,
156
5
                                    const uint8_t* in, size_t in_len) {
157
5
  z_stream z = {};
158
  // The inflateInit macro from zlib.h contains an old-style cast, so we need to suppress the
159
  // warning for this call.
160
5
#pragma GCC diagnostic push
161
5
#pragma GCC diagnostic ignored "-Wold-style-cast"
162
5
  int rv = inflateInit(&z);
163
5
#pragma GCC diagnostic pop
164
5
  if (rv != Z_OK) {
165
    IS_ENVOY_BUG(fmt::format("Cert decompression failure in inflateInit: {}", rv));
166
    return FAILURE;
167
  }
168

            
169
5
  ScopedZStream deleter(z, inflateEnd);
170

            
171
5
  z.next_in = in;
172
5
  z.avail_in = in_len;
173
5
  uint8_t* out_buf = nullptr;
174
5
  bssl::UniquePtr<CRYPTO_BUFFER> decompressed_data(CRYPTO_BUFFER_alloc(&out_buf, uncompressed_len));
175
5
  if (!decompressed_data) {
176
    IS_ENVOY_BUG("Failed to allocate CRYPTO_BUFFER for zlib decompression");
177
    return FAILURE;
178
  }
179
5
  z.next_out = out_buf;
180
5
  z.avail_out = uncompressed_len;
181

            
182
5
  rv = inflate(&z, Z_FINISH);
183
5
  if (rv != Z_STREAM_END) {
184
2
    ENVOY_LOG_PERIODIC(error, std::chrono::seconds(10),
185
2
                       "Cert zlib decompression failure, possibly caused by invalid "
186
2
                       "compressed cert from peer: {}, z.total_out {}, uncompressed_len {}",
187
2
                       rv, z.total_out, uncompressed_len);
188
2
    return FAILURE;
189
2
  }
190

            
191
3
  if (z.total_out != uncompressed_len) {
192
2
    ENVOY_LOG_PERIODIC(error, std::chrono::seconds(10),
193
2
                       "Zlib decompression length did not match peer provided uncompressed "
194
2
                       "length, caused by either invalid peer handshake data or decompression "
195
2
                       "error: z.total_out={}, uncompressed_len={}",
196
2
                       z.total_out, uncompressed_len);
197
2
    return FAILURE;
198
2
  }
199

            
200
1
  ENVOY_LOG(trace, "Cert zlib decompression successful");
201
1
  *out = decompressed_data.release();
202
1
  return SUCCESS;
203
3
}
204

            
205
} // namespace Tls
206
} // namespace TransportSockets
207
} // namespace Extensions
208
} // namespace Envoy