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
10 changes: 10 additions & 0 deletions .changeset/heavy-wasps-grow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
"@openwallet-foundation/askar-react-native": minor
"@openwallet-foundation/askar-nodejs": minor
"@openwallet-foundation/askar-shared": minor
---

feat: update to Askar 0.5.0 release.

- This adds support for providing a custom Argon2 config.
- In addition it adds methods to list the open sessions and scans on a store. Note however that these methods are only implemented in the Node.JS wrapper for now, they will be added to the React Native wrapper at a later stage.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@ const key = Key.fromSeed({ algorithm: KeyAlgs.Bls12381G1, seed });

## Version Compatibility

The JavaScript wrapper is versioned independently from the native bindings. The following table shows the compatibility between the different versions. This library has been tested with specific Node.JS and React Native versions. Newer versions might also work, but they have not been tested.
The JavaScript wrapper is versioned independently from the native bindings. The following table shows the compatibility between the different versions. This library has been tested with specific Node.JS and React Native versions. Newer or older versions might also work, but they have not been tested.

| JavaScript Wrapper | Askar | React Native | Node.JS |
| ------------------ | ------ | ------------ | ---------- |
| v0.3.0 - v0.3.1 | v0.4.1 | - | - |
| v0.4.0 - v0.4.2 | v0.4.5 | 0.75 - 0.79 | 18, 20, 22 |
| v0.4.3 | v0.4.6 | 0.75 - 0.79 | 18, 20, 22 |
| v0.5.0 | v0.4.6 | 0.75 - 0.79 | 20, 22, 24 |
| v0.5.0 | v0.5.0 | 0.76 - 0.81 | 20, 22, 24 |
2 changes: 1 addition & 1 deletion packages/askar-nodejs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
"typescript": "catalog:"
},
"binary": {
"version": "v0.4.6",
"version": "v0.5.0",
"host": "https://github.com/openwallet-foundation/askar/releases/download",
"packageName": "library-{platform}-{arch}.tar.gz"
},
Expand Down
48 changes: 47 additions & 1 deletion packages/askar-nodejs/src/NodeJSAskar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ import type {
StoreGetDefaultProfileOptions,
StoreGetProfileNameOptions,
StoreListProfilesOptions,
StoreListScansOptions,
StoreListSessionsOptions,
StoreOpenOptions,
StoreProvisionOptions,
StoreRekeyOptions,
Expand Down Expand Up @@ -103,6 +105,7 @@ import {
allocateStringListHandle,
type ByteBufferType,
deallocateCallbackBuffer,
decodeHandleList,
type EncryptedBufferType,
encryptedBufferStructToClass,
FFI_ENTRY_LIST_HANDLE,
Expand All @@ -116,6 +119,7 @@ import {
FFI_STRING_LIST_HANDLE,
type NativeCallback,
type NativeCallbackWithResponse,
type NodeJsHandleList,
secretBufferToUint8Array,
serializeArguments,
toNativeCallback,
Expand Down Expand Up @@ -278,7 +282,14 @@ export class NodeJSAskar implements Askar {

const ret = allocateSecretBuffer()

const errorCode = this.nativeAskar.askar_argon2_derive_password(parameters, password, salt, ret)
const errorCode = this.nativeAskar.askar_argon2_derive_password(
parameters,
password,
salt,
// NOTE: we should not serialize the config, it should be passed as struct
options.config ?? null,
ret
)

this.handleError(errorCode)
const byteBuffer = handleReturnPointer<ByteBufferType>(ret)
Expand Down Expand Up @@ -348,6 +359,10 @@ export class NodeJSAskar implements Askar {
return uint8Array
}

private handleListFree(options: { handleList: NodeJsHandleList }): void {
this.nativeAskar.askar_handle_list_free(options.handleList)
}

public keyAeadDecrypt(options: KeyAeadDecryptOptions): Uint8Array {
const { ciphertext, localKeyHandle, nonce, aad, tag } = serializeArguments({
localKeyHandle: options.localKeyHandle,
Expand Down Expand Up @@ -1122,6 +1137,37 @@ export class NodeJSAskar implements Askar {
return ret
}

public async storeListScans(options: StoreListScansOptions): Promise<ScanHandle[]> {
const { storeHandle } = serializeArguments(options)
const handleList = await this.promisifyWithResponse<NodeJsHandleList>(
(cb, cbId) => this.nativeAskar.askar_store_list_scans(storeHandle, cb, cbId),
'FfiHandleList'
)
if (!handleList) return []

const scanHandleList = decodeHandleList(handleList).map((scanHandle) => ScanHandle.fromHandle(scanHandle))
this.handleListFree({ handleList })

return scanHandleList
}

public async storeListSessions(options: StoreListSessionsOptions): Promise<SessionHandle[]> {
const { storeHandle } = serializeArguments(options)
const handleList = await this.promisifyWithResponse<NodeJsHandleList>(
(cb, cbId) => this.nativeAskar.askar_store_list_sessions(storeHandle, cb, cbId),
'FfiHandleList'
)

if (!handleList) return []

const sessionHandleList = decodeHandleList(handleList).map((sessionHandle) =>
SessionHandle.fromHandle(sessionHandle)
)
this.handleListFree({ handleList })

return sessionHandleList
}

public async storeOpen(options: StoreOpenOptions): Promise<StoreHandle> {
const { profile, keyMethod, passKey, specUri } = serializeArguments(options)

Expand Down
14 changes: 14 additions & 0 deletions packages/askar-nodejs/src/ffi/conversion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,20 @@ export const uint8ArrayToByteBufferStruct = (buffer: Uint8Array = new Uint8Array
return { data: buffer, len: buffer.length }
}

export type NodeJsHandleList = {
len: number
data: unknown
}

export const decodeHandleList = (handleList: NodeJsHandleList): number[] => {
// With koffi, pointer data needs to be decoded
const { data, len } = handleList

// If data is a koffi external pointer, decode it as uint8 array
const decoded = koffi.decode(data, 'size_t', len)
return decoded
}

export const byteBufferToUint8Array = (byteBuffer: ByteBufferType): Uint8Array => {
// With koffi, pointer data needs to be decoded
const { data, len } = byteBuffer
Expand Down
28 changes: 28 additions & 0 deletions packages/askar-nodejs/src/ffi/structures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,31 @@ export const AeadParamsStruct = koffi.struct('AeadParams', {
nonceLength: FFI_INT32,
tagLength: FFI_INT32,
})

// Argon2Config structure
export const Argon2ConfigStruct = koffi.struct('Argon2Config', {
algorithm: FFI_INT32,
version: FFI_INT32,
parallelism: FFI_INT32,
mem_cost: FFI_INT32,
time_cost: FFI_INT32,
})

export type Argon2ConfigType = {
algorithm: number
version: number
parallelism: number
mem_cost: number
time_cost: number
}

// FfiHandleList structure
export const FfiHandleListStruct = koffi.struct('FfiHandleList', {
len: FFI_INT32,
data: koffi.pointer('size_t'),
})

export type FfiHandleListType = {
len: number
data: number[]
}
8 changes: 7 additions & 1 deletion packages/askar-nodejs/src/library/bindings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ const ByteBufferType = 'ByteBuffer'
const SecretBufferType = 'ByteBuffer' // SecretBuffer is same as ByteBuffer
const EncryptedBufferType = 'EncryptedBuffer'
const AeadParamsType = 'AeadParams'
const Argon2ConfigType = 'Argon2Config'
const FfiHandleListType = 'FfiHandleList'

// Callback signatures for koffi - must NOT be pointers in signatures
// koffi expects callback types without the * pointer syntax
Expand All @@ -46,7 +48,7 @@ export const nativeBindings = {
askar_set_default_logger: `${FFI_ERROR_CODE} askar_set_default_logger()`,
askar_set_max_log_level: `${FFI_ERROR_CODE} askar_set_max_log_level(${FFI_INT32} maxLevel)`,

askar_argon2_derive_password: `${FFI_ERROR_CODE} askar_argon2_derive_password(${FFI_INT8} parameters, ${ByteBufferType} password, ${ByteBufferType} salt, _Out_ ${SecretBufferType} *ret)`,
askar_argon2_derive_password: `${FFI_ERROR_CODE} askar_argon2_derive_password(${FFI_INT8} parameters, ${ByteBufferType} password, ${ByteBufferType} salt, ${Argon2ConfigType} *config, _Out_ ${SecretBufferType} *ret)`,

askar_entry_list_count: `${FFI_ERROR_CODE} askar_entry_list_count(${FFI_ENTRY_LIST_HANDLE} handle, _Out_ ${FFI_INT32_PTR} count)`,
askar_entry_list_free: `${FFI_VOID} askar_entry_list_free(${FFI_ENTRY_LIST_HANDLE} handle)`,
Expand All @@ -55,6 +57,8 @@ export const nativeBindings = {
askar_entry_list_get_tags: `${FFI_ERROR_CODE} askar_entry_list_get_tags(${FFI_ENTRY_LIST_HANDLE} handle, ${FFI_INT32} index, _Out_ ${FFI_STRING_PTR} tags)`,
askar_entry_list_get_value: `${FFI_ERROR_CODE} askar_entry_list_get_value(${FFI_ENTRY_LIST_HANDLE} handle, ${FFI_INT32} index, _Out_ ${SecretBufferType} *value)`,

askar_handle_list_free: `${FFI_VOID} askar_handle_list_free(${FfiHandleListType} handle)`,

askar_string_list_count: `${FFI_ERROR_CODE} askar_string_list_count(${FFI_STRING_LIST_HANDLE} handle, _Out_ ${FFI_INT32_PTR} count)`,
askar_string_list_free: `${FFI_VOID} askar_string_list_free(${FFI_STRING_LIST_HANDLE} handle)`,
askar_string_list_get_item: `${FFI_ERROR_CODE} askar_string_list_get_item(${FFI_STRING_LIST_HANDLE} handle, ${FFI_INT32} index, _Out_ ${FFI_STRING_PTR} item)`,
Expand Down Expand Up @@ -123,6 +127,8 @@ export const nativeBindings = {
askar_store_get_profile_name: `${FFI_ERROR_CODE} askar_store_get_profile_name(${FFI_STORE_HANDLE} handle, ${CallbackString} cb, ${FFI_CALLBACK_ID} cbId)`,
askar_store_get_default_profile: `${FFI_ERROR_CODE} askar_store_get_default_profile(${FFI_STORE_HANDLE} handle, ${CallbackString} cb, ${FFI_CALLBACK_ID} cbId)`,
askar_store_list_profiles: `${FFI_ERROR_CODE} askar_store_list_profiles(${FFI_STORE_HANDLE} handle, ${CallbackHandle} cb, ${FFI_CALLBACK_ID} cbId)`,
askar_store_list_scans: `${FFI_ERROR_CODE} askar_store_list_scans(${FFI_STORE_HANDLE} handle, ${CallbackHandle} cb, ${FFI_CALLBACK_ID} cbId)`,
askar_store_list_sessions: `${FFI_ERROR_CODE} askar_store_list_sessions(${FFI_STORE_HANDLE} handle, ${CallbackHandle} cb, ${FFI_CALLBACK_ID} cbId)`,
askar_store_open: `${FFI_ERROR_CODE} askar_store_open(${FFI_STRING} specUri, ${FFI_STRING} keyMethod, ${FFI_STRING} passKey, ${FFI_STRING} profile, ${CallbackHandle} cb, ${FFI_CALLBACK_ID} cbId)`,
askar_store_provision: `${FFI_ERROR_CODE} askar_store_provision(${FFI_STRING} specUri, ${FFI_STRING} keyMethod, ${FFI_STRING} passKey, ${FFI_STRING} profile, ${FFI_INT8} recreate, ${CallbackHandle} cb, ${FFI_CALLBACK_ID} cbId)`,
askar_store_rekey: `${FFI_ERROR_CODE} askar_store_rekey(${FFI_STORE_HANDLE} handle, ${FFI_STRING} keyMethod, ${FFI_STRING} passKey, ${CallbackBasic} cb, ${FFI_CALLBACK_ID} cbId)`,
Expand Down
26 changes: 25 additions & 1 deletion packages/askar-nodejs/tests/kdf.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Argon2, Argon2Parameters } from '@openwallet-foundation/askar-shared'
import { Argon2, Argon2Algorithm, Argon2Parameters, Argon2Version } from '@openwallet-foundation/askar-shared'
import { describe, expect, test } from 'vitest'

describe('Argon2', () => {
Expand All @@ -15,4 +15,28 @@ describe('Argon2', () => {
'9ef87bcf828c46c0136a0d1d9e391d713f75b327c6dc190455bd36c1bae33259'
)
})

test('derive password matches react-native-argon2 output', () => {
const password = '000000'
const salt = '13622169116451511306218218219151372412051474242757211221731116372255771137912226'

const passwordBytes = Uint8Array.from(Buffer.from(password))
const saltBytes = Uint8Array.from(Buffer.from(salt))

const derivedPassword = Argon2.derivePassword(
{
algorithm: Argon2Algorithm.Argon2id,
version: Argon2Version.V0x13,
parallelism: 4,
memCost: 64 * 1024,
timeCost: 8,
},
passwordBytes,
saltBytes
)

expect(Buffer.from(derivedPassword).toString('hex')).toBe(
'1128133bb2b55a35c801f1dfc99a525cb8ff27a519bcd035f1a07f9a1cf6eae9'
)
})
})
18 changes: 18 additions & 0 deletions packages/askar-nodejs/tests/store.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,24 @@ describe('Store and Session', () => {
await scanStore.close()
})

test('open sessions and scans', async () => {
const store = await setupWallet()
const session = await store.openSession()

const openSessions = await store.listOpenSessions()
expect(openSessions).toHaveLength(1)
expect(openSessions[0].handle).toEqual(session.handle?.handle)

await session.close()
await expect(store.listOpenSessions()).resolves.toHaveLength(0)

await expect(store.listOpenScans()).resolves.toHaveLength(0)
await store.scan({ category: firstEntry.category }).fetchAll()
await expect(store.listOpenScans()).resolves.toHaveLength(0)

await store.close()
})

test('Transaction basic', async () => {
const txn = await store.openSession(true)

Expand Down
Loading