Dactyloidae iOS initial commit

This commit is contained in:
wuggy 2026-06-26 21:04:09 -07:00
commit 7154a0497e
2123 changed files with 197052 additions and 0 deletions

View file

@ -0,0 +1,77 @@
cmake_minimum_required(VERSION 3.1 FATAL_ERROR)
project(ece VERSION 0.9.0 LANGUAGES C)
set(CMAKE_C_STANDARD 99)
set(CMAKE_C_EXTENSIONS OFF)
set(C_STANDARD_REQUIRED ON)
include(GNUInstallDirs)
find_package(OpenSSL 1.1.0 REQUIRED)
enable_testing()
set(ECE_SOURCES
src/base64url.c
src/encrypt.c
src/decrypt.c
src/keys.c
src/params.c
src/trailer.c)
add_library(ece ${ECE_SOURCES})
set_target_properties(ece PROPERTIES
OUTPUT_NAME ece
VERSION "${ECE_VERSION}")
target_include_directories(ece
PUBLIC include
PRIVATE src
PRIVATE ${OPENSSL_INCLUDE_DIR})
target_link_libraries(ece PRIVATE ${OPENSSL_LIBRARIES})
if(DEFINED ENV{COVERAGE})
target_compile_options(ece PUBLIC "-fprofile-arcs;-ftest-coverage")
target_link_libraries(ece PUBLIC --coverage)
endif()
add_executable(ece-decrypt tool/decrypt.c)
set_target_properties(ece-decrypt PROPERTIES EXCLUDE_FROM_ALL 1)
target_include_directories(ece-decrypt PRIVATE tool)
target_link_libraries(ece-decrypt PRIVATE ece)
add_executable(ece-keygen tool/keygen.c)
set_target_properties(ece-keygen PROPERTIES EXCLUDE_FROM_ALL 1)
target_include_directories(ece-keygen PRIVATE tool)
target_link_libraries(ece-keygen PRIVATE ece)
set(ECE_TEST_SOURCES
test/decrypt/aes128gcm.c
test/decrypt/aesgcm.c
test/encrypt/aes128gcm.c
test/encrypt/aesgcm.c
test/base64url.c
test/e2e.c
test/params.c
test/test.c)
add_executable(ece-test ${ECE_TEST_SOURCES})
set_target_properties(ece-test PROPERTIES EXCLUDE_FROM_ALL 1)
target_include_directories(ece-test
PRIVATE test
PRIVATE ${OPENSSL_INCLUDE_DIR})
target_link_libraries(ece-test
PRIVATE ece
PRIVATE ${OPENSSL_LIBRARIES})
add_test(NAME ece-test COMMAND ece-test)
add_custom_target(check COMMAND ${CMAKE_CTEST_COMMAND}
-C $<CONFIG> --output-on-failure)
add_dependencies(check ece-test)
if(MSVC)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /W4 /WX")
target_compile_definitions(ece PUBLIC "_CRT_SECURE_NO_WARNINGS")
else()
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -pedantic -Werror")
if(${CMAKE_C_COMPILER_ID} MATCHES "(Apple)?Clang")
# GCC's `-Wconversion` reports too many false positives; Clang's is more
# sophisticated.
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wconversion")
endif()
endif()

21
mobile/ios/ThirdParty/ecec/LICENSE vendored Normal file
View file

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2017 Kit Cambridge
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

238
mobile/ios/ThirdParty/ecec/README.md vendored Normal file
View file

