792 lines
22 KiB
C
Executable File
792 lines
22 KiB
C
Executable File
/*
|
|
* libwebsockets - small server side websockets and web server implementation
|
|
*
|
|
* Copyright (C) 2010 - 2019 Andy Green <andy@warmcat.com>
|
|
*
|
|
* 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.
|
|
*/
|
|
|
|
#include "private-lib-core.h"
|
|
#include "private-lib-jose.h"
|
|
#include "private-lib-jose-jwe.h"
|
|
|
|
/*
|
|
* Currently only support flattened or compact (implicitly single signature)
|
|
*/
|
|
|
|
static const char * const jwe_json[] = {
|
|
"protected",
|
|
"iv",
|
|
"ciphertext",
|
|
"tag",
|
|
"encrypted_key"
|
|
};
|
|
|
|
enum enum_jwe_complete_tokens {
|
|
LWS_EJCT_PROTECTED,
|
|
LWS_EJCT_IV,
|
|
LWS_EJCT_CIPHERTEXT,
|
|
LWS_EJCT_TAG,
|
|
LWS_EJCT_RECIP_ENC_KEY,
|
|
};
|
|
|
|
/* parse a JWS complete or flattened JSON object */
|
|
|
|
struct jwe_cb_args {
|
|
struct lws_jws *jws;
|
|
|
|
char *temp;
|
|
int *temp_len;
|
|
};
|
|
|
|
static signed char
|
|
lws_jwe_json_cb(struct lejp_ctx *ctx, char reason)
|
|
{
|
|
struct jwe_cb_args *args = (struct jwe_cb_args *)ctx->user;
|
|
int n, m;
|
|
|
|
if (!(reason & LEJP_FLAG_CB_IS_VALUE) || !ctx->path_match)
|
|
return 0;
|
|
|
|
switch (ctx->path_match - 1) {
|
|
|
|
/* strings */
|
|
|
|
case LWS_EJCT_PROTECTED: /* base64u: JOSE: must contain 'alg' */
|
|
m = LJWS_JOSE;
|
|
goto append_string;
|
|
case LWS_EJCT_IV: /* base64u */
|
|
m = LJWE_IV;
|
|
goto append_string;
|
|
case LWS_EJCT_CIPHERTEXT: /* base64u */
|
|
m = LJWE_CTXT;
|
|
goto append_string;
|
|
case LWS_EJCT_TAG: /* base64u */
|
|
m = LJWE_ATAG;
|
|
goto append_string;
|
|
case LWS_EJCT_RECIP_ENC_KEY: /* base64u */
|
|
m = LJWE_EKEY;
|
|
goto append_string;
|
|
|
|
default:
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
|
|
append_string:
|
|
|
|
if (*args->temp_len < ctx->npos) {
|
|
lwsl_err("%s: out of parsing space\n", __func__);
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* We keep both b64u and decoded in temp mapped using map / map_b64,
|
|
* the jws signature is actually over the b64 content not the plaintext,
|
|
* and we can't do it until we see the protected alg.
|
|
*/
|
|
|
|
if (!args->jws->map_b64.buf[m]) {
|
|
args->jws->map_b64.buf[m] = args->temp;
|
|
args->jws->map_b64.len[m] = 0;
|
|
}
|
|
|
|
memcpy(args->temp, ctx->buf, ctx->npos);
|
|
args->temp += ctx->npos;
|
|
*args->temp_len -= ctx->npos;
|
|
args->jws->map_b64.len[m] += ctx->npos;
|
|
|
|
if (reason == LEJPCB_VAL_STR_END) {
|
|
args->jws->map.buf[m] = args->temp;
|
|
|
|
n = lws_b64_decode_string_len(
|
|
(const char *)args->jws->map_b64.buf[m],
|
|
(int)args->jws->map_b64.len[m],
|
|
(char *)args->temp, *args->temp_len);
|
|
if (n < 0) {
|
|
lwsl_err("%s: b64 decode failed\n", __func__);
|
|
return -1;
|
|
}
|
|
|
|
args->temp += n;
|
|
*args->temp_len -= n;
|
|
args->jws->map.len[m] = (uint32_t)n;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
lws_jwe_json_parse(struct lws_jwe *jwe, const uint8_t *buf, int len,
|
|
char *temp, int *temp_len)
|
|
{
|
|
struct jwe_cb_args args;
|
|
struct lejp_ctx jctx;
|
|
int m = 0;
|
|
|
|
args.jws = &jwe->jws;
|
|
args.temp = temp;
|
|
args.temp_len = temp_len;
|
|
|
|
lejp_construct(&jctx, lws_jwe_json_cb, &args, jwe_json,
|
|
LWS_ARRAY_SIZE(jwe_json));
|
|
|
|
m = lejp_parse(&jctx, (uint8_t *)buf, len);
|
|
lejp_destruct(&jctx);
|
|
if (m < 0) {
|
|
lwsl_notice("%s: parse returned %d\n", __func__, m);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
lws_jwe_init(struct lws_jwe *jwe, struct lws_context *context)
|
|
{
|
|
lws_jose_init(&jwe->jose);
|
|
lws_jws_init(&jwe->jws, &jwe->jwk, context);
|
|
memset(&jwe->jwk, 0, sizeof(jwe->jwk));
|
|
jwe->recip = 0;
|
|
jwe->cek_valid = 0;
|
|
}
|
|
|
|
void
|
|
lws_jwe_destroy(struct lws_jwe *jwe)
|
|
{
|
|
lws_jws_destroy(&jwe->jws);
|
|
lws_jose_destroy(&jwe->jose);
|
|
lws_jwk_destroy(&jwe->jwk);
|
|
/* cleanse the CEK we held on to in case of further encryptions of it */
|
|
lws_explicit_bzero(jwe->cek, sizeof(jwe->cek));
|
|
jwe->cek_valid = 0;
|
|
}
|
|
|
|
static uint8_t *
|
|
be32(uint32_t i, uint32_t *p32)
|
|
{
|
|
uint8_t *p = (uint8_t *)p32;
|
|
|
|
*p++ = (uint8_t)((i >> 24) & 0xff);
|
|
*p++ = (uint8_t)((i >> 16) & 0xff);
|
|
*p++ = (uint8_t)((i >> 8) & 0xff);
|
|
*p++ = (uint8_t)(i & 0xff);
|
|
|
|
return (uint8_t *)p32;
|
|
}
|
|
|
|
/*
|
|
* The key derivation process derives the agreed-upon key from the
|
|
* shared secret Z established through the ECDH algorithm, per
|
|
* Section 6.2.2.2 of [NIST.800-56A].
|
|
*
|
|
*
|
|
* Key derivation is performed using the Concat KDF, as defined in
|
|
* Section 5.8.1 of [NIST.800-56A], where the Digest Method is SHA-256.
|
|
*
|
|
* out must be prepared to take at least 32 bytes or the encrypted key size,
|
|
* whichever is larger.
|
|
*/
|
|
|
|
int
|
|
lws_jwa_concat_kdf(struct lws_jwe *jwe, int direct, uint8_t *out,
|
|
const uint8_t *shared_secret, int sslen)
|
|
{
|
|
int hlen = (int)lws_genhash_size(LWS_GENHASH_TYPE_SHA256), aidlen;
|
|
struct lws_genhash_ctx hash_ctx;
|
|
uint32_t ctr = 1, t;
|
|
const char *aid;
|
|
|
|
if (!jwe->jose.enc_alg || !jwe->jose.alg)
|
|
return -1;
|
|
|
|
/*
|
|
* Hash
|
|
*
|
|
* AlgorithmID || PartyUInfo || PartyVInfo
|
|
* {|| SuppPubInfo }{|| SuppPrivInfo }
|
|
*
|
|
* AlgorithmID
|
|
*
|
|
* The AlgorithmID value is of the form Datalen || Data, where Data
|
|
* is a variable-length string of zero or more octets, and Datalen is
|
|
* a fixed-length, big-endian 32-bit counter that indicates the
|
|
* length (in octets) of Data. In the Direct Key Agreement case,
|
|
* Data is set to the octets of the ASCII representation of the "enc"
|
|
* Header Parameter value. In the Key Agreement with Key Wrapping
|
|
* case, Data is set to the octets of the ASCII representation of the
|
|
* "alg" (algorithm) Header Parameter value.
|
|
*/
|
|
|
|
aid = direct ? jwe->jose.enc_alg->alg : jwe->jose.alg->alg;
|
|
aidlen = (int)strlen(aid);
|
|
|
|
/*
|
|
* PartyUInfo (PartyVInfo is the same deal)
|
|
*
|
|
* The PartyUInfo value is of the form Datalen || Data, where Data is
|
|
* a variable-length string of zero or more octets, and Datalen is a
|
|
* fixed-length, big-endian 32-bit counter that indicates the length
|
|
* (in octets) of Data. If an "apu" (agreement PartyUInfo) Header
|
|
* Parameter is present, Data is set to the result of base64url
|
|
* decoding the "apu" value and Datalen is set to the number of
|
|
* octets in Data. Otherwise, Datalen is set to 0 and Data is set to
|
|
* the empty octet sequence
|
|
*
|
|
* SuppPubInfo
|
|
*
|
|
* This is set to the keydatalen represented as a 32-bit big-endian
|
|
* integer.
|
|
*
|
|
* keydatalen
|
|
*
|
|
* This is set to the number of bits in the desired output key. For
|
|
* "ECDH-ES", this is length of the key used by the "enc" algorithm.
|
|
* For "ECDH-ES+A128KW", "ECDH-ES+A192KW", and "ECDH-ES+A256KW", this
|
|
* is 128, 192, and 256, respectively.
|
|
*
|
|
* Compute Hash i = H(counter || Z || OtherInfo).
|
|
*
|
|
* We must iteratively hash over key material that's larger than
|
|
* one hash output size (256b for SHA-256)
|
|
*/
|
|
|
|
while (ctr <= (uint32_t)((jwe->jose.enc_alg->keybits_fixed + (hlen - 1)) / hlen)) {
|
|
|
|
/*
|
|
* Key derivation is performed using the Concat KDF, as defined
|
|
* in Section 5.8.1 of [NIST.800-56A], where the Digest Method
|
|
* is SHA-256.
|
|
*/
|
|
|
|
if (lws_genhash_init(&hash_ctx, LWS_GENHASH_TYPE_SHA256))
|
|
return -1;
|
|
|
|
if (/* counter */
|
|
lws_genhash_update(&hash_ctx, be32(ctr++, &t), 4) ||
|
|
/* Z */
|
|
lws_genhash_update(&hash_ctx, shared_secret, (unsigned int)sslen) ||
|
|
/* other info */
|
|
lws_genhash_update(&hash_ctx, be32((uint32_t)strlen(aid), &t), 4) ||
|
|
lws_genhash_update(&hash_ctx, aid, (unsigned int)aidlen) ||
|
|
lws_genhash_update(&hash_ctx,
|
|
be32(jwe->jose.e[LJJHI_APU].len, &t), 4) ||
|
|
lws_genhash_update(&hash_ctx, jwe->jose.e[LJJHI_APU].buf,
|
|
jwe->jose.e[LJJHI_APU].len) ||
|
|
lws_genhash_update(&hash_ctx,
|
|
be32(jwe->jose.e[LJJHI_APV].len, &t), 4) ||
|
|
lws_genhash_update(&hash_ctx, jwe->jose.e[LJJHI_APV].buf,
|
|
jwe->jose.e[LJJHI_APV].len) ||
|
|
lws_genhash_update(&hash_ctx,
|
|
be32(jwe->jose.enc_alg->keybits_fixed, &t),
|
|
4) ||
|
|
lws_genhash_destroy(&hash_ctx, out)) {
|
|
lwsl_err("%s: fail\n", __func__);
|
|
lws_genhash_destroy(&hash_ctx, NULL);
|
|
|
|
return -1;
|
|
}
|
|
|
|
out += hlen;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
lws_jwe_be64(uint64_t c, uint8_t *p8)
|
|
{
|
|
int n;
|
|
|
|
for (n = 56; n >= 0; n -= 8)
|
|
*p8++ = (uint8_t)((c >> n) & 0xff);
|
|
}
|
|
|
|
int
|
|
lws_jwe_auth_and_decrypt(struct lws_jwe *jwe, char *temp, int *temp_len)
|
|
{
|
|
int valid_aescbc_hmac, valid_aesgcm;
|
|
char dotstar[96];
|
|
|
|
if (lws_jwe_parse_jose(&jwe->jose, jwe->jws.map.buf[LJWS_JOSE],
|
|
(int)jwe->jws.map.len[LJWS_JOSE],
|
|
temp, temp_len) < 0) {
|
|
lws_strnncpy(dotstar, jwe->jws.map.buf[LJWS_JOSE],
|
|
jwe->jws.map.len[LJWS_JOSE], sizeof(dotstar));
|
|
lwsl_err("%s: JOSE parse '%s' failed\n", __func__, dotstar);
|
|
return -1;
|
|
}
|
|
|
|
if (!jwe->jose.alg) {
|
|
lws_strnncpy(dotstar, jwe->jws.map.buf[LJWS_JOSE],
|
|
jwe->jws.map.len[LJWS_JOSE], sizeof(dotstar));
|
|
lwsl_err("%s: no jose.alg: %s\n", __func__, dotstar);
|
|
|
|
return -1;
|
|
}
|
|
|
|
valid_aescbc_hmac = jwe->jose.enc_alg &&
|
|
jwe->jose.enc_alg->algtype_crypto == LWS_JOSE_ENCTYPE_AES_CBC &&
|
|
(jwe->jose.enc_alg->hmac_type == LWS_GENHMAC_TYPE_SHA256 ||
|
|
jwe->jose.enc_alg->hmac_type == LWS_GENHMAC_TYPE_SHA384 ||
|
|
jwe->jose.enc_alg->hmac_type == LWS_GENHMAC_TYPE_SHA512);
|
|
|
|
valid_aesgcm = jwe->jose.enc_alg &&
|
|
jwe->jose.enc_alg->algtype_crypto == LWS_JOSE_ENCTYPE_AES_GCM;
|
|
|
|
if ((jwe->jose.alg->algtype_signing == LWS_JOSE_ENCTYPE_RSASSA_PKCS1_1_5 ||
|
|
jwe->jose.alg->algtype_signing == LWS_JOSE_ENCTYPE_RSASSA_PKCS1_OAEP)) {
|
|
/* RSA + AESCBC */
|
|
if (valid_aescbc_hmac)
|
|
return lws_jwe_auth_and_decrypt_rsa_aes_cbc_hs(jwe);
|
|
/* RSA + AESGCM */
|
|
if (valid_aesgcm)
|
|
return lws_jwe_auth_and_decrypt_rsa_aes_gcm(jwe);
|
|
}
|
|
|
|
/* AESKW */
|
|
|
|
if (jwe->jose.alg->algtype_signing == LWS_JOSE_ENCTYPE_AES_ECB &&
|
|
valid_aescbc_hmac)
|
|
return lws_jwe_auth_and_decrypt_aeskw_cbc_hs(jwe);
|
|
|
|
/* ECDH-ES + AESKW */
|
|
|
|
if (jwe->jose.alg->algtype_signing == LWS_JOSE_ENCTYPE_ECDHES &&
|
|
valid_aescbc_hmac)
|
|
return lws_jwe_auth_and_decrypt_ecdh_cbc_hs(jwe,
|
|
temp, temp_len);
|
|
|
|
lwsl_err("%s: unknown cipher alg combo %s / %s\n", __func__,
|
|
jwe->jose.alg->alg, jwe->jose.enc_alg ?
|
|
jwe->jose.enc_alg->alg : "NULL");
|
|
|
|
return -1;
|
|
}
|
|
int
|
|
lws_jwe_encrypt(struct lws_jwe *jwe, char *temp, int *temp_len)
|
|
{
|
|
int valid_aescbc_hmac, valid_aesgcm, ot = *temp_len, ret = -1;
|
|
|
|
if (jwe->jose.recipients >= (int)LWS_ARRAY_SIZE(jwe->jose.recipient)) {
|
|
lwsl_err("%s: max recipients reached\n", __func__);
|
|
|
|
return -1;
|
|
}
|
|
|
|
valid_aesgcm = jwe->jose.enc_alg &&
|
|
jwe->jose.enc_alg->algtype_crypto == LWS_JOSE_ENCTYPE_AES_GCM;
|
|
|
|
if (lws_jwe_parse_jose(&jwe->jose, jwe->jws.map.buf[LJWS_JOSE],
|
|
(int)jwe->jws.map.len[LJWS_JOSE], temp, temp_len) < 0) {
|
|
lwsl_err("%s: JOSE parse failed\n", __func__);
|
|
goto bail;
|
|
}
|
|
|
|
temp += ot - *temp_len;
|
|
|
|
valid_aescbc_hmac = jwe->jose.enc_alg &&
|
|
jwe->jose.enc_alg->algtype_crypto == LWS_JOSE_ENCTYPE_AES_CBC &&
|
|
(jwe->jose.enc_alg->hmac_type == LWS_GENHMAC_TYPE_SHA256 ||
|
|
jwe->jose.enc_alg->hmac_type == LWS_GENHMAC_TYPE_SHA384 ||
|
|
jwe->jose.enc_alg->hmac_type == LWS_GENHMAC_TYPE_SHA512);
|
|
|
|
if ((jwe->jose.alg->algtype_signing == LWS_JOSE_ENCTYPE_RSASSA_PKCS1_1_5 ||
|
|
jwe->jose.alg->algtype_signing == LWS_JOSE_ENCTYPE_RSASSA_PKCS1_OAEP)) {
|
|
/* RSA + AESCBC */
|
|
if (valid_aescbc_hmac) {
|
|
ret = lws_jwe_encrypt_rsa_aes_cbc_hs(jwe, temp, temp_len);
|
|
goto bail;
|
|
}
|
|
/* RSA + AESGCM */
|
|
if (valid_aesgcm) {
|
|
ret = lws_jwe_encrypt_rsa_aes_gcm(jwe, temp, temp_len);
|
|
goto bail;
|
|
}
|
|
}
|
|
|
|
/* AESKW */
|
|
|
|
if (jwe->jose.alg->algtype_signing == LWS_JOSE_ENCTYPE_AES_ECB &&
|
|
valid_aescbc_hmac) {
|
|
ret = lws_jwe_encrypt_aeskw_cbc_hs(jwe, temp, temp_len);
|
|
goto bail;
|
|
}
|
|
|
|
/* ECDH-ES + AESKW */
|
|
|
|
if (jwe->jose.alg->algtype_signing == LWS_JOSE_ENCTYPE_ECDHES &&
|
|
valid_aescbc_hmac) {
|
|
ret = lws_jwe_encrypt_ecdh_cbc_hs(jwe, temp, temp_len);
|
|
goto bail;
|
|
}
|
|
|
|
lwsl_err("%s: unknown cipher alg combo %s / %s\n", __func__,
|
|
jwe->jose.alg->alg, jwe->jose.enc_alg ?
|
|
jwe->jose.enc_alg->alg : "NULL");
|
|
|
|
bail:
|
|
if (ret)
|
|
memset(&jwe->jose.recipient[jwe->jose.recipients], 0,
|
|
sizeof(jwe->jose.recipient[0]));
|
|
else
|
|
jwe->jose.recipients++;
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* JWE Compact Serialization consists of
|
|
*
|
|
* BASE64URL(UTF8(JWE Protected Header)) || '.' ||
|
|
* BASE64URL(JWE Encrypted Key) || '.' ||
|
|
* BASE64URL(JWE Initialization Vector) || '.' ||
|
|
* BASE64URL(JWE Ciphertext) || '.' ||
|
|
* BASE64URL(JWE Authentication Tag)
|
|
*
|
|
*
|
|
* In the JWE Compact Serialization, no JWE Shared Unprotected Header or
|
|
* JWE Per-Recipient Unprotected Header are used. In this case, the
|
|
* JOSE Header and the JWE Protected Header are the same.
|
|
*
|
|
* Therefore:
|
|
*
|
|
* - Everything needed in the header part must go in the protected header
|
|
* (it's the only part emitted). We expect the caller did this.
|
|
*
|
|
* - You can't emit Compact representation if there are multiple recipients
|
|
*/
|
|
|
|
int
|
|
lws_jwe_render_compact(struct lws_jwe *jwe, char *out, size_t out_len)
|
|
{
|
|
size_t orig = out_len;
|
|
int n;
|
|
|
|
if (jwe->jose.recipients > 1) {
|
|
lwsl_notice("%s: can't issue compact representation for"
|
|
" multiple recipients (%d)\n", __func__,
|
|
jwe->jose.recipients);
|
|
|
|
return -1;
|
|
}
|
|
|
|
n = lws_jws_base64_enc(jwe->jws.map.buf[LJWS_JOSE],
|
|
jwe->jws.map.len[LJWS_JOSE], out, out_len);
|
|
if (n < 0 || (int)out_len == n) {
|
|
lwsl_info("%s: unable to encode JOSE\n", __func__);
|
|
return -1;
|
|
}
|
|
|
|
out += n;
|
|
*out++ = '.';
|
|
out_len -= (unsigned int)n + 1;
|
|
|
|
n = lws_jws_base64_enc(jwe->jws.map.buf[LJWE_EKEY],
|
|
jwe->jws.map.len[LJWE_EKEY], out, out_len);
|
|
if (n < 0 || (int)out_len == n) {
|
|
lwsl_info("%s: unable to encode EKEY\n", __func__);
|
|
return -1;
|
|
}
|
|
|
|
out += n;
|
|
*out++ = '.';
|
|
out_len -= (unsigned int)n + 1;
|
|
n = lws_jws_base64_enc(jwe->jws.map.buf[LJWE_IV],
|
|
jwe->jws.map.len[LJWE_IV], out, out_len);
|
|
if (n < 0 || (int)out_len == n) {
|
|
lwsl_info("%s: unable to encode IV\n", __func__);
|
|
return -1;
|
|
}
|
|
|
|
out += n;
|
|
*out++ = '.';
|
|
out_len -= (unsigned int)n + 1;
|
|
|
|
n = lws_jws_base64_enc(jwe->jws.map.buf[LJWE_CTXT],
|
|
jwe->jws.map.len[LJWE_CTXT], out, out_len);
|
|
if (n < 0 || (int)out_len == n) {
|
|
lwsl_info("%s: unable to encode CTXT\n", __func__);
|
|
return -1;
|
|
}
|
|
|
|
out += n;
|
|
*out++ = '.';
|
|
out_len -= (unsigned int)n + 1;
|
|
n = lws_jws_base64_enc(jwe->jws.map.buf[LJWE_ATAG],
|
|
jwe->jws.map.len[LJWE_ATAG], out, out_len);
|
|
if (n < 0 || (int)out_len == n) {
|
|
lwsl_info("%s: unable to encode ATAG\n", __func__);
|
|
return -1;
|
|
}
|
|
|
|
out += n;
|
|
*out++ = '\0';
|
|
out_len -= (unsigned int)n;
|
|
|
|
return (int)(orig - out_len);
|
|
}
|
|
|
|
int
|
|
lws_jwe_create_packet(struct lws_jwe *jwe, const char *payload, size_t len,
|
|
const char *nonce, char *out, size_t out_len,
|
|
struct lws_context *context)
|
|
{
|
|
char *buf, *start, *p, *end, *p1, *end1;
|
|
struct lws_jws jws;
|
|
int n, m;
|
|
|
|
lws_jws_init(&jws, &jwe->jwk, context);
|
|
|
|
/*
|
|
* This buffer is local to the function, the actual output is prepared
|
|
* into out. Only the plaintext protected header
|
|
* (which contains the public key, 512 bytes for 4096b) goes in
|
|
* here temporarily.
|
|
*/
|
|
n = LWS_PRE + 2048;
|
|
buf = malloc((unsigned int)n);
|
|
if (!buf) {
|
|
lwsl_notice("%s: malloc %d failed\n", __func__, n);
|
|
return -1;
|
|
}
|
|
|
|
p = start = buf + LWS_PRE;
|
|
end = buf + n - LWS_PRE - 1;
|
|
|
|
/*
|
|
* temporary JWS protected header plaintext
|
|
*/
|
|
|
|
if (!jwe->jose.alg || !jwe->jose.alg->alg)
|
|
goto bail;
|
|
|
|
p += lws_snprintf(p, lws_ptr_diff_size_t(end, p), "{\"alg\":\"%s\",\"jwk\":",
|
|
jwe->jose.alg->alg);
|
|
m = lws_ptr_diff(end, p);
|
|
n = lws_jwk_export(&jwe->jwk, 0, p, &m);
|
|
if (n < 0) {
|
|
lwsl_notice("failed to export jwk\n");
|
|
|
|
goto bail;
|
|
}
|
|
p += n;
|
|
p += lws_snprintf(p, lws_ptr_diff_size_t(end, p), ",\"nonce\":\"%s\"}", nonce);
|
|
|
|
/*
|
|
* prepare the signed outer JSON with all the parts in
|
|
*/
|
|
|
|
p1 = out;
|
|
end1 = out + out_len - 1;
|
|
|
|
p1 += lws_snprintf(p1, lws_ptr_diff_size_t(end1, p1), "{\"protected\":\"");
|
|
jws.map_b64.buf[LJWS_JOSE] = p1;
|
|
n = lws_jws_base64_enc(start, lws_ptr_diff_size_t(p, start), p1, lws_ptr_diff_size_t(end1, p1));
|
|
if (n < 0) {
|
|
lwsl_notice("%s: failed to encode protected\n", __func__);
|
|
goto bail;
|
|
}
|
|
jws.map_b64.len[LJWS_JOSE] = (unsigned int)n;
|
|
p1 += n;
|
|
|
|
p1 += lws_snprintf(p1, lws_ptr_diff_size_t(end1, p1), "\",\"payload\":\"");
|
|
jws.map_b64.buf[LJWS_PYLD] = p1;
|
|
n = lws_jws_base64_enc(payload, len, p1, lws_ptr_diff_size_t(end1, p1));
|
|
if (n < 0) {
|
|
lwsl_notice("%s: failed to encode payload\n", __func__);
|
|
goto bail;
|
|
}
|
|
jws.map_b64.len[LJWS_PYLD] = (unsigned int)n;
|
|
p1 += n;
|
|
|
|
p1 += lws_snprintf(p1, lws_ptr_diff_size_t(end1, p1), "\",\"header\":\"");
|
|
jws.map_b64.buf[LJWS_UHDR] = p1;
|
|
n = lws_jws_base64_enc(payload, len, p1, lws_ptr_diff_size_t(end1, p1));
|
|
if (n < 0) {
|
|
lwsl_notice("%s: failed to encode payload\n", __func__);
|
|
goto bail;
|
|
}
|
|
jws.map_b64.len[LJWS_UHDR] = (unsigned int)n;
|
|
|
|
p1 += n;
|
|
p1 += lws_snprintf(p1, lws_ptr_diff_size_t(end1, p1), "\",\"signature\":\"");
|
|
|
|
/*
|
|
* taking the b64 protected header and the b64 payload, sign them
|
|
* and place the signature into the packet
|
|
*/
|
|
n = lws_jws_sign_from_b64(&jwe->jose, &jws, p1, lws_ptr_diff_size_t(end1, p1));
|
|
if (n < 0) {
|
|
lwsl_notice("sig gen failed\n");
|
|
|
|
goto bail;
|
|
}
|
|
jws.map_b64.buf[LJWS_SIG] = p1;
|
|
jws.map_b64.len[LJWS_SIG] = (unsigned int)n;
|
|
|
|
p1 += n;
|
|
p1 += lws_snprintf(p1, lws_ptr_diff_size_t(end1, p1), "\"}");
|
|
|
|
free(buf);
|
|
|
|
return lws_ptr_diff(p1, out);
|
|
|
|
bail:
|
|
lws_jws_destroy(&jws);
|
|
free(buf);
|
|
|
|
return -1;
|
|
}
|
|
|
|
static const char *protected_en[] = {
|
|
"encrypted_key", "aad", "iv", "ciphertext", "tag"
|
|
};
|
|
|
|
static int protected_idx[] = {
|
|
LJWE_EKEY, LJWE_AAD, LJWE_IV, LJWE_CTXT, LJWE_ATAG
|
|
};
|
|
|
|
/*
|
|
* The complete JWE may look something like this:
|
|
*
|
|
* {
|
|
* "protected":
|
|
* "eyJlbmMiOiJBMTI4Q0JDLUhTMjU2In0",
|
|
* "unprotected":
|
|
* {"jku":"https://server.example.com/keys.jwks"},
|
|
* "recipients":[
|
|
* {"header":
|
|
* {"alg":"RSA1_5","kid":"2011-04-29"},
|
|
* "encrypted_key":
|
|
* "UGhIOguC7IuEvf_NPVaXsGMoLOmwvc1GyqlIKOK1nN94nHPoltGRhWhw7Zx0-
|
|
* kFm1NJn8LE9XShH59_i8J0PH5ZZyNfGy2xGdULU7sHNF6Gp2vPLgNZ__deLKx
|
|
* GHZ7PcHALUzoOegEI-8E66jX2E4zyJKx-YxzZIItRzC5hlRirb6Y5Cl_p-ko3
|
|
* YvkkysZIFNPccxRU7qve1WYPxqbb2Yw8kZqa2rMWI5ng8OtvzlV7elprCbuPh
|
|
* cCdZ6XDP0_F8rkXds2vE4X-ncOIM8hAYHHi29NX0mcKiRaD0-D-ljQTP-cFPg
|
|
* wCp6X-nZZd9OHBv-B3oWh2TbqmScqXMR4gp_A"},
|
|
* {"header":
|
|
* {"alg":"A128KW","kid":"7"},
|
|
* "encrypted_key":
|
|
* "6KB707dM9YTIgHtLvtgWQ8mKwboJW3of9locizkDTHzBC2IlrT1oOQ"}],
|
|
* "iv":
|
|
* "AxY8DCtDaGlsbGljb3RoZQ",
|
|
* "ciphertext":
|
|
* "KDlTtXchhZTGufMYmOYGS4HffxPSUrfmqCHXaI9wOGY",
|
|
* "tag":
|
|
* "Mz-VPPyU4RlcuYv1IwIvzw"
|
|
* }
|
|
*
|
|
* The flattened JWE ends up like this
|
|
*
|
|
* {
|
|
* "protected": "eyJlbmMiOiJBMTI4Q0JDLUhTMjU2In0",
|
|
* "unprotected": {"jku":"https://server.example.com/keys.jwks"},
|
|
* "header": {"alg":"A128KW","kid":"7"},
|
|
* "encrypted_key": "6KB707dM9YTIgHtLvtgWQ8mKwboJW3of9locizkDTHzBC2IlrT1oOQ",
|
|
* "iv": "AxY8DCtDaGlsbGljb3RoZQ",
|
|
* "ciphertext": "KDlTtXchhZTGufMYmOYGS4HffxPSUrfmqCHXaI9wOGY",
|
|
* "tag": "Mz-VPPyU4RlcuYv1IwIvzw"
|
|
* }
|
|
*
|
|
* {
|
|
* "protected":"<integrity-protected header contents>",
|
|
* "unprotected":<non-integrity-protected header contents>,
|
|
* "header":<more non-integrity-protected header contents>,
|
|
* "encrypted_key":"<encrypted key contents>",
|
|
* "aad":"<additional authenticated data contents>",
|
|
* "iv":"<initialization vector contents>",
|
|
* "ciphertext":"<ciphertext contents>",
|
|
* "tag":"<authentication tag contents>"
|
|
* }
|
|
*/
|
|
|
|
int
|
|
lws_jwe_render_flattened(struct lws_jwe *jwe, char *out, size_t out_len)
|
|
{
|
|
char buf[3072], *p1, *end1, protected[128];
|
|
int m, n, jlen, plen;
|
|
|
|
jlen = lws_jose_render(&jwe->jose, jwe->jws.jwk, buf, sizeof(buf));
|
|
if (jlen < 0) {
|
|
lwsl_err("%s: lws_jose_render failed\n", __func__);
|
|
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* prepare the JWE JSON with all the parts in
|
|
*/
|
|
|
|
p1 = out;
|
|
end1 = out + out_len - 1;
|
|
|
|
/*
|
|
* The protected header is b64url encoding of the JOSE header part
|
|
*/
|
|
|
|
plen = lws_snprintf(protected, sizeof(protected),
|
|
"{\"alg\":\"%s\",\"enc\":\"%s\"}",
|
|
jwe->jose.alg->alg, jwe->jose.enc_alg->alg);
|
|
|
|
p1 += lws_snprintf(p1, lws_ptr_diff_size_t(end1, p1), "{\"protected\":\"");
|
|
jwe->jws.map_b64.buf[LJWS_JOSE] = p1;
|
|
n = lws_jws_base64_enc(protected, (size_t)plen, p1, lws_ptr_diff_size_t(end1, p1));
|
|
if (n < 0) {
|
|
lwsl_notice("%s: failed to encode protected\n", __func__);
|
|
goto bail;
|
|
}
|
|
jwe->jws.map_b64.len[LJWS_JOSE] = (unsigned int)n;
|
|
p1 += n;
|
|
|
|
/* unprotected not supported atm */
|
|
|
|
p1 += lws_snprintf(p1, lws_ptr_diff_size_t(end1, p1), "\",\n\"header\":");
|
|
lws_strnncpy(p1, buf, jlen, end1 - p1);
|
|
p1 += strlen(p1);
|
|
|
|
for (m = 0; m < (int)LWS_ARRAY_SIZE(protected_en); m++)
|
|
if (jwe->jws.map.buf[protected_idx[m]]) {
|
|
p1 += lws_snprintf(p1, lws_ptr_diff_size_t(end1, p1), ",\n\"%s\":\"",
|
|
protected_en[m]);
|
|
//jwe->jws.map_b64.buf[protected_idx[m]] = p1;
|
|
n = lws_jws_base64_enc(jwe->jws.map.buf[protected_idx[m]],
|
|
jwe->jws.map.len[protected_idx[m]],
|
|
p1, lws_ptr_diff_size_t(end1, p1));
|
|
if (n < 0) {
|
|
lwsl_notice("%s: failed to encode %s\n",
|
|
__func__, protected_en[m]);
|
|
goto bail;
|
|
}
|
|
//jwe->jws.map_b64.len[protected_idx[m]] = n;
|
|
p1 += n;
|
|
p1 += lws_snprintf(p1, lws_ptr_diff_size_t(end1, p1), "\"");
|
|
}
|
|
|
|
p1 += lws_snprintf(p1, lws_ptr_diff_size_t(end1, p1), "\n}\n");
|
|
|
|
return lws_ptr_diff(p1, out);
|
|
|
|
bail:
|
|
lws_jws_destroy(&jwe->jws);
|
|
|
|
return -1;
|
|
}
|