mirror of
https://github.com/warmcat/libwebsockets.git
synced 2025-03-16 00:00:07 +01:00

lejp_parse() return type is an int... but in the function, the temp for it is a char. This leads to badness that is currently worked around by casting the return through a signed char type. But that leads to more badness since if there's >127 bytes of buffer left after the end of the JSON object, we misreport it. Bite the bullet and fix the temp type, and fix up all the guys who were working around it at the caller return casting to use the resulting straight int. If you are using this api, remove any casting you may have cut- and-pasted like this n = (int)(signed char)lejp_parse(...); ... to just be like this... n = lejp_parse(...);
791 lines
21 KiB
C
Executable file
791 lines
21 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],
|
|
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] = 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++ = (i >> 24) & 0xff;
|
|
*p++ = (i >> 16) & 0xff;
|
|
*p++ = (i >> 8) & 0xff;
|
|
*p++ = 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, sslen) ||
|
|
/* other info */
|
|
lws_genhash_update(&hash_ctx, be32((uint32_t)strlen(aid), &t), 4) ||
|
|
lws_genhash_update(&hash_ctx, aid, 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],
|
|
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],
|
|
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 -= 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 -= 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 -= 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 -= 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 -= 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(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(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, 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, end1 - p1, "{\"protected\":\"");
|
|
jws.map_b64.buf[LJWS_JOSE] = p1;
|
|
n = lws_jws_base64_enc(start, p - start, p1, end1 - p1);
|
|
if (n < 0) {
|
|
lwsl_notice("%s: failed to encode protected\n", __func__);
|
|
goto bail;
|
|
}
|
|
jws.map_b64.len[LJWS_JOSE] = n;
|
|
p1 += n;
|
|
|
|
p1 += lws_snprintf(p1, end1 - p1, "\",\"payload\":\"");
|
|
jws.map_b64.buf[LJWS_PYLD] = p1;
|
|
n = lws_jws_base64_enc(payload, len, p1, end1 - p1);
|
|
if (n < 0) {
|
|
lwsl_notice("%s: failed to encode payload\n", __func__);
|
|
goto bail;
|
|
}
|
|
jws.map_b64.len[LJWS_PYLD] = n;
|
|
p1 += n;
|
|
|
|
p1 += lws_snprintf(p1, end1 - p1, "\",\"header\":\"");
|
|
jws.map_b64.buf[LJWS_UHDR] = p1;
|
|
n = lws_jws_base64_enc(payload, len, p1, end1 - p1);
|
|
if (n < 0) {
|
|
lwsl_notice("%s: failed to encode payload\n", __func__);
|
|
goto bail;
|
|
}
|
|
jws.map_b64.len[LJWS_UHDR] = n;
|
|
|
|
p1 += n;
|
|
p1 += lws_snprintf(p1, 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, 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] = n;
|
|
|
|
p1 += n;
|
|
p1 += lws_snprintf(p1, 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, end1 - p1, "{\"protected\":\"");
|
|
jwe->jws.map_b64.buf[LJWS_JOSE] = p1;
|
|
n = lws_jws_base64_enc(protected, plen, p1, end1 - p1);
|
|
if (n < 0) {
|
|
lwsl_notice("%s: failed to encode protected\n", __func__);
|
|
goto bail;
|
|
}
|
|
jwe->jws.map_b64.len[LJWS_JOSE] = n;
|
|
p1 += n;
|
|
|
|
/* unprotected not supported atm */
|
|
|
|
p1 += lws_snprintf(p1, 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, 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, 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, end1 - p1, "\"");
|
|
}
|
|
|
|
p1 += lws_snprintf(p1, end1 - p1, "\n}\n");
|
|
|
|
return lws_ptr_diff(p1, out);
|
|
|
|
bail:
|
|
lws_jws_destroy(&jwe->jws);
|
|
|
|
return -1;
|
|
}
|