@kokuin/token 0.4.0 → 0.5.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 +100 -5
- package/lib/did.js +168 -11
- package/lib/identity.d.ts +26 -8
- package/lib/identity.js +48 -33
- package/lib/index.d.ts +3 -5
- package/lib/index.js +3 -4
- package/lib/method.d.ts +70 -0
- package/lib/method.js +8 -0
- package/lib/schemas.d.ts +22 -4
- package/lib/schemas.js +16 -3
- package/lib/token.d.ts +23 -0
- package/lib/token.js +32 -8
- package/package.json +2 -3
- package/lib/jwe.d.ts +0 -106
- package/lib/jwe.js +0 -315
- package/lib/rotation.d.ts +0 -16
- package/lib/rotation.js +0 -13
package/lib/method.d.ts
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { ResolveIssuerHeader } from './did.js';
|
|
2
|
+
import type { SignatureAlgorithm } from './schemas.js';
|
|
3
|
+
/** The signing key a DID method resolved for a given issuer and header. */
|
|
4
|
+
export type ResolvedSigningKey = {
|
|
5
|
+
alg: SignatureAlgorithm;
|
|
6
|
+
publicKey: Uint8Array;
|
|
7
|
+
};
|
|
8
|
+
export type AgreementAlgorithm = 'X25519';
|
|
9
|
+
/** A key agreement key a DID method resolved for a recipient. */
|
|
10
|
+
export type ResolvedAgreementKey = {
|
|
11
|
+
alg: AgreementAlgorithm;
|
|
12
|
+
publicKey: Uint8Array;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* A DID method this package can resolve `iss` through without importing its implementation.
|
|
16
|
+
*
|
|
17
|
+
* Methods whose document is a projection of an event log — `did:kokuin:` — cannot be resolved
|
|
18
|
+
* from the identifier alone and cannot be linked from here without a dependency cycle:
|
|
19
|
+
* `@kokuin/controller` depends on this package for signing. Injecting the resolver keeps the
|
|
20
|
+
* dependency one-way.
|
|
21
|
+
*/
|
|
22
|
+
export type DIDMethodResolver = {
|
|
23
|
+
/** The method segment, without `did:` and without the trailing colon. E.g. `kokuin`. */
|
|
24
|
+
method: string;
|
|
25
|
+
/**
|
|
26
|
+
* The key this subject signs with **now** — its head keys, nothing rotated away. The safe default:
|
|
27
|
+
* authenticating a live signer asks whether *this* party can sign today, so a retired key must not
|
|
28
|
+
* answer yes. {@link resolveHistoric} is the other question.
|
|
29
|
+
*/
|
|
30
|
+
resolve(did: string, header: ResolveIssuerHeader): Promise<ResolvedSigningKey>;
|
|
31
|
+
/**
|
|
32
|
+
* The key this subject signed with **at some point in the past**, for verifying an artefact it has
|
|
33
|
+
* already issued — the explicit opt-in half of the split. It accepts a key the subject may have
|
|
34
|
+
* rotated away *because it was compromised*, so the answer is "did once hold", never "holds". Use it
|
|
35
|
+
* only for past-minted material that must survive routine key hygiene (a capability, a revocation
|
|
36
|
+
* record, an archived grant), never to authenticate a live signer.
|
|
37
|
+
*
|
|
38
|
+
* A method whose key set never changes may alias it to {@link resolve}. Optional, so a resolver
|
|
39
|
+
* written before this member typechecks, but its **absence fails closed**: `resolveIssuerWithDoc`
|
|
40
|
+
* refuses rather than falling back to {@link resolve}, which would answer a different question. A
|
|
41
|
+
* method that can retire keys must implement it — including a *wrapper* around one that does.
|
|
42
|
+
*/
|
|
43
|
+
resolveHistoric?(did: string, header: ResolveIssuerHeader): Promise<ResolvedSigningKey>;
|
|
44
|
+
/**
|
|
45
|
+
* Resolve the recipient's key agreement key set, in the method's order. Every entry with its
|
|
46
|
+
* algorithm rather than one chosen key: the set is an OR set, and selection belongs to the
|
|
47
|
+
* encrypting package. Optional: a method with no key agreement omits it.
|
|
48
|
+
*/
|
|
49
|
+
resolveAgreementKey?(did: string): Promise<Array<ResolvedAgreementKey>>;
|
|
50
|
+
/**
|
|
51
|
+
* What this subject has revoked: no capability whose `aud` is in the set is valid. Entries are DIDs,
|
|
52
|
+
* but a wider vocabulary may add others — `did:kokuin:` also denies its own signing keys, spelled
|
|
53
|
+
* `#<key>` — so a reader must **match, never enumerate**. A DID and a key fragment cannot collide.
|
|
54
|
+
*
|
|
55
|
+
* Answer for **now**, not any position a token names: a capability's claims are backdatable, so an
|
|
56
|
+
* anchored check would let the holder pick a moment before it was revoked. A subject that cannot be
|
|
57
|
+
* resolved must reject, never answer the empty set (which reads as "nobody revoked").
|
|
58
|
+
*
|
|
59
|
+
* Optional, but unlike `resolveAgreementKey` its absence **disables enforcement** for every subject
|
|
60
|
+
* of this method, silently and in the passing direction. So a method with no revocation concept
|
|
61
|
+
* omits it, and **a method that can revoke must implement it — including a *wrapper*** (one
|
|
62
|
+
* forwarding only `resolve` type-checks and turns every denial into a pass). It rides this registry
|
|
63
|
+
* so a caller who wired resolution cannot separately forget enforcement; requiring it here would
|
|
64
|
+
* break every implementation for a member most methods have no answer for.
|
|
65
|
+
*/
|
|
66
|
+
resolveDenySet?(did: string): Promise<ReadonlySet<string>>;
|
|
67
|
+
};
|
|
68
|
+
export type MethodRegistry = ReadonlyArray<DIDMethodResolver>;
|
|
69
|
+
/** Total: a malformed DID yields `undefined` rather than throwing. */
|
|
70
|
+
export declare function findMethodResolver(registry: MethodRegistry, did: string): DIDMethodResolver | undefined;
|
package/lib/method.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** Total: a malformed DID yields `undefined` rather than throwing. */ export function findMethodResolver(registry, did) {
|
|
2
|
+
const parts = did.split(':');
|
|
3
|
+
if (parts.length < 3 || parts[0] !== 'did') {
|
|
4
|
+
return undefined;
|
|
5
|
+
}
|
|
6
|
+
const method = parts[1];
|
|
7
|
+
return registry.find((entry)=>entry.method === method);
|
|
8
|
+
}
|
package/lib/schemas.d.ts
CHANGED
|
@@ -10,7 +10,13 @@ export declare const signatureAlgorithmSchema: {
|
|
|
10
10
|
export type SignatureAlgorithm = FromSchema<typeof signatureAlgorithmSchema>;
|
|
11
11
|
/** @internal */
|
|
12
12
|
export declare const validateAlgorithm: import("@sozai/schema").Validator<"EdDSA" | "ES256">;
|
|
13
|
-
/**
|
|
13
|
+
/**
|
|
14
|
+
* The header of a signed token.
|
|
15
|
+
*
|
|
16
|
+
* Supported rather than internal: `@enkaku/protocol` composes its own message schemas out of this,
|
|
17
|
+
* `unsignedHeaderSchema` and `signedPayloadSchema` — a protocol built on kokuin tokens has to
|
|
18
|
+
* describe them in its own wire schema, and it cannot do that from a type alone.
|
|
19
|
+
*/
|
|
14
20
|
export declare const signedHeaderSchema: {
|
|
15
21
|
readonly type: "object";
|
|
16
22
|
readonly properties: {
|
|
@@ -38,7 +44,9 @@ export declare const validateSignedHeader: import("@sozai/schema").Validator<{
|
|
|
38
44
|
alg: "EdDSA" | "ES256";
|
|
39
45
|
typ: "JWT";
|
|
40
46
|
}>;
|
|
41
|
-
/**
|
|
47
|
+
/**
|
|
48
|
+
* The header of an unsigned token. See {@link signedHeaderSchema} for why this is supported.
|
|
49
|
+
*/
|
|
42
50
|
export declare const unsignedHeaderSchema: {
|
|
43
51
|
readonly type: "object";
|
|
44
52
|
readonly properties: {
|
|
@@ -110,7 +118,12 @@ export declare const capabilitySchema: {
|
|
|
110
118
|
};
|
|
111
119
|
}];
|
|
112
120
|
};
|
|
113
|
-
/**
|
|
121
|
+
/**
|
|
122
|
+
* The registered claims a signed token carries. `additionalProperties` is open, so a consumer
|
|
123
|
+
* spreads `properties` into its own object schema and concatenates `required`.
|
|
124
|
+
*
|
|
125
|
+
* Supported rather than internal — see {@link signedHeaderSchema}.
|
|
126
|
+
*/
|
|
114
127
|
export declare const signedPayloadSchema: {
|
|
115
128
|
readonly type: "object";
|
|
116
129
|
readonly properties: {
|
|
@@ -146,7 +159,12 @@ export declare const signedPayloadSchema: {
|
|
|
146
159
|
readonly required: readonly ["iss"];
|
|
147
160
|
readonly additionalProperties: true;
|
|
148
161
|
};
|
|
149
|
-
/**
|
|
162
|
+
/**
|
|
163
|
+
* The registered claims of a signed token, inferred from {@link signedPayloadSchema}.
|
|
164
|
+
*
|
|
165
|
+
* Supported rather than internal, and the most widely used of these: it is what a consumer
|
|
166
|
+
* intersects its own payload type with to describe a token it issues or verifies.
|
|
167
|
+
*/
|
|
150
168
|
export type SignedPayload = FromSchema<typeof signedPayloadSchema>;
|
|
151
169
|
/** @internal */
|
|
152
170
|
export declare const validateSignedPayload: import("@sozai/schema").Validator<{
|
package/lib/schemas.js
CHANGED
|
@@ -8,7 +8,13 @@ import { createValidator } from '@sozai/schema';
|
|
|
8
8
|
enum: SUPPORTED_ALGORITHMS
|
|
9
9
|
};
|
|
10
10
|
/** @internal */ export const validateAlgorithm = createValidator(signatureAlgorithmSchema);
|
|
11
|
-
/**
|
|
11
|
+
/**
|
|
12
|
+
* The header of a signed token.
|
|
13
|
+
*
|
|
14
|
+
* Supported rather than internal: `@enkaku/protocol` composes its own message schemas out of this,
|
|
15
|
+
* `unsignedHeaderSchema` and `signedPayloadSchema` — a protocol built on kokuin tokens has to
|
|
16
|
+
* describe them in its own wire schema, and it cannot do that from a type alone.
|
|
17
|
+
*/ export const signedHeaderSchema = {
|
|
12
18
|
type: 'object',
|
|
13
19
|
properties: {
|
|
14
20
|
typ: {
|
|
@@ -27,7 +33,9 @@ import { createValidator } from '@sozai/schema';
|
|
|
27
33
|
additionalProperties: true
|
|
28
34
|
};
|
|
29
35
|
/** @internal */ export const validateSignedHeader = createValidator(signedHeaderSchema);
|
|
30
|
-
/**
|
|
36
|
+
/**
|
|
37
|
+
* The header of an unsigned token. See {@link signedHeaderSchema} for why this is supported.
|
|
38
|
+
*/ export const unsignedHeaderSchema = {
|
|
31
39
|
type: 'object',
|
|
32
40
|
properties: {
|
|
33
41
|
typ: {
|
|
@@ -65,7 +73,12 @@ import { createValidator } from '@sozai/schema';
|
|
|
65
73
|
}
|
|
66
74
|
]
|
|
67
75
|
};
|
|
68
|
-
/**
|
|
76
|
+
/**
|
|
77
|
+
* The registered claims a signed token carries. `additionalProperties` is open, so a consumer
|
|
78
|
+
* spreads `properties` into its own object schema and concatenates `required`.
|
|
79
|
+
*
|
|
80
|
+
* Supported rather than internal — see {@link signedHeaderSchema}.
|
|
81
|
+
*/ export const signedPayloadSchema = {
|
|
69
82
|
type: 'object',
|
|
70
83
|
properties: {
|
|
71
84
|
iss: {
|
package/lib/token.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { DIDCache, DIDResolver } from './cache.js';
|
|
2
2
|
import type { SigningIdentity } from './identity.js';
|
|
3
|
+
import type { MethodRegistry } from './method.js';
|
|
3
4
|
import { type SignedPayload } from './schemas.js';
|
|
4
5
|
import { type TimeValidationOptions } from './time.js';
|
|
5
6
|
import type { DIDString, SignedToken, Token, UnsignedToken, VerifiedToken } from './types.js';
|
|
@@ -8,6 +9,13 @@ export type VerifyTokenOptions = TimeValidationOptions & {
|
|
|
8
9
|
verifiers?: Verifiers;
|
|
9
10
|
resolver?: DIDResolver;
|
|
10
11
|
cache?: DIDCache;
|
|
12
|
+
/**
|
|
13
|
+
* DID methods that need external resolution to verify an issuer. `did:key` and `did:peer:4` need no
|
|
14
|
+
* entry (they carry their key in the identifier or document); a method whose key set is a projection
|
|
15
|
+
* of state held elsewhere — `did:kokuin:` — cannot be verified without one, and fails with `Unknown
|
|
16
|
+
* DID` when no registry is passed.
|
|
17
|
+
*/
|
|
18
|
+
methods?: MethodRegistry;
|
|
11
19
|
/**
|
|
12
20
|
* Expected audience(s) for the token. When set, verification rejects a token whose `aud`
|
|
13
21
|
* claim is not among the given value(s), and rejects unsigned / `alg:none` tokens outright
|
|
@@ -17,6 +25,18 @@ export type VerifyTokenOptions = TimeValidationOptions & {
|
|
|
17
25
|
* array accepts no audience (every token is rejected).
|
|
18
26
|
*/
|
|
19
27
|
audience?: DIDString | Array<DIDString>;
|
|
28
|
+
/**
|
|
29
|
+
* Verify against a key the issuer has already rotated away, as well as its current ones. Defaults
|
|
30
|
+
* to `false` — the safe question: the signature must be from a key the issuer holds **now**, so a
|
|
31
|
+
* rotated-away (leaked) key mints nothing. Opting in asks whether the issuer held the key **at some
|
|
32
|
+
* point**, which a past-issued capability or revocation record needs to survive routine key hygiene.
|
|
33
|
+
*
|
|
34
|
+
* Set it only for a past-issued token that is not itself proof a live party holds a key. Not a
|
|
35
|
+
* compatibility switch: it is the difference between "a compromised key mints nothing" and "mints
|
|
36
|
+
* anything until the profile resets". A resolver with no `resolveHistoric` rejects with
|
|
37
|
+
* `UnresolvableIssuerError` rather than answering the current-key question.
|
|
38
|
+
*/
|
|
39
|
+
historic?: boolean;
|
|
20
40
|
/**
|
|
21
41
|
* Accept unsigned (`alg:none`) tokens. Defaults to `false`.
|
|
22
42
|
*
|
|
@@ -38,6 +58,9 @@ export type VerifySignedPayloadInput<Payload extends Record<string, unknown> = R
|
|
|
38
58
|
verifiers?: Verifiers;
|
|
39
59
|
resolver?: DIDResolver;
|
|
40
60
|
cache?: DIDCache;
|
|
61
|
+
methods?: MethodRegistry;
|
|
62
|
+
/** See `VerifyTokenOptions.historic`. Defaults to `false` — the issuer's current keys only. */
|
|
63
|
+
historic?: boolean;
|
|
41
64
|
};
|
|
42
65
|
/**
|
|
43
66
|
* Verify the signature of a signed payload and return the public key of the issuer.
|
package/lib/token.js
CHANGED
|
@@ -17,6 +17,11 @@ const verifiedTokens = new WeakSet();
|
|
|
17
17
|
// re-derive `data` from the mutation and stay self-consistent. This map is not reachable from the
|
|
18
18
|
// token, so the value captured at verification time is the one the re-bind compares against.
|
|
19
19
|
const verifiedTokenData = new WeakMap();
|
|
20
|
+
// Tokens verified with `historic: true` — against a key the issuer has since rotated away.
|
|
21
|
+
// Membership blocks the fast path: a later `verifyToken` without `historic` asks the stricter
|
|
22
|
+
// question, and answering it from a weaker check is fail-open, so it re-verifies (which may pass).
|
|
23
|
+
// The reverse needs nothing — a strict answer already implies the loose one.
|
|
24
|
+
const historicallyVerifiedTokens = new WeakSet();
|
|
20
25
|
function assertAudienceValid(payload, audience) {
|
|
21
26
|
if (audience == null) {
|
|
22
27
|
return;
|
|
@@ -44,16 +49,22 @@ function assertUnsignedAllowed(allowUnsigned) {
|
|
|
44
49
|
/**
|
|
45
50
|
* Verify the signature of a signed payload and return the public key of the issuer.
|
|
46
51
|
*/ export async function verifySignedPayload(input) {
|
|
47
|
-
const { signature, payload, header, data, verifiers, resolver, cache } = input;
|
|
52
|
+
const { signature, payload, header, data, verifiers, resolver, cache, methods, historic } = input;
|
|
48
53
|
assertType(validateSignedPayload, payload);
|
|
49
54
|
const effectiveResolver = cache == null ? resolver : async (did)=>{
|
|
50
55
|
const cached = await cache.get(did);
|
|
51
56
|
if (cached != null) return cached;
|
|
52
57
|
return resolver != null ? resolver(did) : undefined;
|
|
53
58
|
};
|
|
54
|
-
const { alg, publicKey, peer4Doc } = await resolveIssuerWithDoc(
|
|
55
|
-
|
|
56
|
-
|
|
59
|
+
const { alg, publicKey, peer4Doc } = await resolveIssuerWithDoc({
|
|
60
|
+
iss: payload.iss,
|
|
61
|
+
header: {
|
|
62
|
+
kid: header.kid
|
|
63
|
+
},
|
|
64
|
+
resolver: effectiveResolver,
|
|
65
|
+
methods,
|
|
66
|
+
historic
|
|
67
|
+
});
|
|
57
68
|
const verify = getVerifier(alg, verifiers);
|
|
58
69
|
const message = typeof data === 'string' ? fromUTF(data) : data;
|
|
59
70
|
const verified = await verify(signature, message, publicKey);
|
|
@@ -145,7 +156,7 @@ function assertVerifiedDataUnchanged(token) {
|
|
|
145
156
|
}
|
|
146
157
|
}
|
|
147
158
|
async function verifyTokenInner(token, options = {}) {
|
|
148
|
-
const { verifiers, resolver, cache, audience, allowUnsigned = false, ...timeOptions } = options;
|
|
159
|
+
const { verifiers, resolver, cache, methods, audience, allowUnsigned = false, historic, ...timeOptions } = options;
|
|
149
160
|
if (typeof token !== 'string') {
|
|
150
161
|
if (isUnsignedToken(token)) {
|
|
151
162
|
assertSignedForAudience(audience);
|
|
@@ -153,7 +164,10 @@ async function verifyTokenInner(token, options = {}) {
|
|
|
153
164
|
assertTimeClaimsValid(token.payload, timeOptions);
|
|
154
165
|
return token;
|
|
155
166
|
}
|
|
156
|
-
|
|
167
|
+
// The `historic` arm is what keeps the fast path from weakening the check: a token verified
|
|
168
|
+
// against a rotated-away key must not satisfy a later caller who asked for the issuer's current
|
|
169
|
+
// keys. It falls through to the full verification below rather than being rejected.
|
|
170
|
+
if (isVerifiedToken(token) && !(historic !== true && historicallyVerifiedTokens.has(token))) {
|
|
157
171
|
// The signature was checked when this object entered `verifiedTokens`, but its payload may
|
|
158
172
|
// have been mutated in place since. Re-bind it to the signed bytes — cheap next to a
|
|
159
173
|
// signature verification, and enough to reject tampering.
|
|
@@ -171,7 +185,9 @@ async function verifyTokenInner(token, options = {}) {
|
|
|
171
185
|
data,
|
|
172
186
|
verifiers,
|
|
173
187
|
resolver,
|
|
174
|
-
cache
|
|
188
|
+
cache,
|
|
189
|
+
methods,
|
|
190
|
+
historic
|
|
175
191
|
});
|
|
176
192
|
assertTimeClaimsValid(token.payload, timeOptions);
|
|
177
193
|
assertAudienceValid(token.payload, audience);
|
|
@@ -182,6 +198,9 @@ async function verifyTokenInner(token, options = {}) {
|
|
|
182
198
|
};
|
|
183
199
|
verifiedTokens.add(result);
|
|
184
200
|
verifiedTokenData.set(result, data);
|
|
201
|
+
if (historic === true) {
|
|
202
|
+
historicallyVerifiedTokens.add(result);
|
|
203
|
+
}
|
|
185
204
|
return result;
|
|
186
205
|
}
|
|
187
206
|
throw new Error('Unsupported token');
|
|
@@ -218,7 +237,9 @@ async function verifyTokenInner(token, options = {}) {
|
|
|
218
237
|
data,
|
|
219
238
|
verifiers,
|
|
220
239
|
resolver,
|
|
221
|
-
cache
|
|
240
|
+
cache,
|
|
241
|
+
methods,
|
|
242
|
+
historic
|
|
222
243
|
});
|
|
223
244
|
assertTimeClaimsValid(payload, timeOptions);
|
|
224
245
|
assertAudienceValid(payload, audience);
|
|
@@ -231,6 +252,9 @@ async function verifyTokenInner(token, options = {}) {
|
|
|
231
252
|
};
|
|
232
253
|
verifiedTokens.add(result);
|
|
233
254
|
verifiedTokenData.set(result, data);
|
|
255
|
+
if (historic === true) {
|
|
256
|
+
historicallyVerifiedTokens.add(result);
|
|
257
|
+
}
|
|
234
258
|
return result;
|
|
235
259
|
}
|
|
236
260
|
throw new Error('Unsupported signature algorithm');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kokuin/token",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "JWT signing and verification",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"json",
|
|
@@ -26,10 +26,9 @@
|
|
|
26
26
|
"lib/*"
|
|
27
27
|
],
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@noble/ciphers": "^2.1.1",
|
|
30
29
|
"@noble/curves": "^2.3.0",
|
|
31
30
|
"@noble/hashes": "^2.3.0",
|
|
32
|
-
"@scure/base": "^2.
|
|
31
|
+
"@scure/base": "^2.3.0",
|
|
33
32
|
"@sozai/codec": "^0.4.0",
|
|
34
33
|
"@sozai/otel": "^0.3.2",
|
|
35
34
|
"@sozai/schema": "^0.1.1",
|
package/lib/jwe.d.ts
DELETED
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
import type { DecryptingIdentity, SigningIdentity } from './identity.js';
|
|
2
|
-
import type { Verifiers } from './verifier.js';
|
|
3
|
-
export type ConcatKDFParams = {
|
|
4
|
-
sharedSecret: Uint8Array;
|
|
5
|
-
keyLength: number;
|
|
6
|
-
algorithmID: string;
|
|
7
|
-
partyUInfo: Uint8Array;
|
|
8
|
-
partyVInfo: Uint8Array;
|
|
9
|
-
};
|
|
10
|
-
export type JWEHeader = {
|
|
11
|
-
alg: string;
|
|
12
|
-
enc: string;
|
|
13
|
-
epk: {
|
|
14
|
-
kty: string;
|
|
15
|
-
crv: string;
|
|
16
|
-
x: string;
|
|
17
|
-
};
|
|
18
|
-
apu?: string;
|
|
19
|
-
apv?: string;
|
|
20
|
-
};
|
|
21
|
-
export type TokenEncrypter = {
|
|
22
|
-
recipientID?: string;
|
|
23
|
-
encrypt(plaintext: Uint8Array): Promise<string>;
|
|
24
|
-
};
|
|
25
|
-
export type EncryptOptions = {
|
|
26
|
-
algorithm: 'X25519';
|
|
27
|
-
};
|
|
28
|
-
export type SharedSecretResult = {
|
|
29
|
-
/**
|
|
30
|
-
* The raw X25519 ECDH output. **Not** uniformly random — run it through a KDF with your own
|
|
31
|
-
* domain separation before using it as a key.
|
|
32
|
-
*/
|
|
33
|
-
sharedSecret: Uint8Array;
|
|
34
|
-
/**
|
|
35
|
-
* Send this to the recipient. They recover the same `sharedSecret` bytes with
|
|
36
|
-
* `identity.agreeKey(ephemeralPublicKey)`.
|
|
37
|
-
*/
|
|
38
|
-
ephemeralPublicKey: Uint8Array;
|
|
39
|
-
};
|
|
40
|
-
/**
|
|
41
|
-
* Concat KDF per RFC 7518 Section 4.6.2.
|
|
42
|
-
* Single SHA-256 iteration (sufficient for 256-bit keys).
|
|
43
|
-
*/
|
|
44
|
-
export declare function concatKDF(params: ConcatKDFParams): Uint8Array;
|
|
45
|
-
/**
|
|
46
|
-
* Perform X25519 key agreement with a recipient DID, without building a JWE.
|
|
47
|
-
*
|
|
48
|
-
* Resolves the recipient's agreement key by the same rule `createTokenEncrypter` uses — a
|
|
49
|
-
* did:peer:4 identity's published `keyAgreement` key, or an EdDSA `did:key`'s birationally
|
|
50
|
-
* derived one — generates a single-use ephemeral key pair, and returns the ECDH output.
|
|
51
|
-
* The ephemeral private key never leaves this function.
|
|
52
|
-
*
|
|
53
|
-
* The recipient recovers the identical bytes with `identity.agreeKey(ephemeralPublicKey)` —
|
|
54
|
-
* called with no `kid`; the sender always resolves the first published `keyAgreement` key, and
|
|
55
|
-
* on a multi-KEM identity a different `kid` silently recovers different bytes.
|
|
56
|
-
*
|
|
57
|
-
* The agreement is anonymous — it authenticates nothing about the sender, only that the secret
|
|
58
|
-
* is recoverable by the recipient. Bind `ephemeralPublicKey` and the recipient DID into your
|
|
59
|
-
* KDF's info/context.
|
|
60
|
-
*
|
|
61
|
-
* ```ts
|
|
62
|
-
* const { sharedSecret, ephemeralPublicKey } = deriveSharedSecret(recipientDID)
|
|
63
|
-
* // ship ephemeralPublicKey alongside whatever the secret protects
|
|
64
|
-
* ```
|
|
65
|
-
*
|
|
66
|
-
* The result is a raw ECDH output, not a key: run it through a KDF before use.
|
|
67
|
-
*
|
|
68
|
-
* @param recipient a `did:key` EdDSA DID, or a `did:peer:4` **long form**. A peer:4 short form
|
|
69
|
-
* throws — the document that carries the agreement key lives only in the long form.
|
|
70
|
-
*/
|
|
71
|
-
export declare function deriveSharedSecret(recipient: string): SharedSecretResult;
|
|
72
|
-
/**
|
|
73
|
-
* Create a token encrypter for a recipient identified by X25519 public key or DID string.
|
|
74
|
-
*/
|
|
75
|
-
export declare function createTokenEncrypter(recipient: Uint8Array, options: EncryptOptions): TokenEncrypter;
|
|
76
|
-
export declare function createTokenEncrypter(recipient: string): TokenEncrypter;
|
|
77
|
-
/**
|
|
78
|
-
* Encrypt plaintext to JWE compact serialization using the given encrypter.
|
|
79
|
-
*/
|
|
80
|
-
export declare function encryptToken(encrypter: TokenEncrypter, plaintext: Uint8Array): Promise<string>;
|
|
81
|
-
/**
|
|
82
|
-
* Decrypt a JWE compact serialization string.
|
|
83
|
-
*/
|
|
84
|
-
export declare function decryptToken(decrypter: DecryptingIdentity, jwe: string): Promise<Uint8Array>;
|
|
85
|
-
export type EnvelopeMode = 'plain' | 'jws' | 'jws-in-jwe' | 'jwe-in-jws';
|
|
86
|
-
export type UnwrappedEnvelope = {
|
|
87
|
-
payload: Record<string, unknown>;
|
|
88
|
-
mode: EnvelopeMode;
|
|
89
|
-
};
|
|
90
|
-
export type WrapOptions = {
|
|
91
|
-
signer?: SigningIdentity;
|
|
92
|
-
encrypter?: TokenEncrypter;
|
|
93
|
-
header?: Record<string, unknown>;
|
|
94
|
-
};
|
|
95
|
-
export type UnwrapOptions = {
|
|
96
|
-
decrypter?: DecryptingIdentity;
|
|
97
|
-
verifiers?: Verifiers;
|
|
98
|
-
};
|
|
99
|
-
/**
|
|
100
|
-
* Wrap a payload into a token string according to the specified envelope mode.
|
|
101
|
-
*/
|
|
102
|
-
export declare function wrapEnvelope(mode: EnvelopeMode, payload: Record<string, unknown>, options: WrapOptions): Promise<string>;
|
|
103
|
-
/**
|
|
104
|
-
* Unwrap a token string, auto-detecting the envelope mode from its structure.
|
|
105
|
-
*/
|
|
106
|
-
export declare function unwrapEnvelope(message: string, options: UnwrapOptions): Promise<UnwrappedEnvelope>;
|