Skip to content

Commit

Permalink
feat: added managed issuer identifier resolution
Browse files Browse the repository at this point in the history
  • Loading branch information
Brummos committed Sep 26, 2024
1 parent 9c566a4 commit d5ca58e
Show file tree
Hide file tree
Showing 6 changed files with 107 additions and 25 deletions.
35 changes: 21 additions & 14 deletions packages/identifier-resolution/src/agent/IdentifierResolution.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,17 @@
import { IAgentContext, IAgentPlugin, IDIDManager, IKeyManager } from '@veramo/core'
import {
ensureManagedIdentifierResult,
ExternalIdentifierCoseKeyOpts,
ExternalIdentifierCoseKeyResult,
ExternalIdentifierJwkOpts,
ExternalIdentifierJwkResult,
ManagedIdentifierKeyOpts,
ManagedIdentifierKeyResult,
ManagedIdentifierOptsOrResult,
schema,
} from '..'
import { resolveExternalIdentifier } from '../functions'
import { schema } from '..'
import { resolveExternalIdentifier, ensureManagedIdentifierResult } from '../functions'
import {
ExternalIdentifierDidOpts,
ExternalIdentifierDidResult,
ExternalIdentifierOpts,
ExternalIdentifierResult,
ExternalIdentifierX5cOpts,
ExternalIdentifierX5cResult,
ExternalIdentifierCoseKeyOpts,
ExternalIdentifierCoseKeyResult,
ExternalIdentifierJwkOpts,
ExternalIdentifierJwkResult,
IIdentifierResolution,
ManagedIdentifierCoseKeyOpts,
ManagedIdentifierCoseKeyResult,
Expand All @@ -30,6 +24,11 @@ import {
ManagedIdentifierResult,
ManagedIdentifierX5cOpts,
ManagedIdentifierX5cResult,
ManagedIdentifierIssuerResult,
ManagedIdentifierKeyOpts,
ManagedIdentifierKeyResult,
ManagedIdentifierOptsOrResult,
ManagedIdentifierIssuerOpts
} from '../types'

/**
Expand All @@ -47,6 +46,7 @@ export class IdentifierResolution implements IAgentPlugin {
identifierManagedGetByX5c: this.identifierGetManagedByX5c.bind(this),
identifierManagedGetByKey: this.identifierGetManagedByKey.bind(this),
identifierManagedGetByCoseKey: this.identifierGetManagedByCoseKey.bind(this),
identifierManagedGetByIssuer: this.identifierGetManagedByIssuer.bind(this),

identifierExternalResolve: this.identifierResolveExternal.bind(this),
identifierExternalResolveByDid: this.identifierExternalResolveByDid.bind(this),
Expand Down Expand Up @@ -106,9 +106,16 @@ export class IdentifierResolution implements IAgentPlugin {
return (await this.identifierGetManaged({ ...args, method: 'cose_key' }, context)) as ManagedIdentifierCoseKeyResult
}

private async identifierGetManagedByIssuer(
args: ManagedIdentifierIssuerOpts,
context: IAgentContext<IKeyManager & IIdentifierResolution>
): Promise<ManagedIdentifierIssuerResult> {
return (await this.identifierGetManaged({ ...args, method: 'oid4vci-issuer' }, context)) as ManagedIdentifierIssuerResult
}

private async identifierGetManagedByJwk(
args: ManagedIdentifierJwkOpts,
context: IAgentContext<IKeyManager & IIdentifierResolution>
args: ManagedIdentifierJwkOpts,
context: IAgentContext<IKeyManager & IIdentifierResolution>
): Promise<ManagedIdentifierJwkResult> {
return (await this.identifierGetManaged({ ...args, method: 'jwk' }, context)) as ManagedIdentifierJwkResult
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ export async function resolveExternalX5cIdentifier(
const verify = opts.verify ?? true
const x5c = opts.identifier.map((derOrPem) => (derOrPem.includes('CERTIFICATE') ? PEMToDer(derOrPem) : derOrPem))
if (x5c.length === 0) {
return Promise.reject('Empty certification chain is now allowed')
return Promise.reject('Empty certification chain is not allowed')
}
const certificates = x5c.map(pemOrDerToX509Certificate)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
isManagedIdentifierCoseKeyOpts,
isManagedIdentifierDidOpts,
isManagedIdentifierDidResult,
isManagedIdentifierIssuerOpts,
isManagedIdentifierJwkOpts,
isManagedIdentifierJwkResult,
isManagedIdentifierKeyOpts,
Expand All @@ -20,6 +21,8 @@ import {
ManagedIdentifierCoseKeyResult,
ManagedIdentifierDidOpts,
ManagedIdentifierDidResult,
ManagedIdentifierIssuerOpts,
ManagedIdentifierIssuerResult,
ManagedIdentifierJwkOpts,
ManagedIdentifierJwkResult,
ManagedIdentifierKeyOpts,
Expand Down Expand Up @@ -270,6 +273,40 @@ export async function getManagedX5cIdentifier(
} satisfies ManagedIdentifierX5cResult
}

export async function getManagedIssuerIdentifier(
opts: ManagedIdentifierIssuerOpts,
context: IAgentContext<IKeyManager>
): Promise<ManagedIdentifierIssuerResult> {
const { identifier } = opts
const method = 'oid4vci-issuer'
// FIXME: We need to eventually determine the JWK based on the issuer. Using a dummy JWK for now
const jwk = {
"kty" : "RSA",
"kid" : "cc34c0a0-bd5a-4a3c-a50d-a2a7db7643df",
"use" : "sig",
"n" : "pjdss8ZaDfEH6K6U7GeW2nxDqR4IP049fk1fK0lndimbMMVBdPv_hSpm8T8EtBDxrUdi1OHZfMhUixGaut-3nQ4GG9nM249oxhCtxqqNvEXrmQRGqczyLxuh-fKn9Fg--hS9UpazHpfVAFnB5aCfXoNhPuI8oByyFKMKaOVgHNqP5NBEqabiLftZD3W_lsFCPGuzr4Vp0YS7zS2hDYScC2oOMu4rGU1LcMZf39p3153Cq7bS2Xh6Y-vw5pwzFYZdjQxDn8x8BG3fJ6j8TGLXQsbKH1218_HcUJRvMwdpbUQG5nvA2GXVqLqdwp054Lzk9_B_f1lVrmOKuHjTNHq48w",
"e" : "AQAB",
"d" : "ksDmucdMJXkFGZxiomNHnroOZxe8AmDLDGO1vhs-POa5PZM7mtUPonxwjVmthmpbZzla-kg55OFfO7YcXhg-Hm2OWTKwm73_rLh3JavaHjvBqsVKuorX3V3RYkSro6HyYIzFJ1Ek7sLxbjDRcDOj4ievSX0oN9l-JZhaDYlPlci5uJsoqro_YrE0PRRWVhtGynd-_aWgQv1YzkfZuMD-hJtDi1Im2humOWxA4eZrFs9eG-whXcOvaSwO4sSGbS99ecQZHM2TcdXeAs1PvjVgQ_dKnZlGN3lTWoWfQP55Z7Tgt8Nf1q4ZAKd-NlMe-7iqCFfsnFwXjSiaOa2CRGZn-Q",
"p" : "4A5nU4ahEww7B65yuzmGeCUUi8ikWzv1C81pSyUKvKzu8CX41hp9J6oRaLGesKImYiuVQK47FhZ--wwfpRwHvSxtNU9qXb8ewo-BvadyO1eVrIk4tNV543QlSe7pQAoJGkxCia5rfznAE3InKF4JvIlchyqs0RQ8wx7lULqwnn0",
"q" : "ven83GM6SfrmO-TBHbjTk6JhP_3CMsIvmSdo4KrbQNvp4vHO3w1_0zJ3URkmkYGhz2tgPlfd7v1l2I6QkIh4Bumdj6FyFZEBpxjE4MpfdNVcNINvVj87cLyTRmIcaGxmfylY7QErP8GFA-k4UoH_eQmGKGK44TRzYj5hZYGWIC8",
"dp" : "lmmU_AG5SGxBhJqb8wxfNXDPJjf__i92BgJT2Vp4pskBbr5PGoyV0HbfUQVMnw977RONEurkR6O6gxZUeCclGt4kQlGZ-m0_XSWx13v9t9DIbheAtgVJ2mQyVDvK4m7aRYlEceFh0PsX8vYDS5o1txgPwb3oXkPTtrmbAGMUBpE",
"dq" : "mxRTU3QDyR2EnCv0Nl0TCF90oliJGAHR9HJmBe__EjuCBbwHfcT8OG3hWOv8vpzokQPRl5cQt3NckzX3fs6xlJN4Ai2Hh2zduKFVQ2p-AF2p6Yfahscjtq-GY9cB85NxLy2IXCC0PF--Sq9LOrTE9QV988SJy_yUrAjcZ5MmECk",
"qi" : "ldHXIrEmMZVaNwGzDF9WG8sHj2mOZmQpw9yrjLK9hAsmsNr5LTyqWAqJIYZSwPTYWhY4nu2O0EY9G9uYiqewXfCKw_UngrJt8Xwfq1Zruz0YY869zPN4GiE9-9rzdZB33RBw8kIOquY3MK74FMwCihYx_LiU2YTHkaoJ3ncvtvg"
} as JWK
const jwkThumbprint = calculateJwkThumbprint({ jwk })

return {
method,
identifier,
jwk,
jwkThumbprint,
issuer: identifier.replace('/.well-known/openid-credential-issuer', ''),
clientId: opts.clientId,
clientIdScheme: opts.clientIdScheme,
opts,
} satisfies ManagedIdentifierIssuerResult
}

export async function getManagedIdentifier(
opts: ManagedIdentifierOptsOrResult & {
crypto?: Crypto
Expand All @@ -292,11 +329,13 @@ export async function getManagedIdentifier(
resolutionResult = await getManagedKeyIdentifier(opts, context)
} else if (isManagedIdentifierCoseKeyOpts(opts)) {
resolutionResult = await getManagedCoseKeyIdentifier(opts, context)
} else if (isManagedIdentifierIssuerOpts(opts)) {
resolutionResult = await getManagedIssuerIdentifier(opts, context)
} else {
return Promise.reject(Error(`Could not determine identifier method. Please provide explicitly`))
}
const { key } = resolutionResult
if (!key || (isManagedIdentifierDidOpts(opts) && isManagedIdentifierDidResult(resolutionResult) && !resolutionResult.identifier)) {
if ((!key && !isManagedIdentifierIssuerOpts(opts)) || (isManagedIdentifierDidOpts(opts) && isManagedIdentifierDidResult(resolutionResult) && !resolutionResult.identifier)) {
console.log(`Cannot find identifier`, opts.identifier)
return Promise.reject(`Cannot find identifier ${opts.identifier}`)
}
Expand All @@ -311,6 +350,11 @@ export async function managedIdentifierToKeyResult(
if (isManagedIdentifierKeyResult(resolved)) {
return resolved
}

if (resolved.key === undefined) {
return Promise.reject(Error(`No key found in result: ${JSON.stringify(resolved)}`))
}

return {
...resolved,
method: 'key',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import {
ManagedIdentifierCoseKeyResult,
ManagedIdentifierDidOpts,
ManagedIdentifierDidResult,
ManagedIdentifierIssuerOpts,
ManagedIdentifierIssuerResult,
ManagedIdentifierJwkOpts,
ManagedIdentifierJwkResult,
ManagedIdentifierKeyOpts,
Expand All @@ -36,6 +38,7 @@ export const identifierResolutionContextMethods: Array<string> = [
'identifierManagedGetByJwk',
'identifierManagedGetByX5c',
'identifierManagedGetByKey',
'identifierManagedGetByIssuer',
'identifierGetManagedByCoseKey',
'identifierExternalResolve',
'identifierExternalResolveByDid',
Expand Down Expand Up @@ -77,6 +80,8 @@ export interface IIdentifierResolution extends IPluginMethodMap {
context: IAgentContext<IKeyManager & IIdentifierResolution>
): Promise<ManagedIdentifierCoseKeyResult>

identifierManagedGetByIssuer(args: ManagedIdentifierIssuerOpts, context: IAgentContext<any>): Promise<ManagedIdentifierIssuerResult>

// TODO: We can create a custom managed identifier method allowing developers to register a callback function to get their implementation hooked up. Needs more investigation as it would also impact the KMS

/**
Expand Down
6 changes: 5 additions & 1 deletion packages/identifier-resolution/src/types/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,11 @@ export function isJwksUrlIdentifier(identifier: ManagedIdentifierType | External
}

export function isKidIdentifier(identifier: ManagedIdentifierType | ExternalIdentifierType): identifier is string {
return typeof identifier === 'string' && !identifier.startsWith('did:')
return typeof identifier === 'string' && !identifier.startsWith('did:') && !identifier.startsWith('http')
}

export function isIssuerIdentifier(identifier: ManagedIdentifierType | ExternalIdentifierType): identifier is string {
return typeof identifier === 'string' && identifier.startsWith('http') && identifier.endsWith('/.well-known/openid-credential-issuer')
}

export function isKeyIdentifier(identifier: ManagedIdentifierType): identifier is IKey {
Expand Down
38 changes: 30 additions & 8 deletions packages/identifier-resolution/src/types/managedIdentifierTypes.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
import { ClientIdScheme } from '@sphereon/ssi-sdk-ext.x509-utils'
import { ICoseKeyJson, JWK } from '@sphereon/ssi-types'
import { DIDDocumentSection, IIdentifier, IKey, TKeyType } from '@veramo/core'
import { isCoseKeyIdentifier, isDidIdentifier, isJwkIdentifier, isKeyIdentifier, isKidIdentifier, isX5cIdentifier, JwkInfo } from './common'
import {
isCoseKeyIdentifier,
isDidIdentifier,
isIssuerIdentifier,
isJwkIdentifier,
isKeyIdentifier,
isKidIdentifier,
isX5cIdentifier,
JwkInfo
} from './common'

/**
* Use whenever we need to pass in an identifier. We can pass in kids, DIDs, IIdentifier objects and x5chains
Expand All @@ -17,6 +26,7 @@ export type ManagedIdentifierOpts = (
| ManagedIdentifierKidOpts
| ManagedIdentifierKeyOpts
| ManagedIdentifierCoseKeyOpts
| ManagedIdentifierIssuerOpts
) &
ManagedIdentifierOptsBase

Expand Down Expand Up @@ -75,6 +85,16 @@ export function isManagedIdentifierCoseKeyOpts(opts: ManagedIdentifierOptsBase):
return ('method' in opts && opts.method === 'cose_key') || isCoseKeyIdentifier(identifier)
}

export type ManagedIdentifierIssuerOpts = Omit<ManagedIdentifierOptsBase, 'method' | 'identifier'> & {
method?: 'oid4vci-issuer'
identifier: string
}

export function isManagedIdentifierIssuerOpts(opts: ManagedIdentifierOptsBase): opts is ManagedIdentifierCoseKeyOpts {
const { identifier } = opts
return ('method' in opts && opts.method === 'oid4vci-issuer') || isIssuerIdentifier(identifier)
}

export type ManagedIdentifierJwkOpts = Omit<ManagedIdentifierOptsBase, 'method' | 'identifier'> & {
method?: 'jwk'
identifier: JWK
Expand All @@ -96,13 +116,13 @@ export function isManagedIdentifierX5cOpts(opts: ManagedIdentifierOptsBase): opt
}

export interface ManagedJwkInfo extends JwkInfo {
kmsKeyRef: string
kmsKeyRef?: string
}

export interface IManagedIdentifierResultBase extends ManagedJwkInfo {
method: ManagedIdentifierMethod
opts: ManagedIdentifierOpts
key: IKey
key?: IKey
kid?: string
issuer?: string
clientId?: string
Expand Down Expand Up @@ -130,10 +150,6 @@ export function isManagedIdentifierKeyResult(object: IManagedIdentifierResultBas
return object!! && typeof object === 'object' && 'method' in object && object.method === 'key'
}

export function isManagedIdentifierCoseKeyResult(object: IManagedIdentifierResultBase): object is ManagedIdentifierCoseKeyResult {
return object!! && typeof object === 'object' && 'method' in object && object.method === 'cose_key'
}

export interface ManagedIdentifierDidResult extends IManagedIdentifierResultBase {
method: 'did'
identifier: IIdentifier
Expand Down Expand Up @@ -167,14 +183,19 @@ export interface ManagedIdentifierCoseKeyResult extends IManagedIdentifierResult
identifier: ICoseKeyJson
}

export interface ManagedIdentifierIssuerResult extends IManagedIdentifierResultBase {
method: 'oid4vci-issuer'
identifier: string
}

export interface ManagedIdentifierX5cResult extends IManagedIdentifierResultBase {
method: 'x5c'
identifier: string[]
x5c: string[]
certificate: any // Certificate(JSON_, but trips schema generator. Probably want to create our own DTO
}

export type ManagedIdentifierMethod = 'did' | 'jwk' | 'x5c' | 'kid' | 'key' | 'cose_key'
export type ManagedIdentifierMethod = 'did' | 'jwk' | 'x5c' | 'kid' | 'key' | 'cose_key' | 'oid4vci-issuer'

export type ManagedIdentifierResult = IManagedIdentifierResultBase &
(
Expand All @@ -184,6 +205,7 @@ export type ManagedIdentifierResult = IManagedIdentifierResultBase &
| ManagedIdentifierKidResult
| ManagedIdentifierKeyResult
| ManagedIdentifierCoseKeyResult
| ManagedIdentifierIssuerResult
)

export type ManagedIdentifierOptsOrResult = (ManagedIdentifierResult | ManagedIdentifierOpts) & {
Expand Down

0 comments on commit d5ca58e

Please sign in to comment.