| /* Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * levelations under the License. |
| */ |
| |
| #include "apr_jose.h" |
| #include "apr_lib.h" |
| #include "apr_encode.h" |
| |
| static |
| apr_status_t apr_jose_flatten(apr_bucket_brigade *bb, apr_jose_text_t *in, |
| apr_pool_t *pool) |
| { |
| apr_bucket *e; |
| |
| /* most common case - one pool bucket, avoid unnecessary duplication */ |
| e = APR_BRIGADE_FIRST(bb); |
| if (e != APR_BRIGADE_SENTINEL(bb)) { |
| if (!APR_BUCKET_NEXT(e) && APR_BUCKET_IS_POOL(e)) { |
| apr_bucket_pool *p = e->data; |
| if (pool == p->pool) { |
| return apr_bucket_read(e, &in->text, &in->len, APR_BLOCK_READ); |
| } |
| } |
| } |
| |
| return apr_brigade_pflatten(bb, (char **)&in->text, &in->len, pool); |
| } |
| |
| static |
| apr_status_t apr_jose_decode_jwk(apr_jose_t **jose, |
| const char *typ, apr_bucket_brigade *bb, apr_jose_cb_t *cb, |
| int level, int flags, apr_pool_t *pool) |
| { |
| apr_json_value_t *key; |
| apr_jose_text_t in; |
| apr_off_t offset; |
| apr_status_t status; |
| |
| status = apr_jose_flatten(bb, &in, pool); |
| if (APR_SUCCESS != status) { |
| return status; |
| } |
| |
| status = apr_json_decode(&key, in.text, in.len, &offset, |
| APR_JSON_FLAGS_WHITESPACE, level, pool); |
| |
| *jose = apr_jose_jwk_make(NULL, key, pool); |
| if (!*jose) { |
| return APR_ENOMEM; |
| } |
| |
| if (APR_SUCCESS != status) { |
| char buf[1024]; |
| apr_strerror(status, buf, sizeof(buf)); |
| apr_errprintf(&(*jose)->result, pool, NULL, 0, |
| "Syntax error: JWK decoding failed at offset %" APR_OFF_T_FMT ": %s", |
| offset, buf); |
| |
| return status; |
| } |
| |
| return APR_SUCCESS; |
| } |
| |
| static |
| apr_status_t apr_jose_decode_jwks(apr_jose_t **jose, |
| const char *typ, apr_bucket_brigade *bb, apr_jose_cb_t *cb, |
| int level, int flags, apr_pool_t *pool) |
| { |
| apr_json_value_t *keys; |
| apr_jose_text_t in; |
| apr_off_t offset; |
| apr_status_t status; |
| |
| status = apr_jose_flatten(bb, &in, pool); |
| if (APR_SUCCESS != status) { |
| return status; |
| } |
| |
| status = apr_json_decode(&keys, in.text, in.len, |
| &offset, APR_JSON_FLAGS_WHITESPACE, level, pool); |
| |
| *jose = apr_jose_jwks_make(NULL, keys, pool); |
| if (!*jose) { |
| return APR_ENOMEM; |
| } |
| |
| if (APR_SUCCESS != status) { |
| char buf[1024]; |
| apr_strerror(status, buf, sizeof(buf)); |
| apr_errprintf(&(*jose)->result, pool, NULL, 0, |
| "Syntax error: JWKS decoding failed at offset %" APR_OFF_T_FMT ": %s", |
| offset, buf); |
| |
| return status; |
| } |
| |
| if (keys->type != APR_JSON_ARRAY) { |
| apr_errprintf(&(*jose)->result, pool, NULL, 0, |
| "Syntax error: JWKS 'keys' is not an array"); |
| return APR_EINVAL; |
| } |
| |
| return APR_SUCCESS; |
| } |
| |
| static |
| apr_status_t apr_jose_decode_jwt(apr_jose_t **jose, |
| const char *typ, apr_bucket_brigade *bb, apr_jose_cb_t *cb, |
| int level, int flags, apr_pool_t *pool) |
| { |
| apr_json_value_t *claims; |
| apr_jose_text_t in; |
| apr_off_t offset; |
| apr_status_t status; |
| |
| status = apr_jose_flatten(bb, &in, pool); |
| if (APR_SUCCESS != status) { |
| return status; |
| } |
| |
| status = apr_json_decode(&claims, in.text, in.len, &offset, |
| APR_JSON_FLAGS_WHITESPACE, level, pool); |
| |
| *jose = apr_jose_jwt_make(NULL, claims, pool); |
| if (!*jose) { |
| return APR_ENOMEM; |
| } |
| |
| if (APR_SUCCESS != status) { |
| char buf[1024]; |
| apr_strerror(status, buf, sizeof(buf)); |
| apr_errprintf(&(*jose)->result, pool, NULL, 0, |
| "Syntax error: JWT decoding failed at offset %" APR_OFF_T_FMT ": %s", |
| offset, buf); |
| |
| return status; |
| } |
| |
| return APR_SUCCESS; |
| } |
| |
| static |
| apr_status_t apr_jose_decode_data(apr_jose_t **jose, const char *typ, |
| apr_bucket_brigade *brigade, apr_jose_cb_t *cb, int level, int flags, |
| apr_pool_t *pool) |
| { |
| apr_jose_text_t in; |
| apr_status_t status; |
| |
| status = apr_jose_flatten(brigade, &in, pool); |
| if (APR_SUCCESS != status) { |
| return status; |
| } |
| |
| *jose = apr_jose_data_make(NULL, typ, (const unsigned char *) in.text, |
| in.len, pool); |
| if (!*jose) { |
| return APR_ENOMEM; |
| } |
| |
| return status; |
| } |
| |
| static |
| apr_status_t apr_jose_decode_jws_signature(apr_jose_t **jose, |
| apr_jose_signature_t *signature, const char *typ, const char *cty, |
| apr_jose_text_t *ph64, apr_jose_text_t *sig64, apr_jose_text_t *pl64, |
| apr_json_value_t *uh, apr_jose_cb_t *cb, int level, int *flags, |
| apr_pool_t *pool, apr_bucket_brigade *bb) |
| { |
| const char *phs; |
| apr_size_t phlen; |
| apr_off_t offset; |
| apr_status_t status = APR_SUCCESS; |
| |
| /* |
| * Base64url-decode the encoded representation of the JWS Protected |
| * Header, following the restriction that no line breaks, |
| * whitespace, or other additional characters have been used. |
| */ |
| |
| phs = apr_pdecode_base64(pool, ph64->text, ph64->len, APR_ENCODE_BASE64URL, |
| &phlen); |
| |
| if (!phs) { |
| apr_errprintf(&(*jose)->result, pool, NULL, 0, |
| "Syntax error: JWS 'protected' is not valid base64url"); |
| return APR_EINVAL; |
| } |
| |
| /* |
| * Verify that the resulting octet sequence is a UTF-8-encoded |
| * representation of a completely valid JSON object conforming to |
| * RFC 7159 [RFC7159]; let the JWS Protected Header be this JSON |
| * object. |
| */ |
| |
| status = apr_json_decode(&signature->protected_header, phs, phlen, &offset, |
| APR_JSON_FLAGS_WHITESPACE, level, pool); |
| if (APR_SUCCESS != status) { |
| char buf[1024]; |
| apr_strerror(status, buf, sizeof(buf)); |
| apr_errprintf(&(*jose)->result, pool, NULL, 0, |
| "Syntax error: JWS 'protected' decoding failed at %" APR_OFF_T_FMT ": %s", |
| offset, buf); |
| |
| return status; |
| } |
| |
| /* |
| * If using the JWS Compact Serialization, let the JOSE Header be |
| * the JWS Protected Header. Otherwise, when using the JWS JSON |
| * Serialization, let the JOSE Header be the union of the members of |
| * the corresponding JWS Protected Header and JWS Unprotected |
| * Header, all of which must be completely valid JSON objects. |
| * During this step, verify that the resulting JOSE Header does not |
| * contain duplicate Header Parameter names. When using the JWS |
| * JSON Serialization, this restriction includes that the same |
| * Header Parameter name also MUST NOT occur in distinct JSON object |
| * values that together comprise the JOSE Header. |
| */ |
| |
| if (uh) { |
| signature->header = apr_json_overlay(pool, signature->protected_header, |
| uh, APR_JSON_FLAGS_STRICT); |
| } else { |
| signature->header = signature->protected_header; |
| } |
| |
| /* |
| * Verify that the implementation understands and can process all |
| * fields that it is required to support, whether required by this |
| * specification, by the algorithm being used, or by the "crit" |
| * Header Parameter value, and that the values of those parameters |
| * are also understood and supported. |
| */ |
| |
| /* |
| * Base64url-decode the encoded representation of the JWS Signature, |
| * following the restriction that no line breaks, whitespace, or |
| * other additional characters have been used. |
| */ |
| |
| signature->sig.data = apr_pdecode_base64_binary(pool, sig64->text, |
| sig64->len, |
| APR_ENCODE_BASE64URL, &signature->sig.len); |
| if (!signature->sig.data) { |
| apr_errprintf(&(*jose)->result, pool, NULL, 0, |
| "Syntax error: JWS signature decoding failed: bad character"); |
| return APR_BADCH; |
| } |
| |
| /* |
| * The verify function is expected to perform some or all of the |
| * following steps: |
| * |
| * FIXME fill in from RFC |
| */ |
| |
| status = cb->verify(bb, *jose, signature, cb->ctx, flags, pool); |
| |
| return status; |
| } |
| |
| static |
| apr_status_t apr_jose_decode_jwe_recipient(apr_jose_t **jose, |
| apr_bucket_brigade *bb, apr_jose_recipient_t *recipient, |
| apr_jose_encryption_t *encryption, const char *typ, const char *cty, |
| apr_jose_text_t *ph64, apr_jose_text_t *aad64, apr_jose_cb_t *cb, |
| int level, int *dflags, apr_pool_t *pool) |
| { |
| apr_json_value_t *header; |
| apr_status_t status; |
| |
| /* |
| * If using the JWE Compact Serialization, let the JOSE Header be |
| * the JWE Protected Header. Otherwise, when using the JWE JSON |
| * Serialization, let the JOSE Header be the union of the members |
| * of the JWE Protected Header, the JWE Shared Unprotected Header |
| * and the corresponding JWE Per-Recipient Unprotected Header, all |
| * of which must be completely valid JSON objects. During this |
| * step, verify that the resulting JOSE Header does not contain |
| * duplicate Header Parameter names. When using the JWE JSON |
| * Serialization, this restriction includes that the same Header |
| * Parameter name also MUST NOT occur in distinct JSON object |
| * values that together comprise the JOSE Header. |
| */ |
| |
| header = apr_json_overlay(pool, recipient->header, |
| apr_json_overlay(pool, encryption->protected, |
| encryption->unprotected, APR_JSON_FLAGS_STRICT), |
| APR_JSON_FLAGS_STRICT); |
| |
| if (!header) { |
| apr_errprintf(&(*jose)->result, pool, NULL, 0, |
| "JWE decryption failed: protected, unprotected and per " |
| "recipient headers had an overlapping element, or were all missing"); |
| return APR_EINVAL; |
| } |
| |
| /* |
| * Verify that the implementation understands and can process all |
| * fields that it is required to support, whether required by this |
| * specification, by the algorithms being used, or by the "crit" |
| * Header Parameter value, and that the values of those parameters |
| * are also understood and supported. |
| */ |
| |
| /* |
| * The decrypt function is expected to perform some or all of the |
| * following steps: |
| * |
| * 6. Determine the Key Management Mode employed by the algorithm |
| * specified by the "alg" (algorithm) Header Parameter. |
| * |
| * 7. Verify that the JWE uses a key known to the recipient. |
| * |
| * 8. When Direct Key Agreement or Key Agreement with Key Wrapping are |
| * employed, use the key agreement algorithm to compute the value |
| * of the agreed upon key. When Direct Key Agreement is employed, |
| * let the CEK be the agreed upon key. When Key Agreement with Key |
| * Wrapping is employed, the agreed upon key will be used to |
| * decrypt the JWE Encrypted Key. |
| * |
| * 9. When Key Wrapping, Key Encryption, or Key Agreement with Key |
| * Wrapping are employed, decrypt the JWE Encrypted Key to produce |
| * the CEK. The CEK MUST have a length equal to that required for |
| * the content encryption algorithm. Note that when there are |
| * multiple recipients, each recipient will only be able to decrypt |
| * JWE Encrypted Key values that were encrypted to a key in that |
| * recipient's possession. It is therefore normal to only be able |
| * to decrypt one of the per-recipient JWE Encrypted Key values to |
| * obtain the CEK value. Also, see Section 11.5 for security |
| * considerations on mitigating timing attacks. |
| * |
| * 10. When Direct Key Agreement or Direct Encryption are employed, |
| * verify that the JWE Encrypted Key value is an empty octet |
| * sequence. |
| * |
| * 11. When Direct Encryption is employed, let the CEK be the shared |
| * symmetric key. |
| * |
| * 12. Record whether the CEK could be successfully determined for this |
| * recipient or not. |
| * |
| * 13. If the JWE JSON Serialization is being used, repeat this process |
| * (steps 4-12) for each recipient contained in the representation. |
| * |
| * 14. Compute the Encoded Protected Header value BASE64URL(UTF8(JWE |
| * Protected Header)). If the JWE Protected Header is not present |
| * (which can only happen when using the JWE JSON Serialization and |
| * no "protected" member is present), let this value be the empty |
| * string. |
| * |
| * 15. Let the Additional Authenticated Data encryption parameter be |
| * ASCII(Encoded Protected Header). However, if a JWE AAD value is |
| * present (which can only be the case when using the JWE JSON |
| * Serialization), instead let the Additional Authenticated Data |
| * encryption parameter be ASCII(Encoded Protected Header || '.' || |
| * BASE64URL(JWE AAD)). |
| * |
| * 16. Decrypt the JWE Ciphertext using the CEK, the JWE Initialization |
| * Vector, the Additional Authenticated Data value, and the JWE |
| * Authentication Tag (which is the Authentication Tag input to the |
| * calculation) using the specified content encryption algorithm, |
| * returning the decrypted plaintext and validating the JWE |
| * Authentication Tag in the manner specified for the algorithm, |
| * rejecting the input without emitting any decrypted output if the |
| * JWE Authentication Tag is incorrect. |
| * |
| * 17. If a "zip" parameter was included, uncompress the decrypted |
| * plaintext using the specified compression algorithm. |
| */ |
| |
| status = cb->decrypt(bb, *jose, recipient, encryption, header, ph64, aad64, |
| cb->ctx, dflags, pool); |
| |
| recipient->status = status; |
| |
| return status; |
| } |
| |
| static |
| apr_status_t apr_jose_decode_compact_jws(apr_jose_t **jose, |
| const char *left, const char *right, |
| apr_json_value_t *ph, const char *typ, const char *cty, |
| apr_jose_text_t *in, apr_jose_text_t *ph64, apr_jose_cb_t *cb, |
| int level, int flags, apr_pool_t *pool, apr_bucket_brigade *bb) |
| { |
| apr_jose_jws_t *jws; |
| apr_jose_text_t sig64; |
| apr_jose_text_t pl64; |
| apr_jose_text_t pls; |
| const char *dot; |
| apr_bucket *e; |
| apr_status_t status = APR_EINVAL; |
| int vflags = APR_JOSE_FLAG_NONE; |
| |
| if (!cb || !cb->verify) { |
| apr_errprintf(&(*jose)->result, pool, NULL, 0, |
| "Verification failed: no verify callback provided"); |
| return APR_EINIT; |
| } |
| |
| *jose = apr_jose_jws_make(*jose, NULL, NULL, NULL, pool); |
| if (!*jose) { |
| return APR_ENOMEM; |
| } |
| jws = (*jose)->jose.jws; |
| |
| /* |
| * If using the JWS Compact Serialization, let the JOSE Header be |
| * the JWS Protected Header. |
| */ |
| |
| jws->signature = apr_jose_signature_make(NULL, NULL, ph, NULL, pool); |
| if (!jws->signature) { |
| return APR_ENOMEM; |
| } |
| |
| dot = memchr(left, '.', right - left); |
| if (!dot) { |
| apr_errprintf(&(*jose)->result, pool, NULL, 0, |
| "Syntax error: JWS compact decoding failed: one lonely dot"); |
| return APR_BADCH; |
| } |
| |
| pl64.text = left; |
| pl64.len = dot - left; |
| |
| left = dot + 1; |
| |
| sig64.text = left; |
| sig64.len = right - left; |
| |
| /* |
| * Validate the JWS Signature against the JWS Signing Input |
| * ASCII(BASE64URL(UTF8(JWS Protected Header)) || '.' || |
| * BASE64URL(JWS Payload)) in the manner defined for the algorithm |
| * being used, which MUST be accurately represented by the value of |
| * the "alg" (algorithm) Header Parameter, which MUST be present. |
| * See Section 10.6 for security considerations on algorithm |
| * validation. Record whether the validation succeeded or not. |
| */ |
| |
| status = apr_brigade_write(bb, NULL, NULL, in->text, |
| sig64.text - in->text - 1); |
| if (APR_SUCCESS != status) { |
| return status; |
| } |
| |
| status = apr_jose_decode_jws_signature(jose, jws->signature, |
| typ, cty, ph64, &sig64, &pl64, NULL, cb, level, &vflags, pool, bb); |
| |
| if (APR_SUCCESS != status) { |
| apr_errprintf(&(*jose)->result, pool, NULL, 0, |
| "JWS verification failed: signature rejected"); |
| return status; |
| } |
| |
| /* |
| * Base64url-decode the encoded representation of the JWS Payload, |
| * following the restriction that no line breaks, whitespace, or |
| * other additional characters have been used. |
| */ |
| |
| pls.text = apr_pdecode_base64(pool, pl64.text, |
| pl64.len, APR_ENCODE_BASE64URL, &pls.len); |
| if (!pls.text) { |
| apr_errprintf(&(*jose)->result, pool, NULL, 0, |
| "Syntax error: JWS 'payload' is not valid base64url"); |
| return APR_BADCH; |
| } |
| |
| |
| apr_brigade_cleanup(bb); |
| e = apr_bucket_pool_create(pls.text, pls.len, pool, |
| bb->bucket_alloc); |
| APR_BRIGADE_INSERT_TAIL(bb, e); |
| |
| return APR_SUCCESS; |
| } |
| |
| static |
| apr_status_t apr_jose_decode_compact_jwe(apr_jose_t **jose, const char *left, |
| const char *right, apr_json_value_t *ph, apr_json_value_t *enc, |
| const char *typ, const char *cty, apr_jose_text_t *ph64, |
| apr_jose_cb_t *cb, int level, int flags, apr_pool_t *pool, |
| apr_bucket_brigade *bb) |
| { |
| const char *dot; |
| apr_jose_jwe_t *jwe; |
| apr_jose_text_t aad64; |
| apr_status_t status; |
| int dflags = APR_JOSE_FLAG_NONE; |
| |
| if (!cb || !cb->decrypt) { |
| apr_errprintf(&(*jose)->result, pool, NULL, 0, |
| "Decryption failed: no decrypt callback provided"); |
| return APR_EINIT; |
| } |
| |
| *jose = apr_jose_jwe_make(*jose, NULL, NULL, NULL, NULL, pool); |
| if (!*jose) { |
| return APR_ENOMEM; |
| } |
| jwe = (*jose)->jose.jwe; |
| |
| jwe->encryption = apr_jose_encryption_make(NULL, NULL, |
| NULL, pool); |
| if (!jwe->encryption) { |
| return APR_ENOMEM; |
| } |
| |
| jwe->recipient = apr_jose_recipient_make(NULL, NULL, NULL, pool); |
| if (!jwe->recipient) { |
| return APR_ENOMEM; |
| } |
| |
| /* |
| * Parse the JWE representation to extract the serialized values |
| * for the components of the JWE. When using the JWE Compact |
| * Serialization, these components are the base64url-encoded |
| * representations of the JWE Protected Header, the JWE Encrypted |
| * Key, the JWE Initialization Vector, the JWE Ciphertext, and the |
| * JWE Authentication Tag, and when using the JWE JSON |
| * Serialization, these components also include the base64url- |
| * encoded representation of the JWE AAD and the unencoded JWE |
| * Shared Unprotected Header and JWE Per-Recipient Unprotected |
| * Header values. When using the JWE Compact Serialization, the |
| * JWE Protected Header, the JWE Encrypted Key, the JWE |
| * Initialization Vector, the JWE Ciphertext, and the JWE |
| * Authentication Tag are represented as base64url-encoded values |
| * in that order, with each value being separated from the next by |
| * a single period ('.') character, resulting in exactly four |
| * delimiting period characters being used. The JWE JSON |
| * Serialization is described in Section 7.2. |
| */ |
| |
| /* protected header */ |
| if (ph) { |
| jwe->encryption->protected = ph; |
| } |
| |
| /* encrypted key */ |
| dot = memchr(left, '.', right - left); |
| if (!dot) { |
| apr_errprintf(&(*jose)->result, pool, NULL, 0, |
| "Syntax error: compact JWE decoding failed: one lonely dot"); |
| return APR_BADCH; |
| } |
| |
| jwe->recipient->ekey.data = apr_pdecode_base64_binary(pool, left, |
| dot - left, APR_ENCODE_BASE64URL, &jwe->recipient->ekey.len); |
| if (!jwe->recipient->ekey.data) { |
| apr_errprintf(&(*jose)->result, pool, NULL, 0, |
| "Syntax error: JWE ekey base64url decoding failed at %" APR_SIZE_T_FMT "", |
| jwe->recipient->ekey.len); |
| return APR_BADCH; |
| } |
| |
| left = dot + 1; |
| |
| /* iv */ |
| dot = memchr(left, '.', right - left); |
| if (!dot) { |
| apr_errprintf(&(*jose)->result, pool, NULL, 0, |
| "Syntax error: JWE compact decoding failed: only two dots"); |
| return APR_BADCH; |
| } |
| |
| jwe->encryption->iv.data = apr_pdecode_base64_binary(pool, left, |
| dot - left, APR_ENCODE_BASE64URL, &jwe->encryption->iv.len); |
| if (!jwe->encryption->iv.data) { |
| apr_errprintf(&(*jose)->result, pool, NULL, 0, |
| "Syntax error: JWE iv base64url decoding failed at %" APR_SIZE_T_FMT "", |
| jwe->encryption->iv.len); |
| return APR_BADCH; |
| } |
| |
| left = dot + 1; |
| |
| /* ciphertext */ |
| dot = memchr(left, '.', right - left); |
| if (!dot) { |
| apr_errprintf(&(*jose)->result, pool, NULL, 0, |
| "Syntax error: JOSE compact JWE decoding failed: only three dots"); |
| |
| return APR_BADCH; |
| } |
| |
| jwe->encryption->cipher.data = apr_pdecode_base64_binary(pool, left, |
| dot - left, APR_ENCODE_BASE64URL, &jwe->encryption->cipher.len); |
| if (!jwe->encryption->cipher.data) { |
| apr_errprintf(&(*jose)->result, pool, NULL, 0, |
| "Syntax error: JWE ciphertext base64url decoding failed at %" APR_SIZE_T_FMT "", |
| jwe->encryption->cipher.len); |
| |
| return APR_BADCH; |
| } |
| |
| left = dot + 1; |
| |
| /* tag */ |
| jwe->encryption->tag.data = apr_pdecode_base64_binary(pool, left, |
| dot - left, APR_ENCODE_BASE64URL, &jwe->encryption->tag.len); |
| if (!jwe->encryption->tag.data) { |
| apr_errprintf(&(*jose)->result, pool, NULL, 0, |
| "Syntax error: JWE tag base64url decoding failed at %" APR_SIZE_T_FMT "", |
| jwe->encryption->tag.len); |
| |
| return APR_BADCH; |
| } |
| |
| /* aad is the empty string in compact serialisation */ |
| memset(&aad64, 0, sizeof(apr_jose_text_t)); |
| |
| status = apr_jose_decode_jwe_recipient(jose, |
| bb, jwe->recipient, jwe->encryption, typ, cty, ph64, &aad64, cb, |
| level, &dflags, pool); |
| |
| if (APR_SUCCESS != status) { |
| apr_errprintf(&(*jose)->result, pool, NULL, 0, |
| "Decryption failed: JWE decryption failed"); |
| |
| return status; |
| } |
| |
| return APR_SUCCESS; |
| } |
| |
| static |
| apr_status_t apr_jose_decode_compact(apr_jose_t **jose, const char *typ, |
| apr_bucket_brigade *brigade, apr_jose_cb_t *cb, int level, int flags, |
| apr_pool_t *pool) |
| { |
| apr_bucket_brigade *bb; |
| apr_jose_text_t in; |
| apr_jose_text_t ph64; |
| apr_jose_text_t phs; |
| apr_json_kv_t *kv; |
| apr_json_value_t *header; |
| const char *left; |
| const char *right; |
| const char *dot; |
| const char *cty = NULL; |
| apr_off_t offset; |
| apr_status_t status = APR_ENOTIMPL; |
| |
| status = apr_jose_flatten(brigade, &in, pool); |
| if (APR_SUCCESS != status) { |
| return status; |
| } |
| |
| left = in.text; |
| right = in.text + in.len; |
| |
| *jose = apr_jose_make(NULL, APR_JOSE_TYPE_NONE, pool); |
| if (!*jose) { |
| return APR_ENOMEM; |
| } |
| |
| bb = apr_brigade_create(pool, brigade->bucket_alloc); |
| if (!bb) { |
| return APR_ENOMEM; |
| } |
| |
| /* |
| * Use a heuristic to see whether this is a JWT, JWE or JWS. |
| * |
| * This is described in https://tools.ietf.org/html/rfc7519#section-7.2 |
| */ |
| |
| /* Verify that the JWT contains at least one period ('.') |
| * character. |
| */ |
| dot = memchr(left, '.', in.len); |
| if (!dot) { |
| apr_errprintf(&(*jose)->result, pool, NULL, 0, |
| "Syntax error: JOSE compact decoding failed: no dots found"); |
| |
| return APR_BADCH; |
| } |
| |
| ph64.text = in.text; |
| ph64.len = dot - in.text; |
| |
| left = dot + 1; |
| |
| /* |
| * Let the Encoded JOSE Header be the portion of the JWT before the |
| * first period ('.') character. |
| * |
| * Base64url decode the Encoded JOSE Header following the |
| * restriction that no line breaks, whitespace, or other additional |
| * characters have been used. |
| */ |
| |
| phs.text = apr_pdecode_base64(pool, ph64.text, ph64.len, APR_ENCODE_BASE64URL, |
| &phs.len); |
| if (!phs.text) { |
| apr_errprintf(&(*jose)->result, pool, NULL, 0, |
| "Syntax error: JOSE header base64url decoding failed at %" APR_SIZE_T_FMT "", |
| phs.len); |
| |
| return APR_BADCH; |
| } |
| |
| /* |
| * Verify that the resulting octet sequence is a UTF-8-encoded |
| * representation of a completely valid JSON object conforming to |
| * RFC 7159 [RFC7159]; let the JOSE Header be this JSON object. |
| */ |
| |
| status = apr_json_decode(&header, phs.text, phs.len, &offset, |
| APR_JSON_FLAGS_WHITESPACE, level, pool); |
| |
| if (APR_SUCCESS != status) { |
| char buf[1024]; |
| apr_strerror(status, buf, sizeof(buf)); |
| apr_errprintf(&(*jose)->result, pool, NULL, 0, |
| "Syntax error: JOSE header decoding failed at %" APR_OFF_T_FMT ": %s", |
| offset, buf); |
| |
| return status; |
| } |
| |
| kv = apr_json_object_get(header, APR_JOSE_JWSE_CONTENT_TYPE, |
| APR_JSON_VALUE_STRING); |
| if (kv) { |
| if (kv->v->type == APR_JSON_STRING) { |
| cty = apr_pstrndup(pool, kv->v->value.string.p, |
| kv->v->value.string.len); |
| } |
| } |
| |
| if (cty) { |
| if (!strcasecmp(cty, "JWT") || !strcasecmp(cty, "application/jwt")) { |
| typ = "JWT"; |
| } |
| } |
| |
| kv = apr_json_object_get(header, APR_JOSE_JWSE_TYPE, APR_JSON_VALUE_STRING); |
| if (kv) { |
| if (kv->v->type == APR_JSON_STRING) { |
| typ = apr_pstrndup(pool, kv->v->value.string.p, |
| kv->v->value.string.len); |
| } |
| } |
| |
| /* |
| * Determine whether the JWT is a JWS or a JWE using any of the |
| * methods described in Section 9 of [JWE]. |
| * |
| * The JOSE Header for a JWS can also be distinguished from the JOSE |
| * Header for a JWE by determining whether an "enc" (encryption |
| * algorithm) member exists. If the "enc" member exists, it is a |
| * JWE; otherwise, it is a JWS. |
| */ |
| |
| kv = apr_json_object_get(header, APR_JOSE_JWE_ENCRYPTION, |
| APR_JSON_VALUE_STRING); |
| if (kv) { |
| status = apr_jose_decode_compact_jwe(jose, left, right, header, kv->v, |
| typ, cty, &ph64, cb, level, flags, pool, bb); |
| } else { |
| status = apr_jose_decode_compact_jws(jose, left, right, header, typ, cty, &in, &ph64, |
| cb, level, flags, pool, bb); |
| } |
| |
| if (APR_SUCCESS == status) { |
| |
| /* |
| * JWT is an anomaly. |
| * |
| * If we have stripped off one level of JOSE, and the content-type |
| * is present and set to JWT, our payload is a next level JOSE. |
| * |
| * If we have stripped off one level of JOSE, and the content-type |
| * is not present but the type is present and set to JWT, our payload |
| * is a JSON object containing claims. |
| */ |
| |
| if (!cty && typ |
| && (!strcasecmp(typ, "JWT") |
| || !strcasecmp(typ, "application/jwt"))) { |
| |
| status = apr_jose_decode_jwt( |
| flags & APR_JOSE_FLAG_DECODE_ALL ? |
| &(*jose)->jose.jws->payload : jose, typ, bb, cb, |
| level, flags, pool); |
| |
| } |
| else { |
| |
| if (level <= 0) { |
| apr_errprintf(&(*jose)->result, pool, NULL, 0, |
| "Syntax error: too many nested JOSE payloads"); |
| return APR_EINVAL; |
| } |
| level--; |
| |
| status = apr_jose_decode( |
| flags & APR_JOSE_FLAG_DECODE_ALL ? |
| &(*jose)->jose.jws->payload : jose, typ, bb, cb, |
| level, flags, pool); |
| } |
| |
| } |
| |
| return status; |
| } |
| |
| static |
| apr_status_t apr_jose_decode_json_jws(apr_jose_t **jose, apr_json_value_t *val, |
| const char *typ, const char *cty, apr_json_value_t *pl, |
| apr_jose_cb_t *cb, int level, int flags, apr_pool_t *pool, |
| apr_bucket_brigade *bb) |
| { |
| apr_jose_text_t ph64; |
| apr_jose_text_t sig64; |
| apr_jose_text_t pl64; |
| apr_jose_text_t pls; |
| apr_jose_jws_t *jws; |
| apr_json_kv_t *kv; |
| apr_json_value_t *uh; |
| apr_bucket *e; |
| apr_status_t status = APR_EINVAL; |
| int vflags = APR_JOSE_FLAG_NONE; |
| |
| if (!cb || !cb->verify) { |
| apr_errprintf(&(*jose)->result, pool, NULL, 0, |
| "Verification failed: no verify callback provided"); |
| |
| return APR_EINIT; |
| } |
| |
| if (pl->type != APR_JSON_STRING) { |
| apr_errprintf(&(*jose)->result, pool, NULL, 0, |
| "Syntax error: JWS 'payload' is not a string"); |
| |
| return APR_EINVAL; |
| } |
| |
| pl64.text = pl->value.string.p; |
| pl64.len = pl->value.string.len; |
| |
| /* |
| * Base64url-decode the encoded representation of the JWS Payload, |
| * following the restriction that no line breaks, whitespace, or |
| * other additional characters have been used. |
| */ |
| |
| pls.text = apr_pdecode_base64(pool, pl64.text, |
| pl64.len, APR_ENCODE_BASE64URL, &pls.len); |
| if (!pls.text) { |
| apr_errprintf(&(*jose)->result, pool, NULL, 0, |
| "Syntax error: JWS 'payload' is not valid base64url"); |
| |
| return APR_BADCH; |
| } |
| |
| *jose = apr_jose_jws_json_make(*jose, NULL, NULL, NULL, pool); |
| if (!*jose) { |
| return APR_ENOMEM; |
| } |
| jws = (*jose)->jose.jws; |
| |
| /* for each signature in signatures... */ |
| kv = apr_json_object_get(val, APR_JOSE_JWS_SIGNATURES, |
| APR_JSON_VALUE_STRING); |
| if (kv) { |
| apr_json_value_t *sigs = kv->v; |
| int i; |
| int verified = 0; |
| |
| if (sigs->type != APR_JSON_ARRAY) { |
| apr_errprintf(&(*jose)->result, pool, NULL, 0, |
| "Syntax error: JWS 'signatures' is not an array"); |
| |
| return APR_EINVAL; |
| } |
| |
| jws->signatures = apr_array_make(pool, sigs->value.array->array->nelts, |
| sizeof(apr_jose_signature_t *)); |
| if (!jws->signatures) { |
| return APR_ENOMEM; |
| } |
| |
| /* |
| * If the JWS JSON Serialization is being used, repeat this process |
| * (steps 4-8) for each digital signature or MAC value contained in |
| * the representation. |
| */ |
| |
| for (i = 0; i < sigs->value.array->array->nelts; i++) { |
| apr_json_value_t *sig = apr_json_array_get(sigs, i); |
| |
| if (sig) { |
| apr_jose_signature_t **sp; |
| |
| if (sig->type != APR_JSON_OBJECT) { |
| apr_errprintf(&(*jose)->result, pool, NULL, 0, |
| "Syntax error: JWS 'signatures' array contains a non-object"); |
| |
| return APR_EINVAL; |
| } |
| |
| sp = apr_array_push(jws->signatures); |
| *sp = apr_pcalloc(pool, sizeof(apr_jose_signature_t)); |
| if (!*sp) { |
| return APR_ENOMEM; |
| } |
| |
| kv = apr_json_object_get(sig, APR_JOSE_JWSE_PROTECTED, |
| APR_JSON_VALUE_STRING); |
| if (!kv) { |
| apr_errprintf(&(*jose)->result, pool, NULL, 0, |
| "Syntax error: JWS 'protected' header is missing"); |
| |
| return APR_EINVAL; |
| } |
| |
| if (kv->v->type != APR_JSON_STRING) { |
| apr_errprintf(&(*jose)->result, pool, NULL, 0, |
| "Syntax error: JWS 'protected' is not a string"); |
| |
| return APR_EINVAL; |
| } |
| |
| ph64.text = kv->v->value.string.p; |
| ph64.len = kv->v->value.string.len; |
| |
| kv = apr_json_object_get(sig, APR_JOSE_JWSE_HEADER, |
| APR_JSON_VALUE_STRING); |
| if (kv) { |
| |
| if (kv->v->type != APR_JSON_OBJECT) { |
| apr_errprintf(&(*jose)->result, pool, NULL, 0, |
| "Syntax error: JWS 'header' is not an object"); |
| |
| return APR_EINVAL; |
| } |
| |
| uh = kv->v; |
| } |
| else { |
| uh = NULL; |
| } |
| |
| kv = apr_json_object_get(sig, APR_JOSE_JWS_SIGNATURE, |
| APR_JSON_VALUE_STRING); |
| if (!kv) { |
| apr_errprintf(&(*jose)->result, pool, NULL, 0, |
| "Syntax error: JWS 'signature' header is missing"); |
| |
| return APR_EINVAL; |
| } |
| |
| if (kv->v->type != APR_JSON_STRING) { |
| apr_errprintf(&(*jose)->result, pool, NULL, 0, |
| "Syntax error: JWS 'signature' is not a string"); |
| |
| return APR_EINVAL; |
| } |
| |
| sig64.text = kv->v->value.string.p; |
| sig64.len = kv->v->value.string.len; |
| |
| /* |
| * Validate the JWS Signature against the JWS Signing Input |
| * ASCII(BASE64URL(UTF8(JWS Protected Header)) || '.' || |
| * BASE64URL(JWS Payload)) in the manner defined for the algorithm |
| * being used, which MUST be accurately represented by the value of |
| * the "alg" (algorithm) Header Parameter, which MUST be present. |
| * See Section 10.6 for security considerations on algorithm |
| * validation. Record whether the validation succeeded or not. |
| */ |
| |
| apr_brigade_cleanup(bb); |
| |
| status = apr_brigade_write(bb, NULL, NULL, ph64.text, |
| ph64.len); |
| if (APR_SUCCESS != status) { |
| return status; |
| } |
| |
| status = apr_brigade_putc(bb, NULL, NULL, '.'); |
| if (APR_SUCCESS != status) { |
| return status; |
| } |
| |
| status = apr_brigade_write(bb, NULL, NULL, pl64.text, |
| pl64.len); |
| if (APR_SUCCESS != status) { |
| return status; |
| } |
| |
| status = apr_jose_decode_jws_signature(jose, *sp, typ, cty, |
| &ph64, &sig64, &pl64, uh, cb, level, &vflags, pool, bb); |
| |
| if (APR_SUCCESS == status) { |
| |
| verified++; |
| |
| if (verified == 1) { |
| |
| apr_brigade_cleanup(bb); |
| e = apr_bucket_pool_create(pls.text, pls.len, pool, |
| bb->bucket_alloc); |
| APR_BRIGADE_INSERT_TAIL(bb, e); |
| |
| if (level <= 0) { |
| apr_errprintf(&(*jose)->result, pool, NULL, 0, |
| "Syntax error: too many nested JOSE payloads"); |
| return APR_EINVAL; |
| } |
| level--; |
| |
| status = apr_jose_decode( |
| flags & APR_JOSE_FLAG_DECODE_ALL ? |
| &(*jose)->jose.jwe->payload : jose, typ, |
| bb, cb, level, flags, pool); |
| |
| if (APR_SUCCESS != status) { |
| return status; |
| } |
| |
| } |
| |
| } |
| |
| if (!(vflags & APR_JOSE_FLAG_BREAK)) { |
| break; |
| } |
| |
| } |
| |
| } |
| |
| if (!verified) { |
| apr_jose_t *j = *jose; |
| |
| if (!j->result.msg) { |
| apr_errprintf(&(*jose)->result, pool, NULL, 0, |
| "JWS verification failed: no signatures matched"); |
| } |
| |
| return APR_ENOVERIFY; |
| } |
| |
| return APR_SUCCESS; |
| } |
| |
| jws->signature = apr_jose_signature_make(NULL, NULL, NULL, |
| NULL, pool); |
| if (!jws->signature) { |
| return APR_ENOMEM; |
| } |
| |
| kv = apr_json_object_get(val, APR_JOSE_JWSE_PROTECTED, |
| APR_JSON_VALUE_STRING); |
| if (!kv) { |
| apr_errprintf(&(*jose)->result, pool, NULL, 0, |
| "Syntax error: JWS 'protected' header is missing"); |
| |
| return APR_EINVAL; |
| } |
| |
| if (kv->v->type != APR_JSON_STRING) { |
| apr_errprintf(&(*jose)->result, pool, NULL, 0, |
| "Syntax error: JWS 'protected' is not a string"); |
| |
| return APR_EINVAL; |
| } |
| |
| ph64.text = kv->v->value.string.p; |
| ph64.len = kv->v->value.string.len; |
| |
| kv = apr_json_object_get(val, APR_JOSE_JWSE_HEADER, APR_JSON_VALUE_STRING); |
| if (kv) { |
| |
| if (kv->v->type != APR_JSON_OBJECT) { |
| apr_errprintf(&(*jose)->result, pool, NULL, 0, |
| "Syntax error: JWS 'header' is not an object"); |
| |
| return APR_EINVAL; |
| } |
| |
| uh = kv->v; |
| } |
| else { |
| uh = NULL; |
| } |
| |
| kv = apr_json_object_get(val, APR_JOSE_JWS_SIGNATURE, |
| APR_JSON_VALUE_STRING); |
| if (!kv) { |
| apr_errprintf(&(*jose)->result, pool, NULL, 0, |
| "Syntax error: JWS 'signature' header is missing"); |
| |
| return APR_EINVAL; |
| } |
| |
| if (kv->v->type != APR_JSON_STRING) { |
| apr_errprintf(&(*jose)->result, pool, NULL, 0, |
| "Syntax error: JWS 'signature' is not a string"); |
| |
| return APR_EINVAL; |
| } |
| |
| sig64.text = kv->v->value.string.p; |
| sig64.len = kv->v->value.string.len; |
| |
| /* |
| * Validate the JWS Signature against the JWS Signing Input |
| * ASCII(BASE64URL(UTF8(JWS Protected Header)) || '.' || |
| * BASE64URL(JWS Payload)) in the manner defined for the algorithm |
| * being used, which MUST be accurately represented by the value of |
| * the "alg" (algorithm) Header Parameter, which MUST be present. |
| * See Section 10.6 for security considerations on algorithm |
| * validation. Record whether the validation succeeded or not. |
| */ |
| |
| apr_brigade_cleanup(bb); |
| |
| status = apr_brigade_write(bb, NULL, NULL, ph64.text, |
| ph64.len); |
| if (APR_SUCCESS != status) { |
| return status; |
| } |
| |
| status = apr_brigade_putc(bb, NULL, NULL, '.'); |
| if (APR_SUCCESS != status) { |
| return status; |
| } |
| |
| status = apr_brigade_write(bb, NULL, NULL, pl64.text, |
| pl64.len); |
| if (APR_SUCCESS != status) { |
| return status; |
| } |
| |
| status = apr_jose_decode_jws_signature(jose, jws->signature, typ, cty, |
| &ph64, &sig64, &pl64, uh, cb, level, &vflags, pool, bb); |
| |
| if (APR_SUCCESS != status) { |
| apr_errprintf(&(*jose)->result, pool, NULL, 0, |
| "JWS verification failed: signature rejected"); |
| |
| return status; |
| } |
| |
| apr_brigade_cleanup(bb); |
| e = apr_bucket_pool_create(pls.text, pls.len, pool, |
| bb->bucket_alloc); |
| APR_BRIGADE_INSERT_TAIL(bb, e); |
| |
| if (level <= 0) { |
| apr_errprintf(&(*jose)->result, pool, NULL, 0, |
| "Syntax error: too many nested JOSE payloads"); |
| return APR_EINVAL; |
| } |
| level--; |
| |
| return apr_jose_decode( |
| flags & APR_JOSE_FLAG_DECODE_ALL ? |
| &(*jose)->jose.jws->payload : jose, typ, bb, cb, |
| level, flags, pool); |
| } |
| |
| static |
| apr_status_t apr_jose_decode_json_jwe(apr_jose_t **jose, apr_json_value_t *val, |
| const char *typ, const char *cty, apr_json_value_t *ct, |
| apr_jose_cb_t *cb, int level, int flags, apr_pool_t *pool, |
| apr_bucket_brigade *bb) |
| { |
| apr_jose_text_t ph64; |
| apr_jose_text_t aad64; |
| apr_jose_jwe_t *jwe; |
| apr_json_kv_t *kv; |
| apr_status_t status = APR_EGENERAL; |
| int dflags = APR_JOSE_FLAG_NONE; |
| |
| if (!cb || !cb->decrypt) { |
| apr_errprintf(&(*jose)->result, pool, NULL, 0, |
| "Decryption failed: no decrypt callback provided"); |
| |
| return APR_EINIT; |
| } |
| |
| if (ct->type != APR_JSON_STRING) { |
| apr_errprintf(&(*jose)->result, pool, NULL, 0, |
| "Syntax error: JWE 'ciphertext' is not a string"); |
| |
| return APR_EINVAL; |
| } |
| |
| *jose = apr_jose_jwe_json_make(*jose, NULL, NULL, NULL, NULL, pool); |
| if (!*jose) { |
| return APR_ENOMEM; |
| } |
| jwe = (*jose)->jose.jwe; |
| |
| jwe->encryption = apr_jose_encryption_make(NULL, NULL, |
| NULL, pool); |
| if (!jwe->encryption) { |
| return APR_ENOMEM; |
| } |
| |
| /* |
| * Base64url decode the encoded representations of the JWE |
| * Protected Header, the JWE Encrypted Key, the JWE Initialization |
| * Vector, the JWE Ciphertext, the JWE Authentication Tag, and the |
| * JWE AAD, following the restriction that no line breaks, |
| * whitespace, or other additional characters have been used. |
| */ |
| |
| kv = apr_json_object_get(val, APR_JOSE_JWSE_PROTECTED, |
| APR_JSON_VALUE_STRING); |
| if (kv) { |
| const char *phs; |
| apr_size_t phlen; |
| apr_off_t offset; |
| |
| if (kv->v->type != APR_JSON_STRING) { |
| apr_errprintf(&(*jose)->result, pool, NULL, 0, |
| "Syntax error: JWE 'protected' is not a string"); |
| |
| return APR_EINVAL; |
| } |
| |
| /* |
| * Verify that the octet sequence resulting from decoding the |
| * encoded JWE Protected Header is a UTF-8-encoded representation |
| * of a completely valid JSON object conforming to RFC 7159 |
| * [RFC7159]; let the JWE Protected Header be this JSON object. |
| */ |
| |
| ph64.text = kv->v->value.string.p; |
| ph64.len = kv->v->value.string.len; |
| |
| phs = apr_pdecode_base64(pool, ph64.text, |
| ph64.len, APR_ENCODE_BASE64URL, &phlen); |
| |
| if (!phs) { |
| apr_errprintf(&(*jose)->result, pool, NULL, 0, |
| "Syntax error: JWE 'protected' is not valid base64url"); |
| |
| return APR_EINVAL; |
| } |
| |
| status = apr_json_decode(&jwe->encryption->protected, phs, phlen, &offset, |
| APR_JSON_FLAGS_WHITESPACE, level, pool); |
| if (APR_SUCCESS != status) { |
| char buf[1024]; |
| apr_strerror(status, buf, sizeof(buf)); |
| apr_errprintf(&(*jose)->result, pool, NULL, 0, |
| "Syntax error: JWE 'protected' decoding failed at %" APR_OFF_T_FMT ": %s", |
| offset, buf); |
| |
| return status; |
| } |
| |
| } |
| else { |
| apr_errprintf(&(*jose)->result, pool, NULL, 0, |
| "Syntax error: JWE 'protected' header is missing"); |
| |
| return APR_EINVAL; |
| } |
| |
| /* unprotected */ |
| kv = apr_json_object_get(val, APR_JOSE_JWE_UNPROTECTED, |
| APR_JSON_VALUE_STRING); |
| if (kv) { |
| |
| if (kv->v->type != APR_JSON_OBJECT) { |
| apr_errprintf(&(*jose)->result, pool, NULL, 0, |
| "Syntax error: JWE 'unprotected' is not an object"); |
| |
| return APR_EINVAL; |
| } |
| |
| jwe->encryption->unprotected = kv->v; |
| } |
| |
| /* ciphertext */ |
| jwe->encryption->cipher.data = apr_pdecode_base64_binary(pool, |
| ct->value.string.p, ct->value.string.len, APR_ENCODE_BASE64URL, |
| &jwe->encryption->cipher.len); |
| if (!jwe->encryption->cipher.data) { |
| apr_errprintf(&(*jose)->result, pool, NULL, 0, |
| "Syntax error: JWE 'ciphertext' is not valid base64url"); |
| |
| return APR_BADCH; |
| } |
| |
| /* iv */ |
| kv = apr_json_object_get(val, APR_JOSE_JWE_IV, APR_JSON_VALUE_STRING); |
| if (kv) { |
| |
| if (kv->v->type != APR_JSON_STRING) { |
| apr_errprintf(&(*jose)->result, pool, NULL, 0, |
| "Syntax error: JWE 'iv' is not a string"); |
| |
| return APR_EINVAL; |
| } |
| |
| jwe->encryption->iv.data = apr_pdecode_base64_binary(pool, |
| kv->v->value.string.p, kv->v->value.string.len, APR_ENCODE_BASE64URL, |
| &jwe->encryption->iv.len); |
| if (!jwe->encryption->iv.data) { |
| apr_errprintf(&(*jose)->result, pool, NULL, 0, |
| "Syntax error: JWE 'iv' is not valid base64url"); |
| |
| return APR_BADCH; |
| } |
| |
| } |
| |
| /* tag */ |
| kv = apr_json_object_get(val, APR_JOSE_JWE_TAG, APR_JSON_VALUE_STRING); |
| if (kv) { |
| |
| if (kv->v->type != APR_JSON_STRING) { |
| apr_errprintf(&(*jose)->result, pool, NULL, 0, |
| "Syntax error: JWE 'tag' is not a string"); |
| |
| return APR_EINVAL; |
| } |
| |
| jwe->encryption->tag.data = apr_pdecode_base64_binary(pool, |
| kv->v->value.string.p, kv->v->value.string.len, APR_ENCODE_BASE64URL, |
| &jwe->encryption->tag.len); |
| if (!jwe->encryption->tag.data) { |
| apr_errprintf(&(*jose)->result, pool, NULL, 0, |
| "Syntax error: JWE 'tag' is not valid base64url"); |
| |
| return APR_BADCH; |
| } |
| |
| } |
| |
| /* aad */ |
| kv = apr_json_object_get(val, APR_JOSE_JWE_AAD, APR_JSON_VALUE_STRING); |
| if (kv) { |
| |
| if (kv->v->type != APR_JSON_STRING) { |
| apr_errprintf(&(*jose)->result, pool, NULL, 0, |
| "Syntax error: JWE 'aad' is not a string"); |
| |
| return APR_EINVAL; |
| } |
| |
| aad64.text = kv->v->value.string.p; |
| aad64.len = kv->v->value.string.len; |
| |
| jwe->encryption->aad.data = apr_pdecode_base64_binary(pool, |
| aad64.text, aad64.len, APR_ENCODE_BASE64URL, |
| &jwe->encryption->aad.len); |
| if (!jwe->encryption->aad.data) { |
| apr_errprintf(&(*jose)->result, pool, NULL, 0, |
| "Syntax error: JWE 'add' is not valid base64url"); |
| |
| return APR_BADCH; |
| } |
| |
| } |
| else { |
| memset(&aad64, 0, sizeof(apr_jose_text_t)); |
| } |
| |
| /* for each recipient in recipients... */ |
| kv = apr_json_object_get(val, APR_JOSE_JWE_RECIPIENTS, |
| APR_JSON_VALUE_STRING); |
| if (kv) { |
| apr_json_value_t *recips = kv->v; |
| int i; |
| int decrypt = 0; |
| |
| if (recips->type != APR_JSON_ARRAY) { |
| apr_errprintf(&(*jose)->result, pool, NULL, 0, |
| "Syntax error: JWE 'recipients' is not an array"); |
| |
| return APR_EINVAL; |
| } |
| |
| (*jose)->jose.jwe->recipients = apr_array_make(pool, |
| recips->value.array->array->nelts, sizeof(apr_jose_recipient_t *)); |
| if (!(*jose)->jose.jwe->recipients) { |
| return APR_ENOMEM; |
| } |
| |
| for (i = 0; i < recips->value.array->array->nelts; i++) { |
| apr_json_value_t *recip = apr_json_array_get(recips, i); |
| |
| if (recip) { |
| apr_jose_recipient_t **rp; |
| apr_jose_recipient_t *recipient; |
| |
| if (recip->type != APR_JSON_OBJECT) { |
| apr_errprintf(&(*jose)->result, pool, NULL, 0, |
| "Syntax error: JWE 'recipients' array contains a non-object"); |
| |
| return APR_EINVAL; |
| } |
| |
| rp = apr_array_push((*jose)->jose.jwe->recipients); |
| *rp = recipient = apr_pcalloc(pool, sizeof(apr_jose_recipient_t)); |
| if (!recipient) { |
| return APR_ENOMEM; |
| } |
| |
| /* unprotected */ |
| kv = apr_json_object_get(recip, APR_JOSE_JWSE_HEADER, |
| APR_JSON_VALUE_STRING); |
| if (kv) { |
| |
| if (kv->v->type != APR_JSON_OBJECT) { |
| apr_errprintf(&(*jose)->result, pool, NULL, 0, |
| "Syntax error: JWE 'header' is not an object"); |
| |
| return APR_EINVAL; |
| } |
| |
| recipient->header = kv->v; |
| } |
| |
| kv = apr_json_object_get(recip, APR_JOSE_JWE_EKEY, |
| APR_JSON_VALUE_STRING); |
| if (kv) { |
| |
| if (kv->v->type != APR_JSON_STRING) { |
| apr_errprintf(&(*jose)->result, pool, NULL, 0, |
| "Syntax error: JWE 'encrypted_key' element must be a string"); |
| |
| return APR_EINVAL; |
| } |
| |
| recipient->ekey.data = apr_pdecode_base64_binary(pool, |
| kv->v->value.string.p, kv->v->value.string.len, APR_ENCODE_BASE64URL, |
| &recipient->ekey.len); |
| if (!recipient->ekey.data) { |
| apr_errprintf(&(*jose)->result, pool, NULL, 0, |
| "Syntax error: JWE 'encrypted_key' is not valid base64url"); |
| |
| return APR_BADCH; |
| } |
| |
| } |
| |
| apr_brigade_cleanup(bb); |
| |
| status = apr_jose_decode_jwe_recipient(jose, bb, recipient, |
| jwe->encryption, typ, cty, &ph64, &aad64, cb, level, |
| &dflags, pool); |
| |
| if (APR_SUCCESS == status) { |
| |
| decrypt++; |
| |
| if (decrypt == 1) { |
| |
| if (level <= 0) { |
| apr_errprintf(&(*jose)->result, pool, NULL, 0, |
| "Syntax error: too many nested JOSE payloads"); |
| return APR_EINVAL; |
| } |
| level--; |
| |
| status = apr_jose_decode( |
| flags & APR_JOSE_FLAG_DECODE_ALL ? |
| &(*jose)->jose.jwe->payload : jose, typ, |
| bb, cb, level, flags, pool); |
| |
| if (APR_SUCCESS != status) { |
| return status; |
| } |
| |
| } |
| |
| } |
| |
| if (!(dflags & APR_JOSE_FLAG_BREAK)) { |
| break; |
| } |
| |
| } |
| |
| } |
| |
| if (!decrypt) { |
| apr_jose_t *j = *jose; |
| |
| if (!j->result.msg) { |
| apr_errprintf(&(*jose)->result, pool, NULL, 0, |
| "JWE decryption failed: no recipients matched"); |
| } |
| |
| return APR_ECRYPT; |
| } |
| |
| return APR_SUCCESS; |
| } |
| |
| /* ok, just one recipient */ |
| kv = apr_json_object_get(val, APR_JOSE_JWE_EKEY, APR_JSON_VALUE_STRING); |
| if (kv) { |
| apr_json_value_t *ekey = kv->v; |
| apr_jose_recipient_t *recipient; |
| |
| if (ekey->type != APR_JSON_STRING) { |
| apr_errprintf(&(*jose)->result, pool, NULL, 0, |
| "Syntax error: JWE 'encrypted_key' element must be a string"); |
| |
| return APR_EINVAL; |
| } |
| |
| recipient = apr_pcalloc(pool, sizeof(apr_jose_recipient_t)); |
| if (!recipient) { |
| return APR_ENOMEM; |
| } |
| |
| /* unprotected */ |
| kv = apr_json_object_get(val, APR_JOSE_JWSE_HEADER, |
| APR_JSON_VALUE_STRING); |
| if (kv) { |
| |
| if (kv->v->type != APR_JSON_OBJECT) { |
| apr_errprintf(&(*jose)->result, pool, NULL, 0, |
| "Syntax error: JWE 'header' is not an object"); |
| |
| return APR_EINVAL; |
| } |
| |
| recipient->header = kv->v; |
| } |
| |
| apr_brigade_cleanup(bb); |
| |
| status = apr_jose_decode_jwe_recipient(jose, bb, recipient, |
| jwe->encryption, typ, cty, &ph64, &aad64, cb, level, &dflags, |
| pool); |
| |
| if (APR_SUCCESS == status) { |
| |
| if (level <= 0) { |
| apr_errprintf(&(*jose)->result, pool, NULL, 0, |
| "Syntax error: too many nested JOSE payloads"); |
| return APR_EINVAL; |
| } |
| level--; |
| |
| return apr_jose_decode( |
| flags & APR_JOSE_FLAG_DECODE_ALL ? |
| &(*jose)->jose.jwe->payload : jose, typ, bb, |
| cb, level, flags, pool); |
| |
| } |
| |
| if (APR_SUCCESS != status) { |
| apr_errprintf(&(*jose)->result, pool, NULL, 0, |
| "Decryption failed: JWE decryption failed"); |
| } |
| |
| } |
| |
| /* no recipient at all */ |
| apr_errprintf(&(*jose)->result, pool, NULL, 0, |
| "Syntax error: No 'recipients' or 'encrypted_key' present"); |
| |
| return APR_EINVAL; |
| |
| } |
| |
| static |
| apr_status_t apr_jose_decode_json(apr_jose_t **jose, const char *typ, |
| apr_bucket_brigade *brigade, apr_jose_cb_t *cb, int level, |
| int flags, apr_pool_t *pool) |
| { |
| apr_json_value_t *val; |
| apr_bucket_brigade *bb; |
| apr_jose_text_t in; |
| apr_off_t offset; |
| apr_status_t status; |
| |
| *jose = apr_jose_make(NULL, APR_JOSE_TYPE_NONE, pool); |
| if (!*jose) { |
| return APR_ENOMEM; |
| } |
| |
| status = apr_jose_flatten(brigade, &in, pool); |
| if (APR_SUCCESS != status) { |
| return status; |
| } |
| |
| bb = apr_brigade_create(pool, brigade->bucket_alloc); |
| if (!bb) { |
| return APR_ENOMEM; |
| } |
| |
| /* |
| * Parse the JWS representation to extract the serialized values for |
| * the components of the JWS. When using the JWS Compact |
| * Serialization, these components are the base64url-encoded |
| * representations of the JWS Protected Header, the JWS Payload, and |
| * the JWS Signature, and when using the JWS JSON Serialization, |
| * these components also include the unencoded JWS Unprotected |
| * Header value. When using the JWS Compact Serialization, the JWS |
| * Protected Header, the JWS Payload, and the JWS Signature are |
| * represented as base64url-encoded values in that order, with each |
| * value being separated from the next by a single period ('.') |
| * character, resulting in exactly two delimiting period characters |
| * being used. The JWS JSON Serialization is described in |
| * Section 7.2. |
| */ |
| |
| status = apr_json_decode(&val, in.text, in.len, &offset, |
| APR_JSON_FLAGS_WHITESPACE, level, pool); |
| if (APR_SUCCESS == status) { |
| apr_json_kv_t *kv; |
| const char *cty = NULL; |
| |
| /* |
| * 9. Distinguishing between JWS and JWE Objects |
| * |
| * If the object is using the JWS JSON Serialization or the JWE JSON |
| * Serialization, the members used will be different. JWSs have a |
| * "payload" member and JWEs do not. JWEs have a "ciphertext" member |
| * and JWSs do not. |
| */ |
| |
| /* are we JWS? */ |
| kv = apr_json_object_get(val, APR_JOSE_JWS_PAYLOAD, |
| APR_JSON_VALUE_STRING); |
| if (kv) { |
| |
| return apr_jose_decode_json_jws(jose, val, typ, cty, |
| kv->v, cb, level, flags, pool, bb); |
| |
| } |
| |
| /* are we JWE? */ |
| kv = apr_json_object_get(val, APR_JOSE_JWE_CIPHERTEXT, |
| APR_JSON_VALUE_STRING); |
| if (kv) { |
| |
| return apr_jose_decode_json_jwe(jose, val, typ, cty, |
| kv->v, cb, level, flags, pool, bb); |
| |
| } |
| |
| apr_errprintf(&(*jose)->result, pool, NULL, 0, |
| "Syntax error: JOSE JSON contained neither a 'payload' nor a 'ciphertext'"); |
| |
| return APR_EINVAL; |
| |
| } |
| else { |
| char buf[1024]; |
| apr_strerror(status, buf, sizeof(buf)); |
| apr_errprintf(&(*jose)->result, pool, NULL, 0, |
| "Syntax error: JOSE JSON decoding failed at character %" APR_OFF_T_FMT ": %s", |
| offset, buf); |
| } |
| |
| return status; |
| } |
| |
| APR_DECLARE(apr_status_t) apr_jose_decode(apr_jose_t **jose, const char *typ, |
| apr_bucket_brigade *brigade, |
| apr_jose_cb_t *cb, int level, |
| int flags, apr_pool_t *pool) |
| { |
| |
| /* handle JOSE and JOSE+JSON */ |
| if (typ) { |
| switch (typ[0]) { |
| case 'a': |
| case 'A': { |
| |
| if (!strncasecmp(typ, "application/", 12)) { |
| const char *sub = typ + 12; |
| |
| if (!strcasecmp(sub, "jwt")) { |
| return apr_jose_decode_compact(jose, typ, brigade, cb, |
| level, flags, pool); |
| } else if (!strcasecmp(sub, "jose")) { |
| return apr_jose_decode_compact(jose, NULL, brigade, cb, |
| level, flags, pool); |
| } else if (!strcasecmp(sub, "jose+json")) { |
| return apr_jose_decode_json(jose, NULL, brigade, cb, level, |
| flags, pool); |
| } else if (!strcasecmp(sub, "jwk+json")) { |
| return apr_jose_decode_jwk(jose, typ, brigade, cb, level, |
| flags, pool); |
| } else if (!strcasecmp(sub, "jwk-set+json")) { |
| return apr_jose_decode_jwks(jose, typ, brigade, cb, level, |
| flags, pool); |
| } |
| |
| } |
| |
| break; |
| } |
| case 'J': |
| case 'j': { |
| |
| if (!strcasecmp(typ, "JWT")) { |
| return apr_jose_decode_compact(jose, typ, brigade, cb, level, flags, |
| pool); |
| } else if (!strcasecmp(typ, "JOSE")) { |
| return apr_jose_decode_compact(jose, NULL, brigade, cb, level, |
| flags, pool); |
| } else if (!strcasecmp(typ, "JOSE+JSON")) { |
| return apr_jose_decode_json(jose, NULL, brigade, cb, level, flags, |
| pool); |
| } else if (!strcasecmp(typ, "JWK+JSON")) { |
| return apr_jose_decode_jwk(jose, typ, brigade, cb, level, flags, |
| pool); |
| } else if (!strcasecmp(typ, "JWK-SET+JSON")) { |
| return apr_jose_decode_jwks(jose, typ, brigade, cb, level, flags, |
| pool); |
| } |
| |
| break; |
| } |
| } |
| } |
| |
| return apr_jose_decode_data(jose, typ, brigade, cb, level, flags, pool); |
| } |