@digitalbazaar/oid4-client 4.3.0 → 5.0.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/authorizationRequest.js +461 -0
- package/lib/authorizationResponse.js +337 -0
- package/lib/convert.js +430 -0
- package/lib/index.js +3 -2
- package/lib/oid4vp.js +20 -836
- package/lib/util.js +66 -1
- package/lib/x509.js +61 -0
- package/package.json +8 -8
package/lib/util.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* Copyright (c) 2022-
|
|
2
|
+
* Copyright (c) 2022-2025 Digital Bazaar, Inc. All rights reserved.
|
|
3
3
|
*/
|
|
4
4
|
import * as base64url from 'base64url-universal';
|
|
5
5
|
import {httpClient} from '@digitalbazaar/http-client';
|
|
@@ -21,6 +21,28 @@ export function assertOptional(x, name, type) {
|
|
|
21
21
|
return assert(x, name, type, true);
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
export function base64Decode(str) {
|
|
25
|
+
if(Uint8Array.fromBase64) {
|
|
26
|
+
return Uint8Array.fromBase64(str);
|
|
27
|
+
}
|
|
28
|
+
return base64url.decode(
|
|
29
|
+
str.replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_'));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function base64Encode(data) {
|
|
33
|
+
if(data.toBase64) {
|
|
34
|
+
return data.toBase64();
|
|
35
|
+
}
|
|
36
|
+
// note: this is base64-no-pad; will only work with specific data lengths
|
|
37
|
+
base64url.encode(data).replace(/-/g, '+').replace(/_/g, '/');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function createNamedError({message, name, cause} = {}) {
|
|
41
|
+
const error = new Error(message, {cause});
|
|
42
|
+
error.name = name;
|
|
43
|
+
return error;
|
|
44
|
+
}
|
|
45
|
+
|
|
24
46
|
export async function discoverIssuer({issuerConfigUrl, agent} = {}) {
|
|
25
47
|
try {
|
|
26
48
|
assert(issuerConfigUrl, 'issuerConfigUrl', 'string');
|
|
@@ -251,6 +273,49 @@ export async function robustDiscoverIssuer({issuer, agent} = {}) {
|
|
|
251
273
|
throw error;
|
|
252
274
|
}
|
|
253
275
|
|
|
276
|
+
export function selectJwk({keys, kid, alg, kty, crv, use} = {}) {
|
|
277
|
+
/* Example JWKs "keys":
|
|
278
|
+
"jwks": {
|
|
279
|
+
"keys": [
|
|
280
|
+
{
|
|
281
|
+
"kty": "EC",
|
|
282
|
+
"use": "enc",
|
|
283
|
+
"crv": "P-256",
|
|
284
|
+
"x": "...",
|
|
285
|
+
"y": "...",
|
|
286
|
+
"alg": "ECDH-ES",
|
|
287
|
+
"kid": "..."
|
|
288
|
+
}
|
|
289
|
+
]
|
|
290
|
+
} */
|
|
291
|
+
if(!Array.isArray(keys)) {
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// match `kid` exactly if given
|
|
296
|
+
if(kid !== undefined) {
|
|
297
|
+
return keys.find(jwk => jwk?.kid === kid);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
return keys.find(jwk => {
|
|
301
|
+
// default unspecified search values to whatever is in `jwk`
|
|
302
|
+
const alg1 = alg ?? jwk.alg;
|
|
303
|
+
const kty1 = kty ?? jwk.kty;
|
|
304
|
+
const crv1 = crv ?? jwk.crv;
|
|
305
|
+
const use1 = use ?? jwk.use;
|
|
306
|
+
const {
|
|
307
|
+
// default missing `alg` value in `jwk` to search value
|
|
308
|
+
alg: alg2 = alg1,
|
|
309
|
+
kty: kty2,
|
|
310
|
+
crv: crv2,
|
|
311
|
+
// default missing `use` value in `jwk` to search value
|
|
312
|
+
use: use2 = use1
|
|
313
|
+
} = jwk;
|
|
314
|
+
// return if `jwk` matches computed values
|
|
315
|
+
return alg1 === alg2 && kty1 === kty2 && crv1 === crv2 && use1 === use2;
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
|
|
254
319
|
export async function signJWT({payload, protectedHeader, signer} = {}) {
|
|
255
320
|
// encode payload and protected header
|
|
256
321
|
const b64Payload = base64url.encode(JSON.stringify(payload));
|
package/lib/x509.js
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) 2023-2025 Digital Bazaar, Inc. All rights reserved.
|
|
3
|
+
*/
|
|
4
|
+
import {
|
|
5
|
+
Certificate,
|
|
6
|
+
CertificateChainValidationEngine,
|
|
7
|
+
id_SubjectAltName
|
|
8
|
+
} from 'pkijs';
|
|
9
|
+
import {base64Decode} from './util.js';
|
|
10
|
+
|
|
11
|
+
export function fromPemOrBase64(str) {
|
|
12
|
+
const tag = 'CERTIFICATE';
|
|
13
|
+
const pattern = new RegExp(
|
|
14
|
+
`-{5}BEGIN ${tag}-{5}([a-zA-Z0-9=+\\/\\n\\r]+)-{5}END ${tag}-{5}`, 'g');
|
|
15
|
+
const matches = pattern.exec(str);
|
|
16
|
+
if(!matches) {
|
|
17
|
+
throw new Error('No PEM or Base64-formatted certificate found.');
|
|
18
|
+
}
|
|
19
|
+
const b64 = matches[1].replace(/\r/g, '').replace(/\n/g, '');
|
|
20
|
+
return _fromBase64(b64);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function hasDomainSubjectAltName({certificate, name} = {}) {
|
|
24
|
+
const subjectAltNames = new Set();
|
|
25
|
+
for(const extension of certificate.extensions) {
|
|
26
|
+
if(extension.extnID === id_SubjectAltName) {
|
|
27
|
+
for(const altName of extension.parsedValue.altNames) {
|
|
28
|
+
// `domain` type
|
|
29
|
+
if(altName.type === 2) {
|
|
30
|
+
subjectAltNames.add(altName.value);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return subjectAltNames.has(name);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function parseCertificateChain({x5c} = {}) {
|
|
39
|
+
return x5c.map(c => Certificate.fromBER(base64Decode(c)));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export async function verifyCertificateChain({
|
|
43
|
+
chain, trustedCertificates
|
|
44
|
+
} = {}) {
|
|
45
|
+
if(!(chain?.length > 0)) {
|
|
46
|
+
throw new Error('No matching certificate.');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const chainEngine = new CertificateChainValidationEngine({
|
|
50
|
+
certs: chain.map(c => typeof c === 'string' ? fromPemOrBase64(c) : c),
|
|
51
|
+
trustedCerts: trustedCertificates.map(
|
|
52
|
+
c => typeof c === 'string' ? fromPemOrBase64(c) : c)
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const verifyResult = await chainEngine.verify();
|
|
56
|
+
return verifyResult;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function _fromBase64(str) {
|
|
60
|
+
return Certificate.fromBER(base64Decode(str));
|
|
61
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@digitalbazaar/oid4-client",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.0.0",
|
|
4
4
|
"description": "An OID4 (VC + VP) client",
|
|
5
5
|
"homepage": "https://github.com/digitalbazaar/oid4-client",
|
|
6
6
|
"author": {
|
|
@@ -25,15 +25,15 @@
|
|
|
25
25
|
"dependencies": {
|
|
26
26
|
"@digitalbazaar/http-client": "^4.0.0",
|
|
27
27
|
"base64url-universal": "^2.0.0",
|
|
28
|
-
"jose": "^
|
|
29
|
-
"jsonpath-plus": "^10.
|
|
28
|
+
"jose": "^6.0.13",
|
|
29
|
+
"jsonpath-plus": "^10.3.0",
|
|
30
30
|
"jsonpointer": "^5.0.1",
|
|
31
|
-
"
|
|
31
|
+
"pkijs": "^3.2.5"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
|
-
"c8": "^
|
|
34
|
+
"c8": "^10.1.3",
|
|
35
35
|
"chai": "^4.3.6",
|
|
36
|
-
"cross-env": "^
|
|
36
|
+
"cross-env": "^10.0.0",
|
|
37
37
|
"eslint": "^8.41.0",
|
|
38
38
|
"eslint-config-digitalbazaar": "^5.0.1",
|
|
39
39
|
"eslint-plugin-jsdoc": "^50.4.1",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"karma-webpack": "^5.0.0",
|
|
50
50
|
"mocha": "^10.0.0",
|
|
51
51
|
"mocha-lcov-reporter": "^1.3.0",
|
|
52
|
-
"webpack": "^5.
|
|
52
|
+
"webpack": "^5.101.3"
|
|
53
53
|
},
|
|
54
54
|
"c8": {
|
|
55
55
|
"reporter": [
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
]
|
|
60
60
|
},
|
|
61
61
|
"engines": {
|
|
62
|
-
"node": ">=
|
|
62
|
+
"node": ">=20"
|
|
63
63
|
},
|
|
64
64
|
"keywords": [
|
|
65
65
|
"OID4",
|