@veramo/credential-ld 3.1.1-next.10
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 +6 -0
- package/build/action-handler.d.ts +28 -0
- package/build/action-handler.d.ts.map +1 -0
- package/build/action-handler.js +155 -0
- package/build/action-handler.js.map +1 -0
- package/build/index.d.ts +15 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +31 -0
- package/build/index.js.map +1 -0
- package/build/ld-context-loader.d.ts +16 -0
- package/build/ld-context-loader.d.ts.map +1 -0
- package/build/ld-context-loader.js +40 -0
- package/build/ld-context-loader.js.map +1 -0
- package/build/ld-credential-module.d.ts +25 -0
- package/build/ld-credential-module.d.ts.map +1 -0
- package/build/ld-credential-module.js +148 -0
- package/build/ld-credential-module.js.map +1 -0
- package/build/ld-default-contexts.d.ts +6 -0
- package/build/ld-default-contexts.d.ts.map +1 -0
- package/build/ld-default-contexts.js +60 -0
- package/build/ld-default-contexts.js.map +1 -0
- package/build/ld-suite-loader.d.ts +15 -0
- package/build/ld-suite-loader.d.ts.map +1 -0
- package/build/ld-suite-loader.js +28 -0
- package/build/ld-suite-loader.js.map +1 -0
- package/build/ld-suites.d.ts +13 -0
- package/build/ld-suites.d.ts.map +1 -0
- package/build/ld-suites.js +15 -0
- package/build/ld-suites.js.map +1 -0
- package/build/suites/EcdsaSecp256k1RecoverySignature2020.d.ts +11 -0
- package/build/suites/EcdsaSecp256k1RecoverySignature2020.d.ts.map +1 -0
- package/build/suites/EcdsaSecp256k1RecoverySignature2020.js +100 -0
- package/build/suites/EcdsaSecp256k1RecoverySignature2020.js.map +1 -0
- package/build/suites/Ed25519Signature2018.d.ts +11 -0
- package/build/suites/Ed25519Signature2018.d.ts.map +1 -0
- package/build/suites/Ed25519Signature2018.js +92 -0
- package/build/suites/Ed25519Signature2018.js.map +1 -0
- package/build/tsdoc-metadata.json +11 -0
- package/build/types.d.ts +174 -0
- package/build/types.d.ts.map +1 -0
- package/build/types.js +3 -0
- package/build/types.js.map +1 -0
- package/contexts/X25519KeyAgreementKey2019.jsonld +26 -0
- package/contexts/did_v0.11.jsonld +58 -0
- package/contexts/ed25519-signature-2018-v1.jsonld +91 -0
- package/contexts/kyc-v1.jsonld +8 -0
- package/contexts/lds-ecdsa-secp256k1-recovery2020-0.0.jsonld +60 -0
- package/contexts/profile-v1.jsonld +8 -0
- package/contexts/security_context_v1.jsonld +58 -0
- package/contexts/socialmedia-v1.jsonld +7 -0
- package/contexts/transmute_v1.jsonld +21 -0
- package/contexts/w3_2018_credentials_v1.jsonld +237 -0
- package/package.json +51 -0
- package/plugin.schema.json +398 -0
- package/src/__tests__/context.loader.test.ts +39 -0
- package/src/__tests__/issue-verify-flow.test.ts +84 -0
- package/src/action-handler.ts +229 -0
- package/src/index.ts +14 -0
- package/src/ld-context-loader.ts +30 -0
- package/src/ld-credential-module.ts +170 -0
- package/src/ld-default-contexts.ts +30 -0
- package/src/ld-suite-loader.ts +29 -0
- package/src/ld-suites.ts +45 -0
- package/src/suites/EcdsaSecp256k1RecoverySignature2020.ts +83 -0
- package/src/suites/Ed25519Signature2018.ts +75 -0
- package/src/types.ts +208 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { ContextDoc } from '../types'
|
|
2
|
+
import { LdContextLoader } from '../ld-context-loader'
|
|
3
|
+
import { LdDefaultContexts } from '../ld-default-contexts'
|
|
4
|
+
|
|
5
|
+
describe('credential-ld context loader', () => {
|
|
6
|
+
const customContext: Record<string, ContextDoc> = {
|
|
7
|
+
'https://example.com/custom/context': {
|
|
8
|
+
'@context': {
|
|
9
|
+
'@version': 1.1,
|
|
10
|
+
id: '@id',
|
|
11
|
+
type: '@type',
|
|
12
|
+
nothing: 'https://example.com/nothing',
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
it('loads custom context from record', async () => {
|
|
18
|
+
expect.assertions(2)
|
|
19
|
+
const loader = new LdContextLoader({ contextsPaths: [customContext] })
|
|
20
|
+
expect(loader.has('https://example.com/custom/context')).toBe(true)
|
|
21
|
+
await expect(loader.get('https://example.com/custom/context')).resolves.toEqual({
|
|
22
|
+
'@context': {
|
|
23
|
+
'@version': 1.1,
|
|
24
|
+
id: '@id',
|
|
25
|
+
type: '@type',
|
|
26
|
+
nothing: 'https://example.com/nothing',
|
|
27
|
+
},
|
|
28
|
+
})
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
it('loads context from default map', async () => {
|
|
32
|
+
expect.assertions(2)
|
|
33
|
+
const loader = new LdContextLoader({ contextsPaths: [LdDefaultContexts] })
|
|
34
|
+
expect(loader.has('https://www.w3.org/2018/credentials/v1')).toBe(true)
|
|
35
|
+
|
|
36
|
+
const credsContext = await loader.get('https://www.w3.org/2018/credentials/v1')
|
|
37
|
+
expect(credsContext['@context']).toBeDefined()
|
|
38
|
+
})
|
|
39
|
+
})
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createAgent,
|
|
3
|
+
CredentialPayload,
|
|
4
|
+
IDIDManager,
|
|
5
|
+
IIdentifier,
|
|
6
|
+
IKeyManager,
|
|
7
|
+
IResolver,
|
|
8
|
+
TAgent,
|
|
9
|
+
} from '../../../core/src'
|
|
10
|
+
import { CredentialIssuer, ICredentialIssuer } from '../../../credential-w3c/src'
|
|
11
|
+
import { DIDManager, MemoryDIDStore } from '../../../did-manager/src'
|
|
12
|
+
import { KeyManager, MemoryKeyStore, MemoryPrivateKeyStore } from '../../../key-manager/src'
|
|
13
|
+
import { KeyManagementSystem } from '../../../kms-local/src'
|
|
14
|
+
import { getDidKeyResolver, KeyDIDProvider } from '../../../did-provider-key/src'
|
|
15
|
+
import { DIDResolverPlugin } from '../../../did-resolver/src'
|
|
16
|
+
import { ContextDoc } from '../types'
|
|
17
|
+
import { CredentialIssuerLD } from '../action-handler'
|
|
18
|
+
import { LdDefaultContexts } from '../ld-default-contexts'
|
|
19
|
+
import { VeramoEd25519Signature2018 } from '../suites/Ed25519Signature2018'
|
|
20
|
+
import { Resolver } from 'did-resolver'
|
|
21
|
+
|
|
22
|
+
const customContext: Record<string, ContextDoc> = {
|
|
23
|
+
'custom:example.context': {
|
|
24
|
+
'@context': {
|
|
25
|
+
nothing: 'custom:example.context#blank',
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
describe('credential-LD full flow', () => {
|
|
31
|
+
let didKeyIdentifier: IIdentifier
|
|
32
|
+
let agent: TAgent<IResolver & IKeyManager & IDIDManager & ICredentialIssuer>
|
|
33
|
+
|
|
34
|
+
beforeAll(async () => {
|
|
35
|
+
agent = createAgent({
|
|
36
|
+
plugins: [
|
|
37
|
+
new KeyManager({
|
|
38
|
+
store: new MemoryKeyStore(),
|
|
39
|
+
kms: {
|
|
40
|
+
local: new KeyManagementSystem(new MemoryPrivateKeyStore()),
|
|
41
|
+
},
|
|
42
|
+
}),
|
|
43
|
+
new DIDManager({
|
|
44
|
+
providers: {
|
|
45
|
+
'did:key': new KeyDIDProvider({ defaultKms: 'local' }),
|
|
46
|
+
},
|
|
47
|
+
store: new MemoryDIDStore(),
|
|
48
|
+
defaultProvider: 'did:key',
|
|
49
|
+
}),
|
|
50
|
+
new DIDResolverPlugin({
|
|
51
|
+
resolver: new Resolver({ ...getDidKeyResolver() }),
|
|
52
|
+
}),
|
|
53
|
+
new CredentialIssuer(),
|
|
54
|
+
new CredentialIssuerLD({
|
|
55
|
+
contextMaps: [LdDefaultContexts, customContext],
|
|
56
|
+
suites: [new VeramoEd25519Signature2018()],
|
|
57
|
+
}),
|
|
58
|
+
],
|
|
59
|
+
})
|
|
60
|
+
didKeyIdentifier = await agent.didManagerCreate()
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
it('works with Ed25519Signature2018', async () => {
|
|
64
|
+
const credential: CredentialPayload = {
|
|
65
|
+
issuer: didKeyIdentifier.did,
|
|
66
|
+
'@context': ['custom:example.context'],
|
|
67
|
+
credentialSubject: {
|
|
68
|
+
nothing: 'else matters',
|
|
69
|
+
},
|
|
70
|
+
}
|
|
71
|
+
const verifiableCredential = await agent.createVerifiableCredential({
|
|
72
|
+
credential,
|
|
73
|
+
proofFormat: 'lds',
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
expect(verifiableCredential).toBeDefined()
|
|
77
|
+
|
|
78
|
+
const verified = await agent.verifyCredential({
|
|
79
|
+
credential: verifiableCredential,
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
expect(verified).toBe(true)
|
|
83
|
+
})
|
|
84
|
+
})
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CredentialPayload,
|
|
3
|
+
IAgentContext,
|
|
4
|
+
IAgentPlugin,
|
|
5
|
+
IIdentifier,
|
|
6
|
+
IKey,
|
|
7
|
+
IResolver,
|
|
8
|
+
PresentationPayload,
|
|
9
|
+
VerifiableCredential,
|
|
10
|
+
VerifiablePresentation,
|
|
11
|
+
} from '@veramo/core'
|
|
12
|
+
import { schema, VeramoLdSignature } from './'
|
|
13
|
+
import Debug from 'debug'
|
|
14
|
+
import { LdContextLoader } from './ld-context-loader'
|
|
15
|
+
import {
|
|
16
|
+
_ExtendedIKey,
|
|
17
|
+
extractIssuer,
|
|
18
|
+
isDefined,
|
|
19
|
+
MANDATORY_CREDENTIAL_CONTEXT,
|
|
20
|
+
mapIdentifierKeysToDoc,
|
|
21
|
+
OrPromise,
|
|
22
|
+
processEntryToArray,
|
|
23
|
+
RecordLike,
|
|
24
|
+
} from '@veramo/utils'
|
|
25
|
+
|
|
26
|
+
import { LdCredentialModule } from './ld-credential-module'
|
|
27
|
+
import { LdSuiteLoader } from './ld-suite-loader'
|
|
28
|
+
import {
|
|
29
|
+
ContextDoc,
|
|
30
|
+
ICreateVerifiableCredentialLDArgs,
|
|
31
|
+
ICreateVerifiablePresentationLDArgs,
|
|
32
|
+
ICredentialIssuerLD,
|
|
33
|
+
IRequiredContext,
|
|
34
|
+
IVerifyCredentialLDArgs,
|
|
35
|
+
IVerifyPresentationLDArgs,
|
|
36
|
+
} from './types'
|
|
37
|
+
|
|
38
|
+
const debug = Debug('veramo:w3c:action-handler')
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* A Veramo plugin that implements the {@link ICredentialIssuerLD} methods.
|
|
42
|
+
*
|
|
43
|
+
* @public
|
|
44
|
+
*/
|
|
45
|
+
export class CredentialIssuerLD implements IAgentPlugin {
|
|
46
|
+
readonly methods: ICredentialIssuerLD
|
|
47
|
+
readonly schema = schema.ICredentialIssuer
|
|
48
|
+
|
|
49
|
+
private ldCredentialModule: LdCredentialModule
|
|
50
|
+
|
|
51
|
+
constructor(options: { contextMaps: RecordLike<OrPromise<ContextDoc>>[]; suites: VeramoLdSignature[] }) {
|
|
52
|
+
this.ldCredentialModule = new LdCredentialModule({
|
|
53
|
+
ldContextLoader: new LdContextLoader({ contextsPaths: options.contextMaps }),
|
|
54
|
+
ldSuiteLoader: new LdSuiteLoader({ veramoLdSignatures: options.suites }),
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
this.methods = {
|
|
58
|
+
createVerifiablePresentationLD: this.createVerifiablePresentationLD.bind(this),
|
|
59
|
+
createVerifiableCredentialLD: this.createVerifiableCredentialLD.bind(this),
|
|
60
|
+
verifyCredentialLD: this.verifyCredentialLD.bind(this),
|
|
61
|
+
verifyPresentationLD: this.verifyPresentationLD.bind(this),
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** {@inheritdoc ICredentialIssuerLD.createVerifiablePresentationLD} */
|
|
66
|
+
public async createVerifiablePresentationLD(
|
|
67
|
+
args: ICreateVerifiablePresentationLDArgs,
|
|
68
|
+
context: IRequiredContext,
|
|
69
|
+
): Promise<VerifiablePresentation> {
|
|
70
|
+
const presentationContext = processEntryToArray(
|
|
71
|
+
args?.presentation?.['@context'],
|
|
72
|
+
MANDATORY_CREDENTIAL_CONTEXT,
|
|
73
|
+
)
|
|
74
|
+
const presentationType = processEntryToArray(args?.presentation?.type, 'VerifiablePresentation')
|
|
75
|
+
|
|
76
|
+
const presentation: PresentationPayload = {
|
|
77
|
+
...args?.presentation,
|
|
78
|
+
'@context': presentationContext,
|
|
79
|
+
type: presentationType,
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (!isDefined(presentation.holder)) {
|
|
83
|
+
throw new Error('invalid_argument: args.presentation.holder must not be empty')
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (args.presentation.verifiableCredential) {
|
|
87
|
+
const credentials = args.presentation.verifiableCredential.map((cred) => {
|
|
88
|
+
if (typeof cred !== 'string' && cred.proof.jwt) {
|
|
89
|
+
return cred.proof.jwt
|
|
90
|
+
} else {
|
|
91
|
+
return cred
|
|
92
|
+
}
|
|
93
|
+
})
|
|
94
|
+
presentation.verifiableCredential = credentials
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
//issuanceDate must not be present for presentations because it is not defined in a @context
|
|
98
|
+
delete presentation.issuanceDate
|
|
99
|
+
|
|
100
|
+
let identifier: IIdentifier
|
|
101
|
+
try {
|
|
102
|
+
identifier = await context.agent.didManagerGet({ did: presentation.holder })
|
|
103
|
+
} catch (e) {
|
|
104
|
+
throw new Error('invalid_argument: args.presentation.holder must be a DID managed by this agent')
|
|
105
|
+
}
|
|
106
|
+
try {
|
|
107
|
+
const { signingKey, verificationMethodId } = await this.findSigningKeyWithId(
|
|
108
|
+
context,
|
|
109
|
+
identifier,
|
|
110
|
+
args.keyRef,
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
return await this.ldCredentialModule.signLDVerifiablePresentation(
|
|
114
|
+
presentation,
|
|
115
|
+
identifier.did,
|
|
116
|
+
signingKey,
|
|
117
|
+
verificationMethodId,
|
|
118
|
+
args.challenge,
|
|
119
|
+
args.domain,
|
|
120
|
+
context,
|
|
121
|
+
)
|
|
122
|
+
} catch (error) {
|
|
123
|
+
debug(error)
|
|
124
|
+
return Promise.reject(error)
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** {@inheritdoc ICredentialIssuerLD.createVerifiableCredentialLD} */
|
|
129
|
+
public async createVerifiableCredentialLD(
|
|
130
|
+
args: ICreateVerifiableCredentialLDArgs,
|
|
131
|
+
context: IRequiredContext,
|
|
132
|
+
): Promise<VerifiableCredential> {
|
|
133
|
+
const credentialContext = processEntryToArray(
|
|
134
|
+
args?.credential?.['@context'],
|
|
135
|
+
MANDATORY_CREDENTIAL_CONTEXT,
|
|
136
|
+
)
|
|
137
|
+
const credentialType = processEntryToArray(args?.credential?.type, 'VerifiableCredential')
|
|
138
|
+
let issuanceDate = args?.credential?.issuanceDate || new Date().toISOString()
|
|
139
|
+
if (issuanceDate instanceof Date) {
|
|
140
|
+
issuanceDate = issuanceDate.toISOString()
|
|
141
|
+
}
|
|
142
|
+
const credential: CredentialPayload = {
|
|
143
|
+
...args?.credential,
|
|
144
|
+
'@context': credentialContext,
|
|
145
|
+
type: credentialType,
|
|
146
|
+
issuanceDate,
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const issuer = extractIssuer(credential)
|
|
150
|
+
if (!issuer || typeof issuer === 'undefined') {
|
|
151
|
+
throw new Error('invalid_argument: args.credential.issuer must not be empty')
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
let identifier: IIdentifier
|
|
155
|
+
try {
|
|
156
|
+
identifier = await context.agent.didManagerGet({ did: issuer })
|
|
157
|
+
} catch (e) {
|
|
158
|
+
throw new Error(`invalid_argument: args.credential.issuer must be a DID managed by this agent. ${e}`)
|
|
159
|
+
}
|
|
160
|
+
try {
|
|
161
|
+
const { signingKey, verificationMethodId } = await this.findSigningKeyWithId(
|
|
162
|
+
context,
|
|
163
|
+
identifier,
|
|
164
|
+
args.keyRef,
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
return await this.ldCredentialModule.issueLDVerifiableCredential(
|
|
168
|
+
credential,
|
|
169
|
+
identifier.did,
|
|
170
|
+
signingKey,
|
|
171
|
+
verificationMethodId,
|
|
172
|
+
context,
|
|
173
|
+
)
|
|
174
|
+
} catch (error) {
|
|
175
|
+
debug(error)
|
|
176
|
+
return Promise.reject(error)
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/** {@inheritdoc ICredentialIssuerLD.verifyCredentialLD} */
|
|
181
|
+
public async verifyCredentialLD(
|
|
182
|
+
args: IVerifyCredentialLDArgs,
|
|
183
|
+
context: IRequiredContext,
|
|
184
|
+
): Promise<boolean> {
|
|
185
|
+
const credential = args.credential
|
|
186
|
+
return this.ldCredentialModule.verifyCredential(credential, context)
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/** {@inheritdoc ICredentialIssuerLD.verifyPresentationLD} */
|
|
190
|
+
public async verifyPresentationLD(
|
|
191
|
+
args: IVerifyPresentationLDArgs,
|
|
192
|
+
context: IRequiredContext,
|
|
193
|
+
): Promise<boolean> {
|
|
194
|
+
const presentation = args.presentation
|
|
195
|
+
return this.ldCredentialModule.verifyPresentation(presentation, args.challenge, args.domain, context)
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
private async findSigningKeyWithId(
|
|
199
|
+
context: IAgentContext<IResolver>,
|
|
200
|
+
identifier: IIdentifier,
|
|
201
|
+
keyRef?: string,
|
|
202
|
+
): Promise<{ signingKey: IKey; verificationMethodId: string }> {
|
|
203
|
+
const extendedKeys: _ExtendedIKey[] = await mapIdentifierKeysToDoc(identifier, 'assertionMethod', context)
|
|
204
|
+
let supportedTypes = this.ldCredentialModule.ldSuiteLoader.getAllSignatureSuiteTypes()
|
|
205
|
+
let signingKey: _ExtendedIKey | undefined
|
|
206
|
+
let verificationMethodId: string
|
|
207
|
+
if (keyRef) {
|
|
208
|
+
signingKey = extendedKeys.find((k) => k.kid === keyRef)
|
|
209
|
+
}
|
|
210
|
+
if (signingKey && !supportedTypes.includes(signingKey.meta.verificationMethod.type)) {
|
|
211
|
+
debug(
|
|
212
|
+
'WARNING: requested signing key DOES NOT correspond to a supported Signature suite type. Looking for the next best key.',
|
|
213
|
+
)
|
|
214
|
+
signingKey = undefined
|
|
215
|
+
}
|
|
216
|
+
if (!signingKey) {
|
|
217
|
+
if (keyRef) {
|
|
218
|
+
debug(
|
|
219
|
+
'WARNING: no signing key was found that matches the reference provided. Searching for the first available signing key.',
|
|
220
|
+
)
|
|
221
|
+
}
|
|
222
|
+
signingKey = extendedKeys.find((k) => supportedTypes.includes(k.meta.verificationMethod.type))
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (!signingKey) throw Error(`key_not_found: No suitable signing key found for ${identifier.did}`)
|
|
226
|
+
verificationMethodId = signingKey.meta.verificationMethod.id
|
|
227
|
+
return { signingKey, verificationMethodId }
|
|
228
|
+
}
|
|
229
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provides a {@link @veramo/credential-ld#CredentialIssuerLD | plugin} for the {@link @veramo/core#Agent} that implements
|
|
3
|
+
* {@link @veramo/credential-ld#ICredentialIssuerLD} interface.
|
|
4
|
+
*
|
|
5
|
+
* @packageDocumentation
|
|
6
|
+
*/
|
|
7
|
+
export { CredentialIssuerLD } from './action-handler'
|
|
8
|
+
export * from './types'
|
|
9
|
+
export { LdDefaultContexts } from './ld-default-contexts'
|
|
10
|
+
export { VeramoLdSignature } from './ld-suites'
|
|
11
|
+
export * from './suites/EcdsaSecp256k1RecoverySignature2020'
|
|
12
|
+
export * from './suites/Ed25519Signature2018'
|
|
13
|
+
const schema = require('../plugin.schema.json')
|
|
14
|
+
export { schema }
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The LdContextLoader is initialized with a List of Map<string, ContextDoc | Promise<ContextDoc>>
|
|
3
|
+
* that it unifies into a single Map to provide to the documentLoader within
|
|
4
|
+
* the w3c credential module.
|
|
5
|
+
*/
|
|
6
|
+
import { isIterable, OrPromise, RecordLike } from '@veramo/utils'
|
|
7
|
+
import { ContextDoc } from './types'
|
|
8
|
+
|
|
9
|
+
export class LdContextLoader {
|
|
10
|
+
private readonly contexts: Record<string, OrPromise<ContextDoc>>
|
|
11
|
+
|
|
12
|
+
constructor(options: { contextsPaths: RecordLike<OrPromise<ContextDoc>>[] }) {
|
|
13
|
+
this.contexts = {}
|
|
14
|
+
Array.from(options.contextsPaths, (mapItem) => {
|
|
15
|
+
const map = isIterable(mapItem) ? mapItem : Object.entries(mapItem)
|
|
16
|
+
// generate-plugin-schema is failing unless we use the cast to `any[]`
|
|
17
|
+
for (const [key, value] of map as any[]) {
|
|
18
|
+
this.contexts[key] = value
|
|
19
|
+
}
|
|
20
|
+
})
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
has(url: string): boolean {
|
|
24
|
+
return this.contexts[url] !== null && typeof this.contexts[url] !== 'undefined'
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async get(url: string): Promise<ContextDoc> {
|
|
28
|
+
return this.contexts[url]
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CredentialPayload,
|
|
3
|
+
IAgentContext,
|
|
4
|
+
IKey,
|
|
5
|
+
IResolver,
|
|
6
|
+
PresentationPayload,
|
|
7
|
+
VerifiableCredential,
|
|
8
|
+
VerifiablePresentation,
|
|
9
|
+
} from '@veramo/core'
|
|
10
|
+
import Debug from 'debug'
|
|
11
|
+
import { extendContextLoader, purposes } from 'jsonld-signatures'
|
|
12
|
+
import * as vc from 'vc-js'
|
|
13
|
+
import { LdContextLoader } from './ld-context-loader'
|
|
14
|
+
import { LdSuiteLoader } from './ld-suite-loader'
|
|
15
|
+
import { RequiredAgentMethods } from './ld-suites'
|
|
16
|
+
|
|
17
|
+
const AssertionProofPurpose = purposes.AssertionProofPurpose
|
|
18
|
+
|
|
19
|
+
const debug = Debug('veramo:w3c:ld-credential-module')
|
|
20
|
+
|
|
21
|
+
export class LdCredentialModule {
|
|
22
|
+
/**
|
|
23
|
+
* TODO: General Implementation Notes
|
|
24
|
+
* - (SOLVED) EcdsaSecp256k1Signature2019 (Signature) and EcdsaSecp256k1VerificationKey2019 (Key)
|
|
25
|
+
* are not useable right now, since they are not able to work with blockChainId and ECRecover.
|
|
26
|
+
* - DID Fragment Resolution.
|
|
27
|
+
* - Key Manager and Verification Methods: Veramo currently implements no link between those.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
private ldContextLoader: LdContextLoader
|
|
31
|
+
ldSuiteLoader: LdSuiteLoader
|
|
32
|
+
constructor(options: { ldContextLoader: LdContextLoader; ldSuiteLoader: LdSuiteLoader }) {
|
|
33
|
+
this.ldContextLoader = options.ldContextLoader
|
|
34
|
+
this.ldSuiteLoader = options.ldSuiteLoader
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
getDocumentLoader(context: IAgentContext<IResolver>) {
|
|
38
|
+
return extendContextLoader(async (url: string) => {
|
|
39
|
+
// console.log(`resolving context for: ${url}`)
|
|
40
|
+
|
|
41
|
+
// did resolution
|
|
42
|
+
if (url.toLowerCase().startsWith('did:')) {
|
|
43
|
+
const resolutionResult = await context.agent.resolveDid({ didUrl: url })
|
|
44
|
+
const didDoc = resolutionResult.didDocument
|
|
45
|
+
|
|
46
|
+
if (!didDoc) return
|
|
47
|
+
|
|
48
|
+
// currently Veramo LD suites can modify the resolution response for DIDs from
|
|
49
|
+
// the document Loader. This allows to fix incompatibilities between DID Documents
|
|
50
|
+
// and LD suites to be fixed specifically within the Veramo LD Suites definition
|
|
51
|
+
this.ldSuiteLoader.getAllSignatureSuites().forEach((x) => x.preDidResolutionModification(url, didDoc))
|
|
52
|
+
|
|
53
|
+
// console.log(`Returning from Documentloader: ${JSON.stringify(returnDocument)}`)
|
|
54
|
+
return {
|
|
55
|
+
contextUrl: null,
|
|
56
|
+
documentUrl: url,
|
|
57
|
+
document: didDoc,
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (this.ldContextLoader.has(url)) {
|
|
62
|
+
const contextDoc = await this.ldContextLoader.get(url)
|
|
63
|
+
return {
|
|
64
|
+
contextUrl: null,
|
|
65
|
+
documentUrl: url,
|
|
66
|
+
document: contextDoc,
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
debug('WARNING: Possible unknown context/identifier for', url)
|
|
71
|
+
console.log(`WARNING: Possible unknown context/identifier for: ${url}`)
|
|
72
|
+
|
|
73
|
+
return vc.defaultDocumentLoader(url)
|
|
74
|
+
})
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async issueLDVerifiableCredential(
|
|
78
|
+
credential: CredentialPayload,
|
|
79
|
+
issuerDid: string,
|
|
80
|
+
key: IKey,
|
|
81
|
+
verificationMethodId: string,
|
|
82
|
+
context: IAgentContext<RequiredAgentMethods>,
|
|
83
|
+
): Promise<VerifiableCredential> {
|
|
84
|
+
const suite = this.ldSuiteLoader.getSignatureSuiteForKeyType(key.type)
|
|
85
|
+
const documentLoader = this.getDocumentLoader(context)
|
|
86
|
+
|
|
87
|
+
// some suites can modify the incoming credential (e.g. add required contexts)W
|
|
88
|
+
suite.preSigningCredModification(credential)
|
|
89
|
+
|
|
90
|
+
return await vc.issue({
|
|
91
|
+
credential,
|
|
92
|
+
suite: suite.getSuiteForSigning(key, issuerDid, verificationMethodId, context),
|
|
93
|
+
documentLoader,
|
|
94
|
+
compactProof: false,
|
|
95
|
+
})
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async signLDVerifiablePresentation(
|
|
99
|
+
presentation: PresentationPayload,
|
|
100
|
+
holderDid: string,
|
|
101
|
+
key: IKey,
|
|
102
|
+
verificationMethodId: string,
|
|
103
|
+
challenge: string | undefined,
|
|
104
|
+
domain: string | undefined,
|
|
105
|
+
context: IAgentContext<RequiredAgentMethods>,
|
|
106
|
+
): Promise<VerifiablePresentation> {
|
|
107
|
+
const suite = this.ldSuiteLoader.getSignatureSuiteForKeyType(key.type)
|
|
108
|
+
const documentLoader = this.getDocumentLoader(context)
|
|
109
|
+
|
|
110
|
+
suite.preSigningPresModification(presentation)
|
|
111
|
+
|
|
112
|
+
return await vc.signPresentation({
|
|
113
|
+
presentation,
|
|
114
|
+
suite: suite.getSuiteForSigning(key, holderDid, verificationMethodId, context),
|
|
115
|
+
challenge,
|
|
116
|
+
domain,
|
|
117
|
+
documentLoader,
|
|
118
|
+
purpose: new AssertionProofPurpose(),
|
|
119
|
+
compactProof: false,
|
|
120
|
+
})
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async verifyCredential(
|
|
124
|
+
credential: VerifiableCredential,
|
|
125
|
+
context: IAgentContext<IResolver>,
|
|
126
|
+
): Promise<boolean> {
|
|
127
|
+
const result = await vc.verifyCredential({
|
|
128
|
+
credential,
|
|
129
|
+
suite: this.ldSuiteLoader.getAllSignatureSuites().map((x) => x.getSuiteForVerification()),
|
|
130
|
+
documentLoader: this.getDocumentLoader(context),
|
|
131
|
+
purpose: new AssertionProofPurpose(),
|
|
132
|
+
compactProof: false,
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
if (result.verified) return true
|
|
136
|
+
|
|
137
|
+
// NOT verified.
|
|
138
|
+
|
|
139
|
+
// result can include raw Error
|
|
140
|
+
debug(`Error verifying LD Verifiable Credential: ${JSON.stringify(result, null, 2)}`)
|
|
141
|
+
// console.log(JSON.stringify(result, null, 2));
|
|
142
|
+
throw Error('Error verifying LD Verifiable Credential')
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
async verifyPresentation(
|
|
146
|
+
presentation: VerifiablePresentation,
|
|
147
|
+
challenge: string | undefined,
|
|
148
|
+
domain: string | undefined,
|
|
149
|
+
context: IAgentContext<IResolver>,
|
|
150
|
+
): Promise<boolean> {
|
|
151
|
+
const result = await vc.verify({
|
|
152
|
+
presentation,
|
|
153
|
+
suite: this.ldSuiteLoader.getAllSignatureSuites().map((x) => x.getSuiteForVerification()),
|
|
154
|
+
documentLoader: this.getDocumentLoader(context),
|
|
155
|
+
challenge,
|
|
156
|
+
domain,
|
|
157
|
+
purpose: new AssertionProofPurpose(),
|
|
158
|
+
compactProof: false,
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
if (result.verified) return true
|
|
162
|
+
|
|
163
|
+
// NOT verified.
|
|
164
|
+
|
|
165
|
+
// result can include raw Error
|
|
166
|
+
console.log(`Error verifying LD Verifiable Presentation`)
|
|
167
|
+
console.log(JSON.stringify(result, null, 2))
|
|
168
|
+
throw Error('Error verifying LD Verifiable Presentation')
|
|
169
|
+
}
|
|
170
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import * as fs from 'fs'
|
|
2
|
+
import * as path from 'path'
|
|
3
|
+
import { ContextDoc } from './types'
|
|
4
|
+
|
|
5
|
+
async function _read(_path: string): Promise<ContextDoc> {
|
|
6
|
+
const contextDefinition = await fs.promises.readFile(path.join(__dirname, '../contexts', _path), {
|
|
7
|
+
encoding: 'utf8',
|
|
8
|
+
})
|
|
9
|
+
return JSON.parse(contextDefinition)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Provides a hardcoded map of common context definitions
|
|
14
|
+
*/
|
|
15
|
+
export const LdDefaultContexts = new Map([
|
|
16
|
+
['https://www.w3.org/2018/credentials/v1', _read('w3_2018_credentials_v1.jsonld')],
|
|
17
|
+
['https://www.w3.org/ns/did/v1', _read('security_context_v1.jsonld')],
|
|
18
|
+
['https://w3id.org/did/v0.11', _read('did_v0.11.jsonld')],
|
|
19
|
+
|
|
20
|
+
['https://veramo.io/contexts/socialmedia/v1', _read('socialmedia-v1.jsonld')],
|
|
21
|
+
['https://veramo.io/contexts/kyc/v1', _read('kyc-v1.jsonld')],
|
|
22
|
+
['https://veramo.io/contexts/profile/v1', _read('profile-v1.jsonld')],
|
|
23
|
+
['https://ns.did.ai/transmute/v1', _read('transmute_v1.jsonld')],
|
|
24
|
+
[
|
|
25
|
+
'https://identity.foundation/EcdsaSecp256k1RecoverySignature2020/lds-ecdsa-secp256k1-recovery2020-0.0.jsonld',
|
|
26
|
+
_read('lds-ecdsa-secp256k1-recovery2020-0.0.jsonld'),
|
|
27
|
+
],
|
|
28
|
+
['https://w3id.org/security/suites/ed25519-2018/v1', _read('ed25519-signature-2018-v1.jsonld')],
|
|
29
|
+
['https://w3id.org/security/suites/x25519-2019/v1', _read('X25519KeyAgreementKey2019.jsonld')],
|
|
30
|
+
])
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { VeramoLdSignature } from './ld-suites'
|
|
2
|
+
import { TKeyType } from '@veramo/core'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Initializes a list of Veramo-wrapped LD Signature suites and exposes those to the Agent Module
|
|
6
|
+
*/
|
|
7
|
+
export class LdSuiteLoader {
|
|
8
|
+
constructor(options: { veramoLdSignatures: VeramoLdSignature[] }) {
|
|
9
|
+
options.veramoLdSignatures.forEach((obj) => {
|
|
10
|
+
this.signatureMap[obj.getSupportedVeramoKeyType()] = obj
|
|
11
|
+
})
|
|
12
|
+
}
|
|
13
|
+
private signatureMap: Record<string, VeramoLdSignature> = {}
|
|
14
|
+
|
|
15
|
+
getSignatureSuiteForKeyType(type: TKeyType) {
|
|
16
|
+
const suite = this.signatureMap[type]
|
|
17
|
+
if (suite) return suite
|
|
18
|
+
|
|
19
|
+
throw new Error('No Veramo LD Signature Suite for ' + type)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
getAllSignatureSuites() {
|
|
23
|
+
return Object.values(this.signatureMap)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
getAllSignatureSuiteTypes() {
|
|
27
|
+
return Object.values(this.signatureMap).map((x) => x.getSupportedVerificationType())
|
|
28
|
+
}
|
|
29
|
+
}
|
package/src/ld-suites.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CredentialPayload,
|
|
3
|
+
IAgentContext,
|
|
4
|
+
IKey,
|
|
5
|
+
IKeyManager,
|
|
6
|
+
IResolver,
|
|
7
|
+
PresentationPayload,
|
|
8
|
+
TKeyType,
|
|
9
|
+
} from '@veramo/core'
|
|
10
|
+
import { DIDDocument } from 'did-resolver/src/resolver'
|
|
11
|
+
|
|
12
|
+
export type RequiredAgentMethods = IResolver & Pick<IKeyManager, 'keyManagerGet' | 'keyManagerSign'>
|
|
13
|
+
|
|
14
|
+
export abstract class VeramoLdSignature {
|
|
15
|
+
// LinkedDataSignature Suites according to
|
|
16
|
+
// https://github.com/digitalbazaar/jsonld-signatures/blob/main/lib/suites/LinkedDataSignature.js
|
|
17
|
+
// Add type definition as soon as https://github.com/digitalbazaar/jsonld-signatures
|
|
18
|
+
// supports those.
|
|
19
|
+
|
|
20
|
+
abstract getSupportedVerificationType(): string
|
|
21
|
+
|
|
22
|
+
abstract getSupportedVeramoKeyType(): TKeyType
|
|
23
|
+
|
|
24
|
+
abstract getSuiteForSigning(
|
|
25
|
+
key: IKey,
|
|
26
|
+
issuerDid: string,
|
|
27
|
+
verificationMethodId: string,
|
|
28
|
+
context: IAgentContext<RequiredAgentMethods>,
|
|
29
|
+
): any
|
|
30
|
+
|
|
31
|
+
abstract getSuiteForVerification(): any
|
|
32
|
+
|
|
33
|
+
abstract preDidResolutionModification(didUrl: string, didDoc: DIDDocument): void
|
|
34
|
+
|
|
35
|
+
abstract preSigningCredModification(credential: CredentialPayload): void
|
|
36
|
+
|
|
37
|
+
preSigningPresModification(presentation: PresentationPayload): void {
|
|
38
|
+
// TODO: Remove invalid field 'verifiers' from Presentation. Needs to be adapted for LD credentials
|
|
39
|
+
// Only remove empty array (vc.signPresentation will throw then)
|
|
40
|
+
const sanitizedPresentation = presentation as any
|
|
41
|
+
if (sanitizedPresentation.verifier.length == 0) {
|
|
42
|
+
delete sanitizedPresentation.verifier
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|