Skip to content
Draft
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
3ae30b0
feat: Add CAL support to runtime
suitable-name Jul 1, 2025
00b3f96
fix: ts-crypto update & test adjustment
suitable-name Jul 1, 2025
8ca4087
fix: added AndroidProvider to providers to initialize
suitable-name Jul 1, 2025
8d4e955
fix: Fixes according to PR comments
suitable-name Jul 1, 2025
2e246b9
Merge branch 'main' into feature/CAL-integration
jkoenig134 Jul 7, 2025
aaaa1b1
Merge branch 'main' into feature/CAL-integration
jkoenig134 Jul 7, 2025
0725b74
Merge branch 'main' into feature/CAL-integration
mergify[bot] Jul 7, 2025
141ad07
chore: undo massive lockfile changes
jkoenig134 Jul 7, 2025
4cbc88b
fix: fixed CAL config and initialization
suitable-name Jul 7, 2025
6328603
Merge branch 'main' into feature/CAL-integration
mergify[bot] Jul 7, 2025
1dfbc32
fix: remove node specific imports
mark-beck Jul 7, 2025
c656ce0
Merge branch 'main' into feature/CAL-integration
mergify[bot] Jul 7, 2025
3edecd4
feat: update to new ts-crypto version
mark-beck Jul 9, 2025
3889bfa
feat: update runtime initialisation, always use hardware backed softw…
mark-beck Jul 9, 2025
b2e6042
fix: ProviderInitConfig loading, baseKey loading
mark-beck Jul 11, 2025
814885a
Merge branch 'main' into feature/CAL-integration
jkoenig134 Jul 11, 2025
33b9484
Merge branch 'main' into feature/CAL-integration
mergify[bot] Jul 11, 2025
7efe1fe
chore: undo
jkoenig134 Jul 15, 2025
e7c916a
chore: update lockfile
jkoenig134 Jul 15, 2025
b1dd485
chore: update to new @nmshd/crypto alpha version
WyvernIXTL Aug 19, 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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
tsconfig.tsbuildinfo
node_modules
packages/*/coverage
nmshd-runtime.code-workspace

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

undo that please.

3,387 changes: 2,505 additions & 882 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion packages/consumption/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@
"@js-soft/docdb-access-loki": "1.2.0",
"@js-soft/docdb-access-mongo": "1.2.0",
"@js-soft/node-logger": "1.2.0",
"@nmshd/crypto": "2.1.2",
"@nmshd/crypto": "2.2.0-alpha.3",
"@types/lodash": "^4.17.18",
"ts-mockito": "^2.6.1"
},
Expand Down
2 changes: 1 addition & 1 deletion packages/content/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@
},
"devDependencies": {
"@types/luxon": "^3.6.2",
"@nmshd/crypto": "2.1.2"
"@nmshd/crypto": "2.2.0-alpha.3"
},
"publishConfig": {
"access": "public",
Expand Down
2 changes: 1 addition & 1 deletion packages/core-types/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
"dependencies": {
"@js-soft/logging-abstractions": "^1.0.1",
"@js-soft/ts-serval": "2.0.12",
"@nmshd/crypto": "^2.1.2",
"@nmshd/crypto": "2.2.0-alpha.3",
"json-stringify-safe": "^5.0.1",
"luxon": "^3.6.1"
},
Expand Down
2 changes: 1 addition & 1 deletion packages/runtime/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@
"@nmshd/consumption": "*",
"@nmshd/content": "*",
"@nmshd/core-types": "*",
"@nmshd/crypto": "2.1.2",
"@nmshd/crypto": "2.2.0-alpha.3",
"@nmshd/iql": "^1.0.3",
"@nmshd/transport": "*",
"@nmshd/typescript-ioc": "3.2.4",
Expand Down
4 changes: 3 additions & 1 deletion packages/transport/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,8 @@
"@js-soft/simple-logger": "1.0.5",
"@js-soft/ts-utils": "^2.3.3",
"@nmshd/core-types": "*",
"@nmshd/crypto": "2.1.2",
"@nmshd/crypto": "2.2.0-alpha.3",
"@nmshd/rs-crypto-node": "0.14.0",
"axios": "^1.10.0",
"fast-json-patch": "^3.1.1",
"form-data": "^4.0.3",
Expand All @@ -94,6 +95,7 @@
"@types/luxon": "^3.6.2",
"@types/qs": "^6.14.0",
"@types/uuid": "^10.0.0",
"@types/tmp": "0.2.6",
"correlation-id": "^5.2.0",
"expect": "^30.0.3",
"testcontainers": "^11.0.3",
Expand Down
77 changes: 76 additions & 1 deletion packages/transport/src/core/CoreCrypto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ import {
CryptoCipher,
CryptoDerivation,
CryptoDerivationAlgorithm,
CryptoDerivationHandle,
CryptoEncryption,
CryptoEncryptionAlgorithm,
CryptoEncryptionHandle,
CryptoExchange,
CryptoExchangeAlgorithm,
CryptoExchangeKeypair,
Expand All @@ -19,7 +21,12 @@ import {
CryptoSignaturePrivateKey,
CryptoSignaturePublicKey,
CryptoSignatures,
Encoding
DeviceBoundDerivedKeyHandle,
DeviceBoundKeyHandle,
Encoding,
PortableDerivedKeyHandle,
PortableKeyHandle,
ProviderIdentifier
} from "@nmshd/crypto";
import { PasswordGenerator } from "../util";
import { TransportError } from "./TransportError";
Expand Down Expand Up @@ -81,6 +88,74 @@ export abstract class CoreCrypto {
}
}

/**
* Generates a handle-based secret key for symmetric encryption.
* Depending on the given version, different algorithms are used:
*
* v1: AES256_GCM
*
* @param providerIdent The provider identifier
* @param version The version which should be used, "latest" is the default.
* @returns A Promise object resolving into a new CryptoSecretKeyHandle.
*/
public static async generateDeviceBoundKeyHandle(providerIdent: ProviderIdentifier, version: TransportVersion = TransportVersion.Latest): Promise<DeviceBoundKeyHandle> {
switch (version) {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
case TransportVersion.V1:
return await CryptoEncryptionHandle.generateDeviceBoundKeyHandle(providerIdent, CryptoEncryptionAlgorithm.XCHACHA20_POLY1305, CryptoHashAlgorithm.SHA512);
default:
throw this.invalidVersion(version);
}
}

