Skip to content

Commit 56a291c

Browse files
committed
fix: jwk thumprint using crypto.subtle
Signed-off-by: Timo Glastra <timo@animo.id>
1 parent 86d0423 commit 56a291c

12 files changed

Lines changed: 35 additions & 35 deletions

File tree

packages/client/lib/AuthorizationCodeClient.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ export const createAuthorizationRequestUrl = async ({
114114
const client_id = clientId ?? authorizationRequest.clientId;
115115

116116
// Authorization server metadata takes precedence
117-
const authorizationMetadata = endpointMetadata.authorizationServerMetadata ?? endpointMetadata.credentialIssuerMetadata
117+
const authorizationMetadata = endpointMetadata.authorizationServerMetadata ?? endpointMetadata.credentialIssuerMetadata;
118118

119119
let { authorizationDetails } = authorizationRequest;
120120
const parMode = authorizationMetadata?.require_pushed_authorization_requests
@@ -182,7 +182,6 @@ export const createAuthorizationRequestUrl = async ({
182182
}
183183
const parEndpoint = authorizationMetadata?.pushed_authorization_request_endpoint;
184184

185-
186185
let queryObj: Record<string, any> | PushedAuthorizationResponse = {
187186
response_type: ResponseType.AUTH_CODE,
188187
...(!pkce.disabled && {

packages/common/lib/dpop/DPoP.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import { jwtDecode } from 'jwt-decode';
2-
import SHA from 'sha.js';
32
import * as u8a from 'uint8arrays';
43
import { v4 as uuidv4 } from 'uuid';
54

5+
import { defaultHasher } from '../hasher';
6+
67
import {
78
calculateJwkThumbprint,
89
CreateJwtCallback,
@@ -68,7 +69,7 @@ export async function createDPoP(options: CreateDPoPOpts): Promise<string> {
6869
throw new Error('expected access token without scheme');
6970
}
7071

71-
const ath = jwtPayloadProps.accessToken ? u8a.toString(SHA('sha256').update(jwtPayloadProps.accessToken).digest(), 'base64url') : undefined;
72+
const ath = jwtPayloadProps.accessToken ? u8a.toString(defaultHasher(jwtPayloadProps.accessToken, 'sha256'), 'base64url') : undefined;
7273
return createJwtCallback(
7374
{ method: 'jwk', type: 'dpop', alg: jwtIssuer.alg, jwk: jwtIssuer.jwk, dPoPSigningAlgValuesSupported },
7475
{
@@ -195,7 +196,7 @@ export async function verifyDPoP(
195196
}
196197

197198
const accessToken = authorizationHeader.replace('DPoP ', '');
198-
const expectedAth = u8a.toString(SHA('sha256').update(accessToken).digest(), 'base64url');
199+
const expectedAth = u8a.toString(defaultHasher(accessToken, 'sha256'), 'base64url');
199200
if (dPoPPayload.ath !== expectedAth) {
200201
throw new Error('invalid_dpop_proof. Invalid ath claim');
201202
}

packages/common/lib/hasher.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { Hasher } from '@sphereon/ssi-types';
2+
import sha from 'sha.js';
3+
4+
const supportedAlgorithms = ['sha256', 'sha384', 'sha512'] as const;
5+
type SupportedAlgorithms = (typeof supportedAlgorithms)[number];
6+
7+
export const defaultHasher: Hasher = (data, algorithm) => {
8+
if (!supportedAlgorithms.includes(algorithm as SupportedAlgorithms)) {
9+
throw new Error(`Unsupported hashing algorithm ${algorithm}`);
10+
}
11+
12+
return new Uint8Array(
13+
sha(algorithm as SupportedAlgorithms)
14+
.update(data)
15+
.digest(),
16+
);
17+
};

packages/common/lib/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@ export * from './jwt';
77
export * from './dpop';
88

99
export { v4 as uuidv4 } from 'uuid';
10+
export { defaultHasher } from './hasher';

packages/common/lib/jwt/JwkThumbprint.ts

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import * as u8a from 'uint8arrays';
22

3+
import { defaultHasher } from '../hasher';
34
import { DigestAlgorithm } from '../types';
45

56
import { JWK } from '.';
@@ -10,11 +11,6 @@ const check = (value: unknown, description: string) => {
1011
}
1112
};
1213

13-
const digest = async (algorithm: DigestAlgorithm, data: Uint8Array) => {
14-
const subtleDigest = `SHA-${algorithm.slice(-3)}`;
15-
return new Uint8Array(await crypto.subtle.digest(subtleDigest, data));
16-
};
17-
1814
export async function calculateJwkThumbprint(jwk: JWK, digestAlgorithm?: DigestAlgorithm): Promise<string> {
1915
if (!jwk || typeof jwk !== 'object') {
2016
throw new TypeError('JWK must be an object');
@@ -48,8 +44,7 @@ export async function calculateJwkThumbprint(jwk: JWK, digestAlgorithm?: DigestA
4844
default:
4945
throw Error('"kty" (Key Type) Parameter missing or unsupported');
5046
}
51-
const data = u8a.fromString(JSON.stringify(components), 'utf-8');
52-
return u8a.toString(await digest(algorithm, data), 'base64url');
47+
return u8a.toString(defaultHasher(algorithm, JSON.stringify(components)), 'base64url');
5348
}
5449

5550
export async function getDigestAlgorithmFromJwkThumbprintUri(uri: string): Promise<DigestAlgorithm> {

packages/oid4vci-common/lib/functions/RandomUtils.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import SHA from 'sha.js';
1+
import { defaultHasher } from '@sphereon/oid4vc-common';
22
import * as u8a from 'uint8arrays';
33
import { SupportedEncodings } from 'uint8arrays/to-string';
44

@@ -26,7 +26,7 @@ export const createCodeChallenge = (codeVerifier: string, codeChallengeMethod?:
2626
if (codeChallengeMethod === CodeChallengeMethod.plain) {
2727
return codeVerifier;
2828
} else if (!codeChallengeMethod || codeChallengeMethod === CodeChallengeMethod.S256) {
29-
return u8a.toString(SHA('sha256').update(codeVerifier).digest(), 'base64url');
29+
return u8a.toString(defaultHasher(codeVerifier, 'sha256'), 'base64url');
3030
} else {
3131
// Just a precaution if a new method would be introduced
3232
throw Error(`code challenge method ${codeChallengeMethod} not implemented`);

packages/oid4vci-common/package.json

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,11 @@
1414
"@sphereon/ssi-types": "0.28.0",
1515
"cross-fetch": "^3.1.8",
1616
"jwt-decode": "^4.0.0",
17-
"sha.js": "^2.4.11",
1817
"uint8arrays": "3.1.1",
1918
"uuid": "^9.0.0"
2019
},
2120
"devDependencies": {
2221
"@types/jest": "^29.5.12",
23-
"@types/sha.js": "^2.4.4",
2422
"@types/uuid": "^9.0.1",
2523
"typescript": "5.4.5"
2624
},

packages/siop-oid4vp/lib/helpers/State.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import { uuidv4 } from '@sphereon/oid4vc-common'
2-
import SHA from 'sha.js'
1+
import { defaultHasher, uuidv4 } from '@sphereon/oid4vc-common'
32

43
import { base64urlEncodeBuffer } from './Encodings'
54

@@ -8,7 +7,7 @@ export function getNonce(state: string, nonce?: string) {
87
}
98

109
export function toNonce(input: string): string {
11-
const buff = SHA('sha256').update(input).digest()
10+
const buff = defaultHasher(input, 'sha256')
1211
return base64urlEncodeBuffer(buff)
1312
}
1413

packages/siop-oid4vp/lib/op/Opts.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { defaultHasher } from '@sphereon/oid4vc-common'
2+
13
import { VerifyAuthorizationRequestOpts } from '../authorization-request'
24
import { AuthorizationResponseOpts } from '../authorization-response'
35
import { LanguageTagUtils } from '../helpers'
@@ -63,7 +65,7 @@ export const createVerifyRequestOptsFromBuilderOrExistingOpts = (opts: {
6365
return opts.builder
6466
? {
6567
verifyJwtCallback: opts.builder.verifyJwtCallback,
66-
hasher: opts.builder.hasher,
68+
hasher: opts.builder.hasher ?? defaultHasher,
6769
verification: {},
6870
supportedVersions: opts.builder.supportedVersions,
6971
correlationId: undefined,

packages/siop-oid4vp/lib/rp/Opts.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { defaultHasher } from '@sphereon/oid4vc-common'
2+
13
import { CreateAuthorizationRequestOpts, PropertyTarget, PropertyTargets, RequestPropertyWithTargets } from '../authorization-request'
24
import { VerifyAuthorizationResponseOpts } from '../authorization-response'
35
// import { CreateAuthorizationRequestOptsSchema } from '../schemas';
@@ -49,7 +51,7 @@ export const createRequestOptsFromBuilderOrExistingOpts = (opts: { builder?: RPB
4951
export const createVerifyResponseOptsFromBuilderOrExistingOpts = (opts: { builder?: RPBuilder; verifyOpts?: VerifyAuthorizationResponseOpts }) => {
5052
return opts.builder
5153
? {
52-
hasher: opts.builder.hasher,
54+
hasher: opts.builder.hasher ?? defaultHasher,
5355
verifyJwtCallback: opts.builder.verifyJwtCallback,
5456
verification: {
5557
presentationVerificationCallback: opts.builder.presentationVerificationCallback,

0 commit comments

Comments
 (0)