Skip to content

Commit ae2f6d9

Browse files
authored
Get rid of the fakeKeyStorage and implement it the enmeshed way (#853)
* feat: add keyStorage * feat: use keyStorage * fix: satisfy eslint * chore: rename keymanagement * fix: mongodb * chore: rename
1 parent 1a31c1c commit ae2f6d9

6 files changed

Lines changed: 136 additions & 84 deletions

File tree

packages/consumption/src/modules/openid4vc/OpenId4VcController.ts

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,28 @@ import { ConsumptionBaseController } from "../../consumption/ConsumptionBaseCont
55
import { ConsumptionController } from "../../consumption/ConsumptionController";
66
import { ConsumptionControllerName } from "../../consumption/ConsumptionControllerName";
77
import { Holder } from "./local/Holder";
8+
import { KeyStorage } from "./local/KeyStorage";
89

910
export class OpenId4VcController extends ConsumptionBaseController {
11+
private keyStorage: KeyStorage;
12+
1013
public constructor(parent: ConsumptionController) {
1114
super(ConsumptionControllerName.OpenId4VcController, parent);
1215
}
1316

17+
public override async init(): Promise<this> {
18+
const collection = await this.parent.accountController.getSynchronizedCollection("openid4vc-keys");
19+
this.keyStorage = new KeyStorage(collection, this._log);
20+
21+
return this;
22+
}
23+
1424
private get fetchInstance(): typeof fetch {
1525
return this.parent.consumptionConfig.fetchInstance ?? fetch;
1626
}
1727

1828
public async fetchCredentialOffer(credentialOfferUrl: string): Promise<{ data: string }> {
19-
const holder = new Holder(this.parent.accountController, this.parent.attributes, this.fetchInstance);
29+
const holder = new Holder(this.keyStorage, this.parent.accountController, this.parent.attributes, this.fetchInstance);
2030
await holder.initializeAgent("96213c3d7fc8d4d6754c7a0fd969598e");
2131
const res = await holder.resolveCredentialOffer(credentialOfferUrl);
2232
return {
@@ -29,7 +39,7 @@ export class OpenId4VcController extends ConsumptionBaseController {
2939
requestedCredentialOffers: string[],
3040
pinCode?: string
3141
): Promise<{ data: string; id: string; type: string; displayInformation: string | undefined }> {
32-
const holder = new Holder(this.parent.accountController, this.parent.attributes, this.fetchInstance);
42+
const holder = new Holder(this.keyStorage, this.parent.accountController, this.parent.attributes, this.fetchInstance);
3343
await holder.initializeAgent("96213c3d7fc8d4d6754c7a0fd969598e");
3444
const credentialOffer = JSON.parse(fetchedCredentialOffer);
3545
const credentials = await holder.requestAndStoreCredentials(credentialOffer, { credentialsToRequest: requestedCredentialOffers, txCode: pinCode });
@@ -47,7 +57,7 @@ export class OpenId4VcController extends ConsumptionBaseController {
4757
}
4858

4959
public async processCredentialOffer(credentialOffer: string): Promise<{ data: string; id: string; type: string; displayInformation: string | undefined }> {
50-
const holder = new Holder(this.parent.accountController, this.parent.attributes, this.fetchInstance);
60+
const holder = new Holder(this.keyStorage, this.parent.accountController, this.parent.attributes, this.fetchInstance);
5161
await holder.initializeAgent("96213c3d7fc8d4d6754c7a0fd969598e");
5262
const res = await holder.resolveCredentialOffer(credentialOffer);
5363
const credentials = await holder.requestAndStoreCredentials(res, { credentialsToRequest: Object.keys(res.offeredCredentialConfigurations) });
@@ -67,7 +77,7 @@ export class OpenId4VcController extends ConsumptionBaseController {
6777
public async resolveAuthorizationRequest(
6878
requestUrl: string
6979
): Promise<{ authorizationRequest: OpenId4VpResolvedAuthorizationRequest; usedCredentials: { id: string; data: string; type: string; displayInformation?: string }[] }> {
70-
const holder = new Holder(this.parent.accountController, this.parent.attributes, this.fetchInstance);
80+
const holder = new Holder(this.keyStorage, this.parent.accountController, this.parent.attributes, this.fetchInstance);
7181
await holder.initializeAgent("96213c3d7fc8d4d6754c7a0fd969598e");
7282
const authorizationRequest = await holder.resolveAuthorizationRequest(requestUrl);
7383

@@ -94,7 +104,7 @@ export class OpenId4VcController extends ConsumptionBaseController {
94104
public async acceptAuthorizationRequest(
95105
authorizationRequest: OpenId4VpResolvedAuthorizationRequest
96106
): Promise<{ status: number; message: string | Record<string, unknown> | null }> {
97-
const holder = new Holder(this.parent.accountController, this.parent.attributes, this.fetchInstance);
107+
const holder = new Holder(this.keyStorage, this.parent.accountController, this.parent.attributes, this.fetchInstance);
98108
await holder.initializeAgent("96213c3d7fc8d4d6754c7a0fd969598e");
99109
// parse the credential type to be sdjwt
100110

@@ -105,7 +115,7 @@ export class OpenId4VcController extends ConsumptionBaseController {
105115
}
106116

107117
public async getVerifiableCredentials(ids?: string[]): Promise<{ id: string; data: string; type: string; displayInformation?: string }[]> {
108-
const holder = new Holder(this.parent.accountController, this.parent.attributes, this.fetchInstance);
118+
const holder = new Holder(this.keyStorage, this.parent.accountController, this.parent.attributes, this.fetchInstance);
109119
await holder.initializeAgent("96213c3d7fc8d4d6754c7a0fd969598e");
110120

111121
const credentials = await holder.getVerifiableCredentials(ids);

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

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { AttributesController } from "../../attributes";
1919
import { EnmeshedHolderFileSystem } from "./EnmeshedHolderFileSystem";
2020
import { EnmshedHolderKeyManagmentService } from "./EnmeshedHolderKeyManagmentService";
2121
import { EnmeshedStorageService } from "./EnmeshedStorageService";
22+
import { KeyStorage } from "./KeyStorage";
2223

2324
export class BaseAgent<AgentModules extends ModulesMap> {
2425
public config: InitConfig;
@@ -29,27 +30,21 @@ export class BaseAgent<AgentModules extends ModulesMap> {
2930
public verificationMethod!: VerificationMethod;
3031

3132
public constructor(
32-
public readonly port: number,
33-
public readonly name: string,
34-
public readonly modules: AgentModules,
35-
public readonly accountController: AccountController,
36-
public readonly attributeController: AttributesController,
33+
private readonly keyStorage: KeyStorage,
34+
modules: AgentModules,
35+
accountController: AccountController,
36+
attributeController: AttributesController,
3737
fetchInstance: typeof fetch
3838
) {
39-
this.name = name;
40-
this.port = port;
41-
4239
const config = {
4340
allowInsecureHttpUrls: true,
4441
logger: new ConsoleLogger(LogLevel.off)
4542
} satisfies InitConfig;
4643

4744
this.config = config;
4845

49-
this.accountController = accountController;
50-
this.attributeController = attributeController;
5146
const dependencyManager = new DependencyManager();
52-
dependencyManager.registerInstance(InjectionSymbols.StorageService, new EnmeshedStorageService(accountController, attributeController));
47+
dependencyManager.registerInstance(InjectionSymbols.StorageService, new EnmeshedStorageService(accountController, attributeController, this.keyStorage));
5348
this.agent = new Agent(
5449
{
5550
config,
@@ -74,7 +69,7 @@ export class BaseAgent<AgentModules extends ModulesMap> {
7469
await storage.save(this.agent.context, new StorageVersionRecord({ storageVersion: "0.5.0" }));
7570

7671
const kmsConfig = this.agent.dependencyManager.resolve(Kms.KeyManagementModuleConfig);
77-
kmsConfig.registerBackend(new EnmshedHolderKeyManagmentService());
72+
kmsConfig.registerBackend(new EnmshedHolderKeyManagmentService(this.keyStorage));
7873

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

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

Lines changed: 26 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { AgentContext, Kms } from "@credo-ts/core";
22
import { ec as EC } from "elliptic";
33
import _sodium from "libsodium-wrappers";
44
import sjcl from "sjcl";
5+
import { KeyStorage } from "./KeyStorage";
56

67
export interface JwkKeyPair {
78
publicKey: JsonWebKey;
@@ -10,10 +11,9 @@ export interface JwkKeyPair {
1011
}
1112

1213
export class EnmshedHolderKeyManagmentService implements Kms.KeyManagementService {
13-
public static readonly backend = "fakeKeyManagementService";
14+
public static readonly backend = "enmeshed";
1415

1516
public readonly backend = EnmshedHolderKeyManagmentService.backend;
16-
public keystore: Map<string, string>;
1717

1818
private readonly b64url = (bytes: Uint8Array) => _sodium.to_base64(bytes, _sodium.base64_variants.URLSAFE_NO_PADDING);
1919
private readonly b64urlDecode = (b64url: string) => _sodium.from_base64(b64url, _sodium.base64_variants.URLSAFE_NO_PADDING);
@@ -33,20 +33,7 @@ export class EnmshedHolderKeyManagmentService implements Kms.KeyManagementServic
3333
return bytes;
3434
};
3535

36-
public constructor() {
37-
if ((globalThis as any).fakeKeyStorage) {
38-
this.keystore = (globalThis as any).fakeKeyStorage;
39-
} else {
40-
this.keystore = new Map<string, string>();
41-
}
42-
this.updateGlobalInstance(this.keystore);
43-
}
44-
45-
public updateGlobalInstance(storage: Map<string, string>): void {
46-
// console.log(`FKM: updating global instance ${JSON.stringify(Array.from(storage.entries()))}`);
47-
(globalThis as any).fakeKeyStorage = storage;
48-
// console.log(`FKM: global instance state ${JSON.stringify(Array.from((globalThis as any).fakeKeyStorage.entries()))}`);
49-
}
36+
public constructor(private readonly keyStorage: KeyStorage) {}
5037

5138
public isOperationSupported(agentContext: AgentContext, operation: Kms.KmsOperation): boolean {
5239
agentContext.config.logger.debug(`EKM: Checking if operation is supported: ${JSON.stringify(operation)}`);
@@ -76,14 +63,14 @@ export class EnmshedHolderKeyManagmentService implements Kms.KeyManagementServic
7663
}
7764
return false;
7865
}
79-
public getPublicKey(agentContext: AgentContext, keyId: string): Promise<Kms.KmsJwkPublic> {
80-
const keyPair = this.keystore.get(keyId);
66+
public async getPublicKey(agentContext: AgentContext, keyId: string): Promise<Kms.KmsJwkPublic> {
67+
const keyPair = await this.keyStorage.getKey(keyId);
8168
if (!keyPair) {
8269
agentContext.config.logger.error(`EKM: Key with id ${keyId} not found`);
8370
throw new Error(`Key with id ${keyId} not found`);
8471
}
8572

86-
return Promise.resolve((JSON.parse(keyPair) as JwkKeyPair).publicKey as Kms.KmsJwkPublic);
73+
return (JSON.parse(keyPair) as JwkKeyPair).publicKey as Kms.KmsJwkPublic;
8774
}
8875
public async createKey<Type extends Kms.KmsCreateKeyType>(agentContext: AgentContext, options: Kms.KmsCreateKeyOptions<Type>): Promise<Kms.KmsCreateKeyReturn<Type>> {
8976
options.keyId ??= "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) {
@@ -122,13 +109,9 @@ export class EnmshedHolderKeyManagmentService implements Kms.KeyManagementServic
122109

123110
agentContext.config.logger.debug(`EKM: Created EC key pair with id ${options.keyId}`);
124111
// store the key pair in the keystore
125-
this.keystore.set(options.keyId, JSON.stringify(jwkKeyPair));
112+
await this.keyStorage.storeKey(options.keyId, JSON.stringify(jwkKeyPair));
126113

127-
this.updateGlobalInstance(this.keystore);
128-
return await Promise.resolve({
129-
keyId: options.keyId,
130-
publicJwk: publicJwk as Kms.KmsJwkPublic
131-
} as Kms.KmsCreateKeyReturn<Type>);
114+
return { keyId: options.keyId, publicJwk: publicJwk as Kms.KmsJwkPublic } as Kms.KmsCreateKeyReturn<Type>;
132115
}
133116

134117
await _sodium.ready;
@@ -157,31 +140,28 @@ export class EnmshedHolderKeyManagmentService implements Kms.KeyManagementServic
157140
keyType: "OKP"
158141
};
159142

160-
// store the key pair in the keystore
161-
this.keystore.set(options.keyId, JSON.stringify(jwkKeyPair));
162-
this.updateGlobalInstance(this.keystore);
163-
return await Promise.resolve({
164-
keyId: options.keyId,
165-
publicJwk: publicJwk as Kms.KmsJwkPublic
166-
} as Kms.KmsCreateKeyReturn<Type>);
143+
await this.keyStorage.storeKey(options.keyId, JSON.stringify(jwkKeyPair));
144+
return { keyId: options.keyId, publicJwk: publicJwk as Kms.KmsJwkPublic } as Kms.KmsCreateKeyReturn<Type>;
167145
}
146+
168147
public importKey<Jwk extends Kms.KmsJwkPrivate>(agentContext: AgentContext, options: Kms.KmsImportKeyOptions<Jwk>): Promise<Kms.KmsImportKeyReturn<Jwk>> {
169148
agentContext.config.logger.debug(`EKM: Importing key with ${JSON.stringify(options)}`);
170149
throw new Error("Method not implemented.");
171150
}
172-
public deleteKey(agentContext: AgentContext, options: Kms.KmsDeleteKeyOptions): Promise<boolean> {
173-
if (this.keystore.has(options.keyId)) {
174-
agentContext.config.logger.debug(`EKM: Deleting key with id ${options.keyId}`);
175-
this.keystore.delete(options.keyId);
176-
this.updateGlobalInstance(this.keystore);
177-
return Promise.resolve(true);
178-
}
179-
throw new Error(`key with id ${options.keyId} not found. and cannot be deleted`);
151+
152+
public async deleteKey(agentContext: AgentContext, options: Kms.KmsDeleteKeyOptions): Promise<boolean> {
153+
const hasKey = await this.keyStorage.hasKey(options.keyId);
154+
if (!hasKey) throw new Error(`key with id ${options.keyId} not found. and cannot be deleted`);
155+
156+
agentContext.config.logger.debug(`EKM: Deleting key with id ${options.keyId}`);
157+
await this.keyStorage.deleteKey(options.keyId);
158+
return true;
180159
}
160+
181161
public async sign(agentContext: AgentContext, options: Kms.KmsSignOptions): Promise<Kms.KmsSignReturn> {
182162
agentContext.config.logger.debug(`EKM: Signing data with key id ${options.keyId} using algorithm ${options.algorithm}`);
183163

184-
const stringifiedKeyPair = this.keystore.get(options.keyId);
164+
const stringifiedKeyPair = await this.keyStorage.getKey(options.keyId);
185165
if (!stringifiedKeyPair) {
186166
throw new Error(`Key with id ${options.keyId} not found`);
187167
}
@@ -273,8 +253,8 @@ export class EnmshedHolderKeyManagmentService implements Kms.KeyManagementServic
273253
}
274254
}
275255

276-
private ecdhEs(localKeyId: string, remotePublicJWK: any): Uint8Array {
277-
const keyPairString = this.keystore.get(localKeyId);
256+
private async ecdhEs(localKeyId: string, remotePublicJWK: any): Promise<Uint8Array> {
257+
const keyPairString = await this.keyStorage.getKey(localKeyId);
278258
if (!keyPairString) {
279259
throw new Error(`Key with id ${localKeyId} not found`);
280260
}
@@ -360,7 +340,7 @@ export class EnmshedHolderKeyManagmentService implements Kms.KeyManagementServic
360340
return hashBuf.subarray(0, keyLength / 8);
361341
}
362342

363-
public encrypt(agentContext: AgentContext, options: Kms.KmsEncryptOptions): Promise<Kms.KmsEncryptReturn> {
343+
public async encrypt(agentContext: AgentContext, options: Kms.KmsEncryptOptions): Promise<Kms.KmsEncryptReturn> {
364344
try {
365345
// encryption via A-256-GCM
366346
// we will call the services side bob and the incoming side alice
@@ -372,7 +352,7 @@ export class EnmshedHolderKeyManagmentService implements Kms.KeyManagementServic
372352
}
373353

374354
// 1. derive the shared secret via ECDH-ES
375-
const sharedSecret = this.ecdhEs(options.key.keyAgreement.keyId, options.key.keyAgreement.externalPublicJwk);
355+
const sharedSecret = await this.ecdhEs(options.key.keyAgreement.keyId, options.key.keyAgreement.externalPublicJwk);
376356
agentContext.config.logger.debug(`EKM: Derived shared secret for encryption using ECDH-ES`);
377357
// 2. Concat KDF to form the final key
378358
const derivedKey = this.concatKdf(sharedSecret, 256, "A256GCM", options.key.keyAgreement);
@@ -403,7 +383,7 @@ export class EnmshedHolderKeyManagmentService implements Kms.KeyManagementServic
403383
tag: tag
404384
};
405385

406-
return Promise.resolve(returnValue);
386+
return returnValue;
407387
} catch (e) {
408388
agentContext.config.logger.error(`EKM: Error during encryption: ${e}`);
409389
throw e;

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

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,19 +15,21 @@ import {
1515
W3cCredentialRecord,
1616
W3cJwtVerifiableCredential
1717
} from "@credo-ts/core";
18-
import { IdentityAttribute } from "@nmshd/content";
18+
import { IdentityAttribute, VerifiableCredential as VerifiableCredentialAttributeValue } from "@nmshd/content";
1919
import { CoreId } from "@nmshd/core-types";
2020
import { AccountController } from "@nmshd/transport";
2121
import { OwnIdentityAttribute } from "../../attributes";
2222
import { AttributesController } from "../../attributes/AttributesController";
23+
import { KeyStorage } from "./KeyStorage";
2324

2425
@injectable()
2526
export class EnmeshedStorageService<T extends BaseRecord> implements StorageService<T> {
2627
public storage: Map<string, T> = new Map<string, T>();
2728

2829
public constructor(
29-
public accountController: AccountController,
30-
public attributeController: AttributesController
30+
private readonly accountController: AccountController,
31+
private readonly attributeController: AttributesController,
32+
private readonly keyStorage: KeyStorage
3133
) {}
3234

3335
public async save(agentContext: AgentContext, record: T): Promise<void> {
@@ -144,10 +146,16 @@ export class EnmeshedStorageService<T extends BaseRecord> implements StorageServ
144146

145147
public async getAll(agentContext: AgentContext, recordClass: BaseRecordConstructor<T>): Promise<T[]> {
146148
const records: T[] = [];
147-
const attributes = await this.attributeController.getLocalAttributes({ "@type": "OwnIdentityAttribute", "content.value.@type": "VerifiableCredential" });
149+
const attributes = (await this.attributeController.getLocalAttributes({
150+
"@type": "OwnIdentityAttribute",
151+
"content.value.@type": "VerifiableCredential"
152+
})) as OwnIdentityAttribute[];
153+
148154
for (const attribute of attributes) {
155+
const value = attribute.content.value as VerifiableCredentialAttributeValue;
156+
149157
// TODO: Correct casting
150-
const type = (attribute as any).content.value.type;
158+
const type = value.type;
151159
let record: T;
152160
if (type === ClaimFormat.SdJwtDc.toString() && recordClass.name === SdJwtVcRecord.name) {
153161
record = new SdJwtVcRecord({ id: (attribute as any).content.id, compactSdJwtVc: (attribute as any).content.value.value }) as unknown as T;
@@ -164,18 +172,14 @@ export class EnmeshedStorageService<T extends BaseRecord> implements StorageServ
164172
agentContext.config.logger.info(`Skipping attribute with id ${attribute.id} and type ${type} as it does not match record class ${recordClass.name}`);
165173
continue;
166174
}
167-
if ((attribute as any).content.value.key !== undefined) {
175+
176+
if (value.key !== undefined) {
168177
// TODO: Remove as this is only a workaround for demo purposes
169178
agentContext.config.logger.info("Found keys to possibly import");
170-
const parsed = JSON.parse((attribute as any).content.value.key) as Map<string, any>;
179+
180+
const parsed = JSON.parse(value.key) as Map<string, any>;
171181
for (const [k, v] of parsed) {
172-
const currentKeys = (globalThis as any).fakeKeyStorage as Map<string, any>;
173-
if (!currentKeys.has(k)) {
174-
(globalThis as any).fakeKeyStorage.set(k, v);
175-
agentContext.config.logger.info(`Added key ${k} to fake keystore`);
176-
} else {
177-
agentContext.config.logger.info(`Key ${k} already in fake keystore`);
178-
}
182+
await this.keyStorage.storeKey(k, v);
179183
}
180184
}
181185
records.push(record);

0 commit comments

Comments
 (0)