@keytrace/claims 0.0.5
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 +105 -0
- package/dist/atproto.d.ts +22 -0
- package/dist/atproto.d.ts.map +1 -0
- package/dist/atproto.js +134 -0
- package/dist/atproto.js.map +1 -0
- package/dist/constants.d.ts +6 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +6 -0
- package/dist/constants.js.map +1 -0
- package/dist/crypto/base64url.d.ts +9 -0
- package/dist/crypto/base64url.d.ts.map +1 -0
- package/dist/crypto/base64url.js +29 -0
- package/dist/crypto/base64url.js.map +1 -0
- package/dist/crypto/canonicalize.d.ts +12 -0
- package/dist/crypto/canonicalize.d.ts.map +1 -0
- package/dist/crypto/canonicalize.js +67 -0
- package/dist/crypto/canonicalize.js.map +1 -0
- package/dist/crypto/signature.d.ts +11 -0
- package/dist/crypto/signature.d.ts.map +1 -0
- package/dist/crypto/signature.js +31 -0
- package/dist/crypto/signature.js.map +1 -0
- package/dist/types.d.ts +125 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/verify.d.ts +19 -0
- package/dist/verify.d.ts.map +1 -0
- package/dist/verify.js +143 -0
- package/dist/verify.js.map +1 -0
- package/package.json +36 -0
- package/src/atproto.ts +177 -0
- package/src/constants.ts +5 -0
- package/src/crypto/base64url.ts +29 -0
- package/src/crypto/canonicalize.ts +74 -0
- package/src/crypto/signature.ts +36 -0
- package/src/types.ts +133 -0
- package/src/verify.ts +167 -0
package/src/verify.ts
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import type { ClaimRecord, ClaimVerificationResult, ES256PublicJwk, KeyRecord, SignedClaimData, VerificationResult, VerificationStep, VerifyOptions } from "./types.js";
|
|
2
|
+
import { resolveHandle, resolvePds, listClaimRecords, getRecordByUri } from "./atproto.js";
|
|
3
|
+
import { verifyES256Signature } from "./crypto/signature.js";
|
|
4
|
+
|
|
5
|
+
// Re-export types for convenience
|
|
6
|
+
export type {
|
|
7
|
+
ClaimIdentity,
|
|
8
|
+
ClaimRecord,
|
|
9
|
+
ClaimSignature,
|
|
10
|
+
ClaimVerificationResult,
|
|
11
|
+
ES256PublicJwk,
|
|
12
|
+
KeyRecord,
|
|
13
|
+
SignedClaimData,
|
|
14
|
+
VerificationResult,
|
|
15
|
+
VerificationStep,
|
|
16
|
+
VerifyOptions,
|
|
17
|
+
} from "./types.js";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Verify all keytrace claims for a handle.
|
|
21
|
+
*
|
|
22
|
+
* @param handle The ATProto handle (e.g., "alice.bsky.social") or DID
|
|
23
|
+
* @param options Optional configuration
|
|
24
|
+
* @returns Verification results for all claims
|
|
25
|
+
*/
|
|
26
|
+
export async function getClaimsForHandle(handle: string, options?: VerifyOptions): Promise<VerificationResult> {
|
|
27
|
+
const did = await resolveHandle(handle, options);
|
|
28
|
+
const result = await getClaimsForDid(did, options);
|
|
29
|
+
|
|
30
|
+
return {
|
|
31
|
+
...result,
|
|
32
|
+
handle: handle.startsWith("did:") ? undefined : handle,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Verify all keytrace claims for a DID.
|
|
38
|
+
*
|
|
39
|
+
* @param did The ATProto DID (e.g., "did:plc:abc123")
|
|
40
|
+
* @param options Optional configuration
|
|
41
|
+
* @returns Verification results for all claims
|
|
42
|
+
*/
|
|
43
|
+
export async function getClaimsForDid(did: string, options?: VerifyOptions): Promise<VerificationResult> {
|
|
44
|
+
// Resolve PDS for the user
|
|
45
|
+
const pdsUrl = await resolvePds(did, options);
|
|
46
|
+
|
|
47
|
+
// Fetch all claim records
|
|
48
|
+
let claimRecords: Array<{ uri: string; rkey: string; value: ClaimRecord }>;
|
|
49
|
+
try {
|
|
50
|
+
claimRecords = await listClaimRecords(pdsUrl, did, options);
|
|
51
|
+
} catch {
|
|
52
|
+
// No claims found
|
|
53
|
+
return {
|
|
54
|
+
did,
|
|
55
|
+
claims: [],
|
|
56
|
+
summary: { total: 0, verified: 0, failed: 0 },
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Verify each claim
|
|
61
|
+
const claimResults: ClaimVerificationResult[] = [];
|
|
62
|
+
|
|
63
|
+
for (const record of claimRecords) {
|
|
64
|
+
const result = await verifySingleClaim(did, record.uri, record.rkey, record.value, options);
|
|
65
|
+
claimResults.push(result);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
did,
|
|
70
|
+
claims: claimResults,
|
|
71
|
+
summary: {
|
|
72
|
+
total: claimResults.length,
|
|
73
|
+
verified: claimResults.filter((c) => c.verified).length,
|
|
74
|
+
failed: claimResults.filter((c) => !c.verified).length,
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Verify a single claim's signature.
|
|
81
|
+
*/
|
|
82
|
+
async function verifySingleClaim(did: string, uri: string, rkey: string, claim: ClaimRecord, options?: VerifyOptions): Promise<ClaimVerificationResult> {
|
|
83
|
+
const steps: VerificationStep[] = [];
|
|
84
|
+
|
|
85
|
+
try {
|
|
86
|
+
// Step 1: Validate claim structure
|
|
87
|
+
if (!claim.sig?.src || !claim.sig?.attestation || !claim.sig?.signedAt) {
|
|
88
|
+
steps.push({
|
|
89
|
+
step: "validate_claim",
|
|
90
|
+
success: false,
|
|
91
|
+
error: "Missing signature fields",
|
|
92
|
+
});
|
|
93
|
+
return buildResult(uri, rkey, claim, false, steps, "Missing signature fields");
|
|
94
|
+
}
|
|
95
|
+
steps.push({ step: "validate_claim", success: true, detail: "Claim structure valid" });
|
|
96
|
+
|
|
97
|
+
// Step 2: Fetch the signing key
|
|
98
|
+
let keyRecord: KeyRecord;
|
|
99
|
+
try {
|
|
100
|
+
keyRecord = await getRecordByUri<KeyRecord>(claim.sig.src, options);
|
|
101
|
+
steps.push({ step: "fetch_key", success: true, detail: `Fetched key from ${claim.sig.src}` });
|
|
102
|
+
} catch (err) {
|
|
103
|
+
const error = `Failed to fetch signing key: ${err instanceof Error ? err.message : String(err)}`;
|
|
104
|
+
steps.push({ step: "fetch_key", success: false, error });
|
|
105
|
+
return buildResult(uri, rkey, claim, false, steps, error);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Step 3: Parse the public JWK
|
|
109
|
+
let publicJwk: ES256PublicJwk;
|
|
110
|
+
try {
|
|
111
|
+
publicJwk = JSON.parse(keyRecord.publicJwk) as ES256PublicJwk;
|
|
112
|
+
if (publicJwk.kty !== "EC" || publicJwk.crv !== "P-256") {
|
|
113
|
+
throw new Error("Invalid key type");
|
|
114
|
+
}
|
|
115
|
+
steps.push({ step: "parse_key", success: true, detail: "Parsed ES256 public key" });
|
|
116
|
+
} catch (err) {
|
|
117
|
+
const error = `Invalid public key format: ${err instanceof Error ? err.message : String(err)}`;
|
|
118
|
+
steps.push({ step: "parse_key", success: false, error });
|
|
119
|
+
return buildResult(uri, rkey, claim, false, steps, error);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Step 4: Reconstruct the signed claim data
|
|
123
|
+
const signedData: SignedClaimData = {
|
|
124
|
+
did,
|
|
125
|
+
subject: claim.identity.subject,
|
|
126
|
+
type: claim.type,
|
|
127
|
+
verifiedAt: claim.sig.signedAt,
|
|
128
|
+
};
|
|
129
|
+
steps.push({
|
|
130
|
+
step: "reconstruct_data",
|
|
131
|
+
success: true,
|
|
132
|
+
detail: `Reconstructed signed data for ${claim.type}:${claim.identity.subject}`,
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
// Step 5: Verify the signature
|
|
136
|
+
const isValid = await verifyES256Signature(signedData, claim.sig.attestation, publicJwk);
|
|
137
|
+
|
|
138
|
+
if (isValid) {
|
|
139
|
+
steps.push({ step: "verify_signature", success: true, detail: "Signature verified" });
|
|
140
|
+
return buildResult(uri, rkey, claim, true, steps);
|
|
141
|
+
} else {
|
|
142
|
+
steps.push({ step: "verify_signature", success: false, error: "Signature verification failed" });
|
|
143
|
+
return buildResult(uri, rkey, claim, false, steps, "Signature verification failed");
|
|
144
|
+
}
|
|
145
|
+
} catch (err) {
|
|
146
|
+
const error = `Unexpected error: ${err instanceof Error ? err.message : String(err)}`;
|
|
147
|
+
steps.push({ step: "unknown", success: false, error });
|
|
148
|
+
return buildResult(uri, rkey, claim, false, steps, error);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Build a claim verification result.
|
|
154
|
+
*/
|
|
155
|
+
function buildResult(uri: string, rkey: string, claim: ClaimRecord, verified: boolean, steps: VerificationStep[], error?: string): ClaimVerificationResult {
|
|
156
|
+
return {
|
|
157
|
+
uri,
|
|
158
|
+
rkey,
|
|
159
|
+
type: claim.type,
|
|
160
|
+
claimUri: claim.claimUri,
|
|
161
|
+
verified,
|
|
162
|
+
steps,
|
|
163
|
+
error,
|
|
164
|
+
identity: claim.identity,
|
|
165
|
+
claim,
|
|
166
|
+
};
|
|
167
|
+
}
|