Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support to zeroize plaintext in S3 record layer #12251

Closed
wants to merge 8 commits into from

Conversation

melshuber
Copy link
Contributor

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.

Checklist
  • documentation is added or updated
  • tests are added or updated

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.
@openssl-machine openssl-machine added the hold: cla required The contributor needs to submit a license agreement label Jun 23, 2020
@mattcaswell mattcaswell added the branch: master Merge to master branch label Jun 23, 2020
CHANGES.md Outdated
@@ -1057,6 +1057,12 @@ OpenSSL 3.0

*Boris Pismenny*

* The SSL option SSL_OP_CLEANSE_PLAINTEXT is introduced. If that
option is set, openssl cleanses plaintext bytes after delivering
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably we should clarify that this means we cleanse internal buffers. The application is still responsible for cleansing its own buffers.

@@ -1477,6 +1477,9 @@ int ssl3_read_bytes(SSL *s, int type, int *recvd_type, unsigned char *buf,
n = len - totalbytes;

memcpy(buf, &(rr->data[rr->off]), n);
if (s->options & SSL_OP_CLEANSE_PLAINTEXT)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This shouldn't be done if peek is set


certbio = NULL;
if (!TEST_ptr(chaincert))
goto end;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The block above seems unnecessary. chaincert is never used.

cbuf[i] = i & 0xff;
}

if (!TEST_true(SSL_write(clientssl, cbuf, sizeof(cbuf)) == sizeof(cbuf)))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TEST_int_eq seems more appropriate than TEST_true here.

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

while ((err = SSL_read(serverssl, &sbuf, sizeof(sbuf))) != sizeof(sbuf)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The loop here seems unnecessary. We know the data is going to be there because there is no network being used here.

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

while ((err = SSL_read(serverssl, &sbuf, sizeof(sbuf))) != sizeof(sbuf)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should do a peek first,

@mattcaswell
Copy link
Member

Please can you submit a CLA:

https://www.openssl.org/policies/cla.html

=item SSL_OP_CLEANSE_PLAINTEXT

By default TLS connections keep a copy of received plaintext data in a
static buffer until it is overwritten by followup data. When enabling
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

followup => next portion of?

@melshuber
Copy link
Contributor Author

Please can you submit a CLA:

https://www.openssl.org/policies/cla.html

Sure the CCLA is already in my mailbox, the ICLA I am singing tomorrow (my printer is dead) and planning to submit them together

@melshuber
Copy link
Contributor Author

are you preferring to amend the commit or a followup and squash later?

@melshuber
Copy link
Contributor Author

I am wondering, if we should add a similar behaviour in in dtls1_read_bytes() as well. But since I am not sure about the peek/read behaviour for datagrams I am a little bit hesitant.

@mattcaswell
Copy link
Member

are you preferring to amend the commit or a followup and squash later?

Preferred is to add additional commits fixing up previous ones using git commit --fixup. That way whoever merges can just use git rebase -i --autosquash

@mattcaswell
Copy link
Member

I am wondering, if we should add a similar behaviour in in dtls1_read_bytes() as well. But since I am not sure about the peek/read behaviour for datagrams I am a little bit hesitant.

Oh, yes. Good point - we should do that. It should be virtually the same.

@mattcaswell
Copy link
Member

Hmm. The travis failures look odd - but I don't think they are related to this change.

@melshuber
Copy link
Contributor Author

Hmm. The travis failures look odd - but I don't think they are related to this change.

Well that concerns me as well. I run the testsuite on my local machine and there all looks good.
However since travis checks more this is just an indication.

The odd thing is that we see red Xes all over the place even on merged commits. I did not check in detail but the logs look similar.

However since it least the 'if' statements are executed in many configurations, I am not sure what to do about this.

@melshuber
Copy link
Contributor Author

melshuber commented Jun 24, 2020

Please can you submit a CLA:
https://www.openssl.org/policies/cla.html

Sure the CCLA is already in my mailbox, the ICLA I am singing tomorrow (my printer is dead) and planning to submit them together

I sent the ICLA and CCLA a couple of hours ago, please tell me if something is missing

@melshuber
Copy link
Contributor Author

To summarize the delta:

  • Addressed your comments
  • Added zeroization for DTLS (and test)
  • Test: Added checks to explicitly check internal buffer contents after SSL_peek, and after SSL_read
  • Test: Moved test functions down, it was in between the large_message tests

@melshuber
Copy link
Contributor Author

are you preferring to amend the commit or a followup and squash later?

Preferred is to add additional commits fixing up previous ones using git commit --fixup. That way whoever merges can just use git rebase -i --autosquash

So hold back with rebasing myself for now

@mattcaswell
Copy link
Member

Close/reopen to kick CLA bot

@mattcaswell mattcaswell reopened this Jun 24, 2020
@openssl-machine openssl-machine removed the hold: cla required The contributor needs to submit a license agreement label Jun 24, 2020
@mattcaswell
Copy link
Member

Travis is now passing :-)

@melshuber
Copy link
Contributor Author

Travis is now passing :-)

