Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/crypto layer #29

Draft
wants to merge 45 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
b1fd2f5
feat(private key): added attributes for cal integration
WyvernIXTL Jan 24, 2025
7823b5e
feat!: added CryptoLayerKeyPair to CryptoPrivateKey private key
WyvernIXTL Jan 28, 2025
be3e2c9
feat: conversion from ts algorithms to cal equivalent
WyvernIXTL Jan 28, 2025
5941371
feat: implemented fromString for modified CryptoPrivateKey
WyvernIXTL Jan 28, 2025
7523ab7
feat: implemented other methods of modified CryptoPrivateKey
WyvernIXTL Jan 28, 2025
a6ae9c3
refactor: creation of cal key from buffers
WyvernIXTL Jan 29, 2025
3b0c06e
refactor: reworked from buffer methods
WyvernIXTL Jan 31, 2025
0937bd3
fix: add missing saved change
WyvernIXTL Jan 31, 2025
86560c7
feat: implemented toPem and toString methods for CryptoPrivateKey
WyvernIXTL Jan 31, 2025
c2bfab2
feat: crypto layer key pair integration for crypto public key
WyvernIXTL Jan 31, 2025
a1e01c5
feat: extended signature private and public key with crypto layer fun…
WyvernIXTL Jan 31, 2025
75b9a73
feat: added crypto layer support to CryptoSignatures
WyvernIXTL Jan 31, 2025
4211081
chore!: update crypto-layer-ts-types dependency to new api
WyvernIXTL Feb 5, 2025
cd1069a
feat!: global providers
WyvernIXTL Feb 5, 2025
e284ab6
refactor: switched cal ts types to package provided by npm repository
WyvernIXTL Feb 18, 2025
23dab29
revert: changes made to crypto public, private, signature public and …
WyvernIXTL Feb 18, 2025
bab59c5
refactor!: fixed eslint warnings and refactored crypto layer provider…
WyvernIXTL Feb 18, 2025
ac92ca3
feat: prototype private key handle
WyvernIXTL Feb 18, 2025
79cd9ba
refactor!: put crypto layer additions into separate folder and rewrot…
WyvernIXTL Feb 19, 2025
e2daf27
fix: failing compilation with tsc
WyvernIXTL Feb 19, 2025
4c036df
feat: implemented methods for crypto private key handle to import and…
WyvernIXTL Feb 19, 2025
58cdb80
refactor: shortened `newFromProviderAndKeyPairHandle`
WyvernIXTL Feb 19, 2025
bfce95e
feat: implemented import and export methods for raw public key in `Cr…
WyvernIXTL Feb 19, 2025
b2f592c
feat: implemented `CryptoSignaturePrivateKeyHandle`
WyvernIXTL Feb 19, 2025
0160290
feat: signatures with key pair handle support
WyvernIXTL Feb 21, 2025
c0f2054
feature: symmetric encryption
mark-beck Feb 25, 2025
0bdc6f9
fix: fix imports after rebase
mark-beck Feb 25, 2025
3317e44
fix: compilation and readded deleted file
WyvernIXTL Feb 26, 2025
9f75e94
test: added test to check software provider initialization
WyvernIXTL Feb 26, 2025
d636189
tests: fix failing to run cal tests
WyvernIXTL Feb 27, 2025
c3c2799
style: fix eslint warnings
WyvernIXTL Feb 27, 2025
fba214d
fix(encryption)!: tests not compiling due to api change
WyvernIXTL Feb 27, 2025
d602cad
test: added test for CryptoKeyPairHandle generation
WyvernIXTL Feb 27, 2025
176603c
chore: version bump of rs-crypto-node and removal of optional deps
WyvernIXTL Feb 27, 2025
a85232f
fix: cal signature key pair generation test failing
WyvernIXTL Mar 3, 2025
ca88abc
test: parameterized test for signature key pair creation
WyvernIXTL Mar 4, 2025
089d534
test: added test for sign and verify
WyvernIXTL Mar 4, 2025
ed0644d
test: added tests serializing and deserializing of CryptoSignaturePri…
WyvernIXTL Mar 4, 2025
e3b998a
test: serialization and deserialization of CryptoSignaturePublicKeyHa…
WyvernIXTL Mar 5, 2025
defe3e6
test: cal signature serialization and deserialization
WyvernIXTL Mar 5, 2025
6625b24
feat: cal public key exportable
WyvernIXTL Mar 5, 2025
d2d4de8
test: test non exportable and exportable signature public keys
WyvernIXTL Mar 5, 2025
b09cdf8
revert: rawPublicKey from CryptoPublicKeyHandle
WyvernIXTL Mar 5, 2025
70f01ad
feat: exported public key class
WyvernIXTL Mar 5, 2025
17f29e0
chore: version bump of rs-crypto
WyvernIXTL Mar 7, 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
Expand Up @@ -8,3 +8,4 @@ dist
dist-test
lib-web
lib-esm
test_cal_db
226 changes: 197 additions & 29 deletions package-lock.json

Large diffs are not rendered by default.

6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,19 +34,23 @@
"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.ts --project ./test/tsconfig.json --exit --delay --fail-zero",
"test:local:node": "npm run test:node",
"test:web": "browsertest-runner",
"test:web:debug": "browsertest-runner --debug"
},
"dependencies": {
"@nmshd/rs-crypto-types": "^0.2.1",
"@types/lodash": "^4.17.15",
"libsodium-wrappers-sumo": "0.7.15",
"lodash": "^4.17.21",
"uuid": "10.0.0"
},
"devDependencies": {
"@js-soft/eslint-config-ts": "^1.6.13",
"@js-soft/license-check": "1.0.9",
"@js-soft/ts-serval": "2.0.10",
"@nmshd/rs-crypto-node": "^0.2.0",
"@types/chai": "^5.0.0",
"@types/libsodium-wrappers-sumo": "^0.7.8",
"@types/mocha": "^10.0.9",
Expand Down
8 changes: 7 additions & 1 deletion src/CryptoErrorCode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,5 +46,11 @@ 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",
CalFailedLoadingProvider = "error.crypto.cal.failedLoadingProvider"
}
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));
}
}
80 changes: 80 additions & 0 deletions src/crypto-layer/CryptoAsymmetricKeyHandle.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { SerializableAsync, serialize, type, validate } from "@js-soft/ts-serval";
import { KeyPairHandle, KeyPairSpec, Provider } from "@nmshd/rs-crypto-types";
import { CoreBuffer } from "src/CoreBuffer";
import { CryptoError } from "../CryptoError";
import { CryptoErrorCode } from "../CryptoErrorCode";
import { CryptoSerializableAsync } from "../CryptoSerializable";
import { getProvider } from "./CryptoLayerProviders";

/**
* Loose check if `value` can be initialized as if it was a `CryptoAsymmetricKeyHandle`.
*/
function isCryptoAsymmetricKeyHandle(value: any): value is CryptoAsymmetricKeyHandle {
return typeof value["providerName"] === "string" && typeof value["id"] === "string";
}

@type("CryptoAsymmetricKeyHandle")
export class CryptoAsymmetricKeyHandle extends CryptoSerializableAsync {
@validate()
@serialize()
public spec: KeyPairSpec;

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

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

public provider: Provider;

public keyPairHandle: KeyPairHandle;

public static async newFromProviderAndKeyPairHandle<T extends CryptoAsymmetricKeyHandle>(
this: new () => T,
provider: Provider,
keyPairHandle: KeyPairHandle,
other?: {
providerName?: string;
keyId?: string;
keySpec?: KeyPairSpec;
}
): Promise<T> {
const result = new this();

result.providerName = other?.providerName ?? (await provider.providerName());
result.id = other?.keyId ?? (await keyPairHandle.id());
result.spec = other?.keySpec ?? (await keyPairHandle.spec());

result.provider = provider;
result.keyPairHandle = keyPairHandle;
return result;
}

public static async from(value: any): Promise<CryptoAsymmetricKeyHandle> {
return await this.fromAny(value);
}

public static async fromBase64(value: string): Promise<CryptoAsymmetricKeyHandle> {
return await this.deserialize(CoreBuffer.base64_utf8(value));
}

public static override async postFrom<T extends SerializableAsync>(value: T): Promise<T> {
if (!isCryptoAsymmetricKeyHandle(value)) {
throw new CryptoError(CryptoErrorCode.WrongParameters, `Expected 'CryptoAsymmetricKeyHandle'.`);
}
const provider = getProvider({ providerName: value.providerName });
if (!provider) {
throw new CryptoError(
CryptoErrorCode.CalFailedLoadingProvider,
`Failed loading provider ${value.providerName}`
);
}
const keyHandle = await provider.loadKeyPair(value.id);

value.keyPairHandle = keyHandle;
value.provider = provider;
return value;
}
}
33 changes: 33 additions & 0 deletions src/crypto-layer/CryptoExportedPublicKey.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { serialize, type, validate } from "@js-soft/ts-serval";
import { KeyPairHandle, KeyPairSpec, Provider } from "@nmshd/rs-crypto-types";
import { CoreBuffer } from "src/CoreBuffer";
import { CryptoSerializable } from "src/CryptoSerializable";
import { getProviderOrThrow, ProviderIdentifier } from "./CryptoLayerProviders";
import { CryptoPublicKeyHandle } from "./CryptoPublicKeyHandle";

@type("CryptoExportedPublicKey")
export class CryptoExportedPublicKey extends CryptoSerializable {
@validate()
@serialize()
public rawPublicKey: CoreBuffer;

@validate()
@serialize()
public spec: KeyPairSpec;

public async into<T extends CryptoPublicKeyHandle>(
constructor: { newFromProviderAndKeyPairHandle(provider: Provider, keyPairHandle: KeyPairHandle): Promise<T> },
providerIdent: ProviderIdentifier
): Promise<T> {
const provider = getProviderOrThrow(providerIdent);
const keyPairHandle = await provider.importPublicKey(this.spec, this.rawPublicKey.buffer);
return await constructor.newFromProviderAndKeyPairHandle(provider, keyPairHandle);
}

public static async from(publicKeyHandle: CryptoPublicKeyHandle): Promise<CryptoExportedPublicKey> {
const exportedPublicKey = new CryptoExportedPublicKey();
exportedPublicKey.spec = publicKeyHandle.spec;
exportedPublicKey.rawPublicKey = new CoreBuffer(await publicKeyHandle.keyPairHandle.getPublicKey());
return exportedPublicKey;
}
}
34 changes: 34 additions & 0 deletions src/crypto-layer/CryptoLayerConfig.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/* eslint-disable @typescript-eslint/naming-convention */
import { AdditionalConfig, ProviderConfig, ProviderFactoryFunctions, SecurityLevel } from "@nmshd/rs-crypto-types";

/**
* Interface holding functions for listing and creating providers and configuration for initializing the key meta data store.
*
* @property factoryFunctions - Functions that list providers or create providers.
* @property keyMetadataStoreConfig - Configuration needed for saving key metadata.
* @property keyMetadataStoreAuth - ~~Optional~~ configuration for authenticating key metadata.
* @property providersToBeInitialized - Array of providers to initalize.
*/
export interface CryptoLayerConfig {
factoryFunctions: ProviderFactoryFunctions;
keyMetadataStoreConfig: Extract<AdditionalConfig, { KVStoreConfig: any } | { FileStoreConfig: any }>;
keyMetadataStoreAuth?: Extract<
AdditionalConfig,
{ StorageConfigHMAC: any } | { StorageConfigDSA: any } | { StorageConfigPass: any }
>;
providersToBeInitialized: CryptoLayerProviderFilter[];
}

/**
* Reference to a specific provider, if a name is supplied, or any provider that fullfills the other requirements.
*/
export type CryptoLayerProviderFilter =
| {
providerName: string;
}
| {
securityLevel: SecurityLevel;
}
| {
providerConfig: ProviderConfig;
};
153 changes: 153 additions & 0 deletions src/crypto-layer/CryptoLayerProviders.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
/* eslint-disable @typescript-eslint/naming-convention */
import {
Provider,
ProviderConfig,
ProviderFactoryFunctions,
ProviderImplConfig,
SecurityLevel
} from "@nmshd/rs-crypto-types";

import { defaults } from "lodash";
import { CryptoError } from "../CryptoError";
import { CryptoErrorCode } from "../CryptoErrorCode";
import { CryptoLayerConfig, CryptoLayerProviderFilter } from "./CryptoLayerConfig";

let PROVIDERS_BY_SECURITY: Map<SecurityLevel, Provider[]> | undefined = undefined;
let PROVIDERS_BY_NAME: Map<string, Provider> | undefined = undefined;

const DEFAULT_PROVIDER_CONFIG: ProviderConfig = {
max_security_level: "Hardware",
min_security_level: "Software",
supported_asym_spec: ["P256", "Curve25519"],
supported_ciphers: ["AesCbc256", "AesGcm256"],
supported_hashes: ["Sha2_256", "Sha2_512"]
};

async function providerBySecurityMapFromProviderByNameMap(
providersByName: Map<string, Provider>
): Promise<Map<SecurityLevel, Provider[]>> {
const providersBySecurity = new Map<SecurityLevel, Provider[]>();
for (const [providerName, provider] of providersByName) {
const caps = await provider.getCapabilities();
if (!caps) {
throw new CryptoError(
CryptoErrorCode.CalFailedLoadingProvider,
`Failed fetching capabilities or security levels of provider ${providerName}`
);
}
if (caps.max_security_level !== caps.min_security_level) {
throw new CryptoError(
CryptoErrorCode.CalFailedLoadingProvider,
`Minimum and maximum security levels of provider ${providerName} must be the same.`
);
}
const securityLevel = caps.min_security_level;

if (!providersBySecurity.has(securityLevel)) {
providersBySecurity.set(securityLevel, []);
}

providersBySecurity.get(securityLevel)!.push(provider);
}
return providersBySecurity;
}

/**
* Creates a provider if possible with the given provider filter. This means, that the provider created must adhere to the filter.
*
* If a `SecurityLevel` is given, the default provider config (`DEFAULT_PROVIDER_CONFIG`) will be used to fill in the rest for the selection.
*/
async function createProviderFromProviderFilter(
providerToBeInitialized: CryptoLayerProviderFilter,
factoryFunctions: ProviderFactoryFunctions,
providerImplConfig: ProviderImplConfig
): Promise<Provider | undefined> {
if ("providerName" in providerToBeInitialized) {
return await factoryFunctions.createProviderFromName(providerToBeInitialized.providerName, providerImplConfig);
}
if ("securityLevel" in providerToBeInitialized) {
const providerConfig: ProviderConfig = defaults(
{
max_security_level: providerToBeInitialized.securityLevel,
min_security_level: providerToBeInitialized.securityLevel
},
DEFAULT_PROVIDER_CONFIG
);
return await factoryFunctions.createProvider(providerConfig, providerImplConfig);
}
if ("providerConfig" in providerToBeInitialized) {
return await factoryFunctions.createProvider(providerToBeInitialized.providerConfig, providerImplConfig);
}

throw new CryptoError(CryptoErrorCode.WrongParameters);
}

/**
* Intializes global providers with the given configuration.
*
* This enables the crypto layer functionality.
*
* @param config Configuration to initialize providers. At least one software provider should be initialized.
*/
export async function initCryptoLayerProviders(config: CryptoLayerConfig): Promise<void> {
if (PROVIDERS_BY_NAME || PROVIDERS_BY_SECURITY) {
return;
}

const providerImplConfig: ProviderImplConfig = { additional_config: [config.keyMetadataStoreConfig] };
if (config.keyMetadataStoreAuth) {
providerImplConfig.additional_config.push(config.keyMetadataStoreAuth);
}

const providers: Map<string, Provider> = new Map();

for (const providerFilter of config.providersToBeInitialized) {
const provider = await createProviderFromProviderFilter(
providerFilter,
config.factoryFunctions,
providerImplConfig
);

if (!provider) {
throw new CryptoError(CryptoErrorCode.CalFailedLoadingProvider, `Failed loading provider.`);
}

providers.set(await provider.providerName(), provider);
}

PROVIDERS_BY_NAME = providers;
PROVIDERS_BY_SECURITY = await providerBySecurityMapFromProviderByNameMap(PROVIDERS_BY_NAME);
}

export type ProviderIdentifier = Exclude<CryptoLayerProviderFilter, { providerConfig: any }>;

/**
* Returns an initialized provider with the given name or security level if possible.
*
* Returns `undefined` if providers are not initialized or provider asked for was not initialized by `initCryptoLayerProviders`.
*/
export function getProvider(identifier: ProviderIdentifier): Provider | undefined {
if (!PROVIDERS_BY_NAME || !PROVIDERS_BY_SECURITY) {
return undefined;
}

if ("providerName" in identifier) {
return PROVIDERS_BY_NAME.get(identifier.providerName);
}
if ("securityLevel" in identifier) {
return PROVIDERS_BY_SECURITY.get(identifier.securityLevel)?.[0];
}

throw new CryptoError(CryptoErrorCode.WrongParameters);
}

export function getProviderOrThrow(identifier: ProviderIdentifier): Provider {
const provider = getProvider(identifier);
if (!provider) {
throw new CryptoError(
CryptoErrorCode.WrongParameters,
`Failed finding provider with name or security level ${identifier}`
);
}
return provider;
}
Loading