diff --git a/CMakeLists.txt b/CMakeLists.txt index 485ae36bb..54602dfdc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1014,6 +1014,7 @@ if (LWS_WITH_SSL) if (LWS_WITH_MBEDTLS) list(APPEND SOURCES lib/tls/mbedtls/ssl.c + lib/tls/mbedtls/x509.c ) if (LWS_WITH_GENCRYPTO) list(APPEND SOURCES @@ -1028,6 +1029,7 @@ if (LWS_WITH_SSL) else() list(APPEND SOURCES lib/tls/openssl/ssl.c + lib/tls/openssl/x509.c ) if (LWS_WITH_GENCRYPTO) list(APPEND SOURCES diff --git a/README.md b/README.md index e6c4de8c3..acefc2ab0 100644 --- a/README.md +++ b/README.md @@ -18,12 +18,25 @@ News ## New features on master + - New Crypto-agile APIs + JOSE / JWS / JWE / JWK support... apis work exactly + the same with OpenSSL or mbedTLS tls library backends, and allow key cycling + and crypto algorithm changes while allowing for grace periods + + [README.crypto-apis](https://libwebsockets.org/git/libwebsockets/tree/READMEs/README.crypto-apis.md) + - CMake config simplification for crypto: `-DLWS_WITH_GENCRYPTO=1` for all generic cipher and hash apis built (which work the same on mbedtls and OpenSSL transparently), and `-DLWS_WITH_JOSE=1` for all JOSE, JWK, JWS and JWE support built (which use gencrypto and so also work the same regardless of tls library backend). + - **`x.509`** - new generic x509 api allows PEM-based certificate and key + trust relationship verification, and conversion between x.509 keys and + JWK. Works for EC and RSA keys, and on mbedtls and OpenSSl the same. + + [x.509 api](https://libwebsockets.org/git/libwebsockets/tree/include/libwebsockets/lws-x509.h), + [x.509 minimal example](https://libwebsockets.org/git/libwebsockets/tree/minimal-examples/crypto/minimal-crypto-x509) + - **`JWE`** - JWE (RFC7516) Algorithms with CI tests: |Key Encryption|Payload authentication + crypt|Enc + Dec Support| diff --git a/READMEs/README.crypto-apis.md b/READMEs/README.crypto-apis.md index 27706c7f7..4a04687ef 100644 --- a/READMEs/README.crypto-apis.md +++ b/READMEs/README.crypto-apis.md @@ -28,6 +28,44 @@ once it's working, you literally just change your JSON defining the keys and JWE or JWS algorithm. (It's up to you to define your policy for which combinations are acceptable by querying the parsed JW structs). +## Crypto supported in generic layer + +### Generic Hash + + - SHA1 + - SHA256 + - SHA384 + - SHA512 + +### Generic HMAC + + - SHA256 + - SHA384 + - SHA512 + +### Generic AES + + - CBC + - CFB128 + - CFB8 + - CTR + - ECB + - OFB + - XTS + - GCM + - KW (Key Wrap) + +### Generic RSA + + - PKCS 1.5 + - OAEP / PSS + +### Generic EC + + - ECDH + - ECDSA + - P256 / P384 / P521 (sic) curves + ## Using the generic layer All the necessary includes are part of `libwebsockets.h`. @@ -35,11 +73,12 @@ All the necessary includes are part of `libwebsockets.h`. Enable `-DLWS_WITH_GENCRYPTO=1` at cmake. |api|header|Functionality| -|---|---|---|---| +|---|---|---| |genhash|[./include/libwebsockets/lws-genhash.h](https://libwebsockets.org/git/libwebsockets/tree/include/libwebsockets/lws-genhash.h)|Provides SHA1 + SHA2 hashes and hmac| |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| |genaes|[./include/libwebsockets/lws-genaes.h](https://libwebsockets.org/git/libwebsockets/tree/include/libwebsockets/lws-genaes.h)|Provides AES in all common variants for encryption and decryption| |genec|[./include/libwebsockets/lws-genec.h](https://libwebsockets.org/git/libwebsockets/tree/include/libwebsockets/lws-genec.h)|Provides Elliptic Curve for encryption, decryption, signing, verification, key generation and creation| +|x509|[./include/libwebsockets/lws-x509.h](https://libwebsockets.org/git/libwebsockets/tree/include/libwebsockets/lws-x509.h)|Apis for X.509 Certificate loading, parsing, and stack verification, plus JWK key extraction from PEM X.509 certificate / private key| 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) @@ -62,19 +101,65 @@ store arbitrary octets that make up the key element's binary representation. ## Using the JOSE layer +The JOSE (JWK / JWS / JWE) stuff is a crypto-agile JSON-based layer +that uses the gencrypto support underneath. + +"Crypto Agility" means the JSON structs include information about the +algorithms and ciphers used in that particular object, making it easy to +upgrade system crypto strength or cycle keys over time while supporting a +transitional period where the old and new keys or algorithms + ciphers +are also valid. + +Uniquely lws generic support means the JOSE stuff also has "tls library +agility", code written to the lws generic or JOSE apis is completely unchanged +even if the underlying tls library changes between OpenSSL and mbedtls, meaning +sharing code between server and client sides is painless. + All the necessary includes are part of `libwebsockets.h`. Enable `-DLWS_WITH_JOSE=1` at CMake. |api|header|Functionality| -|---|---|---|---| +|---|---|---| |JOSE|[./include/libwebsockets/lws-jose.h](https://libwebsockets.org/git/libwebsockets/tree/include/libwebsockets/lws-jose.h)|Provides crypto agility for JWS / JWE| |JWE|[./include/libwebsockets/lws-jwe.h](https://libwebsockets.org/git/libwebsockets/tree/include/libwebsockets/lws-jwe.h)|Provides Encryption and Decryption services for RFC7516 JWE JSON| |JWS|[./include/libwebsockets/lws-jws.h](https://libwebsockets.org/git/libwebsockets/tree/include/libwebsockets/lws-jws.h)|Provides signature and verifcation services for RFC7515 JWS JSON| |JWK|[./include/libwebsockets/lws-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| +Minimal examples are provided in the form of commandline tools for JWK / JWS / JWE / x509 handling: + + - [JWK minimal example](https://libwebsockets.org/git/libwebsockets/tree/minimal-examples/crypto/minimal-crypto-jwk) + - [JWS minimal example](https://libwebsockets.org/git/libwebsockets/tree/minimal-examples/crypto/minimal-crypto-jws) + - [JWE minimal example](https://libwebsockets.org/git/libwebsockets/tree/minimal-examples/crypto/minimal-crypto-jwe) + - [X509 minimal example](https://libwebsockets.org/git/libwebsockets/tree/minimal-examples/crypto/minimal-crypto-x509) + 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) +## Crypto supported in the JOSE layer + +The JOSE RFCs define specific short names for different algorithms + +### JWS + +|JSOE name|Hash|Signature| +---|---|--- +|RS256, RS384, RS512|SHA256/384/512|RSA +|ES256, ES384, ES521|SHA256/384/512|EC + +### JWE + +|Key Encryption|Payload authentication + crypt| +|---|---| +|`RSAES-PKCS1-v1.5` 2048b & 4096b|`AES_128_CBC_HMAC_SHA_256`| +|`RSAES-PKCS1-v1.5` 2048b|`AES_192_CBC_HMAC_SHA_384`| +|`RSAES-PKCS1-v1.5` 2048b|`AES_256_CBC_HMAC_SHA_512`| +|`RSAES-OAEP`|`AES_256_GCM`| +|`AES128KW`, `AES192KW`, `AES256KW`|`AES_128_CBC_HMAC_SHA_256`| +|`AES128KW`, `AES192KW`, `AES256KW`|`AES_192_CBC_HMAC_SHA_384`| +|`AES128KW`, `AES192KW`, `AES256KW`|`AES_256_CBC_HMAC_SHA_512`| +|`ECDH-ES` (P-256/384/521 key)|`AES_128/192/256_GCM`| +|`ECDH-ES+A128/192/256KW` (P-256/384/521 key)|`AES_128/192/256_GCM`| + ### Keys in the JOSE layer Keys in the JOSE layer use a `struct lws_jwk`, this contains two arrays of diff --git a/doc-assets/lws-overview.png b/doc-assets/lws-overview.png index 7355cdfd1..64195d9e7 100644 Binary files a/doc-assets/lws-overview.png and b/doc-assets/lws-overview.png differ diff --git a/include/libwebsockets/lws-gencrypto.h b/include/libwebsockets/lws-gencrypto.h index 7a0b4d2ad..d82fb6083 100644 --- a/include/libwebsockets/lws-gencrypto.h +++ b/include/libwebsockets/lws-gencrypto.h @@ -90,7 +90,7 @@ enum lws_gencrypto_aes_tok { struct lws_gencrypto_keyelem { uint8_t *buf; - uint16_t len; + uint32_t len; }; diff --git a/include/libwebsockets/lws-genec.h b/include/libwebsockets/lws-genec.h index 955b48ab4..7db796e8f 100644 --- a/include/libwebsockets/lws-genec.h +++ b/include/libwebsockets/lws-genec.h @@ -59,7 +59,7 @@ enum enum_lws_dh_side { struct lws_ec_curves { const char *name; int tls_lib_nid; - short key_bytes; + uint16_t key_bytes; }; diff --git a/include/libwebsockets/lws-jwe.h b/include/libwebsockets/lws-jwe.h index ca7dc81b8..3798deeae 100644 --- a/include/libwebsockets/lws-jwe.h +++ b/include/libwebsockets/lws-jwe.h @@ -81,6 +81,9 @@ lws_jwe_render_compact(struct lws_jwe *jwe, char *out, size_t out_len); LWS_VISIBLE int lws_jwe_render_flattened(struct lws_jwe *jwe, char *out, size_t out_len); +LWS_VISIBLE LWS_EXTERN int +lws_jwe_json_parse(struct lws_jwe *jwe, const uint8_t *buf, int len, + char *temp, int *temp_len); /** * lws_jwe_auth_and_decrypt() - confirm and decrypt JWE diff --git a/include/libwebsockets/lws-jws.h b/include/libwebsockets/lws-jws.h index 27ce92bf7..c814bbde9 100644 --- a/include/libwebsockets/lws-jws.h +++ b/include/libwebsockets/lws-jws.h @@ -59,9 +59,11 @@ enum enum_jws_sig_elements { struct lws_jws_map { const char *buf[LWS_JWS_MAX_COMPACT_BLOCKS]; - uint16_t len[LWS_JWS_MAX_COMPACT_BLOCKS]; + uint32_t len[LWS_JWS_MAX_COMPACT_BLOCKS]; }; +#define LWS_JWS_MAX_SIGS 3 + struct lws_jws { struct lws_jwk *jwk; /* the struct lws_jwk containing the signing key */ struct lws_context *context; /* the lws context (used to get random) */ @@ -205,11 +207,17 @@ LWS_VISIBLE LWS_EXTERN int lws_jws_compact_decode(const char *in, int len, struct lws_jws_map *map, struct lws_jws_map *map_b64, char *out, int *out_len); -LWS_VISIBLE int +LWS_VISIBLE LWS_EXTERN int lws_jws_compact_encode(struct lws_jws_map *map_b64, /* b64-encoded */ const struct lws_jws_map *map, /* non-b64 */ char *buf, int *out_len); +LWS_VISIBLE LWS_EXTERN int +lws_jws_sig_confirm_json(const char *in, size_t len, + struct lws_jws *jws, struct lws_jwk *jwk, + struct lws_context *context, + char *temp, int *temp_len); + /** * lws_jws_write_flattened_json() - create flattened JSON sig * diff --git a/include/libwebsockets/lws-x509.h b/include/libwebsockets/lws-x509.h index f8e5cb0fa..8b4ec9b5b 100644 --- a/include/libwebsockets/lws-x509.h +++ b/include/libwebsockets/lws-x509.h @@ -1,7 +1,7 @@ /* * libwebsockets - small server side websockets and web server implementation * - * Copyright (C) 2010-2018 Andy Green + * Copyright (C) 2010 - 2019 Andy Green * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -65,6 +65,107 @@ union lws_tls_cert_info_results { } ns; }; +struct lws_x509_cert; +struct lws_jwk; + +/** + * lws_x509_create() - Allocate an lws_x509_cert object + * + * \param x509: pointer to lws_x509_cert pointer to be set to allocated object + * + * Allocates an lws_x509_cert object and set *x509 to point to it. + */ +LWS_VISIBLE LWS_EXTERN int +lws_x509_create(struct lws_x509_cert **x509); + +/** + * lws_x509_parse_from_pem() - Read one or more x509 certs in PEM format from memory + * + * \param x509: pointer to lws_x509_cert object + * \param pem: pointer to PEM format content + * \param len: length of PEM format content + * + * Parses PEM certificates in memory into a native x509 representation for the + * TLS library. If there are multiple PEM certs concatenated, they are all + * read into the same object and exist as a "chain". + * + * IMPORTANT for compatibility with mbedtls, the last used byte of \p pem + * must be '\0' and the \p len must include it. + * + * Returns 0 if all went OK. + */ +LWS_VISIBLE LWS_EXTERN int +lws_x509_parse_from_pem(struct lws_x509_cert *x509, const void *pem, size_t len); + +/** + * lws_x509_verify() - Validate signing relationship between one or more certs + * and a trusted CA cert + * + * \param x509: pointer to lws_x509_cert object, may contain multiple + * \param trusted: a single, trusted cert object that we are checking for + * \param common_name: NULL, or required CN (Common Name) of \p x509 + * + * Returns 0 if the cert or certs in \p x509 represent a complete chain that is + * ultimately signed by the cert in \p trusted. Returns nonzero if that's not + * the case. + */ +LWS_VISIBLE LWS_EXTERN int +lws_x509_verify(struct lws_x509_cert *x509, struct lws_x509_cert *trusted, + const char *common_name); + +/** + * lws_x509_public_to_jwk() - Copy the public key out of a cert and into a JWK + * + * \param jwk: pointer to the jwk to initialize and set to the public key + * \param x509: pointer to lws_x509_cert object that has the public key + * \param curves: NULL to disallow EC, else a comma-separated list of valid + * curves using the JWA naming, eg, "P-256,P-384,P-521". + * \param rsabits: minimum number of RSA bits required in the cert if RSA + * + * Returns 0 if JWK was set to the certificate public key correctly and the + * curve / the RSA key size was acceptable. Automatically produces an RSA or + * EC JWK depending on what the cert had. + */ +LWS_VISIBLE LWS_EXTERN int +lws_x509_public_to_jwk(struct lws_jwk *jwk, struct lws_x509_cert *x509, + const char *curves, int rsabits); + +/** + * lws_x509_jwk_privkey_pem() - Copy a private key PEM into a jwk that has the + * public part already + * + * \param jwk: pointer to the jwk to initialize and set to the public key + * \param pem: pointer to PEM private key in memory + * \param len: length of PEM private key in memory + * \param passphrase: NULL or passphrase needed to decrypt private key + * + * IMPORTANT for compatibility with mbedtls, the last used byte of \p pem + * must be '\0' and the \p len must include it. + * + * Returns 0 if the private key was successfully added to the JWK, else + * nonzero if failed. + * + * The PEM image in memory is zeroed down on both successful and failed exits. + * The caller should take care to zero down passphrase if used. + */ +LWS_VISIBLE LWS_EXTERN int +lws_x509_jwk_privkey_pem(struct lws_jwk *jwk, void *pem, size_t len, + const char *passphrase); + +/** + * lws_x509_destroy() - Destroy a previously allocated lws_x509_cert object + * + * \param x509: pointer to lws_x509_cert pointer + * + * Deallocates an lws_x509_cert object and sets its pointer to NULL. + */ +LWS_VISIBLE LWS_EXTERN void +lws_x509_destroy(struct lws_x509_cert **x509); + +LWS_VISIBLE LWS_EXTERN int +lws_x509_info(struct lws_x509_cert *x509, enum lws_tls_cert_info type, + union lws_tls_cert_info_results *buf, size_t len); + /** * lws_tls_peer_cert_info() - get information from the peer's TLS cert * diff --git a/lib/core/context.c b/lib/core/context.c index e36e942e8..6c8d16ef8 100644 --- a/lib/core/context.c +++ b/lib/core/context.c @@ -607,7 +607,7 @@ lws_create_vhost(struct lws_context *context, #ifdef LWS_WITH_UNIX_SOCK if (LWS_UNIX_SOCK_ENABLED(vh)) { - lwsl_notice("Creating Vhost '%s' path \"%s\", %d protocols\n", + lwsl_info("Creating Vhost '%s' path \"%s\", %d protocols\n", vh->name, vh->iface, vh->count_protocols); } else #endif @@ -623,7 +623,7 @@ lws_create_vhost(struct lws_context *context, lws_snprintf(buf, sizeof(buf), "port %u", info->port); break; } - lwsl_notice("Creating Vhost '%s' %s, %d protocols, IPv6 %s\n", + lwsl_info("Creating Vhost '%s' %s, %d protocols, IPv6 %s\n", vh->name, buf, vh->count_protocols, LWS_IPV6_ENABLED(vh) ? "on" : "off"); } diff --git a/lib/jose/jwe/enc/aescbc.c b/lib/jose/jwe/enc/aescbc.c index 8776262cd..a08dc82ac 100644 --- a/lib/jose/jwe/enc/aescbc.c +++ b/lib/jose/jwe/enc/aescbc.c @@ -38,7 +38,7 @@ lws_jwe_encrypt_cbc_hs(struct lws_jwe *jwe, uint8_t *cek, /* Caller must have prepared space for the results */ - if (jwe->jws.map.len[LJWE_ATAG] != hlen / 2) { + if (jwe->jws.map.len[LJWE_ATAG] != (unsigned int)hlen / 2) { lwsl_notice("%s: expected tag len %d, got %d\n", __func__, hlen / 2, jwe->jws.map.len[LJWE_ATAG]); return -1; @@ -165,7 +165,7 @@ lws_jwe_auth_and_decrypt_cbc_hs(struct lws_jwe *jwe, uint8_t *enc_cek, /* Some sanity checks on what came in */ - if (jwe->jws.map.len[LJWE_ATAG] != hlen / 2) { + if (jwe->jws.map.len[LJWE_ATAG] != (unsigned int)hlen / 2) { lwsl_notice("%s: expected tag len %d, got %d\n", __func__, hlen / 2, jwe->jws.map.len[LJWE_ATAG]); return -1; diff --git a/lib/jose/jwe/jwe-ecdh-es-aeskw.c b/lib/jose/jwe/jwe-ecdh-es-aeskw.c index 2d508ff08..4be1a5616 100644 --- a/lib/jose/jwe/jwe-ecdh-es-aeskw.c +++ b/lib/jose/jwe/jwe-ecdh-es-aeskw.c @@ -541,7 +541,7 @@ lws_jwe_auth_and_decrypt_ecdh(struct lws_jwe *jwe) /* Confirm space for EKEY */ - if (jwe->jws.map.len[LJWE_EKEY] < enc_hlen) { + if (jwe->jws.map.len[LJWE_EKEY] < (unsigned int)enc_hlen) { lwsl_err("%s: missing EKEY\n", __func__); goto bail; diff --git a/lib/jose/jwe/jwe.c b/lib/jose/jwe/jwe.c index 87d95cf9b..bb8446e40 100644 --- a/lib/jose/jwe/jwe.c +++ b/lib/jose/jwe/jwe.c @@ -26,47 +26,40 @@ #include "jose/private.h" #include "jose/jwe/private.h" -#if 0 -static const char * const jwe_complete_tokens[] = { +/* + * Currently only support flattened or compact (implicitly single signature) + */ + +static const char * const jwe_json[] = { "protected", - "recipients[].header", - "recipients[].header.alg", - "recipients[].header.kid", - "recipients[].encrypted_key", "iv", "ciphertext", "tag", + "encrypted_key" }; enum enum_jwe_complete_tokens { LWS_EJCT_PROTECTED, - LWS_EJCT_HEADER, - LWS_EJCT_HEADER_ALG, - LWS_EJCT_HEADER_KID, - LWS_EJCT_RECIP_ENC_KEY, LWS_EJCT_IV, LWS_EJCT_CIPHERTEXT, LWS_EJCT_TAG, + LWS_EJCT_RECIP_ENC_KEY, }; -struct complete_cb_args { - struct lws_jws_map *map; - struct lws_jws_map *map_b64; - char *out; - int out_len; +/* parse a JWS complete or flattened JSON object */ + +struct jwe_cb_args { + struct lws_jws *jws; + + char *temp; + int *temp_len; }; - -static int -do_map(struct complete_cb_args *args, int index, char *b64, int len) -{ - return 0; -} - static signed char -lws_jwe_parse_complete_cb(struct lejp_ctx *ctx, char reason) +lws_jwe_json_cb(struct lejp_ctx *ctx, char reason) { - struct complete_cb_args *args = (struct complete_cb_args *)ctx->user; + struct jwe_cb_args *args = (struct jwe_cb_args *)ctx->user; + int n, m; if (!(reason & LEJP_FLAG_CB_IS_VALUE) || !ctx->path_match) return 0; @@ -75,49 +68,95 @@ lws_jwe_parse_complete_cb(struct lejp_ctx *ctx, char reason) /* strings */ - case LWS_EJCT_PROTECTED: - case LWS_EJCT_HEADER: - case LWS_EJCT_HEADER_ALG: - case LWS_EJCT_HEADER_KID: - case LWS_EJCT_RECIP_ENC_KEY: - case LWS_EJCT_IV: - case LWS_EJCT_CIPHERTEXT: - case LWS_EJCT_TAG: + case LWS_EJCT_PROTECTED: /* base64u: JOSE: must contain 'alg' */ + m = LJWS_JOSE; + goto append_string; + case LWS_EJCT_IV: /* base64u */ + m = LJWE_IV; + goto append_string; + case LWS_EJCT_CIPHERTEXT: /* base64u */ + m = LJWE_CTXT; + goto append_string; + case LWS_EJCT_TAG: /* base64u */ + m = LJWE_ATAG; + goto append_string; + case LWS_EJCT_RECIP_ENC_KEY: /* base64u */ + m = LJWE_EKEY; + goto append_string; + + default: + return -1; + } + + return 0; + +append_string: + + if (*args->temp_len < ctx->npos) { + lwsl_err("%s: out of parsing space\n", __func__); + return -1; + } + + /* + * We keep both b64u and decoded in temp mapped using map / map_b64, + * the jws signature is actually over the b64 content not the plaintext, + * and we can't do it until we see the protected alg. + */ + + if (!args->jws->map_b64.buf[m]) { + args->jws->map_b64.buf[m] = args->temp; + args->jws->map_b64.len[m] = 0; + } + + memcpy(args->temp, ctx->buf, ctx->npos); + args->temp += ctx->npos; + *args->temp_len -= ctx->npos; + args->jws->map_b64.len[m] += ctx->npos; + + if (reason == LEJPCB_VAL_STR_END) { + args->jws->map.buf[m] = args->temp; + + n = lws_b64_decode_string_len( + (const char *)args->jws->map_b64.buf[m], + args->jws->map_b64.len[m], + (char *)args->temp, *args->temp_len); + if (n < 0) { + lwsl_err("%s: b64 decode failed\n", __func__); + return -1; + } + + args->temp += n; + *args->temp_len -= n; + args->jws->map.len[m] = n; } return 0; } -LWS_VISIBLE int -lws_jws_complete_decode(const char *json_in, int len, - struct lws_jws_map *map, - struct lws_jws_map *map_b64, char *out, - int out_len) +int +lws_jwe_json_parse(struct lws_jwe *jwe, const uint8_t *buf, int len, + char *temp, int *temp_len) { - - struct complete_cb_args args; + struct jwe_cb_args args; struct lejp_ctx jctx; - int blocks, n, m = 0; + int m = 0; - if (!map_b64) - map_b64 = map; + args.jws = &jwe->jws; + args.temp = temp; + args.temp_len = temp_len; - memset(map_b64, 0, sizeof(*map_b64)); - memset(map, 0, sizeof(*map)); + lejp_construct(&jctx, lws_jwe_json_cb, &args, jwe_json, + LWS_ARRAY_SIZE(jwe_json)); - args.map = map; - args.map_b64 = map_b64; - args.out = out; - args.out_len = out_len; - - lejp_construct(&jctx, lws_jwe_parse_complete_cb, &args, - jwe_complete_tokens, - LWS_ARRAY_SIZE(jwe_complete_tokens)); - - m = (int)(signed char)lejp_parse(&jctx, (uint8_t *)json_in, len); + m = (int)(signed char)lejp_parse(&jctx, (uint8_t *)buf, len); lejp_destruct(&jctx); + if (m < 0) { + lwsl_notice("%s: parse returned %d\n", __func__, m); + return -1; + } + + return 0; } -#endif void lws_jwe_init(struct lws_jwe *jwe, struct lws_context *context) @@ -294,6 +333,14 @@ lws_jwe_auth_and_decrypt(struct lws_jwe *jwe, char *temp, int *temp_len) return -1; } + if (!jwe->jose.alg) { + lwsl_err("%s: no jose.alg: %.*s\n", __func__, + jwe->jws.map.len[LJWS_JOSE], + jwe->jws.map.buf[LJWS_JOSE]); + + return -1; + } + valid_aescbc_hmac = jwe->jose.enc_alg && jwe->jose.enc_alg->algtype_crypto == LWS_JOSE_ENCTYPE_AES_CBC && (jwe->jose.enc_alg->hmac_type == LWS_GENHMAC_TYPE_SHA256 || @@ -673,8 +720,8 @@ static int protected_idx[] = { LWS_VISIBLE int lws_jwe_render_flattened(struct lws_jwe *jwe, char *out, size_t out_len) { - char buf[3072], *p1, *end1; - int m, n, jlen; + char buf[3072], *p1, *end1, protected[128]; + int m, n, jlen, plen; jlen = lws_jose_render(&jwe->jose, jwe->jws.jwk, buf, sizeof(buf)); if (jlen < 0) { @@ -694,9 +741,13 @@ lws_jwe_render_flattened(struct lws_jwe *jwe, char *out, size_t out_len) * The protected header is b64url encoding of the JOSE header part */ + plen = lws_snprintf(protected, sizeof(protected), + "{\"alg\":\"%s\",\"enc\":\"%s\"}", + jwe->jose.alg->alg, jwe->jose.enc_alg->alg); + p1 += lws_snprintf(p1, end1 - p1, "{\"protected\":\""); jwe->jws.map_b64.buf[LJWS_JOSE] = p1; - n = lws_jws_base64_enc(buf, jlen, p1, end1 - p1); + n = lws_jws_base64_enc(protected, plen, p1, end1 - p1); if (n < 0) { lwsl_notice("%s: failed to encode protected\n", __func__); goto bail; @@ -710,7 +761,7 @@ lws_jwe_render_flattened(struct lws_jwe *jwe, char *out, size_t out_len) for (m = 0; m < (int)LWS_ARRAY_SIZE(protected_en); m++) if (jwe->jws.map.buf[protected_idx[m]]) { - p1 += lws_snprintf(p1, end1 - p1, ",\"%s\":\"", + p1 += lws_snprintf(p1, end1 - p1, ",\n\"%s\":\"", protected_en[m]); //jwe->jws.map_b64.buf[protected_idx[m]] = p1; n = lws_jws_base64_enc(jwe->jws.map.buf[protected_idx[m]], diff --git a/lib/jose/jwk/jwk.c b/lib/jose/jwk/jwk.c index 10217fe8e..5b59a7911 100644 --- a/lib/jose/jwk/jwk.c +++ b/lib/jose/jwk/jwk.c @@ -562,7 +562,7 @@ lws_jwk_dup_oct(struct lws_jwk *jwk, const void *key, int len) LWS_VISIBLE int lws_jwk_generate(struct lws_context *context, struct lws_jwk *jwk, - enum lws_gencrypto_kty kty, int bits, const char *curve) + enum lws_gencrypto_kty kty, int bits, const char *curve) { int n; diff --git a/lib/jose/jws/jose.c b/lib/jose/jws/jose.c index b07c0d2ee..2cb337be1 100644 --- a/lib/jose/jws/jose.c +++ b/lib/jose/jws/jose.c @@ -485,7 +485,7 @@ lws_jose_render(struct lws_jose *jose, struct lws_jwk *aux_jwk, case LJJHI_ZIP: /* JWE only: Optional: string ("DEF"=deflate) */ if (jose->e[n].buf) { out += lws_snprintf(out, end - out, - "%c\"%s\":\"%s\"", sub ? ',' : ' ', + "%s\"%s\":\"%s\"", sub ? ",\n" : "", jws_jose[n], jose->e[n].buf); sub = 1; } @@ -500,7 +500,7 @@ lws_jose_render(struct lws_jose *jose, struct lws_jwk *aux_jwk, case LJJHI_P2S: /* Additional arg for JWE PBES2: b64url: salt */ if (jose->e[n].buf) { out += lws_snprintf(out, end - out, - "%c\"%s\":\"", sub ? ',' : ' ', + "%s\"%s\":\"", sub ? ",\n" : "", jws_jose[n]); sub = 1; m = lws_b64_encode_string_url((const char *) @@ -519,7 +519,7 @@ lws_jose_render(struct lws_jose *jose, struct lws_jwk *aux_jwk, case LJJHI_X5C: /* Optional: base64 (NOT -url): actual cert */ if (jose->e[n].buf) { out += lws_snprintf(out, end - out, - "%c\"%s\":\"", sub ? ',' : ' ', + "%s\"%s\":\"", sub ? ",\n" : "", jws_jose[n]); sub = 1; m = lws_b64_encode_string((const char *) @@ -536,11 +536,11 @@ lws_jose_render(struct lws_jose *jose, struct lws_jwk *aux_jwk, case LJJHI_JWK: /* Optional: jwk JSON object: public key: */ jwk = n == LJJHI_EPK ? &jose->recipient[0].jwk_ephemeral : aux_jwk; - if (!jwk) - return -1; + if (!jwk || !jwk->kty) + break; - out += lws_snprintf(out, end - out, "%c\"%s\":", - sub ? ',' : ' ', jws_jose[n]); + out += lws_snprintf(out, end - out, "%s\"%s\":", + sub ? ",\n" : "", jws_jose[n]); sub = 1; vl = end - out; m = lws_jwk_export(jwk, 0, out, &vl); @@ -559,12 +559,12 @@ lws_jose_render(struct lws_jose *jose, struct lws_jwk *aux_jwk, break; out += lws_snprintf(out, end - out, - "%c\"%s\":[", sub ? ',' : ' ', jws_jose[n]); + "%s\"%s\":[", sub ? ",\n" : "", jws_jose[n]); sub = 1; m = 0; f = 1; - while (m < jose->e[n].len && (end - out) > 1) { + while ((unsigned int)m < jose->e[n].len && (end - out) > 1) { if (jose->e[n].buf[m] == ' ') { if (!f) *out++ = '\"'; diff --git a/lib/jose/jws/jws.c b/lib/jose/jws/jws.c index 30574f12b..5d90ca179 100644 --- a/lib/jose/jws/jws.c +++ b/lib/jose/jws/jws.c @@ -21,13 +21,20 @@ #include "core/private.h" #include "private.h" -#if 0 + +/* + * Currently only support flattened or compact (implicitly single signature) + */ + static const char * const jws_json[] = { - "protected", - "header", - "payload", - "signature", - "signatures", + "protected", /* base64u */ + "header", /* JSON */ + "payload", /* base64u payload */ + "signature", /* base64u signature */ + + //"signatures[].protected", + //"signatures[].header", + //"signatures[].signature" }; enum lws_jws_json_tok { @@ -35,18 +42,121 @@ enum lws_jws_json_tok { LJWSJT_HEADER, LJWSJT_PAYLOAD, LJWSJT_SIGNATURE, - LJWSJT_SIGNATURES, + + // LJWSJT_SIGNATURES_PROTECTED, + // LJWSJT_SIGNATURES_HEADER, + // LJWSJT_SIGNATURES_SIGNATURE, }; /* parse a JWS complete or flattened JSON object */ +struct jws_cb_args { + struct lws_jws *jws; + + char *temp; + int *temp_len; +}; + static signed char lws_jws_json_cb(struct lejp_ctx *ctx, char reason) { - struct jose_cb_args *args = (struct jose_cb_args *)ctx->user; - int n; + struct jws_cb_args *args = (struct jws_cb_args *)ctx->user; + int n, m; + + if (!(reason & LEJP_FLAG_CB_IS_VALUE) || !ctx->path_match) + return 0; + + switch (ctx->path_match - 1) { + + /* strings */ + + case LJWSJT_PROTECTED: /* base64u: JOSE: must contain 'alg' */ + m = LJWS_JOSE; + goto append_string; + case LJWSJT_PAYLOAD: /* base64u */ + m = LJWS_PYLD; + goto append_string; + case LJWSJT_SIGNATURE: /* base64u */ + m = LJWS_SIG; + goto append_string; + + case LJWSJT_HEADER: /* unprotected freeform JSON */ + break; + + default: + return -1; + } + + return 0; + +append_string: + + if (*args->temp_len < ctx->npos) { + lwsl_err("%s: out of parsing space\n", __func__); + return -1; + } + + /* + * We keep both b64u and decoded in temp mapped using map / map_b64, + * the jws signature is actually over the b64 content not the plaintext, + * and we can't do it until we see the protected alg. + */ + + if (!args->jws->map_b64.buf[m]) { + args->jws->map_b64.buf[m] = args->temp; + args->jws->map_b64.len[m] = 0; + } + + memcpy(args->temp, ctx->buf, ctx->npos); + args->temp += ctx->npos; + *args->temp_len -= ctx->npos; + args->jws->map_b64.len[m] += ctx->npos; + + if (reason == LEJPCB_VAL_STR_END) { + args->jws->map.buf[m] = args->temp; + + n = lws_b64_decode_string_len( + (const char *)args->jws->map_b64.buf[m], + args->jws->map_b64.len[m], + (char *)args->temp, *args->temp_len); + if (n < 0) { + lwsl_err("%s: b64 decode failed\n", __func__); + return -1; + } + + args->temp += n; + *args->temp_len -= n; + args->jws->map.len[m] = n; + } + + return 0; } -#endif + +static int +lws_jws_json_parse(struct lws_jws *jws, const uint8_t *buf, int len, + char *temp, int *temp_len) +{ + struct jws_cb_args args; + struct lejp_ctx jctx; + int m = 0; + + args.jws = jws; + args.temp = temp; + args.temp_len = temp_len; + + lejp_construct(&jctx, lws_jws_json_cb, &args, jws_json, + LWS_ARRAY_SIZE(jws_json)); + + m = (int)(signed char)lejp_parse(&jctx, (uint8_t *)buf, len); + lejp_destruct(&jctx); + if (m < 0) { + lwsl_notice("%s: parse returned %d\n", __func__, m); + return -1; + } + + return 0; +} + LWS_VISIBLE void lws_jws_init(struct lws_jws *jws, struct lws_jwk *jwk, struct lws_context *context) @@ -608,8 +718,22 @@ lws_jws_sig_confirm_compact(struct lws_jws_map *map, struct lws_jwk *jwk, return lws_jws_sig_confirm(&map_b64, map, jwk, context); } +int +lws_jws_sig_confirm_json(const char *in, size_t len, + struct lws_jws *jws, struct lws_jwk *jwk, + struct lws_context *context, + char *temp, int *temp_len) +{ + if (lws_jws_json_parse(jws, (const uint8_t *)in, len, temp, temp_len)) { + lwsl_err("%s: lws_jws_json_parse failed\n", __func__); -LWS_VISIBLE int + return -1; + } + return lws_jws_sig_confirm(&jws->map_b64, &jws->map, jwk, context); +} + + +int lws_jws_sign_from_b64(struct lws_jose *jose, struct lws_jws *jws, char *b64_sig, size_t sig_len) { @@ -768,18 +892,20 @@ lws_jws_write_flattened_json(struct lws_jws *jws, char *flattened, size_t len) if (len < 1) return 1; - n += lws_snprintf(flattened + n, len - n , "{\"payload\": \"%s\",\n", + n += lws_snprintf(flattened + n, len - n , "{\"payload\": \"%.*s\",\n", + jws->map_b64.len[LJWS_PYLD], jws->map_b64.buf[LJWS_PYLD]); - n += lws_snprintf(flattened + n, len - n , " \"protected\": \"%s\",\n", - jws->map_b64.buf[LJWS_JOSE]); + n += lws_snprintf(flattened + n, len - n , " \"protected\": \"%.*s\",\n", + jws->map_b64.len[LJWS_JOSE], + jws->map_b64.buf[LJWS_JOSE]); if (jws->map_b64.buf[LJWS_UHDR]) - n += lws_snprintf(flattened + n, len - n , " \"header\": %s,\n", - jws->map_b64.buf[LJWS_UHDR]); + n += lws_snprintf(flattened + n, len - n , " \"header\": %.*s,\n", + jws->map_b64.len[LJWS_UHDR], jws->map_b64.buf[LJWS_UHDR]); - n += lws_snprintf(flattened + n, len - n , " \"signature\": \"%s\"}\n", - jws->map_b64.buf[LJWS_SIG]); + n += lws_snprintf(flattened + n, len - n , " \"signature\": \"%.*s\"}\n", + jws->map_b64.len[LJWS_SIG], jws->map_b64.buf[LJWS_SIG]); return (n >= len - 1); } diff --git a/lib/tls/lws-gencrypto-common.c b/lib/tls/lws-gencrypto-common.c index 65f074069..243c8abed 100644 --- a/lib/tls/lws-gencrypto-common.c +++ b/lib/tls/lws-gencrypto-common.c @@ -563,6 +563,7 @@ static const struct lws_jose_jwe_alg lws_gencrypto_jwe_enc_map[] = { LWS_JOSE_ENCTYPE_AES_GCM, "A256GCM", NULL, 256, 256, 96 }, + { 0, 0, 0, 0, NULL, NULL, 0, 0, 0 } /* sentinel */ }; LWS_VISIBLE int diff --git a/lib/tls/lws-genec-common.c b/lib/tls/lws-genec-common.c index d3e6da225..3be6a2ed1 100644 --- a/lib/tls/lws-genec-common.c +++ b/lib/tls/lws-genec-common.c @@ -1,7 +1,7 @@ /* * libwebsockets - generic EC api hiding the backend - common parts * - * Copyright (C) 2017 - 2018 Andy Green + * Copyright (C) 2017 - 2019 Andy Green * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -40,6 +40,61 @@ lws_genec_curve(const struct lws_ec_curves *table, const char *name) return NULL; } +extern const struct lws_ec_curves lws_ec_curves[]; + +int +lws_genec_confirm_curve_allowed_by_tls_id(const char *allowed, int id, + struct lws_jwk *jwk) +{ + struct lws_tokenize ts; + lws_tokenize_elem e; + int n, len; + + lws_tokenize_init(&ts, allowed, LWS_TOKENIZE_F_COMMA_SEP_LIST | + LWS_TOKENIZE_F_MINUS_NONTERM); + ts.len = strlen(allowed); + do { + e = lws_tokenize(&ts); + switch (e) { + case LWS_TOKZE_TOKEN: + n = 0; + while (lws_ec_curves[n].name) { + if (id != lws_ec_curves[n].tls_lib_nid) { + n++; + continue; + } + lwsl_info("match curve %s\n", + lws_ec_curves[n].name); + len = strlen(lws_ec_curves[n].name); + jwk->e[LWS_GENCRYPTO_EC_KEYEL_CRV].len = len; + jwk->e[LWS_GENCRYPTO_EC_KEYEL_CRV].buf = + lws_malloc(len + 1, "cert crv"); + if (!jwk->e[LWS_GENCRYPTO_EC_KEYEL_CRV].buf) { + lwsl_err("%s: OOM\n", __func__); + return 1; + } + memcpy(jwk->e[LWS_GENCRYPTO_EC_KEYEL_CRV].buf, + lws_ec_curves[n].name, len + 1); + return 0; + } + break; + + case LWS_TOKZE_DELIMITER: + break; + + default: /* includes ENDED */ + lwsl_err("%s: malformed or curve name in list\n", + __func__); + + return -1; + } + } while (e > 0); + + lwsl_err("%s: unsupported curve group nid %d\n", __func__, n); + + return -1; +} + LWS_VISIBLE void lws_genec_destroy_elements(struct lws_gencrypto_keyelem *el) { diff --git a/lib/tls/mbedtls/lws-genec.c b/lib/tls/mbedtls/lws-genec.c index 604905f6c..c7ff5e279 100644 --- a/lib/tls/mbedtls/lws-genec.c +++ b/lib/tls/mbedtls/lws-genec.c @@ -374,8 +374,8 @@ bail1: LWS_VISIBLE LWS_EXTERN int lws_genecdsa_hash_sign_jws(struct lws_genec_ctx *ctx, const uint8_t *in, - enum lws_genhash_types hash_type, int keybits, - uint8_t *sig, size_t sig_len) + enum lws_genhash_types hash_type, int keybits, + uint8_t *sig, size_t sig_len) { int n, keybytes = lws_gencrypto_bits_to_bytes(keybits); size_t hlen = lws_genhash_size(hash_type); @@ -515,126 +515,3 @@ lws_genecdh_compute_shared_secret(struct lws_genec_ctx *ctx, uint8_t *ss, return 0; } - - -#if 0 -LWS_VISIBLE int -lws_genec_public_decrypt(struct lws_genec_ctx *ctx, const uint8_t *in, - size_t in_len, uint8_t *out, size_t out_max) -{ - size_t olen = 0; - int n; - - ctx->ctx->len = in_len; - n = mbedtls_rsa_rsaes_pkcs1_v15_decrypt(ctx->ctx, NULL, NULL, - MBEDTLS_RSA_PUBLIC, - &olen, in, out, out_max); - if (n) { - lwsl_notice("%s: -0x%x\n", __func__, -n); - - return -1; - } - - return olen; -} - -LWS_VISIBLE int -lws_genec_public_encrypt(struct lws_genec_ctx *ctx, const uint8_t *in, - size_t in_len, uint8_t *out) -{ - int n; - - //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: in_len: %d\n", __func__, -n, - (int)in_len); - - return -1; - } - - return 0; -} - - -LWS_VISIBLE int -lws_genec_render_pkey_asn1(struct lws_genec_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_GENCRYPTO_RSA_KEYEL_COUNT] = { - &ctx->ctx->N, &ctx->ctx->E, &ctx->ctx->D, &ctx->ctx->P, - &ctx->ctx->Q, &ctx->ctx->DP, &ctx->ctx->DQ, - &ctx->ctx->QP, - }; - int n; - - /* 30 82 - sequence - * 09 29 <-- length(0x0929) less 4 bytes - * 02 01 <- length (1) - * 00 - * 02 82 - * 02 01 <- length (513) N - * ... - * - * 02 03 <- length (3) E - * 01 00 01 - * - * 02 82 - * 02 00 <- length (512) D P Q EXP1 EXP2 COEFF - * - * */ - - *p++ = 0x30; - *p++ = 0x82; - totlen = p; - p += 2; - - *p++ = 0x02; - *p++ = 0x01; - *p++ = 0x00; - - for (n = 0; n < LWS_GENCRYPTO_RSA_KEYEL_COUNT; n++) { - int m = mbedtls_mpi_size(mpi[n]); - uint8_t *elen; - - *p++ = 0x02; - elen = p; - if (m < 0x7f) - *p++ = m; - else { - *p++ = 0x82; - *p++ = m >> 8; - *p++ = m & 0xff; - } - - if (p + m > end) - return -1; - - mbedtls_mpi_write_binary(mpi[n], p, m); - if (p[0] & 0x80) { - p[0] = 0x00; - mbedtls_mpi_write_binary(mpi[n], &p[1], m); - m++; - } - if (m < 0x7f) - *elen = m; - else { - *elen++ = 0x82; - *elen++ = m >> 8; - *elen = m & 0xff; - } - p += m; - } - - n = lws_ptr_diff(p, pkey_asn1); - - *totlen++ = (n - 4) >> 8; - *totlen = (n - 4) & 0xff; - - return n; -} -#endif diff --git a/lib/tls/mbedtls/private.h b/lib/tls/mbedtls/private.h index 8d3d1b524..a87a4a433 100644 --- a/lib/tls/mbedtls/private.h +++ b/lib/tls/mbedtls/private.h @@ -21,6 +21,12 @@ * gencrypto mbedtls-specific helper declarations */ +#include + +struct lws_x509_cert { + mbedtls_x509_crt cert; /* has a .next for linked-list / chain */ +}; + mbedtls_md_type_t lws_gencrypto_mbedtls_hash_to_MD_TYPE(enum lws_genhash_types hash_type); diff --git a/lib/tls/mbedtls/ssl.c b/lib/tls/mbedtls/ssl.c index 82444bbaf..b4558ba3e 100644 --- a/lib/tls/mbedtls/ssl.c +++ b/lib/tls/mbedtls/ssl.c @@ -1,7 +1,7 @@ /* * libwebsockets - mbedTLS-specific lws apis * - * Copyright (C) 2010-2017 Andy Green + * 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 @@ -20,7 +20,7 @@ */ #include "core/private.h" -#include +#include "tls/mbedtls/private.h" void lws_tls_err_describe(void) @@ -321,168 +321,6 @@ __lws_tls_shutdown(struct lws *wsi) } } -static time_t -lws_tls_mbedtls_time_to_unix(mbedtls_x509_time *xtime) -{ - struct tm t; - - if (!xtime || !xtime->year || xtime->year < 0) - return (time_t)(long long)-1; - - memset(&t, 0, sizeof(t)); - - t.tm_year = xtime->year - 1900; - t.tm_mon = xtime->mon - 1; /* mbedtls months are 1+, tm are 0+ */ - t.tm_mday = xtime->day - 1; /* mbedtls days are 1+, tm are 0+ */ - t.tm_hour = xtime->hour; - t.tm_min = xtime->min; - t.tm_sec = xtime->sec; - t.tm_isdst = -1; - - return mktime(&t); -} - -static int -lws_tls_mbedtls_get_x509_name(mbedtls_x509_name *name, - union lws_tls_cert_info_results *buf, size_t len) -{ - while (name) { - if (MBEDTLS_OID_CMP(MBEDTLS_OID_AT_CN, &name->oid)) { - name = name->next; - continue; - } - - if (len - 1 < name->val.len) - return -1; - - memcpy(&buf->ns.name[0], name->val.p, name->val.len); - buf->ns.name[name->val.len] = '\0'; - buf->ns.len = name->val.len; - - return 0; - } - - return -1; -} - -static int -lws_tls_mbedtls_cert_info(mbedtls_x509_crt *x509, enum lws_tls_cert_info type, - union lws_tls_cert_info_results *buf, size_t len) -{ - if (!x509) - return -1; - - switch (type) { - case LWS_TLS_CERT_INFO_VALIDITY_FROM: - buf->time = lws_tls_mbedtls_time_to_unix(&x509->valid_from); - if (buf->time == (time_t)(long long)-1) - return -1; - break; - - case LWS_TLS_CERT_INFO_VALIDITY_TO: - buf->time = lws_tls_mbedtls_time_to_unix(&x509->valid_to); - if (buf->time == (time_t)(long long)-1) - return -1; - break; - - case LWS_TLS_CERT_INFO_COMMON_NAME: - return lws_tls_mbedtls_get_x509_name(&x509->subject, buf, len); - - case LWS_TLS_CERT_INFO_ISSUER_NAME: - return lws_tls_mbedtls_get_x509_name(&x509->issuer, buf, len); - - case LWS_TLS_CERT_INFO_USAGE: - buf->usage = x509->key_usage; - break; - - case LWS_TLS_CERT_INFO_OPAQUE_PUBLIC_KEY: - { - char *p = buf->ns.name; - size_t r = len, u; - - switch (mbedtls_pk_get_type(&x509->pk)) { - case MBEDTLS_PK_RSA: - { - mbedtls_rsa_context *rsa = mbedtls_pk_rsa(x509->pk); - - if (mbedtls_mpi_write_string(&rsa->N, 16, p, r, &u)) - return -1; - r -= u; - p += u; - if (mbedtls_mpi_write_string(&rsa->E, 16, p, r, &u)) - return -1; - - p += u; - buf->ns.len = lws_ptr_diff(p, buf->ns.name); - break; - } - case MBEDTLS_PK_ECKEY: - { - mbedtls_ecp_keypair *ecp = mbedtls_pk_ec(x509->pk); - - if (mbedtls_mpi_write_string(&ecp->Q.X, 16, p, r, &u)) - return -1; - r -= u; - p += u; - if (mbedtls_mpi_write_string(&ecp->Q.Y, 16, p, r, &u)) - return -1; - r -= u; - p += u; - if (mbedtls_mpi_write_string(&ecp->Q.Z, 16, p, r, &u)) - return -1; - p += u; - buf->ns.len = lws_ptr_diff(p, buf->ns.name); - break; - } - default: - lwsl_notice("%s: x509 has unsupported pubkey type %d\n", - __func__, - mbedtls_pk_get_type(&x509->pk)); - - return -1; - } - break; - } - - default: - return -1; - } - - return 0; -} - -LWS_VISIBLE LWS_EXTERN int -lws_tls_vhost_cert_info(struct lws_vhost *vhost, enum lws_tls_cert_info type, - union lws_tls_cert_info_results *buf, size_t len) -{ - mbedtls_x509_crt *x509 = ssl_ctx_get_mbedtls_x509_crt(vhost->tls.ssl_ctx); - - return lws_tls_mbedtls_cert_info(x509, type, buf, len); -} - -LWS_VISIBLE int -lws_tls_peer_cert_info(struct lws *wsi, enum lws_tls_cert_info type, - union lws_tls_cert_info_results *buf, size_t len) -{ - mbedtls_x509_crt *x509; - - wsi = lws_get_network_wsi(wsi); - - x509 = ssl_get_peer_mbedtls_x509_crt(wsi->tls.ssl); - - if (!x509) - return -1; - - switch (type) { - case LWS_TLS_CERT_INFO_VERIFIED: - buf->verified = SSL_get_verify_result(wsi->tls.ssl) == X509_V_OK; - return 0; - default: - return lws_tls_mbedtls_cert_info(x509, type, buf, len); - } - - return -1; -} static int tops_fake_POLLIN_for_buffered_mbedtls(struct lws_context_per_thread *pt) diff --git a/lib/tls/mbedtls/x509.c b/lib/tls/mbedtls/x509.c new file mode 100644 index 000000000..70faf1253 --- /dev/null +++ b/lib/tls/mbedtls/x509.c @@ -0,0 +1,411 @@ +/* + * libwebsockets - mbedTLS-specific lws apis + * + * Copyright (C) 2010-2019 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 "tls/mbedtls/private.h" +#include + +static time_t +lws_tls_mbedtls_time_to_unix(mbedtls_x509_time *xtime) +{ + struct tm t; + + if (!xtime || !xtime->year || xtime->year < 0) + return (time_t)(long long)-1; + + memset(&t, 0, sizeof(t)); + + t.tm_year = xtime->year - 1900; + t.tm_mon = xtime->mon - 1; /* mbedtls months are 1+, tm are 0+ */ + t.tm_mday = xtime->day - 1; /* mbedtls days are 1+, tm are 0+ */ + t.tm_hour = xtime->hour; + t.tm_min = xtime->min; + t.tm_sec = xtime->sec; + t.tm_isdst = -1; + + return mktime(&t); +} + +static int +lws_tls_mbedtls_get_x509_name(mbedtls_x509_name *name, + union lws_tls_cert_info_results *buf, size_t len) +{ + while (name) { + if (MBEDTLS_OID_CMP(MBEDTLS_OID_AT_CN, &name->oid)) { + name = name->next; + continue; + } + + if (len - 1 < name->val.len) + return -1; + + memcpy(&buf->ns.name[0], name->val.p, name->val.len); + buf->ns.name[name->val.len] = '\0'; + buf->ns.len = name->val.len; + + return 0; + } + + return -1; +} + +static int +lws_tls_mbedtls_cert_info(mbedtls_x509_crt *x509, enum lws_tls_cert_info type, + union lws_tls_cert_info_results *buf, size_t len) +{ + if (!x509) + return -1; + + switch (type) { + case LWS_TLS_CERT_INFO_VALIDITY_FROM: + buf->time = lws_tls_mbedtls_time_to_unix(&x509->valid_from); + if (buf->time == (time_t)(long long)-1) + return -1; + break; + + case LWS_TLS_CERT_INFO_VALIDITY_TO: + buf->time = lws_tls_mbedtls_time_to_unix(&x509->valid_to); + if (buf->time == (time_t)(long long)-1) + return -1; + break; + + case LWS_TLS_CERT_INFO_COMMON_NAME: + return lws_tls_mbedtls_get_x509_name(&x509->subject, buf, len); + + case LWS_TLS_CERT_INFO_ISSUER_NAME: + return lws_tls_mbedtls_get_x509_name(&x509->issuer, buf, len); + + case LWS_TLS_CERT_INFO_USAGE: + buf->usage = x509->key_usage; + break; + + case LWS_TLS_CERT_INFO_OPAQUE_PUBLIC_KEY: + { + char *p = buf->ns.name; + size_t r = len, u; + + switch (mbedtls_pk_get_type(&x509->pk)) { + case MBEDTLS_PK_RSA: + { + mbedtls_rsa_context *rsa = mbedtls_pk_rsa(x509->pk); + + if (mbedtls_mpi_write_string(&rsa->N, 16, p, r, &u)) + return -1; + r -= u; + p += u; + if (mbedtls_mpi_write_string(&rsa->E, 16, p, r, &u)) + return -1; + + p += u; + buf->ns.len = lws_ptr_diff(p, buf->ns.name); + break; + } + case MBEDTLS_PK_ECKEY: + { + mbedtls_ecp_keypair *ecp = mbedtls_pk_ec(x509->pk); + + if (mbedtls_mpi_write_string(&ecp->Q.X, 16, p, r, &u)) + return -1; + r -= u; + p += u; + if (mbedtls_mpi_write_string(&ecp->Q.Y, 16, p, r, &u)) + return -1; + r -= u; + p += u; + if (mbedtls_mpi_write_string(&ecp->Q.Z, 16, p, r, &u)) + return -1; + p += u; + buf->ns.len = lws_ptr_diff(p, buf->ns.name); + break; + } + default: + lwsl_notice("%s: x509 has unsupported pubkey type %d\n", + __func__, + mbedtls_pk_get_type(&x509->pk)); + + return -1; + } + break; + } + + default: + return -1; + } + + return 0; +} + +int +lws_tls_vhost_cert_info(struct lws_vhost *vhost, enum lws_tls_cert_info type, + union lws_tls_cert_info_results *buf, size_t len) +{ + mbedtls_x509_crt *x509; + + x509 = ssl_ctx_get_mbedtls_x509_crt(vhost->tls.ssl_ctx); + + return lws_tls_mbedtls_cert_info(x509, type, buf, len); +} + +int +lws_tls_peer_cert_info(struct lws *wsi, enum lws_tls_cert_info type, + union lws_tls_cert_info_results *buf, size_t len) +{ + mbedtls_x509_crt *x509; + + wsi = lws_get_network_wsi(wsi); + + x509 = ssl_get_peer_mbedtls_x509_crt(wsi->tls.ssl); + + if (!x509) + return -1; + + switch (type) { + case LWS_TLS_CERT_INFO_VERIFIED: + buf->verified = SSL_get_verify_result(wsi->tls.ssl) == X509_V_OK; + return 0; + default: + return lws_tls_mbedtls_cert_info(x509, type, buf, len); + } + + return -1; +} + +int +lws_x509_info(struct lws_x509_cert *x509, enum lws_tls_cert_info type, + union lws_tls_cert_info_results *buf, size_t len) +{ + return lws_tls_mbedtls_cert_info(&x509->cert, type, buf, len); +} + +int +lws_x509_create(struct lws_x509_cert **x509) +{ + *x509 = lws_malloc(sizeof(**x509), __func__); + + return !(*x509); +} + +/* + * Parse one DER-encoded or one or more concatenated PEM-encoded certificates + * and add them to the chained list. + */ + +int +lws_x509_parse_from_pem(struct lws_x509_cert *x509, const void *pem, size_t len) +{ + int ret; + + mbedtls_x509_crt_init(&x509->cert); + + ret = mbedtls_x509_crt_parse(&x509->cert, pem, len); + if (ret) { + mbedtls_x509_crt_free(&x509->cert); + lwsl_err("%s: unable to parse PEM cert: -0x%x\n", + __func__, -ret); + + return -1; + } + + return 0; +} + +int +lws_x509_verify(struct lws_x509_cert *x509, struct lws_x509_cert *trusted, + const char *common_name) +{ + uint32_t flags = 0; + int ret; + + ret = mbedtls_x509_crt_verify_with_profile(&x509->cert, &trusted->cert, + NULL, + &mbedtls_x509_crt_profile_next, + common_name, &flags, NULL, + NULL); + + if (ret) { + lwsl_err("%s: unable to parse PEM cert: -0x%x\n", + __func__, -ret); + + return -1; + } + + return 0; +} + +#if defined(LWS_WITH_JOSE) + +int +lws_x509_public_to_jwk(struct lws_jwk *jwk, struct lws_x509_cert *x509, + const char *curves, int rsa_min_bits) +{ + int kt = mbedtls_pk_get_type(&x509->cert.pk), n, count = 0, ret = -1; + mbedtls_rsa_context *rsactx; + mbedtls_ecp_keypair *ecpctx; + mbedtls_mpi *mpi[LWS_GENCRYPTO_RSA_KEYEL_COUNT]; + + memset(jwk, 0, sizeof(*jwk)); + + switch (kt) { + case MBEDTLS_PK_RSA: + lwsl_notice("%s: RSA key\n", __func__); + jwk->kty = LWS_GENCRYPTO_KTY_RSA; + rsactx = mbedtls_pk_rsa(x509->cert.pk); + + mpi[LWS_GENCRYPTO_RSA_KEYEL_E] = &rsactx->E; + mpi[LWS_GENCRYPTO_RSA_KEYEL_N] = &rsactx->N; + mpi[LWS_GENCRYPTO_RSA_KEYEL_D] = &rsactx->D; + mpi[LWS_GENCRYPTO_RSA_KEYEL_P] = &rsactx->P; + mpi[LWS_GENCRYPTO_RSA_KEYEL_Q] = &rsactx->Q; + mpi[LWS_GENCRYPTO_RSA_KEYEL_DP] = &rsactx->DP; + mpi[LWS_GENCRYPTO_RSA_KEYEL_DQ] = &rsactx->DQ; + mpi[LWS_GENCRYPTO_RSA_KEYEL_QI] = &rsactx->QP; + + count = LWS_GENCRYPTO_RSA_KEYEL_COUNT; + n = LWS_GENCRYPTO_RSA_KEYEL_E; + break; + + case MBEDTLS_PK_ECKEY: + lwsl_notice("%s: EC key\n", __func__); + jwk->kty = LWS_GENCRYPTO_KTY_EC; + ecpctx = mbedtls_pk_ec(x509->cert.pk); + mpi[LWS_GENCRYPTO_EC_KEYEL_X] = &ecpctx->Q.X; + mpi[LWS_GENCRYPTO_EC_KEYEL_D] = &ecpctx->d; + mpi[LWS_GENCRYPTO_EC_KEYEL_Y] = &ecpctx->Q.Y; + + if (lws_genec_confirm_curve_allowed_by_tls_id(curves, + ecpctx->grp.id, jwk)) + /* already logged */ + goto bail; + + count = LWS_GENCRYPTO_EC_KEYEL_COUNT; + n = LWS_GENCRYPTO_EC_KEYEL_X; + break; + default: + lwsl_err("%s: key type %d not supported\n", __func__, kt); + + return -1; + } + + for (; n < count; n++) { + if (!mbedtls_mpi_size(mpi[n])) + continue; + + jwk->e[n].buf = lws_malloc(mbedtls_mpi_size(mpi[n]), "certjwk"); + if (!jwk->e[n].buf) + goto bail; + jwk->e[n].len = mbedtls_mpi_size(mpi[n]); + mbedtls_mpi_write_binary(mpi[n], jwk->e[n].buf, jwk->e[n].len); + } + + ret = 0; + +bail: + /* jwk destroy will clean up partials */ + if (ret) + lws_jwk_destroy(jwk); + + return ret; +} + +int +lws_x509_jwk_privkey_pem(struct lws_jwk *jwk, void *pem, size_t len, + const char *passphrase) +{ + mbedtls_rsa_context *rsactx; + mbedtls_ecp_keypair *ecpctx; + mbedtls_pk_context pk; + mbedtls_mpi *mpi[LWS_GENCRYPTO_RSA_KEYEL_COUNT]; + int n, ret = -1, count = 0; + + mbedtls_pk_init(&pk); + + n = 0; + if (passphrase) + n = strlen(passphrase); + n = mbedtls_pk_parse_key(&pk, pem, len, (uint8_t *)passphrase, n); + if (n) { + lwsl_err("%s: parse PEM key failed: -0x%x\n", __func__, -n); + + return -1; + } + + /* the incoming private key type */ + switch (mbedtls_pk_get_type(&pk)) { + case MBEDTLS_PK_RSA: + if (jwk->kty != LWS_GENCRYPTO_KTY_RSA) { + lwsl_err("%s: RSA privkey, non-RSA jwk\n", __func__); + goto bail; + } + rsactx = mbedtls_pk_rsa(pk); + mpi[LWS_GENCRYPTO_RSA_KEYEL_D] = &rsactx->D; + mpi[LWS_GENCRYPTO_RSA_KEYEL_P] = &rsactx->P; + mpi[LWS_GENCRYPTO_RSA_KEYEL_Q] = &rsactx->Q; + n = LWS_GENCRYPTO_RSA_KEYEL_D; + count = LWS_GENCRYPTO_RSA_KEYEL_Q + 1; + break; + case MBEDTLS_PK_ECKEY: + if (jwk->kty != LWS_GENCRYPTO_KTY_EC) { + lwsl_err("%s: EC privkey, non-EC jwk\n", __func__); + goto bail; + } + ecpctx = mbedtls_pk_ec(pk); + mpi[LWS_GENCRYPTO_EC_KEYEL_D] = &ecpctx->d; + n = LWS_GENCRYPTO_EC_KEYEL_D; + count = n + 1; + break; + default: + lwsl_err("%s: unusable key type %d\n", __func__, + mbedtls_pk_get_type(&pk)); + goto bail; + } + + for (; n < count; n++) { + if (!mbedtls_mpi_size(mpi[n])) { + lwsl_err("%s: empty privkey\n", __func__); + goto bail; + } + + jwk->e[n].buf = lws_malloc(mbedtls_mpi_size(mpi[n]), "certjwk"); + if (!jwk->e[n].buf) + goto bail; + jwk->e[n].len = mbedtls_mpi_size(mpi[n]); + mbedtls_mpi_write_binary(mpi[n], jwk->e[n].buf, jwk->e[n].len); + } + + ret = 0; + +bail: + mbedtls_pk_free(&pk); + + return ret; +} +#endif + +void +lws_x509_destroy(struct lws_x509_cert **x509) +{ + if (!*x509) + return; + + mbedtls_x509_crt_free(&(*x509)->cert); + + lws_free_set_NULL(*x509); +} diff --git a/lib/tls/openssl/lws-genec.c b/lib/tls/openssl/lws-genec.c index 476a82e2e..4bdb6e322 100644 --- a/lib/tls/openssl/lws-genec.c +++ b/lib/tls/openssl/lws-genec.c @@ -53,7 +53,7 @@ ECDSA_SIG_set0(ECDSA_SIG *sig, BIGNUM *r, BIGNUM *s) } #endif #if !defined(LWS_HAVE_BN_bn2binpad) -static int BN_bn2binpad(const BIGNUM *a, unsigned char *to, int tolen) +int BN_bn2binpad(const BIGNUM *a, unsigned char *to, int tolen) { int i; BN_ULONG l; @@ -377,7 +377,7 @@ lws_genec_new_keypair(struct lws_genec_ctx *ctx, enum enum_lws_dh_side side, goto bail2; m = BN_bn2binpad(bn[n - 1], el[n].buf, el[n].len); - if (m != el[n].len) + if ((uint32_t)m != el[n].len) goto bail2; } diff --git a/lib/tls/openssl/private.h b/lib/tls/openssl/private.h index 39e9ef6d1..15db4d186 100644 --- a/lib/tls/openssl/private.h +++ b/lib/tls/openssl/private.h @@ -1,7 +1,7 @@ /* * libwebsockets - small server side websockets and web server implementation * - * Copyright (C) 2010 - 2018 Andy Green + * Copyright (C) 2010 - 2019 Andy Green * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -21,8 +21,16 @@ * gencrypto openssl-specific helper declarations */ +struct lws_x509_cert { + X509 *cert; /* X509 is opaque, this has to be a pointer */ +}; + int lws_gencrypto_openssl_hash_to_NID(enum lws_genhash_types hash_type); const EVP_MD * lws_gencrypto_openssl_hash_to_EVP_MD(enum lws_genhash_types hash_type); + +#if !defined(LWS_HAVE_BN_bn2binpad) +int BN_bn2binpad(const BIGNUM *a, unsigned char *to, int tolen); +#endif diff --git a/lib/tls/openssl/ssl.c b/lib/tls/openssl/ssl.c index 06859b3ec..6643b5768 100644 --- a/lib/tls/openssl/ssl.c +++ b/lib/tls/openssl/ssl.c @@ -20,6 +20,7 @@ */ #include "core/private.h" +#include "tls/openssl/private.h" #include int openssl_websocket_private_data_index, @@ -550,196 +551,7 @@ __lws_tls_shutdown(struct lws *wsi) return LWS_SSL_CAPABLE_ERROR; } } -#if !defined(LWS_PLAT_OPTEE) -static int -dec(char c) -{ - return c - '0'; -} -#endif -static time_t -lws_tls_openssl_asn1time_to_unix(ASN1_TIME *as) -{ -#if !defined(LWS_PLAT_OPTEE) - - const char *p = (const char *)as->data; - struct tm t; - - /* [YY]YYMMDDHHMMSSZ */ - - memset(&t, 0, sizeof(t)); - - if (strlen(p) == 13) { - t.tm_year = (dec(p[0]) * 10) + dec(p[1]) + 100; - p += 2; - } else { - t.tm_year = (dec(p[0]) * 1000) + (dec(p[1]) * 100) + - (dec(p[2]) * 10) + dec(p[3]); - p += 4; - } - t.tm_mon = (dec(p[0]) * 10) + dec(p[1]) - 1; - p += 2; - t.tm_mday = (dec(p[0]) * 10) + dec(p[1]) - 1; - p += 2; - t.tm_hour = (dec(p[0]) * 10) + dec(p[1]); - p += 2; - t.tm_min = (dec(p[0]) * 10) + dec(p[1]); - p += 2; - t.tm_sec = (dec(p[0]) * 10) + dec(p[1]); - t.tm_isdst = 0; - - return mktime(&t); -#else - return (time_t)-1; -#endif -} - -int -lws_tls_openssl_cert_info(X509 *x509, enum lws_tls_cert_info type, - union lws_tls_cert_info_results *buf, size_t len) -{ - X509_NAME *xn; -#if !defined(LWS_PLAT_OPTEE) - char *p; -#endif - - if (!x509) - return -1; - -#if OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined(X509_get_notBefore) -#define X509_get_notBefore(x) X509_getm_notBefore(x) -#define X509_get_notAfter(x) X509_getm_notAfter(x) -#endif - - switch (type) { - case LWS_TLS_CERT_INFO_VALIDITY_FROM: - buf->time = lws_tls_openssl_asn1time_to_unix( - X509_get_notBefore(x509)); - if (buf->time == (time_t)-1) - return -1; - break; - - case LWS_TLS_CERT_INFO_VALIDITY_TO: - buf->time = lws_tls_openssl_asn1time_to_unix( - X509_get_notAfter(x509)); - if (buf->time == (time_t)-1) - return -1; - break; - - case LWS_TLS_CERT_INFO_COMMON_NAME: -#if defined(LWS_PLAT_OPTEE) - return -1; -#else - xn = X509_get_subject_name(x509); - if (!xn) - return -1; - X509_NAME_oneline(xn, buf->ns.name, (int)len - 2); - p = strstr(buf->ns.name, "/CN="); - if (p) - memmove(buf->ns.name, p + 4, strlen(p + 4) + 1); - buf->ns.len = (int)strlen(buf->ns.name); - return 0; -#endif - case LWS_TLS_CERT_INFO_ISSUER_NAME: - xn = X509_get_issuer_name(x509); - if (!xn) - return -1; - X509_NAME_oneline(xn, buf->ns.name, (int)len - 1); - buf->ns.len = (int)strlen(buf->ns.name); - return 0; - - case LWS_TLS_CERT_INFO_USAGE: -#if defined(LWS_HAVE_X509_get_key_usage) - buf->usage = X509_get_key_usage(x509); - break; -#else - return -1; -#endif - - case LWS_TLS_CERT_INFO_OPAQUE_PUBLIC_KEY: - { -#ifndef USE_WOLFSSL - size_t klen = i2d_X509_PUBKEY(X509_get_X509_PUBKEY(x509), NULL); - uint8_t *tmp, *ptmp; - - if (!klen || klen > len) - return -1; - - tmp = (uint8_t *)OPENSSL_malloc(klen); - if (!tmp) - return -1; - - ptmp = tmp; - if (i2d_X509_PUBKEY( - X509_get_X509_PUBKEY(x509), &ptmp) != (int)klen || - !ptmp || lws_ptr_diff(ptmp, tmp) != (int)klen) { - lwsl_info("%s: cert public key extraction failed\n", - __func__); - if (ptmp) - OPENSSL_free(tmp); - - return -1; - } - - buf->ns.len = (int)klen; - memcpy(buf->ns.name, tmp, klen); - OPENSSL_free(tmp); -#endif - return 0; - } - default: - return -1; - } - - return 0; -} - -LWS_VISIBLE LWS_EXTERN int -lws_tls_vhost_cert_info(struct lws_vhost *vhost, enum lws_tls_cert_info type, - union lws_tls_cert_info_results *buf, size_t len) -{ -#if defined(LWS_HAVE_SSL_CTX_get0_certificate) - X509 *x509 = SSL_CTX_get0_certificate(vhost->tls.ssl_ctx); - - return lws_tls_openssl_cert_info(x509, type, buf, len); -#else - lwsl_notice("openssl is too old to support %s\n", __func__); - - return -1; -#endif -} - -LWS_VISIBLE int -lws_tls_peer_cert_info(struct lws *wsi, enum lws_tls_cert_info type, - union lws_tls_cert_info_results *buf, size_t len) -{ - int rc = 0; - X509 *x509; - - wsi = lws_get_network_wsi(wsi); - - x509 = SSL_get_peer_certificate(wsi->tls.ssl); - - if (!x509) { - lwsl_debug("no peer cert\n"); - - return -1; - } - - switch (type) { - case LWS_TLS_CERT_INFO_VERIFIED: - buf->verified = SSL_get_verify_result(wsi->tls.ssl) == - X509_V_OK; - break; - default: - rc = lws_tls_openssl_cert_info(x509, type, buf, len); - } - - X509_free(x509); - - return rc; -} static int tops_fake_POLLIN_for_buffered_openssl(struct lws_context_per_thread *pt) diff --git a/lib/tls/openssl/x509.c b/lib/tls/openssl/x509.c new file mode 100644 index 000000000..62a122b9e --- /dev/null +++ b/lib/tls/openssl/x509.c @@ -0,0 +1,633 @@ +/* + * libwebsockets - mbedTLS-specific lws apis + * + * Copyright (C) 2010-2019 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 "tls/openssl/private.h" + +#if !defined(LWS_PLAT_OPTEE) +static int +dec(char c) +{ + return c - '0'; +} +#endif + +static time_t +lws_tls_openssl_asn1time_to_unix(ASN1_TIME *as) +{ +#if !defined(LWS_PLAT_OPTEE) + + const char *p = (const char *)as->data; + struct tm t; + + /* [YY]YYMMDDHHMMSSZ */ + + memset(&t, 0, sizeof(t)); + + if (strlen(p) == 13) { + t.tm_year = (dec(p[0]) * 10) + dec(p[1]) + 100; + p += 2; + } else { + t.tm_year = (dec(p[0]) * 1000) + (dec(p[1]) * 100) + + (dec(p[2]) * 10) + dec(p[3]); + p += 4; + } + t.tm_mon = (dec(p[0]) * 10) + dec(p[1]) - 1; + p += 2; + t.tm_mday = (dec(p[0]) * 10) + dec(p[1]) - 1; + p += 2; + t.tm_hour = (dec(p[0]) * 10) + dec(p[1]); + p += 2; + t.tm_min = (dec(p[0]) * 10) + dec(p[1]); + p += 2; + t.tm_sec = (dec(p[0]) * 10) + dec(p[1]); + t.tm_isdst = 0; + + return mktime(&t); +#else + return (time_t)-1; +#endif +} + +int +lws_tls_openssl_cert_info(X509 *x509, enum lws_tls_cert_info type, + union lws_tls_cert_info_results *buf, size_t len) +{ + X509_NAME *xn; +#if !defined(LWS_PLAT_OPTEE) + char *p; +#endif + + if (!x509) + return -1; + +#if OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined(X509_get_notBefore) +#define X509_get_notBefore(x) X509_getm_notBefore(x) +#define X509_get_notAfter(x) X509_getm_notAfter(x) +#endif + + switch (type) { + case LWS_TLS_CERT_INFO_VALIDITY_FROM: + buf->time = lws_tls_openssl_asn1time_to_unix( + X509_get_notBefore(x509)); + if (buf->time == (time_t)-1) + return -1; + break; + + case LWS_TLS_CERT_INFO_VALIDITY_TO: + buf->time = lws_tls_openssl_asn1time_to_unix( + X509_get_notAfter(x509)); + if (buf->time == (time_t)-1) + return -1; + break; + + case LWS_TLS_CERT_INFO_COMMON_NAME: +#if defined(LWS_PLAT_OPTEE) + return -1; +#else + xn = X509_get_subject_name(x509); + if (!xn) + return -1; + X509_NAME_oneline(xn, buf->ns.name, (int)len - 2); + p = strstr(buf->ns.name, "/CN="); + if (p) + memmove(buf->ns.name, p + 4, strlen(p + 4) + 1); + buf->ns.len = (int)strlen(buf->ns.name); + return 0; +#endif + case LWS_TLS_CERT_INFO_ISSUER_NAME: + xn = X509_get_issuer_name(x509); + if (!xn) + return -1; + X509_NAME_oneline(xn, buf->ns.name, (int)len - 1); + buf->ns.len = (int)strlen(buf->ns.name); + return 0; + + case LWS_TLS_CERT_INFO_USAGE: +#if defined(LWS_HAVE_X509_get_key_usage) + buf->usage = X509_get_key_usage(x509); + break; +#else + return -1; +#endif + + case LWS_TLS_CERT_INFO_OPAQUE_PUBLIC_KEY: + { +#ifndef USE_WOLFSSL + size_t klen = i2d_X509_PUBKEY(X509_get_X509_PUBKEY(x509), NULL); + uint8_t *tmp, *ptmp; + + if (!klen || klen > len) + return -1; + + tmp = (uint8_t *)OPENSSL_malloc(klen); + if (!tmp) + return -1; + + ptmp = tmp; + if (i2d_X509_PUBKEY( + X509_get_X509_PUBKEY(x509), &ptmp) != (int)klen || + !ptmp || lws_ptr_diff(ptmp, tmp) != (int)klen) { + lwsl_info("%s: cert public key extraction failed\n", + __func__); + if (ptmp) + OPENSSL_free(tmp); + + return -1; + } + + buf->ns.len = (int)klen; + memcpy(buf->ns.name, tmp, klen); + OPENSSL_free(tmp); +#endif + return 0; + } + default: + return -1; + } + + return 0; +} + +int +lws_tls_vhost_cert_info(struct lws_vhost *vhost, enum lws_tls_cert_info type, + union lws_tls_cert_info_results *buf, size_t len) +{ +#if defined(LWS_HAVE_SSL_CTX_get0_certificate) + X509 *x509 = SSL_CTX_get0_certificate(vhost->tls.ssl_ctx); + + return lws_tls_openssl_cert_info(x509, type, buf, len); +#else + lwsl_notice("openssl is too old to support %s\n", __func__); + + return -1; +#endif +} + +int +lws_x509_info(struct lws_x509_cert *x509, enum lws_tls_cert_info type, + union lws_tls_cert_info_results *buf, size_t len) +{ + return lws_tls_openssl_cert_info(x509->cert, type, buf, len); +} + +int +lws_tls_peer_cert_info(struct lws *wsi, enum lws_tls_cert_info type, + union lws_tls_cert_info_results *buf, size_t len) +{ + int rc = 0; + X509 *x509; + + wsi = lws_get_network_wsi(wsi); + + x509 = SSL_get_peer_certificate(wsi->tls.ssl); + + if (!x509) { + lwsl_debug("no peer cert\n"); + + return -1; + } + + switch (type) { + case LWS_TLS_CERT_INFO_VERIFIED: + buf->verified = SSL_get_verify_result(wsi->tls.ssl) == + X509_V_OK; + break; + default: + rc = lws_tls_openssl_cert_info(x509, type, buf, len); + } + + X509_free(x509); + + return rc; +} + +int +lws_x509_create(struct lws_x509_cert **x509) +{ + *x509 = lws_malloc(sizeof(**x509), __func__); + if (*x509) + (*x509)->cert = NULL; + + return !(*x509); +} + +int +lws_x509_parse_from_pem(struct lws_x509_cert *x509, const void *pem, size_t len) +{ + BIO* bio = BIO_new(BIO_s_mem()); + + BIO_write(bio, pem, len); + x509->cert = PEM_read_bio_X509(bio, NULL, NULL, NULL); + BIO_free(bio); + if (!x509->cert) { + lwsl_err("%s: unable to parse PEM cert\n", __func__); + lws_tls_err_describe(); + + return -1; + } + + return 0; +} + +int +lws_x509_verify(struct lws_x509_cert *x509, struct lws_x509_cert *trusted, + const char *common_name) +{ + char c[32], *p; + int ret; + + if (common_name) { + X509_NAME *xn = X509_get_subject_name(x509->cert); + if (!xn) + return -1; + X509_NAME_oneline(xn, c, (int)sizeof(c) - 2); + p = strstr(c, "/CN="); + if (p) + p = p + 4; + else + p = c; + + if (strcmp(p, common_name)) { + lwsl_err("%s: common name mismatch\n", __func__); + return -1; + } + } + + ret = X509_check_issued(trusted->cert, x509->cert); + if (ret != X509_V_OK) { + lwsl_err("%s: unable to verify cert relationship\n", __func__); + lws_tls_err_describe(); + + return -1; + } + + return 0; +} + +#if defined(LWS_WITH_JOSE) +int +lws_x509_public_to_jwk(struct lws_jwk *jwk, struct lws_x509_cert *x509, + const char *curves, int rsa_min_bits) +{ + int id, n, ret = -1, count; + ASN1_OBJECT *obj = NULL; + const EC_POINT *ecpoint; + const EC_GROUP *ecgroup; + X509_PUBKEY *pubkey; + BIGNUM *mpi[4]; + EVP_PKEY *pkey; + EC_KEY *ecpub; + RSA *rsapub; + + memset(jwk, 0, sizeof(*jwk)); + + pubkey = X509_get_X509_PUBKEY(x509->cert); + if (!pubkey) { + lwsl_err("%s: missing pubkey alg in cert\n", __func__); + + goto bail; + } + + if (X509_PUBKEY_get0_param(&obj, NULL, NULL, NULL, pubkey) != 1) { + lwsl_err("%s: missing pubkey alg in cert\n", __func__); + + goto bail; + } + + id = OBJ_obj2nid(obj); + if (id == NID_undef) { + lwsl_err("%s: missing pubkey alg in cert\n", __func__); + + goto bail; + } + + lwsl_debug("%s: key type %d \"%s\"\n", __func__, id, OBJ_nid2ln(id)); + + pkey = X509_get_pubkey(x509->cert); + if (!pkey) { + lwsl_notice("%s: unable to extract pubkey", __func__); + + goto bail; + } + + switch (id) { + case NID_X9_62_id_ecPublicKey: + lwsl_debug("%s: EC key\n", __func__); + jwk->kty = LWS_GENCRYPTO_KTY_EC; + + if (!curves) { + lwsl_err("%s: ec curves not allowed\n", __func__); + + goto bail1; + } + + ecpub = EVP_PKEY_get1_EC_KEY(pkey); + if (!ecpub) { + lwsl_notice("%s: missing EC pubkey\n", __func__); + + goto bail1; + } + + ecpoint = EC_KEY_get0_public_key(ecpub); + if (!ecpoint) { + lwsl_err("%s: EC_KEY_get0_public_key failed\n", __func__); + goto bail2; + } + + ecgroup = EC_KEY_get0_group(ecpub); + if (!ecgroup) { + lwsl_err("%s: EC_KEY_get0_group failed\n", __func__); + goto bail2; + } + + /* validate the curve against ones we allow */ + + if (lws_genec_confirm_curve_allowed_by_tls_id(curves, + EC_GROUP_get_curve_name(ecgroup), jwk)) + /* already logged */ + goto bail2; + + mpi[LWS_GENCRYPTO_EC_KEYEL_CRV] = NULL; + mpi[LWS_GENCRYPTO_EC_KEYEL_X] = BN_new(); /* X */ + mpi[LWS_GENCRYPTO_EC_KEYEL_D] = NULL; + mpi[LWS_GENCRYPTO_EC_KEYEL_Y] = BN_new(); /* Y */ + + if (EC_POINT_get_affine_coordinates_GFp(ecgroup, ecpoint, + mpi[LWS_GENCRYPTO_EC_KEYEL_X], + mpi[LWS_GENCRYPTO_EC_KEYEL_Y], + NULL) != 1) { + BN_clear_free(mpi[LWS_GENCRYPTO_EC_KEYEL_X]); + BN_clear_free(mpi[LWS_GENCRYPTO_EC_KEYEL_Y]); + lwsl_err("%s: EC_POINT_get_aff failed\n", __func__); + goto bail2; + } + count = LWS_GENCRYPTO_EC_KEYEL_COUNT; + n = LWS_GENCRYPTO_EC_KEYEL_X; + break; + + case NID_rsaEncryption: + lwsl_debug("%s: rsa key\n", __func__); + jwk->kty = LWS_GENCRYPTO_KTY_RSA; + + rsapub = EVP_PKEY_get1_RSA(pkey); + if (!rsapub) { + lwsl_notice("%s: missing RSA pubkey\n", __func__); + + goto bail1; + } + + if (RSA_size(rsapub) * 8 < rsa_min_bits) { + lwsl_err("%s: key bits %d less than minimum %d\n", + __func__, RSA_size(rsapub) * 8, rsa_min_bits); + + goto bail2; + } + +#if defined(LWS_HAVE_RSA_SET0_KEY) + /* we don't need d... but the api wants to write it */ + RSA_get0_key(rsapub, + (const BIGNUM **)&mpi[LWS_GENCRYPTO_RSA_KEYEL_N], + (const BIGNUM **)&mpi[LWS_GENCRYPTO_RSA_KEYEL_E], + (const BIGNUM **)&mpi[LWS_GENCRYPTO_RSA_KEYEL_D]); +#else + mpi[LWS_GENCRYPTO_RSA_KEYEL_E] = rsapub->e; + mpi[LWS_GENCRYPTO_RSA_KEYEL_N] = rsapub->n; + mpi[LWS_GENCRYPTO_RSA_KEYEL_D] = NULL; +#endif + count = LWS_GENCRYPTO_RSA_KEYEL_D; + n = LWS_GENCRYPTO_RSA_KEYEL_E; + break; + default: + lwsl_err("%s: unknown NID\n", __func__); + goto bail2; + } + + for (; n < count; n++) { + if (!mpi[n]) + continue; + jwk->e[n].len = BN_num_bytes(mpi[n]); + jwk->e[n].buf = lws_malloc(jwk->e[n].len, "certkeyimp"); + if (!jwk->e[n].buf) { + if (id == NID_X9_62_id_ecPublicKey) { + BN_clear_free(mpi[LWS_GENCRYPTO_EC_KEYEL_X]); + BN_clear_free(mpi[LWS_GENCRYPTO_EC_KEYEL_Y]); + } + goto bail2; + } + BN_bn2bin(mpi[n], jwk->e[n].buf); + } + + if (id == NID_X9_62_id_ecPublicKey) { + BN_clear_free(mpi[LWS_GENCRYPTO_EC_KEYEL_X]); + BN_clear_free(mpi[LWS_GENCRYPTO_EC_KEYEL_Y]); + } + + ret = 0; + +bail2: + if (id == NID_X9_62_id_ecPublicKey) + EC_KEY_free(ecpub); + else + RSA_free(rsapub); + +bail1: + EVP_PKEY_free(pkey); +bail: + /* jwk destroy will clean any partial state */ + if (ret) + lws_jwk_destroy(jwk); + + return ret; +} + +static int +lws_x509_jwk_privkey_pem_pp_cb(char *buf, int size, int rwflag, void *u) +{ + const char *pp = (const char *)u; + int n = strlen(pp); + + if (n > size - 1) + return -1; + + memcpy(buf, pp, n + 1); + + return n; +} + +int +lws_x509_jwk_privkey_pem(struct lws_jwk *jwk, void *pem, size_t len, + const char *passphrase) +{ + BIO* bio = BIO_new(BIO_s_mem()); + BIGNUM *mpi, *dummy[4]; + EVP_PKEY *pkey = NULL; + const BIGNUM *cmpi; + int n, m, ret = -1; + EC_KEY *ecpriv; + RSA *rsapriv; + + BIO_write(bio, pem, len); + PEM_read_bio_PrivateKey(bio, &pkey, lws_x509_jwk_privkey_pem_pp_cb, + (void *)passphrase); + BIO_free(bio); + lws_explicit_bzero((void *)pem, len); + if (!pkey) { + lwsl_err("%s: unable to parse PEM privkey\n", __func__); + lws_tls_err_describe(); + + return -1; + } + + /* confirm the key type matches the existing jwk situation */ + + switch (jwk->kty) { + case LWS_GENCRYPTO_KTY_EC: + if (EVP_PKEY_type(EVP_PKEY_id(pkey)) != EVP_PKEY_EC) { + lwsl_err("%s: jwk is EC but privkey isn't\n", __func__); + + goto bail; + } + ecpriv = EVP_PKEY_get1_EC_KEY(pkey); + if (!ecpriv) { + lwsl_notice("%s: missing EC key\n", __func__); + + goto bail; + } + + cmpi = EC_KEY_get0_private_key(ecpriv); + + /* quick size check first */ + + n = BN_num_bytes(cmpi); + if (jwk->e[LWS_GENCRYPTO_EC_KEYEL_Y].len != (uint32_t)n) { + lwsl_err("%s: jwk key size doesn't match\n", __func__); + + goto bail1; + } + + /* TODO.. check public curve / group + point */ + + jwk->e[LWS_GENCRYPTO_EC_KEYEL_D].len = n; + jwk->e[LWS_GENCRYPTO_EC_KEYEL_D].buf = lws_malloc(n, "ec"); + if (!jwk->e[LWS_GENCRYPTO_EC_KEYEL_D].buf) + goto bail1; + + m = BN_bn2binpad(cmpi, jwk->e[LWS_GENCRYPTO_EC_KEYEL_D].buf, + jwk->e[LWS_GENCRYPTO_EC_KEYEL_D].len); + if (m != BN_num_bytes(cmpi)) + goto bail1; + + break; + + case LWS_GENCRYPTO_KTY_RSA: + if (EVP_PKEY_type(EVP_PKEY_id(pkey)) != EVP_PKEY_RSA) { + lwsl_err("%s: RSA jwk, non-RSA privkey\n", __func__); + + goto bail; + } + rsapriv = EVP_PKEY_get1_RSA(pkey); + if (!rsapriv) { + lwsl_notice("%s: missing RSA key\n", __func__); + + goto bail; + } + +#if defined(LWS_HAVE_RSA_SET0_KEY) + RSA_get0_key(rsapriv, (const BIGNUM **)&dummy[0], /* n */ + (const BIGNUM **)&dummy[1], /* e */ + (const BIGNUM **)&mpi); /* d */ +#else + dummy[0] = rsapriv->n; + dummy[1] = rsapriv->e; + mpi = rsapriv->d; +#endif + + /* quick size check first */ + + n = BN_num_bytes(mpi); + if (jwk->e[LWS_GENCRYPTO_RSA_KEYEL_N].len != (uint32_t)n) { + lwsl_err("%s: jwk key size doesn't match\n", __func__); + + goto bail1; + } + + /* then check that n & e match what we got from the cert */ + + dummy[2] = BN_bin2bn(jwk->e[LWS_GENCRYPTO_RSA_KEYEL_N].buf, + jwk->e[LWS_GENCRYPTO_RSA_KEYEL_N].len, + NULL); + dummy[3] = BN_bin2bn(jwk->e[LWS_GENCRYPTO_RSA_KEYEL_E].buf, + jwk->e[LWS_GENCRYPTO_RSA_KEYEL_E].len, + NULL); + + m = BN_cmp(dummy[2], dummy[0]) | BN_cmp(dummy[3], dummy[1]); + BN_clear_free(dummy[2]); + BN_clear_free(dummy[3]); + if (m) { + lwsl_err("%s: privkey doesn't match jwk pubkey\n", + __func__); + + goto bail1; + } + + /* accept d from the PEM privkey into the JWK */ + + jwk->e[LWS_GENCRYPTO_RSA_KEYEL_D].len = n; + jwk->e[LWS_GENCRYPTO_RSA_KEYEL_D].buf = lws_malloc(n, "privjk"); + if (!jwk->e[LWS_GENCRYPTO_RSA_KEYEL_D].buf) + goto bail; + + BN_bn2bin(mpi, jwk->e[LWS_GENCRYPTO_RSA_KEYEL_D].buf); + break; + default: + lwsl_err("%s: JWK has unknown kty %d\n", __func__, jwk->kty); + return -1; + } + + ret = 0; + +bail1: + if (jwk->kty == LWS_GENCRYPTO_KTY_EC) + EC_KEY_free(ecpriv); + else + RSA_free(rsapriv); + +bail: + EVP_PKEY_free(pkey); + + return ret; +} +#endif + +void +lws_x509_destroy(struct lws_x509_cert **x509) +{ + if (!*x509) + return; + + if ((*x509)->cert) { + X509_free((*x509)->cert); + (*x509)->cert = NULL; + } + + lws_free_set_NULL(*x509); +} diff --git a/lib/tls/private.h b/lib/tls/private.h index a299b5eca..892490904 100644 --- a/lib/tls/private.h +++ b/lib/tls/private.h @@ -1,7 +1,7 @@ /* * libwebsockets - small server side websockets and web server implementation * - * Copyright (C) 2010 - 2018 Andy Green + * Copyright (C) 2010 - 2019 Andy Green * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -167,6 +167,11 @@ struct lws_lws_tls { unsigned int redirect_to_https:1; }; +struct lws_ec_valid_curves { + int id; + const char *jwa_name; /* list terminates with NULL jwa_name */ +}; + LWS_EXTERN void lws_context_init_alpn(struct lws_vhost *vhost); LWS_EXTERN enum lws_tls_extant @@ -312,4 +317,8 @@ lws_genec_destroy_elements(struct lws_gencrypto_keyelem *el); int lws_gencrypto_mbedtls_rngf(void *context, unsigned char *buf, size_t len); +int +lws_genec_confirm_curve_allowed_by_tls_id(const char *allowed, int id, + struct lws_jwk *jwk); + #endif diff --git a/minimal-examples/crypto/README.md b/minimal-examples/crypto/README.md index 1fc34794c..1c95c13f8 100644 --- a/minimal-examples/crypto/README.md +++ b/minimal-examples/crypto/README.md @@ -2,4 +2,6 @@ ---|--- minimal-crypto-jwe|Examples for lws RFC7516 JWE apis minimal-crypto-jwk|Examples for lws RFC7517 JWK apis +minimal-crypto-jws|Examples for lws RFC7515 JWS apis +minimal-crypto-x509|Examples for lws X.509 apis diff --git a/minimal-examples/crypto/minimal-crypto-jwe/README.md b/minimal-examples/crypto/minimal-crypto-jwe/README.md index a7dcfb75c..f7ddc7a39 100644 --- a/minimal-examples/crypto/minimal-crypto-jwe/README.md +++ b/minimal-examples/crypto/minimal-crypto-jwe/README.md @@ -47,6 +47,7 @@ Commandline option|Meaning -e " "|Encrypt (default is decrypt), eg, -e "RSA1_5 A128CBC-HS256". For decrypt, the cipher information comes from the input JWE. -k |JWK file to encrypt or decrypt with -c|Format the JWE as a linebroken C string +-f|Output flattened representation (instead of compact by default) ``` $ echo -n "plaintext0123456" | ./lws-crypto-jwe -k key-rsa-4096.private -e "RSA1_5 A128CBC-HS256" diff --git a/minimal-examples/crypto/minimal-crypto-jwe/main.c b/minimal-examples/crypto/minimal-crypto-jwe/main.c index d0e173205..67fda3f4b 100644 --- a/minimal-examples/crypto/minimal-crypto-jwe/main.c +++ b/minimal-examples/crypto/minimal-crypto-jwe/main.c @@ -76,11 +76,14 @@ format_c(const char *key) } } +#define MAX_SIZE (4 * 1024 * 1024) + char temp[MAX_SIZE], compact[MAX_SIZE]; + int main(int argc, const char **argv) { int n, enc = 0, result = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE; - char temp[32768], compact[32768], *in; + char *in; struct lws_context_creation_info info; int temp_len = sizeof(temp); struct lws_context *context; @@ -132,7 +135,7 @@ int main(int argc, const char **argv) if (lws_jws_alloc_element(&jwe.jws.map, LJWS_JOSE, lws_concat_temp(temp, temp_len), &temp_len, strlen(p) + - strlen(sp + 1) + 16, 0)) { + strlen(sp + 1) + 32, 0)) { lwsl_err("%s: temp space too small\n", __func__); return 1; } @@ -189,19 +192,23 @@ int main(int argc, const char **argv) /* perform the encryption of the CEK and the plaintext */ - n = lws_jwe_encrypt(&jwe, - lws_concat_temp(temp, temp_len), + n = lws_jwe_encrypt(&jwe, lws_concat_temp(temp, temp_len), &temp_len); if (n < 0) { lwsl_err("%s: lws_jwe_encrypt failed\n", __func__); goto bail1; } + if (lws_cmdline_option(argc, argv, "-f")) + /* output the JWE in flattened form */ + n = lws_jwe_render_flattened(&jwe, compact, + sizeof(compact)); + else + /* output the JWE in compact form */ + n = lws_jwe_render_compact(&jwe, compact, + sizeof(compact)); - /* output the JWE in compact form */ - - n = lws_jwe_render_compact(&jwe, compact, sizeof(compact)); if (n < 0) { - lwsl_err("%s: lws_jwe_render_compact failed: %d\n", + lwsl_err("%s: lws_jwe_render failed: %d\n", __func__, n); goto bail1; } @@ -214,18 +221,27 @@ int main(int argc, const char **argv) goto bail1; } } else { - - /* - * converts a compact serialization to b64 + decoded maps - * held in jws - */ - if (lws_jws_compact_decode(in, n, &jwe.jws.map, &jwe.jws.map_b64, - lws_concat_temp(temp, temp_len), - &temp_len) != 5) { - lwsl_err("%s: lws_jws_compact_decode failed\n", - __func__); - goto bail1; - } + if (lws_cmdline_option(argc, argv, "-f")) { + if (lws_jwe_json_parse(&jwe, (uint8_t *)in, n, + lws_concat_temp(temp, temp_len), + &temp_len)) { + lwsl_err("%s: lws_jws_compact_decode failed\n", + __func__); + goto bail1; + } + } else + /* + * converts a compact serialization to b64 + decoded maps + * held in jws + */ + if (lws_jws_compact_decode(in, n, &jwe.jws.map, + &jwe.jws.map_b64, + lws_concat_temp(temp, temp_len), + &temp_len) != 5) { + lwsl_err("%s: lws_jws_compact_decode failed\n", + __func__); + goto bail1; + } /* * Do the crypto according to what we parsed into the jose diff --git a/minimal-examples/crypto/minimal-crypto-jws/README.md b/minimal-examples/crypto/minimal-crypto-jws/README.md index d2c426d06..97cbf00f4 100644 --- a/minimal-examples/crypto/minimal-crypto-jws/README.md +++ b/minimal-examples/crypto/minimal-crypto-jws/README.md @@ -41,6 +41,7 @@ Commandline option|Meaning -s ""|Sign (default is verify), eg, -e "ES256". For verify, the cipher information comes from the input JWS. -k |JWK file to sign or verify with... sign requires the key has its private part -c|Format the JWE as a linebroken C string +-f|Output flattened representation (instead of compact by default) ``` $ echo -n "plaintext0123456" | ./lws-crypto-jws -s "ES256" -k ec-p256.private diff --git a/minimal-examples/crypto/minimal-crypto-jws/main.c b/minimal-examples/crypto/minimal-crypto-jws/main.c index 9f3a31b6f..d46433ea6 100644 --- a/minimal-examples/crypto/minimal-crypto-jws/main.c +++ b/minimal-examples/crypto/minimal-crypto-jws/main.c @@ -11,12 +11,14 @@ #include #include +#define MAX_SIZE (4 * 1024 * 1024) +char temp[MAX_SIZE], compact[MAX_SIZE]; int main(int argc, const char **argv) { int n, sign = 0, result = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE; - char temp[32768], compact[32768], *in; + char *in; struct lws_context_creation_info info; struct lws_jws_map map; int temp_len = sizeof(temp); @@ -92,7 +94,6 @@ int main(int argc, const char **argv) return 1; } - if (sign) { /* add the plaintext from stdin to the map and a b64 version */ @@ -116,7 +117,7 @@ int main(int argc, const char **argv) /* prepare the space for the b64 signature in the map */ - if (lws_jws_alloc_element(&jws.map, LJWS_SIG, + if (lws_jws_alloc_element(&jws.map_b64, LJWS_SIG, lws_concat_temp(temp, temp_len), &temp_len, lws_base64_size( LWS_JWE_LIMIT_KEY_ELEMENT_BYTES), 0)) { @@ -124,6 +125,8 @@ int main(int argc, const char **argv) goto bail1; } + + /* sign the plaintext */ n = lws_jws_sign_from_b64(&jose, &jws, @@ -136,9 +139,12 @@ int main(int argc, const char **argv) /* set the actual b64 signature size */ jws.map_b64.len[LJWS_SIG] = n; - /* create the compact JWS representation */ - - n = lws_jws_write_compact(&jws, compact, sizeof(compact)); + if (lws_cmdline_option(argc, argv, "-f")) + /* create the flattened representation */ + n = lws_jws_write_flattened_json(&jws, compact, sizeof(compact)); + else + /* create the compact JWS representation */ + n = lws_jws_write_compact(&jws, compact, sizeof(compact)); if (n < 0) { lwsl_notice("%s: write_compact failed\n", __func__); goto bail1; @@ -154,13 +160,31 @@ int main(int argc, const char **argv) } else { /* perform the verify directly on the compact representation */ - if (lws_jws_sig_confirm_compact_b64(in, - lws_concat_used(temp, temp_len), - &map, &jwk, context, - lws_concat_temp(temp, temp_len), - &temp_len) < 0) { - lwsl_notice("%s: confirm rsa sig failed\n", __func__); - goto bail1; + if (lws_cmdline_option(argc, argv, "-f")) { + if (lws_jws_sig_confirm_json(in, n, &jws, &jwk, context, + lws_concat_temp(temp, temp_len), + &temp_len) < 0) { + lwsl_notice("%s: confirm rsa sig failed\n", + __func__); + lwsl_hexdump_notice(jws.map.buf[LJWS_JOSE], jws.map.len[LJWS_JOSE]); + lwsl_hexdump_notice(jws.map.buf[LJWS_PYLD], jws.map.len[LJWS_PYLD]); + lwsl_hexdump_notice(jws.map.buf[LJWS_SIG], jws.map.len[LJWS_SIG]); + + lwsl_hexdump_notice(jws.map_b64.buf[LJWS_JOSE], jws.map_b64.len[LJWS_JOSE]); + lwsl_hexdump_notice(jws.map_b64.buf[LJWS_PYLD], jws.map_b64.len[LJWS_PYLD]); + lwsl_hexdump_notice(jws.map_b64.buf[LJWS_SIG], jws.map_b64.len[LJWS_SIG]); + goto bail1; + } + } else { + if (lws_jws_sig_confirm_compact_b64(in, + lws_concat_used(temp, temp_len), + &map, &jwk, context, + lws_concat_temp(temp, temp_len), + &temp_len) < 0) { + lwsl_notice("%s: confirm rsa sig failed\n", + __func__); + goto bail1; + } } lwsl_notice("VALID\n"); diff --git a/minimal-examples/crypto/minimal-crypto-x509/CMakeLists.txt b/minimal-examples/crypto/minimal-crypto-x509/CMakeLists.txt new file mode 100644 index 000000000..327cdcd96 --- /dev/null +++ b/minimal-examples/crypto/minimal-crypto-x509/CMakeLists.txt @@ -0,0 +1,77 @@ +cmake_minimum_required(VERSION 2.8) +include(CheckCSourceCompiles) + +set(SAMP lws-crypto-x509) +set(SRCS main.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() + +set(requirements 1) +require_lws_config(LWS_WITH_JOSE 1 requirements) + +if (requirements) + + 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() +endif() diff --git a/minimal-examples/crypto/minimal-crypto-x509/README.md b/minimal-examples/crypto/minimal-crypto-x509/README.md new file mode 100644 index 000000000..b0d641ee6 --- /dev/null +++ b/minimal-examples/crypto/minimal-crypto-x509/README.md @@ -0,0 +1,59 @@ +# lws minimal example for X509 + +The example shows how to: + + - confirm one PEM cert or chain (-c) was signed by a trusted PEM cert (-t) + - convert a certificate public key to JWK + - convert a certificate public key and its private key PEM to a private JWK + +The examples work for EC and RSA certs and on mbedtls and OpenSSL the same. + +Notice the logging is on stderr, and only the JWK is output on stdout. + +## build + +``` + $ cmake . && make +``` + +## usage + +Commandline option|Meaning +---|--- +-d |Debug verbosity in decimal, eg, -d15 +-c |Required PEM Certificate(s) to operate on... may be multiple concatednated PEM +-t |Single PEM trusted certificate +-p |Optional private key matching certificate given in -c. If given, only the private JWK is printed to stdout + +Example for confirming trust relationship. Notice the PEM in -c must contain not only +the final certificate but also the certificates for any intermediate CAs. + +``` + $ ./lws-crypto-x509 -c ec-cert.pem -t ca-cert.pem +[2019/01/02 20:31:13:2031] USER: LWS X509 api example +[2019/01/02 20:31:13:2032] NOTICE: Creating Vhost 'default' (serving disabled), 1 protocols, IPv6 off +[2019/01/02 20:31:13:2043] NOTICE: main: certs loaded OK +[2019/01/02 20:31:13:2043] NOTICE: main: verified OK <<<<====== +[2019/01/02 20:31:13:2045] NOTICE: Cert Public JWK +{"crv":"P-521","kty":"EC","x":"_uRNBbIbm0zhk8v6ujvQX9924264ZkqJhit0qamAoCegzuJbLf434kN7_aFEt6u-QWUu6-N1R8t6OlvrLo2jrNY","y":"AU-29XpNyB7e5e3s5t0ylzGEnF601A8A7Tx8m8xxngARZX_bn22itGJ3Y57BTcclPMoG80KjWAMnRVtrKqrD_aGD"} + +[2019/01/02 20:31:13:2045] NOTICE: main: OK +``` + +Example creating JWKs for public and public + private cert + PEM keys: + +``` + $ ./lws-crypto-x509 -c ec-cert.pem -p ec-key.pem +[2019/01/02 20:14:43:4966] USER: LWS X509 api example +[2019/01/02 20:14:43:5225] NOTICE: Creating Vhost 'default' (serving disabled), 1 protocols, IPv6 off +[2019/01/02 20:14:43:5707] NOTICE: lws_x509_public_to_jwk: EC key +[2019/01/02 20:24:59:9514] USER: LWS X509 api example +[2019/01/02 20:24:59:9741] NOTICE: Creating Vhost 'default' (serving disabled), 1 protocols, IPv6 off +[2019/01/02 20:25:00:1261] NOTICE: lws_x509_public_to_jwk: key type 408 "id-ecPublicKey" +[2019/01/02 20:25:00:1269] NOTICE: lws_x509_public_to_jwk: EC key +[2019/01/02 20:25:00:2097] NOTICE: Cert + Key Private JWK +{"crv":"P-521","d":"AU3iQSKfPskMTW4ZncrYLhipUYzLYty2XhemTQ_nSuUB1vB76jHmOYUTRXFBLkVCW8cQYyMa5dMa3Bvv-cdvH0IB","kty":"EC","x":"_uRNBbIbm0zhk8v6ujvQX9924264ZkqJhit0qamAoCegzuJbLf434kN7_aFEt6u-QWUu6-N1R8t6OlvrLo2jrNY","y":"AU-29XpNyB7e5e3s5t0ylzGEnF601A8A7Tx8m8xxngARZX_bn22itGJ3Y57BTcclPMoG80KjWAMnRVtrKqrD_aGD"} + +[2019/01/02 20:25:00:2207] NOTICE: main: OK +``` + diff --git a/minimal-examples/crypto/minimal-crypto-x509/main.c b/minimal-examples/crypto/minimal-crypto-x509/main.c new file mode 100644 index 000000000..63f962a87 --- /dev/null +++ b/minimal-examples/crypto/minimal-crypto-x509/main.c @@ -0,0 +1,191 @@ +/* + * lws-crypto-x509 + * + * Copyright (C) 2018 - 2019 Andy Green + * + * This file is made available under the Creative Commons CC0 1.0 + * Universal Public Domain Dedication. + */ + +#include +#include +#include +#include + +static int +read_pem(const char *filename, char *pembuf, int pembuf_len) +{ + int n, fd = open(filename, LWS_O_RDONLY); + if (fd == -1) + return -1; + + n = read(fd, pembuf, pembuf_len - 1); + close(fd); + + pembuf[n++] = '\0'; + + return n; +} + +static int +read_pem_c509_cert(struct lws_x509_cert **x509, const char *filename, + char *pembuf, int pembuf_len) +{ + int n; + + n = read_pem(filename, pembuf, pembuf_len); + if (n < 0) + return -1; + + if (lws_x509_create(x509)) { + lwsl_err("%s: failed to create x509\n", __func__); + + return -1; + } + + if (lws_x509_parse_from_pem(*x509, pembuf, n) < 0) { + lwsl_err("%s: unable to parse PEM %s\n", __func__, filename); + lws_x509_destroy(x509); + + return -1; + } + + return 0; +} + +int main(int argc, const char **argv) +{ + int n, result = 1, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE; + struct lws_x509_cert *x509 = NULL, *x509_trusted = NULL; + struct lws_context_creation_info info; + struct lws_context *context; + struct lws_jwk jwk; + char pembuf[6144]; + const char *p; + + memset(&jwk, 0, sizeof(jwk)); + + if ((p = lws_cmdline_option(argc, argv, "-d"))) + logs = atoi(p); + + lws_set_log_level(logs, NULL); + lwsl_user("LWS X509 api example\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; + } + + + p = lws_cmdline_option(argc, argv, "-c"); + if (!p) { + lwsl_err("%s: missing -c \n", __func__); + goto bail; + } + if (read_pem_c509_cert(&x509, p, pembuf, sizeof(pembuf))) { + lwsl_err("%s: unable to read \"%s\": errno %d\n", + __func__, p, errno); + goto bail; + } + + p = lws_cmdline_option(argc, argv, "-t"); + if (p) { + + if (read_pem_c509_cert(&x509_trusted, p, pembuf, + sizeof(pembuf))) { + lwsl_err("%s: unable to read \"%s\": errno %d\n", + __func__, p, errno); + goto bail1; + } + + lwsl_notice("%s: certs loaded OK\n", __func__); + + if (lws_x509_verify(x509, x509_trusted, NULL)) { + lwsl_err("%s: verify failed\n", __func__); + goto bail2; + } + + lwsl_notice("%s: verified OK\n", __func__); + } + + if (x509_trusted) { + + /* show the trusted cert public key as a JWK */ + + if (lws_x509_public_to_jwk(&jwk, x509_trusted, + "P-256,P-384,P-521", 4096)) { + lwsl_err("%s: unable to get trusted cert pubkey as JWK\n", + __func__); + + goto bail2; + } + lwsl_info("JWK version of trusted cert:\n"); + lws_jwk_dump(&jwk); + lws_jwk_destroy(&jwk); + } + + /* get the cert public key as a JWK */ + + if (lws_x509_public_to_jwk(&jwk, x509, "P-256,P-384,P-521", 4096)) { + lwsl_err("%s: unable to get cert pubkey as JWK\n", __func__); + + goto bail3; + } + lwsl_info("JWK version of cert:\n"); + lws_jwk_dump(&jwk); + /* only print public if he doesn't provide private */ + if (!lws_cmdline_option(argc, argv, "-p")) { + lwsl_notice("Issuing Cert Public JWK on stdout\n"); + n = sizeof(pembuf); + if (lws_jwk_export(&jwk, 0, pembuf, &n)) + puts(pembuf); + } + + /* if we know where the cert private key is, add that to the cert JWK */ + + p = lws_cmdline_option(argc, argv, "-p"); + if (p) { + n = read_pem(p, pembuf, sizeof(pembuf)); + if (n < 0) { + lwsl_err("%s: unable read privkey %s\n", __func__, p); + + goto bail3; + } + if (lws_x509_jwk_privkey_pem(&jwk, pembuf, n, NULL)) { + lwsl_err("%s: unable to parse privkey %s\n", + __func__, p); + + goto bail3; + } + + lwsl_info("JWK version of cert + privkey:\n"); + lws_jwk_dump(&jwk); + lwsl_notice("Issuing Cert + Private JWK on stdout\n"); + n = sizeof(pembuf); + if (lws_jwk_export(&jwk, 1, pembuf, &n)) + puts(pembuf); + } + + result = 0; + +bail3: + lws_jwk_destroy(&jwk); +bail2: + lws_x509_destroy(&x509_trusted); +bail1: + lws_x509_destroy(&x509); +bail: + lws_context_destroy(context); + + if (result) + lwsl_err("%s: failed\n", __func__); + else + lwsl_notice("%s: OK\n", __func__); + + return result; +}