Feels better this way :)

@melshuber
Copy link
Contributor Author

Since there are still changes requested. Is something missing from my side?


s->rlayer.packet = rdata->packet;
s->rlayer.packet_length = rdata->packet_length;
memcpy(&s->rlayer.rbuf, &(rdata->rbuf), sizeof(SSL3_BUFFER));
memcpy(rbuf, &(rdata->rbuf), sizeof(SSL3_BUFFER));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here we're copying plaintext out of rdata, which comes from item->data. There are two callers of dtls1_copy_record, and in both cases item->data is immediately freed, so we probably also need to cleanse that. This function could be called for any type of record - not just application data. Which makes me wonder whether this option is supposed to cleanse all plaintext (e.g. including handshake/alert plaintext), or just application data plaintext. If the former then I think there are some spots that might have been missed (see further comments below).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will recheck and update

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am trying to get around where DTLS maintains copies of records.
Sof far i found three queues

  • unprocessed_rcds.q:
    I think this contains ciphertext records (no cleansing required)
  • processed_rcds.q: I think this contains plaintext records, which can be either app data or handshake.
    Cleansing is required only when we free it and there is still data left (DTLS_RECORD_LAYER_clear)
  • buffered_app_data.q: Bufferd but not yet deliverd plaintext application data records. Must be cleansed in (DTLS_RECORD_LAYER_clear, in dtls1_read_bytes)

Is it correct that each of these three queues contain DTLS1_RECORD_DATA items where each of those convey it own 16K buffer for a single record?

To my understanding dtls1_copy_record takes an item from one of those queues and replaces s->rlayer.rbuf, with the queued item. The old record is freed (and cleansed) in this process?

Note however, as i understand it dtls1_copy_record, does not copy the record it self, in only copies metadata (pointers and stuff).

Keeping this in mind I think we can revert the cleanse modification in dtls1_copy_record, since it only cleanses application data after it is delivered (and already cleansed) to the application, but we need to add cleansing of leftofer items in buffered_app_data and processed_rcds DTLS_RECORD_LAYER_clear.

I am really not totaly sure about my analysis, please let me know if it is correct. I already have prepared a fixup containing the following changes:

  • Add cleansing in DTLS_RECORD_LAYER_clear
  • Revert cleansing in dtls1_copy_record

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it correct that each of these three queues contain DTLS1_RECORD_DATA items where each of those convey it own 16K buffer for a single record?

Yes

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The old record is freed (and cleansed) in this process?

It is not cleansed. However, I don't think it needs to be. If the old record contains plaintext application data then we'll only copy in a new record if we've already read that data - and if we've read it then (as a result of this PR) we will have already cleansed that application data.

I am really not totaly sure about my analysis, please let me know if it is correct. I already have prepared a fixup containing the following changes:

I agree with your analysis

@@ -1483,6 +1483,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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This cleanses record data that we are expecting to receive. Sometimes we received records of other types, e.g. alerts, or new handshake records (see the sections later in this function for alert handling, and handshake fragment/unexpected handshake messages). We're not cleansing the plaintext for those types of records. Although this in itself begs another question - how far do you go? Information that we learn from plaintext handshake records is stored in the SSL object all over the place. Should we cleanse all of those too (if so then the scope suddenly became a lot more complicated)? Alternatively we clarify that the option is only about application data plaintext and restrict ourselves to worrying about that only. (All of this comment applies to the same section of the DTLS code)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think clearly we should stop at application data. The whole point is to extend residual information protection to application data, which is often a requirement when using secure channels.

Applications transport all kind of items within application data of the secure channel (e.g.: Passwords, PSKs, PINs, ...). This feature aims to support the kind of requirements stating that resuduals thereof must be removed as well. These requirements are not that uncommon.

Further, the current implementation only clenses the application record payload, but not the header.

Regarding handshake data: I think in TLS1.2 most of it is sent plaintext anyways. TLS1.3 is different, but to my understanding this is done mainly to protect the from monitoring metainformation (e.g.: certificates used).

Regarding handshake data within the secure channel (e.g.: KEY-UPDATE): prove me wrong, but i do not think that sensitive information is contained in those (other than metainformation).