/**
* Generates a handle-based secret key for symmetric encryption.
* Depending on the given version, different algorithms are used:
*
* v1: AES256_GCM
*
* @param providerIdent The provider identifier
* @param version The version which should be used, "latest" is the default.
* @returns A Promise object resolving into a new CryptoSecretKeyHandle.
*/
public static async generatePortableKeyHandle(providerIdent: ProviderIdentifier, version: TransportVersion = TransportVersion.Latest): Promise<PortableKeyHandle> {
switch (version) {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
case TransportVersion.V1:
return await CryptoEncryptionHandle.generatePortableKeyHandle(providerIdent, CryptoEncryptionAlgorithm.XCHACHA20_POLY1305, CryptoHashAlgorithm.SHA512);
default:
throw this.invalidVersion(version);
}
}

/**
* Generates a handle-based secret key for symmetric encryption.
* Depending on the given version, different algorithms are used:
*
* v1: AES256_GCM
*
* @param providerIdent The provider identifier
* @param version The version which should be used, "latest" is the default.
* @returns A Promise object resolving into a new CryptoSecretKeyHandle.
*/
public static async generateDeviceBoundDerivationHandle(baseKey: DeviceBoundKeyHandle, keyId: number, context: string): Promise<DeviceBoundDerivedKeyHandle> {
return await CryptoDerivationHandle.deriveDeviceBoundKeyHandle(baseKey, keyId, context);
}

/**
* Generates a handle-based secret key for symmetric encryption.
* Depending on the given version, different algorithms are used:
*
* v1: AES256_GCM
*
* @param providerIdent The provider identifier
* @param version The version which should be used, "latest" is the default.
* @returns A Promise object resolving into a new CryptoSecretKeyHandle.
*/
public static async generatePortableDerivationHandle(baseKey: PortableKeyHandle, keyId: number, context: string): Promise<PortableDerivedKeyHandle> {
return await CryptoDerivationHandle.derivePortableKeyHandle(baseKey, keyId, context);
}

/**
* Generates a high entropy key / hash derived from a low entropy human readable/memorable master password, a unique salt,
* the given symmetric algorithm and the version. Depending on the given version, different key derivation algorithms are used.
Expand Down
173 changes: 173 additions & 0 deletions packages/transport/src/core/CryptoProviderMapping.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
/* eslint-disable @typescript-eslint/naming-convention */
import { DeviceBoundKeyHandle, hasProviderForSecurityLevel, PortableKeyHandle } from "@nmshd/crypto";
import { SecurityLevel } from "@nmshd/rs-crypto-types";
import { CoreCrypto } from "./CoreCrypto";

export const CryptoProviderTypes = {
Software: "Software",
Hardware: "Hardware",
Network: "Network",
LEGACY: "LEGACY"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is LEGACY Uppercase?

} as const;

export const CryptoPurpose = {
DeviceKeyPair: "deviceKeyPair",
BaseKey: "baseKey",
Default: "default"
} as const;
type CryptoPurpose = (typeof CryptoPurpose)[keyof typeof CryptoPurpose];

