/* * validate a JWT access token (locally) * * TODO: document that we're reusing the following settings from the OIDC config section: * - JWKs URI refresh interval * - encryption key material (OIDCPrivateKeyFiles) * - iat slack (OIDCIDTokenIatSlack) * * OIDCOAuthRemoteUserClaim client_id * # 32x 61 hex * OIDCOAuthVerifySharedKeys aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa */ static apr_byte_t oidc_oauth_validate_jwt_access_token(request_rec *r, oidc_cfg *c, const char *access_token, json_t **token, char **response) { apr_jwt_error_t err; apr_jwt_t *jwt = NULL; if (apr_jwt_parse(r->pool, access_token, &jwt, oidc_util_merge_symmetric_key(r->pool, c->private_keys, c->oauth.client_secret, NULL), &err) == FALSE) { oidc_error(r, "could not parse JWT from access_token: %s", apr_jwt_e2s(r->pool, err)); return FALSE; } oidc_debug(r, "successfully parsed JWT with header: %s", jwt->header.value.str); /* validate the access token JWT, validating optional exp + iat */ if (oidc_proto_validate_jwt(r, jwt, NULL, FALSE, FALSE, c->provider.idtoken_iat_slack) == FALSE) { apr_jwt_destroy(jwt); return FALSE; } oidc_debug(r, "verify JWT against %d statically configured public keys and %d shared keys, with JWKs URI set to %s", c->oauth.verify_public_keys ? apr_hash_count(c->oauth.verify_public_keys) : 0, c->oauth.verify_shared_keys ? apr_hash_count(c->oauth.verify_shared_keys) : 0, c->oauth.verify_jwks_uri); oidc_jwks_uri_t jwks_uri = { c->oauth.verify_jwks_uri, c->provider.jwks_refresh_interval, c->oauth.ssl_validate_server }; if (oidc_proto_jwt_verify(r, c, jwt, &jwks_uri, oidc_util_merge_key_sets(r->pool, c->oauth.verify_public_keys, c->oauth.verify_shared_keys)) == FALSE) { oidc_error(r, "JWT access token signature could not be validated, aborting"); apr_jwt_destroy(jwt); return FALSE; } oidc_debug(r, "successfully verified JWT access token: %s", jwt->payload.value.str); *token = jwt->payload.value.json; *response = jwt->payload.value.str; return TRUE; }
static char *test_jwt_parse(apr_pool_t *pool) { // from http://tools.ietf.org/html/draft-ietf-oauth-json-web-token-20 // 3.1. Example JWT char *s = apr_pstrdup(pool, "eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9" ".eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ" ".dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"); apr_jwt_error_t err; apr_jwt_t *jwt = NULL; TST_ASSERT_ERR("apr_jwt_parse", apr_jwt_parse(pool, s, &jwt, NULL, &err), pool, err); TST_ASSERT_STR("header.alg", jwt->header.alg, "HS256"); TST_ASSERT_STR("header.enc", jwt->header.enc, NULL); TST_ASSERT_STR("header.kid", jwt->header.kid, NULL); TST_ASSERT_STR("payload.iss", jwt->payload.iss, "joe"); TST_ASSERT_LONG("payload.exp", (long )jwt->payload.exp, 1300819380L); apr_hash_t *keys = apr_hash_make(pool); apr_jwk_t *jwk; const char * k = "{\"kty\":\"oct\", \"k\":\"AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow\"}"; jwk = NULL; TST_ASSERT_ERR("apr_jwk_parse_json", _jwk_parse(pool, k, &jwk, &err) == 0, pool, err); apr_hash_set(keys, "dummy", APR_HASH_KEY_STRING, jwk); TST_ASSERT_ERR("apr_jws_verify", apr_jws_verify(pool, jwt, keys, &err), pool, err); apr_jwt_destroy(jwt); s[5] = '.'; TST_ASSERT_ERR("corrupted header (1) apr_jwt_parse", apr_jwt_parse(pool, s, &jwt, NULL, &err) == FALSE, pool, err); apr_jwt_destroy(jwt); s[0] = '\0'; TST_ASSERT_ERR("corrupted header (2) apr_jwt_parse", apr_jwt_parse(pool, s, &jwt, NULL, &err) == FALSE, pool, err); apr_jwt_destroy(jwt); return 0; }
static char *test_proto_validate_code(request_rec *r) { // from http://openid.net/specs/openid-connect-core-1_0.html#code-id_tokenExample // A.4 Example using response_type=code id_token const char *s = "eyJraWQiOiIxZTlnZGs3IiwiYWxnIjoiUlMyNTYifQ.ewogIml" "zcyI6ICJodHRwOi8vc2VydmVyLmV4YW1wbGUuY29tIiwKICJzdWIiOiAiMjQ" "4Mjg5NzYxMDAxIiwKICJhdWQiOiAiczZCaGRSa3F0MyIsCiAibm9uY2UiOiA" "ibi0wUzZfV3pBMk1qIiwKICJleHAiOiAxMzExMjgxOTcwLAogImlhdCI6IDE" "zMTEyODA5NzAsCiAiY19oYXNoIjogIkxEa3RLZG9RYWszUGswY25YeENsdEE" "iCn0.XW6uhdrkBgcGx6zVIrCiROpWURs-4goO1sKA4m9jhJIImiGg5muPUcN" "egx6sSv43c5DSn37sxCRrDZZm4ZPBKKgtYASMcE20SDgvYJdJS0cyuFw7Ijp" "_7WnIjcrl6B5cmoM6ylCvsLMwkoQAxVublMwH10oAxjzD6NEFsu9nipkszWh" "sPePf_rM4eMpkmCbTzume-fzZIi5VjdWGGEmzTg32h3jiex-r5WTHbj-u5HL" "7u_KP3rmbdYNzlzd1xWRYTUs4E8nOTgzAUwvwXkIQhOh5TPcSMBYy6X3E7-_" "gr9Ue6n4ND7hTFhtjYs3cjNKIA08qm5cpVYFMFMG6PkhzLQ"; apr_jwt_error_t err; apr_jwt_t *jwt = NULL; TST_ASSERT_ERR("apr_jwt_parse", apr_jwt_parse(r->pool, s, &jwt, NULL, &err), r->pool, err); const char *code = "Qcb0Orv1zh30vL1MPRsbm-diHiMwcLyZvn1arpZv-Jxf_11jnpEX3Tgfvk"; TST_ASSERT("oidc_proto_validate_code", oidc_proto_validate_code(r, NULL, jwt, "code id_token", code)); apr_jwt_destroy(jwt); return 0; }
static char *test_proto_validate_access_token(request_rec *r) { // from http://openid.net/specs/openid-connect-core-1_0.html#id_token-tokenExample // A.3 Example using response_type=id_token token const char *s = "eyJraWQiOiIxZTlnZGs3IiwiYWxnIjoiUlMyNTYifQ.ewogIml" "zcyI6ICJodHRwOi8vc2VydmVyLmV4YW1wbGUuY29tIiwKICJzdWIiOiAiMjQ" "4Mjg5NzYxMDAxIiwKICJhdWQiOiAiczZCaGRSa3F0MyIsCiAibm9uY2UiOiA" "ibi0wUzZfV3pBMk1qIiwKICJleHAiOiAxMzExMjgxOTcwLAogImlhdCI6IDE" "zMTEyODA5NzAsCiAiYXRfaGFzaCI6ICI3N1FtVVB0alBmeld0RjJBbnBLOVJ" "RIgp9.F9gRev0Dt2tKcrBkHy72cmRqnLdzw9FLCCSebV7mWs7o_sv2O5s6zM" "ky2kmhHTVx9HmdvNnx9GaZ8XMYRFeYk8L5NZ7aYlA5W56nsG1iWOou_-gji0" "ibWIuuf4Owaho3YSoi7EvsTuLFz6tq-dLyz0dKABMDsiCmJ5wqkPUDTE3QTX" "jzbUmOzUDli-gCh5QPuZAq0cNW3pf_2n4zpvTYtbmj12cVcxGIMZby7TMWES" "RjQ9_o3jvhVNcCGcE0KAQXejhA1ocJhNEvQNqMFGlBb6_0RxxKjDZ-Oa329e" "GDidOvvp0h5hoES4a8IuGKS7NOcpp-aFwp0qVMDLI-Xnm-Pg"; apr_jwt_error_t err; apr_jwt_t *jwt = NULL; TST_ASSERT_ERR("apr_jwt_parse", apr_jwt_parse(r->pool, s, &jwt, NULL, &err), r->pool, err); const char *access_token = "jHkWEdUXMU1BwAsC4vtUsZwnNvTIxEl0z9K3vx5KF0Y"; TST_ASSERT("oidc_proto_validate_access_token", oidc_proto_validate_access_token(r, NULL, jwt, "id_token token", access_token)); apr_jwt_destroy(jwt); return 0; }
static char *test_jwt_get_string(apr_pool_t *pool) { //apr_jwt_get_string const char *s = "eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9" ".eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ" ".dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"; apr_jwt_t *jwt = NULL; apr_jwt_error_t err; TST_ASSERT_ERR("apr_jwt_parse", apr_jwt_parse(pool, s, &jwt, NULL, &err), pool, err); char *dst; dst = NULL; TST_ASSERT("apr_jwt_get_string (1a)", apr_jwt_get_string(pool, jwt->header.value.json, "typ", TRUE, &dst, &err)); TST_ASSERT_STR("apr_jwt_get_string (1b)", dst, "JWT"); dst = NULL; TST_ASSERT("apr_jwt_get_string (2a)", apr_jwt_get_string(pool, jwt->header.value.json, "alg", TRUE, &dst, &err)); TST_ASSERT_STR("apr_jwt_get_string (2b)", dst, "HS256"); dst = NULL; TST_ASSERT("apr_jwt_get_string (3a)", apr_jwt_get_string(pool, jwt->header.value.json, "dummy", FALSE, &dst, &err)); TST_ASSERT_STR("apr_jwt_get_string (3b)", dst, NULL); apr_jwt_destroy(jwt); return 0; }
static char *test_jwt_verify_rsa(apr_pool_t *pool) { /* * { * "typ": "JWT", * "alg": "RS256", * "x5t": "Z1NCjojeiHAib-Gm8vFE6ya6lPM" * } * { * "nonce": "avSk7S69G4kEE8Km4bPiOjrfChHt6nO4Z397Lp_bQnc,", * "iat": 1411580876, * "at_hash": "yTqsoONZbuWbN6TbgevuDQ", * "sub": "6343a29c-5399-44a7-9b35-4990f4377c96", * "amr": "password", * "auth_time": 1411577267, * "idp": "idsrv", * "name": "ksonaty", * "iss": "https://agsync.com", * "aud": "agsync_implicit", * "exp": 1411584475, * "nbf": 1411580875 * } */ char *s_jwt = apr_pstrdup(pool, "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6IloxTkNqb2plaUhBaWItR204dkZFNnlhNmxQTSJ9.eyJub25jZSI6ImF2U2s3UzY5RzRrRUU4S200YlBpT2pyZkNoSHQ2bk80WjM5N0xwX2JRbmMsIiwiaWF0IjoxNDExNTgwODc2LCJhdF9oYXNoIjoieVRxc29PTlpidVdiTjZUYmdldnVEUSIsInN1YiI6IjYzNDNhMjljLTUzOTktNDRhNy05YjM1LTQ5OTBmNDM3N2M5NiIsImFtciI6InBhc3N3b3JkIiwiYXV0aF90aW1lIjoxNDExNTc3MjY3LCJpZHAiOiJpZHNydiIsIm5hbWUiOiJrc29uYXR5IiwiaXNzIjoiaHR0cHM6Ly9hZ3N5bmMuY29tIiwiYXVkIjoiYWdzeW5jX2ltcGxpY2l0IiwiZXhwIjoxNDExNTg0NDc1LCJuYmYiOjE0MTE1ODA4NzV9.lEG-DgHHa0JuOEuOTBvCqyexjRVcKXBnJJm289o2HyTgclpH80DsOMED9RlXCFfuDY7nw9i2cxUmIMAV42AdTxkMPomK3chytcajvpAZJirlk653bo9GTDXJSKZr5fwyEu--qahsoT5t9qvoWyFdYkvmMHFw1-mAHDGgVe23voc9jPuFFIhRRqIn4e8ikzN4VQeEV1UXJD02kYYFn2TRWURgiFyVeTr2r0MTn-auCEsFS_AfR1Bl_kmpMfqwrsicf5MTBvfPJeuSMt3t3d3LOGBkg36_z21X-ZRN7wy1KTjagr7iQ_y5csIpmtqs_QM55TTB9dW1HIosJPhiuMEJEA"); apr_jwt_t *jwt = NULL; apr_jwt_error_t err; TST_ASSERT_ERR("apr_jwt_parse", apr_jwt_parse(pool, s_jwt, &jwt, NULL, &err), pool, err); char *s_key = "{" "\"kty\": \"RSA\"," "\"use\": \"sig\"," "\"kid\": \"Z1NCjojeiHAib-Gm8vFE6ya6lPM\"," "\"x5t\": \"Z1NCjojeiHAib-Gm8vFE6ya6lPM\"," "\"x5c\": [" "\"MIIFHTCCBAWgAwIBAgIHJ9VlLewUkDANBgkqhkiG9w0BAQsFADCBtDELMAkGA1UEBhMCVVMxEDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxGjAYBgNVBAoTEUdvRGFkZHkuY29tLCBJbmMuMS0wKwYDVQQLEyRodHRwOi8vY2VydHMuZ29kYWRkeS5jb20vcmVwb3NpdG9yeS8xMzAxBgNVBAMTKkdvIERhZGR5IFNlY3VyZSBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjAeFw0xNDA4MTIxNzE0MDdaFw0xNzA4MTgxODI5MjJaMDoxITAfBgNVBAsTGERvbWFpbiBDb250cm9sIFZhbGlkYXRlZDEVMBMGA1UEAwwMKi5hZ3N5bmMuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3lDyn/ZvG32Pw5kYbRuVxHsPfe9Xt8s9vOXnt8z7/T+hZZvealNhCxz9VEwTJ7TsZ9CLi5c30FjoEJYFkKddLAdxKo0oOXWc/AWrQvPwht9a+o6dX2fL/9CmXW1hGHXMH0qiLMrFqMSzZeh+GUY6F1woE/eKsAo6LOhP8X77FlEQT2Eu71wu8KC4B3sH/9QTco50KNw14+bRY5j2V2TZelvsXJnvrN4lXtEVYWFkREKeXzMH8DhDyZzh0NcHa7dFBa7rDusyfIHjuP6uAju/Ao6hhdOGjlKePMVtfusWBAI7MWDChLTqiCTvlZnCpkpTTh5m+i7TbE1TwmdbLceq1wIDAQABo4IBqzCCAacwDAYDVR0TAQH/BAIwADAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDgYDVR0PAQH/BAQDAgWgMDYGA1UdHwQvMC0wK6ApoCeGJWh0dHA6Ly9jcmwuZ29kYWRkeS5jb20vZ2RpZzJzMS04Ny5jcmwwUwYDVR0gBEwwSjBIBgtghkgBhv1tAQcXATA5MDcGCCsGAQUFBwIBFitodHRwOi8vY2VydGlmaWNhdGVzLmdvZGFkZHkuY29tL3JlcG9zaXRvcnkvMHYGCCsGAQUFBwEBBGowaDAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZ29kYWRkeS5jb20vMEAGCCsGAQUFBzAChjRodHRwOi8vY2VydGlmaWNhdGVzLmdvZGFkZHkuY29tL3JlcG9zaXRvcnkvZ2RpZzIuY3J0MB8GA1UdIwQYMBaAFEDCvSeOzDSDMKIz1/tss/C0LIDOMCMGA1UdEQQcMBqCDCouYWdzeW5jLmNvbYIKYWdzeW5jLmNvbTAdBgNVHQ4EFgQUxqwQ5mJfzESbA5InigohAq4lhIYwDQYJKoZIhvcNAQELBQADggEBADXv4q7iw3yCDuVS+edPcyWQJPWo3X7xx83g2omcsqDIoEMgsRLGidiINttAhSIAlUyd9Nsp5cGsT/2ZJMbjRFhhVhRHf61O+F60ZYuKPUKWlXB1Nkk4f48/6PGc5Tu/MXdXttpuIP4Jlbpc0dtv59wrrFs9Sf1V7NuHS96IhxfnBO3J1s3ipudoUwjNtBxN/7vUFzfRuHl1+/oQxhmKDxBDpk0v1iLJTeMkMgc+wPGO55gLR6+5l9qWuuE+fIHeS+LHMzchkBBYJMtbmf/KZfwMA8AOsnGXQOXzpf7Sg8VIiVdeaB0NY1eWyRBisQkivk6wm+7G2VYKh9OeVdX4XqQ=\"" "]" "}"; apr_hash_t *keys = apr_hash_make(pool); apr_jwk_t *jwk = NULL; TST_ASSERT_ERR("apr_jwk_parse_json", _jwk_parse(pool, s_key, &jwk, &err) == 0, pool, err); apr_hash_set(keys, "dummy", APR_HASH_KEY_STRING, jwk); TST_ASSERT_ERR("apr_jws_verify", apr_jws_verify(pool, jwt, keys, &err), pool, err); apr_jwt_destroy(jwt); return 0; }
static char * test_proto_validate_nonce(request_rec *r) { oidc_cfg *c = ap_get_module_config(r->server->module_config, &auth_openidc_module); const char *nonce = "avSk7S69G4kEE8Km4bPiOjrfChHt6nO4Z397Lp_bQnc,"; /* * { * "typ": "JWT", * "alg": "RS256", * "x5t": "Z1NCjojeiHAib-Gm8vFE6ya6lPM" * } * { * "nonce": "avSk7S69G4kEE8Km4bPiOjrfChHt6nO4Z397Lp_bQnc,", * "iat": 1411580876, * "at_hash": "yTqsoONZbuWbN6TbgevuDQ", * "sub": "6343a29c-5399-44a7-9b35-4990f4377c96", * "amr": "password", * "auth_time": 1411577267, * "idp": "idsrv", * "name": "ksonaty", * "iss": "https://agsync.com", * "aud": "agsync_implicit", * "exp": 1411584475, * "nbf": 1411580875 * } */ char *s_jwt = apr_pstrdup(r->pool, "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6IloxTkNqb2plaUhBaWItR204dkZFNnlhNmxQTSJ9.eyJub25jZSI6ImF2U2s3UzY5RzRrRUU4S200YlBpT2pyZkNoSHQ2bk80WjM5N0xwX2JRbmMsIiwiaWF0IjoxNDExNTgwODc2LCJhdF9oYXNoIjoieVRxc29PTlpidVdiTjZUYmdldnVEUSIsInN1YiI6IjYzNDNhMjljLTUzOTktNDRhNy05YjM1LTQ5OTBmNDM3N2M5NiIsImFtciI6InBhc3N3b3JkIiwiYXV0aF90aW1lIjoxNDExNTc3MjY3LCJpZHAiOiJpZHNydiIsIm5hbWUiOiJrc29uYXR5IiwiaXNzIjoiaHR0cHM6Ly9hZ3N5bmMuY29tIiwiYXVkIjoiYWdzeW5jX2ltcGxpY2l0IiwiZXhwIjoxNDExNTg0NDc1LCJuYmYiOjE0MTE1ODA4NzV9.lEG-DgHHa0JuOEuOTBvCqyexjRVcKXBnJJm289o2HyTgclpH80DsOMED9RlXCFfuDY7nw9i2cxUmIMAV42AdTxkMPomK3chytcajvpAZJirlk653bo9GTDXJSKZr5fwyEu--qahsoT5t9qvoWyFdYkvmMHFw1-mAHDGgVe23voc9jPuFFIhRRqIn4e8ikzN4VQeEV1UXJD02kYYFn2TRWURgiFyVeTr2r0MTn-auCEsFS_AfR1Bl_kmpMfqwrsicf5MTBvfPJeuSMt3t3d3LOGBkg36_z21X-ZRN7wy1KTjagr7iQ_y5csIpmtqs_QM55TTB9dW1HIosJPhiuMEJEA"); apr_jwt_t *jwt = NULL; apr_jwt_error_t err; TST_ASSERT_ERR("apr_jwt_parse", apr_jwt_parse(r->pool, s_jwt, &jwt, NULL, &err), r->pool, err); TST_ASSERT("oidc_proto_validate_nonce (1)", oidc_proto_validate_nonce(r, c, &c->provider, nonce, jwt)); TST_ASSERT("oidc_proto_validate_nonce (2)", oidc_proto_validate_nonce( r, c, &c->provider, nonce, jwt) == FALSE); apr_jwt_destroy(jwt); return 0; }
static char *test_plaintext_jwt_parse(apr_pool_t *pool) { // from http://tools.ietf.org/html/draft-ietf-oauth-json-web-token-20 // 6.1. Example Plaintext JWT char *s = apr_pstrdup(pool, "eyJhbGciOiJub25lIn0" ".eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ" "."); apr_jwt_error_t err; apr_jwt_t *jwt = NULL; TST_ASSERT_ERR("apr_jwt_parse", apr_jwt_parse(pool, s, &jwt, NULL, &err), pool, err); TST_ASSERT_STR("header.alg", jwt->header.alg, "none"); TST_ASSERT_STR("payload.iss", jwt->payload.iss, "joe"); TST_ASSERT_LONG("payload.exp", (long )jwt->payload.exp, 1300819380L); apr_jwt_destroy(jwt); return 0; }
/* * check whether the provided string is a valid id_token and return its parsed contents */ apr_byte_t oidc_proto_parse_idtoken(request_rec *r, oidc_cfg *cfg, oidc_provider_t *provider, const char *id_token, const char *nonce, char **user, apr_jwt_t **jwt, apr_byte_t is_code_flow) { oidc_debug(r, "enter"); if (apr_jwt_parse(r->pool, id_token, jwt, cfg->private_keys, provider->client_secret) == FALSE) { if ((*jwt) && ((*jwt)->header.alg) && (apr_jwe_algorithm_is_supported(r->pool, (*jwt)->header.alg) == FALSE)) { oidc_error(r, "JWE content key encryption algorithm is not supported: %s", (*jwt)->header.alg); } if ((*jwt) && ((*jwt)->header.enc) && (apr_jwe_encryption_is_supported(r->pool, (*jwt)->header.enc) == FALSE)) { oidc_error(r, "JWE encryption type is not supported: %s", (*jwt)->header.enc); } oidc_error(r, "apr_jwt_parse failed for JWT with header: \"%s\"", apr_jwt_header_to_string(r->pool, id_token)); apr_jwt_destroy(*jwt); return FALSE; } oidc_debug(r, "successfully parsed (and possibly decrypted) JWT with header: \"%s\"", apr_jwt_header_to_string(r->pool, id_token)); // make signature validation exception for 'code' flow and the algorithm NONE if (is_code_flow == FALSE || strcmp((*jwt)->header.alg, "none") != 0) { apr_byte_t refresh = FALSE; if (oidc_proto_idtoken_verify_signature(r, cfg, provider, *jwt, &refresh) == FALSE) { oidc_error(r, "id_token signature could not be validated, aborting"); apr_jwt_destroy(*jwt); return FALSE; } } /* this is where the meat is */ if (oidc_proto_validate_idtoken(r, provider, *jwt, nonce) == FALSE) { oidc_error(r, "id_token payload could not be validated, aborting"); apr_jwt_destroy(*jwt); return FALSE; } if (oidc_proto_set_remote_user(r, cfg, provider, *jwt, user) == FALSE) { oidc_error(r, "remote user could not be set, aborting"); apr_jwt_destroy(*jwt); return FALSE; } /* log our results */ oidc_debug(r, "valid id_token for user \"%s\" (expires in %" APR_TIME_T_FMT " seconds)", *user, (*jwt)->payload.exp - apr_time_sec(apr_time_now())); /* since we've made it so far, we may as well say it is a valid id_token */ return TRUE; }
static char * test_proto_validate_jwt(request_rec *r) { apr_jwt_t *jwt = NULL; apr_jwt_error_t err; const char *s_secret = "secret"; const char *s_issuer = "https://localhost"; apr_time_t now = apr_time_sec(apr_time_now()); const char *s_jwt_header = "{" "\"alg\": \"HS256\"" "}"; const char *s_jwt_payload = "{" "\"nonce\": \"543210,\"," "\"iat\": %" APR_TIME_T_FMT "," "\"sub\": \"alice\"," "\"iss\": \"%s\"," "\"aud\": \"bob\"," "\"exp\": %" APR_TIME_T_FMT "}"; s_jwt_payload = apr_psprintf(r->pool, s_jwt_payload, now, s_issuer, now + 600); char *s_jwt_header_encoded = NULL; oidc_base64url_encode(r, &s_jwt_header_encoded, s_jwt_header, strlen(s_jwt_header), 1); char *s_jwt_payload_encoded = NULL; oidc_base64url_encode(r, &s_jwt_payload_encoded, s_jwt_payload, strlen(s_jwt_payload), 1); char *s_jwt_message = apr_psprintf(r->pool, "%s.%s", s_jwt_header_encoded, s_jwt_payload_encoded); unsigned int md_len = 0; unsigned char md[EVP_MAX_MD_SIZE]; const EVP_MD *digest = EVP_get_digestbyname("sha256"); TST_ASSERT("HMAC", HMAC(digest, (const unsigned char * )s_secret, strlen(s_secret), (const unsigned char * )s_jwt_message, strlen(s_jwt_message), md, &md_len) != 0); char *s_jwt_signature_encoded = NULL; oidc_base64url_encode(r, &s_jwt_signature_encoded, (const char *) md, md_len, 1); char *s_jwt = apr_psprintf(r->pool, "%s.%s.%s", s_jwt_header_encoded, s_jwt_payload_encoded, s_jwt_signature_encoded); TST_ASSERT_ERR("apr_jwt_parse", apr_jwt_parse(r->pool, s_jwt, &jwt, NULL, &err), r->pool, err); TST_ASSERT_ERR("apr_jws_verify", apr_jws_verify(r->pool, jwt, oidc_util_merge_symmetric_key(r->pool, NULL, s_secret, NULL), &err), r->pool, err); TST_ASSERT_ERR("oidc_proto_validate_jwt", oidc_proto_validate_jwt(r, jwt, s_issuer, TRUE, TRUE, 10), r->pool, err); apr_jwt_destroy(jwt); return 0; }
static char *test_jwt_decrypt(apr_pool_t *pool) { // https://tools.ietf.org/html/draft-ietf-oauth-json-web-token-32#appendix-A.1 // A.2. Example Nested JWT char * s = apr_pstrdup(pool, "eyJhbGciOiJSU0ExXzUiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2IiwiY3R5IjoiSldU" "In0." "g_hEwksO1Ax8Qn7HoN-BVeBoa8FXe0kpyk_XdcSmxvcM5_P296JXXtoHISr_DD_M" "qewaQSH4dZOQHoUgKLeFly-9RI11TG-_Ge1bZFazBPwKC5lJ6OLANLMd0QSL4fYE" "b9ERe-epKYE3xb2jfY1AltHqBO-PM6j23Guj2yDKnFv6WO72tteVzm_2n17SBFvh" "DuR9a2nHTE67pe0XGBUS_TK7ecA-iVq5COeVdJR4U4VZGGlxRGPLRHvolVLEHx6D" "YyLpw30Ay9R6d68YCLi9FYTq3hIXPK_-dmPlOUlKvPr1GgJzRoeC9G5qCvdcHWsq" "JGTO_z3Wfo5zsqwkxruxwA." "UmVkbW9uZCBXQSA5ODA1Mg." "VwHERHPvCNcHHpTjkoigx3_ExK0Qc71RMEParpatm0X_qpg-w8kozSjfNIPPXiTB" "BLXR65CIPkFqz4l1Ae9w_uowKiwyi9acgVztAi-pSL8GQSXnaamh9kX1mdh3M_TT" "-FZGQFQsFhu0Z72gJKGdfGE-OE7hS1zuBD5oEUfk0Dmb0VzWEzpxxiSSBbBAzP10" "l56pPfAtrjEYw-7ygeMkwBl6Z_mLS6w6xUgKlvW6ULmkV-uLC4FUiyKECK4e3WZY" "Kw1bpgIqGYsw2v_grHjszJZ-_I5uM-9RA8ycX9KqPRp9gc6pXmoU_-27ATs9XCvr" "ZXUtK2902AUzqpeEUJYjWWxSNsS-r1TJ1I-FMJ4XyAiGrfmo9hQPcNBYxPz3GQb2" "8Y5CLSQfNgKSGt0A4isp1hBUXBHAndgtcslt7ZoQJaKe_nNJgNliWtWpJ_ebuOpE" "l8jdhehdccnRMIwAmU1n7SPkmhIl1HlSOpvcvDfhUN5wuqU955vOBvfkBOh5A11U" "zBuo2WlgZ6hYi9-e3w29bR0C2-pp3jbqxEDw3iWaf2dc5b-LnR0FEYXvI_tYk5rd" "_J9N0mg0tQ6RbpxNEMNoA9QWk5lgdPvbh9BaO195abQ." "AVO9iT5AV4CzvDJCdhSFlQ"); char * ek = "{\"kty\":\"RSA\"," "\"n\":\"sXchDaQebHnPiGvyDOAT4saGEUetSyo9MKLOoWFsueri23bOdgWp4Dy1WlUzewbgBHod5pcM9H95GQRV3JDXboIRROSBigeC5yjU1hGzHHyXss8UDprecbAYxknTcQkhslANGRUZmdTOQ5qTRsLAt6BTYuyvVRdhS8exSZEy_c4gs_7svlJJQ4H9_NxsiIoLwAEk7-Q3UXERGYw_75IDrGA84-lA_-Ct4eTlXHBIY2EaV7t7LjJaynVJCpkv4LKjTTAumiGUIuQhrNhZLuF_RJLqHpM2kgWFLU7-VTdL1VbC2tejvcI2BlMkEpk1BzBZI0KQB0GaDWFLN-aEAw3vRw\"," "\"e\":\"AQAB\"," "\"d\":\"VFCWOqXr8nvZNyaaJLXdnNPXZKRaWCjkU5Q2egQQpTBMwhprMzWzpR8Sxq1OPThh_J6MUD8Z35wky9b8eEO0pwNS8xlh1lOFRRBoNqDIKVOku0aZb-rynq8cxjDTLZQ6Fz7jSjR1Klop-YKaUHc9GsEofQqYruPhzSA-QgajZGPbE_0ZaVDJHfyd7UUBUKunFMScbflYAAOYJqVIVwaYR5zWEEceUjNnTNo_CVSj-VvXLO5VZfCUAVLgW4dpf1SrtZjSt34YLsRarSb127reG_DUwg9Ch-KyvjT1SkHgUWRVGcyly7uvVGRSDwsXypdrNinPA4jlhoNdizK2zF2CWQ\"" "}"; char * sk = "{" "\"kty\":\"RSA\"," "\"n\":\"ofgWCuLjybRlzo0tZWJjNiuSfb4p4fAkd_wWJcyQoTbji9k0l8W26mPddx" "HmfHQp-Vaw-4qPCJrcS2mJPMEzP1Pt0Bm4d4QlL-yRT-SFd2lZS-pCgNMs" "D1W_YpRPEwOWvG6b32690r2jZ47soMZo9wGzjb_7OMg0LOL-bSf63kpaSH" "SXndS5z5rexMdbBYUsLA9e-KXBdQOS-UTo7WTBEMa2R2CapHg665xsmtdV" "MTBQY4uDZlxvb3qCo5ZwKh9kG4LT6_I5IhlJH7aGhyxXFvUK-DWNmoudF8" "NAco9_h9iaGNj8q2ethFkMLs91kzk2PAcDTW9gb54h4FRWyuXpoQ\"," "\"e\":\"AQAB\"," "\"d\":\"Eq5xpGnNCivDflJsRQBXHx1hdR1k6Ulwe2JZD50LpXyWPEAeP88vLNO97I" "jlA7_GQ5sLKMgvfTeXZx9SE-7YwVol2NXOoAJe46sui395IW_GO-pWJ1O0" "BkTGoVEn2bKVRUCgu-GjBVaYLU6f3l9kJfFNS3E0QbVdxzubSu3Mkqzjkn" "439X0M_V51gfpRLI9JYanrC4D4qAdGcopV_0ZHHzQlBjudU2QvXt4ehNYT" "CBr6XCLQUShb1juUO1ZdiYoFaFQT5Tw8bGUl_x_jTj3ccPDVZFD9pIuhLh" "BOneufuBiB4cS98l2SR_RQyGWSeWjnczT0QU91p1DhOVRuOopznQ\"," "\"p\":\"4BzEEOtIpmVdVEZNCqS7baC4crd0pqnRH_5IB3jw3bcxGn6QLvnEtfdUdi" "YrqBdss1l58BQ3KhooKeQTa9AB0Hw_Py5PJdTJNPY8cQn7ouZ2KKDcmnPG" "BY5t7yLc1QlQ5xHdwW1VhvKn-nXqhJTBgIPgtldC-KDV5z-y2XDwGUc\"," "\"q\":\"uQPEfgmVtjL0Uyyx88GZFF1fOunH3-7cepKmtH4pxhtCoHqpWmT8YAmZxa" "ewHgHAjLYsp1ZSe7zFYHj7C6ul7TjeLQeZD_YwD66t62wDmpe_HlB-TnBA" "-njbglfIsRLtXlnDzQkv5dTltRJ11BKBBypeeF6689rjcJIDEz9RWdc\"," "\"dp\":\"BwKfV3Akq5_MFZDFZCnW-wzl-CCo83WoZvnLQwCTeDv8uzluRSnm71I3Q" "CLdhrqE2e9YkxvuxdBfpT_PI7Yz-FOKnu1R6HsJeDCjn12Sk3vmAktV2zb" "34MCdy7cpdTh_YVr7tss2u6vneTwrA86rZtu5Mbr1C1XsmvkxHQAdYo0\"," "\"dq\":\"h_96-mK1R_7glhsum81dZxjTnYynPbZpHziZjeeHcXYsXaaMwkOlODsWa" "7I9xXDoRwbKgB719rrmI2oKr6N3Do9U0ajaHF-NKJnwgjMd2w9cjz3_-ky" "NlxAr2v4IKhGNpmM5iIgOS1VZnOZ68m6_pbLBSp3nssTdlqvd0tIiTHU\"," "\"qi\":\"IYd7DHOhrWvxkwPQsRM2tOgrjbcrfvtQJipd-DlcxyVuuM9sQLdgjVk2o" "y26F0EmpScGLq2MowX7fhd_QJQ3ydy5cY7YIBi87w93IKLEdfnbJtoOPLU" "W0ITrJReOgo1cq9SbsxYawBgfp_gh6A5603k2-ZQwVK0JKSHuLFkuQ3U\"" "}"; apr_jwt_error_t err; apr_hash_t *keys = apr_hash_make(pool); apr_jwk_t *jwk = NULL; apr_jwt_t *jwt = NULL; TST_ASSERT_ERR("apr_jwk_parse_json (encryption key)", _jwk_parse(pool, ek, &jwk, &err) == 0, pool, err); apr_hash_set(keys, "dummy", APR_HASH_KEY_STRING, jwk); TST_ASSERT_ERR("apr_jwt_parse", apr_jwt_parse(pool, s, &jwt, keys, &err), pool, err); TST_ASSERT_ERR("apr_jwk_parse_json (signing key)", _jwk_parse(pool, sk, &jwk, &err) == 0, pool, err); apr_hash_set(keys, "dummy", APR_HASH_KEY_STRING, jwk); TST_ASSERT_ERR("apr_jws_verify", apr_jws_verify(pool, jwt, keys, &err), pool, err); TST_ASSERT_STR("header.alg", jwt->header.alg, "RS256"); TST_ASSERT_STR("payload.iss", jwt->payload.iss, "joe"); TST_ASSERT_LONG("payload.exp", (long )jwt->payload.exp, 1300819380L); apr_jwt_destroy(jwt); return 0; }
static char *test_jwt_sign_verify(apr_pool_t *pool) { apr_jwt_t jwt; apr_jwk_t *jwk = NULL; apr_jwt_error_t err; char *s_key = "{" "\"kty\" : \"RSA\"," "\"n\": \"ym7jipmB37CgdonwGFVRuZmRfCl3lVh91fmm5CXHcNlUFZNR3D6Q9r63PpGRnfSsX3dOweh8BXd2AJ3mxvcE4z9xH--tA5EaOGI7IVF0Ip_i3flGg85xOADlb8rX3ez1NqkqMVJeeJypKhCCDNfvu_MXSdPLglU969YQF5xKAK8VFRfI6EfxxrZ_3Dvt2CKDV4LTPPJe9KI2_LuLQFBJ3MzlCTVxY6gyaljrWaDq7q5Lt3GB1KYS0Yd8COEQwsclOLm0Tddhg4cle-DfaTMi7xsTZsPKyac5x17Y4N4isHhZULuWHX7o1bs809xcj-_-YCRq6C61je_mzFhuF4pczw\"," "\"e\": \"AQAB\"," "\"d\": \"qvxW_e8DoCnUn8uLHUKTsS1hkXqFI4SHZYFl0jeG6m7ncwHolxvR3ljg9tyGHuFX55sizu7MMuHgrkyxbUWgv0ILD2qmvOiHOTDfuRjP-58JRW0UfqiVQTSgl3jCNRW9WdoxZU-ptD6_NGSVNLwAJsUB2r4mm4PctaMuHINKjp_TnuD-5vfi9Tj88hbqvX_0j8T62ZaLRdERb1KGDM_8bnqQpnLZ0MZQnpLQ8cKIcjj7p0II6pzvqgdO1RqfYx7qG0cbcIRh26rnB9X4rp5BrbvDzKe6NOqacZUcNUmbPzI01-hiT0HgJvV592CBOxt2T31ltQ4wCEdzhQeT3n9_wQ\"" "}"; apr_hash_t *keys = apr_hash_make(pool); TST_ASSERT_ERR("apr_jwk_parse_json", _jwk_parse(pool, s_key, &jwk, &err) == 0, pool, err); apr_hash_set(keys, "dummy", APR_HASH_KEY_STRING, jwk); jwt.payload.value.json = json_object(); json_object_set_new(jwt.payload.value.json, "iss", json_string("https://example.org")); json_object_set_new(jwt.payload.value.json, "sub", json_string("https://example.org")); json_object_set_new(jwt.payload.value.json, "aud", json_string("sample_client")); json_object_set_new(jwt.payload.value.json, "exp", json_integer(apr_time_sec(apr_time_now()) + 60)); json_object_set_new(jwt.payload.value.json, "iat", json_integer(apr_time_sec(apr_time_now()))); jwt.header.value.json = json_object(); json_object_set_new(jwt.header.value.json, "typ", json_string("JWT")); json_object_set_new(jwt.header.value.json, "alg", json_string("RS256")); TST_ASSERT_ERR("apr_jwt_sign (rsa)", apr_jwt_sign(pool, &jwt, jwk, &err), pool, err); TST_ASSERT_ERR("apr_jws_verify (rsa)", apr_jws_verify(pool, &jwt, keys, &err), pool, err); const char *secret = "my_secret4321"; TST_ASSERT_ERR("apr_jwk_parse_symmetric_key", apr_jwk_parse_symmetric_key(pool, NULL, (const unsigned char *)secret, strlen(secret), &jwk, &err), pool, err); apr_hash_set(keys, "dummy", APR_HASH_KEY_STRING, jwk); json_object_set_new(jwt.header.value.json, "alg", json_string("HS256")); TST_ASSERT_ERR("apr_jwt_sign (hmac)", apr_jwt_sign(pool, &jwt, jwk, &err), pool, err); TST_ASSERT_ERR("apr_jws_verify (hmac)", apr_jws_verify(pool, &jwt, keys, &err), pool, err); apr_jwt_destroy(&jwt); return 0; }