diff options
Diffstat (limited to 'jose')
-rw-r--r-- | jose/apr_jose.c | 353 | ||||
-rw-r--r-- | jose/apr_jose_decode.c | 1694 | ||||
-rw-r--r-- | jose/apr_jose_encode.c | 825 |
3 files changed, 2872 insertions, 0 deletions
diff --git a/jose/apr_jose.c b/jose/apr_jose.c new file mode 100644 index 000000000..26f6b0ffa --- /dev/null +++ b/jose/apr_jose.c @@ -0,0 +1,353 @@ +/* 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 + * limitations under the License. + */ + +#include "apr_jose.h" + +APR_DECLARE(apu_err_t *) apr_jose_error(apr_jose_t *jose) +{ + return &jose->result; +} + +APR_DECLARE(apr_status_t) apr_jose_make(apr_jose_t **jose, apr_jose_type_e type, + apr_pool_t *pool) +{ + apr_jose_t *j; + + if (*jose) { + j = *jose; + } else { + *jose = j = apr_pcalloc(pool, sizeof(apr_jose_t)); + if (!j) { + return APR_ENOMEM; + } + } + + j->pool = pool; + j->type = type; + + return APR_SUCCESS; +} + +APR_DECLARE(apr_status_t) apr_jose_data_make(apr_jose_t **jose, const char *typ, + const unsigned char *in, apr_size_t inlen, apr_pool_t *pool) +{ + apr_jose_t *j; + apr_status_t status; + + status = apr_jose_make(jose, APR_JOSE_TYPE_DATA, pool); + if (APR_SUCCESS != status) { + return status; + } + j = *jose; + + j->typ = typ; + j->jose.data = apr_palloc(pool, sizeof(apr_jose_data_t)); + if (!j->jose.data) { + return APR_ENOMEM; + } + j->jose.data->data = in; + j->jose.data->len = inlen; + + return APR_SUCCESS; +} + +APR_DECLARE(apr_status_t) apr_jose_json_make(apr_jose_t **jose, const char *cty, + apr_json_value_t *json, apr_pool_t *pool) +{ + apr_jose_t *j; + apr_status_t status; + + status = apr_jose_make(jose, APR_JOSE_TYPE_JSON, pool); + if (APR_SUCCESS != status) { + return status; + } + j = *jose; + + j->cty = cty; + j->jose.json = apr_palloc(pool, sizeof(apr_jose_json_t)); + if (!j->jose.json) { + return APR_ENOMEM; + } + j->jose.json->json = json; + + return APR_SUCCESS; +} + +APR_DECLARE(apr_status_t) apr_jose_signature_make( + apr_jose_signature_t **signature, apr_json_value_t *header, + apr_json_value_t *protected, apr_pool_t *pool) +{ + apr_jose_signature_t *s; + + *signature = s = apr_pcalloc(pool, sizeof(apr_jose_signature_t)); + if (!s) { + return APR_ENOMEM; + } + + s->header = header; + s->protected_header = protected; + + return APR_SUCCESS; +} + +APR_DECLARE(apr_status_t) apr_jose_recipient_make( + apr_jose_recipient_t **recipient, apr_json_value_t *header, + apr_pool_t *pool) +{ + apr_jose_recipient_t *r; + + *recipient = r = apr_pcalloc(pool, sizeof(apr_jose_recipient_t)); + if (!r) { + return APR_ENOMEM; + } + + r->header = header; + + return APR_SUCCESS; +} + +APR_DECLARE(apr_status_t) apr_jose_encryption_make( + apr_jose_encryption_t **encryption, apr_json_value_t *header, + apr_json_value_t *protected_header, apr_pool_t *pool) +{ + apr_jose_encryption_t *e; + + *encryption = e = apr_pcalloc(pool, sizeof(apr_jose_encryption_t)); + if (!e) { + return APR_ENOMEM; + } + + e->unprotected = header; + e->protected = protected_header; + + return APR_SUCCESS; +} + +APR_DECLARE(apr_status_t) apr_jose_jwe_make(apr_jose_t **jose, + apr_jose_recipient_t *recipient, apr_array_header_t *recipients, + apr_jose_encryption_t *encryption, apr_jose_t *payload, + apr_pool_t *pool) +{ + apr_jose_t *j; + apr_jose_jwe_t *jwe; + apr_status_t status; + + status = apr_jose_make(jose, APR_JOSE_TYPE_JWE, pool); + if (APR_SUCCESS != status) { + return status; + } + j = *jose; + + j->cty = payload->cty; + + jwe = j->jose.jwe = apr_palloc(pool, sizeof(apr_jose_jwe_t)); + if (!jwe) { + return APR_ENOMEM; + } + + jwe->recipient = recipient; + jwe->recipients = recipients; + jwe->encryption = encryption; + jwe->payload = payload; + + return APR_SUCCESS; +} + +APR_DECLARE(apr_status_t) apr_jose_jwe_json_make(apr_jose_t **jose, + apr_jose_recipient_t *recipient, apr_array_header_t *recipients, + apr_jose_encryption_t *encryption, apr_jose_t *payload, + apr_pool_t *pool) +{ + apr_jose_t *j; + apr_jose_jwe_t *jwe; + apr_status_t status; + + status = apr_jose_make(jose, APR_JOSE_TYPE_JWE_JSON, pool); + if (APR_SUCCESS != status) { + return status; + } + j = *jose; + + if (payload) { + j->cty = payload->cty; + } + + jwe = j->jose.jwe = apr_palloc(pool, sizeof(apr_jose_jwe_t)); + if (!jwe) { + return APR_ENOMEM; + } + + jwe->recipient = recipient; + jwe->recipients = recipients; + jwe->encryption = encryption; + jwe->payload = payload; + + return APR_SUCCESS; +} + +APR_DECLARE(apr_status_t) apr_jose_jwk_make(apr_jose_t **jose, + apr_json_value_t *key, apr_pool_t *pool) +{ + apr_jose_t *j; + apr_jose_jwk_t *jwk; + apr_status_t status; + + status = apr_jose_make(jose, APR_JOSE_TYPE_JWK, pool); + if (APR_SUCCESS != status) { + return status; + } + j = *jose; + + jwk = j->jose.jwk = apr_palloc(pool, sizeof(apr_jose_jwk_t)); + if (!jwk) { + return APR_ENOMEM; + } + + jwk->key = key; + + return APR_SUCCESS; +} + +APR_DECLARE(apr_status_t) apr_jose_jwks_make(apr_jose_t **jose, + apr_json_value_t *keys, apr_pool_t *pool) +{ + apr_jose_t *j; + apr_jose_jwks_t *jwks; + apr_status_t status; + + status = apr_jose_make(jose, APR_JOSE_TYPE_JWKS, pool); + if (APR_SUCCESS != status) { + return status; + } + j = *jose; + + jwks = j->jose.jwks = apr_palloc(pool, sizeof(apr_jose_jwks_t)); + if (!jwks) { + return APR_ENOMEM; + } + + jwks->keys = keys; + + return APR_SUCCESS; +} + +APR_DECLARE(apr_status_t) apr_jose_jws_make(apr_jose_t **jose, + apr_jose_signature_t *signature, apr_array_header_t *signatures, + apr_jose_t *payload, apr_pool_t *pool) +{ + apr_jose_t *j; + apr_jose_jws_t *jws; + apr_status_t status; + + status = apr_jose_make(jose, APR_JOSE_TYPE_JWS, pool); + if (APR_SUCCESS != status) { + return status; + } + j = *jose; + + if (payload) { + j->cty = payload->cty; + } + + jws = j->jose.jws = apr_pcalloc(pool, sizeof(apr_jose_jws_t)); + if (!jws) { + return APR_ENOMEM; + } + + jws->signature = signature; + jws->signatures = signatures; + jws->payload = payload; + + return APR_SUCCESS; +} + +APR_DECLARE(apr_status_t) apr_jose_jws_json_make(apr_jose_t **jose, + apr_jose_signature_t *signature, apr_array_header_t *signatures, + apr_jose_t *payload, apr_pool_t *pool) +{ + apr_jose_t *j; + apr_jose_jws_t *jws; + apr_status_t status; + + status = apr_jose_make(jose, APR_JOSE_TYPE_JWS_JSON, pool); + if (APR_SUCCESS != status) { + return status; + } + j = *jose; + + if (payload) { + j->cty = payload->cty; + } + + jws = j->jose.jws = apr_pcalloc(pool, sizeof(apr_jose_jws_t)); + if (!jws) { + return APR_ENOMEM; + } + + jws->signature = signature; + jws->signatures = signatures; + jws->payload = payload; + + return APR_SUCCESS; +} + +APR_DECLARE(apr_status_t) apr_jose_jwt_make(apr_jose_t **jose, apr_json_value_t *claims, + apr_pool_t *pool) +{ + apr_jose_t *j; + apr_jose_jwt_t *jwt; + apr_status_t status; + + status = apr_jose_make(jose, APR_JOSE_TYPE_JWT, pool); + if (APR_SUCCESS != status) { + return status; + } + j = *jose; + + j->cty = "JWT"; + + jwt = j->jose.jwt = apr_palloc(pool, sizeof(apr_jose_jwt_t)); + if (!jwt) { + return APR_ENOMEM; + } + + jwt->claims = claims; + + return APR_SUCCESS; +} + +APR_DECLARE(apr_status_t) apr_jose_text_make(apr_jose_t **jose, const char *cty, + const char *in, apr_size_t inlen, apr_pool_t *pool) +{ + apr_jose_t *j; + apr_status_t status; + + status = apr_jose_make(jose, APR_JOSE_TYPE_TEXT, pool); + if (APR_SUCCESS != status) { + return status; + } + j = *jose; + + j->cty = cty; + j->jose.text = apr_palloc(pool, sizeof(apr_jose_text_t)); + if (!j->jose.text) { + return APR_ENOMEM; + } + j->jose.text->text = in; + j->jose.text->len = inlen; + + return APR_SUCCESS; +} diff --git a/jose/apr_jose_decode.c b/jose/apr_jose_decode.c new file mode 100644 index 000000000..7d0b01d49 --- /dev/null +++ b/jose/apr_jose_decode.c @@ -0,0 +1,1694 @@ +/* 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" + +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); +} + +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_jose_text_t in; + apr_off_t offset; + apr_status_t status; + + status = apr_jose_jwk_make(jose, NULL, pool); + if (APR_SUCCESS != status) { + return status; + } + + status = apr_jose_flatten(bb, &in, pool); + if (APR_SUCCESS != status) { + return status; + } + + status = apr_json_decode(&(*jose)->jose.jwk->key, in.text, in.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: JWK decoding failed at offset %" APR_OFF_T_FMT ": %s", + offset, buf); + + return status; + } + + return APR_SUCCESS; +} + +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_jose_text_t in; + apr_off_t offset; + apr_status_t status; + + status = apr_jose_jwks_make(jose, NULL, pool); + if (APR_SUCCESS != status) { + return status; + } + + status = apr_jose_flatten(bb, &in, pool); + if (APR_SUCCESS != status) { + return status; + } + + status = apr_json_decode(&(*jose)->jose.jwks->keys, in.text, in.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: JWKS decoding failed at offset %" APR_OFF_T_FMT ": %s", + offset, buf); + + return status; + } + + if ((*jose)->jose.jwks->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; +} + +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_jose_text_t in; + apr_off_t offset; + apr_status_t status; + + status = apr_jose_jwt_make(jose, NULL, pool); + if (APR_SUCCESS != status) { + return status; + } + + status = apr_jose_flatten(bb, &in, pool); + if (APR_SUCCESS != status) { + return status; + } + + status = apr_json_decode(&(*jose)->jose.jwt->claims, in.text, in.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: JWT decoding failed at offset %" APR_OFF_T_FMT ": %s", + offset, buf); + + return status; + } + + return APR_SUCCESS; +} + +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; + } + + status = apr_jose_data_make(jose, typ, (const unsigned char *) in.text, in.len, + pool); + if (APR_SUCCESS != status) { + return status; + } + + return status; +} + +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; +} + +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; +} + +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; + } + + status = apr_jose_jws_make(jose, NULL, NULL, NULL, pool); + if (APR_SUCCESS != status) { + return status; + } + jws = (*jose)->jose.jws; + + /* + * If using the JWS Compact Serialization, let the JOSE Header be + * the JWS Protected Header. + */ + + status = apr_jose_signature_make(&jws->signature, NULL, ph, pool); + if (APR_SUCCESS != status) { + return status; + } + + 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; +} + +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; + } + + status = apr_jose_jwe_make(jose, NULL, NULL, NULL, NULL, pool); + if (APR_SUCCESS != status) { + return status; + } + jwe = (*jose)->jose.jwe; + + status = apr_jose_encryption_make(&jwe->encryption, NULL, + NULL, pool); + if (APR_SUCCESS != status) { + return status; + } + + status = apr_jose_recipient_make(&jwe->recipient, NULL, pool); + if (APR_SUCCESS != status) { + return status; + } + + /* + * 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; +} + +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; + + status = apr_jose_make(jose, APR_JOSE_TYPE_NONE, pool); + if (APR_SUCCESS != status) { + return status; + } + + 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 { + + status = apr_jose_decode( + flags & APR_JOSE_FLAG_DECODE_ALL ? + &(*jose)->jose.jws->payload : jose, typ, bb, cb, + level, flags, pool); + } + + } + + return status; +} + +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; + } + + status = apr_jose_jws_json_make(jose, NULL, NULL, NULL, pool); + if (APR_SUCCESS != status) { + return status; + } + 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); + + 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; + } + + status = apr_jose_signature_make(&jws->signature, NULL, NULL, + pool); + if (APR_SUCCESS != status) { + return status; + } + + 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); + + return apr_jose_decode( + flags & APR_JOSE_FLAG_DECODE_ALL ? + &(*jose)->jose.jws->payload : jose, typ, bb, cb, + level, flags, pool); +} + +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; + } + + status = apr_jose_jwe_json_make(jose, NULL, NULL, NULL, NULL, pool); + if (APR_SUCCESS != status) { + return status; + } + jwe = (*jose)->jose.jwe; + + status = apr_jose_encryption_make(&jwe->encryption, NULL, + NULL, pool); + if (APR_SUCCESS != status) { + return status; + } + + /* + * 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) { + + 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) { + + 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; + +} + +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; + + status = apr_jose_make(jose, APR_JOSE_TYPE_NONE, pool); + if (APR_SUCCESS != status) { + return status; + } + + 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_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); +} diff --git a/jose/apr_jose_encode.c b/jose/apr_jose_encode.c new file mode 100644 index 000000000..43a4adc8b --- /dev/null +++ b/jose/apr_jose_encode.c @@ -0,0 +1,825 @@ +/* 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 + * limitations under the License. + */ + +#include "apr_jose.h" +#include "apr_encode.h" + +static apr_status_t apr_jose_encode_base64_json(apr_bucket_brigade *brigade, + apr_brigade_flush flush, void *ctx, apr_json_value_t *json, + apr_pool_t *pool) +{ + apr_status_t status = APR_SUCCESS; + + if (json) { + + apr_bucket_brigade *bb; + + bb = apr_brigade_create(pool, brigade->bucket_alloc); + + status = apr_json_encode(bb, flush, ctx, json, + APR_JSON_FLAGS_WHITESPACE, pool); + if (APR_SUCCESS == status) { + + char *buf; + apr_size_t buflen; + const char *buf64; + apr_size_t buf64len; + + apr_brigade_pflatten(bb, &buf, &buflen, pool); + buf64 = apr_pencode_base64(pool, buf, buflen, APR_ENCODE_BASE64URL, + &buf64len); + status = apr_brigade_write(brigade, flush, ctx, buf64, buf64len); + + } + + } + + return status; +} + +static apr_status_t apr_jose_encode_compact_jwe(apr_bucket_brigade *brigade, + apr_brigade_flush flush, void *ctx, apr_jose_t *jose, apr_jose_cb_t *cb, + apr_pool_t *p) +{ + apr_bucket_brigade *bb = apr_brigade_create(p, + brigade->bucket_alloc); + + apr_jose_jwe_t *jwe = jose->jose.jwe; + + apr_status_t status = APR_SUCCESS; + + /* + * 7.1. JWE Compact Serialization + * + * The JWE Compact Serialization represents encrypted content as a + * compact, URL-safe string. This string is: + * + * BASE64URL(UTF8(JWE Protected Header)) || '.' || + */ + + status = apr_jose_encode_base64_json(brigade, flush, ctx, + jwe->encryption->protected, p); + if (APR_SUCCESS != status) { + return status; + } + + status = apr_brigade_write(brigade, flush, ctx, ".", 1); + if (APR_SUCCESS != status) { + return status; + } + + if (cb && cb->encrypt) { + status = apr_jose_encode(bb, flush, ctx, jwe->payload, cb, p); + if (APR_SUCCESS != status) { + jose->result = jwe->payload->result; + return status; + } + status = cb->encrypt(bb, jose, jwe->recipient, jwe->encryption, + cb->ctx, p); + if (APR_SUCCESS != status) { + return status; + } + } + + if (APR_SUCCESS == status) { + + struct iovec vec[7]; + + /* + * 7. Compute the encoded key value BASE64URL(JWE Encrypted Key). + * + * 10. Compute the encoded Initialization Vector value BASE64URL(JWE + * Initialization Vector). + * + * 16. Compute the encoded ciphertext value BASE64URL(JWE Ciphertext). + * + * 17. Compute the encoded Authentication Tag value BASE64URL(JWE + * Authentication Tag). + * + * 18. If a JWE AAD value is present, compute the encoded AAD value + * BASE64URL(JWE AAD). + * + * 19. Create the desired serialized output. The Compact Serialization + * of this result is the string BASE64URL(UTF8(JWE Protected + * Header)) || '.' || BASE64URL(JWE Encrypted Key) || '.' || + * BASE64URL(JWE Initialization Vector) || '.' || BASE64URL(JWE + * Ciphertext) || '.' || BASE64URL(JWE Authentication Tag). The + * JWE JSON Serialization is described in Section 7.2. + */ + + /* + * BASE64URL(JWE Encrypted Key) || '.' || + */ + + vec[0].iov_base = (void *) apr_pencode_base64_binary(p, + jwe->recipient->ekey.data, jwe->recipient->ekey.len, + APR_ENCODE_BASE64URL, &vec[0].iov_len); + vec[1].iov_base = "."; + vec[1].iov_len = 1; + + /* + * BASE64URL(JWE Initialization Vector) || '.' || + */ + + vec[2].iov_base = (void *) apr_pencode_base64_binary(p, + jwe->encryption->iv.data, jwe->encryption->iv.len, + APR_ENCODE_BASE64URL, &vec[2].iov_len); + vec[3].iov_base = "."; + vec[3].iov_len = 1; + + /* + * BASE64URL(JWE Ciphertext) || '.' || + */ + + vec[4].iov_base = (void *) apr_pencode_base64_binary(p, + jwe->encryption->cipher.data, jwe->encryption->cipher.len, + APR_ENCODE_BASE64URL, &vec[4].iov_len); + vec[5].iov_base = "."; + vec[5].iov_len = 1; + + /* + * BASE64URL(JWE Authentication Tag) + */ + + vec[6].iov_base = (void *)apr_pencode_base64_binary(p, jwe->encryption->tag.data, jwe->encryption->tag.len, + APR_ENCODE_BASE64URL, &vec[6].iov_len); + + status = apr_brigade_writev(brigade, flush, ctx, vec, 7); + + } + + return status; +} + +static apr_status_t apr_jose_encode_compact_jws(apr_bucket_brigade *brigade, + apr_brigade_flush flush, void *ctx, apr_jose_t *jose, apr_jose_cb_t *cb, + apr_pool_t *p) +{ + apr_bucket *e; + apr_bucket_brigade *bb = apr_brigade_create(p, + brigade->bucket_alloc); + apr_jose_text_t payload; + apr_jose_text_t payload64; + + apr_jose_jws_t *jws = jose->jose.jws; + + apr_status_t status; + + status = apr_jose_encode(bb, flush, ctx, jws->payload, cb, p); + if (APR_SUCCESS != status) { + jose->result = jws->payload->result; + return status; + } + + status = apr_brigade_pflatten(bb, (char **)&payload.text, &payload.len, p); + if (APR_SUCCESS != status) { + return status; + } + + payload64.text = apr_pencode_base64(p, payload.text, payload.len, + APR_ENCODE_BASE64URL, &payload64.len); + + apr_brigade_cleanup(bb); + + /* + * 7.1. JWS Compact Serialization + * + * The JWS Compact Serialization represents digitally signed or MACed + * content as a compact, URL-safe string. This string is: + * + * BASE64URL(UTF8(JWS Protected Header)) || '.' || + */ + + if (jws->signature) { + status = apr_jose_encode_base64_json(bb, flush, ctx, + jws->signature->protected_header, p); + if (APR_SUCCESS != status) { + return status; + } + } + + status = apr_brigade_write(bb, flush, ctx, ".", 1); + if (APR_SUCCESS != status) { + return status; + } + + /* + * BASE64URL(JWS Payload) || + */ + + e = apr_bucket_pool_create(payload64.text, payload64.len, p, + bb->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(bb, e); + + /* + * '.' || BASE64URL(JWS Signature) + */ + + if (cb && cb->sign && jws->signature) { + status = cb->sign(bb, jose, jws->signature, cb->ctx, p); + if (APR_SUCCESS != status) { + return status; + } + } + + APR_BRIGADE_CONCAT(brigade, bb); + + status = apr_brigade_write(brigade, flush, ctx, ".", 1); + if (APR_SUCCESS != status) { + return status; + } + + if (jws->signature && jws->signature->sig.data && APR_SUCCESS == status) { + + const char *buf64; + apr_size_t buf64len; + + buf64 = apr_pencode_base64_binary(p, jws->signature->sig.data, + jws->signature->sig.len, + APR_ENCODE_BASE64URL, &buf64len); + status = apr_brigade_write(brigade, flush, ctx, buf64, + buf64len); + + } + + return status; +} + +static apr_status_t apr_jose_encode_json_jwe(apr_bucket_brigade *brigade, + apr_brigade_flush flush, void *ctx, apr_jose_t *jose, apr_jose_cb_t *cb, + apr_pool_t *p) +{ + + apr_json_value_t *json, *key, *val; + char *buf; + const char *buf64; + apr_size_t len; + apr_size_t len64; + + apr_bucket_brigade *bb = apr_brigade_create(p, + brigade->bucket_alloc); + + apr_jose_jwe_t *jwe = jose->jose.jwe; + + apr_status_t status = APR_SUCCESS; + + /* create our json */ + + json = apr_json_object_create(p); + + /* create protected header */ + + if (jwe->encryption) { + apr_jose_encryption_t *e = jwe->encryption; + + if (e->protected) { + + status = apr_jose_encode_base64_json(bb, flush, ctx, + e->protected, p); + if (APR_SUCCESS != status) { + return status; + } + + status = apr_brigade_pflatten(bb, &buf, &len, p); + if (APR_SUCCESS != status) { + return status; + } + + apr_brigade_cleanup(bb); + + key = apr_json_string_create(p, "protected", + APR_JSON_VALUE_STRING); + val = apr_json_string_create(p, buf, len); + apr_json_object_set(json, key, val, p); + + } + + /* create unprotected header */ + + if (e->unprotected) { + + key = apr_json_string_create(p, "unprotected", + APR_JSON_VALUE_STRING); + apr_json_object_set(json, key, e->unprotected, p); + + } + + /* create recipient */ + if (jwe->recipient) { + + apr_jose_recipient_t *recip = jwe->recipient; + + /* create the payload */ + + status = apr_jose_encode(bb, flush, ctx, jwe->payload, cb, p); + if (APR_SUCCESS != status) { + jose->result = jwe->payload->result; + return status; + } + + if (cb && cb->encrypt && recip) { + status = cb->encrypt(bb, jose, recip, e, cb->ctx, p); + if (APR_SUCCESS != status) { + return status; + } + } + + apr_brigade_cleanup(bb); + + /* create header */ + + key = apr_json_string_create(p, "header", + APR_JSON_VALUE_STRING); + apr_json_object_set(json, key, recip->header, p); + + apr_brigade_cleanup(bb); + + /* create encrypted key */ + + buf64 = apr_pencode_base64_binary(p, recip->ekey.data, + recip->ekey.len, + APR_ENCODE_BASE64URL, &len64); + + key = apr_json_string_create(p, "encrypted_key", + APR_JSON_VALUE_STRING); + val = apr_json_string_create(p, buf64, len64); + apr_json_object_set(json, key, val, p); + + } + + /* create recipients */ + if (jwe->recipients) { + + apr_json_value_t *recips; + int i; + + /* create recipients element */ + + key = apr_json_string_create(p, "recipients", + APR_JSON_VALUE_STRING); + recips = apr_json_array_create(p, jwe->recipients->nelts); + apr_json_object_set(json, key, recips, p); + + /* populate each recipient */ + + for (i = 0; i < jwe->recipients->nelts; i++) { + apr_json_value_t *r = apr_json_object_create(p); + apr_jose_recipient_t *recip = APR_ARRAY_IDX( + jwe->recipients, i, apr_jose_recipient_t *); + + if (!recip) { + continue; + } + + apr_json_array_add(recips, r); + + /* create the payload */ + + status = apr_jose_encode(bb, flush, ctx, jwe->payload, cb, p); + if (APR_SUCCESS != status) { + jose->result = jwe->payload->result; + return status; + } + + if (cb && cb->encrypt && recip) { + status = cb->encrypt(bb, jose, recip, e, cb->ctx, p); + if (APR_SUCCESS != status) { + return status; + } + } + + apr_brigade_cleanup(bb); + + /* create header */ + + key = apr_json_string_create(p, "header", + APR_JSON_VALUE_STRING); + apr_json_object_set(r, key, recip->header, p); + + apr_brigade_cleanup(bb); + + /* create encrypted key */ + + buf64 = apr_pencode_base64_binary(p, recip->ekey.data, + recip->ekey.len, + APR_ENCODE_BASE64URL, &len64); + + key = apr_json_string_create(p, "encrypted_key", + APR_JSON_VALUE_STRING); + val = apr_json_string_create(p, buf64, len64); + apr_json_object_set(r, key, val, p); + + } + if (APR_SUCCESS != status) { + return status; + } + + } + + /* create iv */ + + if (e->iv.len) { + + buf64 = apr_pencode_base64_binary(p, e->iv.data, e->iv.len, + APR_ENCODE_BASE64URL, &len64); + + key = apr_json_string_create(p, "iv", APR_JSON_VALUE_STRING); + val = apr_json_string_create(p, buf64, len64); + apr_json_object_set(json, key, val, p); + } + + /* create aad */ + + if (e->aad.len) { + + buf64 = apr_pencode_base64_binary(p, e->aad.data, e->aad.len, + APR_ENCODE_BASE64URL, &len64); + + key = apr_json_string_create(p, "aad", APR_JSON_VALUE_STRING); + val = apr_json_string_create(p, buf64, len64); + apr_json_object_set(json, key, val, p); + } + + /* create ciphertext */ + + if (e->cipher.len) { + + buf64 = apr_pencode_base64_binary(p, e->cipher.data, e->cipher.len, + APR_ENCODE_BASE64URL, &len64); + + key = apr_json_string_create(p, "ciphertext", APR_JSON_VALUE_STRING); + val = apr_json_string_create(p, buf64, len64); + apr_json_object_set(json, key, val, p); + } + + /* create tag */ + + if (e->tag.len) { + + buf64 = apr_pencode_base64_binary(p, e->tag.data, e->tag.len, + APR_ENCODE_BASE64URL, &len64); + + key = apr_json_string_create(p, "tag", APR_JSON_VALUE_STRING); + val = apr_json_string_create(p, buf64, len64); + apr_json_object_set(json, key, val, p); + } + + } + + /* write out our final result */ + + if (json) { + status = apr_json_encode(brigade, flush, ctx, json, + APR_JSON_FLAGS_WHITESPACE, p); + } + + return status; +} + +static apr_status_t apr_jose_encode_json_jws(apr_bucket_brigade *brigade, + apr_brigade_flush flush, void *ctx, apr_jose_t *jose, apr_jose_cb_t *cb, + apr_pool_t *p) +{ + apr_json_value_t *json, *key, *val; + char *buf; + const char *buf64; + apr_size_t len; + apr_size_t len64; + + apr_bucket_brigade *bb = apr_brigade_create(p, + brigade->bucket_alloc); + + apr_jose_jws_t *jws = jose->jose.jws; + + apr_status_t status = APR_SUCCESS; + + /* create our json */ + + json = apr_json_object_create(p); + + /* calculate BASE64URL(JWS Payload) */ + + status = apr_jose_encode(bb, flush, ctx, jws->payload, cb, p); + if (APR_SUCCESS != status) { + jose->result = jws->payload->result; + return status; + } + + status = apr_brigade_pflatten(bb, &buf, &len, p); + if (APR_SUCCESS != status) { + return status; + } + + buf64 = apr_pencode_base64(p, buf, len, + APR_ENCODE_BASE64URL, &len64); + + apr_brigade_cleanup(bb); + + /* add the payload to our json */ + + key = apr_json_string_create(p, "payload", APR_JSON_VALUE_STRING); + val = apr_json_string_create(p, buf64, len64); + apr_json_object_set(json, key, val, p); + + /* calculate the flattened signature */ + + if (jws->signature) { + + /* create protected header */ + + status = apr_jose_encode_base64_json(bb, flush, ctx, + jws->signature->protected_header, p); + if (APR_SUCCESS != status) { + return status; + } + + status = apr_brigade_pflatten(bb, &buf, &len, p); + if (APR_SUCCESS != status) { + return status; + } + + key = apr_json_string_create(p, "protected", APR_JSON_VALUE_STRING); + val = apr_json_string_create(p, buf, len); + apr_json_object_set(json, key, val, p); + + status = apr_brigade_write(bb, flush, ctx, ".", 1); + if (APR_SUCCESS != status) { + return status; + } + + status = apr_brigade_write(bb, flush, ctx, buf64, len64); + if (APR_SUCCESS != status) { + return status; + } + + if (cb && cb->sign && jws->signature) { + status = cb->sign(bb, jose, jws->signature, cb->ctx, p); + if (APR_SUCCESS != status) { + return status; + } + } + + apr_brigade_cleanup(bb); + + /* create header */ + + key = apr_json_string_create(p, "header", APR_JSON_VALUE_STRING); + apr_json_object_set(json, key, jws->signature->header, p); + + apr_brigade_cleanup(bb); + + /* create signature */ + + buf64 = apr_pencode_base64_binary(p, + jws->signature->sig.data, jws->signature->sig.len, + APR_ENCODE_BASE64URL, &len64); + + key = apr_json_string_create(p, "signature", APR_JSON_VALUE_STRING); + val = apr_json_string_create(p, buf64, len64); + apr_json_object_set(json, key, val, p); + + } + + /* otherwise calculate the general signatures */ + + else if (jws->signatures) { + + apr_json_value_t *sigs; + int i; + + /* create signatures element */ + + key = apr_json_string_create(p, "signatures", APR_JSON_VALUE_STRING); + sigs = apr_json_array_create(p, jws->signatures->nelts); + apr_json_object_set(json, key, sigs, p); + + /* populate each signature */ + + for (i = 0; i < jws->signatures->nelts; i++) { + apr_json_value_t *s = apr_json_object_create(p); + apr_jose_signature_t *sig = APR_ARRAY_IDX( + jws->signatures, i, apr_jose_signature_t *); + + apr_json_array_add(sigs, s); + + /* create protected header */ + + status = apr_jose_encode_base64_json(bb, flush, ctx, + sig->protected_header, p); + if (APR_SUCCESS != status) { + return status; + } + + status = apr_brigade_pflatten(bb, &buf, &len, p); + if (APR_SUCCESS != status) { + return status; + } + + /* add protected header to array */ + + key = apr_json_string_create(p, "protected", APR_JSON_VALUE_STRING); + val = apr_json_string_create(p, buf, len); + apr_json_object_set(s, key, val, p); + + status = apr_brigade_write(bb, flush, ctx, ".", 1); + if (APR_SUCCESS != status) { + return status; + } + + status = apr_brigade_write(bb, flush, ctx, buf64, len64); + if (APR_SUCCESS != status) { + return status; + } + + if (cb && cb->sign && sig) { + status = cb->sign(bb, jose, sig, cb->ctx, p); + if (APR_SUCCESS != status) { + return status; + } + } + + apr_brigade_cleanup(bb); + + /* create header */ + + key = apr_json_string_create(p, "header", APR_JSON_VALUE_STRING); + apr_json_object_set(s, key, sig->header, p); + + apr_brigade_cleanup(bb); + + /* create signature */ + + buf64 = apr_pencode_base64_binary(p, sig->sig.data, + sig->sig.len, + APR_ENCODE_BASE64URL, &len64); + + key = apr_json_string_create(p, "signature", APR_JSON_VALUE_STRING); + val = apr_json_string_create(p, buf64, len64); + apr_json_object_set(s, key, val, p); + + } + if (APR_SUCCESS != status) { + return status; + } + + } + + /* write out our final result */ + + if (json) { + status = apr_json_encode(brigade, flush, ctx, json, + APR_JSON_FLAGS_WHITESPACE, p); + } + + return status; +} + +apr_status_t apr_jose_encode(apr_bucket_brigade *brigade, + apr_brigade_flush flush, void *ctx, apr_jose_t *jose, apr_jose_cb_t *cb, + apr_pool_t *pool) +{ + apr_pool_t *p; + apr_status_t status = APR_EINVAL; + + /* if asked to encode nothing, encode nothing */ + if (jose == NULL) { + return APR_SUCCESS; + } + + apr_pool_create(&p, pool); + if (p == NULL) { + return APR_ENOMEM; + } + + /* first, generic data types */ + switch (jose->type) { + case APR_JOSE_TYPE_JWK: { + + /* do nothing for now */ + + break; + } + + case APR_JOSE_TYPE_JWKS: { + + /* do nothing for now */ + + break; + } + + case APR_JOSE_TYPE_DATA: { + + apr_jose_data_t *data = jose->jose.data; + + if (data) { + + struct iovec vec[1]; + + vec[0].iov_base = (void *)data->data; + vec[0].iov_len = data->len; + + status = apr_brigade_writev(brigade, flush, ctx, vec, 1); + if (APR_SUCCESS != status) { + break; + } + } + + break; + } + + case APR_JOSE_TYPE_TEXT: { + + apr_jose_text_t *text = jose->jose.text; + + if (text) { + status = apr_brigade_write(brigade, flush, ctx, text->text, + text->len); + if (APR_SUCCESS != status) { + break; + } + } + + break; + } + + case APR_JOSE_TYPE_JSON: { + + apr_json_value_t *json = jose->jose.json->json; + + if (json) { + status = apr_json_encode(brigade, flush, ctx, json, + APR_JSON_FLAGS_WHITESPACE, p); + } + + break; + } + + case APR_JOSE_TYPE_JWE: { + + status = apr_jose_encode_compact_jwe(brigade, flush, ctx, jose, cb, + p); + + break; + } + + case APR_JOSE_TYPE_JWE_JSON: { + + status = apr_jose_encode_json_jwe(brigade, flush, ctx, jose, cb, p); + + break; + } + + case APR_JOSE_TYPE_JWS: { + + status = apr_jose_encode_compact_jws(brigade, flush, ctx, jose, cb, + p); + + break; + } + + case APR_JOSE_TYPE_JWS_JSON: { + + status = apr_jose_encode_json_jws(brigade, flush, ctx, jose, cb, p); + + break; + } + + case APR_JOSE_TYPE_JWT: { + + apr_json_value_t *claims = jose->jose.jwt->claims; + + if (claims) { + status = apr_json_encode(brigade, flush, ctx, claims, + APR_JSON_FLAGS_WHITESPACE, p); + } + + break; + } + + default: { + apr_errprintf(&jose->result, pool, NULL, 0, + "JOSE type '%d' not recognised", jose->type); + + status = APR_ENOTIMPL; + + break; + } + } + + apr_pool_destroy(p); + + return status; +} |