@private.me/xbind 3.0.1 → 3.0.2
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/README.md +55 -14
- package/dist-standalone/_deps/mldsa-wasm/dist/mldsa.js +1920 -1
- package/dist-standalone/_deps/shared/cjs/errors.js +729 -1
- package/dist-standalone/_deps/shared/cjs/index.js +463 -1
- package/dist-standalone/_deps/shared/cjs/types.js +315 -1
- package/dist-standalone/_deps/shared/errors.js +244 -1
- package/dist-standalone/_deps/shared/index.js +72 -1
- package/dist-standalone/_deps/shared/types.js +86 -1
- package/dist-standalone/_deps/ux-helpers/cjs/errors.js +1 -1
- package/dist-standalone/_deps/ux-helpers/cjs/index.js +1 -1
- package/dist-standalone/_deps/ux-helpers/cjs/pagination.js +1 -1
- package/dist-standalone/_deps/ux-helpers/cjs/progress.js +1 -1
- package/dist-standalone/_deps/ux-helpers/cjs/search.js +1 -1
- package/dist-standalone/_deps/ux-helpers/cjs/types.js +1 -1
- package/dist-standalone/_deps/ux-helpers/errors.js +1 -1
- package/dist-standalone/_deps/ux-helpers/index.js +1 -1
- package/dist-standalone/_deps/ux-helpers/pagination.js +1 -1
- package/dist-standalone/_deps/ux-helpers/progress.js +1 -1
- package/dist-standalone/_deps/ux-helpers/search.js +1 -1
- package/dist-standalone/_deps/xchange/auto-accept.js +1 -1
- package/dist-standalone/_deps/xchange/cjs/auto-accept.js +1 -1
- package/dist-standalone/_deps/xchange/cjs/errors.js +1 -1
- package/dist-standalone/_deps/xchange/cjs/index.js +1 -1
- package/dist-standalone/_deps/xchange/cjs/invite-client.js +1 -1
- package/dist-standalone/_deps/xchange/cjs/lazy-init.js +1 -1
- package/dist-standalone/_deps/xchange/cjs/trust-integration.js +1 -1
- package/dist-standalone/_deps/xchange/cjs/xchange.js +1 -1
- package/dist-standalone/_deps/xchange/errors.js +1 -1
- package/dist-standalone/_deps/xchange/index.js +1 -1
- package/dist-standalone/_deps/xchange/invite-client.js +1 -1
- package/dist-standalone/_deps/xchange/lazy-init.js +1 -1
- package/dist-standalone/_deps/xchange/trust-integration.js +1 -1
- package/dist-standalone/_deps/xchange/xchange.js +1 -1
- package/dist-standalone/_deps/xregistry/cjs/discovery.js +1 -1
- package/dist-standalone/_deps/xregistry/cjs/errors.js +1 -1
- package/dist-standalone/_deps/xregistry/cjs/index.js +1 -1
- package/dist-standalone/_deps/xregistry/cjs/registry.js +1 -1
- package/dist-standalone/_deps/xregistry/cjs/schema.js +1 -1
- package/dist-standalone/_deps/xregistry/cjs/types.js +1 -1
- package/dist-standalone/_deps/xregistry/discovery.js +1 -1
- package/dist-standalone/_deps/xregistry/errors.js +1 -1
- package/dist-standalone/_deps/xregistry/index.js +1 -1
- package/dist-standalone/_deps/xregistry/registry.js +1 -1
- package/dist-standalone/_deps/xregistry/schema.js +1 -1
- package/dist-standalone/_deps/xregistry/types.js +1 -1
- package/dist-standalone/agent-call.js +659 -1
- package/dist-standalone/agent-sdk.js +328 -1
- package/dist-standalone/agent.js +1800 -1
- package/dist-standalone/approval.js +193 -1
- package/dist-standalone/async-iterators.js +382 -1
- package/dist-standalone/auth.js +219 -1
- package/dist-standalone/auto-accept.js +229 -1
- package/dist-standalone/backup-config.js +201 -1
- package/dist-standalone/backup.js +326 -1
- package/dist-standalone/batch-operations.js +388 -1
- package/dist-standalone/cancellation.js +477 -1
- package/dist-standalone/checkpoint.js +186 -1
- package/dist-standalone/circuit-breaker.js +468 -1
- package/dist-standalone/cjs/agent-call.js +701 -1
- package/dist-standalone/cjs/agent-sdk.js +332 -1
- package/dist-standalone/cjs/agent.js +1837 -1
- package/dist-standalone/cjs/approval.js +199 -1
- package/dist-standalone/cjs/async-iterators.js +392 -1
- package/dist-standalone/cjs/auth.js +225 -1
- package/dist-standalone/cjs/auto-accept.js +233 -1
- package/dist-standalone/cjs/backup-config.js +207 -1
- package/dist-standalone/cjs/backup.js +330 -1
- package/dist-standalone/cjs/batch-operations.js +397 -1
- package/dist-standalone/cjs/cancellation.js +490 -1
- package/dist-standalone/cjs/checkpoint.js +193 -1
- package/dist-standalone/cjs/circuit-breaker.js +476 -1
- package/dist-standalone/cjs/cli/init.js +492 -1
- package/dist-standalone/cjs/config-validation.js +522 -1
- package/dist-standalone/cjs/connect.js +312 -1
- package/dist-standalone/cjs/connection-pool.js +506 -1
- package/dist-standalone/cjs/correlation-id.js +339 -1
- package/dist-standalone/cjs/crypto-utils.js +176 -1
- package/dist-standalone/cjs/debug-mode.js +534 -1
- package/dist-standalone/cjs/did-document.js +101 -1
- package/dist-standalone/cjs/did-privateme.js +130 -1
- package/dist-standalone/cjs/did-web.js +201 -1
- package/dist-standalone/cjs/discovery.js +462 -1
- package/dist-standalone/cjs/dual-mode.js +251 -1
- package/dist-standalone/cjs/email-templates.js +313 -1
- package/dist-standalone/cjs/email-transport.js +239 -1
- package/dist-standalone/cjs/envelope.js +538 -1
- package/dist-standalone/cjs/errors.js +913 -1
- package/dist-standalone/cjs/event-emitter.js +461 -1
- package/dist-standalone/cjs/gateway-state.js +55 -1
- package/dist-standalone/cjs/gateway-transport.js +120 -1
- package/dist-standalone/cjs/graceful-degradation.js +403 -1
- package/dist-standalone/cjs/guardrails.js +223 -1
- package/dist-standalone/cjs/health-check.js +336 -1
- package/dist-standalone/cjs/http-compat.js +272 -1
- package/dist-standalone/cjs/http-status-map.js +571 -1
- package/dist-standalone/cjs/identity.js +645 -1
- package/dist-standalone/cjs/index.js +406 -1
- package/dist-standalone/cjs/invitation.js +421 -1
- package/dist-standalone/cjs/invite.js +328 -1
- package/dist-standalone/cjs/key-agreement.js +335 -1
- package/dist-standalone/cjs/lazy-init.js +300 -1
- package/dist-standalone/cjs/logger.js +291 -1
- package/dist-standalone/cjs/mdns-discovery.js +202 -1
- package/dist-standalone/cjs/nonce-store.js +80 -1
- package/dist-standalone/cjs/pairing-manager.js +223 -1
- package/dist-standalone/cjs/plugin-system.js +264 -1
- package/dist-standalone/cjs/plugins/logging.js +168 -1
- package/dist-standalone/cjs/plugins/metrics.js +181 -1
- package/dist-standalone/cjs/plugins/validation.js +302 -1
- package/dist-standalone/cjs/policy.js +320 -1
- package/dist-standalone/cjs/progress-callbacks.js +583 -1
- package/dist-standalone/cjs/redis-nonce-store.js +76 -1
- package/dist-standalone/cjs/registry-middleware.js +50 -1
- package/dist-standalone/cjs/retry-strategies.js +544 -1
- package/dist-standalone/cjs/retry-transport.js +102 -1
- package/dist-standalone/cjs/runtime/browser.js +533 -1
- package/dist-standalone/cjs/runtime/edge.js +526 -1
- package/dist-standalone/cjs/runtime/react-native.js +394 -1
- package/dist-standalone/cjs/security-policy.js +245 -1
- package/dist-standalone/cjs/serialization.js +1040 -1
- package/dist-standalone/cjs/split-channel.js +225 -1
- package/dist-standalone/cjs/subscription-proof.js +230 -1
- package/dist-standalone/cjs/succession.js +148 -1
- package/dist-standalone/cjs/timeouts.js +412 -1
- package/dist-standalone/cjs/trace-context.js +424 -1
- package/dist-standalone/cjs/trace-spans.js +495 -1
- package/dist-standalone/cjs/transport.js +63 -1
- package/dist-standalone/cjs/trust-registry.js +991 -1
- package/dist-standalone/cjs/types/error-response.js +56 -1
- package/dist-standalone/cjs/vault-auth.js +178 -1
- package/dist-standalone/cjs/vault-store-loader.js +194 -1
- package/dist-standalone/cjs/verify.js +25 -1
- package/dist-standalone/cjs/version-info.js +543 -1
- package/dist-standalone/cjs/xfetch.js +340 -1
- package/dist-standalone/cli/init.js +455 -1
- package/dist-standalone/cli/setup.js +514 -1
- package/dist-standalone/cli/types.js +27 -1
- package/dist-standalone/cli/xbind.js +148 -1
- package/dist-standalone/config-validation.js +513 -1
- package/dist-standalone/connect.js +274 -1
- package/dist-standalone/connection-pool.js +500 -1
- package/dist-standalone/correlation-id.js +326 -1
- package/dist-standalone/crypto-utils.js +157 -1
- package/dist-standalone/debug-mode.js +510 -1
- package/dist-standalone/did-document.js +96 -1
- package/dist-standalone/did-privateme.js +121 -1
- package/dist-standalone/did-web.js +196 -1
- package/dist-standalone/discovery.js +458 -1
- package/dist-standalone/dual-mode.js +247 -1
- package/dist-standalone/email-templates.js +309 -1
- package/dist-standalone/email-transport.js +232 -1
- package/dist-standalone/envelope.js +525 -1
- package/dist-standalone/errors.js +896 -1
- package/dist-standalone/event-emitter.js +456 -1
- package/dist-standalone/gateway-state.js +51 -1
- package/dist-standalone/gateway-transport.js +116 -1
- package/dist-standalone/graceful-degradation.js +396 -1
- package/dist-standalone/guardrails.js +216 -1
- package/dist-standalone/health-check.js +332 -1
- package/dist-standalone/http-compat.js +267 -1
- package/dist-standalone/http-status-map.js +561 -1
- package/dist-standalone/identity.js +619 -1
- package/dist-standalone/index.js +78 -1
- package/dist-standalone/invitation.js +415 -1
- package/dist-standalone/invite.js +324 -1
- package/dist-standalone/key-agreement.js +325 -1
- package/dist-standalone/lazy-init.js +295 -1
- package/dist-standalone/logger.js +285 -1
- package/dist-standalone/mdns-discovery.js +195 -1
- package/dist-standalone/nonce-store.js +76 -1
- package/dist-standalone/pairing-manager.js +219 -1
- package/dist-standalone/plugin-system.js +257 -1
- package/dist-standalone/plugins/logging.js +163 -1
- package/dist-standalone/plugins/metrics.js +176 -1
- package/dist-standalone/plugins/validation.js +297 -1
- package/dist-standalone/policy.js +315 -1
- package/dist-standalone/progress-callbacks.js +576 -1
- package/dist-standalone/redis-nonce-store.js +72 -1
- package/dist-standalone/registry-middleware.js +47 -1
- package/dist-standalone/retry-strategies.js +534 -1
- package/dist-standalone/retry-transport.js +98 -1
- package/dist-standalone/runtime/browser.js +516 -1
- package/dist-standalone/runtime/edge.js +511 -1
- package/dist-standalone/runtime/react-native.js +383 -1
- package/dist-standalone/security-policy.js +239 -1
- package/dist-standalone/serialization.js +1031 -1
- package/dist-standalone/split-channel.js +219 -1
- package/dist-standalone/subscription-proof.js +224 -1
- package/dist-standalone/succession.js +142 -1
- package/dist-standalone/timeouts.js +398 -1
- package/dist-standalone/trace-context.js +414 -1
- package/dist-standalone/trace-spans.js +488 -1
- package/dist-standalone/transport.js +59 -1
- package/dist-standalone/trust-registry.js +950 -1
- package/dist-standalone/types/error-response.js +52 -1
- package/dist-standalone/vault-auth.js +174 -1
- package/dist-standalone/vault-store-loader.js +187 -1
- package/dist-standalone/verify.js +16 -1
- package/dist-standalone/version-info.js +530 -1
- package/dist-standalone/xfetch.js +335 -1
- package/package.json +4 -13
- package/share1.dat +0 -0
- package/dist-standalone/_deps/mldsa-wasm/LICENSE +0 -24
- package/dist-standalone/_deps/mldsa-wasm/package.json +0 -46
- package/dist-standalone/_deps/shared/cjs/package.json +0 -1
- package/dist-standalone/_deps/ux-helpers/cjs/package.json +0 -1
- package/dist-standalone/_deps/xchange/cjs/package.json +0 -1
- package/dist-standalone/_deps/xregistry/cjs/package.json +0 -1
- package/dist-standalone/cjs/package.json +0 -3
- package/dist-standalone/package.json +0 -10
|
@@ -1 +1,991 @@
|
|
|
1
|
-
"use strict";var __createBinding=this&&this.__createBinding||(Object.create?function(e,t,r,s){void 0===s&&(s=r);var i=Object.getOwnPropertyDescriptor(t,r);i&&!("get"in i?!t.__esModule:i.writable||i.configurable)||(i={enumerable:!0,get:function(){return t[r]}}),Object.defineProperty(e,s,i)}:function(e,t,r,s){void 0===s&&(s=r),e[s]=t[r]}),__setModuleDefault=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),__importStar=this&&this.__importStar||function(){var e=function(t){return e=Object.getOwnPropertyNames||function(e){var t=[];for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[t.length]=r);return t},e(t)};return function(t){if(t&&t.__esModule)return t;var r={};if(null!=t)for(var s=e(t),i=0;i<s.length;i++)"default"!==s[i]&&__createBinding(r,t,s[i]);return __setModuleDefault(r,t),r}}();Object.defineProperty(exports,"__esModule",{value:!0}),exports.FileTrustRegistry=exports.HttpTrustRegistry=exports.MemoryTrustRegistry=exports.RegistrationRateLimiter=void 0,exports.createEnterpriseTrustRegistry=createEnterpriseTrustRegistry;const shared_1=require("../_deps/shared/index.js"),fs=__importStar(require("node:fs/promises")),path=__importStar(require("node:path"));function isExpired(e){return!!e.expiresAt&&Date.now()>e.expiresAt}class RegistrationRateLimiter{perIPTimestamps=new Map;globalTimestamps=[];perIPLimit;globalLimit;windowMs;cleanupInterval=null;constructor(e=10,t=1e3,r=36e5){this.perIPLimit=e,this.globalLimit=t,this.windowMs=r,this.cleanupInterval=setInterval(()=>this.cleanup(),3e5)}checkLimit(e){const t=Date.now()-this.windowMs;if((this.perIPTimestamps.get(e)||[]).filter(e=>e>t).length>=this.perIPLimit)return!1;return!(this.globalTimestamps.filter(e=>e>t).length>=this.globalLimit)}recordRegistration(e){const t=Date.now(),r=this.perIPTimestamps.get(e)||[];r.push(t),this.perIPTimestamps.set(e,r),this.globalTimestamps.push(t)}getRemainingForIP(e){const t=Date.now()-this.windowMs,r=(this.perIPTimestamps.get(e)||[]).filter(e=>e>t);return Math.max(0,this.perIPLimit-r.length)}getRemainingGlobal(){const e=Date.now()-this.windowMs,t=this.globalTimestamps.filter(t=>t>e);return Math.max(0,this.globalLimit-t.length)}getResetTimeForIP(e){const t=this.perIPTimestamps.get(e)||[];if(0===t.length)return null;const r=t[0];return r?r+this.windowMs:null}cleanup(){const e=Date.now()-this.windowMs;for(const[t,r]of this.perIPTimestamps.entries()){const s=r.filter(t=>t>e);0===s.length?this.perIPTimestamps.delete(t):this.perIPTimestamps.set(t,s)}const t=this.globalTimestamps.filter(t=>t>e);this.globalTimestamps.length=0,this.globalTimestamps.push(...t)}destroy(){this.cleanupInterval&&(clearInterval(this.cleanupInterval),this.cleanupInterval=null)}reset(){this.perIPTimestamps.clear(),this.globalTimestamps.length=0}}exports.RegistrationRateLimiter=RegistrationRateLimiter;class MemoryTrustRegistry{entries=new Map;rateLimiter;constructor(e){e?.enableRateLimiting&&(this.rateLimiter=e.rateLimiter||new RegistrationRateLimiter)}async register(e,t,r,s,i,n,a,o,c,h,l,d,p,u){if(this.rateLimiter&&u&&!this.rateLimiter.checkLimit(u))return(0,shared_1.err)("RATE_LIMIT_EXCEEDED");if(this.entries.has(e))return(0,shared_1.err)("ALREADY_REGISTERED");const y=p?Date.now()+p:void 0;return this.entries.set(e,{did:e,publicKey:t,name:r,scopes:new Set(s??[]),receiveScopes:c?new Set(c):void 0,revoked:!1,rotation_sequence:1,x25519PublicKey:i,mlKemPublicKey:n,mlDsaPublicKey:a,xchange:o,sdkVersion:h,minEnvelopeVersion:l,maxEnvelopeVersion:d,expiresAt:y}),this.rateLimiter&&u&&this.rateLimiter.recordRegistration(u),(0,shared_1.ok)(void 0)}async resolve(e){const t=this.entries.get(e);return t?isExpired(t)?(0,shared_1.err)("EXPIRED"):t.revoked?(0,shared_1.err)("REVOKED"):(0,shared_1.ok)(t.publicKey):(0,shared_1.err)("NOT_FOUND")}async hasScope(e,t){const r=this.entries.get(e);return!(!r||isExpired(r)||r.revoked)&&r.scopes.has(t)}async hasReceiveScope(e,t){const r=this.entries.get(e);return!(!r||isExpired(r)||r.revoked)&&(!r.receiveScopes||r.receiveScopes.has(t))}async revoke(e){const t=this.entries.get(e);return t?(this.entries.set(e,{...t,revoked:!0}),(0,shared_1.ok)(void 0)):(0,shared_1.err)("NOT_FOUND")}async getEntry(e){const t=this.entries.get(e);return t?isExpired(t)?(0,shared_1.err)("EXPIRED"):(0,shared_1.ok)(t):(0,shared_1.err)("NOT_FOUND")}async updateScopes(e,t){const r=this.entries.get(e);return r?isExpired(r)?(0,shared_1.err)("EXPIRED"):r.revoked?(0,shared_1.err)("REVOKED"):(this.entries.set(e,{...r,scopes:new Set(t)}),(0,shared_1.ok)(void 0)):(0,shared_1.err)("NOT_FOUND")}async cleanup(){let e=0;const t=Date.now();for(const[r,s]of this.entries)s.expiresAt&&t>s.expiresAt&&(this.entries.delete(r),e++);return e}get size(){return this.entries.size}}exports.MemoryTrustRegistry=MemoryTrustRegistry;class HttpTrustRegistry{baseUrl;fetchFn;cacheTtlMs;cacheFailureMode;enablePush;bloomFilterSize;bloomFilterFpr;resolveCache=new Map;entryCache=new Map;constructor(e){this.baseUrl=e.baseUrl.replace(/\/$/,""),this.fetchFn=e.fetch??globalThis.fetch.bind(globalThis),this.cacheTtlMs=e.cacheTtlMs??3e4,this.cacheFailureMode=e.cacheFailureMode??"fail-secure",this.enablePush=e.enablePush??!1,this.bloomFilterSize=e.bloomFilterSize??1e4,this.bloomFilterFpr=e.bloomFilterFpr??.01}clearCache(){this.resolveCache.clear(),this.entryCache.clear()}async register(e,t,r,s,i,n,a,o,c,h,l,d,p,u){try{const u=await this.fetchFn(`${this.baseUrl}/registry/register`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({did:e,publicKey:Array.from(t),name:r,scopes:s??[],...c?{receiveScopes:c}:{},...i?{x25519PublicKey:Array.from(i)}:{},...n?{mlKemPublicKey:Array.from(n)}:{},...a?{mlDsaPublicKey:Array.from(a)}:{},...void 0!==o?{xchange:o}:{},...void 0!==h?{sdkVersion:h}:{},...void 0!==l?{minEnvelopeVersion:l}:{},...void 0!==d?{maxEnvelopeVersion:d}:{},...void 0!==p?{ttlMs:p}:{}})});return 409===u.status?(0,shared_1.err)("ALREADY_REGISTERED"):429===u.status?(0,shared_1.err)("RATE_LIMIT_EXCEEDED"):u.ok?(0,shared_1.ok)(void 0):(0,shared_1.err)("NETWORK_ERROR")}catch{return(0,shared_1.err)("NETWORK_ERROR")}}async resolve(e){if(this.cacheTtlMs>0){const t=this.resolveCache.get(e);if(t&&t.expiry>Date.now())return t.value}let t;try{const r=await this.fetchFn(`${this.baseUrl}/registry/resolve/${encodeURIComponent(e)}`);if(404===r.status)t=(0,shared_1.err)("NOT_FOUND");else if(408===r.status)t=(0,shared_1.err)("EXPIRED");else if(410===r.status)t=(0,shared_1.err)("REVOKED");else if(r.ok){const e=await r.json();t=(0,shared_1.ok)(new Uint8Array(e.publicKey))}else t=(0,shared_1.err)("NETWORK_ERROR")}catch{const r=this.resolveCache.get(e);if("fail-secure"===this.cacheFailureMode)t=(0,shared_1.err)("NETWORK_ERROR");else{if(r)return r.value;t=(0,shared_1.err)("NETWORK_ERROR")}}return this.cacheTtlMs>0&&this.resolveCache.set(e,{value:t,expiry:Date.now()+this.cacheTtlMs}),t}async hasScope(e,t){try{return(await this.fetchFn(`${this.baseUrl}/registry/scope/${encodeURIComponent(e)}/${encodeURIComponent(t)}`)).ok}catch{return!1}}async hasReceiveScope(e,t){try{return(await this.fetchFn(`${this.baseUrl}/registry/receive-scope/${encodeURIComponent(e)}/${encodeURIComponent(t)}`)).ok}catch{return!1}}async revoke(e){try{const t=await this.fetchFn(`${this.baseUrl}/registry/revoke/${encodeURIComponent(e)}`,{method:"POST"});return 404===t.status?(0,shared_1.err)("NOT_FOUND"):t.ok?(0,shared_1.ok)(void 0):(0,shared_1.err)("NETWORK_ERROR")}catch{return(0,shared_1.err)("NETWORK_ERROR")}}async getEntry(e){if(this.cacheTtlMs>0){const t=this.entryCache.get(e);if(t&&t.expiry>Date.now())return t.value}let t;try{const r=await this.fetchFn(`${this.baseUrl}/registry/entry/${encodeURIComponent(e)}`);if(404===r.status)t=(0,shared_1.err)("NOT_FOUND");else if(408===r.status)t=(0,shared_1.err)("EXPIRED");else if(r.ok){const e=await r.json();t=(0,shared_1.ok)({did:e.did,publicKey:new Uint8Array(e.publicKey),name:e.name,scopes:new Set(e.scopes),receiveScopes:e.receiveScopes?new Set(e.receiveScopes):void 0,revoked:e.revoked,rotation_sequence:e.rotation_sequence??1,x25519PublicKey:e.x25519PublicKey?new Uint8Array(e.x25519PublicKey):void 0,mlKemPublicKey:e.mlKemPublicKey?new Uint8Array(e.mlKemPublicKey):void 0,mlDsaPublicKey:e.mlDsaPublicKey?new Uint8Array(e.mlDsaPublicKey):void 0,xchange:e.xchange,sdkVersion:e.sdkVersion,minEnvelopeVersion:e.minEnvelopeVersion,maxEnvelopeVersion:e.maxEnvelopeVersion,expiresAt:e.expiresAt})}else t=(0,shared_1.err)("NETWORK_ERROR")}catch{t=(0,shared_1.err)("NETWORK_ERROR")}return this.cacheTtlMs>0&&this.entryCache.set(e,{value:t,expiry:Date.now()+this.cacheTtlMs}),t}async rotate(e,t,r,s){const i=await this.fetchFn(`${this.baseUrl}/registry/rotate`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({did:e,newPublicKey:Array.from(t),proof:Array.from(r),rotationSequence:s})});if(!i.ok)throw new Error(`Key rotation failed: ${i.status} ${i.statusText}`);this.resolveCache.delete(e),this.entryCache.delete(e)}async subscribe(e,t){if(!this.enablePush)throw new Error("Push notifications not enabled (set enablePush: true in HttpTrustRegistryOptions)");const r=new Set(e.map(e=>this.hashDid(e))),s=this.baseUrl.replace(/^http/,"ws")+"/trust/events",i=new globalThis.WebSocket(s);return i.addEventListener("open",()=>{i.send(JSON.stringify({type:"subscribe",dids:e,bloomSize:this.bloomFilterSize,bloomFpr:this.bloomFilterFpr}))}),i.addEventListener("message",e=>{try{const s=JSON.parse(e.data),i=this.hashDid(s.did);r.has(i)&&("revocation"!==s.type&&"succession"!==s.type||(this.resolveCache.delete(s.did),this.entryCache.delete(s.did)),t(s))}catch(e){console.warn("Failed to parse trust event:",e)}}),i.addEventListener("error",e=>{console.error("WebSocket error:",e)}),()=>{i.readyState===globalThis.WebSocket.OPEN&&i.close()}}hashDid(e){let t=0;for(let r=0;r<e.length;r++)t=(t<<5)-t+e.charCodeAt(r),t&=t;return t}async resumeSubscriptions(e){try{return(await this.fetchFn(`${this.baseUrl}/trust/resume-batch`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({proofs:e})})).ok?(0,shared_1.ok)(void 0):(0,shared_1.err)("NETWORK_ERROR")}catch{return(0,shared_1.err)("NETWORK_ERROR")}}async fetchCheckpoint(e){try{const t=await this.fetchFn(`${this.baseUrl}/registry/checkpoint/${encodeURIComponent(e)}`);if(404===t.status)return(0,shared_1.err)("NOT_FOUND");if(!t.ok)return(0,shared_1.err)("NETWORK_ERROR");const r=await t.json();return(0,shared_1.ok)(r)}catch{return(0,shared_1.err)("NETWORK_ERROR")}}async updateScopes(e,t){try{const r=await this.fetchFn(`${this.baseUrl}/registry/${encodeURIComponent(e)}/scopes`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({scopes:t})});return 404===r.status?(0,shared_1.err)("NOT_FOUND"):410===r.status?(0,shared_1.err)("REVOKED"):r.ok?(this.resolveCache.delete(e),this.entryCache.delete(e),(0,shared_1.ok)(void 0)):(0,shared_1.err)("NETWORK_ERROR")}catch{return(0,shared_1.err)("NETWORK_ERROR")}}}exports.HttpTrustRegistry=HttpTrustRegistry;class FileTrustRegistry{path;entries=new Map;initialized=!1;constructor(e){this.path=e.path}async init(){if(!this.initialized){try{await fs.mkdir(path.dirname(this.path),{recursive:!0});const e=(await fs.readFile(this.path,"utf-8").catch(()=>"")).split("\n").filter(e=>e.trim());for(const t of e){const e=JSON.parse(t);if("register"===e.type&&e.publicKey&&e.name)this.entries.set(e.did,{did:e.did,publicKey:new Uint8Array(e.publicKey),name:e.name,scopes:new Set(e.scopes??[]),receiveScopes:e.receiveScopes?new Set(e.receiveScopes):void 0,revoked:!1,rotation_sequence:e.rotation_sequence??1,x25519PublicKey:e.x25519PublicKey?new Uint8Array(e.x25519PublicKey):void 0,mlKemPublicKey:e.mlKemPublicKey?new Uint8Array(e.mlKemPublicKey):void 0,mlDsaPublicKey:e.mlDsaPublicKey?new Uint8Array(e.mlDsaPublicKey):void 0,xchange:e.xchange,sdkVersion:e.sdkVersion,minEnvelopeVersion:e.minEnvelopeVersion,maxEnvelopeVersion:e.maxEnvelopeVersion,expiresAt:e.expiresAt});else if("revoke"===e.type){const t=this.entries.get(e.did);t&&this.entries.set(e.did,{...t,revoked:!0})}else if("update-scopes"===e.type){const t=this.entries.get(e.did);t&&this.entries.set(e.did,{...t,scopes:new Set(e.scopes??[])})}else if("rotate"===e.type&&e.publicKey&&e.rotation_sequence){const t=this.entries.get(e.did);t&&e.rotation_sequence>t.rotation_sequence&&this.entries.set(e.did,{...t,publicKey:new Uint8Array(e.publicKey),rotation_sequence:e.rotation_sequence})}}}catch(e){}this.initialized=!0}}async append(e){await fs.appendFile(this.path,JSON.stringify(e)+"\n","utf-8")}async register(e,t,r,s,i,n,a,o,c,h,l,d,p,u){if(await this.init(),this.entries.has(e))return(0,shared_1.err)("ALREADY_REGISTERED");const y=p?Date.now()+p:void 0,m={did:e,publicKey:t,name:r,scopes:new Set(s??[]),receiveScopes:c?new Set(c):void 0,revoked:!1,rotation_sequence:1,x25519PublicKey:i,mlKemPublicKey:n,mlDsaPublicKey:a,xchange:o,sdkVersion:h,minEnvelopeVersion:l,maxEnvelopeVersion:d,expiresAt:y};return this.entries.set(e,m),await this.append({type:"register",did:e,publicKey:Array.from(t),name:r,scopes:s??[],rotation_sequence:1,...c?{receiveScopes:c}:{},...i?{x25519PublicKey:Array.from(i)}:{},...n?{mlKemPublicKey:Array.from(n)}:{},...a?{mlDsaPublicKey:Array.from(a)}:{},...void 0!==o?{xchange:o}:{},...void 0!==h?{sdkVersion:h}:{},...void 0!==l?{minEnvelopeVersion:l}:{},...void 0!==d?{maxEnvelopeVersion:d}:{},...void 0!==y?{expiresAt:y}:{}}),(0,shared_1.ok)(void 0)}async resolve(e){await this.init();const t=this.entries.get(e);return t?isExpired(t)?(0,shared_1.err)("EXPIRED"):t.revoked?(0,shared_1.err)("REVOKED"):(0,shared_1.ok)(t.publicKey):(0,shared_1.err)("NOT_FOUND")}async hasScope(e,t){await this.init();const r=this.entries.get(e);return!(!r||isExpired(r)||r.revoked)&&r.scopes.has(t)}async hasReceiveScope(e,t){await this.init();const r=this.entries.get(e);return!(!r||isExpired(r)||r.revoked)&&(!r.receiveScopes||r.receiveScopes.has(t))}async revoke(e){await this.init();const t=this.entries.get(e);return t?(this.entries.set(e,{...t,revoked:!0}),await this.append({type:"revoke",did:e}),(0,shared_1.ok)(void 0)):(0,shared_1.err)("NOT_FOUND")}async getEntry(e){await this.init();const t=this.entries.get(e);return t?isExpired(t)?(0,shared_1.err)("EXPIRED"):(0,shared_1.ok)(t):(0,shared_1.err)("NOT_FOUND")}async updateScopes(e,t){await this.init();const r=this.entries.get(e);return r?isExpired(r)?(0,shared_1.err)("EXPIRED"):r.revoked?(0,shared_1.err)("REVOKED"):(this.entries.set(e,{...r,scopes:new Set(t)}),await this.append({type:"update-scopes",did:e,scopes:t}),(0,shared_1.ok)(void 0)):(0,shared_1.err)("NOT_FOUND")}async cleanup(){await this.init();let e=0;const t=Date.now();for(const[r,s]of this.entries)s.expiresAt&&t>s.expiresAt&&(this.entries.delete(r),e++);return e}async rotate(e,t,r,s){await this.init();const i=this.entries.get(e);if(!i)throw new Error(`DID not found: ${e}`);if(s<=i.rotation_sequence)throw new Error(`Rotation sequence ${s} must be > current ${i.rotation_sequence} (rollback attack prevented)`);await this.append({type:"rotate",did:e,publicKey:Array.from(t),proof:Array.from(r),rotation_sequence:s,timestamp:Date.now()}),this.entries.set(e,{...i,publicKey:t,rotation_sequence:s})}get size(){return this.entries.size}}async function createEnterpriseTrustRegistry(e){let t;if("file"===e.storage){if(!e.path)throw new Error("FileTrustRegistry requires path option");t=new FileTrustRegistry({path:e.path})}else if("http"===e.storage){if(!e.baseUrl)throw new Error("HttpTrustRegistry requires baseUrl option");t=new HttpTrustRegistry({baseUrl:e.baseUrl})}else t=new MemoryTrustRegistry;if(e.preload)for(const r of e.preload)await t.register(r.did,r.publicKey,r.name,r.scopes,r.x25519PublicKey,r.mlKemPublicKey,r.mlDsaPublicKey,r.xchange,r.receiveScopes,r.sdkVersion,r.minEnvelopeVersion,r.maxEnvelopeVersion);return t}exports.FileTrustRegistry=FileTrustRegistry;
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.FileTrustRegistry = exports.HttpTrustRegistry = exports.MemoryTrustRegistry = exports.RegistrationRateLimiter = void 0;
|
|
37
|
+
exports.createEnterpriseTrustRegistry = createEnterpriseTrustRegistry;
|
|
38
|
+
const shared_1 = require("../_deps/shared/index.js");
|
|
39
|
+
const fs = __importStar(require("node:fs/promises"));
|
|
40
|
+
const path = __importStar(require("node:path"));
|
|
41
|
+
/* ── Types ── */
|
|
42
|
+
/** Check if a registry entry has expired. */
|
|
43
|
+
function isExpired(entry) {
|
|
44
|
+
if (!entry.expiresAt)
|
|
45
|
+
return false;
|
|
46
|
+
return Date.now() > entry.expiresAt;
|
|
47
|
+
}
|
|
48
|
+
/* ── Rate Limiting ── */
|
|
49
|
+
/**
|
|
50
|
+
* Rate limiter for DID registration endpoints.
|
|
51
|
+
*
|
|
52
|
+
* Implements sliding window algorithm with:
|
|
53
|
+
* - Per-IP limit: 10 registrations/hour
|
|
54
|
+
* - Global limit: 1000 registrations/hour
|
|
55
|
+
*
|
|
56
|
+
* Prevents DID registration spam attacks (SCALE-1).
|
|
57
|
+
*/
|
|
58
|
+
class RegistrationRateLimiter {
|
|
59
|
+
perIPTimestamps = new Map();
|
|
60
|
+
globalTimestamps = [];
|
|
61
|
+
perIPLimit;
|
|
62
|
+
globalLimit;
|
|
63
|
+
windowMs;
|
|
64
|
+
cleanupInterval = null;
|
|
65
|
+
/**
|
|
66
|
+
* Create a new registration rate limiter.
|
|
67
|
+
*
|
|
68
|
+
* @param perIPLimit - Maximum registrations per IP in time window (default: 10)
|
|
69
|
+
* @param globalLimit - Maximum global registrations in time window (default: 1000)
|
|
70
|
+
* @param windowMs - Time window in milliseconds (default: 3600000 = 1 hour)
|
|
71
|
+
*/
|
|
72
|
+
constructor(perIPLimit = 10, globalLimit = 1000, windowMs = 3600000) {
|
|
73
|
+
this.perIPLimit = perIPLimit;
|
|
74
|
+
this.globalLimit = globalLimit;
|
|
75
|
+
this.windowMs = windowMs;
|
|
76
|
+
// Cleanup old timestamps every 5 minutes
|
|
77
|
+
this.cleanupInterval = setInterval(() => this.cleanup(), 300000);
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Check if registration is allowed for the given IP.
|
|
81
|
+
*
|
|
82
|
+
* @param ip - Client IP address
|
|
83
|
+
* @returns True if allowed, false if rate limited
|
|
84
|
+
*/
|
|
85
|
+
checkLimit(ip) {
|
|
86
|
+
const now = Date.now();
|
|
87
|
+
const windowStart = now - this.windowMs;
|
|
88
|
+
// Check per-IP limit
|
|
89
|
+
const ipTimestamps = this.perIPTimestamps.get(ip) || [];
|
|
90
|
+
const recentIPRequests = ipTimestamps.filter((t) => t > windowStart);
|
|
91
|
+
if (recentIPRequests.length >= this.perIPLimit) {
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
// Check global limit
|
|
95
|
+
const recentGlobalRequests = this.globalTimestamps.filter((t) => t > windowStart);
|
|
96
|
+
if (recentGlobalRequests.length >= this.globalLimit) {
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Record a registration attempt.
|
|
103
|
+
*
|
|
104
|
+
* Call this AFTER successful registration to avoid counting failures.
|
|
105
|
+
*
|
|
106
|
+
* @param ip - Client IP address
|
|
107
|
+
*/
|
|
108
|
+
recordRegistration(ip) {
|
|
109
|
+
const now = Date.now();
|
|
110
|
+
// Record per-IP
|
|
111
|
+
const ipTimestamps = this.perIPTimestamps.get(ip) || [];
|
|
112
|
+
ipTimestamps.push(now);
|
|
113
|
+
this.perIPTimestamps.set(ip, ipTimestamps);
|
|
114
|
+
// Record global
|
|
115
|
+
this.globalTimestamps.push(now);
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Get remaining registrations for an IP.
|
|
119
|
+
*
|
|
120
|
+
* @param ip - Client IP address
|
|
121
|
+
* @returns Remaining registrations allowed
|
|
122
|
+
*/
|
|
123
|
+
getRemainingForIP(ip) {
|
|
124
|
+
const now = Date.now();
|
|
125
|
+
const windowStart = now - this.windowMs;
|
|
126
|
+
const ipTimestamps = this.perIPTimestamps.get(ip) || [];
|
|
127
|
+
const recentIPRequests = ipTimestamps.filter((t) => t > windowStart);
|
|
128
|
+
return Math.max(0, this.perIPLimit - recentIPRequests.length);
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Get remaining global registrations.
|
|
132
|
+
*
|
|
133
|
+
* @returns Remaining global registrations allowed
|
|
134
|
+
*/
|
|
135
|
+
getRemainingGlobal() {
|
|
136
|
+
const now = Date.now();
|
|
137
|
+
const windowStart = now - this.windowMs;
|
|
138
|
+
const recentGlobalRequests = this.globalTimestamps.filter((t) => t > windowStart);
|
|
139
|
+
return Math.max(0, this.globalLimit - recentGlobalRequests.length);
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Get reset time for an IP's rate limit.
|
|
143
|
+
*
|
|
144
|
+
* @param ip - Client IP address
|
|
145
|
+
* @returns Unix timestamp (ms) when limit resets, or null if not rate limited
|
|
146
|
+
*/
|
|
147
|
+
getResetTimeForIP(ip) {
|
|
148
|
+
const ipTimestamps = this.perIPTimestamps.get(ip) || [];
|
|
149
|
+
if (ipTimestamps.length === 0)
|
|
150
|
+
return null;
|
|
151
|
+
const oldestTimestamp = ipTimestamps[0];
|
|
152
|
+
if (!oldestTimestamp)
|
|
153
|
+
return null;
|
|
154
|
+
return oldestTimestamp + this.windowMs;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Cleanup old timestamps to prevent memory leaks.
|
|
158
|
+
*
|
|
159
|
+
* Removes entries with no requests in the last window period.
|
|
160
|
+
*/
|
|
161
|
+
cleanup() {
|
|
162
|
+
const now = Date.now();
|
|
163
|
+
const windowStart = now - this.windowMs;
|
|
164
|
+
// Cleanup per-IP timestamps
|
|
165
|
+
for (const [ip, timestamps] of this.perIPTimestamps.entries()) {
|
|
166
|
+
const recentRequests = timestamps.filter((t) => t > windowStart);
|
|
167
|
+
if (recentRequests.length === 0) {
|
|
168
|
+
this.perIPTimestamps.delete(ip);
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
this.perIPTimestamps.set(ip, recentRequests);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
// Cleanup global timestamps
|
|
175
|
+
const recentGlobalRequests = this.globalTimestamps.filter((t) => t > windowStart);
|
|
176
|
+
this.globalTimestamps.length = 0;
|
|
177
|
+
this.globalTimestamps.push(...recentGlobalRequests);
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Stop cleanup interval (for testing or shutdown).
|
|
181
|
+
*/
|
|
182
|
+
destroy() {
|
|
183
|
+
if (this.cleanupInterval) {
|
|
184
|
+
clearInterval(this.cleanupInterval);
|
|
185
|
+
this.cleanupInterval = null;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Reset all rate limits (for testing).
|
|
190
|
+
*/
|
|
191
|
+
reset() {
|
|
192
|
+
this.perIPTimestamps.clear();
|
|
193
|
+
this.globalTimestamps.length = 0;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
exports.RegistrationRateLimiter = RegistrationRateLimiter;
|
|
197
|
+
/* ── Memory Implementation ── */
|
|
198
|
+
/**
|
|
199
|
+
* In-memory trust registry for development and testing.
|
|
200
|
+
*/
|
|
201
|
+
class MemoryTrustRegistry {
|
|
202
|
+
entries = new Map();
|
|
203
|
+
rateLimiter;
|
|
204
|
+
constructor(opts) {
|
|
205
|
+
if (opts?.enableRateLimiting) {
|
|
206
|
+
this.rateLimiter = opts.rateLimiter || new RegistrationRateLimiter();
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
async register(did, publicKey, name, scopes, x25519PublicKey, mlKemPublicKey, mlDsaPublicKey, xchange, receiveScopes, sdkVersion, minEnvelopeVersion, maxEnvelopeVersion, ttlMs, clientIP) {
|
|
210
|
+
// Check rate limit if enabled
|
|
211
|
+
if (this.rateLimiter && clientIP) {
|
|
212
|
+
if (!this.rateLimiter.checkLimit(clientIP)) {
|
|
213
|
+
return (0, shared_1.err)('RATE_LIMIT_EXCEEDED');
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
if (this.entries.has(did))
|
|
217
|
+
return (0, shared_1.err)('ALREADY_REGISTERED');
|
|
218
|
+
const expiresAt = ttlMs ? Date.now() + ttlMs : undefined;
|
|
219
|
+
this.entries.set(did, {
|
|
220
|
+
did,
|
|
221
|
+
publicKey,
|
|
222
|
+
name,
|
|
223
|
+
scopes: new Set(scopes ?? []),
|
|
224
|
+
receiveScopes: receiveScopes ? new Set(receiveScopes) : undefined,
|
|
225
|
+
revoked: false,
|
|
226
|
+
rotation_sequence: 1, // Initial sequence starts at 1
|
|
227
|
+
x25519PublicKey,
|
|
228
|
+
mlKemPublicKey,
|
|
229
|
+
mlDsaPublicKey,
|
|
230
|
+
xchange,
|
|
231
|
+
sdkVersion,
|
|
232
|
+
minEnvelopeVersion,
|
|
233
|
+
maxEnvelopeVersion,
|
|
234
|
+
expiresAt,
|
|
235
|
+
});
|
|
236
|
+
// Record registration after success
|
|
237
|
+
if (this.rateLimiter && clientIP) {
|
|
238
|
+
this.rateLimiter.recordRegistration(clientIP);
|
|
239
|
+
}
|
|
240
|
+
return (0, shared_1.ok)(undefined);
|
|
241
|
+
}
|
|
242
|
+
async resolve(did) {
|
|
243
|
+
const entry = this.entries.get(did);
|
|
244
|
+
if (!entry)
|
|
245
|
+
return (0, shared_1.err)('NOT_FOUND');
|
|
246
|
+
if (isExpired(entry))
|
|
247
|
+
return (0, shared_1.err)('EXPIRED');
|
|
248
|
+
if (entry.revoked)
|
|
249
|
+
return (0, shared_1.err)('REVOKED');
|
|
250
|
+
return (0, shared_1.ok)(entry.publicKey);
|
|
251
|
+
}
|
|
252
|
+
async hasScope(did, scope) {
|
|
253
|
+
const entry = this.entries.get(did);
|
|
254
|
+
if (!entry || isExpired(entry) || entry.revoked)
|
|
255
|
+
return false;
|
|
256
|
+
return entry.scopes.has(scope);
|
|
257
|
+
}
|
|
258
|
+
async hasReceiveScope(did, scope) {
|
|
259
|
+
const entry = this.entries.get(did);
|
|
260
|
+
if (!entry || isExpired(entry) || entry.revoked)
|
|
261
|
+
return false;
|
|
262
|
+
// Undefined = accept all scopes (backward compatibility)
|
|
263
|
+
if (!entry.receiveScopes)
|
|
264
|
+
return true;
|
|
265
|
+
return entry.receiveScopes.has(scope);
|
|
266
|
+
}
|
|
267
|
+
async revoke(did) {
|
|
268
|
+
const entry = this.entries.get(did);
|
|
269
|
+
if (!entry)
|
|
270
|
+
return (0, shared_1.err)('NOT_FOUND');
|
|
271
|
+
this.entries.set(did, { ...entry, revoked: true });
|
|
272
|
+
return (0, shared_1.ok)(undefined);
|
|
273
|
+
}
|
|
274
|
+
async getEntry(did) {
|
|
275
|
+
const entry = this.entries.get(did);
|
|
276
|
+
if (!entry)
|
|
277
|
+
return (0, shared_1.err)('NOT_FOUND');
|
|
278
|
+
if (isExpired(entry))
|
|
279
|
+
return (0, shared_1.err)('EXPIRED');
|
|
280
|
+
return (0, shared_1.ok)(entry);
|
|
281
|
+
}
|
|
282
|
+
async updateScopes(did, scopes) {
|
|
283
|
+
const entry = this.entries.get(did);
|
|
284
|
+
if (!entry)
|
|
285
|
+
return (0, shared_1.err)('NOT_FOUND');
|
|
286
|
+
if (isExpired(entry))
|
|
287
|
+
return (0, shared_1.err)('EXPIRED');
|
|
288
|
+
if (entry.revoked)
|
|
289
|
+
return (0, shared_1.err)('REVOKED');
|
|
290
|
+
this.entries.set(did, { ...entry, scopes: new Set(scopes) });
|
|
291
|
+
return (0, shared_1.ok)(undefined);
|
|
292
|
+
}
|
|
293
|
+
/** Remove all expired entries from the registry. */
|
|
294
|
+
async cleanup() {
|
|
295
|
+
let removed = 0;
|
|
296
|
+
const now = Date.now();
|
|
297
|
+
for (const [did, entry] of this.entries) {
|
|
298
|
+
if (entry.expiresAt && now > entry.expiresAt) {
|
|
299
|
+
this.entries.delete(did);
|
|
300
|
+
removed++;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
return removed;
|
|
304
|
+
}
|
|
305
|
+
/** Number of entries (for testing). */
|
|
306
|
+
get size() {
|
|
307
|
+
return this.entries.size;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
exports.MemoryTrustRegistry = MemoryTrustRegistry;
|
|
311
|
+
/**
|
|
312
|
+
* HTTP-backed trust registry for production use.
|
|
313
|
+
* Delegates to a remote atelier.xail.io service.
|
|
314
|
+
*/
|
|
315
|
+
class HttpTrustRegistry {
|
|
316
|
+
baseUrl;
|
|
317
|
+
fetchFn;
|
|
318
|
+
cacheTtlMs;
|
|
319
|
+
cacheFailureMode;
|
|
320
|
+
enablePush;
|
|
321
|
+
bloomFilterSize;
|
|
322
|
+
bloomFilterFpr;
|
|
323
|
+
resolveCache = new Map();
|
|
324
|
+
entryCache = new Map();
|
|
325
|
+
constructor(opts) {
|
|
326
|
+
this.baseUrl = opts.baseUrl.replace(/\/$/, '');
|
|
327
|
+
this.fetchFn = opts.fetch ?? globalThis.fetch.bind(globalThis);
|
|
328
|
+
this.cacheTtlMs = opts.cacheTtlMs ?? 30_000;
|
|
329
|
+
this.cacheFailureMode = opts.cacheFailureMode ?? 'fail-secure';
|
|
330
|
+
this.enablePush = opts.enablePush ?? false;
|
|
331
|
+
this.bloomFilterSize = opts.bloomFilterSize ?? 10_000;
|
|
332
|
+
this.bloomFilterFpr = opts.bloomFilterFpr ?? 0.01;
|
|
333
|
+
}
|
|
334
|
+
/** Clear all cached entries. Call after registration or revocation. */
|
|
335
|
+
clearCache() {
|
|
336
|
+
this.resolveCache.clear();
|
|
337
|
+
this.entryCache.clear();
|
|
338
|
+
}
|
|
339
|
+
async register(did, publicKey, name, scopes, x25519PublicKey, mlKemPublicKey, mlDsaPublicKey, xchange, receiveScopes, sdkVersion, minEnvelopeVersion, maxEnvelopeVersion, ttlMs, clientIP) {
|
|
340
|
+
try {
|
|
341
|
+
const res = await this.fetchFn(`${this.baseUrl}/registry/register`, {
|
|
342
|
+
method: 'POST',
|
|
343
|
+
headers: { 'Content-Type': 'application/json' },
|
|
344
|
+
body: JSON.stringify({
|
|
345
|
+
did,
|
|
346
|
+
publicKey: Array.from(publicKey),
|
|
347
|
+
name,
|
|
348
|
+
scopes: scopes ?? [],
|
|
349
|
+
...(receiveScopes
|
|
350
|
+
? { receiveScopes }
|
|
351
|
+
: {}),
|
|
352
|
+
...(x25519PublicKey
|
|
353
|
+
? { x25519PublicKey: Array.from(x25519PublicKey) }
|
|
354
|
+
: {}),
|
|
355
|
+
...(mlKemPublicKey
|
|
356
|
+
? { mlKemPublicKey: Array.from(mlKemPublicKey) }
|
|
357
|
+
: {}),
|
|
358
|
+
...(mlDsaPublicKey
|
|
359
|
+
? { mlDsaPublicKey: Array.from(mlDsaPublicKey) }
|
|
360
|
+
: {}),
|
|
361
|
+
...(xchange !== undefined
|
|
362
|
+
? { xchange }
|
|
363
|
+
: {}),
|
|
364
|
+
...(sdkVersion !== undefined
|
|
365
|
+
? { sdkVersion }
|
|
366
|
+
: {}),
|
|
367
|
+
...(minEnvelopeVersion !== undefined
|
|
368
|
+
? { minEnvelopeVersion }
|
|
369
|
+
: {}),
|
|
370
|
+
...(maxEnvelopeVersion !== undefined
|
|
371
|
+
? { maxEnvelopeVersion }
|
|
372
|
+
: {}),
|
|
373
|
+
...(ttlMs !== undefined
|
|
374
|
+
? { ttlMs }
|
|
375
|
+
: {}),
|
|
376
|
+
}),
|
|
377
|
+
});
|
|
378
|
+
if (res.status === 409)
|
|
379
|
+
return (0, shared_1.err)('ALREADY_REGISTERED');
|
|
380
|
+
if (res.status === 429)
|
|
381
|
+
return (0, shared_1.err)('RATE_LIMIT_EXCEEDED');
|
|
382
|
+
if (!res.ok)
|
|
383
|
+
return (0, shared_1.err)('NETWORK_ERROR');
|
|
384
|
+
return (0, shared_1.ok)(undefined);
|
|
385
|
+
}
|
|
386
|
+
catch {
|
|
387
|
+
return (0, shared_1.err)('NETWORK_ERROR');
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
async resolve(did) {
|
|
391
|
+
// Check cache first
|
|
392
|
+
if (this.cacheTtlMs > 0) {
|
|
393
|
+
const cached = this.resolveCache.get(did);
|
|
394
|
+
if (cached && cached.expiry > Date.now())
|
|
395
|
+
return cached.value;
|
|
396
|
+
}
|
|
397
|
+
let result;
|
|
398
|
+
try {
|
|
399
|
+
const res = await this.fetchFn(`${this.baseUrl}/registry/resolve/${encodeURIComponent(did)}`);
|
|
400
|
+
if (res.status === 404)
|
|
401
|
+
result = (0, shared_1.err)('NOT_FOUND');
|
|
402
|
+
else if (res.status === 408)
|
|
403
|
+
result = (0, shared_1.err)('EXPIRED');
|
|
404
|
+
else if (res.status === 410)
|
|
405
|
+
result = (0, shared_1.err)('REVOKED');
|
|
406
|
+
else if (!res.ok)
|
|
407
|
+
result = (0, shared_1.err)('NETWORK_ERROR');
|
|
408
|
+
else {
|
|
409
|
+
const data = (await res.json());
|
|
410
|
+
result = (0, shared_1.ok)(new Uint8Array(data.publicKey));
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
catch {
|
|
414
|
+
// Network failure - apply cache failure mode
|
|
415
|
+
const staleCache = this.resolveCache.get(did);
|
|
416
|
+
if (this.cacheFailureMode === 'fail-secure') {
|
|
417
|
+
// Fail-secure: reject on cache refresh failure (default)
|
|
418
|
+
result = (0, shared_1.err)('NETWORK_ERROR');
|
|
419
|
+
}
|
|
420
|
+
else {
|
|
421
|
+
// Fail-open: accept stale cache if available
|
|
422
|
+
if (staleCache) {
|
|
423
|
+
// Return stale cached value
|
|
424
|
+
return staleCache.value;
|
|
425
|
+
}
|
|
426
|
+
result = (0, shared_1.err)('NETWORK_ERROR');
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
// Update cache with fresh result (only if cache enabled)
|
|
430
|
+
if (this.cacheTtlMs > 0) {
|
|
431
|
+
this.resolveCache.set(did, { value: result, expiry: Date.now() + this.cacheTtlMs });
|
|
432
|
+
}
|
|
433
|
+
return result;
|
|
434
|
+
}
|
|
435
|
+
async hasScope(did, scope) {
|
|
436
|
+
try {
|
|
437
|
+
const res = await this.fetchFn(`${this.baseUrl}/registry/scope/${encodeURIComponent(did)}/${encodeURIComponent(scope)}`);
|
|
438
|
+
return res.ok;
|
|
439
|
+
}
|
|
440
|
+
catch {
|
|
441
|
+
return false;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
async hasReceiveScope(did, scope) {
|
|
445
|
+
try {
|
|
446
|
+
const res = await this.fetchFn(`${this.baseUrl}/registry/receive-scope/${encodeURIComponent(did)}/${encodeURIComponent(scope)}`);
|
|
447
|
+
return res.ok;
|
|
448
|
+
}
|
|
449
|
+
catch {
|
|
450
|
+
return false;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
async revoke(did) {
|
|
454
|
+
try {
|
|
455
|
+
const res = await this.fetchFn(`${this.baseUrl}/registry/revoke/${encodeURIComponent(did)}`, { method: 'POST' });
|
|
456
|
+
if (res.status === 404)
|
|
457
|
+
return (0, shared_1.err)('NOT_FOUND');
|
|
458
|
+
if (!res.ok)
|
|
459
|
+
return (0, shared_1.err)('NETWORK_ERROR');
|
|
460
|
+
return (0, shared_1.ok)(undefined);
|
|
461
|
+
}
|
|
462
|
+
catch {
|
|
463
|
+
return (0, shared_1.err)('NETWORK_ERROR');
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
async getEntry(did) {
|
|
467
|
+
if (this.cacheTtlMs > 0) {
|
|
468
|
+
const cached = this.entryCache.get(did);
|
|
469
|
+
if (cached && cached.expiry > Date.now())
|
|
470
|
+
return cached.value;
|
|
471
|
+
}
|
|
472
|
+
let result;
|
|
473
|
+
try {
|
|
474
|
+
const res = await this.fetchFn(`${this.baseUrl}/registry/entry/${encodeURIComponent(did)}`);
|
|
475
|
+
if (res.status === 404)
|
|
476
|
+
result = (0, shared_1.err)('NOT_FOUND');
|
|
477
|
+
else if (res.status === 408)
|
|
478
|
+
result = (0, shared_1.err)('EXPIRED');
|
|
479
|
+
else if (!res.ok)
|
|
480
|
+
result = (0, shared_1.err)('NETWORK_ERROR');
|
|
481
|
+
else {
|
|
482
|
+
const data = (await res.json());
|
|
483
|
+
result = (0, shared_1.ok)({
|
|
484
|
+
did: data.did,
|
|
485
|
+
publicKey: new Uint8Array(data.publicKey),
|
|
486
|
+
name: data.name,
|
|
487
|
+
scopes: new Set(data.scopes),
|
|
488
|
+
receiveScopes: data.receiveScopes ? new Set(data.receiveScopes) : undefined,
|
|
489
|
+
revoked: data.revoked,
|
|
490
|
+
rotation_sequence: data.rotation_sequence ?? 1,
|
|
491
|
+
x25519PublicKey: data.x25519PublicKey
|
|
492
|
+
? new Uint8Array(data.x25519PublicKey)
|
|
493
|
+
: undefined,
|
|
494
|
+
mlKemPublicKey: data.mlKemPublicKey
|
|
495
|
+
? new Uint8Array(data.mlKemPublicKey)
|
|
496
|
+
: undefined,
|
|
497
|
+
mlDsaPublicKey: data.mlDsaPublicKey
|
|
498
|
+
? new Uint8Array(data.mlDsaPublicKey)
|
|
499
|
+
: undefined,
|
|
500
|
+
xchange: data.xchange,
|
|
501
|
+
sdkVersion: data.sdkVersion,
|
|
502
|
+
minEnvelopeVersion: data.minEnvelopeVersion,
|
|
503
|
+
maxEnvelopeVersion: data.maxEnvelopeVersion,
|
|
504
|
+
expiresAt: data.expiresAt,
|
|
505
|
+
});
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
catch {
|
|
509
|
+
result = (0, shared_1.err)('NETWORK_ERROR');
|
|
510
|
+
}
|
|
511
|
+
if (this.cacheTtlMs > 0) {
|
|
512
|
+
this.entryCache.set(did, { value: result, expiry: Date.now() + this.cacheTtlMs });
|
|
513
|
+
}
|
|
514
|
+
return result;
|
|
515
|
+
}
|
|
516
|
+
/**
|
|
517
|
+
* Rotate a DID to a new public key with cryptographic proof.
|
|
518
|
+
*
|
|
519
|
+
* Sends rotation request to gateway and invalidates local cache.
|
|
520
|
+
*
|
|
521
|
+
* @param did - DID being rotated (old key)
|
|
522
|
+
* @param newPublicKey - New public key bytes
|
|
523
|
+
* @param proof - Succession announcement (dual signatures from old and new keys)
|
|
524
|
+
* @param rotationSequence - Monotonically increasing sequence number (prevents rollback)
|
|
525
|
+
*/
|
|
526
|
+
async rotate(did, newPublicKey, proof, rotationSequence) {
|
|
527
|
+
const res = await this.fetchFn(`${this.baseUrl}/registry/rotate`, {
|
|
528
|
+
method: 'POST',
|
|
529
|
+
headers: { 'Content-Type': 'application/json' },
|
|
530
|
+
body: JSON.stringify({
|
|
531
|
+
did,
|
|
532
|
+
newPublicKey: Array.from(newPublicKey),
|
|
533
|
+
proof: Array.from(proof),
|
|
534
|
+
rotationSequence,
|
|
535
|
+
}),
|
|
536
|
+
});
|
|
537
|
+
if (!res.ok) {
|
|
538
|
+
throw new Error(`Key rotation failed: ${res.status} ${res.statusText}`);
|
|
539
|
+
}
|
|
540
|
+
// Invalidate cache for this DID
|
|
541
|
+
this.resolveCache.delete(did);
|
|
542
|
+
this.entryCache.delete(did);
|
|
543
|
+
}
|
|
544
|
+
/**
|
|
545
|
+
* Subscribe to real-time trust events (revocation, key rotation).
|
|
546
|
+
*
|
|
547
|
+
* Creates a bloom filter from DIDs and connects WebSocket to gateway.
|
|
548
|
+
* Events matching subscribed DIDs trigger the callback and invalidate cache.
|
|
549
|
+
*
|
|
550
|
+
* @param dids - Array of DIDs to monitor
|
|
551
|
+
* @param callback - Function called when trust events occur
|
|
552
|
+
* @returns Unsubscribe function to stop watching
|
|
553
|
+
*
|
|
554
|
+
* @throws Error if push notifications not enabled (enablePush: true)
|
|
555
|
+
*/
|
|
556
|
+
async subscribe(dids, callback) {
|
|
557
|
+
if (!this.enablePush) {
|
|
558
|
+
throw new Error('Push notifications not enabled (set enablePush: true in HttpTrustRegistryOptions)');
|
|
559
|
+
}
|
|
560
|
+
// Simple bloom filter: hash each DID to track subscriptions
|
|
561
|
+
// Production implementation would use full bloom filter library
|
|
562
|
+
const bloomSet = new Set(dids.map(did => this.hashDid(did)));
|
|
563
|
+
// Convert HTTP URL to WebSocket URL
|
|
564
|
+
const wsUrl = this.baseUrl.replace(/^http/, 'ws') + '/trust/events';
|
|
565
|
+
// SAFETY: WebSocket is a standard browser/Node.js API
|
|
566
|
+
const ws = new globalThis.WebSocket(wsUrl);
|
|
567
|
+
ws.addEventListener('open', () => {
|
|
568
|
+
// Send subscription with bloom filter
|
|
569
|
+
ws.send(JSON.stringify({
|
|
570
|
+
type: 'subscribe',
|
|
571
|
+
dids: dids, // Simple implementation sends full DID list
|
|
572
|
+
bloomSize: this.bloomFilterSize,
|
|
573
|
+
bloomFpr: this.bloomFilterFpr,
|
|
574
|
+
}));
|
|
575
|
+
});
|
|
576
|
+
ws.addEventListener('message', (event) => {
|
|
577
|
+
try {
|
|
578
|
+
const trustEvent = JSON.parse(event.data);
|
|
579
|
+
// Check if event DID matches our subscription
|
|
580
|
+
const eventHash = this.hashDid(trustEvent.did);
|
|
581
|
+
if (bloomSet.has(eventHash)) {
|
|
582
|
+
// Invalidate cache for this DID
|
|
583
|
+
if (trustEvent.type === 'revocation' || trustEvent.type === 'succession') {
|
|
584
|
+
this.resolveCache.delete(trustEvent.did);
|
|
585
|
+
this.entryCache.delete(trustEvent.did);
|
|
586
|
+
}
|
|
587
|
+
// Notify callback
|
|
588
|
+
callback(trustEvent);
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
catch (error) {
|
|
592
|
+
// Ignore malformed events
|
|
593
|
+
console.warn('Failed to parse trust event:', error);
|
|
594
|
+
}
|
|
595
|
+
});
|
|
596
|
+
ws.addEventListener('error', (error) => {
|
|
597
|
+
console.error('WebSocket error:', error);
|
|
598
|
+
});
|
|
599
|
+
// Return unsubscribe function
|
|
600
|
+
return () => {
|
|
601
|
+
if (ws.readyState === globalThis.WebSocket.OPEN) {
|
|
602
|
+
ws.close();
|
|
603
|
+
}
|
|
604
|
+
};
|
|
605
|
+
}
|
|
606
|
+
/**
|
|
607
|
+
* Simple hash function for bloom filter (DID → number).
|
|
608
|
+
* Production implementation would use proper bloom filter hashing.
|
|
609
|
+
*/
|
|
610
|
+
hashDid(did) {
|
|
611
|
+
let hash = 0;
|
|
612
|
+
for (let i = 0; i < did.length; i++) {
|
|
613
|
+
hash = ((hash << 5) - hash) + did.charCodeAt(i);
|
|
614
|
+
hash = hash & hash; // Convert to 32-bit integer
|
|
615
|
+
}
|
|
616
|
+
return hash;
|
|
617
|
+
}
|
|
618
|
+
/**
|
|
619
|
+
* Resume subscriptions on this gateway using proofs from another gateway.
|
|
620
|
+
*
|
|
621
|
+
* Allows clients to migrate between gateways without re-subscribing.
|
|
622
|
+
* Validates proofs and restores subscription state.
|
|
623
|
+
*
|
|
624
|
+
* @param proofs - Array of subscription proofs from previous gateway.
|
|
625
|
+
* @returns Success or error.
|
|
626
|
+
*
|
|
627
|
+
* @example
|
|
628
|
+
* ```typescript
|
|
629
|
+
* const registry = new HttpTrustRegistry({ baseUrl: 'https://atelier2.xail.io' });
|
|
630
|
+
* const result = await registry.resumeSubscriptions([proof1, proof2]);
|
|
631
|
+
* ```
|
|
632
|
+
*/
|
|
633
|
+
async resumeSubscriptions(proofs) {
|
|
634
|
+
try {
|
|
635
|
+
const res = await this.fetchFn(`${this.baseUrl}/trust/resume-batch`, {
|
|
636
|
+
method: 'POST',
|
|
637
|
+
headers: { 'Content-Type': 'application/json' },
|
|
638
|
+
body: JSON.stringify({ proofs }),
|
|
639
|
+
});
|
|
640
|
+
if (!res.ok)
|
|
641
|
+
return (0, shared_1.err)('NETWORK_ERROR');
|
|
642
|
+
return (0, shared_1.ok)(undefined);
|
|
643
|
+
}
|
|
644
|
+
catch {
|
|
645
|
+
return (0, shared_1.err)('NETWORK_ERROR');
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
/**
|
|
649
|
+
* Fetch signed checkpoint for a DID (freshness primitive).
|
|
650
|
+
*
|
|
651
|
+
* Checkpoints provide cryptographic proof of DID state at a specific timestamp.
|
|
652
|
+
* Clients verify checkpoint signature and compare rotation_sequence to detect staleness.
|
|
653
|
+
*
|
|
654
|
+
* @param did - DID to fetch checkpoint for
|
|
655
|
+
* @returns Signed checkpoint or error
|
|
656
|
+
*
|
|
657
|
+
* @example
|
|
658
|
+
* ```typescript
|
|
659
|
+
* const checkpoint = await registry.fetchCheckpoint('did:key:z6Mk...');
|
|
660
|
+
* if (checkpoint.ok) {
|
|
661
|
+
* const verified = await verifyCheckpoint(checkpoint.value, gatewayPubKey);
|
|
662
|
+
* if (verified.ok && verified.value) {
|
|
663
|
+
* // Use checkpoint for staleness detection
|
|
664
|
+
* if (isCacheStale(localCache, checkpoint.value)) {
|
|
665
|
+
* // Refresh cache
|
|
666
|
+
* }
|
|
667
|
+
* }
|
|
668
|
+
* }
|
|
669
|
+
* ```
|
|
670
|
+
*/
|
|
671
|
+
async fetchCheckpoint(did) {
|
|
672
|
+
try {
|
|
673
|
+
const res = await this.fetchFn(`${this.baseUrl}/registry/checkpoint/${encodeURIComponent(did)}`);
|
|
674
|
+
if (res.status === 404)
|
|
675
|
+
return (0, shared_1.err)('NOT_FOUND');
|
|
676
|
+
if (!res.ok)
|
|
677
|
+
return (0, shared_1.err)('NETWORK_ERROR');
|
|
678
|
+
const checkpoint = (await res.json());
|
|
679
|
+
return (0, shared_1.ok)(checkpoint);
|
|
680
|
+
}
|
|
681
|
+
catch {
|
|
682
|
+
return (0, shared_1.err)('NETWORK_ERROR');
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
async updateScopes(did, scopes) {
|
|
686
|
+
try {
|
|
687
|
+
const res = await this.fetchFn(`${this.baseUrl}/registry/${encodeURIComponent(did)}/scopes`, {
|
|
688
|
+
method: 'POST',
|
|
689
|
+
headers: { 'Content-Type': 'application/json' },
|
|
690
|
+
body: JSON.stringify({ scopes }),
|
|
691
|
+
});
|
|
692
|
+
if (res.status === 404)
|
|
693
|
+
return (0, shared_1.err)('NOT_FOUND');
|
|
694
|
+
if (res.status === 410)
|
|
695
|
+
return (0, shared_1.err)('REVOKED');
|
|
696
|
+
if (!res.ok)
|
|
697
|
+
return (0, shared_1.err)('NETWORK_ERROR');
|
|
698
|
+
// Clear cache for this DID
|
|
699
|
+
this.resolveCache.delete(did);
|
|
700
|
+
this.entryCache.delete(did);
|
|
701
|
+
return (0, shared_1.ok)(undefined);
|
|
702
|
+
}
|
|
703
|
+
catch {
|
|
704
|
+
return (0, shared_1.err)('NETWORK_ERROR');
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
exports.HttpTrustRegistry = HttpTrustRegistry;
|
|
709
|
+
/**
|
|
710
|
+
* File-based trust registry using JSONL append-only log.
|
|
711
|
+
* Replays all entries on initialization, keeps in-memory Map for fast access.
|
|
712
|
+
* Suitable for production deployments with local persistence.
|
|
713
|
+
*/
|
|
714
|
+
class FileTrustRegistry {
|
|
715
|
+
path;
|
|
716
|
+
entries = new Map();
|
|
717
|
+
initialized = false;
|
|
718
|
+
constructor(opts) {
|
|
719
|
+
this.path = opts.path;
|
|
720
|
+
}
|
|
721
|
+
/** Initialize by replaying JSONL log. Called automatically on first operation. */
|
|
722
|
+
async init() {
|
|
723
|
+
if (this.initialized)
|
|
724
|
+
return;
|
|
725
|
+
try {
|
|
726
|
+
// Ensure directory exists
|
|
727
|
+
await fs.mkdir(path.dirname(this.path), { recursive: true });
|
|
728
|
+
// Read and replay JSONL file
|
|
729
|
+
const content = await fs.readFile(this.path, 'utf-8').catch(() => '');
|
|
730
|
+
const lines = content.split('\n').filter((line) => line.trim());
|
|
731
|
+
for (const line of lines) {
|
|
732
|
+
const record = JSON.parse(line);
|
|
733
|
+
if (record.type === 'register' && record.publicKey && record.name) {
|
|
734
|
+
this.entries.set(record.did, {
|
|
735
|
+
did: record.did,
|
|
736
|
+
publicKey: new Uint8Array(record.publicKey),
|
|
737
|
+
name: record.name,
|
|
738
|
+
scopes: new Set(record.scopes ?? []),
|
|
739
|
+
receiveScopes: record.receiveScopes ? new Set(record.receiveScopes) : undefined,
|
|
740
|
+
revoked: false,
|
|
741
|
+
rotation_sequence: record.rotation_sequence ?? 1,
|
|
742
|
+
x25519PublicKey: record.x25519PublicKey
|
|
743
|
+
? new Uint8Array(record.x25519PublicKey)
|
|
744
|
+
: undefined,
|
|
745
|
+
mlKemPublicKey: record.mlKemPublicKey
|
|
746
|
+
? new Uint8Array(record.mlKemPublicKey)
|
|
747
|
+
: undefined,
|
|
748
|
+
mlDsaPublicKey: record.mlDsaPublicKey
|
|
749
|
+
? new Uint8Array(record.mlDsaPublicKey)
|
|
750
|
+
: undefined,
|
|
751
|
+
xchange: record.xchange,
|
|
752
|
+
sdkVersion: record.sdkVersion,
|
|
753
|
+
minEnvelopeVersion: record.minEnvelopeVersion,
|
|
754
|
+
maxEnvelopeVersion: record.maxEnvelopeVersion,
|
|
755
|
+
expiresAt: record.expiresAt,
|
|
756
|
+
});
|
|
757
|
+
}
|
|
758
|
+
else if (record.type === 'revoke') {
|
|
759
|
+
const entry = this.entries.get(record.did);
|
|
760
|
+
if (entry) {
|
|
761
|
+
this.entries.set(record.did, { ...entry, revoked: true });
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
else if (record.type === 'update-scopes') {
|
|
765
|
+
const entry = this.entries.get(record.did);
|
|
766
|
+
if (entry) {
|
|
767
|
+
this.entries.set(record.did, { ...entry, scopes: new Set(record.scopes ?? []) });
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
else if (record.type === 'rotate' && record.publicKey && record.rotation_sequence) {
|
|
771
|
+
const entry = this.entries.get(record.did);
|
|
772
|
+
if (entry) {
|
|
773
|
+
// Only apply rotation if sequence is greater (prevents replaying stale rotations)
|
|
774
|
+
if (record.rotation_sequence > entry.rotation_sequence) {
|
|
775
|
+
this.entries.set(record.did, {
|
|
776
|
+
...entry,
|
|
777
|
+
publicKey: new Uint8Array(record.publicKey),
|
|
778
|
+
rotation_sequence: record.rotation_sequence,
|
|
779
|
+
});
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
catch (error) {
|
|
786
|
+
// If file doesn't exist yet, that's OK - it will be created on first write
|
|
787
|
+
}
|
|
788
|
+
this.initialized = true;
|
|
789
|
+
}
|
|
790
|
+
/** Append record to JSONL file. */
|
|
791
|
+
async append(record) {
|
|
792
|
+
await fs.appendFile(this.path, JSON.stringify(record) + '\n', 'utf-8');
|
|
793
|
+
}
|
|
794
|
+
async register(did, publicKey, name, scopes, x25519PublicKey, mlKemPublicKey, mlDsaPublicKey, xchange, receiveScopes, sdkVersion, minEnvelopeVersion, maxEnvelopeVersion, ttlMs, clientIP) {
|
|
795
|
+
await this.init();
|
|
796
|
+
if (this.entries.has(did))
|
|
797
|
+
return (0, shared_1.err)('ALREADY_REGISTERED');
|
|
798
|
+
const expiresAt = ttlMs ? Date.now() + ttlMs : undefined;
|
|
799
|
+
const entry = {
|
|
800
|
+
did,
|
|
801
|
+
publicKey,
|
|
802
|
+
name,
|
|
803
|
+
scopes: new Set(scopes ?? []),
|
|
804
|
+
receiveScopes: receiveScopes ? new Set(receiveScopes) : undefined,
|
|
805
|
+
revoked: false,
|
|
806
|
+
rotation_sequence: 1, // Initial sequence starts at 1
|
|
807
|
+
x25519PublicKey,
|
|
808
|
+
mlKemPublicKey,
|
|
809
|
+
mlDsaPublicKey,
|
|
810
|
+
xchange,
|
|
811
|
+
sdkVersion,
|
|
812
|
+
minEnvelopeVersion,
|
|
813
|
+
maxEnvelopeVersion,
|
|
814
|
+
expiresAt,
|
|
815
|
+
};
|
|
816
|
+
this.entries.set(did, entry);
|
|
817
|
+
// Append to JSONL
|
|
818
|
+
await this.append({
|
|
819
|
+
type: 'register',
|
|
820
|
+
did,
|
|
821
|
+
publicKey: Array.from(publicKey),
|
|
822
|
+
name,
|
|
823
|
+
scopes: scopes ?? [],
|
|
824
|
+
rotation_sequence: 1,
|
|
825
|
+
...(receiveScopes ? { receiveScopes } : {}),
|
|
826
|
+
...(x25519PublicKey ? { x25519PublicKey: Array.from(x25519PublicKey) } : {}),
|
|
827
|
+
...(mlKemPublicKey ? { mlKemPublicKey: Array.from(mlKemPublicKey) } : {}),
|
|
828
|
+
...(mlDsaPublicKey ? { mlDsaPublicKey: Array.from(mlDsaPublicKey) } : {}),
|
|
829
|
+
...(xchange !== undefined ? { xchange } : {}),
|
|
830
|
+
...(sdkVersion !== undefined ? { sdkVersion } : {}),
|
|
831
|
+
...(minEnvelopeVersion !== undefined ? { minEnvelopeVersion } : {}),
|
|
832
|
+
...(maxEnvelopeVersion !== undefined ? { maxEnvelopeVersion } : {}),
|
|
833
|
+
...(expiresAt !== undefined ? { expiresAt } : {}),
|
|
834
|
+
});
|
|
835
|
+
return (0, shared_1.ok)(undefined);
|
|
836
|
+
}
|
|
837
|
+
async resolve(did) {
|
|
838
|
+
await this.init();
|
|
839
|
+
const entry = this.entries.get(did);
|
|
840
|
+
if (!entry)
|
|
841
|
+
return (0, shared_1.err)('NOT_FOUND');
|
|
842
|
+
if (isExpired(entry))
|
|
843
|
+
return (0, shared_1.err)('EXPIRED');
|
|
844
|
+
if (entry.revoked)
|
|
845
|
+
return (0, shared_1.err)('REVOKED');
|
|
846
|
+
return (0, shared_1.ok)(entry.publicKey);
|
|
847
|
+
}
|
|
848
|
+
async hasScope(did, scope) {
|
|
849
|
+
await this.init();
|
|
850
|
+
const entry = this.entries.get(did);
|
|
851
|
+
if (!entry || isExpired(entry) || entry.revoked)
|
|
852
|
+
return false;
|
|
853
|
+
return entry.scopes.has(scope);
|
|
854
|
+
}
|
|
855
|
+
async hasReceiveScope(did, scope) {
|
|
856
|
+
await this.init();
|
|
857
|
+
const entry = this.entries.get(did);
|
|
858
|
+
if (!entry || isExpired(entry) || entry.revoked)
|
|
859
|
+
return false;
|
|
860
|
+
// Undefined = accept all scopes (backward compatibility)
|
|
861
|
+
if (!entry.receiveScopes)
|
|
862
|
+
return true;
|
|
863
|
+
return entry.receiveScopes.has(scope);
|
|
864
|
+
}
|
|
865
|
+
async revoke(did) {
|
|
866
|
+
await this.init();
|
|
867
|
+
const entry = this.entries.get(did);
|
|
868
|
+
if (!entry)
|
|
869
|
+
return (0, shared_1.err)('NOT_FOUND');
|
|
870
|
+
this.entries.set(did, { ...entry, revoked: true });
|
|
871
|
+
await this.append({ type: 'revoke', did });
|
|
872
|
+
return (0, shared_1.ok)(undefined);
|
|
873
|
+
}
|
|
874
|
+
async getEntry(did) {
|
|
875
|
+
await this.init();
|
|
876
|
+
const entry = this.entries.get(did);
|
|
877
|
+
if (!entry)
|
|
878
|
+
return (0, shared_1.err)('NOT_FOUND');
|
|
879
|
+
if (isExpired(entry))
|
|
880
|
+
return (0, shared_1.err)('EXPIRED');
|
|
881
|
+
return (0, shared_1.ok)(entry);
|
|
882
|
+
}
|
|
883
|
+
async updateScopes(did, scopes) {
|
|
884
|
+
await this.init();
|
|
885
|
+
const entry = this.entries.get(did);
|
|
886
|
+
if (!entry)
|
|
887
|
+
return (0, shared_1.err)('NOT_FOUND');
|
|
888
|
+
if (isExpired(entry))
|
|
889
|
+
return (0, shared_1.err)('EXPIRED');
|
|
890
|
+
if (entry.revoked)
|
|
891
|
+
return (0, shared_1.err)('REVOKED');
|
|
892
|
+
this.entries.set(did, { ...entry, scopes: new Set(scopes) });
|
|
893
|
+
await this.append({ type: 'update-scopes', did, scopes });
|
|
894
|
+
return (0, shared_1.ok)(undefined);
|
|
895
|
+
}
|
|
896
|
+
/** Remove all expired entries from the registry. */
|
|
897
|
+
async cleanup() {
|
|
898
|
+
await this.init();
|
|
899
|
+
let removed = 0;
|
|
900
|
+
const now = Date.now();
|
|
901
|
+
for (const [did, entry] of this.entries) {
|
|
902
|
+
if (entry.expiresAt && now > entry.expiresAt) {
|
|
903
|
+
this.entries.delete(did);
|
|
904
|
+
removed++;
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
return removed;
|
|
908
|
+
}
|
|
909
|
+
/**
|
|
910
|
+
* Rotate a DID to a new public key with rollback protection.
|
|
911
|
+
*
|
|
912
|
+
* Validates that rotationSequence is greater than current sequence to prevent
|
|
913
|
+
* rollback attacks. Appends rotation event to JSONL and updates in-memory state.
|
|
914
|
+
*
|
|
915
|
+
* @param did - DID being rotated
|
|
916
|
+
* @param newPublicKey - New public key bytes
|
|
917
|
+
* @param proof - Cryptographic proof (e.g., signature from old key)
|
|
918
|
+
* @param rotationSequence - Monotonically increasing sequence number
|
|
919
|
+
* @throws Error if DID not found or sequence validation fails
|
|
920
|
+
*/
|
|
921
|
+
async rotate(did, newPublicKey, proof, rotationSequence) {
|
|
922
|
+
await this.init();
|
|
923
|
+
const entry = this.entries.get(did);
|
|
924
|
+
if (!entry) {
|
|
925
|
+
throw new Error(`DID not found: ${did}`);
|
|
926
|
+
}
|
|
927
|
+
// Rollback protection: new sequence must be greater than current
|
|
928
|
+
if (rotationSequence <= entry.rotation_sequence) {
|
|
929
|
+
throw new Error(`Rotation sequence ${rotationSequence} must be > current ${entry.rotation_sequence} (rollback attack prevented)`);
|
|
930
|
+
}
|
|
931
|
+
// Append rotation event to JSONL
|
|
932
|
+
await this.append({
|
|
933
|
+
type: 'rotate',
|
|
934
|
+
did,
|
|
935
|
+
publicKey: Array.from(newPublicKey),
|
|
936
|
+
proof: Array.from(proof),
|
|
937
|
+
rotation_sequence: rotationSequence,
|
|
938
|
+
timestamp: Date.now(),
|
|
939
|
+
});
|
|
940
|
+
// Update in-memory entry
|
|
941
|
+
this.entries.set(did, {
|
|
942
|
+
...entry,
|
|
943
|
+
publicKey: newPublicKey,
|
|
944
|
+
rotation_sequence: rotationSequence,
|
|
945
|
+
});
|
|
946
|
+
}
|
|
947
|
+
/** Number of entries (for testing). */
|
|
948
|
+
get size() {
|
|
949
|
+
return this.entries.size;
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
exports.FileTrustRegistry = FileTrustRegistry;
|
|
953
|
+
/* ── Enterprise Factory ── */
|
|
954
|
+
/**
|
|
955
|
+
* Create an enterprise trust registry with optional pre-population.
|
|
956
|
+
* Suitable for corporate deployments with centralized trust management.
|
|
957
|
+
*
|
|
958
|
+
* @example
|
|
959
|
+
* ```typescript
|
|
960
|
+
* const registry = await TrustRegistry.enterprise({
|
|
961
|
+
* storage: 'file',
|
|
962
|
+
* path: '/opt/corp/trust.jsonl',
|
|
963
|
+
* preload: [
|
|
964
|
+
* { did: 'did:web:corp.example.com', publicKey: ..., name: 'Corporate Gateway' }
|
|
965
|
+
* ]
|
|
966
|
+
* });
|
|
967
|
+
* ```
|
|
968
|
+
*/
|
|
969
|
+
async function createEnterpriseTrustRegistry(opts) {
|
|
970
|
+
let registry;
|
|
971
|
+
if (opts.storage === 'file') {
|
|
972
|
+
if (!opts.path)
|
|
973
|
+
throw new Error('FileTrustRegistry requires path option');
|
|
974
|
+
registry = new FileTrustRegistry({ path: opts.path });
|
|
975
|
+
}
|
|
976
|
+
else if (opts.storage === 'http') {
|
|
977
|
+
if (!opts.baseUrl)
|
|
978
|
+
throw new Error('HttpTrustRegistry requires baseUrl option');
|
|
979
|
+
registry = new HttpTrustRegistry({ baseUrl: opts.baseUrl });
|
|
980
|
+
}
|
|
981
|
+
else {
|
|
982
|
+
registry = new MemoryTrustRegistry();
|
|
983
|
+
}
|
|
984
|
+
// Pre-populate if requested
|
|
985
|
+
if (opts.preload) {
|
|
986
|
+
for (const entry of opts.preload) {
|
|
987
|
+
await registry.register(entry.did, entry.publicKey, entry.name, entry.scopes, entry.x25519PublicKey, entry.mlKemPublicKey, entry.mlDsaPublicKey, entry.xchange, entry.receiveScopes, entry.sdkVersion, entry.minEnvelopeVersion, entry.maxEnvelopeVersion);
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
return registry;
|
|
991
|
+
}
|