export const CryptoKeyType = {
Signature: "signature",
Encryption: "encryption",
Derivation: "derivation",
Exchange: "exchange"
} as const;
type CryptoKeyType = (typeof CryptoKeyType)[keyof typeof CryptoKeyType];

export const CryptoObject = {
AccountController: "AccountController",
AnonymousTokenController: "AnonymousTokenController",
Certificate: "Certificate",
DeviceController: "DeviceController",
DeviceSecretController: "DeviceSecretController",
IdentityController: "IdentityController",
FileController: "FileController",
MessageController: "MessageController",
RelationshipTemplateController: "RelationshipTemplateController",
RelationshipsController: "RelationshipsController",
RelationshipSecretController: "RelationshipSecretController",
SecretController: "SecretController",
TokenController: "TokenController"
} as const;
type CryptoObject = (typeof CryptoObject)[keyof typeof CryptoObject];

export const ALL_CRYPTO_PROVIDERS = ["SoftwareProvider", "AndroidProvider"];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't get the AndroidProvider here. Shouldn't this information come either from CAL or the app?


const CRYPTO_OPERATION_OBJECT_MAP: Partial<Record<CryptoObject, CryptoKeyType[]>> = {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For me, this is a duplicate of OBJECT_OPERATION_PREFERENCES with less details :)

[CryptoObject.AccountController]: [CryptoKeyType.Encryption, CryptoKeyType.Signature],
[CryptoObject.AnonymousTokenController]: [CryptoKeyType.Derivation],
[CryptoObject.Certificate]: [CryptoKeyType.Encryption],
[CryptoObject.DeviceController]: [CryptoKeyType.Signature, CryptoKeyType.Encryption],
[CryptoObject.DeviceSecretController]: [CryptoKeyType.Derivation],
[CryptoObject.FileController]: [CryptoKeyType.Encryption],
[CryptoObject.IdentityController]: [CryptoKeyType.Encryption],
[CryptoObject.MessageController]: [CryptoKeyType.Encryption],
[CryptoObject.RelationshipTemplateController]: [CryptoKeyType.Derivation, CryptoKeyType.Encryption],
[CryptoObject.RelationshipsController]: [CryptoKeyType.Encryption],
[CryptoObject.RelationshipSecretController]: [CryptoKeyType.Encryption],
[CryptoObject.SecretController]: [CryptoKeyType.Encryption],
[CryptoObject.TokenController]: [CryptoKeyType.Encryption]
};

const OBJECT_OPERATION_PREFERENCES: Partial<
Record<CryptoObject, Partial<Record<CryptoKeyType, SecurityLevel | Partial<Record<Exclude<CryptoPurpose, undefined>, SecurityLevel>>>>>
> = {
[CryptoObject.AccountController]: {
[CryptoKeyType.Signature]: {
[CryptoPurpose.DeviceKeyPair]: CryptoProviderTypes.Hardware,
[CryptoPurpose.Default]: CryptoProviderTypes.Software
},
[CryptoKeyType.Encryption]: {
[CryptoPurpose.BaseKey]: CryptoProviderTypes.Hardware,
[CryptoPurpose.Default]: CryptoProviderTypes.Software
}
},
[CryptoObject.AnonymousTokenController]: {
[CryptoKeyType.Derivation]: CryptoProviderTypes.Software
},
[CryptoObject.Certificate]: {},
[CryptoObject.DeviceController]: {},
[CryptoObject.DeviceSecretController]: {
[CryptoKeyType.Encryption]: CryptoProviderTypes.Hardware
},
[CryptoObject.FileController]: {
[CryptoKeyType.Encryption]: CryptoProviderTypes.Software
},
[CryptoObject.IdentityController]: {},
[CryptoObject.MessageController]: {
[CryptoKeyType.Encryption]: CryptoProviderTypes.Software
},
[CryptoObject.RelationshipTemplateController]: {
[CryptoKeyType.Encryption]: CryptoProviderTypes.Software
},
[CryptoObject.RelationshipsController]: {},
[CryptoObject.RelationshipSecretController]: {},
[CryptoObject.SecretController]: {
[CryptoKeyType.Exchange]: CryptoProviderTypes.Software
},
[CryptoObject.TokenController]: {
[CryptoKeyType.Encryption]: CryptoProviderTypes.Software
}
};

const DEFAULT_OPERATION_PREFERENCES: Partial<Record<CryptoKeyType, SecurityLevel>> = {
[CryptoKeyType.Derivation]: CryptoProviderTypes.Software,
[CryptoKeyType.Signature]: CryptoProviderTypes.Hardware,
[CryptoKeyType.Encryption]: CryptoProviderTypes.Software,
[CryptoKeyType.Exchange]: CryptoProviderTypes.Software
};

