Coder Social home page Coder Social logo

ecec's People

Contributors

linabutler avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

ecec's Issues

Remove aesgcm header parser

ece_webpush_aesgcm_headers_extract_params uses a hand-rolled state machine parser. Parsing strings in C is scary, and Crypto-Key and Encryption are deprecated, anyway. It's better to use a higher-level language like Rust or Swift to extract the params, and pass them to ece_webpush_aes128gcm_decrypt.

Help with webpush payload encryption

Hi to all!
I'm trying to use the ecec library to encrypt a webpush payload from my C program, but I find the documentation a bit obscure, so I ask you for help!
Mozilla Firefox 57 is giving me 'Error: Bad encryption'.
This is my code (I have removed error checking for summarize):

#include "../ecec/include/ece.h"

const char* b64_p256dh = "BAsRT....."; // from the PushManager keys.p256dh
const char* b64_auth = "R434..."; // from the PushManager keys.auth
const char* plaintext = "If the wind in my sail on the sea stays behind me, one day I'll know how far I'll go";
uint8_t p256dh[66] = { 0 }, auth[24] = { 0 };
uint8_t salt[ECE_SALT_LENGTH] = { 0 };
uint8_t rawSenderPubKey[ECE_WEBPUSH_PUBLIC_KEY_LENGTH] = { 0 };
uint8_t ciphertext[4096] = { 0 };
size_t ciphertextLen = ece_aesgcm_ciphertext_max_length(26, 6, strlen(plaintext));
char b64_ciphertext[4096*2] = { 0 };

int len_p256dh = ece_base64url_decode(b64_p256dh, strlen(b64_p256dh), ECE_BASE64URL_IGNORE_PADDING, p256dh, ECE_WEBPUSH_PUBLIC_KEY_LENGTH);
int len_auth = ece_base64url_decode(b64_auth, strlen(b64_auth), ECE_BASE64URL_IGNORE_PADDING, auth, ECE_WEBPUSH_AUTH_SECRET_LENGTH);

int res = ece_webpush_aesgcm_encrypt(p256dh, ECE_WEBPUSH_PUBLIC_KEY_LENGTH, auth, ECE_WEBPUSH_AUTH_SECRET_LENGTH, 26, 6, (const uint8_t*)plaintext, strlen(plaintext), salt, ECE_SALT_LENGTH, rawSenderPubKey, ECE_WEBPUSH_PUBLIC_KEY_LENGTH, ciphertext, &ciphertextLen);

size_t dhHeaderLen = 0;
size_t saltHeaderLen = 0;
ece_webpush_aesgcm_headers_from_params(salt, ECE_SALT_LENGTH, rawSenderPubKey, ECE_WEBPUSH_PUBLIC_KEY_LENGTH, 14, NULL, &dhHeaderLen, NULL, &saltHeaderLen);
char* dhHeader = (char*)malloc(dhHeaderLen + 1);
char* saltHeader = (char*)malloc(saltHeaderLen + 1);
ece_webpush_aesgcm_headers_from_params(salt, ECE_SALT_LENGTH, rawSenderPubKey, ECE_WEBPUSH_PUBLIC_KEY_LENGTH, 26, dhHeader, &dhHeaderLen, saltHeader, &saltHeaderLen);
dhHeader[dhHeaderLen] = '\0';
saltHeader[saltHeaderLen] = '\0';

int len_b64_ciphertext = ece_base64url_encode(ciphertext, ciphertextLen, ECE_BASE64URL_OMIT_PADDING, b64_ciphertext, sizeof(b64_ciphertext));

printf("curl -v -X POST \\\n");
printf("     -H \"Encryption: %s\" \\\n", saltHeader);
printf("     -H \"Authorization: WebPush %s\" \\\n", out);
printf("     -H \"Crypto-Key: %s; p256ecdsa=%s\" \\\n", dhHeader, vapid_pub_key);
printf("     -H \"Content-Length: %d\" \\\n", len_b64_ciphertext);
printf("     -H \"Content-Type: application/octet-stream\" \\\n");
printf("     -H \"Content-Encoding: aesgcm\" \\\n");
printf("     -H \"TTL: 0\" \\\n");
printf("     -d \"%s\" \\\n", b64_ciphertext);
printf("     \"%s\"\n", url);

When I execute the curl command that this program outputs, Mozilla Firefox gives me this error:

PushService:decryptAndNotifyApp: Error decrypting message "https://.../notificaciones/" gAAAA..... Error: Bad encryption
Stack trace:
CryptoError@resource://gre/modules/PushCrypto.jsm:60:5
decode@resource://gre/modules/PushCrypto.jsm:332:13

What can I be doing wrong? And also some doubts:

  • In ece_webpush_aesgcm_encrypt, which values should I pass in rs and padLen?
  • In ece_base64url_decode, should I use ECE_BASE64URL_IGNORE_PADDING or ECE_BASE64URL_REQUIRE_PADDING?
    As you can see, I'm somewhat lost. I hope you can help me. (If this is not the right place to ask for help, please tell me).

Don't expose `ece_buf_t`

Buffers are convenient for internal use, but they complicate FFI callers. Let's make ece_buf_t private, or remove it entirely.

Documentation

Hi,

I'm really struggling how to use the aes128gcm example. I have the pub and priv keys, as well as the PubSubscription data.

Many thanks in advance.

Set up AppVeyor

MSVC has a different set of warnings than clang and GCC. This might require manually installing OpenSSL 1.1.0 onto the build workers. This thread has some tips.

Implement encryption

The signatures would look something like:

int
ece_aes128gcm_encrypt(const ece_buf_t* rawRecvPubKey,
                      const ece_buf_t* authSecret,
                      const ece_buf_t* plaintext,
                      uint32_t rs,
                      ece_buf_t* payload);

int
ece_aesgcm_encrypt(const ece_buf_t* rawRecvPubKey,
                   const ece_buf_t* authSecret,
                   const ece_buf_t* plaintext,
                   uint32_t rs,
                   char** cryptoKeyHeader,
                   char** encryptionHeader,
                   ece_buf_t* ciphertext);

Add tests

  • Add decryption tests for vectors generated by the reference implementation.
  • Set up CI (AppVeyor supports Windows).
  • Run the tests under Valgrind and see what shakes out.

Implement older "aesgcm" scheme

Even though it's now outdated, "aesgcm" is the most widely deployed version. Firefox Desktop doesn't support "aes128gcm" yet, and neither does the Node Web Push library. I think the key derivation process is identical, so we should be able to reuse those functions. The major differences are:

  • How the ephemeral public key and record size are passed (Crypto-Key and Encryption HTTP headers, instead of in the message).
  • Different padding scheme.

We can skip the even older "aesgcm128". It's significantly different, and the only browser to support it is pre-Firefox 45.

Use OpenSSL's memory allocator

OpenSSL includes a replaceable memory allocator. If we decide to simulate failures using an instrumented allocator for #9, it makes sense to use OpenSSL's functions so that we don't need to write our own version of CRYPTO_set_mem_functions.

On the other hand, this makes things harder for callers, who would need to import openssl/crypto.h to free buffer contents. I'm also not sure how well this would work if we add an NSS backend later.

Linking error, -fPIC option required

When I tried to make a shared library uses libecec.a, ar shows error with explanation to use -fPIC options on libecec.a so I fixed it by adding line:

set(CMAKE_POSITION_INDEPENDENT_CODE ON)

into cmake file.

Don't allocate `pad` up front; write encrypted padding directly

We currently allocate an array of zeroes and pass it to the encrypt_block_t functions. There's no need to do this: since we know the padding length, we can write the delimiter and padding directly into the ciphertext array using EVP_EncryptUpdate, one byte at a time.

Add a helper to build the `Crypto-Key` and `Encryption` headers

The dh and salt params use Base64url (- and _ instead of + and /) encoding, without padding (=). We can add a function to build these headers, like so:

// Determine the lengths of the `Crypto-Key` and `Encryption` 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,
                                             /* rs */ 4096,
                                             /* cryptoKeyHeader */ NULL,
                                             &cryptoKeyHeaderLen,
                                             /* encryptionHeader */ NULL,
                                             &encryptionHeaderLen);
assert(!err);

// Allocate space and build the headers.
char* cryptoKeyHeader = malloc(cryptoKeyHeaderLen);
char* encryptionHeader = malloc(encryptionHeaderLen);

err = ece_webpush_aesgcm_headers_from_params(salt, ECE_SALT_LENGTH,
                                             rawSenderPubKey,
                                             ECE_WEBPUSH_PUBLIC_KEY_LENGTH,
                                             /* rs */ 4096,
                                             cryptoKeyHeader,
                                             &cryptoKeyHeaderLen,
                                             encryptionHeader,
                                             &encryptionHeaderLen);
assert(!err);

free(cryptoKeyHeader);
free(encryptionHeader);

Add a streaming interface

