@tideorg/mcp 1.4.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.
- package/GAP_REGISTER.md +117 -0
- package/README.md +38 -0
- package/adapters/AGENTS.md +241 -0
- package/adapters/CLAUDE.md +146 -0
- package/adapters/replit.md +224 -0
- package/canon/anti-patterns.md +2133 -0
- package/canon/concepts.md +816 -0
- package/canon/custom-contracts.md +533 -0
- package/canon/delegation.md +195 -0
- package/canon/feature-mapping.md +637 -0
- package/canon/framework-matrix.md +1125 -0
- package/canon/invariants.md +851 -0
- package/canon/redirect-handler.md +254 -0
- package/canon/tidecloak-bootstrap.md +287 -0
- package/canon/tidecloak-endpoints.md +294 -0
- package/canon/troubleshooting.md +1494 -0
- package/canon/version-policy.md +89 -0
- package/mcp-server/dist/index.d.ts +2 -0
- package/mcp-server/dist/index.js +605 -0
- package/package.json +45 -0
- package/playbooks/add-auth-nextjs-existing.md +799 -0
- package/playbooks/add-auth-nextjs-fresh.md +654 -0
- package/playbooks/add-rbac-nextjs.md +190 -0
- package/playbooks/bootstrap-realm-from-template.md +170 -0
- package/playbooks/configure-e2ee-roles-and-policies.md +196 -0
- package/playbooks/deploy-tidecloak-docker.md +792 -0
- package/playbooks/diagnose-broken-login.md +234 -0
- package/playbooks/diagnose-missing-roles-or-claims.md +253 -0
- package/playbooks/initialize-admin-and-link-account.md +235 -0
- package/playbooks/migrate-from-existing-auth.md +198 -0
- package/playbooks/protect-api-nextjs.md +451 -0
- package/playbooks/protect-routes-nextjs.md +544 -0
- package/playbooks/setup-forseti-e2ee.md +756 -0
- package/playbooks/setup-iga-admin-panel.md +344 -0
- package/playbooks/setup-server-delegation.md +142 -0
- package/playbooks/start-tidecloak-dev.md +130 -0
- package/playbooks/verify-jwt-server-side.md +439 -0
- package/prompts/add-admin-approval-flow.md +50 -0
- package/prompts/build-private-customer-portal.md +85 -0
- package/prompts/migrate-generic-auth-to-tide.md +67 -0
- package/prompts/secure-existing-app.md +80 -0
- package/reference-apps/INDEX.md +87 -0
- package/reference-apps/encrypted-communication/anti-patterns.md +123 -0
- package/reference-apps/encrypted-communication/bootstrap-sequence.md +152 -0
- package/reference-apps/encrypted-communication/manifest.yaml +100 -0
- package/reference-apps/encrypted-communication/role-policy-matrix.md +80 -0
- package/reference-apps/encrypted-communication/scenario.md +134 -0
- package/reference-apps/git-pr-signing-service/anti-patterns.md +61 -0
- package/reference-apps/git-pr-signing-service/bootstrap-sequence.md +157 -0
- package/reference-apps/git-pr-signing-service/manifest.yaml +80 -0
- package/reference-apps/git-pr-signing-service/role-policy-matrix.md +99 -0
- package/reference-apps/git-pr-signing-service/scenario.md +169 -0
- package/reference-apps/iga-admin-governance/anti-patterns.md +133 -0
- package/reference-apps/iga-admin-governance/bootstrap-sequence.md +153 -0
- package/reference-apps/iga-admin-governance/manifest.yaml +87 -0
- package/reference-apps/iga-admin-governance/role-policy-matrix.md +67 -0
- package/reference-apps/iga-admin-governance/scenario.md +126 -0
- package/reference-apps/organisation-password-manager/anti-patterns.md +71 -0
- package/reference-apps/organisation-password-manager/bootstrap-sequence.md +127 -0
- package/reference-apps/organisation-password-manager/manifest.yaml +86 -0
- package/reference-apps/organisation-password-manager/role-policy-matrix.md +59 -0
- package/reference-apps/organisation-password-manager/scenario.md +107 -0
- package/reference-apps/policy-governed-signing/anti-patterns.md +67 -0
- package/reference-apps/policy-governed-signing/bootstrap-sequence.md +128 -0
- package/reference-apps/policy-governed-signing/manifest.yaml +78 -0
- package/reference-apps/policy-governed-signing/role-policy-matrix.md +62 -0
- package/reference-apps/policy-governed-signing/scenario.md +113 -0
- package/skills/tide-diagnostics/SKILL.md +99 -0
- package/skills/tide-integration/SKILL.md +136 -0
- package/skills/tide-learning-capture/SKILL.md +174 -0
- package/skills/tide-rbac-and-e2ee/SKILL.md +214 -0
- package/skills/tide-reviewer/SKILL.md +133 -0
- package/skills/tide-route-and-api-protection/SKILL.md +201 -0
- package/skills/tide-scenario-resolver/SKILL.md +81 -0
- package/skills/tide-setup/SKILL.md +218 -0
- package/skills/tide-solutions-architect/SKILL.md +130 -0
|
@@ -0,0 +1,816 @@
|
|
|
1
|
+
# Tide Core Concepts
|
|
2
|
+
|
|
3
|
+
Agent-operable definitions of Tide's cryptographic model and architecture.
|
|
4
|
+
|
|
5
|
+
**Rule**: Official Tide documentation uses marketing-heavy terminology. This file uses agent-facing names with official terms in parentheses.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Never-Whole-Key Model (Ineffable Cryptography)
|
|
10
|
+
|
|
11
|
+
**What it is**: Cryptographic keys never exist in complete form at any point in their lifecycle. Keys are born fragmented, used blind, and destroyed atomically. **VERIFIED** (whitepaper tier1-article2, Architecture.md)
|
|
12
|
+
|
|
13
|
+
**Why it matters**: No single point of compromise can extract a complete key. No admin, server, ORK node, or attacker can forge tokens or decrypt data alone.
|
|
14
|
+
|
|
15
|
+
**How it works**:
|
|
16
|
+
- Keys generated via Distributed Key Generation (DKG) across ORK nodes using Nested Shamir Secret Sharing
|
|
17
|
+
- Each ORK holds one shard; threshold T ORKs must cooperate for any operation
|
|
18
|
+
- Shards never leave their ORK; operations use threshold signatures and blind protocols
|
|
19
|
+
- Only exception: Ragnarok (Fabric offboarding) deliberately reconstructs keys locally
|
|
20
|
+
|
|
21
|
+
**Agent implication**: Do not implement local key generation, storage, or backup. All cryptographic operations delegate to Fabric via TideCloak.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Fabric (Cybersecurity Fabric)
|
|
26
|
+
|
|
27
|
+
**What it is**: Decentralized network of ORK nodes that collectively perform cryptographic operations. **VERIFIED** (intro.md, Architecture.md)
|
|
28
|
+
|
|
29
|
+
**Official name**: "Cybersecurity Fabric" is marketing. Use "Fabric" or "ORK network" in code and docs.
|
|
30
|
+
|
|
31
|
+
**Deployment configurations**:
|
|
32
|
+
- Mainnet: 20 ORKs, T=14 threshold **VERIFIED** (whitepaper tier1-article2)
|
|
33
|
+
- Test: 5 ORKs, T=3 threshold **VERIFIED** (keylessh `start.sh`)
|
|
34
|
+
- Threshold is configurable via `TIDE_VENDOR_THRESHOLD_SIGNING` / `TIDE_VENDOR_THRESHOLD_TOTAL` env vars **VERIFIED** (keylessh `start.sh`)
|
|
35
|
+
|
|
36
|
+
**Security assumption**: System remains secure if fewer than 30% of ORKs are compromised (honest minority model). On mainnet: safe if <7 of 20 ORKs compromised. **VERIFIED** (Threat-model.md)
|
|
37
|
+
|
|
38
|
+
**Agent implication**: Never hardcode 14/20. Treat threshold as deployment-variable.
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## ORK (Orchestrated Recluders of Keys)
|
|
43
|
+
|
|
44
|
+
**What it is**: Individual nodes in the Fabric. Each holds one shard of distributed keys. **VERIFIED** (tier1-article2)
|
|
45
|
+
|
|
46
|
+
**Official name**: "Orchestrated Recluders of Keys" is a backronym. Use "ORK" or "ORK node".
|
|
47
|
+
|
|
48
|
+
**Responsibilities**:
|
|
49
|
+
- Store one key shard per distributed key
|
|
50
|
+
- Perform threshold cryptographic operations (signing, decryption, PRISM verification)
|
|
51
|
+
- Independently verify claims before partial-signing
|
|
52
|
+
- Execute Forseti policy contracts
|
|
53
|
+
- Coordinate via TWELVE MAP directory service
|
|
54
|
+
|
|
55
|
+
**Agent implication**: ORK endpoints are discovered via adapter JSON (`homeOrkUrl`) or TWELVE MAP, not hardcoded.
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## Threshold Password Authentication (BYOiD)
|
|
60
|
+
|
|
61
|
+
**What it is**: Authentication model where user password is verified via threshold protocol without any party learning the password. No password hashes stored anywhere. **VERIFIED** (byoid.md, tier2-protocol-prism.mdx)
|
|
62
|
+
|
|
63
|
+
**Official name**: "Bring Your Own Identity (BYOiD)" sounds like federated login. It is not. It is threshold password authentication using PRISM (Threshold Oblivious Pseudorandom Function).
|
|
64
|
+
|
|
65
|
+
**How it works**:
|
|
66
|
+
1. User enters password in browser
|
|
67
|
+
2. SWE coordinates PRISM protocol across T+ ORKs
|
|
68
|
+
3. Each ORK applies its CMK shard to the password challenge
|
|
69
|
+
4. Browser assembles partial results into authentication proof
|
|
70
|
+
5. No ORK, server, or attacker learns the password
|
|
71
|
+
|
|
72
|
+
**Agent implication**: Standard login flows (`doLogin()`, `useTideCloak()`) work transparently. Do not implement custom password verification. PRISM is SDK-internal.
|
|
73
|
+
|
|
74
|
+
**Common confusion**: BYOiD is NOT OAuth2/OIDC federated login. It uses OIDC protocol framing but authentication is threshold-verified, not server-checked.
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## Threshold JWT Signing (VVK)
|
|
79
|
+
|
|
80
|
+
**What it is**: JWT tokens are signed by threshold protocol across ORKs, not by TideCloak server. Each VVK ORK independently verifies all claims before partial-signing. **VERIFIED** (tier2-protocol-vvk-jwt-signing.mdx)
|
|
81
|
+
|
|
82
|
+
**How it works**:
|
|
83
|
+
1. TideCloak requests JWT signing from Fabric
|
|
84
|
+
2. Each VVK ORK performs 12-gate verification chain independently **INFERRED** (whitepaper describes gates; specific gate list not in SDK docs - GAP-025)
|
|
85
|
+
3. If all gates pass, ORK produces partial signature
|
|
86
|
+
4. T+ partial signatures combine into final JWT
|
|
87
|
+
5. If any ORK rejects a claim, signing fails
|
|
88
|
+
|
|
89
|
+
**Agent implication**:
|
|
90
|
+
- JWT verification uses embedded JWKS from adapter JSON only. Do not use `createRemoteJWKSet`. **VERIFIED** (keylessh `tideJWT.ts`)
|
|
91
|
+
- Adapter JSON field `jwk: { keys: JWK[] }` contains public keys for local verification **VERIFIED** (keylessh `tidecloakConfig.ts`)
|
|
92
|
+
- Server-side verification pattern: extract JWT → verify signature against adapter JWKS → check `iss`, `azp`, `exp`, `iat` → validate roles **VERIFIED** (keylessh `tideJWT.ts`)
|
|
93
|
+
|
|
94
|
+
**Common confusion**: This is NOT standard Keycloak JWT signing. TideCloak delegates signing to Fabric. Do not fetch JWKS from the remote `{realm}/protocol/openid-connect/certs` endpoint. Use only the embedded `jwk` from `tidecloak.json`. See I-04.
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## IGA (Identity Governance & Administration)
|
|
99
|
+
|
|
100
|
+
**What it is**: Quorum-enforced governance layer. Admin changes to roles, users, clients require multi-admin approval sealed by VVK threshold signatures. **VERIFIED** (IGA.mdx, SetupIGA.md, keylessh `init-tidecloak.sh`)
|
|
101
|
+
|
|
102
|
+
**Official docs weakness**: Reads as "approval workflow" without emphasis on threshold cryptography. IGA approval is cryptographic enforcement, not procedural.
|
|
103
|
+
|
|
104
|
+
**How it works**:
|
|
105
|
+
1. Admin proposes change (create user, modify role, etc.) → draft created automatically for 18 specific admin API actions **VERIFIED** (vendor confirmation, GAP-041 resolved). Not every mutating endpoint creates a draft — many operations (updating user attributes, updating realm settings, creating groups, managing identity providers) execute immediately. Master realm and IGA-disabled realms are always exempt.
|
|
106
|
+
2. Change-set enters approval queue with `draftRecordId`, `changeSetType`, `actionType`
|
|
107
|
+
3. Required quorum of admins approve: `max(1, floor(TotalAdmins * 0.7))` **VERIFIED** (SetupIGA.md)
|
|
108
|
+
4. Approved change is sealed by VVK threshold signatures (Authorization Proofing)
|
|
109
|
+
5. Change is committed to realm
|
|
110
|
+
6. VVK ORKs verify future JWT claims against these proofs
|
|
111
|
+
|
|
112
|
+
**Change-set lifecycle** **VERIFIED** (CHANGE_REQUEST_API.md):
|
|
113
|
+
```
|
|
114
|
+
DRAFT → PENDING → APPROVED → ACTIVE
|
|
115
|
+
→ DENIED
|
|
116
|
+
(cancel at any stage)
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
**Change-set API endpoints** **VERIFIED** (CHANGE_REQUEST_API.md, all exemplar init scripts):
|
|
120
|
+
```
|
|
121
|
+
GET /admin/realms/{realm}/tide-admin/change-set/counts → lightweight counts
|
|
122
|
+
GET /admin/realms/{realm}/tide-admin/change-set/all/requests → all change requests
|
|
123
|
+
GET /admin/realms/{realm}/tide-admin/change-set/{type}/requests → by type (users, roles, clients, groups)
|
|
124
|
+
POST /admin/realms/{realm}/tide-admin/change-set/sign/batch → approve (sign)
|
|
125
|
+
POST /admin/realms/{realm}/tide-admin/change-set/commit/batch → commit approved changes
|
|
126
|
+
POST /admin/realms/{realm}/tide-admin/change-set/cancel/batch → cancel pending changes
|
|
127
|
+
POST /admin/realms/{realm}/tideAdminResources/add-review → submit enclave-signed review (multipart)
|
|
128
|
+
POST /admin/realms/{realm}/tideAdminResources/add-rejection → reject (multipart/form-data, NOT JSON)
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
**Signing modes** **VERIFIED** (CHANGE_REQUEST_API.md):
|
|
132
|
+
- **FirstAdmin**: Signs immediately with VRK. `requiresApprovalPopup: false`. Used during initial setup before multi-admin is configured.
|
|
133
|
+
- **MultiAdmin**: Returns enclave challenge. `requiresApprovalPopup: true`. Client must decode `changeSetDraftRequests` (base64), present to enclave for admin to sign, then submit via `add-review`.
|
|
134
|
+
|
|
135
|
+
**Policy resolution**: When signing, the system uses the `tide-realm-admin` role's policy by default. Pass `policyRoleId` in the sign request to use a role-specific policy instead. **VERIFIED** (CHANGE_REQUEST_API.md)
|
|
136
|
+
|
|
137
|
+
**Note**: The SSH policy `threshold` (per-role approval count for Forseti policy signing) is distinct from the IGA admin quorum (`max(1, floor(N*0.7))`). These are independent systems.
|
|
138
|
+
|
|
139
|
+
**Agent implication**:
|
|
140
|
+
- Enable IGA via `POST /admin/realms/{realm}/tide-admin/toggle-iga` **VERIFIED** (all exemplar init scripts)
|
|
141
|
+
- Canonical setup ordering: license (`setUpTideRealm`) → IGA (`toggle-iga`) → E2EE. This is the only valid sequence. **VERIFIED** (vendor confirmation, batch-02 Q-04, A-21/A-22 resolved). License required for all Tide features (BYOiD login, signing, IGA, vouchers). IGA required for E2EE because `jwk` only injected when IGA enabled. BYOiD-only (license + no IGA) is a valid deployment mode but has no E2EE. DPoP is standard Keycloak, not gated by Tide licensing.
|
|
142
|
+
- After IGA commit, new roles are NOT immediately visible in the doken. Token refresh is required, and propagation may take up to 120s **VERIFIED** (test-cases F3, F6)
|
|
143
|
+
- Change requests have a hardcoded 1-month (2628000 seconds) cryptographic expiry enforced by the Tide enclave. After this window, signing requests are rejected. No automatic database cleanup — stale drafts persist until manually cancelled. Expiry not configurable. **VERIFIED** (vendor confirmation, batch-02 Q-08, A-25 resolved)
|
|
144
|
+
- **ACTIVE vs DRAFT distinction** **VERIFIED** (vendor confirmation, GAP-041 resolved): Role creation (`POST .../roles`) and default-role assignment get `ACTIVE` status (recorded for audit but non-blocking, no sign/commit needed). All other draft-triggering actions get `DRAFT` status requiring quorum sign/commit. This matters for automation: agents creating roles do not need sign/commit, but agents assigning those roles to users do.
|
|
145
|
+
|
|
146
|
+
**Common confusion**: IGA is NOT generic approval workflow. Approval is sealed by threshold signatures. No single admin can bypass.
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## Threshold E2EE (Hermetic E2EE)
|
|
151
|
+
|
|
152
|
+
**What it is**: End-to-end encryption where decryption requires threshold participation from Fabric ORKs. Plaintext exists only on user's device. **VERIFIED** (hermetic-e2ee.md, tier2-protocol-hermetic-e2ee.mdx)
|
|
153
|
+
|
|
154
|
+
**Official name**: "Hermetic E2EE" is marketing. Use "threshold E2EE".
|
|
155
|
+
|
|
156
|
+
**How it works** **VERIFIED** (vendor confirmation, GAP-009 resolved):
|
|
157
|
+
1. User calls `doEncrypt(tag, plaintext)` in browser
|
|
158
|
+
2. A fresh random encryption key is generated per call (<32B: fresh ElGamal scalar; >=32B: fresh 32-byte AES key via `crypto.getRandomValues()`). No key reuse across calls.
|
|
159
|
+
3. Data encrypted locally; the per-call key is ElGamal-encrypted via threshold across ORKs
|
|
160
|
+
4. Ciphertext (TideMemory envelope) stored in application
|
|
161
|
+
5. User calls `doDecrypt(tag, ciphertext)` → Fabric threshold-decrypts the per-call key → plaintext recovered locally
|
|
162
|
+
6. Separately, a session key (`t.ssk`) in the doken authenticates the user to ORKs but does NOT encrypt data. Lives in ORK enclave iframe; destroyed on tab close/logout. Adapters have no encryption key state to manage.
|
|
163
|
+
|
|
164
|
+
**Tag-based role enforcement** **VERIFIED** (e2ee.md):
|
|
165
|
+
- Roles: `_tide_<tag>.selfencrypt` and `_tide_<tag>.selfdecrypt`
|
|
166
|
+
- Enforcement is cryptographic: Fabric won't decrypt without role proof in JWT
|
|
167
|
+
- Tag names are fully application-defined, no reserved prefixes or values. Case-sensitive, passed to ORK as-is (UTF-8, no normalization), max ~237 chars for self-encryption. **VERIFIED** (vendor confirmation, batch-02 Q-10, A-26 resolved)
|
|
168
|
+
|
|
169
|
+
**Agent implication**:
|
|
170
|
+
- Use SDK methods `doEncrypt()`/`doDecrypt()` **VERIFIED** (SDK references)
|
|
171
|
+
- Both `Uint8Array` (binary) and string inputs supported **VERIFIED** (vendor confirmation, GAP-013 resolved). Strings are converted via `TextEncoder`. Base64 encoding is used for JSON transport (~33% overhead).
|
|
172
|
+
- No application-level maximum payload size **VERIFIED** (vendor confirmation, GAP-013 resolved). Payloads under 32 bytes are encrypted directly with ElGamal; payloads of 32 bytes or more use a hybrid scheme (AES-256-GCM for the data, ElGamal for the symmetric key). Practical transmission ceiling is the ASP.NET Core Kestrel default request body limit (~28.6 MB), reconfigurable per deployment.
|
|
173
|
+
- Self-encryption and policy-based encryption share the same size characteristics.
|
|
174
|
+
- Ciphertext is a TideMemory-serialized envelope (SerializedField v1). Returns base64 string when input was string, raw `Uint8Array` when input was `Uint8Array`. Overhead ~157B (small data) / ~217B (large data) before base64. Format versioned (v1), stable within major SDK version. **VERIFIED** (vendor confirmation, batch-02 Q-05, A-28 resolved)
|
|
175
|
+
- E2EE requires online Fabric access; no offline decryption **VERIFIED** (hermetic-e2ee.md)
|
|
176
|
+
- Both `selfencrypt` AND `selfdecrypt` roles needed to round-trip **INFERRED** (roles are separate; no source states granting one implies the other - A-27)
|
|
177
|
+
|
|
178
|
+
**Common confusion**: This is NOT client-side encryption with server-stored keys. Decryption requires live Fabric threshold participation.
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
## SWE (Secure Web Enclave)
|
|
183
|
+
|
|
184
|
+
**What it is**: Browser-delivered JavaScript module that coordinates cryptographic operations. Untrusted dealer: coordinates but cannot extract keys or forge proofs. Verified by SRI hash. **VERIFIED** (SWE.md, tier1-article7)
|
|
185
|
+
|
|
186
|
+
**Official name**: "Secure Web Enclave" oversells trust level. SWE is explicitly an untrusted dealer. Use "SWE (untrusted dealer)" in docs to prevent confusion.
|
|
187
|
+
|
|
188
|
+
**How it works**:
|
|
189
|
+
1. Browser loads SWE iframe from Tide domain
|
|
190
|
+
2. SWE code is SRI-verified: `integrity="sha384-..."` hash must match
|
|
191
|
+
3. SWE coordinates with ORKs but never learns complete keys or plaintext
|
|
192
|
+
4. ORKs verify SWE's instructions independently; untrusted SWE cannot bypass threshold enforcement
|
|
193
|
+
|
|
194
|
+
**CSP requirements** **VERIFIED** (vendor confirmation, GAP-028 resolved):
|
|
195
|
+
```
|
|
196
|
+
frame-src 'self' *
|
|
197
|
+
```
|
|
198
|
+
`frame-src '*'` required for ORK re-homing. Without correct CSP, SWE iframe silently fails.
|
|
199
|
+
|
|
200
|
+
**Agent implication**:
|
|
201
|
+
- Include `silent-check-sso.html` in `public/` for silent token refresh **VERIFIED** (docs + keylessh)
|
|
202
|
+
- Do not modify or vendor SWE code; SRI hash verification will fail
|
|
203
|
+
- SWE failure symptoms: login hangs, E2EE operations timeout, no visible errors (check browser console for CSP violations)
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
## Forseti Policy Engine
|
|
208
|
+
|
|
209
|
+
**What it is**: Programmable policy engine. C# contracts run in sandboxed VmHost on every ORK. Policy decisions are majority-enforced. **VERIFIED** (Forseti.mdx, tier2-protocol-forseti.mdx, keylessh `sshPolicy.ts`)
|
|
210
|
+
|
|
211
|
+
**How it works**:
|
|
212
|
+
1. Application creates Forseti contract (C# code) defining policy rules
|
|
213
|
+
2. Contract is submitted to ORKs via `createTideRequest()`
|
|
214
|
+
3. Each ORK compiles and executes contract in sandbox
|
|
215
|
+
4. Majority of ORKs must approve for operation to proceed
|
|
216
|
+
5. No single ORK bypass; compromised ORK cannot override policy
|
|
217
|
+
|
|
218
|
+
**Contract structure** **VERIFIED** (keylessh `sshPolicy.ts`):
|
|
219
|
+
```typescript
|
|
220
|
+
{
|
|
221
|
+
contract: "using System; ...", // C# code
|
|
222
|
+
modelName: "BasicCustom<APP>:BasicCustom<1>", // model ID
|
|
223
|
+
authFlow: "Policy:1", // protocol version
|
|
224
|
+
authorizer: tc.doken, // VVK-signed session token
|
|
225
|
+
challengeData: { ... } // application-specific data
|
|
226
|
+
}
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
**Model IDs** **VERIFIED** (keylessh `sshPolicy.ts`):
|
|
230
|
+
- `BasicCustom<X>:BasicCustom<1>` - implicit auth, no popup
|
|
231
|
+
- `DynamicCustom<X>:DynamicCustom<1>` - dynamic challenge data
|
|
232
|
+
- `DynamicApprovedCustom<X>:DynamicApprovedCustom<1>` - requires operator approval popup
|
|
233
|
+
|
|
234
|
+
**REQUIRES_RUNTIME_VALIDATION**: Single-app evidence. `<X>` is app-specific (keylessh uses `<SSH>`). Prefix pattern may be Forseti convention.
|
|
235
|
+
|
|
236
|
+
**Built-in default contracts** **VERIFIED** (vendor confirmation, GAP-046 resolved):
|
|
237
|
+
|
|
238
|
+
Four pre-registered contracts in the ORK factory — no need to upload via the contract deployment API:
|
|
239
|
+
|
|
240
|
+
| Contract | Purpose | Validates | Parameters |
|
|
241
|
+
|----------|---------|-----------|------------|
|
|
242
|
+
| `GenericResourceAccessThresholdRoleContract` (`GenericResourceAccessThresholdRole:1`) | Role-gated multi-approval on **client roles** | Approvers only | `role` (string), `resource` (string), `threshold` (number) |
|
|
243
|
+
| `GenericRealmAccessThresholdRoleContract` | Same but checks **realm roles** | Approvers only | `role`, `resource`, `threshold` |
|
|
244
|
+
| `SimpleTagBasedDecryptionContract` | Tag-based decryption. Checks `_tide_{tag}.encrypt`/`.decrypt` roles. One built-in option, not the standard Forseti flow. | Data + Executor | tag from data context |
|
|
245
|
+
| `HederaTX` | Hedera transaction contract | Unknown | Unknown |
|
|
246
|
+
|
|
247
|
+
Use default contracts for simple role-gated multi-approval. Write custom contracts for executor validation, data validation, or complex logic. `GenericResourceAccessThresholdRoleContract` only validates **approvers** (not executor); its `ValidateData` is a no-op and `ValidateExecutor` is not implemented.
|
|
248
|
+
|
|
249
|
+
**C# contract API surface** **VERIFIED** (forseti-crypto-quickstart, tidewarden, test-cases):
|
|
250
|
+
|
|
251
|
+
Every contract implements `IAccessPolicy` from `Ork.Forseti.Sdk` and defines up to three validation methods:
|
|
252
|
+
|
|
253
|
+
| Method | When it runs | Context object | Key properties |
|
|
254
|
+
|--------|-------------|---------------|----------------|
|
|
255
|
+
| `ValidateData(DataContext ctx)` | Always | `ctx.RequestId` (e.g. `"PolicyEnabledEncryption:1"`), `ctx.Data` (positional byte buffer), `ctx.DynamicData`, `ctx.Policy.ExecutionType`, `ctx.Policy.ApprovalType` | |
|
|
256
|
+
| `ValidateApprovers(ApproversContext ctx)` | When `ApprovalType.EXPLICIT` | `ctx.Dokens` (array of doken bytes) | `DokenDto.WrapAll(ctx.Dokens)` wraps all approvers |
|
|
257
|
+
| `ValidateExecutor(ExecutorContext ctx)` | When `ExecutionType.PRIVATE` | `ctx.Doken` (single doken bytes), `ctx.DynamicData` | `new DokenDto(ctx.Doken)` wraps executor |
|
|
258
|
+
|
|
259
|
+
**Data access in contracts**: `ctx.Data` is a positional byte buffer, NOT key-value. Read fields with `data.GetValue(index)` or `data.TryGetValue(index, out var field)`. Tags are extracted by iterating indices.
|
|
260
|
+
|
|
261
|
+
**TideMemory wire format**: Fields are packed sequentially — each field is a 4-byte little-endian length prefix followed by that many data bytes. `ctx.DynamicData` uses the same format; read with `TryReadField(buffer, index, out result)`.
|
|
262
|
+
|
|
263
|
+
**Executor validation pattern**:
|
|
264
|
+
```csharp
|
|
265
|
+
var executor = new DokenDto(ctx.Doken);
|
|
266
|
+
return Decision
|
|
267
|
+
.RequireNotExpired(executor)
|
|
268
|
+
.RequireRole(executor, roleName); // 2-arg: realm role
|
|
269
|
+
// or: .RequireRole(executor, resource, role); // 3-arg: client role
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
**Approver validation pattern**:
|
|
273
|
+
```csharp
|
|
274
|
+
var approvers = DokenDto.WrapAll(ctx.Dokens);
|
|
275
|
+
return Decision
|
|
276
|
+
.Require(approvers.Count > 0, "No approvers")
|
|
277
|
+
.RequireAnyWithRole(approvers, resource, role);
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
**Direction detection**: `ctx.RequestId == "PolicyEnabledEncryption:1"` for encrypt, `"PolicyEnabledDecryption:1"` for decrypt. Contract must check this to apply direction-specific logic.
|
|
281
|
+
|
|
282
|
+
**Error surface**: `PolicyDecision.Deny("message")` — deny messages are string-based and propagated to the client. **VERIFIED** (test-cases F11 negative tests)
|
|
283
|
+
|
|
284
|
+
**Forseti sandbox security model** **VERIFIED** (vendor confirmation, GAP-008 resolved): Five-layer model. Contracts compiled with Roslyn against .NET 8.0 references plus three SDK assemblies (`Ork.Forseti.Sdk`, `Cryptide`, `Ork.Shared`). After compilation, IL vetting enforces a block-list of forbidden namespace prefixes: `System.IO`, `System.Net`, `System.Diagnostics`, `System.Threading`, `System.Reflection`, `System.Runtime.InteropServices`, `System.Reflection.Emit`, `Microsoft.Win32`. `System.Console` and `System.Runtime.CompilerServices.Unsafe` are always hard-blocked. Non-deterministic calls (`DateTime.Now`, `Guid.NewGuid`, `Random`, etc.) blocked by default. Static constructors banned. A contract referencing a blocked namespace compiles but fails IL vetting with `BadPolicy.ForbiddenCall` at upload time. Each policy runs in an isolated `AssemblyLoadContext` inside a separate VmHost process with OS-level CPU/memory limits and gas metering (default 50,000 gas). `Claim(key)` = 5 gas, `Log(message)` = 25-30 gas. Throws `OutOfGasException` when exhausted.
|
|
285
|
+
|
|
286
|
+
**Doken delivery**: Doken is an extra field (`"doken"`) in the OIDC token endpoint response body. Standard OIDC libraries discard unknown fields. To capture it, intercept the raw HTTP response before the OIDC library deserializes it. Doken is delivered at both initial login and token refresh. **VERIFIED** (tidewarden `DokenCapturingClient`)
|
|
287
|
+
|
|
288
|
+
**Agent implication**:
|
|
289
|
+
- Gas metering: default 50,000 gas. `Claim(key)` = 5 gas, `Log(message)` = 25-30 gas. Throws `OutOfGasException` when exhausted. **VERIFIED** (vendor confirmation, GAP-008 resolved)
|
|
290
|
+
- Sandbox: five-layer model fully documented. Namespace block-list enforced via IL vetting. **VERIFIED** (vendor confirmation, GAP-008 resolved)
|
|
291
|
+
- Contract errors propagated to client as string messages: `PolicyDecision.Deny("message")`, `BadPolicy.ForbiddenCall:{target}`, `BadPolicy.EntryTypeNotFound`, `BadPolicy.BudgetExceeded`, `VmHost.Timeout`, `OutOfGasException`. ORK error codes are not systematically surfaced — they arrive as opaque strings. **VERIFIED** (vendor confirmation, GAP-004 resolved). Internal ORK compilation error taxonomy still opaque (GAP-018).
|
|
292
|
+
- Doken accessed via `tc.doken` property **VERIFIED** (keylessh `tideSsh.ts`)
|
|
293
|
+
- Doken is VVK-signed (EdDSA), session-bound. **VERIFIED** (vendor confirmation, GAP-017 resolved)
|
|
294
|
+
- Doken `exp` copied directly from SSO access token `exp` at issuance — they share the same expiration. Auto-reissued when access token is refreshed; SDK handles transparently including mid-operation refresh via `dokenRefreshCallback`. No independent doken TTL configuration.
|
|
295
|
+
- Doken revocation: operates at the signature level via per-user blocklist (`RevokedAuthorizerService`). No explicit "revoke this doken" API.
|
|
296
|
+
- Role claims (`realm_access`, `resource_access`) are snapshot into the doken at issuance and only update on the next token refresh — this explains the up to 120s delay between IGA role changes and doken reflection.
|
|
297
|
+
- A fully-expired doken cannot be refreshed — the user must reauthenticate. Refresh must happen before expiry, not after.
|
|
298
|
+
|
|
299
|
+
**Common confusion**: Forseti is NOT authorization middleware config. Real C# contracts execute in every ORK. No single bypass point.
|
|
300
|
+
|
|
301
|
+
---
|
|
302
|
+
|
|
303
|
+
## DPoP (Demonstration of Proof-of-Possession)
|
|
304
|
+
|
|
305
|
+
**What it is**: RFC 9449 standard. Binds access tokens to a client key pair. Server verifies proof on every request. **VERIFIED** (RFC 9449, keylessh `auth.ts`, `AuthContext.tsx`)
|
|
306
|
+
|
|
307
|
+
**TideCloak DPoP supports two signing algorithms** **VERIFIED** (vendor confirmation, GAP-049 resolved):
|
|
308
|
+
- **ES256** (ECDSA P-256) — **default**. Recommended for new projects due to universal browser WebCrypto support.
|
|
309
|
+
- **EdDSA** (Ed25519) — supported, with automatic fallback logic for browsers that lack Ed25519.
|
|
310
|
+
|
|
311
|
+
The algorithm is negotiated at runtime via the server's `dpop_signing_alg_values_supported` OIDC metadata field. Apps may override the default with `useDPoP.alg`. ES384/ES512 are declared in types but not implemented (would throw at runtime).
|
|
312
|
+
|
|
313
|
+
```typescript
|
|
314
|
+
// Recommended for new projects (ES256 is the default)
|
|
315
|
+
useDPoP: { mode: 'strict', alg: 'ES256' }
|
|
316
|
+
// EdDSA also supported
|
|
317
|
+
useDPoP: { mode: 'strict', alg: 'EdDSA' }
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
**DPoP enablement**: DPoP is configured per-client in TideCloak via the client attribute `"dpop.bound.access.tokens": "true"`. This is NOT a realm-wide setting. **VERIFIED** (test-cases realm.json)
|
|
321
|
+
|
|
322
|
+
**DPoP client SDK**: DPoP requires the `TideCloak` class (not `IAMService`) with `enableDpop: true` in init options:
|
|
323
|
+
```typescript
|
|
324
|
+
const tc = new TideCloak({ url, realm, clientId, vendorId, homeOrkUrl, ... });
|
|
325
|
+
await tc.init({ onLoad: "check-sso", pkceMethod: "S256", enableDpop: true });
|
|
326
|
+
// tc.secureFetch() auto-attaches DPoP headers
|
|
327
|
+
```
|
|
328
|
+
**VERIFIED** (test-cases `DPoPAuthProvider.tsx`)
|
|
329
|
+
|
|
330
|
+
**Server-side verification pattern** **VERIFIED** (keylessh `server/auth.ts`, test-cases `dpop-protected/route.ts`):
|
|
331
|
+
1. Extract `Authorization: DPoP <token>` (NOT `Bearer`)
|
|
332
|
+
2. Extract `DPoP` header (the proof JWT)
|
|
333
|
+
3. Verify JWT structure: `typ: "dpop+jwt"`, `alg` matches expected (EdDSA or ES256)
|
|
334
|
+
4. Verify signature against `jwk` in DPoP proof
|
|
335
|
+
5. Check `htm` (HTTP method) matches request method
|
|
336
|
+
6. Check `htu` (HTTP URI) matches request URL (no query string)
|
|
337
|
+
7. Check `iat` timestamp (120s freshness window)
|
|
338
|
+
8. Check `jti` not replayed (2-min TTL in-memory cache)
|
|
339
|
+
9. Extract access token, verify `cnf.jkt` matches DPoP proof thumbprint
|
|
340
|
+
|
|
341
|
+
Alternative: use `oauth2-dpop` npm package + `jose` for simpler verification. **VERIFIED** (test-cases `dpop-protected/route.ts`)
|
|
342
|
+
|
|
343
|
+
**Agent implication**:
|
|
344
|
+
- DPoP required for Tide's full security guarantees **VERIFIED** (vendor confirmation, GAP-032 resolved). Disabling degrades security.
|
|
345
|
+
- ES256 is the default algorithm. EdDSA supported with automatic fallback. ES384/ES512 not implemented.
|
|
346
|
+
- SDK provides DPoP client-side via `TideCloak.secureFetch()`; server must verify proof
|
|
347
|
+
- Replay protection requires in-memory `jti` cache with TTL
|
|
348
|
+
|
|
349
|
+
**Common confusion**: DPoP is NOT bearer token. Authorization header uses `DPoP` scheme, not `Bearer`. Each request requires fresh proof. Do not cache DPoP headers.
|
|
350
|
+
|
|
351
|
+
---
|
|
352
|
+
|
|
353
|
+
## Adapter JSON
|
|
354
|
+
|
|
355
|
+
**What it is**: Configuration file exported from TideCloak Admin Console. Extends Keycloak adapter format with Tide-specific fields. **VERIFIED** (keylessh `tidecloakConfig.ts`)
|
|
356
|
+
|
|
357
|
+
**Tide extensions**:
|
|
358
|
+
- `jwk: { keys: JWK[] }` - Embedded JWKS for local JWT verification (only present when IGA is enabled) **VERIFIED**
|
|
359
|
+
- `vendorId: string` - Tide vendor identifier **VERIFIED**
|
|
360
|
+
- `homeOrkUrl: string` - Home ORK endpoint **VERIFIED**
|
|
361
|
+
- `client-origin-auth-{origin}: string` - ORK-network-produced signature attesting a web origin is authorized for this client. Required for SDK enclave initialization. One entry per allowed web origin per client. No browser-specific variants. **VERIFIED** (vendor confirmation, GAP-048 resolved)
|
|
362
|
+
|
|
363
|
+
**Storage locations** **VERIFIED** (keylessh `tidecloakConfig.ts`, bridge configs):
|
|
364
|
+
- File: `data/tidecloak.json` (default)
|
|
365
|
+
- Env var: `CLIENT_ADAPTER` (JSON string)
|
|
366
|
+
- Env var: `TIDECLOAK_CONFIG_B64` (base64-encoded JSON)
|
|
367
|
+
|
|
368
|
+
**Agent implication**:
|
|
369
|
+
- `jwk` field is only present when IGA is enabled on the realm. Validate its presence at startup.
|
|
370
|
+
- Adapter JSON enables local JWT verification. Do not use `createRemoteJWKSet` or fetch JWKS remotely. If `jwk` is missing, re-export adapter with IGA enabled. (I-04)
|
|
371
|
+
- Export via vendor endpoint: `GET /admin/realms/{realm}/clients/{client-id}/vendorResources/get-installations-provider?providerId=keycloak-oidc-keycloak-json` **VERIFIED** (vendor confirmation, GAP-044 resolved). There is a single provider ID: `keycloak-oidc-keycloak-json`. The string `tidecloak-oidc-keycloak-json` does not exist in the codebase and should not be used.
|
|
372
|
+
- The config loader must select the `client-origin-auth-{window.location.origin}` entry and pass it to the SDK as `clientOriginAuth`. If no matching entry exists, enclave initialization will fail. **VERIFIED** (vendor confirmation, GAP-048 resolved)
|
|
373
|
+
|
|
374
|
+
**Common confusion**: Adapter JSON is NOT standard Keycloak format. Do not use generic Keycloak adapter parsers.
|
|
375
|
+
|
|
376
|
+
---
|
|
377
|
+
|
|
378
|
+
## Key Types (Theory Backing)
|
|
379
|
+
|
|
380
|
+
Most key types are theory-only; not directly referenced in SDK code.
|
|
381
|
+
|
|
382
|
+
| Key Type | Purpose | SDK Visibility | Source |
|
|
383
|
+
|----------|---------|----------------|--------|
|
|
384
|
+
| **CMK** (Consumer Master Key) | User identity key for BYOiD auth | Transparent to SDK | tier2-protocol-account-creation-keygen.mdx |
|
|
385
|
+
| **VVK** (Vendor Verifiable Key) | Organization JWT signing key | `vendorId` in adapter JSON | tier2-protocol-vvk-jwt-signing.mdx |
|
|
386
|
+
| **CVK** (Consumer Vendor Key) | Per-user personal authority key for Forseti, E2EE | `executeSignRequest()`, `doEncrypt()`/`doDecrypt()` | tier2-protocol-cvk-session.mdx, keylessh `tideSsh.ts` |
|
|
387
|
+
| **VRK** (Vendor Random Key) | Monthly TideCloak-Fabric payment key | Theory-only | tier2-protocol-vendor-licensing.mdx |
|
|
388
|
+
| **DVK** (Device Vendor Key) | Hardware biometric key on authenticator app | Theory-only | tier2-protocol-authenticator-app.mdx |
|
|
389
|
+
| **BRK** (Browser Key) | localStorage cross-verification key | Theory-only | tier2-protocol-authenticator-app.mdx |
|
|
390
|
+
| **RGK** (Ragnarok Key) | Offboarding reconstruction key | Theory-only | tier2-protocol-ragnarok.mdx |
|
|
391
|
+
|
|
392
|
+
**Agent implication**: Focus on CMK, VVK, CVK. Others are protocol backing, not SDK-facing.
|
|
393
|
+
|
|
394
|
+
---
|
|
395
|
+
|
|
396
|
+
## Ragnarok (Fabric Offboarding)
|
|
397
|
+
|
|
398
|
+
**What it is**: Deliberate offboarding from Fabric. The sole deviation from the never-whole-key invariant. Reconstructs keys locally so TideCloak can operate independently. Irreversible. **VERIFIED** (ragnarok.md, tier2-protocol-ragnarok.mdx)
|
|
399
|
+
|
|
400
|
+
**Official name**: "Ragnarok" is whimsical for a destructive operation. Use "Fabric offboarding (Ragnarok)" in docs.
|
|
401
|
+
|
|
402
|
+
**How it works**:
|
|
403
|
+
1. Quorum of admins approve Ragnarok
|
|
404
|
+
2. Keys reconstructed locally via nested threshold (RGK)
|
|
405
|
+
3. TideCloak switches to local EdDSA signing (Heimdall handler)
|
|
406
|
+
4. Fabric integration disabled permanently
|
|
407
|
+
|
|
408
|
+
**Agent implication**:
|
|
409
|
+
- Ragnarok is irreversible once quorum-approved **ASSUMED** (ragnarok.md describes finalization but no rollback - A-24)
|
|
410
|
+
- Post-Ragnarok details undocumented **STILL_UNRESOLVED** (GAP-021)
|
|
411
|
+
- keylessh does not implement Ragnarok
|
|
412
|
+
|
|
413
|
+
---
|
|
414
|
+
|
|
415
|
+
## Required Roles and Configuration
|
|
416
|
+
|
|
417
|
+
**`_tide_enabled` role** **VERIFIED** (vendor confirmation, GAP-031 resolved):
|
|
418
|
+
- `setUpTideRealm` runs after realm import and does not auto-create `_tide_enabled`
|
|
419
|
+
- Declare `_tide_enabled` in the realm template so the role already exists when setup continues
|
|
420
|
+
- If omitted from the imported realm, it must be created and assigned manually afterward
|
|
421
|
+
- Must be in every user's default role set
|
|
422
|
+
- Without it, Tide cryptographic operations fail
|
|
423
|
+
|
|
424
|
+
**`tidebrowser` flow** **VERIFIED** (confirmed across 4 exemplar realm.json files):
|
|
425
|
+
- Tide-specific browser authentication flow
|
|
426
|
+
- Set as `browserFlow` in realm.json
|
|
427
|
+
- Replaces standard Keycloak browser flow
|
|
428
|
+
|
|
429
|
+
**Token lifetimes** **VERIFIED** (confirmed across all exemplar realm.json files):
|
|
430
|
+
- `accessTokenLifespan: 600` (10 min)
|
|
431
|
+
- `ssoSessionIdleTimeout: 1800` (30 min)
|
|
432
|
+
- `ssoSessionMaxLifespan: 36000` (10 hours)
|
|
433
|
+
|
|
434
|
+
---
|
|
435
|
+
|
|
436
|
+
## Undocumented Admin Endpoints
|
|
437
|
+
|
|
438
|
+
Discovered in keylessh; not in API documentation.
|
|
439
|
+
|
|
440
|
+
| Endpoint | Purpose | Evidence |
|
|
441
|
+
|----------|---------|----------|
|
|
442
|
+
| `POST /admin/realms/{realm}/vendorResources/setUpTideRealm` | Initialize Tide features (email + terms) | `init-tidecloak.sh` |
|
|
443
|
+
| `POST /admin/realms/{realm}/tide-admin/toggle-iga` | Enable/disable IGA | `init-tidecloak.sh` |
|
|
444
|
+
| `POST /admin/realms/{realm}/users/{user-id}/tideAdminResources/get-required-action-link` | Generate account linking URL | `init-tidecloak.sh` |
|
|
445
|
+
| `GET /admin/realms/{realm}/clients/{client-id}/vendorResources/get-installations-provider?providerId=keycloak-oidc-keycloak-json` | Download adapter JSON with Tide extensions. Only valid providerId: `keycloak-oidc-keycloak-json`. `tidecloak-oidc-keycloak-json` does not exist. | `init-tidecloak.sh`, vendor confirmation (GAP-044 resolved) |
|
|
446
|
+
|
|
447
|
+
**PARTIALLY_RESOLVED** (GAP-029): Most endpoint paths observed. `setUpTideRealm` fully documented (vendor confirmation, batch-02 Q-03). `get-required-action-link` request/response schema still missing.
|
|
448
|
+
|
|
449
|
+
---
|
|
450
|
+
|
|
451
|
+
## Account Linking Onboarding
|
|
452
|
+
|
|
453
|
+
**PARTIALLY_RESOLVED_BY_KEYLESSH** (GAP-030):
|
|
454
|
+
|
|
455
|
+
New admin users must complete account linking:
|
|
456
|
+
1. Generate time-limited URL via `get-required-action-link` (default: 12 hours lifespan)
|
|
457
|
+
2. User opens link, completes linking in browser
|
|
458
|
+
3. Poll user attributes for `tideUserKey` and `vuid`
|
|
459
|
+
4. When present, linking complete
|
|
460
|
+
|
|
461
|
+
**Evidence**: keylessh `init-tidecloak.sh`
|
|
462
|
+
|
|
463
|
+
**Unclear**: Whether mandatory for non-admin users or non-IGA setups.
|
|
464
|
+
|
|
465
|
+
---
|
|
466
|
+
|
|
467
|
+
## Settlement Layer (Theory-Only)
|
|
468
|
+
|
|
469
|
+
**Anonymous Voucher**: Micro-payment token gating every ORK operation. Triple-blinded: ORK identity, vendor identity, user ID independently obfuscated. **VERIFIED** (tier2-protocol-anonymous-voucher.mdx)
|
|
470
|
+
|
|
471
|
+
**VUID (Vendor User ID)**: Mathematically unlinkable identifier across vendors. Mapped to `vuid` JWT claim. **VERIFIED** (tier2-protocol-double-blind-tss.mdx, keylessh `realm.json`)
|
|
472
|
+
|
|
473
|
+
**Agent implication**: Vouchers are a universal Tide Network requirement — every cryptographic operation requires a voucher. For TideCloak apps using `@tidecloak/js`, the SDK automatically constructs the voucher URL from the user's access token — developers do not need to build this URL manually. **VERIFIED** (vendor confirmation, GAP-051 resolved). Non-TideCloak integrations must operate their own voucher faucet and pass its URL to the Heimdall SDK directly.
|
|
474
|
+
|
|
475
|
+
---
|
|
476
|
+
|
|
477
|
+
## Voucher Gates (Dual Role Requirement)
|
|
478
|
+
|
|
479
|
+
**What it is**: For policy-governed VVK encryption, users need TWO types of roles: voucher gate roles and Forseti contract roles. The voucher system in `VoucherResource.java` independently gates whether encrypt/decrypt operations are allowed for a session before the Forseti contract even runs. **VERIFIED** (operational exemplars: forseti-crypto-quickstart, tidecloak-test-cases)
|
|
480
|
+
|
|
481
|
+
**Two encryption models** **VERIFIED** (test-cases F7, F9, vendor confirmation batch-02 Q-10):
|
|
482
|
+
|
|
483
|
+
| Pattern | Role names | Encryption model | SDK call |
|
|
484
|
+
|---------|-----------|-----------------|----------|
|
|
485
|
+
| Self-encryption | `_tide_{tag}.selfencrypt` / `_tide_{tag}.selfdecrypt` | User-bound (only encryptor can decrypt) | `doEncrypt([{data, tags}])` — no policy bytes |
|
|
486
|
+
| Policy-based | No `_tide_{tag}` roles required. Tags are opaque data; authorization defined by Forseti contract | Shared (any doken satisfying the contract can decrypt) | `doEncrypt([{data, tags}], policyBytes)` — with policy bytes |
|
|
487
|
+
|
|
488
|
+
For self-encryption, the SDK checks `_tide_{tag}.selfencrypt`/`.selfdecrypt` roles on the client and ORK. For policy-based encryption, the SDK skips the tag-role check entirely (`tidecloak.js:703-704`); authorization is handled by the Forseti contract, which can check any Keycloak role (not necessarily `_tide_`-prefixed).
|
|
489
|
+
|
|
490
|
+
**Voucher gate roles**: The voucher system still requires `_tide_*` roles to fund ORK operations, even for policy-based encryption. But you do not need per-tag `_tide_{tag}.encrypt`/`.decrypt` roles — any `_tide_*` role (e.g., `_tide_enabled`, or a generic `_tide_x.selfdecrypt`) satisfies the `vendorsign` voucher check. For `vendordecrypt`, a role matching `_tide_*.selfdecrypt` or `_tide_*.decrypt` is needed.
|
|
491
|
+
|
|
492
|
+
**Forseti contract roles** (e.g., `shared-data-access`): Checked by the custom contract's `ValidateExecutor`. Controls WHO can actually decrypt. Regular realm role, NOT `_tide_*` prefixed. Only relevant for policy-based encryption.
|
|
493
|
+
|
|
494
|
+
**Voucher enforcement rules** **VERIFIED** (vendor confirmation, GAP-051 resolved):
|
|
495
|
+
The `tidevouchers/fromUserSession` endpoint performs role-based voucher authorization before issuing vouchers:
|
|
496
|
+
- `signin` and `updateaccount`: always allowed
|
|
497
|
+
- `vendorsign` (encrypt): allowed if user has ANY `_tide_*` role, an authorizer role, or `tide-realm-admin`
|
|
498
|
+
- `vendordecrypt` (decrypt): allowed if user has a role matching `_tide_*.selfdecrypt` or `_tide_*.decrypt`
|
|
499
|
+
|
|
500
|
+
**Without the voucher**: ORKs refuse before the Forseti contract runs. Error: `"session ID ... has not been allowed a voucher action of type vendordecrypt"`.
|
|
501
|
+
|
|
502
|
+
**Self-encryption role assignment pattern**:
|
|
503
|
+
```json
|
|
504
|
+
{
|
|
505
|
+
"roles": {
|
|
506
|
+
"realm": [
|
|
507
|
+
{ "name": "_tide_secret.selfencrypt", "description": "Voucher gate: enables self-encryption" },
|
|
508
|
+
{ "name": "_tide_secret.selfdecrypt", "description": "Voucher gate: enables self-decryption" }
|
|
509
|
+
]
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
```
|
|
513
|
+
|
|
514
|
+
**Policy-based encryption role assignment pattern** (simplified approach — vendor confirmation, batch-02 Q-10):
|
|
515
|
+
```json
|
|
516
|
+
{
|
|
517
|
+
"roles": {
|
|
518
|
+
"realm": [
|
|
519
|
+
{ "name": "shared-data-access", "description": "Forseti contract role: who can encrypt/decrypt" },
|
|
520
|
+
{ "name": "_tide_x.selfencrypt", "description": "Voucher gate: enables vendorsign (generic tag)" },
|
|
521
|
+
{ "name": "_tide_x.selfdecrypt", "description": "Voucher gate: enables vendordecrypt (generic tag)" }
|
|
522
|
+
]
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
Policy-based encryption does NOT require per-tag `_tide_{tag}.encrypt`/`.decrypt` roles. The SDK skips the tag-role check when a `decryption_policy` is provided. Use a generic voucher gate (e.g., `_tide_x.selfencrypt`/`_tide_x.selfdecrypt` with tag `x`) to satisfy the voucher system. Actual access control is entirely in the Forseti contract checking the business role (e.g., `shared-data-access`).
|
|
528
|
+
|
|
529
|
+
Note: The `SimpleTagBasedDecryptionContract` is one built-in default contract that does check `_tide_{tag}.encrypt`/`.decrypt` roles, but the standard Forseti flow uses custom contracts where authorization is fully contract-defined.
|
|
530
|
+
|
|
531
|
+
**Agent implication**: After adding roles to a running realm, users must refresh their token (or log out and back in) for the doken to pick up new roles. Propagation may take up to 120s. **VERIFIED** (test-cases F3, F7)
|
|
532
|
+
|
|
533
|
+
**Anti-pattern**: Using `_tide_*` roles in your Forseti contract's `ValidateExecutor`. The voucher gate roles are for ORK operation gating only. Your contract should check a regular realm role.
|
|
534
|
+
|
|
535
|
+
**Anti-pattern**: Using self-encryption roles (`selfencrypt`/`selfdecrypt`) when you need shared access. Self-encryption is identity-bound. Use policy-based encryption with a Forseti contract for shared data. Policy-based encryption does not require per-tag `_tide_` roles — authorization is contract-defined.
|
|
536
|
+
|
|
537
|
+
**Anti-pattern**: Renaming `selfencrypt`/`selfdecrypt` to `encrypt`/`decrypt` and expecting this to enable shared decryption. The role suffix does not change the encryption model. The SDK call path determines whether encryption is self-bound or policy-governed. See AP-26 in `canon/anti-patterns.md`.
|
|
538
|
+
|
|
539
|
+
---
|
|
540
|
+
|
|
541
|
+
## Self-Encryption vs Policy-Governed VVK Encryption
|
|
542
|
+
|
|
543
|
+
**What it is**: Two completely separate encryption models with different key binding, access semantics, and SDK call paths. **VERIFIED** (operational exemplars: forseti-crypto-quickstart)
|
|
544
|
+
|
|
545
|
+
**Model 1: Self-encryption** (no policy):
|
|
546
|
+
- SDK calls: `doEncrypt(data)` / `doDecrypt(data)` on `useTideCloak()`
|
|
547
|
+
- Binds ciphertext to the encrypting user's identity (CVK)
|
|
548
|
+
- Only that user can decrypt
|
|
549
|
+
- Giving another user `selfdecrypt` role does NOT let them decrypt your data
|
|
550
|
+
- Use for: personal vaults, user-private data
|
|
551
|
+
|
|
552
|
+
**Model 2: Policy-governed VVK encryption** (with signed policy):
|
|
553
|
+
- SDK calls: `IAMService.doEncrypt(data, signedPolicyBytes)` / `IAMService.doDecrypt(data, signedPolicyBytes)`
|
|
554
|
+
- Encrypts with VVK (organizational key distributed across ORKs)
|
|
555
|
+
- Any user whose doken satisfies the Forseti contract can decrypt
|
|
556
|
+
- Requires a signed Forseti policy with a custom contract
|
|
557
|
+
- Use for: shared data, group messaging, multi-user access
|
|
558
|
+
|
|
559
|
+
**Critical distinction**: The `doEncrypt`/`doDecrypt` on `useTideCloak()` are convenience wrappers that do NOT pass the policy parameter. For shared encryption, call `IAMService` directly.
|
|
560
|
+
|
|
561
|
+
**Agent implication**: Never use self-encryption for shared data. Never assume that assigning decrypt roles to multiple users enables cross-user decryption under self-encryption.
|
|
562
|
+
|
|
563
|
+
**Anti-pattern**: Using `doEncrypt`/`doDecrypt` from `useTideCloak()` and expecting other users to decrypt the result. Self-encryption is identity-bound; shared access requires `IAMService` with a signed policy.
|
|
564
|
+
|
|
565
|
+
---
|
|
566
|
+
|
|
567
|
+
## Policy Signing (5-Step Flow)
|
|
568
|
+
|
|
569
|
+
**What it is**: VVK policy signing requires a 5-step flow through the ORK threshold network. Raw `policy.toBytes()` without a VVK signature is rejected. **VERIFIED** (operational exemplars: forseti-crypto-quickstart)
|
|
570
|
+
|
|
571
|
+
**The 5 steps**:
|
|
572
|
+
|
|
573
|
+
```js
|
|
574
|
+
const tc = IAMService._tc;
|
|
575
|
+
|
|
576
|
+
// Step 1: Initialize the request
|
|
577
|
+
const initialized = await tc.createTideRequest(request.encode());
|
|
578
|
+
|
|
579
|
+
// Step 2: Operator approval (opens popup for user to sign)
|
|
580
|
+
const approvalResults = await tc.requestTideOperatorApproval([
|
|
581
|
+
{ id: "policy-sign", request: initialized },
|
|
582
|
+
]);
|
|
583
|
+
if (approvalResults[0].status !== "approved") throw new Error("Denied");
|
|
584
|
+
|
|
585
|
+
// Step 3: Decode approved request and ATTACH THE ADMIN POLICY
|
|
586
|
+
const approvedRequest = BaseTideRequest.decode(approvalResults[0].request);
|
|
587
|
+
const adminPolicyBytes = await fetchAdminPolicyFromTideCloak(token);
|
|
588
|
+
approvedRequest.addPolicy(adminPolicyBytes);
|
|
589
|
+
|
|
590
|
+
// Step 4: Execute sign request (waitForAll MUST be true)
|
|
591
|
+
const signatures = await tc.executeSignRequest(approvedRequest.encode(), true);
|
|
592
|
+
|
|
593
|
+
// Step 5: Attach VVK signature to the policy object
|
|
594
|
+
policy.signature = signatures[0];
|
|
595
|
+
const signedBytes = policy.toBytes(); // NOW has VVK signature
|
|
596
|
+
```
|
|
597
|
+
|
|
598
|
+
**What fails if each step is skipped**:
|
|
599
|
+
- Skip step 2 (popup): `executeSignRequest` alone does not sign policies
|
|
600
|
+
- Skip step 3 (admin policy): ORKs reject with `"Policy supplied has not been signed"`
|
|
601
|
+
- Skip step 4 (executeSignRequest): approval popup alone does not produce the VVK signature
|
|
602
|
+
- Forget `policy.signature = signatures[0]`: policy bytes have no signature, ORKs reject
|
|
603
|
+
|
|
604
|
+
**Critical pitfalls**:
|
|
605
|
+
- Do NOT call `addPolicy(policy.toBytes())` on the `BaseTideRequest` during construction. Attaching an unsigned policy causes `createTideRequest` to fail immediately. Only call `addPolicy()` in step 3 to attach the signed admin policy.
|
|
606
|
+
- Do NOT store signed policy bytes to the server until verified. A failed signing attempt storing unsigned bytes causes subsequent calls to fetch stale bytes and silently fail.
|
|
607
|
+
|
|
608
|
+
**Agent implication**: Copy the exact pattern from the forseti-crypto-quickstart reference implementation. Deviating from the step order or parameter passing causes ORK byte serialization failures.
|
|
609
|
+
|
|
610
|
+
**Anti-pattern**: Attempting to sign policies from the init script or backend. The backend receives `requiresApprovalPopup: true` and cannot complete the signing. Policy signing MUST happen from the frontend after the admin logs in.
|
|
611
|
+
|
|
612
|
+
---
|
|
613
|
+
|
|
614
|
+
## SDK Internals
|
|
615
|
+
|
|
616
|
+
**What it is**: Internal structure of Tide SDK packages and their exports. **VERIFIED** (operational exemplars: forseti-crypto-quickstart, tidewarden, keylessh)
|
|
617
|
+
|
|
618
|
+
**Package responsibilities** **VERIFIED** (runtime confirmation, GAP-042):
|
|
619
|
+
- **`@tidecloak/nextjs`** — Next.js integration layer. Exports `TideCloakProvider`, `useTideCloak`, `useAuthCallback`, `Authenticated`/`Unauthenticated`, guard components. Does NOT export `Models`, `PolicySignRequest`, `BaseTideRequest`, `ApprovalType`, `ExecutionType`, or any policy/model helpers. Importing these from `@tidecloak/nextjs` returns `undefined` at runtime.
|
|
620
|
+
- **`@tideorg/js`** — Exports `Models` (`Policy`, `BaseTideRequest`, `ExecutionType`, `ApprovalType`) and `Contracts` (`GenericResourceAccessThresholdRoleContract`). **This is the preferred import source for Models and Contracts.**
|
|
621
|
+
- **`heimdall-tide`** — Exports `PolicySignRequest`. Use `PolicySignRequest.New(policy)` to construct policy signing requests. **This is the preferred import source for PolicySignRequest.**
|
|
622
|
+
- **`@tidecloak/js`** — Core TideCloak JS SDK. Exports `IAMService`, `TideCloak` (for DPoP). Re-exports `Models` from `@tideorg/js` and `PolicySignRequest` from `heimdall-tide`, but the re-exports may be incomplete. Prefer importing from the source packages directly.
|
|
623
|
+
- **`asgard-tide`** — Vendor validation library.
|
|
624
|
+
|
|
625
|
+
**Package-boundary rule**: `@tidecloak/nextjs` is the Next.js-facing auth/provider/hooks layer. It does NOT export policy/model helpers (`Models`, `PolicySignRequest`, `BaseTideRequest`, `ApprovalType`, `ExecutionType`). When those helpers are needed, import `Models` from `@tideorg/js` and `PolicySignRequest` from `heimdall-tide`. Importing them from `@tidecloak/nextjs` produces `undefined` at runtime, causing destructuring failures like `Cannot destructure property 'Policy' of 'Models' as it is undefined`.
|
|
626
|
+
|
|
627
|
+
**Import rules**:
|
|
628
|
+
```js
|
|
629
|
+
// Models and Contracts: import from @tideorg/js (NOT @tidecloak/nextjs)
|
|
630
|
+
import { Models, Contracts } from "@tideorg/js";
|
|
631
|
+
const { Policy, ApprovalType, ExecutionType, BaseTideRequest } = Models;
|
|
632
|
+
|
|
633
|
+
// PolicySignRequest: import from heimdall-tide (NOT @tidecloak/nextjs)
|
|
634
|
+
import { PolicySignRequest } from "heimdall-tide";
|
|
635
|
+
|
|
636
|
+
// IAMService and TideCloak: import from @tidecloak/js
|
|
637
|
+
import { IAMService } from "@tidecloak/js";
|
|
638
|
+
import { TideCloak } from "@tidecloak/js"; // for DPoP flows
|
|
639
|
+
|
|
640
|
+
// Policy signing methods live on the TideCloak instance, not IAMService:
|
|
641
|
+
const tc = IAMService._tc; // underlying TideCloak instance
|
|
642
|
+
await tc.createTideRequest(request);
|
|
643
|
+
await tc.requestTideOperatorApproval(requests);
|
|
644
|
+
await tc.executeSignRequest(request, waitForAll);
|
|
645
|
+
|
|
646
|
+
// Next.js hooks/provider: from @tidecloak/nextjs
|
|
647
|
+
import { useTideCloak, TideCloakProvider } from "@tidecloak/nextjs";
|
|
648
|
+
```
|
|
649
|
+
|
|
650
|
+
**Next.js webpack workaround**: `@tidecloak/js` re-exports symbols from `heimdall-tide` that may not resolve. Add to `next.config.ts`:
|
|
651
|
+
```typescript
|
|
652
|
+
config.module.strictExportPresence = false;
|
|
653
|
+
```
|
|
654
|
+
Also add the `@tidecloak/react` ESM alias. See `canon/framework-matrix.md` for the complete webpack config. VERIFIED (learning-batch-004, L-04).
|
|
655
|
+
|
|
656
|
+
**Model IDs format**: `"{name}:{version}"`. Examples: `"Policy:1"`, `"PolicyEnabledEncryption:1"`, `"PolicyEnabledDecryption:1"`. Check `ModelRegistry.js` in `@tideorg/js/dist/Models/` for correct names. Never guess model names.
|
|
657
|
+
|
|
658
|
+
**Agent implication**: Always use the documented import paths. The #1 source of policy-signing failures is importing from the wrong package or incorrect destructuring.
|
|
659
|
+
|
|
660
|
+
**Anti-pattern**: Importing `Models` or `PolicySignRequest` from `@tidecloak/nextjs`. This package does not export them. Dynamic imports like `await import("@tidecloak/nextjs")` return `undefined` for these symbols, producing `Cannot destructure property 'Policy' of 'Models' as it is undefined`. Fix: import `Models` from `@tideorg/js` and `PolicySignRequest` from `heimdall-tide`.
|
|
661
|
+
|
|
662
|
+
**Anti-pattern**: Destructuring `{ ApprovalType, ExecutionType }` from `Models.Policy` instead of `Models`. `Models.Policy` is the Policy class itself. This gives `undefined`, causing `Cannot read properties of undefined (reading 'IMPLICIT')`.
|
|
663
|
+
|
|
664
|
+
### `@tideorg/js` Export Reference (v0.13.26)
|
|
665
|
+
|
|
666
|
+
`@tideorg/js` exports 7 namespaces. Only `Models` and `Contracts` are needed for app-level policy work. The others are internal SDK plumbing.
|
|
667
|
+
|
|
668
|
+
**Top-level namespaces**: `Models`, `Contracts`, `Clients`, `Cryptide`, `Flow`, `Math`, `Tools` (also aliased as `Utils`).
|
|
669
|
+
|
|
670
|
+
#### Models (app-facing — used in policy signing)
|
|
671
|
+
|
|
672
|
+
| Export | Type | Purpose |
|
|
673
|
+
|--------|------|---------|
|
|
674
|
+
| `Models.Policy` | class | Construct a Forseti policy. Constructor: `{ version, contractId, modelId, keyId, approvalType, executionType, params }`. Methods: `toBytes()`, static `from()`. Property: `signature` (set after signing). |
|
|
675
|
+
| `Models.ApprovalType` | enum | `EXPLICIT` (0) = separate approver dokens needed. `IMPLICIT` (1) = no separate approvers. |
|
|
676
|
+
| `Models.ExecutionType` | enum | `PRIVATE` (0) = runs `ValidateExecutor` on user's doken. `PUBLIC` (1) = no executor check. |
|
|
677
|
+
| `Models.BaseTideRequest` | class | Base request for Tide operations. Methods: `encode()`, `decode()`, `addPolicy()`, `addApproval()`, `addAuthorizer()`, `setCustomExpiry()`, `getPolicy()`, `hasPolicy()`, `isInitialized()`, `replicate()`. |
|
|
678
|
+
| `Models.Doken` | class | Authorization/identity token. Properties: `header`, `payload` (`sessionKey`, `tideuserkey`, `vuid`, `homeOrk`, `exp`, `aud`, `realm_access`, `resource_access`), `signature`. Methods: `isExpired()`, `validate()`, `verify()`, `serialize()`. |
|
|
679
|
+
| `Models.SerializedField` | class | Encrypted field envelope (TideMemory format v1). Static: `create()`, `deserialize()`. |
|
|
680
|
+
| `Models.Datum` | class | Data element with tag. Properties: `data`, `tag`. |
|
|
681
|
+
| `Models.AuthRequest` | class | Authentication request. Properties: `keyId`, `purpose`, `keyPub`, `expiry`, `sessionId`. |
|
|
682
|
+
| `Models.VendorData` | class | Vendor data. Properties: `VUID`, `gCMKAuth`, `blindSig`, `AuthToken`. |
|
|
683
|
+
| `Models.VendorSettings` | class | Vendor config. Properties: `regOn`, `backupOn`, `imageURL`, `logoURL`. |
|
|
684
|
+
| `Models.EnclaveEntry` | class | Enclave entry. Properties: `username`, `persona`, `expired`, `userInfo`, `orksBitwise`, `selfRequesti`, `sessKey`. |
|
|
685
|
+
| `Models.PolicyParameters` | class | Policy parameter storage. Methods: `tryGetParameter()`, `getParameter()`, `toBytes()`. |
|
|
686
|
+
| `Models.ModelRegistry` | class | Registry for model builders. Static: `getHumanReadableModelBuilder()`. |
|
|
687
|
+
| `Models.HumanReadableModelBuilder` | class | Builds human-readable request representations. Methods: `getDetailsMap()`, `getRequestDataJson()`, `getExpiry()`. |
|
|
688
|
+
| `Models.Infos.KeyInfo` | class | User key info. Properties: `UserId`, `UserPublic`, `UserM`, `OrkInfo`. |
|
|
689
|
+
| `Models.Infos.OrkInfo` | class | ORK node info. Properties: `orkID`, `orkPublic`, `orkURL`, `orkPaymentPublic`. |
|
|
690
|
+
|
|
691
|
+
#### Contracts (app-facing — used for contract validation)
|
|
692
|
+
|
|
693
|
+
| Export | Type | Purpose |
|
|
694
|
+
|--------|------|---------|
|
|
695
|
+
| `Contracts.BaseContract` | abstract class | Base for contract validation. Abstract: `validateData()`, `validateApprovers()`, `validateExecutor()`. Method: `testPolicy()`. |
|
|
696
|
+
| `Contracts.GenericRealmAccessThresholdRoleContract` | class | Built-in contract checking realm roles with threshold approval. |
|
|
697
|
+
| `Contracts.GenericResourceAccessThresholdRoleContract` | class | Built-in contract checking client/resource roles with threshold approval. |
|
|
698
|
+
| `Contracts.Doken` | class | Doken for contract context. Methods: `hasResourceAccessRole()`, `hasRealmAccessRole()`, `hasVuid()`. |
|
|
699
|
+
|
|
700
|
+
#### Tools (utility — used for serialization)
|
|
701
|
+
|
|
702
|
+
| Export | Type | Purpose |
|
|
703
|
+
|--------|------|---------|
|
|
704
|
+
| `Tools.TideMemory` | class (extends Uint8Array) | Tide's binary serialization format. Static: `CreateFromArray()`, `Create()`. Methods: `WriteValue()`, `GetValue()`, `TryGetValue()`. |
|
|
705
|
+
| `Tools.Threshold` | constant | Default threshold (14). |
|
|
706
|
+
| `Tools.Max` | constant | Default max ORKs (20). |
|
|
707
|
+
| `Tools.CurrentTime()` | function | Current timestamp. |
|
|
708
|
+
| `Tools.WaitForNumberofORKs()` | function | Wait for threshold ORK responses. |
|
|
709
|
+
|
|
710
|
+
#### Cryptide (internal — rarely needed by app code)
|
|
711
|
+
|
|
712
|
+
Low-level cryptographic operations. Sub-namespaces: `Encryption` (ElGamal, AES, DH), `Signing` (EdDSA, BlindSig, signature formats), `Hashing` (SHA256, SHA512, HMAC), `Ed25519` (curve point math), `Components` (key component system), `Serialization` (byte conversion utilities), `Interpolation` (Lagrange interpolation for threshold), `Math` (modular arithmetic).
|
|
713
|
+
|
|
714
|
+
Key class: `Cryptide.TideKey` — key management. Static: `NewKey()`, `FromSerializedComponent()`. Methods: `sign()`, `verify()`, `asymmetricEncrypt()`, `asymmetricDecrypt()`.
|
|
715
|
+
|
|
716
|
+
#### Flow (internal — SDK orchestration)
|
|
717
|
+
|
|
718
|
+
High-level operation flows. Sub-namespaces: `DecryptionFlows`, `EncryptionFlows`, `SigningFlows`, `VoucherFlows`.
|
|
719
|
+
|
|
720
|
+
Key class: `Flow.EncryptionFlows.PolicyAuthorizedEncryptionFlow` — policy-based encrypt/decrypt. Methods: `encrypt()`, `decrypt()`, `createEncryptionRequest()`, `createDecryptionRequest()`, `commitEncrypt()`, `commitDecrypt()`.
|
|
721
|
+
|
|
722
|
+
Key class: `Flow.SigningFlows.dVVKSigningFlow2Step` — two-step VVK signing. Methods: `preSign()`, `sign()`, `getVouchers()`.
|
|
723
|
+
|
|
724
|
+
#### Math (internal — threshold crypto math)
|
|
725
|
+
|
|
726
|
+
| Export | Type | Purpose |
|
|
727
|
+
|--------|------|---------|
|
|
728
|
+
| `Math.KeyDecryption.GetKeys()` | function | Recover keys from decryption shares. |
|
|
729
|
+
| `Math.KeySigning.PreSign()` | function | Prepare threshold signature. |
|
|
730
|
+
| `Math.KeySigning.Sign()` | function | Finalize threshold signature. |
|
|
731
|
+
|
|
732
|
+
---
|
|
733
|
+
|
|
734
|
+
## Admin Policy Endpoint
|
|
735
|
+
|
|
736
|
+
**What it is**: The correct endpoint for fetching the signed admin policy needed in step 3 of policy signing. Two endpoints exist; one is wrong. **VERIFIED** (operational exemplars: forseti-crypto-quickstart, keylessh)
|
|
737
|
+
|
|
738
|
+
**Endpoints**:
|
|
739
|
+
```
|
|
740
|
+
WRONG: GET /admin/realms/{realm}/tide-admin/realm-policy
|
|
741
|
+
Returns: { status: "none" } even when the policy exists
|
|
742
|
+
|
|
743
|
+
CORRECT: GET /realms/{realm}/tide-policy-resources/admin-policy
|
|
744
|
+
Returns: base64-encoded bytes (does not require admin credentials)
|
|
745
|
+
```
|
|
746
|
+
|
|
747
|
+
**Response handling**: The correct endpoint returns base64 text, not raw binary. Decode on backend:
|
|
748
|
+
```js
|
|
749
|
+
// Backend proxy
|
|
750
|
+
const b64 = await tcRes.text();
|
|
751
|
+
const raw = Buffer.from(b64, "base64");
|
|
752
|
+
const bytes = Array.from(new Uint8Array(raw));
|
|
753
|
+
res.json({ policyBytes: bytes });
|
|
754
|
+
|
|
755
|
+
// Frontend
|
|
756
|
+
const data = await res.json();
|
|
757
|
+
const adminPolicyBytes = new Uint8Array(data.policyBytes);
|
|
758
|
+
```
|
|
759
|
+
|
|
760
|
+
**Common mistake**: Treating the base64 string as raw bytes (passing each character's char code as a byte value). If admin policy bytes start with `[65, 81, 65, 65, ...]` (ASCII for `"AQAA..."`), you are passing base64 text as byte values instead of decoding it. The ORK fails with `Index out of range` because the policy structure is garbage.
|
|
761
|
+
|
|
762
|
+
**Agent implication**: Always base64-decode the response. Proxy through your backend since this is a non-admin endpoint.
|
|
763
|
+
|
|
764
|
+
**Anti-pattern**: Using `GET /admin/realms/{realm}/tide-admin/realm-policy` to fetch the admin policy. This endpoint checks status only and returns `{ status: "none" }` regardless of whether the policy exists.
|
|
765
|
+
|
|
766
|
+
---
|
|
767
|
+
|
|
768
|
+
## Realm Policy Setup
|
|
769
|
+
|
|
770
|
+
**What it is**: Endpoints for creating and managing the realm admin policy. The realm admin policy must exist before encryption policies can be signed. **VERIFIED** (operational exemplars: forseti-crypto-quickstart)
|
|
771
|
+
|
|
772
|
+
**Endpoints**:
|
|
773
|
+
```
|
|
774
|
+
GET /tide-admin/realm-policy -> check status
|
|
775
|
+
POST /tide-admin/realm-policy/pending -> create pending policy (needs templateId + contractCode)
|
|
776
|
+
POST /tide-admin/realm-policy/commit -> commit after approval
|
|
777
|
+
GET /tide-admin/policy-templates -> list templates
|
|
778
|
+
POST /tide-admin/policy-templates -> create template
|
|
779
|
+
```
|
|
780
|
+
|
|
781
|
+
**Setup sequence**:
|
|
782
|
+
1. Create a policy template via `POST /tide-admin/policy-templates`
|
|
783
|
+
2. Create a pending realm policy via `POST /tide-admin/realm-policy/pending` with `templateId` and `contractCode`
|
|
784
|
+
3. Approve via enclave (admin browser required)
|
|
785
|
+
4. Commit via `POST /tide-admin/realm-policy/commit`
|
|
786
|
+
|
|
787
|
+
**Storing policy on a role** (`init-cert` endpoint) **VERIFIED** (vendor confirmation, GAP-039 resolved):
|
|
788
|
+
```
|
|
789
|
+
POST /admin/realms/{realm}/tide-iga-provider/role-policy/{roleId}/init-cert
|
|
790
|
+
Content-Type: application/json
|
|
791
|
+
|
|
792
|
+
{
|
|
793
|
+
"initCert": "<base64 signed policy bytes, required>",
|
|
794
|
+
"initCertSig": "<base64 raw VVK signature, optional>"
|
|
795
|
+
}
|
|
796
|
+
```
|
|
797
|
+
There is no `doken` field — this is a pure storage endpoint persisting to `TideRoleDraftEntity`. Returns `{"message":"initCert updated for role <roleId>"}` on success, 404 if role not found, 400 if `initCert` missing. The stored policy is consumed during multi-admin change-set signing/commit to produce vendor-signed access proofs.
|
|
798
|
+
|
|
799
|
+
Note: path is `tide-iga-provider`, not `tide-admin`. The JS admin client does not have this endpoint wired up — use raw HTTP.
|
|
800
|
+
|
|
801
|
+
**Critical requirement**: `templateId` is REQUIRED when creating a pending realm policy. Omitting it causes the request to fail.
|
|
802
|
+
|
|
803
|
+
**Agent implication**: The realm admin policy is a prerequisite for all encryption policy signing. Verify it exists before attempting to sign encryption policies.
|
|
804
|
+
|
|
805
|
+
**Anti-pattern**: Attempting to sign encryption policies without first setting up the realm admin policy. The ORKs will reject with `"Policy supplied has not been signed"` because there is no admin policy to attach in step 3 of the policy signing flow.
|
|
806
|
+
|
|
807
|
+
---
|
|
808
|
+
|
|
809
|
+
## Status Legend
|
|
810
|
+
|
|
811
|
+
- **VERIFIED** - Directly sourced from documentation or exemplar evidence (keylessh, forseti-crypto-quickstart, tidewarden, test-cases, CHANGE_REQUEST_API.md)
|
|
812
|
+
- **INFERRED** - Strongly implied by source material
|
|
813
|
+
- **ASSUMED** - Operator guidance where sources are silent
|
|
814
|
+
- **REQUIRES_RUNTIME_VALIDATION** - Single-app evidence; needs confirmation across apps
|
|
815
|
+
- **STILL_UNRESOLVED** - Open gap
|
|
816
|
+
- **PARTIALLY_RESOLVED** - Partial evidence; gaps remain
|