/* * parse JSON object from string in to JWT value */ static apr_byte_t apr_jwt_base64url_decode_object(apr_pool_t *pool, const char *str, apr_jwt_value_t *value, apr_jwt_error_t *err) { /* base64url-decode the string representation into value->str */ if (apr_jwt_base64url_decode(pool, &value->str, str, 1) <= 0) { apr_jwt_error(err, "apr_jwt_base64url_decode of (%s) failed", str); return FALSE; } /* decode the string in to a JSON structure into value->json */ json_error_t json_error; value->json = json_loads(value->str, 0, &json_error); /* check that we've actually got a JSON value back */ if (value->json == NULL) { apr_jwt_error(err, "JSON parsing (json_loads) failed: %s (%s)", json_error.text, value->str); return FALSE; } /* check that the value is a JSON object */ if (!json_is_object(value->json)) { apr_jwt_error(err, "JSON value is not an object"); return FALSE; } return TRUE; }
/* * parse an RSA JWK in raw format (n,e,d) */ static apr_byte_t apr_jwk_parse_rsa_raw(apr_pool_t *pool, json_t *json, apr_jwk_key_rsa_t **jwk_key_rsa, apr_jwt_error_t *err) { /* allocate space */ *jwk_key_rsa = apr_pcalloc(pool, sizeof(apr_jwk_key_rsa_t)); apr_jwk_key_rsa_t *key = *jwk_key_rsa; /* parse the mandatory modulus */ char *s_modulus = NULL; if (apr_jwt_get_string(pool, json, "n", TRUE, &s_modulus, err) == FALSE) return FALSE; /* base64url decode the modulus and get its size */ key->modulus_len = apr_jwt_base64url_decode(pool, (char **) &key->modulus, s_modulus, 1); if (key->modulus_len <= 0) { apr_jwt_error(err, "apr_jwt_base64url_decode of modulus failed"); return FALSE; } /* parse the mandatory exponent */ char *s_exponent = NULL; if (apr_jwt_get_string(pool, json, "e", TRUE, &s_exponent, err) == FALSE) return FALSE; /* base64url decode the exponent and get its size */ key->exponent_len = apr_jwt_base64url_decode(pool, (char **) &key->exponent, s_exponent, 1); if (key->exponent_len <= 0) { apr_jwt_error(err, "apr_jwt_base64url_decode of exponent failed"); return FALSE; } /* parse the optional private exponent */ char *s_private_exponent = NULL; apr_jwt_get_string(pool, json, "d", FALSE, &s_private_exponent, NULL); if (s_private_exponent != NULL) { /* base64url decode the private exponent and get its size */ key->private_exponent_len = apr_jwt_base64url_decode(pool, (char **) &key->private_exponent, s_private_exponent, 1); if (key->private_exponent_len <= 0) { apr_jwt_error(err, "apr_jwt_base64url_decode of private exponent failed"); return FALSE; } } /* that went well */ return TRUE; }
/* * convert RSA key to JWK JSON string representation and kid */ apr_byte_t apr_jwk_to_json(apr_pool_t *pool, apr_jwk_t *jwk, char **s_json, apr_jwt_error_t *err) { if (jwk->type != APR_JWK_KEY_RSA) { apr_jwt_error(err, "non RSA keys (%d) not yet supported", jwk->type); return FALSE; } apr_jwk_key_rsa_t *key = jwk->key.rsa; unsigned char *n_enc = NULL; int n_len = apr_jwt_base64url_encode(pool, (char **) &n_enc, (const char *) key->modulus, key->modulus_len, 0); if (n_len <= 0) { apr_jwt_error(err, "apr_jwt_base64url_encode of modulus failed"); return FALSE; } unsigned char *e_enc = NULL; if (apr_jwt_base64url_encode(pool, (char **) &e_enc, (const char *) key->exponent, key->exponent_len, 0) <= 0) { apr_jwt_error(err, "apr_jwt_base64url_encode of public exponent failed"); return FALSE; } unsigned char *d_enc = NULL; if (key->private_exponent_len > 0) { if (apr_jwt_base64url_encode(pool, (char **) &d_enc, (const char *) key->private_exponent, key->private_exponent_len, 0) <= 0) { apr_jwt_error(err, "apr_jwt_base64url_encode of private exponent failed"); return FALSE; } } char *p = apr_psprintf(pool, "{ \"kty\" : \"RSA\""); p = apr_psprintf(pool, "%s, \"n\": \"%s\"", p, n_enc); p = apr_psprintf(pool, "%s, \"e\": \"%s\"", p, e_enc); if (d_enc != NULL) p = apr_psprintf(pool, "%s, \"d\": \"%s\"", p, d_enc); p = apr_psprintf(pool, "%s, \"kid\" : \"%s\"", p, jwk->kid); p = apr_psprintf(pool, "%s }", p); *s_json = p; return TRUE; }
/* * verify HMAC signature on JWT */ static apr_byte_t apr_jws_verify_hmac(apr_pool_t *pool, apr_jwt_t *jwt, apr_jwk_t *jwk, apr_jwt_error_t *err) { if (jwk->type != APR_JWK_KEY_OCT) { apr_jwt_error(err, "key type of provided JWK cannot be used for HMAC verification: %d", jwk->type); return FALSE; } /* get the OpenSSL digest function */ const EVP_MD *digest = NULL; if ((digest = apr_jws_crypto_alg_to_evp(pool, jwt->header.alg, err)) == NULL) return FALSE; /* prepare the message */ unsigned char *msg = (unsigned char *) jwt->message; unsigned int msg_len = strlen(jwt->message); /* prepare the hash */ unsigned int md_len = 0; unsigned char md[EVP_MAX_MD_SIZE]; /* apply the HMAC function to the message with the provided key */ if (!HMAC(digest, jwk->key.oct->k, jwk->key.oct->k_len, msg, msg_len, md, &md_len)) { apr_jwt_error_openssl(err, "HMAC"); return FALSE; } /* check that the length of the hash matches what was provided to us in the signature */ if (md_len != jwt->signature.length) { apr_jwt_error(err, "calculated hash length (%d) differs from the length of the signature provided in the JWT (%d)", md_len, jwt->signature.length); return FALSE; } /* do a comparison of the provided hash value against calculated hash value */ if (apr_jwt_memcmp(md, jwt->signature.bytes, md_len) == FALSE) { apr_jwt_error(err, "calculated hash differs from the signature provided in the JWT"); return FALSE; } /* all OK if we got to here */ return TRUE; }
/* * parse JSON JWK */ apr_byte_t apr_jwk_parse_json(apr_pool_t *pool, json_t *json, apr_jwk_t **j_jwk, apr_jwt_error_t *err) { /* check that we've actually got a JSON value back */ if (json == NULL) { apr_jwt_error(err, "JWK JSON is NULL"); return FALSE; } /* check that the value is a JSON object */ if (!json_is_object(json)) { apr_jwt_error(err, "JWK JSON is not a JSON object"); return FALSE; } /* allocate memory for the JWK */ *j_jwk = apr_pcalloc(pool, sizeof(apr_jwk_t)); apr_jwk_t *jwk = *j_jwk; /* get the mandatory key type */ char *kty = NULL; if (apr_jwt_get_string(pool, json, "kty", TRUE, &kty, err) == FALSE) return FALSE; /* get the optional kid */ apr_jwt_get_string(pool, json, "kid", FALSE, &jwk->kid, NULL); /* parse the key */ if (apr_strnatcmp(kty, "RSA") == 0) return apr_jwk_parse_rsa(pool, json, jwk, err); if (apr_strnatcmp(kty, "EC") == 0) return apr_jwk_parse_ec(pool, json, jwk, err); if (apr_strnatcmp(kty, "oct") == 0) return apr_jwk_parse_oct(pool, json, jwk, err); apr_jwt_error(err, "wrong or unsupported JWK key representation \"%s\" (\"RSA\", \"EC\" and \"oct\" are supported key types)", kty); return FALSE; }
/* * get (optional) string from JWT */ apr_byte_t apr_jwt_get_string(apr_pool_t *pool, json_t *json, const char *claim_name, apr_byte_t is_mandatory, char **result, apr_jwt_error_t *err) { json_t *v = json_object_get(json, claim_name); if (v != NULL) { if (json_is_string(v)) { *result = apr_pstrdup(pool, json_string_value(v)); } else if (is_mandatory) { apr_jwt_error(err, "mandatory JSON key \"%s\" was found but the type is not a string", claim_name); return FALSE; } } else if (is_mandatory) { apr_jwt_error(err, "mandatory JSON key \"%s\" could not be found", claim_name); return FALSE; } return TRUE; }
/* * verify the signature on a JWT */ apr_byte_t apr_jws_verify(apr_pool_t *pool, apr_jwt_t *jwt, apr_hash_t *keys, apr_jwt_error_t *err) { apr_byte_t rc = FALSE; apr_jwk_t *jwk = NULL; apr_hash_index_t *hi; if (jwt->header.kid != NULL) { jwk = apr_hash_get(keys, jwt->header.kid, APR_HASH_KEY_STRING); if (jwk != NULL) { rc = apr_jws_verify_with_jwk(pool, jwt, jwk, err); } else { apr_jwt_error(err, "could not find key with kid: %s", jwt->header.kid); rc = FALSE; } } else { for (hi = apr_hash_first(pool, keys); hi; hi = apr_hash_next(hi)) { apr_hash_this(hi, NULL, NULL, (void **) &jwk); rc = apr_jws_verify_with_jwk(pool, jwt, jwk, err); if (rc == TRUE) break; } if (rc == FALSE) apr_jwt_error(err, "could not verify signature against any of the (%d) provided keys%s", apr_hash_count(keys), apr_hash_count(keys) > 0 ? "" : apr_psprintf(pool, "; you have probably provided no or incorrect keys/key-types for algorithm: %s", jwt->header.alg)); } return rc; }
/* * parse (optional) timestamp from payload */ static apr_byte_t apr_jwt_get_timestamp(apr_pool_t *pool, json_t *json, const char *claim_name, apr_byte_t is_mandatory, json_int_t *result, apr_jwt_error_t *err) { *result = APR_JWT_CLAIM_TIME_EMPTY; json_t *v = json_object_get(json, claim_name); if (v != NULL) { if (json_is_integer(v)) { *result = json_integer_value(v); } else if (is_mandatory) { apr_jwt_error(err, "mandatory JSON key \"%s\" was found but the type is not a number", claim_name); return FALSE; } } else if (is_mandatory) { apr_jwt_error(err, "mandatory JSON key \"%s\" could not be found", claim_name); return FALSE; } return TRUE; }
/* * return an EVP structure for the specified algorithm */ const EVP_MD *apr_jws_crypto_alg_to_evp(apr_pool_t *pool, const char *alg, apr_jwt_error_t *err) { const EVP_MD *result = NULL; char *digest = apr_jws_alg_to_openssl_digest(alg); if (digest == NULL) { apr_jwt_error(err, "no OpenSSL digest algorithm name found for algorithm \"%s\"", alg); return NULL; } result = EVP_get_digestbyname(digest); if (result == NULL) { apr_jwt_error(err, "no OpenSSL digest algorithm found for algorithm \"%s\"", digest); return NULL; } return result; }
/* * parse a JOSE header from a compact serialized string */ apr_byte_t apr_jwt_header_parse(apr_pool_t *pool, const char *s_json, apr_array_header_t **unpacked, apr_jwt_header_t *header, apr_jwt_error_t *err) { *unpacked = apr_jwt_compact_deserialize(pool, s_json); if ((*unpacked)->nelts < 1) { apr_jwt_error(err, "could not deserialize at least one element"); return FALSE; } if (apr_jwt_parse_header_object(pool, ((const char**) (*unpacked)->elts)[0], header, err) == FALSE) return FALSE; return TRUE; }
/* * hash a string value with the specified algorithm */ apr_byte_t apr_jws_hash_string(apr_pool_t *pool, const char *alg, const char *msg, char **hash, unsigned int *hash_len, apr_jwt_error_t *err) { char *s_digest = apr_jws_alg_to_openssl_digest(alg); if (s_digest == NULL) { apr_jwt_error(err, "no OpenSSL digest algorithm name found for algorithm \"%s\"", alg); return FALSE; } return apr_jws_hash_bytes(pool, s_digest, (const unsigned char *) msg, strlen(msg), (unsigned char **) hash, hash_len, err); }
/* * parse an EC JWK */ static apr_byte_t apr_jwk_parse_ec(apr_pool_t *pool, json_t *json, apr_jwk_t *jwk, apr_jwt_error_t *err) { /* allocated space and set key type */ jwk->type = APR_JWK_KEY_EC; jwk->key.ec = apr_pcalloc(pool, sizeof(apr_jwk_key_ec_t)); /* parse x */ char *s_x = NULL; if (apr_jwt_get_string(pool, json, "x", TRUE, &s_x, err) == FALSE) return FALSE; /* base64url decode x and get its size */ jwk->key.ec->x_len = apr_jwt_base64url_decode(pool, (char **) &jwk->key.ec->x, s_x, 1); if (jwk->key.ec->x_len <= 0) { apr_jwt_error(err, "apr_jwt_base64url_decode of x length failed"); return FALSE; } /* parse y */ char *s_y = NULL; if (apr_jwt_get_string(pool, json, "y", TRUE, &s_y, err) == FALSE) return FALSE; /* base64url decode y and get its size */ jwk->key.ec->y_len = apr_jwt_base64url_decode(pool, (char **) &jwk->key.ec->y, s_y, 1); if (jwk->key.ec->y_len <= 0) { apr_jwt_error(err, "apr_jwt_base64url_decode of y length failed"); return FALSE; } /* that went well */ return TRUE; }
/* * parse an RSA JWK */ static apr_byte_t apr_jwk_parse_rsa(apr_pool_t *pool, json_t *json, apr_jwk_t *jwk, apr_jwt_error_t *err) { jwk->type = APR_JWK_KEY_RSA; char *s_test = NULL; apr_jwt_get_string(pool, json, "n", FALSE, &s_test, NULL); if (s_test != NULL) return apr_jwk_parse_rsa_raw(pool, json, &jwk->key.rsa, err); json_t *v = json_object_get(json, "x5c"); if (v != NULL) return apr_jwk_parse_rsa_x5c(pool, json, jwk, err); apr_jwt_error(err, "wrong or unsupported RSA key representation, no \"n\" or \"x5c\" key found in JWK JSON value"); return FALSE; }
/* * parse a an octet sequence used to represent a symmetric key */ static apr_byte_t apr_jwk_parse_oct(apr_pool_t *pool, json_t *json, apr_jwk_t *jwk, apr_jwt_error_t *err) { /* allocated space and set key type */ jwk->type = APR_JWK_KEY_OCT; jwk->key.oct = apr_pcalloc(pool, sizeof(apr_jwk_key_oct_t)); /* parse k */ char *s_k = NULL; if (apr_jwt_get_string(pool, json, "k", TRUE, &s_k, err) == FALSE) return FALSE; /* base64url decode k and get its size */ jwk->key.oct->k_len = apr_jwt_base64url_decode(pool, (char **) &jwk->key.oct->k, s_k, 1); if (jwk->key.oct->k_len <= 0) { apr_jwt_error(err, "apr_jwt_base64url_decode of k length failed"); return FALSE; } /* that went well */ return TRUE; }
/* * hash a byte sequence with the specified algorithm */ apr_byte_t apr_jws_hash_bytes(apr_pool_t *pool, const char *s_digest, const unsigned char *input, unsigned int input_len, unsigned char **output, unsigned int *output_len, apr_jwt_error_t *err) { unsigned char md_value[EVP_MAX_MD_SIZE]; EVP_MD_CTX ctx; EVP_MD_CTX_init(&ctx); const EVP_MD *evp_digest = NULL; if ((evp_digest = EVP_get_digestbyname(s_digest)) == NULL) { apr_jwt_error(err, "no OpenSSL digest algorithm found for algorithm \"%s\"", s_digest); return FALSE; } if (!EVP_DigestInit_ex(&ctx, evp_digest, NULL)) { apr_jwt_error_openssl(err, "EVP_DigestInit_ex"); return FALSE; } if (!EVP_DigestUpdate(&ctx, input, input_len)) { apr_jwt_error_openssl(err, "EVP_DigestUpdate"); return FALSE; } if (!EVP_DigestFinal_ex(&ctx, md_value, output_len)) { apr_jwt_error_openssl(err, "EVP_DigestFinal_ex"); return FALSE; } EVP_MD_CTX_cleanup(&ctx); *output = apr_pcalloc(pool, *output_len); memcpy(*output, md_value, *output_len); return TRUE; }
/* * calculate a hash and base64url encode the result */ static apr_byte_t apr_jwk_hash_and_base64urlencode(apr_pool_t *pool, const unsigned char *input, const int input_len, char **output, apr_jwt_error_t *err) { unsigned int hash_len = SHA_DIGEST_LENGTH; unsigned char hash[SHA_DIGEST_LENGTH]; // TODO: upgrade to SHA2? /* hash it */ if (!SHA1(input, input_len, hash)) { apr_jwt_error_openssl(err, "SHA1"); return FALSE; } /* base64url encode the key fingerprint */ if (apr_jwt_base64url_encode(pool, output, (const char *) hash, hash_len, 0) <= 0) { apr_jwt_error(err, "apr_jwt_base64url_encode of hash failed"); return FALSE; } return TRUE; }
/* * parse an RSA JWK in X.509 format (x5c) */ static apr_byte_t apr_jwk_parse_rsa_x5c(apr_pool_t *pool, json_t *json, apr_jwk_t *jwk, apr_jwt_error_t *err) { apr_byte_t rv = FALSE; /* get the "x5c" array element from the JSON object */ json_t *v = json_object_get(json, "x5c"); if (v == NULL) { apr_jwt_error(err, "JSON key \"%s\" could not be found", "x5c"); return FALSE; } if (!json_is_array(v)) { apr_jwt_error(err, "JSON key \"%s\" was found but its value is not a JSON array", "x5c"); return FALSE; } /* take the first element of the array */ v = json_array_get(v, 0); if (v == NULL) { apr_jwt_error(err, "first element in JSON array is \"null\""); return FALSE; } if (!json_is_string(v)) { apr_jwt_error(err, "first element in array is not a JSON string"); return FALSE; } const char *s_x5c = json_string_value(v); /* PEM-format it */ const int len = 75; int i = 0; char *s = apr_psprintf(pool, "-----BEGIN CERTIFICATE-----\n"); while (i < strlen(s_x5c)) { s = apr_psprintf(pool, "%s%s\n", s, apr_pstrndup(pool, s_x5c + i, len)); i += len; } s = apr_psprintf(pool, "%s-----END CERTIFICATE-----\n", s); BIO *input = NULL; /* put it in BIO memory */ if ((input = BIO_new(BIO_s_mem())) == NULL) { apr_jwt_error_openssl(err, "memory allocation BIO_new/BIO_s_mem"); return FALSE; } if (BIO_puts(input, s) <= 0) { BIO_free(input); apr_jwt_error_openssl(err, "BIO_puts"); return FALSE; } /* do the actual parsing */ rv = apr_jwk_rsa_bio_to_key(pool, input, &jwk->key.rsa, FALSE, err); BIO_free(input); return rv; }
/* * verify EC signature on JWT */ static apr_byte_t apr_jws_verify_ec(apr_pool_t *pool, apr_jwt_t *jwt, apr_jwk_t *jwk, apr_jwt_error_t *err) { int nid = apr_jws_ec_alg_to_curve(jwt->header.alg); if (nid == -1) { apr_jwt_error(err, "no OpenSSL Elliptic Curve identifier found for algorithm \"%s\"", jwt->header.alg); return FALSE; } EC_GROUP *curve = EC_GROUP_new_by_curve_name(nid); if (curve == NULL) { apr_jwt_error(err, "no OpenSSL Elliptic Curve found for algorithm \"%s\"", jwt->header.alg); return FALSE; } apr_byte_t rc = FALSE; /* get the OpenSSL digest function */ const EVP_MD *digest = NULL; if ((digest = apr_jws_crypto_alg_to_evp(pool, jwt->header.alg, err)) == NULL) return FALSE; EVP_MD_CTX ctx; EVP_MD_CTX_init(&ctx); EC_KEY * pubkey = EC_KEY_new(); EC_KEY_set_group(pubkey, curve); BIGNUM * x = BN_new(); BIGNUM * y = BN_new(); BN_bin2bn(jwk->key.ec->x, jwk->key.ec->x_len, x); BN_bin2bn(jwk->key.ec->y, jwk->key.ec->y_len, y); if (!EC_KEY_set_public_key_affine_coordinates(pubkey, x, y)) { apr_jwt_error_openssl(err, "EC_KEY_set_public_key_affine_coordinates"); return FALSE; } EVP_PKEY* pEcKey = EVP_PKEY_new(); if (!EVP_PKEY_assign_EC_KEY(pEcKey, pubkey)) { pEcKey = NULL; apr_jwt_error_openssl(err, "EVP_PKEY_assign_EC_KEY"); goto end; } ctx.pctx = EVP_PKEY_CTX_new(pEcKey, NULL); if (!EVP_PKEY_verify_init(ctx.pctx)) { apr_jwt_error_openssl(err, "EVP_PKEY_verify_init"); goto end; } if (!EVP_VerifyInit_ex(&ctx, digest, NULL)) { apr_jwt_error_openssl(err, "EVP_VerifyInit_ex"); goto end; } if (!EVP_VerifyUpdate(&ctx, jwt->message, strlen(jwt->message))) { apr_jwt_error_openssl(err, "EVP_VerifyUpdate"); goto end; } if (!EVP_VerifyFinal(&ctx, (const unsigned char *) jwt->signature.bytes, jwt->signature.length, pEcKey)) { apr_jwt_error_openssl(err, "wrong key? EVP_VerifyFinal"); goto end; } rc = TRUE; end: if (pEcKey) { EVP_PKEY_free(pEcKey); } else if (pubkey) { EC_KEY_free(pubkey); } EVP_MD_CTX_cleanup(&ctx); return rc; }
/* * parse and (optionally) decrypt a JSON Web Token */ apr_byte_t apr_jwt_parse(apr_pool_t *pool, const char *s_json, apr_jwt_t **j_jwt, apr_hash_t *keys, apr_jwt_error_t *err) { *j_jwt = apr_pcalloc(pool, sizeof(apr_jwt_t)); apr_jwt_t *jwt = *j_jwt; apr_array_header_t *unpacked = NULL; if (apr_jwt_header_parse(pool, s_json, &unpacked, &jwt->header, err) == FALSE) return FALSE; if (unpacked->nelts < 2) { apr_jwt_error(err, "could not successfully deserialize 2 or more elements from JWT header"); return FALSE; } if (apr_jwe_is_encrypted_jwt(pool, &jwt->header)) { char *decrypted = NULL; if ((apr_jwe_decrypt_jwt(pool, &jwt->header, unpacked, keys, &decrypted, err) == FALSE) || (decrypted == NULL)) return FALSE; apr_array_clear(unpacked); unpacked = NULL; json_decref(jwt->header.value.json); if (apr_jwt_header_parse(pool, (const char *) decrypted, &unpacked, &jwt->header, err) == FALSE) return FALSE; if (unpacked->nelts < 2) { apr_jwt_error(err, "could not successfully deserialize 2 or more elements from decrypted JWT header"); return FALSE; } } /* concat the base64url-encoded payload to the base64url-encoded header for signature verification purposes */ jwt->message = apr_pstrcat(pool, ((const char**) unpacked->elts)[0], ".", ((const char**) unpacked->elts)[1], NULL); /* parse the payload fields */ if (apr_jwt_parse_payload(pool, ((const char**) unpacked->elts)[1], &jwt->payload, err) == FALSE) { json_decref(jwt->header.value.json); return FALSE; } if (unpacked->nelts > 2 && strcmp(jwt->header.alg, "none") != 0) { /* remainder is the signature */ if (apr_jwt_parse_signature(pool, ((const char**) unpacked->elts)[2], &jwt->signature) == FALSE) { json_decref(jwt->header.value.json); json_decref(jwt->payload.value.json); apr_jwt_error(err, "could not successfully parse (base64urldecode) JWT signature"); return FALSE; } } return TRUE; }