/* * verify HMAC signature on JWT */ static apr_byte_t apr_jws_verify_rsa(apr_pool_t *pool, apr_jwt_t *jwt, apr_jwk_t *jwk, apr_jwt_error_t *err) { 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); RSA * pubkey = RSA_new(); BIGNUM * modulus = BN_new(); BIGNUM * exponent = BN_new(); BN_bin2bn(jwk->key.rsa->modulus, jwk->key.rsa->modulus_len, modulus); BN_bin2bn(jwk->key.rsa->exponent, jwk->key.rsa->exponent_len, exponent); pubkey->n = modulus; pubkey->e = exponent; EVP_PKEY* pRsaKey = EVP_PKEY_new(); if (!EVP_PKEY_assign_RSA(pRsaKey, pubkey)) { pRsaKey = NULL; apr_jwt_error_openssl(err, "EVP_PKEY_assign_RSA"); goto end; } if (apr_jws_signature_starts_with(pool, jwt->header.alg, "PS") == TRUE) { int status = 0; unsigned char *pDecrypted = apr_pcalloc(pool, jwt->signature.length); status = RSA_public_decrypt(jwt->signature.length, jwt->signature.bytes, pDecrypted, pubkey, RSA_NO_PADDING); if (status == -1) { apr_jwt_error_openssl(err, "RSA_public_decrypt"); goto end; } unsigned char *pDigest = apr_pcalloc(pool, RSA_size(pubkey)); unsigned int uDigestLen = RSA_size(pubkey); if (!EVP_DigestInit(&ctx, digest)) { apr_jwt_error_openssl(err, "EVP_DigestInit"); goto end; } if (!EVP_DigestUpdate(&ctx, jwt->message, strlen(jwt->message))) { apr_jwt_error_openssl(err, "EVP_DigestUpdate"); goto end; } if (!EVP_DigestFinal(&ctx, pDigest, &uDigestLen)) { apr_jwt_error_openssl(err, "wrong key? EVP_DigestFinal"); goto end; } /* verify the data */ status = RSA_verify_PKCS1_PSS(pubkey, pDigest, digest, pDecrypted, -2 /* salt length recovered from signature*/); if (status != 1) { apr_jwt_error_openssl(err, "RSA_verify_PKCS1_PSS"); goto end; } rc = TRUE; } else if (apr_jws_signature_starts_with(pool, jwt->header.alg, "RS") == TRUE) { 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, pRsaKey)) { apr_jwt_error_openssl(err, "wrong key? EVP_VerifyFinal"); goto end; } rc = TRUE; } end: if (pRsaKey) { EVP_PKEY_free(pRsaKey); } else if (pubkey) { RSA_free(pubkey); } EVP_MD_CTX_cleanup(&ctx); return rc; }
static apr_byte_t apr_jwk_parse_rsa_key(apr_pool_t *pool, int is_private_key, const char *kid, const char *filename, apr_jwk_t **j_jwk, apr_jwt_error_t *err) { BIO *input = NULL; apr_jwk_key_rsa_t *key = NULL; apr_byte_t rv = FALSE; if ((input = BIO_new(BIO_s_file())) == NULL) { apr_jwt_error_openssl(err, "BIO_new/BIO_s_file"); goto end; } if (BIO_read_filename(input, filename) <= 0) { apr_jwt_error_openssl(err, "BIO_read_filename"); goto end; } if (apr_jwk_rsa_bio_to_key(pool, input, &key, is_private_key, err) == FALSE) goto end; /* allocate memory for the JWK */ *j_jwk = apr_pcalloc(pool, sizeof(apr_jwk_t)); apr_jwk_t *jwk = *j_jwk; jwk->type = APR_JWK_KEY_RSA; jwk->key.rsa = key; if (kid != NULL) { jwk->kid = apr_pstrdup(pool, kid); } else { /* calculate a unique key identifier (kid) by fingerprinting the key params */ // TODO: based just on sha1 hash of modulus "n" now..., could do this based on jwk->value.str if (apr_jwk_hash_and_base64urlencode(pool, key->modulus, key->modulus_len, &jwk->kid, err) == FALSE) goto end; } rv = TRUE; end: if (input) BIO_free(input); return rv; }
/* * 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; }
/* * 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; }
/* * 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; }
/* * 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 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; }
/* * convert the RSA public key in the X.509 certificate in the BIO pointed to * by "input" to a JSON Web Key object */ static apr_byte_t apr_jwk_rsa_bio_to_key(apr_pool_t *pool, BIO *input, apr_jwk_key_rsa_t **jwk_key_rsa, int is_private_key, apr_jwt_error_t *err) { X509 *x509 = NULL; EVP_PKEY *pkey = NULL; apr_byte_t rv = FALSE; if (is_private_key) { /* get the private key struct from the BIO */ if ((pkey = PEM_read_bio_PrivateKey(input, NULL, NULL, NULL)) == NULL) { apr_jwt_error_openssl(err, "PEM_read_bio_PrivateKey"); goto end; } } else { /* read the X.509 struct */ if ((x509 = PEM_read_bio_X509_AUX(input, NULL, NULL, NULL)) == NULL) { apr_jwt_error_openssl(err, "PEM_read_bio_X509_AUX"); goto end; } /* get the public key struct from the X.509 struct */ if ((pkey = X509_get_pubkey(x509)) == NULL) { apr_jwt_error_openssl(err, "X509_get_pubkey"); goto end; } } /* allocate space */ *jwk_key_rsa = apr_pcalloc(pool, sizeof(apr_jwk_key_rsa_t)); apr_jwk_key_rsa_t *key = *jwk_key_rsa; /* get the RSA key from the public key struct */ RSA *rsa = EVP_PKEY_get1_RSA(pkey); if (rsa == NULL) { apr_jwt_error_openssl(err, "EVP_PKEY_get1_RSA"); goto end; } /* convert the modulus bignum in to a key/len */ key->modulus_len = BN_num_bytes(rsa->n); key->modulus = apr_pcalloc(pool, key->modulus_len); BN_bn2bin(rsa->n, key->modulus); /* convert the exponent bignum in to a key/len */ key->exponent_len = BN_num_bytes(rsa->e); key->exponent = apr_pcalloc(pool, key->exponent_len); BN_bn2bin(rsa->e, key->exponent); /* convert the private exponent bignum in to a key/len */ if (rsa->d != NULL) { key->private_exponent_len = BN_num_bytes(rsa->d); key->private_exponent = apr_pcalloc(pool, key->private_exponent_len); BN_bn2bin(rsa->d, key->private_exponent); } RSA_free(rsa); rv = TRUE; end: if (pkey) EVP_PKEY_free(pkey); if (x509) X509_free(x509); return rv; }