Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 16 additions & 6 deletions packages/consumption/src/modules/openid4vc/OpenId4VcController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,28 @@ import { ConsumptionBaseController } from "../../consumption/ConsumptionBaseCont
import { ConsumptionController } from "../../consumption/ConsumptionController";
import { ConsumptionControllerName } from "../../consumption/ConsumptionControllerName";
import { Holder } from "./local/Holder";
import { KeyStorage } from "./local/KeyStorage";

export class OpenId4VcController extends ConsumptionBaseController {
private keyStorage: KeyStorage;

public constructor(parent: ConsumptionController) {
super(ConsumptionControllerName.OpenId4VcController, parent);
}

public override async init(): Promise<this> {
const collection = await this.parent.accountController.getSynchronizedCollection("openid4vc-keys");
this.keyStorage = new KeyStorage(collection, this._log);

return this;
}

private get fetchInstance(): typeof fetch {
return this.parent.consumptionConfig.fetchInstance ?? fetch;
}

public async fetchCredentialOffer(credentialOfferUrl: string): Promise<{ data: string }> {
const holder = new Holder(this.parent.accountController, this.parent.attributes, this.fetchInstance);
const holder = new Holder(this.keyStorage, this.parent.accountController, this.parent.attributes, this.fetchInstance);
await holder.initializeAgent("96213c3d7fc8d4d6754c7a0fd969598e");
const res = await holder.resolveCredentialOffer(credentialOfferUrl);
return {
Expand All @@ -29,7 +39,7 @@ export class OpenId4VcController extends ConsumptionBaseController {
requestedCredentialOffers: string[],
pinCode?: string
): Promise<{ data: string; id: string; type: string; displayInformation: string | undefined }> {
const holder = new Holder(this.parent.accountController, this.parent.attributes, this.fetchInstance);
const holder = new Holder(this.keyStorage, this.parent.accountController, this.parent.attributes, this.fetchInstance);
await holder.initializeAgent("96213c3d7fc8d4d6754c7a0fd969598e");
const credentialOffer = JSON.parse(fetchedCredentialOffer);
const credentials = await holder.requestAndStoreCredentials(credentialOffer, { credentialsToRequest: requestedCredentialOffers, txCode: pinCode });
Expand All @@ -47,7 +57,7 @@ export class OpenId4VcController extends ConsumptionBaseController {
}

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

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

Expand All @@ -105,7 +115,7 @@ export class OpenId4VcController extends ConsumptionBaseController {
}

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

const credentials = await holder.getVerifiableCredentials(ids);
Expand Down
19 changes: 7 additions & 12 deletions packages/consumption/src/modules/openid4vc/local/BaseAgent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { AttributesController } from "../../attributes";
import { EnmeshedHolderFileSystem } from "./EnmeshedHolderFileSystem";
import { EnmshedHolderKeyManagmentService } from "./EnmeshedHolderKeyManagmentService";
import { EnmeshedStorageService } from "./EnmeshedStorageService";
import { KeyStorage } from "./KeyStorage";

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

public constructor(
public readonly port: number,
public readonly name: string,
public readonly modules: AgentModules,
public readonly accountController: AccountController,
public readonly attributeController: AttributesController,
private readonly keyStorage: KeyStorage,
modules: AgentModules,
accountController: AccountController,
attributeController: AttributesController,
fetchInstance: typeof fetch
) {
this.name = name;
this.port = port;

const config = {
allowInsecureHttpUrls: true,
logger: new ConsoleLogger(LogLevel.off)
} satisfies InitConfig;

this.config = config;

this.accountController = accountController;
this.attributeController = attributeController;
const dependencyManager = new DependencyManager();
dependencyManager.registerInstance(InjectionSymbols.StorageService, new EnmeshedStorageService(accountController, attributeController));
dependencyManager.registerInstance(InjectionSymbols.StorageService, new EnmeshedStorageService(accountController, attributeController, this.keyStorage));
this.agent = new Agent(
{
config,
Expand All @@ -74,7 +69,7 @@ export class BaseAgent<AgentModules extends ModulesMap> {
await storage.save(this.agent.context, new StorageVersionRecord({ storageVersion: "0.5.0" }));

const kmsConfig = this.agent.dependencyManager.resolve(Kms.KeyManagementModuleConfig);
kmsConfig.registerBackend(new EnmshedHolderKeyManagmentService());
kmsConfig.registerBackend(new EnmshedHolderKeyManagmentService(this.keyStorage));

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { AgentContext, Kms } from "@credo-ts/core";
import { ec as EC } from "elliptic";
import _sodium from "libsodium-wrappers";
import sjcl from "sjcl";
import { KeyStorage } from "./KeyStorage";

export interface JwkKeyPair {
publicKey: JsonWebKey;
Expand All @@ -10,10 +11,9 @@ export interface JwkKeyPair {
}

export class EnmshedHolderKeyManagmentService implements Kms.KeyManagementService {
public static readonly backend = "fakeKeyManagementService";
public static readonly backend = "enmeshed";

public readonly backend = EnmshedHolderKeyManagmentService.backend;
public keystore: Map<string, string>;

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

public constructor() {
if ((globalThis as any).fakeKeyStorage) {
this.keystore = (globalThis as any).fakeKeyStorage;
} else {
this.keystore = new Map<string, string>();
}
this.updateGlobalInstance(this.keystore);
}

public updateGlobalInstance(storage: Map<string, string>): void {
// console.log(`FKM: updating global instance ${JSON.stringify(Array.from(storage.entries()))}`);
(globalThis as any).fakeKeyStorage = storage;
// console.log(`FKM: global instance state ${JSON.stringify(Array.from((globalThis as any).fakeKeyStorage.entries()))}`);
}
public constructor(private readonly keyStorage: KeyStorage) {}

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

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

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

this.updateGlobalInstance(this.keystore);
return await Promise.resolve({
keyId: options.keyId,
publicJwk: publicJwk as Kms.KmsJwkPublic
} as Kms.KmsCreateKeyReturn<Type>);
return { keyId: options.keyId, publicJwk: publicJwk as Kms.KmsJwkPublic } as Kms.KmsCreateKeyReturn<Type>;
}

await _sodium.ready;
Expand Down Expand Up @@ -157,31 +140,28 @@ export class EnmshedHolderKeyManagmentService implements Kms.KeyManagementServic
keyType: "OKP"
};

// store the key pair in the keystore
this.keystore.set(options.keyId, JSON.stringify(jwkKeyPair));
this.updateGlobalInstance(this.keystore);
return await Promise.resolve({
keyId: options.keyId,
publicJwk: publicJwk as Kms.KmsJwkPublic
} as Kms.KmsCreateKeyReturn<Type>);
await this.keyStorage.storeKey(options.keyId, JSON.stringify(jwkKeyPair));
return { keyId: options.keyId, publicJwk: publicJwk as Kms.KmsJwkPublic } as Kms.KmsCreateKeyReturn<Type>;
}

public importKey<Jwk extends Kms.KmsJwkPrivate>(agentContext: AgentContext, options: Kms.KmsImportKeyOptions<Jwk>): Promise<Kms.KmsImportKeyReturn<Jwk>> {
agentContext.config.logger.debug(`EKM: Importing key with ${JSON.stringify(options)}`);
throw new Error("Method not implemented.");
}
public deleteKey(agentContext: AgentContext, options: Kms.KmsDeleteKeyOptions): Promise<boolean> {
if (this.keystore.has(options.keyId)) {
agentContext.config.logger.debug(`EKM: Deleting key with id ${options.keyId}`);
this.keystore.delete(options.keyId);
this.updateGlobalInstance(this.keystore);
return Promise.resolve(true);
}
throw new Error(`key with id ${options.keyId} not found. and cannot be deleted`);

public async deleteKey(agentContext: AgentContext, options: Kms.KmsDeleteKeyOptions): Promise<boolean> {
const hasKey = await this.keyStorage.hasKey(options.keyId);
if (!hasKey) throw new Error(`key with id ${options.keyId} not found. and cannot be deleted`);

agentContext.config.logger.debug(`EKM: Deleting key with id ${options.keyId}`);
await this.keyStorage.deleteKey(options.keyId);
return true;
}

public async sign(agentContext: AgentContext, options: Kms.KmsSignOptions): Promise<Kms.KmsSignReturn> {
agentContext.config.logger.debug(`EKM: Signing data with key id ${options.keyId} using algorithm ${options.algorithm}`);

const stringifiedKeyPair = this.keystore.get(options.keyId);
const stringifiedKeyPair = await this.keyStorage.getKey(options.keyId);
if (!stringifiedKeyPair) {
throw new Error(`Key with id ${options.keyId} not found`);
}
Expand Down Expand Up @@ -273,8 +253,8 @@ export class EnmshedHolderKeyManagmentService implements Kms.KeyManagementServic
}
}

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

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

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

return Promise.resolve(returnValue);
return returnValue;
} catch (e) {
agentContext.config.logger.error(`EKM: Error during encryption: ${e}`);
throw e;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,21 @@ import {
W3cCredentialRecord,
W3cJwtVerifiableCredential
} from "@credo-ts/core";
import { IdentityAttribute } from "@nmshd/content";
import { IdentityAttribute, VerifiableCredential as VerifiableCredentialAttributeValue } from "@nmshd/content";
import { CoreId } from "@nmshd/core-types";
import { AccountController } from "@nmshd/transport";
import { OwnIdentityAttribute } from "../../attributes";
import { AttributesController } from "../../attributes/AttributesController";
import { KeyStorage } from "./KeyStorage";

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

public constructor(
public accountController: AccountController,
public attributeController: AttributesController
private readonly accountController: AccountController,
private readonly attributeController: AttributesController,
private readonly keyStorage: KeyStorage
) {}

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

public async getAll(agentContext: AgentContext, recordClass: BaseRecordConstructor<T>): Promise<T[]> {
const records: T[] = [];
const attributes = await this.attributeController.getLocalAttributes({ "@type": "OwnIdentityAttribute", "content.value.@type": "VerifiableCredential" });
const attributes = (await this.attributeController.getLocalAttributes({
"@type": "OwnIdentityAttribute",
"content.value.@type": "VerifiableCredential"
})) as OwnIdentityAttribute[];

for (const attribute of attributes) {
const value = attribute.content.value as VerifiableCredentialAttributeValue;

// TODO: Correct casting
const type = (attribute as any).content.value.type;
const type = value.type;
let record: T;
if (type === ClaimFormat.SdJwtDc.toString() && recordClass.name === SdJwtVcRecord.name) {
record = new SdJwtVcRecord({ id: (attribute as any).content.id, compactSdJwtVc: (attribute as any).content.value.value }) as unknown as T;
Expand All @@ -164,18 +172,14 @@ export class EnmeshedStorageService<T extends BaseRecord> implements StorageServ
agentContext.config.logger.info(`Skipping attribute with id ${attribute.id} and type ${type} as it does not match record class ${recordClass.name}`);
continue;
}
if ((attribute as any).content.value.key !== undefined) {

if (value.key !== undefined) {
// TODO: Remove as this is only a workaround for demo purposes
agentContext.config.logger.info("Found keys to possibly import");
const parsed = JSON.parse((attribute as any).content.value.key) as Map<string, any>;

const parsed = JSON.parse(value.key) as Map<string, any>;
for (const [k, v] of parsed) {
const currentKeys = (globalThis as any).fakeKeyStorage as Map<string, any>;
if (!currentKeys.has(k)) {
(globalThis as any).fakeKeyStorage.set(k, v);
agentContext.config.logger.info(`Added key ${k} to fake keystore`);
} else {
agentContext.config.logger.info(`Key ${k} already in fake keystore`);
}
await this.keyStorage.storeKey(k, v);
}
}
records.push(record);
Expand Down
Loading