diff --git a/dist/src/client-factories/siopv2.factory.js b/dist/src/client-factories/siopv2.factory.js index ec65a2d..553f6e6 100644 --- a/dist/src/client-factories/siopv2.factory.js +++ b/dist/src/client-factories/siopv2.factory.js @@ -7,7 +7,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; -import { importJWK, jwtVerify } from "jose"; +import { decodeJwt, EncryptJWT, importJWK, jwtVerify } from "jose"; import { OauthError } from "../oauth-responses"; import { KeyStore } from '../key-store'; import { STATE_KEY, NONCE_KEY } from '../constants'; @@ -71,8 +71,15 @@ export function createSiopv2Client({ oauth, eventHandler, storage }) { "client_encryption_key": publicKey, "client_encryption_alg": "ECDH-ES" }; + const { authorization_server_encryption_key, direct_post_encryption_alg } = decodeJwt(request); + localStorage.setItem("authorizationServerEncryptionKey", JSON.stringify(authorization_server_encryption_key)); + localStorage.setItem("directPostEncryptionAlg", JSON.stringify(direct_post_encryption_alg)); const id_token = yield this.keyStore.sign(payload, client_id); + const response = authorization_server_encryption_key && (yield new EncryptJWT({ id_token }) + .setProtectedHeader({ alg: direct_post_encryption_alg, enc: "A256GCM" }) + .encrypt(yield importJWK(authorization_server_encryption_key, direct_post_encryption_alg))); return { + response, id_token, client_id, redirect_uri, diff --git a/dist/src/client-factories/verifiable-credentials-issuance.factory.js b/dist/src/client-factories/verifiable-credentials-issuance.factory.js index 1f00ba3..803d98b 100644 --- a/dist/src/client-factories/verifiable-credentials-issuance.factory.js +++ b/dist/src/client-factories/verifiable-credentials-issuance.factory.js @@ -7,6 +7,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; +import { EncryptJWT, importJWK, jwtDecrypt } from "jose"; import { OauthError } from "../oauth-responses"; import { KeyStore } from '../key-store'; import { CredentialsStore } from '../credentials-store'; @@ -40,14 +41,34 @@ export function createVerifiableCredentialsIssuanceClient({ oauth, eventHandler, } getTokenParams(preauthorizedCode) { return __awaiter(this, void 0, void 0, function* () { - return { - grant_type: this.grantType, - client_id: this.clientId, - client_secret: this.clientSecret, - redirect_uri: this.redirectUri, - 'pre-authorized_code': preauthorizedCode, - scope: this.scope - }; + const authorization_server_encryption_key = JSON.parse(localStorage.getItem("authorizationServerEncryptionKey") || "null"); + const direct_post_encryption_alg = JSON.parse(localStorage.getItem("directPostEncryptionAlg") || "null"); + if (authorization_server_encryption_key && direct_post_encryption_alg) { + const params = { + grant_type: this.grantType, + client_secret: this.clientSecret, + redirect_uri: this.redirectUri, + 'pre-authorized_code': preauthorizedCode, + scope: this.scope + }; + const encrypted_request = yield new EncryptJWT(params) + .setProtectedHeader({ alg: direct_post_encryption_alg, enc: "A256GCM" }) + .encrypt(yield importJWK(authorization_server_encryption_key, direct_post_encryption_alg)); + return { + client_id: this.clientId, + encrypted_request, + }; + } + else { + return { + grant_type: this.grantType, + client_id: this.clientId, + client_secret: this.clientSecret, + redirect_uri: this.redirectUri, + 'pre-authorized_code': preauthorizedCode, + scope: this.scope + }; + } }); } getToken(preauthorizedCode) { @@ -55,9 +76,16 @@ export function createVerifiableCredentialsIssuanceClient({ oauth, eventHandler, // TODO throw an error in case of misconfiguration (tokenPath) const { oauth: { api, tokenPath = '' } } = this; const body = yield this.getTokenParams(preauthorizedCode); - return api.post(tokenPath, body).then(({ data }) => { - return data; - }).catch(({ status, response }) => { + return api.post(tokenPath, body).then((_a) => __awaiter(this, [_a], void 0, function* ({ data }) { + if (data.encrypted_response) { + const { privateKey } = JSON.parse(localStorage.getItem("encryptionKeyPair") || "{}"); + const { payload: response } = yield jwtDecrypt(data.encrypted_response, yield importJWK(privateKey, "ECDH-ES")); + return response; + } + else { + return data; + } + })).catch(({ status, response }) => { throw new OauthError(Object.assign({ status }, response.data)); }); }); @@ -73,11 +101,29 @@ export function createVerifiableCredentialsIssuanceClient({ oauth, eventHandler, proof_type: 'jwt', jwt: proofJwt }; - return { - credential_identifier: credentialIdentifier, - format, - proof - }; + const authorization_server_encryption_key = JSON.parse(localStorage.getItem("authorizationServerEncryptionKey") || "null"); + const direct_post_encryption_alg = JSON.parse(localStorage.getItem("directPostEncryptionAlg") || "null"); + if (authorization_server_encryption_key && direct_post_encryption_alg) { + const params = { + credential_identifier: credentialIdentifier, + format, + proof + }; + const encrypted_request = yield new EncryptJWT(params) + .setProtectedHeader({ alg: direct_post_encryption_alg, enc: "A256GCM" }) + .encrypt(yield importJWK(authorization_server_encryption_key, direct_post_encryption_alg)); + return { + client_id: this.clientId, + encrypted_request, + }; + } + else { + return { + credential_identifier: credentialIdentifier, + format, + proof + }; + } }); } getCredential(_a, credentialIdentifier_1, format_1) { @@ -88,9 +134,16 @@ export function createVerifiableCredentialsIssuanceClient({ oauth, eventHandler, headers: { 'Authorization': `Bearer ${accessToken}` } - }).then(({ data }) => { - return data; - }).catch(({ status, response }) => { + }).then((_a) => __awaiter(this, [_a], void 0, function* ({ data }) { + if (data.encrypted_response) { + const { privateKey } = JSON.parse(localStorage.getItem("encryptionKeyPair") || "{}"); + const { payload: response } = yield jwtDecrypt(data.encrypted_response, yield importJWK(privateKey, "ECDH-ES")); + return response; + } + else { + return data; + } + })).catch(({ status, response }) => { throw new OauthError(Object.assign({ status }, response.data)); }).then((response) => __awaiter(this, void 0, void 0, function* () { yield this.credentialsStore.insertCredential(credentialIdentifier, response); diff --git a/dist/src/client-factories/verifiable-presentations.factory.js b/dist/src/client-factories/verifiable-presentations.factory.js index d0cb2bd..7297aa3 100644 --- a/dist/src/client-factories/verifiable-presentations.factory.js +++ b/dist/src/client-factories/verifiable-presentations.factory.js @@ -7,7 +7,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; -import { decodeJwt } from "jose"; +import { EncryptJWT, importJWK, jwtDecrypt } from "jose"; import { OauthError } from "../oauth-responses"; import { KeyStore } from '../key-store'; import { CredentialsStore } from '../credentials-store'; @@ -52,10 +52,13 @@ export function createVerifiablePresentationsClient({ oauth, eventHandler, stora } generatePresentation(_a, credentials_1) { return __awaiter(this, arguments, void 0, function* ({ request, redirect_uri }, credentials) { - const url = new URL(redirect_uri); - const { presentation_definition } = yield parseVerifiablePresentationRequest(request); + const { presentation_definition, authorization_server_encryption_key, direct_post_encryption_alg } = yield parseVerifiablePresentationRequest(request); const presentation = yield this.credentialsStore.presentation(presentation_definition, credentials); - return Object.assign({ redirect_uri }, presentation); + const response = authorization_server_encryption_key && (yield new EncryptJWT(presentation) + .setProtectedHeader({ alg: direct_post_encryption_alg, enc: "A256GCM" }) + .encrypt(yield importJWK(authorization_server_encryption_key, direct_post_encryption_alg))); + return Object.assign({ response, + redirect_uri }, presentation); }); } state() { @@ -162,24 +165,30 @@ function parseVerifiablePresentationsParams(params) { }); } function parseVerifiablePresentationRequest(request) { - let decodedRequest; - try { - decodedRequest = decodeJwt(request); - } - catch (error) { - return Promise.reject(new OauthError({ - error: 'unkown_error', - error_description: error.toString() - })); - } - const presentation_definition = decodedRequest['presentation_definition']; - if (!presentation_definition) { - return Promise.reject(new OauthError({ - error: 'unkown_error', - error_description: 'presentation_definition parameter is missing in VerifiablePresentations request.' - })); - } - return Promise.resolve({ - presentation_definition + return __awaiter(this, void 0, void 0, function* () { + let decodedRequest; + const { privateKey } = JSON.parse(localStorage.getItem("encryptionKeyPair") || "{}"); + try { + const { payload } = yield jwtDecrypt(request, yield importJWK(privateKey, "ECDH-ES")); + decodedRequest = payload; + } + catch (error) { + return Promise.reject(new OauthError({ + error: 'unkown_error', + error_description: error.toString() + })); + } + const presentation_definition = decodedRequest['presentation_definition']; + if (!presentation_definition) { + return Promise.reject(new OauthError({ + error: 'unkown_error', + error_description: 'presentation_definition parameter is missing in VerifiablePresentations request.' + })); + } + return Promise.resolve({ + authorization_server_encryption_key: decodedRequest['authorization_server_encryption_key'], + direct_post_encryption_alg: decodedRequest['direct_post_encryption_alg'], + presentation_definition + }); }); } diff --git a/dist/src/credentials-store.js b/dist/src/credentials-store.js index 87652eb..e4b1280 100644 --- a/dist/src/credentials-store.js +++ b/dist/src/credentials-store.js @@ -9,6 +9,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge }; import { decodeJwt } from 'jose'; import { decodeSdJwt } from '@sd-jwt/decode'; +import { OauthError } from './oauth-responses'; import { CREDENTIALS_KEY } from './constants'; import { KeyStore } from './key-store'; export class CredentialsStore { @@ -171,6 +172,12 @@ export class Credential { } static fromResponse(credentialId_1, _a) { return __awaiter(this, arguments, void 0, function* (credentialId, { format, credential }) { + if (!format || !credential) { + throw new OauthError({ + error: "invalid_credential", + error_description: 'Invalid credential response.' + }); + } if (format == 'vc+sd-jwt') { return decodeSdJwt(credential, () => { return Promise.resolve(new Uint8Array()); }).then(formattedCredential => { const params = { diff --git a/src/client-factories/siopv2.factory.ts b/src/client-factories/siopv2.factory.ts index d32737b..091b359 100644 --- a/src/client-factories/siopv2.factory.ts +++ b/src/client-factories/siopv2.factory.ts @@ -1,4 +1,4 @@ -import { importJWK, jwtVerify } from "jose" +import { decodeJwt, EncryptJWT, importJWK, jwtVerify, JWK } from "jose" import { BorutaOauth } from "../boruta-oauth" import { OauthError, Siopv2Success } from "../oauth-responses" import { KeyStore } from '../key-store' @@ -100,9 +100,32 @@ export function createSiopv2Client({ oauth, eventHandler, storage }: Siopv2Facto "client_encryption_alg": "ECDH-ES" } + const { + authorization_server_encryption_key, + direct_post_encryption_alg + } = decodeJwt<{ + authorization_server_encryption_key: JWK + direct_post_encryption_alg: string + }>(request) + + localStorage.setItem( + "authorizationServerEncryptionKey", + JSON.stringify(authorization_server_encryption_key) + ) + + localStorage.setItem( + "directPostEncryptionAlg", + JSON.stringify(direct_post_encryption_alg) + ) + const id_token = await this.keyStore.sign(payload, client_id) + const response = authorization_server_encryption_key && await new EncryptJWT({ id_token }) + .setProtectedHeader({ alg: direct_post_encryption_alg, enc: "A256GCM" }) + .encrypt(await importJWK(authorization_server_encryption_key, direct_post_encryption_alg)) + return { + response, id_token, client_id, redirect_uri, diff --git a/src/client-factories/verifiable-credentials-issuance.factory.ts b/src/client-factories/verifiable-credentials-issuance.factory.ts index 13b7e0b..436020d 100644 --- a/src/client-factories/verifiable-credentials-issuance.factory.ts +++ b/src/client-factories/verifiable-credentials-issuance.factory.ts @@ -1,3 +1,4 @@ +import { EncryptJWT, importJWK, jwtDecrypt } from "jose" import { BorutaOauth } from "../boruta-oauth" import { OauthError, PreauthorizedCodeSuccess, TokenSuccess, CredentialSuccess } from "../oauth-responses" import { KeyStore } from '../key-store' @@ -63,13 +64,41 @@ export function createVerifiableCredentialsIssuanceClient({ oauth, eventHandler, } async getTokenParams (preauthorizedCode: string) { - return { - grant_type: this.grantType, - client_id: this.clientId, - client_secret: this.clientSecret, - redirect_uri: this.redirectUri, - 'pre-authorized_code': preauthorizedCode, - scope: this.scope + const authorization_server_encryption_key = JSON.parse( + localStorage.getItem("authorizationServerEncryptionKey") || "null" + ) + const direct_post_encryption_alg = JSON.parse( + localStorage.getItem("directPostEncryptionAlg") || "null" + ) + + if (authorization_server_encryption_key && direct_post_encryption_alg) { + const params = { + grant_type: this.grantType, + client_secret: this.clientSecret, + redirect_uri: this.redirectUri, + 'pre-authorized_code': preauthorizedCode, + scope: this.scope + } + + const encrypted_request = await new EncryptJWT(params) + .setProtectedHeader({ alg: direct_post_encryption_alg, enc: "A256GCM" }) + .encrypt( + await importJWK(authorization_server_encryption_key, direct_post_encryption_alg) + ) + + return { + client_id: this.clientId, + encrypted_request, + } + } else { + return { + grant_type: this.grantType, + client_id: this.clientId, + client_secret: this.clientSecret, + redirect_uri: this.redirectUri, + 'pre-authorized_code': preauthorizedCode, + scope: this.scope + } } } @@ -78,8 +107,18 @@ export function createVerifiableCredentialsIssuanceClient({ oauth, eventHandler, const { oauth: { api, tokenPath = '' } } = this const body = await this.getTokenParams(preauthorizedCode) - return api.post(tokenPath, body).then(({ data }) => { - return data + return api.post(tokenPath, body).then(async ({ data }) => { + if (data.encrypted_response) { + const { privateKey } = JSON.parse(localStorage.getItem("encryptionKeyPair") || "{}") + + const { payload: response } = await jwtDecrypt( + data.encrypted_response, + await importJWK(privateKey, "ECDH-ES") + ) + return response + } else { + return data + } }).catch(({ status, response }) => { throw new OauthError({ status, ...response.data }) }) @@ -98,10 +137,36 @@ export function createVerifiableCredentialsIssuanceClient({ oauth, eventHandler, jwt: proofJwt } - return { - credential_identifier: credentialIdentifier, - format, - proof + const authorization_server_encryption_key = JSON.parse( + localStorage.getItem("authorizationServerEncryptionKey") || "null" + ) + const direct_post_encryption_alg = JSON.parse( + localStorage.getItem("directPostEncryptionAlg") || "null" + ) + + if (authorization_server_encryption_key && direct_post_encryption_alg) { + const params = { + credential_identifier: credentialIdentifier, + format, + proof + } + + const encrypted_request = await new EncryptJWT(params) + .setProtectedHeader({ alg: direct_post_encryption_alg, enc: "A256GCM" }) + .encrypt( + await importJWK(authorization_server_encryption_key, direct_post_encryption_alg) + ) + + return { + client_id: this.clientId, + encrypted_request, + } + } else { + return { + credential_identifier: credentialIdentifier, + format, + proof + } } } @@ -115,8 +180,18 @@ export function createVerifiableCredentialsIssuanceClient({ oauth, eventHandler, headers: { 'Authorization': `Bearer ${accessToken}` } - }).then(({ data }) => { - return data + }).then(async ({ data }) => { + if (data.encrypted_response) { + const { privateKey } = JSON.parse(localStorage.getItem("encryptionKeyPair") || "{}") + + const { payload: response } = await jwtDecrypt( + data.encrypted_response, + await importJWK(privateKey, "ECDH-ES") + ) + return response + } else { + return data + } }).catch(({ status, response }) => { throw new OauthError({ status, ...response.data }) }).then(async response => { diff --git a/src/client-factories/verifiable-presentations.factory.ts b/src/client-factories/verifiable-presentations.factory.ts index 9b4ad9c..901c759 100644 --- a/src/client-factories/verifiable-presentations.factory.ts +++ b/src/client-factories/verifiable-presentations.factory.ts @@ -1,4 +1,4 @@ -import { SignJWT, decodeJwt } from "jose"; +import { EncryptJWT, importJWK, JWK, jwtDecrypt } from "jose"; import { BorutaOauth } from "../boruta-oauth" import { OauthError, PresentationDefinition, VerifiablePresentationSuccess } from "../oauth-responses" import { KeyStore } from '../key-store' @@ -25,6 +25,7 @@ export type VerifiablePresentationResult = VerifiablePresentationSuccess & { type PresentationResult = PresentationCredentials & { redirect_uri: string + response: string } export function createVerifiablePresentationsClient({ oauth, eventHandler, storage }: VerifiablePresentationsFactoryParams) { @@ -92,13 +93,20 @@ export function createVerifiablePresentationsClient({ oauth, eventHandler, stora redirect_uri }: VerifiablePresentationSuccess, credentials?: Array): Promise { - const url = new URL(redirect_uri) - - const { presentation_definition } = await parseVerifiablePresentationRequest(request) + const { + presentation_definition, + authorization_server_encryption_key, + direct_post_encryption_alg + } = await parseVerifiablePresentationRequest(request) const presentation = await this.credentialsStore.presentation(presentation_definition, credentials) + const response = authorization_server_encryption_key && await new EncryptJWT(presentation) + .setProtectedHeader({ alg: direct_post_encryption_alg, enc: "A256GCM" }) + .encrypt(await importJWK(authorization_server_encryption_key, direct_post_encryption_alg)) + return { + response, redirect_uri, ...presentation } @@ -216,10 +224,26 @@ async function parseVerifiablePresentationsParams(params: URLSearchParams): Prom }) } -function parseVerifiablePresentationRequest(request: string): Promise<{ presentation_definition: PresentationDefinition }> { - let decodedRequest +async function parseVerifiablePresentationRequest(request: string): Promise<{ + presentation_definition: PresentationDefinition + authorization_server_encryption_key: JWK + direct_post_encryption_alg: string +}> { + let decodedRequest: { + presentation_definition: PresentationDefinition + authorization_server_encryption_key: JWK + direct_post_encryption_alg: string + } + + const { privateKey } = JSON.parse(localStorage.getItem("encryptionKeyPair") || "{}"); try { - decodedRequest = decodeJwt(request) + const { payload } = await jwtDecrypt<{ + presentation_definition: PresentationDefinition + authorization_server_encryption_key: JWK + direct_post_encryption_alg: string + }>(request, await importJWK(privateKey, "ECDH-ES")) + + decodedRequest = payload } catch (error) { return Promise.reject(new OauthError({ error: 'unkown_error', @@ -236,6 +260,8 @@ function parseVerifiablePresentationRequest(request: string): Promise<{ presenta } return Promise.resolve({ + authorization_server_encryption_key: decodedRequest['authorization_server_encryption_key'], + direct_post_encryption_alg: decodedRequest['direct_post_encryption_alg'], presentation_definition }) } diff --git a/src/credentials-store.ts b/src/credentials-store.ts index dacae1a..92c9ce5 100644 --- a/src/credentials-store.ts +++ b/src/credentials-store.ts @@ -1,7 +1,7 @@ import { decodeJwt } from 'jose' import { decodeSdJwt } from '@sd-jwt/decode' -import { CredentialSuccess, PresentationDefinition, InputDescriptor } from './oauth-responses' +import { CredentialSuccess, PresentationDefinition, InputDescriptor, OauthError } from './oauth-responses' import { Storage } from './storage' import { EventHandler } from './event-handler' import { CREDENTIALS_KEY } from './constants' @@ -250,6 +250,12 @@ export class Credential { } static async fromResponse(credentialId: string, { format, credential }: CredentialSuccess): Promise { + if (!format || !credential) { + throw new OauthError({ + error: "invalid_credential", + error_description: 'Invalid credential response.' + }) + } if (format == 'vc+sd-jwt') { return decodeSdJwt( diff --git a/src/oauth-responses.ts b/src/oauth-responses.ts index 5436c6b..6ea33aa 100644 --- a/src/oauth-responses.ts +++ b/src/oauth-responses.ts @@ -49,15 +49,18 @@ export interface TokenSuccess { expires_in: number refresh_token?: string state?: string + encrypted_response?: string authorization_details?: object } export interface CredentialSuccess { - format: string - credential: string + format?: string + credential?: string + encrypted_response?: string } export interface Siopv2Success { + response?: string id_token: string client_id: string redirect_uri: string