const FALLBACK_PREFERENCE: SecurityLevel = CryptoProviderTypes.Software;

export function getPreferredProviderLevel(cryptoObject: CryptoObject, cryptoOperation: CryptoKeyType, purpose?: Exclude<CryptoPurpose, undefined>): SecurityLevel {
const allowedOps = CRYPTO_OPERATION_OBJECT_MAP[cryptoObject];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you need this autorization on code level base?

if (allowedOps && !allowedOps.includes(cryptoOperation)) {
throw new Error(`Operation '${cryptoOperation}' is not supported for object '${cryptoObject}'.`);
}

let chosenSecurityLevel: SecurityLevel | undefined;
const objectPrefs = OBJECT_OPERATION_PREFERENCES[cryptoObject];
if (objectPrefs) {
const operationPrefOrMap = objectPrefs[cryptoOperation];
if (operationPrefOrMap) {
if (typeof operationPrefOrMap === "object") {
if (purpose && operationPrefOrMap[purpose]) {
chosenSecurityLevel = operationPrefOrMap[purpose];
}
} else {
chosenSecurityLevel = operationPrefOrMap;
}
}
}

if (!chosenSecurityLevel) {
const operationPref = DEFAULT_OPERATION_PREFERENCES[cryptoOperation];
chosenSecurityLevel = operationPref ?? FALLBACK_PREFERENCE;
}

if (!hasProviderForSecurityLevel(chosenSecurityLevel)) {
if (chosenSecurityLevel !== FALLBACK_PREFERENCE && !hasProviderForSecurityLevel(FALLBACK_PREFERENCE)) {
throw new Error(`No provider available for either ${chosenSecurityLevel} or fallback ${FALLBACK_PREFERENCE} for operation ${cryptoOperation}.`);
}
return FALLBACK_PREFERENCE;
}

return chosenSecurityLevel;
}

export async function getPreferredProviderKeyHandle(
cryptoObject: CryptoObject,
cryptoOperation: CryptoKeyType,
isPortable: boolean,
purpose?: CryptoPurpose
): Promise<DeviceBoundKeyHandle | PortableKeyHandle> {
const securityLevel = getPreferredProviderLevel(cryptoObject, cryptoOperation, purpose);

switch (securityLevel) {
case CryptoProviderTypes.Hardware:
if (isPortable) {
return await CoreCrypto.generatePortableKeyHandle({ securityLevel });
}
return await CoreCrypto.generateDeviceBoundKeyHandle({ securityLevel });

case CryptoProviderTypes.Software:
if (isPortable) {
return await CoreCrypto.generatePortableKeyHandle({ securityLevel });
}
return await CoreCrypto.generateDeviceBoundKeyHandle({ securityLevel });

default:
throw new Error(`Unsupported SecurityLevel '${securityLevel}' encountered for key handle generation.`);
}
}
20 changes: 17 additions & 3 deletions packages/transport/src/core/Transport.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { ILogger, ILoggerFactory } from "@js-soft/logging-abstractions";
import { SimpleLoggerFactory } from "@js-soft/simple-logger";
import { EventBus } from "@js-soft/ts-utils";
import { SodiumWrapper } from "@nmshd/crypto";
import { CryptoLayerConfig, initCryptoLayerProviders, SodiumWrapper } from "@nmshd/crypto";
import { AgentOptions } from "http";
import { AgentOptions as HTTPSAgentOptions } from "https";
import _ from "lodash";
Expand Down Expand Up @@ -86,7 +86,8 @@ export class Transport {
customConfig: IConfigOverwrite,
public readonly eventBus: EventBus,
loggerFactory: ILoggerFactory = new SimpleLoggerFactory(),
public readonly correlator?: ICorrelator
public readonly correlator?: ICorrelator,
public readonly cryptoLayerConfig?: CryptoLayerConfig
) {
this._config = _.defaultsDeep({}, customConfig, Transport.defaultConfig);

Expand Down Expand Up @@ -120,7 +121,20 @@ export class Transport {

public async init(): Promise<Transport> {
log.trace("Initializing Libsodium...");
await SodiumWrapper.ready();
const sodium = SodiumWrapper.ready();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did you split the await of SodiumWrapper?


if (this.cryptoLayerConfig) {
log.trace("Initializing Crypto Layer...");
try {
await initCryptoLayerProviders(this.cryptoLayerConfig);
log.trace("Crypto Layer initialized successfully");
} catch (error) {
log.warn("Failed to initialize Crypto Layer, continuing without it.", error);
}
log.trace("Crypto Layer initialized");
}

await sodium;
log.trace("Libsodium initialized");

log.info("Transport initialized");
Expand Down
Loading