Skip to content

Commit

Permalink
Add support to zeroize plaintext in S3 record layer
Browse files Browse the repository at this point in the history
Some applications want even all plaintext copies beeing
zeroized. However, currently plaintext residuals are kept in rbuf
within the s3 record layer.

This patch add the option SSL_OP_CLEANSE_PLAINTEXT to its friends to
optionally enable cleansing of decrypted plaintext data.

Reviewed-by: Matt Caswell <matt@openssl.org>
Reviewed-by: Shane Lontis <shane.lontis@oracle.com>
Reviewed-by: Dmitry Belyavskiy <beldmit@gmail.com>
(Merged from #12251)
  • Loading branch information
martin-cherry authored and beldmit committed Jul 7, 2020
1 parent 1c9761d commit 163b801
Show file tree
Hide file tree
Showing 7 changed files with 148 additions and 1 deletion.
8 changes: 8 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -1100,6 +1100,14 @@ OpenSSL 3.0

*Boris Pismenny*

* The SSL option SSL_OP_CLEANSE_PLAINTEXT is introduced. If that
option is set, openssl cleanses (zeroize) plaintext bytes from
internal buffers after delivering them to the application. Note,
the application is still responsible for cleansing other copies
(e.g.: data received by SSL_read(3)).

*Martin Elshuber*

OpenSSL 1.1.1
-------------

Expand Down
14 changes: 14 additions & 0 deletions doc/man3/SSL_CTX_set_options.pod
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,20 @@ functionality is not required. Those applications can turn this feature off by
setting this option. This is a server-side opton only. It is ignored by
clients.

=item SSL_OP_CLEANSE_PLAINTEXT

By default TLS connections keep a copy of received plaintext
application data in a static buffer until it is overwritten by the
next portion of data. When enabling SSL_OP_CLEANSE_PLAINTEXT
deciphered application data is cleansed by calling OPENSSL_cleanse(3)
after passing data to the application. Data is also cleansed when
releasing the connection (eg. L<SSL_free(3)>).

Since OpenSSL only cleanses internal buffers, the application is still
responsible for cleansing all other buffers. Most notably, this
applies to buffers passed to functions like L<SSL_read(3)>,
L<SSL_peek(3)> but also like L<SSL_write(3)>.

=back

The following options no longer have any effect but their identifiers are
Expand Down
3 changes: 2 additions & 1 deletion include/openssl/ssl.h
Original file line number Diff line number Diff line change
Expand Up @@ -321,7 +321,8 @@ typedef int (*SSL_async_callback_fn)(SSL *s, void *arg);
/* Disable Extended master secret */
# define SSL_OP_NO_EXTENDED_MASTER_SECRET 0x00000001U

/* Reserved value (until OpenSSL 3.0.0) 0x00000002U */
/* Cleanse plaintext copies of data delivered to the application */
# define SSL_OP_CLEANSE_PLAINTEXT 0x00000002U

/* Allow initial connection to servers that don't support RI */
# define SSL_OP_LEGACY_SERVER_CONNECT 0x00000004U
Expand Down
6 changes: 6 additions & 0 deletions ssl/record/rec_layer_d1.c
Original file line number Diff line number Diff line change
Expand Up @@ -74,13 +74,17 @@ void DTLS_RECORD_LAYER_clear(RECORD_LAYER *rl)

while ((item = pqueue_pop(d->processed_rcds.q)) != NULL) {
rdata = (DTLS1_RECORD_DATA *)item->data;
if (rl->s->options & SSL_OP_CLEANSE_PLAINTEXT)
OPENSSL_cleanse(rdata->rbuf.buf, rdata->rbuf.len);
OPENSSL_free(rdata->rbuf.buf);
OPENSSL_free(item->data);
pitem_free(item);
}

while ((item = pqueue_pop(d->buffered_app_data.q)) != NULL) {
rdata = (DTLS1_RECORD_DATA *)item->data;
if (rl->s->options & SSL_OP_CLEANSE_PLAINTEXT)
OPENSSL_cleanse(rdata->rbuf.buf, rdata->rbuf.len);
OPENSSL_free(rdata->rbuf.buf);
OPENSSL_free(item->data);
pitem_free(item);
Expand Down Expand Up @@ -514,6 +518,8 @@ int dtls1_read_bytes(SSL *s, int type, int *recvd_type, unsigned char *buf,
if (SSL3_RECORD_get_length(rr) == 0)
SSL3_RECORD_set_read(rr);
} else {
if (s->options & SSL_OP_CLEANSE_PLAINTEXT)
OPENSSL_cleanse(&(SSL3_RECORD_get_data(rr)[SSL3_RECORD_get_off(rr)]), n);
SSL3_RECORD_sub_length(rr, n);
SSL3_RECORD_add_off(rr, n);
if (SSL3_RECORD_get_length(rr) == 0) {
Expand Down
2 changes: 2 additions & 0 deletions ssl/record/rec_layer_s3.c
Original file line number Diff line number Diff line change
Expand Up @@ -1484,6 +1484,8 @@ int ssl3_read_bytes(SSL *s, int type, int *recvd_type, unsigned char *buf,
if (SSL3_RECORD_get_length(rr) == 0)
SSL3_RECORD_set_read(rr);
} else {
if (s->options & SSL_OP_CLEANSE_PLAINTEXT)
OPENSSL_cleanse(&(rr->data[rr->off]), n);
SSL3_RECORD_sub_length(rr, n);
SSL3_RECORD_add_off(rr, n);
if (SSL3_RECORD_get_length(rr) == 0) {
Expand Down
2 changes: 2 additions & 0 deletions ssl/record/ssl3_buffer.c
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,8 @@ int ssl3_release_read_buffer(SSL *s)
SSL3_BUFFER *b;

b = RECORD_LAYER_get_rbuf(&s->rlayer);
if (s->options & SSL_OP_CLEANSE_PLAINTEXT)
OPENSSL_cleanse(b->buf, b->len);
OPENSSL_free(b->buf);
b->buf = NULL;
return 1;
Expand Down
114 changes: 114 additions & 0 deletions test/sslapitest.c
Original file line number Diff line number Diff line change
Expand Up @@ -1595,6 +1595,119 @@ static int test_large_message_dtls(void)
}
#endif

static int execute_cleanse_plaintext(const SSL_METHOD *smeth,
const SSL_METHOD *cmeth,
int min_version, int max_version)
{
size_t i;
SSL_CTX *cctx = NULL, *sctx = NULL;
SSL *clientssl = NULL, *serverssl = NULL;
int testresult = 0;
SSL3_RECORD *rr;
void *zbuf;

static unsigned char cbuf[16000];
static unsigned char sbuf[16000];

if (!TEST_true(create_ssl_ctx_pair(libctx,
smeth, cmeth,
min_version, max_version,
&sctx, &cctx, cert,
privkey)))
goto end;

if (!TEST_true(create_ssl_objects(sctx, cctx, &serverssl, &clientssl,
NULL, NULL)))
goto end;

if (!TEST_true(SSL_set_options(serverssl, SSL_OP_CLEANSE_PLAINTEXT)))
goto end;

if (!TEST_true(create_ssl_connection(serverssl, clientssl,
SSL_ERROR_NONE)))
goto end;

for (i = 0; i < sizeof(cbuf); i++) {
cbuf[i] = i & 0xff;
}

if (!TEST_int_eq(SSL_write(clientssl, cbuf, sizeof(cbuf)), sizeof(cbuf)))
goto end;

if (!TEST_int_eq(SSL_peek(serverssl, &sbuf, sizeof(sbuf)), sizeof(sbuf)))
goto end;

if (!TEST_mem_eq(cbuf, sizeof(cbuf), sbuf, sizeof(sbuf)))
goto end;

/*
* Since we called SSL_peek(), we know the data in the record
* layer is a plaintext record. We can gather the pointer to check
* for zeroization after SSL_read().
*/
rr = serverssl->rlayer.rrec;
zbuf = &rr->data[rr->off];
if (!TEST_int_eq(rr->length, sizeof(cbuf)))
goto end;

/*
* After SSL_peek() the plaintext must still be stored in the
* record.
*/
if (!TEST_mem_eq(cbuf, sizeof(cbuf), zbuf, sizeof(cbuf)))
goto end;

memset(sbuf, 0, sizeof(sbuf));
if (!TEST_int_eq(SSL_read(serverssl, &sbuf, sizeof(sbuf)), sizeof(sbuf)))
goto end;

if (!TEST_mem_eq(cbuf, sizeof(cbuf), sbuf, sizeof(cbuf)))
goto end;

/* Check if rbuf is cleansed */
memset(cbuf, 0, sizeof(cbuf));
if (!TEST_mem_eq(cbuf, sizeof(cbuf), zbuf, sizeof(cbuf)))
goto end;

testresult = 1;
end:
SSL_free(serverssl);
SSL_free(clientssl);
SSL_CTX_free(sctx);
SSL_CTX_free(cctx);

return testresult;
}

static int test_cleanse_plaintext(void)
{
#if !defined(OPENSSL_NO_TLS1_2)
if (!TEST_true(execute_cleanse_plaintext(TLS_server_method(),
TLS_client_method(),
TLS1_2_VERSION,
TLS1_2_VERSION)))
return 0;

#endif

#if !defined(OPENSSL_NO_TLS1_3)
if (!TEST_true(execute_cleanse_plaintext(TLS_server_method(),
TLS_client_method(),
TLS1_3_VERSION,
TLS1_3_VERSION)))
return 0;
#endif

#if !defined(OPENSSL_NO_DTLS)
if (!TEST_true(execute_cleanse_plaintext(DTLS_server_method(),
DTLS_client_method(),
DTLS1_VERSION,
0)))
return 0;
#endif
return 1;
}

#ifndef OPENSSL_NO_OCSP
static int ocsp_server_cb(SSL *s, void *arg)
{
Expand Down Expand Up @@ -8324,6 +8437,7 @@ int setup_tests(void)
#ifndef OPENSSL_NO_DTLS
ADD_TEST(test_large_message_dtls);
#endif
ADD_TEST(test_cleanse_plaintext);
#ifndef OPENSSL_NO_OCSP
ADD_TEST(test_tlsext_status_type);
#endif
Expand Down

0 comments on commit 163b801

Please sign in to comment.