@arcblock/jwt 1.29.21 → 1.29.22
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/esm/index.d.mts +40 -1
- package/esm/index.mjs +101 -5
- package/lib/index.cjs +104 -4
- package/lib/index.d.cts +40 -1
- package/package.json +5 -5
package/esm/index.d.mts
CHANGED
|
@@ -35,6 +35,28 @@ type JwtVerifyOptions = Partial<{
|
|
|
35
35
|
*/
|
|
36
36
|
declare function sign(signer: string, sk?: BytesType, payload?: {}, doSign?: boolean, version?: string): Promise<string>;
|
|
37
37
|
declare function signV2(signer: string, sk?: BytesType, payload?: any): Promise<string>;
|
|
38
|
+
type PasskeyAssertion = {
|
|
39
|
+
authenticatorData: string;
|
|
40
|
+
clientDataJSON: string;
|
|
41
|
+
signature: string;
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* Compute the WebAuthn challenge for an unsigned JWT token.
|
|
45
|
+
* Always uses SHA3 hash-before-sign (v1.1.0 semantics, required for passkey).
|
|
46
|
+
*
|
|
47
|
+
* @param unsignedToken - the unsigned token (headerB64.bodyB64), may also accept 3-part tokens (ignores third segment)
|
|
48
|
+
* @returns hex-encoded SHA3 hash suitable as WebAuthn challenge
|
|
49
|
+
*/
|
|
50
|
+
declare function getChallenge(unsignedToken: string): string;
|
|
51
|
+
/**
|
|
52
|
+
* Assemble a complete JWT from an unsigned token and a passkey assertion.
|
|
53
|
+
* The assertion is JSON-serialized and base64-encoded as the JWT's third segment.
|
|
54
|
+
*
|
|
55
|
+
* @param unsignedToken - the unsigned token (headerB64.bodyB64)
|
|
56
|
+
* @param assertion - WebAuthn assertion with base64url-encoded fields
|
|
57
|
+
* @returns complete 3-part JWT string
|
|
58
|
+
*/
|
|
59
|
+
declare function assemble(unsignedToken: string, assertion: PasskeyAssertion): string;
|
|
38
60
|
declare function decode(token: string, bodyOnly?: true): JwtBody;
|
|
39
61
|
declare function decode(token: string, bodyOnly?: false): JwtToken;
|
|
40
62
|
/**
|
|
@@ -88,6 +110,23 @@ declare function signDelegationToken(signer: string, sk: BytesType, payload: {
|
|
|
88
110
|
nbf?: string;
|
|
89
111
|
exp?: string;
|
|
90
112
|
}): Promise<string>;
|
|
113
|
+
/**
|
|
114
|
+
* Create an unsigned delegation token for passkey two-phase signing.
|
|
115
|
+
* Unlike signDelegationToken, this does not sign (passkey has no sk), and pk is passed directly.
|
|
116
|
+
*/
|
|
117
|
+
declare function signDelegationTokenUnsigned(signer: string, pk: BytesType, payload: {
|
|
118
|
+
sub: string;
|
|
119
|
+
ops: string[];
|
|
120
|
+
deny?: string[];
|
|
121
|
+
delegation?: string;
|
|
122
|
+
iat?: string;
|
|
123
|
+
nbf?: string;
|
|
124
|
+
exp?: string;
|
|
125
|
+
}): Promise<string>;
|
|
126
|
+
/**
|
|
127
|
+
* Assemble a complete delegation token from an unsigned token and a passkey assertion.
|
|
128
|
+
*/
|
|
129
|
+
declare function assembleDelegationToken(unsignedToken: string, assertion: PasskeyAssertion): string;
|
|
91
130
|
declare function decodeDelegationToken(token: string): DelegationToken;
|
|
92
131
|
declare function verifyDelegationToken(token: string): Promise<boolean>;
|
|
93
132
|
declare function checkDelegationTokenScope(scope: string, {
|
|
@@ -98,4 +137,4 @@ declare function checkDelegationTokenScope(scope: string, {
|
|
|
98
137
|
deny?: string[];
|
|
99
138
|
}): boolean;
|
|
100
139
|
//#endregion
|
|
101
|
-
export { DelegationToken, DelegationTokenBody, DelegationTokenHeader, JwtBody, JwtHeader, JwtToken, JwtVerifyOptions, checkDelegationTokenScope, decode, decodeDelegationToken, sign, signDelegationToken, signV2, verify, verifyDelegationToken };
|
|
140
|
+
export { DelegationToken, DelegationTokenBody, DelegationTokenHeader, JwtBody, JwtHeader, JwtToken, JwtVerifyOptions, PasskeyAssertion, assemble, assembleDelegationToken, checkDelegationTokenScope, decode, decodeDelegationToken, getChallenge, sign, signDelegationToken, signDelegationTokenUnsigned, signV2, verify, verifyDelegationToken };
|
package/esm/index.mjs
CHANGED
|
@@ -72,6 +72,32 @@ async function sign(signer, sk, payload = {}, doSign = true, version = "1.0.0")
|
|
|
72
72
|
async function signV2(signer, sk, payload = {}) {
|
|
73
73
|
return sign(signer, sk, payload, !!sk, "1.1.0");
|
|
74
74
|
}
|
|
75
|
+
/**
|
|
76
|
+
* Compute the WebAuthn challenge for an unsigned JWT token.
|
|
77
|
+
* Always uses SHA3 hash-before-sign (v1.1.0 semantics, required for passkey).
|
|
78
|
+
*
|
|
79
|
+
* @param unsignedToken - the unsigned token (headerB64.bodyB64), may also accept 3-part tokens (ignores third segment)
|
|
80
|
+
* @returns hex-encoded SHA3 hash suitable as WebAuthn challenge
|
|
81
|
+
*/
|
|
82
|
+
function getChallenge(unsignedToken) {
|
|
83
|
+
if (!unsignedToken) throw new Error("Cannot compute challenge from empty token");
|
|
84
|
+
const parts = unsignedToken.split(".");
|
|
85
|
+
return hasher(toHex(`${parts[0]}.${parts[1]}`));
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Assemble a complete JWT from an unsigned token and a passkey assertion.
|
|
89
|
+
* The assertion is JSON-serialized and base64-encoded as the JWT's third segment.
|
|
90
|
+
*
|
|
91
|
+
* @param unsignedToken - the unsigned token (headerB64.bodyB64)
|
|
92
|
+
* @param assertion - WebAuthn assertion with base64url-encoded fields
|
|
93
|
+
* @returns complete 3-part JWT string
|
|
94
|
+
*/
|
|
95
|
+
function assemble(unsignedToken, assertion) {
|
|
96
|
+
if (!unsignedToken) throw new Error("Cannot assemble JWT from empty token");
|
|
97
|
+
if (unsignedToken.split(".").length !== 2) throw new Error("Cannot assemble JWT: expected unsigned token with exactly 2 parts (headerB64.bodyB64)");
|
|
98
|
+
if (!assertion || !assertion.authenticatorData || !assertion.clientDataJSON || !assertion.signature) throw new Error("Cannot assemble JWT: assertion must contain authenticatorData, clientDataJSON, and signature");
|
|
99
|
+
return `${unsignedToken}.${toBase64(JSON.stringify(assertion))}`;
|
|
100
|
+
}
|
|
75
101
|
function decode(token, bodyOnly = true) {
|
|
76
102
|
const [headerB64, bodyB64, sigB64] = token.split(".");
|
|
77
103
|
const header = JSON.parse(fromBase64(headerB64).toString());
|
|
@@ -154,14 +180,27 @@ async function verify(token, signerPk, options) {
|
|
|
154
180
|
return false;
|
|
155
181
|
}
|
|
156
182
|
}
|
|
183
|
+
const alg = header.alg.toLowerCase();
|
|
184
|
+
if (alg === "passkey") {
|
|
185
|
+
const [, , sigB64] = token.split(".");
|
|
186
|
+
const assertionRaw = JSON.parse(fromBase64(sigB64).toString());
|
|
187
|
+
if (!assertionRaw.authenticatorData || !assertionRaw.clientDataJSON || !assertionRaw.signature) {
|
|
188
|
+
debug("verify.error.incompletePasskeyAssertion");
|
|
189
|
+
return false;
|
|
190
|
+
}
|
|
191
|
+
const challenge = hasher(toHex(`${headerB64}.${bodyB64}`));
|
|
192
|
+
const extra = JSON.stringify({
|
|
193
|
+
authenticatorData: assertionRaw.authenticatorData,
|
|
194
|
+
clientDataJSON: assertionRaw.clientDataJSON
|
|
195
|
+
});
|
|
196
|
+
return await getSigner(types.KeyType.PASSKEY).verify(challenge, assertionRaw.signature, signerPk, extra);
|
|
197
|
+
}
|
|
157
198
|
const signers = {
|
|
158
199
|
secp256k1: getSigner(types.KeyType.SECP256K1),
|
|
159
200
|
es256k: getSigner(types.KeyType.SECP256K1),
|
|
160
201
|
ed25519: getSigner(types.KeyType.ED25519),
|
|
161
|
-
ethereum: getSigner(types.KeyType.ETHEREUM)
|
|
162
|
-
passkey: getSigner(types.KeyType.PASSKEY)
|
|
202
|
+
ethereum: getSigner(types.KeyType.ETHEREUM)
|
|
163
203
|
};
|
|
164
|
-
const alg = header.alg.toLowerCase();
|
|
165
204
|
if (signers[alg]) {
|
|
166
205
|
const msgHex = toHex(`${headerB64}.${bodyB64}`);
|
|
167
206
|
const coercedBodyVersion = body.version ? semver.coerce(body.version) : null;
|
|
@@ -190,6 +229,10 @@ const delegationTokenHeaders = {
|
|
|
190
229
|
[types.KeyType.ETHEREUM]: {
|
|
191
230
|
alg: "Ethereum",
|
|
192
231
|
typ: "DelegationToken"
|
|
232
|
+
},
|
|
233
|
+
[types.KeyType.PASSKEY]: {
|
|
234
|
+
alg: "Passkey",
|
|
235
|
+
typ: "DelegationToken"
|
|
193
236
|
}
|
|
194
237
|
};
|
|
195
238
|
async function signDelegationToken(signer, sk, payload) {
|
|
@@ -223,6 +266,45 @@ async function signDelegationToken(signer, sk, payload) {
|
|
|
223
266
|
toBase64(getSigner(type.pk).sign(msgHash, sk))
|
|
224
267
|
].join(".");
|
|
225
268
|
}
|
|
269
|
+
/**
|
|
270
|
+
* Create an unsigned delegation token for passkey two-phase signing.
|
|
271
|
+
* Unlike signDelegationToken, this does not sign (passkey has no sk), and pk is passed directly.
|
|
272
|
+
*/
|
|
273
|
+
async function signDelegationTokenUnsigned(signer, pk, payload) {
|
|
274
|
+
if (isValid(signer) === false) throw new Error("Cannot sign DelegationToken with invalid signer");
|
|
275
|
+
const type = toTypeInfo(signer);
|
|
276
|
+
if (type.pk === void 0) throw new Error("Cannot determine key type from signer");
|
|
277
|
+
const header = delegationTokenHeaders[type.pk];
|
|
278
|
+
const headerB64 = toBase64(stringify(header));
|
|
279
|
+
const delegationSignerMethod = extractMethod(signer) || "abt";
|
|
280
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
281
|
+
const pkHex = typeof pk === "string" ? pk : toHex(pk);
|
|
282
|
+
const body = {
|
|
283
|
+
iss: toDid(signer, delegationSignerMethod),
|
|
284
|
+
sub: payload.sub,
|
|
285
|
+
iat: payload.iat || String(now),
|
|
286
|
+
nbf: payload.nbf || String(now),
|
|
287
|
+
exp: payload.exp || String(now + 300),
|
|
288
|
+
version: DELEGATION_TOKEN_VERSION,
|
|
289
|
+
ops: payload.ops,
|
|
290
|
+
pk: pkHex
|
|
291
|
+
};
|
|
292
|
+
if (payload.deny && payload.deny.length > 0) {
|
|
293
|
+
for (const d of payload.deny) if (!d.startsWith("fg:")) throw new Error(`Invalid deny pattern "${d}": must start with "fg:"`);
|
|
294
|
+
body.deny = payload.deny;
|
|
295
|
+
}
|
|
296
|
+
if (payload.delegation) body.delegation = payload.delegation;
|
|
297
|
+
return `${headerB64}.${toBase64(stringify(body))}`;
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Assemble a complete delegation token from an unsigned token and a passkey assertion.
|
|
301
|
+
*/
|
|
302
|
+
function assembleDelegationToken(unsignedToken, assertion) {
|
|
303
|
+
if (!unsignedToken) throw new Error("Cannot assemble DelegationToken from empty token");
|
|
304
|
+
if (unsignedToken.split(".").length !== 2) throw new Error("Cannot assemble DelegationToken: expected unsigned token with exactly 2 parts (headerB64.bodyB64)");
|
|
305
|
+
if (!assertion || !assertion.authenticatorData || !assertion.clientDataJSON || !assertion.signature) throw new Error("Cannot assemble DelegationToken: assertion must contain authenticatorData, clientDataJSON, and signature");
|
|
306
|
+
return `${unsignedToken}.${toBase64(JSON.stringify(assertion))}`;
|
|
307
|
+
}
|
|
226
308
|
function decodeDelegationToken(token) {
|
|
227
309
|
const parts = token.split(".");
|
|
228
310
|
if (parts.length !== 3) throw new Error("Invalid delegation token format: expected 3 parts");
|
|
@@ -265,13 +347,27 @@ async function verifyDelegationToken(token) {
|
|
|
265
347
|
debug("delegationToken.verify.error.futureIat");
|
|
266
348
|
return false;
|
|
267
349
|
}
|
|
350
|
+
const alg = header.alg.toLowerCase();
|
|
351
|
+
if (alg === "passkey") {
|
|
352
|
+
const [, , sigB64] = token.split(".");
|
|
353
|
+
const assertionRaw = JSON.parse(fromBase64(sigB64).toString());
|
|
354
|
+
if (!assertionRaw.authenticatorData || !assertionRaw.clientDataJSON || !assertionRaw.signature) {
|
|
355
|
+
debug("delegationToken.verify.error.incompletePasskeyAssertion");
|
|
356
|
+
return false;
|
|
357
|
+
}
|
|
358
|
+
const challenge = hasher(toHex(`${headerB64}.${bodyB64}`));
|
|
359
|
+
const extra = JSON.stringify({
|
|
360
|
+
authenticatorData: assertionRaw.authenticatorData,
|
|
361
|
+
clientDataJSON: assertionRaw.clientDataJSON
|
|
362
|
+
});
|
|
363
|
+
return await getSigner(types.KeyType.PASSKEY).verify(challenge, assertionRaw.signature, body.pk, extra);
|
|
364
|
+
}
|
|
268
365
|
const signers = {
|
|
269
366
|
ed25519: getSigner(types.KeyType.ED25519),
|
|
270
367
|
es256k: getSigner(types.KeyType.SECP256K1),
|
|
271
368
|
secp256k1: getSigner(types.KeyType.SECP256K1),
|
|
272
369
|
ethereum: getSigner(types.KeyType.ETHEREUM)
|
|
273
370
|
};
|
|
274
|
-
const alg = header.alg.toLowerCase();
|
|
275
371
|
if (!signers[alg]) {
|
|
276
372
|
debug("delegationToken.verify.error.unknownAlg");
|
|
277
373
|
return false;
|
|
@@ -289,4 +385,4 @@ function checkDelegationTokenScope(scope, { ops, deny }) {
|
|
|
289
385
|
}
|
|
290
386
|
|
|
291
387
|
//#endregion
|
|
292
|
-
export { checkDelegationTokenScope, decode, decodeDelegationToken, sign, signDelegationToken, signV2, verify, verifyDelegationToken };
|
|
388
|
+
export { assemble, assembleDelegationToken, checkDelegationTokenScope, decode, decodeDelegationToken, getChallenge, sign, signDelegationToken, signDelegationTokenUnsigned, signV2, verify, verifyDelegationToken };
|
package/lib/index.cjs
CHANGED
|
@@ -76,6 +76,32 @@ async function sign(signer, sk, payload = {}, doSign = true, version = "1.0.0")
|
|
|
76
76
|
async function signV2(signer, sk, payload = {}) {
|
|
77
77
|
return sign(signer, sk, payload, !!sk, "1.1.0");
|
|
78
78
|
}
|
|
79
|
+
/**
|
|
80
|
+
* Compute the WebAuthn challenge for an unsigned JWT token.
|
|
81
|
+
* Always uses SHA3 hash-before-sign (v1.1.0 semantics, required for passkey).
|
|
82
|
+
*
|
|
83
|
+
* @param unsignedToken - the unsigned token (headerB64.bodyB64), may also accept 3-part tokens (ignores third segment)
|
|
84
|
+
* @returns hex-encoded SHA3 hash suitable as WebAuthn challenge
|
|
85
|
+
*/
|
|
86
|
+
function getChallenge(unsignedToken) {
|
|
87
|
+
if (!unsignedToken) throw new Error("Cannot compute challenge from empty token");
|
|
88
|
+
const parts = unsignedToken.split(".");
|
|
89
|
+
return hasher((0, _ocap_util.toHex)(`${parts[0]}.${parts[1]}`));
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Assemble a complete JWT from an unsigned token and a passkey assertion.
|
|
93
|
+
* The assertion is JSON-serialized and base64-encoded as the JWT's third segment.
|
|
94
|
+
*
|
|
95
|
+
* @param unsignedToken - the unsigned token (headerB64.bodyB64)
|
|
96
|
+
* @param assertion - WebAuthn assertion with base64url-encoded fields
|
|
97
|
+
* @returns complete 3-part JWT string
|
|
98
|
+
*/
|
|
99
|
+
function assemble(unsignedToken, assertion) {
|
|
100
|
+
if (!unsignedToken) throw new Error("Cannot assemble JWT from empty token");
|
|
101
|
+
if (unsignedToken.split(".").length !== 2) throw new Error("Cannot assemble JWT: expected unsigned token with exactly 2 parts (headerB64.bodyB64)");
|
|
102
|
+
if (!assertion || !assertion.authenticatorData || !assertion.clientDataJSON || !assertion.signature) throw new Error("Cannot assemble JWT: assertion must contain authenticatorData, clientDataJSON, and signature");
|
|
103
|
+
return `${unsignedToken}.${(0, _ocap_util.toBase64)(JSON.stringify(assertion))}`;
|
|
104
|
+
}
|
|
79
105
|
function decode(token, bodyOnly = true) {
|
|
80
106
|
const [headerB64, bodyB64, sigB64] = token.split(".");
|
|
81
107
|
const header = JSON.parse((0, _ocap_util.fromBase64)(headerB64).toString());
|
|
@@ -158,14 +184,27 @@ async function verify(token, signerPk, options) {
|
|
|
158
184
|
return false;
|
|
159
185
|
}
|
|
160
186
|
}
|
|
187
|
+
const alg = header.alg.toLowerCase();
|
|
188
|
+
if (alg === "passkey") {
|
|
189
|
+
const [, , sigB64] = token.split(".");
|
|
190
|
+
const assertionRaw = JSON.parse((0, _ocap_util.fromBase64)(sigB64).toString());
|
|
191
|
+
if (!assertionRaw.authenticatorData || !assertionRaw.clientDataJSON || !assertionRaw.signature) {
|
|
192
|
+
debug$1("verify.error.incompletePasskeyAssertion");
|
|
193
|
+
return false;
|
|
194
|
+
}
|
|
195
|
+
const challenge = hasher((0, _ocap_util.toHex)(`${headerB64}.${bodyB64}`));
|
|
196
|
+
const extra = JSON.stringify({
|
|
197
|
+
authenticatorData: assertionRaw.authenticatorData,
|
|
198
|
+
clientDataJSON: assertionRaw.clientDataJSON
|
|
199
|
+
});
|
|
200
|
+
return await (0, _ocap_mcrypto.getSigner)(_ocap_mcrypto.types.KeyType.PASSKEY).verify(challenge, assertionRaw.signature, signerPk, extra);
|
|
201
|
+
}
|
|
161
202
|
const signers = {
|
|
162
203
|
secp256k1: (0, _ocap_mcrypto.getSigner)(_ocap_mcrypto.types.KeyType.SECP256K1),
|
|
163
204
|
es256k: (0, _ocap_mcrypto.getSigner)(_ocap_mcrypto.types.KeyType.SECP256K1),
|
|
164
205
|
ed25519: (0, _ocap_mcrypto.getSigner)(_ocap_mcrypto.types.KeyType.ED25519),
|
|
165
|
-
ethereum: (0, _ocap_mcrypto.getSigner)(_ocap_mcrypto.types.KeyType.ETHEREUM)
|
|
166
|
-
passkey: (0, _ocap_mcrypto.getSigner)(_ocap_mcrypto.types.KeyType.PASSKEY)
|
|
206
|
+
ethereum: (0, _ocap_mcrypto.getSigner)(_ocap_mcrypto.types.KeyType.ETHEREUM)
|
|
167
207
|
};
|
|
168
|
-
const alg = header.alg.toLowerCase();
|
|
169
208
|
if (signers[alg]) {
|
|
170
209
|
const msgHex = (0, _ocap_util.toHex)(`${headerB64}.${bodyB64}`);
|
|
171
210
|
const coercedBodyVersion = body.version ? semver.default.coerce(body.version) : null;
|
|
@@ -194,6 +233,10 @@ const delegationTokenHeaders = {
|
|
|
194
233
|
[_ocap_mcrypto.types.KeyType.ETHEREUM]: {
|
|
195
234
|
alg: "Ethereum",
|
|
196
235
|
typ: "DelegationToken"
|
|
236
|
+
},
|
|
237
|
+
[_ocap_mcrypto.types.KeyType.PASSKEY]: {
|
|
238
|
+
alg: "Passkey",
|
|
239
|
+
typ: "DelegationToken"
|
|
197
240
|
}
|
|
198
241
|
};
|
|
199
242
|
async function signDelegationToken(signer, sk, payload) {
|
|
@@ -227,6 +270,45 @@ async function signDelegationToken(signer, sk, payload) {
|
|
|
227
270
|
(0, _ocap_util.toBase64)((0, _ocap_mcrypto.getSigner)(type.pk).sign(msgHash, sk))
|
|
228
271
|
].join(".");
|
|
229
272
|
}
|
|
273
|
+
/**
|
|
274
|
+
* Create an unsigned delegation token for passkey two-phase signing.
|
|
275
|
+
* Unlike signDelegationToken, this does not sign (passkey has no sk), and pk is passed directly.
|
|
276
|
+
*/
|
|
277
|
+
async function signDelegationTokenUnsigned(signer, pk, payload) {
|
|
278
|
+
if ((0, _arcblock_did.isValid)(signer) === false) throw new Error("Cannot sign DelegationToken with invalid signer");
|
|
279
|
+
const type = (0, _arcblock_did.toTypeInfo)(signer);
|
|
280
|
+
if (type.pk === void 0) throw new Error("Cannot determine key type from signer");
|
|
281
|
+
const header = delegationTokenHeaders[type.pk];
|
|
282
|
+
const headerB64 = (0, _ocap_util.toBase64)((0, json_stable_stringify.default)(header));
|
|
283
|
+
const delegationSignerMethod = (0, _arcblock_did.extractMethod)(signer) || "abt";
|
|
284
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
285
|
+
const pkHex = typeof pk === "string" ? pk : (0, _ocap_util.toHex)(pk);
|
|
286
|
+
const body = {
|
|
287
|
+
iss: (0, _arcblock_did.toDid)(signer, delegationSignerMethod),
|
|
288
|
+
sub: payload.sub,
|
|
289
|
+
iat: payload.iat || String(now),
|
|
290
|
+
nbf: payload.nbf || String(now),
|
|
291
|
+
exp: payload.exp || String(now + 300),
|
|
292
|
+
version: DELEGATION_TOKEN_VERSION,
|
|
293
|
+
ops: payload.ops,
|
|
294
|
+
pk: pkHex
|
|
295
|
+
};
|
|
296
|
+
if (payload.deny && payload.deny.length > 0) {
|
|
297
|
+
for (const d of payload.deny) if (!d.startsWith("fg:")) throw new Error(`Invalid deny pattern "${d}": must start with "fg:"`);
|
|
298
|
+
body.deny = payload.deny;
|
|
299
|
+
}
|
|
300
|
+
if (payload.delegation) body.delegation = payload.delegation;
|
|
301
|
+
return `${headerB64}.${(0, _ocap_util.toBase64)((0, json_stable_stringify.default)(body))}`;
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Assemble a complete delegation token from an unsigned token and a passkey assertion.
|
|
305
|
+
*/
|
|
306
|
+
function assembleDelegationToken(unsignedToken, assertion) {
|
|
307
|
+
if (!unsignedToken) throw new Error("Cannot assemble DelegationToken from empty token");
|
|
308
|
+
if (unsignedToken.split(".").length !== 2) throw new Error("Cannot assemble DelegationToken: expected unsigned token with exactly 2 parts (headerB64.bodyB64)");
|
|
309
|
+
if (!assertion || !assertion.authenticatorData || !assertion.clientDataJSON || !assertion.signature) throw new Error("Cannot assemble DelegationToken: assertion must contain authenticatorData, clientDataJSON, and signature");
|
|
310
|
+
return `${unsignedToken}.${(0, _ocap_util.toBase64)(JSON.stringify(assertion))}`;
|
|
311
|
+
}
|
|
230
312
|
function decodeDelegationToken(token) {
|
|
231
313
|
const parts = token.split(".");
|
|
232
314
|
if (parts.length !== 3) throw new Error("Invalid delegation token format: expected 3 parts");
|
|
@@ -269,13 +351,27 @@ async function verifyDelegationToken(token) {
|
|
|
269
351
|
debug$1("delegationToken.verify.error.futureIat");
|
|
270
352
|
return false;
|
|
271
353
|
}
|
|
354
|
+
const alg = header.alg.toLowerCase();
|
|
355
|
+
if (alg === "passkey") {
|
|
356
|
+
const [, , sigB64] = token.split(".");
|
|
357
|
+
const assertionRaw = JSON.parse((0, _ocap_util.fromBase64)(sigB64).toString());
|
|
358
|
+
if (!assertionRaw.authenticatorData || !assertionRaw.clientDataJSON || !assertionRaw.signature) {
|
|
359
|
+
debug$1("delegationToken.verify.error.incompletePasskeyAssertion");
|
|
360
|
+
return false;
|
|
361
|
+
}
|
|
362
|
+
const challenge = hasher((0, _ocap_util.toHex)(`${headerB64}.${bodyB64}`));
|
|
363
|
+
const extra = JSON.stringify({
|
|
364
|
+
authenticatorData: assertionRaw.authenticatorData,
|
|
365
|
+
clientDataJSON: assertionRaw.clientDataJSON
|
|
366
|
+
});
|
|
367
|
+
return await (0, _ocap_mcrypto.getSigner)(_ocap_mcrypto.types.KeyType.PASSKEY).verify(challenge, assertionRaw.signature, body.pk, extra);
|
|
368
|
+
}
|
|
272
369
|
const signers = {
|
|
273
370
|
ed25519: (0, _ocap_mcrypto.getSigner)(_ocap_mcrypto.types.KeyType.ED25519),
|
|
274
371
|
es256k: (0, _ocap_mcrypto.getSigner)(_ocap_mcrypto.types.KeyType.SECP256K1),
|
|
275
372
|
secp256k1: (0, _ocap_mcrypto.getSigner)(_ocap_mcrypto.types.KeyType.SECP256K1),
|
|
276
373
|
ethereum: (0, _ocap_mcrypto.getSigner)(_ocap_mcrypto.types.KeyType.ETHEREUM)
|
|
277
374
|
};
|
|
278
|
-
const alg = header.alg.toLowerCase();
|
|
279
375
|
if (!signers[alg]) {
|
|
280
376
|
debug$1("delegationToken.verify.error.unknownAlg");
|
|
281
377
|
return false;
|
|
@@ -293,11 +389,15 @@ function checkDelegationTokenScope(scope, { ops, deny }) {
|
|
|
293
389
|
}
|
|
294
390
|
|
|
295
391
|
//#endregion
|
|
392
|
+
exports.assemble = assemble;
|
|
393
|
+
exports.assembleDelegationToken = assembleDelegationToken;
|
|
296
394
|
exports.checkDelegationTokenScope = checkDelegationTokenScope;
|
|
297
395
|
exports.decode = decode;
|
|
298
396
|
exports.decodeDelegationToken = decodeDelegationToken;
|
|
397
|
+
exports.getChallenge = getChallenge;
|
|
299
398
|
exports.sign = sign;
|
|
300
399
|
exports.signDelegationToken = signDelegationToken;
|
|
400
|
+
exports.signDelegationTokenUnsigned = signDelegationTokenUnsigned;
|
|
301
401
|
exports.signV2 = signV2;
|
|
302
402
|
exports.verify = verify;
|
|
303
403
|
exports.verifyDelegationToken = verifyDelegationToken;
|
package/lib/index.d.cts
CHANGED
|
@@ -35,6 +35,28 @@ type JwtVerifyOptions = Partial<{
|
|
|
35
35
|
*/
|
|
36
36
|
declare function sign(signer: string, sk?: BytesType, payload?: {}, doSign?: boolean, version?: string): Promise<string>;
|
|
37
37
|
declare function signV2(signer: string, sk?: BytesType, payload?: any): Promise<string>;
|
|
38
|
+
type PasskeyAssertion = {
|
|
39
|
+
authenticatorData: string;
|
|
40
|
+
clientDataJSON: string;
|
|
41
|
+
signature: string;
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* Compute the WebAuthn challenge for an unsigned JWT token.
|
|
45
|
+
* Always uses SHA3 hash-before-sign (v1.1.0 semantics, required for passkey).
|
|
46
|
+
*
|
|
47
|
+
* @param unsignedToken - the unsigned token (headerB64.bodyB64), may also accept 3-part tokens (ignores third segment)
|
|
48
|
+
* @returns hex-encoded SHA3 hash suitable as WebAuthn challenge
|
|
49
|
+
*/
|
|
50
|
+
declare function getChallenge(unsignedToken: string): string;
|
|
51
|
+
/**
|
|
52
|
+
* Assemble a complete JWT from an unsigned token and a passkey assertion.
|
|
53
|
+
* The assertion is JSON-serialized and base64-encoded as the JWT's third segment.
|
|
54
|
+
*
|
|
55
|
+
* @param unsignedToken - the unsigned token (headerB64.bodyB64)
|
|
56
|
+
* @param assertion - WebAuthn assertion with base64url-encoded fields
|
|
57
|
+
* @returns complete 3-part JWT string
|
|
58
|
+
*/
|
|
59
|
+
declare function assemble(unsignedToken: string, assertion: PasskeyAssertion): string;
|
|
38
60
|
declare function decode(token: string, bodyOnly?: true): JwtBody;
|
|
39
61
|
declare function decode(token: string, bodyOnly?: false): JwtToken;
|
|
40
62
|
/**
|
|
@@ -88,6 +110,23 @@ declare function signDelegationToken(signer: string, sk: BytesType, payload: {
|
|
|
88
110
|
nbf?: string;
|
|
89
111
|
exp?: string;
|
|
90
112
|
}): Promise<string>;
|
|
113
|
+
/**
|
|
114
|
+
* Create an unsigned delegation token for passkey two-phase signing.
|
|
115
|
+
* Unlike signDelegationToken, this does not sign (passkey has no sk), and pk is passed directly.
|
|
116
|
+
*/
|
|
117
|
+
declare function signDelegationTokenUnsigned(signer: string, pk: BytesType, payload: {
|
|
118
|
+
sub: string;
|
|
119
|
+
ops: string[];
|
|
120
|
+
deny?: string[];
|
|
121
|
+
delegation?: string;
|
|
122
|
+
iat?: string;
|
|
123
|
+
nbf?: string;
|
|
124
|
+
exp?: string;
|
|
125
|
+
}): Promise<string>;
|
|
126
|
+
/**
|
|
127
|
+
* Assemble a complete delegation token from an unsigned token and a passkey assertion.
|
|
128
|
+
*/
|
|
129
|
+
declare function assembleDelegationToken(unsignedToken: string, assertion: PasskeyAssertion): string;
|
|
91
130
|
declare function decodeDelegationToken(token: string): DelegationToken;
|
|
92
131
|
declare function verifyDelegationToken(token: string): Promise<boolean>;
|
|
93
132
|
declare function checkDelegationTokenScope(scope: string, {
|
|
@@ -98,4 +137,4 @@ declare function checkDelegationTokenScope(scope: string, {
|
|
|
98
137
|
deny?: string[];
|
|
99
138
|
}): boolean;
|
|
100
139
|
//#endregion
|
|
101
|
-
export { DelegationToken, DelegationTokenBody, DelegationTokenHeader, JwtBody, JwtHeader, JwtToken, JwtVerifyOptions, checkDelegationTokenScope, decode, decodeDelegationToken, sign, signDelegationToken, signV2, verify, verifyDelegationToken };
|
|
140
|
+
export { DelegationToken, DelegationTokenBody, DelegationTokenHeader, JwtBody, JwtHeader, JwtToken, JwtVerifyOptions, PasskeyAssertion, assemble, assembleDelegationToken, checkDelegationTokenScope, decode, decodeDelegationToken, getChallenge, sign, signDelegationToken, signDelegationTokenUnsigned, signV2, verify, verifyDelegationToken };
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@arcblock/jwt",
|
|
3
3
|
"description": "JSON Web Token variant for arcblock DID solutions",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"version": "1.29.
|
|
5
|
+
"version": "1.29.22",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "wangshijun",
|
|
8
8
|
"email": "shijun@arcblock.io",
|
|
@@ -19,15 +19,15 @@
|
|
|
19
19
|
"access": "public"
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@arcblock/did": "1.29.
|
|
23
|
-
"@ocap/mcrypto": "1.29.
|
|
24
|
-
"@ocap/util": "1.29.
|
|
22
|
+
"@arcblock/did": "1.29.22",
|
|
23
|
+
"@ocap/mcrypto": "1.29.22",
|
|
24
|
+
"@ocap/util": "1.29.22",
|
|
25
25
|
"debug": "^4.4.3",
|
|
26
26
|
"json-stable-stringify": "^1.0.1",
|
|
27
27
|
"semver": "^7.6.3"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
|
-
"@ocap/wallet": "1.29.
|
|
30
|
+
"@ocap/wallet": "1.29.22",
|
|
31
31
|
"@types/json-stable-stringify": "^1.0.36",
|
|
32
32
|
"@types/node": "^22.7.5",
|
|
33
33
|
"@types/semver": "^7.5.8",
|