-
Notifications
You must be signed in to change notification settings - Fork 11
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
Update the public API to remove footguns, and document it. #52
Conversation
/cc @tiesselune - could you please check that this doesn't interfere with your use of the crate, apart from the obvious breakage from removing the |
|
||
// We have some existing test data in b64, and some in hex, | ||
// and it's easy to make a second `try_decrypt` helper function | ||
// than to re-encode all the data. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These tests have just moved from lib.rs
into legacy.rs
to be next to their other aesgcm
friends, I haven't changed them at all.
@@ -181,61 +200,6 @@ mod aes128gcm_tests { | |||
assert_eq!(ciphertext, "0c6bfaadad67958803092d454676f397000010004104fe33f4ab0dea71914db55823f73b54948f41306d920732dbb9a59a53286482200e597a7b7bc260ba1c227998580992e93973002f3012a28ae8f06bbb78e5ec0ff297de5b429bba7153d3a4ae0caa091fd425f3b4b5414add8ab37a19c1bbb05cf5cb5b2a2e0562d558635641ec52812c6c8ff42e95ccb86be7cd"); | |||
} | |||
|
|||
#[test] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We had quite a few tests for for dealing with record boundaries etc. Per the discussion in mozilla/application-services#1068, we do not need this for webpush, and I don't think it works right in the legacy aesgcm
scheme anyway.
I intend to push a follow-up PR that removes the multi-record stuff entirely; it seemed like too much churn to do all at once in this PR.
0542f86
to
a50068a
Compare
/// | ||
/// You should only use this is you're implementing a custom `Cryptographer` and want to check | ||
/// that it is working as intended. This function will panic if the tests fail. | ||
/// |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The disadvantage of hiding things from the public API, is that it makes it harder to test our custom NSS-based ece backend over in the application-services repo. It currently has its own copy of all the aes128gcm tests which use a bunch of the now-hidden implementation details. What if instead, we exposed a function here specifically for testing custom backends?
(My theory with making it generic over T: Cryptographer
rather than e.g. taking a Box<dyn Cryptographer>
is that it won't produce any code unless you actually try to use a concrete instantiation of it, and since we only use it in tests, it won't contribute to the size of our production builds. I have done exactly zero work to confirm whether that's actually what will happen).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ref mozilla/application-services#3941 for the corresponding appservices change.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IIRC: you might be able to use a feature flag to make a "test" version that does expose this as public. It's a bit hacky, but we've done something similar in other repos.
f014226
to
5ba0ddd
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good.
- I definitely think this rates a version bump in
Cargo.toml
- might be a bit of debugging detritus in one of the tests.
- Thanks for adding all the docs!
[RFC8188](https://tools.ietf.org/html/rfc8188) | ||
* `aesgcm`: the draft scheme described in | ||
[draft-ietf-webpush-encryption-04](https://tools.ietf.org/html/draft-ietf-webpush-encryption-04) and | ||
[draft-ietf-httpbis-encryption-encoding-03](https://tools.ietf.org/html/draft-ietf-httpbis-encryption-encoding-03_) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would really be in favor of dropping the oldest one of these. I'll file a separate ticket. Most of what we're seeing is either 'aes128gcm' or 'aesgcm'.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think the code actually supports that format, this could just be a documentation issue or misunderstanding on my side.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jrconlin I'm not very familiar with the various versions of these specs, could you please help me understand what you mean here? The two linked "draft" documents only seem to mention "aesgcm", which IIUC is the "legacy" line in your graph and the corresponding "legacy" API in this crate. Neither contains the string "aesgcm128" that I can see.
I suspect we don't have anything to remove in the code, but I'd be happy to update the doc references here to make things more clear, if you can suggest any revisions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Heh. The fun of a long lived and somewhat popular draft is that you get stuff like this. I don't expect that this PR should drop support for the very old protocol, but I do think we should add it as a future issue.
In essence, three "versions" of the encryption format started being used over time. The very first version "aesgcm128" used a combination of header and body content in order to encrypt/decrypt the message. It was reasonably early so not a lot of libraries adopted it, but firefox said that they'd support it as part of the WebPush pre-cursor. The next version cleaned things up, but still used header/body format (albeit, with different headers and formats), and was labeled "aesgcm". This version was WIDELY adopted by third party encoder libraries and (as you can see) is still very much in use. The final RFC proposed "aes128gcm", which made encryption/decryption self contained within the body of the message. While folks have been encouraging subscription providers to update their libraries to use the RFC format (since it's both more efficient and far more secure), a lot of subscription services have not.
There are not a lot of changes between "aesgcm128" and "aesgcm", but they are present. Supporting the very old format just adds unneeded complexity and potential surface area for the library. For most client side / decryption models, we can definitely control this from the Push Server by simply blocking messages that use "aesgcm128" with a 415 or something. For encryption, however, it would be best to remove that particular format as a potential option.
All things must come to an end, and for "aesgcm128", it's way overdue.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've dug in a bit more here, and I don't believe this crate has support for aesgcm128
. I've been using the desktop implementation for reference to help get my head around it.
In order to support aesgcm128
, we would need to be:
- Using a KDF info string of "Content-Encoding: aesgcm128"; I can't find such a string anywhere in the repo.
- Emitting an
Encryption-Key
header as part of the encryption process; I can only find support for emitting theCrypto-Key
andEncryption
headers of the newer-but-still-legacyaesgcm
scheme.
I've made the following updates to help clarify what I believe the situation is, but if I've misunderstood, please correct me:
- I've delete a reference to the
Encryption-Key
header in one of the comments, which was the only place I could find such a string, and which is incorrect since the comment is specifically in the context ofaesgcm
which uses theEncryption
field. - I've added an explicit note in the readme saying this scheme is not and will never be supported, along with a link to the older draft RFC for reference (
draft-thomson-http-encryption-02
, which I dug out of the comments in the desktop implementation).
/// | ||
/// You should only use this is you're implementing a custom `Cryptographer` and want to check | ||
/// that it is working as intended. This function will panic if the tests fail. | ||
/// |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IIRC: you might be able to use a feature flag to make a "test" version that does expose this as public. It's a bit hacky, but we've done something similar in other repos.
src/legacy.rs
Outdated
// since it's a sampled random, endian doesn't really matter. | ||
let pad = ((usize::from(padr[0]) + (usize::from(padr[1]) << 8)) % 4095) + 1; | ||
let params = WebPushParams::new(4096, pad, Vec::from(salt)); | ||
let mut pad_length = ((usize::from(padr[0]) + (usize::from(padr[1]) << 8)) % 4095) + 1; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: We do this here and in lib.rs
. Would it be worth making this into a pub(crate)
function?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I intend to propose a change to how this padding works in a follow-up PR, but yeah, it's probably worth pulling out a quick helper in the meantime.
Thanks @jrconlin, updated with nits addressed. |
Update: I'm concerned that we may be accidentally using multiple records when we use this encryption scheme for send-tab. Both the rust component and desktop appear to have support for arbitrarily-long payloads and splitting them across multiple records. I need to dig a big deeper into the implications there before we can land this :-/ |
AFAIK, there are several issues associated with multi-part records that lead to the decision to not support this aspect of the protocol. (no guarantee of delivery, potential for out of order delivery, potential drop via bridge systems, etc.) For Send Tab, I believe that we created PushBox specifically to address large data associated with a push message, but I have no idea if there may be even more metadata that may exceed the size limits. I do know that the PushServer rejects messages that are too large. Of course, if the UA wants to deal with the headaches associated with handling multi-part records, they are absolutely free to do so. |
@rfk I haven't taken the time to actually test this on the newer |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll admit to being initially confused by aes128gcm
vs aesgcm128
but otherwise the PR explanation and comments were helpful. 🚀
COMPLETELY understandable. A lot of folks are. One of the reasons I really want to get rid of the very old Possibly by dragging it behind a woodshed. |
Right, yes. To add more context: Send Tab also encrypts the larger-payload data that it stores in PushBox, and it encrypts it using the same ECE scheme as we use for push (because, well, it's already there and works). |
I am absolutely 100% behind the idea of killing off the old |
a9d065b
to
e167d74
Compare
After a bit of poking at send-tab, I've decided to continue to support multiple records when encrypting with |
This is a significant refactor of the public API of the crate, simplifying the API surface and removing some of the footgun potential noted by Martin in his review at mozilla/application-services#1068. In particular: * The public `encrypt` functions no longer take a `salt` parameter. The right thing to do is to generate a new random `salt` for each encryption so we just do that for you automatically. * Many internal implementation details are now `pub(crate)` rather than `pub`, to avoid potential confusion from consumers. * We refuse to encrypt or decrypt across multiple records in the legacy `aesgcm` scheme, because the only consumer of that schema is webpush, and webpush restricts consumers to using only a single record. We still have the code lying around to encrypt/decrypt across record boundaries, but we don't have high confidence that it works correctly for `aesgcm` and intend to refactor that away in a future commit. So, may as well adjust the interface to reflect that while we're in here making breaking changes. To go along with the revised interface, this commit also significantly expands to docs in order to help set consumer expectations and context.
This is a significant refactor of the public API of the crate, simplifying
the API surface and removing some of the footgun potential noted by Martin
in his review at mozilla/application-services#1068.
In particular:
encrypt
functions no longer take asalt
parameter. Theright thing to do is to generate a new random
salt
for each encryptionso we just do that for you automatically.
pub(crate)
rather thanpub
,to avoid potential confusion from consumers.
consumer in practice is webpush, and webpush restricts consumers to using
only a single record.
We still have the code lying around to encrypt/decrypt across record
boundaries, but we don't have high confidence that it works correctly
and intend to remove it in a future commit. So, may as well adjust the
interface to reflect that while we're in here making breaking changes.
To go along with the revised interface, this commit also significantly
expands to docs in order to help set consumer expectations and context.
(Ref #27 for an earlier version of this change).