mirror of
https://libwebsockets.org/repo/libwebsockets
synced 2024-11-22 00:57:52 +00:00
c9731c5f17
This is a huge patch that should be a global NOP. For unix type platforms it enables -Wconversion to issue warnings (-> error) for all automatic casts that seem less than ideal but are normally concealed by the toolchain. This is things like passing an int to a size_t argument. Once enabled, I went through all args on my default build (which build most things) and tried to make the removed default cast explicit. With that approach it neither change nor bloat the code, since it compiles to whatever it was doing before, just with the casts made explicit... in a few cases I changed some length args from int to size_t but largely left the causes alone. From now on, new code that is relying on less than ideal casting will complain and nudge me to improve it by warnings.
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;
|
|
}
|