@kya-os/mcp-i-core 1.2.3-canary.7 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (231) hide show
  1. package/.claude/settings.local.json +9 -0
  2. package/.turbo/turbo-build.log +4 -0
  3. package/.turbo/turbo-test$colon$coverage.log +4514 -0
  4. package/.turbo/turbo-test.log +2973 -0
  5. package/COMPLIANCE_IMPROVEMENT_REPORT.md +483 -0
  6. package/Composer 3.md +615 -0
  7. package/GPT-5.md +1169 -0
  8. package/OPUS-plan.md +352 -0
  9. package/PHASE_3_AND_4.1_SUMMARY.md +585 -0
  10. package/PHASE_3_SUMMARY.md +317 -0
  11. package/PHASE_4.1.3_SUMMARY.md +428 -0
  12. package/PHASE_4.1_COMPLETE.md +525 -0
  13. package/PHASE_4_USER_DID_IDENTITY_LINKING_PLAN.md +1240 -0
  14. package/SCHEMA_COMPLIANCE_REPORT.md +275 -0
  15. package/TEST_PLAN.md +571 -0
  16. package/coverage/coverage-final.json +57 -0
  17. package/dist/__tests__/utils/mock-providers.d.ts +1 -2
  18. package/dist/__tests__/utils/mock-providers.d.ts.map +1 -1
  19. package/dist/__tests__/utils/mock-providers.js.map +1 -1
  20. package/dist/cache/oauth-config-cache.d.ts +69 -0
  21. package/dist/cache/oauth-config-cache.d.ts.map +1 -0
  22. package/dist/cache/oauth-config-cache.js +76 -0
  23. package/dist/cache/oauth-config-cache.js.map +1 -0
  24. package/dist/identity/idp-token-resolver.d.ts +53 -0
  25. package/dist/identity/idp-token-resolver.d.ts.map +1 -0
  26. package/dist/identity/idp-token-resolver.js +108 -0
  27. package/dist/identity/idp-token-resolver.js.map +1 -0
  28. package/dist/identity/idp-token-storage.interface.d.ts +42 -0
  29. package/dist/identity/idp-token-storage.interface.d.ts.map +1 -0
  30. package/dist/identity/idp-token-storage.interface.js +12 -0
  31. package/dist/identity/idp-token-storage.interface.js.map +1 -0
  32. package/dist/identity/user-did-manager.d.ts +39 -1
  33. package/dist/identity/user-did-manager.d.ts.map +1 -1
  34. package/dist/identity/user-did-manager.js +69 -3
  35. package/dist/identity/user-did-manager.js.map +1 -1
  36. package/dist/index.d.ts +22 -0
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.js +39 -1
  39. package/dist/index.js.map +1 -1
  40. package/dist/runtime/audit-logger.d.ts +37 -0
  41. package/dist/runtime/audit-logger.d.ts.map +1 -0
  42. package/dist/runtime/audit-logger.js +9 -0
  43. package/dist/runtime/audit-logger.js.map +1 -0
  44. package/dist/runtime/base.d.ts +58 -2
  45. package/dist/runtime/base.d.ts.map +1 -1
  46. package/dist/runtime/base.js +266 -11
  47. package/dist/runtime/base.js.map +1 -1
  48. package/dist/services/access-control.service.d.ts.map +1 -1
  49. package/dist/services/access-control.service.js +200 -35
  50. package/dist/services/access-control.service.js.map +1 -1
  51. package/dist/services/authorization/authorization-registry.d.ts +29 -0
  52. package/dist/services/authorization/authorization-registry.d.ts.map +1 -0
  53. package/dist/services/authorization/authorization-registry.js +57 -0
  54. package/dist/services/authorization/authorization-registry.js.map +1 -0
  55. package/dist/services/authorization/types.d.ts +53 -0
  56. package/dist/services/authorization/types.d.ts.map +1 -0
  57. package/dist/services/authorization/types.js +10 -0
  58. package/dist/services/authorization/types.js.map +1 -0
  59. package/dist/services/batch-delegation.service.d.ts +53 -0
  60. package/dist/services/batch-delegation.service.d.ts.map +1 -0
  61. package/dist/services/batch-delegation.service.js +95 -0
  62. package/dist/services/batch-delegation.service.js.map +1 -0
  63. package/dist/services/oauth-config.service.d.ts +53 -0
  64. package/dist/services/oauth-config.service.d.ts.map +1 -0
  65. package/dist/services/oauth-config.service.js +117 -0
  66. package/dist/services/oauth-config.service.js.map +1 -0
  67. package/dist/services/oauth-provider-registry.d.ts +77 -0
  68. package/dist/services/oauth-provider-registry.d.ts.map +1 -0
  69. package/dist/services/oauth-provider-registry.js +112 -0
  70. package/dist/services/oauth-provider-registry.js.map +1 -0
  71. package/dist/services/oauth-service.d.ts +77 -0
  72. package/dist/services/oauth-service.d.ts.map +1 -0
  73. package/dist/services/oauth-service.js +348 -0
  74. package/dist/services/oauth-service.js.map +1 -0
  75. package/dist/services/oauth-token-retrieval.service.d.ts +49 -0
  76. package/dist/services/oauth-token-retrieval.service.d.ts.map +1 -0
  77. package/dist/services/oauth-token-retrieval.service.js +150 -0
  78. package/dist/services/oauth-token-retrieval.service.js.map +1 -0
  79. package/dist/services/provider-resolver.d.ts +48 -0
  80. package/dist/services/provider-resolver.d.ts.map +1 -0
  81. package/dist/services/provider-resolver.js +120 -0
  82. package/dist/services/provider-resolver.js.map +1 -0
  83. package/dist/services/provider-validator.d.ts +55 -0
  84. package/dist/services/provider-validator.d.ts.map +1 -0
  85. package/dist/services/provider-validator.js +135 -0
  86. package/dist/services/provider-validator.js.map +1 -0
  87. package/dist/services/tool-context-builder.d.ts +57 -0
  88. package/dist/services/tool-context-builder.d.ts.map +1 -0
  89. package/dist/services/tool-context-builder.js +125 -0
  90. package/dist/services/tool-context-builder.js.map +1 -0
  91. package/dist/services/tool-protection.service.d.ts +87 -10
  92. package/dist/services/tool-protection.service.d.ts.map +1 -1
  93. package/dist/services/tool-protection.service.js +282 -112
  94. package/dist/services/tool-protection.service.js.map +1 -1
  95. package/dist/types/oauth-required-error.d.ts +40 -0
  96. package/dist/types/oauth-required-error.d.ts.map +1 -0
  97. package/dist/types/oauth-required-error.js +40 -0
  98. package/dist/types/oauth-required-error.js.map +1 -0
  99. package/dist/utils/did-helpers.d.ts +33 -0
  100. package/dist/utils/did-helpers.d.ts.map +1 -1
  101. package/dist/utils/did-helpers.js +40 -0
  102. package/dist/utils/did-helpers.js.map +1 -1
  103. package/dist/utils/index.d.ts +1 -0
  104. package/dist/utils/index.d.ts.map +1 -1
  105. package/dist/utils/index.js +1 -0
  106. package/dist/utils/index.js.map +1 -1
  107. package/docs/API_REFERENCE.md +1362 -0
  108. package/docs/COMPLIANCE_MATRIX.md +691 -0
  109. package/docs/STATUSLIST2021_GUIDE.md +696 -0
  110. package/docs/W3C_VC_DELEGATION_GUIDE.md +710 -0
  111. package/package.json +24 -50
  112. package/scripts/audit-compliance.ts +724 -0
  113. package/src/__tests__/cache/tool-protection-cache.test.ts +640 -0
  114. package/src/__tests__/config/provider-runtime-config.test.ts +309 -0
  115. package/src/__tests__/delegation-e2e.test.ts +690 -0
  116. package/src/__tests__/identity/user-did-manager.test.ts +213 -0
  117. package/src/__tests__/index.test.ts +56 -0
  118. package/src/__tests__/integration/full-flow.test.ts +776 -0
  119. package/src/__tests__/integration.test.ts +281 -0
  120. package/src/__tests__/providers/base.test.ts +173 -0
  121. package/src/__tests__/providers/memory.test.ts +319 -0
  122. package/src/__tests__/regression/phase2-regression.test.ts +427 -0
  123. package/src/__tests__/runtime/audit-logger.test.ts +154 -0
  124. package/src/__tests__/runtime/base-extensions.test.ts +593 -0
  125. package/src/__tests__/runtime/base.test.ts +869 -0
  126. package/src/__tests__/runtime/delegation-flow.test.ts +164 -0
  127. package/src/__tests__/runtime/proof-client-did.test.ts +375 -0
  128. package/src/__tests__/runtime/route-interception.test.ts +686 -0
  129. package/src/__tests__/runtime/tool-protection-enforcement.test.ts +908 -0
  130. package/src/__tests__/services/agentshield-integration.test.ts +784 -0
  131. package/src/__tests__/services/provider-resolver-edge-cases.test.ts +487 -0
  132. package/src/__tests__/services/tool-protection-oauth-provider.test.ts +480 -0
  133. package/src/__tests__/services/tool-protection.service.test.ts +1366 -0
  134. package/src/__tests__/utils/mock-providers.ts +340 -0
  135. package/src/cache/oauth-config-cache.d.ts +69 -0
  136. package/src/cache/oauth-config-cache.d.ts.map +1 -0
  137. package/src/cache/oauth-config-cache.js +71 -0
  138. package/src/cache/oauth-config-cache.js.map +1 -0
  139. package/src/cache/oauth-config-cache.ts +123 -0
  140. package/src/cache/tool-protection-cache.ts +171 -0
  141. package/src/compliance/EXAMPLE.md +412 -0
  142. package/src/compliance/__tests__/schema-verifier.test.ts +797 -0
  143. package/src/compliance/index.ts +8 -0
  144. package/src/compliance/schema-registry.ts +460 -0
  145. package/src/compliance/schema-verifier.ts +708 -0
  146. package/src/config/__tests__/remote-config.spec.ts +268 -0
  147. package/src/config/remote-config.ts +174 -0
  148. package/src/config.ts +309 -0
  149. package/src/delegation/__tests__/audience-validator.test.ts +112 -0
  150. package/src/delegation/__tests__/bitstring.test.ts +346 -0
  151. package/src/delegation/__tests__/cascading-revocation.test.ts +628 -0
  152. package/src/delegation/__tests__/delegation-graph.test.ts +584 -0
  153. package/src/delegation/__tests__/utils.test.ts +152 -0
  154. package/src/delegation/__tests__/vc-issuer.test.ts +442 -0
  155. package/src/delegation/__tests__/vc-verifier.test.ts +922 -0
  156. package/src/delegation/audience-validator.ts +52 -0
  157. package/src/delegation/bitstring.ts +278 -0
  158. package/src/delegation/cascading-revocation.ts +370 -0
  159. package/src/delegation/delegation-graph.ts +299 -0
  160. package/src/delegation/index.ts +14 -0
  161. package/src/delegation/statuslist-manager.ts +353 -0
  162. package/src/delegation/storage/__tests__/memory-graph-storage.test.ts +366 -0
  163. package/src/delegation/storage/__tests__/memory-statuslist-storage.test.ts +228 -0
  164. package/src/delegation/storage/index.ts +9 -0
  165. package/src/delegation/storage/memory-graph-storage.ts +178 -0
  166. package/src/delegation/storage/memory-statuslist-storage.ts +77 -0
  167. package/src/delegation/utils.ts +42 -0
  168. package/src/delegation/vc-issuer.ts +232 -0
  169. package/src/delegation/vc-verifier.ts +568 -0
  170. package/src/identity/idp-token-resolver.ts +147 -0
  171. package/src/identity/idp-token-storage.interface.ts +59 -0
  172. package/src/identity/user-did-manager.ts +370 -0
  173. package/src/index.ts +260 -0
  174. package/src/providers/base.d.ts +91 -0
  175. package/src/providers/base.d.ts.map +1 -0
  176. package/src/providers/base.js +38 -0
  177. package/src/providers/base.js.map +1 -0
  178. package/src/providers/base.ts +96 -0
  179. package/src/providers/memory.ts +142 -0
  180. package/src/runtime/audit-logger.ts +39 -0
  181. package/src/runtime/base.ts +1329 -0
  182. package/src/services/__tests__/access-control.integration.test.ts +443 -0
  183. package/src/services/__tests__/access-control.proof-response-validation.test.ts +578 -0
  184. package/src/services/__tests__/access-control.service.test.ts +970 -0
  185. package/src/services/__tests__/batch-delegation.service.test.ts +351 -0
  186. package/src/services/__tests__/crypto.service.test.ts +531 -0
  187. package/src/services/__tests__/oauth-provider-registry.test.ts +142 -0
  188. package/src/services/__tests__/proof-verifier.integration.test.ts +485 -0
  189. package/src/services/__tests__/proof-verifier.test.ts +489 -0
  190. package/src/services/__tests__/provider-resolution.integration.test.ts +198 -0
  191. package/src/services/__tests__/provider-resolver.test.ts +217 -0
  192. package/src/services/__tests__/storage.service.test.ts +358 -0
  193. package/src/services/access-control.service.ts +990 -0
  194. package/src/services/authorization/authorization-registry.ts +66 -0
  195. package/src/services/authorization/types.ts +71 -0
  196. package/src/services/batch-delegation.service.ts +137 -0
  197. package/src/services/crypto.service.ts +302 -0
  198. package/src/services/errors.ts +76 -0
  199. package/src/services/index.ts +9 -0
  200. package/src/services/oauth-config.service.d.ts +53 -0
  201. package/src/services/oauth-config.service.d.ts.map +1 -0
  202. package/src/services/oauth-config.service.js +113 -0
  203. package/src/services/oauth-config.service.js.map +1 -0
  204. package/src/services/oauth-config.service.ts +166 -0
  205. package/src/services/oauth-provider-registry.d.ts +57 -0
  206. package/src/services/oauth-provider-registry.d.ts.map +1 -0
  207. package/src/services/oauth-provider-registry.js +73 -0
  208. package/src/services/oauth-provider-registry.js.map +1 -0
  209. package/src/services/oauth-provider-registry.ts +123 -0
  210. package/src/services/oauth-service.ts +510 -0
  211. package/src/services/oauth-token-retrieval.service.ts +245 -0
  212. package/src/services/proof-verifier.ts +478 -0
  213. package/src/services/provider-resolver.d.ts +48 -0
  214. package/src/services/provider-resolver.d.ts.map +1 -0
  215. package/src/services/provider-resolver.js +106 -0
  216. package/src/services/provider-resolver.js.map +1 -0
  217. package/src/services/provider-resolver.ts +144 -0
  218. package/src/services/provider-validator.ts +170 -0
  219. package/src/services/storage.service.ts +566 -0
  220. package/src/services/tool-context-builder.ts +172 -0
  221. package/src/services/tool-protection.service.ts +958 -0
  222. package/src/types/oauth-required-error.ts +63 -0
  223. package/src/types/tool-protection.ts +155 -0
  224. package/src/utils/__tests__/did-helpers.test.ts +101 -0
  225. package/src/utils/base64.ts +148 -0
  226. package/src/utils/cors.ts +83 -0
  227. package/src/utils/did-helpers.ts +150 -0
  228. package/src/utils/index.ts +8 -0
  229. package/src/utils/storage-keys.ts +278 -0
  230. package/tsconfig.json +21 -0
  231. package/vitest.config.ts +56 -0
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Authorization Registry
3
+ *
4
+ * Registry for managing available authorization services.
5
+ * Allows looking up the appropriate service for a given authorization type.
6
+ *
7
+ * @package @kya-os/mcp-i-core
8
+ */
9
+
10
+ import type { AuthorizationService } from "./types.js";
11
+ import type { ToolProtection, AuthorizationRequirement } from "@kya-os/contracts/tool-protection";
12
+
13
+ export class AuthorizationRegistry {
14
+ private services: Map<string, AuthorizationService> = new Map();
15
+
16
+ /**
17
+ * Register an authorization service
18
+ */
19
+ register(service: AuthorizationService): void {
20
+ this.services.set(service.type, service);
21
+ }
22
+
23
+ /**
24
+ * Get an authorization service by type
25
+ */
26
+ getService(type: string): AuthorizationService | null {
27
+ return this.services.get(type) || null;
28
+ }
29
+
30
+ /**
31
+ * Resolve authorization requirement for a tool
32
+ *
33
+ * Determines the authorization requirement based on the tool protection config.
34
+ * Handles backward compatibility with legacy `oauthProvider` field.
35
+ */
36
+ resolveRequirement(
37
+ toolProtection: ToolProtection
38
+ ): AuthorizationRequirement | null {
39
+ if (!toolProtection.requiresDelegation) {
40
+ return null;
41
+ }
42
+
43
+ // Use the explicit authorization field if present
44
+ if (toolProtection.authorization) {
45
+ return toolProtection.authorization;
46
+ }
47
+
48
+ // Legacy fallback: oauthProvider field
49
+ if (toolProtection.oauthProvider) {
50
+ return {
51
+ type: 'oauth',
52
+ provider: toolProtection.oauthProvider,
53
+ };
54
+ }
55
+
56
+ // If requiresDelegation is true but no auth specified, default to 'none' (consent only)
57
+ // UNLESS we are in a transition period where ProviderResolver might infer scopes.
58
+ // This logic will be refined as we move logic from ProviderResolver to here.
59
+ // For now, return null to let downstream logic handle fallbacks if needed,
60
+ // or return 'none' if we want to enforce explicit config.
61
+
62
+ // Returning null allows the legacy ProviderResolver to attempt scope inference
63
+ return null;
64
+ }
65
+ }
66
+
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Authorization Service Types
3
+ *
4
+ * Shared types for authorization services and flows.
5
+ *
6
+ * @package @kya-os/mcp-i-core
7
+ */
8
+
9
+ import type { ToolProtection } from "@kya-os/contracts/tool-protection";
10
+
11
+ /**
12
+ * Authorization Flow Result
13
+ */
14
+ export interface AuthorizationResult {
15
+ success: boolean;
16
+ credential?: unknown; // VC or token
17
+ userDid?: string;
18
+ metadata?: Record<string, unknown>;
19
+ error?: Error;
20
+ }
21
+
22
+ /**
23
+ * Authorization Flow
24
+ * Represents an initiated authorization flow
25
+ */
26
+ export interface AuthorizationFlow {
27
+ /** URL to redirect the user to */
28
+ url: string;
29
+
30
+ /** Unique identifier for this flow */
31
+ flowId?: string;
32
+
33
+ /** Metadata about the flow */
34
+ metadata?: Record<string, unknown>;
35
+ }
36
+
37
+ /**
38
+ * Authorization Service Interface
39
+ * Each authorization type implements this
40
+ */
41
+ export interface AuthorizationService {
42
+ /** Unique type identifier (e.g., 'oauth', 'mdl', 'idv') */
43
+ type: string;
44
+
45
+ /**
46
+ * Check if authorization is required for the given tool protection
47
+ */
48
+ isRequired(toolProtection: ToolProtection): boolean;
49
+
50
+ /**
51
+ * Initiate authorization flow
52
+ * Returns URL or flow identifier
53
+ */
54
+ initiateFlow(
55
+ toolProtection: ToolProtection,
56
+ sessionId: string,
57
+ projectId: string,
58
+ agentDid: string,
59
+ serverUrl: string
60
+ ): Promise<AuthorizationFlow>;
61
+
62
+ /**
63
+ * Verify authorization result
64
+ * Called after user completes flow
65
+ */
66
+ verifyAuthorization(
67
+ flowId: string,
68
+ result: unknown
69
+ ): Promise<AuthorizationResult>;
70
+ }
71
+
@@ -0,0 +1,137 @@
1
+ /**
2
+ * Batch Delegation Service
3
+ *
4
+ * Groups tools by OAuth provider for batch delegation flows.
5
+ * Enables single OAuth flow for multiple tools requiring the same provider.
6
+ *
7
+ * @package @kya-os/mcp-i-core
8
+ */
9
+
10
+ import type { ToolProtection } from "@kya-os/contracts/tool-protection";
11
+ import type { ProviderResolver } from "./provider-resolver.js";
12
+ import type { ToolProtectionService } from "./tool-protection.service.js";
13
+
14
+ /**
15
+ * Tool group by provider
16
+ */
17
+ export interface ToolGroup {
18
+ /** OAuth provider name */
19
+ provider: string;
20
+
21
+ /** Tool names in this group */
22
+ tools: string[];
23
+
24
+ /** Merged scopes from all tools in group */
25
+ scopes: string[];
26
+
27
+ /** Highest risk level in group */
28
+ riskLevel: "low" | "medium" | "high";
29
+ }
30
+
31
+ /**
32
+ * Service for grouping tools by OAuth provider
33
+ */
34
+ export class BatchDelegationService {
35
+ constructor(
36
+ private providerResolver: ProviderResolver,
37
+ private toolProtectionService: ToolProtectionService
38
+ ) {}
39
+
40
+ /**
41
+ * Group tools by OAuth provider
42
+ *
43
+ * Returns a map of provider → tools that require that provider.
44
+ * Tools that don't require delegation are skipped.
45
+ *
46
+ * @param toolNames - Array of tool names to group
47
+ * @param projectId - Project ID for provider resolution
48
+ * @param agentDid - Agent DID for fetching tool protections
49
+ * @returns Map of provider name to ToolGroup
50
+ */
51
+ async groupToolsByProvider(
52
+ toolNames: string[],
53
+ projectId: string,
54
+ agentDid: string
55
+ ): Promise<Map<string, ToolGroup>> {
56
+ const groups = new Map<string, ToolGroup>();
57
+
58
+ for (const toolName of toolNames) {
59
+ const protection = await this.toolProtectionService.checkToolProtection(
60
+ toolName,
61
+ agentDid
62
+ );
63
+
64
+ if (!protection?.requiresDelegation) {
65
+ continue; // Skip tools that don't require delegation
66
+ }
67
+
68
+ // Resolve provider for this tool
69
+ let provider: string;
70
+ try {
71
+ provider = await this.providerResolver.resolveProvider(
72
+ protection,
73
+ projectId
74
+ );
75
+ } catch (error) {
76
+ console.warn(
77
+ `[BatchDelegation] Could not resolve provider for tool "${toolName}", skipping`,
78
+ error instanceof Error ? error.message : String(error)
79
+ );
80
+ continue;
81
+ }
82
+
83
+ // Get or create group for this provider
84
+ if (!groups.has(provider)) {
85
+ groups.set(provider, {
86
+ provider,
87
+ tools: [],
88
+ scopes: [],
89
+ riskLevel: "medium", // Default
90
+ });
91
+ }
92
+
93
+ const group = groups.get(provider)!;
94
+ group.tools.push(toolName);
95
+
96
+ // Merge scopes (deduplicate)
97
+ const allScopes = new Set([
98
+ ...group.scopes,
99
+ ...(protection.requiredScopes || []),
100
+ ]);
101
+ group.scopes = Array.from(allScopes);
102
+
103
+ // Use highest risk level
104
+ if (protection.riskLevel) {
105
+ const riskOrder: Record<string, number> = { low: 1, medium: 2, high: 3, critical: 4 };
106
+ const currentRisk = riskOrder[group.riskLevel] || 1;
107
+ const toolRisk = riskOrder[protection.riskLevel] || 1;
108
+ if (toolRisk > currentRisk) {
109
+ // Map critical to high for ToolGroup interface
110
+ group.riskLevel = protection.riskLevel === "critical" ? "high" : protection.riskLevel;
111
+ }
112
+ }
113
+ }
114
+
115
+ return groups;
116
+ }
117
+
118
+ /**
119
+ * Get all tools that require a specific provider
120
+ *
121
+ * @param provider - Provider name to filter by
122
+ * @param projectId - Project ID for provider resolution
123
+ * @param agentDid - Agent DID for fetching tool protections
124
+ * @returns Array of tool names requiring the specified provider
125
+ */
126
+ async getToolsForProvider(
127
+ provider: string,
128
+ projectId: string,
129
+ agentDid: string
130
+ ): Promise<string[]> {
131
+ // This would require fetching all tool protections
132
+ // For now, return empty array (can be enhanced later)
133
+ // TODO: Implement if needed for Phase 4 batch delegation UI
134
+ return [];
135
+ }
136
+ }
137
+
@@ -0,0 +1,302 @@
1
+ /**
2
+ * CryptoService
3
+ *
4
+ * Centralized cryptographic operations service that provides consistent
5
+ * signature verification across all platforms (Cloudflare, Node.js, etc.).
6
+ *
7
+ * This service eliminates code duplication and ensures cryptographic operations
8
+ * behave identically everywhere.
9
+ */
10
+
11
+ import { CryptoProvider } from "../providers/base.js";
12
+ import {
13
+ base64urlDecodeToString,
14
+ base64urlDecodeToBytes,
15
+ base64urlEncodeFromBytes,
16
+ bytesToBase64,
17
+ base64ToBytes,
18
+ } from "../utils/base64.js";
19
+
20
+ /**
21
+ * Minimal JWK interface to avoid external dependencies
22
+ */
23
+ export interface Ed25519JWK {
24
+ kty: "OKP";
25
+ crv: "Ed25519";
26
+ x: string; // Base64url encoded public key
27
+ kid?: string;
28
+ use?: string;
29
+ }
30
+
31
+ /**
32
+ * JWS parsing result
33
+ */
34
+ export interface ParsedJWS {
35
+ header: Record<string, unknown>;
36
+ payload?: Record<string, unknown>;
37
+ signatureBytes: Uint8Array;
38
+ signingInput: string;
39
+ }
40
+
41
+ export class CryptoService {
42
+ constructor(private cryptoProvider: CryptoProvider) {}
43
+
44
+ /**
45
+ * Verify raw Ed25519 signature
46
+ * @param data - Data that was signed
47
+ * @param signature - Signature bytes
48
+ * @param publicKey - Base64 encoded Ed25519 public key (32 bytes)
49
+ */
50
+ async verifyEd25519(
51
+ data: Uint8Array,
52
+ signature: Uint8Array,
53
+ publicKey: string
54
+ ): Promise<boolean> {
55
+ try {
56
+ const result = await this.cryptoProvider.verify(
57
+ data,
58
+ signature,
59
+ publicKey
60
+ );
61
+ // Ensure we always return a boolean (handle undefined from unmocked providers)
62
+ return result === true;
63
+ } catch (error) {
64
+ // Log error for debugging but return false for invalid signatures
65
+ console.error("[CryptoService] Ed25519 verification error:", error);
66
+ return false;
67
+ }
68
+ }
69
+
70
+ /**
71
+ * Parse JWS into components
72
+ * @param jws - Full compact JWS string (header.payload.signature)
73
+ * @returns Parsed JWS components
74
+ */
75
+ parseJWS(jws: string): ParsedJWS {
76
+ const parts = jws.split(".");
77
+ if (parts.length !== 3) {
78
+ throw new Error("Invalid JWS format: expected header.payload.signature");
79
+ }
80
+
81
+ const [headerB64, payloadB64, signatureB64] = parts;
82
+
83
+ // Decode header
84
+ let header: Record<string, unknown>;
85
+ try {
86
+ header = JSON.parse(base64urlDecodeToString(headerB64)) as Record<
87
+ string,
88
+ unknown
89
+ >;
90
+ } catch (error) {
91
+ throw new Error(
92
+ `Invalid header base64: ${error instanceof Error ? error.message : String(error)}`
93
+ );
94
+ }
95
+
96
+ // Decode payload (optional, may be detached)
97
+ let payload: Record<string, unknown> | undefined;
98
+ if (payloadB64) {
99
+ try {
100
+ payload = JSON.parse(base64urlDecodeToString(payloadB64)) as Record<
101
+ string,
102
+ unknown
103
+ >;
104
+ } catch (error) {
105
+ // Payload decoding failed - this is an error for non-detached JWS
106
+ // Re-throw to let caller handle it (they can check if it's detached format)
107
+ throw new Error(
108
+ `Invalid payload base64: ${error instanceof Error ? error.message : String(error)}`
109
+ );
110
+ }
111
+ }
112
+
113
+ // Decode signature bytes
114
+ let signatureBytes: Uint8Array;
115
+ try {
116
+ signatureBytes = base64urlDecodeToBytes(signatureB64);
117
+ } catch (error) {
118
+ // Invalid signature base64 - this is a fatal error
119
+ throw new Error(
120
+ `Invalid signature base64: ${error instanceof Error ? error.message : String(error)}`
121
+ );
122
+ }
123
+
124
+ // Create signing input (header.payload)
125
+ const signingInput = `${headerB64}.${payloadB64}`;
126
+
127
+ return {
128
+ header,
129
+ payload,
130
+ signatureBytes,
131
+ signingInput,
132
+ };
133
+ }
134
+
135
+ /**
136
+ * Verify JWS signature (full compact format: header.payload.signature)
137
+ * @param jws - Full compact JWS string (or detached format: header..signature)
138
+ * @param publicKeyJwk - Ed25519 public key in JWK format
139
+ * @param options - Verification options
140
+ * @param options.detachedPayload - Optional detached payload (Uint8Array or string) for detached JWS format
141
+ * @param options.expectedKid - Optional expected key ID to validate
142
+ * @param options.alg - Optional expected algorithm (defaults to 'EdDSA')
143
+ */
144
+ async verifyJWS(
145
+ jws: string,
146
+ publicKeyJwk: Ed25519JWK,
147
+ options?: {
148
+ detachedPayload?: Uint8Array | string;
149
+ expectedKid?: string;
150
+ alg?: "EdDSA";
151
+ }
152
+ ): Promise<boolean> {
153
+ try {
154
+ // Validate JWK format
155
+ if (!this.isValidEd25519JWK(publicKeyJwk)) {
156
+ console.error("[CryptoService] Invalid Ed25519 JWK format");
157
+ return false;
158
+ }
159
+
160
+ // Validate expected kid if provided
161
+ if (options?.expectedKid && publicKeyJwk.kid !== options.expectedKid) {
162
+ console.error("[CryptoService] Key ID mismatch");
163
+ return false;
164
+ }
165
+
166
+ // Parse JWS components - handle malformed JWS gracefully
167
+ let parsed: ParsedJWS;
168
+ try {
169
+ parsed = this.parseJWS(jws);
170
+ } catch (error) {
171
+ // Malformed JWS - check if it's detached format with provided payload
172
+ if (options?.detachedPayload !== undefined) {
173
+ const parts = jws.split(".");
174
+ if (parts.length === 3 && parts[1] === "") {
175
+ // Detached format: header..signature
176
+ try {
177
+ const headerB64 = parts[0];
178
+ const signatureB64 = parts[2];
179
+ const header = JSON.parse(
180
+ base64urlDecodeToString(headerB64)
181
+ ) as Record<string, unknown>;
182
+ const signatureBytes = base64urlDecodeToBytes(signatureB64);
183
+
184
+ parsed = {
185
+ header,
186
+ payload: undefined,
187
+ signatureBytes,
188
+ signingInput: "", // Will be reconstructed below
189
+ };
190
+ } catch {
191
+ console.error("[CryptoService] Invalid detached JWS format");
192
+ return false;
193
+ }
194
+ } else {
195
+ console.error("[CryptoService] Invalid JWS format:", error);
196
+ return false;
197
+ }
198
+ } else {
199
+ console.error("[CryptoService] Invalid JWS format:", error);
200
+ return false;
201
+ }
202
+ }
203
+
204
+ // Validate algorithm
205
+ const expectedAlg = options?.alg || "EdDSA";
206
+ if (parsed.header.alg !== expectedAlg) {
207
+ console.error(
208
+ `[CryptoService] Unsupported algorithm: ${parsed.header.alg}, expected ${expectedAlg}`
209
+ );
210
+ return false;
211
+ }
212
+
213
+ // Handle detached payload if provided
214
+ let signingInput: string;
215
+ let signingInputBytes: Uint8Array;
216
+
217
+ if (options?.detachedPayload !== undefined) {
218
+ // Detached format: reconstruct signing input from header + detached payload
219
+ const headerB64 = jws.split(".")[0];
220
+ let payloadB64: string;
221
+
222
+ if (options.detachedPayload instanceof Uint8Array) {
223
+ // Uint8Array payload
224
+ payloadB64 = base64urlEncodeFromBytes(options.detachedPayload);
225
+ } else {
226
+ // String payload (backward compatibility)
227
+ payloadB64 = base64urlEncodeFromBytes(
228
+ new TextEncoder().encode(options.detachedPayload)
229
+ );
230
+ }
231
+
232
+ signingInput = `${headerB64}.${payloadB64}`;
233
+ signingInputBytes = new TextEncoder().encode(signingInput);
234
+ } else {
235
+ // Full compact format: use parsed signing input
236
+ if (!parsed.signingInput) {
237
+ console.error(
238
+ "[CryptoService] Missing signing input for compact JWS"
239
+ );
240
+ return false;
241
+ }
242
+ signingInput = parsed.signingInput;
243
+ signingInputBytes = new TextEncoder().encode(signingInput);
244
+ }
245
+
246
+ // Extract raw public key from JWK
247
+ let publicKeyBase64: string;
248
+ try {
249
+ publicKeyBase64 = this.jwkToBase64PublicKey(publicKeyJwk);
250
+ } catch (error) {
251
+ console.error("[CryptoService] Failed to extract public key:", error);
252
+ return false;
253
+ }
254
+
255
+ // Verify signature
256
+ return await this.verifyEd25519(
257
+ signingInputBytes,
258
+ parsed.signatureBytes,
259
+ publicKeyBase64
260
+ );
261
+ } catch (error) {
262
+ // Security-safe failure: never throw, always return false
263
+ console.error("[CryptoService] JWS verification error:", error);
264
+ return false;
265
+ }
266
+ }
267
+
268
+ /**
269
+ * Validate Ed25519 JWK format
270
+ */
271
+ private isValidEd25519JWK(jwk: unknown): jwk is Ed25519JWK {
272
+ return (
273
+ typeof jwk === "object" &&
274
+ jwk !== null &&
275
+ "kty" in jwk &&
276
+ jwk.kty === "OKP" &&
277
+ "crv" in jwk &&
278
+ jwk.crv === "Ed25519" &&
279
+ "x" in jwk &&
280
+ typeof jwk.x === "string" &&
281
+ jwk.x.length > 0
282
+ );
283
+ }
284
+
285
+ /**
286
+ * Convert Ed25519 JWK to base64 encoded public key
287
+ */
288
+ private jwkToBase64PublicKey(jwk: Ed25519JWK): string {
289
+ // The 'x' field contains the base64url encoded public key
290
+ // Convert from base64url to standard base64
291
+ const publicKeyBytes = base64urlDecodeToBytes(jwk.x);
292
+
293
+ // Verify key length (Ed25519 public keys are 32 bytes)
294
+ if (publicKeyBytes.length !== 32) {
295
+ throw new Error(
296
+ `Invalid Ed25519 public key length: ${publicKeyBytes.length}`
297
+ );
298
+ }
299
+
300
+ return bytesToBase64(publicKeyBytes);
301
+ }
302
+ }
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Proof Verification Error Codes and Types
3
+ *
4
+ * Specific error codes for proof verification failures to enable
5
+ * better error handling and debugging.
6
+ */
7
+
8
+ /**
9
+ * Error codes for proof verification
10
+ */
11
+ export const PROOF_VERIFICATION_ERROR_CODES = {
12
+ // Proof structure errors
13
+ INVALID_PROOF_STRUCTURE: "INVALID_PROOF_STRUCTURE",
14
+ MISSING_REQUIRED_FIELD: "MISSING_REQUIRED_FIELD",
15
+
16
+ // Security errors
17
+ NONCE_REPLAY_DETECTED: "NONCE_REPLAY_DETECTED",
18
+ TIMESTAMP_SKEW_EXCEEDED: "TIMESTAMP_SKEW_EXCEEDED",
19
+ TIMESTAMP_INVALID: "TIMESTAMP_INVALID",
20
+
21
+ // Signature errors
22
+ INVALID_JWS_SIGNATURE: "INVALID_JWS_SIGNATURE",
23
+ INVALID_JWS_FORMAT: "INVALID_JWS_FORMAT",
24
+ INVALID_JWS_HEADER: "INVALID_JWS_HEADER",
25
+ INVALID_JWS_PAYLOAD: "INVALID_JWS_PAYLOAD",
26
+ INVALID_JWS_SIGNATURE_BASE64: "INVALID_JWS_SIGNATURE_BASE64",
27
+ UNSUPPORTED_ALGORITHM: "UNSUPPORTED_ALGORITHM",
28
+
29
+ // JWK errors
30
+ INVALID_JWK_FORMAT: "INVALID_JWK_FORMAT",
31
+ INVALID_JWK_KTY: "INVALID_JWK_KTY",
32
+ INVALID_JWK_CRV: "INVALID_JWK_CRV",
33
+ INVALID_JWK_X_FIELD: "INVALID_JWK_X_FIELD",
34
+ INVALID_JWK_KEY_LENGTH: "INVALID_JWK_KEY_LENGTH",
35
+ JWK_KID_MISMATCH: "JWK_KID_MISMATCH",
36
+
37
+ // DID resolution errors
38
+ DID_RESOLUTION_FAILED: "DID_RESOLUTION_FAILED",
39
+ DID_DOCUMENT_NOT_FOUND: "DID_DOCUMENT_NOT_FOUND",
40
+ VERIFICATION_METHOD_NOT_FOUND: "VERIFICATION_METHOD_NOT_FOUND",
41
+ PUBLIC_KEY_NOT_FOUND: "PUBLIC_KEY_NOT_FOUND",
42
+ UNSUPPORTED_DID_METHOD: "UNSUPPORTED_DID_METHOD",
43
+
44
+ // Generic errors
45
+ VERIFICATION_ERROR: "VERIFICATION_ERROR",
46
+ INTERNAL_ERROR: "INTERNAL_ERROR",
47
+ } as const;
48
+
49
+ export type ProofVerificationErrorCode =
50
+ typeof PROOF_VERIFICATION_ERROR_CODES[keyof typeof PROOF_VERIFICATION_ERROR_CODES];
51
+
52
+ /**
53
+ * Proof verification error with specific error code
54
+ */
55
+ export class ProofVerificationError extends Error {
56
+ constructor(
57
+ public readonly code: ProofVerificationErrorCode,
58
+ message: string,
59
+ public readonly details?: Record<string, unknown>
60
+ ) {
61
+ super(message);
62
+ this.name = "ProofVerificationError";
63
+ }
64
+ }
65
+
66
+ /**
67
+ * Create a proof verification error
68
+ */
69
+ export function createProofVerificationError(
70
+ code: ProofVerificationErrorCode,
71
+ message: string,
72
+ details?: Record<string, unknown>
73
+ ): ProofVerificationError {
74
+ return new ProofVerificationError(code, message, details);
75
+ }
76
+
@@ -0,0 +1,9 @@
1
+ export { CryptoService } from './crypto.service.js';
2
+ export type { Ed25519JWK, ParsedJWS } from './crypto.service.js';
3
+
4
+ export { AccessControlApiService } from './access-control.service.js';
5
+ export type {
6
+ AccessControlApiServiceConfig,
7
+ AccessControlApiServiceMetrics,
8
+ } from './access-control.service.js';
9
+