Skip to content

Commit 1a31c1c

Browse files
tnotheismergify[bot]Magnus-Kuhnjkoenig134
authored
Add EUDIPLO tests (#831)
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> Co-authored-by: mkuhn <magnus.kuhn@js-soft.com> Co-authored-by: Julian König <33655937+jkoenig134@users.noreply.github.com> Co-authored-by: Magnus Kuhn <127854942+Magnus-Kuhn@users.noreply.github.com>
1 parent 6d1acce commit 1a31c1c

21 files changed

Lines changed: 2900 additions & 1068 deletions

.dev/eudiplo-assets/service.db

268 KB
Binary file not shown.
15.7 KB
Binary file not shown.

package-lock.json

Lines changed: 2519 additions & 874 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@
3030
"test:teardown": "docker compose -f .dev/compose.yml down -fsv"
3131
},
3232
"devDependencies": {
33+
"@babel/preset-env": "^7.28.5",
34+
"@babel/preset-typescript": "^7.28.5",
3335
"@js-soft/eslint-config-ts": "^2.0.4",
3436
"@js-soft/license-check": "^1.0.10",
3537
"@types/jest": "^30.0.0",

packages/app-runtime/package.json

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,16 @@
4343
"testEnvironment": "node",
4444
"testTimeout": 60000,
4545
"transform": {
46-
"^.+\\.ts$": [
46+
"^.+\\.(t|j)s$": [
4747
"ts-jest",
4848
{
4949
"tsconfig": "test/tsconfig.json"
5050
}
5151
]
52-
}
52+
},
53+
"transformIgnorePatterns": [
54+
"<rootDir>/node_modules/(?!(@noble|@stablelib|@credo\\-ts/core/node_modules/uuid)/)"
55+
]
5356
},
5457
"dependencies": {
5558
"@js-soft/docdb-access-loki": "^1.3.1",

packages/app-runtime/test/tsconfig.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
"compilerOptions": {
44
"baseUrl": "../",
55
"noEmit": true,
6-
"composite": false
6+
"composite": false,
7+
"allowJs": true // we want to use the ts compiler to compile ESM node modules into CJS which is understood by jest. This is required since those node modules are javascript.
78
},
89
"files": ["../../../node_modules/jest-expect-message/types/index.d.ts"],
910
"include": ["**/*.ts", "../src/**/*.ts"],

packages/consumption/package.json

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,17 +50,20 @@
5050
"testEnvironment": "node",
5151
"testTimeout": 60000,
5252
"transform": {
53-
"^.+\\.ts$": [
53+
"^.+\\.(t|j)s$": [
5454
"ts-jest",
5555
{
5656
"tsconfig": "test/tsconfig.json"
5757
}
5858
]
59-
}
59+
},
60+
"transformIgnorePatterns": [
61+
"<rootDir>/node_modules/(?!(@noble|@stablelib|@credo\\-ts/core/node_modules/uuid)/)"
62+
]
6063
},
6164
"dependencies": {
62-
"@credo-ts/core": "v0.6.0-alpha-20250919145226",
63-
"@credo-ts/openid4vc": "v0.6.0-alpha-20250919145226",
65+
"@credo-ts/core": "v0.6.0-alpha-20251110135447",
66+
"@credo-ts/openid4vc": "v0.6.0-alpha-20251110135447",
6467
"@js-soft/docdb-querytranslator": "^1.1.6",
6568
"@js-soft/ts-serval": "2.0.14",
6669
"@js-soft/ts-utils": "2.3.5",

packages/consumption/src/modules/openid4vc/local/BaseAgent.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,14 @@ import {
44
DependencyManager,
55
DidKey,
66
InjectionSymbols,
7+
Kms,
78
LogLevel,
9+
StorageVersionRecord,
810
type InitConfig,
911
type KeyDidCreateOptions,
1012
type ModulesMap,
1113
type VerificationMethod
1214
} from "@credo-ts/core";
13-
import { KeyManagementModuleConfig } from "@credo-ts/core/build/modules/kms";
1415
import { AccountController } from "@nmshd/transport";
1516
import { EventEmitter } from "events";
1617
import webSocket from "ws";
@@ -70,10 +71,9 @@ export class BaseAgent<AgentModules extends ModulesMap> {
7071
public async initializeAgent(privateKey: string): Promise<void> {
7172
// as we are not using askar we need to set the storage version
7273
const storage = this.agent.dependencyManager.resolve<EnmeshedStorageService<any>>(InjectionSymbols.StorageService);
73-
const versionRecord = { id: "STORAGE_VERSION_RECORD_ID", storageVersion: "0.5.0", value: "0.5.0" };
74-
await storage.save(this.agent.context, versionRecord);
74+
await storage.save(this.agent.context, new StorageVersionRecord({ storageVersion: "0.5.0" }));
7575

76-
const kmsConfig = this.agent.dependencyManager.resolve(KeyManagementModuleConfig);
76+
const kmsConfig = this.agent.dependencyManager.resolve(Kms.KeyManagementModuleConfig);
7777
kmsConfig.registerBackend(new EnmshedHolderKeyManagmentService());
7878

7979
if (kmsConfig.backends.length === 0) throw new Error("No KMS backend registered");

packages/consumption/src/modules/openid4vc/local/EnmeshedHolderKeyManagmentService.ts

Lines changed: 20 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,4 @@
1-
import { AgentContext } from "@credo-ts/core";
2-
import {
3-
KeyManagementService,
4-
KmsCreateKeyOptions,
5-
KmsCreateKeyReturn,
6-
KmsCreateKeyType,
7-
KmsDecryptOptions,
8-
KmsDecryptReturn,
9-
KmsDeleteKeyOptions,
10-
KmsEncryptOptions,
11-
KmsEncryptReturn,
12-
KmsImportKeyOptions,
13-
KmsImportKeyReturn,
14-
KmsJwkPrivate,
15-
KmsJwkPublic,
16-
KmsOperation,
17-
KmsRandomBytesOptions,
18-
KmsRandomBytesReturn,
19-
KmsSignOptions,
20-
KmsSignReturn,
21-
KmsVerifyOptions,
22-
KmsVerifyReturn
23-
} from "@credo-ts/core/build/modules/kms";
1+
import { AgentContext, Kms } from "@credo-ts/core";
242
import { ec as EC } from "elliptic";
253
import _sodium from "libsodium-wrappers";
264
import sjcl from "sjcl";
@@ -31,7 +9,7 @@ export interface JwkKeyPair {
319
keyType?: string;
3210
}
3311

34-
export class EnmshedHolderKeyManagmentService implements KeyManagementService {
12+
export class EnmshedHolderKeyManagmentService implements Kms.KeyManagementService {
3513
public static readonly backend = "fakeKeyManagementService";
3614

3715
public readonly backend = EnmshedHolderKeyManagmentService.backend;
@@ -70,7 +48,7 @@ export class EnmshedHolderKeyManagmentService implements KeyManagementService {
7048
// console.log(`FKM: global instance state ${JSON.stringify(Array.from((globalThis as any).fakeKeyStorage.entries()))}`);
7149
}
7250

73-
public isOperationSupported(agentContext: AgentContext, operation: KmsOperation): boolean {
51+
public isOperationSupported(agentContext: AgentContext, operation: Kms.KmsOperation): boolean {
7452
agentContext.config.logger.debug(`EKM: Checking if operation is supported: ${JSON.stringify(operation)}`);
7553
if (operation.operation === "createKey") {
7654
if (operation.type.kty === "OKP") {
@@ -98,18 +76,16 @@ export class EnmshedHolderKeyManagmentService implements KeyManagementService {
9876
}
9977
return false;
10078
}
101-
102-
public getPublicKey(agentContext: AgentContext, keyId: string): Promise<KmsJwkPublic> {
79+
public getPublicKey(agentContext: AgentContext, keyId: string): Promise<Kms.KmsJwkPublic> {
10380
const keyPair = this.keystore.get(keyId);
10481
if (!keyPair) {
10582
agentContext.config.logger.error(`EKM: Key with id ${keyId} not found`);
10683
throw new Error(`Key with id ${keyId} not found`);
10784
}
10885

109-
return Promise.resolve((JSON.parse(keyPair) as JwkKeyPair).publicKey as KmsJwkPublic);
86+
return Promise.resolve((JSON.parse(keyPair) as JwkKeyPair).publicKey as Kms.KmsJwkPublic);
11087
}
111-
112-
public async createKey<Type extends KmsCreateKeyType>(agentContext: AgentContext, options: KmsCreateKeyOptions<Type>): Promise<KmsCreateKeyReturn<Type>> {
88+
public async createKey<Type extends Kms.KmsCreateKeyType>(agentContext: AgentContext, options: Kms.KmsCreateKeyOptions<Type>): Promise<Kms.KmsCreateKeyReturn<Type>> {
11389
options.keyId ??= "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) {
11490
// Use libsodium's randombytes_uniform for secure random number generation
11591
const r = _sodium.randombytes_uniform(16);
@@ -151,8 +127,8 @@ export class EnmshedHolderKeyManagmentService implements KeyManagementService {
151127
this.updateGlobalInstance(this.keystore);
152128
return await Promise.resolve({
153129
keyId: options.keyId,
154-
publicJwk: publicJwk as KmsJwkPublic
155-
} as KmsCreateKeyReturn<Type>);
130+
publicJwk: publicJwk as Kms.KmsJwkPublic
131+
} as Kms.KmsCreateKeyReturn<Type>);
156132
}
157133

158134
await _sodium.ready;
@@ -186,16 +162,14 @@ export class EnmshedHolderKeyManagmentService implements KeyManagementService {
186162
this.updateGlobalInstance(this.keystore);
187163
return await Promise.resolve({
188164
keyId: options.keyId,
189-
publicJwk: publicJwk as KmsJwkPublic
190-
} as KmsCreateKeyReturn<Type>);
165+
publicJwk: publicJwk as Kms.KmsJwkPublic
166+
} as Kms.KmsCreateKeyReturn<Type>);
191167
}
192-
193-
public importKey<Jwk extends KmsJwkPrivate>(agentContext: AgentContext, options: KmsImportKeyOptions<Jwk>): Promise<KmsImportKeyReturn<Jwk>> {
168+
public importKey<Jwk extends Kms.KmsJwkPrivate>(agentContext: AgentContext, options: Kms.KmsImportKeyOptions<Jwk>): Promise<Kms.KmsImportKeyReturn<Jwk>> {
194169
agentContext.config.logger.debug(`EKM: Importing key with ${JSON.stringify(options)}`);
195170
throw new Error("Method not implemented.");
196171
}
197-
198-
public deleteKey(agentContext: AgentContext, options: KmsDeleteKeyOptions): Promise<boolean> {
172+
public deleteKey(agentContext: AgentContext, options: Kms.KmsDeleteKeyOptions): Promise<boolean> {
199173
if (this.keystore.has(options.keyId)) {
200174
agentContext.config.logger.debug(`EKM: Deleting key with id ${options.keyId}`);
201175
this.keystore.delete(options.keyId);
@@ -204,8 +178,7 @@ export class EnmshedHolderKeyManagmentService implements KeyManagementService {
204178
}
205179
throw new Error(`key with id ${options.keyId} not found. and cannot be deleted`);
206180
}
207-
208-
public async sign(agentContext: AgentContext, options: KmsSignOptions): Promise<KmsSignReturn> {
181+
public async sign(agentContext: AgentContext, options: Kms.KmsSignOptions): Promise<Kms.KmsSignReturn> {
209182
agentContext.config.logger.debug(`EKM: Signing data with key id ${options.keyId} using algorithm ${options.algorithm}`);
210183

211184
const stringifiedKeyPair = this.keystore.get(options.keyId);
@@ -236,7 +209,7 @@ export class EnmshedHolderKeyManagmentService implements KeyManagementService {
236209

237210
return await Promise.resolve({
238211
signature: signatureBytes
239-
} as KmsSignReturn);
212+
} as Kms.KmsSignReturn);
240213
}
241214

242215
await _sodium.ready;
@@ -263,11 +236,11 @@ export class EnmshedHolderKeyManagmentService implements KeyManagementService {
263236
const signature = sodium.crypto_sign_detached(options.data, fullPrivateKeyBytes);
264237

265238
return {
266-
signature: signature
239+
signature: signature as Uint8Array<ArrayBuffer> // I hope this cast doesn't paper over something
267240
};
268241
}
269242

270-
public verify(agentContext: AgentContext, options: KmsVerifyOptions): Promise<KmsVerifyReturn> {
243+
public verify(agentContext: AgentContext, options: Kms.KmsVerifyOptions): Promise<Kms.KmsVerifyReturn> {
271244
agentContext.config.logger.debug(`EKM: Verifying signature with key id ${options.key.keyId} using algorithm ${options.algorithm}`);
272245
// Use P-256 (aka secp256r1)
273246
const ec = new EC("p256");
@@ -293,7 +266,7 @@ export class EnmshedHolderKeyManagmentService implements KeyManagementService {
293266
const dataHash = ec.hash().update(options.data).digest();
294267
try {
295268
const verified = key.verify(dataHash, signature);
296-
return Promise.resolve({ verified: verified } as KmsVerifyReturn);
269+
return Promise.resolve({ verified: verified } as Kms.KmsVerifyReturn);
297270
} catch (e) {
298271
agentContext.config.logger.error(`EKM: Error during signature verification: ${e}`);
299272
throw e;
@@ -387,7 +360,7 @@ export class EnmshedHolderKeyManagmentService implements KeyManagementService {
387360
return hashBuf.subarray(0, keyLength / 8);
388361
}
389362

390-
public encrypt(agentContext: AgentContext, options: KmsEncryptOptions): Promise<KmsEncryptReturn> {
363+
public encrypt(agentContext: AgentContext, options: Kms.KmsEncryptOptions): Promise<Kms.KmsEncryptReturn> {
391364
try {
392365
// encryption via A-256-GCM
393366
// we will call the services side bob and the incoming side alice
@@ -437,12 +410,11 @@ export class EnmshedHolderKeyManagmentService implements KeyManagementService {
437410
}
438411
}
439412

440-
public decrypt(agentContext: AgentContext, options: KmsDecryptOptions): Promise<KmsDecryptReturn> {
413+
public decrypt(agentContext: AgentContext, options: Kms.KmsDecryptOptions): Promise<Kms.KmsDecryptReturn> {
441414
agentContext.config.logger.debug(`EKM: Decrypting data with key id ${options.key.keyId} using options ${options}`);
442415
throw new Error("Method not implemented.");
443416
}
444-
445-
public randomBytes(agentContext: AgentContext, options: KmsRandomBytesOptions): KmsRandomBytesReturn {
417+
public randomBytes(agentContext: AgentContext, options: Kms.KmsRandomBytesOptions): Kms.KmsRandomBytesReturn {
446418
agentContext.config.logger.debug(`EKM: Generating ${options.length} random bytes`);
447419
return _sodium.randombytes_buf(options.length); // Uint8Array
448420
}

packages/consumption/src/modules/openid4vc/local/EnmeshedStorageService.ts

Lines changed: 11 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -194,34 +194,27 @@ export class EnmeshedStorageService<T extends BaseRecord> implements StorageServ
194194
agentContext.config.logger.debug(`Finding records by query ${JSON.stringify(query)} and options ${JSON.stringify(queryOptions)}`);
195195
const records: T[] = [];
196196
for (const record of await this.getAll(agentContext, recordClass)) {
197-
let match = true;
198-
for (const [key, value] of Object.entries(query)) {
199-
if ((record as any)[key] !== value) {
200-
match = false;
201-
break;
202-
}
203-
}
204-
if (match) {
197+
if (this.matchesQuery(record, query)) {
205198
records.push(record);
206199
}
207200
}
208201
if (records.length === 0) {
209202
// try to recover over local storage - temporary fix
210203
for (const record of this.storage.values()) {
211-
let match = true;
212-
// there may be keys labeled with an $or - solve them accordingly
213-
// TODO: $or and other operators not yet supported
214-
for (const [key, value] of Object.entries(query)) {
215-
if ((record as any)[key] !== value) {
216-
match = false;
217-
break;
218-
}
219-
}
220-
if (match) {
204+
if (this.matchesQuery(record, query)) {
221205
records.push(record);
222206
}
223207
}
224208
}
225209
return records;
226210
}
211+
212+
private matchesQuery(record: BaseRecord, query: Query<T>): boolean {
213+
return Object.entries(query).every(([key, value]) => {
214+
if (key === "$or") {
215+
return (value as any[]).some((subquery) => this.matchesQuery(record, subquery));
216+
}
217+
return record.getTags()[key] === value;
218+
});
219+
}
227220
}

0 commit comments

Comments
 (0)