Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
4faea8e
Feature/crypto layer secret key handle (#33)
WyvernIXTL Jun 3, 2025
817f9bb
Merge branch 'main' into feature/cal-integration
jkoenig134 Jun 3, 2025
50bd8bb
Merge branch 'main' into feature/cal-integration
jkoenig134 Jun 3, 2025
b058a83
Cleanup cal branch (#37)
jkoenig134 Jun 3, 2025
2cac5b7
Merge branch 'main' into feature/cal-integration
jkoenig134 Jun 3, 2025
45df57f
Remove serialization for derived key handles, remove keySpec property…
WyvernIXTL Jun 23, 2025
8d46218
Merge branch 'main' into feature/cal-integration
jkoenig134 Jun 23, 2025
72a9d97
feat: cal provider scope
WyvernIXTL Jun 30, 2025
8f589f4
refactor!: changed crypto layer init
WyvernIXTL Jun 30, 2025
781824f
chore: bump rs-crypto-types to v0.11.0 and rs-crypto-node to v0.14.0
WyvernIXTL Jul 1, 2025
781f5a7
feat: implemented provider initialization that is reproducable via a …
WyvernIXTL Jul 1, 2025
9eafe2c
feat: added clear providers function
WyvernIXTL Jul 1, 2025
9f97815
fix: wrong imports
WyvernIXTL Jul 1, 2025
54114d3
fix: wrong import in tests
WyvernIXTL Jul 1, 2025
6428fa7
feat!: handle provider creation
WyvernIXTL Jul 1, 2025
5461a91
fix: eslint error due to not exhaustive switch
WyvernIXTL Jul 1, 2025
9389086
test: signature key pair creation and loading
WyvernIXTL Jul 4, 2025
fc8f36d
test: provider creation
WyvernIXTL Jul 4, 2025
6aa68d0
fix: providers to be initialized wrong serialize implementation
WyvernIXTL Jul 8, 2025
7ebdc59
refactor!: renamed CryptoLayerProviderToBeInitialized to ProviderInit…
WyvernIXTL Jul 10, 2025
7ddd32d
fix!: replaced key handles with custom serializable in providers to b…
WyvernIXTL Jul 10, 2025
491036e
feat: added functions to retreive stored key count and metadata of a …
WyvernIXTL Jul 22, 2025
399fdc0
removed!: AES CBC algorithm
WyvernIXTL Jul 29, 2025
41cf83c
refactor!: clarified config classes, variable names and provider init…
WyvernIXTL Aug 7, 2025
3f1a4c6
docs: added comment regarding the use of mocks for testing provider i…
WyvernIXTL Aug 7, 2025
884f835
removed!: ChaCha20 algorithm
WyvernIXTL Aug 7, 2025
7a68ce7
chore: merge main into feature/cal-integration
WyvernIXTL Aug 7, 2025
69d236b
chore: pruned package lock via npm prune
WyvernIXTL Aug 7, 2025
748d6c1
fix: eslint error due to console log in test setup
WyvernIXTL Aug 7, 2025
29ee192
fix: update dependencies with vulnerabilities
WyvernIXTL Aug 7, 2025
47681f3
fix: remove comment in webpack.test.config.js
WyvernIXTL Aug 21, 2025
daa96b2
chore: removed dependency override and updated dependencies
WyvernIXTL Aug 21, 2025
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
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,7 @@ dist
dist-test
lib-web
lib-esm
test_cal_db
.nyc_output
.nycrc
coverage
15,732 changes: 5,294 additions & 10,438 deletions package-lock.json

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,24 +25,27 @@
"test": "npm run test:node && npm run test:web",
"test:local": "npm run test",
"test:ci": "npm run test",
"test:node": "mocha -r ts-node/register -r tsconfig-paths/register -r test/fixtures.ts ./test/index.ts --project ./test/tsconfig.json --exit",
"test:node": "mocha -r ts-node/register -r tsconfig-paths/register -r test/fixtures.ts ./test/index.node.ts --project ./test/tsconfig.json --exit",
"test:local:node": "npm run test:node",
"test:web": "browsertest-runner",
"test:web:debug": "browsertest-runner --debug"
},
"dependencies": {
"@nmshd/rs-crypto-types": "^0.12.1",
"libsodium-wrappers-sumo": "0.7.15",
"uuid": "^11.1.0"
},
"devDependencies": {
"@js-soft/eslint-config-ts": "^2.0.1",
"@js-soft/license-check": "^1.0.9",
"@js-soft/ts-serval": "^2.0.12",
"@nmshd/rs-crypto-node": "^0.15.0",
"@types/chai": "^5.2.2",
"@types/libsodium-wrappers-sumo": "^0.7.8",
"@types/mocha": "^10.0.10",
"@types/node": "^24.0.14",
"@types/uuid": "^10.0.0",
"@typestrong/ts-mockito": "^2.7.12",
Comment thread
jkoenig134 marked this conversation as resolved.
"bt-runner": "^4.0.7",
"chai": "^5.2.1",
"eslint": "^9.31.0",
Expand Down
16 changes: 15 additions & 1 deletion src/CryptoErrorCode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,5 +46,19 @@ export enum CryptoErrorCode {
StateWrongNonce = "error.crypto.state.wrongNonce",
StateWrongCounter = "error.crypto.state.wrongCounter",
StateWrongOrder = "error.crypto.state.orderDoesNotMatch",
StateWrongType = "error.crypto.state.wrongType"
StateWrongType = "error.crypto.state.wrongType",

CalNonExtractable = "error.crypto.cal.nonExtractable",
CalUnsupportedAlgorithm = "error.crypto.cal.unsupportedAlgorithm",
CalWrongProvider = "error.crypto.cal.wrongProvider",
CalUninitializedKey = "error.crypto.cal.uninitializedKey",
CalLoadingProvider = "error.crypto.cal.loadingProvider",
CalProvidersNotInitialized = "error.crypto.cal.providersNotInitialized",
CalThisProviderNotInitialized = "error.crypto.cal.thisProviderNotInitialized",
CalProvidersAlreadyInitialized = "error.crypto.cal.providersAlreadyInitialized",
CalImportOfKey = "error.crypto.cal.calImportOfKey",
CalKeyDerivation = "error.crypto.cal.keyDerivation",
CalLoadKey = "error.crypto.cal.loadKey",

DeserializeValidation = "error.deserialize.validation"
}
12 changes: 11 additions & 1 deletion src/CryptoSerializable.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ISerializable, Serializable } from "@js-soft/ts-serval";
import { ISerializable, ISerializableAsync, Serializable, SerializableAsync } from "@js-soft/ts-serval";
import { CoreBuffer } from "./CoreBuffer";

export abstract class CryptoSerializable extends Serializable implements ISerializable {
Expand All @@ -10,3 +10,13 @@ export abstract class CryptoSerializable extends Serializable implements ISerial
return CoreBuffer.utf8_base64(this.serialize(verbose));
}
}

