@keytrace/claims 0.0.10 → 0.0.12
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 +1 -1
- package/dist/constants.d.ts +1 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +1 -0
- package/dist/constants.js.map +1 -1
- package/dist/types.d.ts +27 -11
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +8 -1
- package/dist/types.js.map +1 -1
- package/dist/verify.d.ts +1 -0
- package/dist/verify.d.ts.map +1 -1
- package/dist/verify.js +42 -14
- package/dist/verify.js.map +1 -1
- package/package.json +1 -1
- package/src/constants.ts +1 -0
- package/src/types.ts +28 -11
- package/src/verify.ts +42 -14
package/README.md
CHANGED
|
@@ -92,7 +92,7 @@ interface ClaimVerificationResult {
|
|
|
92
92
|
2. Fetches the user's PDS endpoint from their DID document
|
|
93
93
|
3. Lists all `dev.keytrace.claim` records from their repo
|
|
94
94
|
4. For each claim:
|
|
95
|
-
- Fetches the signing key from the `
|
|
95
|
+
- Fetches the signing key from the primary signature's `src` AT URI
|
|
96
96
|
- Reconstructs the signed claim data
|
|
97
97
|
- Verifies the ES256 signature using Web Crypto API
|
|
98
98
|
|
package/dist/constants.d.ts
CHANGED
|
@@ -2,5 +2,6 @@ export declare const PUBLIC_API_URL = "https://public.api.bsky.app";
|
|
|
2
2
|
export declare const PLC_DIRECTORY_URL = "https://plc.directory";
|
|
3
3
|
export declare const COLLECTION_NSID = "dev.keytrace.claim";
|
|
4
4
|
export declare const KEY_COLLECTION_NSID = "dev.keytrace.key";
|
|
5
|
+
export declare const SERVER_KEY_COLLECTION_NSID = "dev.keytrace.serverPublicKey";
|
|
5
6
|
export declare const DEFAULT_TIMEOUT = 10000;
|
|
6
7
|
//# sourceMappingURL=constants.d.ts.map
|
package/dist/constants.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,cAAc,gCAAgC,CAAC;AAC5D,eAAO,MAAM,iBAAiB,0BAA0B,CAAC;AACzD,eAAO,MAAM,eAAe,uBAAuB,CAAC;AACpD,eAAO,MAAM,mBAAmB,qBAAqB,CAAC;AACtD,eAAO,MAAM,eAAe,QAAQ,CAAC"}
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,cAAc,gCAAgC,CAAC;AAC5D,eAAO,MAAM,iBAAiB,0BAA0B,CAAC;AACzD,eAAO,MAAM,eAAe,uBAAuB,CAAC;AACpD,eAAO,MAAM,mBAAmB,qBAAqB,CAAC;AACtD,eAAO,MAAM,0BAA0B,iCAAiC,CAAC;AACzE,eAAO,MAAM,eAAe,QAAQ,CAAC"}
|
package/dist/constants.js
CHANGED
|
@@ -2,5 +2,6 @@ export const PUBLIC_API_URL = "https://public.api.bsky.app";
|
|
|
2
2
|
export const PLC_DIRECTORY_URL = "https://plc.directory";
|
|
3
3
|
export const COLLECTION_NSID = "dev.keytrace.claim";
|
|
4
4
|
export const KEY_COLLECTION_NSID = "dev.keytrace.key";
|
|
5
|
+
export const SERVER_KEY_COLLECTION_NSID = "dev.keytrace.serverPublicKey";
|
|
5
6
|
export const DEFAULT_TIMEOUT = 10000;
|
|
6
7
|
//# sourceMappingURL=constants.js.map
|
package/dist/constants.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,cAAc,GAAG,6BAA6B,CAAC;AAC5D,MAAM,CAAC,MAAM,iBAAiB,GAAG,uBAAuB,CAAC;AACzD,MAAM,CAAC,MAAM,eAAe,GAAG,oBAAoB,CAAC;AACpD,MAAM,CAAC,MAAM,mBAAmB,GAAG,kBAAkB,CAAC;AACtD,MAAM,CAAC,MAAM,eAAe,GAAG,KAAK,CAAC"}
|
|
1
|
+
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,cAAc,GAAG,6BAA6B,CAAC;AAC5D,MAAM,CAAC,MAAM,iBAAiB,GAAG,uBAAuB,CAAC;AACzD,MAAM,CAAC,MAAM,eAAe,GAAG,oBAAoB,CAAC;AACpD,MAAM,CAAC,MAAM,mBAAmB,GAAG,kBAAkB,CAAC;AACtD,MAAM,CAAC,MAAM,0BAA0B,GAAG,8BAA8B,CAAC;AACzE,MAAM,CAAC,MAAM,eAAe,GAAG,KAAK,CAAC"}
|
package/dist/types.d.ts
CHANGED
|
@@ -19,25 +19,44 @@ export interface ClaimSignature {
|
|
|
19
19
|
signedAt: string;
|
|
20
20
|
/** JWS compact serialization */
|
|
21
21
|
attestation: string;
|
|
22
|
+
/** Ordered list of field names included in the signed payload */
|
|
23
|
+
signedFields?: string[];
|
|
24
|
+
/** Datetime when this signature was retracted (ISO 8601) */
|
|
25
|
+
retractedAt?: string;
|
|
22
26
|
}
|
|
23
27
|
/**
|
|
24
|
-
* Raw claim record from ATProto
|
|
28
|
+
* Raw claim record from ATProto.
|
|
29
|
+
* Supports both legacy format (sig: single object) and new format (sigs: array).
|
|
25
30
|
*/
|
|
26
31
|
export interface ClaimRecord {
|
|
27
32
|
$type: "dev.keytrace.claim";
|
|
28
33
|
type: string;
|
|
29
34
|
claimUri: string;
|
|
30
35
|
identity: ClaimIdentity;
|
|
31
|
-
|
|
36
|
+
/** @deprecated Use sigs array instead. Retained for backwards compatibility with old records. */
|
|
37
|
+
sig?: ClaimSignature;
|
|
38
|
+
/** One or more cryptographic attestation signatures from verification services */
|
|
39
|
+
sigs?: ClaimSignature[];
|
|
32
40
|
comment?: string;
|
|
33
41
|
createdAt: string;
|
|
34
42
|
prerelease?: boolean;
|
|
43
|
+
nonce?: string;
|
|
44
|
+
retractedAt?: string;
|
|
35
45
|
}
|
|
36
46
|
/**
|
|
37
|
-
*
|
|
47
|
+
* Get the attestation signature from a claim record.
|
|
48
|
+
* Looks for a sig with kid starting with "attest:", falls back to sigs[0] or legacy sig field.
|
|
49
|
+
*/
|
|
50
|
+
export declare function getPrimarySig(record: {
|
|
51
|
+
sigs?: ClaimSignature[];
|
|
52
|
+
sig?: ClaimSignature;
|
|
53
|
+
}): ClaimSignature | undefined;
|
|
54
|
+
/**
|
|
55
|
+
* Public key record from keytrace service.
|
|
56
|
+
* Supports both old (dev.keytrace.key) and new (dev.keytrace.serverPublicKey) collection names.
|
|
38
57
|
*/
|
|
39
58
|
export interface KeyRecord {
|
|
40
|
-
$type: "dev.keytrace.key";
|
|
59
|
+
$type: "dev.keytrace.key" | "dev.keytrace.serverPublicKey";
|
|
41
60
|
publicJwk: string;
|
|
42
61
|
validFrom: string;
|
|
43
62
|
validUntil: string;
|
|
@@ -52,14 +71,11 @@ export interface ES256PublicJwk {
|
|
|
52
71
|
y: string;
|
|
53
72
|
}
|
|
54
73
|
/**
|
|
55
|
-
* Claim data that is signed (canonical form)
|
|
74
|
+
* Claim data that is signed (canonical form).
|
|
75
|
+
* New format uses record field names: claimUri, did, identity.subject, type.
|
|
76
|
+
* Legacy format used: did, subject, type, verifiedAt.
|
|
56
77
|
*/
|
|
57
|
-
export
|
|
58
|
-
did: string;
|
|
59
|
-
subject: string;
|
|
60
|
-
type: string;
|
|
61
|
-
verifiedAt: string;
|
|
62
|
-
}
|
|
78
|
+
export type SignedClaimData = Record<string, string>;
|
|
63
79
|
/**
|
|
64
80
|
* Verification step detail for debugging/auditing
|
|
65
81
|
*/
|
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;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/types.js
CHANGED
|
@@ -1,2 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Get the attestation signature from a claim record.
|
|
3
|
+
* Looks for a sig with kid starting with "attest:", falls back to sigs[0] or legacy sig field.
|
|
4
|
+
*/
|
|
5
|
+
export function getPrimarySig(record) {
|
|
6
|
+
const attestSig = record.sigs?.find((s) => s.kid?.startsWith("attest:"));
|
|
7
|
+
return attestSig ?? record.sigs?.[0] ?? record.sig;
|
|
8
|
+
}
|
|
2
9
|
//# sourceMappingURL=types.js.map
|
package/dist/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAgDA;;;GAGG;AACH,MAAM,UAAU,aAAa,CAAC,MAAyD;IACrF,MAAM,SAAS,GAAG,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE,UAAU,CAAC,SAAS,CAAC,CAAC,CAAC;IACzE,OAAO,SAAS,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC;AACrD,CAAC"}
|
package/dist/verify.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { VerificationResult, VerifyOptions } from "./types.js";
|
|
2
2
|
export type { ClaimIdentity, ClaimRecord, ClaimSignature, ClaimVerificationResult, ES256PublicJwk, KeyRecord, SignedClaimData, VerificationResult, VerificationStep, VerifyOptions, } from "./types.js";
|
|
3
|
+
export { getPrimarySig } from "./types.js";
|
|
3
4
|
/**
|
|
4
5
|
* Verify all keytrace claims for a handle.
|
|
5
6
|
*
|
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;AASxK,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;AACpB,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAE3C;;;;;;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,7 +1,9 @@
|
|
|
1
|
+
import { getPrimarySig } from "./types.js";
|
|
1
2
|
import { resolveHandle, resolvePds, listClaimRecords, getRecordByUri } from "./atproto.js";
|
|
2
3
|
import { verifyES256Signature } from "./crypto/signature.js";
|
|
3
4
|
/** Default trusted signer handles */
|
|
4
5
|
const DEFAULT_TRUSTED_SIGNERS = ["keytrace.dev"];
|
|
6
|
+
export { getPrimarySig } from "./types.js";
|
|
5
7
|
/**
|
|
6
8
|
* Verify all keytrace claims for a handle.
|
|
7
9
|
*
|
|
@@ -65,8 +67,10 @@ export async function getClaimsForDid(did, options) {
|
|
|
65
67
|
async function verifySingleClaim(did, uri, rkey, claim, trustedDids, options) {
|
|
66
68
|
const steps = [];
|
|
67
69
|
try {
|
|
70
|
+
// Resolve the primary signature (supports both old `sig` and new `sigs` format)
|
|
71
|
+
const sig = getPrimarySig(claim);
|
|
68
72
|
// Step 1: Validate claim structure
|
|
69
|
-
if (!
|
|
73
|
+
if (!sig?.src || !sig?.attestation || !sig?.signedAt) {
|
|
70
74
|
steps.push({
|
|
71
75
|
step: "validate_claim",
|
|
72
76
|
success: false,
|
|
@@ -76,9 +80,9 @@ async function verifySingleClaim(did, uri, rkey, claim, trustedDids, options) {
|
|
|
76
80
|
}
|
|
77
81
|
steps.push({ step: "validate_claim", success: true, detail: "Claim structure valid" });
|
|
78
82
|
// Step 2: Validate signing key is from a trusted signer
|
|
79
|
-
const signerDid = extractDidFromAtUri(
|
|
83
|
+
const signerDid = extractDidFromAtUri(sig.src);
|
|
80
84
|
if (!signerDid || !trustedDids.has(signerDid)) {
|
|
81
|
-
const error = `Signing key is not from a trusted signer (source: ${
|
|
85
|
+
const error = `Signing key is not from a trusted signer (source: ${sig.src})`;
|
|
82
86
|
steps.push({ step: "validate_signer", success: false, error });
|
|
83
87
|
return buildResult(uri, rkey, claim, false, steps, error);
|
|
84
88
|
}
|
|
@@ -86,8 +90,8 @@ async function verifySingleClaim(did, uri, rkey, claim, trustedDids, options) {
|
|
|
86
90
|
// Step 3: Fetch the signing key
|
|
87
91
|
let keyRecord;
|
|
88
92
|
try {
|
|
89
|
-
keyRecord = await getRecordByUri(
|
|
90
|
-
steps.push({ step: "fetch_key", success: true, detail: `Fetched key from ${
|
|
93
|
+
keyRecord = await getRecordByUri(sig.src, options);
|
|
94
|
+
steps.push({ step: "fetch_key", success: true, detail: `Fetched key from ${sig.src}` });
|
|
91
95
|
}
|
|
92
96
|
catch (err) {
|
|
93
97
|
const error = `Failed to fetch signing key: ${err instanceof Error ? err.message : String(err)}`;
|
|
@@ -108,20 +112,44 @@ async function verifySingleClaim(did, uri, rkey, claim, trustedDids, options) {
|
|
|
108
112
|
steps.push({ step: "parse_key", success: false, error });
|
|
109
113
|
return buildResult(uri, rkey, claim, false, steps, error);
|
|
110
114
|
}
|
|
111
|
-
// Step 5: Reconstruct the signed claim data
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
115
|
+
// Step 5: Reconstruct the signed claim data.
|
|
116
|
+
// Use signedFields to determine which fields were signed and reconstruct them.
|
|
117
|
+
const fields = sig.signedFields ?? [];
|
|
118
|
+
const isNewFormat = fields.includes("identity.subject");
|
|
119
|
+
let signedData;
|
|
120
|
+
if (isNewFormat) {
|
|
121
|
+
// New format: signedFields tells us exactly which fields to include.
|
|
122
|
+
// Map each field name to its value from the record.
|
|
123
|
+
const valueMap = {
|
|
124
|
+
claimUri: claim.claimUri,
|
|
125
|
+
createdAt: sig.signedAt, // createdAt was set to signedAt during attestation
|
|
126
|
+
did,
|
|
127
|
+
"identity.subject": claim.identity.subject,
|
|
128
|
+
type: claim.type,
|
|
129
|
+
};
|
|
130
|
+
signedData = {};
|
|
131
|
+
for (const field of fields) {
|
|
132
|
+
if (field in valueMap) {
|
|
133
|
+
signedData[field] = valueMap[field];
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
// Legacy format: { did, subject, type, verifiedAt }
|
|
139
|
+
signedData = {
|
|
140
|
+
did,
|
|
141
|
+
subject: claim.identity.subject,
|
|
142
|
+
type: claim.type,
|
|
143
|
+
verifiedAt: sig.signedAt,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
118
146
|
steps.push({
|
|
119
147
|
step: "reconstruct_data",
|
|
120
148
|
success: true,
|
|
121
|
-
detail: `Reconstructed signed data for ${claim.type}:${claim.identity.subject}`,
|
|
149
|
+
detail: `Reconstructed signed data for ${claim.type}:${claim.identity.subject} (${isNewFormat ? "new" : "legacy"} format)`,
|
|
122
150
|
});
|
|
123
151
|
// Step 6: Verify the signature
|
|
124
|
-
const isValid = await verifyES256Signature(signedData,
|
|
152
|
+
const isValid = await verifyES256Signature(signedData, sig.attestation, publicJwk);
|
|
125
153
|
if (isValid) {
|
|
126
154
|
steps.push({ step: "verify_signature", success: true, detail: "Signature verified" });
|
|
127
155
|
return buildResult(uri, rkey, claim, true, steps);
|
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;AAE7D,qCAAqC;AACrC,MAAM,uBAAuB,GAAG,CAAC,cAAc,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"verify.js","sourceRoot":"","sources":["../src/verify.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/constants.ts
CHANGED
|
@@ -2,4 +2,5 @@ export const PUBLIC_API_URL = "https://public.api.bsky.app";
|
|
|
2
2
|
export const PLC_DIRECTORY_URL = "https://plc.directory";
|
|
3
3
|
export const COLLECTION_NSID = "dev.keytrace.claim";
|
|
4
4
|
export const KEY_COLLECTION_NSID = "dev.keytrace.key";
|
|
5
|
+
export const SERVER_KEY_COLLECTION_NSID = "dev.keytrace.serverPublicKey";
|
|
5
6
|
export const DEFAULT_TIMEOUT = 10000;
|
package/src/types.ts
CHANGED
|
@@ -20,27 +20,47 @@ export interface ClaimSignature {
|
|
|
20
20
|
signedAt: string;
|
|
21
21
|
/** JWS compact serialization */
|
|
22
22
|
attestation: string;
|
|
23
|
+
/** Ordered list of field names included in the signed payload */
|
|
24
|
+
signedFields?: string[];
|
|
25
|
+
/** Datetime when this signature was retracted (ISO 8601) */
|
|
26
|
+
retractedAt?: string;
|
|
23
27
|
}
|
|
24
28
|
|
|
25
29
|
/**
|
|
26
|
-
* Raw claim record from ATProto
|
|
30
|
+
* Raw claim record from ATProto.
|
|
31
|
+
* Supports both legacy format (sig: single object) and new format (sigs: array).
|
|
27
32
|
*/
|
|
28
33
|
export interface ClaimRecord {
|
|
29
34
|
$type: "dev.keytrace.claim";
|
|
30
35
|
type: string;
|
|
31
36
|
claimUri: string;
|
|
32
37
|
identity: ClaimIdentity;
|
|
33
|
-
|
|
38
|
+
/** @deprecated Use sigs array instead. Retained for backwards compatibility with old records. */
|
|
39
|
+
sig?: ClaimSignature;
|
|
40
|
+
/** One or more cryptographic attestation signatures from verification services */
|
|
41
|
+
sigs?: ClaimSignature[];
|
|
34
42
|
comment?: string;
|
|
35
43
|
createdAt: string;
|
|
36
44
|
prerelease?: boolean;
|
|
45
|
+
nonce?: string;
|
|
46
|
+
retractedAt?: string;
|
|
37
47
|
}
|
|
38
48
|
|
|
39
49
|
/**
|
|
40
|
-
*
|
|
50
|
+
* Get the attestation signature from a claim record.
|
|
51
|
+
* Looks for a sig with kid starting with "attest:", falls back to sigs[0] or legacy sig field.
|
|
52
|
+
*/
|
|
53
|
+
export function getPrimarySig(record: { sigs?: ClaimSignature[]; sig?: ClaimSignature }): ClaimSignature | undefined {
|
|
54
|
+
const attestSig = record.sigs?.find((s) => s.kid?.startsWith("attest:"));
|
|
55
|
+
return attestSig ?? record.sigs?.[0] ?? record.sig;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Public key record from keytrace service.
|
|
60
|
+
* Supports both old (dev.keytrace.key) and new (dev.keytrace.serverPublicKey) collection names.
|
|
41
61
|
*/
|
|
42
62
|
export interface KeyRecord {
|
|
43
|
-
$type: "dev.keytrace.key";
|
|
63
|
+
$type: "dev.keytrace.key" | "dev.keytrace.serverPublicKey";
|
|
44
64
|
publicJwk: string;
|
|
45
65
|
validFrom: string;
|
|
46
66
|
validUntil: string;
|
|
@@ -57,14 +77,11 @@ export interface ES256PublicJwk {
|
|
|
57
77
|
}
|
|
58
78
|
|
|
59
79
|
/**
|
|
60
|
-
* Claim data that is signed (canonical form)
|
|
80
|
+
* Claim data that is signed (canonical form).
|
|
81
|
+
* New format uses record field names: claimUri, did, identity.subject, type.
|
|
82
|
+
* Legacy format used: did, subject, type, verifiedAt.
|
|
61
83
|
*/
|
|
62
|
-
export
|
|
63
|
-
did: string;
|
|
64
|
-
subject: string;
|
|
65
|
-
type: string;
|
|
66
|
-
verifiedAt: string;
|
|
67
|
-
}
|
|
84
|
+
export type SignedClaimData = Record<string, string>;
|
|
68
85
|
|
|
69
86
|
/**
|
|
70
87
|
* Verification step detail for debugging/auditing
|
package/src/verify.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ClaimRecord, ClaimVerificationResult, ES256PublicJwk, KeyRecord, SignedClaimData, VerificationResult, VerificationStep, VerifyOptions } from "./types.js";
|
|
2
|
+
import { getPrimarySig } from "./types.js";
|
|
2
3
|
import { resolveHandle, resolvePds, listClaimRecords, getRecordByUri } from "./atproto.js";
|
|
3
4
|
import { verifyES256Signature } from "./crypto/signature.js";
|
|
4
5
|
|
|
@@ -18,6 +19,7 @@ export type {
|
|
|
18
19
|
VerificationStep,
|
|
19
20
|
VerifyOptions,
|
|
20
21
|
} from "./types.js";
|
|
22
|
+
export { getPrimarySig } from "./types.js";
|
|
21
23
|
|
|
22
24
|
/**
|
|
23
25
|
* Verify all keytrace claims for a handle.
|
|
@@ -90,8 +92,11 @@ async function verifySingleClaim(did: string, uri: string, rkey: string, claim:
|
|
|
90
92
|
const steps: VerificationStep[] = [];
|
|
91
93
|
|
|
92
94
|
try {
|
|
95
|
+
// Resolve the primary signature (supports both old `sig` and new `sigs` format)
|
|
96
|
+
const sig = getPrimarySig(claim);
|
|
97
|
+
|
|
93
98
|
// Step 1: Validate claim structure
|
|
94
|
-
if (!
|
|
99
|
+
if (!sig?.src || !sig?.attestation || !sig?.signedAt) {
|
|
95
100
|
steps.push({
|
|
96
101
|
step: "validate_claim",
|
|
97
102
|
success: false,
|
|
@@ -102,9 +107,9 @@ async function verifySingleClaim(did: string, uri: string, rkey: string, claim:
|
|
|
102
107
|
steps.push({ step: "validate_claim", success: true, detail: "Claim structure valid" });
|
|
103
108
|
|
|
104
109
|
// Step 2: Validate signing key is from a trusted signer
|
|
105
|
-
const signerDid = extractDidFromAtUri(
|
|
110
|
+
const signerDid = extractDidFromAtUri(sig.src);
|
|
106
111
|
if (!signerDid || !trustedDids.has(signerDid)) {
|
|
107
|
-
const error = `Signing key is not from a trusted signer (source: ${
|
|
112
|
+
const error = `Signing key is not from a trusted signer (source: ${sig.src})`;
|
|
108
113
|
steps.push({ step: "validate_signer", success: false, error });
|
|
109
114
|
return buildResult(uri, rkey, claim, false, steps, error);
|
|
110
115
|
}
|
|
@@ -113,8 +118,8 @@ async function verifySingleClaim(did: string, uri: string, rkey: string, claim:
|
|
|
113
118
|
// Step 3: Fetch the signing key
|
|
114
119
|
let keyRecord: KeyRecord;
|
|
115
120
|
try {
|
|
116
|
-
keyRecord = await getRecordByUri<KeyRecord>(
|
|
117
|
-
steps.push({ step: "fetch_key", success: true, detail: `Fetched key from ${
|
|
121
|
+
keyRecord = await getRecordByUri<KeyRecord>(sig.src, options);
|
|
122
|
+
steps.push({ step: "fetch_key", success: true, detail: `Fetched key from ${sig.src}` });
|
|
118
123
|
} catch (err) {
|
|
119
124
|
const error = `Failed to fetch signing key: ${err instanceof Error ? err.message : String(err)}`;
|
|
120
125
|
steps.push({ step: "fetch_key", success: false, error });
|
|
@@ -135,21 +140,44 @@ async function verifySingleClaim(did: string, uri: string, rkey: string, claim:
|
|
|
135
140
|
return buildResult(uri, rkey, claim, false, steps, error);
|
|
136
141
|
}
|
|
137
142
|
|
|
138
|
-
// Step 5: Reconstruct the signed claim data
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
143
|
+
// Step 5: Reconstruct the signed claim data.
|
|
144
|
+
// Use signedFields to determine which fields were signed and reconstruct them.
|
|
145
|
+
const fields = sig.signedFields ?? [];
|
|
146
|
+
const isNewFormat = fields.includes("identity.subject");
|
|
147
|
+
let signedData: SignedClaimData;
|
|
148
|
+
if (isNewFormat) {
|
|
149
|
+
// New format: signedFields tells us exactly which fields to include.
|
|
150
|
+
// Map each field name to its value from the record.
|
|
151
|
+
const valueMap: Record<string, string> = {
|
|
152
|
+
claimUri: claim.claimUri,
|
|
153
|
+
createdAt: sig.signedAt, // createdAt was set to signedAt during attestation
|
|
154
|
+
did,
|
|
155
|
+
"identity.subject": claim.identity.subject,
|
|
156
|
+
type: claim.type,
|
|
157
|
+
};
|
|
158
|
+
signedData = {};
|
|
159
|
+
for (const field of fields) {
|
|
160
|
+
if (field in valueMap) {
|
|
161
|
+
signedData[field] = valueMap[field];
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
} else {
|
|
165
|
+
// Legacy format: { did, subject, type, verifiedAt }
|
|
166
|
+
signedData = {
|
|
167
|
+
did,
|
|
168
|
+
subject: claim.identity.subject,
|
|
169
|
+
type: claim.type,
|
|
170
|
+
verifiedAt: sig.signedAt,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
145
173
|
steps.push({
|
|
146
174
|
step: "reconstruct_data",
|
|
147
175
|
success: true,
|
|
148
|
-
detail: `Reconstructed signed data for ${claim.type}:${claim.identity.subject}`,
|
|
176
|
+
detail: `Reconstructed signed data for ${claim.type}:${claim.identity.subject} (${isNewFormat ? "new" : "legacy"} format)`,
|
|
149
177
|
});
|
|
150
178
|
|
|
151
179
|
// Step 6: Verify the signature
|
|
152
|
-
const isValid = await verifyES256Signature(signedData,
|
|
180
|
+
const isValid = await verifyES256Signature(signedData, sig.attestation, publicJwk);
|
|
153
181
|
|
|
154
182
|
if (isValid) {
|
|
155
183
|
steps.push({ step: "verify_signature", success: true, detail: "Signature verified" });
|