@kokuin/token 0.5.0 → 0.6.0
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/lib/did.d.ts +11 -1
- package/lib/did.js +47 -3
- package/lib/identity.js +9 -4
- package/lib/index.d.ts +3 -3
- package/lib/index.js +2 -2
- package/lib/method.d.ts +22 -0
- package/lib/token.js +6 -3
- package/lib/utils.d.ts +5 -1
- package/lib/utils.js +15 -1
- package/package.json +13 -13
package/lib/did.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { DIDResolver } from './cache.js';
|
|
2
|
-
import { type MethodRegistry } from './method.js';
|
|
2
|
+
import { type MethodRegistry, type ResolvedSigningKey } from './method.js';
|
|
3
3
|
import type { DIDDoc } from './peer4.js';
|
|
4
4
|
import type { SignatureAlgorithm } from './schemas.js';
|
|
5
5
|
import type { DIDString } from './types.js';
|
|
@@ -142,3 +142,13 @@ export declare function normalizeDID(did: string): string;
|
|
|
142
142
|
* A sender MUST use the published key: the derived one is a different key and will not decrypt.
|
|
143
143
|
*/
|
|
144
144
|
export declare function getAgreementKey(doc: DIDDoc): Uint8Array | null;
|
|
145
|
+
/**
|
|
146
|
+
* The signing keys a DID carries in its identifier, or `null` when it carries none. `did:key` is its
|
|
147
|
+
* key; a `did:peer:4` long form embeds the document, so its `authentication` keys (signing is what
|
|
148
|
+
* a pin or a membership is for) are readable from the string. Anything network-backed -- a
|
|
149
|
+
* short-form `did:peer:4`, `did:kokuin:` -- answers `null` rather than looking up.
|
|
150
|
+
*
|
|
151
|
+
* Throws on an identifier that claims to carry a key and does not; the caller treats that as no
|
|
152
|
+
* match.
|
|
153
|
+
*/
|
|
154
|
+
export declare function signingKeysOf(did: string): Array<ResolvedSigningKey> | null;
|
package/lib/did.js
CHANGED
|
@@ -282,11 +282,11 @@ function hasKeyPrefix(did) {
|
|
|
282
282
|
}
|
|
283
283
|
function resolveKidOrAuth(doc, kid) {
|
|
284
284
|
if (kid == null) {
|
|
285
|
-
const
|
|
286
|
-
if (
|
|
285
|
+
const firstAuth = doc.authentication?.[0];
|
|
286
|
+
if (firstAuth == null) {
|
|
287
287
|
throw new Error('resolveIssuer: did:peer:4 token missing kid and doc has no authentication entries');
|
|
288
288
|
}
|
|
289
|
-
return resolveKidFromDoc(doc,
|
|
289
|
+
return resolveKidFromDoc(doc, firstAuth);
|
|
290
290
|
}
|
|
291
291
|
// A kid must reference a method the subject authorized for `authentication`. Without this
|
|
292
292
|
// check a key listed only under `assertionMethod` (or any other relationship) could sign
|
|
@@ -365,3 +365,47 @@ function resolveKidFromDoc(doc, kid) {
|
|
|
365
365
|
}
|
|
366
366
|
return null;
|
|
367
367
|
}
|
|
368
|
+
/** As long as `resolveKidFromDoc` allows: base58 decoding is O(n^2). */ const MAX_KEY_ENCODED = 64;
|
|
369
|
+
/**
|
|
370
|
+
* The signing keys a DID carries in its identifier, or `null` when it carries none. `did:key` is its
|
|
371
|
+
* key; a `did:peer:4` long form embeds the document, so its `authentication` keys (signing is what
|
|
372
|
+
* a pin or a membership is for) are readable from the string. Anything network-backed -- a
|
|
373
|
+
* short-form `did:peer:4`, `did:kokuin:` -- answers `null` rather than looking up.
|
|
374
|
+
*
|
|
375
|
+
* Throws on an identifier that claims to carry a key and does not; the caller treats that as no
|
|
376
|
+
* match.
|
|
377
|
+
*/ export function signingKeysOf(did) {
|
|
378
|
+
if (did.startsWith('did:key:')) {
|
|
379
|
+
const [alg, publicKey] = getSignatureInfo(did);
|
|
380
|
+
return [
|
|
381
|
+
{
|
|
382
|
+
alg,
|
|
383
|
+
publicKey
|
|
384
|
+
}
|
|
385
|
+
];
|
|
386
|
+
}
|
|
387
|
+
if (!isPeer4(did) || did === getPeer4ShortForm(did)) {
|
|
388
|
+
return null;
|
|
389
|
+
}
|
|
390
|
+
const { doc } = decodePeer4(did);
|
|
391
|
+
const keys = [];
|
|
392
|
+
for (const id of doc.authentication ?? []){
|
|
393
|
+
const method = doc.verificationMethod.find((entry)=>entry.id === id);
|
|
394
|
+
if (method == null || method.publicKeyMultibase.length > MAX_KEY_ENCODED) {
|
|
395
|
+
continue;
|
|
396
|
+
}
|
|
397
|
+
let info = null;
|
|
398
|
+
try {
|
|
399
|
+
info = getAlgorithmAndPublicKey(decodeMultibase(method.publicKeyMultibase));
|
|
400
|
+
} catch {
|
|
401
|
+
continue;
|
|
402
|
+
}
|
|
403
|
+
if (info != null) {
|
|
404
|
+
keys.push({
|
|
405
|
+
alg: info[0],
|
|
406
|
+
publicKey: info[1]
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
return keys;
|
|
411
|
+
}
|
package/lib/identity.js
CHANGED
|
@@ -169,19 +169,21 @@ function isClassical(spec) {
|
|
|
169
169
|
function chooseMethod(input) {
|
|
170
170
|
if (input.didMethod != null) {
|
|
171
171
|
if (input.didMethod === 'key') {
|
|
172
|
-
|
|
172
|
+
const [firstKey, ...rest] = input.keys;
|
|
173
|
+
if (firstKey === undefined || rest.length > 0) {
|
|
173
174
|
throw new Error('IdentityError.InvalidMethod: did:key requires exactly one key');
|
|
174
175
|
}
|
|
175
|
-
if (!isClassical(
|
|
176
|
+
if (!isClassical(firstKey)) {
|
|
176
177
|
throw new Error('IdentityError.InvalidMethod: did:key requires a classical algorithm');
|
|
177
178
|
}
|
|
178
|
-
if (
|
|
179
|
+
if (firstKey.purpose !== 'sig') {
|
|
179
180
|
throw new Error('IdentityError.InvalidMethod: did:key requires a signing key');
|
|
180
181
|
}
|
|
181
182
|
}
|
|
182
183
|
return input.didMethod;
|
|
183
184
|
}
|
|
184
|
-
|
|
185
|
+
const [firstKey, ...rest] = input.keys;
|
|
186
|
+
if (firstKey !== undefined && rest.length === 0 && isClassical(firstKey) && firstKey.purpose === 'sig') {
|
|
185
187
|
return 'key';
|
|
186
188
|
}
|
|
187
189
|
return 'peer:4';
|
|
@@ -356,6 +358,9 @@ function buildIdentity(id, longForm, doc, keys) {
|
|
|
356
358
|
const keys = resolveKeys(input);
|
|
357
359
|
if (method === 'key') {
|
|
358
360
|
const [k] = keys;
|
|
361
|
+
if (k === undefined) {
|
|
362
|
+
throw new Error('createIdentity requires at least one key');
|
|
363
|
+
}
|
|
359
364
|
const id = getDID(CODECS.EdDSA, k.publicKey);
|
|
360
365
|
const doc = {
|
|
361
366
|
'@context': [
|
package/lib/index.d.ts
CHANGED
|
@@ -10,10 +10,10 @@
|
|
|
10
10
|
* @module token
|
|
11
11
|
*/
|
|
12
12
|
export { type CreateInMemoryDIDCacheOptions, createInMemoryDIDCache, type DIDCache, type DIDResolver, } from './cache.js';
|
|
13
|
-
export { CODECS, getAgreementKey, getAlgorithmAndPublicKey, getDID, getSignatureInfo, IssuerKeyNotFoundError, isIssuerKeyNotFoundError, isUnresolvableIssuerError, normalizeDID, type ResolveIssuerHeader, type ResolveIssuerMode, type ResolveIssuerWithDocResult, resolveIssuer, resolveIssuerWithDoc, UnresolvableIssuerError, } from './did.js';
|
|
13
|
+
export { CODECS, getAgreementKey, getAlgorithmAndPublicKey, getDID, getSignatureInfo, IssuerKeyNotFoundError, isIssuerKeyNotFoundError, isUnresolvableIssuerError, normalizeDID, type ResolveIssuerHeader, type ResolveIssuerMode, type ResolveIssuerWithDocResult, resolveIssuer, resolveIssuerWithDoc, signingKeysOf, UnresolvableIssuerError, } from './did.js';
|
|
14
14
|
export { type CreateIdentityInput, createFullIdentity, createIdentity, createKeyAgreementIdentity, createKeyAgreementIdentityForDID, createSigningIdentity, createSigningIdentityForDID, type FullIdentity, type Identity, type IdentityKeySpec, type IdentityProvider, isFullIdentity, isKeyAgreementIdentity, isOwnIdentity, isSigningIdentity, type KeyAgreementIdentity, type KeyAlg, type KeyPurpose, type MultiKeyIdentity, type OwnIdentity, type ResolvedKey, randomIdentity, type SigningIdentity, type SignTokenOptions, } from './identity.js';
|
|
15
15
|
export type { KeyEntry, KeyStore, MutableKeyEntry } from './keystore.js';
|
|
16
|
-
export { type AgreementAlgorithm, type DIDMethodResolver, findMethodResolver, type MethodRegistry, type ResolvedAgreementKey, type ResolvedSigningKey, } from './method.js';
|
|
16
|
+
export { type AgreementAlgorithm, type DIDMethodResolver, findMethodResolver, type MemberQuery, type MethodRegistry, type ResolvedAgreementKey, type ResolvedMember, type ResolvedSigningKey, } from './method.js';
|
|
17
17
|
export { decodeMultibase, encodeMultibase, multihashSHA256, verifyMultihash, } from './multibase.js';
|
|
18
18
|
export { type DecodePeer4Options, type DIDDoc, decodePeer4, encodePeer4, getPeer4ShortForm, isPeer4, type VerificationMethod, validateDIDDoc, } from './peer4.js';
|
|
19
19
|
export { capabilitySchema, type SignatureAlgorithm, type SignedHeader, type SignedPayload, type SupportedHeader, signedHeaderSchema, signedPayloadSchema, supportedHeaderSchema, type UnsignedHeader, unsignedHeaderSchema, validateAlgorithm, validateSignedHeader, validateSignedPayload, validateUnsignedHeader, } from './schemas.js';
|
|
@@ -21,5 +21,5 @@ export { decodePrivateKey, encodePrivateKey, randomPrivateKey, } from './signer.
|
|
|
21
21
|
export { assertTimeClaimsValid, now, type TimeClaimsPayload, type TimeValidationOptions, } from './time.js';
|
|
22
22
|
export { createUnsignedToken, isSignedToken, isUnsignedToken, isVerifiedToken, signToken, type VerifyTokenOptions, verifyToken, } from './token.js';
|
|
23
23
|
export type * from './types.js';
|
|
24
|
-
export { concatBytes, stringifyToken } from './utils.js';
|
|
24
|
+
export { concatBytes, decodeSignedToken, stringifyToken } from './utils.js';
|
|
25
25
|
export { getVerifier, type Verifier, type Verifiers, } from './verifier.js';
|
package/lib/index.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
*
|
|
10
10
|
* @module token
|
|
11
11
|
*/ export { createInMemoryDIDCache } from './cache.js';
|
|
12
|
-
export { CODECS, getAgreementKey, getAlgorithmAndPublicKey, getDID, getSignatureInfo, IssuerKeyNotFoundError, isIssuerKeyNotFoundError, isUnresolvableIssuerError, normalizeDID, resolveIssuer, resolveIssuerWithDoc, UnresolvableIssuerError } from './did.js';
|
|
12
|
+
export { CODECS, getAgreementKey, getAlgorithmAndPublicKey, getDID, getSignatureInfo, IssuerKeyNotFoundError, isIssuerKeyNotFoundError, isUnresolvableIssuerError, normalizeDID, resolveIssuer, resolveIssuerWithDoc, signingKeysOf, UnresolvableIssuerError } from './did.js';
|
|
13
13
|
export { createFullIdentity, createIdentity, createKeyAgreementIdentity, createKeyAgreementIdentityForDID, createSigningIdentity, createSigningIdentityForDID, isFullIdentity, isKeyAgreementIdentity, isOwnIdentity, isSigningIdentity, randomIdentity } from './identity.js';
|
|
14
14
|
export { findMethodResolver } from './method.js';
|
|
15
15
|
export { decodeMultibase, encodeMultibase, multihashSHA256, verifyMultihash } from './multibase.js';
|
|
@@ -18,5 +18,5 @@ export { capabilitySchema, signedHeaderSchema, signedPayloadSchema, supportedHea
|
|
|
18
18
|
export { decodePrivateKey, encodePrivateKey, randomPrivateKey } from './signer.js';
|
|
19
19
|
export { assertTimeClaimsValid, now } from './time.js';
|
|
20
20
|
export { createUnsignedToken, isSignedToken, isUnsignedToken, isVerifiedToken, signToken, verifyToken } from './token.js';
|
|
21
|
-
export { concatBytes, stringifyToken } from './utils.js';
|
|
21
|
+
export { concatBytes, decodeSignedToken, stringifyToken } from './utils.js';
|
|
22
22
|
export { getVerifier } from './verifier.js';
|
package/lib/method.d.ts
CHANGED
|
@@ -5,6 +5,17 @@ export type ResolvedSigningKey = {
|
|
|
5
5
|
alg: SignatureAlgorithm;
|
|
6
6
|
publicKey: Uint8Array;
|
|
7
7
|
};
|
|
8
|
+
/** The member of a subject's management tier being asked about: its DID, `add` digest and generation. */
|
|
9
|
+
export type MemberQuery = {
|
|
10
|
+
did: string;
|
|
11
|
+
add: string;
|
|
12
|
+
gen: number;
|
|
13
|
+
};
|
|
14
|
+
/** The signing key a subject's log pins for a member, and when that membership lapses (unix seconds). */
|
|
15
|
+
export type ResolvedMember = {
|
|
16
|
+
key: ResolvedSigningKey;
|
|
17
|
+
lease: number;
|
|
18
|
+
};
|
|
8
19
|
export type AgreementAlgorithm = 'X25519';
|
|
9
20
|
/** A key agreement key a DID method resolved for a recipient. */
|
|
10
21
|
export type ResolvedAgreementKey = {
|
|
@@ -64,6 +75,17 @@ export type DIDMethodResolver = {
|
|
|
64
75
|
* break every implementation for a member most methods have no answer for.
|
|
65
76
|
*/
|
|
66
77
|
resolveDenySet?(did: string): Promise<ReadonlySet<string>>;
|
|
78
|
+
/**
|
|
79
|
+
* The key and lease the subject's log holds for one **member** of its management tier, answered for
|
|
80
|
+
* **now**. A grant issued by a member rather than by the subject's own key is valid only while that
|
|
81
|
+
* membership is live, so the verifier asks the log rather than trusting the grant's claims.
|
|
82
|
+
*
|
|
83
|
+
* Optional, and its **absence fails closed**: a method that cannot answer refuses every
|
|
84
|
+
* member-issued grant rather than falling back to {@link resolve}, which names the subject's own
|
|
85
|
+
* keys and would answer a different question. A member that is unknown, removed or lapsed must
|
|
86
|
+
* reject, never resolve.
|
|
87
|
+
*/
|
|
88
|
+
resolveMember?(subject: string, member: MemberQuery): Promise<ResolvedMember>;
|
|
67
89
|
};
|
|
68
90
|
export type MethodRegistry = ReadonlyArray<DIDMethodResolver>;
|
|
69
91
|
/** Total: a malformed DID yields `undefined` rather than throwing. */
|
package/lib/token.js
CHANGED
|
@@ -125,12 +125,12 @@ function matchesVerifiableData(token, data) {
|
|
|
125
125
|
if (data === `${b64uFromJSON(token.header)}.${b64uFromJSON(token.payload)}`) {
|
|
126
126
|
return true;
|
|
127
127
|
}
|
|
128
|
-
const
|
|
129
|
-
if (
|
|
128
|
+
const [headerPart, payloadPart, extra] = data.split('.');
|
|
129
|
+
if (headerPart === undefined || payloadPart === undefined || extra !== undefined) {
|
|
130
130
|
return false;
|
|
131
131
|
}
|
|
132
132
|
try {
|
|
133
|
-
return canonicalStringify(b64uToJSON(
|
|
133
|
+
return canonicalStringify(b64uToJSON(headerPart)) === canonicalStringify(token.header) && canonicalStringify(b64uToJSON(payloadPart)) === canonicalStringify(token.payload);
|
|
134
134
|
} catch {
|
|
135
135
|
// invalid base64url or JSON in data
|
|
136
136
|
return false;
|
|
@@ -210,6 +210,9 @@ async function verifyTokenInner(token, options = {}) {
|
|
|
210
210
|
throw new Error('Invalid token format: expected 3 parts separated by dots');
|
|
211
211
|
}
|
|
212
212
|
const [encodedHeader, encodedPayload, signature] = parts;
|
|
213
|
+
if (encodedHeader === undefined || encodedPayload === undefined) {
|
|
214
|
+
throw new Error('Invalid token format: expected 3 parts separated by dots');
|
|
215
|
+
}
|
|
213
216
|
const header = b64uToJSON(encodedHeader);
|
|
214
217
|
if (header.typ !== 'JWT') {
|
|
215
218
|
throw new Error('Invalid token header type');
|
package/lib/utils.d.ts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
|
-
import type { Token } from './types.js';
|
|
1
|
+
import type { SignedToken, Token } from './types.js';
|
|
2
2
|
export declare function concatBytes(a: Uint8Array, b: Uint8Array): Uint8Array;
|
|
3
3
|
/**
|
|
4
4
|
* Convert a Token object to its JWT string representation.
|
|
5
5
|
*/
|
|
6
6
|
export declare function stringifyToken(token: Token): string;
|
|
7
|
+
/**
|
|
8
|
+
* Parse a signed token string without verifying it. `data` keeps the original signed segments.
|
|
9
|
+
*/
|
|
10
|
+
export declare function decodeSignedToken<Payload extends Record<string, unknown> = Record<string, unknown>>(token: string): SignedToken<Payload>;
|
package/lib/utils.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { b64uFromJSON } from '@sozai/codec';
|
|
1
|
+
import { b64uFromJSON, b64uToJSON } from '@sozai/codec';
|
|
2
2
|
export function concatBytes(a, b) {
|
|
3
3
|
const out = new Uint8Array(a.length + b.length);
|
|
4
4
|
out.set(a, 0);
|
|
@@ -17,3 +17,17 @@ export function concatBytes(a, b) {
|
|
|
17
17
|
}
|
|
18
18
|
return parts.join('.');
|
|
19
19
|
}
|
|
20
|
+
/**
|
|
21
|
+
* Parse a signed token string without verifying it. `data` keeps the original signed segments.
|
|
22
|
+
*/ export function decodeSignedToken(token) {
|
|
23
|
+
const [header, payload, signature, ...rest] = token.split('.');
|
|
24
|
+
if (!header || !payload || !signature || rest.length > 0) {
|
|
25
|
+
throw new Error('Invalid token format: expected 3 parts separated by dots');
|
|
26
|
+
}
|
|
27
|
+
return {
|
|
28
|
+
data: `${header}.${payload}`,
|
|
29
|
+
header: b64uToJSON(header),
|
|
30
|
+
payload: b64uToJSON(payload),
|
|
31
|
+
signature
|
|
32
|
+
};
|
|
33
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kokuin/token",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "JWT signing and verification",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"json",
|
|
@@ -26,17 +26,17 @@
|
|
|
26
26
|
"lib/*"
|
|
27
27
|
],
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@
|
|
30
|
-
"@noble/
|
|
31
|
-
"@
|
|
32
|
-
"@
|
|
33
|
-
"@sozai/
|
|
34
|
-
"@sozai/
|
|
35
|
-
"@
|
|
29
|
+
"@kokuin/otel": "^0.3.0",
|
|
30
|
+
"@noble/curves": "^2.4.0",
|
|
31
|
+
"@noble/hashes": "^2.4.0",
|
|
32
|
+
"@scure/base": "^2.4.0",
|
|
33
|
+
"@sozai/codec": "^0.4.3",
|
|
34
|
+
"@sozai/otel": "^0.3.4",
|
|
35
|
+
"@sozai/schema": "^0.1.6"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
|
-
"@opentelemetry/
|
|
39
|
-
"@opentelemetry/sdk-trace
|
|
38
|
+
"@opentelemetry/api": "^1.10.0",
|
|
39
|
+
"@opentelemetry/sdk-trace": "^3.0.0",
|
|
40
40
|
"uint8arrays": "^6.1.1"
|
|
41
41
|
},
|
|
42
42
|
"publishConfig": {
|
|
@@ -44,9 +44,9 @@
|
|
|
44
44
|
},
|
|
45
45
|
"scripts": {
|
|
46
46
|
"build": "pnpm run build:clean && pnpm run build:js && pnpm run build:types",
|
|
47
|
-
"build:clean": "del lib",
|
|
48
|
-
"build:js": "swc src -d ./lib --config-file ../../node_modules/@kigu/dev/swc.json --strip-leading-paths",
|
|
49
|
-
"build:types": "tsc --emitDeclarationOnly --skipLibCheck",
|
|
47
|
+
"build:clean": "del-cli lib",
|
|
48
|
+
"build:js": "del-cli 'lib/**/*.js' && swc src -d ./lib --config-file ../../node_modules/@kigu/dev/swc.json --strip-leading-paths",
|
|
49
|
+
"build:types": "del-cli 'lib/**/*.d.ts' 'lib/**/*.d.ts.map' && tsc --emitDeclarationOnly --skipLibCheck",
|
|
50
50
|
"test": "pnpm run test:types && pnpm run test:unit",
|
|
51
51
|
"test:types": "tsc --noEmit --skipLibCheck -p tsconfig.test.json",
|
|
52
52
|
"test:unit": "vitest run"
|