@keytrace/claims 0.0.5 → 0.0.10
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/dist/types.d.ts +2 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/verify.d.ts.map +1 -1
- package/dist/verify.js +42 -6
- package/dist/verify.js.map +1 -1
- package/package.json +6 -1
- package/src/types.ts +2 -0
- package/src/verify.ts +48 -6
package/dist/types.d.ts
CHANGED
|
@@ -121,5 +121,7 @@ export interface VerifyOptions {
|
|
|
121
121
|
plcDirectoryUrl?: string;
|
|
122
122
|
/** Public ATProto API URL for handle resolution (default: https://public.api.bsky.app) */
|
|
123
123
|
publicApiUrl?: string;
|
|
124
|
+
/** Trusted signer handles whose signing keys are accepted (default: ["keytrace.dev"]) */
|
|
125
|
+
trustedSigners?: string[];
|
|
124
126
|
}
|
|
125
127
|
//# sourceMappingURL=types.d.ts.map
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,kCAAkC;IAClC,GAAG,EAAE,MAAM,CAAC;IACZ,uCAAuC;IACvC,GAAG,EAAE,MAAM,CAAC;IACZ,4BAA4B;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,gCAAgC;IAChC,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,oBAAoB,CAAC;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,aAAa,CAAC;IACxB,GAAG,EAAE,cAAc,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,kBAAkB,CAAC;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,IAAI,CAAC;IACV,GAAG,EAAE,OAAO,CAAC;IACb,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;CACX;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACtC,iCAAiC;IACjC,GAAG,EAAE,MAAM,CAAC;IACZ,iBAAiB;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,qCAAqC;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,mCAAmC;IACnC,QAAQ,EAAE,MAAM,CAAC;IACjB,4CAA4C;IAC5C,QAAQ,EAAE,OAAO,CAAC;IAClB,mCAAmC;IACnC,KAAK,EAAE,gBAAgB,EAAE,CAAC;IAC1B,2CAA2C;IAC3C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,kEAAkE;IAClE,QAAQ,EAAE,aAAa,CAAC;IACxB,wBAAwB;IACxB,KAAK,EAAE,WAAW,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,gCAAgC;IAChC,GAAG,EAAE,MAAM,CAAC;IACZ,qCAAqC;IACrC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,0CAA0C;IAC1C,MAAM,EAAE,uBAAuB,EAAE,CAAC;IAClC,yBAAyB;IACzB,OAAO,EAAE;QACP,KAAK,EAAE,MAAM,CAAC;QACd,QAAQ,EAAE,MAAM,CAAC;QACjB,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC;CACH;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,2DAA2D;IAC3D,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;IACrB,6CAA6C;IAC7C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,yDAAyD;IACzD,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,0FAA0F;IAC1F,YAAY,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/verify.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"verify.d.ts","sourceRoot":"","sources":["../src/verify.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAoF,kBAAkB,EAAoB,aAAa,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"verify.d.ts","sourceRoot":"","sources":["../src/verify.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAoF,kBAAkB,EAAoB,aAAa,EAAE,MAAM,YAAY,CAAC;AAQxK,YAAY,EACV,aAAa,EACb,WAAW,EACX,cAAc,EACd,uBAAuB,EACvB,cAAc,EACd,SAAS,EACT,eAAe,EACf,kBAAkB,EAClB,gBAAgB,EAChB,aAAa,GACd,MAAM,YAAY,CAAC;AAEpB;;;;;;GAMG;AACH,wBAAsB,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAQ7G;AAED;;;;;;GAMG;AACH,wBAAsB,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAsCvG"}
|
package/dist/verify.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { resolveHandle, resolvePds, listClaimRecords, getRecordByUri } from "./atproto.js";
|
|
2
2
|
import { verifyES256Signature } from "./crypto/signature.js";
|
|
3
|
+
/** Default trusted signer handles */
|
|
4
|
+
const DEFAULT_TRUSTED_SIGNERS = ["keytrace.dev"];
|
|
3
5
|
/**
|
|
4
6
|
* Verify all keytrace claims for a handle.
|
|
5
7
|
*
|
|
@@ -23,6 +25,9 @@ export async function getClaimsForHandle(handle, options) {
|
|
|
23
25
|
* @returns Verification results for all claims
|
|
24
26
|
*/
|
|
25
27
|
export async function getClaimsForDid(did, options) {
|
|
28
|
+
// Resolve trusted signer handles to DIDs once for the whole batch
|
|
29
|
+
const trustedSigners = options?.trustedSigners ?? DEFAULT_TRUSTED_SIGNERS;
|
|
30
|
+
const trustedDids = await resolveTrustedDids(trustedSigners, options);
|
|
26
31
|
// Resolve PDS for the user
|
|
27
32
|
const pdsUrl = await resolvePds(did, options);
|
|
28
33
|
// Fetch all claim records
|
|
@@ -41,7 +46,7 @@ export async function getClaimsForDid(did, options) {
|
|
|
41
46
|
// Verify each claim
|
|
42
47
|
const claimResults = [];
|
|
43
48
|
for (const record of claimRecords) {
|
|
44
|
-
const result = await verifySingleClaim(did, record.uri, record.rkey, record.value, options);
|
|
49
|
+
const result = await verifySingleClaim(did, record.uri, record.rkey, record.value, trustedDids, options);
|
|
45
50
|
claimResults.push(result);
|
|
46
51
|
}
|
|
47
52
|
return {
|
|
@@ -57,7 +62,7 @@ export async function getClaimsForDid(did, options) {
|
|
|
57
62
|
/**
|
|
58
63
|
* Verify a single claim's signature.
|
|
59
64
|
*/
|
|
60
|
-
async function verifySingleClaim(did, uri, rkey, claim, options) {
|
|
65
|
+
async function verifySingleClaim(did, uri, rkey, claim, trustedDids, options) {
|
|
61
66
|
const steps = [];
|
|
62
67
|
try {
|
|
63
68
|
// Step 1: Validate claim structure
|
|
@@ -70,7 +75,15 @@ async function verifySingleClaim(did, uri, rkey, claim, options) {
|
|
|
70
75
|
return buildResult(uri, rkey, claim, false, steps, "Missing signature fields");
|
|
71
76
|
}
|
|
72
77
|
steps.push({ step: "validate_claim", success: true, detail: "Claim structure valid" });
|
|
73
|
-
// Step 2:
|
|
78
|
+
// Step 2: Validate signing key is from a trusted signer
|
|
79
|
+
const signerDid = extractDidFromAtUri(claim.sig.src);
|
|
80
|
+
if (!signerDid || !trustedDids.has(signerDid)) {
|
|
81
|
+
const error = `Signing key is not from a trusted signer (source: ${claim.sig.src})`;
|
|
82
|
+
steps.push({ step: "validate_signer", success: false, error });
|
|
83
|
+
return buildResult(uri, rkey, claim, false, steps, error);
|
|
84
|
+
}
|
|
85
|
+
steps.push({ step: "validate_signer", success: true, detail: `Signing key from trusted signer (${signerDid})` });
|
|
86
|
+
// Step 3: Fetch the signing key
|
|
74
87
|
let keyRecord;
|
|
75
88
|
try {
|
|
76
89
|
keyRecord = await getRecordByUri(claim.sig.src, options);
|
|
@@ -81,7 +94,7 @@ async function verifySingleClaim(did, uri, rkey, claim, options) {
|
|
|
81
94
|
steps.push({ step: "fetch_key", success: false, error });
|
|
82
95
|
return buildResult(uri, rkey, claim, false, steps, error);
|
|
83
96
|
}
|
|
84
|
-
// Step
|
|
97
|
+
// Step 4: Parse the public JWK
|
|
85
98
|
let publicJwk;
|
|
86
99
|
try {
|
|
87
100
|
publicJwk = JSON.parse(keyRecord.publicJwk);
|
|
@@ -95,7 +108,7 @@ async function verifySingleClaim(did, uri, rkey, claim, options) {
|
|
|
95
108
|
steps.push({ step: "parse_key", success: false, error });
|
|
96
109
|
return buildResult(uri, rkey, claim, false, steps, error);
|
|
97
110
|
}
|
|
98
|
-
// Step
|
|
111
|
+
// Step 5: Reconstruct the signed claim data
|
|
99
112
|
const signedData = {
|
|
100
113
|
did,
|
|
101
114
|
subject: claim.identity.subject,
|
|
@@ -107,7 +120,7 @@ async function verifySingleClaim(did, uri, rkey, claim, options) {
|
|
|
107
120
|
success: true,
|
|
108
121
|
detail: `Reconstructed signed data for ${claim.type}:${claim.identity.subject}`,
|
|
109
122
|
});
|
|
110
|
-
// Step
|
|
123
|
+
// Step 6: Verify the signature
|
|
111
124
|
const isValid = await verifyES256Signature(signedData, claim.sig.attestation, publicJwk);
|
|
112
125
|
if (isValid) {
|
|
113
126
|
steps.push({ step: "verify_signature", success: true, detail: "Signature verified" });
|
|
@@ -140,4 +153,27 @@ function buildResult(uri, rkey, claim, verified, steps, error) {
|
|
|
140
153
|
claim,
|
|
141
154
|
};
|
|
142
155
|
}
|
|
156
|
+
/**
|
|
157
|
+
* Extract the DID from an AT URI (at://did/collection/rkey)
|
|
158
|
+
*/
|
|
159
|
+
function extractDidFromAtUri(atUri) {
|
|
160
|
+
const match = atUri.match(/^at:\/\/([^/]+)\//);
|
|
161
|
+
return match?.[1] ?? null;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Resolve an array of handles to their DIDs.
|
|
165
|
+
*/
|
|
166
|
+
async function resolveTrustedDids(handles, options) {
|
|
167
|
+
const dids = new Set();
|
|
168
|
+
await Promise.all(handles.map(async (handle) => {
|
|
169
|
+
try {
|
|
170
|
+
const did = await resolveHandle(handle, options);
|
|
171
|
+
dids.add(did);
|
|
172
|
+
}
|
|
173
|
+
catch {
|
|
174
|
+
// Skip handles that fail to resolve
|
|
175
|
+
}
|
|
176
|
+
}));
|
|
177
|
+
return dids;
|
|
178
|
+
}
|
|
143
179
|
//# sourceMappingURL=verify.js.map
|
package/dist/verify.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"verify.js","sourceRoot":"","sources":["../src/verify.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC3F,OAAO,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"verify.js","sourceRoot":"","sources":["../src/verify.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@keytrace/claims",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.10",
|
|
4
4
|
"description": "Verify keytrace identity claims",
|
|
5
5
|
"files": [
|
|
6
6
|
"src",
|
|
@@ -30,6 +30,11 @@
|
|
|
30
30
|
"typescript": "^5.7.0",
|
|
31
31
|
"vitest": "^2.1.0"
|
|
32
32
|
},
|
|
33
|
+
"repository": {
|
|
34
|
+
"type": "git",
|
|
35
|
+
"url": "https://github.com/orta/keytrace",
|
|
36
|
+
"directory": "packages/claims"
|
|
37
|
+
},
|
|
33
38
|
"engines": {
|
|
34
39
|
"node": ">=18"
|
|
35
40
|
}
|
package/src/types.ts
CHANGED
|
@@ -130,4 +130,6 @@ export interface VerifyOptions {
|
|
|
130
130
|
plcDirectoryUrl?: string;
|
|
131
131
|
/** Public ATProto API URL for handle resolution (default: https://public.api.bsky.app) */
|
|
132
132
|
publicApiUrl?: string;
|
|
133
|
+
/** Trusted signer handles whose signing keys are accepted (default: ["keytrace.dev"]) */
|
|
134
|
+
trustedSigners?: string[];
|
|
133
135
|
}
|
package/src/verify.ts
CHANGED
|
@@ -2,6 +2,9 @@ import type { ClaimRecord, ClaimVerificationResult, ES256PublicJwk, KeyRecord, S
|
|
|
2
2
|
import { resolveHandle, resolvePds, listClaimRecords, getRecordByUri } from "./atproto.js";
|
|
3
3
|
import { verifyES256Signature } from "./crypto/signature.js";
|
|
4
4
|
|
|
5
|
+
/** Default trusted signer handles */
|
|
6
|
+
const DEFAULT_TRUSTED_SIGNERS = ["keytrace.dev"];
|
|
7
|
+
|
|
5
8
|
// Re-export types for convenience
|
|
6
9
|
export type {
|
|
7
10
|
ClaimIdentity,
|
|
@@ -41,6 +44,10 @@ export async function getClaimsForHandle(handle: string, options?: VerifyOptions
|
|
|
41
44
|
* @returns Verification results for all claims
|
|
42
45
|
*/
|
|
43
46
|
export async function getClaimsForDid(did: string, options?: VerifyOptions): Promise<VerificationResult> {
|
|
47
|
+
// Resolve trusted signer handles to DIDs once for the whole batch
|
|
48
|
+
const trustedSigners = options?.trustedSigners ?? DEFAULT_TRUSTED_SIGNERS;
|
|
49
|
+
const trustedDids = await resolveTrustedDids(trustedSigners, options);
|
|
50
|
+
|
|
44
51
|
// Resolve PDS for the user
|
|
45
52
|
const pdsUrl = await resolvePds(did, options);
|
|
46
53
|
|
|
@@ -61,7 +68,7 @@ export async function getClaimsForDid(did: string, options?: VerifyOptions): Pro
|
|
|
61
68
|
const claimResults: ClaimVerificationResult[] = [];
|
|
62
69
|
|
|
63
70
|
for (const record of claimRecords) {
|
|
64
|
-
const result = await verifySingleClaim(did, record.uri, record.rkey, record.value, options);
|
|
71
|
+
const result = await verifySingleClaim(did, record.uri, record.rkey, record.value, trustedDids, options);
|
|
65
72
|
claimResults.push(result);
|
|
66
73
|
}
|
|
67
74
|
|
|
@@ -79,7 +86,7 @@ export async function getClaimsForDid(did: string, options?: VerifyOptions): Pro
|
|
|
79
86
|
/**
|
|
80
87
|
* Verify a single claim's signature.
|
|
81
88
|
*/
|
|
82
|
-
async function verifySingleClaim(did: string, uri: string, rkey: string, claim: ClaimRecord, options?: VerifyOptions): Promise<ClaimVerificationResult> {
|
|
89
|
+
async function verifySingleClaim(did: string, uri: string, rkey: string, claim: ClaimRecord, trustedDids: Set<string>, options?: VerifyOptions): Promise<ClaimVerificationResult> {
|
|
83
90
|
const steps: VerificationStep[] = [];
|
|
84
91
|
|
|
85
92
|
try {
|
|
@@ -94,7 +101,16 @@ async function verifySingleClaim(did: string, uri: string, rkey: string, claim:
|
|
|
94
101
|
}
|
|
95
102
|
steps.push({ step: "validate_claim", success: true, detail: "Claim structure valid" });
|
|
96
103
|
|
|
97
|
-
// Step 2:
|
|
104
|
+
// Step 2: Validate signing key is from a trusted signer
|
|
105
|
+
const signerDid = extractDidFromAtUri(claim.sig.src);
|
|
106
|
+
if (!signerDid || !trustedDids.has(signerDid)) {
|
|
107
|
+
const error = `Signing key is not from a trusted signer (source: ${claim.sig.src})`;
|
|
108
|
+
steps.push({ step: "validate_signer", success: false, error });
|
|
109
|
+
return buildResult(uri, rkey, claim, false, steps, error);
|
|
110
|
+
}
|
|
111
|
+
steps.push({ step: "validate_signer", success: true, detail: `Signing key from trusted signer (${signerDid})` });
|
|
112
|
+
|
|
113
|
+
// Step 3: Fetch the signing key
|
|
98
114
|
let keyRecord: KeyRecord;
|
|
99
115
|
try {
|
|
100
116
|
keyRecord = await getRecordByUri<KeyRecord>(claim.sig.src, options);
|
|
@@ -105,7 +121,7 @@ async function verifySingleClaim(did: string, uri: string, rkey: string, claim:
|
|
|
105
121
|
return buildResult(uri, rkey, claim, false, steps, error);
|
|
106
122
|
}
|
|
107
123
|
|
|
108
|
-
// Step
|
|
124
|
+
// Step 4: Parse the public JWK
|
|
109
125
|
let publicJwk: ES256PublicJwk;
|
|
110
126
|
try {
|
|
111
127
|
publicJwk = JSON.parse(keyRecord.publicJwk) as ES256PublicJwk;
|
|
@@ -119,7 +135,7 @@ async function verifySingleClaim(did: string, uri: string, rkey: string, claim:
|
|
|
119
135
|
return buildResult(uri, rkey, claim, false, steps, error);
|
|
120
136
|
}
|
|
121
137
|
|
|
122
|
-
// Step
|
|
138
|
+
// Step 5: Reconstruct the signed claim data
|
|
123
139
|
const signedData: SignedClaimData = {
|
|
124
140
|
did,
|
|
125
141
|
subject: claim.identity.subject,
|
|
@@ -132,7 +148,7 @@ async function verifySingleClaim(did: string, uri: string, rkey: string, claim:
|
|
|
132
148
|
detail: `Reconstructed signed data for ${claim.type}:${claim.identity.subject}`,
|
|
133
149
|
});
|
|
134
150
|
|
|
135
|
-
// Step
|
|
151
|
+
// Step 6: Verify the signature
|
|
136
152
|
const isValid = await verifyES256Signature(signedData, claim.sig.attestation, publicJwk);
|
|
137
153
|
|
|
138
154
|
if (isValid) {
|
|
@@ -165,3 +181,29 @@ function buildResult(uri: string, rkey: string, claim: ClaimRecord, verified: bo
|
|
|
165
181
|
claim,
|
|
166
182
|
};
|
|
167
183
|
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Extract the DID from an AT URI (at://did/collection/rkey)
|
|
187
|
+
*/
|
|
188
|
+
function extractDidFromAtUri(atUri: string): string | null {
|
|
189
|
+
const match = atUri.match(/^at:\/\/([^/]+)\//);
|
|
190
|
+
return match?.[1] ?? null;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Resolve an array of handles to their DIDs.
|
|
195
|
+
*/
|
|
196
|
+
async function resolveTrustedDids(handles: string[], options?: VerifyOptions): Promise<Set<string>> {
|
|
197
|
+
const dids = new Set<string>();
|
|
198
|
+
await Promise.all(
|
|
199
|
+
handles.map(async (handle) => {
|
|
200
|
+
try {
|
|
201
|
+
const did = await resolveHandle(handle, options);
|
|
202
|
+
dids.add(did);
|
|
203
|
+
} catch {
|
|
204
|
+
// Skip handles that fail to resolve
|
|
205
|
+
}
|
|
206
|
+
}),
|
|
207
|
+
);
|
|
208
|
+
return dids;
|
|
209
|
+
}
|