diff --git a/doc/schemas/gov_openapi.json b/doc/schemas/gov_openapi.json index 90ab5ed30d61..1c3102936e97 100644 --- a/doc/schemas/gov_openapi.json +++ b/doc/schemas/gov_openapi.json @@ -291,27 +291,6 @@ }, "type": "object" }, - "KeyIdInfo": { - "properties": { - "cert": { - "$ref": "#/components/schemas/Pem" - }, - "issuer": { - "$ref": "#/components/schemas/string" - } - }, - "required": [ - "issuer", - "cert" - ], - "type": "object" - }, - "KeyIdInfo_array": { - "items": { - "$ref": "#/components/schemas/KeyIdInfo" - }, - "type": "array" - }, "MDType": { "enum": [ "NONE", @@ -808,10 +787,12 @@ }, "issuer": { "$ref": "#/components/schemas/string" + }, + "public_key": { + "$ref": "#/components/schemas/base64string" } }, "required": [ - "cert", "issuer" ], "type": "object" @@ -1222,12 +1203,6 @@ }, "type": "object" }, - "string_to_KeyIdInfo_array": { - "additionalProperties": { - "$ref": "#/components/schemas/KeyIdInfo_array" - }, - "type": "object" - }, "string_to_OpenIDJWKMetadata_array": { "additionalProperties": { "$ref": "#/components/schemas/OpenIDJWKMetadata_array" @@ -1473,31 +1448,6 @@ } ] }, - "/gov/jwt_keys/all": { - "get": { - "deprecated": true, - "description": "This endpoint is deprecated from 5.0.0. It is replaced by POST /gov/service/jwk", - "operationId": "GetGovJwtKeysAll", - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/string_to_KeyIdInfo_array" - } - } - }, - "description": "Default response description" - }, - "default": { - "$ref": "#/components/responses/default" - } - }, - "x-ccf-forwarding": { - "$ref": "#/components/x-ccf-forwarding/always" - } - } - }, "/gov/kv/constitution": { "get": { "deprecated": true, diff --git a/include/ccf/crypto/jwk.h b/include/ccf/crypto/jwk.h index 1b4886cb1a22..fa3200f39948 100644 --- a/include/ccf/crypto/jwk.h +++ b/include/ccf/crypto/jwk.h @@ -27,13 +27,27 @@ namespace ccf::crypto JsonWebKeyType kty; std::optional kid = std::nullopt; std::optional> x5c = std::nullopt; - std::optional issuer = std::nullopt; bool operator==(const JsonWebKey&) const = default; }; DECLARE_JSON_TYPE_WITH_OPTIONAL_FIELDS(JsonWebKey); DECLARE_JSON_REQUIRED_FIELDS(JsonWebKey, kty); - DECLARE_JSON_OPTIONAL_FIELDS(JsonWebKey, kid, x5c, issuer); + DECLARE_JSON_OPTIONAL_FIELDS(JsonWebKey, kid, x5c); + + struct JsonWebKeyExtended + { + JsonWebKeyType kty; + std::optional kid = std::nullopt; + std::optional> x5c = std::nullopt; + std::optional n = std::nullopt; + std::optional e = std::nullopt; + std::optional issuer = std::nullopt; + + bool operator==(const JsonWebKeyExtended&) const = default; + }; + DECLARE_JSON_TYPE_WITH_OPTIONAL_FIELDS(JsonWebKeyExtended); + DECLARE_JSON_REQUIRED_FIELDS(JsonWebKeyExtended, kty); + DECLARE_JSON_OPTIONAL_FIELDS(JsonWebKeyExtended, kid, x5c, n, e, issuer); enum class JsonWebKeyECCurve { diff --git a/include/ccf/service/tables/jwt.h b/include/ccf/service/tables/jwt.h index 23ebe5268499..8e3beea15f4a 100644 --- a/include/ccf/service/tables/jwt.h +++ b/include/ccf/service/tables/jwt.h @@ -37,16 +37,18 @@ namespace ccf using JwtIssuer = std::string; using JwtKeyId = std::string; using Cert = std::vector; + using PublicKey = std::vector; struct OpenIDJWKMetadata { - Cert cert; + std::optional cert; + std::optional public_key; JwtIssuer issuer; std::optional constraint; }; DECLARE_JSON_TYPE_WITH_OPTIONAL_FIELDS(OpenIDJWKMetadata); - DECLARE_JSON_REQUIRED_FIELDS(OpenIDJWKMetadata, cert, issuer); - DECLARE_JSON_OPTIONAL_FIELDS(OpenIDJWKMetadata, constraint); + DECLARE_JSON_REQUIRED_FIELDS(OpenIDJWKMetadata, issuer); + DECLARE_JSON_OPTIONAL_FIELDS(OpenIDJWKMetadata, cert, public_key, constraint); using JwtIssuers = ServiceMap; using JwtPublicSigningKeys = @@ -75,7 +77,7 @@ namespace ccf struct JsonWebKeySet { - std::vector keys; + std::vector keys; bool operator!=(const JsonWebKeySet& rhs) const { diff --git a/samples/constitutions/default/actions.js b/samples/constitutions/default/actions.js index 654ecc065326..1d1d405c6782 100644 --- a/samples/constitutions/default/actions.js +++ b/samples/constitutions/default/actions.js @@ -130,15 +130,22 @@ function checkJwks(value, field) { for (const [i, jwk] of value.keys.entries()) { checkType(jwk.kid, "string", `${field}.keys[${i}].kid`); checkType(jwk.kty, "string", `${field}.keys[${i}].kty`); - checkType(jwk.x5c, "array", `${field}.keys[${i}].x5c`); - checkLength(jwk.x5c, 1, null, `${field}.keys[${i}].x5c`); - for (const [j, b64der] of jwk.x5c.entries()) { - checkType(b64der, "string", `${field}.keys[${i}].x5c[${j}]`); - const pem = - "-----BEGIN CERTIFICATE-----\n" + - b64der + - "\n-----END CERTIFICATE-----"; - checkX509CertBundle(pem, `${field}.keys[${i}].x5c[${j}]`); + if (jwk.x5c) { + checkType(jwk.x5c, "array", `${field}.keys[${i}].x5c`); + checkLength(jwk.x5c, 1, null, `${field}.keys[${i}].x5c`); + for (const [j, b64der] of jwk.x5c.entries()) { + checkType(b64der, "string", `${field}.keys[${i}].x5c[${j}]`); + const pem = + "-----BEGIN CERTIFICATE-----\n" + + b64der + + "\n-----END CERTIFICATE-----"; + checkX509CertBundle(pem, `${field}.keys[${i}].x5c[${j}]`); + } + } else if (jwk.n && jwk.e) { + checkType(jwk.n, "string", `${field}.keys[${i}].n`); + checkType(jwk.e, "string", `${field}.keys[${i}].e`); + } else { + throw new Error("JWK must contain either x5c or n and e"); } } } diff --git a/src/endpoints/authentication/jwt_auth.cpp b/src/endpoints/authentication/jwt_auth.cpp index 05ceb862ff2d..e4042f1c08dd 100644 --- a/src/endpoints/authentication/jwt_auth.cpp +++ b/src/endpoints/authentication/jwt_auth.cpp @@ -3,6 +3,7 @@ #include "ccf/endpoints/authentication/jwt_auth.h" +#include "ccf/crypto/rsa_key_pair.h" #include "ccf/ds/nonstd.h" #include "ccf/pal/locking.h" #include "ccf/rpc_context.h" @@ -146,6 +147,7 @@ namespace ccf { token_keys = std::vector{OpenIDJWKMetadata{ .cert = *fallback_key, + .public_key = std::nullopt, .issuer = *fallback_issuers->get(key_id), .constraint = std::nullopt}}; } @@ -160,10 +162,34 @@ namespace ccf for (const auto& metadata : *token_keys) { - auto verifier = verifiers->get_verifier(metadata.cert); - if (!::http::JwtVerifier::validate_token_signature(token, verifier)) + if (metadata.public_key.has_value()) { - error_reason = "Signature verification failed"; + auto pubkey = + ccf::crypto::make_rsa_public_key(metadata.public_key.value()); + // To Do cache + if (!pubkey->verify( + (uint8_t*)token.signed_content.data(), + token.signed_content.size(), + token.signature.data(), + token.signature.size(), + ccf::crypto::MDType::SHA256)) + { + error_reason = "Signature verification failed (raw public key)"; + continue; + } + } + else if (metadata.cert.has_value()) + { + auto verifier = verifiers->get_verifier(metadata.cert.value()); + if (!::http::JwtVerifier::validate_token_signature(token, verifier)) + { + error_reason = "Signature verification failed (certificate)"; + continue; + } + } + else + { + error_reason = "Missing public key or certificate"; continue; } diff --git a/src/node/gov/handlers/service_state.h b/src/node/gov/handlers/service_state.h index c941e14f960a..936e9d05fede 100644 --- a/src/node/gov/handlers/service_state.h +++ b/src/node/gov/handlers/service_state.h @@ -578,10 +578,29 @@ namespace ccf::gov::endpoints { auto info = nlohmann::json::object(); - // cert is stored as DER - convert to PEM for API - const auto cert_pem = - ccf::crypto::cert_der_to_pem(metadata.cert); - info["certificate"] = cert_pem.str(); + std::string key{}, value{}; + if (metadata.public_key.has_value()) + { + key = "publicKey"; + value = ccf::crypto::make_rsa_public_key( + metadata.public_key.value()) + ->public_key_pem() + .str(); + } + else if (metadata.cert.has_value()) + { + key = "certificate"; + value = + ccf::crypto::cert_der_to_pem(metadata.cert.value()).str(); + } + else + { + // This must not happen, but we intentionally ignore it here + // as this is just a key reporting endpoint, this situation + // must be prevented at the time of storing the new keys. + } + + info[key] = value; info["issuer"] = metadata.issuer; info["constraint"] = metadata.constraint; diff --git a/src/node/rpc/jwt_management.h b/src/node/rpc/jwt_management.h index af7c011ac8ef..b49e363a184c 100644 --- a/src/node/rpc/jwt_management.h +++ b/src/node/rpc/jwt_management.h @@ -2,6 +2,7 @@ // Licensed under the Apache 2.0 License. #pragma once +#include "ccf/crypto/rsa_key_pair.h" #include "ccf/crypto/verifier.h" #include "ccf/ds/hex.h" #include "ccf/service/tables/jwt.h" @@ -12,6 +13,67 @@ #include #include +namespace +{ + std::pair, bool> try_parse_jwk( + const ccf::crypto::JsonWebKeyExtended& jwk) + { + const auto& kid = jwk.kid.value(); + if ( + jwk.e.has_value() && !jwk.e->empty() && jwk.n.has_value() && + !jwk.n->empty()) + { + std::vector der; + ccf::crypto::JsonWebKeyRSAPublic data; + data.kty = ccf::crypto::JsonWebKeyType::RSA; + data.kid = jwk.kid; + data.n = jwk.n.value(); + data.e = jwk.e.value(); + try + { + const auto pubkey = ccf::crypto::make_rsa_public_key(data); + der = pubkey->public_key_der(); + } + catch (const std::invalid_argument& exc) + { + throw std::logic_error( + fmt::format("Failed to construct RSA public key: {}", exc.what())); + } + return {der, false}; + } + else if (jwk.x5c.has_value() && !jwk.x5c->empty()) + { + auto& der_base64 = jwk.x5c.value()[0]; + ccf::Cert der; + try + { + der = ccf::crypto::raw_from_b64(der_base64); + } + catch (const std::invalid_argument& e) + { + throw std::logic_error( + fmt::format("Could not parse x5c of key id {}: {}", kid, e.what())); + } + try + { + ccf::crypto::make_unique_verifier(der); // throws + } + catch (std::invalid_argument& exc) + { + throw std::logic_error(fmt::format( + "JWKS kid {} has an invalid X.509 certificate: {}", kid, exc.what())); + } + + return {der, true}; + } + else + { + throw std::logic_error( + fmt::format("JWKS kid {} has neither x5c or RSA public key", kid)); + } + } +} + namespace ccf { static void legacy_remove_jwt_public_signing_keys( @@ -37,8 +99,8 @@ namespace ccf const std::string& issuer, const std::string& constraint) { // Only accept key constraints for the same (sub)domain. This is to avoid - // setting keys from issuer A which will be used to validate iss claims for - // issuer B, so this doesn't make sense (at least for now). + // setting keys from issuer A which will be used to validate iss claims + // for issuer B, so this doesn't make sense (at least for now). const auto issuer_domain = ::http::parse_url_full(issuer).host; const auto constraint_domain = ::http::parse_url_full(constraint).host; @@ -48,13 +110,13 @@ namespace ccf return false; } - // Either constraint's domain == issuer's domain or it is a subdomain, e.g.: - // limited.facebook.com + // Either constraint's domain == issuer's domain or it is a subdomain, + // e.g.: limited.facebook.com // .facebook.com // // It may make sense to support vice-versa too, but we haven't found any - // instances of that so far, so leaveing it only-way only for facebook-like - // cases. + // instances of that so far, so leaveing it only-way only for + // facebook-like cases. if (issuer_domain != constraint_domain) { const auto pattern = "." + constraint_domain; @@ -68,8 +130,8 @@ namespace ccf ccf::kv::Tx& tx, std::string issuer) { // Unlike resetting JWT keys for a particular issuer, removing keys can be - // safely done on both table revisions, as soon as the application shouldn't - // use them anyway after being ask about that explicitly. + // safely done on both table revisions, as soon as the application + // shouldn't use them anyway after being ask about that explicitly. legacy_remove_jwt_public_signing_keys(tx, issuer); auto keys = @@ -113,74 +175,46 @@ namespace ccf LOG_FAIL_FMT("{}: JWKS has no keys", log_prefix); return false; } - std::map> new_keys; + using DerValue = std::pair, bool /* is cert */>; + std::map new_keys; std::map issuer_constraints; - for (auto& jwk : jwks.keys) - { - if (!jwk.kid.has_value()) - { - LOG_FAIL_FMT("No kid for JWT signing key"); - return false; - } - if (!jwk.x5c.has_value() && jwk.x5c->empty()) - { - LOG_FAIL_FMT("{}: JWKS is invalid (empty x5c)", log_prefix); - return false; - } - - auto& der_base64 = jwk.x5c.value()[0]; - ccf::Cert der; - auto const& kid = jwk.kid.value(); - try - { - der = ccf::crypto::raw_from_b64(der_base64); - } - catch (const std::invalid_argument& e) - { - LOG_FAIL_FMT( - "{}: Could not parse x5c of key id {}: {}", - log_prefix, - kid, - e.what()); - return false; - } - - try - { - ccf::crypto::make_unique_verifier( - (std::vector)der); // throws on error - } - catch (std::invalid_argument& exc) + try + { + for (auto& jwk : jwks.keys) { - LOG_FAIL_FMT( - "{}: JWKS kid {} has an invalid X.509 certificate: {}", - log_prefix, - kid, - exc.what()); - return false; - } + if (!jwk.kid.has_value()) + { + throw(std::logic_error("Missing kid for JWT signing key")); + } - LOG_INFO_FMT("{}: Storing JWT signing key with kid {}", log_prefix, kid); - new_keys.emplace(kid, der); + const auto& kid = jwk.kid.value(); + auto [der, is_cert] = try_parse_jwk(jwk); - if (jwk.issuer) - { - if (!check_issuer_constraint(issuer, *jwk.issuer)) + if (jwk.issuer) { - LOG_FAIL_FMT( - "{}: JWKS kid {} with issuer constraint {} fails validation " - "against issuer {}", - log_prefix, - kid, - *jwk.issuer, - issuer); - return false; + if (!check_issuer_constraint(issuer, *jwk.issuer)) + { + throw std::logic_error(fmt::format( + "JWKS kid {} with issuer constraint {} fails validation " + "against " + "issuer {}", + kid, + *jwk.issuer, + issuer)); + } + + issuer_constraints.emplace(kid, *jwk.issuer); } - issuer_constraints.emplace(kid, *jwk.issuer); + new_keys.emplace(kid, DerValue{std::move(der), is_cert}); } } + catch (const std::exception& exc) + { + LOG_FAIL_FMT("{}: {}", log_prefix, exc.what()); + return false; + } if (new_keys.empty()) { @@ -201,9 +235,20 @@ namespace ccf return true; }); - for (auto& [kid, der] : new_keys) + for (auto& [kid, data] : new_keys) { - OpenIDJWKMetadata value{der, issuer, std::nullopt}; + OpenIDJWKMetadata value{std::nullopt, std::nullopt, issuer, std::nullopt}; + + const auto& [der, is_cert] = data; + if (is_cert) + { + value.cert = der; + } + else + { + value.public_key = der; + } + const auto it = issuer_constraints.find(kid); if (it != issuer_constraints.end()) { diff --git a/src/node/rpc/member_frontend.h b/src/node/rpc/member_frontend.h index 438b8709b45f..b8a9620826d2 100644 --- a/src/node/rpc/member_frontend.h +++ b/src/node/rpc/member_frontend.h @@ -67,14 +67,6 @@ namespace ccf DECLARE_JSON_TYPE(JsBundle) DECLARE_JSON_REQUIRED_FIELDS(JsBundle, metadata, modules) - struct KeyIdInfo - { - JwtIssuer issuer; - ccf::crypto::Pem cert; - }; - DECLARE_JSON_TYPE(KeyIdInfo) - DECLARE_JSON_REQUIRED_FIELDS(KeyIdInfo, issuer, cert) - struct FullMemberDetails : public ccf::MemberDetails { ccf::crypto::Pem cert; @@ -1098,30 +1090,6 @@ namespace ccf "5.0.0", "POST /gov/recovery/members/{memberId}:recover") .install(); - using JWTKeyMap = std::map>; - - auto get_jwt_keys = [this](auto& ctx, nlohmann::json&& body) { - auto keys = ctx.tx.ro(network.jwt_public_signing_keys_metadata); - JWTKeyMap kmap; - keys->foreach([&kmap](const auto& k, const auto& v) { - std::vector info; - for (const auto& metadata : v) - { - info.push_back(KeyIdInfo{ - metadata.issuer, ccf::crypto::cert_der_to_pem(metadata.cert)}); - } - kmap.emplace(k, std::move(info)); - return true; - }); - - return make_success(kmap); - }; - make_endpoint( - "/jwt_keys/all", HTTP_GET, json_adapter(get_jwt_keys), no_auth_required) - .set_auto_schema() - .set_openapi_deprecated_replaced("5.0.0", "POST /gov/service/jwk") - .install(); - auto post_proposals_js = [this](ccf::endpoints::EndpointContext& ctx) { std::optional cose_auth_id = std::nullopt; diff --git a/tests/infra/jwt_issuer.py b/tests/infra/jwt_issuer.py index 1882e57b02f5..34803be7963b 100644 --- a/tests/infra/jwt_issuer.py +++ b/tests/infra/jwt_issuer.py @@ -107,6 +107,22 @@ def __exit__(self, exc_type, exc_value, traceback): self.stop() +def get_jwt_issuers(args, node): + with node.api_versioned_client(api_version=args.gov_api_version) as c: + r = c.get("/gov/service/jwk") + assert r.status_code == HTTPStatus.OK, r + body = r.body.json() + return body["issuers"] + + +def get_jwt_keys(args, node): + with node.api_versioned_client(api_version=args.gov_api_version) as c: + r = c.get("/gov/service/jwk") + assert r.status_code == HTTPStatus.OK, r + body = r.body.json() + return body["keys"] + + class JwtIssuer: TEST_JWT_ISSUER_NAME = "https://example.issuer" TEST_CA_BUNDLE_NAME = "test_ca_bundle_name" @@ -243,25 +259,20 @@ def wait_for_refresh(self, network, args, kid=None): return time.sleep(0.1) else: - with primary.client( - network.consortium.get_any_active_member().local_id - ) as c: - while time.time() < end_time: - logs = [] - r = c.get("/gov/jwt_keys/all", log_capture=logs) - assert r.status_code == 200, r - keys = r.body.json() - if kid_ in keys: - kid_vals = keys[kid_] - if primary.version_after("ccf-5.0.0-dev17"): - assert len(kid_vals) == 1 - stored_cert = kid_vals[0]["cert"] - else: - stored_cert = kid_vals["cert"] - if self.cert_pem == stored_cert: - flush_info(logs) - return - time.sleep(0.1) + while time.time() < end_time: + logs = [] + keys = get_jwt_keys(args, primary) + if kid_ in keys: + kid_vals = keys[kid_] + if primary.version_after("ccf-5.0.0-dev17"): + assert len(kid_vals) == 1 + stored_cert = kid_vals[0]["cert"] + else: + stored_cert = kid_vals["cert"] + if self.cert_pem == stored_cert: + flush_info(logs) + return + time.sleep(0.1) flush_info(logs) raise TimeoutError( f"JWT public signing keys were not refreshed after {timeout}s" diff --git a/tests/js-custom-authorization/custom_authorization.py b/tests/js-custom-authorization/custom_authorization.py index 2dbd949e4ec0..7f7aa068687c 100644 --- a/tests/js-custom-authorization/custom_authorization.py +++ b/tests/js-custom-authorization/custom_authorization.py @@ -20,6 +20,8 @@ from http import HTTPStatus import subprocess from contextlib import contextmanager +from cryptography.x509 import load_pem_x509_certificate +from cryptography.hazmat.backends import default_backend from loguru import logger as LOG @@ -102,6 +104,35 @@ def set_issuer_with_a_key(primary, network, issuer, kid, constraint): network.consortium.set_jwt_issuer(primary, metadata_fp.name) +def to_b64(number: int): + as_bytes = number.to_bytes((number.bit_length() + 7) // 8, "big") + return base64.b64encode(as_bytes).decode("ascii") + + +def set_issuer_with_a_raw_key(primary, network, issuer, kid, constraint): + with tempfile.NamedTemporaryFile(prefix="ccf", mode="w+") as metadata_fp: + cert = load_pem_x509_certificate(issuer.cert_pem.encode(), default_backend()) + pubkey = cert.public_key() + data = { + "issuer": issuer.issuer_url, + "auto_refresh": False, + "jwks": { + "keys": [ + { + "kty": "RSA", + "kid": kid, + "n": to_b64(pubkey.public_numbers().n), + "e": to_b64(pubkey.public_numbers().e), + "issuer": constraint, + } + ] + }, + } + json.dump(data, metadata_fp) + metadata_fp.flush() + network.consortium.set_jwt_issuer(primary, metadata_fp.name) + + def parse_error_message(r): return r.body.json()["error"]["details"][0]["message"] @@ -394,6 +425,40 @@ def test_jwt_auth(network, args): return network +@reqs.description("JWT authentication as by OpenID spec with raw public key") +def test_jwt_auth_raw_key(network, args): + primary, _ = network.find_nodes() + + issuer = infra.jwt_issuer.JwtIssuer("https://example.issuer") + + jwt_kid = "my_key_id" + + LOG.info("Add JWT issuer with initial keys") + + set_issuer_with_a_raw_key(primary, network, issuer, jwt_kid, issuer.name) + + LOG.info("Calling jwt endpoint after storing keys") + with primary.client("user0") as c: + r = c.get("/app/jwt", headers=infra.jwt_issuer.make_bearer_header("garbage")) + assert r.status_code == HTTPStatus.UNAUTHORIZED, r.status_code + assert "Malformed JWT" in parse_error_message(r), r + + jwt_mismatching_key_priv_pem, _ = infra.crypto.generate_rsa_keypair(2048) + jwt = infra.crypto.create_jwt({}, jwt_mismatching_key_priv_pem, jwt_kid) + r = c.get("/app/jwt", headers=infra.jwt_issuer.make_bearer_header(jwt)) + assert r.status_code == HTTPStatus.UNAUTHORIZED, r.status_code + assert "JWT payload is missing required field" in parse_error_message(r), r + + r = c.get( + "/app/jwt", + headers=infra.jwt_issuer.make_bearer_header(issuer.issue_jwt(jwt_kid)), + ) + assert r.status_code == HTTPStatus.OK, r.status_code + + network.consortium.remove_jwt_issuer(primary, issuer.name) + return network + + @reqs.description("JWT authentication as by MSFT Entra (single tenant)") def test_jwt_auth_msft_single_tenant(network, args): """For a specific tenant, only tokens with this issuer+tenant can auth.""" @@ -708,6 +773,7 @@ def run_authn(args): network.start_and_open(args) network = test_cert_auth(network, args) network = test_jwt_auth(network, args) + network = test_jwt_auth_raw_key(network, args) network = test_jwt_auth_msft_single_tenant(network, args) network = test_jwt_auth_msft_multitenancy(network, args) network = test_jwt_auth_msft_same_kids_different_issuers(network, args) diff --git a/tests/jwt_test.py b/tests/jwt_test.py index ef0e861fd3f6..b18fca1f21d7 100644 --- a/tests/jwt_test.py +++ b/tests/jwt_test.py @@ -12,33 +12,16 @@ import infra.e2e_args import infra.proposal import suite.test_requirements as reqs -import infra.jwt_issuer +from infra.jwt_issuer import get_jwt_issuers, get_jwt_keys from infra.runner import ConcurrentRunner import ca_certs import ccf.ledger from ccf.tx_id import TxID import infra.clients -import http from loguru import logger as LOG -def get_jwt_issuers(args, node): - with node.api_versioned_client(api_version=args.gov_api_version) as c: - r = c.get("/gov/service/jwk") - assert r.status_code == http.HTTPStatus.OK, r - body = r.body.json() - return body["issuers"] - - -def get_jwt_keys(args, node): - with node.api_versioned_client(api_version=args.gov_api_version) as c: - r = c.get("/gov/service/jwk") - assert r.status_code == http.HTTPStatus.OK, r - body = r.body.json() - return body["keys"] - - def set_issuer_with_keys(network, primary, issuer, kids): with tempfile.NamedTemporaryFile(prefix="ccf", mode="w+") as metadata_fp: json.dump({"issuer": issuer.name}, metadata_fp)