Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Created an issuer verification of an EBSI issued credential #236

Open
wants to merge 6 commits into
base: develop
Choose a base branch
from
66 changes: 66 additions & 0 deletions packages/oid4vci-holder/__tests__/EBSIFunctions.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import {verifyEBSICredentialIssuer} from "../src/agent/OID4VCIHolder";
import {CredentialMapper} from "@sphereon/ssi-types";

const nock = require("nock")

const BASE_URL = 'https://api-conformance.ebsi.eu'
const GET_VALID_ISSUER_URI = '/trusted-issuers-registry/v4/issuers/did:ebsi:ziDnioxYYLW1a3qUbqTFz4W'
const GET_INVALID_ISSUER_URI = '/trusted-issuers-registry/v4/issuers/invalid_issuer'

describe('EBSI Functions', () => {
describe('verifyEBSICredentialIssuer', () => {

const validIssuerResult = {
did: "did:ebsi:ziDnioxYYLW1a3qUbqTFz4W",
attributes: [{
hash: "test",
body: "eyJ0eXAiOiJKV1QiLCJraWQiOiIxODNkY2E4NDRiNzM5OGM4MTQ0ZTJiMzk5OWM3MzA2Y2I3OTYzMDJhZWQxNDdkNjY4ZmI2ZmI5YmE0OTZkNTBkIiwiYWxnIjoiRVMyNTZLIn0.eyJpc3N1ZXIiOiJkaWQ6ZWJzaTp6aURuaW94WVlMVzFhM3FVYnFURno0VyIsImlhdCI6MTcxNDQxMzA4OCwianRpIjoidXJuOnV1aWQ6NWZiN2Q5OGItMTA4Yy00YmMwLTlmZmMtYzY5Zjg0ZWQ3ODhmIiwibmJmIjoxNzE0NDEzMDg4LCJleHAiOjE3NDU5NDkwODgsInN1YiI6ImRpZDplYnNpOnpleWJBaUp4elVVcldRMVlNNTFTWTM1IiwidmMiOnsiQGNvbnRleHQiOlsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiXSwiaWQiOiJ1cm46dXVpZDo1ZmI3ZDk4Yi0xMDhjLTRiYzAtOWZmYy1jNjlmODRlZDc4OGYiLCJ0eXBlIjpbIlZlcmlmaWFibGVDcmVkZW50aWFsIiwiVmVyaWZpYWJsZUF0dGVzdGF0aW9uIiwiVmVyaWZpYWJsZUF1dGhvcmlzYXRpb25Ub09uYm9hcmQiXSwiaXNzdWFuY2VEYXRlIjoiMjAyNC0wNC0yOVQxNzo1MToyOFoiLCJpc3N1ZWQiOiIyMDI0LTA0LTI5VDE3OjUxOjI4WiIsInZhbGlkRnJvbSI6IjIwMjQtMDQtMjlUMTc6NTE6MjhaIiwiZXhwaXJhdGlvbkRhdGUiOiIyMDI1LTA0LTI5VDE3OjUxOjI4WiIsImlzc3VlciI6ImRpZDplYnNpOnppRG5pb3hZWUxXMWEzcVVicVRGejRXIiwiY3JlZGVudGlhbFN1YmplY3QiOnsiaWQiOiJkaWQ6ZWJzaTp6ZXliQWlKeHpVVXJXUTFZTTUxU1kzNSIsImFjY3JlZGl0ZWRGb3IiOltdfSwidGVybXNPZlVzZSI6eyJpZCI6ImRpZDplYnNpOnpleWJBaUp4elVVcldRMVlNNTFTWTM1IiwidHlwZSI6Iklzc3VhbmNlQ2VydGlmaWNhdGUifSwiY3JlZGVudGlhbFNjaGVtYSI6eyJpZCI6Imh0dHBzOi8vYXBpLXBpbG90LmVic2kuZXUvdHJ1c3RlZC1zY2hlbWFzLXJlZ2lzdHJ5L3YyL3NjaGVtYXMvejNNZ1VGVWtiNzIydXE0eDNkdjV5QUptbk5tekRGZUs1VUM4eDgzUW9lTEpNIiwidHlwZSI6IkZ1bGxKc29uU2NoZW1hVmFsaWRhdG9yMjAyMSJ9fX0.QWNWTWlrbUpLcFJaLVBGczQ0U3Mxb200Mk4yb3JzWndsTXp3REpHTTMxSUM2WG5ZVXJ0ZlY4RHFTbVQtaXBIMEdLSDZhclFEcGtrbXZTTy1NenYxWEE",
issuerType: "RootTAO",
tao: "did:ebsi:zeybAiJxzUUrWQ1YM51SY35",
rootTao: "did:ebsi:ziDnioxYYLW1a3qUbqTFz4W"
}]
}

const notIssuerResult = {
did: "did:ebsi:ziDnioxYYLW1a3qUbqTFz4W",
attributes: [{
hash: "test",
body: "eyJ0eXAiOiJKV1QiLCJraWQiOiIxODNkY2E4NDRiNzM5OGM4MTQ0ZTJiMzk5OWM3MzA2Y2I3OTYzMDJhZWQxNDdkNjY4ZmI2ZmI5YmE0OTZkNTBkIiwiYWxnIjoiRVMyNTZLIn0.eyJpc3N1ZXIiOiJkaWQ6ZWJzaTp6aURuaW94WVlMVzFhM3FVYnFURno0VyIsImlhdCI6MTcxNDQxMzA4OCwianRpIjoidXJuOnV1aWQ6NWZiN2Q5OGItMTA4Yy00YmMwLTlmZmMtYzY5Zjg0ZWQ3ODhmIiwibmJmIjoxNzE0NDEzMDg4LCJleHAiOjE3NDU5NDkwODgsInN1YiI6ImRpZDplYnNpOnpleWJBaUp4elVVcldRMVlNNTFTWTM1IiwidmMiOnsiQGNvbnRleHQiOlsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiXSwiaWQiOiJ1cm46dXVpZDo1ZmI3ZDk4Yi0xMDhjLTRiYzAtOWZmYy1jNjlmODRlZDc4OGYiLCJ0eXBlIjpbIlZlcmlmaWFibGVDcmVkZW50aWFsIiwiVmVyaWZpYWJsZUF0dGVzdGF0aW9uIiwiVmVyaWZpYWJsZUF1dGhvcmlzYXRpb25Ub09uYm9hcmQiXSwiaXNzdWFuY2VEYXRlIjoiMjAyNC0wNC0yOVQxNzo1MToyOFoiLCJpc3N1ZWQiOiIyMDI0LTA0LTI5VDE3OjUxOjI4WiIsInZhbGlkRnJvbSI6IjIwMjQtMDQtMjlUMTc6NTE6MjhaIiwiZXhwaXJhdGlvbkRhdGUiOiIyMDI1LTA0LTI5VDE3OjUxOjI4WiIsImlzc3VlciI6ImRpZDplYnNpOnppRG5pb3hZWUxXMWEzcVVicVRGejRXIiwiY3JlZGVudGlhbFN1YmplY3QiOnsiaWQiOiJkaWQ6ZWJzaTp6ZXliQWlKeHpVVXJXUTFZTTUxU1kzNSIsImFjY3JlZGl0ZWRGb3IiOltdfSwidGVybXNPZlVzZSI6eyJpZCI6ImRpZDplYnNpOnpleWJBaUp4elVVcldRMVlNNTFTWTM1IiwidHlwZSI6Iklzc3VhbmNlQ2VydGlmaWNhdGUifSwiY3JlZGVudGlhbFNjaGVtYSI6eyJpZCI6Imh0dHBzOi8vYXBpLXBpbG90LmVic2kuZXUvdHJ1c3RlZC1zY2hlbWFzLXJlZ2lzdHJ5L3YyL3NjaGVtYXMvejNNZ1VGVWtiNzIydXE0eDNkdjV5QUptbk5tekRGZUs1VUM4eDgzUW9lTEpNIiwidHlwZSI6IkZ1bGxKc29uU2NoZW1hVmFsaWRhdG9yMjAyMSJ9fX0.QWNWTWlrbUpLcFJaLVBGczQ0U3Mxb200Mk4yb3JzWndsTXp3REpHTTMxSUM2WG5ZVXJ0ZlY4RHFTbVQtaXBIMEdLSDZhclFEcGtrbXZTTy1NenYxWEE",
issuerType: "Revoked or Undefined",
tao: "did:ebsi:zeybAiJxzUUrWQ1YM51SY35",
rootTao: "did:ebsi:ziDnioxYYLW1a3qUbqTFz4W"
}]
}

it(`should return the issuer's did and attributes if the issuer is valid`, async () => {
nock(BASE_URL)
.get(GET_VALID_ISSUER_URI)
.reply(200, JSON.stringify(validIssuerResult))
await expect(verifyEBSICredentialIssuer({
wrappedVc: CredentialMapper.toWrappedVerifiableCredential("eyJ0eXAiOiJKV1QiLCJraWQiOiIxODNkY2E4NDRiNzM5OGM4MTQ0ZTJiMzk5OWM3MzA2Y2I3OTYzMDJhZWQxNDdkNjY4ZmI2ZmI5YmE0OTZkNTBkIiwiYWxnIjoiRVMyNTZLIn0.eyJpc3N1ZXIiOiJkaWQ6ZWJzaTp6aURuaW94WVlMVzFhM3FVYnFURno0VyIsImlhdCI6MTcxNDQxMzA4OCwianRpIjoidXJuOnV1aWQ6NWZiN2Q5OGItMTA4Yy00YmMwLTlmZmMtYzY5Zjg0ZWQ3ODhmIiwibmJmIjoxNzE0NDEzMDg4LCJleHAiOjE3NDU5NDkwODgsInN1YiI6ImRpZDplYnNpOnpleWJBaUp4elVVcldRMVlNNTFTWTM1IiwidmMiOnsiQGNvbnRleHQiOlsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiXSwiaWQiOiJ1cm46dXVpZDo1ZmI3ZDk4Yi0xMDhjLTRiYzAtOWZmYy1jNjlmODRlZDc4OGYiLCJ0eXBlIjpbIlZlcmlmaWFibGVDcmVkZW50aWFsIiwiVmVyaWZpYWJsZUF0dGVzdGF0aW9uIiwiVmVyaWZpYWJsZUF1dGhvcmlzYXRpb25Ub09uYm9hcmQiXSwiaXNzdWFuY2VEYXRlIjoiMjAyNC0wNC0yOVQxNzo1MToyOFoiLCJpc3N1ZWQiOiIyMDI0LTA0LTI5VDE3OjUxOjI4WiIsInZhbGlkRnJvbSI6IjIwMjQtMDQtMjlUMTc6NTE6MjhaIiwiZXhwaXJhdGlvbkRhdGUiOiIyMDI1LTA0LTI5VDE3OjUxOjI4WiIsImlzc3VlciI6ImRpZDplYnNpOnppRG5pb3hZWUxXMWEzcVVicVRGejRXIiwiY3JlZGVudGlhbFN1YmplY3QiOnsiaWQiOiJkaWQ6ZWJzaTp6ZXliQWlKeHpVVXJXUTFZTTUxU1kzNSIsImFjY3JlZGl0ZWRGb3IiOltdfSwidGVybXNPZlVzZSI6eyJpZCI6ImRpZDplYnNpOnpleWJBaUp4elVVcldRMVlNNTFTWTM1IiwidHlwZSI6Iklzc3VhbmNlQ2VydGlmaWNhdGUifSwiY3JlZGVudGlhbFNjaGVtYSI6eyJpZCI6Imh0dHBzOi8vYXBpLXBpbG90LmVic2kuZXUvdHJ1c3RlZC1zY2hlbWFzLXJlZ2lzdHJ5L3YyL3NjaGVtYXMvejNNZ1VGVWtiNzIydXE0eDNkdjV5QUptbk5tekRGZUs1VUM4eDgzUW9lTEpNIiwidHlwZSI6IkZ1bGxKc29uU2NoZW1hVmFsaWRhdG9yMjAyMSJ9fX0.QWNWTWlrbUpLcFJaLVBGczQ0U3Mxb200Mk4yb3JzWndsTXp3REpHTTMxSUM2WG5ZVXJ0ZlY4RHFTbVQtaXBIMEdLSDZhclFEcGtrbXZTTy1NenYxWEE")
})).resolves.toEqual(validIssuerResult)
})

it(`should return undefined if the issuer type is not RootTAO, TAO or TI`, async () => {
nock(BASE_URL)
.get(GET_VALID_ISSUER_URI)
.reply(200, JSON.stringify(notIssuerResult))
await expect(verifyEBSICredentialIssuer({
wrappedVc: CredentialMapper.toWrappedVerifiableCredential("eyJ0eXAiOiJKV1QiLCJraWQiOiIxODNkY2E4NDRiNzM5OGM4MTQ0ZTJiMzk5OWM3MzA2Y2I3OTYzMDJhZWQxNDdkNjY4ZmI2ZmI5YmE0OTZkNTBkIiwiYWxnIjoiRVMyNTZLIn0.eyJpc3N1ZXIiOiJkaWQ6ZWJzaTp6aURuaW94WVlMVzFhM3FVYnFURno0VyIsImlhdCI6MTcxNDQxMzA4OCwianRpIjoidXJuOnV1aWQ6NWZiN2Q5OGItMTA4Yy00YmMwLTlmZmMtYzY5Zjg0ZWQ3ODhmIiwibmJmIjoxNzE0NDEzMDg4LCJleHAiOjE3NDU5NDkwODgsInN1YiI6ImRpZDplYnNpOnpleWJBaUp4elVVcldRMVlNNTFTWTM1IiwidmMiOnsiQGNvbnRleHQiOlsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiXSwiaWQiOiJ1cm46dXVpZDo1ZmI3ZDk4Yi0xMDhjLTRiYzAtOWZmYy1jNjlmODRlZDc4OGYiLCJ0eXBlIjpbIlZlcmlmaWFibGVDcmVkZW50aWFsIiwiVmVyaWZpYWJsZUF0dGVzdGF0aW9uIiwiVmVyaWZpYWJsZUF1dGhvcmlzYXRpb25Ub09uYm9hcmQiXSwiaXNzdWFuY2VEYXRlIjoiMjAyNC0wNC0yOVQxNzo1MToyOFoiLCJpc3N1ZWQiOiIyMDI0LTA0LTI5VDE3OjUxOjI4WiIsInZhbGlkRnJvbSI6IjIwMjQtMDQtMjlUMTc6NTE6MjhaIiwiZXhwaXJhdGlvbkRhdGUiOiIyMDI1LTA0LTI5VDE3OjUxOjI4WiIsImlzc3VlciI6ImRpZDplYnNpOnppRG5pb3hZWUxXMWEzcVVicVRGejRXIiwiY3JlZGVudGlhbFN1YmplY3QiOnsiaWQiOiJkaWQ6ZWJzaTp6ZXliQWlKeHpVVXJXUTFZTTUxU1kzNSIsImFjY3JlZGl0ZWRGb3IiOltdfSwidGVybXNPZlVzZSI6eyJpZCI6ImRpZDplYnNpOnpleWJBaUp4elVVcldRMVlNNTFTWTM1IiwidHlwZSI6Iklzc3VhbmNlQ2VydGlmaWNhdGUifSwiY3JlZGVudGlhbFNjaGVtYSI6eyJpZCI6Imh0dHBzOi8vYXBpLXBpbG90LmVic2kuZXUvdHJ1c3RlZC1zY2hlbWFzLXJlZ2lzdHJ5L3YyL3NjaGVtYXMvejNNZ1VGVWtiNzIydXE0eDNkdjV5QUptbk5tekRGZUs1VUM4eDgzUW9lTEpNIiwidHlwZSI6IkZ1bGxKc29uU2NoZW1hVmFsaWRhdG9yMjAyMSJ9fX0.QWNWTWlrbUpLcFJaLVBGczQ0U3Mxb200Mk4yb3JzWndsTXp3REpHTTMxSUM2WG5ZVXJ0ZlY4RHFTbVQtaXBIMEdLSDZhclFEcGtrbXZTTy1NenYxWEE")
})).rejects.toBeUndefined()
})

it(`should return undefined if the issuer's did is not provided`, async () => {
await expect(verifyEBSICredentialIssuer({
wrappedVc: CredentialMapper.toWrappedVerifiableCredential("eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjE4M2RjYTg0NGI3Mzk4YzgxNDRlMmIzOTk5YzczMDZjYjc5NjMwMmFlZDE0N2Q2NjhmYjZmYjliYTQ5NmQ1MGQifQ.eyJpc3N1ZXIiOiJkaWQ6ZWJzaTp6aURuaW94WVlMVzFhM3FVYnFURno0VyIsImlhdCI6MTcxNDQxMzA4OCwianRpIjoidXJuOnV1aWQ6NWZiN2Q5OGItMTA4Yy00YmMwLTlmZmMtYzY5Zjg0ZWQ3ODhmIiwibmJmIjoxNzE0NDEzMDg4LCJleHAiOjE3NDU5NDkwODgsInN1YiI6ImRpZDplYnNpOnpleWJBaUp4elVVcldRMVlNNTFTWTM1IiwidmMiOnsiQGNvbnRleHQiOlsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiXSwiaWQiOiJ1cm46dXVpZDo1ZmI3ZDk4Yi0xMDhjLTRiYzAtOWZmYy1jNjlmODRlZDc4OGYiLCJ0eXBlIjpbIlZlcmlmaWFibGVDcmVkZW50aWFsIiwiVmVyaWZpYWJsZUF0dGVzdGF0aW9uIiwiVmVyaWZpYWJsZUF1dGhvcmlzYXRpb25Ub09uYm9hcmQiXSwiaXNzdWFuY2VEYXRlIjoiMjAyNC0wNC0yOVQxNzo1MToyOFoiLCJpc3N1ZWQiOiIyMDI0LTA0LTI5VDE3OjUxOjI4WiIsInZhbGlkRnJvbSI6IjIwMjQtMDQtMjlUMTc6NTE6MjhaIiwiZXhwaXJhdGlvbkRhdGUiOiIyMDI1LTA0LTI5VDE3OjUxOjI4WiIsImlzc3VlciI6IiIsImNyZWRlbnRpYWxTdWJqZWN0Ijp7ImlkIjoiZGlkOmVic2k6emV5YkFpSnh6VVVyV1ExWU01MVNZMzUiLCJhY2NyZWRpdGVkRm9yIjpbXX0sInRlcm1zT2ZVc2UiOnsiaWQiOiJkaWQ6ZWJzaTp6ZXliQWlKeHpVVXJXUTFZTTUxU1kzNSIsInR5cGUiOiJJc3N1YW5jZUNlcnRpZmljYXRlIn0sImNyZWRlbnRpYWxTY2hlbWEiOnsiaWQiOiJodHRwczovL2FwaS1waWxvdC5lYnNpLmV1L3RydXN0ZWQtc2NoZW1hcy1yZWdpc3RyeS92Mi9zY2hlbWFzL3ozTWdVRlVrYjcyMnVxNHgzZHY1eUFKbW5ObXpERmVLNVVDOHg4M1FvZUxKTSIsInR5cGUiOiJGdWxsSnNvblNjaGVtYVZhbGlkYXRvcjIwMjEifX19.OFcZ-7iqoP_LmxTWqM9rR4aOK8VPyKyRJ2R8MD6m1jT2LzyqMVKzX__EF6e0ghs73l-nVtJBIu28QFsMFxAODg")
})).rejects.toBeUndefined()
})

it(`should return undefined if the issuer's did is invalid`, async () => {
nock(BASE_URL).get(GET_INVALID_ISSUER_URI).reply(400)
await expect(verifyEBSICredentialIssuer({
wrappedVc: CredentialMapper.toWrappedVerifiableCredential("eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjE4M2RjYTg0NGI3Mzk4YzgxNDRlMmIzOTk5YzczMDZjYjc5NjMwMmFlZDE0N2Q2NjhmYjZmYjliYTQ5NmQ1MGQifQ.eyJpc3N1ZXIiOiJkaWQ6ZWJzaTp6aURuaW94WVlMVzFhM3FVYnFURno0VyIsImlhdCI6MTcxNDQxMzA4OCwianRpIjoidXJuOnV1aWQ6NWZiN2Q5OGItMTA4Yy00YmMwLTlmZmMtYzY5Zjg0ZWQ3ODhmIiwibmJmIjoxNzE0NDEzMDg4LCJleHAiOjE3NDU5NDkwODgsInN1YiI6ImRpZDplYnNpOnpleWJBaUp4elVVcldRMVlNNTFTWTM1IiwidmMiOnsiQGNvbnRleHQiOlsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiXSwiaWQiOiJ1cm46dXVpZDo1ZmI3ZDk4Yi0xMDhjLTRiYzAtOWZmYy1jNjlmODRlZDc4OGYiLCJ0eXBlIjpbIlZlcmlmaWFibGVDcmVkZW50aWFsIiwiVmVyaWZpYWJsZUF0dGVzdGF0aW9uIiwiVmVyaWZpYWJsZUF1dGhvcmlzYXRpb25Ub09uYm9hcmQiXSwiaXNzdWFuY2VEYXRlIjoiMjAyNC0wNC0yOVQxNzo1MToyOFoiLCJpc3N1ZWQiOiIyMDI0LTA0LTI5VDE3OjUxOjI4WiIsInZhbGlkRnJvbSI6IjIwMjQtMDQtMjlUMTc6NTE6MjhaIiwiZXhwaXJhdGlvbkRhdGUiOiIyMDI1LTA0LTI5VDE3OjUxOjI4WiIsImlzc3VlciI6ImludmFsaWRfaXNzdWVyIiwiY3JlZGVudGlhbFN1YmplY3QiOnsiaWQiOiJkaWQ6ZWJzaTp6ZXliQWlKeHpVVXJXUTFZTTUxU1kzNSIsImFjY3JlZGl0ZWRGb3IiOltdfSwidGVybXNPZlVzZSI6eyJpZCI6ImRpZDplYnNpOnpleWJBaUp4elVVcldRMVlNNTFTWTM1IiwidHlwZSI6Iklzc3VhbmNlQ2VydGlmaWNhdGUifSwiY3JlZGVudGlhbFNjaGVtYSI6eyJpZCI6Imh0dHBzOi8vYXBpLXBpbG90LmVic2kuZXUvdHJ1c3RlZC1zY2hlbWFzLXJlZ2lzdHJ5L3YyL3NjaGVtYXMvejNNZ1VGVWtiNzIydXE0eDNkdjV5QUptbk5tekRGZUs1VUM4eDgzUW9lTEpNIiwidHlwZSI6IkZ1bGxKc29uU2NoZW1hVmFsaWRhdG9yMjAyMSJ9fX0.r0kAeMRrwn8lQNJakAmfWRtLmRQdRbULNjbbvTPsirpVGmN5O0V9O7eQ7_S4sHTF8p_AShSanv4MLvtRfCvg1A")
})).rejects.toBeUndefined()
})
})
})
33 changes: 31 additions & 2 deletions packages/oid4vci-holder/src/agent/OID4VCIHolder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ import { OID4VCIMachine } from '../machine/oid4vciMachine'
import {
AddContactIdentityArgs,
AddIssuerBrandingArgs,
AssertValidCredentialsArgs,
AssertValidCredentialsArgs, Attribute,
createCredentialsToSelectFromArgs,
CredentialToAccept,
CredentialToSelectFromResult,
Expand All @@ -84,7 +84,7 @@ import {
StartResult,
StoreCredentialBrandingArgs,
StoreCredentialsArgs,
VerificationResult,
VerificationResult, VerifyEBSICredentialIssuerArgs, VerifyEBSICredentialIssuerResult,
} from '../types/IOID4VCIHolder'
import {
getBasicIssuerLocaleBranding,
Expand All @@ -97,6 +97,7 @@ import {
verifyCredentialToAccept,
} from './OID4VCIHolderService'

import 'cross-fetch/polyfill'
/**
* {@inheritDoc IOID4VCIHolder}
*/
Expand Down Expand Up @@ -189,6 +190,30 @@ export function signCallback(
}
}

export async function verifyEBSICredentialIssuer(args: VerifyEBSICredentialIssuerArgs): Promise<VerifyEBSICredentialIssuerResult | undefined> {
const { wrappedVc } = args

const issuer = wrappedVc.decoded?.iss ?? (typeof wrappedVc.decoded?.vc?.issuer === 'string' ? wrappedVc.decoded?.vc?.issuer : wrappedVc.decoded?.vc?.issuer?.existingInstanceId)

if (!issuer) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are you using an approach that we have never used?

Return a promise reject with an error so you do not have to list undefined as a result type. Or resolve undefined if that is an okay outcome. Not a reject with undefined. That makes no sense.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refactored error handling

return Promise.reject(undefined)
}

const url = `https://api-conformance.ebsi.eu/trusted-issuers-registry/v4/issuers/${issuer}`;
const response = await fetch(url)
if (response.status !== 200) {
return Promise.reject(undefined)
}

const payload = await response.json()

if (!payload.attributes.some((a: Attribute) => ['RootTAO', 'TAO', 'TI'].includes(a.issuerType))) {
return Promise.reject(undefined)
Copy link
Contributor

@nklomp nklomp Sep 11, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This makes no sense. Provide a proper error.

Also I believe the type should be an optional argument as well. If I want to only verify a party to be a trusted issuer I need to be able to provide TI as a single array value. If I want more types I should be able to provide these as well. The default should be TI and a TI only

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refactored error handling and moved issuer type to a parameter

}

return payload
}

