diff --git a/CMakeLists.txt b/CMakeLists.txt index e44fee848..7de8765df 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -106,8 +106,10 @@ option(LWS_WITH_NO_LOGS "Disable all logging from being compiled in" OFF) option(LWS_AVOID_SIGPIPE_IGN "Android 7+ reportedly needs this" OFF) option(LWS_WITH_STATS "Keep statistics of lws internal operations" OFF) option(LWS_WITH_JWS "JSON Web Signature (RFC7515) API" OFF) +option(LWS_WITH_JWE "JSON Web Encryption (RFC7516) API" OFF) option(LWS_WITH_GENHASH "Enable support for Generic Hash (SHA1 + SHA2 with api independent of TLS backend)" OFF) option(LWS_WITH_GENRSA "Enable support for Generic RSA (RSA with api independent of TLS backend)" OFF) +option(LWS_WITH_GENEC "Enable support for Generic EC (EC with api independent of TLS backend)" OFF) option(LWS_WITH_SELFTESTS "Selftests run at context creation" OFF) option(LWS_WITH_GCOV "Build with gcc gcov coverage instrumentation" OFF) option(LWS_WITH_EXPORT_LWSTARGETS "Export libwebsockets CMake targets. Disable if they conflict with an outer cmake project." ON) @@ -156,6 +158,9 @@ if(LWS_WITH_DISTRO_RECOMMENDED) set(LWS_WITH_LEJP_CONF 1) set(LWS_WITH_PLUGINS 1) set(LWS_ROLE_RAW_PROXY 1) + set(LWS_WITH_GENHASH 1) + set(LWS_WITH_GENRSA 1) + set(LWS_WITH_GENEC 1) endif() # do you care about this? Then send me a patch where it disables it on travis @@ -295,10 +300,15 @@ if (LWS_WITH_ACME) set (LWS_WITH_JWS 1) endif() +if (LWS_WITH_JWE) + set(LWS_WITH_JWS 1) +endif() + if (LWS_WITH_JWS) set(LWS_WITH_LEJP 1) set(LWS_WITH_GENHASH 1) set(LWS_WITH_GENRSA 1) + set(LWS_WITH_GENEC 1) endif() if (LWS_WITH_PLUGINS AND NOT LWS_WITH_LIBUV) @@ -1021,6 +1031,11 @@ if (LWS_WITH_SSL) lib/tls/mbedtls/lws-genrsa.c ) endif() + if (LWS_WITH_GENEC) + list(APPEND SOURCES + lib/tls/mbedtls/lws-genec.c + ) + endif() else() list(APPEND SOURCES lib/tls/openssl/ssl.c @@ -1035,6 +1050,11 @@ if (LWS_WITH_SSL) lib/tls/openssl/lws-genrsa.c ) endif() + if (LWS_WITH_GENEC) + list(APPEND SOURCES + lib/tls/openssl/lws-genec.c + ) + endif() endif() if (NOT LWS_WITHOUT_SERVER) @@ -1191,8 +1211,14 @@ endif() if (LWS_WITH_JWS) list(APPEND SOURCES - lib/misc/jws/jwk.c - lib/misc/jws/jws.c) + lib/jose/jwk/jwk.c + lib/jose/jws/jose.c + lib/jose/jws/jws.c) +endif() + +if (LWS_WITH_JWE) + list(APPEND SOURCES + lib/jose/jwe/jwe.c) endif() # Add helper files for Windows. diff --git a/READMEs/README.crypto-apis.md b/READMEs/README.crypto-apis.md new file mode 100644 index 000000000..193c801f6 --- /dev/null +++ b/READMEs/README.crypto-apis.md @@ -0,0 +1,40 @@ +# Lws Crypto Apis + +## Overview + +![lws crypto overview](/doc-assets/lws-crypto-overview.svg) + +Lws provides a "generic" crypto layer on top of both OpenSSL and +compatible tls library, and mbedtls. Using this layer, your code +can work without any changes on both types of tls library crypto +backends... it's as simple as rebuilding lws with `-DLWS_WITH_MBEDTLS=0` +or `=1` at cmake. + +The generic layer can be used directly (as in, eg, the sshd plugin), +or via another layer on top, which processes JOSE JSON objects using +JWS (JSON Web Signatures), JWK (JSON Web Keys), and JWE (JSON Web +Encryption). + +## Using the generic layer + +All the necessary includes are part of `libwebsockets.h`. + +|api|cmake|header|Functionality| +|---|---|---|---| +|genhash|`LWS_WITH_GENHASH`|[./include/libwebsockets/lws-genhash.h](https://libwebsockets.org/git/libwebsockets/tree/include/libwebsockets/lws-genhash.h)|Provides SHA1 + SHA2 hashes and hmac| +|genrsa|`LWS_WITH_GENRSA`|[./include/libwebsockets/lws-genrsa.h](https://libwebsockets.org/git/libwebsockets/tree/include/libwebsockets/lws-genrsa.h)|Provides RSA encryption, decryption, signing, verification, key generation and creation| + +Unit tests for these apis, which serve as usage examples, can be found in [./minimal-examples/api-tests/api-test-gencrypto](https://libwebsockets.org/git/libwebsockets/tree/minimal-examples/api-tests/api-test-gencrypto) + +## Using the JOSE layer + +All the necessary includes are part of `libwebsockets.h`. + +|api|cmake|header|Functionality| +|---|---|---|---| +|JOSE|`LWS_WITH_JWS`|[./include/libwebsockets/jose.h](https://libwebsockets.org/git/libwebsockets/tree/include/libwebsockets/lws-jose.h)|Provides signature and verifcation services for RFC7515 JOSE JSON| +|JWS|`LWS_WITH_JWS`|[./include/libwebsockets/jws.h](https://libwebsockets.org/git/libwebsockets/tree/include/libwebsockets/lws-jws.h)|Provides signature and verifcation services for RFC7515 JWS JSON| +|JWK|`LWS_WITH_JWS`|[./include/libwebsockets/jwk.h](https://libwebsockets.org/git/libwebsockets/tree/include/libwebsockets/lws-jwk.h)|Provides signature and verifcation services for RFC7517 JWK JSON, both "keys" arrays and singletons| + +Unit tests for these apis, which serve as usage examples, can be found in [./minimal-examples/api-tests/api-test-jose](https://libwebsockets.org/git/libwebsockets/tree/minimal-examples/api-tests/api-test-jose) + diff --git a/cmake/lws_config.h.in b/cmake/lws_config.h.in index 41c06afcf..ee2f4f800 100644 --- a/cmake/lws_config.h.in +++ b/cmake/lws_config.h.in @@ -75,6 +75,7 @@ #cmakedefine LWS_WITH_HTTP_PROXY #cmakedefine LWS_WITH_HTTP_STREAM_COMPRESSION #cmakedefine LWS_WITH_IPV6 +#cmakedefine LWS_WITH_JWE #cmakedefine LWS_WITH_JWS #cmakedefine LWS_WITH_LEJP #cmakedefine LWS_WITH_LIBEV diff --git a/doc-assets/lws-crypto-overview.svg b/doc-assets/lws-crypto-overview.svg new file mode 100644 index 000000000..559c8c4a0 --- /dev/null +++ b/doc-assets/lws-crypto-overview.svg @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + OpenSSL + mbedTLS + and derivitives + + genhash, genrsa, genaes, genec + JOSE, JWS, JWK, JWE + + TLS-library-specificand cipher-specifickeys using EVPor bignum + TLS library-independentmetadata +binary key elements + JWK JSON keycreation and parsing + + + + + libwebsockets + + + diff --git a/include/libwebsockets.h b/include/libwebsockets.h index 7cc5c28b7..831904f3a 100644 --- a/include/libwebsockets.h +++ b/include/libwebsockets.h @@ -422,9 +422,11 @@ struct lws; #endif #include -#include #include +#include #include +#include +#include #endif diff --git a/include/libwebsockets/lws-genec.h b/include/libwebsockets/lws-genec.h new file mode 100644 index 000000000..38bd9c851 --- /dev/null +++ b/include/libwebsockets/lws-genec.h @@ -0,0 +1,34 @@ +/* + * libwebsockets - Generic Elliptic Curve Encryption + * + * Copyright (C) 2010-2018 Andy Green + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + * + * included from libwebsockets.h + */ + +/* include/libwebsockets/lws-jwk.h must be included before this */ + +struct lws_genec_ctx { +#if defined(LWS_WITH_MBEDTLS) + mbedtls_rsa_context *ctx; +#else + BIGNUM *bn[LWS_COUNT_EC_KEY_ELEMENTS]; + RSA *rsa; +#endif +}; + diff --git a/include/libwebsockets/lws-genrsa.h b/include/libwebsockets/lws-genrsa.h index 3e427e292..f9a204ba5 100644 --- a/include/libwebsockets/lws-genrsa.h +++ b/include/libwebsockets/lws-genrsa.h @@ -32,44 +32,23 @@ */ ///@{ -enum enum_jwk_tok { - JWK_KEY_E, - JWK_KEY_N, - JWK_KEY_D, - JWK_KEY_P, - JWK_KEY_Q, - JWK_KEY_DP, - JWK_KEY_DQ, - JWK_KEY_QI, - JWK_KTY, /* also serves as count of real elements */ - JWK_KEY, -}; - -#define LWS_COUNT_RSA_ELEMENTS JWK_KTY +/* include/libwebsockets/lws-jwk.h must be included before this */ struct lws_genrsa_ctx { #if defined(LWS_WITH_MBEDTLS) mbedtls_rsa_context *ctx; #else - BIGNUM *bn[LWS_COUNT_RSA_ELEMENTS]; + BIGNUM *bn[LWS_COUNT_RSA_KEY_ELEMENTS]; RSA *rsa; #endif -}; - -struct lws_genrsa_element { - uint8_t *buf; - uint16_t len; -}; - -struct lws_genrsa_elements { - struct lws_genrsa_element e[LWS_COUNT_RSA_ELEMENTS]; + struct lws_context *context; }; /** lws_jwk_destroy_genrsa_elements() - Free allocations in genrsa_elements * - * \param el: your struct lws_genrsa_elements + * \param el: your struct lws_jwk_elements * - * This is a helper for user code making use of struct lws_genrsa_elements + * This is a helper for user code making use of struct lws_jwk_elements * where the elements are allocated on the heap, it frees any non-NULL * buf element and sets the buf to NULL. * @@ -77,7 +56,7 @@ struct lws_genrsa_elements { * creation and destruction themselves. */ LWS_VISIBLE LWS_EXTERN void -lws_jwk_destroy_genrsa_elements(struct lws_genrsa_elements *el); +lws_jwk_destroy_genrsa_elements(struct lws_jwk_elements *el); /** lws_genrsa_public_decrypt_create() - Create RSA public decrypt context * @@ -92,7 +71,8 @@ lws_jwk_destroy_genrsa_elements(struct lws_genrsa_elements *el); * This and related APIs operate identically with OpenSSL or mbedTLS backends. */ LWS_VISIBLE LWS_EXTERN int -lws_genrsa_create(struct lws_genrsa_ctx *ctx, struct lws_genrsa_elements *el); +lws_genrsa_create(struct lws_genrsa_ctx *ctx, struct lws_jwk_elements *el, + struct lws_context *context); /** lws_genrsa_new_keypair() - Create new RSA keypair * @@ -110,7 +90,24 @@ lws_genrsa_create(struct lws_genrsa_ctx *ctx, struct lws_genrsa_elements *el); */ LWS_VISIBLE LWS_EXTERN int lws_genrsa_new_keypair(struct lws_context *context, struct lws_genrsa_ctx *ctx, - struct lws_genrsa_elements *el, int bits); + struct lws_jwk_elements *el, int bits); + +/** lws_genrsa_public_encrypt() - Perform RSA public encryption + * + * \param ctx: your struct lws_genrsa_ctx + * \param in: plaintext input + * \param in_len: length of plaintext input + * \param out: encrypted output + * + * Performs PKCS1 v1.5 Encryption + * + * Returns <0 for error, or length of decrypted data. + * + * This and related APIs operate identically with OpenSSL or mbedTLS backends. + */ +LWS_VISIBLE LWS_EXTERN int +lws_genrsa_public_encrypt(struct lws_genrsa_ctx *ctx, const uint8_t *in, + size_t in_len, uint8_t *out); /** lws_genrsa_public_decrypt() - Perform RSA public decryption * @@ -120,7 +117,7 @@ lws_genrsa_new_keypair(struct lws_context *context, struct lws_genrsa_ctx *ctx, * \param out: decrypted output * \param out_max: size of output buffer * - * Performs the decryption. + * Performs PKCS1 v1.5 Decryption * * Returns <0 for error, or length of decrypted data. * diff --git a/include/libwebsockets/lws-jose.h b/include/libwebsockets/lws-jose.h new file mode 100644 index 000000000..a46af795e --- /dev/null +++ b/include/libwebsockets/lws-jose.h @@ -0,0 +1,71 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2010-2018 Andy Green + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + * + * included from libwebsockets.h + */ + +enum lws_jws_jose_hdr_indexes { + LJJHI_ALG, /* REQUIRED */ + LJJHI_JKU, /* Optional: string */ + LJJHI_JWK, /* Optional: jwk JSON object: public key: */ + LJJHI_KID, /* Optional: string */ + LJJHI_X5U, /* Optional: string: url of public key cert / chain */ + LJJHI_X5C, /* Optional: base64 (NOT -url): actual cert */ + LJJHI_X5T, /* Optional: base64url: SHA-1 of actual cert */ + LJJHI_X5T_S256, /* Optional: base64url: SHA-256 of actual cert */ + LJJHI_TYP, /* Optional: string: media type */ + LJJHI_CTY, /* Optional: string: content media type */ + LJJHI_CRIT, /* Optional for send, REQUIRED: array of strings: + * mustn't contain standardized strings or null set */ + + LJJHI_ENC, /* JWE only: Optional: string */ + LJJHI_ZIP, /* JWE only: Optional: string ("DEF" = deflate) */ + + LJJHI_EPK, /* Additional arg for JWE ECDH: ephemeral public key */ + LJJHI_APU, /* Additional arg for JWE ECDH: base64url */ + LJJHI_APV, /* Additional arg for JWE ECDH: base64url */ + LJJHI_IV, /* Additional arg for JWE AES: base64url */ + LJJHI_TAG, /* Additional arg for JWE AES: base64url */ + LJJHI_P2S, /* Additional arg for JWE PBES2: base64url: salt */ + LJJHI_P2C, /* Additional arg for JWE PBES2: integer: count */ + + LWS_COUNT_JOSE_HDR_ELEMENTS +}; + +struct lws_jose { + /* jose header elements */ + struct lws_jwk_elements e[LWS_COUNT_JOSE_HDR_ELEMENTS]; +}; + +enum lws_jws_algtype { + LWS_JWK_ENCTYPE_NONE, + LWS_JWK_ENCTYPE_RSASSA, + LWS_JWK_ENCTYPE_EC +}; + +struct cb_hdr_s { + enum lws_genhash_types hash_type; + enum lws_genhmac_types hmac_type; + char alg[24]; /* for jwe, the JWA enc alg name, eg "ECDH-ES" */ + char curve[16]; + enum lws_jws_algtype algtype; /* for jws, the signing cipher */ + + char is_jwe; +}; diff --git a/include/libwebsockets/lws-jwe.h b/include/libwebsockets/lws-jwe.h new file mode 100644 index 000000000..d5227c9fc --- /dev/null +++ b/include/libwebsockets/lws-jwe.h @@ -0,0 +1,22 @@ +/* + * libwebsockets - JSON Web Encryption + * + * Copyright (C) 2010-2018 Andy Green + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + * + * included from libwebsockets.h + */ diff --git a/include/libwebsockets/lws-jwk.h b/include/libwebsockets/lws-jwk.h index 2e2eabd73..d4c846844 100644 --- a/include/libwebsockets/lws-jwk.h +++ b/include/libwebsockets/lws-jwk.h @@ -25,35 +25,113 @@ /*! \defgroup jwk JSON Web Keys * ## JSON Web Keys API * - * Lws provides an API to parse JSON Web Keys into a struct lws_genrsa_elements. + * Lws provides an API to parse JSON Web Keys into a struct lws_jwk_elements. * * "oct" and "RSA" type keys are supported. For "oct" keys, they are held in - * the "e" member of the struct lws_genrsa_elements. + * the "e" member of the struct lws_jwk_elements. * * Keys elements are allocated on the heap. You must destroy the allocations - * in the struct lws_genrsa_elements by calling + * in the struct lws_jwk_elements by calling * lws_jwk_destroy_genrsa_elements() when you are finished with it. */ ///@{ -struct lws_jwk { - char keytype[5]; /**< "oct" or "RSA" */ - struct lws_genrsa_elements el; /**< OCTet key is in el.e */ +enum lws_jwk_kyt { + LWS_JWK_KYT_UNKNOWN, + LWS_JWK_KYT_OCT, + LWS_JWK_KYT_RSA, + LWS_JWK_KYT_EC }; +/* + * Keytypes where the same element name is reused must all agree to put the + * same-named element at the same e[] index. It's because we may not know the + * keytype until the end. + */ + +enum enum_jwk_oct_tok { + JWK_OCT_KEYEL_K, + + LWS_COUNT_OCT_KEY_ELEMENTS +}; + +enum enum_jwk_rsa_tok { + JWK_RSA_KEYEL_E, + JWK_RSA_KEYEL_N, + JWK_RSA_KEYEL_D, /* note... same offset as EC D */ + JWK_RSA_KEYEL_P, + JWK_RSA_KEYEL_Q, + JWK_RSA_KEYEL_DP, + JWK_RSA_KEYEL_DQ, + JWK_RSA_KEYEL_QI, + + LWS_COUNT_RSA_KEY_ELEMENTS +}; + +enum enum_jwk_ec_tok { + JWK_EC_KEYEL_CRV, + JWK_EC_KEYEL_X, + JWK_EC_KEYEL_D, /* note... same offset as RSA D */ + JWK_EC_KEYEL_Y, + + LWS_COUNT_EC_KEY_ELEMENTS +}; + +enum enum_jwk_meta_tok { + JWK_META_KTY, + JWK_META_KID, + JWK_META_USE, + JWK_META_KEY_OPS, + JWK_META_X5C, + JWK_META_ALG, + + LWS_COUNT_JWK_ELEMENTS +}; + +/* largest number of key elements for any algorithm */ +#define LWS_COUNT_ALG_KEY_ELEMENTS_MAX LWS_COUNT_RSA_KEY_ELEMENTS + +struct lws_jwk_elements { + uint8_t *buf; + uint16_t len; +}; + +struct lws_jwk { + /* key data elements */ + struct lws_jwk_elements e[LWS_COUNT_ALG_KEY_ELEMENTS_MAX]; + /* generic meta key elements, like KID */ + struct lws_jwk_elements meta[LWS_COUNT_JWK_ELEMENTS]; + int kty; /**< one of LWS_JWK_ */ +}; + +typedef int (*lws_jwk_key_import_callback)(struct lws_jwk *s, void *user); + /** lws_jwk_import() - Create a JSON Web key from the textual representation * * \param s: the JWK object to create + * \param cb: callback for each jwk-processed key, or NULL if importing a single + * key with no parent "keys" JSON + * \param user: pointer to be passed to the callback, otherwise ignored by lws. + * NULL if importing a single key with no parent "keys" JSON * \param in: a single JWK JSON stanza in utf-8 * \param len: the length of the JWK JSON stanza in bytes * * Creates an lws_jwk struct filled with data from the JSON representation. - * "oct" and "rsa" key types are supported. * - * For "oct" type keys, it is loaded into el.e. + * There are two ways to use this... with some protocols a single jwk is + * delivered with no parent "keys": [] array. If you call this with cb and + * user as NULL, then the input will be interpreted like that and the results + * placed in s. + * + * The second case is that you are dealing with a "keys":[] array with one or + * more keys in it. In this case, the function iterates through the keys using + * s as a temporary jwk, and calls the user-provided callback for each key in + * turn while it return 0 (nonzero return from the callback terminates the + * iteration through any further keys). */ LWS_VISIBLE LWS_EXTERN int -lws_jwk_import(struct lws_jwk *s, const char *in, size_t len); +lws_jwk_import(struct lws_jwk *s, lws_jwk_key_import_callback cb, void *user, + const char *in, size_t len); /** lws_jwk_destroy() - Destroy a JSON Web key * @@ -84,9 +162,21 @@ lws_jwk_export(struct lws_jwk *s, int _private, char *p, size_t len); * \param filename: filename to load from * * Returns 0 for OK or -1 for failure + * + * There are two ways to use this... with some protocols a single jwk is + * delivered with no parent "keys": [] array. If you call this with cb and + * user as NULL, then the input will be interpreted like that and the results + * placed in s. + * + * The second case is that you are dealing with a "keys":[] array with one or + * more keys in it. In this case, the function iterates through the keys using + * s as a temporary jwk, and calls the user-provided callback for each key in + * turn while it return 0 (nonzero return from the callback terminates the + * iteration through any further keys, leaving the last one in s). */ LWS_VISIBLE int -lws_jwk_load(struct lws_jwk *s, const char *filename); +lws_jwk_load(struct lws_jwk *s, const char *filename, + lws_jwk_key_import_callback cb, void *user); /** lws_jwk_save() - Export a JSON Web key to a file * diff --git a/include/libwebsockets/lws-jws.h b/include/libwebsockets/lws-jws.h index 7112fc3db..3485146d5 100644 --- a/include/libwebsockets/lws-jws.h +++ b/include/libwebsockets/lws-jws.h @@ -34,7 +34,8 @@ ///@{ LWS_VISIBLE LWS_EXTERN int -lws_jws_confirm_sig(const char *in, size_t len, struct lws_jwk *jwk); +lws_jws_confirm_sig(const char *in, size_t len, struct lws_jwk *jwk, + struct lws_context *context); /** * lws_jws_sign_from_b64() - add b64 sig to b64 hdr + payload @@ -47,6 +48,7 @@ lws_jws_confirm_sig(const char *in, size_t len, struct lws_jwk *jwk); * \param sig_len: max bytes we can write at b64_sig * \param hash_type: one of LWS_GENHASH_TYPE_SHA[256|384|512] * \param jwk: the struct lws_jwk containing the signing key + * \param context: the lws context (used to get random) * * This adds a b64-coded JWS signature of the b64-encoded protected header * and b64-encoded payload, at \p b64_sig. The signature will be as large @@ -62,7 +64,8 @@ lws_jws_confirm_sig(const char *in, size_t len, struct lws_jwk *jwk); LWS_VISIBLE LWS_EXTERN int lws_jws_sign_from_b64(const char *b64_hdr, size_t hdr_len, const char *b64_pay, size_t pay_len, char *b64_sig, size_t sig_len, - enum lws_genhash_types hash_type, struct lws_jwk *jwk); + enum lws_genhash_types hash_type, struct lws_jwk *jwk, + struct lws_context *context); /** * lws_jws_create_packet() - add b64 sig to b64 hdr + payload @@ -73,6 +76,7 @@ lws_jws_sign_from_b64(const char *b64_hdr, size_t hdr_len, const char *b64_pay, * \param nonce: Nonse string to include in protected header * \param out: buffer to take signed packet * \param out_len: size of \p out buffer + * \param conext: lws_context to get random from * * This creates a "flattened" JWS packet from the jwk and the plaintext * payload, and signs it. The packet is written into \p out. @@ -84,7 +88,8 @@ lws_jws_sign_from_b64(const char *b64_hdr, size_t hdr_len, const char *b64_pay, */ LWS_VISIBLE LWS_EXTERN int lws_jws_create_packet(struct lws_jwk *jwk, const char *payload, size_t len, - const char *nonce, char *out, size_t out_len); + const char *nonce, char *out, size_t out_len, + struct lws_context *context); /** * lws_jws_base64_enc() - encode input data into b64url data @@ -98,4 +103,21 @@ lws_jws_create_packet(struct lws_jwk *jwk, const char *payload, size_t len, */ LWS_VISIBLE LWS_EXTERN int lws_jws_base64_enc(const char *in, size_t in_len, char *out, size_t out_max); + +/** + * lws_jws_encode_section() - encode input data into b64url data, prepending . if not first + * + * \param in: the incoming plaintext + * \param in_len: the length of the incoming plaintext in bytes + * \param first: nonzero if the first section + * \param out: the buffer to store the b64url encoded data to + * \param out_max: the length of \p out in bytes + * + * Returns either -1 if problems, or the number of bytes written to \p out. + * If the section is not the first one, '.' is prepended. + */ + +LWS_VISIBLE LWS_EXTERN int +lws_jws_encode_section(const char *in, size_t in_len, int first, char **p, + char *end); ///@} diff --git a/lib/README.md b/lib/README.md index d6475b8da..2c0c900eb 100644 --- a/lib/README.md +++ b/lib/README.md @@ -6,6 +6,7 @@ Path|Sources ---|--- lib/core|Core lws code related to generic fd and wsi servicing and management lib/event-libs|Code containing optional event-lib specific adaptations +lib/jose|JOSE / JWS / JWK / JWE implementations lib/misc|Code for various mostly optional miscellaneous features lib/plat|Platform-specific adaptation code lib/roles|Code for specific optional wsi roles, eg, http/1, h2, ws, raw, etc diff --git a/lib/core/context.c b/lib/core/context.c index 9749447a6..e36e942e8 100644 --- a/lib/core/context.c +++ b/lib/core/context.c @@ -1273,10 +1273,6 @@ lws_create_context(const struct lws_context_creation_info *info) /* expedite post-context init (eg, protocols) */ lws_cancel_service(context); -#if defined(LWS_WITH_SELFTESTS) - lws_jws_selftest(); -#endif - return context; bail: diff --git a/lib/core/private.h b/lib/core/private.h index 9a8dc8684..43aa14a5f 100644 --- a/lib/core/private.h +++ b/lib/core/private.h @@ -1337,7 +1337,7 @@ int lws_tls_check_cert_lifetime(struct lws_vhost *vhost); int lws_jws_selftest(void); - +int lws_jwe_selftest(void); #ifndef LWS_NO_CLIENT LWS_EXTERN int lws_client_socket_service(struct lws *wsi, diff --git a/lib/jose/README.md b/lib/jose/README.md new file mode 100644 index 000000000..cf7284f39 --- /dev/null +++ b/lib/jose/README.md @@ -0,0 +1,51 @@ +# JOSE support + +JOSE is a set of web standards aimed at encapsulating crypto +operations flexibly inside JSON objects. + +Lws provides lightweight apis to performs operations on JWK, JWS and JWE +independent of the tls backend in use. The JSON parsing is handled by the lws +lejp stream parser. + +|Part|RFC|Function| +|---|---|---| +|JWS|[RFC7515](https://tools.ietf.org/html/rfc7515)|JSON Web Signatures| +|JWE|[RFC7516](https://tools.ietf.org/html/rfc7516)|JSON Web Encryption| +|JWK|[RFC7517](https://tools.ietf.org/html/rfc7517)|JSON Web Keys| +|JWA|[RFC7518](https://tools.ietf.org/html/rfc7518)|JSON Web Algorithms| + +JWA is a set of recommendations for which combinations of algorithms +are deemed desirable and secure, which implies what must be done for +useful implementations of JWS, JWE and JWK. + +## Supported algorithms + +Symmetric ciphers are not currently supported... symmetric keys and HMAC +are supported though. + +For the required and recommended asymmetric algorithms, support currently +looks like this + +|JWK kty|JWA|lws| +|---|---|---| +|EC|Recommended+|no| +|RSA|Required|yes| +|oct|Required|yes| + +|JWE alg|JWA|lws| +|---|---|---| +|RSA1_5|Recommended-|yes (no JWE yet but lws_genrsa supports)| +|RSA-OAEP|Recommended+|no| +|ECDH-ES|Recommended+|no| + +|JWS alg|JWA|lws| +|---|---|---| +|HS256|Required|yes| +|RS256|Recommended+|yes| +|ES256|Recommended|no| + +## API tests + +See `./minimal-examples/api-tests/api-test-jose/` for example test code. +The tests are built and confirmed during CI. + diff --git a/lib/jose/jwe/jwe.c b/lib/jose/jwe/jwe.c new file mode 100644 index 000000000..cc7c9c82d --- /dev/null +++ b/lib/jose/jwe/jwe.c @@ -0,0 +1,27 @@ +/* + * libwebsockets - JSON Web Encryption support + * + * Copyright (C) 2018 Andy Green + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + * + * + * This supports RFC7516 JSON Web Encryption + * + * + */ +#include "core/private.h" + diff --git a/lib/jose/jwk/jwk.c b/lib/jose/jwk/jwk.c new file mode 100644 index 000000000..a216697f7 --- /dev/null +++ b/lib/jose/jwk/jwk.c @@ -0,0 +1,497 @@ +/* + * libwebsockets - JSON Web Key support + * + * Copyright (C) 2017 - 2018 Andy Green + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + +#include "core/private.h" + +#include +#include + +static const char * const kyt_names[] = { + "unknown", /* LWS_JWK_KYT_UNKNOWN */ + "oct", /* LWS_JWK_KYT_OCT */ + "RSA", /* LWS_JWK_KYT_RSA */ + "EC" /* LWS_JWK_KYT_EC */ +}; + +/* + * These are the entire legal token set for names in jwk. + * + * The first version is used to parse a detached single jwk that don't have any + * parent JSON context. The second version is used to parse full jwk objects + * that has a "keys": [ ] array containing the keys. + */ + +static const char * const jwk_tok[] = { + "keys[]", /* dummy */ + "e", "n", "d", "p", "q", "dp", "dq", "qi", /* RSA */ + "kty", /* generic */ + "k", /* symmetric oct key data */ + "crv", "x", "y", /* EC (also "D") */ + "kid", /* generic */ + "use" /* mutually exclusive with "key_ops" */, + "key_ops" /* mutually exclusive with "use" */, + "x5c", /* generic */ + "alg" /* generic */ +}, * const jwk_outer_tok[] = { + "keys[]", + "keys[].e", "keys[].n", "keys[].d", "keys[].p", "keys[].q", "keys[].dp", + "keys[].dq", "keys[].qi", + + "keys[].kty", "keys[].k", /* generic */ + "keys[].crv", "keys[].x", "keys[].y", /* EC (also "D") */ + "keys[].kid", "keys[].use" /* mutually exclusive with "key_ops" */, + "keys[].key_ops", /* mutually exclusive with "use" */ + "keys[].x5c", "keys[].alg" +}; + +/* information about each token declared above */ + +#define FLAG_META (1 << 12) +#define FLAG_RSA (1 << 13) +#define FLAG_EC (1 << 14) +#define FLAG_OCT (1 << 15) + +unsigned short tok_map[] = { + FLAG_RSA | FLAG_EC | FLAG_OCT | FLAG_META | 0, /* padding */ + FLAG_RSA | JWK_RSA_KEYEL_E, + FLAG_RSA | JWK_RSA_KEYEL_N, + FLAG_RSA | FLAG_EC | JWK_RSA_KEYEL_D, + FLAG_RSA | JWK_RSA_KEYEL_P, + FLAG_RSA | JWK_RSA_KEYEL_Q, + FLAG_RSA | JWK_RSA_KEYEL_DP, + FLAG_RSA | JWK_RSA_KEYEL_DQ, + FLAG_RSA | JWK_RSA_KEYEL_QI, + + FLAG_RSA | FLAG_EC | FLAG_OCT | FLAG_META | JWK_META_KTY, + FLAG_OCT | JWK_OCT_KEYEL_K, + + FLAG_EC | JWK_EC_KEYEL_CRV, + FLAG_EC | JWK_EC_KEYEL_X, + FLAG_EC | JWK_EC_KEYEL_Y, + + FLAG_RSA | FLAG_EC | FLAG_OCT | FLAG_META | JWK_META_KID, + FLAG_RSA | FLAG_EC | FLAG_OCT | FLAG_META | JWK_META_USE, + + FLAG_RSA | FLAG_EC | FLAG_OCT | FLAG_META | JWK_META_KEY_OPS, + FLAG_RSA | FLAG_EC | FLAG_OCT | FLAG_META | JWK_META_X5C, + FLAG_RSA | FLAG_EC | FLAG_OCT | FLAG_META | JWK_META_ALG, +}; + +struct cb_lws_jwk { + struct lws_jwk *s; + char *b64; + lws_jwk_key_import_callback per_key_cb; + void *user; + int b64max; + int pos; + unsigned short possible; +}; + +static int +_lws_jwk_set_element_jwk(struct lws_jwk_elements *e, char *in, int len) +{ + e->buf = lws_malloc(len + 1, "jwk"); + if (!e->buf) + return -1; + + memcpy(e->buf, in, len); + e->buf[len] = '\0'; + e->len = len; + + return 0; +} + +static int +_lws_jwk_set_element_jwk_b64(struct lws_jwk_elements *e, char *in, int len) +{ + int dec_size = ((len * 3) / 4) + 4, n; + + e->buf = lws_malloc(dec_size, "jwk"); + if (!e->buf) + return -1; + + n = lws_b64_decode_string_len(in, len, (char *)e->buf, dec_size - 1); + if (n < 0) + return -1; + e->len = n; + + return 0; +} + +void +lws_jwk_destroy_elements(struct lws_jwk_elements *el, int m) +{ + int n; + + for (n = 0; n < m; n++) + if (el[n].buf) + lws_free_set_NULL(el[n].buf); +} + +LWS_VISIBLE void +lws_jwk_destroy(struct lws_jwk *s) +{ + lws_jwk_destroy_elements(s->e, LWS_ARRAY_SIZE(s->e)); + lws_jwk_destroy_elements(s->meta, LWS_ARRAY_SIZE(s->meta)); +} + +static signed char +cb_jwk(struct lejp_ctx *ctx, char reason) +{ + struct cb_lws_jwk *cbs = (struct cb_lws_jwk *)ctx->user; + struct lws_jwk *s = cbs->s; + int idx, poss; + + if (reason == LEJPCB_VAL_STR_START) + cbs->pos = 0; + + if (reason == LEJPCB_OBJECT_START && ctx->path_match == 0 + 1) + /* + * new keys[] member is starting + * + * Until we see some JSON names, it could be anything... + * there is no requirement for kty to be given first and eg, + * ACME specifies the keys must be ordered in lexographic + * order - where kty is not first. + */ + cbs->possible = FLAG_RSA | FLAG_EC | FLAG_OCT; + + if (reason == LEJPCB_OBJECT_END && ctx->path_match == 0 + 1) { + /* we completed parsing a key */ + if (cbs->per_key_cb && cbs->possible) { + if (cbs->per_key_cb(cbs->s, cbs->user)) { + + lwsl_notice("%s: user cb halts import\n", __func__); + + return -2; + } + + /* clear it down */ + lws_jwk_destroy(cbs->s); + cbs->possible = 0; + } + } + + if (!(reason & LEJP_FLAG_CB_IS_VALUE) || !ctx->path_match) + return 0; + + if (ctx->path_match == 0 + 1) + return 0; + + idx = tok_map[ctx->path_match - 1]; + + switch (idx) { + /* note: kty is not necessarily first... we have to keep track of + * what could match given which element names have already been + * seen. Once kty comes, we confirm it's still possible (ie, it's + * not trying to tell us that it's RSA when we saw a "crv" + * already) and then reduce the possibilities to just the one that + * kty told. */ + case FLAG_RSA | FLAG_EC | FLAG_OCT | FLAG_META | JWK_META_KTY: + + if (!strcmp(ctx->buf, "oct")) { + if (!(cbs->possible & FLAG_OCT)) + goto elements_mismatch; + s->kty = LWS_JWK_KYT_OCT; + cbs->possible = FLAG_OCT; + break; + } + if (!strcmp(ctx->buf, "RSA")) { + if (!(cbs->possible & FLAG_RSA)) + goto elements_mismatch; + s->kty = LWS_JWK_KYT_RSA; + cbs->possible = FLAG_RSA; + break; + } + if (!strcmp(ctx->buf, "EC")) { + if (!(cbs->possible & FLAG_EC)) + goto elements_mismatch; + s->kty = LWS_JWK_KYT_EC; + cbs->possible = FLAG_EC; + break; + } + lwsl_err("%s: Unknown KTY '%s'\n", __func__, ctx->buf); + return -1; + + default: + + if (cbs->pos + ctx->npos >= cbs->b64max) + goto bail; + + memcpy(cbs->b64 + cbs->pos, ctx->buf, ctx->npos); + cbs->pos += ctx->npos; + + if (reason == LEJPCB_VAL_STR_CHUNK) + return 0; + + /* chunking has been collated */ + + poss = idx & (FLAG_RSA | FLAG_EC | FLAG_OCT); + cbs->possible &= poss; + if (!cbs->possible) + goto elements_mismatch; + + if (idx & FLAG_META) { + if (_lws_jwk_set_element_jwk(&s->meta[idx & 0x7f], + cbs->b64, cbs->pos) < 0) + goto bail; + + break; + } + + /* key data... do the base64 decode then */ + + if (_lws_jwk_set_element_jwk_b64(&s->e[idx & 0x7f], + cbs->b64, cbs->pos) < 0) + goto bail; + + break; + } + + return 0; + +elements_mismatch: + lwsl_err("%s: jwk elements mismatch\n", __func__); + +bail: + lwsl_err("%s: element failed\n", __func__); + + return -1; +} + +LWS_VISIBLE int +lws_jwk_import(struct lws_jwk *s, lws_jwk_key_import_callback cb, void *user, + const char *in, size_t len) +{ + struct lejp_ctx jctx; + struct cb_lws_jwk cbs; + const int b64max = (((8192 / 8) * 4) / 3) + 1; /* enough for 8K key */ + const char * const *tok = jwk_outer_tok; + char b64[b64max]; + int m; + + memset(s, 0, sizeof(*s)); + cbs.s = s; + cbs.b64 = b64; + cbs.b64max = b64max; + cbs.pos = 0; + cbs.per_key_cb = cb; + cbs.user = user; + cbs.possible = FLAG_RSA | FLAG_EC | FLAG_OCT; + + if (cb == NULL) + tok = jwk_tok; + + lejp_construct(&jctx, cb_jwk, &cbs, tok, LWS_ARRAY_SIZE(jwk_tok)); + m = (int)(signed char)lejp_parse(&jctx, (uint8_t *)in, len); + lejp_destruct(&jctx); + + if (m < 0) { + lwsl_notice("%s: parse got %d\n", __func__, m); + + return -1; + } + + if (s->kty == LWS_JWK_KYT_UNKNOWN) { + lwsl_notice("%s: missing or unknown kyt\n", __func__); + return -1; + } + + return 0; +} + +LWS_VISIBLE int +lws_jwk_export(struct lws_jwk *s, int private, char *p, size_t len) +{ + char *start = p, *end = &p[len - 1]; + int n, limit = LWS_COUNT_JWK_ELEMENTS; + + /* RFC7638 lexicographic order requires + * RSA: e -> kty -> n + * oct: k -> kty + */ + + p += lws_snprintf(p, end - p, "{"); + + switch (s->kty) { + + case LWS_JWK_KYT_OCT: + if (!s->e[JWK_OCT_KEYEL_K].buf) + return -1; + + p += lws_snprintf(p, end - p, "\"k\":\""); + n = lws_jws_base64_enc((const char *)s->e[JWK_OCT_KEYEL_K].buf, + s->e[JWK_OCT_KEYEL_K].len, p, end - p - 4); + if (n < 0) { + lwsl_notice("%s: enc failed\n", __func__); + return -1; + } + p += n; + + p += lws_snprintf(p, end - p, "\",\"kty\":\"%s\"}", + kyt_names[s->kty]); + + return p - start; + + case LWS_JWK_KYT_RSA: + if (!s->e[JWK_RSA_KEYEL_E].buf || + !s->e[JWK_RSA_KEYEL_N].buf || + (private && (!s->e[JWK_RSA_KEYEL_D].buf || + !s->e[JWK_RSA_KEYEL_P].buf || + !s->e[JWK_RSA_KEYEL_Q].buf)) + ) { + lwsl_notice("%s: not enough elements filled\n", + __func__); + return -1; + } + + if (!private) + limit = JWK_RSA_KEYEL_N + 1; + + for (n = 0; n < limit; n++) { + int m; + + if (!s->e[n].buf) + continue; + lwsl_info("%d: len %d\n", n, s->e[n].len); + + if (n) + p += lws_snprintf(p, end - p, ","); + p += lws_snprintf(p, end - p, "\"%s\":\"", jwk_tok[n]); + m = lws_jws_base64_enc((const char *)s->e[n].buf, + s->e[n].len, p, + end - p - 4); + if (m < 0) { + lwsl_notice("%s: enc fail inlen %d outlen %d\n", + __func__, (int)s->e[n].len, + lws_ptr_diff(end, p) - 4); + return -1; + } + p += m; + *p++ = '\"'; + + if (!n) /* RFC7638 lexicographic order */ + p += lws_snprintf(p, end - p, ",\"kty\":\"%s\"", + kyt_names[s->kty]); + } + + p += lws_snprintf(p, end - p, "}"); + + return p - start; + + case LWS_JWK_KYT_EC: + return p - start; + + default: + break; + } + + lwsl_err("%s: unknown key type %d\n", __func__, s->kty); + + return -1; +} + +LWS_VISIBLE int +lws_jwk_rfc7638_fingerprint(struct lws_jwk *s, char *digest32) +{ + struct lws_genhash_ctx hash_ctx; + int tmpsize = 2536, n; + char *tmp; + + tmp = lws_malloc(tmpsize, "rfc7638 tmp"); + + n = lws_jwk_export(s, 0, tmp, tmpsize); + if (n < 0) + goto bail; + + if (lws_genhash_init(&hash_ctx, LWS_GENHASH_TYPE_SHA256)) + goto bail; + + if (lws_genhash_update(&hash_ctx, tmp, n)) { + lws_genhash_destroy(&hash_ctx, NULL); + + goto bail; + } + lws_free(tmp); + + if (lws_genhash_destroy(&hash_ctx, digest32)) + return -1; + + return 0; + +bail: + lws_free(tmp); + + return -1; +} + +LWS_VISIBLE int +lws_jwk_load(struct lws_jwk *s, const char *filename, + lws_jwk_key_import_callback cb, void *user) +{ + int buflen = 4096; + char *buf = lws_malloc(buflen, "jwk-load"); + int n; + + if (!buf) + return -1; + + n = lws_plat_read_file(filename, buf, buflen); + if (n < 0) + goto bail; + + n = lws_jwk_import(s, cb, user, buf, n); + lws_free(buf); + + return n; +bail: + lws_free(buf); + + return -1; +} + +LWS_VISIBLE int +lws_jwk_save(struct lws_jwk *s, const char *filename) +{ + int buflen = 4096; + char *buf = lws_malloc(buflen, "jwk-save"); + int n, m; + + if (!buf) + return -1; + + n = lws_jwk_export(s, 1, buf, buflen); + if (n < 0) + goto bail; + + m = lws_plat_write_file(filename, buf, n); + + lws_free(buf); + if (m) + return -1; + + return 0; + +bail: + lws_free(buf); + + return -1; +} diff --git a/lib/jose/jws/jose.c b/lib/jose/jws/jose.c new file mode 100644 index 000000000..6abb96553 --- /dev/null +++ b/lib/jose/jws/jose.c @@ -0,0 +1,254 @@ +/* + * libwebsockets - JSON Web Signature support + * + * Copyright (C) 2017 - 2018 Andy Green + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + * + * JOSE is actually specified as part of JWS RFC7515. JWE references RFC7515 + * to specify its JOSE JSON object. So it lives in ./lib/jose/jws/jose.c. + */ + +#include "core/private.h" + +#include + +static const char * const jws_jose[] = { + "alg", /* REQUIRED */ + "jku", + "jwk", + "kid", + "x5u", + "x5c", + "x5t", + "x5t#S256", + "typ", + "cty", + "crit", + + /* valid for JWE only below here */ + + "enc", + "zip", /* ("DEF" = deflate) */ + + "epk", /* valid for JWE ECDH only */ + "apu", /* valid for JWE ECDH only */ + "apv", /* valid for JWE ECDH only */ + "iv", /* valid for JWE AES only */ + "tag", /* valid for JWE AES only */ + "p2s", /* valid for JWE PBES2 only */ + "p2c" /* valid for JWE PBES2 only */ +}; + +static signed char +lws_jws_jose_cb(struct lejp_ctx *ctx, char reason) +{ + struct cb_hdr_s *s = (struct cb_hdr_s *)ctx->user; + + if (!(reason & LEJP_FLAG_CB_IS_VALUE) || !ctx->path_match) + return 0; + + switch (ctx->path_match - 1) { + + /* strings */ + + case LJJHI_ALG: /* REQUIRED */ + + lws_strncpy(s->alg, ctx->buf, sizeof(s->alg)); + + if (s->is_jwe) { + + lwsl_err("%s: JWE alg\n", __func__); + + /* interpret as for JWE... just store the string */ + + return 0; + } + + /* interpret as for JWS */ + + if (!strcmp(ctx->buf, "HS256")) { + s->hmac_type = LWS_GENHMAC_TYPE_SHA256; + s->algtype = LWS_JWK_ENCTYPE_NONE; + break; + } + if (!strcmp(ctx->buf, "HS384")) { + s->hmac_type = LWS_GENHMAC_TYPE_SHA384; + s->algtype = LWS_JWK_ENCTYPE_NONE; + break; + } + if (!strcmp(ctx->buf, "HS512")) { + s->hmac_type = LWS_GENHMAC_TYPE_SHA512; + s->algtype = LWS_JWK_ENCTYPE_NONE; + break; + } + + if (!strcmp(ctx->buf, "RS256")) { + s->hash_type = LWS_GENHASH_TYPE_SHA256; + s->algtype = LWS_JWK_ENCTYPE_RSASSA; + break; + } + if (!strcmp(ctx->buf, "RS384")) { + s->hash_type = LWS_GENHASH_TYPE_SHA384; + s->algtype = LWS_JWK_ENCTYPE_RSASSA; + break; + } + if (!strcmp(ctx->buf, "RS512")) { + s->hash_type = LWS_GENHASH_TYPE_SHA512; + s->algtype = LWS_JWK_ENCTYPE_RSASSA; + break; + } + + if (!strcmp(ctx->buf, "ES256")) { + s->hash_type = LWS_GENHASH_TYPE_SHA256; + s->algtype = LWS_JWK_ENCTYPE_EC; + strncpy(s->curve, "P-256", sizeof(s->curve)); + break; + } + if (!strcmp(ctx->buf, "ES384")) { + s->hash_type = LWS_GENHASH_TYPE_SHA384; + s->algtype = LWS_JWK_ENCTYPE_EC; + strncpy(s->curve, "P-384", sizeof(s->curve)); + break; + } + if (!strcmp(ctx->buf, "ES512")) { + s->hash_type = LWS_GENHASH_TYPE_SHA512; + s->algtype = LWS_JWK_ENCTYPE_EC; + strncpy(s->curve, "P-521", sizeof(s->curve)); + break; + } + + return -1; + + case LJJHI_TYP: /* Optional: string: media type */ + if (strcmp(ctx->buf, "JWT")) + return -1; + break; + + case LJJHI_JKU: /* Optional: string */ + case LJJHI_KID: /* Optional: string */ + case LJJHI_X5U: /* Optional: string: url of public key cert / chain */ + case LJJHI_CTY: /* Optional: string: content media type */ + + /* base64 */ + + case LJJHI_X5C: /* Optional: base64 (NOT -url): actual cert */ + + /* base64-url */ + + case LJJHI_X5T: /* Optional: base64url: SHA-1 of actual cert */ + case LJJHI_X5T_S256: /* Optional: base64url: SHA-256 of actual cert */ + + /* array of strings */ + + case LJJHI_CRIT: /* Optional for send, REQUIRED: array of strings: + * mustn't contain standardized strings or null set */ + break; + + /* jwk child */ + + case LJJHI_JWK: /* Optional: jwk JSON object: public key: */ + + /* past here, JWE only */ + + case LJJHI_ENC: /* JWE only: Optional: string */ + if (!s->is_jwe) + return -1; + break; + + case LJJHI_ZIP: /* JWE only: Optional: string ("DEF" = deflate) */ + if (!s->is_jwe) + return -1; + break; + + case LJJHI_EPK: /* Additional arg for JWE ECDH */ + if (!s->is_jwe) + return -1; + break; + + case LJJHI_APU: /* Additional arg for JWE ECDH */ + if (!s->is_jwe) + return -1; + break; + + case LJJHI_APV: /* Additional arg for JWE ECDH */ + if (!s->is_jwe) + return -1; + break; + + case LJJHI_IV: /* Additional arg for JWE AES */ + if (!s->is_jwe) + return -1; + break; + + case LJJHI_TAG: /* Additional arg for JWE AES */ + if (!s->is_jwe) + return -1; + break; + + case LJJHI_P2S: /* Additional arg for JWE PBES2 */ + if (!s->is_jwe) + return -1; + break; + case LJJHI_P2C: /* Additional arg for JWE PBES2 */ + if (!s->is_jwe) + return -1; + break; + + /* ignore what we don't understand */ + + default: + return 0; + } + + return 0; +} + +static int +lws_jose_parse(struct cb_hdr_s *args, uint8_t *buf, int n, int is_jwe) +{ + struct lejp_ctx jctx; + int m; + + args->alg[0] = '\0'; + args->curve[0] = '\0'; + args->algtype = -1; + args->is_jwe = is_jwe; + + lejp_construct(&jctx, lws_jws_jose_cb, args, jws_jose, + LWS_ARRAY_SIZE(jws_jose)); + + m = (int)(signed char)lejp_parse(&jctx, (uint8_t *)buf, n); + lejp_destruct(&jctx); + if (m < 0) { + lwsl_notice("parse got %d: alg %s\n", m, args->alg); + return -1; + } + + return 0; +} + +int +lws_jws_parse_jose(struct cb_hdr_s *args, uint8_t *buf, int n) +{ + return lws_jose_parse(args, buf, n, 0); +} + +int +lws_jwe_parse_jose(struct cb_hdr_s *args, uint8_t *buf, int n) +{ + return lws_jose_parse(args, buf, n, 1); +} diff --git a/lib/jose/jws/jws.c b/lib/jose/jws/jws.c new file mode 100644 index 000000000..206551201 --- /dev/null +++ b/lib/jose/jws/jws.c @@ -0,0 +1,367 @@ +/* + * libwebsockets - JSON Web Signature support + * + * Copyright (C) 2017 - 2018 Andy Green + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + +#include "core/private.h" +#include "private.h" + +LWS_VISIBLE int +lws_jws_base64_enc(const char *in, size_t in_len, char *out, size_t out_max) +{ + int n; + + n = lws_b64_encode_string_url(in, in_len, out, out_max - 1); + if (n < 0) + return n; /* too large for output buffer */ + + /* trim the terminal = */ + while (n && out[n - 1] == '=') + n--; + + out[n] = '\0'; + + return n; +} + +LWS_VISIBLE int +lws_jws_encode_section(const char *in, size_t in_len, int first, char **p, + char *end) +{ + int n, len = (end - *p) - 1; + char *p_entry = *p; + + if (len < 3) + return -1; + + if (!first) + *(*p)++ = '.'; + + n = lws_jws_base64_enc(in, in_len, *p, len - 1); + if (n < 0) + return -1; + + *p += n; + + return (*p) - p_entry; +} + +static int +lws_jws_find_sig(const char *in, size_t len) +{ + const char *p = in + len - 1; + + while (len--) + if (*p == '.') + return (p + 1) - in; + else + p--; + + lwsl_notice("%s failed\n", __func__); + return -1; +} + +LWS_VISIBLE int +lws_jws_confirm_sig(const char *in, size_t len, struct lws_jwk *jwk, + struct lws_context *context) +{ + int sig_pos = lws_jws_find_sig(in, len), pos = 0, n, m, h_len; + uint8_t digest[LWS_GENHASH_LARGEST]; + struct lws_genhash_ctx hash_ctx; + struct lws_genrsa_ctx rsactx; + struct lws_genhmac_ctx ctx; + struct cb_hdr_s args; + char buf[2048]; + + /* 1) there has to be a signature */ + + if (sig_pos < 0) + return -1; + + /* 2) find length of first, hdr, block */ + + while (pos < (int)len && in[pos] != '.') + pos++; + if (pos == (int)len) + return -1; + + /* 3) Decode the header block */ + + n = lws_b64_decode_string_len(in, pos, buf, sizeof(buf) - 1); + if (n < 0) + return -1; + + /* 4) Require either: + * typ: JWT (if present) and alg: HS256/384/512 + * typ: JWT (if present) and alg: RS256/384/512 + * typ: JWT (if present) and alg: ES256/384/512 + */ + + m = lws_jws_parse_jose(&args, (unsigned char *)buf, n); + if (m < 0) { + lwsl_notice("parse got %d: alg %s\n", m, args.alg); + return -1; + } + + /* 5) decode the B64URL signature part into buf / m */ + + m = lws_b64_decode_string_len(in + sig_pos, len - sig_pos, + buf, sizeof(buf) - 1); + + switch (args.algtype) { + case LWS_JWK_ENCTYPE_RSASSA: + + /* RSASSA-PKCS1-v1_5 using SHA-256/384/512 */ + + /* 6(RSA): compute the hash of the payload into "digest" */ + + if (lws_genhash_init(&hash_ctx, args.hash_type)) + return -1; + + if (lws_genhash_update(&hash_ctx, (uint8_t *)in, sig_pos - 1)) { + lws_genhash_destroy(&hash_ctx, NULL); + + return -1; + } + if (lws_genhash_destroy(&hash_ctx, digest)) + return -1; + + h_len = lws_genhash_size(args.hash_type); + + if (lws_genrsa_create(&rsactx, jwk->e, context)) { + lwsl_notice("%s: lws_genrsa_public_decrypt_create\n", + __func__); + return -1; + } + + n = lws_genrsa_public_verify(&rsactx, digest, args.hash_type, + (uint8_t *)buf, m); + + lws_genrsa_destroy(&rsactx); + if (n < 0) { + lwsl_notice("decrypt fail\n"); + return -1; + } + + break; + + case LWS_JWK_ENCTYPE_NONE: + + /* SHA256/384/512 HMAC */ + + h_len = lws_genhmac_size(args.hmac_type); + if (m < 0 || m != h_len) + return -1; + + /* 6) compute HMAC over payload */ + + if (lws_genhmac_init(&ctx, args.hmac_type, + jwk->e[JWK_RSA_KEYEL_E].buf, + jwk->e[JWK_RSA_KEYEL_E].len)) + return -1; + + if (lws_genhmac_update(&ctx, (uint8_t *)in, sig_pos - 1)) { + lws_genhmac_destroy(&ctx, NULL); + + return -1; + } + if (lws_genhmac_destroy(&ctx, digest)) + return -1; + + /* 7) Compare the computed and decoded hashes */ + + if (memcmp(digest, buf, h_len)) { + lwsl_notice("digest mismatch\n"); + + return -1; + } + + break; + + case LWS_JWK_ENCTYPE_EC: + + lwsl_err("%s: EC not supported yet\n", __func__); + return -1; + + default: + lwsl_err("%s: unknown alg from jose\n", __func__); + return -1; + } + + return 0; +} + +LWS_VISIBLE int +lws_jws_sign_from_b64(const char *b64_hdr, size_t hdr_len, const char *b64_pay, + size_t pay_len, char *b64_sig, size_t sig_len, + enum lws_genhash_types hash_type, struct lws_jwk *jwk, + struct lws_context *context) +{ + uint8_t digest[LWS_GENHASH_LARGEST]; + struct lws_genhash_ctx hash_ctx; + struct lws_genrsa_ctx rsactx; + uint8_t *buf; + int n; + + if (lws_genhash_init(&hash_ctx, hash_type)) + return -1; + + if (b64_hdr) { + if (lws_genhash_update(&hash_ctx, (uint8_t *)b64_hdr, hdr_len)) + goto hash_fail; + if (lws_genhash_update(&hash_ctx, (uint8_t *)".", 1)) + goto hash_fail; + } + if (lws_genhash_update(&hash_ctx, (uint8_t *)b64_pay, pay_len)) + goto hash_fail; + + if (lws_genhash_destroy(&hash_ctx, digest)) + return -1; + + if (jwk->kty == LWS_JWK_KYT_RSA) { + if (lws_genrsa_create(&rsactx, jwk->e, context)) { + lwsl_notice("%s: lws_genrsa_public_decrypt_create\n", + __func__); + return -1; + } + + n = jwk->e[JWK_RSA_KEYEL_N].len; + buf = lws_malloc(n, "jws sign"); + if (!buf) + return -1; + + n = lws_genrsa_public_sign(&rsactx, digest, hash_type, buf, n); + lws_genrsa_destroy(&rsactx); + if (n < 0) { + lws_free(buf); + + return -1; + } + + n = lws_jws_base64_enc((char *)buf, n, b64_sig, sig_len); + lws_free(buf); + + return n; + } + + if (jwk->kty == LWS_JWK_KYT_OCT) + return lws_jws_base64_enc((char *)digest, + lws_genhash_size(hash_type), + b64_sig, sig_len); + + /* unknown key type */ + + return -1; + +hash_fail: + lws_genhash_destroy(&hash_ctx, NULL); + return -1; +} + +LWS_VISIBLE int +lws_jws_create_packet(struct lws_jwk *jwk, 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, *b64_hdr, *b64_pay; + int n, b64_hdr_len, b64_pay_len; + + /* + * This buffer is local to the function, the actual output + * is prepared into vhd->buf. 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 + */ + + p += lws_snprintf(p, end - p, "{\"alg\":\"RS256\",\"jwk\":"); + n = lws_jwk_export(jwk, 0, p, end - p); + 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\":\""); + b64_hdr = 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; + } + b64_hdr_len = n; + p1 += n; + + p1 += lws_snprintf(p1, end1 - p1, "\",\"payload\":\""); + b64_pay = 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; + } + b64_pay_len = 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(b64_hdr, b64_hdr_len, b64_pay, b64_pay_len, + p1, end1 - p1, LWS_GENHASH_TYPE_SHA256, jwk, + context); + if (n < 0) { + lwsl_notice("sig gen failed\n"); + + goto bail; + } + p1 += n; + p1 += lws_snprintf(p1, end1 - p1, "\"}"); + + free(buf); + + return p1 - out; + +bail: + free(buf); + + return -1; +} diff --git a/lib/jose/jws/private.h b/lib/jose/jws/private.h new file mode 100644 index 000000000..b5d39f219 --- /dev/null +++ b/lib/jose/jws/private.h @@ -0,0 +1,29 @@ +/* + * libwebsockets - JSON Web Signature support + * + * Copyright (C) 2018 Andy Green + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + * + * JOSE is actually specified as part of JWS RFC7515. JWE references RFC7515 + * to specify its JOSE JSON object. So it lives in ./lib/jose/jws/jose.c. + */ + +int +lws_jws_parse_jose(struct cb_hdr_s *args, uint8_t *buf, int n); + +int +lws_jwe_parse_jose(struct cb_hdr_s *args, uint8_t *buf, int n); diff --git a/lib/jose/private.h b/lib/jose/private.h new file mode 100644 index 000000000..d39f71299 --- /dev/null +++ b/lib/jose/private.h @@ -0,0 +1,23 @@ +/* + * libwebsockets - jose private header + * + * Copyright (C) 2018 Andy Green + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + +void +lws_jwk_destroy_elements(struct lws_jwk_elements *el, int m); diff --git a/lib/misc/jws/jwk.c b/lib/misc/jws/jwk.c deleted file mode 100644 index fca8346eb..000000000 --- a/lib/misc/jws/jwk.c +++ /dev/null @@ -1,325 +0,0 @@ -/* - * libwebsockets - JSON Web Key support - * - * Copyright (C) 2017 Andy Green - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation: - * version 2.1 of the License. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, - * MA 02110-1301 USA - */ - -#include "core/private.h" - -#include -#include - -static const char * const jwk_tok[] = { - "e", "n", "d", "p", "q", "dp", "dq", "qi", "kty", "k", -}; - -static int -_lws_jwk_set_element(struct lws_genrsa_element *e, char *in, int len) -{ - int dec_size = ((len * 3) / 4) + 4, n; - - e->buf = lws_malloc(dec_size, "jwk"); - if (!e->buf) - return -1; - - n = lws_b64_decode_string_len(in, len, (char *)e->buf, dec_size - 1); - if (n < 0) - return -1; - e->len = n; - - return 0; -} - -struct cb_lws_jwk { - struct lws_jwk *s; - char *b64; - int b64max; - int pos; -}; - -static signed char -cb_jwk(struct lejp_ctx *ctx, char reason) -{ - struct cb_lws_jwk *cbs = (struct cb_lws_jwk *)ctx->user; - struct lws_jwk *s = cbs->s; - int idx; - - if (reason == LEJPCB_VAL_STR_START) - cbs->pos = 0; - - if (!(reason & LEJP_FLAG_CB_IS_VALUE) || !ctx->path_match) - return 0; - - switch (ctx->path_match - 1) { - case JWK_KTY: - lws_strncpy(s->keytype, ctx->buf, sizeof(s->keytype)); - if (!strcmp(ctx->buf, "oct")) { - break; - } - if (!strcmp(ctx->buf, "RSA")) { - break; - } - return -1; - - case JWK_KEY: -// if (strcmp(s->keytype, "oct")) -// return -1; - idx = JWK_KEY_E; - goto read_element1; - - case JWK_KEY_N: - case JWK_KEY_E: - case JWK_KEY_D: - case JWK_KEY_P: - case JWK_KEY_Q: - case JWK_KEY_DP: - case JWK_KEY_DQ: - case JWK_KEY_QI: - idx = ctx->path_match - 1; - goto read_element; - } - - return 0; - -read_element: -/* kty is no longer first in lex order */ -// if (strcmp(s->keytype, "RSA")) -// return -1; - -read_element1: - - if (cbs->pos + ctx->npos >= cbs->b64max) - return -1; - - memcpy(cbs->b64 + cbs->pos, ctx->buf, ctx->npos); - cbs->pos += ctx->npos; - - if (reason == LEJPCB_VAL_STR_CHUNK) - return 0; - - if (_lws_jwk_set_element(&s->el.e[idx], cbs->b64, cbs->pos) < 0) { - lws_jwk_destroy_genrsa_elements(&s->el); - - return -1; - } - - return 0; -} - -LWS_VISIBLE int -lws_jwk_import(struct lws_jwk *s, const char *in, size_t len) -{ - struct lejp_ctx jctx; - struct cb_lws_jwk cbs; - const int b64max = (((8192 / 8) * 4) / 3) + 1; /* enough for 8K key */ - char b64[b64max]; - int m; - - memset(s, 0, sizeof(*s)); - cbs.s = s; - cbs.b64 = b64; - cbs.b64max = b64max; - cbs.pos = 0; - lejp_construct(&jctx, cb_jwk, &cbs, jwk_tok, LWS_ARRAY_SIZE(jwk_tok)); - m = (int)(signed char)lejp_parse(&jctx, (uint8_t *)in, len); - lejp_destruct(&jctx); - - if (m < 0) { - lwsl_notice("%s: parse got %d\n", __func__, m); - - return -1; - } - - return 0; -} - -LWS_VISIBLE void -lws_jwk_destroy(struct lws_jwk *s) -{ - lws_jwk_destroy_genrsa_elements(&s->el); -} - -LWS_VISIBLE int -lws_jwk_export(struct lws_jwk *s, int private, char *p, size_t len) -{ - char *start = p, *end = &p[len - 1]; - int n, limit = LWS_COUNT_RSA_ELEMENTS; - - /* RFC7638 lexicographic order requires - * RSA: e -> kty -> n - * oct: k -> kty - */ - - p += lws_snprintf(p, end - p, "{"); - - if (!strcmp(s->keytype, "oct")) { - if (!s->el.e[JWK_KEY_E].buf) - return -1; - - p += lws_snprintf(p, end - p, "\"k\":\""); - n = lws_jws_base64_enc((const char *)s->el.e[JWK_KEY_E].buf, - s->el.e[JWK_KEY_E].len, p, - end - p - 4); - if (n < 0) { - lwsl_notice("%s: enc failed\n", __func__); - return -1; - } - p += n; - - p += lws_snprintf(p, end - p, "\",\"kty\":\"%s\"}", s->keytype); - - return p - start; - } - - if (!strcmp(s->keytype, "RSA")) { - if (!s->el.e[JWK_KEY_E].buf || - !s->el.e[JWK_KEY_N].buf || - (private && (!s->el.e[JWK_KEY_D].buf || - !s->el.e[JWK_KEY_P].buf || - !s->el.e[JWK_KEY_Q].buf)) - ) { - lwsl_notice("%s: not enough elements filled\n", - __func__); - return -1; - } - - if (!private) - limit = JWK_KEY_N + 1; - - for (n = 0; n < limit; n++) { - int m; - - if (!s->el.e[n].buf) - continue; - lwsl_info("%d: len %d\n", n, s->el.e[n].len); - - if (n) - p += lws_snprintf(p, end - p, ","); - p += lws_snprintf(p, end - p, "\"%s\":\"", jwk_tok[n]); - m = lws_jws_base64_enc((const char *)s->el.e[n].buf, - s->el.e[n].len, p, - end - p - 4); - if (m < 0) { - lwsl_notice("%s: enc fail inlen %d outlen %d\n", - __func__, (int)s->el.e[n].len, - lws_ptr_diff(end, p) - 4); - return -1; - } - p += m; - *p++ = '\"'; - - if (!n) /* RFC7638 lexicographic order */ - p += lws_snprintf(p, end - p, ",\"kty\":\"%s\"", - s->keytype); - } - - p += lws_snprintf(p, end - p, "}"); - - return p - start; - } - - lwsl_err("%s: unknown key type %s\n", __func__, s->keytype); - - return -1; -} - -LWS_VISIBLE int -lws_jwk_rfc7638_fingerprint(struct lws_jwk *s, char *digest32) -{ - struct lws_genhash_ctx hash_ctx; - int tmpsize = 2536, n; - char *tmp; - - tmp = lws_malloc(tmpsize, "rfc7638 tmp"); - - n = lws_jwk_export(s, 0, tmp, tmpsize); - if (n < 0) - goto bail; - - if (lws_genhash_init(&hash_ctx, LWS_GENHASH_TYPE_SHA256)) - goto bail; - - if (lws_genhash_update(&hash_ctx, tmp, n)) { - lws_genhash_destroy(&hash_ctx, NULL); - - goto bail; - } - lws_free(tmp); - - if (lws_genhash_destroy(&hash_ctx, digest32)) - return -1; - - return 0; - -bail: - lws_free(tmp); - - return -1; -} - -LWS_VISIBLE int -lws_jwk_load(struct lws_jwk *s, const char *filename) -{ - int buflen = 4096; - char *buf = lws_malloc(buflen, "jwk-load"); - int n; - - if (!buf) - return -1; - - n = lws_plat_read_file(filename, buf, buflen); - if (n < 0) - goto bail; - - n = lws_jwk_import(s, buf, n); - lws_free(buf); - - return n; -bail: - lws_free(buf); - - return -1; -} - -LWS_VISIBLE int -lws_jwk_save(struct lws_jwk *s, const char *filename) -{ - int buflen = 4096; - char *buf = lws_malloc(buflen, "jwk-save"); - int n, m; - - if (!buf) - return -1; - - n = lws_jwk_export(s, 1, buf, buflen); - if (n < 0) - goto bail; - - m = lws_plat_write_file(filename, buf, n); - - lws_free(buf); - if (m) - return -1; - - return 0; - -bail: - lws_free(buf); - - return -1; -} diff --git a/lib/misc/jws/jws.c b/lib/misc/jws/jws.c deleted file mode 100644 index ae68dc860..000000000 --- a/lib/misc/jws/jws.c +++ /dev/null @@ -1,642 +0,0 @@ -/* - * libwebsockets - JSON Web Signature support - * - * Copyright (C) 2017 Andy Green - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation: - * version 2.1 of the License. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, - * MA 02110-1301 USA - */ - -#include "core/private.h" - -/* - * JSON Web Signature is defined in RFC7515 - * - * https://tools.ietf.org/html/rfc7515 - * - * It's basically a way to wrap some JSON with a JSON "header" describing the - * crypto, and a signature, all in a BASE64 wrapper with elided terminating '='. - * - * The signature stays with the content, it serves a different purpose than eg - * a TLS tunnel to transfer it. - * - * RFC7518 (JSON Web Algorithms) says for the "alg" names - * - * | HS256 | HMAC using SHA-256 | Required | - * | HS384 | HMAC using SHA-384 | Optional | - * | HS512 | HMAC using SHA-512 | Optional | - * | RS256 | RSASSA-PKCS1-v1_5 using | Recommended | - * | RS384 | RSASSA-PKCS1-v1_5 using | Optional | - * | | SHA-384 | | - * | RS512 | RSASSA-PKCS1-v1_5 using | Optional | - * | | SHA-512 | | - * | ES256 | ECDSA using P-256 and SHA-256 | Recommended+ | - * | ES384 | ECDSA using P-384 and SHA-384 | Optional | - * | ES512 | ECDSA using P-521 and SHA-512 | Optional | - * - * Boulder (FOSS ACME provider) supports RS256, ES256, ES384 and ES512 - * currently. The "Recommended+" just means it is recommended but will likely - * be "very recommended" soon. - * - * We support HS256/384/512 for symmetric crypto, but the choice for the - * asymmetric crypto isn't as easy to make. - * - * Normally you'd choose the EC option but these are defined to use the - * "NIST curves" (RFC7518 3.4) which are believed to be insecure. - * - * https://safecurves.cr.yp.to/ - * - * For that reason we implement RS256/384/512 for asymmetric. - */ - -#if defined(LWS_WITH_SELFTESTS) -static const char - *test1 = "{\"typ\":\"JWT\",\r\n \"alg\":\"HS256\"}", - *test1_enc = "eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9", - *test2 = "{\"iss\":\"joe\",\r\n \"exp\":1300819380,\r\n" - " \"http://example.com/is_root\":true}", - *test2_enc = "eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQ" - "ogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ", - *key_jwk = "{\"kty\":\"oct\",\r\n" - " \"k\":\"AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQ" - "Lr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow\"}", - *hash_enc = "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk", - /* the key from worked example in RFC7515 A-1, as a JWK */ - *rfc7515_rsa_key = - "{\"kty\":\"RSA\"," - " \"n\":\"ofgWCuLjybRlzo0tZWJjNiuSfb4p4fAkd_wWJcyQoTbji9k0l8W26mPddx" - "HmfHQp-Vaw-4qPCJrcS2mJPMEzP1Pt0Bm4d4QlL-yRT-SFd2lZS-pCgNMs" - "D1W_YpRPEwOWvG6b32690r2jZ47soMZo9wGzjb_7OMg0LOL-bSf63kpaSH" - "SXndS5z5rexMdbBYUsLA9e-KXBdQOS-UTo7WTBEMa2R2CapHg665xsmtdV" - "MTBQY4uDZlxvb3qCo5ZwKh9kG4LT6_I5IhlJH7aGhyxXFvUK-DWNmoudF8" - "NAco9_h9iaGNj8q2ethFkMLs91kzk2PAcDTW9gb54h4FRWyuXpoQ\"," - "\"e\":\"AQAB\"," - "\"d\":\"Eq5xpGnNCivDflJsRQBXHx1hdR1k6Ulwe2JZD50LpXyWPEAeP88vLNO97I" - "jlA7_GQ5sLKMgvfTeXZx9SE-7YwVol2NXOoAJe46sui395IW_GO-pWJ1O0" - "BkTGoVEn2bKVRUCgu-GjBVaYLU6f3l9kJfFNS3E0QbVdxzubSu3Mkqzjkn" - "439X0M_V51gfpRLI9JYanrC4D4qAdGcopV_0ZHHzQlBjudU2QvXt4ehNYT" - "CBr6XCLQUShb1juUO1ZdiYoFaFQT5Tw8bGUl_x_jTj3ccPDVZFD9pIuhLh" - "BOneufuBiB4cS98l2SR_RQyGWSeWjnczT0QU91p1DhOVRuOopznQ\"," - "\"p\":\"4BzEEOtIpmVdVEZNCqS7baC4crd0pqnRH_5IB3jw3bcxGn6QLvnEtfdUdi" - "YrqBdss1l58BQ3KhooKeQTa9AB0Hw_Py5PJdTJNPY8cQn7ouZ2KKDcmnPG" - "BY5t7yLc1QlQ5xHdwW1VhvKn-nXqhJTBgIPgtldC-KDV5z-y2XDwGUc\"," - "\"q\":\"uQPEfgmVtjL0Uyyx88GZFF1fOunH3-7cepKmtH4pxhtCoHqpWmT8YAmZxa" - "ewHgHAjLYsp1ZSe7zFYHj7C6ul7TjeLQeZD_YwD66t62wDmpe_HlB-TnBA" - "-njbglfIsRLtXlnDzQkv5dTltRJ11BKBBypeeF6689rjcJIDEz9RWdc\"," - "\"dp\":\"BwKfV3Akq5_MFZDFZCnW-wzl-CCo83WoZvnLQwCTeDv8uzluRSnm71I3Q" - "CLdhrqE2e9YkxvuxdBfpT_PI7Yz-FOKnu1R6HsJeDCjn12Sk3vmAktV2zb" - "34MCdy7cpdTh_YVr7tss2u6vneTwrA86rZtu5Mbr1C1XsmvkxHQAdYo0\"," - "\"dq\":\"h_96-mK1R_7glhsum81dZxjTnYynPbZpHziZjeeHcXYsXaaMwkOlODsWa" - "7I9xXDoRwbKgB719rrmI2oKr6N3Do9U0ajaHF-NKJnwgjMd2w9cjz3_-ky" - "NlxAr2v4IKhGNpmM5iIgOS1VZnOZ68m6_pbLBSp3nssTdlqvd0tIiTHU\"," - "\"qi\":\"IYd7DHOhrWvxkwPQsRM2tOgrjbcrfvtQJipd-DlcxyVuuM9sQLdgjVk2o" - "y26F0EmpScGLq2MowX7fhd_QJQ3ydy5cY7YIBi87w93IKLEdfnbJtoOPLU" - "W0ITrJReOgo1cq9SbsxYawBgfp_gh6A5603k2-ZQwVK0JKSHuLFkuQ3U\"" - "}", - *rfc7515_rsa_a1 = /* the signed worked example in RFC7515 A-1 */ - "eyJhbGciOiJSUzI1NiJ9" - ".eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFt" - "cGxlLmNvbS9pc19yb290Ijp0cnVlfQ" - ".cC4hiUPoj9Eetdgtv3hF80EGrhuB__dzERat0XF9g2VtQgr9PJbu3XOiZj5RZmh7" - "AAuHIm4Bh-0Qc_lF5YKt_O8W2Fp5jujGbds9uJdbF9CUAr7t1dnZcAcQjbKBYNX4" - "BAynRFdiuB--f_nZLgrnbyTyWzO75vRK5h6xBArLIARNPvkSjtQBMHlb1L07Qe7K" - "0GarZRmB_eSN9383LcOLn6_dO--xi12jzDwusC-eOkHWEsqtFZESc6BfI7noOPqv" - "hJ1phCnvWh6IeYI2w9QOYEUipUTI8np6LbgGY9Fs98rqVt5AXLIhWkWywlVmtVrB" - "p0igcN_IoypGlUPQGe77Rw"; -#endif - -LWS_VISIBLE int -lws_jws_base64_enc(const char *in, size_t in_len, char *out, size_t out_max) -{ - int n; - - n = lws_b64_encode_string_url(in, in_len, out, out_max - 1); - if (n < 0) - return n; /* too large for output buffer */ - - /* trim the terminal = */ - while (n && out[n - 1] == '=') - n--; - - out[n] = '\0'; - - return n; -} - -LWS_VISIBLE int -lws_jws_encode_section(const char *in, size_t in_len, int first, char **p, - char *end) -{ - int n, len = (end - *p) - 1; - char *p_entry = *p; - - if (len < 3) - return -1; - - if (!first) - *(*p)++ = '.'; - - n = lws_jws_base64_enc(in, in_len, *p, len - 1); - if (n < 0) - return -1; - - *p += n; - - return (*p) - p_entry; -} - -static int -lws_jws_find_sig(const char *in, size_t len) -{ - const char *p = in + len - 1; - - while (len--) - if (*p == '.') - return (p + 1) - in; - else - p--; - - lwsl_notice("%s failed\n", __func__); - return -1; -} - - -static const char * const jhdr_tok[] = { - "typ", - "alg", -}; -enum enum_jhdr_tok { - JHP_TYP, - JHP_ALG -}; -struct cb_hdr_s { - enum lws_genhash_types hash_type; - enum lws_genhmac_types hmac_type; - char alg[10]; - int is_rsa:1; -}; - -static signed char -cb_hdr(struct lejp_ctx *ctx, char reason) -{ - struct cb_hdr_s *s = (struct cb_hdr_s *)ctx->user; - - if (!(reason & LEJP_FLAG_CB_IS_VALUE) || !ctx->path_match) - return 0; - - switch (ctx->path_match - 1) { - case JHP_TYP: /* it is optional */ - if (strcmp(ctx->buf, "JWT")) - return -1; - break; - case JHP_ALG: - lws_strncpy(s->alg, ctx->buf, sizeof(s->alg)); - if (!strcmp(ctx->buf, "HS256")) { - s->hmac_type = LWS_GENHMAC_TYPE_SHA256; - break; - } - if (!strcmp(ctx->buf, "HS384")) { - s->hmac_type = LWS_GENHMAC_TYPE_SHA384; - break; - } - if (!strcmp(ctx->buf, "HS512")) { - s->hmac_type = LWS_GENHMAC_TYPE_SHA512; - break; - } - if (!strcmp(ctx->buf, "RS256")) { - s->hash_type = LWS_GENHASH_TYPE_SHA256; - s->is_rsa = 1; - break; - } - if (!strcmp(ctx->buf, "RS384")) { - s->hash_type = LWS_GENHASH_TYPE_SHA384; - s->is_rsa = 1; - break; - } - if (!strcmp(ctx->buf, "RS512")) { - s->hash_type = LWS_GENHASH_TYPE_SHA512; - s->is_rsa = 1; - break; - } - return -1; - } - - return 0; -} - -LWS_VISIBLE int -lws_jws_confirm_sig(const char *in, size_t len, struct lws_jwk *jwk) -{ - int sig_pos = lws_jws_find_sig(in, len), pos = 0, n, m, h_len; - uint8_t digest[LWS_GENHASH_LARGEST]; - struct lws_genhash_ctx hash_ctx; - struct lws_genrsa_ctx rsactx; - struct lws_genhmac_ctx ctx; - struct cb_hdr_s args; - struct lejp_ctx jctx; - char buf[2048]; - - /* 1) there has to be a signature */ - - if (sig_pos < 0) - return -1; - - /* 2) find length of first, hdr, block */ - - while (pos < (int)len && in[pos] != '.') - pos++; - if (pos == (int)len) - return -1; - - /* 3) Decode the header block */ - - n = lws_b64_decode_string_len(in, pos, buf, sizeof(buf) - 1); - if (n < 0) - return -1; - - /* 4) Require either: - * typ: JWT (if present) and alg: HS256/384/512 - * typ: JWT (if present) and alg: RS256/384/512 - */ - - args.alg[0] = '\0'; - args.is_rsa = 0; - lejp_construct(&jctx, cb_hdr, &args, jhdr_tok, LWS_ARRAY_SIZE(jhdr_tok)); - m = (int)(signed char)lejp_parse(&jctx, (uint8_t *)buf, n); - lejp_destruct(&jctx); - if (m < 0) { - lwsl_notice("parse got %d: alg %s\n", m, args.alg); - return -1; - } - - /* 5) decode the B64URL signature part into buf / m */ - - m = lws_b64_decode_string_len(in + sig_pos, len - sig_pos, - buf, sizeof(buf) - 1); - - if (args.is_rsa) { - - /* RSASSA-PKCS1-v1_5 using SHA-256/384/512 */ - - /* 6(RSA): compute the hash of the payload into "digest" */ - - if (lws_genhash_init(&hash_ctx, args.hash_type)) - return -1; - - if (lws_genhash_update(&hash_ctx, (uint8_t *)in, sig_pos - 1)) { - lws_genhash_destroy(&hash_ctx, NULL); - - return -1; - } - if (lws_genhash_destroy(&hash_ctx, digest)) - return -1; - - h_len = lws_genhash_size(args.hash_type); - - if (lws_genrsa_create(&rsactx, &jwk->el)) { - lwsl_notice("%s: lws_genrsa_public_decrypt_create\n", - __func__); - return -1; - } - - n = lws_genrsa_public_verify(&rsactx, digest, args.hash_type, - (uint8_t *)buf, m); - - lws_genrsa_destroy(&rsactx); - if (n < 0) { - lwsl_notice("decrypt fail\n"); - return -1; - } - - return 0; - } - - /* SHA256/384/512 HMAC */ - - h_len = lws_genhmac_size(args.hmac_type); - if (m < 0 || m != h_len) - return -1; - - /* 6) compute HMAC over payload */ - - if (lws_genhmac_init(&ctx, args.hmac_type, jwk->el.e[JWK_KEY_E].buf, - jwk->el.e[JWK_KEY_E].len)) - return -1; - - if (lws_genhmac_update(&ctx, (uint8_t *)in, sig_pos - 1)) { - lws_genhmac_destroy(&ctx, NULL); - - return -1; - } - if (lws_genhmac_destroy(&ctx, digest)) - return -1; - - /* 7) Compare the computed and decoded hashes */ - - if (memcmp(digest, buf, h_len)) { - lwsl_notice("digest mismatch\n"); - - return -1; - } - - return 0; -} - -LWS_VISIBLE int -lws_jws_sign_from_b64(const char *b64_hdr, size_t hdr_len, const char *b64_pay, - size_t pay_len, char *b64_sig, size_t sig_len, - enum lws_genhash_types hash_type, struct lws_jwk *jwk) -{ - uint8_t digest[LWS_GENHASH_LARGEST]; - struct lws_genhash_ctx hash_ctx; - struct lws_genrsa_ctx rsactx; - uint8_t *buf; - int n; - - if (lws_genhash_init(&hash_ctx, hash_type)) - return -1; - - if (b64_hdr) { - if (lws_genhash_update(&hash_ctx, (uint8_t *)b64_hdr, hdr_len)) - goto hash_fail; - if (lws_genhash_update(&hash_ctx, (uint8_t *)".", 1)) - goto hash_fail; - } - if (lws_genhash_update(&hash_ctx, (uint8_t *)b64_pay, pay_len)) - goto hash_fail; - - if (lws_genhash_destroy(&hash_ctx, digest)) - return -1; - - if (!strcmp(jwk->keytype, "RSA")) { - if (lws_genrsa_create(&rsactx, &jwk->el)) { - lwsl_notice("%s: lws_genrsa_public_decrypt_create\n", - __func__); - return -1; - } - - n = jwk->el.e[JWK_KEY_N].len; - buf = lws_malloc(n, "jws sign"); - if (!buf) - return -1; - - n = lws_genrsa_public_sign(&rsactx, digest, hash_type, buf, n); - lws_genrsa_destroy(&rsactx); - if (n < 0) { - lws_free(buf); - - return -1; - } - - n = lws_jws_base64_enc((char *)buf, n, b64_sig, sig_len); - lws_free(buf); - - return n; - } - - if (!strcmp(jwk->keytype, "oct")) - return lws_jws_base64_enc((char *)digest, - lws_genhash_size(hash_type), - b64_sig, sig_len); - - /* unknown key type */ - - return -1; - -hash_fail: - lws_genhash_destroy(&hash_ctx, NULL); - return -1; -} - -LWS_VISIBLE int -lws_jws_create_packet(struct lws_jwk *jwk, const char *payload, size_t len, - const char *nonce, char *out, size_t out_len) -{ - char *buf, *start, *p, *end, *p1, *end1, *b64_hdr, *b64_pay; - int n, b64_hdr_len, b64_pay_len; - - /* - * This buffer is local to the function, the actual output - * is prepared into vhd->buf. 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 - */ - - p += lws_snprintf(p, end - p, "{\"alg\":\"RS256\",\"jwk\":"); - n = lws_jwk_export(jwk, 0, p, end - p); - 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\":\""); - b64_hdr = 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; - } - b64_hdr_len = n; - p1 += n; - - p1 += lws_snprintf(p1, end1 - p1, "\",\"payload\":\""); - b64_pay = 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; - } - b64_pay_len = 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(b64_hdr, b64_hdr_len, b64_pay, b64_pay_len, - p1, end1 - p1, LWS_GENHASH_TYPE_SHA256, jwk); - if (n < 0) { - lwsl_notice("sig gen failed\n"); - - goto bail; - } - p1 += n; - p1 += lws_snprintf(p1, end1 - p1, "\"}"); - - free(buf); - - return p1 - out; - -bail: - free(buf); - - return -1; -} - - -#if defined(LWS_WITH_SELFTESTS) -/* - * These are the inputs and outputs from the worked example in RFC7515 - * Appendix A.1. - * - * 1) has a fixed header + payload, and a fixed SHA256 HMAC key, and must give - * a fixed BASE64URL result. - * - * 2) has a fixed header + payload and is signed with a key given in JWK format - */ -int -lws_jws_selftest(void) -{ - struct lws_genhmac_ctx ctx; - struct lws_jwk jwk; - char buf[2048], *p = buf, *end = buf + sizeof(buf) - 1, *enc_ptr, *p1; - uint8_t digest[LWS_GENHASH_LARGEST]; - int n; - - /* Test 1: SHA256 on RFC7515 worked example */ - - /* 1.1: decode the JWK oct key */ - - if (lws_jwk_import(&jwk, key_jwk, strlen(key_jwk)) < 0) { - lwsl_notice("Failed to decode JWK test key\n"); - return -1; - } - - /* 1.2: create JWS known hdr + known payload */ - - n = lws_jws_encode_section(test1, strlen(test1), 1, &p, end); - if (n < 0) - goto bail; - if (strcmp(buf, test1_enc)) - goto bail; - - enc_ptr = p + 1; /* + 1 skips the . */ - n = lws_jws_encode_section(test2, strlen(test2), 0, &p, end); - if (n < 0) - goto bail; - if (strcmp(enc_ptr, test2_enc)) - goto bail; - - /* 1.3: use HMAC SHA-256 with known key on the hdr . payload */ - - if (lws_genhmac_init(&ctx, LWS_GENHMAC_TYPE_SHA256, - jwk.el.e[JWK_KEY_E].buf, jwk.el.e[JWK_KEY_E].len)) - goto bail; - if (lws_genhmac_update(&ctx, (uint8_t *)buf, p - buf)) - goto bail_destroy_hmac; - lws_genhmac_destroy(&ctx, digest); - - /* 1.4: append a base64 encode of the computed HMAC digest */ - - enc_ptr = p + 1; /* + 1 skips the . */ - n = lws_jws_encode_section((const char *)digest, 32, 0, &p, end); - if (n < 0) - goto bail; - if (strcmp(enc_ptr, hash_enc)) /* check against known B64URL hash */ - goto bail; - - /* 1.5: Check we can agree the signature matches the payload */ - - if (lws_jws_confirm_sig(buf, p - buf, &jwk) < 0) { - lwsl_notice("confirm sig failed\n"); - goto bail; - } - - lws_jwk_destroy(&jwk); /* finished with the key from the first test */ - - /* Test 2: RSA256 on RFC7515 worked example */ - - /* 2.1: turn the known JWK key for the RSA test into a lws_jwk */ - - if (lws_jwk_import(&jwk, rfc7515_rsa_key, strlen(rfc7515_rsa_key))) { - lwsl_notice("Failed to read JWK key\n"); - goto bail2; - } - - /* 2.2: check the signature on the test packet from RFC7515 A-1 */ - - if (lws_jws_confirm_sig(rfc7515_rsa_a1, strlen(rfc7515_rsa_a1), - &jwk) < 0) { - lwsl_notice("confirm rsa sig failed\n"); - goto bail; - } - - /* 2.3: generate our own signature for a copy of the test packet */ - - memcpy(buf, rfc7515_rsa_a1, strlen(rfc7515_rsa_a1)); - - /* set p to second . */ - p = strchr(buf + 1, '.'); - p1 = strchr(p + 1, '.'); - - n = lws_jws_sign_from_b64(buf, p - buf, p + 1, p1 - (p + 1), - p1 + 1, sizeof(buf) - (p1 - buf) - 1, - LWS_GENHASH_TYPE_SHA256, &jwk); - if (n < 0) - goto bail; - - puts(buf); - - /* 2.4: confirm our signature can be verified */ - - if (lws_jws_confirm_sig(buf, (p1 + 1 + n) - buf, &jwk) < 0) { - lwsl_notice("confirm rsa sig 2 failed\n"); - goto bail; - } - - lws_jwk_destroy(&jwk); - - /* end */ - - lwsl_notice("%s: selftest OK\n", __func__); - - return 0; - -bail_destroy_hmac: - lws_genhmac_destroy(&ctx, NULL); - -bail: - lws_jwk_destroy(&jwk); -bail2: - lwsl_err("%s: selftest failed ++++++++++++++++++++\n", __func__); - - return 1; -} -#endif diff --git a/lib/tls/mbedtls/lws-genec.c b/lib/tls/mbedtls/lws-genec.c new file mode 100644 index 000000000..e69de29bb diff --git a/lib/tls/mbedtls/lws-genrsa.c b/lib/tls/mbedtls/lws-genrsa.c index 99b2e7565..937ed6927 100644 --- a/lib/tls/mbedtls/lws-genrsa.c +++ b/lib/tls/mbedtls/lws-genrsa.c @@ -1,7 +1,7 @@ /* * libwebsockets - generic RSA api hiding the backend * - * Copyright (C) 2017 Andy Green + * Copyright (C) 2017 - 2018 Andy Green * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -22,40 +22,39 @@ * same whether you are using openssl or mbedtls hash functions underneath. */ #include "core/private.h" +#include "../../jose/private.h" LWS_VISIBLE void -lws_jwk_destroy_genrsa_elements(struct lws_genrsa_elements *el) +lws_jwk_destroy_genrsa_elements(struct lws_jwk_elements *el) { - int n; - - for (n = 0; n < LWS_COUNT_RSA_ELEMENTS; n++) - if (el->e[n].buf) - lws_free_set_NULL(el->e[n].buf); + lws_jwk_destroy_elements(el, LWS_COUNT_RSA_KEY_ELEMENTS); } LWS_VISIBLE int -lws_genrsa_create(struct lws_genrsa_ctx *ctx, struct lws_genrsa_elements *el) +lws_genrsa_create(struct lws_genrsa_ctx *ctx, struct lws_jwk_elements *el, + struct lws_context *context) { memset(ctx, 0, sizeof(*ctx)); ctx->ctx = lws_zalloc(sizeof(*ctx->ctx), "genrsa"); if (!ctx->ctx) return 1; + ctx->context = context; mbedtls_rsa_init(ctx->ctx, MBEDTLS_RSA_PKCS_V15, 0); { int n; - mbedtls_mpi *mpi[LWS_COUNT_RSA_ELEMENTS] = { + mbedtls_mpi *mpi[LWS_COUNT_RSA_KEY_ELEMENTS] = { &ctx->ctx->E, &ctx->ctx->N, &ctx->ctx->D, &ctx->ctx->P, &ctx->ctx->Q, &ctx->ctx->DP, &ctx->ctx->DQ, &ctx->ctx->QP, }; - for (n = 0; n < LWS_COUNT_RSA_ELEMENTS; n++) - if (el->e[n].buf && - mbedtls_mpi_read_binary(mpi[n], el->e[n].buf, - el->e[n].len)) { + for (n = 0; n < LWS_COUNT_RSA_KEY_ELEMENTS; n++) + if (el[n].buf && + mbedtls_mpi_read_binary(mpi[n], el[n].buf, + el[n].len)) { lwsl_notice("mpi load failed\n"); lws_free_set_NULL(ctx->ctx); @@ -63,7 +62,7 @@ lws_genrsa_create(struct lws_genrsa_ctx *ctx, struct lws_genrsa_elements *el) } } - ctx->ctx->len = el->e[JWK_KEY_N].len; + ctx->ctx->len = el[JWK_RSA_KEYEL_N].len; return 0; } @@ -79,7 +78,7 @@ _rngf(void *context, unsigned char *buf, size_t len) LWS_VISIBLE int lws_genrsa_new_keypair(struct lws_context *context, struct lws_genrsa_ctx *ctx, - struct lws_genrsa_elements *el, int bits) + struct lws_jwk_elements *el, int bits) { int n; @@ -97,30 +96,30 @@ lws_genrsa_new_keypair(struct lws_context *context, struct lws_genrsa_ctx *ctx, } { - mbedtls_mpi *mpi[LWS_COUNT_RSA_ELEMENTS] = { + mbedtls_mpi *mpi[LWS_COUNT_RSA_KEY_ELEMENTS] = { &ctx->ctx->E, &ctx->ctx->N, &ctx->ctx->D, &ctx->ctx->P, &ctx->ctx->Q, &ctx->ctx->DP, &ctx->ctx->DQ, &ctx->ctx->QP, }; - for (n = 0; n < LWS_COUNT_RSA_ELEMENTS; n++) + for (n = 0; n < LWS_COUNT_RSA_KEY_ELEMENTS; n++) if (mbedtls_mpi_size(mpi[n])) { - el->e[n].buf = lws_malloc( + el[n].buf = lws_malloc( mbedtls_mpi_size(mpi[n]), "genrsakey"); - if (!el->e[n].buf) + if (!el[n].buf) goto cleanup; - el->e[n].len = mbedtls_mpi_size(mpi[n]); - mbedtls_mpi_write_binary(mpi[n], el->e[n].buf, - el->e[n].len); + el[n].len = mbedtls_mpi_size(mpi[n]); + mbedtls_mpi_write_binary(mpi[n], el[n].buf, + el[n].len); } } return 0; cleanup: - for (n = 0; n < LWS_COUNT_RSA_ELEMENTS; n++) - if (el->e[n].buf) - lws_free_set_NULL(el->e[n].buf); + for (n = 0; n < LWS_COUNT_JWK_ELEMENTS; n++) + if (el[n].buf) + lws_free_set_NULL(el[n].buf); cleanup_1: lws_free(ctx->ctx); @@ -153,12 +152,14 @@ lws_genrsa_public_encrypt(struct lws_genrsa_ctx *ctx, const uint8_t *in, { int n; - ctx->ctx->len = in_len; - n = mbedtls_rsa_rsaes_pkcs1_v15_encrypt(ctx->ctx, NULL, NULL, + //ctx->ctx->len = in_len; // ??? + ctx->ctx->padding = MBEDTLS_RSA_PKCS_V15; + n = mbedtls_rsa_rsaes_pkcs1_v15_encrypt(ctx->ctx, _rngf, ctx->context, MBEDTLS_RSA_PRIVATE, in_len, in, out); if (n) { - lwsl_notice("%s: -0x%x\n", __func__, -n); + lwsl_notice("%s: -0x%x: in_len: %d\n", __func__, -n, + (int)in_len); return -1; } @@ -245,7 +246,7 @@ lws_genrsa_render_pkey_asn1(struct lws_genrsa_ctx *ctx, int _private, uint8_t *pkey_asn1, size_t pkey_asn1_len) { uint8_t *p = pkey_asn1, *totlen, *end = pkey_asn1 + pkey_asn1_len - 1; - mbedtls_mpi *mpi[LWS_COUNT_RSA_ELEMENTS] = { + mbedtls_mpi *mpi[LWS_COUNT_RSA_KEY_ELEMENTS] = { &ctx->ctx->N, &ctx->ctx->E, &ctx->ctx->D, &ctx->ctx->P, &ctx->ctx->Q, &ctx->ctx->DP, &ctx->ctx->DQ, &ctx->ctx->QP, @@ -277,7 +278,7 @@ lws_genrsa_render_pkey_asn1(struct lws_genrsa_ctx *ctx, int _private, *p++ = 0x01; *p++ = 0x00; - for (n = 0; n < LWS_COUNT_RSA_ELEMENTS; n++) { + for (n = 0; n < LWS_COUNT_RSA_KEY_ELEMENTS; n++) { int m = mbedtls_mpi_size(mpi[n]); uint8_t *elen; diff --git a/lib/tls/mbedtls/mbedtls-server.c b/lib/tls/mbedtls/mbedtls-server.c index df6ddf2c6..78e308491 100644 --- a/lib/tls/mbedtls/mbedtls-server.c +++ b/lib/tls/mbedtls/mbedtls-server.c @@ -453,7 +453,7 @@ lws_tls_acme_sni_cert_create(struct lws_vhost *vhost, const char *san_a, int buflen = 0x560; uint8_t *buf = lws_malloc(buflen, "tmp cert buf"), *p = buf, *pkey_asn1; struct lws_genrsa_ctx ctx; - struct lws_genrsa_elements el; + struct lws_jwk_elements el; uint8_t digest[32]; struct lws_genhash_ctx hash_ctx; int pkey_asn1_len = 3 * 1024; @@ -498,8 +498,8 @@ lws_tls_acme_sni_cert_create(struct lws_vhost *vhost, const char *san_a, /* we need to drop 1 + (keybits / 8) bytes of n in here, 00 + key */ *p++ = 0x00; - memcpy(p, el.e[JWK_KEY_N].buf, el.e[JWK_KEY_N].len); - p += el.e[JWK_KEY_N].len; + memcpy(p, el.e[JWK_RSA_KEYEL_N].buf, el.e[JWK_RSA_KEYEL_N].len); + p += el.e[JWK_RSA_KEYEL_N].len; memcpy(p, ss_cert_san_leadin, sizeof(ss_cert_san_leadin)); p += sizeof(ss_cert_san_leadin); diff --git a/lib/tls/openssl/lws-genec.c b/lib/tls/openssl/lws-genec.c new file mode 100644 index 000000000..e69de29bb diff --git a/lib/tls/openssl/lws-genrsa.c b/lib/tls/openssl/lws-genrsa.c index a136327b3..8bb52bf0b 100644 --- a/lib/tls/openssl/lws-genrsa.c +++ b/lib/tls/openssl/lws-genrsa.c @@ -22,31 +22,31 @@ * same whether you are using openssl or mbedtls hash functions underneath. */ #include "core/private.h" +#include "../../jose/private.h" LWS_VISIBLE void -lws_jwk_destroy_genrsa_elements(struct lws_genrsa_elements *el) +lws_jwk_destroy_genrsa_elements(struct lws_jwk_elements *el) { - int n; - - for (n = 0; n < LWS_COUNT_RSA_ELEMENTS; n++) - if (el->e[n].buf) - lws_free_set_NULL(el->e[n].buf); + lws_jwk_destroy_elements(el, LWS_COUNT_RSA_KEY_ELEMENTS); } LWS_VISIBLE int -lws_genrsa_create(struct lws_genrsa_ctx *ctx, struct lws_genrsa_elements *el) +lws_genrsa_create(struct lws_genrsa_ctx *ctx, struct lws_jwk_elements *el, + struct lws_context *context) { int n; memset(ctx, 0, sizeof(*ctx)); + ctx->context = context; + /* Step 1: * * convert the MPI for e and n to OpenSSL BIGNUMs */ for (n = 0; n < 5; n++) { - ctx->bn[n] = BN_bin2bn(el->e[n].buf, el->e[n].len, NULL); + ctx->bn[n] = BN_bin2bn(el[n].buf, el[n].len, NULL); if (!ctx->bn[n]) { lwsl_notice("mpi load failed\n"); goto bail; @@ -65,18 +65,20 @@ lws_genrsa_create(struct lws_genrsa_ctx *ctx, struct lws_genrsa_elements *el) } #if defined(LWS_HAVE_RSA_SET0_KEY) - if (RSA_set0_key(ctx->rsa, ctx->bn[JWK_KEY_N], ctx->bn[JWK_KEY_E], - ctx->bn[JWK_KEY_D]) != 1) { + if (RSA_set0_key(ctx->rsa, ctx->bn[JWK_RSA_KEYEL_N], + ctx->bn[JWK_RSA_KEYEL_E], + ctx->bn[JWK_RSA_KEYEL_D]) != 1) { lwsl_notice("RSA_set0_key failed\n"); goto bail; } - RSA_set0_factors(ctx->rsa, ctx->bn[JWK_KEY_P], ctx->bn[JWK_KEY_Q]); + RSA_set0_factors(ctx->rsa, ctx->bn[JWK_RSA_KEYEL_P], + ctx->bn[JWK_RSA_KEYEL_Q]); #else - ctx->rsa->e = ctx->bn[JWK_KEY_E]; - ctx->rsa->n = ctx->bn[JWK_KEY_N]; - ctx->rsa->d = ctx->bn[JWK_KEY_D]; - ctx->rsa->p = ctx->bn[JWK_KEY_P]; - ctx->rsa->q = ctx->bn[JWK_KEY_Q]; + ctx->rsa->e = ctx->bn[JWK_RSA_KEYEL_E]; + ctx->rsa->n = ctx->bn[JWK_RSA_KEYEL_N]; + ctx->rsa->d = ctx->bn[JWK_RSA_KEYEL_D]; + ctx->rsa->p = ctx->bn[JWK_RSA_KEYEL_P]; + ctx->rsa->q = ctx->bn[JWK_RSA_KEYEL_Q]; #endif return 0; @@ -98,7 +100,7 @@ bail: LWS_VISIBLE int lws_genrsa_new_keypair(struct lws_context *context, struct lws_genrsa_ctx *ctx, - struct lws_genrsa_elements *el, int bits) + struct lws_jwk_elements *el, int bits) { BIGNUM *bn; int n; @@ -128,9 +130,10 @@ lws_genrsa_new_keypair(struct lws_context *context, struct lws_genrsa_ctx *ctx, { const BIGNUM *mpi[5]; - RSA_get0_key(ctx->rsa, &mpi[JWK_KEY_N], &mpi[JWK_KEY_E], - &mpi[JWK_KEY_D]); - RSA_get0_factors(ctx->rsa, &mpi[JWK_KEY_P], &mpi[JWK_KEY_Q]); + RSA_get0_key(ctx->rsa, &mpi[JWK_RSA_KEYEL_N], + &mpi[JWK_RSA_KEYEL_E], &mpi[JWK_RSA_KEYEL_D]); + RSA_get0_factors(ctx->rsa, &mpi[JWK_RSA_KEYEL_P], + &mpi[JWK_RSA_KEYEL_Q]); #else { BIGNUM *mpi[5] = { @@ -140,41 +143,66 @@ lws_genrsa_new_keypair(struct lws_context *context, struct lws_genrsa_ctx *ctx, #endif for (n = 0; n < 5; n++) if (BN_num_bytes(mpi[n])) { - el->e[n].buf = lws_malloc( + el[n].buf = lws_malloc( BN_num_bytes(mpi[n]), "genrsakey"); - if (!el->e[n].buf) + if (!el[n].buf) goto cleanup; - el->e[n].len = BN_num_bytes(mpi[n]); - BN_bn2bin(mpi[n], el->e[n].buf); + el[n].len = BN_num_bytes(mpi[n]); + BN_bn2bin(mpi[n], el[n].buf); } } return 0; cleanup: - for (n = 0; n < LWS_COUNT_RSA_ELEMENTS; n++) - if (el->e[n].buf) - lws_free_set_NULL(el->e[n].buf); + for (n = 0; n < LWS_COUNT_RSA_KEY_ELEMENTS; n++) + if (el[n].buf) + lws_free_set_NULL(el[n].buf); cleanup_1: RSA_free(ctx->rsa); return -1; } +/* + * flen must be less than RSA_size(rsa) - 11 for the PKCS #1 v1.5 + * based padding modes + */ + +LWS_VISIBLE int +lws_genrsa_public_encrypt(struct lws_genrsa_ctx *ctx, const uint8_t *in, + size_t in_len, uint8_t *out) +{ + int m; + + m = RSA_public_encrypt((int)in_len, in, out, ctx->rsa, + RSA_PKCS1_PADDING); + + /* the bignums are also freed by freeing the RSA */ + RSA_free(ctx->rsa); + ctx->rsa = NULL; + + if (m != -1) + return m; + + return -1; +} + LWS_VISIBLE int lws_genrsa_public_decrypt(struct lws_genrsa_ctx *ctx, const uint8_t *in, size_t in_len, uint8_t *out, size_t out_max) { - uint32_t m; + int m; - m = RSA_public_decrypt((int)in_len, in, out, ctx->rsa, RSA_PKCS1_PADDING); + m = RSA_public_decrypt((int)in_len, in, out, ctx->rsa, + RSA_PKCS1_PADDING); /* the bignums are also freed by freeing the RSA */ RSA_free(ctx->rsa); ctx->rsa = NULL; - if (m != (uint32_t)-1) - return (int)m; + if (m != -1) + return m; return -1; } diff --git a/minimal-examples/api-tests/api-test-jose/CMakeLists.txt b/minimal-examples/api-tests/api-test-jose/CMakeLists.txt new file mode 100644 index 000000000..49207b7b2 --- /dev/null +++ b/minimal-examples/api-tests/api-test-jose/CMakeLists.txt @@ -0,0 +1,73 @@ +cmake_minimum_required(VERSION 2.8) +include(CheckCSourceCompiles) + +set(SAMP lws-api-test-jose) +set(SRCS main.c jwk.c jws.c jwe.c) + +# If we are being built as part of lws, confirm current build config supports +# reqconfig, else skip building ourselves. +# +# If we are being built externally, confirm installed lws was configured to +# support reqconfig, else error out with a helpful message about the problem. +# +MACRO(require_lws_config reqconfig _val result) + + if (DEFINED ${reqconfig}) + if (${reqconfig}) + set (rq 1) + else() + set (rq 0) + endif() + else() + set(rq 0) + endif() + + if (${_val} EQUAL ${rq}) + set(SAME 1) + else() + set(SAME 0) + endif() + + if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME}) + if (${_val}) + message("${SAMP}: skipping as lws being built without ${reqconfig}") + else() + message("${SAMP}: skipping as lws built with ${reqconfig}") + endif() + set(${result} 0) + else() + if (LWS_WITH_MINIMAL_EXAMPLES) + set(MET ${SAME}) + else() + CHECK_C_SOURCE_COMPILES("#include \nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig}) + if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig}) + set(HAS_${reqconfig} 0) + else() + set(HAS_${reqconfig} 1) + endif() + if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val})) + set(MET 1) + else() + set(MET 0) + endif() + endif() + if (NOT MET) + if (${_val}) + message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}") + else() + message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project") + endif() + endif() + endif() +ENDMACRO() + + + + add_executable(${SAMP} ${SRCS}) + + if (websockets_shared) + target_link_libraries(${SAMP} websockets_shared) + add_dependencies(${SAMP} websockets_shared) + else() + target_link_libraries(${SAMP} websockets) + endif() diff --git a/minimal-examples/api-tests/api-test-jose/README.md b/minimal-examples/api-tests/api-test-jose/README.md new file mode 100644 index 000000000..74034c793 --- /dev/null +++ b/minimal-examples/api-tests/api-test-jose/README.md @@ -0,0 +1,22 @@ +# lws api test lwsac + +Demonstrates how to use and performs selftests for lwsac + +## build + +``` + $ cmake . && make +``` + +## usage + +Commandline option|Meaning +---|--- +-d |Debug verbosity in decimal, eg, -d15 + +``` + $ ./lws-api-test-lwsac +[2018/10/09 09:14:17:4834] USER: LWS API selftest: lwsac +[2018/10/09 09:14:17:4835] USER: Completed: PASS +``` + diff --git a/minimal-examples/api-tests/api-test-jose/jwe.c b/minimal-examples/api-tests/api-test-jose/jwe.c new file mode 100644 index 000000000..77637e36e --- /dev/null +++ b/minimal-examples/api-tests/api-test-jose/jwe.c @@ -0,0 +1,332 @@ +/* + * lws-api-test-jose - RFC7516 jwe tests + * + * Copyright (C) 2018 Andy Green + * + * This file is made available under the Creative Commons CC0 1.0 + * Universal Public Domain Dedication. + */ + +#include + + +/* A.2. Example JWE using RSAES-PKCS1-v1_5 and AES_128_CBC_HMAC_SHA_256 */ + +/* "Live long and prosper." */ +static +uint8_t + +#if 0 +lws_jwe_ex_a2_plaintext[] = { + 76, 105, 118, 101, 32, 108, 111, 110, + 103, 32, 97, 110, 100, 32, 112, 114, + 111, 115, 112, 101, 114, 46 +}, +#endif +*lws_jwe_ex_a2_jose_hdr = (uint8_t *) + "{\"alg\":\"RSA1_5\",\"enc\":\"A128CBC-HS256\"}", + +*lws_jwe_ex_a2_jose_hdr_b64utf8 = (unsigned char *) + "eyJhbGciOiJSU0ExXzUiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0", + +lws_jwe_ex_a2_cek[] = { + 4, 211, 31, 197, 84, 157, 252, 254, + 11, 100, 157, 250, 63, 170, 106, 206, + 107, 124, 212, 45, 111, 107, 9, 219, + 200, 177, 0, 240, 143, 156, 44, 207 +}, + +*lws_jwe_ex_a2_jwk_json = (uint8_t *) +"{" + "\"kty\":\"RSA\"," + "\"n\":\"sXchDaQebHnPiGvyDOAT4saGEUetSyo9MKLOoWFsueri23bOdgWp4Dy1Wl" + "UzewbgBHod5pcM9H95GQRV3JDXboIRROSBigeC5yjU1hGzHHyXss8UDpre" + "cbAYxknTcQkhslANGRUZmdTOQ5qTRsLAt6BTYuyvVRdhS8exSZEy_c4gs_" + "7svlJJQ4H9_NxsiIoLwAEk7-Q3UXERGYw_75IDrGA84-lA_-Ct4eTlXHBI" + "Y2EaV7t7LjJaynVJCpkv4LKjTTAumiGUIuQhrNhZLuF_RJLqHpM2kgWFLU" + "7-VTdL1VbC2tejvcI2BlMkEpk1BzBZI0KQB0GaDWFLN-aEAw3vRw\"," + "\"e\":\"AQAB\"," + "\"d\":\"VFCWOqXr8nvZNyaaJLXdnNPXZKRaWCjkU5Q2egQQpTBMwhprMzWzpR8Sxq" + "1OPThh_J6MUD8Z35wky9b8eEO0pwNS8xlh1lOFRRBoNqDIKVOku0aZb-ry" + "nq8cxjDTLZQ6Fz7jSjR1Klop-YKaUHc9GsEofQqYruPhzSA-QgajZGPbE_" + "0ZaVDJHfyd7UUBUKunFMScbflYAAOYJqVIVwaYR5zWEEceUjNnTNo_CVSj" + "-VvXLO5VZfCUAVLgW4dpf1SrtZjSt34YLsRarSb127reG_DUwg9Ch-Kyvj" + "T1SkHgUWRVGcyly7uvVGRSDwsXypdrNinPA4jlhoNdizK2zF2CWQ\"," + "\"p\":\"9gY2w6I6S6L0juEKsbeDAwpd9WMfgqFoeA9vEyEUuk4kLwBKcoe1x4HG68" + "ik918hdDSE9vDQSccA3xXHOAFOPJ8R9EeIAbTi1VwBYnbTp87X-xcPWlEP" + "krdoUKW60tgs1aNd_Nnc9LEVVPMS390zbFxt8TN_biaBgelNgbC95sM\"," + "\"q\":\"uKlCKvKv_ZJMVcdIs5vVSU_6cPtYI1ljWytExV_skstvRSNi9r66jdd9-y" + "BhVfuG4shsp2j7rGnIio901RBeHo6TPKWVVykPu1iYhQXw1jIABfw-MVsN" + "-3bQ76WLdt2SDxsHs7q7zPyUyHXmps7ycZ5c72wGkUwNOjYelmkiNS0\"," + "\"dp\":\"w0kZbV63cVRvVX6yk3C8cMxo2qCM4Y8nsq1lmMSYhG4EcL6FWbX5h9yuv" + "ngs4iLEFk6eALoUS4vIWEwcL4txw9LsWH_zKI-hwoReoP77cOdSL4AVcra" + "Hawlkpyd2TWjE5evgbhWtOxnZee3cXJBkAi64Ik6jZxbvk-RR3pEhnCs\"," + "\"dq\":\"o_8V14SezckO6CNLKs_btPdFiO9_kC1DsuUTd2LAfIIVeMZ7jn1Gus_Ff" + "7B7IVx3p5KuBGOVF8L-qifLb6nQnLysgHDh132NDioZkhH7mI7hPG-PYE_" + "odApKdnqECHWw0J-F0JWnUd6D2B_1TvF9mXA2Qx-iGYn8OVV1Bsmp6qU\"," + "\"qi\":\"eNho5yRBEBxhGBtQRww9QirZsB66TrfFReG_CcteI1aCneT0ELGhYlRlC" + "tUkTRclIfuEPmNsNDPbLoLqqCVznFbvdB7x-Tl-m0l_eFTj2KiqwGqE9PZ" + "B9nNTwMVvH3VRRSLWACvPnSiwP8N5Usy-WRXS-V7TbpxIhvepTfE0NNo\"" +"}", + +lws_jwe_ex_a2_jwk_enc_key[] = { + 80, 104, 72, 58, 11, 130, 236, 139, + 132, 189, 255, 205, 61, 86, 151, 176, + 99, 40, 44, 233, 176, 189, 205, 70, + 202, 169, 72, 40, 226, 181, 156, 223, + 120, 156, 115, 232, 150, 209, 145, 133, + 104, 112, 237, 156, 116, 250, 65, 102, + 212, 210, 103, 240, 177, 61, 93, 40, + 71, 231, 223, 226, 240, 157, 15, 31, + 150, 89, 200, 215, 198, 203, 108, 70, + 117, 66, 212, 238, 193, 205, 23, 161, + 169, 218, 243, 203, 128, 214, 127, 253, + 215, 139, 43, 17, 135, 103, 179, 220, + 28, 2, 212, 206, 131, 158, 128, 66, + 62, 240, 78, 186, 141, 125, 132, 227, + 60, 137, 43, 31, 152, 199, 54, 72, + 34, 212, 115, 11, 152, 101, 70, 42, + 219, 233, 142, 66, 151, 250, 126, 146, + 141, 216, 190, 73, 50, 177, 146, 5, + 52, 247, 28, 197, 21, 59, 170, 247, + 181, 89, 131, 241, 169, 182, 246, 99, + 15, 36, 102, 166, 182, 172, 197, 136, + 230, 120, 60, 58, 219, 243, 149, 94, + 222, 150, 154, 194, 110, 227, 225, 112, + 39, 89, 233, 112, 207, 211, 241, 124, + 174, 69, 221, 179, 107, 196, 225, 127, + 167, 112, 226, 12, 242, 16, 24, 28, + 120, 182, 244, 213, 244, 153, 194, 162, + 69, 160, 244, 248, 63, 165, 141, 4, + 207, 249, 193, 79, 131, 0, 169, 233, + 127, 167, 101, 151, 125, 56, 112, 111, + 248, 29, 232, 90, 29, 147, 110, 169, + 146, 114, 165, 204, 71, 136, 41, 252 +} +#if 0 +, +*lws_jwe_ex_a2_jwk_enc_key_b64 = (uint8_t *) + "UGhIOguC7IuEvf_NPVaXsGMoLOmwvc1GyqlIKOK1nN94nHPoltGRhWhw7Zx0-kFm" + "1NJn8LE9XShH59_i8J0PH5ZZyNfGy2xGdULU7sHNF6Gp2vPLgNZ__deLKxGHZ7Pc" + "HALUzoOegEI-8E66jX2E4zyJKx-YxzZIItRzC5hlRirb6Y5Cl_p-ko3YvkkysZIF" + "NPccxRU7qve1WYPxqbb2Yw8kZqa2rMWI5ng8OtvzlV7elprCbuPhcCdZ6XDP0_F8" + "rkXds2vE4X-ncOIM8hAYHHi29NX0mcKiRaD0-D-ljQTP-cFPgwCp6X-nZZd9OHBv" + "-B3oWh2TbqmScqXMR4gp_A", + +lws_jwe_ex_a2_iv[] = { + 3, 22, 60, 12, 43, 67, 104, 105, + 108, 108, 105, 99, 111, 116, 104, 101 +}, + +*lws_jwe_ex_a2_iv_b64 = (uint8_t *) + "AxY8DCtDaGlsbGljb3RoZQ", + +lws_jwe_ex_a2_aad[] = { + 101, 121, 74, 104, 98, 71, 99, 105, + 79, 105, 74, 83, 85, 48, 69, 120, + 88, 122, 85, 105, 76, 67, 74, 108, + 98, 109, 77, 105, 79, 105, 74, 66, + 77, 84, 73, 52, 81, 48, 74, 68, + 76, 85, 104, 84, 77, 106, 85, 50, + 73, 110, 48 +}, + +lws_jwe_ex_a2_ciphertext[] = { + 40, 57, 83, 181, 119, 33, 133, 148, + 198, 185, 243, 24, 152, 230, 6, 75, + 129, 223, 127, 19, 210, 82, 183, 230, + 168, 33, 215, 104, 143, 112, 56, 102 +}, + +*lws_jwe_ex_a2_ciphertext_b64 = (uint8_t *) + "KDlTtXchhZTGufMYmOYGS4HffxPSUrfmqCHXaI9wOGY", + +lws_jwe_ex_a2_authtag[] = { + 246, 17, 244, 190, 4, 95, 98, 3, + 231, 0, 115, 157, 242, 203, 100, 191 +}, + +*lws_jwe_ex_a2_authtag_b64 = (uint8_t *) + "9hH0vgRfYgPnAHOd8stkvw", + +*lws_jwe_ex_a2_aggregated = (uint8_t *) + "eyJhbGciOiJSU0ExXzUiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0." + "UGhIOguC7IuEvf_NPVaXsGMoLOmwvc1GyqlIKOK1nN94nHPoltGRhWhw7Zx0-kFm" + "1NJn8LE9XShH59_i8J0PH5ZZyNfGy2xGdULU7sHNF6Gp2vPLgNZ__deLKxGHZ7Pc" + "HALUzoOegEI-8E66jX2E4zyJKx-YxzZIItRzC5hlRirb6Y5Cl_p-ko3YvkkysZIF" + "NPccxRU7qve1WYPxqbb2Yw8kZqa2rMWI5ng8OtvzlV7elprCbuPhcCdZ6XDP0_F8" + "rkXds2vE4X-ncOIM8hAYHHi29NX0mcKiRaD0-D-ljQTP-cFPgwCp6X-nZZd9OHBv" + "-B3oWh2TbqmScqXMR4gp_A." + "AxY8DCtDaGlsbGljb3RoZQ." + "KDlTtXchhZTGufMYmOYGS4HffxPSUrfmqCHXaI9wOGY." + "9hH0vgRfYgPnAHOd8stkvw" +#endif +; + +/* + * These are the inputs and outputs from the worked example in RFC7515 + * Appendix A.1. + * + * 1) has a fixed header + payload, and a fixed SHA256 HMAC key, and must give + * a fixed BASE64URL result. + * + * 2) has a fixed header + payload and is signed with a key given in JWK format + */ +int +test_jwe(struct lws_context *context) +{ + struct lws_genrsa_ctx rsactx; + struct lws_jwk jwk; + uint8_t enc_cek[sizeof(lws_jwe_ex_a2_jwk_enc_key) + 2048]; + char buf[2048], *p = buf, *end = buf + sizeof(buf) - 1; + int n; + + /* Test 1: A.2 */ + + /* Decode the JWK JSON key */ + + if (lws_jwk_import(&jwk, NULL, NULL, (char *)lws_jwe_ex_a2_jwk_json, + strlen((char *)lws_jwe_ex_a2_jwk_json)) < 0) { + lwsl_notice("Failed to decode JWK test key\n"); + return -1; + } + + if (jwk.kty != LWS_JWK_KYT_RSA) { + lwsl_err("%s: unexpected kty %d\n", __func__, jwk.kty); + + return -1; + } + + /* A.2.1: encode JOSE header and confirm matches official string */ + + n = lws_jws_encode_section((char *)lws_jwe_ex_a2_jose_hdr, + strlen((char *)lws_jwe_ex_a2_jose_hdr), 1, + &p, end); + if (n < 0) + goto bail; + if (strcmp(buf, (char *)lws_jwe_ex_a2_jose_hdr_b64utf8)) + goto bail; + + /* A.2.3: Encrypt the CEK with the recipient's public key using the + * RSAES-PKCS1-v1_5 algorithm to produce the JWE Encrypted Key. + */ + + if (lws_genrsa_create(&rsactx, jwk.e, context)) { + lwsl_notice("%s: lws_genrsa_public_decrypt_create\n", + __func__); + goto bail; + } + + memset(enc_cek, 0, sizeof(enc_cek)); + + n = lws_genrsa_public_encrypt(&rsactx, lws_jwe_ex_a2_cek, + sizeof(lws_jwe_ex_a2_cek), enc_cek); + lws_genrsa_destroy(&rsactx); + if (n < 0) { + lwsl_err("%s: encrypt cek fail\n", __func__); + goto bail; + } +#if 0 + if (memcmp(enc_cek, lws_jwe_ex_a2_jwk_enc_key, sizeof(enc_cek))) { + lwsl_err("%s: encrypt cek wrong output\n", __func__); + lwsl_hexdump_notice(enc_cek, sizeof(enc_cek)); + lwsl_hexdump_notice(lws_jwe_ex_a2_jwk_enc_key, + sizeof(lws_jwe_ex_a2_jwk_enc_key)); + goto bail; + } + + + enc_ptr = p + 1; /* + 1 skips the . */ + n = lws_jws_encode_section(test2, strlen(test2), 0, &p, end); + if (n < 0) + goto bail; + if (strcmp(enc_ptr, test2_enc)) + goto bail; + + /* 1.3: use HMAC SHA-256 with known key on the hdr . payload */ + + if (lws_genhmac_init(&ctx, LWS_GENHMAC_TYPE_SHA256, + jwk.el.e[JWK_RSA_KEYEL_E].buf, + jwk.el.e[JWK_RSA_KEYEL_E].len)) + goto bail; + if (lws_genhmac_update(&ctx, (uint8_t *)buf, p - buf)) + goto bail_destroy_hmac; + lws_genhmac_destroy(&ctx, digest); + + /* 1.4: append a base64 encode of the computed HMAC digest */ + + enc_ptr = p + 1; /* + 1 skips the . */ + n = lws_jws_encode_section((const char *)digest, 32, 0, &p, end); + if (n < 0) + goto bail; + if (strcmp(enc_ptr, hash_enc)) /* check against known B64URL hash */ + goto bail; + + /* 1.5: Check we can agree the signature matches the payload */ + + if (lws_jws_confirm_sig(buf, p - buf, &jwk) < 0) { + lwsl_notice("confirm sig failed\n"); + goto bail; + } + + lws_jwk_destroy(&jwk); /* finished with the key from the first test */ + + /* Test 2: RSA256 on RFC7515 worked example */ + + /* 2.1: turn the known JWK key for the RSA test into a lws_jwk */ + + if (lws_jwk_import(&jwk, rfc7515_rsa_key, strlen(rfc7515_rsa_key))) { + lwsl_notice("Failed to read JWK key\n"); + goto bail2; + } + + /* 2.2: check the signature on the test packet from RFC7515 A-1 */ + + if (lws_jws_confirm_sig(rfc7515_rsa_a1, strlen(rfc7515_rsa_a1), + &jwk) < 0) { + lwsl_notice("confirm rsa sig failed\n"); + goto bail; + } + + /* 2.3: generate our own signature for a copy of the test packet */ + + memcpy(buf, rfc7515_rsa_a1, strlen(rfc7515_rsa_a1)); + + /* set p to second . */ + p = strchr(buf + 1, '.'); + p1 = strchr(p + 1, '.'); + + n = lws_jws_sign_from_b64(buf, p - buf, p + 1, p1 - (p + 1), + p1 + 1, sizeof(buf) - (p1 - buf) - 1, + LWS_GENHASH_TYPE_SHA256, &jwk); + if (n < 0) + goto bail; + + puts(buf); + + /* 2.4: confirm our signature can be verified */ + + if (lws_jws_confirm_sig(buf, (p1 + 1 + n) - buf, &jwk) < 0) { + lwsl_notice("confirm rsa sig 2 failed\n"); + goto bail; + } +#endif + lws_jwk_destroy(&jwk); + + /* end */ + + lwsl_notice("%s: selftest OK\n", __func__); + + return 0; +#if 0 +bail_destroy_hmac: + lws_genhmac_destroy(&ctx, NULL); +#endif +bail: + lws_jwk_destroy(&jwk); +//bail2: + lwsl_err("%s: selftest failed ++++++++++++++++++++\n", __func__); + + return 1; + +} diff --git a/minimal-examples/api-tests/api-test-jose/jwk.c b/minimal-examples/api-tests/api-test-jose/jwk.c new file mode 100644 index 000000000..b4fb95aae --- /dev/null +++ b/minimal-examples/api-tests/api-test-jose/jwk.c @@ -0,0 +1,350 @@ +/* + * lws-api-test-jose - RFC7517 jwk tests + * + * Copyright (C) 2018 Andy Green + * + * This file is made available under the Creative Commons CC0 1.0 + * Universal Public Domain Dedication. + */ + +#include + +static +uint8_t *lws_jwe_ex_a1_jwk_json = (uint8_t *) /* EC + RSA public keys */ + "{\"keys\":" + "[" + "{\"kty\":\"EC\"," + "\"crv\":\"P-256\"," + "\"x\":\"MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4\"," + "\"y\":\"4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM\"," + "\"use\":\"enc\"," + "\"kid\":\"1\"}," + + "{\"kty\":\"RSA\"," + "\"n\": \"0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx" + "4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMs" + "tn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2" + "QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbI" + "SD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqb" + "w0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw\"," + "\"e\":\"AQAB\"," + "\"alg\":\"RS256\"," + "\"kid\":\"2011-04-29\"}" + "]" + "}", + +*lws_jwe_ex_a2_jwk_json = (uint8_t *) /* EC + RSA private keys */ + "{\"keys\":" + "[" + "{\"kty\":\"EC\"," + "\"crv\":\"P-256\"," + "\"x\":\"MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4\"," + "\"y\":\"4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM\"," + "\"d\":\"870MB6gfuTJ4HtUnUvYMyJpr5eUZNP4Bk43bVdj3eAE\"," + "\"use\":\"enc\"," + "\"kid\":\"1\"}," + + "{\"kty\":\"RSA\"," + "\"n\":\"0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4" + "cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMst" + "n64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2Q" + "vzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbIS" + "D08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw" + "0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw\"," + "\"e\":\"AQAB\"," + "\"d\":\"X4cTteJY_gn4FYPsXB8rdXix5vwsg1FLN5E3EaG6RJoVH-HLLKD9" + "M7dx5oo7GURknchnrRweUkC7hT5fJLM0WbFAKNLWY2vv7B6NqXSzUvxT0_YSfqij" + "wp3RTzlBaCxWp4doFk5N2o8Gy_nHNKroADIkJ46pRUohsXywbReAdYaMwFs9tv8d" + "_cPVY3i07a3t8MN6TNwm0dSawm9v47UiCl3Sk5ZiG7xojPLu4sbg1U2jx4IBTNBz" + "nbJSzFHK66jT8bgkuqsk0GjskDJk19Z4qwjwbsnn4j2WBii3RL-Us2lGVkY8fkFz" + "me1z0HbIkfz0Y6mqnOYtqc0X4jfcKoAC8Q\"," + "\"p\":\"83i-7IvMGXoMXCskv73TKr8637FiO7Z27zv8oj6pbWUQyLPQBQxtPV" + "nwD20R-60eTDmD2ujnMt5PoqMrm8RfmNhVWDtjjMmCMjOpSXicFHj7XOuVIYQyqV" + "WlWEh6dN36GVZYk93N8Bc9vY41xy8B9RzzOGVQzXvNEvn7O0nVbfs\"," + "\"q\":\"3dfOR9cuYq-0S-mkFLzgItgMEfFzB2q3hWehMuG0oCuqnb3vobLyum" + "qjVZQO1dIrdwgTnCdpYzBcOfW5r370AFXjiWft_NGEiovonizhKpo9VVS78TzFgx" + "kIdrecRezsZ-1kYd_s1qDbxtkDEgfAITAG9LUnADun4vIcb6yelxk\"," + "\"dp\":\"G4sPXkc6Ya9y8oJW9_ILj4xuppu0lzi_H7VTkS8xj5SdX3coE0oim" + "YwxIi2emTAue0UOa5dpgFGyBJ4c8tQ2VF402XRugKDTP8akYhFo5tAA77Qe_Nmtu" + "YZc3C3m3I24G2GvR5sSDxUyAN2zq8Lfn9EUms6rY3Ob8YeiKkTiBj0\"," + "\"dq\":\"s9lAH9fggBsoFR8Oac2R_E2gw282rT2kGOAhvIllETE1efrA6huUU" + "vMfBcMpn8lqeW6vzznYY5SSQF7pMdC_agI3nG8Ibp1BUb0JUiraRNqUfLhcQb_d9" + "GF4Dh7e74WbRsobRonujTYN1xCaP6TO61jvWrX-L18txXw494Q_cgk\"," + "\"qi\":\"GyM_p6JrXySiz1toFgKbWV-JdI3jQ4ypu9rbMWx3rQJBfmt0FoYzg" + "UIZEVFEcOqwemRN81zoDAaa-Bk0KWNGDjJHZDdDmFhW3AN7lI-puxk_mHZGJ11rx" + "yR8O55XLSe3SPmRfKwZI6yU24ZxvQKFYItdldUKGzO6Ia6zTKhAVRU\"," + "\"alg\":\"RS256\"," + "\"kid\":\"2011-04-29\"}" + "]" + "}", +*lws_jwe_ex_a3_jwk_json = (uint8_t *) /* oct symmetric keys */ + "{\"keys\":" + "[" + "{\"kty\":\"oct\"," + "\"alg\":\"A128KW\"," + "\"k\":\"GawgguFyGrWKav7AX4VKUg\"}," + + "{\"kty\":\"oct\"," + "\"k\":\"AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75" + "aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow\"," + "\"kid\":\"HMAC key used in JWS spec Appendix A.1 example\"}" + "]" + "}", + +*lws_jwe_ex_b_jwk_json = (uint8_t *) /* x5c example (no parent JSON) */ + "{\"kty\":\"RSA\"," + "\"use\":\"sig\"," + "\"kid\":\"1b94c\"," + "\"n\":\"vrjOfz9Ccdgx5nQudyhdoR17V-IubWMeOZCwX_jj0hgAsz2J_pqYW08" + "PLbK_PdiVGKPrqzmDIsLI7sA25VEnHU1uCLNwBuUiCO11_-7dYbsr4iJmG0Q" + "u2j8DsVyT1azpJC_NG84Ty5KKthuCaPod7iI7w0LK9orSMhBEwwZDCxTWq4a" + "YWAchc8t-emd9qOvWtVMDC2BXksRngh6X5bUYLy6AyHKvj-nUy1wgzjYQDwH" + "MTplCoLtU-o-8SNnZ1tmRoGE9uJkBLdh5gFENabWnU5m1ZqZPdwS-qo-meMv" + "VfJb6jJVWRpl2SUtCnYG2C32qvbWbjZ_jBPD5eunqsIo1vQ\"," + "\"e\":\"AQAB\"," + "\"x5c\":" + "[\"MIIDQjCCAiqgAwIBAgIGATz/FuLiMA0GCSqGSIb3DQEBBQUAMGIxCzAJB" + "gNVBAYTAlVTMQswCQYDVQQIEwJDTzEPMA0GA1UEBxMGRGVudmVyMRwwGgYD" + "VQQKExNQaW5nIElkZW50aXR5IENvcnAuMRcwFQYDVQQDEw5CcmlhbiBDYW1" + "wYmVsbDAeFw0xMzAyMjEyMzI5MTVaFw0xODA4MTQyMjI5MTVaMGIxCzAJBg" + "NVBAYTAlVTMQswCQYDVQQIEwJDTzEPMA0GA1UEBxMGRGVudmVyMRwwGgYDV" + "QQKExNQaW5nIElkZW50aXR5IENvcnAuMRcwFQYDVQQDEw5CcmlhbiBDYW1w" + "YmVsbDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL64zn8/QnH" + "YMeZ0LncoXaEde1fiLm1jHjmQsF/449IYALM9if6amFtPDy2yvz3YlRij66" + "s5gyLCyO7ANuVRJx1NbgizcAblIgjtdf/u3WG7K+IiZhtELto/A7Fck9Ws6" + "SQvzRvOE8uSirYbgmj6He4iO8NCyvaK0jIQRMMGQwsU1quGmFgHIXPLfnpn" + "fajr1rVTAwtgV5LEZ4Iel+W1GC8ugMhyr4/p1MtcIM42EA8BzE6ZQqC7VPq" + "PvEjZ2dbZkaBhPbiZAS3YeYBRDWm1p1OZtWamT3cEvqqPpnjL1XyW+oyVVk" + "aZdklLQp2Btgt9qr21m42f4wTw+Xrp6rCKNb0CAwEAATANBgkqhkiG9w0BA" + "QUFAAOCAQEAh8zGlfSlcI0o3rYDPBB07aXNswb4ECNIKG0CETTUxmXl9KUL" + "+9gGlqCz5iWLOgWsnrcKcY0vXPG9J1r9AqBNTqNgHq2G03X09266X5CpOe1" + "zFo+Owb1zxtp3PehFdfQJ610CDLEaS9V9Rqp17hCyybEpOGVwe8fnk+fbEL" + "2Bo3UPGrpsHzUoaGpDftmWssZkhpBJKVMJyf/RuP2SmmaIzmnw9JiSlYhzo" + "4tpzd5rFXhjRbg4zW9C+2qok+2+qDM1iJ684gPHMIY8aLWrdgQTxkumGmTq" + "gawR+N5MDtdPTEQ0XfIBc2cJEUyMTY5MPvACWpkA6SdS4xSvdXK3IVfOWA==\"]" + "}", +*lws_jwe_ex_c1_jwk_json = (uint8_t *) /* RSA enc private key (no parent JSON) */ + "{" + "\"kty\":\"RSA\"," + "\"kid\":\"juliet@capulet.lit\"," + "\"use\":\"enc\"," + "\"n\":\"t6Q8PWSi1dkJj9hTP8hNYFlvadM7DflW9mWepOJhJ66w7nyoK1gPNqFMSQRy" + "O125Gp-TEkodhWr0iujjHVx7BcV0llS4w5ACGgPrcAd6ZcSR0-Iqom-QFcNP" + "8Sjg086MwoqQU_LYywlAGZ21WSdS_PERyGFiNnj3QQlO8Yns5jCtLCRwLHL0" + "Pb1fEv45AuRIuUfVcPySBWYnDyGxvjYGDSM-AqWS9zIQ2ZilgT-GqUmipg0X" + "OC0Cc20rgLe2ymLHjpHciCKVAbY5-L32-lSeZO-Os6U15_aXrk9Gw8cPUaX1" + "_I8sLGuSiVdt3C_Fn2PZ3Z8i744FPFGGcG1qs2Wz-Q\"," + "\"e\":\"AQAB\"," + "\"d\":\"GRtbIQmhOZtyszfgKdg4u_N-R_mZGU_9k7JQ_jn1DnfTuMdSNprTeaSTyWfS" + "NkuaAwnOEbIQVy1IQbWVV25NY3ybc_IhUJtfri7bAXYEReWaCl3hdlPKXy9U" + "vqPYGR0kIXTQRqns-dVJ7jahlI7LyckrpTmrM8dWBo4_PMaenNnPiQgO0xnu" + "ToxutRZJfJvG4Ox4ka3GORQd9CsCZ2vsUDmsXOfUENOyMqADC6p1M3h33tsu" + "rY15k9qMSpG9OX_IJAXmxzAh_tWiZOwk2K4yxH9tS3Lq1yX8C1EWmeRDkK2a" + "hecG85-oLKQt5VEpWHKmjOi_gJSdSgqcN96X52esAQ\"," + "\"p\":\"2rnSOV4hKSN8sS4CgcQHFbs08XboFDqKum3sc4h3GRxrTmQdl1ZK9uw-PIHf" + "QP0FkxXVrx-WE-ZEbrqivH_2iCLUS7wAl6XvARt1KkIaUxPPSYB9yk31s0Q8" + "UK96E3_OrADAYtAJs-M3JxCLfNgqh56HDnETTQhH3rCT5T3yJws\"," + "\"q\":\"1u_RiFDP7LBYh3N4GXLT9OpSKYP0uQZyiaZwBtOCBNJgQxaj10RWjsZu0c6I" + "edis4S7B_coSKB0Kj9PaPaBzg-IySRvvcQuPamQu66riMhjVtG6TlV8CLCYK" + "rYl52ziqK0E_ym2QnkwsUX7eYTB7LbAHRK9GqocDE5B0f808I4s\"," + "\"dp\":\"KkMTWqBUefVwZ2_Dbj1pPQqyHSHjj90L5x_MOzqYAJMcLMZtbUtwKqvVDq3" + "tbEo3ZIcohbDtt6SbfmWzggabpQxNxuBpoOOf_a_HgMXK_lhqigI4y_kqS1w" + "Y52IwjUn5rgRrJ-yYo1h41KR-vz2pYhEAeYrhttWtxVqLCRViD6c\"," + "\"dq\":\"AvfS0-gRxvn0bwJoMSnFxYcK1WnuEjQFluMGfwGitQBWtfZ1Er7t1xDkbN9" + "GQTB9yqpDoYaN06H7CFtrkxhJIBQaj6nkF5KKS3TQtQ5qCzkOkmxIe3KRbBy" + "mXxkb5qwUpX5ELD5xFc6FeiafWYY63TmmEAu_lRFCOJ3xDea-ots\"," + "\"qi\":\"lSQi-w9CpyUReMErP1RsBLk7wNtOvs5EQpPqmuMvqW57NBUczScEoPwmUqq" + "abu9V0-Py4dQ57_bapoKRu1R90bvuFnU63SHWEFglZQvJDMeAvmj4sm-Fp0o" + "Yu_neotgQ0hzbI5gry7ajdYy9-2lNx_76aBZoOUu9HCJ-UsfSOI8\"" + "}" /*, +lws_jwe_ex_c1_plaintext[] = { + 123, 34, 107, 116, 121, 34, 58, 34, 82, 83, 65, 34, 44, 34, 107, + 105, 100, 34, 58, 34, 106, 117, 108, 105, 101, 116, 64, 99, 97, 112, + 117, 108, 101, 116, 46, 108, 105, 116, 34, 44, 34, 117, 115, 101, 34, + 58, 34, 101, 110, 99, 34, 44, 34, 110, 34, 58, 34, 116, 54, 81, 56, + 80, 87, 83, 105, 49, 100, 107, 74, 106, 57, 104, 84, 80, 56, 104, 78, + 89, 70, 108, 118, 97, 100, 77, 55, 68, 102, 108, 87, 57, 109, 87, + 101, 112, 79, 74, 104, 74, 54, 54, 119, 55, 110, 121, 111, 75, 49, + 103, 80, 78, 113, 70, 77, 83, 81, 82, 121, 79, 49, 50, 53, 71, 112, + 45, 84, 69, 107, 111, 100, 104, 87, 114, 48, 105, 117, 106, 106, 72, + 86, 120, 55, 66, 99, 86, 48, 108, 108, 83, 52, 119, 53, 65, 67, 71, + 103, 80, 114, 99, 65, 100, 54, 90, 99, 83, 82, 48, 45, 73, 113, 111, + 109, 45, 81, 70, 99, 78, 80, 56, 83, 106, 103, 48, 56, 54, 77, 119, + 111, 113, 81, 85, 95, 76, 89, 121, 119, 108, 65, 71, 90, 50, 49, 87, + 83, 100, 83, 95, 80, 69, 82, 121, 71, 70, 105, 78, 110, 106, 51, 81, + 81, 108, 79, 56, 89, 110, 115, 53, 106, 67, 116, 76, 67, 82, 119, 76, + 72, 76, 48, 80, 98, 49, 102, 69, 118, 52, 53, 65, 117, 82, 73, 117, + 85, 102, 86, 99, 80, 121, 83, 66, 87, 89, 110, 68, 121, 71, 120, 118, + 106, 89, 71, 68, 83, 77, 45, 65, 113, 87, 83, 57, 122, 73, 81, 50, + 90, 105, 108, 103, 84, 45, 71, 113, 85, 109, 105, 112, 103, 48, 88, + 79, 67, 48, 67, 99, 50, 48, 114, 103, 76, 101, 50, 121, 109, 76, 72, + 106, 112, 72, 99, 105, 67, 75, 86, 65, 98, 89, 53, 45, 76, 51, 50, + 45, 108, 83, 101, 90, 79, 45, 79, 115, 54, 85, 49, 53, 95, 97, 88, + 114, 107, 57, 71, 119, 56, 99, 80, 85, 97, 88, 49, 95, 73, 56, 115, + 76, 71, 117, 83, 105, 86, 100, 116, 51, 67, 95, 70, 110, 50, 80, 90, + 51, 90, 56, 105, 55, 52, 52, 70, 80, 70, 71, 71, 99, 71, 49, 113, + 115, 50, 87, 122, 45, 81, 34, 44, 34, 101, 34, 58, 34, 65, 81, 65, + 66, 34, 44, 34, 100, 34, 58, 34, 71, 82, 116, 98, 73, 81, 109, 104, + 79, 90, 116, 121, 115, 122, 102, 103, 75, 100, 103, 52, 117, 95, 78, + 45, 82, 95, 109, 90, 71, 85, 95, 57, 107, 55, 74, 81, 95, 106, 110, + 49, 68, 110, 102, 84, 117, 77, 100, 83, 78, 112, 114, 84, 101, 97, + 83, 84, 121, 87, 102, 83, 78, 107, 117, 97, 65, 119, 110, 79, 69, 98, + 73, 81, 86, 121, 49, 73, 81, 98, 87, 86, 86, 50, 53, 78, 89, 51, 121, + 98, 99, 95, 73, 104, 85, 74, 116, 102, 114, 105, 55, 98, 65, 88, 89, + 69, 82, 101, 87, 97, 67, 108, 51, 104, 100, 108, 80, 75, 88, 121, 57, + 85, 118, 113, 80, 89, 71, 82, 48, 107, 73, 88, 84, 81, 82, 113, 110, + 115, 45, 100, 86, 74, 55, 106, 97, 104, 108, 73, 55, 76, 121, 99, + 107, 114, 112, 84, 109, 114, 77, 56, 100, 87, 66, 111, 52, 95, 80, + 77, 97, 101, 110, 78, 110, 80, 105, 81, 103, 79, 48, 120, 110, 117, + 84, 111, 120, 117, 116, 82, 90, 74, 102, 74, 118, 71, 52, 79, 120, + 52, 107, 97, 51, 71, 79, 82, 81, 100, 57, 67, 115, 67, 90, 50, 118, + 115, 85, 68, 109, 115, 88, 79, 102, 85, 69, 78, 79, 121, 77, 113, 65, + 68, 67, 54, 112, 49, 77, 51, 104, 51, 51, 116, 115, 117, 114, 89, 49, + 53, 107, 57, 113, 77, 83, 112, 71, 57, 79, 88, 95, 73, 74, 65, 88, + 109, 120, 122, 65, 104, 95, 116, 87, 105, 90, 79, 119, 107, 50, 75, + 52, 121, 120, 72, 57, 116, 83, 51, 76, 113, 49, 121, 88, 56, 67, 49, + 69, 87, 109, 101, 82, 68, 107, 75, 50, 97, 104, 101, 99, 71, 56, 53, + 45, 111, 76, 75, 81, 116, 53, 86, 69, 112, 87, 72, 75, 109, 106, 79, + 105, 95, 103, 74, 83, 100, 83, 103, 113, 99, 78, 57, 54, 88, 53, 50, + 101, 115, 65, 81, 34, 44, 34, 112, 34, 58, 34, 50, 114, 110, 83, 79, + 86, 52, 104, 75, 83, 78, 56, 115, 83, 52, 67, 103, 99, 81, 72, 70, + 98, 115, 48, 56, 88, 98, 111, 70, 68, 113, 75, 117, 109, 51, 115, 99, + 52, 104, 51, 71, 82, 120, 114, 84, 109, 81, 100, 108, 49, 90, 75, 57, + 117, 119, 45, 80, 73, 72, 102, 81, 80, 48, 70, 107, 120, 88, 86, 114, + 120, 45, 87, 69, 45, 90, 69, 98, 114, 113, 105, 118, 72, 95, 50, 105, + 67, 76, 85, 83, 55, 119, 65, 108, 54, 88, 118, 65, 82, 116, 49, 75, + 107, 73, 97, 85, 120, 80, 80, 83, 89, 66, 57, 121, 107, 51, 49, 115, + 48, 81, 56, 85, 75, 57, 54, 69, 51, 95, 79, 114, 65, 68, 65, 89, 116, + 65, 74, 115, 45, 77, 51, 74, 120, 67, 76, 102, 78, 103, 113, 104, 53, + 54, 72, 68, 110, 69, 84, 84, 81, 104, 72, 51, 114, 67, 84, 53, 84, + 51, 121, 74, 119, 115, 34, 44, 34, 113, 34, 58, 34, 49, 117, 95, 82, + 105, 70, 68, 80, 55, 76, 66, 89, 104, 51, 78, 52, 71, 88, 76, 84, 57, + 79, 112, 83, 75, 89, 80, 48, 117, 81, 90, 121, 105, 97, 90, 119, 66, + 116, 79, 67, 66, 78, 74, 103, 81, 120, 97, 106, 49, 48, 82, 87, 106, + 115, 90, 117, 48, 99, 54, 73, 101, 100, 105, 115, 52, 83, 55, 66, 95, + 99, 111, 83, 75, 66, 48, 75, 106, 57, 80, 97, 80, 97, 66, 122, 103, + 45, 73, 121, 83, 82, 118, 118, 99, 81, 117, 80, 97, 109, 81, 117, 54, + 54, 114, 105, 77, 104, 106, 86, 116, 71, 54, 84, 108, 86, 56, 67, 76, + 67, 89, 75, 114, 89, 108, 53, 50, 122, 105, 113, 75, 48, 69, 95, 121, + 109, 50, 81, 110, 107, 119, 115, 85, 88, 55, 101, 89, 84, 66, 55, 76, + 98, 65, 72, 82, 75, 57, 71, 113, 111, 99, 68, 69, 53, 66, 48, 102, + 56, 48, 56, 73, 52, 115, 34, 44, 34, 100, 112, 34, 58, 34, 75, 107, + 77, 84, 87, 113, 66, 85, 101, 102, 86, 119, 90, 50, 95, 68, 98, 106, + 49, 112, 80, 81, 113, 121, 72, 83, 72, 106, 106, 57, 48, 76, 53, 120, + 95, 77, 79, 122, 113, 89, 65, 74, 77, 99, 76, 77, 90, 116, 98, 85, + 116, 119, 75, 113, 118, 86, 68, 113, 51, 116, 98, 69, 111, 51, 90, + 73, 99, 111, 104, 98, 68, 116, 116, 54, 83, 98, 102, 109, 87, 122, + 103, 103, 97, 98, 112, 81, 120, 78, 120, 117, 66, 112, 111, 79, 79, + 102, 95, 97, 95, 72, 103, 77, 88, 75, 95, 108, 104, 113, 105, 103, + 73, 52, 121, 95, 107, 113, 83, 49, 119, 89, 53, 50, 73, 119, 106, 85, + 110, 53, 114, 103, 82, 114, 74, 45, 121, 89, 111, 49, 104, 52, 49, + 75, 82, 45, 118, 122, 50, 112, 89, 104, 69, 65, 101, 89, 114, 104, + 116, 116, 87, 116, 120, 86, 113, 76, 67, 82, 86, 105, 68, 54, 99, 34, + 44, 34, 100, 113, 34, 58, 34, 65, 118, 102, 83, 48, 45, 103, 82, 120, + 118, 110, 48, 98, 119, 74, 111, 77, 83, 110, 70, 120, 89, 99, 75, 49, + 87, 110, 117, 69, 106, 81, 70, 108, 117, 77, 71, 102, 119, 71, 105, + 116, 81, 66, 87, 116, 102, 90, 49, 69, 114, 55, 116, 49, 120, 68, + 107, 98, 78, 57, 71, 81, 84, 66, 57, 121, 113, 112, 68, 111, 89, 97, + 78, 48, 54, 72, 55, 67, 70, 116, 114, 107, 120, 104, 74, 73, 66, 81, + 97, 106, 54, 110, 107, 70, 53, 75, 75, 83, 51, 84, 81, 116, 81, 53, + 113, 67, 122, 107, 79, 107, 109, 120, 73, 101, 51, 75, 82, 98, 66, + 121, 109, 88, 120, 107, 98, 53, 113, 119, 85, 112, 88, 53, 69, 76, + 68, 53, 120, 70, 99, 54, 70, 101, 105, 97, 102, 87, 89, 89, 54, 51, + 84, 109, 109, 69, 65, 117, 95, 108, 82, 70, 67, 79, 74, 51, 120, 68, + 101, 97, 45, 111, 116, 115, 34, 44, 34, 113, 105, 34, 58, 34, 108, + 83, 81, 105, 45, 119, 57, 67, 112, 121, 85, 82, 101, 77, 69, 114, 80, + 49, 82, 115, 66, 76, 107, 55, 119, 78, 116, 79, 118, 115, 53, 69, 81, + 112, 80, 113, 109, 117, 77, 118, 113, 87, 53, 55, 78, 66, 85, 99, + 122, 83, 99, 69, 111, 80, 119, 109, 85, 113, 113, 97, 98, 117, 57, + 86, 48, 45, 80, 121, 52, 100, 81, 53, 55, 95, 98, 97, 112, 111, 75, + 82, 117, 49, 82, 57, 48, 98, 118, 117, 70, 110, 85, 54, 51, 83, 72, + 87, 69, 70, 103, 108, 90, 81, 118, 74, 68, 77, 101, 65, 118, 109, + 106, 52, 115, 109, 45, 70, 112, 48, 111, 89, 117, 95, 110, 101, 111, + 116, 103, 81, 48, 104, 122, 98, 73, 53, 103, 114, 121, 55, 97, 106, + 100, 89, 121, 57, 45, 50, 108, 78, 120, 95, 55, 54, 97, 66, 90, 111, + 79, 85, 117, 57, 72, 67, 74, 45, 85, 115, 102, 83, 79, 73, 56, 34, + 125 } */ +; + +static int +key_import_callback(struct lws_jwk *s, void *user) +{ + lwsl_notice("%s: key type %d\n", __func__, s->kty); + + return 0; +} + + +int +test_jwk(struct lws_context *context) +{ + struct lws_jwk jwk; + + /* Test 1: A.1: Example public keys */ + + if (lws_jwk_import(&jwk, key_import_callback, NULL, + (char *)lws_jwe_ex_a1_jwk_json, + strlen((char *)lws_jwe_ex_a1_jwk_json)) < 0) { + lwsl_notice("Failed to decode JWK test key\n"); + goto bail1; + } + + lws_jwk_destroy(&jwk); + + /* Test 1: A.2: Example private keys */ + + if (lws_jwk_import(&jwk, key_import_callback, NULL, + (char *)lws_jwe_ex_a2_jwk_json, + strlen((char *)lws_jwe_ex_a2_jwk_json)) < 0) { + lwsl_notice("Failed at A.2\n"); + goto bail1; + } + + lws_jwk_destroy(&jwk); + + /* Test 1: A.3: Example symmetric keys */ + + if (lws_jwk_import(&jwk, key_import_callback, NULL, + (char *)lws_jwe_ex_a3_jwk_json, + strlen((char *)lws_jwe_ex_a3_jwk_json)) < 0) { + lwsl_notice("Failed at A.3\n"); + goto bail1; + } + + lws_jwk_destroy(&jwk); + + /* Test 1: B: Example x509 cert chain (no parent JSON) */ + + if (lws_jwk_import(&jwk, NULL, NULL, (char *)lws_jwe_ex_b_jwk_json, + strlen((char *)lws_jwe_ex_b_jwk_json)) < 0) { + lwsl_notice("Failed at B\n"); + goto bail1; + } + + lws_jwk_destroy(&jwk); + + /* Test 1: C.1: Example private key (no parent JSON) */ + + if (lws_jwk_import(&jwk, NULL, NULL, + (char *)lws_jwe_ex_c1_jwk_json, + strlen((char *)lws_jwe_ex_c1_jwk_json)) < 0) { + lwsl_notice("Failed at B\n"); + goto bail1; + } + + lws_jwk_destroy(&jwk); + + /* end */ + + lwsl_notice("%s: selftest OK\n", __func__); + + return 0; + +//bail: +// lws_jwk_destroy(&jwk); +bail1: + lwsl_err("%s: selftest failed ++++++++++++++++++++\n", __func__); + + return 1; + +} diff --git a/minimal-examples/api-tests/api-test-jose/jws.c b/minimal-examples/api-tests/api-test-jose/jws.c new file mode 100644 index 000000000..d82b0ca78 --- /dev/null +++ b/minimal-examples/api-tests/api-test-jose/jws.c @@ -0,0 +1,248 @@ +/* + * lws-api-test-jose - RFC7515 jws tests + * + * Copyright (C) 2018 Andy Green + * + * This file is made available under the Creative Commons CC0 1.0 + * Universal Public Domain Dedication. + */ + +#include + +/* + * JSON Web Signature is defined in RFC7515 + * + * https://tools.ietf.org/html/rfc7515 + * + * It's basically a way to wrap some JSON with a JSON "header" describing the + * crypto, and a signature, all in a BASE64 wrapper with elided terminating '='. + * + * The signature stays with the content, it serves a different purpose than eg + * a TLS tunnel to transfer it. + * + * RFC7518 (JSON Web Algorithms) says for the "alg" names + * + * | HS256 | HMAC using SHA-256 | Required | + * | HS384 | HMAC using SHA-384 | Optional | + * | HS512 | HMAC using SHA-512 | Optional | + * | RS256 | RSASSA-PKCS1-v1_5 using | Recommended | + * | RS384 | RSASSA-PKCS1-v1_5 using | Optional | + * | | SHA-384 | | + * | RS512 | RSASSA-PKCS1-v1_5 using | Optional | + * | | SHA-512 | | + * | ES256 | ECDSA using P-256 and SHA-256 | Recommended+ | + * | ES384 | ECDSA using P-384 and SHA-384 | Optional | + * | ES512 | ECDSA using P-521 and SHA-512 | Optional | + * + * Boulder (FOSS ACME provider) supports RS256, ES256, ES384 and ES512 + * currently. The "Recommended+" just means it is recommended but will likely + * be "very recommended" soon. + * + * We support HS256/384/512 for symmetric crypto, but the choice for the + * asymmetric crypto isn't as easy to make. + * + * Normally you'd choose the EC option but these are defined to use the + * "NIST curves" (RFC7518 3.4) which are believed to be insecure. + * + * https://safecurves.cr.yp.to/ + * + * For that reason we implement RS256/384/512 for asymmetric. + */ + +static const char + *test1 = "{\"typ\":\"JWT\",\r\n \"alg\":\"HS256\"}", + *test1_enc = "eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9", + *test2 = "{\"iss\":\"joe\",\r\n \"exp\":1300819380,\r\n" + " \"http://example.com/is_root\":true}", + *test2_enc = "eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQ" + "ogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ", + *key_jwk = "{\"kty\":\"oct\",\r\n" + " \"k\":\"AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQ" + "Lr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow\"}", + *hash_enc = "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk", + /* the key from worked example in RFC7515 A-1, as a JWK */ + *rfc7515_rsa_key = + "{\"kty\":\"RSA\"," + " \"n\":\"ofgWCuLjybRlzo0tZWJjNiuSfb4p4fAkd_wWJcyQoTbji9k0l8W26mPddx" + "HmfHQp-Vaw-4qPCJrcS2mJPMEzP1Pt0Bm4d4QlL-yRT-SFd2lZS-pCgNMs" + "D1W_YpRPEwOWvG6b32690r2jZ47soMZo9wGzjb_7OMg0LOL-bSf63kpaSH" + "SXndS5z5rexMdbBYUsLA9e-KXBdQOS-UTo7WTBEMa2R2CapHg665xsmtdV" + "MTBQY4uDZlxvb3qCo5ZwKh9kG4LT6_I5IhlJH7aGhyxXFvUK-DWNmoudF8" + "NAco9_h9iaGNj8q2ethFkMLs91kzk2PAcDTW9gb54h4FRWyuXpoQ\"," + "\"e\":\"AQAB\"," + "\"d\":\"Eq5xpGnNCivDflJsRQBXHx1hdR1k6Ulwe2JZD50LpXyWPEAeP88vLNO97I" + "jlA7_GQ5sLKMgvfTeXZx9SE-7YwVol2NXOoAJe46sui395IW_GO-pWJ1O0" + "BkTGoVEn2bKVRUCgu-GjBVaYLU6f3l9kJfFNS3E0QbVdxzubSu3Mkqzjkn" + "439X0M_V51gfpRLI9JYanrC4D4qAdGcopV_0ZHHzQlBjudU2QvXt4ehNYT" + "CBr6XCLQUShb1juUO1ZdiYoFaFQT5Tw8bGUl_x_jTj3ccPDVZFD9pIuhLh" + "BOneufuBiB4cS98l2SR_RQyGWSeWjnczT0QU91p1DhOVRuOopznQ\"," + "\"p\":\"4BzEEOtIpmVdVEZNCqS7baC4crd0pqnRH_5IB3jw3bcxGn6QLvnEtfdUdi" + "YrqBdss1l58BQ3KhooKeQTa9AB0Hw_Py5PJdTJNPY8cQn7ouZ2KKDcmnPG" + "BY5t7yLc1QlQ5xHdwW1VhvKn-nXqhJTBgIPgtldC-KDV5z-y2XDwGUc\"," + "\"q\":\"uQPEfgmVtjL0Uyyx88GZFF1fOunH3-7cepKmtH4pxhtCoHqpWmT8YAmZxa" + "ewHgHAjLYsp1ZSe7zFYHj7C6ul7TjeLQeZD_YwD66t62wDmpe_HlB-TnBA" + "-njbglfIsRLtXlnDzQkv5dTltRJ11BKBBypeeF6689rjcJIDEz9RWdc\"," + "\"dp\":\"BwKfV3Akq5_MFZDFZCnW-wzl-CCo83WoZvnLQwCTeDv8uzluRSnm71I3Q" + "CLdhrqE2e9YkxvuxdBfpT_PI7Yz-FOKnu1R6HsJeDCjn12Sk3vmAktV2zb" + "34MCdy7cpdTh_YVr7tss2u6vneTwrA86rZtu5Mbr1C1XsmvkxHQAdYo0\"," + "\"dq\":\"h_96-mK1R_7glhsum81dZxjTnYynPbZpHziZjeeHcXYsXaaMwkOlODsWa" + "7I9xXDoRwbKgB719rrmI2oKr6N3Do9U0ajaHF-NKJnwgjMd2w9cjz3_-ky" + "NlxAr2v4IKhGNpmM5iIgOS1VZnOZ68m6_pbLBSp3nssTdlqvd0tIiTHU\"," + "\"qi\":\"IYd7DHOhrWvxkwPQsRM2tOgrjbcrfvtQJipd-DlcxyVuuM9sQLdgjVk2o" + "y26F0EmpScGLq2MowX7fhd_QJQ3ydy5cY7YIBi87w93IKLEdfnbJtoOPLU" + "W0ITrJReOgo1cq9SbsxYawBgfp_gh6A5603k2-ZQwVK0JKSHuLFkuQ3U\"" + "}", + *rfc7515_rsa_a1 = /* the signed worked example in RFC7515 A-1 */ + "eyJhbGciOiJSUzI1NiJ9" + ".eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFt" + "cGxlLmNvbS9pc19yb290Ijp0cnVlfQ" + ".cC4hiUPoj9Eetdgtv3hF80EGrhuB__dzERat0XF9g2VtQgr9PJbu3XOiZj5RZmh7" + "AAuHIm4Bh-0Qc_lF5YKt_O8W2Fp5jujGbds9uJdbF9CUAr7t1dnZcAcQjbKBYNX4" + "BAynRFdiuB--f_nZLgrnbyTyWzO75vRK5h6xBArLIARNPvkSjtQBMHlb1L07Qe7K" + "0GarZRmB_eSN9383LcOLn6_dO--xi12jzDwusC-eOkHWEsqtFZESc6BfI7noOPqv" + "hJ1phCnvWh6IeYI2w9QOYEUipUTI8np6LbgGY9Fs98rqVt5AXLIhWkWywlVmtVrB" + "p0igcN_IoypGlUPQGe77Rw"; + +/* + * These are the inputs and outputs from the worked example in RFC7515 + * Appendix A.1. + * + * 1) has a fixed header + payload, and a fixed SHA256 HMAC key, and must give + * a fixed BASE64URL result. + * + * 2) has a fixed header + payload and is signed with a key given in JWK format + */ +int +test_jws(struct lws_context *context) +{ + struct lws_genhmac_ctx ctx; + struct lws_jwk jwk; + char buf[2048], *p = buf, *end = buf + sizeof(buf) - 1, *enc_ptr, *p1; + uint8_t digest[LWS_GENHASH_LARGEST]; + int n; + + /* Test 1: SHA256 on RFC7515 worked example */ + + /* 1.1: decode the JWK oct key */ + + if (lws_jwk_import(&jwk, NULL, NULL, key_jwk, strlen(key_jwk)) < 0) { + lwsl_notice("Failed to decode JWK test key\n"); + return -1; + } + if (jwk.kty != LWS_JWK_KYT_OCT) { + lwsl_err("%s: unexpected kty %d\n", __func__, jwk.kty); + + return -1; + } + + /* 1.2: create JWS known hdr + known payload */ + + n = lws_jws_encode_section(test1, strlen(test1), 1, &p, end); + if (n < 0) + goto bail; + if (strcmp(buf, test1_enc)) + goto bail; + + enc_ptr = p + 1; /* + 1 skips the . */ + n = lws_jws_encode_section(test2, strlen(test2), 0, &p, end); + if (n < 0) + goto bail; + if (strcmp(enc_ptr, test2_enc)) + goto bail; + + /* 1.3: use HMAC SHA-256 with known key on the hdr . payload */ + + if (lws_genhmac_init(&ctx, LWS_GENHMAC_TYPE_SHA256, + jwk.e[JWK_OCT_KEYEL_K].buf, + jwk.e[JWK_OCT_KEYEL_K].len)) + goto bail; + if (lws_genhmac_update(&ctx, (uint8_t *)buf, p - buf)) + goto bail_destroy_hmac; + lws_genhmac_destroy(&ctx, digest); + + /* 1.4: append a base64 encode of the computed HMAC digest */ + + enc_ptr = p + 1; /* + 1 skips the . */ + n = lws_jws_encode_section((const char *)digest, 32, 0, &p, end); + if (n < 0) + goto bail; + if (strcmp(enc_ptr, hash_enc)) { /* check against known B64URL hash */ + lwsl_err("%s: b64 enc of computed HMAC mismatches '%s' '%s'\n", + __func__, enc_ptr, hash_enc); + goto bail; + } + + /* 1.5: Check we can agree the signature matches the payload */ + + if (lws_jws_confirm_sig(buf, p - buf, &jwk, context) < 0) { + lwsl_notice("confirm sig failed\n"); + goto bail; + } + + lws_jwk_destroy(&jwk); /* finished with the key from the first test */ + + /* Test 2: RSA256 on RFC7515 worked example */ + + /* 2.1: turn the known JWK key for the RSA test into a lws_jwk */ + + if (lws_jwk_import(&jwk, NULL, NULL, + rfc7515_rsa_key, strlen(rfc7515_rsa_key))) { + lwsl_notice("%s: 2.2: Failed to read JWK key\n", __func__); + goto bail2; + } + + if (jwk.kty != LWS_JWK_KYT_RSA) { + lwsl_err("%s: 2.2: kty: %d instead of RSA\n", __func__, jwk.kty); + } + + /* 2.2: check the signature on the test packet from RFC7515 A-1 */ + + if (lws_jws_confirm_sig(rfc7515_rsa_a1, strlen(rfc7515_rsa_a1), + &jwk, context) < 0) { + lwsl_notice("%s: 2.2: confirm rsa sig failed\n", __func__); + goto bail; + } + + /* 2.3: generate our own signature for a copy of the test packet */ + + memcpy(buf, rfc7515_rsa_a1, strlen(rfc7515_rsa_a1)); + + /* set p to second . */ + p = strchr(buf + 1, '.'); + p1 = strchr(p + 1, '.'); + + n = lws_jws_sign_from_b64(buf, p - buf, p + 1, p1 - (p + 1), + p1 + 1, sizeof(buf) - (p1 - buf) - 1, + LWS_GENHASH_TYPE_SHA256, &jwk, context); + if (n < 0) { + lwsl_err("%s: failed signing test packet\n", __func__); + goto bail; + } + + // puts(buf); + + /* 2.4: confirm our signature can be verified */ + + if (lws_jws_confirm_sig(buf, (p1 + 1 + n) - buf, &jwk, context) < 0) { + lwsl_notice("confirm rsa sig 2 failed\n"); + goto bail; + } + + lws_jwk_destroy(&jwk); + + /* end */ + + lwsl_notice("%s: selftest OK\n", __func__); + + return 0; + +bail_destroy_hmac: + lws_genhmac_destroy(&ctx, NULL); + +bail: + lws_jwk_destroy(&jwk); +bail2: + lwsl_err("%s: selftest failed ++++++++++++++++++++\n", __func__); + + return 1; +} diff --git a/minimal-examples/api-tests/api-test-jose/main.c b/minimal-examples/api-tests/api-test-jose/main.c new file mode 100644 index 000000000..3a5b27b0e --- /dev/null +++ b/minimal-examples/api-tests/api-test-jose/main.c @@ -0,0 +1,51 @@ +/* + * lws-api-test-jose + * + * Copyright (C) 2018 Andy Green + * + * This file is made available under the Creative Commons CC0 1.0 + * Universal Public Domain Dedication. + */ + +#include + +int +test_jwk(struct lws_context *context); +int +test_jws(struct lws_context *context); +int +test_jwe(struct lws_context *context); + +int main(int argc, const char **argv) +{ + struct lws_context_creation_info info; + struct lws_context *context; + const char *p; + int result = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE; + + if ((p = lws_cmdline_option(argc, argv, "-d"))) + logs = atoi(p); + + lws_set_log_level(logs, NULL); + lwsl_user("LWS JOSE api tests\n"); + + memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ + info.port = CONTEXT_PORT_NO_LISTEN; + info.options = 0; + + context = lws_create_context(&info); + if (!context) { + lwsl_err("lws init failed\n"); + return 1; + } + + result |= test_jwk(context); + result |= test_jws(context); + result |= test_jwe(context); + + lwsl_user("Completed: %s\n", result ? "FAIL" : "PASS"); + + lws_context_destroy(context); + + return result; +} diff --git a/minimal-examples/api-tests/api-test-jose/selftest.sh b/minimal-examples/api-tests/api-test-jose/selftest.sh new file mode 100755 index 000000000..16d1e2e8e --- /dev/null +++ b/minimal-examples/api-tests/api-test-jose/selftest.sh @@ -0,0 +1,24 @@ +#!/bin/bash +# +# $1: path to minimal example binaries... +# if lws is built with -DLWS_WITH_MINIMAL_EXAMPLES=1 +# that will be ./bin from your build dir +# +# $2: path for logs and results. The results will go +# in a subdir named after the directory this script +# is in +# +# $3: offset for test index count +# +# $4: total test count +# +# $5: path to ./minimal-examples dir in lws +# +# Test return code 0: OK, 254: timed out, other: error indication + +. $5/selftests-library.sh + +COUNT_TESTS=1 + +dotest $1 $2 apiselftest +exit $FAILS diff --git a/plugins/acme-client/protocol_lws_acme_client.c b/plugins/acme-client/protocol_lws_acme_client.c index c10b411db..c30056c24 100644 --- a/plugins/acme-client/protocol_lws_acme_client.c +++ b/plugins/acme-client/protocol_lws_acme_client.c @@ -435,13 +435,14 @@ lws_acme_load_create_auth_keys(struct per_vhost_data__lws_acme_client *vhd, { int n; - if (!lws_jwk_load(&vhd->jwk, vhd->pvop[LWS_TLS_SET_AUTH_PATH])) + if (!lws_jwk_load(&vhd->jwk, vhd->pvop[LWS_TLS_SET_AUTH_PATH], + NULL, NULL)) return 0; - strcpy(vhd->jwk.keytype, "RSA"); + vhd->jwk.kty = LWS_JWK_KYT_RSA; lwsl_notice("Generating ACME %d-bit keypair... " "will take a little while\n", bits); - n = lws_genrsa_new_keypair(vhd->context, &vhd->rsactx, &vhd->jwk.el, + n = lws_genrsa_new_keypair(vhd->context, &vhd->rsactx, vhd->jwk.e, bits); if (n) { lwsl_notice("failed to create keypair\n"); @@ -786,7 +787,8 @@ pkt_add_hdrs: ac->replay_nonce, &ac->buf[LWS_PRE], sizeof(ac->buf) - - LWS_PRE); + LWS_PRE, + lws_get_context(wsi)); if (ac->len < 0) { ac->len = 0; lwsl_notice("lws_jws_create_packet failed\n"); diff --git a/plugins/ssh-base/sshd.c b/plugins/ssh-base/sshd.c index 980e4eb3d..baa79e80e 100644 --- a/plugins/ssh-base/sshd.c +++ b/plugins/ssh-base/sshd.c @@ -548,7 +548,7 @@ lws_ssh_exec_finish(void *finish_handle, int retcode) static int lws_ssh_parse_plaintext(struct per_session_data__sshd *pss, uint8_t *p, size_t len) { - struct lws_genrsa_elements el; + struct lws_jwk_elements e[LWS_COUNT_RSA_KEY_ELEMENTS]; struct lws_genrsa_ctx ctx; struct lws_ssh_channel *ch; struct lws_subprotocol_scp *scp; @@ -1247,19 +1247,19 @@ again: * the E and N factors */ - memset(&el, 0, sizeof(el)); + memset(e, 0, sizeof(e)); pp = pss->ua->pubkey; m = lws_g32(&pp); pp += m; m = lws_g32(&pp); - el.e[JWK_KEY_E].buf = pp; - el.e[JWK_KEY_E].len = m; + e[JWK_RSA_KEYEL_E].buf = pp; + e[JWK_RSA_KEYEL_E].len = m; pp += m; m = lws_g32(&pp); - el.e[JWK_KEY_N].buf = pp; - el.e[JWK_KEY_N].len = m; + e[JWK_RSA_KEYEL_N].buf = pp; + e[JWK_RSA_KEYEL_N].len = m; - if (lws_genrsa_create(&ctx, &el)) + if (lws_genrsa_create(&ctx, e, pss->vhd->context)) goto ua_fail; /* diff --git a/scripts/libwebsockets.spec b/scripts/libwebsockets.spec index 84790b1cb..dd90a4fce 100644 --- a/scripts/libwebsockets.spec +++ b/scripts/libwebsockets.spec @@ -110,6 +110,7 @@ rm -rf $RPM_BUILD_ROOT "/usr/include/libwebsockets/lws-genhash.h" "/usr/include/libwebsockets/lws-genrsa.h" "/usr/include/libwebsockets/lws-http.h" +"/usr/include/libwebsockets/lws-jose.h" "/usr/include/libwebsockets/lws-jwk.h" "/usr/include/libwebsockets/lws-jws.h" "/usr/include/libwebsockets/lws-lejp.h"