diff --git a/include/libwebsockets/lws-jws.h b/include/libwebsockets/lws-jws.h index c59affb96..0ad4714b7 100644 --- a/include/libwebsockets/lws-jws.h +++ b/include/libwebsockets/lws-jws.h @@ -455,6 +455,46 @@ lws_jwt_sign_compact(struct lws_context *ctx, struct lws_jwk *jwk, const char *alg, char *out, size_t *out_len, char *temp, int tl, const char *format, ...) LWS_FORMAT(8); +struct lws_jwt_sign_info { + const char *alg; + /**< entry: signing alg name, like "RS256" */ + const char *jose_hdr; + /**< entry: optional JOSE hdr; if present, alg field is ignored; instead the + * whole claim object has to be provided in this parameter */ + size_t jose_hdr_len; + /**< entry: if jose_hdr is not NULL, JOSE header length without terminating '\0' */ + char *out; + /**< exit: signed JWT in compact form*/ + size_t *out_len; + /**< entry,exit: buffer size of out; actual size of JWT on exit */ + char *temp; + /**< exit undefined content, used by the function as a temporary scratchpad; MUST + * be large enogh to store various intermediate representations */ + int tl; + /**< entry: size of temp buffer */ +}; + +/** + * lws_jwt_sign_compact() - generate a compact JWT using a key and JOSE header + * + * \param ctx: the lws_context + * \param jwk: the signing key + * \param info: info describing the JWT's content and output/temp buffers + * \param format: a printf style format specification of the claims object + * \param ...: zero or more args for the format specification + * + * Creates a JWT in a single step, from the format string and args through to + * outputting a well-formed compact JWT representation in out. The provided + * JOSE header's syntax is checked before it is added to the JWT. + * + * Returns 0 if all is well and *out_len is the amount of data in out, else + * nonzero if failed. Temp must be large enough to hold various intermediate + * representations. + */ +LWS_VISIBLE LWS_EXTERN int +lws_jwt_sign_via_info(struct lws_context *ctx, struct lws_jwk *jwk, + const struct lws_jwt_sign_info *info, const char *format, ...) LWS_FORMAT(4); + /** * lws_jwt_token_sanity() - check a validated jwt payload for sanity * diff --git a/lib/jose/jws/jws.c b/lib/jose/jws/jws.c index 549796afb..6364c6be7 100644 --- a/lib/jose/jws/jws.c +++ b/lib/jose/jws/jws.c @@ -1039,80 +1039,110 @@ bail: return r; } -int -lws_jwt_sign_compact(struct lws_context *ctx, struct lws_jwk *jwk, - const char *alg, char *out, size_t *out_len, char *temp, - int tl, const char *format, ...) +static int lws_jwt_vsign_via_info(struct lws_context *ctx, struct lws_jwk *jwk, + const struct lws_jwt_sign_info *info, const char *format, va_list ap) { - int n, r = 1, otl = tl; + size_t actual_hdr_len; struct lws_jose jose; struct lws_jws jws; - va_list ap; - char *q; + va_list ap_cpy; + int n, r = 1; + int otl, tlr; + char *p, *q; lws_jws_init(&jws, jwk, ctx); lws_jose_init(&jose); - if (lws_gencrypto_jws_alg_to_definition(alg, &jose.alg)) { - lwsl_err("%s: unknown alg %s\n", __func__, alg); + otl = tlr = info->tl; + p = info->temp; + /* + * We either just use the provided info->jose_hdr, or build a + * minimal header from info->alg + */ + actual_hdr_len = info->jose_hdr ? info->jose_hdr_len : + 10 + strlen(info->alg); + + if (actual_hdr_len > INT_MAX) { + goto bail; + } + + if (lws_jws_alloc_element(&jws.map, LJWS_JOSE, info->temp, &tlr, + actual_hdr_len, 0)) { + lwsl_err("%s: temp space too small\n", __func__); goto bail; } - /* create JOSE header, also needed for output */ + if (!info->jose_hdr) { - if (lws_jws_alloc_element(&jws.map, LJWS_JOSE, temp, &tl, - strlen(alg) + 10, 0)) { - lwsl_err("%s: temp space too small\n", __func__); - return 1; + /* get algorithm from 'alg' string and write minimal JOSE header */ + if (lws_gencrypto_jws_alg_to_definition(info->alg, &jose.alg)) { + lwsl_err("%s: unknown alg %s\n", __func__, info->alg); + + goto bail; + } + jws.map.len[LJWS_JOSE] = (uint32_t)lws_snprintf( + (char *)jws.map.buf[LJWS_JOSE], (size_t)otl, + "{\"alg\":\"%s\"}", info->alg); + } else { + + /* + * Get algorithm by parsing the given JOSE header and copy it, + * if it's ok + */ + if (lws_jws_parse_jose(&jose, info->jose_hdr, + (int)actual_hdr_len, info->temp, &tlr)) { + lwsl_err("%s: invalid jose header\n", __func__); + goto bail; + } + tlr = otl; + memcpy((char *)jws.map.buf[LJWS_JOSE], info->jose_hdr, + actual_hdr_len); + jws.map.len[LJWS_JOSE] = (uint32_t)actual_hdr_len; + tlr -= (int)actual_hdr_len; } - jws.map.len[LJWS_JOSE] = (uint32_t)lws_snprintf((char *)jws.map.buf[LJWS_JOSE], - (size_t)tl, "{\"alg\":\"%s\"}", alg); + p += otl - tlr; + otl = tlr; - temp += otl - tl; - otl = tl; - - va_start(ap, format); - n = vsnprintf(NULL, 0, format, ap); - va_end(ap); - if (n + 2 >= tl) + va_copy(ap_cpy, ap); + n = vsnprintf(NULL, 0, format, ap_cpy); + va_end(ap_cpy); + if (n + 2 >= tlr) goto bail; q = lws_malloc((unsigned int)n + 2, __func__); if (!q) goto bail; - va_start(ap, format); vsnprintf(q, (unsigned int)n + 2, format, ap); - va_end(ap); /* add the plaintext from stdin to the map and a b64 version */ jws.map.buf[LJWS_PYLD] = q; jws.map.len[LJWS_PYLD] = (uint32_t)n; - if (lws_jws_encode_b64_element(&jws.map_b64, LJWS_PYLD, temp, &tl, + if (lws_jws_encode_b64_element(&jws.map_b64, LJWS_PYLD, p, &tlr, jws.map.buf[LJWS_PYLD], jws.map.len[LJWS_PYLD])) goto bail1; - temp += otl - tl; - otl = tl; + p += otl - tlr; + otl = tlr; /* add the b64 JOSE header to the b64 map */ - if (lws_jws_encode_b64_element(&jws.map_b64, LJWS_JOSE, temp, &tl, + if (lws_jws_encode_b64_element(&jws.map_b64, LJWS_JOSE, p, &tlr, jws.map.buf[LJWS_JOSE], jws.map.len[LJWS_JOSE])) goto bail1; - temp += otl - tl; - otl = tl; + p += otl - tlr; + otl = tlr; /* prepare the space for the b64 signature in the map */ - if (lws_jws_alloc_element(&jws.map_b64, LJWS_SIG, temp, &tl, + if (lws_jws_alloc_element(&jws.map_b64, LJWS_SIG, p, &tlr, (size_t)lws_base64_size(LWS_JWE_LIMIT_KEY_ELEMENT_BYTES), 0)) goto bail1; @@ -1129,10 +1159,10 @@ lws_jwt_sign_compact(struct lws_context *ctx, struct lws_jwk *jwk, jws.map_b64.len[LJWS_SIG] = (uint32_t)n; /* create the compact JWS representation */ - if (lws_jws_write_compact(&jws, out, *out_len)) + if (lws_jws_write_compact(&jws, info->out, *info->out_len)) goto bail1; - *out_len = strlen(out); + *info->out_len = strlen(info->out); r = 0; @@ -1148,6 +1178,45 @@ bail: return r; } +int +lws_jwt_sign_via_info(struct lws_context *ctx, struct lws_jwk *jwk, + const struct lws_jwt_sign_info *info, const char *format, + ...) +{ + int ret; + va_list ap; + + va_start(ap, format); + ret = lws_jwt_vsign_via_info(ctx, jwk, info, format, ap); + va_end(ap); + + return ret; +} + +int +lws_jwt_sign_compact(struct lws_context *ctx, struct lws_jwk *jwk, + const char *alg, char *out, size_t *out_len, char *temp, + int tl, const char *format, ...) +{ + struct lws_jwt_sign_info info = { + .alg = alg, + .jose_hdr = NULL, + .out = out, + .out_len = out_len, + .temp = temp, + .tl = tl + }; + int r = 1; + va_list ap; + + va_start(ap, format); + + r = lws_jwt_vsign_via_info(ctx, jwk, &info, format, ap); + + va_end(ap); + return r; +} + int lws_jwt_token_sanity(const char *in, size_t in_len, const char *iss, const char *aud, diff --git a/minimal-examples/api-tests/api-test-jose/jws.c b/minimal-examples/api-tests/api-test-jose/jws.c index d8e4a701f..7626e1ca0 100644 --- a/minimal-examples/api-tests/api-test-jose/jws.c +++ b/minimal-examples/api-tests/api-test-jose/jws.c @@ -737,6 +737,215 @@ bail: return ret; } +static char + rsa_cert[] = "-----BEGIN CERTIFICATE-----\n" + "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\n" + "e2Hl/7M0c/lbZ4FvXgARlex2rkgS0Ka06HE=\n" + "-----END CERTIFICATE-----\n", + rsa_key[] = "-----BEGIN PRIVATE KEY-----\n" + "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\n" + "fBrpEY1IATtPq1taBZZogRqI3rOkkPk=\n" + "-----END PRIVATE KEY-----\n"; + +int +test_jwt_RS256(struct lws_context *context) +{ + struct lws_jwk jwk; + struct lws_x509_cert *pub = NULL; + int ret = -1; + int ret_encode; + char sha1_fingerprint[30]; + uint8_t sha1sum[20]; + char der_buf[LWS_ARRAY_SIZE(rsa_cert)]; + union lws_tls_cert_info_results *der_info = + (union lws_tls_cert_info_results *)der_buf; + + if (lws_x509_create(&pub)) { + lwsl_err("%s: failed to create x509 public key\n", __func__); + goto bail; + } + + if (lws_x509_parse_from_pem(pub, rsa_cert, LWS_ARRAY_SIZE(rsa_cert))) { + lwsl_err("%s: failed to parse x509 public key\n", __func__); + goto bail; + } + + if (lws_x509_public_to_jwk(&jwk, pub, NULL, 2048)) { + lwsl_err("%s: failed to copy public key to jwk\n", __func__); + goto bail; + } + + if (lws_x509_jwk_privkey_pem(&jwk, (char *)rsa_key, + LWS_ARRAY_SIZE(rsa_key), NULL)) { + lwsl_err("%s: failed to copy private key to jwk\n", __func__); + goto bail; + } + + if (lws_x509_info(pub, LWS_TLS_CERT_INFO_DER_RAW, der_info, + LWS_ARRAY_SIZE(der_buf) - sizeof(der_info) + + sizeof(der_info->ns.name)) || + der_info->ns.len <= 0) { + lwsl_err("%s: failed to parse x509 public key\n", __func__); + goto bail; + } + + if (!lws_SHA1((unsigned char *)der_info->ns.name, + (size_t)der_info->ns.len, sha1sum)) { + lwsl_err("%s: sha1sum of public key failed\n", __func__); + goto bail; + } + + ret_encode = lws_b64_encode_string_url((char *)sha1sum, + LWS_ARRAY_SIZE(sha1sum), sha1_fingerprint, + LWS_ARRAY_SIZE(sha1_fingerprint)); + if (ret_encode < 0) { + lwsl_err("%s: failed to encode sha1sum to base64url\n", __func__); + goto bail; + } + + while (sha1_fingerprint[--ret_encode] == '=') + sha1_fingerprint[ret_encode] = '\0'; + + lwsl_notice("%s: cert fingerprint '%s'\n", __func__, sha1_fingerprint); + + /* now produce jwt with some additional header fields */ + { + unsigned long long ull = lws_now_secs(); + char buf[8192]; + size_t cml = 2048, cml2 = 2048; + const char hdr_fmt[] = "{\"alg\":\"RS256\", \"typ\":\"JWT\", \"x5t\":\"%s\"}"; + char jose_hdr[LWS_ARRAY_SIZE(hdr_fmt) + LWS_ARRAY_SIZE(sha1_fingerprint)]; + + struct lws_jwt_sign_info info = { + .alg = NULL, + .jose_hdr = jose_hdr, + .jose_hdr_len = (size_t)lws_snprintf(jose_hdr, LWS_ARRAY_SIZE(jose_hdr), hdr_fmt, sha1_fingerprint), + .out = buf, + .out_len = &cml2, + .temp = buf + cml2, + .tl = 4096 + }; + + lwsl_notice("%s: jose_hdr of len %zu: '%s'\n", __func__, info.jose_hdr_len, info.jose_hdr); + if (lws_jwt_sign_via_info(context, &jwk, &info, + "{\"iss\":\"warmcat.com\",\"aud\":" + "\"https://libwebsockets.org/sai\"," + "\"iat\":%llu," + "\"nbf\":%llu," + "\"exp\":%llu," + "\"sub\":\"manage\"}", ull, + ull - 60, ull + (30 * 24 * 3600) + )) { + lwsl_err("%s: failed to create JWT\n", __func__); + goto bail1; + } + + lwsl_notice("%s: jwt test '%s'\n", __func__, buf); + + if (lws_jwt_signed_validate(context, &jwk, "RS256", + (const char *)buf, cml2, + (char *)buf + 2048, 2048, + (char *)buf + 4096, &cml)) { + lwsl_err("%s: failed to parse JWT\n", __func__); + + goto bail1; + } + + lwsl_notice("%s: jwt valid, payload '%s'\n", + __func__, buf + 4096); + } + + /* end */ + ret = 0; + +bail1: + lws_jwk_destroy(&jwk); + lws_x509_destroy(&pub); + +bail: + lwsl_notice("%s: selftest %s\n", __func__, ret ? "FAIL" : "OK"); + + return ret; +} + int test_jws(struct lws_context *context) { @@ -747,6 +956,7 @@ test_jws(struct lws_context *context) n |= test_jws_RS256(context); n |= test_jws_ES256(context); n |= test_jws_ES512(context); + n |= test_jwt_RS256(context); return n; }