export class OID4VCIHolder implements IAgentPlugin {
readonly eventTypes: Array<OID4VCIHolderEvent> = [
OID4VCIHolderEvent.CONTACT_IDENTITY_CREATED,
Expand Down Expand Up @@ -235,12 +260,14 @@ export class OID4VCIHolder implements IAgentPlugin {
private readonly onContactIdentityCreated?: (args: OnContactIdentityCreatedArgs) => Promise<void>
private readonly onCredentialStored?: (args: OnCredentialStoredArgs) => Promise<void>
private readonly onIdentifierCreated?: (args: OnIdentifierCreatedArgs) => Promise<void>
private readonly onVerifyEBSICredentialIssuer?: (args: VerifyEBSICredentialIssuerArgs) => Promise<VerifyEBSICredentialIssuerResult>

constructor(options?: OID4VCIHolderOptions) {
const {
onContactIdentityCreated,
onCredentialStored,
onIdentifierCreated,
onVerifyEBSICredentialIssuer,
vcFormatPreferences,
jsonldCryptographicSuitePreferences,
didMethodPreferences,
Expand All @@ -266,6 +293,7 @@ export class OID4VCIHolder implements IAgentPlugin {
this.onContactIdentityCreated = onContactIdentityCreated
this.onCredentialStored = onCredentialStored
this.onIdentifierCreated = onIdentifierCreated
this.onVerifyEBSICredentialIssuer = onVerifyEBSICredentialIssuer
}

public async onEvent(event: any, context: RequiredContext): Promise<void> {
Expand Down Expand Up @@ -745,6 +773,7 @@ export class OID4VCIHolder implements IAgentPlugin {
credentialsToAccept.map((credentialToAccept) =>
verifyCredentialToAccept({
mappedCredential: credentialToAccept,
onVerifyEBSICredentialIssuer: this.onVerifyEBSICredentialIssuer,
context,
}),
),
Expand Down
11 changes: 10 additions & 1 deletion packages/oid4vci-holder/src/agent/OID4VCIHolderService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ export const selectCredentialLocaleBranding = async (
}

export const verifyCredentialToAccept = async (args: VerifyCredentialToAcceptArgs): Promise<VerificationResult> => {
const { mappedCredential, hasher, context } = args
const { mappedCredential, hasher, onVerifyEBSICredentialIssuer, context } = args

const credential = mappedCredential.credentialToAccept.credentialResponse.credential as OriginalVerifiableCredential
if (!credential) {
Expand All @@ -139,6 +139,15 @@ export const verifyCredentialToAccept = async (args: VerifyCredentialToAcceptArg
}
}

if (onVerifyEBSICredentialIssuer) {
Copy link
Contributor

@nklomp nklomp Sep 11, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And where is the code that only applies this if we encounter actual EBSI issuers? You cannot just assume EBSI and only EBSI or not of course. A wallet or RP can be member of many ecosystems

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved it to the if clause to make sure the code will only execute for an EBSI issuer

const response = await onVerifyEBSICredentialIssuer({
wrappedVc: wrappedVC
})
if (!response) {
return Promise.reject(Error('The issuer of the EBSI credential cannot be trusted.'))
}
}

const verificationResult: VerificationResult = await verifyCredential(
{
credential,
Expand Down
22 changes: 22 additions & 0 deletions packages/oid4vci-holder/src/types/IOID4VCIHolder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ export type OID4VCIHolderOptions = {
onContactIdentityCreated?: (args: OnContactIdentityCreatedArgs) => Promise<void>
onCredentialStored?: (args: OnCredentialStoredArgs) => Promise<void>
onIdentifierCreated?: (args: OnIdentifierCreatedArgs) => Promise<void>
onVerifyEBSICredentialIssuer?: (args: VerifyEBSICredentialIssuerArgs) => Promise<VerifyEBSICredentialIssuerResult>
vcFormatPreferences?: Array<string>
jsonldCryptographicSuitePreferences?: Array<string>
defaultAuthorizationRequestOptions?: AuthorizationRequestOpts
Expand Down Expand Up @@ -164,6 +165,7 @@ export enum SupportedLanguage {

export type VerifyCredentialToAcceptArgs = {
mappedCredential: MappedCredentialToAccept
onVerifyEBSICredentialIssuer?: (args: VerifyEBSICredentialIssuerArgs) => Promise<VerifyEBSICredentialIssuerResult>
hasher?: Hasher
context: RequiredContext
}
Expand Down Expand Up @@ -602,4 +604,24 @@ export type RequiredContext = IAgentContext<
IKeyManager &
ISDJwtPlugin
>

export type IssuerType = 'RootTAO' | 'TAO' | 'TI' | 'Revoked or Undefined'

export type VerifyEBSICredentialIssuerArgs = {
wrappedVc: WrappedVerifiableCredential
}

export type Attribute = {
hash: string
body: string
issuerType: IssuerType
tao: string
rootTao: string
}

export type VerifyEBSICredentialIssuerResult = {
did: string
attributes: Attribute[]
}

export type DidAgents = TAgent<IResolver & IDIDManager>