@ -0,0 +1,238 @@
# ecec
![GitHub version](https://badge.fury.io/gh/web-push-libs%2Fecec.svg)
[![Build Status](https://travis-ci.org/web-push-libs/ecec.svg?branch=master)](https://travis-ci.org/web-push-libs/ecec)
[![Coverage](https://img.shields.io/codecov/c/github/web-push-libs/ecec/master.svg)](https://codecov.io/github/web-push-libs/ecec)
**ecec** is a C implementation of the [HTTP Encrypted Content-Encoding](http://httpwg.org/http-extensions/draft-ietf-httpbis-encryption-encoding.html) draft. It's a port of the reference [JavaScript implementation](https://github.com/martinthomson/encrypted-content-encoding).
Encrypted content-coding is used to encrypt [Web Push messages](https://webpush-wg.github.io/webpush-encryption/), and can be used standalone.
## Table of Contents
- [Usage](#usage)
* [Generating subscription keys](#generating-subscription-keys)
* [`aes128gcm`](#aes128gcm)
* [`aesgcm`](#aesgcm)
- [Building](#building)
* [Dependencies](#dependencies)
* [macOS and \*nix](#macos-and-nix)
* [Windows](#windows)
- [What is encrypted content-coding?](#what-is-encrypted-content-coding)
* [Web Push](#web-push)
* [`aes128gcm`](#aes128gcm-1)
* [`aesgcm`](#aesgcm-1)
- [License](#license)
## Usage
### Generating subscription keys
```c
#include <ece.h>
int
main() {
// The subscription private key. This key should never be sent to the app
// server. It should be persisted with the endpoint and auth secret, and used
// to decrypt all messages sent to the subscription.
uint8_t rawRecvPrivKey[ECE_WEBPUSH_PRIVATE_KEY_LENGTH];
// The subscription public key. This key should be sent to the app server,
// and used to encrypt messages. The Push DOM API exposes the public key via
// `pushSubscription.getKey("p256dh")`.
uint8_t rawRecvPubKey[ECE_WEBPUSH_PUBLIC_KEY_LENGTH];
// The shared auth secret. This secret should be persisted with the
// subscription information, and sent to the app server. The DOM API exposes
// the auth secret via `pushSubscription.getKey("auth")`.
uint8_t authSecret[ECE_WEBPUSH_AUTH_SECRET_LENGTH];
int err = ece_webpush_generate_keys(
rawRecvPrivKey, ECE_WEBPUSH_PRIVATE_KEY_LENGTH, rawRecvPubKey,
ECE_WEBPUSH_PUBLIC_KEY_LENGTH, authSecret, ECE_WEBPUSH_AUTH_SECRET_LENGTH);
if (err) {
return 1;
}
return 0;
}
```
### `aes128gcm`
This is the scheme from the latest version of the encrypted content-coding draft. It's not currently supported by any encryption library or browser, but will eventually replace `aesgcm`. This scheme removes the `Crypto-Key` and `Encryption` headers. Instead, the salt, record size, and sender public key are included in the payload as a binary header block.
```c
// Assume `rawSubPrivKey` and `authSecret` contain the subscription private key
// and auth secret.
uint8_t rawSubPrivKey[ECE_WEBPUSH_PRIVATE_KEY_LENGTH] = {0};
uint8_t authSecret[ECE_WEBPUSH_AUTH_SECRET_LENGTH] = {0};
// Assume `payload` points to the contents of the encrypted payload, and
// `payloadLen` specifies the length.
uint8_t* payload = NULL;
size_t payloadLen = 0;
size_t plaintextLen = ece_aes128gcm_plaintext_max_length(payload, payloadLen);
assert(plaintextLen > 0);
uint8_t* plaintext = calloc(plaintextLen, sizeof(uint8_t));
assert(plaintext);
int err =
ece_webpush_aes128gcm_decrypt(rawSubPrivKey, ECE_WEBPUSH_PRIVATE_KEY_LENGTH,
authSecret, ECE_WEBPUSH_AUTH_SECRET_LENGTH,
payload, payloadLen, plaintext, &plaintextLen);
assert(err == ECE_OK);
// `plaintext[0..plaintextLen]` contains the decrypted message.
free(plaintext);
```
### `aesgcm`
All [Web Push libraries](https://github.com/web-push-libs) support the "aesgcm" scheme, as well as Firefox 46+ and Chrome 50+. The app server includes its public key in the `Crypto-Key` HTTP header, the salt and record size in the `Encryption` header, and the encrypted payload in the body of the `POST` request.
* The `Crypto-Key` header comprises one or more comma-delimited parameters. The first parameter must include a `dh` name-value pair, containing the sender's Base64url-encoded public key.
* The `Encryption` header must include a `salt` name-value pair containing the sender's Base64url-encoded salt, and an optional `rs` pair specifying the record size.
If the `Crypto-Key` header contains multiple keys, the sender must also include a `keyid` to match the encryption parameters to the key. The drafts have examples for [a single key without a `keyid`](https://tools.ietf.org/html/draft-ietf-webpush-encryption-04#section-5), and [multiple keys with `keyid`s](https://tools.ietf.org/html/draft-ietf-httpbis-encryption-encoding-02#section-5.6).
```c
uint8_t rawSubPrivKey[ECE_WEBPUSH_PRIVATE_KEY_LENGTH] = {0};
uint8_t authSecret[ECE_WEBPUSH_AUTH_SECRET_LENGTH] = {0};
const char* cryptoKeyHeader = "dh=...";
const char* encryptionHeader = "salt=...; rs=...";
uint8_t* ciphertext = NULL;
size_t ciphertextLen = 0;
uint8_t salt[ECE_SALT_LENGTH];
uint8_t rawSenderPubKey[ECE_WEBPUSH_PUBLIC_KEY_LENGTH];
uint32_t rs = 0;
int err =
ece_webpush_aesgcm_headers_extract_params(cryptoKeyHeader, encryptionHeader,
salt, ECE_SALT_LENGTH,
rawSenderPubKey,
ECE_WEBPUSH_PUBLIC_KEY_LENGTH, &rs);
assert(err == ECE_OK);
size_t plaintextLen = ece_aesgcm_plaintext_max_length(rs, ciphertextLen);
assert(plaintextLen > 0);
uint8_t* plaintext = calloc(plaintextLen, sizeof(uint8_t));
assert(plaintext);
err = ece_webpush_aesgcm_decrypt(
rawSubPrivKey, ECE_WEBPUSH_PRIVATE_KEY_LENGTH, authSecret,
ECE_WEBPUSH_AUTH_SECRET_LENGTH, salt, ECE_SALT_LENGTH, rawSenderPubKey,
ECE_WEBPUSH_PUBLIC_KEY_LENGTH, rs, ciphertext, ciphertextLen, plaintext,
&plaintextLen);
assert(err == ECE_OK);
// `plaintext[0..plaintextLen]` contains the decrypted message.
free(plaintext);
```
## Building
### Dependencies
* [OpenSSL](https://www.openssl.org/) 1.1.0 or higher
* [CMake](https://cmake.org/) 3.1 or higher
* A C99-capable compiler, like [Clang](https://clang.llvm.org/) 3.4, [GCC](https://gcc.gnu.org/) 4.6, or [Visual Studio](https://www.visualstudio.com/vs/community/) 2015
### macOS and \*nix
OpenSSL 1.1.0 is new, and backward-incompatible with 1.0.x. If your package manager ([MacPorts](https://www.macports.org/), [Homebrew](https://brew.sh/), [APT](https://help.ubuntu.com/community/AptGet/Howto), [DNF](https://dnf.readthedocs.io/en/latest/), [yum](http://yum.baseurl.org/)) doesn't have 1.1.0 yet, you'll need to compile it yourself. **ecec** does this to run its tests on [Travis CI](https://docs.travis-ci.com/user/ci-environment/); please see `.travis/install.sh` for the commands.
In particular, you'll need to set the `OPENSSL_ROOT_DIR` cache entry for CMake to find your compiled version. To build the library:
```shell
> mkdir build
> cd build
> cmake -DOPENSSL_ROOT_DIR=/usr/local ..
> make
```
To build the decryption tool:
```shell
> make ece-decrypt
> ./ece-decrypt
```
To run the tests:
```shell
> make check
```
### Windows
[Shining Light](https://slproweb.com/products/Win32OpenSSL.html) provides OpenSSL binaries for Windows. The installer will ask if you want to copy the OpenSSL DLLs into the system directory, or the OpenSSL binaries directory. If you choose the binaries directory, you'll need to add it to your `Path`.
To do so, right-click the Start button, navigate to "System" > "Advanced system settings" > "Environment Variables...", find `Path` under "System variables", click "Edit" > "New", and enter the directory name. This will be `C:\OpenSSL-Win64\bin` if you've installed the 64-bit version in the default location.
You can then build the library like so:
```powershell
> mkdir build
> cd build
> cmake -G "Visual Studio 14 2015 Win64" -DOPENSSL_ROOT_DIR=C:\OpenSSL-Win64 ..
> cmake --build . [--config Debug|Release]
```
To build the decryption tool:
```powershell
> cmake --build . --target ece-decrypt [--config Debug|Release]
> .\[Debug|Release]\ece-decrypt
```
To run the tests:
```powershell
> cmake --build . --target check [--config Debug|Release]
```
## What is encrypted content-coding?
Like [TLS](https://en.wikipedia.org/wiki/Transport_Layer_Security), encrypted content-coding uses Diffie-Hellman key exchange to derive a shared secret, which, in turn, is used to derive a symmetric encryption key for a block cipher. This encoding uses [ECDH](https://en.wikipedia.org/wiki/Elliptic_curve_Diffie-Hellman) for key exchange, and [AES](https://en.wikipedia.org/wiki/Advanced_Encryption_Standard) [GCM](https://en.wikipedia.org/wiki/Galois/Counter_Mode) for the block cipher.
Key exchange is a process where a sender and a receiver generate public-private key pairs, then exchange public keys. The sender combines the receiver's public key with its own private key to obtain a secret. Meanwhile, the receiver combines the sender's public key with its private key to obtain the same secret. [Wikipedia](https://en.wikipedia.org/wiki/DiffieHellman_key_exchange) has a good visual explanation.
The shared ECDH secret isn't directly usable as an encryption key. Instead, both the sender and receiver combine the shared ECDH secret with an [authentication secret](https://tools.ietf.org/html/draft-ietf-webpush-encryption-08#section-3.2), to produce a 32-byte pseudorandom key (PRK). The auth secret is a random 16-byte array generated by the receiver, and shared with the sender along with the receiver's public key. Both parties use [HKDF](https://tools.ietf.org/html/rfc5869) to derive the PRK from the ECDH secret, using the formula `PRK = HKDF-Expand(HKDF-Extract(authSecret, sharedSecret), prkInfo, 32)`. RFC 5869 describes the inputs to `HKDF-Expand` and `HKDF-Extract`, and how they work. `prkInfo` is different depending on the encryption scheme used; more on that later.
Next, the sender and receiver combine the PRK with a random 16-byte salt. The salt is generated by the sender, and shared with the receiver as part of the message payload. The PRK undergoes two rounds of HKDF to derive the symmetric key and nonce: `key = HKDF-Expand(HKDF-Extract(salt, PRK), keyInfo, 16)`, and `nonce = HKDF-Expand(HKDF-Extract(salt, PRK), nonceInfo, 12)`. As with `prkInfo` above, `keyInfo` and `nonceInfo` are different depending on the exact scheme.
Finally, the sender chunks the plaintext into fixed-size records, and includes this size in the message payload as the `rs`. The chunks are numbered 0 to N; this is called the sequence number (SEQ), and is used to derive the [IV](https://en.wikipedia.org/wiki/Initialization_vector). All chunks should be `rs` bytes long, but the final chunk can be smaller if needed.
Each plaintext chunk is padded, then encrypted with AES using the 16-byte symmetric key and a 12-byte IV. The IV is [generated](https://tools.ietf.org/html/draft-ietf-httpbis-encryption-encoding-07#section-2.3) from the nonce by [XOR-ing](https://en.wikipedia.org/wiki/Exclusive_or) the last 6 bytes of the 12-byte nonce with the sequence number. Afterward, the sender appends the GCM authentication tag to the encrypted chunk, producing the final encrypted record.
To decrypt the message, the receiver chunks the ciphertext into N encrypted records, decrypts each chunk, validates the auth tag, and removes the padding.
### Web Push
In Web Push, the app server is the sender, and the browser ("user agent") is the receiver. The browser generates a public-private ECDH key pair and 16-byte auth secret for each push subscription. These keys are static; they're used to decrypt all messages sent to this subscription. The browser exposes the subscription endpoint, public key, and auth secret to the web app via the [Push DOM API](https://w3c.github.io/push-api/). The web app then delivers the endpoint and keys to the app server.
When the app server wants to send a push message, it generates its own public-private key pair, and computes the shared ECDH secret using the subscription public key. This key pair is ephemeral: it should be discarded after the message is sent, and a new key pair used for the next message. The app server encrypts the payload using the process outlined above, and includes the salt, sender public key, and ciphertext in a `POST` request to the endpoint. The push endpoint relays the encrypted payload to the browser. Finally, the browser decrypts the payload with the subscription private key, and delivers the plaintext to the web app. Because the endpoint doesn't know the private key, it can't decrypt or tamper with the message.
### `aes128gcm`
* `prkInfo` is the string `"WebPush: info\0"`, followed by the receiver and sender public keys in uncompressed form. Unlike `aesgcm`, these are not length-prefixed.
* `keyInfo` is the static string `"Content-Encoding: aes128gcm\0"`.
* `nonceInfo` is the static string `"Content-Encoding: nonce\0"`.
* Padding is at the end of each plaintext chunk. The padding block comprises the delimiter, which is `0x02` for the last chunk, and `0x01` for the other chunks. Up to `rs - 16` bytes of `0x0` padding can follow the delimiter.
### `aesgcm`
* `prkInfo` is the static string `"Content-Encoding: auth\0"`.
* `keyInfo` is `"Content-Encoding: aesgcm\0P-256\0"`, followed by the length-prefixed (unsigned 16-bit integers) receiver and sender public keys in uncompressed form.
* `nonceInfo` is `"Content-Encoding: nonce\0P-256\0"`, followed by the length-prefixed public keys in the same form as `keyInfo`.
* Padding is at the beginning of each plaintext chunk. The padding block comprises the number (unsigned 16-bit integer) of padding bytes, followed by that many `0x0`-valued bytes.
## License
MIT.

625
mobile/ios/ThirdParty/ecec/include/ece.h vendored Normal file
View file

@ -0,0 +1,625 @@
#ifndef ECE_H
#define ECE_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#define ECE_SALT_LENGTH 16
#define ECE_TAG_LENGTH 16
#define ECE_WEBPUSH_PRIVATE_KEY_LENGTH 32
#define ECE_WEBPUSH_PUBLIC_KEY_LENGTH 65
#define ECE_WEBPUSH_AUTH_SECRET_LENGTH 16
#define ECE_AES128GCM_MIN_RS 18
#define ECE_AES128GCM_HEADER_LENGTH 21
#define ECE_AES128GCM_MAX_KEY_ID_LENGTH 255
#define ECE_AES128GCM_PAD_SIZE 1
#define ECE_AESGCM_MIN_RS 3
#define ECE_AESGCM_PAD_SIZE 2
#define ECE_OK 0
#define ECE_ERROR_OUT_OF_MEMORY -1
#define ECE_ERROR_INVALID_PRIVATE_KEY -2
#define ECE_ERROR_INVALID_PUBLIC_KEY -3
#define ECE_ERROR_COMPUTE_SECRET -4
#define ECE_ERROR_ENCODE_PUBLIC_KEY -5
#define ECE_ERROR_DECRYPT -6
#define ECE_ERROR_DECRYPT_PADDING -7
#define ECE_ERROR_ZERO_PLAINTEXT -8
#define ECE_ERROR_SHORT_BLOCK -9
#define ECE_ERROR_SHORT_HEADER -10
#define ECE_ERROR_ZERO_CIPHERTEXT -11
#define ECE_ERROR_HKDF -12
#define ECE_ERROR_INVALID_ENCRYPTION_HEADER -13
#define ECE_ERROR_INVALID_CRYPTO_KEY_HEADER -14
#define ECE_ERROR_INVALID_RS -15
#define ECE_ERROR_INVALID_SALT -16
#define ECE_ERROR_INVALID_DH -17
#define ECE_ERROR_ENCRYPT -18
#define ECE_ERROR_ENCRYPT_PADDING -19
#define ECE_ERROR_INVALID_AUTH_SECRET -20
#define ECE_ERROR_GENERATE_KEYS -21
#define ECE_ERROR_DECRYPT_TRUNCATED -22
// Annotates a variable or parameter as unused to avoid compiler warnings.
#define ECE_UNUSED(x) (void) (x)
/*!
* The policy for appending trailing "=" characters to Base64url-encoded output.
*/
typedef enum ece_base64url_encode_policy_e {
/*! Omits padding, even if the input is not a multiple of 4. */
ECE_BASE64URL_OMIT_PADDING,
/*! Includes padding if the input is not a multiple of 4. */
ECE_BASE64URL_INCLUDE_PADDING,
} ece_base64url_encode_policy_t;
/*!
* The policy for handling trailing "=" characters in Base64url-encoded input.
*/
typedef enum ece_base64url_decode_policy_e {
/*!
* Fails decoding if the input is unpadded. RFC 4648, section 3.2 requires
* padding, unless the referring specification prohibits it.
*/
ECE_BASE64URL_REQUIRE_PADDING,
/*! Tolerates padded and unpadded input. */
ECE_BASE64URL_IGNORE_PADDING,
/*!
* Fails decoding if the input is padded. This follows the strict Base64url
* variant used in JWS (RFC 7515, Appendix C) and
* draft-ietf-httpbis-encryption-encoding-03. */
ECE_BASE64URL_REJECT_PADDING,
} ece_base64url_decode_policy_t;
/*!
* Generates a public-private ECDH key pair and authentication secret for a Web
* Push subscription.
*
* \sa ece_webpush_aes128gcm_decrypt(),
* ece_webpush_aesgcm_decrypt()
*
* \param rawRecvPrivKey[in] The subscription private key. This key should
* be stored locally, and used to decrypt incoming
* messages.
* \param rawRecvPrivKeyLen[in] The length of the subscription private key. Must
* be `ECE_WEBPUSH_PRIVATE_KEY_LENGTH`.
* \param rawRecvPubKey[in] The subscription public key, in uncompressed
* form. This key should be shared with the app
* server, and used to encrypt outgoing messages.
* \param rawRecvPubKeyLen[in] The length of the subscription public key. Must
* be `ECE_WEBPUSH_PUBLIC_KEY_LENGTH`.
* \param authSecret[in] The authentication secret. This secret should
* be stored locally and shared with the app
* server. It's used to derive the content
* encryption key and nonce.
* \param authSecretLen[in] The length of the authentication secret. Must
* be `ECE_WEBPUSH_AUTH_SECRET_LENGTH`.
*
* \return `ECE_OK` on success, or an error code if key
* generation fails.
*/
int
ece_webpush_generate_keys(uint8_t* rawRecvPrivKey, size_t rawRecvPrivKeyLen,
uint8_t* rawRecvPubKey, size_t rawRecvPubKeyLen,
uint8_t* authSecret, size_t authSecretLen);
/*!
* Calculates the maximum "aes128gcm" plaintext length. The caller should
* allocate and pass an array of this length to the "aes128gcm" decryption
* functions.
*
* \sa ece_aes128gcm_decrypt(),
* ece_webpush_aes128gcm_decrypt()
*
* \param payload[in] The encrypted payload.
* \param payloadLen[in] The length of the encrypted payload.
*
* \return The maximum plaintext length, or 0 if the payload
* header is truncated or invalid.
*/
size_t
ece_aes128gcm_plaintext_max_length(const uint8_t* payload, size_t payloadLen);
/*!
* Decrypts a message encrypted using the "aes128gcm" scheme, with a symmetric
* key. The key is shared out of band, and identified by the `keyId` parameter
* in the payload header.
*
* \sa ece_aes128gcm_plaintext_max_length(),
* ece_aes128gcm_payload_extract_params()
*
* \param ikm[in] The input keying material (IKM) for the content
* encryption key and nonce.
* \param ikmLen[in] The length of the IKM.
* \param payload[in] The encrypted payload.
* \param payloadLen[in] The length of the encrypted payload.
* \param plaintext[in] An empty array. Must be large enough to hold the
* full plaintext.
* \param plaintextLen[in,out] The input is the length of the empty `plaintext`
* array. On success, the output is set to the
* actual plaintext length, and
* `[0..plaintextLen]` contains the plaintext.
*
* \return `ECE_OK` on success, or an error code if
* the payload is empty or malformed.
*/
int
ece_aes128gcm_decrypt(const uint8_t* ikm, size_t ikmLen, const uint8_t* payload,
size_t payloadLen, uint8_t* plaintext,
size_t* plaintextLen);
/*!
* Decrypts a Web Push message encrypted using the "aes128gcm" scheme.
*
* \sa ece_aes128gcm_plaintext_max_length()
*
* \param rawRecvPrivKey[in] The subscription private key.
* \param rawRecvPrivKeyLen[in] The length of the subscription private key. Must
* be `ECE_WEBPUSH_PRIVATE_KEY_LENGTH`.
* \param authSecret[in] The authentication secret.
* \param authSecretLen[in] The length of the authentication secret. Must be
* `ECE_WEBPUSH_AUTH_SECRET_LENGTH`.
* \param payload[in] The encrypted payload.
* \param payloadLen[in] The length of the encrypted payload.
* \param plaintext[in] An empty array. Must be large enough to hold the
* full plaintext.
* \param plaintextLen[in,out] The input is the length of the empty `plaintext`
* array. On success, the output is set to the
* the actual plaintext length, and
* `[0..plaintextLen]` contains the plaintext.
*
* \return `ECE_OK` on success, or an error code if
* the payload is empty or malformed.
*/
int
ece_webpush_aes128gcm_decrypt(const uint8_t* rawRecvPrivKey,
size_t rawRecvPrivKeyLen,
const uint8_t* authSecret, size_t authSecretLen,
const uint8_t* payload, size_t payloadLen,
uint8_t* plaintext, size_t* plaintextLen);
/*!
* Calculates the maximum "aes128gcm" encrypted payload length. The caller
* should allocate and pass an array of this length to the "aes128gcm"
* encryption functions.
*
* \param rs[in] The record size. This is the length of each encrypted
* plaintext chunk, including room for the padding
* delimiter and GCM authentication tag. Must be at
* least `ECE_AES128GCM_MIN_RS`.
* \param padLen[in] The length of additional padding, used to hide the
* plaintext length. Padding is added to the plaintext
* during encryption, and discarded during decryption.
* \param plaintextLen[in] The length of the plaintext.
*
* \return The maximum payload length, or 0 if `rs` is too
* small.
*/
size_t
ece_aes128gcm_payload_max_length(uint32_t rs, size_t padLen,
size_t plaintextLen);
/*!
* Encrypts a Web Push message using the "aes128gcm" scheme. This function
* automatically generates an ephemeral ECDH key pair and a random salt.
*
* \sa ece_aes128gcm_payload_max_length()
*
* \param rawRecvPubKey[in] The subscription public key, in uncompressed
* form.
* \param rawRecvPubKeyLen[in] The length of the subscription public key. Must
* be `ECE_WEBPUSH_PUBLIC_KEY_LENGTH`.
* \param authSecret[in] The authentication secret.
* \param authSecretLen[in] The length of the authentication secret. Must be
* `ECE_WEBPUSH_AUTH_SECRET_LENGTH`.
* \param rs[in] The record size. Must be at least
* `ECE_AES128GCM_MIN_RS`.
* \param padLen[in] The length of additional padding to include in
* the ciphertext, if any.
* \param plaintext[in] The plaintext to encrypt.
* \param plaintextLen[in] The length of the plaintext.
* \param payload[in] An empty array. Must be large enough to hold the
* full payload.
* \param payloadLen[in,out] The input is the length of the empty `payload`
* array. On success, the output is set to the
* actual payload length, and
* `payload[0..payloadLen]` contains the payload.
*
* \return `ECE_OK` on success, or an error code if
* encryption fails.
*/
int
ece_webpush_aes128gcm_encrypt(const uint8_t* rawRecvPubKey,
size_t rawRecvPubKeyLen,
const uint8_t* authSecret, size_t authSecretLen,
uint32_t rs, size_t padLen,
const uint8_t* plaintext, size_t plaintextLen,
uint8_t* payload, size_t* payloadLen);
/*!
* Encrypts a Web Push message using the "aes128gcm" scheme, with an explicit
* sender key and salt. The sender key can be reused, but the salt *must* be
* unique to avoid deriving the same content encryption key for multiple
* messages.
*
* \warning In general, you should only use this function
* for testing. `ece_webpush_aes128gcm_encrypt`
* is safer because it doesn't risk accidental
* salt reuse.
*
* \sa ece_aes128gcm_payload_max_length(),
* ece_webpush_aes128gcm_encrypt()
*
* \param rawSenderPrivKey[in] The sender private key.
* \param rawSenderPrivKeyLen[in] The length of the sender private key. Must be
* `ECE_WEBPUSH_PRIVATE_KEY_LENGTH`.
* \param authSecret[in] The authentication secret.
* \param authSecretLen[in] The length of the authentication secret. Must
* be `ECE_WEBPUSH_AUTH_SECRET_LENGTH`.
* \param salt[in] The encryption salt.
* \param saltLen[in] The length of the salt. Must be
* `ECE_SALT_LENGTH`.
* \param rawRecvPubKey[in] The subscription public key, in uncompressed
* form. Must be `ECE_WEBPUSH_PUBLIC_KEY_LENGTH`.
* \param rawRecvPubKeyLen[in] The length of the subscription public key.
* \param rs[in] The record size. Must be at least
* `ECE_AES128GCM_MIN_RS`.
* \param padLen[in] The length of additional padding to include in
* the ciphertext, if any.
* \param plaintext[in] The plaintext to encrypt.
* \param plaintextLen[in] The length of the plaintext.
* \param payload[in] An empty array. Must be large enough to hold
* the full payload.
* \param payloadLen[in,out] The input is the length of the empty `payload`
* array. On success, the output is set to the
* actual payload length, and
* `payload[0..payloadLen]` contains the payload.
*
* \return `ECE_OK` on success, or an error code if
* encryption fails.
*/
int
ece_webpush_aes128gcm_encrypt_with_keys(
const uint8_t* rawSenderPrivKey, size_t rawSenderPrivKeyLen,
const uint8_t* authSecret, size_t authSecretLen, const uint8_t* salt,
size_t saltLen, const uint8_t* rawRecvPubKey, size_t rawRecvPubKeyLen,
uint32_t rs, size_t padLen, const uint8_t* plaintext, size_t plaintextLen,
uint8_t* payload, size_t* payloadLen);
/*!
* Calculates the maximum "aesgcm" ciphertext length. The caller should allocate
* and pass an array of this length to `ece_webpush_aesgcm_encrypt_with_keys`.
*
* \param rs[in] The record size. Must be least `ECE_AESGCM_MIN_RS`.
* \param padLen[in] The length of additional padding.
* \param plaintextLen[in] The length of the plaintext.
*
* \return The maximum ciphertext length, or 0 if `rs` is too
* small.
*/
size_t
ece_aesgcm_ciphertext_max_length(uint32_t rs, size_t padLen,
size_t plaintextLen);
/*!
* Encrypts a Web Push message using the "aesgcm" scheme. Like
* `ece_webpush_aes128gcm_encrypt`, this function generates a sender key pair
* and salt.
*
* \sa ece_aesgcm_ciphertext_max_length()
*
* \param rawRecvPubKey[in] The subscription public key, in uncompressed
* form.
* \param rawRecvPubKeyLen[in] The length of the subscription public key. Must
* be `ECE_WEBPUSH_PUBLIC_KEY_LENGTH`.
* \param authSecret[in] The authentication secret.
* \param authSecretLen[in] The length of the authentication secret. Must
* be `ECE_WEBPUSH_AUTH_SECRET_LENGTH`.
* \param rs[in] The record size. Must be at least
* `ECE_AES128GCM_MIN_RS`.
* \param padLen[in] The length of additional padding to include in
* the ciphertext, if any.
* \param plaintext[in] The plaintext to encrypt.
* \param plaintextLen[in] The length of the plaintext.
* \param salt[in] An empty array to hold the salt.
* \param saltLen[in] The length of the empty `salt` array. Must be
* `ECE_SALT_LENGTH`.
* \param rawSenderPubKey[in] An empty array to hold the sender public key.
* \param rawSenderPubKeyLen[in] The length of the empty `rawSenderPubKey`
* array. Must be `ECE_WEBPUSH_PUBLIC_KEY_LENGTH`.
* \param ciphertext[in] An empty array to hold the ciphertext.
* \param ciphertextLen[in, out] The input is the length of the empty
* `ciphertext` array. On success, the output is
* set to the actual ciphertext length, and
* `ciphertext[0..ciphertextLen]` contains the
* ciphertext.
*
* \return `ECE_OK` on success, or an error code if
* encryption fails.
*/
int
ece_webpush_aesgcm_encrypt(const uint8_t* rawRecvPubKey,
size_t rawRecvPubKeyLen, const uint8_t* authSecret,
size_t authSecretLen, uint32_t rs, size_t padLen,
const uint8_t* plaintext, size_t plaintextLen,
uint8_t* salt, size_t saltLen,
uint8_t* rawSenderPubKey, size_t rawSenderPubKeyLen,
uint8_t* ciphertext, size_t* ciphertextLen);
/*!
* Encrypts a Web Push message using the "aesgcm" scheme and explicit keys.
*
* \warning `ece_webpush_aesgcm_encrypt` is safer because
* it doesn't risk accidental salt reuse.
*
* \sa ece_aesgcm_ciphertext_max_length(),
* ece_webpush_aesgcm_encrypt()
*
* \param rawSenderPrivKey[in] The sender private key.
* \param rawSenderPrivKeyLen[in] The length of the sender private key. Must be
* `ECE_WEBPUSH_PRIVATE_KEY_LENGTH`.
* \param authSecret[in] The authentication secret.
* \param authSecretLen[in] The length of the authentication secret. Must
* be `ECE_WEBPUSH_AUTH_SECRET_LENGTH`.
* \param salt[in] The encryption salt.
* \param saltLen[in] The length of the salt. Must be
* `ECE_SALT_LENGTH`.
* \param rawRecvPubKey[in] The subscription public key, in uncompressed
* form. Must be `ECE_WEBPUSH_PUBLIC_KEY_LENGTH`.
* \param rawRecvPubKeyLen[in] The length of the subscription public key.
* \param rs[in] The record size. Must be at least
* `ECE_AES128GCM_MIN_RS`.
* \param padLen[in] The length of additional padding to include in
* the ciphertext, if any.
* \param plaintext[in] The plaintext to encrypt.
* \param plaintextLen[in] The length of the plaintext.
* \param ciphertext[in] An empty array. Must be large enough to hold
* the full ciphertext.
* \param ciphertextLen[in,out] The input is the length of the empty
* `ciphertext` array. On success, the output is
* set to the actual ciphertext length, and
* `ciphertext[0..ciphertextLen]` contains the
* ciphertext.
*
* \return `ECE_OK` on success, or an error code if
* encryption fails.
*/
int
ece_webpush_aesgcm_encrypt_with_keys(
const uint8_t* rawSenderPrivKey, size_t rawSenderPrivKeyLen,
const uint8_t* authSecret, size_t authSecretLen, const uint8_t* salt,
size_t saltLen, const uint8_t* rawRecvPubKey, size_t rawRecvPubKeyLen,
uint32_t rs, size_t padLen, const uint8_t* plaintext, size_t plaintextLen,
uint8_t* ciphertext, size_t* ciphertextLen);
/*!
* Calculates the maximum "aesgcm" plaintext length. The caller should allocate
* and pass an array of this length to `ece_webpush_aesgcm_decrypt`.
*
* \sa ece_webpush_aesgcm_decrypt()
*
* \param rs[in] The record size. Must be at least
* `ECE_AESGCM_MIN_RS`.
* \param ciphertextLen[in] The ciphertext length.
*
* \return The maximum plaintext length.
*/
size_t
ece_aesgcm_plaintext_max_length(uint32_t rs, size_t ciphertextLen);
/*!
* Decrypts a Web Push message encrypted using the "aesgcm" scheme.
*
* \sa ece_aesgcm_plaintext_max_length()
*
* \param rawRecvPrivKey[in] The subscription private key.
* \param rawRecvPrivKeyLen[in] The length of the subscription private key.
* Must be `ECE_WEBPUSH_PRIVATE_KEY_LENGTH`.
* \param authSecret[in] The authentication secret.
* \param authSecretLen[in] The length of the authentication secret. Must
* be `ECE_WEBPUSH_AUTH_SECRET_LENGTH`.
* \param salt[in]
* \param salt[in] The salt, from the `Encryption` header.
* \param saltLen[in] The length of the salt. Must be
* `ECE_SALT_LENGTH`.
* \param rawSenderPubKey[in] The sender public key, in uncompressed form,
* from the `Crypto-Key` header.
* \param rawSenderPubKeyLen[in] The length of the sender public key. Must be
* `ECE_WEBPUSH_PUBLIC_KEY_LENGTH`.
* \param rs[in] The record size. Must be at least
* `ECE_AESGCM_MIN_RS`.
* \param ciphertext[in] The ciphertext.
* \param ciphertextLen[in] The length of the ciphertext.
* \param plaintext[in] An empty array. Must be large enough to hold
* the full plaintext.
* \param plaintextLen[in,out] The input is the length of the empty
* `plaintext` array. On success, the output is
* set to the actual plaintext length, and
* `[0..plaintextLen]` contains the plaintext.
*
* \return `ECE_OK` on success, or an error code if the
* headers or ciphertext are malformed.
*/
int
ece_webpush_aesgcm_decrypt(const uint8_t* rawRecvPrivKey,
size_t rawRecvPrivKeyLen, const uint8_t* authSecret,
size_t authSecretLen, const uint8_t* salt,
size_t saltLen, const uint8_t* rawSenderPubKey,
size_t rawSenderPubKeyLen, uint32_t rs,
const uint8_t* ciphertext, size_t ciphertextLen,
uint8_t* plaintext, size_t* plaintextLen);
/*!
* Extracts "aes128gcm" decryption parameters from an encrypted payload.
* `salt`, `keyId`, and `ciphertext` are pointers into `payload`, and must not
* outlive it.
*
* \sa ece_aes128gcm_decrypt()
*
* \param payload[in] The encrypted payload.
* \param payloadLen[in] The length of the encrypted payload.
* \param salt[out] The encryption salt.
* \param saltLen[out] The length of the salt.
* \param keyId[out] An identifier for the keying material.
* \param keyIdLen[out] The length of the key ID.
* \param rs[out] The record size.
* \param ciphertext[out] The ciphertext.
* \param ciphertextLen[out] The length of the ciphertext.
*
* \return `ECE_OK` on success, or an error code if the
* payload header is truncated or invalid.
*/
int
ece_aes128gcm_payload_extract_params(const uint8_t* payload, size_t payloadLen,
const uint8_t** salt, size_t* saltLen,
const uint8_t** keyId, size_t* keyIdLen,
uint32_t* rs, const uint8_t** ciphertext,
size_t* ciphertextLen);
/*!
* Extracts "aesgcm" decryption parameters from the `Crypto-Key` and
* `Encryption` headers.
*
* \sa ece_webpush_aesgcm_decrypt(),
* ece_webpush_aesgcm_headers_from_params()
*
* \param cryptoKeyHeader[in] The value of the `Crypto-Key` HTTP header.
* \param encryptionHeader[in] The value of the `Encryption` HTTP header.
* \param salt[in] An empty array to hold the encryption salt,
* extracted from the `Encryption` header.
* \param saltLen[in] The length of the empty `salt` array. Must be
* `ECE_SALT_LENGTH`.
* \param rawSenderPubKey[in] An empty array to hold the sender public key,
* in uncompressed form, extracted from the
* `Crypto-Key` header.
* \param rawSenderPubKeyLen[in] The length of the empty `rawSenderPubKey`
* array. Must be `ECE_WEBPUSH_PUBLIC_KEY_LENGTH`.
* \param rs[out] The record size.
*
* \return `ECE_OK` on success, or an error code if the
* headers are malformed.
*/
int
ece_webpush_aesgcm_headers_extract_params(const char* cryptoKeyHeader,
const char* encryptionHeader,
uint8_t* salt, size_t saltLen,
uint8_t* rawSenderPubKey,
size_t rawSenderPubKeyLen,
uint32_t* rs);
/*!
* Builds the `Crypto-Key` and `Encryption` headers from the "aesgcm"
* encryption parameters.
*
* \sa ece_webpush_aesgcm_encrypt_with_keys(),
* ece_webpush_aesgcm_headers_extract_params()
*
* \param salt[in] The encryption salt, to include in the
* `Encryption` header.
* \param saltLen[in] The length of the salt. Must be
* `ECE_SALT_LENGTH`.
* \param rawSenderPubKey[in] The sender public key, in uncompressed
* form, to include in the `Crypto-Key`
* header.
* \param rawSenderPubKeyLen[in] The length of the sender public key. Must
* be `ECE_WEBPUSH_PUBLIC_KEY_LENGTH`.
* \param rs[in] The record size, to include in the
* `Encryption` header.
* \param cryptoKeyHeader[in] An empty array to hold the `Crypto-Key`
* header. May be `NULL` if
* `cryptoKeyHeaderLen` is 0. The header is
* *not* null-terminated; you'll need to add
* a trailing `'\0'` if you want to treat
* `cryptoKeyHeader` as a C string.
* \param cryptoKeyHeaderLen[in, out] The input is the length of the empty
* `cryptoKeyHeader` array. If 0, the output
* is set to the length required to hold
* the result. On success,
* `[0..cryptoKeyHeaderLen]` contains the
* header.
* \param encryptionHeader[in] An empty array to hold the `Encryption`
* header. May be `NULL` if
* `encryptionHeaderLen` is 0. Like
* `cryptoKeyHeader`, this header is not
* null-terminated.
* \param encryptionHeaderLen[in, out] The input is the length of the empty
* `encryptionHeader` array. If 0, the
* output is set to the length required to
* hold the result. On success,
* `[0..encryptionHeaderLen]` contains the
* header.
*
* \return `ECE_OK` on success, or an error code if
* `cryptoKeyHeaderLen` or
* `encryptionHeaderLen` is too small.
*/
int
ece_webpush_aesgcm_headers_from_params(const void* salt, size_t saltLen,
const void* rawSenderPubKey,
size_t rawSenderPubKeyLen, uint32_t rs,
char* cryptoKeyHeader,
size_t* cryptoKeyHeaderLen,
char* encryptionHeader,
size_t* encryptionHeaderLen);
/*!
* Converts a byte array to a Base64url-encoded (RFC 4648) string.
*
* \param binary[in] The byte array to encode.
* \param binaryLen[in] The length of the byte array.
* \param paddingPolicy[in] The policy for padding the encoded output.
* \param base64[in] An empty array to hold the encoded result. May be
* `NULL` if `base64Len` is 0. This function does
* *not* null-terminate `base64`. This makes it easier
* to include Base64url-encoded substrings in larger
* strings, but means you'll need to add a trailing
* `'\0'` if you want to treat `base64` as a C string.
* \param base64Len[in] The length of the empty `base64` array. On success,
* `base64[0..base64Len]` contains the result.
*
* \return The encoded length. If `binaryLen` is 0, returns the
* length of the array required to hold the result. If
* `binaryLen` is not large enough to hold the full
* result, returns 0.
*/
size_t
ece_base64url_encode(const void* binary, size_t binaryLen,
ece_base64url_encode_policy_t paddingPolicy, char* base64,
size_t base64Len);
/*!
* Decodes a Base64url-encoded (RFC 4648) string.
*
* \param base64[in] The encoded string.
* \param base64Len[in] The length of the encoded string.
* \param paddingPolicy[in] The policy for handling "=" padding in the encoded
* input.
* \param binary[in] An empty array to hold the decoded result. May be
* `NULL` if `binaryLen` is 0.
* \param binaryLen[in] The length of the empty `binary` array. On success,
* `binary[0..binaryLen]` contains the result.
*
* \return The actual decoded length. If `binaryLen` is 0,
* returns the length of the array
* required to hold the result. If `base64` contains
* invalid characters, or `binaryLen` is not large
* enough to hold the full result, returns 0.
*/
size_t
ece_base64url_decode(const char* base64, size_t base64Len,
ece_base64url_decode_policy_t paddingPolicy,
uint8_t* binary, size_t binaryLen);
#ifdef __cplusplus
}
#endif
#endif /* ECE_H */

View file

@ -0,0 +1,91 @@
#ifndef ECE_KEYS_H
#define ECE_KEYS_H
#ifdef __cplusplus
extern "C" {
#endif
#include <openssl/ec.h>
#define ECE_AES_KEY_LENGTH 16
#define ECE_NONCE_LENGTH 12
#define ECE_WEBPUSH_IKM_LENGTH 32
// HKDF info strings for the "aes128gcm" scheme. Note that the lengths include
// the NUL terminator.
#define ECE_WEBPUSH_AES128GCM_IKM_INFO_PREFIX "WebPush: info\0"
#define ECE_WEBPUSH_AES128GCM_IKM_INFO_PREFIX_LENGTH 14
#define ECE_WEBPUSH_AES128GCM_IKM_INFO_LENGTH 144
#define ECE_AES128GCM_KEY_INFO "Content-Encoding: aes128gcm\0"
#define ECE_AES128GCM_KEY_INFO_LENGTH 28
#define ECE_AES128GCM_NONCE_INFO "Content-Encoding: nonce\0"
#define ECE_AES128GCM_NONCE_INFO_LENGTH 24
// HKDF info strings for the "aesgcm" scheme.
#define ECE_WEBPUSH_AESGCM_IKM_INFO "Content-Encoding: auth\0"
#define ECE_WEBPUSH_AESGCM_IKM_INFO_LENGTH 23
#define ECE_WEBPUSH_AESGCM_KEY_INFO_PREFIX "Content-Encoding: aesgcm\0P-256\0"
#define ECE_WEBPUSH_AESGCM_KEY_INFO_PREFIX_LENGTH 31
#define ECE_WEBPUSH_AESGCM_KEY_INFO_LENGTH 165
#define ECE_WEBPUSH_AESGCM_NONCE_INFO_PREFIX "Content-Encoding: nonce\0P-256\0"
#define ECE_WEBPUSH_AESGCM_NONCE_INFO_PREFIX_LENGTH 30
#define ECE_WEBPUSH_AESGCM_NONCE_INFO_LENGTH 164
// Key derivation modes.
typedef enum ece_mode_e {
ECE_MODE_ENCRYPT,
ECE_MODE_DECRYPT,
} ece_mode_t;
typedef int (*derive_key_and_nonce_t)(ece_mode_t mode, EC_KEY* localKey,
EC_KEY* remoteKey,
const uint8_t* authSecret,
size_t authSecretLen, const uint8_t* salt,
size_t saltLen, uint8_t* key,
uint8_t* nonce);
// Generates a 96-bit IV for decryption, 48 bits of which are populated.
void
ece_generate_iv(const uint8_t* nonce, uint64_t counter, uint8_t* iv);
// Inflates a raw ECDH private key into an OpenSSL `EC_KEY` containing a
// private and public key pair. Returns `NULL` on error.
EC_KEY*
ece_import_private_key(const uint8_t* rawKey, size_t rawKeyLen);
// Inflates a raw ECDH public key into an `EC_KEY` containing a public key.
// Returns `NULL` on error.
EC_KEY*
ece_import_public_key(const uint8_t* rawKey, size_t rawKeyLen);
// Derives the "aes128gcm" content encryption key and nonce.
int
ece_aes128gcm_derive_key_and_nonce(const uint8_t* salt, size_t saltLen,
const uint8_t* ikm, size_t ikmLen,
uint8_t* key, uint8_t* nonce);
// Derives the "aes128gcm" decryption key and nonce given the receiver private
// key, sender public key, authentication secret, and sender salt.
int
ece_webpush_aes128gcm_derive_key_and_nonce(ece_mode_t mode, EC_KEY* localKey,
EC_KEY* remoteKey,
const uint8_t* authSecret,
size_t authSecretLen,
const uint8_t* salt, size_t saltLen,
uint8_t* key, uint8_t* nonce);
// Derives the "aesgcm" decryption key and nonce given the receiver private key,
// sender public key, authentication secret, and sender salt.
int
ece_webpush_aesgcm_derive_key_and_nonce(ece_mode_t mode, EC_KEY* recvPrivKey,
EC_KEY* senderPubKey,
const uint8_t* authSecret,
size_t authSecretLen,
const uint8_t* salt, size_t saltLen,
uint8_t* key, uint8_t* nonce);
#ifdef __cplusplus
}
#endif
#endif /* ECE_KEYS_H */

View file

@ -0,0 +1,31 @@
#ifndef ECE_TRAILER_H
#define ECE_TRAILER_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
typedef bool (*needs_trailer_t)(uint32_t rs, size_t ciphertextLen);
// Adjusts the aesgcm record size to account for the authentication tag.
// aesgcm includes the size of the padding delimiter, but not the tag.
uint32_t
ece_aesgcm_rs(uint32_t rs);
// Indicates if an "aesgcm" ciphertext is a multiple of the record size, and
// needs a padding-only trailing block to prevent truncation attacks.
bool
ece_aesgcm_needs_trailer(uint32_t rs, size_t ciphertextLen);
// Provided for completeness, but always returns false because "aes128gcm" uses
// a padding scheme that doesn't need a trailer.
bool
ece_aes128gcm_needs_trailer(uint32_t rs, size_t ciphertextLen);
#ifdef __cplusplus
}
#endif
#endif /* ECE_TRAILER_H */

View file

@ -0,0 +1,289 @@
#include "ece.h"
// This file implements Base64url encoding and decoding per RFC 4648. Originally
// implemented in https://bugzilla.mozilla.org/show_bug.cgi?id=1256488 and
// https://bugzilla.mozilla.org/show_bug.cgi?id=1205137.
#include <assert.h>
#include <stdbool.h>
#define ECE_BASE64URL_INVALID_CHAR 64
#define ECE_BASE64URL_INVALID_PADDING 3
// Maps an index to a character in the Base64url alphabet.
static const char ece_base64url_encode_table[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
// Maps a character in the Base64url alphabet to its index, per RFC 4648,
// Table 2. Invalid characters map to 64.
static const uint8_t ece_base64url_decode_table[] = {
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 62, 64, 64, 52, 53, 54, 55, 56, 57, 58, 59, 60,
61, 64, 64, 64, 64, 64, 64, 64, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 64, 64, 64, 64,
63, 64, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42,
43, 44, 45, 46, 47, 48, 49, 50, 51, 64, 64, 64, 64,
};
// Returns the size of the buffer required to hold the Base64url output,
// or 0 if `binaryLen` is too large.
static inline size_t
ece_base64url_base64_length(size_t binaryLen) {
if (binaryLen / 3 > SIZE_MAX / 4) {
return 0;
}
// Base64 expands each 3-byte quantum to 4 bytes. The final quantum can be
// 2 or 3 bytes.
size_t baseLen = (binaryLen / 3) * 4;
size_t finalLen = 0;
switch (binaryLen % 3) {
case 1:
finalLen = 2;
break;
case 2:
finalLen = 3;
break;
}
if (finalLen > SIZE_MAX - baseLen) {
return 0;
}
return baseLen + finalLen;
}
// Encodes a `binary` quantum into `base64`, and returns the number of bytes
// written. A 3-byte quantum encodes to 4 bytes, a 2-byte quantum encodes to
// 3 bytes, and a 1-byte quantum encodes to 2 bytes.
static inline int
ece_base64url_encode_quantum(const uint8_t* binary, size_t binaryLen,
char* base64) {
assert(binaryLen <= 3);
uint32_t quantum = 0;
for (size_t i = 0; i < binaryLen; i++) {
quantum <<= 8;
quantum |= (uint32_t) binary[i];
}
switch (binaryLen) {
case 1:
base64[0] = ece_base64url_encode_table[(quantum >> 2) & 0x3f];
base64[1] = ece_base64url_encode_table[(quantum << 4) & 0x3f];
return 2;
case 2:
base64[0] = ece_base64url_encode_table[(quantum >> 10) & 0x3f];
base64[1] = ece_base64url_encode_table[(quantum >> 4) & 0x3f];
base64[2] = ece_base64url_encode_table[(quantum << 2) & 0x3f];
return 3;
case 3:
base64[0] = ece_base64url_encode_table[(quantum >> 18) & 0x3f];
base64[1] = ece_base64url_encode_table[(quantum >> 12) & 0x3f];
base64[2] = ece_base64url_encode_table[(quantum >> 6) & 0x3f];
base64[3] = ece_base64url_encode_table[quantum & 0x3f];
return 4;
}
return 0;
}
// Returns the number of trailing `=` characters to remove from the end of
// `base64`, based on the `paddingPolicy`. Valid values are 0, 1, or 2;
// 3 means the input is invalid.
static inline size_t
ece_base64url_decode_pad_length(const char* base64, size_t base64Len,
ece_base64url_decode_policy_t paddingPolicy) {
// Determine whether to check for and ignore trailing padding.
bool maybePadded = false;
switch (paddingPolicy) {
case ECE_BASE64URL_REQUIRE_PADDING:
if (base64Len % 4) {
// Padded input length must be a multiple of 4.
return ECE_BASE64URL_INVALID_PADDING;
}
maybePadded = true;
break;
case ECE_BASE64URL_IGNORE_PADDING:
// Check for padding only if the length is a multiple of 4.
maybePadded = !(base64Len % 4);
break;
// If we're expecting unpadded input, no need for additional checks.
// `=` isn't in the decode table, so padded strings will fail to decode.
default:
// Invalid decode padding policy.
assert(false);
case ECE_BASE64URL_REJECT_PADDING:
break;
}
if (maybePadded && base64[base64Len - 1] == '=') {
base64Len--;
if (base64[base64Len - 1] == '=') {
return 2;
}
return 1;
}
return 0;
}
// Returns the size of the buffer required to hold the binary output, or 0 if
// `base64Len` is truncated.
static inline size_t
ece_base64url_binary_length(size_t base64Len) {
size_t requiredBinaryLen = (base64Len / 4) * 3;
switch (base64Len % 4) {
case 1:
return 0;
case 2:
requiredBinaryLen++;
break;
case 3:
requiredBinaryLen += 2;
break;
}
return requiredBinaryLen;
}
// Converts a Base64url character `c` to its index.
static inline uint8_t
ece_base64url_decode_byte(char b) {
return (b & ~0x7f) ? ECE_BASE64URL_INVALID_CHAR
: ece_base64url_decode_table[b & 0x7f];
}
// Decodes a `base64` encoded quantum into `binary`. A 4-byte quantum decodes to
// 3 bytes, a 3-byte quantum decodes to 2 bytes, and a 2-byte quantum decodes to
// 1 byte.
static inline bool
ece_base64url_decode_quantum(const char* base64, size_t base64Len,
uint8_t* binary) {
assert(base64Len <= 4);
uint32_t quantum = 0;
for (size_t i = 0; i < base64Len; i++) {
uint8_t b = ece_base64url_decode_byte(base64[i]);
if (b == ECE_BASE64URL_INVALID_CHAR) {
return false;
}
quantum <<= 6;
quantum |= (uint32_t) b;
}
switch (base64Len) {
case 0:
return true;
case 2:
binary[0] = (quantum >> 4) & 0xff;
return true;
case 3:
binary[0] = (quantum >> 10) & 0xff;
binary[1] = (quantum >> 2) & 0xff;
return true;
case 4:
binary[0] = (quantum >> 16) & 0xff;
binary[1] = (quantum >> 8) & 0xff;
binary[2] = quantum & 0xff;
return true;
}
return false;
}
size_t
ece_base64url_encode(const void* binary, size_t binaryLen,
ece_base64url_encode_policy_t paddingPolicy, char* base64,
size_t base64Len) {
// Don't encode empty strings.
if (!binaryLen) {
return 0;
}
// Ensure we have enough room to hold the output.
size_t requiredBase64Len = ece_base64url_base64_length(binaryLen);
if (!requiredBase64Len) {
return 0;
}
size_t padLen = 0;
if (paddingPolicy == ECE_BASE64URL_INCLUDE_PADDING) {
switch (requiredBase64Len % 4) {
case 2:
padLen = 2;
break;
case 3:
padLen = 1;
break;
}
if (padLen > SIZE_MAX - requiredBase64Len) {
return 0;
}
requiredBase64Len += padLen;
}
if (base64Len) {
if (base64Len < requiredBase64Len) {
return 0;
}
const uint8_t* input = binary;
for (; binaryLen >= 3; binaryLen -= 3) {
base64 += ece_base64url_encode_quantum(input, 3, base64);
input += 3;
}
base64 += ece_base64url_encode_quantum(input, binaryLen, base64);
if (paddingPolicy == ECE_BASE64URL_INCLUDE_PADDING) {
while (padLen) {
*base64++ = '=';
padLen--;
}
} else {
assert(paddingPolicy == ECE_BASE64URL_OMIT_PADDING);
}
}
return requiredBase64Len;
}
size_t
ece_base64url_decode(const char* base64, size_t base64Len,
ece_base64url_decode_policy_t paddingPolicy,
uint8_t* binary, size_t binaryLen) {
// Don't decode empty strings.
if (!base64Len) {
return 0;
}
// Ensure we have enough room to hold the output.
size_t padLen =
ece_base64url_decode_pad_length(base64, base64Len, paddingPolicy);
if (padLen == ECE_BASE64URL_INVALID_PADDING) {
return 0;
}
base64Len -= padLen;
size_t requiredBinaryLen = ece_base64url_binary_length(base64Len);
if (binaryLen) {
if (binaryLen < requiredBinaryLen) {
return 0;
}
for (; base64Len >= 4; base64Len -= 4) {
if (!ece_base64url_decode_quantum(base64, 4, binary)) {
return 0;
}
base64 += 4;
binary += 3;
}
if (!ece_base64url_decode_quantum(base64, base64Len, binary)) {
return 0;
}
}
return requiredBinaryLen;
}

425
mobile/ios/ThirdParty/ecec/src/decrypt.c vendored Normal file
View file

@ -0,0 +1,425 @@
#include "ece.h"
#include "ece/keys.h"
#include "ece/trailer.h"
#include <assert.h>
#include <string.h>
#include <openssl/evp.h>
#include <openssl/rand.h>
typedef int (*unpad_t)(uint8_t* block, bool lastRecord, size_t* blockLen);
// Calculates the maximum plaintext length, including room for the padding
// delimiter and padding.
static inline size_t
ece_plaintext_max_length(uint32_t rs, size_t padSize, size_t ciphertextLen) {
assert(padSize <= 2);
size_t overhead = padSize + ECE_TAG_LENGTH;
if (rs <= overhead) {
return 0;
}
size_t numRecords = ciphertextLen / rs;
if (ciphertextLen % rs) {
// If the ciphertext length doesn't fall on a record boundary, we have
// a smaller final record.
numRecords++;
}
if (numRecords > ciphertextLen / ECE_TAG_LENGTH) {
// Each record includes a trailing auth tag. If the number of records
// exceeds the number of tags, the ciphertext is truncated.
return 0;
}
return ciphertextLen - (ECE_TAG_LENGTH * numRecords);
}
// Extracts an unsigned 16-bit integer in network byte order.
static inline uint16_t
ece_read_uint16_be(const uint8_t* bytes) {
uint16_t value = (uint16_t) bytes[1];
value |= bytes[0] << 8;
return value;
}
// Converts an encrypted record to a decrypted block.
static int
ece_decrypt_record(EVP_CIPHER_CTX* ctx, const uint8_t* key, const uint8_t* iv,
const uint8_t* record, size_t recordLen, uint8_t* block) {
int chunkLen = -1;
if (EVP_DecryptInit_ex(ctx, EVP_aes_128_gcm(), NULL, key, iv) != 1) {
return ECE_ERROR_DECRYPT;
}
assert(recordLen > ECE_TAG_LENGTH);
size_t blockLen = recordLen - ECE_TAG_LENGTH;
// The authentication tag is included at the end of the encrypted record.
uint8_t tag[ECE_TAG_LENGTH];
memcpy(tag, &record[blockLen], ECE_TAG_LENGTH);
if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, ECE_TAG_LENGTH, tag) !=
1) {
return ECE_ERROR_DECRYPT;
}
if (blockLen > INT_MAX ||
EVP_DecryptUpdate(ctx, block, &chunkLen, record, (int) blockLen) != 1) {
return ECE_ERROR_DECRYPT;
}
// Since we're using a stream cipher, finalization shouldn't write out any
// bytes.
assert(EVP_CIPHER_CTX_block_size(ctx) == 1);
if (EVP_DecryptFinal_ex(ctx, NULL, &chunkLen) != 1) {
return ECE_ERROR_DECRYPT;
}
if (EVP_CIPHER_CTX_reset(ctx) != 1) {
return ECE_ERROR_DECRYPT;
}
return ECE_OK;
}
static int
ece_decrypt_records(const uint8_t* key, const uint8_t* nonce, uint32_t rs,
size_t padSize, const uint8_t* ciphertext,
size_t ciphertextLen, unpad_t unpad, uint8_t* plaintext,
size_t* plaintextLen) {
int err = ECE_OK;
EVP_CIPHER_CTX* ctx = NULL;
// Make sure the plaintext array is large enough to hold the full plaintext.
size_t maxPlaintextLen = ece_plaintext_max_length(rs, padSize, ciphertextLen);
if (!maxPlaintextLen) {
err = ECE_ERROR_DECRYPT;
goto end;
}
if (*plaintextLen < maxPlaintextLen) {
err = ECE_ERROR_OUT_OF_MEMORY;
goto end;
}
ctx = EVP_CIPHER_CTX_new();
if (!ctx) {
err = ECE_ERROR_OUT_OF_MEMORY;
goto end;
}
// The offset at which to start reading the ciphertext.
size_t ciphertextStart = 0;
// The offset at which to start writing the plaintext.
size_t plaintextStart = 0;
for (size_t counter = 0; ciphertextStart < ciphertextLen; counter++) {
size_t ciphertextEnd;
if (rs > ciphertextLen - ciphertextStart) {
// This check is equivalent to `ciphertextStart + rs > ciphertextLen`;
// it's written this way to avoid an integer overflow.
ciphertextEnd = ciphertextLen;
} else {
ciphertextEnd = ciphertextStart + rs;
}
assert(ciphertextEnd > ciphertextStart);
// The full length of the encrypted record.
size_t recordLen = ciphertextEnd - ciphertextStart;
if (recordLen <= ECE_TAG_LENGTH) {
err = ECE_ERROR_SHORT_BLOCK;
goto end;
}
// Generate the IV for this record using the nonce.
uint8_t iv[ECE_NONCE_LENGTH];
ece_generate_iv(nonce, counter, iv);
// Decrypt the record.
err = ece_decrypt_record(ctx, key, iv, &ciphertext[ciphertextStart],
recordLen, &plaintext[plaintextStart]);
if (err) {
goto end;
}
// `unpad` sets `blockLen` to the actual plaintext block length, without
// the padding delimiter and padding.
bool lastRecord = ciphertextEnd >= ciphertextLen;
size_t blockLen = recordLen - ECE_TAG_LENGTH;
if (blockLen < padSize) {
err = ECE_ERROR_DECRYPT_PADDING;
goto end;
}
err = unpad(&plaintext[plaintextStart], lastRecord, &blockLen);
if (err) {
goto end;
}
ciphertextStart = ciphertextEnd;
plaintextStart += blockLen;
}
// Finally, set the actual plaintext length.
*plaintextLen = plaintextStart;
end:
EVP_CIPHER_CTX_free(ctx);
return err;
}
// A generic decryption function shared by "aesgcm" and "aes128gcm".
// `deriveKeyAndNonce` and `unpad` are function pointers that change based on
// the scheme.
static int
ece_webpush_decrypt(const uint8_t* rawRecvPrivKey, size_t rawRecvPrivKeyLen,
const uint8_t* authSecret, size_t authSecretLen,
const uint8_t* salt, size_t saltLen,
const uint8_t* rawSenderPubKey, size_t rawSenderPubKeyLen,
uint32_t rs, size_t padSize, const uint8_t* ciphertext,
size_t ciphertextLen, needs_trailer_t needsTrailer,
derive_key_and_nonce_t deriveKeyAndNonce, unpad_t unpad,
uint8_t* plaintext, size_t* plaintextLen) {
int err = ECE_OK;
EC_KEY* recvPrivKey = NULL;
EC_KEY* senderPubKey = NULL;
if (authSecretLen != ECE_WEBPUSH_AUTH_SECRET_LENGTH) {
err = ECE_ERROR_INVALID_AUTH_SECRET;
goto end;
}
if (saltLen != ECE_SALT_LENGTH) {
err = ECE_ERROR_INVALID_SALT;
goto end;
}
if (!ciphertextLen) {
err = ECE_ERROR_ZERO_CIPHERTEXT;
goto end;
}
if (needsTrailer(rs, ciphertextLen)) {
// If we're missing a trailing block, the ciphertext is truncated. This only
// applies to "aesgcm".
err = ECE_ERROR_DECRYPT_TRUNCATED;
goto end;
}
recvPrivKey = ece_import_private_key(rawRecvPrivKey, rawRecvPrivKeyLen);
if (!recvPrivKey) {
err = ECE_ERROR_INVALID_PRIVATE_KEY;
goto end;
}
senderPubKey = ece_import_public_key(rawSenderPubKey, rawSenderPubKeyLen);
if (!senderPubKey) {
err = ECE_ERROR_INVALID_PUBLIC_KEY;
goto end;
}
uint8_t key[ECE_AES_KEY_LENGTH];
uint8_t nonce[ECE_NONCE_LENGTH];
err = deriveKeyAndNonce(ECE_MODE_DECRYPT, recvPrivKey, senderPubKey,
authSecret, authSecretLen, salt, saltLen, key, nonce);
if (err) {
goto end;
}
err = ece_decrypt_records(key, nonce, rs, padSize, ciphertext, ciphertextLen,
unpad, plaintext, plaintextLen);
end:
EC_KEY_free(recvPrivKey);
EC_KEY_free(senderPubKey);
return err;
}
// Removes padding from a decrypted "aesgcm" block.
static int
ece_aesgcm_unpad(uint8_t* block, bool lastRecord, size_t* blockLen) {
ECE_UNUSED(lastRecord);
assert(*blockLen >= ECE_AESGCM_PAD_SIZE);
uint16_t padLen = ece_read_uint16_be(block);
if (padLen > *blockLen - ECE_AESGCM_PAD_SIZE) {
return ECE_ERROR_DECRYPT_PADDING;
}
size_t plaintextStart = ECE_AESGCM_PAD_SIZE + padLen;
for (size_t i = ECE_AESGCM_PAD_SIZE; i < plaintextStart; i++) {
if (block[i]) {
// All padding bytes must be zero.
return ECE_ERROR_DECRYPT_PADDING;
}
}
// Move the unpadded plaintext to the start of the block.
*blockLen -= plaintextStart;
memmove(block, &block[plaintextStart], *blockLen);
return ECE_OK;
}
// Removes padding from a decrypted "aes128gcm" block.
static int
ece_aes128gcm_unpad(uint8_t* block, bool lastRecord, size_t* blockLen) {
// Remove trailing padding.
while (*blockLen > 0) {
(*blockLen)--;
if (!block[*blockLen]) {
continue;
}
uint8_t padDelim = lastRecord ? 2 : 1;
if (block[*blockLen] != padDelim) {
// Last record needs to start padding with a 2; preceding records need
// to start padding with a 1.
return ECE_ERROR_DECRYPT_PADDING;
}
return ECE_OK;
}
// All zero plaintext.
return ECE_ERROR_ZERO_PLAINTEXT;
}
int
ece_webpush_generate_keys(uint8_t* rawRecvPrivKey, size_t rawRecvPrivKeyLen,
uint8_t* rawRecvPubKey, size_t rawRecvPubKeyLen,
uint8_t* authSecret, size_t authSecretLen) {
int err = ECE_OK;
EC_KEY* subKey = NULL;
// Generate a public-private ECDH key pair for the push subscription.
subKey = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1);
if (!subKey) {
err = ECE_ERROR_OUT_OF_MEMORY;
goto end;
}
if (EC_KEY_generate_key(subKey) != 1) {
err = ECE_ERROR_GENERATE_KEYS;
goto end;
}
if (!EC_KEY_priv2oct(subKey, rawRecvPrivKey, rawRecvPrivKeyLen)) {
err = ECE_ERROR_INVALID_PRIVATE_KEY;
goto end;
}
const EC_GROUP* subGrp = EC_KEY_get0_group(subKey);
const EC_POINT* rawSubPubKeyPt = EC_KEY_get0_public_key(subKey);
if (!EC_POINT_point2oct(subGrp, rawSubPubKeyPt, POINT_CONVERSION_UNCOMPRESSED,
rawRecvPubKey, rawRecvPubKeyLen, NULL)) {
err = ECE_ERROR_INVALID_PUBLIC_KEY;
goto end;
}
if (authSecretLen > INT_MAX ||
RAND_bytes(authSecret, (int) authSecretLen) != 1) {
err = ECE_ERROR_INVALID_AUTH_SECRET;
goto end;
}
end:
EC_KEY_free(subKey);
return err;
}
size_t
ece_aes128gcm_plaintext_max_length(const uint8_t* payload, size_t payloadLen) {
const uint8_t* salt;
size_t saltLen;
const uint8_t* keyId;
size_t keyIdLen;
uint32_t rs;
const uint8_t* ciphertext;
size_t ciphertextLen;
int err = ece_aes128gcm_payload_extract_params(
payload, payloadLen, &salt, &saltLen, &keyId, &keyIdLen, &rs, &ciphertext,
&ciphertextLen);
if (err) {
return 0;
}
return ece_plaintext_max_length(rs, ECE_AES128GCM_PAD_SIZE, ciphertextLen);
}
size_t
ece_aesgcm_plaintext_max_length(uint32_t rs, size_t ciphertextLen) {
rs = ece_aesgcm_rs(rs);
if (!rs) {
return 0;
}
return ece_plaintext_max_length(rs, ECE_AESGCM_PAD_SIZE, ciphertextLen);
}
int
ece_aes128gcm_decrypt(const uint8_t* ikm, size_t ikmLen, const uint8_t* payload,
size_t payloadLen, uint8_t* plaintext,
size_t* plaintextLen) {
const uint8_t* salt;
size_t saltLen;
const uint8_t* keyId;
size_t keyIdLen;
uint32_t rs;
const uint8_t* ciphertext;
size_t ciphertextLen;
int err = ece_aes128gcm_payload_extract_params(
payload, payloadLen, &salt, &saltLen, &keyId, &keyIdLen, &rs, &ciphertext,
&ciphertextLen);
if (err) {
return err;
}
uint8_t key[ECE_AES_KEY_LENGTH];
uint8_t nonce[ECE_NONCE_LENGTH];
err =
ece_aes128gcm_derive_key_and_nonce(salt, saltLen, ikm, ikmLen, key, nonce);
if (err) {
return err;
}
return ece_decrypt_records(key, nonce, rs, ECE_AES128GCM_PAD_SIZE, ciphertext,
ciphertextLen, &ece_aes128gcm_unpad, plaintext,
plaintextLen);
}
int
ece_webpush_aes128gcm_decrypt(const uint8_t* rawRecvPrivKey,
size_t rawRecvPrivKeyLen,
const uint8_t* authSecret, size_t authSecretLen,
const uint8_t* payload, size_t payloadLen,
uint8_t* plaintext, size_t* plaintextLen) {
const uint8_t* salt;
size_t saltLen;
const uint8_t* rawSenderPubKey;
size_t rawSenderPubKeyLen;
uint32_t rs;
const uint8_t* ciphertext;
size_t ciphertextLen;
int err = ece_aes128gcm_payload_extract_params(
payload, payloadLen, &salt, &saltLen, &rawSenderPubKey, &rawSenderPubKeyLen,
&rs, &ciphertext, &ciphertextLen);
if (err) {
return err;
}
return ece_webpush_decrypt(
rawRecvPrivKey, rawRecvPrivKeyLen, authSecret, authSecretLen, salt, saltLen,
rawSenderPubKey, rawSenderPubKeyLen, rs, ECE_AES128GCM_PAD_SIZE, ciphertext,
ciphertextLen, &ece_aes128gcm_needs_trailer,
&ece_webpush_aes128gcm_derive_key_and_nonce, &ece_aes128gcm_unpad,
plaintext, plaintextLen);
}
int
ece_webpush_aesgcm_decrypt(const uint8_t* rawRecvPrivKey,
size_t rawRecvPrivKeyLen, const uint8_t* authSecret,
size_t authSecretLen, const uint8_t* salt,
size_t saltLen, const uint8_t* rawSenderPubKey,
size_t rawSenderPubKeyLen, uint32_t rs,
const uint8_t* ciphertext, size_t ciphertextLen,
uint8_t* plaintext, size_t* plaintextLen) {
rs = ece_aesgcm_rs(rs);
if (!rs) {
return 0;
}
return ece_webpush_decrypt(
rawRecvPrivKey, rawRecvPrivKeyLen, authSecret, authSecretLen, salt, saltLen,
rawSenderPubKey, rawSenderPubKeyLen, rs, ECE_AESGCM_PAD_SIZE, ciphertext,
ciphertextLen, &ece_aesgcm_needs_trailer,
&ece_webpush_aesgcm_derive_key_and_nonce, &ece_aesgcm_unpad, plaintext,
plaintextLen);
}

617
mobile/ios/ThirdParty/ecec/src/encrypt.c vendored Normal file
View file

@ -0,0 +1,617 @@
#include "ece.h"
#include "ece/keys.h"
#include "ece/trailer.h"
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <openssl/ec.h>
#include <openssl/evp.h>
#include <openssl/rand.h>
typedef size_t (*min_block_pad_length_t)(size_t padLen, size_t maxBlockLen);
typedef int (*encrypt_block_t)(EVP_CIPHER_CTX* ctx,
const uint8_t* blockPlaintext,
size_t blockPlaintextLen, size_t blockPadLen,
bool lastRecord, uint8_t* record);
static const uint8_t pad = 0;
// Writes an unsigned 32-bit integer in network byte order.
static inline void
ece_write_uint32_be(uint8_t* bytes, uint32_t value) {
bytes[0] = (value >> 24) & 0xff;
bytes[1] = (value >> 16) & 0xff;
bytes[2] = (value >> 8) & 0xff;
bytes[3] = value & 0xff;
}
// Writes an unsigned 16-bit integer in network byte order.
static inline void
ece_write_uint16_be(uint8_t* bytes, uint16_t value) {
bytes[0] = (value >> 8) & 0xff;
bytes[1] = value & 0xff;
}
// Calculates the padding so that the block contains at least one plaintext
// byte.
static inline size_t
ece_min_block_pad_length(size_t padLen, size_t maxBlockLen) {
assert(maxBlockLen >= 1);
size_t blockPadLen = maxBlockLen - 1;
if (padLen && !blockPadLen) {
// If `maxBlockLen` is 1, we can only include 1 byte of data, so write
// the padding first.
blockPadLen++;
}
return blockPadLen > padLen ? padLen : blockPadLen;
}
// Calculates the padding for an "aesgcm" block. We still want one plaintext
// byte per block, but the padding length must fit into a `uint16_t`.
static size_t
ece_aesgcm_min_block_pad_length(size_t padLen, size_t maxBlockLen) {
size_t blockPadLen = ece_min_block_pad_length(padLen, maxBlockLen);
return blockPadLen > UINT16_MAX ? UINT16_MAX : blockPadLen;
}
// Calculates the maximum length of an encrypted ciphertext. This does not
// account for the "aes128gcm" header length.
static inline size_t
ece_ciphertext_max_length(uint32_t rs, size_t padSize, size_t padLen,
size_t plaintextLen, needs_trailer_t needsTrailer) {
// The per-record overhead for the padding delimiter and authentication tag.
// 17 for "aes128gcm", 18 for "aesgcm".
assert(padSize <= 2);
size_t overhead = padSize + ECE_TAG_LENGTH;
if (rs <= overhead) {
return 0;
}
if (padLen > SIZE_MAX - plaintextLen) {
return 0;
}
// The total length of the data to encrypt, including the plaintext and
// padding.
size_t dataLen = plaintextLen + padLen;
// The maximum length of data to include in each record, excluding the
// padding delimiter and authentication tag.
size_t maxBlockLen = rs - overhead;
// The total number of encrypted records.
assert(maxBlockLen >= 1);
size_t numRecords = dataLen / maxBlockLen;
if (plaintextLen % rs || needsTrailer(rs, plaintextLen)) {
// If the plaintext length doesn't fall on a record boundary, or if
// we need to write an empty trailing record, allocate space to hold
// an extra padding delimiter and authentication tag.
numRecords++;
}
if (numRecords > (SIZE_MAX - dataLen) / overhead) {
return 0;
}
return dataLen + (overhead * numRecords);
}
// Encrypts an "aes128gcm" block into `record`.
static int
ece_aes128gcm_encrypt_block(EVP_CIPHER_CTX* ctx, const uint8_t* blockPlaintext,
size_t blockPlaintextLen, size_t blockPadLen,
bool lastRecord, uint8_t* record) {
int chunkLen = -1;
// The plaintext block precedes the padding.
if (blockPlaintextLen > INT_MAX ||
EVP_EncryptUpdate(ctx, record, &chunkLen, blockPlaintext,
(int) blockPlaintextLen) != 1) {
return ECE_ERROR_ENCRYPT;
}
// The padding block comprises the delimiter, followed by zeros up to the end
// of the block.
uint8_t padDelim = lastRecord ? 2 : 1;
if (EVP_EncryptUpdate(ctx, &record[blockPlaintextLen], &chunkLen, &padDelim,
ECE_AES128GCM_PAD_SIZE) != 1) {
return ECE_ERROR_ENCRYPT;
}
for (size_t i = 0; i < blockPadLen; i++) {
if (EVP_EncryptUpdate(
ctx, &record[blockPlaintextLen + ECE_AES128GCM_PAD_SIZE + i],
&chunkLen, &pad, 1) != 1) {
return ECE_ERROR_ENCRYPT;
}
}
return ECE_OK;
}
// Encrypts an "aesgcm" block into `record`.
static int
ece_aesgcm_encrypt_block(EVP_CIPHER_CTX* ctx, const uint8_t* blockPlaintext,
size_t plaintextLen, size_t blockPadLen,
bool lastRecord, uint8_t* record) {
ECE_UNUSED(lastRecord);
int chunkLen = -1;
// The padding block comprises the padding length as a 16-bit integer,
// followed by that many zeros. We checked that the length fits into a
// `uint16_t` in `ece_aesgcm_min_block_pad_length`, so this cast is safe.
uint8_t padDelim[ECE_AESGCM_PAD_SIZE];
ece_write_uint16_be(padDelim, (uint16_t) blockPadLen);
if (EVP_EncryptUpdate(ctx, record, &chunkLen, padDelim,
ECE_AESGCM_PAD_SIZE) != 1) {
return ECE_ERROR_ENCRYPT;
}
for (size_t i = 0; i < blockPadLen; i++) {
if (EVP_EncryptUpdate(ctx, &record[ECE_AESGCM_PAD_SIZE + i], &chunkLen,
&pad, 1) != 1) {
return ECE_ERROR_ENCRYPT;
}
}
// The plaintext block follows the padding.
if (plaintextLen > INT_MAX ||
EVP_EncryptUpdate(ctx, &record[ECE_AESGCM_PAD_SIZE + blockPadLen],
&chunkLen, blockPlaintext, (int) plaintextLen) != 1) {
return ECE_ERROR_ENCRYPT;
}
return ECE_OK;
}
// A generic encryption function shared by "aesgcm" and "aes128gcm".
// `deriveKeyAndNonce`, `minBlockPadLen`, `encryptBlock`, and `needsTrailer`
// change depending on the scheme.
static int
ece_webpush_encrypt_plaintext(
EC_KEY* senderPrivKey, EC_KEY* recvPubKey, const uint8_t* authSecret,
size_t authSecretLen, const uint8_t* salt, size_t saltLen, uint32_t rs,
size_t padSize, size_t padLen, const uint8_t* plaintext, size_t plaintextLen,
derive_key_and_nonce_t deriveKeyAndNonce,
min_block_pad_length_t minBlockPadLen, encrypt_block_t encryptBlock,
needs_trailer_t needsTrailer, uint8_t* ciphertext, size_t* ciphertextLen) {
int err = ECE_OK;
EVP_CIPHER_CTX* ctx = NULL;
if (authSecretLen != ECE_WEBPUSH_AUTH_SECRET_LENGTH) {
err = ECE_ERROR_INVALID_AUTH_SECRET;
goto end;
}
if (saltLen != ECE_SALT_LENGTH) {
err = ECE_ERROR_INVALID_SALT;
goto end;
}
if (!plaintextLen) {
err = ECE_ERROR_ZERO_PLAINTEXT;
goto end;
}
// Make sure the ciphertext buffer is large enough to hold the ciphertext.
size_t maxCiphertextLen =
ece_ciphertext_max_length(rs, padSize, padLen, plaintextLen, needsTrailer);
if (!maxCiphertextLen) {
err = ECE_ERROR_INVALID_RS;
goto end;
}
if (*ciphertextLen < maxCiphertextLen) {
err = ECE_ERROR_OUT_OF_MEMORY;
goto end;
}
ctx = EVP_CIPHER_CTX_new();
if (!ctx) {
err = ECE_ERROR_OUT_OF_MEMORY;
goto end;
}
uint8_t key[ECE_AES_KEY_LENGTH];
uint8_t nonce[ECE_NONCE_LENGTH];
err = deriveKeyAndNonce(ECE_MODE_ENCRYPT, senderPrivKey, recvPubKey,
authSecret, ECE_WEBPUSH_AUTH_SECRET_LENGTH, salt,
ECE_SALT_LENGTH, key, nonce);
if (err) {
goto end;
}
assert(padSize <= 2);
size_t overhead = padSize + ECE_TAG_LENGTH;
// The maximum amount of plaintext and padding that will fit into a full
// block. The last block can be smaller.
assert(rs > overhead);
size_t maxBlockLen = rs - overhead;
// The offset at which to start reading the plaintext.
size_t plaintextStart = 0;
// The offset at which to start writing the ciphertext.
size_t ciphertextStart = 0;
// The record sequence number, used to generate the IV.
size_t counter = 0;
bool lastRecord = false;
while (!lastRecord) {
size_t blockPadLen = minBlockPadLen(padLen, maxBlockLen);
assert(blockPadLen <= padLen);
padLen -= blockPadLen;
// Fill the rest of the block with plaintext.
assert(blockPadLen <= maxBlockLen);
size_t maxBlockPlaintextLen = maxBlockLen - blockPadLen;
size_t plaintextEnd;
if (maxBlockPlaintextLen >= plaintextLen - plaintextStart) {
// Equivalent to `plaintextStart + maxBlockPlaintextLen >= plaintextLen`
// without overflow.
plaintextEnd = plaintextLen;
} else {
plaintextEnd = plaintextStart + maxBlockPlaintextLen;
}
// The length of the plaintext.
assert(plaintextEnd >= plaintextStart);
size_t blockPlaintextLen = plaintextEnd - plaintextStart;
// The length of the plaintext and padding. This should never overflow
// because `maxBlockPlaintextLen` accounts for `blockPadLen`.
assert(blockPlaintextLen <= maxBlockPlaintextLen);
size_t blockLen = blockPlaintextLen + blockPadLen;
// The length of the full encrypted record, including the plaintext,
// padding, padding delimiter, and auth tag. This should never overflow
// because `maxBlockLen` accounts for `overhead`.
assert(blockLen <= maxBlockLen);
size_t recordLen = blockLen + overhead;
size_t ciphertextEnd;
if (recordLen >= maxCiphertextLen - ciphertextStart) {
// Equivalent to `ciphertextStart + recordLen >= maxCiphertextLen`
// without overflow.
ciphertextEnd = maxCiphertextLen;
} else {
ciphertextEnd = ciphertextStart + recordLen;
}
assert(ciphertextEnd > ciphertextStart);
bool plaintextExhausted = plaintextEnd >= plaintextLen;
if (!padLen && plaintextExhausted && !needsTrailer(rs, ciphertextEnd)) {
// We've reached the last record when the padding and plaintext are
// exhausted, and we don't need to write an empty trailing record.
lastRecord = true;
}
if (!lastRecord && blockLen < maxBlockLen) {
// 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.
err = ECE_ERROR_ENCRYPT_PADDING;
goto end;
}
// Generate the IV for this record using the nonce.
uint8_t iv[ECE_NONCE_LENGTH];
ece_generate_iv(nonce, counter, iv);
if (EVP_EncryptInit_ex(ctx, EVP_aes_128_gcm(), NULL, key, iv) != 1) {
err = ECE_ERROR_ENCRYPT;
goto end;
}
// Encrypt and pad the block.
err = encryptBlock(ctx, &plaintext[plaintextStart], blockPlaintextLen,
blockPadLen, lastRecord, &ciphertext[ciphertextStart]);
if (err) {
err = ECE_ERROR_ENCRYPT;
goto end;
}
// OpenSSL requires us to finalize the encryption, but, since we're using a
// stream cipher, finalization shouldn't write out any bytes.
int chunkLen = -1;
assert(EVP_CIPHER_CTX_block_size(ctx) == 1);
if (EVP_EncryptFinal_ex(ctx, NULL, &chunkLen) != 1) {
err = ECE_ERROR_ENCRYPT;
goto end;
}
// Append the authentication tag.
if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, ECE_TAG_LENGTH,
&ciphertext[ciphertextEnd - ECE_TAG_LENGTH]) != 1) {
err = ECE_ERROR_ENCRYPT;
goto end;
}
if (EVP_CIPHER_CTX_reset(ctx) != 1) {
err = ECE_ERROR_ENCRYPT;
goto end;
}
plaintextStart = plaintextEnd;
ciphertextStart = ciphertextEnd;
counter++;
}
// Finally, set the actual ciphertext length.
*ciphertextLen = ciphertextStart;
end:
EVP_CIPHER_CTX_free(ctx);
return err;
}
// Encrypts a Web Push message using the "aes128gcm" scheme.
static int
ece_webpush_aes128gcm_encrypt_plaintext(
EC_KEY* senderPrivKey, EC_KEY* recvPubKey, const uint8_t* authSecret,
size_t authSecretLen, const uint8_t* salt, size_t saltLen, uint32_t rs,
size_t padLen, const uint8_t* plaintext, size_t plaintextLen,
uint8_t* payload, size_t* payloadLen) {
size_t headerLen =
ECE_AES128GCM_HEADER_LENGTH + ECE_WEBPUSH_PUBLIC_KEY_LENGTH;
if (*payloadLen < headerLen) {
return ECE_ERROR_OUT_OF_MEMORY;
}
// Write the header.
memcpy(payload, salt, ECE_SALT_LENGTH);
ece_write_uint32_be(&payload[ECE_SALT_LENGTH], rs);
payload[ECE_SALT_LENGTH + 4] = ECE_WEBPUSH_PUBLIC_KEY_LENGTH;
if (!EC_POINT_point2oct(
EC_KEY_get0_group(senderPrivKey), EC_KEY_get0_public_key(senderPrivKey),
POINT_CONVERSION_UNCOMPRESSED, &payload[ECE_AES128GCM_HEADER_LENGTH],
ECE_WEBPUSH_PUBLIC_KEY_LENGTH, NULL)) {
return ECE_ERROR_ENCODE_PUBLIC_KEY;
}
// Write the ciphertext.
size_t ciphertextLen = *payloadLen - headerLen;
int err = ece_webpush_encrypt_plaintext(
senderPrivKey, recvPubKey, authSecret, authSecretLen, salt, saltLen, rs,
ECE_AES128GCM_PAD_SIZE, padLen, plaintext, plaintextLen,
&ece_webpush_aes128gcm_derive_key_and_nonce, &ece_min_block_pad_length,
&ece_aes128gcm_encrypt_block, &ece_aes128gcm_needs_trailer,
&payload[headerLen], &ciphertextLen);
if (err) {
return err;
}
*payloadLen = headerLen + ciphertextLen;
return ECE_OK;
}
size_t
ece_aes128gcm_payload_max_length(uint32_t rs, size_t padLen,
size_t plaintextLen) {
size_t ciphertextLen =
ece_ciphertext_max_length(rs, ECE_AES128GCM_PAD_SIZE, padLen, plaintextLen,
&ece_aes128gcm_needs_trailer);
if (!ciphertextLen) {
return 0;
}
size_t maxHeaderLen =
ECE_AES128GCM_HEADER_LENGTH + ECE_AES128GCM_MAX_KEY_ID_LENGTH;
if (ciphertextLen > SIZE_MAX - maxHeaderLen) {
return 0;
}
return maxHeaderLen + ciphertextLen;
}
int
ece_webpush_aes128gcm_encrypt(const uint8_t* rawRecvPubKey,
size_t rawRecvPubKeyLen,
const uint8_t* authSecret, size_t authSecretLen,
uint32_t rs, size_t padLen,
const uint8_t* plaintext, size_t plaintextLen,
uint8_t* payload, size_t* payloadLen) {
int err = ECE_OK;
EC_KEY* recvPubKey = NULL;
EC_KEY* senderPrivKey = NULL;
// Generate a random salt.
uint8_t salt[ECE_SALT_LENGTH];
if (RAND_bytes(salt, ECE_SALT_LENGTH) != 1) {
err = ECE_ERROR_INVALID_SALT;
goto end;
}
// Import the receiver public key.
recvPubKey = ece_import_public_key(rawRecvPubKey, rawRecvPubKeyLen);
if (!recvPubKey) {
err = ECE_ERROR_INVALID_PUBLIC_KEY;
goto end;
}
// Generate the sender ECDH key pair.
senderPrivKey = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1);
if (!senderPrivKey) {
err = ECE_ERROR_OUT_OF_MEMORY;
goto end;
}
if (EC_KEY_generate_key(senderPrivKey) != 1) {
err = ECE_ERROR_INVALID_PRIVATE_KEY;
goto end;
}
// Encrypt the message.
err = ece_webpush_aes128gcm_encrypt_plaintext(
senderPrivKey, recvPubKey, authSecret, authSecretLen, salt, ECE_SALT_LENGTH,
rs, padLen, plaintext, plaintextLen, payload, payloadLen);
end:
EC_KEY_free(recvPubKey);
EC_KEY_free(senderPrivKey);
return err;
}
int
ece_webpush_aes128gcm_encrypt_with_keys(
const uint8_t* rawSenderPrivKey, size_t rawSenderPrivKeyLen,
const uint8_t* authSecret, size_t authSecretLen, const uint8_t* salt,
size_t saltLen, const uint8_t* rawRecvPubKey, size_t rawRecvPubKeyLen,
uint32_t rs, size_t padLen, const uint8_t* plaintext, size_t plaintextLen,
uint8_t* payload, size_t* payloadLen) {
int err = ECE_OK;
EC_KEY* senderPrivKey = NULL;
EC_KEY* recvPubKey = NULL;
senderPrivKey = ece_import_private_key(rawSenderPrivKey, rawSenderPrivKeyLen);
if (!senderPrivKey) {
err = ECE_ERROR_INVALID_PRIVATE_KEY;
goto end;
}
recvPubKey = ece_import_public_key(rawRecvPubKey, rawRecvPubKeyLen);
if (!recvPubKey) {
err = ECE_ERROR_INVALID_PUBLIC_KEY;
goto end;
}
err = ece_webpush_aes128gcm_encrypt_plaintext(
senderPrivKey, recvPubKey, authSecret, authSecretLen, salt, saltLen, rs,
padLen, plaintext, plaintextLen, payload, payloadLen);
end:
EC_KEY_free(senderPrivKey);
EC_KEY_free(recvPubKey);
return err;
}
size_t
ece_aesgcm_ciphertext_max_length(uint32_t rs, size_t padLen,
size_t plaintextLen) {
rs = ece_aesgcm_rs(rs);
if (!rs) {
return 0;
}
return ece_ciphertext_max_length(rs, ECE_AESGCM_PAD_SIZE, padLen,
plaintextLen, &ece_aesgcm_needs_trailer);
}
int
ece_webpush_aesgcm_encrypt(const uint8_t* rawRecvPubKey,
size_t rawRecvPubKeyLen, const uint8_t* authSecret,
size_t authSecretLen, uint32_t rs, size_t padLen,
const uint8_t* plaintext, size_t plaintextLen,
uint8_t* salt, size_t saltLen,
uint8_t* rawSenderPubKey, size_t rawSenderPubKeyLen,
uint8_t* ciphertext, size_t* ciphertextLen) {
int err = ECE_OK;
EC_KEY* recvPubKey = NULL;
EC_KEY* senderPrivKey = NULL;
rs = ece_aesgcm_rs(rs);
if (!rs) {
err = ECE_ERROR_INVALID_RS;
goto end;
}
if (saltLen != ECE_SALT_LENGTH) {
err = ECE_ERROR_INVALID_SALT;
goto end;
}
if (rawSenderPubKeyLen != ECE_WEBPUSH_PUBLIC_KEY_LENGTH) {
err = ECE_ERROR_INVALID_DH;
goto end;
}
// Generate a random salt.
if (saltLen > INT_MAX || RAND_bytes(salt, (int) saltLen) != 1) {
err = ECE_ERROR_INVALID_SALT;
goto end;
}
// Import the receiver public key.
recvPubKey = ece_import_public_key(rawRecvPubKey, rawRecvPubKeyLen);
if (!recvPubKey) {
err = ECE_ERROR_INVALID_PUBLIC_KEY;
goto end;
}
// Generate the sender ECDH key pair.
senderPrivKey = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1);
if (!senderPrivKey) {
err = ECE_ERROR_OUT_OF_MEMORY;
goto end;
}
if (EC_KEY_generate_key(senderPrivKey) != 1) {
err = ECE_ERROR_INVALID_PRIVATE_KEY;
goto end;
}
if (!EC_POINT_point2oct(EC_KEY_get0_group(senderPrivKey),
EC_KEY_get0_public_key(senderPrivKey),
POINT_CONVERSION_UNCOMPRESSED, rawSenderPubKey,
rawSenderPubKeyLen, NULL)) {
err = ECE_ERROR_ENCODE_PUBLIC_KEY;
goto end;
}
err = ece_webpush_encrypt_plaintext(
senderPrivKey, recvPubKey, authSecret, authSecretLen, salt, saltLen, rs,
ECE_AESGCM_PAD_SIZE, padLen, plaintext, plaintextLen,
&ece_webpush_aesgcm_derive_key_and_nonce, &ece_aesgcm_min_block_pad_length,
&ece_aesgcm_encrypt_block, &ece_aesgcm_needs_trailer, ciphertext,
ciphertextLen);
end:
EC_KEY_free(recvPubKey);
EC_KEY_free(senderPrivKey);
return err;
}
int
ece_webpush_aesgcm_encrypt_with_keys(
const uint8_t* rawSenderPrivKey, size_t rawSenderPrivKeyLen,
const uint8_t* authSecret, size_t authSecretLen, const uint8_t* salt,
size_t saltLen, const uint8_t* rawRecvPubKey, size_t rawRecvPubKeyLen,
uint32_t rs, size_t padLen, const uint8_t* plaintext, size_t plaintextLen,
uint8_t* ciphertext, size_t* ciphertextLen) {
int err = ECE_OK;
EC_KEY* senderPrivKey = NULL;
EC_KEY* recvPubKey = NULL;
rs = ece_aesgcm_rs(rs);
if (!rs) {
err = ECE_ERROR_INVALID_RS;
goto end;
}
senderPrivKey = ece_import_private_key(rawSenderPrivKey, rawSenderPrivKeyLen);
if (!senderPrivKey) {
err = ECE_ERROR_INVALID_PRIVATE_KEY;
goto end;
}
recvPubKey = ece_import_public_key(rawRecvPubKey, rawRecvPubKeyLen);
if (!recvPubKey) {
err = ECE_ERROR_INVALID_PUBLIC_KEY;
goto end;
}
err = ece_webpush_encrypt_plaintext(
senderPrivKey, recvPubKey, authSecret, authSecretLen, salt, saltLen, rs,
ECE_AESGCM_PAD_SIZE, padLen, plaintext, plaintextLen,
&ece_webpush_aesgcm_derive_key_and_nonce, &ece_aesgcm_min_block_pad_length,
&ece_aesgcm_encrypt_block, &ece_aesgcm_needs_trailer, ciphertext,
ciphertextLen);
end:
EC_KEY_free(senderPrivKey);
EC_KEY_free(recvPubKey);
return err;
}

410
mobile/ios/ThirdParty/ecec/src/keys.c vendored Normal file
View file

@ -0,0 +1,410 @@
#include "ece/keys.h"
#include "ece.h"
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <openssl/evp.h>
#include <openssl/kdf.h>
// Writes an unsigned 16-bit integer in network byte order.
static inline void
ece_write_uint16_be(uint8_t* bytes, uint16_t value) {
bytes[0] = (value >> 8) & 0xff;
bytes[1] = value & 0xff;
}
// Extracts an unsigned 64-bit integer in network byte order.
static inline uint64_t
ece_read_uint64_be(const uint8_t* bytes) {
uint64_t value = bytes[7];
value |= (uint64_t) bytes[6] << 8;
value |= (uint64_t) bytes[5] << 16;
value |= (uint64_t) bytes[4] << 24;
value |= (uint64_t) bytes[3] << 32;
value |= (uint64_t) bytes[2] << 40;
value |= (uint64_t) bytes[1] << 48;
value |= (uint64_t) bytes[0] << 56;
return value;
}
// Writes an unsigned 64-bit integer in network byte order.
static inline void
ece_write_uint64_be(uint8_t* bytes, uint64_t value) {
bytes[0] = (value >> 56) & 0xff;
bytes[1] = (value >> 48) & 0xff;
bytes[2] = (value >> 40) & 0xff;
bytes[3] = (value >> 32) & 0xff;
bytes[4] = (value >> 24) & 0xff;
bytes[5] = (value >> 16) & 0xff;
bytes[6] = (value >> 8) & 0xff;
bytes[7] = value & 0xff;
}
void
ece_generate_iv(const uint8_t* nonce, uint64_t counter, uint8_t* iv) {
// Copy the first 4 bytes as-is, since `(x ^ 0) == x`.
size_t offset = ECE_NONCE_LENGTH - 8;
memcpy(iv, nonce, offset);
// Combine the remaining unsigned 64-bit integer with the record sequence
// number using XOR. See the "nonce derivation" section of the draft.
uint64_t mask = ece_read_uint64_be(&nonce[offset]);
ece_write_uint64_be(&iv[offset], mask ^ counter);
}
EC_KEY*
ece_import_private_key(const uint8_t* rawKey, size_t rawKeyLen) {
EC_KEY* key = NULL;
EC_POINT* pubKeyPt = NULL;
key = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1);
if (!key) {
goto error;
}
if (EC_KEY_oct2priv(key, rawKey, rawKeyLen) != 1) {
goto error;
}
const EC_GROUP* group = EC_KEY_get0_group(key);
pubKeyPt = EC_POINT_new(group);
if (!pubKeyPt) {
goto error;
}
const BIGNUM* privKey = EC_KEY_get0_private_key(key);
if (EC_POINT_mul(group, pubKeyPt, privKey, NULL, NULL, NULL) != 1) {
goto error;
}
if (EC_KEY_set_public_key(key, pubKeyPt) != 1) {
goto error;
}
goto end;
error:
EC_KEY_free(key);
key = NULL;
end:
EC_POINT_free(pubKeyPt);
return key;
}
EC_KEY*
ece_import_public_key(const uint8_t* rawKey, size_t rawKeyLen) {
EC_KEY* key = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1);
if (!key) {
return NULL;
}
if (EC_KEY_oct2key(key, rawKey, rawKeyLen, NULL) != 1) {
EC_KEY_free(key);
return NULL;
}
return key;
}
// HKDF from RFC 5869: `HKDF-Expand(HKDF-Extract(salt, ikm), info, length)`.
static int
ece_hkdf_sha256(const void* salt, size_t saltLen, const void* ikm,
size_t ikmLen, const void* info, size_t infoLen,
uint8_t* output, size_t outputLen) {
int err = ECE_OK;
EVP_PKEY_CTX* ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL);
if (!ctx) {
err = ECE_ERROR_HKDF;
goto end;
}
if (EVP_PKEY_derive_init(ctx) != 1) {
err = ECE_ERROR_HKDF;
goto end;
}
if (EVP_PKEY_CTX_set_hkdf_md(ctx, EVP_sha256()) != 1) {
err = ECE_ERROR_HKDF;
goto end;
}
if (saltLen > INT_MAX ||
EVP_PKEY_CTX_set1_hkdf_salt(ctx, salt, (int) saltLen) != 1) {
err = ECE_ERROR_HKDF;
goto end;
}
if (ikmLen > INT_MAX ||
EVP_PKEY_CTX_set1_hkdf_key(ctx, ikm, (int) ikmLen) != 1) {
err = ECE_ERROR_HKDF;
goto end;
}
if (infoLen > INT_MAX ||
EVP_PKEY_CTX_add1_hkdf_info(ctx, info, (int) infoLen) != 1) {
err = ECE_ERROR_HKDF;
goto end;
}
if (EVP_PKEY_derive(ctx, output, &outputLen) != 1) {
err = ECE_ERROR_HKDF;
goto end;
}
end:
EVP_PKEY_CTX_free(ctx);
return err;
}
// Computes the ECDH shared secret, used as the input key material (IKM) for
// HKDF.
static uint8_t*
ece_compute_secret(EC_KEY* privKey, EC_KEY* pubKey, size_t* sharedSecretLen) {
uint8_t* sharedSecret = NULL;
const EC_GROUP* group = EC_KEY_get0_group(privKey);
const EC_POINT* pubKeyPt = EC_KEY_get0_public_key(pubKey);
*sharedSecretLen = (size_t)((EC_GROUP_get_degree(group) + 7) / 8);
sharedSecret = calloc(*sharedSecretLen, sizeof(uint8_t));
if (!sharedSecret) {
goto error;
}
if (ECDH_compute_key(sharedSecret, *sharedSecretLen, pubKeyPt, privKey,
NULL) <= 0) {
goto error;
}
goto end;
error:
free(sharedSecret);
sharedSecret = NULL;
*sharedSecretLen = 0;
end:
return sharedSecret;
}
// The "aes128gcm" IKM info string is "WebPush: info\0", followed by the
// receiver and sender public keys.
static int
ece_webpush_aes128gcm_generate_info(EC_KEY* recvKey, EC_KEY* senderKey,
const char* prefix, size_t prefixLen,
uint8_t* info) {
size_t offset = 0;
// Copy the prefix.
memcpy(info, prefix, prefixLen);
offset += prefixLen;
// Copy the receiver public key.
const EC_GROUP* recvGrp = EC_KEY_get0_group(recvKey);
const EC_POINT* recvPubKeyPt = EC_KEY_get0_public_key(recvKey);
size_t recvPubKeyLen =
EC_POINT_point2oct(recvGrp, recvPubKeyPt, POINT_CONVERSION_UNCOMPRESSED,
&info[offset], ECE_WEBPUSH_PUBLIC_KEY_LENGTH, NULL);
if (!recvPubKeyLen) {
return ECE_ERROR_ENCODE_PUBLIC_KEY;
}
offset += recvPubKeyLen;
// Copy the sender public key.
const EC_GROUP* senderGrp = EC_KEY_get0_group(senderKey);
const EC_POINT* senderPubKeyPt = EC_KEY_get0_public_key(senderKey);
size_t senderPubKeyLen =
EC_POINT_point2oct(senderGrp, senderPubKeyPt, POINT_CONVERSION_UNCOMPRESSED,
&info[offset], ECE_WEBPUSH_PUBLIC_KEY_LENGTH, NULL);
if (!senderPubKeyLen) {
return ECE_ERROR_ENCODE_PUBLIC_KEY;
}
return ECE_OK;
}
int
ece_aes128gcm_derive_key_and_nonce(const uint8_t* salt, size_t saltLen,
const uint8_t* ikm, size_t ikmLen,
uint8_t* key, uint8_t* nonce) {
int err =
ece_hkdf_sha256(salt, saltLen, ikm, ikmLen, ECE_AES128GCM_KEY_INFO,
ECE_AES128GCM_KEY_INFO_LENGTH, key, ECE_AES_KEY_LENGTH);
if (err) {
return err;
}
return ece_hkdf_sha256(salt, saltLen, ikm, ikmLen, ECE_AES128GCM_NONCE_INFO,
ECE_AES128GCM_NONCE_INFO_LENGTH, nonce,
ECE_NONCE_LENGTH);
}
int
ece_webpush_aes128gcm_derive_key_and_nonce(ece_mode_t mode, EC_KEY* localKey,
EC_KEY* remoteKey,
const uint8_t* authSecret,
size_t authSecretLen,
const uint8_t* salt, size_t saltLen,
uint8_t* key, uint8_t* nonce) {
int err = ECE_OK;
uint8_t* sharedSecret = NULL;
size_t sharedSecretLen = 0;
sharedSecret = ece_compute_secret(localKey, remoteKey, &sharedSecretLen);
if (!sharedSecret) {
err = ECE_ERROR_COMPUTE_SECRET;
goto end;
}
// The new "aes128gcm" scheme includes the sender and receiver public keys in
// the info string when deriving the Web Push IKM.
uint8_t ikmInfo[ECE_WEBPUSH_AES128GCM_IKM_INFO_LENGTH];
switch (mode) {
case ECE_MODE_ENCRYPT:
// For encryption, the remote static public key is the receiver key, and the
// local ephemeral private key is the sender key.
err = ece_webpush_aes128gcm_generate_info(
remoteKey, localKey, ECE_WEBPUSH_AES128GCM_IKM_INFO_PREFIX,
ECE_WEBPUSH_AES128GCM_IKM_INFO_PREFIX_LENGTH, ikmInfo);
break;
case ECE_MODE_DECRYPT:
// For decryption, the local static private key is the receiver key, and the
// remote ephemeral public key is the sender key.
err = ece_webpush_aes128gcm_generate_info(
localKey, remoteKey, ECE_WEBPUSH_AES128GCM_IKM_INFO_PREFIX,
ECE_WEBPUSH_AES128GCM_IKM_INFO_PREFIX_LENGTH, ikmInfo);
break;
default:
assert(false);
err = ECE_ERROR_DECRYPT;
}
if (err) {
goto end;
}
uint8_t ikm[ECE_WEBPUSH_IKM_LENGTH];
err = ece_hkdf_sha256(
authSecret, authSecretLen, sharedSecret, sharedSecretLen, ikmInfo,
ECE_WEBPUSH_AES128GCM_IKM_INFO_LENGTH, ikm, ECE_WEBPUSH_IKM_LENGTH);
if (err) {
goto end;
}
err = ece_aes128gcm_derive_key_and_nonce(salt, saltLen, ikm,
ECE_WEBPUSH_IKM_LENGTH, key, nonce);
end:
free(sharedSecret);
return err;
}
// The "aesgcm" info string is "Content-Encoding: <aesgcm | nonce>\0P-256\0",
// followed by the length-prefixed (unsigned 16-bit integers) receiver and
// sender public keys.
static int
ece_webpush_aesgcm_generate_info(EC_KEY* recvKey, EC_KEY* senderKey,
const char* prefix, size_t prefixLen,
uint8_t* info) {
size_t offset = 0;
// Copy the prefix.
memcpy(info, prefix, prefixLen);
offset += prefixLen;
// Copy the length-prefixed receiver public key.
ece_write_uint16_be(&info[offset], ECE_WEBPUSH_PUBLIC_KEY_LENGTH);
offset += 2;
const EC_GROUP* recvGrp = EC_KEY_get0_group(recvKey);
const EC_POINT* recvPubKeyPt = EC_KEY_get0_public_key(recvKey);
size_t recvPubKeyLen =
EC_POINT_point2oct(recvGrp, recvPubKeyPt, POINT_CONVERSION_UNCOMPRESSED,
&info[offset], ECE_WEBPUSH_PUBLIC_KEY_LENGTH, NULL);
if (!recvPubKeyLen) {
return ECE_ERROR_ENCODE_PUBLIC_KEY;
}
offset += recvPubKeyLen;
// Copy the length-prefixed sender public key.
ece_write_uint16_be(&info[offset], ECE_WEBPUSH_PUBLIC_KEY_LENGTH);
offset += 2;
const EC_GROUP* senderGrp = EC_KEY_get0_group(senderKey);
const EC_POINT* senderPubKeyPt = EC_KEY_get0_public_key(senderKey);
size_t senderPubKeyLen =
EC_POINT_point2oct(senderGrp, senderPubKeyPt, POINT_CONVERSION_UNCOMPRESSED,
&info[offset], ECE_WEBPUSH_PUBLIC_KEY_LENGTH, NULL);
if (!senderPubKeyLen) {
return ECE_ERROR_ENCODE_PUBLIC_KEY;
}
return ECE_OK;
}
int
ece_webpush_aesgcm_derive_key_and_nonce(ece_mode_t mode, EC_KEY* localKey,
EC_KEY* remoteKey,
const uint8_t* authSecret,
size_t authSecretLen,
const uint8_t* salt, size_t saltLen,
uint8_t* key, uint8_t* nonce) {
ECE_UNUSED(mode);
int err = ECE_OK;
uint8_t* sharedSecret = NULL;
size_t sharedSecretLen = 0;
sharedSecret = ece_compute_secret(localKey, remoteKey, &sharedSecretLen);
if (!sharedSecret) {
err = ECE_ERROR_COMPUTE_SECRET;
goto end;
}
// The old "aesgcm" scheme uses a static info string to derive the Web Push
// IKM.
uint8_t ikm[ECE_WEBPUSH_IKM_LENGTH];
err = ece_hkdf_sha256(authSecret, authSecretLen, sharedSecret,
sharedSecretLen, ECE_WEBPUSH_AESGCM_IKM_INFO,
ECE_WEBPUSH_AESGCM_IKM_INFO_LENGTH, ikm,
ECE_WEBPUSH_IKM_LENGTH);
if (err) {
goto end;
}
// Next, derive the AES decryption key and nonce. We include the sender and
// receiver public keys in the info strings.
uint8_t keyInfo[ECE_WEBPUSH_AESGCM_KEY_INFO_LENGTH];
uint8_t nonceInfo[ECE_WEBPUSH_AESGCM_NONCE_INFO_LENGTH];
switch (mode) {
case ECE_MODE_ENCRYPT:
err = ece_webpush_aesgcm_generate_info(
remoteKey, localKey, ECE_WEBPUSH_AESGCM_KEY_INFO_PREFIX,
ECE_WEBPUSH_AESGCM_KEY_INFO_PREFIX_LENGTH, keyInfo);
if (err) {
break;
}
err = ece_webpush_aesgcm_generate_info(
remoteKey, localKey, ECE_WEBPUSH_AESGCM_NONCE_INFO_PREFIX,
ECE_WEBPUSH_AESGCM_NONCE_INFO_PREFIX_LENGTH, nonceInfo);
break;
case ECE_MODE_DECRYPT:
err = ece_webpush_aesgcm_generate_info(
localKey, remoteKey, ECE_WEBPUSH_AESGCM_KEY_INFO_PREFIX,
ECE_WEBPUSH_AESGCM_KEY_INFO_PREFIX_LENGTH, keyInfo);
if (err) {
break;
}
err = ece_webpush_aesgcm_generate_info(
localKey, remoteKey, ECE_WEBPUSH_AESGCM_NONCE_INFO_PREFIX,
ECE_WEBPUSH_AESGCM_NONCE_INFO_PREFIX_LENGTH, nonceInfo);
break;
default:
assert(false);
err = ECE_ERROR_DECRYPT;
}
if (err) {
goto end;
}
err = ece_hkdf_sha256(salt, saltLen, ikm, ECE_WEBPUSH_IKM_LENGTH, keyInfo,
ECE_WEBPUSH_AESGCM_KEY_INFO_LENGTH, key,
ECE_AES_KEY_LENGTH);
if (err) {
goto end;
}
err = ece_hkdf_sha256(salt, saltLen, ikm, ECE_WEBPUSH_IKM_LENGTH, nonceInfo,
ECE_WEBPUSH_AESGCM_NONCE_INFO_LENGTH, nonce,
ECE_NONCE_LENGTH);
end:
free(sharedSecret);
return err;
}

574
mobile/ios/ThirdParty/ecec/src/params.c vendored Normal file
View file

@ -0,0 +1,574 @@
#include "ece.h"
// This file implements a parser for the `Crypto-Key` and `Encryption` HTTP
// headers, used by the older "aesgcm" encoding. The newer "aes128gcm" encoding
// includes the relevant information in a binary header, directly in the
// payload.
#include <assert.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define ECE_HEADER_STATE_BEGIN_PARAM 1
#define ECE_HEADER_STATE_BEGIN_NAME 2
#define ECE_HEADER_STATE_NAME 3
#define ECE_HEADER_STATE_END_NAME 4
#define ECE_HEADER_STATE_BEGIN_VALUE 5
#define ECE_HEADER_STATE_VALUE 6
#define ECE_HEADER_STATE_BEGIN_QUOTED_VALUE 7
#define ECE_HEADER_STATE_QUOTED_VALUE 8
#define ECE_HEADER_STATE_END_VALUE 9
#define ECE_HEADER_STATE_INVALID_HEADER 10
#define ECE_HEADER_DH_PREFIX "dh="
#define ECE_HEADER_DH_PREFIX_LENGTH 3
// Extracts an unsigned 32-bit integer in network byte order.
static inline uint32_t
ece_read_uint32_be(const uint8_t* bytes) {
uint32_t value = bytes[3];
value |= (uint32_t) bytes[2] << 8;
value |= (uint32_t) bytes[1] << 16;
value |= (uint32_t) bytes[0] << 24;
return value;
}
// A linked list that holds name-value pairs for a parameter in a header
// value. For example, if the parameter is `a=b; c=d; e=f`, the parser will
// allocate three `ece_header_pairs_t` structures, one for each ;-delimited
// pair. "=" separates the name and value.
typedef struct ece_header_pairs_s {
struct ece_header_pairs_s* next;
// The name and value are pointers into the backing header value; the parser
// doesn't allocate new strings. Freeing the backing string will invalidate
// all `name` and `value` references. Also, because these are not true C
// strings, it's important to use them with functions that take a length, like
// `strncmp`. Functions that assume a NUL-terminated string will read until
// the end of the backing string.
const char* name;
const char* value;
size_t nameLen;
size_t valueLen;
} ece_header_pairs_t;
// Initializes a name-value pair node at the head of the pair list. `head` may
// be `NULL`.
static ece_header_pairs_t*
ece_header_pairs_alloc(ece_header_pairs_t* head) {
ece_header_pairs_t* pairs = malloc(sizeof(ece_header_pairs_t));
if (!pairs) {
return NULL;
}
pairs->next = head;
pairs->name = NULL;
pairs->value = NULL;
pairs->nameLen = 0;
pairs->valueLen = 0;
return pairs;
}
// Indicates whether a name-value pair node matches the `name`.
static inline bool
ece_header_pairs_has_name(ece_header_pairs_t* pair, const char* name) {
return !strncmp(pair->name, name, pair->nameLen);
}
// Indicates whether a name-value pair node matches the `value`.
static inline bool
ece_header_pairs_has_value(ece_header_pairs_t* pair, const char* value) {
return !strncmp(pair->value, value, pair->valueLen);
}
// Copies a pair node's value into a C string.
static char*
ece_header_pairs_value_to_str(ece_header_pairs_t* pair) {
char* value = malloc(pair->valueLen + 1);
if (!value) {
return NULL;
}
strncpy(value, pair->value, pair->valueLen);
value[pair->valueLen] = '\0';
return value;
}
// Frees a name-value pair list and all its nodes.
static void
ece_header_pairs_free(ece_header_pairs_t* pairs) {
ece_header_pairs_t* pair = pairs;
while (pair) {
ece_header_pairs_t* next = pair->next;
free(pair);
pair = next;
}
}
// A linked list that holds parameters extracted from a header value. For
// example, if the header value is `a=b; c=d, e=f; g=h`, the parser will
// allocate two `ece_header_params_t` structures: one to hold the parameter
// `a=b; c=d`, and the other to hold `e=f; g=h`.
typedef struct ece_header_params_s {
struct ece_header_params_s* next;
ece_header_pairs_t* pairs;
} ece_header_params_t;
// Initializes a parameter node at the head of the parameter list. `head` may be
// `NULL`.
static ece_header_params_t*
ece_header_params_alloc(ece_header_params_t* head) {
ece_header_params_t* params = malloc(sizeof(ece_header_params_t));
if (!params) {
return NULL;
}
params->next = head;
params->pairs = NULL;
return params;
}
// Reverses a parameter list in-place and returns a pointer to the new head.
static ece_header_params_t*
ece_header_params_reverse(ece_header_params_t* params) {
ece_header_params_t* sibling = NULL;
while (params) {
ece_header_params_t* next = params->next;
params->next = sibling;
sibling = params;
params = next;
}
return sibling;
}
// Frees a parameter list and all its nodes.
static void
ece_header_params_free(ece_header_params_t* params) {
ece_header_params_t* param = params;
while (param) {
ece_header_pairs_free(param->pairs);
ece_header_params_t* next = param->next;
free(param);
param = next;
}
}
// Indicates whether `c` is whitespace, per `WSP` in RFC 5234, Appendix B.1.
static inline bool
ece_header_is_space(char c) {
return c == ' ' || c == '\t';
}
// Indicates whether `c` can appear in a pair name. Only lowercase letters and
// numbers are allowed.
static inline bool
ece_header_is_valid_pair_name(char c) {
return (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9');
}
// Indicates whether `c` can appear in a pair value. This includes all
// characters in the Base64url alphabet.
static inline bool
ece_header_is_valid_pair_value(char c) {
return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') ||
(c >= '0' && c <= '9') || c == '-' || c == '_';
}
// A header parameter parser.
typedef struct ece_header_parser_s {
int state;
ece_header_params_t* params;
} ece_header_parser_t;
// Parses the next token in `input` and updates the parser state. Returns true
// if the caller should advance to the next character; false otherwise.
static bool
ece_header_parse(ece_header_parser_t* parser, const char* input) {
switch (parser->state) {
case ECE_HEADER_STATE_BEGIN_PARAM: {
ece_header_params_t* param = ece_header_params_alloc(parser->params);
if (!param) {
break;
}
parser->params = param;
parser->state = ECE_HEADER_STATE_BEGIN_NAME;
return false;
}
case ECE_HEADER_STATE_BEGIN_NAME:
if (ece_header_is_space(*input)) {
return true;
}
if (ece_header_is_valid_pair_name(*input)) {
ece_header_pairs_t* pair = ece_header_pairs_alloc(parser->params->pairs);
if (!pair) {
break;
}
parser->params->pairs = pair;
pair->name = input;
parser->state = ECE_HEADER_STATE_NAME;
return false;
}
break;
case ECE_HEADER_STATE_NAME:
if (ece_header_is_valid_pair_name(*input)) {
parser->params->pairs->nameLen++;
return true;
}
if (ece_header_is_space(*input) || *input == '=') {
parser->state = ECE_HEADER_STATE_END_NAME;
return false;
}
break;
case ECE_HEADER_STATE_END_NAME:
if (ece_header_is_space(*input)) {
return true;
}
if (*input == '=') {
parser->state = ECE_HEADER_STATE_BEGIN_VALUE;
return true;
}
break;
case ECE_HEADER_STATE_BEGIN_VALUE:
if (ece_header_is_space(*input)) {
return true;
}
if (ece_header_is_valid_pair_value(*input)) {
parser->params->pairs->value = input;
parser->state = ECE_HEADER_STATE_VALUE;
return false;
}
if (*input == '"') {
parser->state = ECE_HEADER_STATE_BEGIN_QUOTED_VALUE;
return true;
}
break;
case ECE_HEADER_STATE_VALUE:
if (ece_header_is_space(*input) || *input == ';' || *input == ',') {
parser->state = ECE_HEADER_STATE_END_VALUE;
return false;
}
if (ece_header_is_valid_pair_value(*input)) {
parser->params->pairs->valueLen++;
return true;
}
break;
case ECE_HEADER_STATE_BEGIN_QUOTED_VALUE:
if (ece_header_is_valid_pair_value(*input)) {
// Quoted strings allow spaces and escapes, but neither `Crypto-Key` nor
// `Encryption` accept them. We keep the parser simple by rejecting
// non-Base64url characters here. We also disallow empty quoted strings.
parser->params->pairs->value = input;
parser->params->pairs->valueLen++;
parser->state = ECE_HEADER_STATE_QUOTED_VALUE;
return true;
}
break;
case ECE_HEADER_STATE_QUOTED_VALUE:
if (ece_header_is_valid_pair_value(*input)) {
parser->params->pairs->valueLen++;
return true;
}
if (*input == '"') {
parser->state = ECE_HEADER_STATE_END_VALUE;
return true;
}
break;
case ECE_HEADER_STATE_END_VALUE:
if (ece_header_is_space(*input)) {
return true;
}
if (*input == ';') {
// New name-value pair for the same parameter. Advance the parser;
// `ECE_HEADER_STATE_BEGIN_NAME` will prepend a new node to the pairs
// list.
parser->state = ECE_HEADER_STATE_BEGIN_NAME;
return true;
}
if (*input == ',') {
// New parameter. Advance the parser; `ECE_HEADER_STATE_BEGIN_PARAM` will
// prepend a new node to the parameters list and begin parsing its pairs.
parser->state = ECE_HEADER_STATE_BEGIN_PARAM;
return true;
}
break;
default:
// Unexpected parser state.
assert(false);
}
parser->state = ECE_HEADER_STATE_INVALID_HEADER;
return false;
}
// Parses a `header` value of the form `a=b; c=d; e=f, g=h, i=j` into a
// parameter list.
static ece_header_params_t*
ece_header_extract_params(const char* header) {
ece_header_parser_t parser;
parser.state = ECE_HEADER_STATE_BEGIN_PARAM;
parser.params = NULL;
const char* input = header;
while (*input) {
if (ece_header_parse(&parser, input)) {
input++;
}
if (parser.state == ECE_HEADER_STATE_INVALID_HEADER) {
goto error;
}
}
if (parser.state != ECE_HEADER_STATE_END_VALUE) {
// If the header ends with an unquoted value, the parser might still be in a
// non-terminal state. Try to parse an extra space to reach the terminal
// state.
ece_header_parse(&parser, " ");
if (parser.state != ECE_HEADER_STATE_END_VALUE) {
// If we're still in a non-terminal state, the header is incomplete.
goto error;
}
}
return ece_header_params_reverse(parser.params);
error:
ece_header_params_free(parser.params);
return NULL;
}
int
ece_aes128gcm_payload_extract_params(const uint8_t* payload, size_t payloadLen,
const uint8_t** salt, size_t* saltLen,
const uint8_t** keyId, size_t* keyIdLen,
uint32_t* rs, const uint8_t** ciphertext,
size_t* ciphertextLen) {
if (payloadLen < ECE_AES128GCM_HEADER_LENGTH) {
return ECE_ERROR_SHORT_HEADER;
}
*saltLen = ECE_SALT_LENGTH;
*keyIdLen = payload[ECE_SALT_LENGTH + 4];
if (payloadLen < ECE_AES128GCM_HEADER_LENGTH + *keyIdLen) {
return ECE_ERROR_SHORT_HEADER;
}
*rs = ece_read_uint32_be(&payload[ECE_SALT_LENGTH]);
if (*rs < ECE_AES128GCM_MIN_RS) {
return ECE_ERROR_INVALID_RS;
}
size_t payloadStart = ECE_AES128GCM_HEADER_LENGTH + *keyIdLen;
*ciphertextLen = payloadLen - payloadStart;
if (!(*ciphertextLen)) {
return ECE_ERROR_ZERO_CIPHERTEXT;
}
*salt = payload;
if (keyIdLen) {
*keyId = &payload[ECE_AES128GCM_HEADER_LENGTH];
} else {
*keyId = NULL;
}
*ciphertext = &payload[payloadStart];
return ECE_OK;
}
int
ece_webpush_aesgcm_headers_extract_params(const char* cryptoKeyHeader,
const char* encryptionHeader,
uint8_t* salt, size_t saltLen,
uint8_t* rawSenderPubKey,
size_t rawSenderPubKeyLen,
uint32_t* rs) {
int err = ECE_OK;
ece_header_params_t* encryptionParams = NULL;
ece_header_params_t* cryptoKeyParams = NULL;
char* keyId = NULL;
uint32_t rsValue = 0;
size_t decodedSaltLen = 0;
size_t decodedKeyLen = 0;
// First, extract the key ID, salt, and record size from the first key in the
// `Encryption` header.
encryptionParams = ece_header_extract_params(encryptionHeader);
if (!encryptionParams) {
err = ECE_ERROR_INVALID_ENCRYPTION_HEADER;
goto end;
}
for (ece_header_pairs_t* pair = encryptionParams->pairs; pair;
pair = pair->next) {
if (ece_header_pairs_has_name(pair, "keyid")) {
// The key ID is optional, and is used to identify the public key in the
// `Crypto-Key` header if multiple encryption keys are specified.
if (keyId) {
err = ECE_ERROR_INVALID_ENCRYPTION_HEADER;
goto end;
}
keyId = ece_header_pairs_value_to_str(pair);
if (!keyId) {
err = ECE_ERROR_OUT_OF_MEMORY;
goto end;
}
continue;
}
if (ece_header_pairs_has_name(pair, "rs")) {
// The record size is optional.
if (rsValue) {
err = ECE_ERROR_INVALID_ENCRYPTION_HEADER;
goto end;
}
char* value = ece_header_pairs_value_to_str(pair);
if (!value) {
err = ECE_ERROR_OUT_OF_MEMORY;
goto end;
}
int result = sscanf(value, "%" SCNu32, &rsValue);
free(value);
if (result <= 0 || rsValue < ECE_AESGCM_MIN_RS) {
err = ECE_ERROR_INVALID_RS;
goto end;
}
continue;
}
if (ece_header_pairs_has_name(pair, "salt")) {
// The salt is required, and must be Base64url-encoded without padding.
if (decodedSaltLen) {
err = ECE_ERROR_INVALID_ENCRYPTION_HEADER;
goto end;
}
decodedSaltLen =
ece_base64url_decode(pair->value, pair->valueLen,
ECE_BASE64URL_REJECT_PADDING, salt, saltLen);
if (!decodedSaltLen) {
break;
}
continue;
}
}
if (decodedSaltLen != saltLen) {
err = ECE_ERROR_INVALID_SALT;
goto end;
}
if (!rsValue) {
// The record size defaults to 4096 if unspecified.
rsValue = 4096;
}
*rs = rsValue;
// Next, find the ephemeral public key in the `Crypto-Key` header.
cryptoKeyParams = ece_header_extract_params(cryptoKeyHeader);
if (!cryptoKeyParams) {
err = ECE_ERROR_INVALID_CRYPTO_KEY_HEADER;
goto end;
}
ece_header_params_t* cryptoKeyParam = cryptoKeyParams;
if (keyId) {
// If the sender specified a key ID in the `Encryption` header, find the
// matching parameter in the `Crypto-Key` header. Otherwise, we assume
// there's only one key, and use the first one we see.
while (cryptoKeyParam) {
bool keyIdMatches = false;
for (ece_header_pairs_t* pair = cryptoKeyParam->pairs; pair;
pair = pair->next) {
if (!ece_header_pairs_has_name(pair, "keyid")) {
continue;
}
keyIdMatches = ece_header_pairs_has_value(pair, keyId);
if (keyIdMatches) {
break;
}
}
if (keyIdMatches) {
break;
}
cryptoKeyParam = cryptoKeyParam->next;
}
if (!cryptoKeyParam) {
// We don't have a matching key ID with a `dh` name-value pair.
err = ECE_ERROR_INVALID_DH;
goto end;
}
}
for (ece_header_pairs_t* pair = cryptoKeyParam->pairs; pair;
pair = pair->next) {
if (!ece_header_pairs_has_name(pair, "dh")) {
continue;
}
// The sender's public key must be Base64url-encoded without padding.
decodedKeyLen = ece_base64url_decode(pair->value, pair->valueLen,
ECE_BASE64URL_REJECT_PADDING,
rawSenderPubKey, rawSenderPubKeyLen);
break;
}
if (decodedKeyLen != rawSenderPubKeyLen) {
err = ECE_ERROR_INVALID_DH;
goto end;
}
end:
ece_header_params_free(encryptionParams);
ece_header_params_free(cryptoKeyParams);
free(keyId);
return err;
}
int
ece_webpush_aesgcm_headers_from_params(const void* salt, size_t saltLen,
const void* rawSenderPubKey,
size_t rawSenderPubKeyLen, uint32_t rs,
char* cryptoKeyHeader,
size_t* cryptoKeyHeaderLen,
char* encryptionHeader,
size_t* encryptionHeaderLen) {
size_t b64SenderPubKeyLen = ece_base64url_encode(
rawSenderPubKey, rawSenderPubKeyLen, ECE_BASE64URL_OMIT_PADDING, NULL, 0);
if (!b64SenderPubKeyLen ||
b64SenderPubKeyLen > SIZE_MAX - ECE_HEADER_DH_PREFIX_LENGTH) {
return ECE_ERROR_INVALID_DH;
}
size_t requiredCryptoKeyHeaderLen =
b64SenderPubKeyLen + ECE_HEADER_DH_PREFIX_LENGTH;
if (*cryptoKeyHeaderLen) {
if (*cryptoKeyHeaderLen < requiredCryptoKeyHeaderLen) {
return ECE_ERROR_OUT_OF_MEMORY;
}
memcpy(cryptoKeyHeader, ECE_HEADER_DH_PREFIX, ECE_HEADER_DH_PREFIX_LENGTH);
ece_base64url_encode(
rawSenderPubKey, rawSenderPubKeyLen, ECE_BASE64URL_OMIT_PADDING,
&cryptoKeyHeader[ECE_HEADER_DH_PREFIX_LENGTH], b64SenderPubKeyLen);
}
*cryptoKeyHeaderLen = requiredCryptoKeyHeaderLen;
size_t b64SaltLen =
ece_base64url_encode(salt, saltLen, ECE_BASE64URL_OMIT_PADDING, NULL, 0);
if (!b64SaltLen) {
return ECE_ERROR_INVALID_SALT;
}
int maybeEncryptionPrefixLen = snprintf(NULL, 0, "rs=%" PRIu32 ";salt=", rs);
if (maybeEncryptionPrefixLen <= 0) {
return ECE_ERROR_INVALID_SALT;
}
size_t encryptionPrefixLen = (size_t) maybeEncryptionPrefixLen;
if (b64SaltLen > SIZE_MAX - encryptionPrefixLen) {
return ECE_ERROR_INVALID_SALT;
}
size_t requiredEncryptionHeaderLen = b64SaltLen + encryptionPrefixLen;
if (*encryptionHeaderLen) {
if (*encryptionHeaderLen < requiredEncryptionHeaderLen) {
return ECE_ERROR_OUT_OF_MEMORY;
}
sprintf(encryptionHeader, "rs=%" PRIu32 ";salt=", rs);
ece_base64url_encode(salt, saltLen, ECE_BASE64URL_OMIT_PADDING,
&encryptionHeader[encryptionPrefixLen], b64SaltLen);
}
*encryptionHeaderLen = requiredEncryptionHeaderLen;
return ECE_OK;
}

View file

@ -0,0 +1,19 @@
#include "ece/trailer.h"
#include "ece.h"
uint32_t
ece_aesgcm_rs(uint32_t rs) {
return rs > UINT32_MAX - ECE_TAG_LENGTH ? 0 : rs + ECE_TAG_LENGTH;
}
bool
ece_aesgcm_needs_trailer(uint32_t rs, size_t ciphertextLen) {
return !(ciphertextLen % rs);
}
bool
ece_aes128gcm_needs_trailer(uint32_t rs, size_t ciphertextLen) {
ECE_UNUSED(rs);
ECE_UNUSED(ciphertextLen);
return false;
}

View file

@ -0,0 +1,178 @@
#include "test.h"
#include <string.h>
typedef struct base64url_encode_test_s {
const char* binary;
size_t binaryLen;
ece_base64url_encode_policy_t paddingPolicy;
const char* base64;
size_t base64Len;
} base64url_encode_test_t;
static base64url_encode_test_t base64url_encode_tests[] = {
{"f", 1, ECE_BASE64URL_OMIT_PADDING, "Zg", 2},
{"f", 1, ECE_BASE64URL_INCLUDE_PADDING, "Zg==", 4},
{"fo", 2, ECE_BASE64URL_OMIT_PADDING, "Zm8", 3},
{"fo", 2, ECE_BASE64URL_INCLUDE_PADDING, "Zm8=", 4},
{"foo", 3, ECE_BASE64URL_OMIT_PADDING, "Zm9v", 4},
{"foo", 3, ECE_BASE64URL_INCLUDE_PADDING, "Zm9v", 4},
{"foob", 4, ECE_BASE64URL_OMIT_PADDING, "Zm9vYg", 6},
{"foob", 4, ECE_BASE64URL_INCLUDE_PADDING, "Zm9vYg==", 8},
{"fooba", 5, ECE_BASE64URL_OMIT_PADDING, "Zm9vYmE", 7},
{"fooba", 5, ECE_BASE64URL_INCLUDE_PADDING, "Zm9vYmE=", 8},
{"foobar", 6, ECE_BASE64URL_OMIT_PADDING, "Zm9vYmFy", 8},
{"foobar", 6, ECE_BASE64URL_INCLUDE_PADDING, "Zm9vYmFy", 8},
{"\x14\xfb\x9c\x03\xd9\x7e", 6, ECE_BASE64URL_OMIT_PADDING, "FPucA9l-", 8},
{"\x14\xfb\x9c\x03\xd9\x7e", 6, ECE_BASE64URL_INCLUDE_PADDING, "FPucA9l-", 8},
{"\x14\xfb\x9c\x03\xd9", 5, ECE_BASE64URL_OMIT_PADDING, "FPucA9k", 7},
{"\x14\xfb\x9c\x03\xd9", 5, ECE_BASE64URL_INCLUDE_PADDING, "FPucA9k=", 8},
{"\x14\xfb\x9c\x03", 4, ECE_BASE64URL_OMIT_PADDING, "FPucAw", 6},
{"\x14\xfb\x9c\x03", 4, ECE_BASE64URL_INCLUDE_PADDING, "FPucAw==", 8},
};
typedef struct base64url_decode_test_s {
const char* base64;
size_t base64Len;
ece_base64url_decode_policy_t paddingPolicy;
size_t requiredBinaryLen;
const char* binary;
size_t binaryLen;
} base64url_decode_test_t;
static base64url_decode_test_t base64url_decode_tests[] = {
// Test vectors from RFC 4648, section 10.
{"", 0, ECE_BASE64URL_REQUIRE_PADDING, 0, NULL, 0},
{"Zg", 2, ECE_BASE64URL_REQUIRE_PADDING, 0, NULL, 0},
{"Zg", 2, ECE_BASE64URL_IGNORE_PADDING, 1, "f", 1},
{"Zg", 2, ECE_BASE64URL_REJECT_PADDING, 1, "f", 1},
{"Zg==", 4, ECE_BASE64URL_REQUIRE_PADDING, 1, "f", 1},
{"Zg==", 4, ECE_BASE64URL_IGNORE_PADDING, 1, "f", 1},
{"Zg==", 4, ECE_BASE64URL_REJECT_PADDING, 3, NULL, 0},
{"Zm8", 3, ECE_BASE64URL_REQUIRE_PADDING, 0, NULL, 0},
{"Zm8", 3, ECE_BASE64URL_IGNORE_PADDING, 2, "fo", 2},
{"Zm8", 3, ECE_BASE64URL_REJECT_PADDING, 2, "fo", 2},
{"Zm8=", 4, ECE_BASE64URL_REQUIRE_PADDING, 2, "fo", 2},
{"Zm8=", 4, ECE_BASE64URL_IGNORE_PADDING, 2, "fo", 2},
{"Zm8=", 4, ECE_BASE64URL_REJECT_PADDING, 3, NULL, 0},
{"Zm9v", 4, ECE_BASE64URL_REQUIRE_PADDING, 3, "foo", 3},
{"Zm9v", 4, ECE_BASE64URL_IGNORE_PADDING, 3, "foo", 3},
{"Zm9v", 4, ECE_BASE64URL_REJECT_PADDING, 3, "foo", 3},
{"Zm9vYg", 6, ECE_BASE64URL_REQUIRE_PADDING, 0, NULL, 0},
{"Zm9vYg", 6, ECE_BASE64URL_IGNORE_PADDING, 4, "foob", 4},
{"Zm9vYg", 6, ECE_BASE64URL_REJECT_PADDING, 4, "foob", 4},
{"Zm9vYg==", 8, ECE_BASE64URL_REQUIRE_PADDING, 4, "foob", 4},
{"Zm9vYg==", 8, ECE_BASE64URL_IGNORE_PADDING, 4, "foob", 4},
{"Zm9vYg==", 8, ECE_BASE64URL_REJECT_PADDING, 6, NULL, 0},
{"Zm9vYmE", 7, ECE_BASE64URL_REQUIRE_PADDING, 0, NULL, 0},
{"Zm9vYmE", 7, ECE_BASE64URL_IGNORE_PADDING, 5, "fooba", 5},
{"Zm9vYmE", 7, ECE_BASE64URL_REJECT_PADDING, 5, "fooba", 5},
{"Zm9vYmE=", 8, ECE_BASE64URL_REQUIRE_PADDING, 5, "fooba", 5},
{"Zm9vYmE=", 8, ECE_BASE64URL_IGNORE_PADDING, 5, "fooba", 5},
{"Zm9vYmE=", 8, ECE_BASE64URL_REJECT_PADDING, 6, NULL, 0},
{"Zm9vYmFy", 8, ECE_BASE64URL_REQUIRE_PADDING, 6, "foobar", 6},
{"Zm9vYmFy", 8, ECE_BASE64URL_IGNORE_PADDING, 6, "foobar", 6},
{"Zm9vYmFy", 8, ECE_BASE64URL_REJECT_PADDING, 6, "foobar", 6},
// Examples from RFC 4648, section 9.
{"FPucA9l-", 8, ECE_BASE64URL_REQUIRE_PADDING, 6, "\x14\xfb\x9c\x03\xd9\x7e",
6},
{"FPucA9l-", 8, ECE_BASE64URL_IGNORE_PADDING, 6, "\x14\xfb\x9c\x03\xd9\x7e",
6},
{"FPucA9l-", 8, ECE_BASE64URL_REJECT_PADDING, 6, "\x14\xfb\x9c\x03\xd9\x7e",
6},
{"FPucA9k", 7, ECE_BASE64URL_REQUIRE_PADDING, 0, NULL, 0},
{"FPucA9k", 7, ECE_BASE64URL_IGNORE_PADDING, 5, "\x14\xfb\x9c\x03\xd9", 5},
{"FPucA9k", 7, ECE_BASE64URL_REJECT_PADDING, 5, "\x14\xfb\x9c\x03\xd9", 5},
{"FPucA9k=", 8, ECE_BASE64URL_REQUIRE_PADDING, 5, "\x14\xfb\x9c\x03\xd9", 5},
{"FPucA9k=", 8, ECE_BASE64URL_IGNORE_PADDING, 5, "\x14\xfb\x9c\x03\xd9", 5},
{"FPucA9k=", 8, ECE_BASE64URL_REJECT_PADDING, 6, NULL, 0},
{"FPucAw", 6, ECE_BASE64URL_REQUIRE_PADDING, 0, NULL, 0},
{"FPucAw", 6, ECE_BASE64URL_IGNORE_PADDING, 4, "\x14\xfb\x9c\x03", 4},
{"FPucAw", 6, ECE_BASE64URL_REJECT_PADDING, 4, "\x14\xfb\x9c\x03", 4},
{"FPucAw==", 8, ECE_BASE64URL_REQUIRE_PADDING, 4, "\x14\xfb\x9c\x03", 4},
{"FPucAw==", 8, ECE_BASE64URL_IGNORE_PADDING, 4, "\x14\xfb\x9c\x03", 4},
{"FPucAw==", 8, ECE_BASE64URL_REJECT_PADDING, 6, NULL, 0},
};
void
test_base64url_encode(void) {
size_t tests =
sizeof(base64url_encode_tests) / sizeof(base64url_encode_test_t);
for (size_t i = 0; i < tests; i++) {
base64url_encode_test_t t = base64url_encode_tests[i];
size_t requiredBase64Len =
ece_base64url_encode(t.binary, t.binaryLen, t.paddingPolicy, NULL, 0);
ece_assert(
requiredBase64Len == t.base64Len,
"Got required length %zu for `%s` with padding policy %d; want %zu",
requiredBase64Len, t.base64, t.paddingPolicy, t.base64Len);
char* base64 = malloc(requiredBase64Len + 1);
ece_assert(base64,
"Failed to allocate string for `%s` with padding policy %d",
t.base64, t.paddingPolicy);
size_t actualBase64Len = ece_base64url_encode(
t.binary, t.binaryLen, t.paddingPolicy, base64, requiredBase64Len + 1);
ece_assert(actualBase64Len == t.base64Len,
"Got length %zu for `%s` with padding policy %d; want %zu",
actualBase64Len, t.base64, t.paddingPolicy, t.base64Len);
free(base64);
}
}
void
test_base64url_decode(void) {
size_t tests =
sizeof(base64url_decode_tests) / sizeof(base64url_decode_test_t);
for (size_t i = 0; i < tests; i++) {
base64url_decode_test_t t = base64url_decode_tests[i];
size_t requiredBinaryLen =
ece_base64url_decode(t.base64, t.base64Len, t.paddingPolicy, NULL, 0);
ece_assert(requiredBinaryLen == t.requiredBinaryLen,
"Got required length %zu for `%s` with padding %d; want %zu",
requiredBinaryLen, t.base64, t.paddingPolicy,
t.requiredBinaryLen);
uint8_t* binary = calloc(requiredBinaryLen, sizeof(uint8_t));
ece_assert(binary, "Failed to allocate buffer for `%s` with padding %d",
t.base64, t.paddingPolicy);
size_t binaryLen = ece_base64url_decode(
t.base64, t.base64Len, t.paddingPolicy, binary, requiredBinaryLen);
ece_assert(binaryLen == t.binaryLen,
"Got length %zu for `%s` with padding %d; want %zu", binaryLen,
t.base64, t.paddingPolicy, t.binaryLen);
ece_assert(!memcmp(binary, t.binary, binaryLen),
"Wrong output for `%s` with padding %d", t.base64,
t.paddingPolicy);
free(binary);
}
}

View file

@ -0,0 +1,497 @@
#include "test.h"
#include <string.h>
typedef struct webpush_aes128gcm_decrypt_ok_test_s {
const char* desc;
const char* plaintext;
const char* recvPrivKey;
const char* authSecret;
const char* payload;
size_t payloadLen;
size_t maxPlaintextLen;
size_t plaintextLen;
} webpush_aes128gcm_decrypt_ok_test_t;
static webpush_aes128gcm_decrypt_ok_test_t
webpush_aes128gcm_decrypt_ok_tests[] = {
{
.desc = "rs = 24, pad = 0",
.plaintext = "I am the walrus",
.recvPrivKey = "\xc8\x99\xd1\x1d\x32\xe2\xb7\xe6\xfe\x74\x98\x78\x6f\x50"
"\xf2\x3b\x98\xac\xe5\x39\x7a\xd2\x61\xde\x39\xba\x64\x49"
"\xec\xc1\x2c\xad",
.authSecret =
"\x99\x6f\xad\x8b\x50\xaa\x2d\x02\xb8\x3f\x26\x41\x2b\x2e\x2a\xee",
.payload = "\x49\x5c\xe6\xc8\xde\x93\xa4\x53\x9e\x86\x2e\x86\x34\x99\x3c"
"\xbb\x00\x00"
"\x00\x18\x41\x04\x3c\x33\x78\xa2\xc0\xab\x95\x4e\x14\x98\x71"
"\x8e\x85\xf0"
"\x8b\xb7\x23\xfb\x7d\x25\xe1\x35\xa6\x63\xfe\x38\x58\x84\xeb"
"\x81\x92\x33"
"\x6b\xf9\x0a\x54\xed\x72\x0f\x1c\x04\x5c\x0b\x40\x5e\x9b\xbc"
"\x3a\x21\x42"
"\xb1\x6c\x89\x08\x67\x34\xc3\x74\xeb\xaf\x70\x99\xe6\x42\x7e"
"\x2d\x32\xc8"
"\xad\xa5\x01\x87\x03\xc5\x4b\x10\xb4\x81\xe1\x02\x7d\x72\x09"
"\xd8\xc6\xb4"
"\x35\x53\xfa\x13\x3a\xfa\x59\x7f\x2d\xdc\x45\xa5\xba\x81\x40"
"\x94\x4e\x64"
"\x90\xbb\x8d\x6d\x99\xba\x1d\x02\xe6\x0d\x95\xf4\x8c\xe6\x44"
"\x47\x7c\x17"
"\x23\x1d\x95\xb9\x7a\x4f\x95\xdd",
.payloadLen = 152,
.maxPlaintextLen = 18,
.plaintextLen = 15,
},
{
.desc = "rs = 49, pad = 84; ciphertext length falls on record boundary",
.plaintext = "Hello, world",
.recvPrivKey = "\x67\x00\x4a\x4e\xa8\x20\xde\xed\x8e\x49\xdb\x5e\x94\x80"
"\xe6\x3d\x3e\xa3\xcc\xe1\xae\x8e\x1a\x60\x60\x97\x13\xd5"
"\x27\xd0\x01\xef",
.authSecret =
"\x95\xf1\x75\x70\xe5\x08\xef\x6a\x2b\x2a\xd1\xb4\xf5\xca\xde\x33",
.payload =
"\xfb\x28\x83\xce\xc1\xc4\xfc\xad\xd6\xd1\x37\x1f\x6e\xa4\x91\xe0\x00"
"\x00\x00\x31\x41\x04\x2d\x44\x1e\xe7\xf9\xff\x6a\x03\x29\xa6\x49\x27"
"\xd0\x52\x4f\xdb\xe7\xb2\x2c\x6f\xb6\x5e\x10\xab\x4f\xdc\x03\x8f\x94"
"\x42\x0a\x0c\xa3\xfa\x28\xda\xd3\x6c\x84\xec\x91\xa1\x62\xea\xe0\x78"
"\xfa\xad\x2c\x1c\xed\x78\xde\x81\x13\xe1\x96\x02\xb2\x0e\x89\x4f\x49"
"\x76\xb9\x73\xe2\xfc\xf6\x82\xfa\x0c\x8c\xcd\x9a\xf3\xd5\xbf\xf1\xed"
"\xe1\x6f\xad\x5a\x31\xce\x19\xd3\x8b\x5e\x1f\xe1\xf7\x8a\x4f\xad\x84"
"\x2b\xbc\x10\x25\x4c\x2c\x6c\xdd\x96\xa2\xb5\x52\x84\xd9\x72\xc5\x3c"
"\xad\x8c\x3b\xac\xb1\x0f\x5f\x57\xeb\x0d\x4a\x43\x33\xb6\x04\x10\x2b"
"\xa1\x17\xca\xe2\x91\x08\xfb\xd9\xf6\x29\xa8\xba\x69\x60\xdd\x01\x94"
"\x5b\x39\xed\x37\xba\x70\x6c\x43\x4a\x10\xfd\x2b\xd2\x09\x4f\xf9\x24"
"\x9b\xcd\xad\x45\x13\x5f\x5f\xe4\x5f\xcd\x38\x07\x1f\x8b\x2d\x39\x41"
"\xaf\xda\x43\x98\x10\xd7\x7a\xac\xaf\x7c\xe5\x0b\x54\x32\x5b\xf5\x8c"
"\x95\x03\x33\x7d\x07\x37\x85\xa3\x23\xdf\xa3\x43",
.payloadLen = 233,
.maxPlaintextLen = 99,
.plaintextLen = 12,
},
{
.desc = "Example from draft-ietf-webpush-encryption-latest",
.plaintext = "When I grow up, I want to be a watermelon",
.recvPrivKey = "\xab\x57\x57\xa7\x0d\xd4\xa5\x3e\x55\x3a\x6b\xbf\x71\xff"
"\xef\xea\x28\x74\xec\x07\xa6\xb3\x79\xe3\xc4\x8f\x89\x5a"
"\x02\xdc\x33\xde",
.authSecret =
"\x05\x30\x59\x32\xa1\xc7\xea\xbe\x13\xb6\xce\xc9\xfd\xa4\x88\x82",
.payload = "\x0c\x6b\xfa\xad\xad\x67\x95\x88\x03\x09\x2d\x45\x46\x76\xf3"
"\x97\x00\x00\x10\x00\x41\x04\xfe\x33\xf4\xab\x0d\xea\x71\x91"
"\x4d\xb5\x58\x23\xf7\x3b\x54\x94\x8f\x41\x30\x6d\x92\x07\x32"
"\xdb\xb9\xa5\x9a\x53\x28\x64\x82\x20\x0e\x59\x7a\x7b\x7b\xc2"
"\x60\xba\x1c\x22\x79\x98\x58\x09\x92\xe9\x39\x73\x00\x2f\x30"
"\x12\xa2\x8a\xe8\xf0\x6b\xbb\x78\xe5\xec\x0f\xf2\x97\xde\x5b"
"\x42\x9b\xba\x71\x53\xd3\xa4\xae\x0c\xaa\x09\x1f\xd4\x25\xf3"
"\xb4\xb5\x41\x4a\xdd\x8a\xb3\x7a\x19\xc1\xbb\xb0\x5c\xf5\xcb"
"\x5b\x2a\x2e\x05\x62\xd5\x58\x63\x56\x41\xec\x52\x81\x2c\x6c"
"\x8f\xf4\x2e\x95\xcc\xb8\x6b\xe7\xcd",
.payloadLen = 144,
.maxPlaintextLen = 42,
.plaintextLen = 41,
},
{
.desc = "rs = 18, pad = 0",
.plaintext = "1",
.recvPrivKey = "\x27\x43\x3f\xab\x89\x70\xb3\xcb\x52\x84\xb6\x11\x83\xef"
"\xb4\x62\x86\x56\x2c\xd2\xa7\x33\x0d\x8c\xae\x96\x09\x11"
"\xa5\x57\x1d\x0c",
.authSecret =
"\xd6\x5a\x04\xdf\x95\xf2\xdb\x5e\x60\x48\x39\xf7\x17\xdc\xde\x79",
.payload = "\x7c\xae\xbd\xbc\x20\x93\x8e\xe3\x40\xa9\x46\xf1\xbd\x4f\x68"
"\xf1\x00\x00\x00\x12\x41\x04\x37\xcf\xdb\x52\x23\xd9\xf9\x5e"
"\xaa\x02\xf6\xed\x94\x0f\xf2\x2e\xaf\x05\xb3\x62\x2e\x94\x9d"
"\xc3\xce\x9f\x33\x5e\x6e\xf9\xb2\x6a\xea\xac\xca\x0f\x74\x08"
"\x0a\x8b\x36\x45\x92\xf2\xcc\xc6\xd5\xed\xdd\x43\x00\x4b\x70"
"\xb9\x18\x87\xd1\x44\xd9\xfa\x93\xf1\x6c\x3b\xc7\xea\x68\xf4"
"\xfd\x54\x7a\x94\xec\xa8\x4b\x16\xe1\x38\xa6\x08\x01\x77",
.payloadLen = 104,
.maxPlaintextLen = 2,
.plaintextLen = 1,
},
};
typedef struct webpush_aes128gcm_err_decrypt_test_s {
const char* desc;
const char* recvPrivKey;
const char* authSecret;
const char* payload;
size_t payloadLen;
size_t maxPlaintextLen;
int err;
} webpush_aes128gcm_err_decrypt_test_t;
static webpush_aes128gcm_err_decrypt_test_t
webpush_aes128gcm_err_decrypt_tests[] = {
{
// Header block shorter than 21 bytes.
.desc = "Missing header block",
.recvPrivKey = "\x1b\xe8\x3f\x38\x33\x2e\xf0\x96\x81\xfa\xf3\xf3\x07\xb1"
"\xff\x2e\x10\xca\xb7\x8c\xc7\xcd\xab\x68\x3a\xc0\xee\x92"
"\xac\x3f\x6e\xe1",
.authSecret =
"\x34\x71\xbb\x98\x48\x1e\x02\x53\x3b\xf3\x95\x42\xbc\xf3\xdb\xa4",
.payload = "\x45\xb7\x4d\x2b\x69\xbe\x9b\x07\x4d\xe3\xb3\x5a\xa8\x7e\x7c"
"\x15\x61\x1d",
.payloadLen = 18,
.maxPlaintextLen = 0,
.err = ECE_ERROR_SHORT_HEADER,
},
{
// Sender key shorter than 65 bytes.
.desc = "Truncated sender key",
.recvPrivKey = "\xce\x88\xe8\xe0\xb3\x05\x7a\x47\x52\xeb\x4c\x8f\xa9\x31"
"\xeb\x62\x1c\x30\x2d\xa5\xad\x03\xb8\x1a\xf4\x59\xcf\x67"
"\x35\x56\x0c\xae",
.authSecret =
"\x5c\x31\xe0\xd9\x6d\x9a\x13\x98\x99\xac\x09\x69\xd3\x59\xf7\x40",
.payload = "\xde\x5b\x69\x6b\x87\xf1\xa1\x5c\xb6\xad\xeb\xdd\x79\xd6\xf9"
"\x9e\x00\x00\x00\x12\x01\x00\xb6\xbc\x18\x26\xc3\x7c\x9f\x73"
"\xdd\x6b\x48\x59\xc2\xb5\x05\x18\x19\x52",
.payloadLen = 40,
.maxPlaintextLen = 2,
.err = ECE_ERROR_COMPUTE_SECRET,
},
{
// The payload is encrypted with only the first 12 bytes of the auth
// secret.
.desc = "Truncated auth secret",
.recvPrivKey = "\x60\xc7\x63\x6a\x51\x7d\xe7\x03\x9a\x0a\xc2\xd0\xe3\x06"
"\x44\x00\x79\x4c\x78\xe7\xe0\x49\x39\x81\x29\xa2\x27\xce"
"\xe0\xf9\xa8\x01",
.authSecret =
"\x35\x5a\x38\xcd\x6d\x9b\xef\x15\x99\x0e\x2d\x33\x08\xdb\xd6\x00",
.payload = "\x81\x15\xf4\x98\x8b\x8c\x39\x2a\x7b\xac\xb4\x3c\x8f\x1a\xc5"
"\x65\x00\x00\x00\x12\x41\x04\x19\x94\x48\x3c\x54\x1e\x9b\xc3"
"\x9a\x6a\xf0\x3f\xf7\x13\xaa\x77\x45\xc2\x84\xe1\x38\xa4\x2a"
"\x24\x35\xb7\x97\xb2\x0c\x4b\x69\x8c\xf5\x11\x8b\x4f\x85\x55"
"\x31\x7c\x19\x0e\xab\xeb\xfa\xb7\x49\xc1\x64\xd3\xf6\xbd\xeb"
"\xe0\xd4\x41\x71\x91\x31\xa3\x57\xd8\x89\x0a\x13\xc4\xdb\xd4"
"\xb1\x6f\xf3\xdd\x5a\x83\xf7\xc9\x1a\xd6\xe0\x40\xac\x42\x73"
"\x0a\x7f\x0b\x3c\xd3\x24\x5e\x9f\x8d\x6f\xf3\x1c\x75\x1d\x41"
"\x0c\xfd",
.payloadLen = 122,
.maxPlaintextLen = 4,
.err = ECE_ERROR_DECRYPT,
},
{
.desc = "Early final record",
.recvPrivKey = "\x5d\xda\x1d\x91\x8b\xc4\x07\xba\x3c\xda\x12\xcb\x80\x14"
"\xd4\x9a\xa7\xe0\x26\x90\x02\x82\x03\x04\x46\x6b\xc8\x00"
"\x34\xca\x92\x40",
.authSecret =
"\x40\xc2\x41\xfd\xe4\x26\x9e\xe1\xe6\xd7\x25\x59\x2d\x98\x27\x18",
.payload = "\xdb\xe2\x15\x50\x7d\x1a\xd3\xd2\xea\xea\xbe\xae\x6e\x87\x4d"
"\x8f\x00\x00\x00\x12\x41\x04\x7b\xc4\x34\x3f\x34\xa8\x34\x8c"
"\xdc\x4e\x46\x2f\xfc\x7c\x40\xaa\x6a\x8c\x61\xa7\x39\xc4\xc4"
"\x1d\x45\x12\x55\x05\xf7\x0e\x9f\xc5\xf9\xef\xa8\x68\x52\xdd"
"\x48\x8d\xcf\x8e\x8e\xa2\xca\xfb\x75\xe0\x7a\xbd\x5e\xe7\xc9"
"\xd5\xc0\x38\xba\xfe\xf0\x79\x57\x1b\x0b\xda\x29\x44\x11\xce"
"\x98\xc7\x6d\xd0\x31\xc0\xe5\x80\x57\x7a\x49\x80\xa3\x75\xe4"
"\x5e\xd3\x04\x29\xbe\x0e\x2e\xe9\xda\x7e\x6d\xf8\x69\x6d\x01"
"\xb8\xec",
.payloadLen = 122,
.maxPlaintextLen = 4,
.err = ECE_ERROR_DECRYPT_PADDING,
},
};
typedef struct aes128gcm_ok_decrypt_test_s {
const char* desc;
const char* plaintext;
const char* ikm;
const char* payload;
size_t payloadLen;
size_t maxPlaintextLen;
size_t plaintextLen;
} aes128gcm_ok_decrypt_test_t;
static aes128gcm_ok_decrypt_test_t aes128gcm_ok_decrypt_tests[] = {
{
.desc = "rs = 18, pad = 8",
.plaintext = "When I grow up, I want to be a watermelon",
.ikm = "\x28\xc0\x66\x11\x4a\x2d\xa5\x21\xca\x89\xf4\x21\x9d\xa8\xac\xc0",
.payload =
"\x1f\xc2\xec\x59\x4d\xbd\xa8\xc8\xab\x26\x25\x47\x04\x65\xb8\xcd\x00\x00"
"\x00\x12\x00\x92\x56\xfe\x1c\x43\x4f\x71\x8e\x85\x16\x3a\x0f\x52\x69\xc1"
"\xb8\x24\x55\x73\x60\x7d\x06\x06\xc3\x97\xfc\xfd\xc3\x27\xd5\xf9\x0c\x44"
"\x8d\x6a\x11\xa0\xc4\xb8\xd0\x51\xc8\x54\x94\xb0\x0f\xb5\xeb\xb9\xe6\x85"
"\x38\x2f\x88\xee\x5a\xce\x19\x1b\xfa\x73\x1d\xa2\xc9\xb2\x3f\x0a\xe4\xfe"
"\x4b\x9a\xd5\xf5\x4d\xf0\xec\xc8\x17\x9f\xc6\xdb\xed\x3a\x94\x33\xbe\x4f"
"\x92\xa8\xdd\xf1\x0d\x5f\x29\x9f\x76\x73\xfb\x79\x33\x69\xc9\x6b\xf5\x20"
"\x5b\x4e\xa5\x47\xef\xa3\xd4\x4b\x6c\xaa\x47\xac\x97\x9a\xa1\x69\x45\x2a"
"\xf6\xf6\x84\x65\xda\xba\x9b\x8a\xb3\x9c\xed\x91\x15\xd4\x4f\xbb\x7c\xf6"
"\xc6\xfa\x0f\x86\x71\xa2\xa1\x2c\xf6\x18\x18\x86\x94\xf1\x7c\x2f\x63\xb7"
"\x46\xe0\x6e\x9a\x51\x20\x6a\x8c\x54\xc9\x91\x54\xb1\x84\xa9\xec\x8a\x29"
"\x71\x4e\xfd\xb6\x8f\xde\xe4\xc4\x2f\x57\xb3\x2e\x48\x8d\x5c\x47\x51\x05"
"\xd0\x57\xb6\x55\x15\xc4\xa0\xeb\x59\x5b\xd6\xe8\xa7\x11\x65\x18\xad\xfb"
"\xc5\xdf\xbc\x51\x71\x01\xae\x72\x2b\x19\x14\xa1\x47\x3e\x35\xbb\x52\xa7"
"\xc9\xad\x22\x09\xc6\xea\x8f\x2b\x60\x5f\x8d\xf9\x78\x65\x4e\xd5\x2c\x71"
"\x17\x5c\x12\x4e\xb3\xa5\x6e\xfa\xfe\x64\x77\xd8\x05\x07\x4d\xd0\x29\x15"
"\x65\x37\x4b\xb1\x02\x8c\x9b\xbd\x59\xd6\x4d\x56\x27\xe7\x28\x02\x5a\x30"
"\x59\x10\x0f\x48\xe8\x88\x5f\x7f\xe4\x4e\x0d\xec\xbb\x7b\x98\x08\x3d\x85"
"\xe1\xc8\x2b\x11\x8a\x8a\xf5\xb3\x8f\x33\xb3\xb6\x7b\xa6\xd5\x8e\x58\xc1"
"\x3a\xff\xaf\x8c\x2b\x99\x9d\x4f\xc2\x09\xed\x73\x7a\x04\x74\x93\x48\x1b"
"\xfa\xfd\x71\x9d\x49\x8d\xf0\xa9\x8c\x6f\x43\x48\xc7\x24\x3a\xa6\x78\x9a"
"\xf3\x36\x85\x4b\x7e\x87\xf9\x5a\x05\x47\xcf\x73\x5e\xc3\x83\xa8\x27\x4d"
"\xdc\xf5\xd9\x76\x43\x85\x37\x36\xb4\xc6\x06\x3f\x48\x95\xab\x38\x38\xc9"
"\x99\x9c\xec\x7b\x73\x1c\xda\xcb\xd5\x0f\x8c\x06\xde\x9f\xe8\x0a\xad\xaf"
"\x91\xd1\x1b\x9a\x35\xaa\xdf\x41\xa0\x5c\x6b\xae\xda\x0c\x6d\x00\xb4\xa8"
"\xc0\xc3\x69\xf9\x8f\x4c\x6e\x6a\x97\x76\xab\x41\x7e\x28\x39\x1b\x47\x5c"
"\xe7\xfc\x01\x65\xdb\xe4\x9e\xf9\x89\x1f\x9c\xef\x82\xe2\x86\x7e\xd6\xd6"
"\x7c\x4a\x5a\x71\xda\xa1\xf7\x5d\x26\x5f\x85\x92\xa8\x1d\xb4\x8c\xbc\x92"
"\xc3\x82\xd6\x3a\x96\xf5\x80\x0f\xa8\xec\xa9\xe2\x02\x7b\xaf\xb7\x4b\xc9"
"\xe3\x3b\xdc\xd3\xb8\xf4\xd8\xe0\x5f\x36\xdd\xa5\x44\xf8\x97\x5e\xcb\xea"
"\x47\x8d\xb8\x36\x61\xa1\xdb\xc5\xfc\xcb\x7f\xeb\x05\x57\xde\xd7\x3a\x37"
"\x90\xc3\x52\x69\xfa\x59\xe4\x75\x0e\x55\xc7\x29\xa0\x08\xc9\x8c\xe9\xee"
"\x88\x82\xe0\xc2\xae\xaf\x1e\xbe\x40\x3b\xe9\x6d\xaa\x25\xb4\x2a\xc0\x1b"
"\x6a\xd4\x35\x5b\xc3\x60\xcd\xd1\x31\x10\xe3\xff\xc7\x6a\xb4\x51\xf5\x9e"
"\x04\xa8\xab\x3f\x1a\x4a\x69\xdf\x21\x91\xab\x4b\x60\xfd\x31\x76\x13\x2b"
"\x8f\x99\x1d\x3a\xb2\x96\xa0\x36\x93\x36\xb5\x35\xaa\xcc\x15\x97\x7d\x50"
"\x5b\xe2\xc5\xd4\xb6\xb7\xbc\x55\xd8\x3c\xd1\x7c\x1e\x80\x35\x7f\x4a\x21"
"\x8d\x72\x93\x3f\xa6\x09\x74\x73\x29\x6d\x7d\xdc\x30\xd7\xa1\x7b\x73\x23"
"\xac\x17\x3e\xc3\x47\x72\x45\x1a\xa6\x70\x95\xca\xcf\xbc\x87\x6c\x05\x56"
"\xa7\xae\x2f\x2c\x64\x55\x50\xd8\xab\x05\xe6\xa7\x87\xa5\x5a\x1c\x0c\xe1"
"\x59\x7b\x95\x8e\xe7\xea\xff\x10\x29\x93\x02\xd9\x9c\x35\xca\xc9\x83\xb9"
"\x6c\x0f\xec\xaf\x61\x59\x3a\x17\x66\xd3\xbc\xc7\xc2\xd5\x00\x4a\x4c\x43"
"\x91\xcb\x41\xb1\x36\x79\x35\xe3\x9d\x73\xf3\xa2\xe0\x84\x59\xd2\x83\x2c"
"\x18\x34\xf2\x60\x6a\x87\x40\x10\xd7\xcf\x17\x7e\x7b\xcf\x61\xad\x41\x3d"
"\x0f\xfc\xe3\x3d\x6b\xef\x39\xb9\x61\x39\xda\x24\xaf\xc9\xac\xe7\x94\x28"
"\x8d\x79\x75\xac\x74\xc9\x86\x66\x1d\x40\x34\x42\x25\xf7\x99\xf5\x96\x35"
"\xa9\x1f\x98\x7b\x54\x42\x3a\x5e\x10\x60\x9b\x6d\x8e\xb4\xda\xe5\xc2\xc8"
"\x2e\x53\xae\xc3\xa3\xdd\xe5\xaf\xbf\x06\x2c\x42\xe2\x95\x91\xd9\x3e\x49"
"\xe4\x54\x80\x90\xb5\x22\x7e\x13\xda\x62\x70\x14\x7e\x5d\x9d\xee\x3f\x2e"
"\x8d\x2f\x7d\xe0\xaf\x1d\xd7\x61\x27\x9d\xd3\xf2\x8a\xce\x13\x74\x73\x15"
"\x11\xf2\x1a",
.payloadLen = 903,
.maxPlaintextLen = 98,
.plaintextLen = 41,
},
};
typedef struct aes128gcm_err_decrypt_test_s {
const char* desc;
const char* ikm;
const char* payload;
size_t payloadLen;
size_t maxPlaintextLen;
int err;
} aes128gcm_err_decrypt_test_t;
static aes128gcm_err_decrypt_test_t aes128gcm_err_decrypt_tests[] = {
{
.desc = "Truncated ciphertext, rs = 18",
.ikm = "\x28\xc0\x66\x11\x4a\x2d\xa5\x21\xca\x89\xf4\x21\x9d\xa8\xac\xc0",
.payload = "\x1f\xc2\xec\x59\x4d\xbd\xa8\xc8\xab\x26\x25\x47\x04\x65\xb8"
"\xcd\x00\x00\x00\x12\x00\x92\x56\xfe\x1c\x43\x4f\x71\x8e\x85"
"\x16\x3a\x0f\x52\x69\xc1\xb8\x24\x55",
.payloadLen = 40,
.maxPlaintextLen = 0,
.err = ECE_ERROR_DECRYPT,
},
{
.desc = "rs <= block overhead",
.ikm = "\x2f\xb1\x75\xc2\x71\xb9\x2f\x6b\x55\xe4\xf2\xa2\x52\xd1\x45\x43",
.payload = "\x76\xf9\x1d\x48\x4e\x84\x91\xda\x55\xc5\xf7\xbf\xe6\xd3\x3e"
"\x89\x00\x00\x00\x02\x00",
.payloadLen = 21,
.maxPlaintextLen = 0,
.err = ECE_ERROR_INVALID_RS,
},
{
.desc = "Zero plaintext",
.ikm = "\x64\xc7\x0e\x64\xa7\x25\x55\x14\x51\xf2\x08\xdf\xba\xa0\xb9\x72",
.payload = "\xaa\xd2\x05\x7d\x33\x53\xb7\xff\x37\xbd\xe4\x2a\xe1\xd5\x0f"
"\xda\x00\x00\x00\x20\x00\xbb\xc7\xb9\x65\x76\x0b\xf0\x66\x2b"
"\x93\xf4\xe5\xd6\x94\xb7\x65\xf0\xcd\x15\x9b\x28\x01\xa5",
.payloadLen = 44,
.maxPlaintextLen = 7,
.err = ECE_ERROR_ZERO_PLAINTEXT,
},
{
.desc = "Bad early padding delimiter",
.ikm = "\x64\xc7\x0e\x64\xa7\x25\x55\x14\x51\xf2\x08\xdf\xba\xa0\xb9\x72",
.payload = "\xaa\xd2\x05\x7d\x33\x53\xb7\xff\x37\xbd\xe4\x2a\xe1\xd5\x0f"
"\xda\x00\x00\x00\x20\x00\xb9\xc7\xb9\x65\x76\x0b\xf0\x9e\x42"
"\xb1\x08\x43\x38\x75\xa3\x06\xc9\x78\x06\x0a\xfc\x7c\x7d\xe9"
"\x52\x85\x91\x8b\x58\x02\x60\xf3\x45\x38\x7a\x28\xe5\x25\x66"
"\x2f\x48\xc1\xc3\x32\x04\xb1\x95\xb5\x4e\x9e\x70\xd4\x0e\x3c"
"\xf3\xef\x0c\x67\x1b\xe0\x14\x49\x7e\xdc",
.payloadLen = 85,
.maxPlaintextLen = 32,
.err = ECE_ERROR_DECRYPT_PADDING,
},
{
.desc = "Bad final padding delimiter",
.ikm = "\x64\xc7\x0e\x64\xa7\x25\x55\x14\x51\xf2\x08\xdf\xba\xa0\xb9\x72",
.payload = "\xaa\xd2\x05\x7d\x33\x53\xb7\xff\x37\xbd\xe4\x2a\xe1\xd5\x0f"
"\xda\x00\x00\x00\x20\x00\xba\xc7\xb9\x65\x76\x0b\xf0\x9e\x42"
"\xb1\x08\x4a\x69\xe4\x50\x1b\x8d\x49\xdb\xc6\x79\x23\x4d\x47"
"\xc2\x57\x16",
.payloadLen = 48,
.maxPlaintextLen = 11,
.err = ECE_ERROR_DECRYPT_PADDING,
},
{
.desc = "Invalid auth tag",
.ikm = "\x64\xc7\x0e\x64\xa7\x25\x55\x14\x51\xf2\x08\xdf\xba\xa0\xb9\x72",
.payload = "\xaa\xd2\x05\x7d\x33\x53\xb7\xff\x37\xbd\xe4\x2a\xe1\xd5\x0f"
"\xda\x00\x00\x00\x20\x00\xbb\xc6\xb1\x1d\x46\x3a\x7e\x0f\x07"
"\x2b\xbe\xaa\x44\xe0\xd6\x2e\x4b\xe5\xf9\x5d\x25\xe3\x86\x71"
"\xe0\x7d",
.payloadLen = 47,
.maxPlaintextLen = 10,
.err = ECE_ERROR_DECRYPT,
},
{
// 2 records; last record is "\x00" without a delimiter.
.desc = "rs = 21, truncated padding for last record",
.ikm = "\x1a\x5c\x05\x64\x16\xdf\x83\x73\x87\x51\x01\xd1\x11\x98\x47\x83",
.payload = "\x53\x06\xdc\x45\xdd\x8e\x51\x00\x16\x53\x3c\x1e\xba\xe5\x50"
"\x53\x00\x00\x00\x15\x00\xa7\x0d\x92\x4e\xe6\x08\xd0\xc1\xc1"
"\x00\x88\x5a\xe8\x78\x1d\xd1\x47\x67\x02\x12\x63\xf7\x9d\x22"
"\xa9\x44\x8d\xb2\x33\x6e\xe0\xe5\x72\xe2\x3c\x38\x49\x70",
.payloadLen = 59,
.maxPlaintextLen = 6,
.err = ECE_ERROR_ZERO_PLAINTEXT,
},
{
// 2 records; last record is just the auth tag.
.desc = "rs = 21, auth tag for last record",
.ikm = "\xc1\xc9\xc0\x91\x9d\x81\x0a\xe7\xd9\xe8\x0c\x45\xbc\x21\xa9\xfa",
.payload = "\xc1\xaf\x29\x07\x6f\x69\x25\x60\xde\x6d\x1f\xde\x02\x11\x69"
"\x79\x00\x00\x00\x15\x00\x46\x9f\xde\x73\xa7\x8a\x2a\x66\x1d"
"\xb0\xf1\xae\x55\xec\xec\x86\x6a\xaa\xe5\xf3\x04\xa3\x3e\xc3"
"\xb0\xbb\x16\xe9\x0a\xab\xc4\xba\xe0\xed\xbb\x73\x46",
.payloadLen = 58,
.maxPlaintextLen = 5,
.err = ECE_ERROR_SHORT_BLOCK,
},
};
void
test_webpush_aes128gcm_decrypt_ok(void) {
size_t tests = sizeof(webpush_aes128gcm_decrypt_ok_tests) /
sizeof(webpush_aes128gcm_decrypt_ok_test_t);
for (size_t i = 0; i < tests; i++) {
webpush_aes128gcm_decrypt_ok_test_t t =
webpush_aes128gcm_decrypt_ok_tests[i];
const void* recvPrivKey = t.recvPrivKey;
const void* authSecret = t.authSecret;
const void* payload = t.payload;
size_t plaintextLen =
ece_aes128gcm_plaintext_max_length(payload, t.payloadLen);
ece_assert(plaintextLen == t.maxPlaintextLen,
"Got plaintext max length %zu for `%s`; want %zu", plaintextLen,
t.desc, t.maxPlaintextLen);
uint8_t* plaintext = calloc(plaintextLen, sizeof(uint8_t));
int err = ece_webpush_aes128gcm_decrypt(
recvPrivKey, ECE_WEBPUSH_PRIVATE_KEY_LENGTH, authSecret,
ECE_WEBPUSH_AUTH_SECRET_LENGTH, payload, t.payloadLen, plaintext,
&plaintextLen);
ece_assert(!err, "Got %d decrypting payload for `%s`", err, t.desc);
ece_assert(plaintextLen == t.plaintextLen,
"Got plaintext length %zu for `%s`; want %zu", plaintextLen,
t.desc, t.plaintextLen);
ece_assert(!memcmp(plaintext, t.plaintext, plaintextLen),
"Wrong plaintext for `%s`", t.desc);
free(plaintext);
}
}
void
test_webpush_aes128gcm_decrypt_err(void) {
size_t tests = sizeof(webpush_aes128gcm_err_decrypt_tests) /
sizeof(webpush_aes128gcm_err_decrypt_test_t);
for (size_t i = 0; i < tests; i++) {
webpush_aes128gcm_err_decrypt_test_t t =
webpush_aes128gcm_err_decrypt_tests[i];
const void* recvPrivKey = t.recvPrivKey;
const void* authSecret = t.authSecret;
const void* payload = t.payload;
size_t plaintextLen =
ece_aes128gcm_plaintext_max_length(payload, t.payloadLen);
ece_assert(plaintextLen == t.maxPlaintextLen,
"Got plaintext max length %zu for `%s`; want %zu", plaintextLen,
t.desc, t.maxPlaintextLen);
uint8_t* plaintext = calloc(plaintextLen, sizeof(uint8_t));
int err = ece_webpush_aes128gcm_decrypt(
recvPrivKey, ECE_WEBPUSH_PRIVATE_KEY_LENGTH, authSecret,
ECE_WEBPUSH_AUTH_SECRET_LENGTH, payload, t.payloadLen, plaintext,
&plaintextLen);
ece_assert(err == t.err, "Got %d decrypting payload for `%s`; want %d", err,
t.desc, t.err);
free(plaintext);
}
}
void
test_aes128gcm_decrypt_ok(void) {
size_t tests =
sizeof(aes128gcm_ok_decrypt_tests) / sizeof(aes128gcm_ok_decrypt_test_t);
for (size_t i = 0; i < tests; i++) {
aes128gcm_ok_decrypt_test_t t = aes128gcm_ok_decrypt_tests[i];
size_t plaintextLen = ece_aes128gcm_plaintext_max_length(
(const uint8_t*) t.payload, t.payloadLen);
ece_assert(plaintextLen == t.maxPlaintextLen,
"Got plaintext max length %zu for `%s`; want %zu", plaintextLen,
t.desc, t.maxPlaintextLen);
uint8_t* plaintext = calloc(plaintextLen, sizeof(uint8_t));
int err = ece_aes128gcm_decrypt((const uint8_t*) t.ikm, 16,
(const uint8_t*) t.payload, t.payloadLen,
plaintext, &plaintextLen);
ece_assert(!err, "Got %d decrypting payload for `%s`", err, t.desc);
ece_assert(plaintextLen == t.plaintextLen,
"Got plaintext length %zu for `%s`; want %zu", plaintextLen,
t.desc, t.plaintextLen);
ece_assert(!memcmp(plaintext, t.plaintext, plaintextLen),
"Wrong plaintext for `%s`", t.desc);
free(plaintext);
}
}
void
test_aes128gcm_decrypt_err(void) {
size_t tests =
sizeof(aes128gcm_err_decrypt_tests) / sizeof(aes128gcm_err_decrypt_test_t);
for (size_t i = 0; i < tests; i++) {
aes128gcm_err_decrypt_test_t t = aes128gcm_err_decrypt_tests[i];
const void* ikm = t.ikm;
const void* payload = t.payload;
size_t plaintextLen =
ece_aes128gcm_plaintext_max_length(payload, t.payloadLen);
ece_assert(plaintextLen == t.maxPlaintextLen,
"Got plaintext max length %zu for `%s`; want %zu", plaintextLen,
t.desc, t.maxPlaintextLen);
uint8_t* plaintext = calloc(plaintextLen, sizeof(uint8_t));
int err = ece_aes128gcm_decrypt(ikm, 16, payload, t.payloadLen, plaintext,
&plaintextLen);
ece_assert(err == t.err, "Got %d decrypting payload for `%s`; want %d", err,
t.desc, t.err);
free(plaintext);
}
}

View file

@ -0,0 +1,306 @@
#include "test.h"
#include <string.h>
typedef struct webpush_aesgcm_decrypt_ok_test_s {
const char* desc;
const char* plaintext;
const char* recvPrivKey;
const char* authSecret;
const char* ciphertext;
const char* cryptoKey;
const char* encryption;
size_t ciphertextLen;
size_t maxPlaintextLen;
size_t plaintextLen;
} webpush_aesgcm_decrypt_ok_test_t;
typedef struct webpush_aesgcm_decrypt_err_test_s {
const char* desc;
const char* recvPrivKey;
const char* authSecret;
const char* ciphertext;
const char* cryptoKey;
const char* encryption;
size_t maxPlaintextLen;
size_t ciphertextLen;
int err;
} webpush_aesgcm_decrypt_err_test_t;
static webpush_aesgcm_decrypt_ok_test_t webpush_aesgcm_decrypt_ok_tests[] = {
{
.desc = "rs = 24, pad = 0",
.plaintext = "Some message",
.recvPrivKey = "\xe2\x1d\xb7\x1b\xf2\xa4\x5c\x2f\x53\xbc\x14\x8a\xda\xfd"
"\x10\xec\x89\xa9\x4b\x66\x00\xb9\x17\x7c\x85\x0c\x8d\xd2"
"\xb1\x40\xc0\x18",
.authSecret =
"\x69\x30\xdc\xe8\x97\x9b\xcd\x1e\x9e\x49\xcc\xb6\xa0\xba\x38\x45",
.ciphertext = "\x3a\x8d\xf8\xc3\x61\x7d\x55\x59\xd3\x30\x57\xca\xb5\xdc"
"\x78\xf0\x06\x56\x43\xd2\xe2\xf4\xce\x83\x6a\xe5\x89\x56"
"\x05\xd4",
.cryptoKey = "dh="
"BCHFVrflyxibGLlgztLwKelsRZp4gqX3tNfAKFaxAcBhpvYeN1yIUMrxa"
"DKiLh4LNKPtj0BOXGdr-IQ-QP82Wjo",
.encryption = "salt=zCU18Rw3A5aB_Xi-vfixmA; rs=24",
.ciphertextLen = 30,
.maxPlaintextLen = 14,
.plaintextLen = 12,
},
{
.desc = "rs = 8, pad = 16",
.plaintext = "Yet another message",
.recvPrivKey = "\xe2\x1d\xb7\x1b\xf2\xa4\x5c\x2f\x53\xbc\x14\x8a\xda\xfd"
"\x10\xec\x89\xa9\x4b\x66\x00\xb9\x17\x7c\x85\x0c\x8d\xd2"
"\xb1\x40\xc0\x18",
.authSecret =
"\xea\x99\x70\x66\x74\xa9\x55\x46\xc5\xec\x03\xc3\x5e\xeb\x37\x51",
.ciphertext =
"\xb8\x40\xb9\x07\xfb\x51\xf9\xfb\x90\xdd\xd7\xa5\x41\xca\xf3\xac\x30"
"\xa9\xe3\x45\xba\x8a\x93\x19\x8c\x66\x7b\xf1\x44\x83\x27\x9b\x0c\x8f"
"\xee\x9b\x00\xe5\x46\xdc\x02\xba\x26\xa1\x65\xf4\x4e\x80\xa1\x68\x81"
"\x61\x8b\xcc\x65\xfc\x13\x85\x5c\x66\x0e\x7c\x3a\x44\x7b\x55\x78\xb2"
"\x85\x33\x90\xd6\x82\x5d\x44\xc2\x43\xa8\x87\x01\xf6\x12\x18\x83\x0f"
"\x48\x0c\xde\x2a\x3e\x77\xbf\x56\x2b\x33\x8f\x64\x4f\x6b\x0d\x65\x12"
"\xbf\xae\x09\xb2\xd2\x26\x49\xf5\xff\x00\x34\x1f\x00\x1a\xef\x6d\x99"
"\x1e\x69\x6c\x61\xe7\x71\x06\xe7\xd4\x0c\x38\x49\x45\x26\xa1\xee\x7b"
"\x87\x4c\x51\x1a\x6c\x31\x78",
.cryptoKey = "dh=BEaA4gzA3i0JDuirGhiLgymS4hfFX7TNTdEhSk_"
"HBlLpkjgCpjPL5c-GL9uBGIfa_fhGNKKFhXz1k9Kyens2ZpQ",
.encryption = "salt=ZFhzj0S-n29g9P2p4-I7tA; rs=8",
.ciphertextLen = 143,
.maxPlaintextLen = 47,
.plaintextLen = 19,
},
{
.desc = "rs = 3, pad = 0",
.plaintext = "Small record size",
.recvPrivKey = "\xe2\x1d\xb7\x1b\xf2\xa4\x5c\x2f\x53\xbc\x14\x8a\xda\xfd"
"\x10\xec\x89\xa9\x4b\x66\x00\xb9\x17\x7c\x85\x0c\x8d\xd2"
"\xb1\x40\xc0\x18",
.authSecret =
"\x83\x6a\xd6\x54\x75\x02\xa5\x4c\x60\x70\xbf\x53\xcf\xbb\xf2\x79",
.ciphertext =
"\xa1\x8e\x1e\xe5\xe0\xda\xb4\x35\x6d\xd9\xfa\x50\xca\x5c\x5b\x3c\x93"
"\x3e\xde\xfa\xdf\x84\x36\xac\x7c\xf7\x3c\x43\x53\xd6\xb7\x8b\x4f\x7c"
"\xc5\x5b\xcf\xfb\x03\x34\xbf\xdc\xbe\xbd\x03\x5d\x79\x1d\x17\x34\xb5"
"\x97\x1b\x09\xb2\x3e\x79\xd1\x44\xb1\xe0\xc3\x25\xd3\x58\xa1\x8c\x89"
"\x97\x0a\x3a\xf0\xf5\x1e\x71\x16\x01\x6b\x08\x0a\x89\x0f\x71\xb0\x5c"
"\x0d\x6f\xcd\x2e\x13\xe7\x2b\xc6\x55\xb5\x29\xdd\x29\xdd\x30\x37\x53"
"\xd4\xe0\x9c\x12\x87\xd3\x9f\xb5\x42\xeb\x16\x7e\xcc\xff\x2b\xcd\x24"
"\x1f\x0c\x20\x41\xc6\x63\xd4\xec\xe2\x12\xce\xbf\x19\xe0\x7c\xb3\x14"
"\x21\x89\x78\x17\xea\x89\x9e\xf2\x51\xf3\x65\x28\x5d\x71\xe5\x46\x99"
"\xe1\x7e\xa2\x53\xf4\xd7\xc7\x92\xa5\x43\x2f\xed\xa1\x5d\x5d\x5d\x9d"
"\x9e\x10\x03\x07\x25\x47\x12\x74\x0a\xef\x07\xac\xd1\xa4\x57\x44\x23"
"\x17\x5c\x1d\xc2\x72\xa3\x50\x48\x62\x00\x18\x4c\x37\x40\x23\x2f\xff"
"\x20\x95\x42\x3d\x8f\xdb\xd8\x21\x39\x5b\xfe\xa8\x86\x56\x0a\x07\xbc"
"\x43\xc8\x7f\x20\x88\xdb\x96\x59\x70\x20\x06\x8c\xd4\x7d\x27\x0b\x51"
"\x58\xcf\x0b\x7f\x32\x25\x3a\x40\x52\x2e\xb0\xea\x95\xe0\x5d\x45\x9f"
"\x7d\xe9\xdd\x7b\x91\x6b\x79\x0a\x67\x30\x52\xf5\x2f\x81\xbe\xdf\xcc"
"\x2c\x23\xff\x88\x48\x83\xb3\x74\xef\x10\x22\xb5\xc7\xc6\x6b\x5c\xd5"
"\x2e\x71\xcd\x8b\xbc\x6c\xca\x92\x80\x28\xa7\xbc\x21\x30\x75\xe1\x85"
"\x0a\x68\xa0\x85\x18\xe3\xf2\xdd\x73\x45\x6a\x34\x0f\x5e\x2b\x1d\xb4"
"\x0b\x3d\x1f\xdb\xd4\xfe\x52\xbc\x47\x89\x4e\xd4\xbd\x36\x6d\xe2\x89"
"\xb3",
.cryptoKey = "dh=BCg6ZIGuE2ZNm2ti6Arf4CDVD_8--"
"aLXAGLYhpghwjl1xxVjTLLpb7zihuEOGGbyt8Qj0_"
"fYHBP4ObxwJNl56bk",
.encryption = "salt=5LIDBXbvkBvvb7ZdD-T4PQ; rs=3",
.ciphertextLen = 341,
.maxPlaintextLen = 53,
.plaintextLen = 17,
},
{
.desc = "Example from draft-ietf-httpbis-encryption-encoding-02",
.plaintext = "I am the walrus",
.recvPrivKey = "\xf4\x55\xa5\xd7\x9f\xd0\x51\x00\x16\x0d\xa0\xf7\x93\x79"
"\x79\xd1\x90\x59\x40\x9e\x1a\xbb\x6e\xc5\xd5\x5e\x05\xd2"
"\xe2\xd2\x0f\xf3",
.authSecret =
"\x47\x6f\x6f\x20\x67\x6f\x6f\x20\x67\x27\x20\x6a\x6f\x6f\x62\x21",
.ciphertext = "\xea\x7a\x80\x41\x43\x04\xf2\x13\x6a\xc3\x92\x77\x92\x5f"
"\x1c\xa5\x55\x49\xca\x55\xca\x62\xa6\x4e\x7a\xc7\x99\x1b"
"\xc5\x2e\x78\xaa\x40",
.cryptoKey = "keyid=\"dhkey\"; "
"dh="
"\"BNoRDbb84JGm8g5Z5CFxurSqsXWJ11ItfXEWYVLE85Y7CYkDjXsIEc4"
"aqxYaQ1G8BqkXCJ6DPpDrWtdWj_mugHU\"",
.encryption = "keyid=\"dhkey\"; salt=\"lngarbyKfMoi9Z75xYXmkg\"",
.ciphertextLen = 33,
.maxPlaintextLen = 17,
.plaintextLen = 15,
},
};
static webpush_aesgcm_decrypt_err_test_t webpush_aesgcm_decrypt_err_tests[] = {
{
.desc = "rs = 7, no trailer",
.recvPrivKey = "\xd9\xbb\xb8\xa5\xa3\x80\x65\xb2\xf6\x79\xfd\x6e\xfb\x04"
"\xf3\x38\xdb\x93\x21\xc0\xcf\x73\x4d\x28\xd3\x35\x09\x82"
"\x0e\x3a\x5d\x37",
.authSecret =
"\x42\x80\xe2\xd2\xee\xaf\x72\xc9\x48\x54\x92\xa2\xa2\xe5\xcc\x5f",
// "O hai". The ciphertext is exactly `rs + 16`, without a trailer.
.ciphertext = "\x60\x6e\x05\xf9\xbd\x3a\xcb\x9f\x74\x85\x19\x67\x4a\xcc\x3f"
"\xbe\xe3\xb0\xeb\x65\x7d\x23\x3f",
.cryptoKey = "dh=BD_"
"bsTUpxBMvSv8eksith3vijMLj44D4jhJjO51y7wK1ytbUlsyYBBYYyB5AAe5b"
"nREA_WipTgemDVz00LiWcfM",
.encryption = "salt=xKWvs_jWWeg4KOsot_uBhA; rs=7",
.ciphertextLen = 23,
.maxPlaintextLen = 7,
.err = ECE_ERROR_DECRYPT_TRUNCATED,
},
{
// Last block is only 1 byte; pad length prefix is 2 bytes.
.desc = "Pad size > last block length",
.recvPrivKey = "\x0a\x8b\x04\x44\x05\x57\x82\xf4\xef\xa2\x1e\xd4\x92\xb4"
"\x42\xd9\x5f\xa2\x5e\x83\x6c\xd1\xb5\xe5\x7b\xd2\x3a\xf2"
"\xac\xe4\x95\xeb",
.authSecret =
"\x42\xd1\x99\x79\x8f\x0c\x41\xf0\x9a\xab\xe5\xf0\x28\xe5\x46\x05",
.ciphertext = "\x26\xf5\xfd\x1e\xc2\x78\x94\xbe\x60\xcc\xff\x3f\xb8\x22\x9c"
"\xea\xcd\x79\x89\x12\x1a\x36\x10\xf8\xa4\x50\xa0\xab\x9f\x9d"
"\x7f\x06\xd4\xa8\x47\x0d\x52\x4a\xaf",
.cryptoKey = "dh=BBNZNEi5Ew_ID5S4Y9jWBi1NeVDje6Mjs7SDLViUn6A8VAZj-"
"6X3QAuYQ3j20BblqjwTgYst7PRnY6UGrKyLbmU",
.encryption = "salt=ot8hzbwOo6CYe6ZhdlwKtg; rs=6",
.ciphertextLen = 39,
.maxPlaintextLen = 7,
.err = ECE_ERROR_DECRYPT_PADDING,
},
{
// Last block is 1 byte, but claims its pad length is 2.
.desc = "Padding length > last block length",
.recvPrivKey = "\xf6\x5f\x9a\x85\xc0\x4c\xf8\x8d\x32\x93\x06\xd6\x88\x34"
"\xbd\x29\x1a\xcf\x76\x1c\xaf\x4d\x9d\x12\xc4\xa8\x8f\xa6"
"\x3d\x9a\x79\xa2",
.authSecret =
"\x80\xa1\xbf\x3f\xaf\x9d\x7b\x9a\x72\xcd\x2f\x21\xc8\x7f\xcd\xc9",
.ciphertext = "\xa1\x64\x8e\x14\x0f\x94\x3b\x9a\x16\xab\xe9\x08\xef\xd4\x47"
"\x68\x57\xf0\x01\xe8\xcb\x89\x02\x78\x0b\xb7\x93\x9a\xb4\x93"
"\x06\x5e\x20\x02\xb2\xd7\x7f\x1e\xe5\x67\xe6",
.cryptoKey = "dh=BKe2IBO_cwmEzQyTVscSbQcj0Y3uBSzGZ_mHlANMciS8uGpb7U8_"
"Bw7TNdlYfpwWDLd0cxM8YYWNDbNJ_p2Rp4o",
.encryption = "salt=z7QJ6UR89SiFRkd4RsC4Vg; rs=6",
.ciphertextLen = 41,
.maxPlaintextLen = 9,
.err = ECE_ERROR_DECRYPT_PADDING,
},
{
// First block has no padding, but claims its pad length is 1.
.desc = "Non-zero padding",
.recvPrivKey = "\x23\x3b\x9a\xc4\xba\x85\x26\x68\xd2\xbb\xc1\xa3\x2c\x2a"
"\x36\xa0\x46\x83\x66\x30\xc1\xba\xd5\xb8\x9b\x84\xf4\xab"
"\x1d\x36\x5e\xc3",
.authSecret =
"\x70\xca\x56\x41\x6e\x7c\x06\xba\x43\x6c\x9f\x0a\xa9\xb4\xbd\x8a",
.ciphertext = "\x41\xdb\xe3\x87\x42\xe4\x65\x72\xae\xff\x51\xef\xbf\x9e\x83"
"\xd2\xb3\x92\x17\xa3\x30\xc3\x7c\xb4\x17\xcc\x64\xc5\x43\x65"
"\xc1\x5b\xb6\x53\x58\x9a\x90\xe5\x14\x19\x1b",
.cryptoKey = "dh=BBicj01QI0ryiFzAaty9VpW_crgq9XbU1bOCtEZI9UNE6tuOgp4lyN_"
"UN0N905ECnLWK5v_sCPUIxnQgOuCseSo",
.encryption = "salt=SbkGHONbQBBsBcj9dLyIUw; rs=6",
.ciphertextLen = 41,
.maxPlaintextLen = 9,
.err = ECE_ERROR_DECRYPT_PADDING,
},
{
.desc = "rs = 6, auth tag for last record",
.recvPrivKey = "\x9e\x13\x93\xf7\x5e\xf5\xc6\xea\x10\x04\x91\xa4\x89\x9d"
"\xda\xa9\x3e\x6a\xc3\xf2\x0b\x27\xde\x3f\x3c\xf8\x95\x36"
"\xed\x4b\x15\x26",
.authSecret =
"\xde\xb5\xa1\xb1\x10\x94\xfc\xa7\x5a\xa9\xf2\x8f\x6d\xdd\xf3\x05",
.ciphertext = "\x0b\xbb\xb7\x8f\x90\x0b\xe1\x8c\xe1\xdb\x26\x01\xfe\xe9\x8d"
"\xea\xdc\xeb\x54\x7c\x6b\xb7\xb0\xf9\x6d\xa4\xc4\x5b\xd0\xc4"
"\xd4\x19\x37\xba\x9f\x5f\x63\x8c",
.cryptoKey = "dh=BI38Qs_OhDmQIxbszc6Nako-MrX3FzAE_8HzxM1wgoEIG4ocxyF-"
"YAAVhfkpJUvDpRyKW2LDHIaoylaZuxQfRhE",
.encryption = "salt=QClh48OlvGpSjZ0Mg0e8rg; rs=6",
.ciphertextLen = 38,
.maxPlaintextLen = 6,
.err = ECE_ERROR_SHORT_BLOCK,
},
};
void
test_webpush_aesgcm_decrypt_ok(void) {
size_t length = sizeof(webpush_aesgcm_decrypt_ok_tests) /
sizeof(webpush_aesgcm_decrypt_ok_test_t);
for (size_t i = 0; i < length; i++) {
webpush_aesgcm_decrypt_ok_test_t t = webpush_aesgcm_decrypt_ok_tests[i];
const void* recvPrivKey = t.recvPrivKey;
const void* authSecret = t.authSecret;
const void* ciphertext = t.ciphertext;
uint8_t salt[ECE_SALT_LENGTH];
uint8_t rawSenderPubKey[ECE_WEBPUSH_PUBLIC_KEY_LENGTH];
uint32_t rs;
int err = ece_webpush_aesgcm_headers_extract_params(
t.cryptoKey, t.encryption, salt, ECE_SALT_LENGTH, rawSenderPubKey,
ECE_WEBPUSH_PUBLIC_KEY_LENGTH, &rs);
ece_assert(!err, "Got %d parsing crypto headers", err);
size_t plaintextLen = ece_aesgcm_plaintext_max_length(rs, t.ciphertextLen);
ece_assert(plaintextLen == t.maxPlaintextLen,
"Got plaintext max length %zu for `%s`; want %zu", plaintextLen,
t.desc, t.maxPlaintextLen);
uint8_t* plaintext = calloc(plaintextLen, sizeof(uint8_t));
err = ece_webpush_aesgcm_decrypt(
recvPrivKey, ECE_WEBPUSH_PRIVATE_KEY_LENGTH, authSecret,
ECE_WEBPUSH_AUTH_SECRET_LENGTH, salt, ECE_SALT_LENGTH, rawSenderPubKey,
ECE_WEBPUSH_PUBLIC_KEY_LENGTH, rs, ciphertext, t.ciphertextLen, plaintext,
&plaintextLen);
ece_assert(!err, "Got %d decrypting ciphertext for `%s`", err, t.desc);
ece_assert(plaintextLen == t.plaintextLen,
"Got plaintext length %zu for `%s`; want %zu", plaintextLen,
t.desc, t.plaintextLen);
ece_assert(!memcmp(plaintext, t.plaintext, plaintextLen),
"Wrong plaintext for `%s`", t.desc);
free(plaintext);
}
}
void
test_webpush_aesgcm_decrypt_err(void) {
size_t tests = sizeof(webpush_aesgcm_decrypt_err_tests) /
sizeof(webpush_aesgcm_decrypt_err_test_t);
for (size_t i = 0; i < tests; i++) {
webpush_aesgcm_decrypt_err_test_t t = webpush_aesgcm_decrypt_err_tests[i];
const void* recvPrivKey = t.recvPrivKey;
const void* authSecret = t.authSecret;
const void* ciphertext = t.ciphertext;
uint8_t salt[ECE_SALT_LENGTH];
uint8_t rawSenderPubKey[ECE_WEBPUSH_PUBLIC_KEY_LENGTH];
uint32_t rs;
int err = ece_webpush_aesgcm_headers_extract_params(
t.cryptoKey, t.encryption, salt, ECE_SALT_LENGTH, rawSenderPubKey,
ECE_WEBPUSH_PUBLIC_KEY_LENGTH, &rs);
ece_assert(!err, "Got %d parsing crypto headers", err);
size_t plaintextLen = ece_aesgcm_plaintext_max_length(rs, t.ciphertextLen);
ece_assert(plaintextLen == t.maxPlaintextLen,
"Got plaintext max length %zu for `%s`; want %zu", plaintextLen,
t.desc, t.maxPlaintextLen);
uint8_t* plaintext = calloc(plaintextLen, sizeof(uint8_t));
err = ece_webpush_aesgcm_decrypt(
recvPrivKey, ECE_WEBPUSH_PRIVATE_KEY_LENGTH, authSecret,
ECE_WEBPUSH_AUTH_SECRET_LENGTH, salt, ECE_SALT_LENGTH, rawSenderPubKey,
ECE_WEBPUSH_PUBLIC_KEY_LENGTH, rs, ciphertext, t.ciphertextLen, plaintext,
&plaintextLen);
ece_assert(err == t.err, "Got %d decrypting ciphertext for `%s`; want %d",
err, t.desc, t.err);
free(plaintext);
}
}

134
mobile/ios/ThirdParty/ecec/test/e2e.c vendored Normal file
View file

@ -0,0 +1,134 @@
#include "test.h"
#include <inttypes.h>
#include <string.h>
void
test_webpush_aes128gcm_e2e(void) {
uint8_t rawRecvPrivKey[ECE_WEBPUSH_PRIVATE_KEY_LENGTH];
uint8_t rawRecvPubKey[ECE_WEBPUSH_PUBLIC_KEY_LENGTH];
uint8_t authSecret[ECE_WEBPUSH_AUTH_SECRET_LENGTH];
int err = ece_webpush_generate_keys(
rawRecvPrivKey, ECE_WEBPUSH_PRIVATE_KEY_LENGTH, rawRecvPubKey,
ECE_WEBPUSH_PUBLIC_KEY_LENGTH, authSecret, ECE_WEBPUSH_AUTH_SECRET_LENGTH);
ece_assert(!err, "Got %d generating keys", err);
const void* input = "When I grow up, I want to be a watermelon";
size_t inputLen = strlen(input);
size_t payloadLen = ece_aes128gcm_payload_max_length(4096, 0, inputLen);
ece_assert(payloadLen == 334, "Got %zu for payload max length; want 334",
payloadLen);
uint8_t* payload = calloc(payloadLen, sizeof(uint8_t));
err = ece_webpush_aes128gcm_encrypt(rawRecvPubKey,
ECE_WEBPUSH_PUBLIC_KEY_LENGTH, authSecret,
ECE_WEBPUSH_AUTH_SECRET_LENGTH, 4096, 0,
input, inputLen, payload, &payloadLen);
ece_assert(!err, "Got %d encrypting plaintext", err);
ece_assert(payloadLen == 144, "Got %zu for payload length; want 144",
payloadLen);
size_t plaintextLen = ece_aes128gcm_plaintext_max_length(payload, payloadLen);
ece_assert(plaintextLen == 42, "Got %zu for plaintext max length; want 42",
plaintextLen);
uint8_t* plaintext = calloc(plaintextLen, sizeof(uint8_t));
err = ece_webpush_aes128gcm_decrypt(
rawRecvPrivKey, ECE_WEBPUSH_PRIVATE_KEY_LENGTH, authSecret,
ECE_WEBPUSH_AUTH_SECRET_LENGTH, payload, payloadLen, plaintext,
&plaintextLen);
ece_assert(!err, "Got %d decrypting payload", err);
ece_assert(plaintextLen == inputLen, "Got %zu for plaintext length; want %zu",
plaintextLen, inputLen);
ece_assert(!memcmp(plaintext, input, inputLen),
"Got `%s` for plaintext; want `%s`", plaintext, input);
free(payload);
free(plaintext);
}
void
test_webpush_aesgcm_e2e(void) {
uint8_t rawRecvPrivKey[ECE_WEBPUSH_PRIVATE_KEY_LENGTH];
uint8_t rawRecvPubKey[ECE_WEBPUSH_PUBLIC_KEY_LENGTH];
uint8_t authSecret[ECE_WEBPUSH_AUTH_SECRET_LENGTH];
int err = ece_webpush_generate_keys(
rawRecvPrivKey, ECE_WEBPUSH_PRIVATE_KEY_LENGTH, rawRecvPubKey,
ECE_WEBPUSH_PUBLIC_KEY_LENGTH, authSecret, ECE_WEBPUSH_AUTH_SECRET_LENGTH);
ece_assert(!err, "Got %d generating keys", err);
const void* input = "If the wind in my sail on the sea stays behind me, one "
"day I'll know how far I'll go";
size_t inputLen = strlen(input);
size_t ciphertextLen = ece_aesgcm_ciphertext_max_length(26, 6, inputLen);
ece_assert(ciphertextLen == 162,
"Got %zu for ciphertext max length; want 162", ciphertextLen);
uint8_t* ciphertext = calloc(ciphertextLen, sizeof(uint8_t));
uint8_t encryptSalt[ECE_SALT_LENGTH];
uint8_t encryptRawSenderPubKey[ECE_WEBPUSH_PUBLIC_KEY_LENGTH];
err = ece_webpush_aesgcm_encrypt(
rawRecvPubKey, ECE_WEBPUSH_PUBLIC_KEY_LENGTH, authSecret,
ECE_WEBPUSH_AUTH_SECRET_LENGTH, 26, 6, input, inputLen, encryptSalt,
ECE_SALT_LENGTH, encryptRawSenderPubKey, ECE_WEBPUSH_PUBLIC_KEY_LENGTH,
ciphertext, &ciphertextLen);
ece_assert(!err, "Got %d encrypting plaintext", err);
ece_assert(ciphertextLen == 162, "Got %zu for ciphertext length; want 162",
ciphertextLen);
size_t cryptoKeyHeaderLen = 0;
size_t encryptionHeaderLen = 0;
err = ece_webpush_aesgcm_headers_from_params(
encryptSalt, ECE_SALT_LENGTH, encryptRawSenderPubKey,
ECE_WEBPUSH_PUBLIC_KEY_LENGTH, 14, NULL, &cryptoKeyHeaderLen, NULL,
&encryptionHeaderLen);
ece_assert(!err, "Got %d determining crypto header lengths", err);
char* cryptoKeyHeader = malloc(cryptoKeyHeaderLen + 1);
char* encryptionHeader = malloc(encryptionHeaderLen + 1);
err = ece_webpush_aesgcm_headers_from_params(
encryptSalt, ECE_SALT_LENGTH, encryptRawSenderPubKey,
ECE_WEBPUSH_PUBLIC_KEY_LENGTH, 26, cryptoKeyHeader, &cryptoKeyHeaderLen,
encryptionHeader, &encryptionHeaderLen);
ece_assert(!err, "Got %d formatting crypto headers", err);
cryptoKeyHeader[cryptoKeyHeaderLen] = '\0';
encryptionHeader[encryptionHeaderLen] = '\0';
uint8_t decryptSalt[ECE_SALT_LENGTH];
uint8_t decryptRawSenderPubKey[ECE_WEBPUSH_PUBLIC_KEY_LENGTH];
uint32_t rs;
err = ece_webpush_aesgcm_headers_extract_params(
cryptoKeyHeader, encryptionHeader, decryptSalt, ECE_SALT_LENGTH,
decryptRawSenderPubKey, ECE_WEBPUSH_PUBLIC_KEY_LENGTH, &rs);
ece_assert(!err, "Got %d extracting crypto params", err);
ece_assert(!memcmp(encryptSalt, decryptSalt, ECE_SALT_LENGTH),
"Wrong salt for `%s`", input);
ece_assert(!memcmp(encryptRawSenderPubKey, decryptRawSenderPubKey,
ECE_WEBPUSH_PUBLIC_KEY_LENGTH),
"Wrong sender public key for `%s`", input);
ece_assert(rs == 26, "Got rs = %" PRIu32 "; want 26", rs);
size_t plaintextLen = ece_aesgcm_plaintext_max_length(rs, ciphertextLen);
ece_assert(plaintextLen == 98, "Got %zu for plaintext max length; want 98",
plaintextLen);
uint8_t* plaintext = calloc(plaintextLen, sizeof(uint8_t));
err = ece_webpush_aesgcm_decrypt(
rawRecvPrivKey, ECE_WEBPUSH_PRIVATE_KEY_LENGTH, authSecret,
ECE_WEBPUSH_AUTH_SECRET_LENGTH, decryptSalt, ECE_SALT_LENGTH,
decryptRawSenderPubKey, ECE_WEBPUSH_PUBLIC_KEY_LENGTH, rs, ciphertext,
ciphertextLen, plaintext, &plaintextLen);
ece_assert(!err, "Got %d decrypting ciphertext", err);
ece_assert(plaintextLen == inputLen, "Got %zu for plaintext length; want %zu",
plaintextLen, inputLen);
ece_assert(!memcmp(plaintext, input, inputLen),
"Got `%s` for plaintext; want `%s`", plaintext, input);
free(ciphertext);
free(cryptoKeyHeader);
free(encryptionHeader);
free(plaintext);
}

View file

@ -0,0 +1,372 @@
#include "test.h"
#include <string.h>
#include <openssl/rand.h>
typedef struct webpush_aes128gcm_encrypt_ok_test_s {
const char* desc;
const char* payload;
const char* senderPrivKey;
const char* recvPubKey;
const char* authSecret;
const char* salt;
const char* plaintext;
size_t plaintextLen;
size_t padLen;
size_t maxPayloadLen;
size_t payloadLen;
uint32_t rs;
} webpush_aes128gcm_encrypt_ok_test_t;
static webpush_aes128gcm_encrypt_ok_test_t
webpush_aes128gcm_encrypt_ok_tests[] = {
{
.desc = "Example from draft-ietf-webpush-encryption-latest",
.payload = "\x0c\x6b\xfa\xad\xad\x67\x95\x88\x03\x09\x2d\x45\x46\x76\xf3"
"\x97\x00\x00\x10\x00\x41\x04\xfe\x33\xf4\xab\x0d\xea\x71\x91"
"\x4d\xb5\x58\x23\xf7\x3b\x54\x94\x8f\x41\x30\x6d\x92\x07\x32"
"\xdb\xb9\xa5\x9a\x53\x28\x64\x82\x20\x0e\x59\x7a\x7b\x7b\xc2"
"\x60\xba\x1c\x22\x79\x98\x58\x09\x92\xe9\x39\x73\x00\x2f\x30"
"\x12\xa2\x8a\xe8\xf0\x6b\xbb\x78\xe5\xec\x0f\xf2\x97\xde\x5b"
"\x42\x9b\xba\x71\x53\xd3\xa4\xae\x0c\xaa\x09\x1f\xd4\x25\xf3"
"\xb4\xb5\x41\x4a\xdd\x8a\xb3\x7a\x19\xc1\xbb\xb0\x5c\xf5\xcb"
"\x5b\x2a\x2e\x05\x62\xd5\x58\x63\x56\x41\xec\x52\x81\x2c\x6c"
"\x8f\xf4\x2e\x95\xcc\xb8\x6b\xe7\xcd",
.senderPrivKey =
"\xc9\xf5\x8f\x89\x81\x3e\x9f\x8e\x87\x2e\x71\xf4\x2a\xa6"
"\x4e\x17\x57\xc9\x25\x4d\xcc\x62\xb7\x2d\xdc\x01\x0b\xb4"
"\x04\x3e\xa1\x1c",
.recvPubKey =
"\x04\x25\x71\xb2\xbe\xcd\xfd\xe3\x60\x55\x1a\xaf\x1e\xd0\xf4"
"\xcd\x36\x6c\x11\xce\xbe\x55\x5f\x89\xbc\xb7\xb1\x86\xa5\x33"
"\x39\x17\x31\x68\xec\xe2\xeb\xe0\x18\x59\x7b\xd3\x04\x79\xb8"
"\x6e\x3c\x8f\x8e\xce\xd5\x77\xca\x59\x18\x7e\x92\x46\x99\x0d"
"\xb6\x82\x00\x8b\x0e",
.authSecret =
"\x05\x30\x59\x32\xa1\xc7\xea\xbe\x13\xb6\xce\xc9\xfd\xa4\x88\x82",
.salt =
"\x0c\x6b\xfa\xad\xad\x67\x95\x88\x03\x09\x2d\x45\x46\x76\xf3\x97",
.plaintext = "When I grow up, I want to be a watermelon",
.plaintextLen = 41,
.padLen = 0,
.maxPayloadLen = 334,
.payloadLen = 144,
.rs = 4096,
},
{
.desc = "rs = 24, pad = 6",
.payload = "\xff\x80\x50\x30\xa1\x08\xe1\x14\xe6\xc1\x7f\xad\x61\x86\xa1"
"\xa6\x00\x00"
"\x00\x18\x41\x04\x30\xef\xcb\x1e\xb0\x43\xb8\x05\xe4\xe4\x4b"
"\xab\x35\xf8"
"\x25\x13\xc3\x3f\xed\xb2\x87\x00\xf7\xe5\x68\xac\x8b\x61\xe8"
"\xd8\x35\x66"
"\x5a\x51\xeb\x66\x79\xb2\xdb\x22\x8a\x10\xc0\xc3\xfe\x50\x77"
"\x06\x28\x48"
"\xd9\xbb\x3d\x60\x27\x9f\x93\xce\x35\x48\x47\x28\xaa\x1f\xd2"
"\xc1\x71\x39"
"\x49\xae\xc9\x8f\x05\x09\x6c\x72\x98\xfd\x3f\x51\xc4\xf8\x18"
"\xfa\xfa\x1f"
"\xe6\x15\xd8\x44\x7b\x3a\x05\x40\x60\x31\xf6\x40\x1a\xc2\x4f"
"\x2a\x77\x5c"
"\xa5\x24\x56\xa9\x21\xb8\x3b\x9e\x00\x42\xc3\xa6\x3e\x1a\xfa"
"\x1a\xe0\x12"
"\x77\x4d\x9d\x77\x5b\xe8\xd1\x94\x19\x45\x1d\x37\xff\x59\xff"
"\x59\x2e\x84"
"\xf0\x74\x40\xa6\x3f\xc1\x7f\x5c\xab\xcb\x9a\x50\xed\xda\xf7"
"\x53\x70\xdb"
"\x64\x7f\x94\x44\x7d\x3f\x16\x62\x69\xd8\x71\x1d\xf0\xf5\x7e"
"\x56\x04\x95"
"\x76\xe1\x13\x0a\x5a\x5e\x1f\x94\xba\x8a\x5d\x0b\x00\x07\xc6"
"\xc0\xfd\x29"
"\x98\x42\x9e\x7d\x63\xd4\xef\x91\x97\x98\xf4\x6e\xcf\x5f\x0b"
"\x28\xfb\x80"
"\xf5\xb2\x43\x9d\xe2\x6b\x8a\x52\x20\x0b\xc7\xd6\xaf\x7a\x48"
"\x40\x72\x1f"
"\xe8\xbe\x85\x24\xa6\x91\xb6\xef\x0e\xda\xe9\x0b\xb6\xf5\x92"
"\x78\x94\x81"
"\x9b\x83\x1b\x45\xb5\x3f\x84\x01\xfe\x02\x2d\xbb\x64\xed\x75"
"\x65\x35\x09"
"\x04\xac\x0b\x51\x71\x35\xd7\xf8\xab\xbc\x98\x12\x7f\xb1\x63"
"\x86\x4d\x4d"
"\x4a\x30\x74\x25\xb2\xcd\x43\xdb\x22\xaf\x26\x7d\x71\xc3\x71"
"\x46\x99\x4a"
"\x8c\x48\x05\xad\xc3\x41\xbf\xba\x27\xaf\x09\xfd\x80\xbd\x5e"
"\xff\x51\xd8"
"\x77\x28\x2a\x2f\xbf\xbf\xeb\x10\x19\x9e\x78\x79\xe4\xb9\xd1"
"\x3a\x46\xd5"
"\x7f\xb7\xd7\x86\x82\x48\x53\xe1\xcc\x89\xca\xfb\xaf\x14\xde"
"\x1e\x92\x4c"
"\x94\x4f\xeb\x8b\x62\x6c\xe0\x20\x7d\x6f\x9f\xa9\xd8\x49\xee"
"\xca\xc6\x9b"
"\x42\xd6\xe7\xa2\x3b\xd5\x12\x4d\x49\x62\x2b\x44\xb3\x5c\x5b"
"\x15\xfb\x0e"
"\x6a\x77\x81\xa5\x03\xf1\xa4\xe0\x62\xe0\x15\xd5\x57\xd9\x5d"
"\x44\xd9\xd8"
"\xb0\x79\x9b\x3a\xaf\xce\x83\xd5\xd4",
.senderPrivKey =
"\x0f\x28\xbe\xaf\x7e\x27\x79\x3c\x03\x63\x8d\xc2\x97\x3a"
"\x15\xb0\x01\x6e\x1b\x36\x7c\xbf\xfd\xa8\x86\x1a\xb1\x75"
"\xf3\x1b\xce\x02",
.recvPubKey =
"\x04\xc0\xd1\xa8\x12\xb2\x91\x29\x1d\xd7\xbe\xee\x35\x87\x13"
"\xc1\x26\xc5\x89\xf3\x63\x3c\x26\xd1\xa2\x01\x31\x1d\xe0\x36"
"\xdc\x10\x93\x1e\x4e\xe1\x42\xf6\x19\x21\xa3\xea\x58\x64\xe8"
"\x72\xa9\x38\x41\xa5\x29\x44\xe5\xb3\xf6\xac\xce\xcc\xe8\xc8"
"\x28\xfb\x04\xa4\xcd",
.authSecret =
"\x9d\x77\x35\xd8\xde\x19\x62\xb9\x83\x94\xb0\x7f\xfe\x28\x7e\x20",
.salt =
"\xff\x80\x50\x30\xa1\x08\xe1\x14\xe6\xc1\x7f\xad\x61\x86\xa1\xa6",
.plaintext = "I am the very model of a modern Major-General, I've "
"information vegetable, animal, and mineral",
.plaintextLen = 94,
.padLen = 6,
.maxPayloadLen = 631,
.payloadLen = 441,
.rs = 24,
},
{
// 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.
.desc = "rs = 18, pad = 31",
.payload = "\xe4\x98\x88\xd2\xb2\x8f\x27\x7f\x84\x7b\xc5\xde\x96\xf0\xf8"
"\x1b\x00\x00"
"\x00\x12\x41\x04\x00\xb8\x33\xe4\x81\xa9\x9a\xa3\x30\xdc\xb2"
"\x77\x92\x2d"
"\x5f\x84\xaf\x2e\x9c\xe6\x11\xad\x2a\xd3\xed\x0f\x5b\x43\x19"
"\x12\xd3\x5e"
"\xa7\x2f\xc5\xbf\x76\xb7\x69\xd9\x52\x67\x78\xf5\xab\xfa\x05"
"\x86\x50\x98"
"\x8d\xa5\xe5\x31\xff\x82\xd1\xa7\x04\x37\x94\xc7\x17\x06\x3a"
"\xeb\x95\x8b"
"\xf1\x16\xbc\xcf\x50\x74\x2f\xd4\xd6\x9b\xd0\xea\x7e\x3f\x61"
"\x1c\x70\x9b"
"\xf2\xcd\xf5\xcd\x47\xc6\x42\x6c\xb8\x32\x3b\x53\x98\xc4\x3c"
"\x0d\x0b\x92"
"\xcc\x98\x2d\xa1\xc2\x4c\xe5\xfe\xe2\xb2\x03\xf7\xad\x78\xca"
"\x44\xf0\x49"
"\x0f\x34\x07\xf5\xfe\xe8\x83\x26\x6e\xe4\x70\x35\x19\x5d\xe0"
"\xfe\x6d\x8a"
"\x75\xe4\x87\xdf\x25\x6d\xb5\x97\xa7\x5e\x45\xae\x4f\xb5\x5b"
"\x82\x59\xcb"
"\x0b\x2d\x19\xe7\xb0\x57\x14\x26\x7e\xb5\x60\xae\x07\x2b\x7a"
"\x66\x59\x51"
"\x91\x7a\x06\x87\x32\xdf\x30\x9b\xe2\x56\xf9\x0f\x2a\xdd\xa3"
"\x2f\x05\xfe"
"\xaa\x5e\x9b\x06\x95\xbc\xa2\xcc\xf2\x2a\xae\xfc\x7d\xa9\xce"
"\xeb\xc5\xd4"
"\x0c\x12\xd3\x2a\xdb\x5c\x84\xcb\x32\x0a\xf9\x44\x01\x60\x95"
"\x36\x2f\xeb"
"\xba\x4f\xfa\x4a\x99\x83\x0e\x49\x58\xea\x2b\xba\x50\x8c\xb6"
"\x83\xa5\x8d"
"\x20\x27\xd4\xb7\x47\x26\xa8\x53\xb2\x4b\x47\xcc\xba\x75\x1a"
"\xbe\x9d\x9a"
"\xb2\xda\x9e\xc2\xba\x9c\x7c\xcf\x0c\xf1\x73\x05\xba\xe3\x14"
"\xd3\x8a\x68"
"\x76\x18\xb0\x77\x2f\xcb\x71\xd4\x41\x90\x27\xa4\xbf\x43\x5c"
"\xb7\x21\xaa"
"\xd7\x4e\xfc\x17\x99\x81\xb7\x16\x96\x04\xbf\x97\xec\xac\x41"
"\xe7\x38\x84"
"\x45\x69\x33\x73\x48\x18\x13\x29\x23\xb5\x6c\x15\x2d\x6c\x9e"
"\x59\xae\xf9"
"\x95\xac\xa5\x9d\xe0\xbf\x2c\x80\x3a\x07\x18\x08\x89\x67\x0a"
"\x08\xe6\x4a"
"\x20\xd2\xbf\xa8\x53\xe0\x11\x28\x72\x94\x7b\xaa\xaf\xfb\x51"
"\x0c\xc9\xe7"
"\x5d\x63\x10\xed\x6a\xac\xbd\x2e\x0b\xa3\xa2\x9b\xe4\x2c\x65"
"\x32\xea\x4e"
"\x33\x46\xe1\xf0\x57\x16\x46\x37\x1c\x71\x66\x5e\x3f\xac\x9d"
"\x76\xfa\xee"
"\x1f\x12\x2e\x64\xd4\x90\xdd\x2a\x3e\x31\x81\x6e\xab\x58\x3f"
"\x17\x28\x41"
"\xa0\x75\xd2\x05\xf3\x18\x71\x4a\x8c\x70\xce\x0f\x32\x7f\x4d"
"\x92\xb8\xc9"
"\xdc\xb8\x13\xe6\xd2\x4f\xe8\x56\x33\xf1\xa9\xc7\xc1\xe4\xa1"
"\xfb\x31\x4d"
"\xd5\xfe\x3e\x28\x0e\x39\x08\xf3\x6c\x8c\xbf\xb8\x0b\x7d\x92"
"\x43\xab\xaf"
"\xfa\x65\xc2\x16\xcf\x1a\xa8\xb8\xd6\x26\xa6\x30\xdf\xe8\x18"
"\x6c\xe9\x77"
"\xa5\xb8\xf3\x64\x9d\x37\x53\xb9\x17\x6c\x36\x7e\x4e\x07\xf2"
"\x20\xa1\x75"
"\x80\x61\x38\xe8\x88\x25\xa2\xf3\x49\x84\x20\x58\x2b\x96\x20"
"\x96\x58\xbb"
"\xfa\x8f\x2b\xa6\x93\x3a\x83\xc2\x5e\xdb\x26\x91\x87\x79\x65"
"\x42\xe2\xac"
"\x49\xb8\x07\x86\x36\xbd\xdc\x26\x8e\x11\x62\x5e\x8b\xff\x9f"
"\x0a\x34\x3d"
"\x3a\x4c\x06\x08\x0e\xf0\x80\x3b\x8d\xcd\x8e\x84\x1d\x0e\x27"
"\x59\xe4\x83"
"\xea\x19\xb9\x03\x32\x4d\x9e\xc4\xd5\x2f\x49\x1a\xce\xf3\xee"
"\xff\x44\x1c"
"\x37\x88\x1c\x75\x93\xea\xc3\x16\x21\x33\x7a\x5e\x86\x59\xf9"
"\x3e\x20\x07"
"\x9b\x0e\x26\xeb\xfe\x56\xc1\x04\x55\xd1\x09\x71\x13\x0b\xd2"
"\xa2\xc1\x59"
"\xc7\x4f\x48\xb2\xe5\x26\x53\x0a\x76\xf6\x4c\xca\x2e\xfb\x24"
"\x6e\x79\x3d"
"\x11\xfb\x75\xa6\x68\x01\x8e\x70\xc3\x10\x71\x00\xf8\x1b\xa3"
"\xb1\x6a\xe4"
"\x0a\x83\x8f\x18\xd4\xc4\x7f\x1d\x71\x32\xf1\x74\x68\x8e\xc5"
"\x38\x23\x94"
"\xe0\x11\x99\x21\x73\x1a\x16\x87\x9b\x85\x8f\xf3\x8f\x72\x85"
"\x1e\xa3\xd9"
"\xf5\x26\x3f\xec\x5a\x60\x6d\x12\x71\xa8\x9b\x84\xcc\xa5\x3e"
"\xd7\x3c\x52"
"\x54\xe2\x45\xbf\x8f\x2f\x27\xc2\xc1\xc8\x7f\x39\xee\xa7\x8c"
"\x70\x17\xc8"
"\xc6\xb5\xab\x01\x66\x30\x32\xb5\x8d\xa3\x10\x57\x28\x5e\x56"
"\xc2\x03\xf4"
"\xe4\x8d\x67\x89\xc6\x6b\x26\x95\xa9\x00\xe0\x04\x82\xbd\x84"
"\x65\x59\xec"
"\xdd\xd4\x02\x64\xb3\x8e\x27\x96\x47\xd1\xec\x0f\xcc\xdc\x18"
"\x81\x83\x8b"
"\xbe\x0c\x83\x5e\x26\x90\xef\x05\x8b\x8f\x6a\x03\xe2\x9c\xd9"
"\xeb\x95\x84"
"\xe9\x7f\xbc\x30\x97\x73\xc3\x68\x8e\x5e\x03\xf9\xd3\x8e\x3e"
"\x45\x48\x73"
"\x8a\x5f\x56\x9c\x59\x14\x7d\x3e\x82\x3c\xcc\xac\x71\xd5\xe8"
"\x82\x5d\x51"
"\x34\xce\x98\x13\xcd\x0b\x8f\x96\x27\xa3\xdb\xfa\x45\xb8\x3a"
"\x59\xc8\x3d"
"\x2b\x4d\x3a\xd4\x37\x77\x8a\x3c\xb1\xbc\x77\xba\x16\xc9\x23"
"\x06\xf4\x26"
"\x1a\x2a\x1f\x0d\x5c\x7e\xda\xec\xf9\x26\xf9\x2d\x7c\x9d\xfc"
"\xae\x87\x51"
"\x3a\x68\xb8\xc7\xef\x7c\x63\x26\x4b\x85\x87\x67\xc1\x1a\xaa"
"\x41\xd2\x7c"
"\x63\x6f\x52\xe2\x85\x51\xe9\x3a\x96\x9c\xdc\x96\xd4\x38\x67"
"\xb7\xcb\xd6"
"\x8f\xe0\x35\x7b\xd3\x34\x15\xfa\xf2\x2a\xae\xeb\xc9\x57\xf4"
"\xb5\x73\x7a"
"\x04\xab\x72\x77\xb4\xed\x40\x08\xf0\x9e\xda\xff\x5a\x6d\xb6"
"\x9f\x6c\xb0"
"\x6f\x3d\x0b\x76\x68\x89\x06\xb2\xf5\x3b\x27\xe6\x3f\x37\x28"
"\xba\x2e\xda"
"\x50\x5f\xb1\xb3\x2f\x81\xdd\xdc\x6d\x30\x5f\xd5\x94\x9e\xdd"
"\x05\x49\x0c"
"\xb1\x61\x8f\x0c\xe1\x43\x0e\x9f\x5e\xdf\x50\x01\x2d\xc3",
.senderPrivKey =
"\x78\x30\x57\x7b\xaf\xcf\xc4\x58\x28\xda\x0c\x40\xaa\xb0"
"\x9f\xb2\x27\xbf\xea\xe0\x68\xaa\xb8\xc0\x64\x22\x2a\xcb"
"\xe6\xef\xfd\x34",
.recvPubKey =
"\x04\xc3\xd7\x14\xcb\x42\xe2\xb0\xa1\xd6\xf9\x85\x99\xe2\xf1"
"\x86\xb8\xc2\xba\x6f\x6f\xab\x5e\x09\xa2\xab\xca\x86\x5c\x08"
"\x05\x89\x2b\x2c\x37\x29\x33\x0e\xf8\x3d\xc9\xdf\x4b\x44\x36"
"\x2b\x03\x9a\x06\x09\xd3\x6b\xeb\x93\x21\xa4\x31\xec\x12\x35"
"\x06\xdd\xd9\x0f\x24",
.authSecret =
"\xe4\xd7\xb7\x9d\xec\xde\xde\x12\xc3\xe9\xd9\x0d\x3e\x05\x73\x0f",
.salt =
"\xe4\x98\x88\xd2\xb2\x8f\x27\x7f\x84\x7b\xc5\xde\x96\xf0\xf8\x1b",
.plaintext = "Push the button, Frank!",
.plaintextLen = 23,
.padLen = 31,
.maxPayloadLen = 1265,
.payloadLen = 1058,
.rs = 18,
},
};
void
test_webpush_aes128gcm_encrypt_ok(void) {
size_t tests = sizeof(webpush_aes128gcm_encrypt_ok_tests) /
sizeof(webpush_aes128gcm_encrypt_ok_test_t);
for (size_t i = 0; i < tests; i++) {
webpush_aes128gcm_encrypt_ok_test_t t =
webpush_aes128gcm_encrypt_ok_tests[i];
const void* senderPrivKey = t.senderPrivKey;
const void* authSecret = t.authSecret;
const void* salt = t.salt;
const void* recvPubKey = t.recvPubKey;
const void* plaintext = t.plaintext;
size_t payloadLen =
ece_aes128gcm_payload_max_length(t.rs, t.padLen, t.plaintextLen);
ece_assert(payloadLen == t.maxPayloadLen,
"Got payload max length %zu for `%s`; want %zu", payloadLen,
t.desc, t.maxPayloadLen);
uint8_t* payload = calloc(payloadLen, sizeof(uint8_t));
int err = ece_webpush_aes128gcm_encrypt_with_keys(
senderPrivKey, ECE_WEBPUSH_PRIVATE_KEY_LENGTH, authSecret,
ECE_WEBPUSH_AUTH_SECRET_LENGTH, salt, ECE_SALT_LENGTH, recvPubKey,
ECE_WEBPUSH_PUBLIC_KEY_LENGTH, t.rs, t.padLen, plaintext, t.plaintextLen,
payload, &payloadLen);
ece_assert(!err, "Got %d encrypting payload for `%s`", err, t.desc);
ece_assert(payloadLen == t.payloadLen,
"Got actual payload length %zu for `%s`; want %zu", payloadLen,
t.desc, t.payloadLen);
ece_assert(!memcmp(payload, t.payload, payloadLen),
"Wrong payload for `%s`", t.desc);
free(payload);
}
}
void
test_webpush_aes128gcm_encrypt_pad(void) {
static const uint32_t maxRs = 128;
const void* senderPrivKey = "\xac\xae\xc1\xc3\x7c\x30\x7c\xb9\x02\x8f\xbb\xd9"
"\xc7\xf3\xc6\x89\x26\x60\x08\x95\x9a\x5e\xd4\x03"
"\x42\x21\xb2\xda\x72\x01\x82\x8f";
const void* authSecret =
"\x44\x29\x81\x2d\x53\x5f\xbf\xdb\xea\xc8\x6d\xb7\x14\x5c\x6a\xf2";
const void* salt =
"\x45\x2b\xfb\xea\x8c\xc7\xa7\x57\x14\xd2\x03\xcf\xf1\x02\xe8\x76";
const void* recvPubKey =
"\x04\x2d\x78\x8d\x3e\x8e\x82\xf2\xd7\xea\xef\xbd\xe3\xa1\xbe\xde\xa2\x1f"
"\x3b\xc9\x60\x33\x15\x73\x22\xa0\x9e\x14\x46\x55\xa3\xdf\x78\xfd\xca\xc8"
"\x10\xe3\x02\x2a\xb5\x6a\x0e\xa9\xb8\xec\x06\x73\x8a\xce\x41\x1f\x49\x54"
"\x7b\xc0\x0d\x1a\x1c\xde\x97\xce\x7b\xdd\x26";
// For rs = `ECE_AES128GCM_MIN_RS`, we allow any amount of padding, because we
// can only include one byte of data per record.
for (uint32_t rs = ECE_AES128GCM_MIN_RS + 1; rs <= maxRs; rs++) {
// Generate a random plaintext.
size_t plaintextLen = (size_t)(rand() % (int) (maxRs - 1) + 1);
uint8_t* plaintext = calloc(plaintextLen, sizeof(uint8_t));
int ok = RAND_bytes(plaintext, (int) plaintextLen);
ece_assert(ok == 1, "Got %d generating plaintext for rs = %d", ok, rs);
size_t maxPadLen = (rs - ECE_AES128GCM_MIN_RS) * (plaintextLen + 1);
// Encrypting with the maximum padding length should succeed.
size_t payloadLen =
ece_aes128gcm_payload_max_length(rs, maxPadLen, plaintextLen);
uint8_t* payload = calloc(payloadLen, sizeof(uint8_t));
int err = ece_webpush_aes128gcm_encrypt_with_keys(
senderPrivKey, ECE_WEBPUSH_PRIVATE_KEY_LENGTH, authSecret,
ECE_WEBPUSH_AUTH_SECRET_LENGTH, salt, ECE_SALT_LENGTH, recvPubKey,
ECE_WEBPUSH_PUBLIC_KEY_LENGTH, rs, maxPadLen, plaintext, plaintextLen,
payload, &payloadLen);
ece_assert(!err, "Got %d encrypting with rs = %d, padLen = %zu", err, rs,
maxPadLen);
// Adding more padding should fail.
size_t badPadLen = maxPadLen + 1;
payloadLen = ece_aes128gcm_payload_max_length(rs, badPadLen, plaintextLen);
payload = realloc(payload, payloadLen);
err = ece_webpush_aes128gcm_encrypt_with_keys(
senderPrivKey, ECE_WEBPUSH_PRIVATE_KEY_LENGTH, authSecret,
ECE_WEBPUSH_AUTH_SECRET_LENGTH, salt, ECE_SALT_LENGTH, recvPubKey,
ECE_WEBPUSH_PUBLIC_KEY_LENGTH, rs, badPadLen, plaintext, plaintextLen,
payload, &payloadLen);
ece_assert(err == ECE_ERROR_ENCRYPT_PADDING,
"Want error encrypting with rs = %d, padLen = %zu", rs,
badPadLen);
free(plaintext);
free(payload);
}
}

View file

@ -0,0 +1,219 @@
#include "test.h"
#include <stdlib.h>
#include <string.h>
#include <openssl/rand.h>
typedef struct webpush_aesgcm_encrypt_ok_test_s {
const char* desc;
const char* ciphertext;
const char* senderPrivKey;
const char* recvPubKey;
const char* authSecret;
const char* salt;
const char* plaintext;
size_t plaintextLen;
size_t padLen;
size_t maxCiphertextLen;
size_t ciphertextLen;
uint32_t rs;
} webpush_aesgcm_encrypt_ok_test_t;
static webpush_aesgcm_encrypt_ok_test_t webpush_aesgcm_encrypt_ok_tests[] = {
{
.desc = "Example from draft-ietf-webpush-encryption-04",
.ciphertext = "\xea\x7a\x80\x41\x43\x04\xf2\x13\x6a\xc3\x92\x77\x92\x5f"
"\x1c\xa5\x55\x49\xca\x55\xca\x62\xa6\x4e\x7a\xc7\x99\x1b"
"\xc5\x2e\x78\xaa\x40",
.senderPrivKey = "\x9c\x24\x9c\x7a\x4f\x90\xa4\x48\xe6\x38\xe9\x53\xfa"
"\xb4\x37\xf2\x76\x73\xbd\xd3\xe5\xa9\xad\x34\x67\x2d"
"\x22\xea\x6d\x8e\x26\xf6",
.recvPubKey = "\x04\x21\x24\x06\x3c\xcb\xf1\x9d\xc2\xfa\x88\xb6\x43\xba"
"\x04\xe6\xdd\x8d\xa7\xea\x7b\xa2\xc8\xc6\x2e\x0f\x77\xa9"
"\x43\xf4\xc2\xfa\x91\x4f\x6d\x44\x11\x6c\x9f\xd1\xc4\x03"
"\x41\xc6\xa4\x40\xca\xb3\xe2\x14\x0a\x60\xe4\x37\x8a\x5d"
"\xa7\x35\x97\x2d\xe0\x78\x00\x51\x05",
.authSecret =
"\x47\x6f\x6f\x20\x67\x6f\x6f\x20\x67\x27\x20\x6a\x6f\x6f\x62\x21",
.salt = "\x96\x78\x1a\xad\xbc\x8a\x7c\xca\x22\xf5\x9e\xf9\xc5\x85\xe6\x92",
.plaintext = "I am the walrus",
.plaintextLen = 15,
.padLen = 0,
.maxCiphertextLen = 33,
.ciphertextLen = 33,
.rs = 4096,
},
{
.desc = "rs = 4, trailer",
.ciphertext =
"\xe8\x7d\x81\x05\x8d\x25\xd1\xdb\xfc\xf3\x1c\x5d\x52\xd4\x39\x51\xb4"
"\x7c\x63\x07\x4d\x6d\xb3\xf0\xaf\x89\xe4\xb3\xae\x64\x3d\x00\xb5\xce"
"\xda\x84\x4c\xf7\x9b\xce\xb7\xf9\x4e\xdc\x95\xa9\xf8\x78\x4d\xcf\xf0"
"\xb7\xeb\xdc\x27\x7e\x9a\x17\xdb\xd4\x62\x78\xaa\x8d\x7e\xe9\x65\x24"
"\x81\x22\xe8\x40\x81\x37\x4c\xab\xd0\x3d\xb8\x3b\x98\x39\xdb\x34\x28"
"\x88\xf9\x68\x58\x35\x26\x58\x34\xbb\x11\xe6\xdb\x1e\xb0\x95\xcd\x17"
"\x5e\x12\xc5\xf8\x3f\x1f\xb6\xed\x3a\x4a\x00\x2b\x05\x7c\x10\xeb\x97"
"\x94\x16\x6a\xc6\x1d\xc0\x00\x74\xf8\xd0\x95\xbb\xb6\x7f\xa8\x18\x35"
"\xac\x1b\xa7\x16\xc8\xde\x94\xda\x49\xfb\xdb\x0a\x74\xf6\xac\x68\x89"
"\x7b\xb3\x8d\x3f\xba\x88\x7a\x8b\xc9\x44\xa4\xc9\xdd\x83\x61\x20\x8b"
"\xc3\x82\x57\x65\xe6\xb8\xc7\x69\xd5\x4d\xa7\xa4\x6f\x26\xf0\x50\xd0"
"\xad\xc9\x5a\x16\xde\xa1\xa7\x50\xb0\xf7\xcf\x4d\x3b\xf4\xf5\x1f\xad"
"\x32\xaf\x7a\x0a\x4a\x6a\x4c\x33\x2b\xa8\x49\xe5\xcf\x78\xa3\xf7\x8b"
"\x38\x98\xdc\x3a\x8c\x95\x3c\xde\xb1\xc9\x79\x9b\x0e\xb4\x6e\xad\x37"
"\xe0\xfa\xf4\xba\xc2\xbe\xd6\x56\x58\xb4\x86\x1c\xf9\x42\xfd\x77\xf2"
"\x36\x33\xc5\xe5\xa1\x2e\xe1\x3c\xba\xd7\x64\xa2\x1a\x92\x5c\x79\x47"
"\xf1\xd3\x18\x34\x9a\x9d\x96\x10\x71\xb8\x74\xc6\xb0\xc1\x06\x66\x62"
"\x34\x97\x27\x84\x36\xa9\x3c\xd1\xa9\xa4\xe0\x32\xa9\x64\x9f\x29\x7a"
"\x4d\x8b\xa4\x59\x44\xf6\x3b\xdb\x0b\xce\x45\xce\x0d\x75\xd3\x7c\x24"
"\xa0\x69\x0f\xa0\x96\x0d\x86\x6e\x99\x2d\x61\x63\x15\xba\x01\x3b\x80"
"\xc6\xe3\xac\xfd\x97\x26\x07\x42\x4b\xec\x10\x88\x8d\xd5\x0a\x15\x13"
"\x42\xf1\x5d\x48\x18\x36\xfd\xf3\x65\x8c\x86\xe8\x58\xac\xc0\xf2\x5f"
"\x3f\xe0\xce\xe5\xc2\x29\x5f\x04\x4e\x1e\x82\x89\xbc\x52\x50\x9d\x4f"
"\x5c\x4e\xb5\x56\x5d\x4e\x62\x0a\xe5\x9c\xdb\x26\xa5\x02\x09\x63\xfc"
"\x6e\x43\xca\x0e\x7c\x45\x29\xb3\x22\x38\xfd\x3a\xf6\x0e\xda\xf6\xc5"
"\x6c\x9f\xb4\x08\x95\xb1\xe6\x01\x4a\xd7\x3b\x69\xb3\xcb\x55\x2c\xe8"
"\xc0\xa8\xbb\x14\xff\xe9\x66\xc7\x4d\x40\xfc\x28\xd2\x05\xab\xee\x5f"
"\x73\xed\x9a\x0d\xb9\x64\xe0\x69\xe4\x86\xb3\x02\xef\x58\xcc\xf6\xa3"
"\x4b\x4e",
.senderPrivKey = "\xf1\xfe\xd7\x17\xb0\xd8\x35\xd2\x9b\x9e\x4e\x4f\x4c"
"\xf8\x7d\x0c\xb9\x85\xe2\xbc\x44\xff\xd6\xd4\x37\xa4"
"\xe7\x4a\x18\x04\x55\x87",
.recvPubKey = "\x04\x09\x22\xf1\x7c\xed\x7d\x09\xd4\x55\x10\x2d\xd9\x0c"
"\x31\x46\xce\x33\x2f\x45\xd2\x34\x96\x46\xfe\x64\xca\xbb"
"\x7f\x41\x23\x03\xbe\x5c\x23\x53\x9a\xff\x5c\x30\x72\xdd"
"\x85\xa9\x0b\x76\x92\xe1\x96\x22\x09\x39\xee\xb7\x3e\x0e"
"\xf2\x47\x59\x32\xb1\xcd\xa9\x16\xc7",
.authSecret =
"\xd3\xf8\x22\xf4\x8c\x74\x74\x19\x5d\xa6\x4d\x78\x5f\x40\x8f\x26",
.salt = "\x77\x10\x67\x4b\x1e\xcd\x0b\xf7\xa7\xd3\x25\x88\xea\xe9\x53\x9c",
.plaintext = "I am the very model of a modern Major-General.",
.plaintextLen = 46,
.padLen = 0,
.maxCiphertextLen = 478,
.ciphertextLen = 478,
.rs = 4,
},
{
.desc = "rs = 6, pad = 4",
.ciphertext = "\x54\x6d\x13\x0b\x1e\xf0\xc2\x3e\xff\x49\x87\xe5\xc6\x57\xf1"
"\x94\xb1\xb8\xad\x5a\xda\xb7\x02\x32\x3c\xf9\xc0\x7b\xd8\x28"
"\x20\x9e\x5d\xe7\x5e\x8b\xf6\x6a\xa5\xf8\xf6\x3b\x0a\x66\xd3"
"\x99\xeb\x8b\x98\x70\x6c\xfc\xa5\xa5\x3f\x8f\x50\x8c\x26\x56"
"\x5a\x34\xe4",
.senderPrivKey = "\xe3\x31\x8e\xc3\x99\xa9\xc4\x71\x7a\xa9\x4b\xa4\xed\xb3"
"\xaa\x1d\x90\x96\x5c\xe7\x6a\x57\x6b\x52\xa0\x7c\x27\x44"
"\x0c\x6d\x9b\xd1",
.recvPubKey = "\x04\xaa\xec\x79\x05\x22\xad\x2b\x70\x56\x05\x58\x99\xda\xa6"
"\x47\x7d\x55\xab\x3f\x16\x5d\x76\x00\x56\x19\xf3\xdf\xa9\x72"
"\x19\xf5\x59\x02\x5d\x09\xc0\x80\x83\x70\xc6\x06\x8a\x66\x51"
"\x81\xd4\x3d\x74\xac\x3d\x0a\xca\x28\x67\x5e\x81\x55\x9a\x20"
"\x4a\x6d\x44\xb5\x91",
.authSecret =
"\x54\x9d\xe2\xfd\x40\x52\xef\x4e\x05\x28\x45\x13\x3e\x37\x61\x3d",
.salt = "\x3a\x4b\xd3\x72\x75\x09\x5d\x71\xc9\x81\xe0\x9c\xde\x0d\xa6\x2a",
.plaintext = "Hello",
.plaintextLen = 5,
.padLen = 4,
.maxCiphertextLen = 63,
.ciphertextLen = 63,
.rs = 6,
},
};
void
test_webpush_aesgcm_encrypt_ok(void) {
size_t tests = sizeof(webpush_aesgcm_encrypt_ok_tests) /
sizeof(webpush_aesgcm_encrypt_ok_test_t);
for (size_t i = 0; i < tests; i++) {
webpush_aesgcm_encrypt_ok_test_t t = webpush_aesgcm_encrypt_ok_tests[i];
const void* senderPrivKey = t.senderPrivKey;
const void* authSecret = t.authSecret;
const void* salt = t.salt;
const void* recvPubKey = t.recvPubKey;
const void* plaintext = t.plaintext;
size_t ciphertextLen =
ece_aesgcm_ciphertext_max_length(t.rs, t.padLen, t.plaintextLen);
ece_assert(ciphertextLen == t.maxCiphertextLen,
"Got ciphertext max length %zu for `%s`; want %zu",
ciphertextLen, t.desc, t.maxCiphertextLen);
uint8_t* ciphertext = calloc(ciphertextLen, sizeof(uint8_t));
int err = ece_webpush_aesgcm_encrypt_with_keys(
senderPrivKey, ECE_WEBPUSH_PRIVATE_KEY_LENGTH, authSecret,
ECE_WEBPUSH_AUTH_SECRET_LENGTH, salt, ECE_SALT_LENGTH, recvPubKey,
ECE_WEBPUSH_PUBLIC_KEY_LENGTH, t.rs, t.padLen, plaintext, t.plaintextLen,
ciphertext, &ciphertextLen);
ece_assert(!err, "Got %d encrypting ciphertext for `%s`", err, t.desc);
ece_assert(ciphertextLen == t.ciphertextLen,
"Got actual ciphertext length %zu for `%s`; want %zu",
ciphertextLen, t.desc, t.ciphertextLen);
ece_assert(!memcmp(ciphertext, t.ciphertext, ciphertextLen),
"Wrong ciphertext for `%s`", t.desc);
free(ciphertext);
}
}
void
test_webpush_aesgcm_encrypt_pad(void) {
static const uint32_t maxRs = 128;
const void* senderPrivKey = "\xac\xae\xc1\xc3\x7c\x30\x7c\xb9\x02\x8f\xbb\xd9"
"\xc7\xf3\xc6\x89\x26\x60\x08\x95\x9a\x5e\xd4\x03"
"\x42\x21\xb2\xda\x72\x01\x82\x8f";
const void* authSecret =
"\x44\x29\x81\x2d\x53\x5f\xbf\xdb\xea\xc8\x6d\xb7\x14\x5c\x6a\xf2";
const void* salt =
"\x45\x2b\xfb\xea\x8c\xc7\xa7\x57\x14\xd2\x03\xcf\xf1\x02\xe8\x76";
const void* recvPubKey =
"\x04\x2d\x78\x8d\x3e\x8e\x82\xf2\xd7\xea\xef\xbd\xe3\xa1\xbe\xde\xa2\x1f"
"\x3b\xc9\x60\x33\x15\x73\x22\xa0\x9e\x14\x46\x55\xa3\xdf\x78\xfd\xca\xc8"
"\x10\xe3\x02\x2a\xb5\x6a\x0e\xa9\xb8\xec\x06\x73\x8a\xce\x41\x1f\x49\x54"
"\x7b\xc0\x0d\x1a\x1c\xde\x97\xce\x7b\xdd\x26";
for (uint32_t rs = ECE_AESGCM_MIN_RS + 1; rs <= maxRs; rs++) {
// Generate a random plaintext.
size_t plaintextLen = (size_t)(rand() % (int) (maxRs - 1) + 1);
uint8_t* plaintext = calloc(plaintextLen, sizeof(uint8_t));
int ok = RAND_bytes(plaintext, (int) plaintextLen);
ece_assert(ok == 1, "Got %d generating plaintext for rs = %d", ok, rs);
size_t maxPadLen = (rs - ECE_AESGCM_MIN_RS) * (plaintextLen + 1);
// Encrypting with the maximum padding length should succeed.
size_t ciphertextLen =
ece_aesgcm_ciphertext_max_length(rs, maxPadLen, plaintextLen);
uint8_t* ciphertext = calloc(ciphertextLen, sizeof(uint8_t));
int err = ece_webpush_aesgcm_encrypt_with_keys(
senderPrivKey, ECE_WEBPUSH_PRIVATE_KEY_LENGTH, authSecret,
ECE_WEBPUSH_AUTH_SECRET_LENGTH, salt, ECE_SALT_LENGTH, recvPubKey,
ECE_WEBPUSH_PUBLIC_KEY_LENGTH, rs, maxPadLen, plaintext, plaintextLen,
ciphertext, &ciphertextLen);
ece_assert(!err, "Got %d encrypting with rs = %d, padLen = %zu", err, rs,
maxPadLen);
// Adding more padding should fail.
size_t badPadLen = maxPadLen + 1;
ciphertextLen =
ece_aesgcm_ciphertext_max_length(rs, badPadLen, plaintextLen);
ciphertext = realloc(ciphertext, ciphertextLen);
err = ece_webpush_aesgcm_encrypt_with_keys(
senderPrivKey, ECE_WEBPUSH_PRIVATE_KEY_LENGTH, authSecret,
ECE_WEBPUSH_AUTH_SECRET_LENGTH, salt, ECE_SALT_LENGTH, recvPubKey,
ECE_WEBPUSH_PUBLIC_KEY_LENGTH, rs, badPadLen, plaintext, plaintextLen,
ciphertext, &ciphertextLen);
ece_assert(err == ECE_ERROR_ENCRYPT_PADDING,
"Want error encrypting with rs = %d, padLen = %zu", rs,
badPadLen);
free(plaintext);
free(ciphertext);
}
}

391
mobile/ios/ThirdParty/ecec/test/params.c vendored Normal file
View file

@ -0,0 +1,391 @@
#include "test.h"
#include <inttypes.h>
#include <string.h>
typedef struct webpush_aesgcm_from_params_test_s {
const char* cryptoKey;
const char* encryption;
const char* salt;
const char* rawSenderPubKey;
size_t cryptoKeyLen;
size_t encryptionLen;
size_t saltLen;
size_t rawSenderPubKeyLen;
uint32_t rs;
} webpush_aesgcm_from_params_test_t;
static webpush_aesgcm_from_params_test_t webpush_aesgcm_from_params_tests[] = {
{
.cryptoKey = "dh=Iy1Je2Kv11A",
.cryptoKeyLen = 14,
.encryption = "rs=7;salt=upk1yFkp1xI",
.encryptionLen = 21,
.salt = "\xba\x99\x35\xc8\x59\x29\xd7\x12",
.saltLen = 8,
.rawSenderPubKey = "\x23\x2d\x49\x7b\x62\xaf\xd7\x50",
.rawSenderPubKeyLen = 8,
.rs = 7,
},
{
.cryptoKey = "dh=mwyOULZ4upjVbCpQLRLOeg",
.cryptoKeyLen = 25,
.encryption = "rs=4096;salt=qzqku7FRdW9Vs97w8O8DoA",
.encryptionLen = 35,
.salt = "\xab\x3a\xa4\xbb\xb1\x51\x75\x6f\x55\xb3\xde\xf0\xf0\xef\x03\xa0",
.saltLen = 16,
.rawSenderPubKey =
"\x9b\x0c\x8e\x50\xb6\x78\xba\x98\xd5\x6c\x2a\x50\x2d\x12\xce\x7a",
.rawSenderPubKeyLen = 16,
.rs = 4096,
},
};
typedef struct webpush_aesgcm_extract_params_ok_test_s {
const char* desc;
const char* cryptoKey;
const char* encryption;
const char* salt;
const char* rawSenderPubKey;
uint32_t rs;
} webpush_aesgcm_extract_params_ok_test_t;
static webpush_aesgcm_extract_params_ok_test_t
webpush_aesgcm_extract_params_ok_tests[] = {
{
.desc = "Multiple keys in Crypto-Key header",
.cryptoKey = "keyid=p256dh;dh=Iy1Je2Kv11A,p256ecdsa=o2M8QfiEKuI",
.encryption = "keyid=p256dh;salt=upk1yFkp1xI",
.salt = "\xba\x99\x35\xc8\x59\x29\xd7\x12",
.rawSenderPubKey = "\x23\x2d\x49\x7b\x62\xaf\xd7\x50",
.rs = 4096,
},
{
.desc = "Multiple keys in both headers",
.cryptoKey = "keyid=a;dh=bX0VbuZy8HQ,dh=Iy1Je2Kv11A;keyid=p256dh",
.encryption =
"salt=upk1yFkp1xI;rs=48;keyid=p256dh,salt=U0DM1JsdIbU;keyid=a",
.salt = "\xba\x99\x35\xc8\x59\x29\xd7\x12",
.rawSenderPubKey = "\x23\x2d\x49\x7b\x62\xaf\xd7\x50",
.rs = 48,
},
{
.desc = "Quoted key ID pair value",
.cryptoKey = "dh=\"byfHbUffc-k\"",
.encryption = "salt=C11AvAsp6Gc",
.salt = "\x0b\x5d\x40\xbc\x0b\x29\xe8\x67",
.rawSenderPubKey = "\x6f\x27\xc7\x6d\x47\xdf\x73\xe9",
.rs = 4096,
},
{
.desc = "Quoted salt pair value and rs = 24",
.cryptoKey = "dh=ybuT4VDz-Bg",
.encryption = "rs=24; salt=\"H7U7wcIoIKs\"",
.salt = "\x1f\xb5\x3b\xc1\xc2\x28\x20\xab",
.rawSenderPubKey = "\xc9\xbb\x93\xe1\x50\xf3\xf8\x18",
.rs = 24,
},
{
.desc = "Multiple keys, extra whitespace, strange key ID",
.cryptoKey = " dh= \"ujIToeKunCY\" ,keyid = hello ; dh = I7p5M0yyP8A ",
.encryption = "salt=ie_oYLhw7SI; keyid=\"hello\"; rs =6 , salt = "
"6NAh50bfJZc ;keyid=ujIToeKunCY ",
.salt = "\x89\xef\xe8\x60\xb8\x70\xed\x22",
.rawSenderPubKey = "\x23\xba\x79\x33\x4c\xb2\x3f\xc0",
.rs = 6,
},
{
// Invalid, but we don't check the entire `Crypto-Key` param once we see a
// match.
.desc = "Duplicate key ID in Crypto-Key",
.cryptoKey = "keyid=a;keyid=b;dh=pbmv1QkcEDY",
.encryption = "salt=Esao8aTBfIk;keyid=b",
.salt = "\x12\xc6\xa8\xf1\xa4\xc1\x7c\x89",
.rawSenderPubKey = "\xa5\xb9\xaf\xd5\x09\x1c\x10\x36",
.rs = 4096,
},
};
typedef struct webpush_aesgcm_extract_params_err_test_s {
const char* desc;
const char* cryptoKey;
const char* encryption;
int err;
} webpush_aesgcm_extract_params_err_test_t;
static webpush_aesgcm_extract_params_err_test_t
webpush_aesgcm_extract_params_err_tests[] = {
{
.desc = "Invalid record size",
.cryptoKey = "dh=pbmv1QkcEDY",
.encryption = "salt=Esao8aTBfIk;rs=bad",
.err = ECE_ERROR_INVALID_RS,
},
{
.desc = "Blank Crypto-Key header",
.cryptoKey = " \t ",
.encryption = "salt=Esao8aTBfIk",
.err = ECE_ERROR_INVALID_CRYPTO_KEY_HEADER,
},
{
.desc = "Empty Encryption header",
.cryptoKey = "dh=pbmv1QkcEDY",
.encryption = "",
.err = ECE_ERROR_INVALID_ENCRYPTION_HEADER,
},
{
.desc = "Crypto-Key missing pair value",
.cryptoKey = "dh=",
.encryption = "salt=Esao8aTBfIk",
.err = ECE_ERROR_INVALID_CRYPTO_KEY_HEADER,
},
{
.desc = "Encryption missing pair value with trailing whitespace",
.cryptoKey = "dh=pbmv1QkcEDY",
.encryption = "salt= ",
.err = ECE_ERROR_INVALID_ENCRYPTION_HEADER,
},
{
.desc = "Crypto-Key pair without value",
.cryptoKey = "dh=pbmv1QkcEDY; keyid, dh=rqowftPcCVo",
.encryption = "salt=Esao8aTBfIk",
.err = ECE_ERROR_INVALID_CRYPTO_KEY_HEADER,
},
{
.desc = "Encryption pair without value",
.cryptoKey = "dh=pbmv1QkcEDY",
.encryption = "rs; salt=Esao8aTBfIk",
.err = ECE_ERROR_INVALID_ENCRYPTION_HEADER,
},
{
.desc = "Crypto-Key missing pair name",
.cryptoKey = "dh=pbmv1QkcEDY; =rqowftPcCVo",
.encryption = "salt=Esao8aTBfIk",
.err = ECE_ERROR_INVALID_CRYPTO_KEY_HEADER,
},
{
.desc = "Encryption missing pair name",
.cryptoKey = "dh=pbmv1QkcEDY",
.encryption = "=Esao8aTBfIk",
.err = ECE_ERROR_INVALID_ENCRYPTION_HEADER,
},
{
.desc = "Whitespace in quoted Crypto-Key pair value",
.cryptoKey = "dh=byfHbUffc-k; param=\" \"",
.encryption = "salt=C11AvAsp6Gc",
.err = ECE_ERROR_INVALID_CRYPTO_KEY_HEADER,
},
{
.desc = "Empty quoted value in Encryption header",
.cryptoKey = "dh=byfHbUffc-k",
.encryption = "salt=\"\"; rs=6",
.err = ECE_ERROR_INVALID_ENCRYPTION_HEADER,
},
{
.desc = "Invalid character in Crypto-Key pair value",
.cryptoKey = "dh==byfHbUffc-k",
.encryption = "salt=C11AvAsp6Gc",
.err = ECE_ERROR_INVALID_CRYPTO_KEY_HEADER,
},
{
.desc = "Invalid character in Encryption pair name",
.cryptoKey = "dh=byfHbUffc-k",
.encryption = "sa!t=C11AvAsp6Gc",
.err = ECE_ERROR_INVALID_ENCRYPTION_HEADER,
},
{
.desc = "Leading , in Crypto-Key header",
.cryptoKey = ",dh=byfHbUffc-k",
.encryption = "salt=C11AvAsp6Gc",
.err = ECE_ERROR_INVALID_CRYPTO_KEY_HEADER,
},
{
.desc = "Trailing ; in Crypto-Key header",
.cryptoKey = "dh=byfHbUffc-k;",
.encryption = "salt=C11AvAsp6Gc",
.err = ECE_ERROR_INVALID_CRYPTO_KEY_HEADER,
},
{
.desc = "Leading ; in Encryption header",
.cryptoKey = "dh=byfHbUffc-k",
.encryption = "; salt=C11AvAsp6Gc",
.err = ECE_ERROR_INVALID_ENCRYPTION_HEADER,
},
{
.desc = "Trailing , in Encryption header",
.cryptoKey = "dh=byfHbUffc-k",
.encryption = "salt=C11AvAsp6Gc,",
.err = ECE_ERROR_INVALID_ENCRYPTION_HEADER,
},
{
.desc = "Unterminated quoted value in Encryption header",
.cryptoKey = "dh=byfHbUffc-k",
.encryption = "rs=6; salt=\"C11AvAsp6Gc",
.err = ECE_ERROR_INVALID_ENCRYPTION_HEADER,
},
{
.desc = "Invalid quoted name in Crypto-Key header",
.cryptoKey = "\"dh\"=\"byfHbUffc-k\"",
.encryption = "salt=C11AvAsp6Gc",
.err = ECE_ERROR_INVALID_CRYPTO_KEY_HEADER,
},
{
.desc = "Invalid quoted name in Encryption header",
.cryptoKey = "dh=byfHbUffc-k",
.encryption = "\"salt\"=C11AvAsp6Gc",
.err = ECE_ERROR_INVALID_ENCRYPTION_HEADER,
},
{
.desc = "Mismatched key IDs",
.cryptoKey = "keyid=p256dh;dh=pbmv1QkcEDY",
.encryption = "keyid=different;salt=Esao8aTBfIk",
.err = ECE_ERROR_INVALID_DH,
},
{
.desc = "Multiple mismatched key IDs",
.cryptoKey = "keyid=a;dh=bX0VbuZy8HQ,dh=Iy1Je2Kv11A;keyid=b",
.encryption = "salt=upk1yFkp1xI;rs=48;keyid=c,salt=U0DM1JsdIbU;keyid=d",
.err = ECE_ERROR_INVALID_DH,
},
{
.desc = "Key ID with matching pair value, wrong pair name",
.cryptoKey = "p256dh=p256dh;dh=pbmv1QkcEDY",
.encryption = "keyid=p256dh;salt=Esao8aTBfIk",
.err = ECE_ERROR_INVALID_DH,
},
{
.desc = "Invalid Base64url-encoded salt",
.cryptoKey = "dh=pbmv1QkcEDY",
.encryption = "salt=99999",
.err = ECE_ERROR_INVALID_SALT,
},
{
.desc = "Invalid Base64url-encoded dh pair value",
.cryptoKey = "dh=zzzzz",
.encryption = "salt=Esao8aTBfIk",
.err = ECE_ERROR_INVALID_DH,
},
{
.desc = "Invalid character at end of salt pair name",
.cryptoKey = "dh=pbmv1QkcEDY",
.encryption = "salt !=Esao8aTBfIk",
.err = ECE_ERROR_INVALID_ENCRYPTION_HEADER,
},
{
.desc = "Invalid character at end of salt",
.cryptoKey = "dh=pbmv1QkcEDY",
.encryption = "salt=Esao8aTBfIk!",
.err = ECE_ERROR_INVALID_ENCRYPTION_HEADER,
},
{
.desc = "Invalid character after quoted dh pair value",
.cryptoKey = "dh=\"pbmv1QkcEDY\"!",
.encryption = "salt=Esao8aTBfIk",
.err = ECE_ERROR_INVALID_CRYPTO_KEY_HEADER,
},
{
.desc = "Duplicate key ID in Encryption header",
.cryptoKey = "keyid=b;dh=pbmv1QkcEDY",
.encryption = "keyid=a;salt=Esao8aTBfIk;keyid=b",
.err = ECE_ERROR_INVALID_ENCRYPTION_HEADER,
},
{
.desc = "Duplicate record size in Encryption header",
.cryptoKey = "dh=pbmv1QkcEDY",
.encryption = "rs=5;salt=Esao8aTBfIk;rs=10",
.err = ECE_ERROR_INVALID_ENCRYPTION_HEADER,
},
{
.desc = "Duplicate salt in Encryption header",
.cryptoKey = "dh=pbmv1QkcEDY",
.encryption = "salt=Esao8aTBfIk; salt=Esao8aTBfIk",
.err = ECE_ERROR_INVALID_ENCRYPTION_HEADER,
},
{
.desc = "Missing salt in Encryption header",
.cryptoKey = "dh=pbmv1QkcEDY",
.encryption = "rs=5",
.err = ECE_ERROR_INVALID_SALT,
},
};
void
test_webpush_aesgcm_headers_from_params(void) {
size_t length = sizeof(webpush_aesgcm_from_params_tests) /
sizeof(webpush_aesgcm_from_params_test_t);
for (size_t i = 0; i < length; i++) {
webpush_aesgcm_from_params_test_t t = webpush_aesgcm_from_params_tests[i];
size_t cryptoKeyLen = 0;
size_t encryptionLen = 0;
int err = ece_webpush_aesgcm_headers_from_params(
t.salt, t.saltLen, t.rawSenderPubKey, t.rawSenderPubKeyLen, t.rs, NULL,
&cryptoKeyLen, NULL, &encryptionLen);
ece_assert(!err, "Got %d determining lengths for (%s, %s)", err,
t.cryptoKey, t.encryption);
ece_assert(cryptoKeyLen == t.cryptoKeyLen,
"Got Crypto-Key length %zu for (%s, %s); want %zu", cryptoKeyLen,
t.cryptoKey, t.encryption, t.cryptoKeyLen);
ece_assert(encryptionLen == t.encryptionLen,
"Got Encryption length %zu for (%s, %s); want %zu",
encryptionLen, t.cryptoKey, t.encryption, t.encryptionLen);
char* cryptoKey = malloc(cryptoKeyLen + 1);
char* encryption = malloc(encryptionLen + 1);
err = ece_webpush_aesgcm_headers_from_params(
t.salt, t.saltLen, t.rawSenderPubKey, t.rawSenderPubKeyLen, t.rs,
cryptoKey, &cryptoKeyLen, encryption, &encryptionLen);
ece_assert(!err, "Got %d formatting headers for (%s, %s)", err, t.cryptoKey,
t.encryption);
ece_assert(!memcmp(cryptoKey, t.cryptoKey, t.cryptoKeyLen),
"Wrong Crypto-Key for (%s, %s)", t.cryptoKey, t.encryption);
ece_assert(!memcmp(encryption, t.encryption, t.encryptionLen),
"Wrong Encryption for (%s, %s)", t.cryptoKey, t.encryption);
free(cryptoKey);
free(encryption);
}
}
void
test_webpush_aesgcm_headers_extract_params_ok(void) {
size_t length = sizeof(webpush_aesgcm_extract_params_ok_tests) /
sizeof(webpush_aesgcm_extract_params_ok_test_t);
for (size_t i = 0; i < length; i++) {
webpush_aesgcm_extract_params_ok_test_t t =
webpush_aesgcm_extract_params_ok_tests[i];
uint8_t salt[8];
uint32_t rs;
uint8_t rawSenderPubKey[8];
int err = ece_webpush_aesgcm_headers_extract_params(
t.cryptoKey, t.encryption, salt, 8, rawSenderPubKey, 8, &rs);
ece_assert(!err, "Got %d extracting params for `%s`", err, t.desc);
ece_assert(!memcmp(salt, t.salt, 8), "Wrong salt for `%s`", t.desc);
ece_assert(rs == t.rs, "Got rs = %" PRIu32 " for `%s`; want %" PRIu32, rs,
t.desc, t.rs);
ece_assert(!memcmp(rawSenderPubKey, t.rawSenderPubKey, 8),
"Wrong public key for `%s`", t.desc);
}
}
void
test_webpush_aesgcm_headers_extract_params_err(void) {
size_t length = sizeof(webpush_aesgcm_extract_params_err_tests) /
sizeof(webpush_aesgcm_extract_params_err_test_t);
for (size_t i = 0; i < length; i++) {
webpush_aesgcm_extract_params_err_test_t t =
webpush_aesgcm_extract_params_err_tests[i];
uint8_t salt[8];
uint32_t rs;
uint8_t rawSenderPubKey[8];
int err = ece_webpush_aesgcm_headers_extract_params(
t.cryptoKey, t.encryption, salt, 8, rawSenderPubKey, 8, &rs);
ece_assert(err == t.err, "Got %d extracting params for `%s`; want %d", err,
t.desc, t.err);
}
}

66
mobile/ios/ThirdParty/ecec/test/test.c vendored Normal file
View file

@ -0,0 +1,66 @@
#include "test.h"
#include <inttypes.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
int
main() {
test_webpush_aesgcm_headers_from_params();
test_webpush_aesgcm_headers_extract_params_ok();
test_webpush_aesgcm_headers_extract_params_err();
test_webpush_aesgcm_encrypt_ok();
test_webpush_aesgcm_encrypt_pad();
test_webpush_aesgcm_decrypt_ok();
test_webpush_aesgcm_decrypt_err();
test_webpush_aes128gcm_encrypt_ok();
test_webpush_aes128gcm_encrypt_pad();
test_webpush_aes128gcm_decrypt_ok();
test_webpush_aes128gcm_decrypt_err();
test_aes128gcm_decrypt_ok();
test_aes128gcm_decrypt_err();
test_webpush_aes128gcm_e2e();
test_webpush_aesgcm_e2e();
test_base64url_encode();
test_base64url_decode();
return 0;
}
void
ece_log(const char* funcName, int line, const char* expr, const char* format,
...) {
char* message = NULL;
va_list args;
va_start(args, format);
// Determine the size of the formatted message, then allocate and write to a
// buffer large enough to hold the message. `vsnprintf` mutates its argument
// list, so we make a copy for calculating the size.
va_list sizeArgs;
va_copy(sizeArgs, args);
int size = vsnprintf(NULL, 0, format, sizeArgs);
va_end(sizeArgs);
if (size < 0) {
goto error;
}
message = malloc((size_t) size + 1);
if (!message || vsprintf(message, format, args) != size) {
goto error;
}
message[size] = '\0';
fprintf(stderr, "[%s:%d] (%s): %s\n", funcName, line, expr, message);
goto end;
error:
fprintf(stderr, "[%s:%d]: %s\n", funcName, line, expr);
end:
va_end(args);
free(message);
}

70
mobile/ios/ThirdParty/ecec/test/test.h vendored Normal file
View file

@ -0,0 +1,70 @@
#include <stdio.h>
#include <stdlib.h>
#include <ece.h>
// This macro is similar to the standard `assert`, but accepts a format string
// with an informative failure message.
#define ece_assert(cond, format, ...) \
do { \
if (!(cond)) { \
ece_log(__func__, __LINE__, #cond, format, __VA_ARGS__); \
abort(); \
} \
} while (0)
// Logs an assertion failure to standard error.
void
ece_log(const char* funcName, int line, const char* expr, const char* format,
...);
void
test_webpush_aesgcm_headers_from_params(void);
void
test_webpush_aesgcm_headers_extract_params_ok(void);
void
test_webpush_aesgcm_headers_extract_params_err(void);
void
test_webpush_aesgcm_encrypt_ok(void);
void
test_webpush_aesgcm_encrypt_pad(void);
void
test_webpush_aesgcm_decrypt_ok(void);
void
test_webpush_aesgcm_decrypt_err(void);
void
test_webpush_aes128gcm_encrypt_ok(void);
void
test_webpush_aes128gcm_encrypt_pad(void);
void
test_aes128gcm_decrypt_ok(void);
void
test_webpush_aes128gcm_decrypt_ok(void);
void
test_aes128gcm_decrypt_err(void);
void
test_webpush_aes128gcm_decrypt_err(void);
void
test_webpush_aes128gcm_e2e(void);
void
test_webpush_aesgcm_e2e(void);
void
test_base64url_encode(void);
void
test_base64url_decode(void);

View file

@ -0,0 +1,88 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ece.h>
int
main(int argc, char** argv) {
if (argc < 4) {
fprintf(stderr, "Usage: %s <auth-secret> <receiver-private> <message>",
argv[0]);
return 2;
}
int err = 0;
uint8_t* payload = NULL;
uint8_t* plaintext = NULL;
uint8_t authSecret[ECE_WEBPUSH_AUTH_SECRET_LENGTH];
if (!ece_base64url_decode(argv[1], strlen(argv[1]),
ECE_BASE64URL_REJECT_PADDING, authSecret,
ECE_WEBPUSH_AUTH_SECRET_LENGTH)) {
fprintf(stderr, "Error: Failed to Base64url-decode auth secret\n");
goto error;
}
uint8_t rawRecvPrivKey[ECE_WEBPUSH_PRIVATE_KEY_LENGTH];
if (!ece_base64url_decode(argv[2], strlen(argv[2]),
ECE_BASE64URL_REJECT_PADDING, rawRecvPrivKey,
ECE_WEBPUSH_PRIVATE_KEY_LENGTH)) {
fprintf(stderr, "Error: Failed to Base64url-decode private key\n");
goto error;
}
size_t payloadBase64Len = strlen(argv[3]);
size_t payloadLen = ece_base64url_decode(
argv[3], payloadBase64Len, ECE_BASE64URL_REJECT_PADDING, NULL, 0);
if (!payloadLen) {
fprintf(stderr, "Error: Empty or invalid Base64url-encoded message\n");
goto error;
}
payload = calloc(payloadLen, sizeof(uint8_t));
if (!payload) {
fprintf(
stderr,
"Error: Failed to allocate %zu bytes for Base64url-decoded message\n",
payloadLen);
goto error;
}
payloadLen =
ece_base64url_decode(argv[3], payloadBase64Len,
ECE_BASE64URL_REJECT_PADDING, payload, payloadLen);
if (!payloadLen) {
fprintf(stderr, "Error: Failed to Base64url-decode message\n");
goto error;
}
size_t plaintextLen = ece_aes128gcm_plaintext_max_length(payload, payloadLen);
if (!plaintextLen) {
fprintf(stderr, "Error: Encrypted message too short\n");
goto error;
}
plaintextLen++;
plaintext = calloc(plaintextLen, sizeof(uint8_t));
if (!plaintext) {
fprintf(stderr,
"Error: Failed to allocate %zu bytes for decrypted message\n",
plaintextLen);
goto error;
}
err = ece_webpush_aes128gcm_decrypt(
rawRecvPrivKey, ECE_WEBPUSH_PRIVATE_KEY_LENGTH, authSecret,
ECE_WEBPUSH_AUTH_SECRET_LENGTH, payload, payloadLen, plaintext,
&plaintextLen);
if (err) {
fprintf(stderr, "Error: Failed to decrypt message: %d\n", err);
goto error;
}
plaintext[plaintextLen] = '\0';
printf("Decrypted message: %s\n", plaintext);
goto end;
error:
err = 1;
end:
free(payload);
free(plaintext);
return err;
}

View file

@ -0,0 +1,63 @@
#include <stdio.h>
#include <stdlib.h>
#include <ece.h>
static const char ece_keygen_hex_alphabet[] = "0123456789abcdef";
char*
ece_keygen_hex_encode(const uint8_t* binary, size_t binaryLen) {
if (binaryLen > SIZE_MAX / 2 - 1) {
return NULL;
}
char* encoded = malloc(binaryLen * 2 + 1);
if (!encoded) {
return NULL;
}
char* hex = encoded;
for (size_t i = 0; i < binaryLen; i++) {
*hex++ = ece_keygen_hex_alphabet[(binary[i] >> 4) & 0xf];
*hex++ = ece_keygen_hex_alphabet[binary[i] & 0xf];
}
*hex = '\0';
return encoded;
}
int
main(int argc, char** argv) {
uint8_t rawRecvPrivKey[ECE_WEBPUSH_PRIVATE_KEY_LENGTH];
uint8_t rawRecvPubKey[ECE_WEBPUSH_PUBLIC_KEY_LENGTH];
uint8_t authSecret[ECE_WEBPUSH_AUTH_SECRET_LENGTH];
int err = ece_webpush_generate_keys(
rawRecvPrivKey, ECE_WEBPUSH_PRIVATE_KEY_LENGTH, rawRecvPubKey,
ECE_WEBPUSH_PUBLIC_KEY_LENGTH, authSecret, ECE_WEBPUSH_AUTH_SECRET_LENGTH);
if (err) {
fprintf(stderr, "Error: Failed to generate subscription keys: %d\n", err);
return 1;
}
char* hexRecvPrivKey =
ece_keygen_hex_encode(rawRecvPrivKey, ECE_WEBPUSH_PRIVATE_KEY_LENGTH);
if (hexRecvPrivKey) {
printf("Private key: %s\n", hexRecvPrivKey);
}
char* hexRecvPubKey =
ece_keygen_hex_encode(rawRecvPubKey, ECE_WEBPUSH_PUBLIC_KEY_LENGTH);
if (hexRecvPubKey) {
printf("Public key: %s\n", hexRecvPubKey);
}
char* hexAuthSecret =
ece_keygen_hex_encode(authSecret, ECE_WEBPUSH_AUTH_SECRET_LENGTH);
if (hexAuthSecret) {
printf("Authentication secret: %s\n", hexAuthSecret);
}
free(hexRecvPrivKey);
free(hexRecvPubKey);
free(hexAuthSecret);
return 0;
}