gdc-common-utils-ts 2.0.9 → 2.0.11
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 -0
- package/dist/utils/deterministic-jwk.d.ts +60 -0
- package/dist/utils/deterministic-jwk.js +97 -0
- package/dist/utils/index.d.ts +2 -0
- package/dist/utils/index.js +2 -0
- package/dist/utils/jwt-signer.d.ts +80 -0
- package/dist/utils/jwt-signer.js +134 -0
- package/dist/utils/jwt.d.ts +47 -0
- package/dist/utils/jwt.js +56 -0
- package/dist/utils/vp-token.js +4 -12
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -136,6 +136,7 @@ If you need the canonical explanation of how DIDComm envelope, batch body,
|
|
|
136
136
|
entry types, FHIR-like resources, and `resource.meta.claims` fit together,
|
|
137
137
|
read first:
|
|
138
138
|
|
|
139
|
+
- [`docs/101-ID_TOKEN.md`](docs/101-ID_TOKEN.md)
|
|
139
140
|
- [`docs/101-COMMUNICATION_LAYERING.md`](docs/101-COMMUNICATION_LAYERING.md)
|
|
140
141
|
- [`docs/101-BUNDLE_EDITOR_READER.md`](docs/101-BUNDLE_EDITOR_READER.md)
|
|
141
142
|
- [`docs/101-CLINICAL-IPS.md`](docs/101-CLINICAL-IPS.md)
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { ClassicPublicJwk } from '../interfaces/Cryptography.types';
|
|
2
|
+
/**
|
|
3
|
+
* Legacy JOSE signature algorithms still commonly used by BFF, controller, and
|
|
4
|
+
* test fixtures before a runtime switches to post-quantum signers.
|
|
5
|
+
*
|
|
6
|
+
* Notes:
|
|
7
|
+
* - `ES384` maps to the NIST P-384 curve
|
|
8
|
+
* - `ES256K` maps to the secp256k1 curve often used by wallet-oriented flows
|
|
9
|
+
*
|
|
10
|
+
* Post-quantum deterministic key generation remains available through
|
|
11
|
+
* `ICryptography.generateKeyPairMlDsa(...)` and
|
|
12
|
+
* `ICryptography.generateKeyPairMlKem(...)`.
|
|
13
|
+
*/
|
|
14
|
+
export type DeterministicEcJwkAlgorithm = 'ES256K' | 'ES384';
|
|
15
|
+
/**
|
|
16
|
+
* Deterministic EC signing material derived from a stable textual seed.
|
|
17
|
+
*
|
|
18
|
+
* This helper exists for:
|
|
19
|
+
* - reproducible tests
|
|
20
|
+
* - local/live demos
|
|
21
|
+
* - external-signing examples where auditors must regenerate the same public
|
|
22
|
+
* JWK without importing PEM files or depending on random key generation
|
|
23
|
+
*
|
|
24
|
+
* Important:
|
|
25
|
+
* - the derived private key is deterministic for the same
|
|
26
|
+
* `seed + purpose + alg`
|
|
27
|
+
* - ECDSA signatures generated later may still differ across crypto providers,
|
|
28
|
+
* so callers should normally assert signature verification rather than the
|
|
29
|
+
* full compact token bytes
|
|
30
|
+
*/
|
|
31
|
+
export type DeterministicEcJwkPair = {
|
|
32
|
+
publicJwk: ClassicPublicJwk & {
|
|
33
|
+
use: 'sig';
|
|
34
|
+
kid: string;
|
|
35
|
+
};
|
|
36
|
+
privateJwk: ClassicPublicJwk & {
|
|
37
|
+
use: 'sig';
|
|
38
|
+
kid: string;
|
|
39
|
+
d: string;
|
|
40
|
+
};
|
|
41
|
+
alg: DeterministicEcJwkAlgorithm;
|
|
42
|
+
seed: string;
|
|
43
|
+
purpose: string;
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* Derives one reproducible EC JWK pair from a stable seed string.
|
|
47
|
+
*
|
|
48
|
+
* @param input.seed Stable textual seed chosen by the caller.
|
|
49
|
+
* @param input.purpose Logical namespace such as `virtual-bff`, `controller`,
|
|
50
|
+
* or `demo-host`.
|
|
51
|
+
* @param input.alg JOSE algorithm/curve family. Defaults to `ES384`.
|
|
52
|
+
*
|
|
53
|
+
* @see RFC 7517 JSON Web Key (JWK)
|
|
54
|
+
* @see RFC 7518 JSON Web Algorithms (JWA)
|
|
55
|
+
*/
|
|
56
|
+
export declare function deriveDeterministicEcJwkPair(input: {
|
|
57
|
+
seed: string;
|
|
58
|
+
purpose: string;
|
|
59
|
+
alg?: DeterministicEcJwkAlgorithm;
|
|
60
|
+
}): DeterministicEcJwkPair;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
// Copyright 2026 Antifraud Services Inc. under the Apache License, Version 2.0.
|
|
2
|
+
import { createECDH, createHash } from 'crypto';
|
|
3
|
+
const EC_CURVE_BY_ALG = {
|
|
4
|
+
ES256K: {
|
|
5
|
+
alg: 'ES256K',
|
|
6
|
+
nodeCurve: 'secp256k1',
|
|
7
|
+
jwkCrv: 'secp256k1',
|
|
8
|
+
coordinateBytes: 32,
|
|
9
|
+
},
|
|
10
|
+
ES384: {
|
|
11
|
+
alg: 'ES384',
|
|
12
|
+
nodeCurve: 'secp384r1',
|
|
13
|
+
jwkCrv: 'P-384',
|
|
14
|
+
coordinateBytes: 48,
|
|
15
|
+
},
|
|
16
|
+
};
|
|
17
|
+
function sha256Base64Url(input) {
|
|
18
|
+
return createHash('sha256').update(input, 'utf8').digest('base64url');
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Expands an arbitrary text seed into enough bytes for one EC private scalar.
|
|
22
|
+
*
|
|
23
|
+
* Each block includes both a deterministic `attempt` and `counter`, so callers
|
|
24
|
+
* can keep retrying reproducible candidates until the target curve accepts one.
|
|
25
|
+
*/
|
|
26
|
+
function expandSeedBytes(seed, purpose, size, attempt) {
|
|
27
|
+
const chunks = [];
|
|
28
|
+
let counter = 0;
|
|
29
|
+
while (Buffer.concat(chunks).length < size) {
|
|
30
|
+
chunks.push(createHash('sha512')
|
|
31
|
+
.update(seed, 'utf8')
|
|
32
|
+
.update(':', 'utf8')
|
|
33
|
+
.update(purpose, 'utf8')
|
|
34
|
+
.update(':', 'utf8')
|
|
35
|
+
.update(String(attempt), 'utf8')
|
|
36
|
+
.update(':', 'utf8')
|
|
37
|
+
.update(String(counter), 'utf8')
|
|
38
|
+
.digest());
|
|
39
|
+
counter += 1;
|
|
40
|
+
}
|
|
41
|
+
return Buffer.concat(chunks).subarray(0, size);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Derives one reproducible EC JWK pair from a stable seed string.
|
|
45
|
+
*
|
|
46
|
+
* @param input.seed Stable textual seed chosen by the caller.
|
|
47
|
+
* @param input.purpose Logical namespace such as `virtual-bff`, `controller`,
|
|
48
|
+
* or `demo-host`.
|
|
49
|
+
* @param input.alg JOSE algorithm/curve family. Defaults to `ES384`.
|
|
50
|
+
*
|
|
51
|
+
* @see RFC 7517 JSON Web Key (JWK)
|
|
52
|
+
* @see RFC 7518 JSON Web Algorithms (JWA)
|
|
53
|
+
*/
|
|
54
|
+
export function deriveDeterministicEcJwkPair(input) {
|
|
55
|
+
const alg = input.alg ?? 'ES384';
|
|
56
|
+
const curve = EC_CURVE_BY_ALG[alg];
|
|
57
|
+
for (let attempt = 0; attempt < 256; attempt += 1) {
|
|
58
|
+
const candidate = expandSeedBytes(input.seed, input.purpose, curve.coordinateBytes, attempt);
|
|
59
|
+
try {
|
|
60
|
+
const ecdh = createECDH(curve.nodeCurve);
|
|
61
|
+
ecdh.setPrivateKey(candidate);
|
|
62
|
+
const privateKey = ecdh.getPrivateKey();
|
|
63
|
+
const publicKey = ecdh.getPublicKey(undefined, 'uncompressed');
|
|
64
|
+
const x = publicKey.subarray(1, 1 + curve.coordinateBytes);
|
|
65
|
+
const y = publicKey.subarray(1 + curve.coordinateBytes, 1 + (curve.coordinateBytes * 2));
|
|
66
|
+
const kid = `${input.purpose}-${alg.toLowerCase()}-${sha256Base64Url(`${input.seed}:${input.purpose}:${alg}`).slice(0, 16)}`;
|
|
67
|
+
return {
|
|
68
|
+
alg,
|
|
69
|
+
seed: input.seed,
|
|
70
|
+
purpose: input.purpose,
|
|
71
|
+
publicJwk: {
|
|
72
|
+
kty: 'EC',
|
|
73
|
+
crv: curve.jwkCrv,
|
|
74
|
+
alg,
|
|
75
|
+
use: 'sig',
|
|
76
|
+
kid,
|
|
77
|
+
x: x.toString('base64url'),
|
|
78
|
+
y: y.toString('base64url'),
|
|
79
|
+
},
|
|
80
|
+
privateJwk: {
|
|
81
|
+
kty: 'EC',
|
|
82
|
+
crv: curve.jwkCrv,
|
|
83
|
+
alg,
|
|
84
|
+
use: 'sig',
|
|
85
|
+
kid,
|
|
86
|
+
x: x.toString('base64url'),
|
|
87
|
+
y: y.toString('base64url'),
|
|
88
|
+
d: privateKey.toString('base64url'),
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
// Retry the next deterministic candidate until the curve accepts it.
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
throw new Error(`Could not derive a valid ${alg} key from deterministic seed '${input.seed}'.`);
|
|
97
|
+
}
|
package/dist/utils/index.d.ts
CHANGED
|
@@ -19,6 +19,7 @@ export * from './did-resolution';
|
|
|
19
19
|
export * from './dataspace-discovery';
|
|
20
20
|
export * from './dataspace-discovery-defaults';
|
|
21
21
|
export * from './dataspace-protocol';
|
|
22
|
+
export * from './deterministic-jwk';
|
|
22
23
|
export * from './employee';
|
|
23
24
|
export * from './evidence-blockchain-references';
|
|
24
25
|
export * from './didcomm';
|
|
@@ -63,6 +64,7 @@ export * from './invoice-bundle';
|
|
|
63
64
|
export * from './ips-bundle-claims';
|
|
64
65
|
export * from './interoperable-resource-operation';
|
|
65
66
|
export * from './jwt';
|
|
67
|
+
export * from './jwt-signer';
|
|
66
68
|
export * from './jwk-thumbprint';
|
|
67
69
|
export * from './legal-organization-onboarding';
|
|
68
70
|
export * from './legal-organization-verification-transaction';
|
package/dist/utils/index.js
CHANGED
|
@@ -19,6 +19,7 @@ export * from './did-resolution.js';
|
|
|
19
19
|
export * from './dataspace-discovery.js';
|
|
20
20
|
export * from './dataspace-discovery-defaults.js';
|
|
21
21
|
export * from './dataspace-protocol.js';
|
|
22
|
+
export * from './deterministic-jwk.js';
|
|
22
23
|
export * from './employee.js';
|
|
23
24
|
export * from './evidence-blockchain-references.js';
|
|
24
25
|
export * from './didcomm.js';
|
|
@@ -63,6 +64,7 @@ export * from './invoice-bundle.js';
|
|
|
63
64
|
export * from './ips-bundle-claims.js';
|
|
64
65
|
export * from './interoperable-resource-operation.js';
|
|
65
66
|
export * from './jwt.js';
|
|
67
|
+
export * from './jwt-signer.js';
|
|
66
68
|
export * from './jwk-thumbprint.js';
|
|
67
69
|
export * from './legal-organization-onboarding.js';
|
|
68
70
|
export * from './legal-organization-verification-transaction.js';
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { type JoseSignatureAlgorithm } from '../constants/cryptography';
|
|
2
|
+
import type { ICryptography } from '../interfaces/ICryptography';
|
|
3
|
+
import type { PublicJwk, ClassicPublicJwk } from '../interfaces/Cryptography.types';
|
|
4
|
+
export type JwtSignerMode = 'deterministic' | 'random';
|
|
5
|
+
export type JwtSignerMaterial = {
|
|
6
|
+
kind: 'classic-ec';
|
|
7
|
+
publicJwk: ClassicPublicJwk & {
|
|
8
|
+
kid: string;
|
|
9
|
+
use: 'sig';
|
|
10
|
+
};
|
|
11
|
+
privateJwk: ClassicPublicJwk & {
|
|
12
|
+
kid: string;
|
|
13
|
+
use: 'sig';
|
|
14
|
+
d: string;
|
|
15
|
+
};
|
|
16
|
+
} | {
|
|
17
|
+
kind: 'ml-dsa';
|
|
18
|
+
publicJwk: PublicJwk & {
|
|
19
|
+
kid: string;
|
|
20
|
+
use?: string;
|
|
21
|
+
};
|
|
22
|
+
secretKeyBytes: Uint8Array;
|
|
23
|
+
};
|
|
24
|
+
export type PreparedJwtSigningInput = {
|
|
25
|
+
protectedHeader: Record<string, unknown>;
|
|
26
|
+
encodedHeader: string;
|
|
27
|
+
encodedPayload: string;
|
|
28
|
+
signingInput: string;
|
|
29
|
+
signingBytes: Uint8Array;
|
|
30
|
+
};
|
|
31
|
+
export interface JwtSigner {
|
|
32
|
+
getMode(): JwtSignerMode;
|
|
33
|
+
getAlgorithm(): JoseSignatureAlgorithm;
|
|
34
|
+
getPurpose(): string;
|
|
35
|
+
getPublicJwk(): PublicJwk;
|
|
36
|
+
getPrivateMaterial(): JWKLikePrivateMaterial;
|
|
37
|
+
getKid(): string;
|
|
38
|
+
getThumbprint(): string;
|
|
39
|
+
getThumbprintUri(): string;
|
|
40
|
+
prepareJwt(input: {
|
|
41
|
+
payload: Record<string, unknown>;
|
|
42
|
+
header?: Record<string, unknown>;
|
|
43
|
+
typ?: string;
|
|
44
|
+
kid?: string;
|
|
45
|
+
}): PreparedJwtSigningInput;
|
|
46
|
+
buildCompact(signatureBase64Url: string, prepared: Pick<PreparedJwtSigningInput, 'encodedHeader' | 'encodedPayload'>): string;
|
|
47
|
+
buildUnsignedJwt(payload: Record<string, unknown>, options?: Readonly<{
|
|
48
|
+
nowSeconds?: number;
|
|
49
|
+
ttlSeconds?: number;
|
|
50
|
+
nonce?: string;
|
|
51
|
+
}>): string;
|
|
52
|
+
}
|
|
53
|
+
export type JWKLikePrivateMaterial = (ClassicPublicJwk & {
|
|
54
|
+
kid: string;
|
|
55
|
+
use: 'sig';
|
|
56
|
+
d: string;
|
|
57
|
+
}) | Uint8Array;
|
|
58
|
+
export type CreateJwtSignerOptions = {
|
|
59
|
+
alg: JoseSignatureAlgorithm;
|
|
60
|
+
purpose: string;
|
|
61
|
+
seed?: string | Uint8Array;
|
|
62
|
+
cryptography?: Pick<ICryptography, 'generateKeyPairMlDsa'>;
|
|
63
|
+
};
|
|
64
|
+
/**
|
|
65
|
+
* Creates one high-level JWT signer façade for docs, tests, BFF helpers, and
|
|
66
|
+
* controller/wallet flows.
|
|
67
|
+
*
|
|
68
|
+
* Contract:
|
|
69
|
+
* - if `seed` is provided, the signer runs in deterministic mode
|
|
70
|
+
* - if `seed` is omitted, the signer generates fresh random material
|
|
71
|
+
*
|
|
72
|
+
* This helper intentionally exposes a small integrator-facing surface:
|
|
73
|
+
* - get the public JWK / kid / thumbprint
|
|
74
|
+
* - prepare the exact `header.payload` signing input
|
|
75
|
+
* - assemble the final compact JWT afterwards
|
|
76
|
+
*
|
|
77
|
+
* It does not hide the real external-signing step. That step still belongs to
|
|
78
|
+
* a KMS, HSM, wallet, or trusted issuer.
|
|
79
|
+
*/
|
|
80
|
+
export declare function createJwtSigner(options: CreateJwtSignerOptions): Promise<JwtSigner>;
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
// Copyright 2026 Antifraud Services Inc. under the Apache License, Version 2.0.
|
|
2
|
+
import { randomBytes } from 'crypto';
|
|
3
|
+
import { ClassicalJoseSignatureAlgorithms, JwkKeyUses } from '../constants/cryptography.js';
|
|
4
|
+
import { Content } from './content.js';
|
|
5
|
+
import { buildJwtCompact, buildUnsignedJwt, prepareJwtBytesForSignature, prepareJwtForSignature, } from './jwt.js';
|
|
6
|
+
import { computeRfc7638JwkThumbprint, toJwkThumbprintSha256Urn, } from './jwk-thumbprint.js';
|
|
7
|
+
import { deriveDeterministicEcJwkPair, } from './deterministic-jwk.js';
|
|
8
|
+
function isClassicEcAlg(alg) {
|
|
9
|
+
return alg === ClassicalJoseSignatureAlgorithms.Es384 || alg === ClassicalJoseSignatureAlgorithms.Es256K;
|
|
10
|
+
}
|
|
11
|
+
function resolveMode(seed) {
|
|
12
|
+
return seed === undefined ? 'random' : 'deterministic';
|
|
13
|
+
}
|
|
14
|
+
function resolveSeedString(seed) {
|
|
15
|
+
if (typeof seed === 'string')
|
|
16
|
+
return seed;
|
|
17
|
+
if (seed instanceof Uint8Array)
|
|
18
|
+
return Content.bytesToRawBase64UrlSafe(seed);
|
|
19
|
+
return randomBytes(32).toString('base64url');
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Creates one high-level JWT signer façade for docs, tests, BFF helpers, and
|
|
23
|
+
* controller/wallet flows.
|
|
24
|
+
*
|
|
25
|
+
* Contract:
|
|
26
|
+
* - if `seed` is provided, the signer runs in deterministic mode
|
|
27
|
+
* - if `seed` is omitted, the signer generates fresh random material
|
|
28
|
+
*
|
|
29
|
+
* This helper intentionally exposes a small integrator-facing surface:
|
|
30
|
+
* - get the public JWK / kid / thumbprint
|
|
31
|
+
* - prepare the exact `header.payload` signing input
|
|
32
|
+
* - assemble the final compact JWT afterwards
|
|
33
|
+
*
|
|
34
|
+
* It does not hide the real external-signing step. That step still belongs to
|
|
35
|
+
* a KMS, HSM, wallet, or trusted issuer.
|
|
36
|
+
*/
|
|
37
|
+
export async function createJwtSigner(options) {
|
|
38
|
+
const mode = resolveMode(options.seed);
|
|
39
|
+
const seedText = resolveSeedString(options.seed);
|
|
40
|
+
const alg = options.alg;
|
|
41
|
+
let material;
|
|
42
|
+
if (isClassicEcAlg(alg)) {
|
|
43
|
+
const pair = deriveDeterministicEcJwkPair({
|
|
44
|
+
seed: seedText,
|
|
45
|
+
purpose: options.purpose,
|
|
46
|
+
alg,
|
|
47
|
+
});
|
|
48
|
+
material = {
|
|
49
|
+
kind: 'classic-ec',
|
|
50
|
+
publicJwk: pair.publicJwk,
|
|
51
|
+
privateJwk: pair.privateJwk,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
if (!options.cryptography) {
|
|
56
|
+
throw new Error(`createJwtSigner requires options.cryptography for post-quantum algorithm '${alg}'.`);
|
|
57
|
+
}
|
|
58
|
+
const seedBytes = options.seed === undefined
|
|
59
|
+
? undefined
|
|
60
|
+
: options.seed instanceof Uint8Array
|
|
61
|
+
? options.seed
|
|
62
|
+
: Content.stringToBytesUTF8(options.seed);
|
|
63
|
+
const generated = await options.cryptography.generateKeyPairMlDsa(seedBytes, alg);
|
|
64
|
+
material = {
|
|
65
|
+
kind: 'ml-dsa',
|
|
66
|
+
publicJwk: {
|
|
67
|
+
...generated.publicJWKey,
|
|
68
|
+
use: JwkKeyUses.Signature,
|
|
69
|
+
},
|
|
70
|
+
secretKeyBytes: generated.secretKeyBytes,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
const sourcePublicJwk = material.publicJwk;
|
|
74
|
+
const thumbprintJwk = sourcePublicJwk;
|
|
75
|
+
const thumbprint = computeRfc7638JwkThumbprint(thumbprintJwk);
|
|
76
|
+
const thumbprintUri = toJwkThumbprintSha256Urn(thumbprintJwk);
|
|
77
|
+
const keyId = thumbprintUri;
|
|
78
|
+
const publicJwk = {
|
|
79
|
+
...sourcePublicJwk,
|
|
80
|
+
kid: keyId,
|
|
81
|
+
};
|
|
82
|
+
const privateMaterial = material.kind === 'classic-ec'
|
|
83
|
+
? {
|
|
84
|
+
...material.privateJwk,
|
|
85
|
+
kid: keyId,
|
|
86
|
+
}
|
|
87
|
+
: material.secretKeyBytes;
|
|
88
|
+
return {
|
|
89
|
+
getMode() {
|
|
90
|
+
return mode;
|
|
91
|
+
},
|
|
92
|
+
getAlgorithm() {
|
|
93
|
+
return alg;
|
|
94
|
+
},
|
|
95
|
+
getPurpose() {
|
|
96
|
+
return options.purpose;
|
|
97
|
+
},
|
|
98
|
+
getPublicJwk() {
|
|
99
|
+
return publicJwk;
|
|
100
|
+
},
|
|
101
|
+
getPrivateMaterial() {
|
|
102
|
+
return privateMaterial;
|
|
103
|
+
},
|
|
104
|
+
getKid() {
|
|
105
|
+
return keyId;
|
|
106
|
+
},
|
|
107
|
+
getThumbprint() {
|
|
108
|
+
return thumbprint;
|
|
109
|
+
},
|
|
110
|
+
getThumbprintUri() {
|
|
111
|
+
return thumbprintUri;
|
|
112
|
+
},
|
|
113
|
+
prepareJwt(input) {
|
|
114
|
+
const protectedHeader = {
|
|
115
|
+
alg,
|
|
116
|
+
typ: input.typ ?? 'JWT',
|
|
117
|
+
...(input.header || {}),
|
|
118
|
+
kid: keyId,
|
|
119
|
+
};
|
|
120
|
+
const prepared = prepareJwtForSignature(protectedHeader, input.payload);
|
|
121
|
+
return {
|
|
122
|
+
protectedHeader,
|
|
123
|
+
...prepared,
|
|
124
|
+
signingBytes: prepareJwtBytesForSignature(protectedHeader, input.payload),
|
|
125
|
+
};
|
|
126
|
+
},
|
|
127
|
+
buildCompact(signatureBase64Url, prepared) {
|
|
128
|
+
return buildJwtCompact(prepared.encodedHeader, prepared.encodedPayload, signatureBase64Url);
|
|
129
|
+
},
|
|
130
|
+
buildUnsignedJwt(payload, optionsForUnsigned) {
|
|
131
|
+
return buildUnsignedJwt(payload, optionsForUnsigned);
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
}
|
package/dist/utils/jwt.d.ts
CHANGED
|
@@ -51,6 +51,53 @@ export declare function encodeSignature(signatureBytes?: Uint8Array): string;
|
|
|
51
51
|
* @returns A compact JWT string.
|
|
52
52
|
*/
|
|
53
53
|
export declare function compactJWT(header: object, payload: object, signatureBytes?: Uint8Array): Promise<string>;
|
|
54
|
+
/**
|
|
55
|
+
* Prepares the JOSE compact-signing input for an externally signed JWT/JWS.
|
|
56
|
+
*
|
|
57
|
+
* This helper is intended for BFF, wallet, Expo/native, or server-side flows
|
|
58
|
+
* where the signing key lives in an external KMS/HSM and the application must:
|
|
59
|
+
*
|
|
60
|
+
* 1. build the protected header and JWT payload locally
|
|
61
|
+
* 2. obtain the canonical `base64url(header).base64url(payload)` string
|
|
62
|
+
* 3. sign that exact byte sequence with the external signer
|
|
63
|
+
* 4. assemble the final compact JWS/JWT by appending the returned signature
|
|
64
|
+
*
|
|
65
|
+
* References:
|
|
66
|
+
* - RFC 7515 (JWS), Compact Serialization
|
|
67
|
+
* - RFC 7519 (JWT)
|
|
68
|
+
* - OpenID Connect Core 1.0 (`id_token` profile claims such as `email`)
|
|
69
|
+
*
|
|
70
|
+
* Note:
|
|
71
|
+
* - this helper only prepares the compact signing input
|
|
72
|
+
* - it does not verify that the payload is a particular profile such as
|
|
73
|
+
* `vp_token`, `id_token`, or `private_key_jwt`
|
|
74
|
+
*/
|
|
75
|
+
export declare function prepareJwtForSignature(header: object, payload: object): {
|
|
76
|
+
encodedHeader: string;
|
|
77
|
+
encodedPayload: string;
|
|
78
|
+
signingInput: string;
|
|
79
|
+
};
|
|
80
|
+
/**
|
|
81
|
+
* Returns the UTF-8 bytes of the canonical compact JWT/JWS signing input.
|
|
82
|
+
*
|
|
83
|
+
* This is the exact byte sequence that an external signer must sign before the
|
|
84
|
+
* caller assembles the final compact JWT string with
|
|
85
|
+
* `buildJwtCompact(...)`.
|
|
86
|
+
*/
|
|
87
|
+
export declare function prepareJwtBytesForSignature(header: object, payload: object): Uint8Array;
|
|
88
|
+
/**
|
|
89
|
+
* Assembles the final compact JWT/JWS once the caller already has:
|
|
90
|
+
*
|
|
91
|
+
* - the base64url-encoded protected header
|
|
92
|
+
* - the base64url-encoded payload
|
|
93
|
+
* - the detached signature returned by the external signer, also base64url-encoded
|
|
94
|
+
*
|
|
95
|
+
* This helper is profile-agnostic. It can be used for:
|
|
96
|
+
* - OpenID Connect `id_token`
|
|
97
|
+
* - `vp_token`
|
|
98
|
+
* - other compact JWS/JWT profiles that follow RFC 7515 / RFC 7519
|
|
99
|
+
*/
|
|
100
|
+
export declare function buildJwtCompact(encodedHeader: string, encodedPayload: string, signatureBase64Url: string): string;
|
|
54
101
|
/**
|
|
55
102
|
* Builds an unsigned compact JWT with `alg=none`.
|
|
56
103
|
*
|
package/dist/utils/jwt.js
CHANGED
|
@@ -152,6 +152,62 @@ export async function compactJWT(header, payload, signatureBytes) {
|
|
|
152
152
|
const encodedSignature = encodeSignature(signatureBytes);
|
|
153
153
|
return `${encodedHeader}.${encodedPayload}.${encodedSignature}`;
|
|
154
154
|
}
|
|
155
|
+
/**
|
|
156
|
+
* Prepares the JOSE compact-signing input for an externally signed JWT/JWS.
|
|
157
|
+
*
|
|
158
|
+
* This helper is intended for BFF, wallet, Expo/native, or server-side flows
|
|
159
|
+
* where the signing key lives in an external KMS/HSM and the application must:
|
|
160
|
+
*
|
|
161
|
+
* 1. build the protected header and JWT payload locally
|
|
162
|
+
* 2. obtain the canonical `base64url(header).base64url(payload)` string
|
|
163
|
+
* 3. sign that exact byte sequence with the external signer
|
|
164
|
+
* 4. assemble the final compact JWS/JWT by appending the returned signature
|
|
165
|
+
*
|
|
166
|
+
* References:
|
|
167
|
+
* - RFC 7515 (JWS), Compact Serialization
|
|
168
|
+
* - RFC 7519 (JWT)
|
|
169
|
+
* - OpenID Connect Core 1.0 (`id_token` profile claims such as `email`)
|
|
170
|
+
*
|
|
171
|
+
* Note:
|
|
172
|
+
* - this helper only prepares the compact signing input
|
|
173
|
+
* - it does not verify that the payload is a particular profile such as
|
|
174
|
+
* `vp_token`, `id_token`, or `private_key_jwt`
|
|
175
|
+
*/
|
|
176
|
+
export function prepareJwtForSignature(header, payload) {
|
|
177
|
+
const encodedHeader = encodeHeader(header);
|
|
178
|
+
const encodedPayload = Content.objectToRawBase64UrlSafe(payload);
|
|
179
|
+
return {
|
|
180
|
+
encodedHeader,
|
|
181
|
+
encodedPayload,
|
|
182
|
+
signingInput: `${encodedHeader}.${encodedPayload}`,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Returns the UTF-8 bytes of the canonical compact JWT/JWS signing input.
|
|
187
|
+
*
|
|
188
|
+
* This is the exact byte sequence that an external signer must sign before the
|
|
189
|
+
* caller assembles the final compact JWT string with
|
|
190
|
+
* `buildJwtCompact(...)`.
|
|
191
|
+
*/
|
|
192
|
+
export function prepareJwtBytesForSignature(header, payload) {
|
|
193
|
+
const { signingInput } = prepareJwtForSignature(header, payload);
|
|
194
|
+
return new TextEncoder().encode(signingInput);
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Assembles the final compact JWT/JWS once the caller already has:
|
|
198
|
+
*
|
|
199
|
+
* - the base64url-encoded protected header
|
|
200
|
+
* - the base64url-encoded payload
|
|
201
|
+
* - the detached signature returned by the external signer, also base64url-encoded
|
|
202
|
+
*
|
|
203
|
+
* This helper is profile-agnostic. It can be used for:
|
|
204
|
+
* - OpenID Connect `id_token`
|
|
205
|
+
* - `vp_token`
|
|
206
|
+
* - other compact JWS/JWT profiles that follow RFC 7515 / RFC 7519
|
|
207
|
+
*/
|
|
208
|
+
export function buildJwtCompact(encodedHeader, encodedPayload, signatureBase64Url) {
|
|
209
|
+
return `${encodedHeader}.${encodedPayload}.${String(signatureBase64Url || '').trim()}`;
|
|
210
|
+
}
|
|
155
211
|
/**
|
|
156
212
|
* Builds an unsigned compact JWT with `alg=none`.
|
|
157
213
|
*
|
package/dist/utils/vp-token.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
import { Content } from './content.js';
|
|
1
|
+
import { buildJwtCompact, prepareJwtBytesForSignature, prepareJwtForSignature } from './jwt.js';
|
|
3
2
|
import { ORGANIZATION_ACTIVATION_VC_TYPES, REPRESENTATIVE_ACTIVATION_VC_TYPES, W3cCredentialContexts, W3cCredentialTypes, } from '../constants/verifiable-credentials.js';
|
|
4
3
|
function fallbackId() {
|
|
5
4
|
const rand = Math.random().toString(36).slice(2, 10);
|
|
@@ -224,13 +223,7 @@ export function addLegalRepresentativeCredential(vpPayload, vc) {
|
|
|
224
223
|
return addTypedVC(vpPayload, vc, [...REPRESENTATIVE_ACTIVATION_VC_TYPES], 'LegalRepresentative');
|
|
225
224
|
}
|
|
226
225
|
export function prepareForSignature(header, payload) {
|
|
227
|
-
|
|
228
|
-
const encodedPayload = Content.objectToRawBase64UrlSafe(payload);
|
|
229
|
-
return {
|
|
230
|
-
encodedHeader,
|
|
231
|
-
encodedPayload,
|
|
232
|
-
signingInput: `${encodedHeader}.${encodedPayload}`,
|
|
233
|
-
};
|
|
226
|
+
return prepareJwtForSignature(header, payload);
|
|
234
227
|
}
|
|
235
228
|
/**
|
|
236
229
|
* Returns the UTF-8 bytes of the canonical `base64url(header).base64url(payload)`
|
|
@@ -241,8 +234,7 @@ export function prepareForSignature(header, payload) {
|
|
|
241
234
|
* `buildVpTokenCompact(...)`.
|
|
242
235
|
*/
|
|
243
236
|
export function prepareBytesForSignature(header, payload) {
|
|
244
|
-
|
|
245
|
-
return new TextEncoder().encode(signingInput);
|
|
237
|
+
return prepareJwtBytesForSignature(header, payload);
|
|
246
238
|
}
|
|
247
239
|
/**
|
|
248
240
|
* Assembles the final compact VP JWT once the caller already has:
|
|
@@ -252,5 +244,5 @@ export function prepareBytesForSignature(header, payload) {
|
|
|
252
244
|
* - the detached signature returned by the external signer, also base64url-encoded
|
|
253
245
|
*/
|
|
254
246
|
export function buildVpTokenCompact(encodedHeader, encodedPayload, signatureBase64Url) {
|
|
255
|
-
return
|
|
247
|
+
return buildJwtCompact(encodedHeader, encodedPayload, signatureBase64Url);
|
|
256
248
|
}
|