Strategic work for IETF submission of draft-nennemann-act-01 and
draft-nennemann-wimse-ect-02:
Package restructure:
- move ACT and ECT refimpls to workspace/packages/{act,ect}/
- ietf-act and ietf-ect distribution names (sibling packages)
- cross-spec interop test plan (INTEROP-TEST-PLAN.md)
ACT draft -01 revisions:
- rename 'par' claim to 'pred' (align with ECT)
- rename 'Agent Compact Token' to 'Agent Context Token' (semantic
alignment with ECT family)
- add Applicability section (MCP, OpenAI, LangGraph, A2A, CrewAI)
- add DAG vs Linear Delegation Chains section (differentiator vs
txn-tokens-for-agents actchain, Agentic JWT, AIP/IBCTs)
- add Related Work: AIP, SentinelAgent, Agentic JWT, txn-tokens-for-agents,
HDP, SCITT-AI-agent-execution
- pin SCITT arch to -22, note AUTH48 status
Outreach drafts:
- Emirdag liaison email (SCITT-AI coordination)
- OAuth ML response on txn-tokens-for-agents-06
Strategy document:
- STRATEGY.md with phased action plan, risk register, timeline
Submodule:
- update workspace/drafts/ietf-wimse-ect pointer to -02 commit
192 lines
6.5 KiB
Python
192 lines
6.5 KiB
Python
"""Tests for act.verify module."""
|
|
|
|
import time
|
|
import uuid
|
|
|
|
import pytest
|
|
|
|
from act.crypto import (
|
|
ACTKeyResolver,
|
|
KeyRegistry,
|
|
generate_ed25519_keypair,
|
|
sign,
|
|
)
|
|
from act.errors import (
|
|
ACTAudienceMismatchError,
|
|
ACTCapabilityError,
|
|
ACTExpiredError,
|
|
ACTPhaseError,
|
|
ACTSignatureError,
|
|
ACTValidationError,
|
|
)
|
|
from act.ledger import ACTLedger
|
|
from act.lifecycle import transition_to_record
|
|
from act.token import (
|
|
ACTMandate,
|
|
ACTRecord,
|
|
Capability,
|
|
Delegation,
|
|
TaskClaim,
|
|
encode_jws,
|
|
)
|
|
from act.verify import ACTVerifier
|
|
|
|
|
|
@pytest.fixture
|
|
def setup():
|
|
iss_priv, iss_pub = generate_ed25519_keypair()
|
|
sub_priv, sub_pub = generate_ed25519_keypair()
|
|
registry = KeyRegistry()
|
|
registry.register("iss-key", iss_pub)
|
|
registry.register("sub-key", sub_pub)
|
|
resolver = ACTKeyResolver(registry=registry)
|
|
base_time = 1772064000
|
|
return {
|
|
"iss_priv": iss_priv, "iss_pub": iss_pub,
|
|
"sub_priv": sub_priv, "sub_pub": sub_pub,
|
|
"registry": registry, "resolver": resolver,
|
|
"base_time": base_time,
|
|
}
|
|
|
|
|
|
def make_mandate(setup, **overrides):
|
|
bt = setup["base_time"]
|
|
defaults = dict(
|
|
alg="EdDSA", kid="iss-key",
|
|
iss="agent-issuer", sub="agent-subject",
|
|
aud="agent-subject",
|
|
iat=bt, exp=bt + 900,
|
|
jti=str(uuid.uuid4()),
|
|
task=TaskClaim(purpose="test"),
|
|
cap=[Capability(action="read.data")],
|
|
)
|
|
defaults.update(overrides)
|
|
return ACTMandate(**defaults)
|
|
|
|
|
|
def sign_mandate(mandate, priv_key):
|
|
sig = sign(priv_key, mandate.signing_input())
|
|
return encode_jws(mandate, sig)
|
|
|
|
|
|
class TestVerifyMandate:
|
|
def test_valid_mandate(self, setup):
|
|
verifier = ACTVerifier(
|
|
setup["resolver"],
|
|
verifier_id="agent-subject",
|
|
trusted_issuers={"agent-issuer"},
|
|
)
|
|
mandate = make_mandate(setup)
|
|
compact = sign_mandate(mandate, setup["iss_priv"])
|
|
result = verifier.verify_mandate(compact, now=setup["base_time"] + 100)
|
|
assert result.iss == "agent-issuer"
|
|
|
|
def test_expired(self, setup):
|
|
verifier = ACTVerifier(setup["resolver"], verifier_id="agent-subject")
|
|
mandate = make_mandate(setup)
|
|
compact = sign_mandate(mandate, setup["iss_priv"])
|
|
with pytest.raises(ACTExpiredError):
|
|
verifier.verify_mandate(compact, now=setup["base_time"] + 2000)
|
|
|
|
def test_wrong_audience(self, setup):
|
|
verifier = ACTVerifier(
|
|
setup["resolver"], verifier_id="other-agent",
|
|
trusted_issuers={"agent-issuer"},
|
|
)
|
|
mandate = make_mandate(setup)
|
|
compact = sign_mandate(mandate, setup["iss_priv"])
|
|
with pytest.raises(ACTAudienceMismatchError):
|
|
verifier.verify_mandate(
|
|
compact, now=setup["base_time"] + 100, check_sub=False,
|
|
)
|
|
|
|
def test_untrusted_issuer(self, setup):
|
|
verifier = ACTVerifier(
|
|
setup["resolver"], verifier_id="agent-subject",
|
|
trusted_issuers={"trusted-only"},
|
|
)
|
|
mandate = make_mandate(setup)
|
|
compact = sign_mandate(mandate, setup["iss_priv"])
|
|
with pytest.raises(ACTValidationError, match="not trusted"):
|
|
verifier.verify_mandate(compact, now=setup["base_time"] + 100)
|
|
|
|
def test_signature_failure(self, setup):
|
|
verifier = ACTVerifier(setup["resolver"], verifier_id="agent-subject")
|
|
mandate = make_mandate(setup)
|
|
compact = sign_mandate(mandate, setup["iss_priv"])
|
|
# Tamper with signature
|
|
parts = compact.split(".")
|
|
parts[2] = parts[2][:-4] + "XXXX"
|
|
tampered = ".".join(parts)
|
|
with pytest.raises(ACTSignatureError):
|
|
verifier.verify_mandate(tampered, now=setup["base_time"] + 100)
|
|
|
|
def test_phase2_as_mandate(self, setup):
|
|
verifier = ACTVerifier(setup["resolver"])
|
|
mandate = make_mandate(setup)
|
|
record, compact = transition_to_record(
|
|
mandate, sub_kid="sub-key", sub_private_key=setup["sub_priv"],
|
|
exec_act="read.data", pred=[], status="completed",
|
|
exec_ts=setup["base_time"] + 100,
|
|
)
|
|
with pytest.raises(ACTPhaseError):
|
|
verifier.verify_mandate(compact, now=setup["base_time"] + 100)
|
|
|
|
def test_future_iat(self, setup):
|
|
verifier = ACTVerifier(setup["resolver"], verifier_id="agent-subject")
|
|
bt = setup["base_time"]
|
|
mandate = make_mandate(setup, iat=bt + 1000, exp=bt + 2000)
|
|
compact = sign_mandate(mandate, setup["iss_priv"])
|
|
with pytest.raises(ACTValidationError, match="future"):
|
|
verifier.verify_mandate(compact, now=bt)
|
|
|
|
|
|
class TestVerifyRecord:
|
|
def test_valid_record(self, setup):
|
|
verifier = ACTVerifier(
|
|
setup["resolver"],
|
|
verifier_id="agent-subject",
|
|
trusted_issuers={"agent-issuer"},
|
|
)
|
|
mandate = make_mandate(setup)
|
|
record, compact = transition_to_record(
|
|
mandate, sub_kid="sub-key", sub_private_key=setup["sub_priv"],
|
|
exec_act="read.data", pred=[],
|
|
exec_ts=setup["base_time"] + 100, status="completed",
|
|
)
|
|
result = verifier.verify_record(
|
|
compact, now=setup["base_time"] + 200, check_aud=False,
|
|
)
|
|
assert result.exec_act == "read.data"
|
|
|
|
def test_wrong_signer(self, setup):
|
|
verifier = ACTVerifier(setup["resolver"])
|
|
mandate = make_mandate(setup)
|
|
record = ACTRecord.from_mandate(
|
|
mandate, kid="sub-key", exec_act="read.data",
|
|
pred=[], exec_ts=setup["base_time"] + 100, status="completed",
|
|
)
|
|
# Sign with iss key instead of sub key
|
|
sig = sign(setup["iss_priv"], record.signing_input())
|
|
compact = encode_jws(record, sig)
|
|
with pytest.raises(ACTSignatureError):
|
|
verifier.verify_record(compact, now=setup["base_time"] + 200)
|
|
|
|
def test_with_dag_validation(self, setup):
|
|
verifier = ACTVerifier(
|
|
setup["resolver"], verifier_id="agent-subject",
|
|
trusted_issuers={"agent-issuer"},
|
|
)
|
|
ledger = ACTLedger()
|
|
mandate = make_mandate(setup)
|
|
record, compact = transition_to_record(
|
|
mandate, sub_kid="sub-key", sub_private_key=setup["sub_priv"],
|
|
exec_act="read.data", pred=[],
|
|
exec_ts=setup["base_time"] + 100, status="completed",
|
|
)
|
|
result = verifier.verify_record(
|
|
compact, store=ledger,
|
|
now=setup["base_time"] + 200, check_aud=False,
|
|
)
|
|
assert result.status == "completed"
|