diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d2be7ad..fd1cdfd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -32,9 +32,6 @@ jobs: - name: Check Style run: pnpm style:check - - name: Check Types - run: pnpm types:check - - name: Compile run: pnpm build diff --git a/package.json b/package.json index c039af7..56fd95d 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "scripts": { "style:check": "biome check --unsafe .", "style:fix": "pnpm style:check --write", - "types:check": "pnpm -r check-types", + "types:check": "pnpm -r types:check", "build": "pnpm -r build", "clean": "pnpm -r clean", "test": "node --import tsx --test packages/**/tests/*.test.ts", diff --git a/packages/askar-nodejs/package.json b/packages/askar-nodejs/package.json index 6dfbf94..d1ef3b4 100644 --- a/packages/askar-nodejs/package.json +++ b/packages/askar-nodejs/package.json @@ -37,7 +37,7 @@ "typescript": "catalog:" }, "binary": { - "version": "v0.4.5", + "version": "v0.4.6", "host": "https://github.com/openwallet-foundation/askar/releases/download", "packageName": "library-{platform}-{arch}.tar.gz" }, diff --git a/packages/askar-nodejs/src/NodeJSAskar.ts b/packages/askar-nodejs/src/NodeJSAskar.ts index 1ab24b6..3d0cf07 100644 --- a/packages/askar-nodejs/src/NodeJSAskar.ts +++ b/packages/askar-nodejs/src/NodeJSAskar.ts @@ -1,5 +1,6 @@ import type { AeadParamsOptions, + Argon2DerivePasswordOptions, Askar, AskarErrorObject, EncryptedBuffer, @@ -263,6 +264,21 @@ export class NodeJSAskar implements Askar { this.handleError(errorCode) } + public argon2DerivePassword(options: Argon2DerivePasswordOptions) { + const { parameters, password, salt } = serializeArguments(options) + + const ret = allocateSecretBuffer() + + const errorCode = this.nativeAskar.askar_argon2_derive_password(parameters, password, salt, ret) + + this.handleError(errorCode) + const byteBuffer = handleReturnPointer(ret) + const bufferArray = new Uint8Array(Buffer.from(secretBufferToBuffer(byteBuffer))) + this.nativeAskar.askar_buffer_free(byteBuffer) + + return bufferArray + } + public entryListCount(options: EntryListCountOptions): number { const { entryListHandle } = serializeArguments(options) const ret = allocateInt32Buffer() diff --git a/packages/askar-nodejs/src/library/bindings.ts b/packages/askar-nodejs/src/library/bindings.ts index e938879..0634ad3 100644 --- a/packages/askar-nodejs/src/library/bindings.ts +++ b/packages/askar-nodejs/src/library/bindings.ts @@ -36,6 +36,8 @@ export const nativeBindings = { askar_set_default_logger: [FFI_ERROR_CODE, []], askar_set_max_log_level: [FFI_ERROR_CODE, [FFI_INT32]], + askar_argon2_derive_password: [FFI_ERROR_CODE, [FFI_INT8, ByteBufferStruct, ByteBufferStruct, SecretBufferStructPtr]], + askar_entry_list_count: [FFI_ERROR_CODE, [FFI_ENTRY_LIST_HANDLE, FFI_INT32_PTR]], askar_entry_list_free: [FFI_VOID, [FFI_ENTRY_LIST_HANDLE]], askar_entry_list_get_category: [FFI_ERROR_CODE, [FFI_ENTRY_LIST_HANDLE, FFI_INT32, FFI_STRING_PTR]], diff --git a/packages/askar-nodejs/tests/indy_wallet_sqlite_upgraded.db b/packages/askar-nodejs/tests/indy_wallet_sqlite_upgraded.db index 4754d82..070a544 100644 Binary files a/packages/askar-nodejs/tests/indy_wallet_sqlite_upgraded.db and b/packages/askar-nodejs/tests/indy_wallet_sqlite_upgraded.db differ diff --git a/packages/askar-nodejs/tests/kdf.test.ts b/packages/askar-nodejs/tests/kdf.test.ts new file mode 100644 index 0000000..2e79367 --- /dev/null +++ b/packages/askar-nodejs/tests/kdf.test.ts @@ -0,0 +1,23 @@ +import { ok } from 'node:assert' +import { before, describe, test } from 'node:test' +import { Argon2, Argon2Parameters } from '@openwallet-foundation/askar-shared' +import { setup } from './utils' + +describe('Argon2', () => { + before(setup) + + test('derive password', () => { + const password = 'my password' + const salt = 'long enough salt' + + const passwordBytes = Uint8Array.from(Buffer.from(password)) + const saltBytes = Uint8Array.from(Buffer.from(salt)) + + const derivedPassword = Argon2.derivePassword(Argon2Parameters.Interactive, passwordBytes, saltBytes) + + ok( + Buffer.from(derivedPassword).toString('hex') === + '9ef87bcf828c46c0136a0d1d9e391d713f75b327c6dc190455bd36c1bae33259' + ) + }) +}) diff --git a/packages/askar-react-native/cpp/HostObject.cpp b/packages/askar-react-native/cpp/HostObject.cpp index 66d369e..045dfe7 100644 --- a/packages/askar-react-native/cpp/HostObject.cpp +++ b/packages/askar-react-native/cpp/HostObject.cpp @@ -15,6 +15,9 @@ FunctionMap AskarTurboModuleHostObject::functionMapping(jsi::Runtime &rt) { fMap.insert( std::make_tuple("setDefaultLogger", &askar::setDefaultLogger)); + fMap.insert( + std::make_tuple("argon2DerivePassword", &askar::argon2DerivePassword)); + fMap.insert(std::make_tuple("storeCopyTo", &askar::storeCopyTo)); fMap.insert(std::make_tuple("storeOpen", &askar::storeOpen)); fMap.insert( diff --git a/packages/askar-react-native/cpp/askar.cpp b/packages/askar-react-native/cpp/askar.cpp index 4b24e74..d71c151 100644 --- a/packages/askar-react-native/cpp/askar.cpp +++ b/packages/askar-react-native/cpp/askar.cpp @@ -22,6 +22,23 @@ jsi::Value setDefaultLogger(jsi::Runtime &rt, jsi::Object options) { return createReturnValue(rt, code, nullptr); } +jsi::Value argon2DerivePassword(jsi::Runtime &rt, jsi::Object options) { + auto parameters = + jsiToValue(rt, options, "parameters"); + auto password = + jsiToValue(rt, options, "password"); + auto salt = + jsiToValue(rt, options, "salt"); + + SecretBuffer out; + + ErrorCode code = askar_argon2_derive_password(parameters, password, salt, &out); + + auto ret = createReturnValue(rt, code, &out); + askar_buffer_free(out); + return ret; +} + jsi::Value entryListCount(jsi::Runtime &rt, jsi::Object options) { auto entryListHandle = jsiToValue(rt, options, "entryListHandle"); diff --git a/packages/askar-react-native/cpp/askar.h b/packages/askar-react-native/cpp/askar.h index b260943..61dbba7 100644 --- a/packages/askar-react-native/cpp/askar.h +++ b/packages/askar-react-native/cpp/askar.h @@ -20,6 +20,8 @@ jsi::Value setDefaultLogger(jsi::Runtime &rt, jsi::Object options); // jsi::Value setMaxLogLevel(jsi::Runtime &rt, jsi::Object options); // jsi::Value clearCustomLogger(jsi::Runtime &rt, jsi::Object options); +jsi::Value argon2DerivePassword(jsi::Runtime &rt, jsi::Object options); + jsi::Value entryListCount(jsi::Runtime &rt, jsi::Object options); jsi::Value entryListFree(jsi::Runtime &rt, jsi::Object options); jsi::Value entryListGetCategory(jsi::Runtime &rt, jsi::Object options); diff --git a/packages/askar-react-native/cpp/include/libaries_askar.h b/packages/askar-react-native/cpp/include/libaries_askar.h index 5e120a5..3ba8e60 100644 --- a/packages/askar-react-native/cpp/include/libaries_askar.h +++ b/packages/askar-react-native/cpp/include/libaries_askar.h @@ -48,25 +48,6 @@ typedef struct Option_EnabledCallback Option_EnabledCallback; typedef struct Option_FlushCallback Option_FlushCallback; -typedef struct SecretBuffer { - int64_t len; - uint8_t *data; -} SecretBuffer; - -typedef struct FfiResultList_Entry FfiEntryList; - -typedef struct ArcHandle_FfiEntryList { - const FfiEntryList *_0; -} ArcHandle_FfiEntryList; - -typedef struct ArcHandle_FfiEntryList EntryListHandle; - -typedef struct ArcHandle_LocalKey { - const struct LocalKey *_0; -} ArcHandle_LocalKey; - -typedef struct ArcHandle_LocalKey LocalKeyHandle; - /** * ByteBuffer is a struct that represents an array of bytes to be sent over the FFI boundaries. * There are several cases when you might want to use this, but the primary one for us @@ -153,6 +134,25 @@ typedef struct ByteBuffer { uint8_t *data; } ByteBuffer; +typedef struct SecretBuffer { + int64_t len; + uint8_t *data; +} SecretBuffer; + +typedef struct FfiResultList_Entry FfiEntryList; + +typedef struct ArcHandle_FfiEntryList { + const FfiEntryList *_0; +} ArcHandle_FfiEntryList; + +typedef struct ArcHandle_FfiEntryList EntryListHandle; + +typedef struct ArcHandle_LocalKey { + const struct LocalKey *_0; +} ArcHandle_LocalKey; + +typedef struct ArcHandle_LocalKey LocalKeyHandle; + typedef struct EncryptedBuffer { struct SecretBuffer buffer; int64_t tag_pos; @@ -241,6 +241,18 @@ typedef void (*LogCallback)(const void *context, extern "C" { #endif // __cplusplus +/** + * ## Derive password using Argon2 + * + * If the first provided argument is 1, it will use `PARAMS_INTERACTTIVE` and otherwise it will + * fallback to `PARAMS_MODERATE`. + * + */ +ErrorCode askar_argon2_derive_password(int8_t parameters, + struct ByteBuffer password, + struct ByteBuffer salt, + struct SecretBuffer *out); + void askar_buffer_free(struct SecretBuffer buffer); void askar_clear_custom_logger(void); diff --git a/packages/askar-react-native/package.json b/packages/askar-react-native/package.json index a75b8ba..cb59e3b 100644 --- a/packages/askar-react-native/package.json +++ b/packages/askar-react-native/package.json @@ -49,7 +49,7 @@ "react-native": ">= 0.71" }, "binary": { - "version": "v0.4.5", + "version": "v0.4.6", "host": "https://github.com/openwallet-foundation/askar/releases/download", "packageName": "library-ios-android.tar.gz" } diff --git a/packages/askar-react-native/src/NativeBindings.ts b/packages/askar-react-native/src/NativeBindings.ts index 3a2d368..a2f260e 100644 --- a/packages/askar-react-native/src/NativeBindings.ts +++ b/packages/askar-react-native/src/NativeBindings.ts @@ -7,6 +7,12 @@ export interface NativeBindings { version(options: Record): string getCurrentError(options: Record): string + argon2DerivePassword(options: { + parameters: number + password: ArrayBuffer + salt: ArrayBuffer + }): ReturnObject + entryListCount(options: { entryListHandle: string }): ReturnObject entryListFree(options: { entryListHandle: string }): ReturnObject entryListGetCategory(options: { entryListHandle: string; index: number }): ReturnObject diff --git a/packages/askar-react-native/src/ReactNativeAskar.ts b/packages/askar-react-native/src/ReactNativeAskar.ts index 188766b..4ae8b5e 100644 --- a/packages/askar-react-native/src/ReactNativeAskar.ts +++ b/packages/askar-react-native/src/ReactNativeAskar.ts @@ -1,4 +1,5 @@ import type { + Argon2DerivePasswordOptions, Askar, AskarErrorObject, EntryListCountOptions, @@ -76,9 +77,6 @@ import type { StoreRenameProfileOptions, StoreSetDefaultProfileOptions, } from '@openwallet-foundation/askar-shared' -import type { NativeBindings } from './NativeBindings' -import type { Callback, CallbackWithResponse, ReturnObject } from './serialize' - import { AeadParams, AskarError, @@ -91,7 +89,8 @@ import { StoreHandle, handleInvalidNullResponse, } from '@openwallet-foundation/askar-shared' - +import type { NativeBindings } from './NativeBindings' +import type { Callback, CallbackWithResponse, ReturnObject } from './serialize' import { serializeArguments } from './serialize' export class ReactNativeAskar implements Askar { @@ -187,6 +186,11 @@ export class ReactNativeAskar implements Askar { throw new Error('Method not implemented. setMaxLogLevel') } + public argon2DerivePassword(options: Argon2DerivePasswordOptions): Uint8Array { + const serializedOptions = serializeArguments(options) + return handleInvalidNullResponse(this.handleError(this.askar.argon2DerivePassword(serializedOptions))) + } + public entryListCount(options: EntryListCountOptions): number { const serializedOptions = serializeArguments(options) return handleInvalidNullResponse(this.handleError(this.askar.entryListCount(serializedOptions))) diff --git a/packages/askar-shared/src/askar/Askar.ts b/packages/askar-shared/src/askar/Askar.ts index 2ee5e5f..f803b44 100644 --- a/packages/askar-shared/src/askar/Askar.ts +++ b/packages/askar-shared/src/askar/Askar.ts @@ -33,6 +33,12 @@ export type SetCustomLoggerOptions = { } export type SetMaxLogLevelOptions = { logLevel: number } +export type Argon2DerivePasswordOptions = { + parameters: number + password: Uint8Array + salt: Uint8Array +} + export type EntryListCountOptions = { entryListHandle: EntryListHandle } export type EntryListFreeOptions = { entryListHandle: EntryListHandle } export type EntryListGetCategoryOptions = { @@ -363,6 +369,8 @@ export type Askar = { setDefaultLogger(): void setMaxLogLevel(options: SetMaxLogLevelOptions): void + argon2DerivePassword(options: Argon2DerivePasswordOptions): Uint8Array + entryListCount(options: EntryListCountOptions): number entryListFree(options: EntryListFreeOptions): void entryListGetCategory(options: EntryListGetCategoryOptions): string diff --git a/packages/askar-shared/src/crypto/Argon2.ts b/packages/askar-shared/src/crypto/Argon2.ts new file mode 100644 index 0000000..6943233 --- /dev/null +++ b/packages/askar-shared/src/crypto/Argon2.ts @@ -0,0 +1,12 @@ +import { askar } from '../askar' + +export enum Argon2Parameters { + Moderate = 0, + Interactive = 1, +} + +export class Argon2 { + public static derivePassword(parameters: Argon2Parameters, password: Uint8Array, salt: Uint8Array): Uint8Array { + return askar.argon2DerivePassword({ parameters, password, salt }) + } +} diff --git a/packages/askar-shared/src/crypto/index.ts b/packages/askar-shared/src/crypto/index.ts index 02422e0..ea251fa 100644 --- a/packages/askar-shared/src/crypto/index.ts +++ b/packages/askar-shared/src/crypto/index.ts @@ -1,5 +1,6 @@ export * from './handles' export * from './Key' +export * from './Argon2' export * from './EcdhEs' export * from './Ecdh1PU' export * from './CryptoBox'