33Node map (11 nodes):
44 resolve -> validate_schema
55 validate_schema -> check_revocation
6- check_revocation -> verify_signature (or failed)
7- verify_signature -> validate_vc (or failed)
6+ check_revocation -> verify_identity (or failed)
7+ verify_identity -> validate_vc (or failed)
88 validate_vc -> validate_delegation (or failed)
99 validate_delegation -> cross_ref_capabilities (or failed)
1010 cross_ref_capabilities -> check_reputation (or failed)
2323
2424from langgraph .graph import END , StateGraph
2525
26+ from airlock .config import get_config
2627from airlock .crypto .keys import KeyPair , resolve_public_key
2728from airlock .crypto .signing import sign_attestation , verify_model
28- from airlock .config import get_config
2929from airlock .crypto .vc import extract_capabilities , validate_credential
3030from airlock .engine .state import SessionManager
3131from airlock .gateway .revocation import RedisRevocationStore , RevocationStore
@@ -84,6 +84,7 @@ class OrchestrationState(TypedDict, total=False):
8484 _challenge_outcome : str | None
8585 _tier : int # TrustTier int value
8686 _local_only : bool
87+ _bearer_token : str | None
8788
8889
8990# ---------------------------------------------------------------------------
@@ -306,6 +307,7 @@ async def _handle_handshake(self, event: HandshakeReceived) -> None:
306307 "_challenge_outcome" : None ,
307308 "_tier" : TrustTier .UNKNOWN ,
308309 "_local_only" : getattr (request , "privacy_mode" , "any" ) == "local_only" ,
310+ "_bearer_token" : getattr (event , "bearer_token" , None ),
309311 }
310312
311313 # Run the graph synchronously through all nodes.
@@ -620,34 +622,64 @@ def _node_check_revocation(self, state: OrchestrationState) -> OrchestrationStat
620622 def _route_after_revocation (self , state : OrchestrationState ) -> str :
621623 if state .get ("failed_at" ) == "check_revocation" :
622624 return "failed"
623- return "verify_signature"
625+ return "verify_identity"
626+
627+ def _node_verify_identity (self , state : OrchestrationState ) -> OrchestrationState :
628+ """Node 2: verify agent identity via OAuth bearer token or Ed25519 signature.
624629
625- def _node_verify_signature (self , state : OrchestrationState ) -> OrchestrationState :
626- """Node 2: verify the Ed25519 signature on the HandshakeRequest."""
630+ Dual-mode authentication:
631+ 1. If a bearer token is present, attempt OAuth validation first.
632+ 2. If OAuth succeeds and token subject matches initiator DID, mark valid.
633+ 3. Otherwise fall back to Ed25519 signature verification.
634+ """
627635 checks : list [CheckResult ] = list (state .get ("check_results" , []))
628636 request = state ["handshake" ]
637+ auth_method = "ed25519"
629638
630- try :
631- verify_key = resolve_public_key (request .initiator .did )
632- valid = verify_model (request , verify_key )
633- except Exception as exc :
634- valid = False
635- logger .debug ("Signature verification error: %s" , exc )
639+ # --- Try OAuth bearer token first ---
640+ oauth_validated = False
641+ bearer_token = state .get ("_bearer_token" )
642+ if bearer_token is not None :
643+ try :
644+ from airlock .oauth .token_validator import validate_access_token
645+
646+ gateway_vk = (
647+ self ._airlock_keypair .verify_key if self ._airlock_keypair is not None else None
648+ )
649+ token_data = validate_access_token (bearer_token , gateway_vk )
650+ if token_data .get ("sub" ) == request .initiator .did :
651+ oauth_validated = True
652+ auth_method = "oauth"
653+ except ImportError :
654+ logger .debug ("OAuth module not available, falling back to Ed25519" )
655+ except Exception as exc :
656+ logger .debug ("OAuth token validation failed: %s" , exc )
657+
658+ # --- Fall back to Ed25519 signature verification ---
659+ valid = oauth_validated
660+ if not valid :
661+ try :
662+ verify_key = resolve_public_key (request .initiator .did )
663+ valid = verify_model (request , verify_key )
664+ except Exception as exc :
665+ valid = False
666+ logger .debug ("Signature verification error: %s" , exc )
636667
668+ detail = f"{ auth_method } identity verified" if valid else "Identity verification failed"
637669 checks .append (
638670 CheckResult (
639671 check = VerificationCheck .SIGNATURE ,
640672 passed = valid ,
641- detail = "Ed25519 signature valid" if valid else "Signature verification failed" ,
673+ detail = detail ,
642674 )
643675 )
644676 state ["check_results" ] = checks
645677 state ["_sig_valid" ] = valid
646678 if valid :
647679 state ["session" ].state = VerificationState .SIGNATURE_VERIFIED
648680 else :
649- state ["error" ] = "Invalid signature "
650- state ["failed_at" ] = "verify_signature "
681+ state ["error" ] = "Invalid identity credentials "
682+ state ["failed_at" ] = "verify_identity "
651683 state ["verdict" ] = TrustVerdict .REJECTED
652684 state ["session" ].state = VerificationState .FAILED
653685 return state
@@ -1055,7 +1087,7 @@ def _node_failed(self, state: OrchestrationState) -> OrchestrationState:
10551087 # Conditional edge functions
10561088 # ------------------------------------------------------------------
10571089
1058- def _route_after_signature (self , state : OrchestrationState ) -> str :
1090+ def _route_after_identity (self , state : OrchestrationState ) -> str :
10591091 return "validate_vc" if state .get ("_sig_valid" ) else "failed"
10601092
10611093 def _route_after_vc (self , state : OrchestrationState ) -> str :
@@ -1068,6 +1100,9 @@ def _route_after_reputation(self, state: OrchestrationState) -> str:
10681100 elif routing in ("fast_path" , "issue_verdict" ):
10691101 return "issue_verdict"
10701102 else :
1103+ cfg = get_config ()
1104+ if cfg .challenge_fallback_mode == "disabled" :
1105+ return "issue_verdict"
10711106 return "semantic_challenge"
10721107
10731108 # ------------------------------------------------------------------
@@ -1079,7 +1114,7 @@ def _build_graph(self) -> Any:
10791114
10801115 graph .add_node ("validate_schema" , self ._node_validate_schema )
10811116 graph .add_node ("check_revocation" , self ._node_check_revocation )
1082- graph .add_node ("verify_signature " , self ._node_verify_signature )
1117+ graph .add_node ("verify_identity " , self ._node_verify_identity )
10831118 graph .add_node ("validate_vc" , self ._node_validate_vc )
10841119 graph .add_node ("validate_delegation" , self ._node_validate_delegation )
10851120 graph .add_node ("cross_ref_capabilities" , self ._node_cross_ref_capabilities )
@@ -1095,11 +1130,11 @@ def _build_graph(self) -> Any:
10951130 graph .add_conditional_edges (
10961131 "check_revocation" ,
10971132 self ._route_after_revocation ,
1098- {"verify_signature " : "verify_signature " , "failed" : "failed" },
1133+ {"verify_identity " : "verify_identity " , "failed" : "failed" },
10991134 )
11001135 graph .add_conditional_edges (
1101- "verify_signature " ,
1102- self ._route_after_signature ,
1136+ "verify_identity " ,
1137+ self ._route_after_identity ,
11031138 {"validate_vc" : "validate_vc" , "failed" : "failed" },
11041139 )
11051140 graph .add_conditional_edges (
0 commit comments