We could add an "if" to exclude records other than application data, but I do not think that clansing a little bit too much notably more hurts perfomance when comparing it to application data cleansing.

So regarding:
(1) "if so then the scope suddenly became a lot more complicated" and
(2) "Alternatively we clarify that the option is only about application data plaintext and restrict ourselves to worrying about that only."

ad (1) totally correct -> I will be a never ending story.
ad (2) I think this is the way to got: I will clarify this in the manpage.

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

if (memcmp(cbuf, sbuf, sizeof(cbuf)))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use TEST_mem_eq instead of memcmp. You get better diagnostic info if the test ever fails.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will do

* After SSL_peek() the plaintext must still be stored in the
* record.
*/
if (memcmp(cbuf, zbuf, sizeof(cbuf)))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TEST_mem_eq...and so on below

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will do

@@ -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);
Copy link
Contributor Author

@melshuber melshuber Jun 26, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Depending on the read sematics for datagram connections i think the n should be replaced by SSL3_RECORD_get_length(rr)

Since when for datagram sockets when reading up to n bytes from a datagram with a length larger than n. The remaining bytes are dropped and never delivered to the application.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry n need to be replaced by:
len - SSL3_RECORD_get_off(rr)

However reading the code twice i think OpenSSL does not drop the data, so n seems fine anyways

Copy link
Member

@mattcaswell mattcaswell left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved subject to the minor wording tweak.

Needs second review.

CHANGES.md Outdated
* 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 to cleanse other copies (e.g.:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A better phrasing might "...is still responsible for cleansing other copies"

@mattcaswell mattcaswell added the approval: review pending This pull request needs review by a committer label Jun 30, 2020
@melshuber
Copy link
Contributor Author

Fixed proposal in CHANGES.md

Copy link
Member

@mattcaswell mattcaswell left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reconfirmed.

Ping @openssl/committers.

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 notable this
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NIT: notably (not sure if it should have a , or not)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

L<SSL_peek(3)>


/*
* Since we called SSL_peek(), we know the data is stored as
* plaintext record. We can gather the pointer to check for
Copy link
Member

@slontis slontis Jul 2, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the data is a

/*
* Since we called SSL_peek(), we know the data is stored as
* plaintext record. We can gather the pointer to check for
* zerozation after SSL_read().
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

zeroization

Copy link
Member

@slontis slontis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good apart from some small NITS.

@melshuber
Copy link
Contributor Author

@slontis: thx for the review,

I added update according to to your proposial (except the one i commeneted on).

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 notable this applies
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@slontis made two comments about this. I think you missed the one "notable" -> "notably"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ups sorry

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So @slontis also meant the comma after the notably.

I am not sure about this either.
What do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@slontis; sorry i misread your comment

fixed it as well

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure about the comma - so whatever.

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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just L<SSL_peek(3)> should be sufficient here (and is consistent with what we normally do). There are symlinks in place to resolve where multiple functions are defined in a single file.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried with L<SSL_peek(3)> and this leads to a dead link in the html man page.
therefor i did not add it in the beginning

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

at least when using links2(1)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm. Strange. But this should work, so if it isn't then we've got that problem throughout the man pages. Not an issue for this PR to resolve - so I think we should just have it asL<SSL_peek(3)> for consistency.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok,

JFI: Here is what i tried:

./Configure --prefix=$(pwd)/tmp linux-x86_64
make install
links2 tmp/share/doc/openssl/html/man3/SSL_CTX_set_options.html

there are no symlinks in tmp/share/doc/openssl/html/man3/

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@levitte...any thoughts on that?

@mattcaswell mattcaswell added approval: done This pull request has the required number of approvals and removed approval: review pending This pull request needs review by a committer labels Jul 6, 2020
Copy link
Member

@beldmit beldmit left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@openssl-machine openssl-machine added approval: ready to merge The 24 hour grace period has passed, ready to merge and removed approval: done This pull request has the required number of approvals labels Jul 7, 2020
@openssl-machine
Copy link
Collaborator

This pull request is ready to merge

openssl-machine pushed a commit that referenced this pull request Jul 7, 2020
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)
@beldmit
Copy link
Member

beldmit commented Jul 7, 2020

Squashed and merged. Thanks!

@beldmit beldmit closed this Jul 7, 2020
@melshuber
Copy link
Contributor Author

Thanks all

thuang910 pushed a commit to thuang910/openssl that referenced this pull request Jan 5, 2021
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 openssl#12251)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
approval: ready to merge The 24 hour grace period has passed, ready to merge branch: master Merge to master branch
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants