summaryrefslogtreecommitdiff
path: root/lib/quic-crypto.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/quic-crypto.c')
-rw-r--r--lib/quic-crypto.c520
1 files changed, 520 insertions, 0 deletions
diff --git a/lib/quic-crypto.c b/lib/quic-crypto.c
new file mode 100644
index 000000000..aaa7bdb7a
--- /dev/null
+++ b/lib/quic-crypto.c
@@ -0,0 +1,520 @@
+/***************************************************************************
+ * _ _ ____ _
+ * Project ___| | | | _ \| |
+ * / __| | | | |_) | |
+ * | (__| |_| | _ <| |___
+ * \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 1998 - 2019, Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.haxx.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ***************************************************************************/
+
+#include "curl_setup.h"
+
+#ifdef USE_NGTCP2
+#include <ngtcp2/ngtcp2.h>
+#include <openssl/ssl.h>
+#include <openssl/evp.h>
+#include <openssl/kdf.h>
+#include "quic-crypto.h"
+/* The last 3 #include files should be in this order */
+#include "curl_printf.h"
+#include "curl_memory.h"
+#include "memdebug.h"
+
+void Curl_qc_prf_sha256(struct Context *ctx)
+{
+ ctx->prf = EVP_sha256();
+}
+
+void Curl_qc_aead_aes_128_gcm(struct Context *ctx)
+{
+ ctx->aead = EVP_aes_128_gcm();
+ ctx->hp = EVP_aes_128_ctr();
+}
+
+size_t Curl_qc_aead_nonce_length(const struct Context *ctx)
+{
+ return EVP_CIPHER_iv_length(ctx->aead);
+}
+
+
+int Curl_qc_negotiated_prf(struct Context *ctx, SSL *ssl)
+{
+ switch(SSL_CIPHER_get_id(SSL_get_current_cipher(ssl))) {
+ case 0x03001301u: /* TLS_AES_128_GCM_SHA256 */
+ case 0x03001303u: /* TLS_CHACHA20_POLY1305_SHA256 */
+ ctx->prf = EVP_sha256();
+ return 0;
+ case 0x03001302u: /* TLS_AES_256_GCM_SHA384 */
+ ctx->prf = EVP_sha384();
+ return 0;
+ default:
+ return -1;
+ }
+}
+
+int Curl_qc_negotiated_aead(struct Context *ctx, SSL *ssl)
+{
+ switch(SSL_CIPHER_get_id(SSL_get_current_cipher(ssl))) {
+ case 0x03001301u: /* TLS_AES_128_GCM_SHA256 */
+ ctx->aead = EVP_aes_128_gcm();
+ ctx->hp = EVP_aes_128_ctr();
+ return 0;
+ case 0x03001302u: /* TLS_AES_256_GCM_SHA384 */
+ ctx->aead = EVP_aes_256_gcm();
+ ctx->hp = EVP_aes_256_ctr();
+ return 0;
+ case 0x03001303u: /* TLS_CHACHA20_POLY1305_SHA256 */
+ ctx->aead = EVP_chacha20_poly1305();
+ ctx->hp = EVP_chacha20();
+ return 0;
+ default:
+ return -1;
+ }
+}
+
+ssize_t Curl_qc_encrypt_pn(uint8_t *dest, size_t destlen,
+ const uint8_t *plaintext, size_t plaintextlen,
+ const struct Context *ctx,
+ const uint8_t *key, size_t keylen,
+ const uint8_t *nonce, size_t noncelen)
+{
+ EVP_CIPHER_CTX *actx = EVP_CIPHER_CTX_new();
+ size_t outlen = 0;
+ int len;
+ (void)destlen;
+ (void)keylen;
+ (void)noncelen;
+
+ if(!actx)
+ return -1;
+
+ if(EVP_EncryptInit_ex(actx, ctx->hp, NULL, key, nonce) != 1)
+ goto error;
+
+ if(EVP_EncryptUpdate(actx, dest, &len, plaintext, (int)plaintextlen) != 1)
+ goto error;
+
+ assert(len > 0);
+
+ outlen = len;
+
+ if(EVP_EncryptFinal_ex(actx, dest + outlen, &len) != 1)
+ goto error;
+
+ assert(len == 0);
+ /* outlen += len; */
+
+ EVP_CIPHER_CTX_free(actx);
+ return outlen;
+
+ error:
+ EVP_CIPHER_CTX_free(actx);
+ return -1;
+}
+
+static int hkdf_expand(uint8_t *dest, size_t destlen, const uint8_t *secret,
+ size_t secretlen, const uint8_t *info, size_t infolen,
+ const struct Context *ctx)
+{
+ void *pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL);
+ if(!pctx)
+ return -1;
+
+ if(EVP_PKEY_derive_init(pctx) != 1)
+ goto err;
+
+ if(EVP_PKEY_CTX_hkdf_mode(pctx, EVP_PKEY_HKDEF_MODE_EXPAND_ONLY) != 1)
+ goto err;
+
+ if(EVP_PKEY_CTX_set_hkdf_md(pctx, ctx->prf) != 1)
+ goto err;
+
+ if(EVP_PKEY_CTX_set1_hkdf_salt(pctx, "", 0) != 1)
+ goto err;
+
+ if(EVP_PKEY_CTX_set1_hkdf_key(pctx, secret, (int)secretlen) != 1)
+ goto err;
+
+ if(EVP_PKEY_CTX_add1_hkdf_info(pctx, info, (int)infolen) != 1)
+ goto err;
+
+ if(EVP_PKEY_derive(pctx, dest, &destlen) != 1)
+ goto err;
+
+ return 0;
+ err:
+ EVP_PKEY_CTX_free(pctx);
+ return -1;
+}
+
+static int hkdf_extract(uint8_t *dest, size_t destlen,
+ const uint8_t *secret, size_t secretlen,
+ const uint8_t *salt, size_t saltlen,
+ const struct Context *ctx)
+{
+ void *pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL);
+ if(!pctx)
+ return -1;
+
+ if(EVP_PKEY_derive_init(pctx) != 1)
+ goto err;
+
+ if(EVP_PKEY_CTX_hkdf_mode(pctx, EVP_PKEY_HKDEF_MODE_EXTRACT_ONLY) != 1) {
+ goto err;
+ }
+
+ if(EVP_PKEY_CTX_set_hkdf_md(pctx, ctx->prf) != 1) {
+ goto err;
+ }
+
+ if(EVP_PKEY_CTX_set1_hkdf_salt(pctx, salt, (int)saltlen) != 1) {
+ goto err;
+ }
+
+ if(EVP_PKEY_CTX_set1_hkdf_key(pctx, secret, (int)secretlen) != 1) {
+ goto err;
+ }
+
+ if(EVP_PKEY_derive(pctx, dest, &destlen) != 1) {
+ goto err;
+ }
+
+ EVP_PKEY_CTX_free(pctx);
+ return 0;
+ err:
+ EVP_PKEY_CTX_free(pctx);
+ return -1;
+}
+
+static int qhkdf_expand(uint8_t *dest, size_t destlen,
+ const uint8_t *secret, size_t secretlen,
+ const uint8_t *qlabel, size_t qlabellen,
+ const struct Context *ctx)
+{
+ uint8_t info[256];
+ static const char LABEL[] = "quic ";
+
+ uint8_t *p = &info[0];
+ *p++ = (destlen / 256) & 0xff;
+ *p++ = destlen % 256;
+ *p++ = (strlen(LABEL) + qlabellen) & 0xff;
+ memcpy(p, LABEL, strlen(LABEL));
+ p += strlen(LABEL);
+ memcpy(p, qlabel, qlabellen);
+ p += qlabellen;
+ *p++ = 0;
+
+ return hkdf_expand(dest, destlen, secret, secretlen, &info[0],
+ p - &info[0], ctx);
+}
+
+static size_t aead_key_length(const struct Context *ctx)
+{
+ return EVP_CIPHER_key_length(ctx->aead);
+}
+
+static size_t aead_tag_length(const struct Context *ctx)
+{
+ if(ctx->aead == EVP_aes_128_gcm() || ctx->aead == EVP_aes_256_gcm()) {
+ return EVP_GCM_TLS_TAG_LEN;
+ }
+ if(ctx->aead == EVP_chacha20_poly1305()) {
+ return EVP_CHACHAPOLY_TLS_TAG_LEN;
+ }
+ assert(0);
+}
+
+size_t Curl_qc_aead_max_overhead(const struct Context *ctx)
+{
+ return aead_tag_length(ctx);
+}
+
+ssize_t Curl_qc_encrypt(uint8_t *dest, size_t destlen,
+ const uint8_t *plaintext, size_t plaintextlen,
+ const struct Context *ctx,
+ const uint8_t *key, size_t keylen,
+ const uint8_t *nonce, size_t noncelen,
+ const uint8_t *ad, size_t adlen)
+{
+ size_t taglen = aead_tag_length(ctx);
+ EVP_CIPHER_CTX *actx;
+ size_t outlen = 0;
+ int len;
+ (void)keylen;
+
+ if(destlen < plaintextlen + taglen) {
+ return -1;
+ }
+
+ actx = EVP_CIPHER_CTX_new();
+ if(!actx)
+ return -1;
+
+ if(EVP_EncryptInit_ex(actx, ctx->aead, NULL, NULL, NULL) != 1)
+ goto error;
+
+ if(EVP_CIPHER_CTX_ctrl(actx, EVP_CTRL_AEAD_SET_IVLEN,
+ (int)noncelen, NULL) != 1)
+ goto error;
+
+ if(EVP_EncryptInit_ex(actx, NULL, NULL, key, nonce) != 1)
+ goto error;
+
+ if(EVP_EncryptUpdate(actx, NULL, &len, ad, (int)adlen) != 1)
+ goto error;
+
+ if(EVP_EncryptUpdate(actx, dest, &len, plaintext, (int)plaintextlen) != 1)
+ goto error;
+
+ outlen = len;
+ if(EVP_EncryptFinal_ex(actx, dest + outlen, &len) != 1)
+ goto error;
+
+ outlen += len;
+ assert(outlen + taglen <= destlen);
+
+ if(EVP_CIPHER_CTX_ctrl(actx, EVP_CTRL_AEAD_GET_TAG,
+ (int)taglen, dest + outlen) != 1)
+ goto error;
+
+ outlen += taglen;
+
+ EVP_CIPHER_CTX_free(actx);
+ return outlen;
+
+ error:
+ EVP_CIPHER_CTX_free(actx);
+ return -1;
+}
+
+ssize_t Curl_qc_decrypt(uint8_t *dest, size_t destlen,
+ const uint8_t *ciphertext, size_t ciphertextlen,
+ const struct Context *ctx,
+ const uint8_t *key, size_t keylen,
+ const uint8_t *nonce, size_t noncelen,
+ const uint8_t *ad, size_t adlen)
+{
+ size_t taglen = aead_tag_length(ctx);
+ const uint8_t *tag;
+ EVP_CIPHER_CTX *actx;
+ size_t outlen;
+ int len;
+ (void)keylen;
+
+ if(taglen > ciphertextlen || destlen + taglen < ciphertextlen) {
+ return -1;
+ }
+
+ ciphertextlen -= taglen;
+ tag = ciphertext + ciphertextlen;
+
+ actx = EVP_CIPHER_CTX_new();
+ if(!actx)
+ return -1;
+
+ if(EVP_DecryptInit_ex(actx, ctx->aead, NULL, NULL, NULL) != 1)
+ goto error;
+
+ if(EVP_CIPHER_CTX_ctrl(actx, EVP_CTRL_AEAD_SET_IVLEN, (int)noncelen, NULL) !=
+ 1)
+ goto error;
+
+ if(EVP_DecryptInit_ex(actx, NULL, NULL, key, nonce) != 1)
+ goto error;
+
+ if(EVP_DecryptUpdate(actx, NULL, &len, ad, (int)adlen) != 1)
+ goto error;
+
+ if(EVP_DecryptUpdate(actx, dest, &len, ciphertext, (int)ciphertextlen) != 1)
+ goto error;
+
+ outlen = len;
+ if(EVP_CIPHER_CTX_ctrl(actx, EVP_CTRL_AEAD_SET_TAG,
+ (int)taglen, (char *)tag) != 1)
+ goto error;
+
+ if(EVP_DecryptFinal_ex(actx, dest + outlen, &len) != 1)
+ goto error;
+
+ outlen += len;
+
+ EVP_CIPHER_CTX_free(actx);
+ return outlen;
+ error:
+ EVP_CIPHER_CTX_free(actx);
+ return -1;
+}
+
+int Curl_qc_derive_initial_secret(uint8_t *dest, size_t destlen,
+ const ngtcp2_cid *secret,
+ const uint8_t *salt,
+ size_t saltlen)
+{
+ struct Context ctx;
+ Curl_qc_prf_sha256(&ctx);
+ return hkdf_extract(dest, destlen, secret->data, secret->datalen, salt,
+ saltlen, &ctx);
+}
+
+int Curl_qc_derive_client_initial_secret(uint8_t *dest,
+ size_t destlen,
+ const uint8_t *secret,
+ size_t secretlen)
+{
+ static uint8_t LABEL[] = "client in";
+ struct Context ctx;
+ Curl_qc_prf_sha256(&ctx);
+ return qhkdf_expand(dest, destlen, secret, secretlen, LABEL,
+ strlen((char *)LABEL), &ctx);
+}
+
+ssize_t Curl_qc_derive_packet_protection_key(uint8_t *dest, size_t destlen,
+ const uint8_t *secret,
+ size_t secretlen,
+ const struct Context *ctx)
+{
+ int rv;
+ static uint8_t LABEL_KEY[] = "key";
+ size_t keylen = aead_key_length(ctx);
+ if(keylen > destlen) {
+ return -1;
+ }
+
+ rv = qhkdf_expand(dest, keylen, secret, secretlen, LABEL_KEY,
+ strlen((char *)LABEL_KEY), ctx);
+ if(rv) {
+ return -1;
+ }
+
+ return keylen;
+}
+
+ssize_t Curl_qc_derive_packet_protection_iv(uint8_t *dest, size_t destlen,
+ const uint8_t *secret,
+ size_t secretlen,
+ const struct Context *ctx)
+{
+ int rv;
+ static uint8_t LABEL_IV[] = "iv";
+
+ size_t ivlen = CURLMAX(8, Curl_qc_aead_nonce_length(ctx));
+ if(ivlen > destlen) {
+ return -1;
+ }
+
+ rv = qhkdf_expand(dest, ivlen, secret, secretlen, LABEL_IV,
+ strlen((char *)LABEL_IV), ctx);
+ if(rv) {
+ return -1;
+ }
+
+ return ivlen;
+}
+
+int Curl_qc_derive_server_initial_secret(uint8_t *dest, size_t destlen,
+ const uint8_t *secret,
+ size_t secretlen)
+{
+ static uint8_t LABEL[] = "server in";
+ struct Context ctx;
+ Curl_qc_prf_sha256(&ctx);
+ return qhkdf_expand(dest, destlen, secret, secretlen, LABEL,
+ strlen((char *)LABEL), &ctx);
+}
+
+static int
+hkdf_expand_label(uint8_t *dest, size_t destlen, const uint8_t *secret,
+ size_t secretlen, const uint8_t *label, size_t labellen,
+ const struct Context *ctx)
+{
+ uint8_t info[256];
+ static const uint8_t LABEL[] = "tls13 ";
+
+ uint8_t *p = &info[0];
+ *p++ = (destlen / 256)&0xff;
+ *p++ = destlen % 256;
+ *p++ = (strlen((char *)LABEL) + labellen)&0xff;
+ memcpy(p, LABEL, strlen((char *)LABEL));
+ p += strlen((char *)LABEL);
+ memcpy(p, label, labellen);
+ p += labellen;
+ *p++ = 0;
+
+ return hkdf_expand(dest, destlen, secret, secretlen, &info[0],
+ p - &info[0], ctx);
+}
+
+ssize_t
+Curl_qc_derive_header_protection_key(uint8_t *dest, size_t destlen,
+ const uint8_t *secret, size_t secretlen,
+ const struct Context *ctx)
+{
+ int rv;
+ static uint8_t LABEL[] = "quic hp";
+
+ size_t keylen = aead_key_length(ctx);
+ if(keylen > destlen)
+ return -1;
+
+ rv = hkdf_expand_label(dest, keylen, secret, secretlen, LABEL,
+ strlen((char *)LABEL), ctx);
+
+ if(rv)
+ return -1;
+
+ return keylen;
+}
+
+ssize_t Curl_qc_hp_mask(uint8_t *dest, size_t destlen,
+ const struct Context *ctx,
+ const uint8_t *key, size_t keylen,
+ const uint8_t *sample, size_t samplelen)
+{
+ static uint8_t PLAINTEXT[] = "\x00\x00\x00\x00\x00";
+ EVP_CIPHER_CTX *actx;
+ size_t outlen = 0;
+ int len;
+ (void)destlen; /* TODO: make use of these! */
+ (void)keylen;
+ (void)samplelen;
+
+ actx = EVP_CIPHER_CTX_new();
+ if(!actx)
+ return -1;
+
+ if(EVP_EncryptInit_ex(actx, ctx->hp, NULL, key, sample) != 1)
+ goto error;
+ if(EVP_EncryptUpdate(actx, dest, &len, PLAINTEXT,
+ (int)strlen((char *)PLAINTEXT)) != 1)
+ goto error;
+
+ DEBUGASSERT(len == 5);
+
+ outlen = len;
+
+ if(EVP_EncryptFinal_ex(actx, dest + outlen, &len) != 1)
+ goto error;
+
+ DEBUGASSERT(len == 0);
+
+ return outlen;
+ error:
+ EVP_CIPHER_CTX_free(actx);
+ return -1;
+}
+
+
+#endif