From f742d005a1584029c43760940b8b41751776a3ff Mon Sep 17 00:00:00 2001 From: Mirko Mollik Date: Mon, 6 May 2024 21:48:15 +0200 Subject: [PATCH] Add DID and x509 for key reference Fixes #30 Signed-off-by: Mirko Mollik --- apps/verifier-backend/package.json | 3 +- apps/verifier-backend/src/key/key.service.ts | 51 +++++++++++++++++-- .../verifier/relying-party-manager.service.ts | 4 +- pnpm-lock.yaml | 45 +++++++++++----- 4 files changed, 82 insertions(+), 21 deletions(-) diff --git a/apps/verifier-backend/package.json b/apps/verifier-backend/package.json index 0325b01a..6892444b 100644 --- a/apps/verifier-backend/package.json +++ b/apps/verifier-backend/package.json @@ -49,7 +49,8 @@ "passport-http-bearer": "^1.0.1", "reflect-metadata": "^0.2.0", "rxjs": "^7.8.1", - "uuid": "^9.0.1" + "uuid": "^9.0.1", + "web-did-resolver": "^2.0.27" }, "devDependencies": { "@nestjs/cli": "^10.0.0", diff --git a/apps/verifier-backend/src/key/key.service.ts b/apps/verifier-backend/src/key/key.service.ts index 3b24a53d..df7ebe8e 100644 --- a/apps/verifier-backend/src/key/key.service.ts +++ b/apps/verifier-backend/src/key/key.service.ts @@ -1,11 +1,19 @@ import { ConflictException, Injectable, OnModuleInit } from '@nestjs/common'; import { ES256 } from '@sd-jwt/crypto-nodejs'; import { existsSync, mkdirSync, writeFileSync, readFileSync } from 'node:fs'; -import { JWK } from 'jose'; +import { JWK, JWTPayload } from 'jose'; import { v4 } from 'uuid'; import { HttpService } from '@nestjs/axios'; import { firstValueFrom } from 'rxjs'; import { encodeDidJWK } from 'src/verifier/did'; +import { X509Certificate } from 'node:crypto'; +import { Resolver } from 'did-resolver'; +import web from 'web-did-resolver'; + +const webResolver = web.getResolver(); +const resolver = new Resolver({ + ...webResolver, +}); interface IssuerMetadata { issuer: string; @@ -90,10 +98,45 @@ export class KeyService implements OnModuleInit { return this.privateKey; } - async resolvePublicKey(issuer: string, kid: string): Promise { + /** + * Resolve the public key from the issuer, the function will first check for the x5c header, then for the did document and finally for the issuer metadata. + * @param payload + * @param header + * @returns + */ + async resolvePublicKey(payload: JWTPayload, header: JWK): Promise { + if (header.x5c) { + const cert = new X509Certificate(Buffer.from(header.x5c[0], 'base64')); + //TODO: implement the validation of the certificate chain and also the comparison of the identifier + if (cert.subject !== payload.iss) { + throw new Error('Subject and issuer do not match'); + } + return cert.publicKey.export({ format: 'jwk' }) as JWK; + } + if (payload.iss.startsWith('did:')) { + const did = await resolver.resolve(payload.iss); + if (!did) { + throw new ConflictException('DID not found'); + } + //TODO: header.kid can be relative or absolute, we need to handle this + const key = did.didDocument.verificationMethod.find( + (vm) => vm.id === header.kid + ); + if (!key) { + throw new ConflictException('Key not found'); + } + if (!key.publicKeyJwk) { + throw new ConflictException( + 'Public key not found, we are only supporting JWK keys for now.' + ); + } + return key.publicKeyJwk; + } + + // lets look for a did const response = await firstValueFrom( this.httpService.get( - `${issuer}/.well-known/jwt-vc-issuer` + `${payload.iss}/.well-known/jwt-vc-issuer` ) ).then( (r) => r.data, @@ -101,7 +144,7 @@ export class KeyService implements OnModuleInit { throw new ConflictException('Issuer not reachable'); } ); - const key = response.jwks.keys.find((key) => key.kid === kid); + const key = response.jwks.keys.find((key) => key.kid === header.kid); if (!key) { throw new Error('Key not found'); } diff --git a/apps/verifier-backend/src/verifier/relying-party-manager.service.ts b/apps/verifier-backend/src/verifier/relying-party-manager.service.ts index fffb1946..4e251f4c 100644 --- a/apps/verifier-backend/src/verifier/relying-party-manager.service.ts +++ b/apps/verifier-backend/src/verifier/relying-party-manager.service.ts @@ -188,8 +188,8 @@ export class RelyingPartyManagerService { const payload = decodedVC.jwt?.payload as JWTPayload; const header = decodedVC.jwt?.header as JWK; const publicKey = await this.keyService.resolvePublicKey( - payload.iss as string, - header.kid as string + payload, + header ); const verify = await ES256.getVerifier(publicKey); return verify(data, signature); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 029cb74a..4172021a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -630,6 +630,9 @@ importers: uuid: specifier: ^9.0.1 version: 9.0.1 + web-did-resolver: + specifier: ^2.0.27 + version: 2.0.27(encoding@0.1.13) devDependencies: '@nestjs/cli': specifier: ^10.0.0 @@ -7885,6 +7888,9 @@ packages: wcwidth@1.0.1: resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==} + web-did-resolver@2.0.27: + resolution: {integrity: sha512-YxQlNdeYBXLhVpMW62+TPlc6sSOiWyBYq7DNvY6FXmXOD9g0zLeShpq2uCKFFQV/WlSrBi/yebK/W5lMTDxMUQ==} + webidl-conversions@3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} @@ -8693,7 +8699,7 @@ snapshots: '@babel/traverse': 7.24.1 '@babel/types': 7.24.0 convert-source-map: 2.0.0 - debug: 4.3.4(supports-color@5.5.0) + debug: 4.3.4 gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -9486,7 +9492,7 @@ snapshots: '@babel/helper-split-export-declaration': 7.22.6 '@babel/parser': 7.24.4 '@babel/types': 7.24.0 - debug: 4.3.4(supports-color@5.5.0) + debug: 4.3.4 globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -11811,13 +11817,13 @@ snapshots: agent-base@6.0.2: dependencies: - debug: 4.3.4(supports-color@5.5.0) + debug: 4.3.4 transitivePeerDependencies: - supports-color agent-base@7.1.1: dependencies: - debug: 4.3.4(supports-color@5.5.0) + debug: 4.3.4 transitivePeerDependencies: - supports-color @@ -12724,6 +12730,10 @@ snapshots: ms: 2.1.2 optional: true + debug@4.3.4: + dependencies: + ms: 2.1.2 + debug@4.3.4(supports-color@5.5.0): dependencies: ms: 2.1.2 @@ -13372,7 +13382,7 @@ snapshots: extract-zip@2.0.1: dependencies: - debug: 4.3.4(supports-color@5.5.0) + debug: 4.3.4 get-stream: 5.2.0 yauzl: 2.10.0 optionalDependencies: @@ -13649,7 +13659,7 @@ snapshots: dependencies: basic-ftp: 5.0.5 data-uri-to-buffer: 6.0.2 - debug: 4.3.4(supports-color@5.5.0) + debug: 4.3.4 fs-extra: 11.2.0 transitivePeerDependencies: - supports-color @@ -13845,7 +13855,7 @@ snapshots: http-proxy-agent@7.0.2: dependencies: agent-base: 7.1.1 - debug: 4.3.4(supports-color@5.5.0) + debug: 4.3.4 transitivePeerDependencies: - supports-color @@ -13879,14 +13889,14 @@ snapshots: https-proxy-agent@5.0.1: dependencies: agent-base: 6.0.2 - debug: 4.3.4(supports-color@5.5.0) + debug: 4.3.4 transitivePeerDependencies: - supports-color https-proxy-agent@7.0.4: dependencies: agent-base: 7.1.1 - debug: 4.3.4(supports-color@5.5.0) + debug: 4.3.4 transitivePeerDependencies: - supports-color @@ -14187,7 +14197,7 @@ snapshots: istanbul-lib-source-maps@4.0.1: dependencies: - debug: 4.3.4(supports-color@5.5.0) + debug: 4.3.4 istanbul-lib-coverage: 3.2.2 source-map: 0.6.1 transitivePeerDependencies: @@ -15542,7 +15552,7 @@ snapshots: dependencies: '@tootallnate/quickjs-emscripten': 0.23.0 agent-base: 7.1.1 - debug: 4.3.4(supports-color@5.5.0) + debug: 4.3.4 get-uri: 6.0.3 http-proxy-agent: 7.0.2 https-proxy-agent: 7.0.4 @@ -15879,7 +15889,7 @@ snapshots: proxy-agent@6.4.0: dependencies: agent-base: 7.1.1 - debug: 4.3.4(supports-color@5.5.0) + debug: 4.3.4 http-proxy-agent: 7.0.2 https-proxy-agent: 7.0.4 lru-cache: 7.18.3 @@ -16409,7 +16419,7 @@ snapshots: socks-proxy-agent@8.0.3: dependencies: agent-base: 7.1.1 - debug: 4.3.4(supports-color@5.5.0) + debug: 4.3.4 socks: 2.8.3 transitivePeerDependencies: - supports-color @@ -16634,7 +16644,7 @@ snapshots: dependencies: component-emitter: 1.3.1 cookiejar: 2.1.4 - debug: 4.3.4(supports-color@5.5.0) + debug: 4.3.4 fast-safe-stringify: 2.1.1 form-data: 4.0.0 formidable: 2.1.2 @@ -17181,6 +17191,13 @@ snapshots: dependencies: defaults: 1.0.4 + web-did-resolver@2.0.27(encoding@0.1.13): + dependencies: + cross-fetch: 4.0.0(encoding@0.1.13) + did-resolver: 4.1.0 + transitivePeerDependencies: + - encoding + webidl-conversions@3.0.1: {} webpack-dev-middleware@5.3.4(webpack@5.90.3(esbuild@0.20.1)):