ece_decrypt and ece_encrypt operate on the full plaintext and ciphertext. This is fine for Web Push, which shouldn't be used for large messages, but we'll want to think about a streaming API for other uses. Here's a sketch of how such an API might look:

// Create a stream with a buffer size of 8k.
ece_decrypt_stream_t* s = ece_decrypt_stream_new(ECE_SCHEME_AES128GCM, 8192);

ece_buf_t ciphertext;
// Read encrypted blocks from a file, socket, libuv stream, etc.

size_t written = ece_decrypt_stream_write(s, &ciphertext);
if (written < ciphertext.length) {
  // Once the buffer is full, or an error occurs, read the decrypted data.
  // `chunk` is a slice of the stream's buffer, not a copy. This avoids an
  // intermediate copy if we want to write the plaintext to another stream,
  // but does mean we'll need to copy the slice if we want to keep the plaintext
  // in memory.
  ece_buf_t chunk;
  int err = ece_decrypt_stream_read(s, &chunk);
  if (err) {
    // Decryption failed. Close the source stream and free `s`.
    ece_decrypt_stream_free(s);
  }
} else {
  // We can keep writing to `s`.
}

// Once we've written all decrypted data from the source stream, flush all
// data remaining in the buffer. Returns true if we need to read from `s` again.
if (ece_decrypt_stream_flush(s)) {
  ece_buf_t lastChunk;
  int err = ece_decrypt_stream_read(s, &lastChunk);
  if (err) {
    ece_decrypt_stream_free(s);
  }
}

Consider a Rust port

This is a good place for a safe and expressive systems language, and would also provide some opportunities for learning about shipping Rust on iOS.

Investigate adding an NSS backend

I used OpenSSL for expediency, but there's no reason we can't use NSS if it exposes similar primitives. @martinthomson, if you have cycles to help, or point me in the right direction, that would be most welcome.

Support aes128gcm usage without ECDH

The PRK generation in ece_aes128gcm_derive_key_and_nonce is Web Push-specific, but the key and nonce derivation, chunking, and decryption are generic. The PRK derivation could move to an ece_aes128gcm_webpush_derive_prk function, and the generic decryption routine would then take the PRK as an input.

This would be more involved for aesgcm, but, given that it's an older encoding that's only used for Web Push, I think we can leave it be.

Security review

Now that the API has stabilized a bit, and this is a serious project™, let's make sure we're not doing anything silly.

Padding shenanigans

I spent some time this afternoon thinking about how we handle padding, and I think it's still wrong. In particular, we'll underflow aes128gcm records if rs < plaintextLen < padLen, but not if rs = overhead + 1. The updated Node library emits truncated records for this case, too, so we'll need to change it.

I think this snippet will handle it correctly, but I'm not entirely sure:

// The maximum amount of data (plaintext and padding) that will fit into
// a block. The last block can be smaller.
size_t dataPerBlock = rs - overhead;

// The offset at which to start reading the plaintext.
size_t plaintextStart = 0;

// The record sequence number.
size_t counter = 0;

bool lastRecord = false;
while (!lastRecord) {
  // Consume the padding first, leaving 1 byte for the plaintext. For
  // "aesgcm", `blockPadLen` must also fit in a `uint16_t`.
  size_t blockPadLen = dataPerBlock - 1;
  if (padLen && !blockPadLen) {
    // If `dataPerBlock` is 1, we can only include 1 byte of data, so write
    // the padding first.
    blockPadLen++;
  }
  if (blockPadLen > padLen) {
    blockPadLen = padLen;
  }
  padLen -= blockPadLen;

  // Fill the rest of the block with plaintext.
  size_t plaintextEnd = plaintextStart + dataPerBlock - blockPadLen;
  if (plaintextEnd > plaintextLen) {
    plaintextEnd = plaintextLen;
    if (!padLen) {
      // We've reached the last record when the plaintext and padding are
      // exhausted. For "aesgcm", `lastRecord = plaintextEnd % rs > 0`;
      // we need to write an empty trailing block if the ciphertext is a
      // multiple of the record size.
      lastRecord = true;
    }
  }
  size_t blockPlaintextLen = plaintextEnd - plaintextStart;

  size_t blockLen = blockPadLen + blockPlaintextLen;
  if (!lastRecord && blockLen < dataPerBlock) {
    // 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 fail encryption.
    err = ECE_ERROR_ENCRYPT_PADDING;
    goto error;
  }

  // ...
}

@martinthomson, does that look right to you?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.