export abstract class CryptoSerializableAsync extends SerializableAsync implements ISerializableAsync {
public override serialize(verbose = true): string {
return JSON.stringify(this.toJSON(verbose));
}

public toBase64(verbose = true): string {
return CoreBuffer.utf8_base64(this.serialize(verbose));
}
}
2 changes: 2 additions & 0 deletions src/CryptoValidation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,8 @@ export class CryptoValidation {

switch (algorithm) {
case CryptoEncryptionAlgorithm.XCHACHA20_POLY1305:
case CryptoEncryptionAlgorithm.AES128_GCM:
case CryptoEncryptionAlgorithm.AES256_GCM:
break;
default:
error = new CryptoError(
Expand Down
168 changes: 168 additions & 0 deletions src/crypto-layer/CryptoDerivationHandle.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
/* eslint-disable @typescript-eslint/naming-convention */
import { KDF, KeySpec } from "@nmshd/rs-crypto-types";
import { CoreBuffer, ICoreBuffer } from "../CoreBuffer";
import { CryptoDerivationAlgorithm } from "../CryptoDerivation";
import { CryptoError } from "../CryptoError";
import { CryptoErrorCode } from "../CryptoErrorCode";
import { CryptoValidation } from "../CryptoValidation";
import { CryptoEncryptionAlgorithm } from "../encryption/CryptoEncryption";
import { CryptoHashAlgorithm } from "../hash/CryptoHash";
import { CryptoLayerProviderIdentifier } from "./CryptoLayerConfig";
import { getProvider } from "./CryptoLayerProviders";
import { CryptoLayerUtils } from "./CryptoLayerUtils";
import { BaseDerivedKeyHandle } from "./encryption/BaseDerivedKeyHandle";
import { BaseKeyHandle } from "./encryption/BaseKeyHandle";
import { CryptoEncryptionHandle } from "./encryption/CryptoEncryptionHandle";
import { DeviceBoundDerivedKeyHandle } from "./encryption/DeviceBoundDerivedKeyHandle";
import { DeviceBoundKeyHandle } from "./encryption/DeviceBoundKeyHandle";
import { PortableDerivedKeyHandle } from "./encryption/PortableDerivedKeyHandle";
import { PortableKeyHandle } from "./encryption/PortableKeyHandle";

export interface DeriveKeyHandleFromPasswordParameters {
providerIdent: CryptoLayerProviderIdentifier;
password: ICoreBuffer;
salt: ICoreBuffer;
resultingKeyEncryptionAlgorithm: CryptoEncryptionAlgorithm;
resultingKeyHashAlgorithm: CryptoHashAlgorithm;
derivationAlgorithm: CryptoDerivationAlgorithm;
derivationIterations: number;
derivationMemoryLimit: number;
derivationParallelism: number;
}

export class CryptoDerivationHandle {
private static async deriveKeyHandleFromPassword<T extends BaseDerivedKeyHandle>(
constructor: new () => T,
derivationParameters: DeriveKeyHandleFromPasswordParameters,
keySpecOfResultingKey: KeySpec
): Promise<T> {
CryptoValidation.checkBuffer(new CoreBuffer(derivationParameters.salt), 8, 64, "salt", true);

const provider = getProvider(derivationParameters.providerIdent);

const kdfParameters: KDF = CryptoLayerUtils.kdfFromCryptoDerivation(
derivationParameters.derivationAlgorithm,
derivationParameters.derivationIterations,
derivationParameters.derivationMemoryLimit,
derivationParameters.derivationParallelism
);

let keyHandle;
try {
keyHandle = await provider.deriveKeyFromPassword(
derivationParameters.password.toUtf8(),
derivationParameters.salt.buffer,
keySpecOfResultingKey,
kdfParameters
);
} catch (e) {
throw new CryptoError(
CryptoErrorCode.CalKeyDerivation,
`Provider ${await provider.providerName()} failed to derive key from password.`,
undefined,
e as Error,
CryptoDerivationHandle.deriveKeyHandleFromPassword
);
}

return await CryptoEncryptionHandle._keyHandleFromProviderAndCalKeyHandle(constructor, provider, keyHandle);
}

/**
* Derive an ephemeral {@link DeviceBoundDerivedKeyHandle} from a password.
*/
public static async deriveDeviceBoundKeyHandleFromPassword(
parameters: DeriveKeyHandleFromPasswordParameters
): Promise<DeviceBoundDerivedKeyHandle> {
const keySpec: KeySpec = {
cipher: CryptoLayerUtils.cipherFromCryptoEncryptionAlgorithm(parameters.resultingKeyEncryptionAlgorithm),
signing_hash: CryptoLayerUtils.cryptoHashFromCryptoHashAlgorithm(parameters.resultingKeyHashAlgorithm),
ephemeral: true,
non_exportable: true
};

return await CryptoDerivationHandle.deriveKeyHandleFromPassword<DeviceBoundDerivedKeyHandle>(
DeviceBoundDerivedKeyHandle,
parameters,
keySpec
);
}

/**
* Derive an ephemeral {@link PortableDerivedKeyHandle} from a password.
*/
public static async derivePortableKeyHandleFromPassword(
parameters: DeriveKeyHandleFromPasswordParameters
): Promise<PortableDerivedKeyHandle> {
const keySpec: KeySpec = {
cipher: CryptoLayerUtils.cipherFromCryptoEncryptionAlgorithm(parameters.resultingKeyEncryptionAlgorithm),
signing_hash: CryptoLayerUtils.cryptoHashFromCryptoHashAlgorithm(parameters.resultingKeyHashAlgorithm),
ephemeral: true,
non_exportable: false
};

return await CryptoDerivationHandle.deriveKeyHandleFromPassword<PortableDerivedKeyHandle>(
PortableDerivedKeyHandle,
parameters,
keySpec
);
}

private static async deriveKeyFromBaseKeyHandle<T extends BaseKeyHandle, R extends BaseDerivedKeyHandle>(
constructor: new () => R,
baseKey: T,
keyId: number,
context: string
): Promise<R> {
const bytes = CoreBuffer.fromUtf8(`id:${keyId};ctx:${context}`);

let keyHandle;
try {
keyHandle = await baseKey.keyHandle.deriveKey(bytes.buffer);
} catch (e) {
throw new CryptoError(
CryptoErrorCode.CalKeyDerivation,
`Failed to derive key from base key.`,
undefined,
e as Error,
CryptoDerivationHandle.deriveKeyFromBaseKeyHandle
);
}

return await CryptoEncryptionHandle._keyHandleFromProviderAndCalKeyHandle(
constructor,
baseKey.provider,
keyHandle
);
}

/**
* Derive an ephemeral {@link DeviceBoundDerivedKeyHandle} from a {@link DeviceBoundKeyHandle} with the same algorithms.
*/
public static async deriveDeviceBoundKeyHandle(
baseKey: DeviceBoundKeyHandle,
keyId: number,
context: string
): Promise<DeviceBoundDerivedKeyHandle> {
return await CryptoDerivationHandle.deriveKeyFromBaseKeyHandle<
DeviceBoundKeyHandle,
DeviceBoundDerivedKeyHandle
>(DeviceBoundDerivedKeyHandle, baseKey, keyId, context);
}

/**
* Derive an ephemeral {@link PortableDerivedKeyHandle} from a {@link PortableKeyHandle} with the same algorithms.
*/
public static async derivePortableKeyHandle(
baseKey: PortableKeyHandle,
keyId: number,
context: string
): Promise<PortableDerivedKeyHandle> {
return await CryptoDerivationHandle.deriveKeyFromBaseKeyHandle<PortableKeyHandle, PortableDerivedKeyHandle>(
PortableDerivedKeyHandle,
baseKey,
keyId,
context
);
}
}
142 changes: 142 additions & 0 deletions src/crypto-layer/CryptoLayerConfig.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import { serialize, type, validate } from "@js-soft/ts-serval";
import { AdditionalConfig, KeyHandle, KeyPairHandle, SecurityLevel } from "@nmshd/rs-crypto-types";
import { CryptoSerializable } from "../CryptoSerializable";
import { CryptoEncryptionAlgorithm } from "../encryption/CryptoEncryption";
import { CryptoHashAlgorithm } from "../hash/CryptoHash";
import { CryptoSignatureAlgorithm } from "../signature/CryptoSignatureAlgorithm";
import { getProvider } from "./CryptoLayerProviders";
import { DeviceBoundKeyHandle } from "./encryption/DeviceBoundKeyHandle";
import { DeviceBoundKeyPairHandle } from "./signature/DeviceBoundKeyPairHandle";

type KeyHandleType = "symmetric" | "asymmetric";

interface IProviderInitConfigKeyHandle {
type: KeyHandleType;
keyId: string;
providerName: string;
}

/**
* Configuration for loading a key handle from a provider without loading on deserialization.
*/
@type("ProviderInitConfigKeyHandle")
export class ProviderInitConfigKeyHandle extends CryptoSerializable {
@validate()
@serialize()
public type: KeyHandleType;

@validate()
@serialize()
public keyId: string;

@validate()
@serialize()
public providerName: string;

public static from(value: IProviderInitConfigKeyHandle): ProviderInitConfigKeyHandle {
return ProviderInitConfigKeyHandle.fromAny(value);
}

public static fromDeviceBoundHandle(
handle: DeviceBoundKeyHandle | DeviceBoundKeyPairHandle
): ProviderInitConfigKeyHandle {
return ProviderInitConfigKeyHandle.from({
type: handle instanceof DeviceBoundKeyHandle ? "symmetric" : "asymmetric",
keyId: handle.id,
providerName: handle.providerName
});
}

public async loadKeyHandle(): Promise<KeyHandle | KeyPairHandle> {
const provider = getProvider({ providerName: this.providerName });

switch (this.type) {
case "asymmetric":
return await provider.loadKeyPair(this.keyId);
case "symmetric":
return await provider.loadKey(this.keyId);
}
}
}

interface IProviderInitConfig {
providerName: string;
masterEncryptionKeyHandle?: ProviderInitConfigKeyHandle;
masterSignatureKeyHandle?: ProviderInitConfigKeyHandle;
requiredProvider?: ProviderInitConfig;
}

/**
* Recursive configuration used to load providers and their dependencies.
*/
@type("ProviderInitConfig")
export class ProviderInitConfig extends CryptoSerializable {
@validate()
@serialize()
public providerName: string;

@validate({ nullable: true })
@serialize()
public masterEncryptionKeyHandle?: ProviderInitConfigKeyHandle;

@validate({ nullable: true })
@serialize()
public masterSignatureKeyHandle?: ProviderInitConfigKeyHandle;

@validate({ nullable: true })
@serialize()
public requiredProvider?: ProviderInitConfig;

public static from(value: IProviderInitConfig): ProviderInitConfig {
return ProviderInitConfig.fromAny(value);
}
}

// eslint-disable-next-line @typescript-eslint/naming-convention
export type StorageConfig = Extract<AdditionalConfig, { KVStoreConfig: any } | { FileStoreConfig: any }>;

export type CryptoLayerProviderIdentifier =
| {
providerName: string;
}
| {
securityLevel: SecurityLevel;
};

export type StorageSecuritySpec =
| {
type: "asymmetric";
asymmetricKeyAlgorithm: CryptoSignatureAlgorithm;
encryptionAlgorithm: CryptoEncryptionAlgorithm | undefined;
hashingAlgorithm: CryptoHashAlgorithm;
}
| {
type: "symmetric";
encryptionAlgorithm: CryptoEncryptionAlgorithm;
hashingAlgorithm: CryptoHashAlgorithm;
};

export interface StorageSecurityConfig {
name: string;
signature: StorageSecuritySpec;
encryption: StorageSecuritySpec;
}

export type KeyMetadata =
| {
id: string;
type: "symmetric";
encryptionAlgorithm: CryptoEncryptionAlgorithm;
hashAlgorithm: CryptoHashAlgorithm;
deviceBound: boolean;
ephemeral: boolean;
}
| {
id: string;
type: "asymmetric";
asymmetricKeyAlgorithm: CryptoSignatureAlgorithm;
encryptionAlgorithm?: CryptoEncryptionAlgorithm;
hashAlgorithm: CryptoHashAlgorithm;
deviceBound: boolean;
ephemeral: boolean;
};
Loading