summaryrefslogtreecommitdiffstats
path: root/third_party/rust/ece
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
commit2aa4a82499d4becd2284cdb482213d541b8804dd (patch)
treeb80bf8bf13c3766139fbacc530efd0dd9d54394c /third_party/rust/ece
parentInitial commit. (diff)
downloadfirefox-2aa4a82499d4becd2284cdb482213d541b8804dd.tar.xz
firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.zip
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/ece')
-rw-r--r--third_party/rust/ece/.cargo-checksum.json1
-rw-r--r--third_party/rust/ece/CODE_OF_CONDUCT.md15
-rw-r--r--third_party/rust/ece/Cargo.toml60
-rw-r--r--third_party/rust/ece/LICENSE373
-rw-r--r--third_party/rust/ece/README.md23
-rw-r--r--third_party/rust/ece/src/aes128gcm.rs222
-rw-r--r--third_party/rust/ece/src/aesgcm.rs252
-rw-r--r--third_party/rust/ece/src/common.rs251
-rw-r--r--third_party/rust/ece/src/crypto/holder.rs50
-rw-r--r--third_party/rust/ece/src/crypto/mod.rs105
-rw-r--r--third_party/rust/ece/src/crypto/openssl.rs203
-rw-r--r--third_party/rust/ece/src/error.rs51
-rw-r--r--third_party/rust/ece/src/lib.rs441
13 files changed, 2047 insertions, 0 deletions
diff --git a/third_party/rust/ece/.cargo-checksum.json b/third_party/rust/ece/.cargo-checksum.json
new file mode 100644
index 0000000000..53d279430d
--- /dev/null
+++ b/third_party/rust/ece/.cargo-checksum.json
@@ -0,0 +1 @@
+{"files":{"CODE_OF_CONDUCT.md":"902d5357af363426631d907e641e220b3ec89039164743f8442b3f120479b7cf","Cargo.toml":"a79a698dd321168a78077b9611101873d7e4221876eccb5b08f4e283d074584b","LICENSE":"1f256ecad192880510e84ad60474eab7589218784b9a50bc7ceee34c2b91f1d5","README.md":"95b8f2f9a8adefedc23db615756366eb5555fc2e256152e3f534fddcfebe18ea","src/aes128gcm.rs":"5239bcd2ce3d4768f68c10e8fa2f9c49bd459282768939160bd21c0cdc46ca91","src/aesgcm.rs":"41bab3915ced4ec00c8da4763faf0436c3ad3f6fada4bf5fd41f639904988c5c","src/common.rs":"ac8425072c22e32f2b6f7a34d9795fb766e989ce737cdd5565c34560acb976f9","src/crypto/holder.rs":"38424503c9e0c36c304bc38dd4db268672f8a9f2fa70525d785eecae54aa47a9","src/crypto/mod.rs":"27194e01b55c81d84b84a96985092538bfbbd2fbf0210420a98e2d7a6a0af594","src/crypto/openssl.rs":"5661f3a9fe2ce2a4d9c5cefe8a8aa9a99e1216d4129ff6b2f2e017102e02f60a","src/error.rs":"53a75d97335b9ec63fc325436f59d5baf66c8265eb847c4f5e40716dbb50438f","src/lib.rs":"6c45f743c71b6410d2c5808be786da98bb813384d565801f99c309258042a99c"},"package":"53d97f19730c1eb3332d0657d0f3ca72795d77c61d8eb26bdd7f15edc0c61eb2"} \ No newline at end of file
diff --git a/third_party/rust/ece/CODE_OF_CONDUCT.md b/third_party/rust/ece/CODE_OF_CONDUCT.md
new file mode 100644
index 0000000000..498baa3fb0
--- /dev/null
+++ b/third_party/rust/ece/CODE_OF_CONDUCT.md
@@ -0,0 +1,15 @@
+# Community Participation Guidelines
+
+This repository is governed by Mozilla's code of conduct and etiquette guidelines.
+For more details, please read the
+[Mozilla Community Participation Guidelines](https://www.mozilla.org/about/governance/policies/participation/).
+
+## How to Report
+For more information on how to report violations of the Community Participation Guidelines, please read our '[How to Report](https://www.mozilla.org/about/governance/policies/participation/reporting/)' page.
+
+<!--
+## Project Specific Etiquette
+
+In some cases, there will be additional project etiquette i.e.: (https://bugzilla.mozilla.org/page.cgi?id=etiquette.html).
+Please update for your project.
+-->
diff --git a/third_party/rust/ece/Cargo.toml b/third_party/rust/ece/Cargo.toml
new file mode 100644
index 0000000000..111322e31b
--- /dev/null
+++ b/third_party/rust/ece/Cargo.toml
@@ -0,0 +1,60 @@
+# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
+#
+# When uploading crates to the registry Cargo will automatically
+# "normalize" Cargo.toml files for maximal compatibility
+# with all versions of Cargo and also rewrite `path` dependencies
+# to registry (e.g., crates.io) dependencies
+#
+# If you believe there's an error in this file please file an
+# issue against the rust-lang/cargo repository. If you're
+# editing this file be aware that the upstream Cargo.toml
+# will likely look very different (and much more reasonable)
+
+[package]
+edition = "2018"
+name = "ece"
+version = "1.2.1"
+authors = ["Edouard Oger <eoger@fastmail.com>", "JR Conlin <jrconlin@gmail.com>"]
+description = "Encrypted Content-Encoding for HTTP Rust implementation."
+keywords = ["http-ece", "web-push"]
+license = "MPL-2.0"
+repository = "https://github.com/mozilla/rust-ece"
+[dependencies.base64]
+version = "0.12"
+
+[dependencies.byteorder]
+version = "1.3"
+
+[dependencies.hkdf]
+version = "0.9"
+optional = true
+
+[dependencies.lazy_static]
+version = "1.4"
+optional = true
+
+[dependencies.once_cell]
+version = "1.4"
+
+[dependencies.openssl]
+version = "0.10"
+optional = true
+
+[dependencies.serde]
+version = "1.0"
+features = ["derive"]
+optional = true
+
+[dependencies.sha2]
+version = "0.9"
+optional = true
+
+[dependencies.thiserror]
+version = "1.0"
+[dev-dependencies.hex]
+version = "0.4"
+
+[features]
+backend-openssl = ["openssl", "lazy_static", "hkdf", "sha2"]
+default = ["backend-openssl", "serializable-keys"]
+serializable-keys = ["serde"]
diff --git a/third_party/rust/ece/LICENSE b/third_party/rust/ece/LICENSE
new file mode 100644
index 0000000000..a612ad9813
--- /dev/null
+++ b/third_party/rust/ece/LICENSE
@@ -0,0 +1,373 @@
+Mozilla Public License Version 2.0
+==================================
+
+1. Definitions
+--------------
+
+1.1. "Contributor"
+ means each individual or legal entity that creates, contributes to
+ the creation of, or owns Covered Software.
+
+1.2. "Contributor Version"
+ means the combination of the Contributions of others (if any) used
+ by a Contributor and that particular Contributor's Contribution.
+
+1.3. "Contribution"
+ means Covered Software of a particular Contributor.
+
+1.4. "Covered Software"
+ means Source Code Form to which the initial Contributor has attached
+ the notice in Exhibit A, the Executable Form of such Source Code
+ Form, and Modifications of such Source Code Form, in each case
+ including portions thereof.
+
+1.5. "Incompatible With Secondary Licenses"
+ means
+
+ (a) that the initial Contributor has attached the notice described
+ in Exhibit B to the Covered Software; or
+
+ (b) that the Covered Software was made available under the terms of
+ version 1.1 or earlier of the License, but not also under the
+ terms of a Secondary License.
+
+1.6. "Executable Form"
+ means any form of the work other than Source Code Form.
+
+1.7. "Larger Work"
+ means a work that combines Covered Software with other material, in
+ a separate file or files, that is not Covered Software.
+
+1.8. "License"
+ means this document.
+
+1.9. "Licensable"
+ means having the right to grant, to the maximum extent possible,
+ whether at the time of the initial grant or subsequently, any and
+ all of the rights conveyed by this License.
+
+1.10. "Modifications"
+ means any of the following:
+
+ (a) any file in Source Code Form that results from an addition to,
+ deletion from, or modification of the contents of Covered
+ Software; or
+
+ (b) any new file in Source Code Form that contains any Covered
+ Software.
+
+1.11. "Patent Claims" of a Contributor
+ means any patent claim(s), including without limitation, method,
+ process, and apparatus claims, in any patent Licensable by such
+ Contributor that would be infringed, but for the grant of the
+ License, by the making, using, selling, offering for sale, having
+ made, import, or transfer of either its Contributions or its
+ Contributor Version.
+
+1.12. "Secondary License"
+ means either the GNU General Public License, Version 2.0, the GNU
+ Lesser General Public License, Version 2.1, the GNU Affero General
+ Public License, Version 3.0, or any later versions of those
+ licenses.
+
+1.13. "Source Code Form"
+ means the form of the work preferred for making modifications.
+
+1.14. "You" (or "Your")
+ means an individual or a legal entity exercising rights under this
+ License. For legal entities, "You" includes any entity that
+ controls, is controlled by, or is under common control with You. For
+ purposes of this definition, "control" means (a) the power, direct
+ or indirect, to cause the direction or management of such entity,
+ whether by contract or otherwise, or (b) ownership of more than
+ fifty percent (50%) of the outstanding shares or beneficial
+ ownership of such entity.
+
+2. License Grants and Conditions
+--------------------------------
+
+2.1. Grants
+
+Each Contributor hereby grants You a world-wide, royalty-free,
+non-exclusive license:
+
+(a) under intellectual property rights (other than patent or trademark)
+ Licensable by such Contributor to use, reproduce, make available,
+ modify, display, perform, distribute, and otherwise exploit its
+ Contributions, either on an unmodified basis, with Modifications, or
+ as part of a Larger Work; and
+
+(b) under Patent Claims of such Contributor to make, use, sell, offer
+ for sale, have made, import, and otherwise transfer either its
+ Contributions or its Contributor Version.
+
+2.2. Effective Date
+
+The licenses granted in Section 2.1 with respect to any Contribution
+become effective for each Contribution on the date the Contributor first
+distributes such Contribution.
+
+2.3. Limitations on Grant Scope
+
+The licenses granted in this Section 2 are the only rights granted under
+this License. No additional rights or licenses will be implied from the
+distribution or licensing of Covered Software under this License.
+Notwithstanding Section 2.1(b) above, no patent license is granted by a
+Contributor:
+
+(a) for any code that a Contributor has removed from Covered Software;
+ or
+
+(b) for infringements caused by: (i) Your and any other third party's
+ modifications of Covered Software, or (ii) the combination of its
+ Contributions with other software (except as part of its Contributor
+ Version); or
+
+(c) under Patent Claims infringed by Covered Software in the absence of
+ its Contributions.
+
+This License does not grant any rights in the trademarks, service marks,
+or logos of any Contributor (except as may be necessary to comply with
+the notice requirements in Section 3.4).
+
+2.4. Subsequent Licenses
+
+No Contributor makes additional grants as a result of Your choice to
+distribute the Covered Software under a subsequent version of this
+License (see Section 10.2) or under the terms of a Secondary License (if
+permitted under the terms of Section 3.3).
+
+2.5. Representation
+
+Each Contributor represents that the Contributor believes its
+Contributions are its original creation(s) or it has sufficient rights
+to grant the rights to its Contributions conveyed by this License.
+
+2.6. Fair Use
+
+This License is not intended to limit any rights You have under
+applicable copyright doctrines of fair use, fair dealing, or other
+equivalents.
+
+2.7. Conditions
+
+Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
+in Section 2.1.
+
+3. Responsibilities
+-------------------
+
+3.1. Distribution of Source Form
+
+All distribution of Covered Software in Source Code Form, including any
+Modifications that You create or to which You contribute, must be under
+the terms of this License. You must inform recipients that the Source
+Code Form of the Covered Software is governed by the terms of this
+License, and how they can obtain a copy of this License. You may not
+attempt to alter or restrict the recipients' rights in the Source Code
+Form.
+
+3.2. Distribution of Executable Form
+
+If You distribute Covered Software in Executable Form then:
+
+(a) such Covered Software must also be made available in Source Code
+ Form, as described in Section 3.1, and You must inform recipients of
+ the Executable Form how they can obtain a copy of such Source Code
+ Form by reasonable means in a timely manner, at a charge no more
+ than the cost of distribution to the recipient; and
+
+(b) You may distribute such Executable Form under the terms of this
+ License, or sublicense it under different terms, provided that the
+ license for the Executable Form does not attempt to limit or alter
+ the recipients' rights in the Source Code Form under this License.
+
+3.3. Distribution of a Larger Work
+
+You may create and distribute a Larger Work under terms of Your choice,
+provided that You also comply with the requirements of this License for
+the Covered Software. If the Larger Work is a combination of Covered
+Software with a work governed by one or more Secondary Licenses, and the
+Covered Software is not Incompatible With Secondary Licenses, this
+License permits You to additionally distribute such Covered Software
+under the terms of such Secondary License(s), so that the recipient of
+the Larger Work may, at their option, further distribute the Covered
+Software under the terms of either this License or such Secondary
+License(s).
+
+3.4. Notices
+
+You may not remove or alter the substance of any license notices
+(including copyright notices, patent notices, disclaimers of warranty,
+or limitations of liability) contained within the Source Code Form of
+the Covered Software, except that You may alter any license notices to
+the extent required to remedy known factual inaccuracies.
+
+3.5. Application of Additional Terms
+
+You may choose to offer, and to charge a fee for, warranty, support,
+indemnity or liability obligations to one or more recipients of Covered
+Software. However, You may do so only on Your own behalf, and not on
+behalf of any Contributor. You must make it absolutely clear that any
+such warranty, support, indemnity, or liability obligation is offered by
+You alone, and You hereby agree to indemnify every Contributor for any
+liability incurred by such Contributor as a result of warranty, support,
+indemnity or liability terms You offer. You may include additional
+disclaimers of warranty and limitations of liability specific to any
+jurisdiction.
+
+4. Inability to Comply Due to Statute or Regulation
+---------------------------------------------------
+
+If it is impossible for You to comply with any of the terms of this
+License with respect to some or all of the Covered Software due to
+statute, judicial order, or regulation then You must: (a) comply with
+the terms of this License to the maximum extent possible; and (b)
+describe the limitations and the code they affect. Such description must
+be placed in a text file included with all distributions of the Covered
+Software under this License. Except to the extent prohibited by statute
+or regulation, such description must be sufficiently detailed for a
+recipient of ordinary skill to be able to understand it.
+
+5. Termination
+--------------
+
+5.1. The rights granted under this License will terminate automatically
+if You fail to comply with any of its terms. However, if You become
+compliant, then the rights granted under this License from a particular
+Contributor are reinstated (a) provisionally, unless and until such
+Contributor explicitly and finally terminates Your grants, and (b) on an
+ongoing basis, if such Contributor fails to notify You of the
+non-compliance by some reasonable means prior to 60 days after You have
+come back into compliance. Moreover, Your grants from a particular
+Contributor are reinstated on an ongoing basis if such Contributor
+notifies You of the non-compliance by some reasonable means, this is the
+first time You have received notice of non-compliance with this License
+from such Contributor, and You become compliant prior to 30 days after
+Your receipt of the notice.
+
+5.2. If You initiate litigation against any entity by asserting a patent
+infringement claim (excluding declaratory judgment actions,
+counter-claims, and cross-claims) alleging that a Contributor Version
+directly or indirectly infringes any patent, then the rights granted to
+You by any and all Contributors for the Covered Software under Section
+2.1 of this License shall terminate.
+
+5.3. In the event of termination under Sections 5.1 or 5.2 above, all
+end user license agreements (excluding distributors and resellers) which
+have been validly granted by You or Your distributors under this License
+prior to termination shall survive termination.
+
+************************************************************************
+* *
+* 6. Disclaimer of Warranty *
+* ------------------------- *
+* *
+* Covered Software is provided under this License on an "as is" *
+* basis, without warranty of any kind, either expressed, implied, or *
+* statutory, including, without limitation, warranties that the *
+* Covered Software is free of defects, merchantable, fit for a *
+* particular purpose or non-infringing. The entire risk as to the *
+* quality and performance of the Covered Software is with You. *
+* Should any Covered Software prove defective in any respect, You *
+* (not any Contributor) assume the cost of any necessary servicing, *
+* repair, or correction. This disclaimer of warranty constitutes an *
+* essential part of this License. No use of any Covered Software is *
+* authorized under this License except under this disclaimer. *
+* *
+************************************************************************
+
+************************************************************************
+* *
+* 7. Limitation of Liability *
+* -------------------------- *
+* *
+* Under no circumstances and under no legal theory, whether tort *
+* (including negligence), contract, or otherwise, shall any *
+* Contributor, or anyone who distributes Covered Software as *
+* permitted above, be liable to You for any direct, indirect, *
+* special, incidental, or consequential damages of any character *
+* including, without limitation, damages for lost profits, loss of *
+* goodwill, work stoppage, computer failure or malfunction, or any *
+* and all other commercial damages or losses, even if such party *
+* shall have been informed of the possibility of such damages. This *
+* limitation of liability shall not apply to liability for death or *
+* personal injury resulting from such party's negligence to the *
+* extent applicable law prohibits such limitation. Some *
+* jurisdictions do not allow the exclusion or limitation of *
+* incidental or consequential damages, so this exclusion and *
+* limitation may not apply to You. *
+* *
+************************************************************************
+
+8. Litigation
+-------------
+
+Any litigation relating to this License may be brought only in the
+courts of a jurisdiction where the defendant maintains its principal
+place of business and such litigation shall be governed by laws of that
+jurisdiction, without reference to its conflict-of-law provisions.
+Nothing in this Section shall prevent a party's ability to bring
+cross-claims or counter-claims.
+
+9. Miscellaneous
+----------------
+
+This License represents the complete agreement concerning the subject
+matter hereof. If any provision of this License is held to be
+unenforceable, such provision shall be reformed only to the extent
+necessary to make it enforceable. Any law or regulation which provides
+that the language of a contract shall be construed against the drafter
+shall not be used to construe this License against a Contributor.
+
+10. Versions of the License
+---------------------------
+
+10.1. New Versions
+
+Mozilla Foundation is the license steward. Except as provided in Section
+10.3, no one other than the license steward has the right to modify or
+publish new versions of this License. Each version will be given a
+distinguishing version number.
+
+10.2. Effect of New Versions
+
+You may distribute the Covered Software under the terms of the version
+of the License under which You originally received the Covered Software,
+or under the terms of any subsequent version published by the license
+steward.
+
+10.3. Modified Versions
+
+If you create software not governed by this License, and you want to
+create a new license for such software, you may create and use a
+modified version of this License if you rename the license and remove
+any references to the name of the license steward (except to note that
+such modified license differs from this License).
+
+10.4. Distributing Source Code Form that is Incompatible With Secondary
+Licenses
+
+If You choose to distribute Source Code Form that is Incompatible With
+Secondary Licenses under the terms of this version of the License, the
+notice described in Exhibit B of this License must be attached.
+
+Exhibit A - Source Code Form License Notice
+-------------------------------------------
+
+ This Source Code Form is subject to the terms of the Mozilla Public
+ License, v. 2.0. If a copy of the MPL was not distributed with this
+ file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+If it is not possible or desirable to put the notice in a particular
+file, then You may include the notice in a location (such as a LICENSE
+file in a relevant directory) where a recipient would be likely to look
+for such a notice.
+
+You may add additional accurate notices of copyright ownership.
+
+Exhibit B - "Incompatible With Secondary Licenses" Notice
+---------------------------------------------------------
+
+ This Source Code Form is "Incompatible With Secondary Licenses", as
+ defined by the Mozilla Public License, v. 2.0.
diff --git a/third_party/rust/ece/README.md b/third_party/rust/ece/README.md
new file mode 100644
index 0000000000..880aafa839
--- /dev/null
+++ b/third_party/rust/ece/README.md
@@ -0,0 +1,23 @@
+# rust-ece &emsp; [![Build Status]][circleci] [![Latest Version]][crates.io]
+
+[Build Status]: https://circleci.com/gh/mozilla/rust-ece.svg?style=svg
+[circleci]: https://circleci.com/gh/mozilla/rust-ece
+[Latest Version]: https://img.shields.io/crates/v/ece.svg
+[crates.io]: https://crates.io/crates/ece
+
+*This crate has not been security reviewed yet, use at your own risk ([tracking issue](https://github.com/mozilla/rust-ece/issues/18))*.
+
+[ece](https://crates.io/crates/ece) is a Rust implementation of the HTTP Encrypted Content-Encoding standard (RFC 8188). It is a port of the [ecec](https://github.com/web-push-libs/ecec) C library.
+This crate is destined to be used by higher-level Web Push libraries, both on the server and the client side.
+
+[Documentation](https://docs.rs/ece/)
+
+## Cryptographic backends
+
+This crate is designed to be used with different crypto backends. At the moment only [openssl](https://github.com/sfackler/rust-openssl) is supported.
+
+## Implemented schemes
+
+Currently, two HTTP ece schemes are available to consumers of the crate:
+- The newer [RFC8188](https://tools.ietf.org/html/rfc8188) `aes128gcm` standard.
+- The legacy [draft-03](https://tools.ietf.org/html/draft-ietf-httpbis-encryption-encoding-03) `aesgcm` scheme.
diff --git a/third_party/rust/ece/src/aes128gcm.rs b/third_party/rust/ece/src/aes128gcm.rs
new file mode 100644
index 0000000000..041159fd96
--- /dev/null
+++ b/third_party/rust/ece/src/aes128gcm.rs
@@ -0,0 +1,222 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::{
+ common::*,
+ crypto::{self, LocalKeyPair, RemotePublicKey},
+ error::*,
+};
+use byteorder::{BigEndian, ByteOrder};
+
+const ECE_AES128GCM_MIN_RS: u32 = 18;
+const ECE_AES128GCM_HEADER_LENGTH: usize = 21;
+// The max AES128GCM Key ID Length is 255 octets. We use far less of that because we use
+// the "key_id" to store the exchanged public key since we don't cache the key_ids.
+// Code fails if the key_id is not a public key length field.
+const ECE_AES128GCM_PAD_SIZE: usize = 1;
+
+const ECE_WEBPUSH_AES128GCM_IKM_INFO_PREFIX: &str = "WebPush: info\0";
+const ECE_WEBPUSH_AES128GCM_IKM_INFO_LENGTH: usize = 144; // 14 (prefix len) + 65 (pub key len) * 2;
+
+const ECE_WEBPUSH_IKM_LENGTH: usize = 32;
+const ECE_AES128GCM_KEY_INFO: &str = "Content-Encoding: aes128gcm\0";
+const ECE_AES128GCM_NONCE_INFO: &str = "Content-Encoding: nonce\0";
+
+// TODO: When done, remove the aes128gcm prefixes and the EC_ ones.
+// As for now it makes it easier to Ctrl + F into ecec :)
+
+pub struct Aes128GcmEceWebPush;
+impl Aes128GcmEceWebPush {
+ /// Encrypts a Web Push message using the "aes128gcm" scheme. This function
+ /// automatically generates an ephemeral ECDH key pair.
+ pub fn encrypt(
+ remote_pub_key: &dyn RemotePublicKey,
+ auth_secret: &[u8],
+ plaintext: &[u8],
+ params: WebPushParams,
+ ) -> Result<Vec<u8>> {
+ let cryptographer = crypto::holder::get_cryptographer();
+ let local_prv_key = cryptographer.generate_ephemeral_keypair()?;
+ Self::encrypt_with_keys(
+ &*local_prv_key,
+ remote_pub_key,
+ auth_secret,
+ plaintext,
+ params,
+ )
+ }
+
+ /// Encrypts a Web Push message using the "aes128gcm" scheme, with an explicit
+ /// sender key. The sender key can be reused.
+ pub fn encrypt_with_keys(
+ local_prv_key: &dyn LocalKeyPair,
+ remote_pub_key: &dyn RemotePublicKey,
+ auth_secret: &[u8],
+ plaintext: &[u8],
+ params: WebPushParams,
+ ) -> Result<Vec<u8>> {
+ let cryptographer = crypto::holder::get_cryptographer();
+ let salt = match params.salt {
+ Some(salt) => salt,
+ None => {
+ let mut salt = [0u8; ECE_SALT_LENGTH];
+ cryptographer.random_bytes(&mut salt)?;
+ salt.to_vec()
+ }
+ };
+ let mut header = vec![0u8; ECE_AES128GCM_HEADER_LENGTH + ECE_WEBPUSH_PUBLIC_KEY_LENGTH];
+ header[0..ECE_SALT_LENGTH].copy_from_slice(&salt);
+ BigEndian::write_u32(&mut header[ECE_SALT_LENGTH..], params.rs);
+ header[ECE_SALT_LENGTH + 4] = ECE_WEBPUSH_PUBLIC_KEY_LENGTH as u8;
+ let raw_local_pub_key = local_prv_key.pub_as_raw()?;
+ header[ECE_AES128GCM_HEADER_LENGTH
+ ..ECE_AES128GCM_HEADER_LENGTH + ECE_WEBPUSH_PUBLIC_KEY_LENGTH]
+ .copy_from_slice(&raw_local_pub_key);
+ let mut ciphertext = Self::common_encrypt(
+ local_prv_key,
+ remote_pub_key,
+ auth_secret,
+ &salt,
+ params.rs,
+ params.pad_length,
+ plaintext,
+ )?;
+ // TODO: Not efficient and probably allocates more,
+ // we should allocate the buffer upfront if possible.
+ header.append(&mut ciphertext);
+ Ok(header)
+ }
+
+ /// Decrypts a Web Push message encrypted using the "aes128gcm" scheme.
+ pub fn decrypt(
+ local_prv_key: &dyn LocalKeyPair,
+ auth_secret: &[u8],
+ payload: &[u8],
+ ) -> Result<Vec<u8>> {
+ if payload.len() < ECE_AES128GCM_HEADER_LENGTH {
+ return Err(Error::HeaderTooShort);
+ }
+
+ let key_id_len = payload[ECE_SALT_LENGTH + 4] as usize;
+ if payload.len() < ECE_AES128GCM_HEADER_LENGTH + key_id_len {
+ return Err(Error::HeaderTooShort);
+ }
+
+ let rs = BigEndian::read_u32(&payload[ECE_SALT_LENGTH..]);
+ if rs < ECE_AES128GCM_MIN_RS {
+ return Err(Error::InvalidRecordSize);
+ }
+
+ let salt = &payload[0..ECE_SALT_LENGTH];
+ if key_id_len != ECE_WEBPUSH_PUBLIC_KEY_LENGTH {
+ return Err(Error::InvalidKeyLength);
+ }
+ let key_id_pos = ECE_AES128GCM_HEADER_LENGTH;
+ let key_id = &payload[key_id_pos..key_id_pos + key_id_len];
+
+ let ciphertext_start = ECE_AES128GCM_HEADER_LENGTH + key_id_len;
+ if payload.len() == ciphertext_start {
+ return Err(Error::ZeroCiphertext);
+ }
+ let ciphertext = &payload[ciphertext_start..];
+ let cryptographer = crypto::holder::get_cryptographer();
+ let key = cryptographer.import_public_key(key_id)?;
+ Self::common_decrypt(local_prv_key, &*key, auth_secret, salt, rs, ciphertext)
+ }
+}
+
+impl EceWebPush for Aes128GcmEceWebPush {
+ /// Always returns false because "aes128gcm" uses
+ /// a padding scheme that doesn't need a trailer.
+ fn needs_trailer(_: u32, _: usize) -> bool {
+ false
+ }
+
+ fn pad_size() -> usize {
+ ECE_AES128GCM_PAD_SIZE
+ }
+
+ fn min_block_pad_length(pad_len: usize, max_block_len: usize) -> usize {
+ ece_min_block_pad_length(pad_len, max_block_len)
+ }
+
+ fn pad(plaintext: &[u8], block_pad_len: usize, last_record: bool) -> Result<Vec<u8>> {
+ let mut block = Vec::with_capacity(plaintext.len() + 1 /* delimiter */ + block_pad_len);
+ block.extend_from_slice(plaintext);
+ block.push(if last_record { 2 } else { 1 });
+ let padding = vec![0u8; block_pad_len];
+ block.extend(padding);
+ Ok(block)
+ }
+
+ fn unpad(block: &[u8], last_record: bool) -> Result<&[u8]> {
+ let pos = match block.iter().rposition(|&b| b != 0) {
+ Some(pos) => pos,
+ None => return Err(Error::ZeroCiphertext),
+ };
+ let expected_delim = if last_record { 2 } else { 1 };
+ if block[pos] != expected_delim {
+ return Err(Error::DecryptPadding);
+ }
+ Ok(&block[..pos])
+ }
+
+ /// Derives the "aes128gcm" decryption key and nonce given the receiver private
+ /// key, sender public key, authentication secret, and sender salt.
+ fn derive_key_and_nonce(
+ ece_mode: EceMode,
+ local_prv_key: &dyn LocalKeyPair,
+ remote_pub_key: &dyn RemotePublicKey,
+ auth_secret: &[u8],
+ salt: &[u8],
+ ) -> Result<KeyAndNonce> {
+ let cryptographer = crypto::holder::get_cryptographer();
+ let shared_secret = cryptographer.compute_ecdh_secret(remote_pub_key, local_prv_key)?;
+ let raw_remote_pub_key = remote_pub_key.as_raw()?;
+ let raw_local_pub_key = local_prv_key.pub_as_raw()?;
+
+ // The new "aes128gcm" scheme includes the sender and receiver public keys in
+ // the info string when deriving the Web Push IKM.
+ let ikm_info = match ece_mode {
+ EceMode::ENCRYPT => generate_info(&raw_remote_pub_key, &raw_local_pub_key),
+ EceMode::DECRYPT => generate_info(&raw_local_pub_key, &raw_remote_pub_key),
+ }?;
+ let cryptographer = crypto::holder::get_cryptographer();
+ let ikm = cryptographer.hkdf_sha256(
+ auth_secret,
+ &shared_secret,
+ &ikm_info,
+ ECE_WEBPUSH_IKM_LENGTH,
+ )?;
+ let key = cryptographer.hkdf_sha256(
+ salt,
+ &ikm,
+ ECE_AES128GCM_KEY_INFO.as_bytes(),
+ ECE_AES_KEY_LENGTH,
+ )?;
+ let nonce = cryptographer.hkdf_sha256(
+ salt,
+ &ikm,
+ ECE_AES128GCM_NONCE_INFO.as_bytes(),
+ ECE_NONCE_LENGTH,
+ )?;
+ Ok((key, nonce))
+ }
+}
+
+// The "aes128gcm" IKM info string is "WebPush: info\0", followed by the
+// receiver and sender public keys.
+fn generate_info(
+ raw_recv_pub_key: &[u8],
+ raw_sender_pub_key: &[u8],
+) -> Result<[u8; ECE_WEBPUSH_AES128GCM_IKM_INFO_LENGTH]> {
+ let mut info = [0u8; ECE_WEBPUSH_AES128GCM_IKM_INFO_LENGTH];
+ let prefix = ECE_WEBPUSH_AES128GCM_IKM_INFO_PREFIX.as_bytes();
+ let mut offset = prefix.len();
+ info[0..offset].copy_from_slice(prefix);
+ info[offset..offset + ECE_WEBPUSH_PUBLIC_KEY_LENGTH].copy_from_slice(raw_recv_pub_key);
+ offset += ECE_WEBPUSH_PUBLIC_KEY_LENGTH;
+ info[offset..].copy_from_slice(raw_sender_pub_key);
+ Ok(info)
+}
diff --git a/third_party/rust/ece/src/aesgcm.rs b/third_party/rust/ece/src/aesgcm.rs
new file mode 100644
index 0000000000..d994c114a1
--- /dev/null
+++ b/third_party/rust/ece/src/aesgcm.rs
@@ -0,0 +1,252 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This supports the now obsolete HTTP-ECE Draft 02 "aesgcm" content
+ * type. There are a number of providers that still use this format,
+ * and there's no real mechanism to return the client supported crypto
+ * versions.
+ *
+ * */
+
+use crate::{
+ common::*,
+ crypto::{self, LocalKeyPair, RemotePublicKey},
+ error::*,
+};
+use std::collections::HashMap;
+
+const ECE_AESGCM_PAD_SIZE: usize = 2;
+
+const ECE_WEBPUSH_AESGCM_KEYPAIR_LENGTH: usize = 134; // (2 + Raw Key Length) * 2
+const ECE_WEBPUSH_AESGCM_AUTHINFO: &str = "Content-Encoding: auth\0";
+
+// a DER prefixed key is "\04" + ECE_WEBPUSH_RAW_KEY_LENGTH
+const ECE_WEBPUSH_RAW_KEY_LENGTH: usize = 65;
+const ECE_WEBPUSH_IKM_LENGTH: usize = 32;
+
+pub struct AesGcmEncryptedBlock {
+ pub dh: Vec<u8>,
+ pub salt: Vec<u8>,
+ pub rs: u32,
+ pub ciphertext: Vec<u8>,
+}
+
+impl AesGcmEncryptedBlock {
+ pub fn aesgcm_rs(rs: u32) -> u32 {
+ if rs > u32::max_value() - ECE_TAG_LENGTH as u32 {
+ return 0;
+ }
+ rs + ECE_TAG_LENGTH as u32
+ }
+
+ /// Create a new block from the various header strings and body content.
+ pub fn new(
+ dh: &[u8],
+ salt: &[u8],
+ rs: u32,
+ ciphertext: Vec<u8>,
+ ) -> Result<AesGcmEncryptedBlock> {
+ Ok(AesGcmEncryptedBlock {
+ dh: dh.to_owned(),
+ salt: salt.to_owned(),
+ rs: Self::aesgcm_rs(rs),
+ ciphertext,
+ })
+ }
+
+ /// Return the headers Hash, NOTE you may need to merge Crypto-Key if there's
+ /// already a VAPID element present.
+ pub fn headers(self) -> HashMap<String, String> {
+ let mut result: HashMap<String, String> = HashMap::new();
+ let mut rs = "".to_owned();
+ result.insert(
+ "Crypto-Key".to_owned(),
+ format!(
+ "dh={}",
+ base64::encode_config(&self.dh, base64::URL_SAFE_NO_PAD)
+ ),
+ );
+ if self.rs > 0 {
+ rs = format!(";rs={}", self.rs);
+ }
+ result.insert(
+ "Encryption".to_owned(),
+ format!(
+ "salt={}{}",
+ base64::encode_config(&self.salt, base64::URL_SAFE_NO_PAD),
+ rs
+ ),
+ );
+ result
+ }
+
+ /// Encode the body as a String.
+ /// If you need the bytes, probably just call .ciphertext directly
+ pub fn body(self) -> String {
+ base64::encode_config(&self.ciphertext, base64::URL_SAFE_NO_PAD)
+ }
+}
+
+pub struct AesGcmEceWebPush;
+impl AesGcmEceWebPush {
+ /// Encrypts a Web Push message using the "aesgcm" scheme. This function
+ /// automatically generates an ephemeral ECDH key pair.
+ pub fn encrypt(
+ remote_pub_key: &dyn RemotePublicKey,
+ auth_secret: &[u8],
+ plaintext: &[u8],
+ params: WebPushParams,
+ ) -> Result<AesGcmEncryptedBlock> {
+ let cryptographer = crypto::holder::get_cryptographer();
+ let local_prv_key = cryptographer.generate_ephemeral_keypair()?;
+ Self::encrypt_with_keys(
+ &*local_prv_key,
+ remote_pub_key,
+ auth_secret,
+ plaintext,
+ params,
+ )
+ }
+
+ /// Encrypts a Web Push message using the "aesgcm" scheme, with an explicit
+ /// sender key. The sender key can be reused.
+ pub fn encrypt_with_keys(
+ local_prv_key: &dyn LocalKeyPair,
+ remote_pub_key: &dyn RemotePublicKey,
+ auth_secret: &[u8],
+ plaintext: &[u8],
+ params: WebPushParams,
+ ) -> Result<AesGcmEncryptedBlock> {
+ let cryptographer = crypto::holder::get_cryptographer();
+ let salt = {
+ let mut salt = [0u8; ECE_SALT_LENGTH];
+ cryptographer.random_bytes(&mut salt)?;
+ salt.to_vec()
+ };
+ let raw_local_pub_key = local_prv_key.pub_as_raw()?;
+ let ciphertext = Self::common_encrypt(
+ local_prv_key,
+ remote_pub_key,
+ auth_secret,
+ &salt,
+ params.rs,
+ params.pad_length,
+ plaintext,
+ )?;
+ Ok(AesGcmEncryptedBlock {
+ salt,
+ dh: raw_local_pub_key,
+ rs: params.rs,
+ ciphertext,
+ })
+ }
+
+ /// Decrypts a Web Push message encrypted using the "aesgcm" scheme.
+ pub fn decrypt(
+ local_prv_key: &dyn LocalKeyPair,
+ auth_secret: &[u8],
+ block: &AesGcmEncryptedBlock,
+ ) -> Result<Vec<u8>> {
+ let cryptographer = crypto::holder::get_cryptographer();
+ let sender_key = cryptographer.import_public_key(&block.dh)?;
+ Self::common_decrypt(
+ local_prv_key,
+ &*sender_key,
+ auth_secret,
+ &block.salt,
+ block.rs,
+ &block.ciphertext,
+ )
+ }
+}
+
+impl EceWebPush for AesGcmEceWebPush {
+ fn needs_trailer(rs: u32, ciphertextlen: usize) -> bool {
+ ciphertextlen as u32 % rs == 0
+ }
+
+ fn pad_size() -> usize {
+ ECE_AESGCM_PAD_SIZE
+ }
+
+ fn min_block_pad_length(pad_len: usize, max_block_len: usize) -> usize {
+ ece_min_block_pad_length(pad_len, max_block_len)
+ }
+
+ fn pad(plaintext: &[u8], _: usize, _: bool) -> Result<Vec<u8>> {
+ let plen = plaintext.len();
+ let mut block = vec![0; plen + ECE_AESGCM_PAD_SIZE];
+ block[2..].copy_from_slice(plaintext);
+ Ok(block)
+ }
+
+ fn unpad(block: &[u8], _: bool) -> Result<&[u8]> {
+ let padding_size = (((block[0] as u16) << 8) | block[1] as u16) as usize;
+ if padding_size >= block.len() - 2 {
+ return Err(Error::DecryptPadding);
+ }
+ if block[2..(2 + padding_size)].iter().any(|b| *b != 0u8) {
+ return Err(Error::DecryptPadding);
+ }
+ Ok(&block[(2 + padding_size)..])
+ }
+
+ /// Derives the "aesgcm" decryption keyn and nonce given the receiver private
+ /// key, sender public key, authentication secret, and sender salt.
+ fn derive_key_and_nonce(
+ ece_mode: EceMode,
+ local_prv_key: &dyn LocalKeyPair,
+ remote_pub_key: &dyn RemotePublicKey,
+ auth_secret: &[u8],
+ salt: &[u8],
+ ) -> Result<KeyAndNonce> {
+ let cryptographer = crypto::holder::get_cryptographer();
+ let shared_secret = cryptographer.compute_ecdh_secret(remote_pub_key, local_prv_key)?;
+ let raw_remote_pub_key = remote_pub_key.as_raw()?;
+ let raw_local_pub_key = local_prv_key.pub_as_raw()?;
+
+ let keypair = match ece_mode {
+ EceMode::ENCRYPT => encode_keys(&raw_remote_pub_key, &raw_local_pub_key),
+ EceMode::DECRYPT => encode_keys(&raw_local_pub_key, &raw_remote_pub_key),
+ }?;
+ let keyinfo = generate_info("aesgcm", &keypair)?;
+ let nonceinfo = generate_info("nonce", &keypair)?;
+ let ikm = cryptographer.hkdf_sha256(
+ auth_secret,
+ &shared_secret,
+ &ECE_WEBPUSH_AESGCM_AUTHINFO.as_bytes(),
+ ECE_WEBPUSH_IKM_LENGTH,
+ )?;
+ let key = cryptographer.hkdf_sha256(salt, &ikm, &keyinfo, ECE_AES_KEY_LENGTH)?;
+ let nonce = cryptographer.hkdf_sha256(salt, &ikm, &nonceinfo, ECE_NONCE_LENGTH)?;
+ Ok((key, nonce))
+ }
+}
+
+fn encode_keys(raw_key1: &[u8], raw_key2: &[u8]) -> Result<Vec<u8>> {
+ let mut combined = vec![0u8; ECE_WEBPUSH_AESGCM_KEYPAIR_LENGTH];
+
+ if raw_key1.len() > ECE_WEBPUSH_RAW_KEY_LENGTH || raw_key2.len() > ECE_WEBPUSH_RAW_KEY_LENGTH {
+ return Err(Error::InvalidKeyLength);
+ }
+ // length prefix each key
+ combined[0] = 0;
+ combined[1] = 65;
+ combined[2..67].copy_from_slice(raw_key1);
+ combined[67] = 0;
+ combined[68] = 65;
+ combined[69..].copy_from_slice(raw_key2);
+ Ok(combined)
+}
+
+// The "aesgcm" IKM info string is "WebPush: info", followed by the
+// receiver and sender public keys prefixed by their lengths.
+fn generate_info(encoding: &str, keypair: &[u8]) -> Result<Vec<u8>> {
+ let info_str = format!("Content-Encoding: {}\0P-256\0", encoding);
+ let offset = info_str.len();
+ let mut info = vec![0u8; offset + keypair.len()];
+ info[0..offset].copy_from_slice(info_str.as_bytes());
+ info[offset..offset + ECE_WEBPUSH_AESGCM_KEYPAIR_LENGTH].copy_from_slice(keypair);
+ Ok(info)
+}
diff --git a/third_party/rust/ece/src/common.rs b/third_party/rust/ece/src/common.rs
new file mode 100644
index 0000000000..984e25430f
--- /dev/null
+++ b/third_party/rust/ece/src/common.rs
@@ -0,0 +1,251 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::{
+ crypto::{self, LocalKeyPair, RemotePublicKey},
+ error::*,
+};
+use byteorder::{BigEndian, ByteOrder};
+use std::cmp::min;
+
+// From keys.h:
+pub const ECE_AES_KEY_LENGTH: usize = 16;
+pub const ECE_NONCE_LENGTH: usize = 12;
+
+// From ece.h:
+pub const ECE_SALT_LENGTH: usize = 16;
+pub const ECE_TAG_LENGTH: usize = 16;
+//const ECE_WEBPUSH_PRIVATE_KEY_LENGTH: usize = 32;
+pub const ECE_WEBPUSH_PUBLIC_KEY_LENGTH: usize = 65;
+pub const ECE_WEBPUSH_AUTH_SECRET_LENGTH: usize = 16;
+const ECE_WEBPUSH_DEFAULT_RS: u32 = 4096;
+
+// TODO: Make it nicer to use with a builder pattern.
+pub struct WebPushParams {
+ pub rs: u32,
+ pub pad_length: usize,
+ pub salt: Option<Vec<u8>>,
+}
+
+impl WebPushParams {
+ /// Random salt, record size = 4096 and padding length = 0.
+ pub fn default() -> Self {
+ Self {
+ rs: ECE_WEBPUSH_DEFAULT_RS,
+ pad_length: 2,
+ salt: None,
+ }
+ }
+
+ /// Never use the same salt twice as it will derive the same content encryption
+ /// key for multiple messages if the same sender private key is used!
+ pub fn new(rs: u32, pad_length: usize, salt: Vec<u8>) -> Self {
+ Self {
+ rs,
+ pad_length,
+ salt: Some(salt),
+ }
+ }
+}
+
+pub enum EceMode {
+ ENCRYPT,
+ DECRYPT,
+}
+
+pub type KeyAndNonce = (Vec<u8>, Vec<u8>);
+
+pub trait EceWebPush {
+ fn common_encrypt(
+ local_prv_key: &dyn LocalKeyPair,
+ remote_pub_key: &dyn RemotePublicKey,
+ auth_secret: &[u8],
+ salt: &[u8],
+ rs: u32,
+ pad_len: usize,
+ plaintext: &[u8],
+ ) -> Result<Vec<u8>> {
+ if auth_secret.len() != ECE_WEBPUSH_AUTH_SECRET_LENGTH {
+ return Err(Error::InvalidAuthSecret);
+ }
+ if salt.len() != ECE_SALT_LENGTH {
+ return Err(Error::InvalidSalt);
+ }
+ if plaintext.is_empty() {
+ return Err(Error::ZeroPlaintext);
+ }
+ let (key, nonce) = Self::derive_key_and_nonce(
+ EceMode::ENCRYPT,
+ local_prv_key,
+ remote_pub_key,
+ auth_secret,
+ salt,
+ )?;
+ let overhead = (Self::pad_size() + ECE_TAG_LENGTH) as u32;
+ // The maximum amount of plaintext and padding that will fit into a full
+ // block. The last block can be smaller.
+ assert!(rs > overhead);
+ let max_block_len = (rs - overhead) as usize;
+
+ // TODO: We should at least try to guess the capacity beforehand by
+ // re-implementing ece_ciphertext_max_length.
+ let mut ciphertext = Vec::with_capacity(plaintext.len());
+
+ // The offset at which to start reading the plaintext.
+ let mut plaintext_start = 0;
+ let mut pad_len = pad_len;
+ let mut last_record = false;
+ let mut counter = 0;
+ while !last_record {
+ let block_pad_len = Self::min_block_pad_length(pad_len, max_block_len);
+ assert!(block_pad_len <= pad_len);
+ pad_len -= block_pad_len;
+
+ // Fill the rest of the block with plaintext.
+ assert!(block_pad_len <= max_block_len);
+ let max_block_plaintext_len = max_block_len - block_pad_len;
+ let plaintext_end = min(plaintext_start + max_block_plaintext_len, plaintext.len());
+
+ // The length of the plaintext.
+ assert!(plaintext_end >= plaintext_start);
+ let block_plaintext_len = plaintext_end - plaintext_start;
+
+ // The length of the plaintext and padding. This should never overflow
+ // because `max_block_plaintext_len` accounts for `block_pad_len`.
+ assert!(block_plaintext_len <= max_block_plaintext_len);
+ let block_len = block_plaintext_len + block_pad_len;
+
+ // The length of the full encrypted record, including the plaintext,
+ // padding, padding delimiter, and auth tag. This should never overflow
+ // because `max_block_len` accounts for `overhead`.
+ assert!(block_len <= max_block_len);
+ let record_len = block_len + overhead as usize;
+
+ let plaintext_exhausted = plaintext_end >= plaintext.len();
+ if pad_len == 0
+ && plaintext_exhausted
+ && !Self::needs_trailer(rs, ciphertext.len() + record_len)
+ {
+ // We've reached the last record when the padding and plaintext are
+ // exhausted, and we don't need to write an empty trailing record.
+ last_record = true;
+ }
+
+ if !last_record && block_len < max_block_len {
+ // We have padding left, but not enough plaintext to form a full record.
+ // Writing trailing padding-only records will still leak size information,
+ // so we force the caller to pick a smaller padding length.
+ return Err(Error::EncryptPadding);
+ }
+
+ let iv = generate_iv(&nonce, counter);
+ let block = Self::pad(
+ &plaintext[plaintext_start..plaintext_end],
+ block_pad_len,
+ last_record,
+ )?;
+ let cryptographer = crypto::holder::get_cryptographer();
+ let mut record = cryptographer.aes_gcm_128_encrypt(&key, &iv, &block)?;
+ ciphertext.append(&mut record);
+ plaintext_start = plaintext_end;
+ counter += 1;
+ }
+ Ok(ciphertext)
+ }
+
+ fn common_decrypt(
+ local_prv_key: &dyn LocalKeyPair,
+ remote_pub_key: &dyn RemotePublicKey,
+ auth_secret: &[u8],
+ salt: &[u8],
+ rs: u32,
+ ciphertext: &[u8],
+ ) -> Result<Vec<u8>> {
+ if auth_secret.len() != ECE_WEBPUSH_AUTH_SECRET_LENGTH {
+ return Err(Error::InvalidAuthSecret);
+ }
+ if salt.len() != ECE_SALT_LENGTH {
+ return Err(Error::InvalidSalt);
+ }
+ if ciphertext.is_empty() {
+ return Err(Error::ZeroCiphertext);
+ }
+ if Self::needs_trailer(rs, ciphertext.len()) {
+ // If we're missing a trailing block, the ciphertext is truncated.
+ return Err(Error::DecryptTruncated);
+ }
+ let (key, nonce) = Self::derive_key_and_nonce(
+ EceMode::DECRYPT,
+ local_prv_key,
+ remote_pub_key,
+ auth_secret,
+ salt,
+ )?;
+ let chunks = ciphertext.chunks(rs as usize);
+ let records_count = chunks.len();
+ let items = chunks
+ .enumerate()
+ .map(|(count, record)| {
+ if record.len() <= ECE_TAG_LENGTH {
+ return Err(Error::BlockTooShort);
+ }
+ let iv = generate_iv(&nonce, count);
+ assert!(record.len() > ECE_TAG_LENGTH);
+ let cryptographer = crypto::holder::get_cryptographer();
+ let plaintext = cryptographer.aes_gcm_128_decrypt(&key, &iv, record)?;
+ let last_record = count == records_count - 1;
+ if plaintext.len() < Self::pad_size() {
+ return Err(Error::BlockTooShort);
+ }
+ Ok(Self::unpad(&plaintext, last_record)?.to_vec())
+ })
+ .collect::<Result<Vec<Vec<u8>>>>()?;
+ // TODO: There was a way to do it without this last line.
+ Ok(items.into_iter().flatten().collect::<Vec<u8>>())
+ }
+
+ fn pad_size() -> usize;
+ /// Calculates the padding so that the block contains at least one plaintext
+ /// byte.
+ fn min_block_pad_length(pad_len: usize, max_block_len: usize) -> usize;
+ fn needs_trailer(rs: u32, ciphertext_len: usize) -> bool;
+ fn pad(plaintext: &[u8], block_pad_len: usize, last_record: bool) -> Result<Vec<u8>>;
+ fn unpad(block: &[u8], last_record: bool) -> Result<&[u8]>;
+ fn derive_key_and_nonce(
+ ece_mode: EceMode,
+ local_prv_key: &dyn LocalKeyPair,
+ remote_pub_key: &dyn RemotePublicKey,
+ auth_secret: &[u8],
+ salt: &[u8],
+ ) -> Result<KeyAndNonce>;
+}
+
+// Calculates the padding so that the block contains at least one plaintext
+// byte.
+pub fn ece_min_block_pad_length(pad_len: usize, max_block_len: usize) -> usize {
+ assert!(max_block_len >= 1);
+ let mut block_pad_len = max_block_len - 1;
+ if pad_len > 0 && block_pad_len == 0 {
+ // If `max_block_len` is 1, we can only include 1 byte of data, so write
+ // the padding first.
+ block_pad_len += 1;
+ }
+ if block_pad_len > pad_len {
+ pad_len
+ } else {
+ block_pad_len
+ }
+}
+
+/// Generates a 96-bit IV, 48 bits of which are populated.
+fn generate_iv(nonce: &[u8], counter: usize) -> [u8; ECE_NONCE_LENGTH] {
+ let mut iv = [0u8; ECE_NONCE_LENGTH];
+ let offset = ECE_NONCE_LENGTH - 8;
+ iv[0..offset].copy_from_slice(&nonce[0..offset]);
+ // Combine the remaining unsigned 64-bit integer with the record sequence
+ // number using XOR. See the "nonce derivation" section of the draft.
+ let mask = BigEndian::read_u64(&nonce[offset..]);
+ BigEndian::write_u64(&mut iv[offset..], mask ^ (counter as u64));
+ iv
+}
diff --git a/third_party/rust/ece/src/crypto/holder.rs b/third_party/rust/ece/src/crypto/holder.rs
new file mode 100644
index 0000000000..fb78a73f52
--- /dev/null
+++ b/third_party/rust/ece/src/crypto/holder.rs
@@ -0,0 +1,50 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use super::Cryptographer;
+use once_cell::sync::OnceCell;
+
+static CRYPTOGRAPHER: OnceCell<&'static dyn Cryptographer> = OnceCell::new();
+
+#[derive(Debug, thiserror::Error)]
+#[error("Cryptographer already initialized")]
+pub struct SetCryptographerError(());
+
+/// Sets the global object that will be used for cryptographic operations.
+///
+/// This is a convenience wrapper over [`set_cryptographer`],
+/// but takes a `Box<dyn Cryptographer>` instead.
+#[cfg(not(feature = "backend-openssl"))]
+pub fn set_boxed_cryptographer(c: Box<dyn Cryptographer>) -> Result<(), SetCryptographerError> {
+ // Just leak the Box. It wouldn't be freed as a `static` anyway, and we
+ // never allow this to be re-assigned (so it's not a meaningful memory leak).
+ set_cryptographer(Box::leak(c))
+}
+
+/// Sets the global object that will be used for cryptographic operations.
+///
+/// This function may only be called once in the lifetime of a program.
+///
+/// Any calls into this crate that perform cryptography prior to calling this
+/// function will panic.
+pub fn set_cryptographer(c: &'static dyn Cryptographer) -> Result<(), SetCryptographerError> {
+ CRYPTOGRAPHER.set(c).map_err(|_| SetCryptographerError(()))
+}
+
+pub(crate) fn get_cryptographer() -> &'static dyn Cryptographer {
+ autoinit_crypto();
+ *CRYPTOGRAPHER
+ .get()
+ .expect("`rust-ece` cryptographer not initialized!")
+}
+
+#[cfg(feature = "backend-openssl")]
+#[inline]
+fn autoinit_crypto() {
+ let _ = set_cryptographer(&super::openssl::OpensslCryptographer);
+}
+
+#[cfg(not(feature = "backend-openssl"))]
+#[inline]
+fn autoinit_crypto() {}
diff --git a/third_party/rust/ece/src/crypto/mod.rs b/third_party/rust/ece/src/crypto/mod.rs
new file mode 100644
index 0000000000..1f3648179b
--- /dev/null
+++ b/third_party/rust/ece/src/crypto/mod.rs
@@ -0,0 +1,105 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::error::*;
+use std::any::Any;
+
+pub(crate) mod holder;
+#[cfg(feature = "backend-openssl")]
+mod openssl;
+
+#[cfg(not(feature = "backend-openssl"))]
+pub use holder::{set_boxed_cryptographer, set_cryptographer};
+
+pub trait RemotePublicKey: Send + Sync + 'static {
+ /// Export the key component in the
+ /// binary uncompressed point representation.
+ fn as_raw(&self) -> Result<Vec<u8>>;
+ /// For downcasting purposes.
+ fn as_any(&self) -> &dyn Any;
+}
+
+pub trait LocalKeyPair: Send + Sync + 'static {
+ /// Export the public key component in the
+ /// binary uncompressed point representation.
+ fn pub_as_raw(&self) -> Result<Vec<u8>>;
+ /// Export the raw components of the keypair.
+ fn raw_components(&self) -> Result<EcKeyComponents>;
+ /// For downcasting purposes.
+ fn as_any(&self) -> &dyn Any;
+}
+
+#[derive(Clone, Debug, PartialEq)]
+#[cfg_attr(
+ feature = "serializable-keys",
+ derive(serde::Serialize, serde::Deserialize)
+)]
+pub enum EcCurve {
+ P256,
+}
+
+impl Default for EcCurve {
+ fn default() -> Self {
+ EcCurve::P256
+ }
+}
+
+#[derive(Clone, Debug, PartialEq)]
+#[cfg_attr(
+ feature = "serializable-keys",
+ derive(serde::Serialize, serde::Deserialize)
+)]
+pub struct EcKeyComponents {
+ // The curve is only kept in case the ECE standard changes in the future.
+ curve: EcCurve,
+ // The `d` value of the EC Key.
+ private_key: Vec<u8>,
+ // The uncompressed x,y-representation of the public component of the EC Key.
+ public_key: Vec<u8>,
+}
+
+impl EcKeyComponents {
+ pub fn new<T: Into<Vec<u8>>>(private_key: T, public_key: T) -> Self {
+ EcKeyComponents {
+ private_key: private_key.into(),
+ public_key: public_key.into(),
+ curve: Default::default(),
+ }
+ }
+ pub fn curve(&self) -> &EcCurve {
+ &self.curve
+ }
+ /// The `d` value of the EC Key.
+ pub fn private_key(&self) -> &[u8] {
+ &self.private_key
+ }
+ /// The uncompressed x,y-representation of the public component of the EC Key.
+ pub fn public_key(&self) -> &[u8] {
+ &self.public_key
+ }
+}
+
+pub trait Cryptographer: Send + Sync + 'static {
+ /// Generate a random ephemeral local key pair.
+ fn generate_ephemeral_keypair(&self) -> Result<Box<dyn LocalKeyPair>>;
+ /// Import a local keypair from its raw components.
+ fn import_key_pair(&self, components: &EcKeyComponents) -> Result<Box<dyn LocalKeyPair>>;
+ /// Import the public key component in the binary uncompressed point representation.
+ fn import_public_key(&self, raw: &[u8]) -> Result<Box<dyn RemotePublicKey>>;
+ fn compute_ecdh_secret(
+ &self,
+ remote: &dyn RemotePublicKey,
+ local: &dyn LocalKeyPair,
+ ) -> Result<Vec<u8>>;
+ fn hkdf_sha256(&self, salt: &[u8], secret: &[u8], info: &[u8], len: usize) -> Result<Vec<u8>>;
+ /// Should return [ciphertext, auth_tag].
+ fn aes_gcm_128_encrypt(&self, key: &[u8], iv: &[u8], data: &[u8]) -> Result<Vec<u8>>;
+ fn aes_gcm_128_decrypt(
+ &self,
+ key: &[u8],
+ iv: &[u8],
+ ciphertext_and_tag: &[u8],
+ ) -> Result<Vec<u8>>;
+ fn random_bytes(&self, dest: &mut [u8]) -> Result<()>;
+}
diff --git a/third_party/rust/ece/src/crypto/openssl.rs b/third_party/rust/ece/src/crypto/openssl.rs
new file mode 100644
index 0000000000..039f94b547
--- /dev/null
+++ b/third_party/rust/ece/src/crypto/openssl.rs
@@ -0,0 +1,203 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::{
+ crypto::{Cryptographer, EcKeyComponents, LocalKeyPair, RemotePublicKey},
+ error::*,
+};
+use hkdf::Hkdf;
+use lazy_static::lazy_static;
+use openssl::{
+ bn::{BigNum, BigNumContext},
+ derive::Deriver,
+ ec::{EcGroup, EcKey, EcPoint, PointConversionForm},
+ nid::Nid,
+ pkey::{PKey, Private, Public},
+ rand::rand_bytes,
+ symm::{Cipher, Crypter, Mode},
+};
+use sha2::Sha256;
+use std::{any::Any, fmt};
+
+lazy_static! {
+ static ref GROUP_P256: EcGroup = EcGroup::from_curve_name(Nid::X9_62_PRIME256V1).unwrap();
+}
+const AES_GCM_TAG_LENGTH: usize = 16;
+
+#[derive(Clone, Debug)]
+pub struct OpenSSLRemotePublicKey {
+ raw_pub_key: Vec<u8>,
+}
+
+impl OpenSSLRemotePublicKey {
+ fn from_raw(raw: &[u8]) -> Result<Self> {
+ Ok(OpenSSLRemotePublicKey {
+ raw_pub_key: raw.to_vec(),
+ })
+ }
+
+ fn to_pkey(&self) -> Result<PKey<Public>> {
+ let mut bn_ctx = BigNumContext::new()?;
+ let point = EcPoint::from_bytes(&GROUP_P256, &self.raw_pub_key, &mut bn_ctx)?;
+ let ec = EcKey::from_public_key(&GROUP_P256, &point)?;
+ PKey::from_ec_key(ec).map_err(std::convert::Into::into)
+ }
+}
+
+impl RemotePublicKey for OpenSSLRemotePublicKey {
+ fn as_raw(&self) -> Result<Vec<u8>> {
+ Ok(self.raw_pub_key.to_vec())
+ }
+ fn as_any(&self) -> &dyn Any {
+ self
+ }
+}
+
+#[derive(Clone)]
+pub struct OpenSSLLocalKeyPair {
+ ec_key: EcKey<Private>,
+}
+
+impl fmt::Debug for OpenSSLLocalKeyPair {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(
+ f,
+ "{:?}",
+ base64::encode_config(&self.ec_key.private_key().to_vec(), base64::URL_SAFE)
+ )
+ }
+}
+
+impl OpenSSLLocalKeyPair {
+ /// Generate a random local key pair using OpenSSL `ECKey::generate`.
+ fn generate_random() -> Result<Self> {
+ let ec_key = EcKey::generate(&GROUP_P256)?;
+ Ok(OpenSSLLocalKeyPair { ec_key })
+ }
+
+ fn to_pkey(&self) -> Result<PKey<Private>> {
+ PKey::from_ec_key(self.ec_key.clone()).map_err(std::convert::Into::into)
+ }
+
+ fn from_raw_components(components: &EcKeyComponents) -> Result<Self> {
+ let d = BigNum::from_slice(&components.private_key())?;
+ let mut bn_ctx = BigNumContext::new()?;
+ let ec_point = EcPoint::from_bytes(&GROUP_P256, &components.public_key(), &mut bn_ctx)?;
+ let mut x = BigNum::new()?;
+ let mut y = BigNum::new()?;
+ ec_point.affine_coordinates_gfp(&GROUP_P256, &mut x, &mut y, &mut bn_ctx)?;
+ let public_key = EcKey::from_public_key_affine_coordinates(&GROUP_P256, &x, &y)?;
+ let private_key = EcKey::from_private_components(&GROUP_P256, &d, public_key.public_key())?;
+ Ok(Self {
+ ec_key: private_key,
+ })
+ }
+}
+
+impl LocalKeyPair for OpenSSLLocalKeyPair {
+ /// Export the public key component in the binary uncompressed point representation
+ /// using OpenSSL `PointConversionForm::UNCOMPRESSED`.
+ fn pub_as_raw(&self) -> Result<Vec<u8>> {
+ let pub_key_point = self.ec_key.public_key();
+ let mut bn_ctx = BigNumContext::new()?;
+ let uncompressed =
+ pub_key_point.to_bytes(&GROUP_P256, PointConversionForm::UNCOMPRESSED, &mut bn_ctx)?;
+ Ok(uncompressed)
+ }
+
+ fn raw_components(&self) -> Result<EcKeyComponents> {
+ let private_key = self.ec_key.private_key();
+ Ok(EcKeyComponents::new(
+ private_key.to_vec(),
+ self.pub_as_raw()?,
+ ))
+ }
+
+ fn as_any(&self) -> &dyn Any {
+ self
+ }
+}
+
+impl From<EcKey<Private>> for OpenSSLLocalKeyPair {
+ fn from(key: EcKey<Private>) -> OpenSSLLocalKeyPair {
+ OpenSSLLocalKeyPair { ec_key: key }
+ }
+}
+
+pub struct OpensslCryptographer;
+impl Cryptographer for OpensslCryptographer {
+ fn generate_ephemeral_keypair(&self) -> Result<Box<dyn LocalKeyPair>> {
+ Ok(Box::new(OpenSSLLocalKeyPair::generate_random()?))
+ }
+
+ fn import_key_pair(&self, components: &EcKeyComponents) -> Result<Box<dyn LocalKeyPair>> {
+ Ok(Box::new(OpenSSLLocalKeyPair::from_raw_components(
+ components,
+ )?))
+ }
+
+ fn import_public_key(&self, raw: &[u8]) -> Result<Box<dyn RemotePublicKey>> {
+ Ok(Box::new(OpenSSLRemotePublicKey::from_raw(raw)?))
+ }
+
+ fn compute_ecdh_secret(
+ &self,
+ remote: &dyn RemotePublicKey,
+ local: &dyn LocalKeyPair,
+ ) -> Result<Vec<u8>> {
+ let local_any = local.as_any();
+ let local = local_any.downcast_ref::<OpenSSLLocalKeyPair>().unwrap();
+ let private = local.to_pkey()?;
+ let remote_any = remote.as_any();
+ let remote = remote_any.downcast_ref::<OpenSSLRemotePublicKey>().unwrap();
+ let public = remote.to_pkey()?;
+ let mut deriver = Deriver::new(&private)?;
+ deriver.set_peer(&public)?;
+ let shared_key = deriver.derive_to_vec()?;
+ Ok(shared_key)
+ }
+
+ fn hkdf_sha256(&self, salt: &[u8], secret: &[u8], info: &[u8], len: usize) -> Result<Vec<u8>> {
+ let (_, hk) = Hkdf::<Sha256>::extract(Some(&salt[..]), &secret);
+ let mut okm = vec![0u8; len];
+ hk.expand(&info, &mut okm).unwrap();
+ Ok(okm)
+ }
+
+ fn aes_gcm_128_encrypt(&self, key: &[u8], iv: &[u8], data: &[u8]) -> Result<Vec<u8>> {
+ let cipher = Cipher::aes_128_gcm();
+ let mut c = Crypter::new(cipher, Mode::Encrypt, key, Some(iv))?;
+ let mut out = vec![0u8; data.len() + cipher.block_size()];
+ let count = c.update(data, &mut out)?;
+ let rest = c.finalize(&mut out[count..])?;
+ let mut tag = vec![0u8; AES_GCM_TAG_LENGTH];
+ c.get_tag(&mut tag)?;
+ out.truncate(count + rest);
+ out.append(&mut tag);
+ Ok(out)
+ }
+
+ fn aes_gcm_128_decrypt(
+ &self,
+ key: &[u8],
+ iv: &[u8],
+ ciphertext_and_tag: &[u8],
+ ) -> Result<Vec<u8>> {
+ let block_len = ciphertext_and_tag.len() - AES_GCM_TAG_LENGTH;
+ let ciphertext = &ciphertext_and_tag[0..block_len];
+ let tag = &ciphertext_and_tag[block_len..];
+ let cipher = Cipher::aes_128_gcm();
+ let mut c = Crypter::new(cipher, Mode::Decrypt, key, Some(iv))?;
+ let mut out = vec![0u8; ciphertext.len() + cipher.block_size()];
+ let count = c.update(ciphertext, &mut out)?;
+ c.set_tag(tag)?;
+ let rest = c.finalize(&mut out[count..])?;
+ out.truncate(count + rest);
+ Ok(out)
+ }
+
+ fn random_bytes(&self, dest: &mut [u8]) -> Result<()> {
+ Ok(rand_bytes(dest)?)
+ }
+}
diff --git a/third_party/rust/ece/src/error.rs b/third_party/rust/ece/src/error.rs
new file mode 100644
index 0000000000..f4b6a6b05a
--- /dev/null
+++ b/third_party/rust/ece/src/error.rs
@@ -0,0 +1,51 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+pub type Result<T> = std::result::Result<T, Error>;
+
+#[derive(Debug, thiserror::Error)]
+pub enum Error {
+ #[error("Invalid auth secret")]
+ InvalidAuthSecret,
+
+ #[error("Invalid salt")]
+ InvalidSalt,
+
+ #[error("Invalid key length")]
+ InvalidKeyLength,
+
+ #[error("Invalid record size")]
+ InvalidRecordSize,
+
+ #[error("Invalid header size (too short)")]
+ HeaderTooShort,
+
+ #[error("Truncated ciphertext")]
+ DecryptTruncated,
+
+ #[error("Zero-length ciphertext")]
+ ZeroCiphertext,
+
+ #[error("Zero-length plaintext")]
+ ZeroPlaintext,
+
+ #[error("Block too short")]
+ BlockTooShort,
+
+ #[error("Invalid decryption padding")]
+ DecryptPadding,
+
+ #[error("Invalid encryption padding")]
+ EncryptPadding,
+
+ #[error("Could not decode base64 entry")]
+ DecodeError(#[from] base64::DecodeError),
+
+ #[error("Crypto backend error")]
+ CryptoError,
+
+ #[cfg(feature = "backend-openssl")]
+ #[error("OpenSSL error: {0}")]
+ OpenSSLError(#[from] openssl::error::ErrorStack),
+}
diff --git a/third_party/rust/ece/src/lib.rs b/third_party/rust/ece/src/lib.rs
new file mode 100644
index 0000000000..db00a99cae
--- /dev/null
+++ b/third_party/rust/ece/src/lib.rs
@@ -0,0 +1,441 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#![warn(rust_2018_idioms)]
+mod aes128gcm;
+mod aesgcm;
+mod common;
+pub mod crypto;
+mod error;
+
+pub use crate::{
+ aes128gcm::Aes128GcmEceWebPush,
+ aesgcm::{AesGcmEceWebPush, AesGcmEncryptedBlock},
+ common::{WebPushParams, ECE_WEBPUSH_AUTH_SECRET_LENGTH},
+ crypto::{Cryptographer, EcKeyComponents, LocalKeyPair, RemotePublicKey},
+ error::*,
+};
+
+/// Generate a local ECE key pair and auth nonce.
+pub fn generate_keypair_and_auth_secret(
+) -> Result<(Box<dyn LocalKeyPair>, [u8; ECE_WEBPUSH_AUTH_SECRET_LENGTH])> {
+ let cryptographer = crypto::holder::get_cryptographer();
+ let local_key_pair = cryptographer.generate_ephemeral_keypair()?;
+ let mut auth_secret = [0u8; ECE_WEBPUSH_AUTH_SECRET_LENGTH];
+ cryptographer.random_bytes(&mut auth_secret)?;
+ Ok((local_key_pair, auth_secret))
+}
+
+/// Encrypt a block using default AES128GCM encoding.
+///
+/// param remote_pub &[u8] - The remote public key
+/// param remote_auth &u8 - The remote authorization token
+/// param salt &[u8] - The locally generated random salt
+/// param data &[u8] - The data to encrypt
+///
+pub fn encrypt(remote_pub: &[u8], remote_auth: &[u8], salt: &[u8], data: &[u8]) -> Result<Vec<u8>> {
+ let cryptographer = crypto::holder::get_cryptographer();
+ let remote_key = cryptographer.import_public_key(remote_pub)?;
+ let local_key_pair = cryptographer.generate_ephemeral_keypair()?;
+ let mut padr = [0u8; 2];
+ cryptographer.random_bytes(&mut padr)?;
+ // 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));
+ Aes128GcmEceWebPush::encrypt_with_keys(
+ &*local_key_pair,
+ &*remote_key,
+ &remote_auth,
+ data,
+ params,
+ )
+}
+
+/// Decrypt a block using default AES128GCM encoding.
+///
+/// param components &str - The locally generated private key components.
+/// param auth &str - The locally generated auth token (this value was shared with the encryptor)
+/// param data &[u8] - The encrypted data block
+///
+pub fn decrypt(components: &EcKeyComponents, auth: &[u8], data: &[u8]) -> Result<Vec<u8>> {
+ let cryptographer = crypto::holder::get_cryptographer();
+ let priv_key = cryptographer.import_key_pair(components).unwrap();
+ Aes128GcmEceWebPush::decrypt(&*priv_key, &auth, data)
+}
+
+#[cfg(all(test, feature = "backend-openssl"))]
+fn generate_keys() -> Result<(Box<dyn LocalKeyPair>, Box<dyn LocalKeyPair>)> {
+ let cryptographer = crypto::holder::get_cryptographer();
+ let local_key = cryptographer.generate_ephemeral_keypair()?;
+ let remote_key = cryptographer.generate_ephemeral_keypair()?;
+ Ok((local_key, remote_key))
+}
+
+#[cfg(all(test, feature = "backend-openssl"))]
+mod aes128gcm_tests {
+ use super::*;
+ use hex;
+
+ #[allow(clippy::too_many_arguments)]
+ fn try_encrypt(
+ private_key: &str,
+ public_key: &str,
+ remote_pub_key: &str,
+ auth_secret: &str,
+ salt: &str,
+ pad_length: usize,
+ rs: u32,
+ plaintext: &str,
+ ) -> Result<String> {
+ let cryptographer = crypto::holder::get_cryptographer();
+ let private_key = hex::decode(private_key).unwrap();
+ let public_key = hex::decode(public_key).unwrap();
+ let ec_key = EcKeyComponents::new(private_key, public_key);
+ let local_key_pair = cryptographer.import_key_pair(&ec_key)?;
+ let remote_pub_key = hex::decode(remote_pub_key).unwrap();
+ let remote_pub_key = cryptographer.import_public_key(&remote_pub_key).unwrap();
+ let auth_secret = hex::decode(auth_secret).unwrap();
+ let salt = hex::decode(salt).unwrap();
+ let plaintext = plaintext.as_bytes();
+ let params = WebPushParams::new(rs, pad_length, salt);
+ let ciphertext = Aes128GcmEceWebPush::encrypt_with_keys(
+ &*local_key_pair,
+ &*remote_pub_key,
+ &auth_secret,
+ &plaintext,
+ params,
+ )?;
+ Ok(hex::encode(ciphertext))
+ }
+
+ fn try_decrypt(
+ private_key: &str,
+ public_key: &str,
+ auth_secret: &str,
+ payload: &str,
+ ) -> Result<String> {
+ let private_key = hex::decode(private_key).unwrap();
+ let public_key = hex::decode(public_key).unwrap();
+ let ec_key = EcKeyComponents::new(private_key, public_key);
+ let plaintext = decrypt(
+ &ec_key,
+ &hex::decode(auth_secret).unwrap(),
+ &hex::decode(payload).unwrap(),
+ )?;
+ Ok(String::from_utf8(plaintext).unwrap())
+ }
+
+ #[test]
+ fn test_e2e() {
+ let (local_key, remote_key) = generate_keys().unwrap();
+ let plaintext = b"When I grow up, I want to be a watermelon";
+ let mut auth_secret = vec![0u8; 16];
+ let cryptographer = crypto::holder::get_cryptographer();
+ cryptographer.random_bytes(&mut auth_secret).unwrap();
+ let remote_public = cryptographer
+ .import_public_key(&remote_key.pub_as_raw().unwrap())
+ .unwrap();
+ let params = WebPushParams::default();
+ let ciphertext = Aes128GcmEceWebPush::encrypt_with_keys(
+ &*local_key,
+ &*remote_public,
+ &auth_secret,
+ plaintext,
+ params,
+ )
+ .unwrap();
+ let decrypted =
+ Aes128GcmEceWebPush::decrypt(&*remote_key, &auth_secret, &ciphertext).unwrap();
+ assert_eq!(decrypted, plaintext.to_vec());
+ }
+
+ #[test]
+ fn test_conv_fn() -> Result<()> {
+ let (local_key, auth) = generate_keypair_and_auth_secret()?;
+ let plaintext = b"Mary had a little lamb, with some nice mint jelly";
+ let mut salt = vec![0u8; 16];
+ let cryptographer = crypto::holder::get_cryptographer();
+ cryptographer.random_bytes(&mut salt)?;
+ let encoded = encrypt(&local_key.pub_as_raw()?, &auth, &salt, plaintext).unwrap();
+ let decoded = decrypt(&local_key.raw_components()?, &auth, &encoded)?;
+ assert_eq!(decoded, plaintext.to_vec());
+ Ok(())
+ }
+
+ #[test]
+ fn try_encrypt_ietf_rfc() {
+ let ciphertext = try_encrypt(
+ "c9f58f89813e9f8e872e71f42aa64e1757c9254dcc62b72ddc010bb4043ea11c",
+ "04fe33f4ab0dea71914db55823f73b54948f41306d920732dbb9a59a53286482200e597a7b7bc260ba1c227998580992e93973002f3012a28ae8f06bbb78e5ec0f",
+ "042571b2becdfde360551aaf1ed0f4cd366c11cebe555f89bcb7b186a53339173168ece2ebe018597bd30479b86e3c8f8eced577ca59187e9246990db682008b0e",
+ "05305932a1c7eabe13b6cec9fda48882",
+ "0c6bfaadad67958803092d454676f397",
+ 0,
+ 4096,
+ "When I grow up, I want to be a watermelon",
+ ).unwrap();
+ assert_eq!(ciphertext, "0c6bfaadad67958803092d454676f397000010004104fe33f4ab0dea71914db55823f73b54948f41306d920732dbb9a59a53286482200e597a7b7bc260ba1c227998580992e93973002f3012a28ae8f06bbb78e5ec0ff297de5b429bba7153d3a4ae0caa091fd425f3b4b5414add8ab37a19c1bbb05cf5cb5b2a2e0562d558635641ec52812c6c8ff42e95ccb86be7cd");
+ }
+
+ #[test]
+ fn try_encrypt_rs_24_pad_6() {
+ let ciphertext = try_encrypt(
+ "0f28beaf7e27793c03638dc2973a15b0016e1b367cbffda8861ab175f31bce02",
+ "0430efcb1eb043b805e4e44bab35f82513c33fedb28700f7e568ac8b61e8d835665a51eb6679b2db228a10c0c3fe5077062848d9bb3d60279f93ce35484728aa1f",
+ "04c0d1a812b291291dd7beee358713c126c589f3633c26d1a201311de036dc10931e4ee142f61921a3ea5864e872a93841a52944e5b3f6accecce8c828fb04a4cd",
+ "9d7735d8de1962b98394b07ffe287e20",
+ "ff805030a108e114e6c17fad6186a1a6",
+ 6,
+ 24,
+ "I am the very model of a modern Major-General, I've information vegetable, animal, and mineral",
+ ).unwrap();
+ assert_eq!(ciphertext, "ff805030a108e114e6c17fad6186a1a600000018410430efcb1eb043b805e4e44bab35f82513c33fedb28700f7e568ac8b61e8d835665a51eb6679b2db228a10c0c3fe5077062848d9bb3d60279f93ce35484728aa1fd2c1713949aec98f05096c7298fd3f51c4f818fafa1fe615d8447b3a05406031f6401ac24f2a775ca52456a921b83b9e0042c3a63e1afa1ae012774d9d775be8d19419451d37ff59ff592e84f07440a63fc17f5cabcb9a50eddaf75370db647f94447d3f166269d8711df0f57e56049576e1130a5a5e1f94ba8a5d0b0007c6c0fd2998429e7d63d4ef919798f46ecf5f0b28fb80f5b2439de26b8a52200bc7d6af7a4840721fe8be8524a691b6ef0edae90bb6f5927894819b831b45b53f8401fe022dbb64ed7565350904ac0b517135d7f8abbc98127fb163864d4d4a307425b2cd43db22af267d71c37146994a8c4805adc341bfba27af09fd80bd5eff51d877282a2fbfbfeb10199e7879e4b9d13a46d57fb7d786824853e1cc89cafbaf14de1e924c944feb8b626ce0207d6f9fa9d849eecac69b42d6e7a23bd5124d49622b44b35c5b15fb0e6a7781a503f1a4e062e015d557d95d44d9d8b0799b3aafce83d5d4");
+ }
+
+ #[test]
+ fn try_encrypt_rs_18_pad_31() {
+ // This test is also interesting because the data length (54) is a
+ // multiple of rs (18). We'll allocate memory to hold 4 records, but only
+ // write 3.
+ let ciphertext = try_encrypt(
+ "7830577bafcfc45828da0c40aab09fb227bfeae068aab8c064222acbe6effd34",
+ "0400b833e481a99aa330dcb277922d5f84af2e9ce611ad2ad3ed0f5b431912d35ea72fc5bf76b769d9526778f5abfa058650988da5e531ff82d1a7043794c71706",
+ "04c3d714cb42e2b0a1d6f98599e2f186b8c2ba6f6fab5e09a2abca865c0805892b2c3729330ef83dc9df4b44362b039a0609d36beb9321a431ec123506ddd90f24",
+ "e4d7b79decdede12c3e9d90d3e05730f",
+ "e49888d2b28f277f847bc5de96f0f81b",
+ 31,
+ 18,
+ "Push the button, Frank!",
+ ).unwrap();
+ assert_eq!(ciphertext, "e49888d2b28f277f847bc5de96f0f81b00000012410400b833e481a99aa330dcb277922d5f84af2e9ce611ad2ad3ed0f5b431912d35ea72fc5bf76b769d9526778f5abfa058650988da5e531ff82d1a7043794c717063aeb958bf116bccf50742fd4d69bd0ea7e3f611c709bf2cdf5cd47c6426cb8323b5398c43c0d0b92cc982da1c24ce5fee2b203f7ad78ca44f0490f3407f5fee883266ee47035195de0fe6d8a75e487df256db597a75e45ae4fb55b8259cb0b2d19e7b05714267eb560ae072b7a665951917a068732df309be256f90f2adda32f05feaa5e9b0695bca2ccf22aaefc7da9ceebc5d40c12d32adb5c84cb320af944016095362febba4ffa4a99830e4958ea2bba508cb683a58d2027d4b74726a853b24b47ccba751abe9d9ab2da9ec2ba9c7ccf0cf17305bae314d38a687618b0772fcb71d4419027a4bf435cb721aad74efc179981b7169604bf97ecac41e73884456933734818132923b56c152d6c9e59aef995aca59de0bf2c803a07180889670a08e64a20d2bfa853e0112872947baaaffb510cc9e75d6310ed6aacbd2e0ba3a29be42c6532ea4e3346e1f0571646371c71665e3fac9d76faee1f122e64d490dd2a3e31816eab583f172841a075d205f318714a8c70ce0f327f4d92b8c9dcb813e6d24fe85633f1a9c7c1e4a1fb314dd5fe3e280e3908f36c8cbfb80b7d9243abaffa65c216cf1aa8b8d626a630dfe8186ce977a5b8f3649d3753b9176c367e4e07f220a175806138e88825a2f3498420582b96209658bbfa8f2ba6933a83c25edb269187796542e2ac49b8078636bddc268e11625e8bff9f0a343d3a4c06080ef0803b8dcd8e841d0e2759e483ea19b903324d9ec4d52f491acef3eeff441c37881c7593eac31621337a5e8659f93e20079b0e26ebfe56c10455d10971130bd2a2c159c74f48b2e526530a76f64cca2efb246e793d11fb75a668018e70c3107100f81ba3b16ae40a838f18d4c47f1d7132f174688ec5382394e0119921731a16879b858ff38f72851ea3d9f5263fec5a606d1271a89b84cca53ed73c5254e245bf8f2f27c2c1c87f39eea78c7017c8c6b5ab01663032b58da31057285e56c203f4e48d6789c66b2695a900e00482bd846559ecddd40264b38e279647d1ec0fccdc1881838bbe0c835e2690ef058b8f6a03e29cd9eb9584e97fbc309773c3688e5e03f9d38e3e4548738a5f569c59147d3e823cccac71d5e8825d5134ce9813cd0b8f9627a3dbfa45b83a59c83d2b4d3ad437778a3cb1bc77ba16c92306f4261a2a1f0d5c7edaecf926f92d7c9dfcae87513a68b8c7ef7c63264b858767c11aaa41d27c636f52e28551e93a969cdc96d43867b7cbd68fe0357bd33415faf22aaeebc957f4b5737a04ab7277b4ed4008f09edaff5a6db69f6cb06f3d0b76688906b2f53b27e63f3728ba2eda505fb1b32f81dddc6d305fd5949edd05490cb1618f0ce1430e9f5edf50012dc3");
+ }
+
+ #[test]
+ fn test_decrypt_rs_24_pad_0() {
+ let plaintext = try_decrypt(
+ "c899d11d32e2b7e6fe7498786f50f23b98ace5397ad261de39ba6449ecc12cad",
+ "04b3fc72e4365cbeb5c78862396eb5e66fd905b483a1b3eac04695f4b802e5b493c5e3b70eb427b6c728b2b204fc255fa218cb45f34d235242705e0d1ea87236e0",
+ "996fad8b50aa2d02b83f26412b2e2aee",
+ "495ce6c8de93a4539e862e8634993cbb0000001841043c3378a2c0ab954e1498718e85f08bb723fb7d25e135a663fe385884eb8192336bf90a54ed720f1c045c0b405e9bbc3a2142b16c89086734c374ebaf7099e6427e2d32c8ada5018703c54b10b481e1027d7209d8c6b43553fa133afa597f2ddc45a5ba8140944e6490bb8d6d99ba1d02e60d95f48ce644477c17231d95b97a4f95dd"
+ ).unwrap();
+ assert_eq!(plaintext, "I am the walrus");
+ }
+
+ #[test]
+ fn test_decrypt_rs_49_pad_84_ciphertext_len_falls_on_record_boundary() {
+ let plaintext = try_decrypt(
+ "67004a4ea820deed8e49db5e9480e63d3ea3cce1ae8e1a60609713d527d001ef",
+ "04014e8f14b92da07ce083b93f96367e87b217a47f7ef2ee93a9d343aa063e575a9f30d59c690c6a39b3fc815b150ca7dd149601741337b53507a51f41b173a721",
+ "95f17570e508ef6a2b2ad1b4f5cade33",
+ "fb2883cec1c4fcadd6d1371f6ea491e00000003141042d441ee7f9ff6a0329a64927d0524fdbe7b22c6fb65e10ab4fdc038f94420a0ca3fa28dad36c84ec91a162eae078faad2c1ced78de8113e19602b20e894f4976b973e2fcf682fa0c8ccd9af3d5bff1ede16fad5a31ce19d38b5e1fe1f78a4fad842bbc10254c2c6cdd96a2b55284d972c53cad8c3bacb10f5f57eb0d4a4333b604102ba117cae29108fbd9f629a8ba6960dd01945b39ed37ba706c434a10fd2bd2094ff9249bcdad45135f5fe45fcd38071f8b2d3941afda439810d77aacaf7ce50b54325bf58c9503337d073785a323dfa343"
+ ).unwrap();
+ assert_eq!(plaintext, "Hello, world");
+ }
+
+ #[test]
+ fn test_decrypt_ietf_rfc() {
+ let plaintext = try_decrypt(
+ "ab5757a70dd4a53e553a6bbf71ffefea2874ec07a6b379e3c48f895a02dc33de",
+ "042571b2becdfde360551aaf1ed0f4cd366c11cebe555f89bcb7b186a53339173168ece2ebe018597bd30479b86e3c8f8eced577ca59187e9246990db682008b0e",
+ "05305932a1c7eabe13b6cec9fda48882",
+ "0c6bfaadad67958803092d454676f397000010004104fe33f4ab0dea71914db55823f73b54948f41306d920732dbb9a59a53286482200e597a7b7bc260ba1c227998580992e93973002f3012a28ae8f06bbb78e5ec0ff297de5b429bba7153d3a4ae0caa091fd425f3b4b5414add8ab37a19c1bbb05cf5cb5b2a2e0562d558635641ec52812c6c8ff42e95ccb86be7cd"
+ ).unwrap();
+ assert_eq!(plaintext, "When I grow up, I want to be a watermelon");
+ }
+
+ #[test]
+ fn test_decrypt_rs_18_pad_0() {
+ let plaintext = try_decrypt(
+ "27433fab8970b3cb5284b61183efb46286562cd2a7330d8cae960911a5571d0c",
+ "04515d4326355652399da24b2be9241e633b5cf14faf0cf3a6fd60317b954c0a2f4848548004b27b0cf7480bc810c6bec03a8fb79c8ea00fc8b05e00f8834563ef",
+ "d65a04df95f2db5e604839f717dcde79",
+ "7caebdbc20938ee340a946f1bd4f68f100000012410437cfdb5223d9f95eaa02f6ed940ff22eaf05b3622e949dc3ce9f335e6ef9b26aeaacca0f74080a8b364592f2ccc6d5eddd43004b70b91887d144d9fa93f16c3bc7ea68f4fd547a94eca84b16e138a6080177"
+ ).unwrap();
+ assert_eq!(plaintext, "1");
+ }
+
+ #[test]
+ fn test_decrypt_missing_header_block() {
+ let err = try_decrypt(
+ "1be83f38332ef09681faf3f307b1ff2e10cab78cc7cdab683ac0ee92ac3f6ee1",
+ "04dba991ca215343f36bdd3e857cafde3d18bf57f1835b2833bad414f0884162051ac96a0b24490037d07cf528e4e18e100a1a64eb744748544bf1e220dabacf2c",
+ "3471bb98481e02533bf39542bcf3dba4",
+ "45b74d2b69be9b074de3b35aa87e7c15611d",
+ )
+ .unwrap_err();
+ match err {
+ Error::HeaderTooShort => {}
+ _ => unreachable!(),
+ };
+ }
+
+ #[test]
+ fn test_decrypt_truncated_sender_key() {
+ let err = try_decrypt(
+ "ce88e8e0b3057a4752eb4c8fa931eb621c302da5ad03b81af459cf6735560cae",
+ "04a325d99084c40de0ce722a042c448d94a32691721ca79e3cf745e78c69886194b02cea19224176795a9d4dbbb2073af2ccd6fa6f0a4c7c4968556be502a3ba81",
+ "5c31e0d96d9a139899ac0969d359f740",
+ "de5b696b87f1a15cb6adebdd79d6f99e000000120100b6bc1826c37c9f73dd6b4859c2b505181952",
+ )
+ .unwrap_err();
+ match err {
+ Error::InvalidKeyLength => {}
+ _ => unreachable!(),
+ };
+ }
+
+ #[test]
+ fn test_decrypt_truncated_auth_secret() {
+ let err = try_decrypt(
+ "60c7636a517de7039a0ac2d0e3064400794c78e7e049398129a227cee0f9a801",
+ "04fdd04128a85c05896d7f81fe118bdcb887b9f3c1ff4183adc4c824d128607300e986b2dfb5a610e5af43e408a00730584f93e3dfddfc44737d5f08fb2d6f8916",
+ "355a38cd6d9bef15990e2d3308dbd600",
+ "8115f4988b8c392a7bacb43c8f1ac5650000001241041994483c541e9bc39a6af03ff713aa7745c284e138a42a2435b797b20c4b698cf5118b4f8555317c190eabebfab749c164d3f6bdebe0d441719131a357d8890a13c4dbd4b16ff3dd5a83f7c91ad6e040ac42730a7f0b3cd3245e9f8d6ff31c751d410cfd"
+ ).unwrap_err();
+ match err {
+ Error::OpenSSLError(_) => {}
+ _ => unreachable!(),
+ };
+ }
+
+ #[test]
+ fn test_decrypt_early_final_record() {
+ let err = try_decrypt(
+ "5dda1d918bc407ba3cda12cb8014d49aa7e0269002820304466bc80034ca9240",
+ "04c95c6520dad11e8f6a1bf8031a40c2a4ee1045c1903be06a1dfa7f829cceb2de02481ae6bd0476121b12c5532d0b231788077efa0683a5bfe0d62339b251cb35",
+ "40c241fde4269ee1e6d725592d982718",
+ "dbe215507d1ad3d2eaeabeae6e874d8f0000001241047bc4343f34a8348cdc4e462ffc7c40aa6a8c61a739c4c41d45125505f70e9fc5f9efa86852dd488dcf8e8ea2cafb75e07abd5ee7c9d5c038bafef079571b0bda294411ce98c76dd031c0e580577a4980a375e45ed30429be0e2ee9da7e6df8696d01b8ec"
+ ).unwrap_err();
+ match err {
+ Error::DecryptPadding => {}
+ _ => unreachable!(),
+ };
+ }
+}
+
+// =====================
+#[cfg(all(test, feature = "backend-openssl"))]
+mod aesgcm_tests {
+ use base64;
+
+ use super::*;
+
+ fn try_decrypt(
+ priv_key: &str,
+ pub_key: &str,
+ auth_secret: &str,
+ block: &AesGcmEncryptedBlock,
+ ) -> Result<String> {
+ // The AesGcmEncryptedBlock is composed from the `Crypto-Key` & `Encryption` headers, and post body
+ // The Block will attempt to decode the base64 strings for dh & salt, so no additional action needed.
+ // Since the body is most likely not encoded, it is expected to be a raw buffer of [u8]
+ let priv_key_raw = base64::decode_config(priv_key, base64::URL_SAFE_NO_PAD)?;
+ let pub_key_raw = base64::decode_config(pub_key, base64::URL_SAFE_NO_PAD)?;
+ let ec_key = EcKeyComponents::new(priv_key_raw, pub_key_raw);
+ let cryptographer = crypto::holder::get_cryptographer();
+ let priv_key = cryptographer.import_key_pair(&ec_key)?;
+ let auth_secret = base64::decode_config(auth_secret, base64::URL_SAFE_NO_PAD)?;
+ let plaintext = AesGcmEceWebPush::decrypt(&*priv_key, &auth_secret, &block)?;
+ Ok(String::from_utf8(plaintext).unwrap())
+ }
+
+ #[test]
+ fn test_decode() {
+ // generated the content using pywebpush, which verified against the client.
+ let auth_raw = "LsuUOBKVQRY6-l7_Ajo-Ag";
+ let priv_key_raw = "yerDmA9uNFoaUnSt2TkWWLwPseG1qtzS2zdjUl8Z7tc";
+ let pub_key_raw = "BLBlTYure2QVhJCiDt4gRL0JNmUBMxtNB5B6Z1hDg5h-Epw6mVFV4whoYGBlWNY-ENR1FObkGFyMf7-6ZMHMAxw";
+
+ // Incoming Crypto-Key: dh=
+ let dh = "BJvcyzf8ocm6F7lbFePebtXU7OHkmylXN9FL2g-yBHwUKqo6cD-FP1h5SHEQQ-xEgJl-F0xEEmSaEx2-qeJHYmk";
+ // Incoming Encryption-Key: salt=
+ let salt = "8qX1ZgkLD50LHgocZdPKZQ";
+ // Incoming Body (this is normally raw bytes. It's encoded here for presentation)
+ let ciphertext = base64::decode_config("8Vyes671P_VDf3G2e6MgY6IaaydgR-vODZZ7L0ZHbpCJNVaf_2omEms2tiPJiU22L3BoECKJixiOxihcsxWMjTgAcplbvfu1g6LWeP4j8dMAzJionWs7OOLif6jBKN6LGm4EUw9e26EBv9hNhi87-HaEGbfBMGcLvm1bql1F",
+ base64::URL_SAFE_NO_PAD).unwrap();
+ let plaintext = "Amidst the mists and coldest frosts I thrust my fists against the\nposts and still demand to see the ghosts.\n";
+
+ let block = AesGcmEncryptedBlock::new(
+ &base64::decode_config(dh, base64::URL_SAFE_NO_PAD).unwrap(),
+ &base64::decode_config(salt, base64::URL_SAFE_NO_PAD).unwrap(),
+ 4096,
+ ciphertext,
+ )
+ .unwrap();
+
+ let result = try_decrypt(priv_key_raw, pub_key_raw, auth_raw, &block).unwrap();
+
+ assert!(result == plaintext)
+ }
+
+ #[test]
+ fn test_decode_padding() {
+ // generated the content using pywebpush, which verified against the client.
+ let auth_raw = "LsuUOBKVQRY6-l7_Ajo-Ag";
+ let priv_key_raw = "yerDmA9uNFoaUnSt2TkWWLwPseG1qtzS2zdjUl8Z7tc";
+ let pub_key_raw = "BLBlTYure2QVhJCiDt4gRL0JNmUBMxtNB5B6Z1hDg5h-Epw6mVFV4whoYGBlWNY-ENR1FObkGFyMf7-6ZMHMAxw";
+
+ // Incoming Crypto-Key: dh=
+ let dh = "BCX7KJ_1Em-LjeB56E2KDoMjKDhTaDhjv8c6dwbvZQZ_Gsfp3AT54x2zYUPcBwd1GVyGsk55ProJ98cFrVxrPz4";
+ // Incoming Encryption-Key: salt=
+ let salt = "x2I2OZpSCoe-Cc5UW36Nng";
+ // Incoming Body (this is normally raw bytes. It's encoded here for presentation)
+ let ciphertext = base64::decode_config("Ua3-WW5kTbt11dBTiXBP6_hLBYhBNOtDFfue5QHMTd2DicL0wutDnt5z9pjRJ76w562egPq5qro95YLnsX0NWGmDQbsQ0Azds6jcBGsxHPt0p5GELAtR4AJj2OsB_LV7dTuGHN2SqsyXLARjTFN2wsF3xWhmuw",
+ base64::URL_SAFE_NO_PAD).unwrap();
+ let plaintext = "Tabs are the real indent";
+
+ let block = AesGcmEncryptedBlock::new(
+ &base64::decode_config(dh, base64::URL_SAFE_NO_PAD).unwrap(),
+ &base64::decode_config(salt, base64::URL_SAFE_NO_PAD).unwrap(),
+ 4096,
+ ciphertext,
+ )
+ .unwrap();
+
+ let result = try_decrypt(priv_key_raw, pub_key_raw, auth_raw, &block).unwrap();
+
+ println!(
+ "Result: b64={}",
+ base64::encode_config(&result, base64::URL_SAFE_NO_PAD)
+ );
+ println!(
+ "Plaintext: b64={}",
+ base64::encode_config(&plaintext, base64::URL_SAFE_NO_PAD)
+ );
+ assert!(result == plaintext)
+ }
+
+ #[test]
+ fn test_e2e() {
+ let (local_key, remote_key) = generate_keys().unwrap();
+ let plaintext = b"When I grow up, I want to be a watermelon";
+ let mut auth_secret = vec![0u8; 16];
+ let cryptographer = crypto::holder::get_cryptographer();
+ cryptographer.random_bytes(&mut auth_secret).unwrap();
+ let remote_public = cryptographer
+ .import_public_key(&remote_key.pub_as_raw().unwrap())
+ .unwrap();
+ let params = WebPushParams::default();
+ let ciphertext = AesGcmEceWebPush::encrypt_with_keys(
+ &*local_key,
+ &*remote_public,
+ &auth_secret,
+ plaintext,
+ params,
+ )
+ .unwrap();
+ let decrypted = AesGcmEceWebPush::decrypt(&*remote_key, &auth_secret, &ciphertext).unwrap();
+ assert_eq!(decrypted, plaintext.to_vec());
+ }
+
+ #[test]
+ fn test_keygen() {
+ let cryptographer = crypto::holder::get_cryptographer();
+ cryptographer.generate_ephemeral_keypair().unwrap();
+ }
+
+ // If decode using externally validated data works, and e2e using the same decoder work, things
+ // should encode/decode.
+ // Other tests to be included if required, but skipping for now because of time constraints.
+}