@sphereon/ssi-sdk-ext.identifier-resolution 0.24.1-next.100
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +433 -0
- package/dist/agent/IdentifierResolution.d.ts +33 -0
- package/dist/agent/IdentifierResolution.d.ts.map +1 -0
- package/dist/agent/IdentifierResolution.js +93 -0
- package/dist/agent/IdentifierResolution.js.map +1 -0
- package/dist/functions/LegacySupport.d.ts +12 -0
- package/dist/functions/LegacySupport.d.ts.map +1 -0
- package/dist/functions/LegacySupport.js +39 -0
- package/dist/functions/LegacySupport.js.map +1 -0
- package/dist/functions/externalIdentifierFunctions.d.ts +10 -0
- package/dist/functions/externalIdentifierFunctions.d.ts.map +1 -0
- package/dist/functions/externalIdentifierFunctions.js +167 -0
- package/dist/functions/externalIdentifierFunctions.js.map +1 -0
- package/dist/functions/index.d.ts +4 -0
- package/dist/functions/index.d.ts.map +1 -0
- package/dist/functions/index.js +20 -0
- package/dist/functions/index.js.map +1 -0
- package/dist/functions/managedIdentifierFunctions.d.ts +28 -0
- package/dist/functions/managedIdentifierFunctions.d.ts.map +1 -0
- package/dist/functions/managedIdentifierFunctions.js +252 -0
- package/dist/functions/managedIdentifierFunctions.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +30 -0
- package/dist/index.js.map +1 -0
- package/dist/tsdoc-metadata.json +11 -0
- package/dist/types/IIdentifierResolution.d.ts +37 -0
- package/dist/types/IIdentifierResolution.d.ts.map +1 -0
- package/dist/types/IIdentifierResolution.js +16 -0
- package/dist/types/IIdentifierResolution.js.map +1 -0
- package/dist/types/common.d.ts +17 -0
- package/dist/types/common.d.ts.map +1 -0
- package/dist/types/common.js +40 -0
- package/dist/types/common.js.map +1 -0
- package/dist/types/externalIdentifierTypes.d.ts +80 -0
- package/dist/types/externalIdentifierTypes.d.ts.map +1 -0
- package/dist/types/externalIdentifierTypes.js +35 -0
- package/dist/types/externalIdentifierTypes.js.map +1 -0
- package/dist/types/index.d.ts +5 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +21 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/managedIdentifierTypes.d.ts +98 -0
- package/dist/types/managedIdentifierTypes.d.ts.map +1 -0
- package/dist/types/managedIdentifierTypes.js +50 -0
- package/dist/types/managedIdentifierTypes.js.map +1 -0
- package/package.json +68 -0
- package/plugin.schema.json +2393 -0
- package/src/agent/IdentifierResolution.ts +112 -0
- package/src/functions/LegacySupport.ts +50 -0
- package/src/functions/externalIdentifierFunctions.ts +183 -0
- package/src/functions/index.ts +3 -0
- package/src/functions/managedIdentifierFunctions.ts +278 -0
- package/src/index.ts +11 -0
- package/src/types/IIdentifierResolution.ts +79 -0
- package/src/types/common.ts +47 -0
- package/src/types/externalIdentifierTypes.ts +119 -0
- package/src/types/index.ts +4 -0
- package/src/types/managedIdentifierTypes.ts +157 -0
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { IAgentContext, IAgentPlugin, IDIDManager, IKeyManager } from '@veramo/core'
|
|
2
|
+
import { ensureManagedIdentifierResult, ManagedIdentifierKeyOpts, ManagedIdentifierKeyResult, ManagedIdentifierOptsOrResult, schema } from '..'
|
|
3
|
+
import { resolveExternalIdentifier } from '../functions'
|
|
4
|
+
import {
|
|
5
|
+
ExternalIdentifierDidOpts,
|
|
6
|
+
ExternalIdentifierDidResult,
|
|
7
|
+
ExternalIdentifierOpts,
|
|
8
|
+
ExternalIdentifierResult,
|
|
9
|
+
ExternalIdentifierX5cOpts,
|
|
10
|
+
ExternalIdentifierX5cResult,
|
|
11
|
+
IIdentifierResolution,
|
|
12
|
+
ManagedIdentifierDidOpts,
|
|
13
|
+
ManagedIdentifierDidResult,
|
|
14
|
+
ManagedIdentifierJwkOpts,
|
|
15
|
+
ManagedIdentifierJwkResult,
|
|
16
|
+
ManagedIdentifierKidOpts,
|
|
17
|
+
ManagedIdentifierKidResult,
|
|
18
|
+
ManagedIdentifierResult,
|
|
19
|
+
ManagedIdentifierX5cOpts,
|
|
20
|
+
ManagedIdentifierX5cResult,
|
|
21
|
+
} from '../types'
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @public
|
|
25
|
+
*/
|
|
26
|
+
export class IdentifierResolution implements IAgentPlugin {
|
|
27
|
+
private readonly _crypto: Crypto
|
|
28
|
+
|
|
29
|
+
readonly schema = schema.IMnemonicInfoGenerator
|
|
30
|
+
readonly methods: IIdentifierResolution = {
|
|
31
|
+
identifierManagedGet: this.identifierGetManaged.bind(this),
|
|
32
|
+
identifierManagedGetByDid: this.identifierGetManagedByDid.bind(this),
|
|
33
|
+
identifierManagedGetByKid: this.identifierGetManagedByKid.bind(this),
|
|
34
|
+
identifierManagedGetByJwk: this.identifierGetManagedByJwk.bind(this),
|
|
35
|
+
identifierManagedGetByX5c: this.identifierGetManagedByX5c.bind(this),
|
|
36
|
+
identifierManagedGetByKey: this.identifierGetManagedByKey.bind(this),
|
|
37
|
+
|
|
38
|
+
identifierExternalResolve: this.identifierResolveExternal.bind(this),
|
|
39
|
+
identifierExternalResolveByDid: this.identifierExternalResolveByDid.bind(this),
|
|
40
|
+
identifierExternalResolveByX5c: this.identifierExternalResolveByX5c.bind(this),
|
|
41
|
+
|
|
42
|
+
// todo: JWKSet, oidc-discovery, oid4vci-issuer etc. Anything we already can resolve and need keys of
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* TODO: Add a cache, as we are retrieving the same keys/info quite often
|
|
47
|
+
*/
|
|
48
|
+
constructor(opts?: { crypto?: Crypto }) {
|
|
49
|
+
this._crypto = opts?.crypto ?? global.crypto
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Main method for managed identifiers. We always go through this method (also the other methods below) as we want to
|
|
54
|
+
* integrate a plugin for anomaly detection. Having a single method helps
|
|
55
|
+
* @param args
|
|
56
|
+
* @param context
|
|
57
|
+
* @private
|
|
58
|
+
*/
|
|
59
|
+
private async identifierGetManaged(
|
|
60
|
+
args: ManagedIdentifierOptsOrResult,
|
|
61
|
+
context: IAgentContext<IKeyManager & IIdentifierResolution>
|
|
62
|
+
): Promise<ManagedIdentifierResult> {
|
|
63
|
+
return await ensureManagedIdentifierResult({ ...args, crypto: this._crypto }, context)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
private async identifierGetManagedByDid(
|
|
67
|
+
args: ManagedIdentifierDidOpts,
|
|
68
|
+
context: IAgentContext<IKeyManager & IDIDManager & IIdentifierResolution>
|
|
69
|
+
): Promise<ManagedIdentifierDidResult> {
|
|
70
|
+
return (await this.identifierGetManaged({ ...args, method: 'did' }, context)) as ManagedIdentifierDidResult
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
private async identifierGetManagedByKid(
|
|
74
|
+
args: ManagedIdentifierKidOpts,
|
|
75
|
+
context: IAgentContext<IKeyManager & IIdentifierResolution>
|
|
76
|
+
): Promise<ManagedIdentifierKidResult> {
|
|
77
|
+
return (await this.identifierGetManaged({ ...args, method: 'kid' }, context)) as ManagedIdentifierKidResult
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
private async identifierGetManagedByKey(
|
|
81
|
+
args: ManagedIdentifierKeyOpts,
|
|
82
|
+
context: IAgentContext<IKeyManager & IIdentifierResolution>
|
|
83
|
+
): Promise<ManagedIdentifierKeyResult> {
|
|
84
|
+
return (await this.identifierGetManaged({ ...args, method: 'key' }, context)) as ManagedIdentifierKeyResult
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
private async identifierGetManagedByJwk(
|
|
88
|
+
args: ManagedIdentifierJwkOpts,
|
|
89
|
+
context: IAgentContext<IKeyManager & IIdentifierResolution>
|
|
90
|
+
): Promise<ManagedIdentifierJwkResult> {
|
|
91
|
+
return (await this.identifierGetManaged({ ...args, method: 'jwk' }, context)) as ManagedIdentifierJwkResult
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
private async identifierGetManagedByX5c(
|
|
95
|
+
args: ManagedIdentifierX5cOpts,
|
|
96
|
+
context: IAgentContext<IKeyManager & IIdentifierResolution>
|
|
97
|
+
): Promise<ManagedIdentifierX5cResult> {
|
|
98
|
+
return (await this.identifierGetManaged({ ...args, method: 'x5c' }, context)) as ManagedIdentifierX5cResult
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
private async identifierResolveExternal(args: ExternalIdentifierOpts, context: IAgentContext<IKeyManager>): Promise<ExternalIdentifierResult> {
|
|
102
|
+
return await resolveExternalIdentifier({ ...args, crypto: this._crypto }, context)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
private async identifierExternalResolveByDid(args: ExternalIdentifierDidOpts, context: IAgentContext<any>): Promise<ExternalIdentifierDidResult> {
|
|
106
|
+
return (await this.identifierResolveExternal({ ...args, method: 'did' }, context)) as ExternalIdentifierDidResult
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
private async identifierExternalResolveByX5c(args: ExternalIdentifierX5cOpts, context: IAgentContext<any>): Promise<ExternalIdentifierX5cResult> {
|
|
110
|
+
return (await this.identifierResolveExternal({ ...args, method: 'x5c' }, context)) as ExternalIdentifierX5cResult
|
|
111
|
+
}
|
|
112
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { IIdentifier } from '@veramo/core'
|
|
2
|
+
import { ManagedIdentifierDidOpts, ManagedIdentifierOptsOrResult } from '../types'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Converts legacy id opts key refs to the new ManagedIdentifierOpts
|
|
6
|
+
* @param opts
|
|
7
|
+
*/
|
|
8
|
+
export function legacyKeyRefsToIdentifierOpts(opts: {
|
|
9
|
+
idOpts?: ManagedIdentifierOptsOrResult
|
|
10
|
+
iss?: string
|
|
11
|
+
keyRef?: string
|
|
12
|
+
didOpts?: any
|
|
13
|
+
}): ManagedIdentifierOptsOrResult {
|
|
14
|
+
if (!opts.idOpts) {
|
|
15
|
+
console.warn(
|
|
16
|
+
`Legacy idOpts being used. Support will be dropped in the future. Consider switching to the idOpts, to have support for DIDs, JWKS, x5c etc. See https://github.com/Sphereon-Opensource/SSI-SDK-crypto-extensions/tree/feature/multi_identifier_support/packages/identifier-resolution`
|
|
17
|
+
)
|
|
18
|
+
// legacy way
|
|
19
|
+
let kmsKeyRef =
|
|
20
|
+
opts.keyRef ??
|
|
21
|
+
opts.didOpts?.idOpts?.kmsKeyRef ??
|
|
22
|
+
(typeof opts.didOpts?.idOpts.identifier === 'object' ? (opts.didOpts?.idOpts.identifier as IIdentifier).keys[0].kid : undefined)
|
|
23
|
+
if (!kmsKeyRef) {
|
|
24
|
+
throw Error('Key ref is needed for access token signer')
|
|
25
|
+
}
|
|
26
|
+
return {
|
|
27
|
+
kmsKeyRef: opts.keyRef ?? kmsKeyRef,
|
|
28
|
+
identifier: kmsKeyRef,
|
|
29
|
+
issuer: opts.iss,
|
|
30
|
+
} satisfies ManagedIdentifierDidOpts
|
|
31
|
+
} else {
|
|
32
|
+
const idOpts = opts.idOpts
|
|
33
|
+
if (opts.keyRef && !idOpts.kmsKeyRef) {
|
|
34
|
+
// legacy way
|
|
35
|
+
console.warn(
|
|
36
|
+
`Legacy keyRef being used. Support will be dropped in the future. Consider switching to the idOpts, to have support for DIDs, JWKS, x5c etc. See https://github.com/Sphereon-Opensource/SSI-SDK-crypto-extensions/tree/feature/multi_identifier_support/packages/identifier-resolution`
|
|
37
|
+
)
|
|
38
|
+
idOpts.kmsKeyRef = opts.keyRef
|
|
39
|
+
}
|
|
40
|
+
if (opts.iss && !idOpts.issuer) {
|
|
41
|
+
// legacy way
|
|
42
|
+
console.warn(
|
|
43
|
+
`Legacy iss being used. Support will be dropped in the future. Consider switching to the idOpts, to have support for DIDs, JWKS, x5c etc. See https://github.com/Sphereon-Opensource/SSI-SDK-crypto-extensions/tree/feature/multi_identifier_support/packages/identifier-resolution`
|
|
44
|
+
)
|
|
45
|
+
idOpts.issuer = opts.iss
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return idOpts
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { didDocumentToJwks, getAgentResolver } from '@sphereon/ssi-sdk-ext.did-utils'
|
|
2
|
+
import { calculateJwkThumbprint, JWK } from '@sphereon/ssi-sdk-ext.key-utils'
|
|
3
|
+
import {
|
|
4
|
+
getSubjectDN,
|
|
5
|
+
pemOrDerToX509Certificate,
|
|
6
|
+
PEMToDer,
|
|
7
|
+
validateX509CertificateChain,
|
|
8
|
+
X509ValidationResult,
|
|
9
|
+
} from '@sphereon/ssi-sdk-ext.x509-utils'
|
|
10
|
+
import { contextHasPlugin } from '@sphereon/ssi-sdk.agent-config'
|
|
11
|
+
import { IParsedDID, parseDid } from '@sphereon/ssi-types'
|
|
12
|
+
import { IAgentContext, IDIDManager, IResolver } from '@veramo/core'
|
|
13
|
+
import { isDefined } from '@veramo/utils'
|
|
14
|
+
import { CryptoEngine, setEngine } from 'pkijs'
|
|
15
|
+
import {
|
|
16
|
+
ExternalIdentifierDidOpts,
|
|
17
|
+
ExternalIdentifierDidResult,
|
|
18
|
+
ExternalIdentifierMethod,
|
|
19
|
+
ExternalIdentifierOpts,
|
|
20
|
+
ExternalIdentifierResult,
|
|
21
|
+
ExternalIdentifierX5cOpts,
|
|
22
|
+
ExternalIdentifierX5cResult,
|
|
23
|
+
ExternalJwkInfo,
|
|
24
|
+
isExternalIdentifierDidOpts,
|
|
25
|
+
isExternalIdentifierJwksUrlOpts,
|
|
26
|
+
isExternalIdentifierKidOpts,
|
|
27
|
+
isExternalIdentifierOidcDiscoveryOpts,
|
|
28
|
+
isExternalIdentifierX5cOpts,
|
|
29
|
+
} from '../types'
|
|
30
|
+
|
|
31
|
+
export async function resolveExternalIdentifier(
|
|
32
|
+
opts: ExternalIdentifierOpts & {
|
|
33
|
+
crypto?: Crypto
|
|
34
|
+
},
|
|
35
|
+
context: IAgentContext<any>
|
|
36
|
+
): Promise<ExternalIdentifierResult> {
|
|
37
|
+
let method: ExternalIdentifierMethod | undefined
|
|
38
|
+
if (isExternalIdentifierDidOpts(opts)) {
|
|
39
|
+
return resolveExternalDidIdentifier(opts, context)
|
|
40
|
+
} else if (isExternalIdentifierX5cOpts(opts)) {
|
|
41
|
+
return resolveExternalX5cIdentifier(opts, context)
|
|
42
|
+
} else if (isExternalIdentifierKidOpts(opts)) {
|
|
43
|
+
method = 'kid'
|
|
44
|
+
} else if (isExternalIdentifierJwksUrlOpts(opts)) {
|
|
45
|
+
method = 'jwks-url'
|
|
46
|
+
} else if (isExternalIdentifierOidcDiscoveryOpts(opts)) {
|
|
47
|
+
method = 'oidc-discovery'
|
|
48
|
+
}
|
|
49
|
+
throw Error(`External resolution method ${method} is not yet implemented`)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export async function resolveExternalX5cIdentifier(
|
|
53
|
+
opts: ExternalIdentifierX5cOpts & {
|
|
54
|
+
crypto?: Crypto
|
|
55
|
+
},
|
|
56
|
+
context: IAgentContext<IResolver & IDIDManager>
|
|
57
|
+
): Promise<ExternalIdentifierX5cResult> {
|
|
58
|
+
if (!isExternalIdentifierX5cOpts(opts)) {
|
|
59
|
+
return Promise.reject('External x5c Identifier args need to be provided')
|
|
60
|
+
}
|
|
61
|
+
const verify = opts.verify ?? true
|
|
62
|
+
const x5c = opts.identifier.map((derOrPem) => (derOrPem.includes('CERTIFICATE') ? PEMToDer(derOrPem) : derOrPem))
|
|
63
|
+
if (x5c.length === 0) {
|
|
64
|
+
return Promise.reject('Empty certification chain is now allowed')
|
|
65
|
+
}
|
|
66
|
+
const certificates = x5c.map(pemOrDerToX509Certificate)
|
|
67
|
+
|
|
68
|
+
let verificationResult: X509ValidationResult | undefined
|
|
69
|
+
let issuerJWK: JWK | undefined
|
|
70
|
+
let jwks: ExternalJwkInfo[] = []
|
|
71
|
+
|
|
72
|
+
if (verify) {
|
|
73
|
+
// We use the agent plugin if it is available as that is more powerful, but revert to the function otherwise
|
|
74
|
+
if (contextHasPlugin(context, 'verifyCertificateChain')) {
|
|
75
|
+
verificationResult = (await context.agent.verifyCertificateChain({
|
|
76
|
+
chain: opts.identifier,
|
|
77
|
+
trustAnchors: opts.trustAnchors ?? [],
|
|
78
|
+
verificationTime: opts.verificationTime,
|
|
79
|
+
})) as X509ValidationResult // We need to cast, as we know this is the value and we do not want to rely on the x509 plugin perse
|
|
80
|
+
} else {
|
|
81
|
+
verificationResult = await validateX509CertificateChain({
|
|
82
|
+
chain: opts.identifier,
|
|
83
|
+
trustAnchors: opts.trustAnchors ?? [],
|
|
84
|
+
verificationTime: opts.verificationTime,
|
|
85
|
+
})
|
|
86
|
+
}
|
|
87
|
+
if (verificationResult.certificateChain) {
|
|
88
|
+
jwks = verificationResult.certificateChain.map((cert) => {
|
|
89
|
+
return {
|
|
90
|
+
jwk: cert.publicKeyJWK,
|
|
91
|
+
kid: cert.subject.dn.DN,
|
|
92
|
+
jwkThumbprint: calculateJwkThumbprint({ jwk: cert.publicKeyJWK }),
|
|
93
|
+
} satisfies ExternalJwkInfo
|
|
94
|
+
})
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
if (!jwks || jwks.length === 0) {
|
|
98
|
+
const cryptoEngine = new CryptoEngine({
|
|
99
|
+
name: 'identifier_resolver_external',
|
|
100
|
+
crypto: opts.crypto ?? global.crypto,
|
|
101
|
+
})
|
|
102
|
+
setEngine(cryptoEngine.name, cryptoEngine)
|
|
103
|
+
jwks = await Promise.all(
|
|
104
|
+
certificates.map(async (cert) => {
|
|
105
|
+
const pk = await cert.getPublicKey(undefined, cryptoEngine)
|
|
106
|
+
const jwk = (await cryptoEngine.exportKey('jwk', pk)) as JWK
|
|
107
|
+
return {
|
|
108
|
+
jwk,
|
|
109
|
+
kid: getSubjectDN(cert).DN,
|
|
110
|
+
jwkThumbprint: calculateJwkThumbprint({ jwk }),
|
|
111
|
+
} satisfies ExternalJwkInfo
|
|
112
|
+
})
|
|
113
|
+
)
|
|
114
|
+
}
|
|
115
|
+
if (jwks.length === 0) {
|
|
116
|
+
return Promise.reject('Empty certification chain is now allowed')
|
|
117
|
+
}
|
|
118
|
+
if (!issuerJWK) {
|
|
119
|
+
issuerJWK = jwks[0].jwk
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
method: 'x5c',
|
|
124
|
+
verificationResult,
|
|
125
|
+
issuerJWK,
|
|
126
|
+
jwks,
|
|
127
|
+
certificates,
|
|
128
|
+
x5c,
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export async function resolveExternalDidIdentifier(
|
|
133
|
+
opts: ExternalIdentifierDidOpts,
|
|
134
|
+
context: IAgentContext<IResolver & IDIDManager>
|
|
135
|
+
): Promise<ExternalIdentifierDidResult> {
|
|
136
|
+
if (!isExternalIdentifierDidOpts(opts)) {
|
|
137
|
+
return Promise.reject('External DID Identifier args need to be provided')
|
|
138
|
+
} else if (!contextHasPlugin<IResolver & IDIDManager>(context, 'resolveDid')) {
|
|
139
|
+
return Promise.reject(Error(`Cannot get external DID identifier if DID resolver plugin is not enabled!`))
|
|
140
|
+
}
|
|
141
|
+
const { uniresolverResolution = false, localResolution = true, resolverResolution = true } = opts
|
|
142
|
+
const did = opts.identifier
|
|
143
|
+
let parsed: IParsedDID
|
|
144
|
+
try {
|
|
145
|
+
parsed = parseDid(did)
|
|
146
|
+
} catch (error: unknown) {
|
|
147
|
+
// Error from did resolution spec
|
|
148
|
+
return Promise.reject(error)
|
|
149
|
+
}
|
|
150
|
+
const didParsed = parsed
|
|
151
|
+
const didResolutionResult = await getAgentResolver(context, {
|
|
152
|
+
uniresolverResolution,
|
|
153
|
+
localResolution,
|
|
154
|
+
resolverResolution,
|
|
155
|
+
}).resolve(did)
|
|
156
|
+
const didDocument = didResolutionResult.didDocument ?? undefined
|
|
157
|
+
const didJwks = didDocument ? didDocumentToJwks(didDocument) : undefined
|
|
158
|
+
const jwks = didJwks
|
|
159
|
+
? Array.from(
|
|
160
|
+
new Set(
|
|
161
|
+
Object.values(didJwks)
|
|
162
|
+
.filter((jwks) => isDefined(jwks) && jwks.length > 0)
|
|
163
|
+
.flatMap((jwks) => jwks)
|
|
164
|
+
)
|
|
165
|
+
).map((jwk) => {
|
|
166
|
+
return { jwk, jwkThumbprint: calculateJwkThumbprint({ jwk }), kid: jwk.kid }
|
|
167
|
+
})
|
|
168
|
+
: []
|
|
169
|
+
|
|
170
|
+
if (didResolutionResult?.didDocument) {
|
|
171
|
+
// @ts-ignore // Mandatory on the original object, but we already provide it directly
|
|
172
|
+
delete didResolutionResult['didDocument']
|
|
173
|
+
}
|
|
174
|
+
return {
|
|
175
|
+
method: 'did',
|
|
176
|
+
did,
|
|
177
|
+
jwks,
|
|
178
|
+
didJwks,
|
|
179
|
+
didDocument,
|
|
180
|
+
didResolutionResult,
|
|
181
|
+
didParsed,
|
|
182
|
+
}
|
|
183
|
+
}
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
import { getFirstKeyWithRelation } from '@sphereon/ssi-sdk-ext.did-utils'
|
|
2
|
+
import { calculateJwkThumbprint, JWK, toJwk } from '@sphereon/ssi-sdk-ext.key-utils'
|
|
3
|
+
import { pemOrDerToX509Certificate } from '@sphereon/ssi-sdk-ext.x509-utils'
|
|
4
|
+
import { contextHasDidManager, contextHasKeyManager } from '@sphereon/ssi-sdk.agent-config'
|
|
5
|
+
import { IAgentContext, IIdentifier, IKey, IKeyManager } from '@veramo/core'
|
|
6
|
+
import { CryptoEngine, setEngine } from 'pkijs'
|
|
7
|
+
import {
|
|
8
|
+
IIdentifierResolution,
|
|
9
|
+
isManagedIdentifierDidOpts,
|
|
10
|
+
isManagedIdentifierDidResult,
|
|
11
|
+
isManagedIdentifierJwkOpts,
|
|
12
|
+
isManagedIdentifierJwkResult,
|
|
13
|
+
isManagedIdentifierKeyOpts,
|
|
14
|
+
isManagedIdentifierKeyResult,
|
|
15
|
+
isManagedIdentifierKidOpts,
|
|
16
|
+
isManagedIdentifierX5cOpts,
|
|
17
|
+
ManagedIdentifierDidOpts,
|
|
18
|
+
ManagedIdentifierDidResult,
|
|
19
|
+
ManagedIdentifierJwkOpts,
|
|
20
|
+
ManagedIdentifierJwkResult,
|
|
21
|
+
ManagedIdentifierKeyOpts,
|
|
22
|
+
ManagedIdentifierKeyResult,
|
|
23
|
+
ManagedIdentifierKidOpts,
|
|
24
|
+
ManagedIdentifierKidResult,
|
|
25
|
+
ManagedIdentifierOptsOrResult,
|
|
26
|
+
ManagedIdentifierResult,
|
|
27
|
+
ManagedIdentifierX5cOpts,
|
|
28
|
+
ManagedIdentifierX5cResult,
|
|
29
|
+
} from '../types'
|
|
30
|
+
|
|
31
|
+
export async function getManagedKidIdentifier(
|
|
32
|
+
opts: ManagedIdentifierKidOpts,
|
|
33
|
+
context: IAgentContext<IKeyManager>
|
|
34
|
+
): Promise<ManagedIdentifierKidResult> {
|
|
35
|
+
const method = 'kid'
|
|
36
|
+
if (!contextHasKeyManager(context)) {
|
|
37
|
+
return Promise.reject(Error(`Cannot get Key/JWK identifier if KeyManager plugin is not enabled!`))
|
|
38
|
+
}
|
|
39
|
+
const key = await context.agent.keyManagerGet({ kid: opts.kmsKeyRef ?? opts.identifier })
|
|
40
|
+
const jwk = toJwk(key.publicKeyHex, key.type, { key })
|
|
41
|
+
const jwkThumbprint = (key.meta?.jwkThumbprint as string) ?? calculateJwkThumbprint({ jwk })
|
|
42
|
+
const kid = opts.kid ?? (key.meta?.verificationMethod?.id as string) ?? jwkThumbprint
|
|
43
|
+
const issuer = opts.issuer ?? kid // The different identifiers should set the value. Defaults to the kid
|
|
44
|
+
return {
|
|
45
|
+
method,
|
|
46
|
+
key,
|
|
47
|
+
identifier: opts.identifier,
|
|
48
|
+
jwk,
|
|
49
|
+
jwkThumbprint,
|
|
50
|
+
kid,
|
|
51
|
+
issuer,
|
|
52
|
+
kmsKeyRef: key.kid,
|
|
53
|
+
opts,
|
|
54
|
+
} satisfies ManagedIdentifierKidResult
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function isManagedIdentifierResult(identifier: ManagedIdentifierOptsOrResult & { crypto?: Crypto }): identifier is ManagedIdentifierResult {
|
|
58
|
+
return 'key' in identifier && 'kmsKeyRef' in identifier && 'method' in identifier && 'opts' in identifier
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Allows to get a managed identifier result in case identifier options are passed in, but returns the identifier directly in case results are passed in. This means resolution can have happened before, or happens in this method
|
|
63
|
+
* @param identifier
|
|
64
|
+
* @param context
|
|
65
|
+
*/
|
|
66
|
+
export async function ensureManagedIdentifierResult(
|
|
67
|
+
identifier: ManagedIdentifierOptsOrResult & {
|
|
68
|
+
crypto?: Crypto
|
|
69
|
+
},
|
|
70
|
+
context: IAgentContext<IKeyManager>
|
|
71
|
+
): Promise<ManagedIdentifierResult> {
|
|
72
|
+
const { lazyDisabled = false } = identifier
|
|
73
|
+
return !lazyDisabled && isManagedIdentifierResult(identifier) ? identifier : await getManagedIdentifier(identifier, context)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* This function is just a convenience function to get a common result. The user already apparently had a key, so could have called the kid version as well
|
|
78
|
+
* @param opts
|
|
79
|
+
* @param _context
|
|
80
|
+
*/
|
|
81
|
+
export async function getManagedKeyIdentifier(opts: ManagedIdentifierKeyOpts, _context?: IAgentContext<any>): Promise<ManagedIdentifierKeyResult> {
|
|
82
|
+
const method = 'key'
|
|
83
|
+
const key: IKey = opts.identifier
|
|
84
|
+
if (opts.kmsKeyRef && opts.kmsKeyRef !== key.kid) {
|
|
85
|
+
return Promise.reject(Error(`Cannot get a managed key object by providing a key and a kmsKeyRef that are different.}`))
|
|
86
|
+
}
|
|
87
|
+
const jwk = toJwk(key.publicKeyHex, key.type, { key })
|
|
88
|
+
const jwkThumbprint = (key.meta?.jwkThumbprint as string) ?? calculateJwkThumbprint({ jwk })
|
|
89
|
+
const kid = opts.kid ?? (key.meta?.verificationMethod?.id as string) ?? jwkThumbprint
|
|
90
|
+
const issuer = opts.issuer ?? kid // The different identifiers should set the value. Defaults to the kid
|
|
91
|
+
return {
|
|
92
|
+
method,
|
|
93
|
+
key,
|
|
94
|
+
identifier: key,
|
|
95
|
+
jwk,
|
|
96
|
+
jwkThumbprint,
|
|
97
|
+
kid,
|
|
98
|
+
issuer,
|
|
99
|
+
kmsKeyRef: key.kid,
|
|
100
|
+
opts,
|
|
101
|
+
} satisfies ManagedIdentifierKeyResult
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export async function getManagedDidIdentifier(opts: ManagedIdentifierDidOpts, context: IAgentContext<any>): Promise<ManagedIdentifierDidResult> {
|
|
105
|
+
const method = 'did'
|
|
106
|
+
if (!contextHasDidManager(context)) {
|
|
107
|
+
return Promise.reject(Error(`Cannot get DID identifier if DID Manager plugin is not enabled!`))
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
let identifier: IIdentifier
|
|
111
|
+
if (typeof opts.identifier === 'string') {
|
|
112
|
+
identifier = await context.agent.didManagerGet({ did: opts.identifier.split('#')[0] })
|
|
113
|
+
} else {
|
|
114
|
+
identifier = opts.identifier
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const did = identifier.did
|
|
118
|
+
const keys = identifier?.keys // fixme: We really want to return the vmRelationship keys here actually
|
|
119
|
+
const extendedKey = await getFirstKeyWithRelation(
|
|
120
|
+
{
|
|
121
|
+
...opts,
|
|
122
|
+
identifier,
|
|
123
|
+
vmRelationship: opts.vmRelationship ?? 'verificationMethod',
|
|
124
|
+
},
|
|
125
|
+
context
|
|
126
|
+
)
|
|
127
|
+
const key = extendedKey
|
|
128
|
+
const controllerKeyId = identifier.controllerKeyId
|
|
129
|
+
const jwk = toJwk(key.publicKeyHex, key.type, { key })
|
|
130
|
+
const jwkThumbprint = key.meta?.jwkThumbprint ?? calculateJwkThumbprint({ jwk })
|
|
131
|
+
let kid = opts.kid ?? extendedKey.meta?.verificationMethod?.id
|
|
132
|
+
if (!kid.startsWith(did)) {
|
|
133
|
+
// Make sure we create a fully qualified kid
|
|
134
|
+
const hash = kid.startsWith('#') ? '' : '#'
|
|
135
|
+
kid = `${did}${hash}${kid}`
|
|
136
|
+
}
|
|
137
|
+
const issuer = opts.issuer ?? did
|
|
138
|
+
return {
|
|
139
|
+
method,
|
|
140
|
+
key,
|
|
141
|
+
did,
|
|
142
|
+
kmsKeyRef: key.kid,
|
|
143
|
+
jwk,
|
|
144
|
+
jwkThumbprint,
|
|
145
|
+
controllerKeyId,
|
|
146
|
+
kid,
|
|
147
|
+
keys,
|
|
148
|
+
issuer,
|
|
149
|
+
identifier,
|
|
150
|
+
opts,
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export async function getManagedJwkIdentifier(
|
|
155
|
+
opts: ManagedIdentifierJwkOpts,
|
|
156
|
+
context: IAgentContext<IKeyManager>
|
|
157
|
+
): Promise<ManagedIdentifierJwkResult> {
|
|
158
|
+
const method = 'jwk'
|
|
159
|
+
const { kid, issuer } = opts
|
|
160
|
+
if (!contextHasKeyManager(context)) {
|
|
161
|
+
return Promise.reject(Error(`Cannot get Key/JWK identifier if KeyManager plugin is not enabled!`))
|
|
162
|
+
}
|
|
163
|
+
const key = await context.agent.keyManagerGet({ kid: opts.kmsKeyRef ?? calculateJwkThumbprint({ jwk: opts.identifier }) })
|
|
164
|
+
const jwk = opts.identifier ?? toJwk(key.publicKeyHex, key.type, { key })
|
|
165
|
+
const jwkThumbprint = (key.meta?.jwkThumbprint as string) ?? calculateJwkThumbprint({ jwk })
|
|
166
|
+
// we explicitly do not set the kid and issuer, meaning it can remain null. Normally you do not provide a kid and issuer with Jwks.
|
|
167
|
+
return {
|
|
168
|
+
method,
|
|
169
|
+
key,
|
|
170
|
+
kmsKeyRef: key.kid,
|
|
171
|
+
identifier: jwk,
|
|
172
|
+
jwk,
|
|
173
|
+
jwkThumbprint,
|
|
174
|
+
kid,
|
|
175
|
+
issuer,
|
|
176
|
+
opts,
|
|
177
|
+
} satisfies ManagedIdentifierJwkResult
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export async function getManagedX5cIdentifier(
|
|
181
|
+
opts: ManagedIdentifierX5cOpts & {
|
|
182
|
+
crypto?: Crypto
|
|
183
|
+
},
|
|
184
|
+
context: IAgentContext<IKeyManager>
|
|
185
|
+
): Promise<ManagedIdentifierX5cResult> {
|
|
186
|
+
const { kid, issuer } = opts
|
|
187
|
+
const method = 'x5c'
|
|
188
|
+
const x5c = opts.identifier
|
|
189
|
+
if (x5c.length === 0) {
|
|
190
|
+
return Promise.reject(`Cannot resolve x5c when an empty x5c is passed in`)
|
|
191
|
+
} else if (!contextHasKeyManager(context)) {
|
|
192
|
+
return Promise.reject(Error(`Cannot get X5c identifier if KeyManager plugin is not enabled!`))
|
|
193
|
+
}
|
|
194
|
+
const cryptoImpl = opts.crypto ?? crypto
|
|
195
|
+
const certificate = pemOrDerToX509Certificate(x5c[0])
|
|
196
|
+
const cryptoEngine = new CryptoEngine({ name: 'identifier_resolver_managed', crypto: cryptoImpl })
|
|
197
|
+
setEngine(cryptoEngine.name, cryptoEngine)
|
|
198
|
+
const pk = await certificate.getPublicKey(undefined, cryptoEngine)
|
|
199
|
+
const jwk = (await cryptoEngine.subtle.exportKey('jwk', pk)) as JWK
|
|
200
|
+
const jwkThumbprint = calculateJwkThumbprint({ jwk })
|
|
201
|
+
const key = await context.agent.keyManagerGet({ kid: opts.kmsKeyRef ?? jwkThumbprint })
|
|
202
|
+
// we explicitly do not set the kid and issuer, meaning it can remain null. Normally you do not provide a kid and issuer with x5c.
|
|
203
|
+
|
|
204
|
+
return {
|
|
205
|
+
method,
|
|
206
|
+
x5c,
|
|
207
|
+
identifier: x5c,
|
|
208
|
+
certificate,
|
|
209
|
+
jwk,
|
|
210
|
+
jwkThumbprint,
|
|
211
|
+
key,
|
|
212
|
+
kmsKeyRef: key.kid,
|
|
213
|
+
kid,
|
|
214
|
+
issuer,
|
|
215
|
+
opts,
|
|
216
|
+
} satisfies ManagedIdentifierX5cResult
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
export async function getManagedIdentifier(
|
|
220
|
+
opts: ManagedIdentifierOptsOrResult & {
|
|
221
|
+
crypto?: Crypto
|
|
222
|
+
},
|
|
223
|
+
context: IAgentContext<IKeyManager>
|
|
224
|
+
): Promise<ManagedIdentifierResult> {
|
|
225
|
+
let resolutionResult: ManagedIdentifierResult
|
|
226
|
+
if (isManagedIdentifierResult(opts)) {
|
|
227
|
+
opts
|
|
228
|
+
}
|
|
229
|
+
if (isManagedIdentifierKidOpts(opts)) {
|
|
230
|
+
resolutionResult = await getManagedKidIdentifier(opts, context)
|
|
231
|
+
} else if (isManagedIdentifierDidOpts(opts)) {
|
|
232
|
+
resolutionResult = await getManagedDidIdentifier(opts, context)
|
|
233
|
+
} else if (isManagedIdentifierJwkOpts(opts)) {
|
|
234
|
+
resolutionResult = await getManagedJwkIdentifier(opts, context)
|
|
235
|
+
} else if (isManagedIdentifierX5cOpts(opts)) {
|
|
236
|
+
resolutionResult = await getManagedX5cIdentifier(opts, context)
|
|
237
|
+
} else if (isManagedIdentifierKeyOpts(opts)) {
|
|
238
|
+
resolutionResult = await getManagedKeyIdentifier(opts, context)
|
|
239
|
+
} else {
|
|
240
|
+
return Promise.reject(Error(`Could not determine identifier method. Please provide explicitly`))
|
|
241
|
+
}
|
|
242
|
+
const { key } = resolutionResult
|
|
243
|
+
if (!key || (isManagedIdentifierDidOpts(opts) && isManagedIdentifierDidResult(resolutionResult) && !resolutionResult.identifier)) {
|
|
244
|
+
console.log(`Cannot find identifier`, opts.identifier)
|
|
245
|
+
return Promise.reject(`Cannot find identifier ${opts.identifier}`)
|
|
246
|
+
}
|
|
247
|
+
return resolutionResult
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
export async function managedIdentifierToKeyResult(
|
|
251
|
+
identifier: ManagedIdentifierOptsOrResult,
|
|
252
|
+
context: IAgentContext<IIdentifierResolution & IKeyManager>
|
|
253
|
+
): Promise<ManagedIdentifierKeyResult> {
|
|
254
|
+
const resolved = await ensureManagedIdentifierResult(identifier, context)
|
|
255
|
+
if (isManagedIdentifierKeyResult(resolved)) {
|
|
256
|
+
return resolved
|
|
257
|
+
}
|
|
258
|
+
return {
|
|
259
|
+
...resolved,
|
|
260
|
+
method: 'key',
|
|
261
|
+
identifier: resolved.key,
|
|
262
|
+
} satisfies ManagedIdentifierKeyResult
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
export async function managedIdentifierToJwk(
|
|
266
|
+
identifier: ManagedIdentifierOptsOrResult,
|
|
267
|
+
context: IAgentContext<IIdentifierResolution & IKeyManager>
|
|
268
|
+
): Promise<ManagedIdentifierJwkResult> {
|
|
269
|
+
const resolved = await ensureManagedIdentifierResult(identifier, context)
|
|
270
|
+
if (isManagedIdentifierJwkResult(resolved)) {
|
|
271
|
+
return resolved
|
|
272
|
+
}
|
|
273
|
+
return {
|
|
274
|
+
...resolved,
|
|
275
|
+
method: 'jwk',
|
|
276
|
+
identifier: resolved.jwk,
|
|
277
|
+
} satisfies ManagedIdentifierJwkResult
|
|
278
|
+
}
|
package/src/index.ts
ADDED