More test cases

This commit is contained in:
Fang-Pen Lin
2025-11-11 17:00:40 -08:00
parent e31c8c364c
commit 771b026175
2 changed files with 135 additions and 22 deletions

View File

@@ -14,8 +14,82 @@ Feature: Challenge
And I create a RSA private key pair as cert_key
And I sign the certificate signing request csr with private key cert_key and output it as csr_pem in PEM format
And I submit the certificate signing request PEM csr_pem certificate order to the ACME server as order
And I select challenge with type http-01 for domain localhost from order at order as challenge
And I select challenge with type http-01 for domain localhost from order in order as challenge
And I serve challenge response for challenge at localhost
And I tell ACME server that challenge is ready to be verified
And I poll and finalize the ACME order order as finalized_order
And the value finalized_order.body with jq ".status" should be equal to "valid"
Scenario: Did not finish all challenges
Given I have an ACME cert profile as "acme_profile"
When I have an ACME client connecting to "{BASE_URL}/api/v1/pki/acme/profiles/{acme_profile.id}/directory"
Then I register a new ACME account with email fangpen@infisical.com and EAB key id "{acme_profile.eab_kid}" with secret "{acme_profile.eab_secret}" as acme_account
When I create certificate signing request as csr
Then I add names to certificate signing request csr
"""
{
"COMMON_NAME": "localhost"
}
"""
And I add subject alternative name to certificate signing request csr
"""
[
"infisical.com"
]
"""
And I create a RSA private key pair as cert_key
And I sign the certificate signing request csr with private key cert_key and output it as csr_pem in PEM format
And I submit the certificate signing request PEM csr_pem certificate order to the ACME server as order
And I select challenge with type http-01 for domain localhost from order in order as challenge
And I serve challenge response for challenge at localhost
And I tell ACME server that challenge is ready to be verified
# the localhost auth should be valid
And I memorize order with jq ".authorizations | map(select(.body.identifier.value == "localhost")) | first | .uri" as localhost_auth
And I peak and memorize the next nonce as nonce
When I send a raw ACME request to "{localhost_auth}"
"""
{
"protected": {
"alg": "RS256",
"nonce": "{nonce}",
"url": "{localhost_auth}",
"kid": "{acme_account.uri}"
}
}
"""
Then the value response.status_code should be equal to 200
And the value response with jq ".status" should be equal to "valid"
# the infisical.com auth should still be pending
And I memorize order with jq ".authorizations | map(select(.body.identifier.value == "infisical.com")) | first | .uri" as infisical_auth
And I memorize response.headers with jq ".["replay-nonce"]" as nonce
When I send a raw ACME request to "{infisical_auth}"
"""
{
"protected": {
"alg": "RS256",
"nonce": "{nonce}",
"url": "{infisical_auth}",
"kid": "{acme_account.uri}"
}
}
"""
Then the value response.status_code should be equal to 200
And the value response with jq ".status" should be equal to "pending"
# the order should be pending as well
And I memorize response.headers with jq ".["replay-nonce"]" as nonce
When I send a raw ACME request to "{order.uri}"
"""
{
"protected": {
"alg": "RS256",
"nonce": "{nonce}",
"url": "{order.uri}",
"kid": "{acme_account.uri}"
}
}
"""
Then the value response.status_code should be equal to 200
And the value response with jq ".status" should be equal to "pending"

View File

@@ -8,6 +8,7 @@ import acme.client
import httpx
import jq
import requests
import requests.structures
import glom
from faker import Faker
from acme import client
@@ -115,6 +116,8 @@ def eval_var(context: Context, var_path: str, as_json: bool = True):
value = value.to_json()
elif isinstance(value, requests.Response):
value = value.json()
elif isinstance(value, requests.structures.CaseInsensitiveDict):
value = dict(value.lower_items())
elif isinstance(value, httpx.Response):
value = value.json()
return value
@@ -569,51 +572,58 @@ def step_impl(context: Context, var_path: str):
print(json.dumps(value.json(), indent=2))
@then(
"I select challenge with type {challenge_type} for domain {domain} from order at {var_path} as {challenge_var}"
)
def step_impl(
def select_challenge(
context: Context,
challenge_type: str,
order_var_path: str,
domain: str,
var_path: str,
challenge_var: str,
):
order = eval_var(context, var_path, as_json=False)
acme_client = context.acme_client
order = eval_var(context, order_var_path, as_json=False)
if isinstance(order, dict):
order_body = messages.Order.from_json(order)
order = messages.OrderResource(
body=order_body,
authorizations=[
acme_client._authzr_from_response(
acme_client._post_as_get(url), uri=url
)
for url in order_body.authorizations
],
)
if not isinstance(order, messages.OrderResource):
raise ValueError(
f"Expected OrderResource but got {type(order)!r} at {var_path!r}"
f"Expected OrderResource but got {type(order)!r} at {order_var_path!r}"
)
auths = list(
filter(lambda o: o.body.identifier.value == domain, order.authorizations)
)
if not auths:
raise ValueError(
f"Authorization for domain {domain!r} not found in {var_path!r}"
f"Authorization for domain {domain!r} not found in {order_var_path!r}"
)
if len(auths) > 1:
raise ValueError(
f"More than one order for domain {domain!r} found in {var_path!r}"
f"More than one order for domain {domain!r} found in {order_var_path!r}"
)
auth = auths[0]
challenges = list(filter(lambda a: a.typ == challenge_type, auth.body.challenges))
if not challenges:
raise ValueError(
f"Authorization type {challenge_type!r} not found in {var_path!r}"
f"Authorization type {challenge_type!r} not found in {order_var_path!r}"
)
if len(challenges) > 1:
raise ValueError(
f"More than one authorization for type {challenge_type!r} found in {var_path!r}"
f"More than one authorization for type {challenge_type!r} found in {order_var_path!r}"
)
context.vars[challenge_var] = challenges[0]
return challenges[0]
@then("I serve challenge response for {var_path} at {hostname}")
def step_impl(context: Context, var_path: str, hostname: str):
if hostname != "localhost":
raise ValueError("Currently only localhost is supported")
challenge = eval_var(context, var_path, as_json=False)
def serve_challenge(
context: Context,
challenge: messages.ChallengeBody,
):
response, validation = challenge.response_and_validation(
context.acme_client.net.key
)
@@ -629,14 +639,43 @@ def step_impl(context: Context, var_path: str, hostname: str):
context.web_server = web_server
@then("I tell ACME server that {var_path} is ready to be verified")
def step_impl(context: Context, var_path: str):
challenge = eval_var(context, var_path, as_json=False)
def notify_challenge_ready(context: Context, challenge: messages.ChallengeBody):
acme_client = context.acme_client
response, validation = challenge.response_and_validation(acme_client.net.key)
acme_client.answer_challenge(challenge, response)
@then(
"I select challenge with type {challenge_type} for domain {domain} from order in {var_path} as {challenge_var}"
)
def step_impl(
context: Context,
challenge_type: str,
domain: str,
var_path: str,
challenge_var: str,
):
challenge = select_challenge(
context=context,
challenge_type=challenge_type,
domain=domain,
order_var_path=var_path,
)
context.vars[challenge_var] = challenge
@then("I serve challenge response for {var_path} at {hostname}")
def step_impl(context: Context, var_path: str, hostname: str):
challenge = eval_var(context, var_path, as_json=False)
serve_challenge(context=context, challenge=challenge)
@then("I tell ACME server that {var_path} is ready to be verified")
def step_impl(context: Context, var_path: str):
challenge = eval_var(context, var_path, as_json=False)
notify_challenge_ready(context=context, challenge=challenge)
@then("I poll and finalize the ACME order {var_path} as {finalized_var}")
def step_impl(context: Context, var_path: str, finalized_var: str):
order = eval_var(context, var_path, as_json=False)