KO
|
EN
gitlite — search
Search
#javascript
#python
#hacktoberfest
#react
#ai
#typescript
#llm
#go
#golang
#android
#machine-learning
#rust
#deep-learning
#linux
ecec
★ 31
Open GitHub ↗
Web Push encryption and decryption in C.
Download README (.md)
Explore Similar Repositories
UT-ECE-Student-Resources
:
A curated list of valuable resources from our studies at the University of Tehran (UT), School of Electrical and Computer Engineering (ECE)
ecef
:
欢迎使用水星浏览器支持库。 水星浏览器支持库创建于《Chromium Embedded Framework》工程实现对易语言开发Chromium浏览器应用程序的支持。
Radar-Detecting-and-Tracking
:
ECE 2595 Radar Signal Processing Projects
ecency-vision
:
Immutable, decentralized, uncensored, rewarding communities built, owned and operated by its users
ece_fl_public
:
No description available.
// repository documentation
Was this content helpful?
★ 0
(0 ratings)
Select Rating:
★
★
★
★
★
Submit Feedback
Recent Feedback
×
Download README
Do you want to download the
README.md
file for
ecec
?
Download (.md)
# ecec  [](https://travis-ci.org/web-push-libs/ecec) [](https://codecov.io/github/web-push-libs/ecec) **ecec** is a C implementation of [HTTP Encrypted Content-Encoding](https://tools.ietf.org/html/rfc8188). 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) + [Encryption](#encryption) + [Decryption](#decryption) * [`aesgcm`](#aesgcm) + [Encryption](#encryption-1) + [Decryption](#decryption-1) - [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 described in [RFC 8188](https://tools.ietf.org/html/rfc8188). It's supported in Firefox 55+ and Chrome 60+, and replaces the older `aesgcm` scheme from earlier drafts. This scheme includes the salt, record size, and sender public key in a binary header block in the payload. #### Encryption This example program writes an encrypted Web Push message to a file, then prints out a [`curl`](https://curl.haxx.se/) command to send the message. ```c #include <assert.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <ece.h> int main() { // The endpoint, public key, and auth secret for the push subscription. These // are exposed via `JSON.stringify(pushSubscription)` in the browser. const char* endpoint = "https://updates.push.services.mozilla.com/..."; const char* p256dh = "BDwwYm4O5dZG9SO6Vaz168iDLGWMmitkj5LFvunvMfgmI2fZdAEaiHT" "DfKR0fvr0D3V56cSGSeUwP0xNdrXho5k"; const char* auth = "xcmQLthL5H2pJNuxrZO-qQ"; // The message to encrypt. const void* plaintext = "I'm just like my country, I'm young, scrappy, and " "hungry, and I'm not throwing away my shot."; size_t plaintextLen = strlen(plaintext); // How many bytes of padding to include in the encrypted message. Padding // obfuscates the plaintext length, making it harder to guess the contents // based on the encrypted payload length. size_t padLen = 0; // Base64url-decode the subscription public key and auth secret. `recv` is // short for "receiver", which, in our case, is the browser. uint8_t rawRecvPubKey[ECE_WEBPUSH_PUBLIC_KEY_LENGTH]; size_t rawRecvPubKeyLen = ece_base64url_decode(p256dh, strlen(p256dh), ECE_BASE64URL_REJECT_PADDING, rawRecvPubKey, ECE_WEBPUSH_PUBLIC_KEY_LENGTH); assert(rawRecvPubKeyLen > 0); uint8_t authSecret[ECE_WEBPUSH_AUTH_SECRET_LENGTH]; size_t authSecretLen = ece_base64url_decode(auth, strlen(auth), ECE_BASE64URL_REJECT_PADDING, authSecret, ECE_WEBPUSH_AUTH_SECRET_LENGTH); assert(authSecretLen > 0); // Allocate a buffer large enough to hold the encrypted payload. The payload // length depends on the record size, padding, and plaintext length, plus a // fixed-length header block. Smaller records and additional padding take // more space. The maximum payload length rounds up to the nearest whole // record, so the actual length after encryption might be smaller. size_t payloadLen = ece_aes128gcm_payload_max_length(ECE_WEBPUSH_DEFAULT_RS, padLen, plaintextLen); assert(payloadLen > 0); uint8_t* payload = calloc(payloadLen, sizeof(uint8_t)); assert(payload); // Encrypt the plaintext. `payload` holds the header block and ciphertext; // `payloadLen` is an in-out parameter set to the actual payload length. int err = ece_webpush_aes128gcm_encrypt( rawRecvPubKey, rawRecvPubKeyLen, authSecret, authSecretLen, ECE_WEBPUSH_DEFAULT_RS, padLen, plaintext, plaintextLen, payload, &payloadLen); assert(err == ECE_OK); // Write the payload out to a file. const char* filename = "aes128gcm.bin"; FILE* payloadFile = fopen(filename, "wb"); assert(payloadFile); size_t payloadFileLen = fwrite(payload, sizeof(uint8_t), payloadLen, payloadFile); assert(payloadLen == payloadFileLen); fclose(payloadFile); printf( "curl -v -X POST -H \"Content-Encoding: aes128gcm\" --data-binary @%s %s\n", filename, endpoint); free(payload); return 0; } ``` #### Decryption ```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). #### Encryption ```c #include <assert.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <ece.h> int main() { const char* endpoint = "https://updates.push.services.mozilla.com/..."; const char* p256dh = "BDwwYm4O5dZG9SO6Vaz168iDLGWMmitkj5LFvunvMfgmI2fZdAEaiHT" "DfKR0fvr0D3V56cSGSeUwP0xNdrXho5k"; const char* auth = "xcmQLthL5H2pJNuxrZO-qQ"; const void* plaintext = "I'm just like my country, I'm young, scrappy, and " "hungry, and I'm not throwing away my shot."; size_t plaintextLen = strlen(plaintext); size_t padLen = 0; uint8_t rawRecvPubKey[ECE_WEBPUSH_PUBLIC_KEY_LENGTH]; size_t rawRecvPubKeyLen = ece_base64url_decode(p256dh, strlen(p256dh), ECE_BASE64URL_REJECT_PADDING, rawRecvPubKey, ECE_WEBPUSH_PUBLIC_KEY_LENGTH); assert(rawRecvPubKeyLen > 0); uint8_t authSecret[ECE_WEBPUSH_AUTH_SECRET_LENGTH]; size_t authSecretLen = ece_base64url_decode(auth, strlen(auth), ECE_BASE64URL_REJECT_PADDING, authSecret, ECE_WEBPUSH_AUTH_SECRET_LENGTH); assert(authSecretLen > 0); size_t ciphertextLen = ece_aesgcm_ciphertext_max_length( ECE_WEBPUSH_DEFAULT_RS, padLen, plaintextLen); assert(ciphertextLen > 0); uint8_t* ciphertext = calloc(ciphertextLen, sizeof(uint8_t)); assert(ciphertext); // Encrypt the plaintext and fetch encryption parameters for the headers. // `salt` holds the encryption salt, which we include in the `Encryption` // header. `rawSenderPubKey` holds the ephemeral sender, or app server, // public key, which we include as the `dh` parameter in the `Crypto-Key` // header. `ciphertextLen` is an in-out parameter set to the actual ciphertext // length. uint8_t salt[ECE_SALT_LENGTH]; uint8_t rawSenderPubKey[ECE_WEBPUSH_PUBLIC_KEY_LENGTH]; int err = ece_webpush_aesgcm_encrypt( rawRecvPubKey, rawRecvPubKeyLen, authSecret, authSecretLen, ECE_WEBPUSH_DEFAULT_RS, padLen, plaintext, plaintextLen, salt, ECE_SALT_LENGTH, rawSenderPubKey, ECE_WEBPUSH_PUBLIC_KEY_LENGTH, ciphertext, &ciphertextLen); assert(err == ECE_OK); // Build the `Crypto-Key` and `Encryption` HTTP headers. First, we pass // `NULL`s for `cryptoKeyHeader` and `encryptionHeader`, and 0 for their // lengths, to calculate the lengths of the buffers we need. Then, we // allocate, write out, and null-terminate the headers. size_t cryptoKeyHeaderLen = 0; size_t encryptionHeaderLen = 0; err = ece_webpush_aesgcm_headers_from_params( salt, ECE_SALT_LENGTH, rawSenderPubKey, ECE_WEBPUSH_PUBLIC_KEY_LENGTH, ECE_WEBPUSH_DEFAULT_RS, NULL, &cryptoKeyHeaderLen, NULL, &encryptionHeaderLen); assert(err == ECE_OK); // Allocate an extra byte for the null terminator. char* cryptoKeyHeader = calloc(cryptoKeyHeaderLen + 1, 1); assert(cryptoKeyHeader); char* encryptionHeader = calloc(encryptionHeaderLen + 1, 1); assert(encryptionHeader); err = ece_webpush_aesgcm_headers_from_params( salt, ECE_SALT_LENGTH, rawSenderPubKey, ECE_WEBPUSH_PUBLIC_KEY_LENGTH, ECE_WEBPUSH_DEFAULT_RS, cryptoKeyHeader, &cryptoKeyHeaderLen, encryptionHeader, &encryptionHeaderLen); assert(err == ECE_OK); cryptoKeyHeader[cryptoKeyHeaderLen] = '\0'; encryptionHeader[encryptionHeaderLen] = '\0'; const char* filename = "aesgcm.bin"; FILE* ciphertextFile = fopen(filename, "wb"); assert(ciphertextFile); size_t ciphertextFileLen = fwrite(ciphertext, sizeof(uint8_t), ciphertextLen, ciphertextFile); assert(ciphertextLen == ciphertextFileLen); fclose(ciphertextFile); printf("curl -v -X POST -H \"Content-Encoding: aesgcm\" -H \"Crypto-Key: " "%s\" -H \"Encryption: %s\" --data-binary @%s %s\n", cryptoKeyHeader, encryptionHeader, filename, endpoint); free(ciphertext); free(cryptoKeyHeader); free(encryptionHeader); return 0; } ``` #### Decryption ```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/Diffie–Hellman_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.