Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
39b3ea3
feat: kms for secure env, node, askar
TimoGlastra Nov 30, 2024
d9d73f6
temp
TimoGlastra Dec 6, 2024
02d5e49
Merge remote-tracking branch 'upstream/main' into feat/kms
TimoGlastra Feb 19, 2025
ba5ae40
temp
TimoGlastra Feb 19, 2025
1e35cf3
chore: valibot to zod
TimoGlastra Feb 22, 2025
b605d0d
Merge remote-tracking branch 'upstream/main' into feat/kms
TimoGlastra Apr 7, 2025
70085ce
a lot of changes
TimoGlastra Apr 20, 2025
05043b0
tenants package working
TimoGlastra Apr 20, 2025
eda0701
qa package working
TimoGlastra Apr 23, 2025
6bc2963
drpc package working
TimoGlastra Apr 23, 2025
2409e45
cheqd package working
TimoGlastra Apr 23, 2025
1afa3ba
askar package working
TimoGlastra Apr 23, 2025
2a4f595
action menu package works
TimoGlastra Apr 23, 2025
064fb32
indy vdr package working
TimoGlastra Apr 27, 2025
5f960c9
test: v2-indy-connectionless-proofs.e2e.test.ts working
TimoGlastra Apr 28, 2025
f70f883
fixed more modules
TimoGlastra May 1, 2025
a1aa274
connections module working
TimoGlastra May 1, 2025
2fa433a
didcomm package working
TimoGlastra May 1, 2025
caa0d18
anoncreds package working
TimoGlastra May 1, 2025
df2b5fa
fix: revert bbs in kms
TimoGlastra May 2, 2025
47d93fe
docs(changeset): refactor!: remove support for BBS signatures
TimoGlastra May 2, 2025
cfa36cb
remove Key and KeyType
TimoGlastra May 2, 2025
5d2926a
more updates
TimoGlastra May 3, 2025
6963e16
more updates
TimoGlastra May 11, 2025
e7b5ea5
fix: use raw signatures for node kms
TimoGlastra May 11, 2025
f5a5f19
first openid4vc test working
TimoGlastra May 11, 2025
66d23bc
openid4vc package working
TimoGlastra May 12, 2025
0505a01
all packages except core pass
TimoGlastra May 12, 2025
d92dd4f
all tests working!!
TimoGlastra May 12, 2025
c42b537
i lied
TimoGlastra May 12, 2025
be06d89
docs(changeset): the automatic backup functionality has been removed …
TimoGlastra May 12, 2025
bc83a55
docs(changeset): the BBS module has been deprecated and removed. It w…
TimoGlastra May 12, 2025
3d0404f
docs(changeset): The wallet API has been completely rewritten to be m…
TimoGlastra May 12, 2025
c23ca9f
docs(changeset): The `Key` and `Jwk` classes have been removed in fav…
TimoGlastra May 12, 2025
270ef2a
docs(changeset): When signing with dids in Credo, it is now required …
TimoGlastra May 12, 2025
eaa3acd
docs(changeset): when signing in Credo, it is now required to always …
TimoGlastra May 12, 2025
c2e2718
fix test
TimoGlastra May 12, 2025
9c268c5
Merge remote-tracking branch 'upstream/main' into feat/kms
TimoGlastra May 12, 2025
7b13bdb
fix type errors
TimoGlastra May 12, 2025
324f8ad
fix type errors
TimoGlastra May 12, 2025
e4efce0
docs(changeset): The wallet config has been removed from the main age…
TimoGlastra May 12, 2025
90cca62
fix type errors
TimoGlastra May 12, 2025
0e31465
fix some tets
TimoGlastra May 12, 2025
9aaae00
fix demo
TimoGlastra May 12, 2025
cd40964
increase timeout as RSA signatures are kinda slow
TimoGlastra May 12, 2025
132246d
Merge remote-tracking branch 'upstream/main' into feat/kms
TimoGlastra May 15, 2025
40bccfd
first batch of feedback
TimoGlastra May 15, 2025
c4eb831
more feedback
TimoGlastra May 15, 2025
41defce
make jwk private
TimoGlastra May 15, 2025
e6f74f2
refactor key vs keyId
TimoGlastra May 15, 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
2 changes: 1 addition & 1 deletion packages/askar/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
},
"peerDependencies": {
"@openwallet-foundation/askar-shared": "^0.3.1",
"@animo-id/expo-secure-environment": "^0.1.0-alpha.11"
"@animo-id/expo-secure-environment": "^0.1.0-alpha.12"
},
"peerDependenciesMeta": {
"@animo-id/expo-secure-environment": {
Expand Down
87 changes: 87 additions & 0 deletions packages/askar/src/AskarApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { AgentContext } from '@credo-ts/core'
import { injectable } from 'tsyringe'

import { AskarStoreRotateKeyOptions, AskarStoreExportOptions, AskarStoreImportOptions } from './AskarApiOptions'
import { AskarStoreManager } from './AskarStoreManager'

@injectable()
export class AskarApi {
public constructor(private agentContext: AgentContext, private askarStoreManager: AskarStoreManager) {}

public get isStoreOpen() {
return this.askarStoreManager.isStoreOpen(this.agentContext)
}

/**
* @throws {AskarStoreDuplicateError} if the wallet already exists
* @throws {AskarStoreError} if another error occurs
*/
public async provisionStore(): Promise<void> {
await this.askarStoreManager.provisionStore(this.agentContext)
}

/**
* @throws {AskarStoreNotFoundError} if the wallet does not exist
* @throws {AskarStoreError} if another error occurs
*/
public async openStore(): Promise<void> {
await this.askarStoreManager.openStore(this.agentContext)
}

/**
* Rotate the key of the current askar store.
*
* NOTE: multiple agent contexts (tenants) can use the same store. This method rotates the key for the whole store,
* it is advised to only run this method on the root tenant agent when using profile per wallet database strategy.
* After running this method you should change the store configuration in the Askar module.
*
* @throws {AskarStoreNotFoundError} if the wallet does not exist
* @throws {AskarStoreError} if another error occurs
*/
public async rotateStoreKey(options: AskarStoreRotateKeyOptions): Promise<void> {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Askar specific operations will now be part of the askar api. So there's no global rotateWalletKey anymore, as that's specific to Askars way of doing this.

await this.askarStoreManager.rotateStoreKey(this.agentContext, options)
}

/**
* Exports the current askar store.
*
* NOTE: a store can contain profiles for multiple tenants. When you export a store
* all profiles will be exported with it.
*
* NOTE: store must be open before store can be expored
*/
public async exportStore(options: AskarStoreExportOptions) {
await this.askarStoreManager.exportStore(this.agentContext, options)
}

/**
* Imports from an external store config into the current askar store config.
*
* NOTE: store must be closed first (using `closeStore`) before store can be imported
*/
public async importStore(options: AskarStoreImportOptions) {
await this.askarStoreManager.importStore(this.agentContext, options)
}

/**
* Delete the current askar store.
*
* NOTE: multiple agent contexts (tenants) can use the same store. This method deletes the whole store.
*
*
* @throws {AskarStoreNotFoundError} if the wallet does not exist
* @throws {AskarStoreError} if another error occurs
*/
public async deleteStore(): Promise<void> {
await this.askarStoreManager.deleteStore(this.agentContext)
}

/**
* Close the current askar store.
*
* This will close all sessions (also for tenants) in this store.
*/
public async closeStore() {
await this.askarStoreManager.closeStore(this.agentContext)
}
}
28 changes: 28 additions & 0 deletions packages/askar/src/AskarApiOptions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import type { AskarModuleConfigStoreOptions } from './AskarModuleConfig'

export interface AskarStoreExportOptions {
/**
* The store config to export the current store to.
*/
exportToStore: AskarModuleConfigStoreOptions
}

export interface AskarStoreImportOptions {
/**
* The store config to import the current store from.
*/
importFromStore: AskarModuleConfigStoreOptions
}

export interface AskarStoreRotateKeyOptions {
/**
* The new key to use for the store.
*/
newKey: string

/**
* The new key derivation method to use for the store. If not provided the
* key derivation method from the current store config will be used.
*/
newKeyDerivationMethod?: AskarModuleConfigStoreOptions['keyDerivationMethod']
}
96 changes: 56 additions & 40 deletions packages/askar/src/AskarModule.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import type { AskarModuleConfigOptions } from './AskarModuleConfig'
import type { AgentContext, DependencyManager, Module } from '@credo-ts/core'

import { CredoError, InjectionSymbols } from '@credo-ts/core'
import { Store } from '@openwallet-foundation/askar-shared'
import { AgentConfig, CredoError, InjectionSymbols, Kms } from '@credo-ts/core'

import { AskarApi } from './AskarApi'
import { AskarMultiWalletDatabaseScheme, AskarModuleConfig } from './AskarModuleConfig'
import { AskarStoreManager } from './AskarStoreManager'
import { AksarKeyManagementService } from './kms/AskarKeyManagementService'
import { AskarStorageService } from './storage'
import { assertAskarWallet } from './utils/assertAskarWallet'
import { AskarProfileWallet, AskarWallet } from './wallet'

export class AskarModule implements Module {
public readonly config: AskarModuleConfig
Expand All @@ -16,52 +16,68 @@ export class AskarModule implements Module {
this.config = new AskarModuleConfig(config)
}

public api = AskarApi

public register(dependencyManager: DependencyManager) {
dependencyManager.registerInstance(AskarModuleConfig, this.config)

if (dependencyManager.isRegistered(InjectionSymbols.Wallet)) {
throw new CredoError('There is an instance of Wallet already registered')
} else {
dependencyManager.registerContextScoped(InjectionSymbols.Wallet, AskarWallet)
if (!this.config.enableKms && !this.config.enableStorage) {
dependencyManager
.resolve(AgentConfig)
.logger.warn(`Both 'enableKms' and 'enableStorage' are disabled, meaning Askar won't be used by the agent.`)
}

// If the multiWalletDatabaseScheme is set to ProfilePerWallet, we want to register the AskarProfileWallet
if (this.config.multiWalletDatabaseScheme === AskarMultiWalletDatabaseScheme.ProfilePerWallet) {
dependencyManager.registerContextScoped(AskarProfileWallet)
}
if (this.config.enableKms) {
const kmsConfig = dependencyManager.resolve(Kms.KeyManagementModuleConfig)
kmsConfig.registerBackend(new AksarKeyManagementService())
}

if (dependencyManager.isRegistered(InjectionSymbols.StorageService)) {
throw new CredoError('There is an instance of StorageService already registered')
} else {
if (this.config.enableStorage) {
if (dependencyManager.isRegistered(InjectionSymbols.StorageService)) {
throw new CredoError(
'Unable to register AskatStoreService. There is an instance of StorageService already registered'
)
}
dependencyManager.registerSingleton(InjectionSymbols.StorageService, AskarStorageService)
}

dependencyManager.registerSingleton(AskarStoreManager)
}

public async initialize(agentContext: AgentContext): Promise<void> {
// We MUST use an askar wallet here
assertAskarWallet(agentContext.wallet)

const wallet = agentContext.wallet

// Register the Askar store instance on the dependency manager
// This allows it to be re-used for tenants
agentContext.dependencyManager.registerInstance(Store, agentContext.wallet.store)

// If the multiWalletDatabaseScheme is set to ProfilePerWallet, we want to register the AskarProfileWallet
// and return that as the wallet for all tenants, but not for the main agent, that should use the AskarWallet
if (this.config.multiWalletDatabaseScheme === AskarMultiWalletDatabaseScheme.ProfilePerWallet) {
agentContext.dependencyManager.container.register(InjectionSymbols.Wallet, {
useFactory: (container) => {
// If the container is the same as the root dependency manager container
// it means we are in the main agent, and we should use the root wallet
if (container === agentContext.dependencyManager.container) {
return wallet
}

// Otherwise we want to return the AskarProfileWallet
return container.resolve(AskarProfileWallet)
},
})
public async onInitializeContext(agentContext: AgentContext, metadata: Record<string, unknown>) {
// TODO: I think we should also register the store here
if (agentContext.contextCorrelationId === 'default') {
const storeManager = agentContext.dependencyManager.resolve(AskarStoreManager)
await storeManager.getInitializedStoreWithProfile(agentContext)
return
} else if (this.config.multiWalletDatabaseScheme === AskarMultiWalletDatabaseScheme.DatabasePerWallet) {
agentContext.dependencyManager.registerInstance('AskarContextMetadata', metadata)
const storeManager = agentContext.dependencyManager.resolve(AskarStoreManager)
await storeManager.getInitializedStoreWithProfile(agentContext)
}
}

public async onProvisionContext(agentContext: AgentContext) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I added new module methods:

  • onProvisionContext
  • onCloseContext
  • onDeleteContext
  • onInitializeContext

this will allow for lifecycle handling of specific contexts

Copy link
Contributor

Choose a reason for hiding this comment

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

This is really great!

// We don't have any side effects to run
if (agentContext.contextCorrelationId === 'default') return null
if (this.config.multiWalletDatabaseScheme === AskarMultiWalletDatabaseScheme.ProfilePerWallet) return null

// For new stores (so not profiles) we need to generate a wallet key
return {
walletKey: this.config.askar.storeGenerateRawKey({}),
}
}

public async onDeleteContext(agentContext: AgentContext) {
const storeManager = agentContext.dependencyManager.resolve(AskarStoreManager)

// Will delete either the store (when root agent context or database per wallet) or profile (when not root agent context and profile per wallet)
await storeManager.deleteContext(agentContext)
}

public async onCloseContext(agentContext: AgentContext): Promise<void> {
const storeManager = agentContext.dependencyManager.resolve(AskarStoreManager)

await storeManager.closeContext(agentContext)
}
}
70 changes: 69 additions & 1 deletion packages/askar/src/AskarModuleConfig.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { Askar } from '@openwallet-foundation/askar-shared'
import type { AskarWalletPostgresStorageConfig, AskarWalletSqliteStorageConfig } from './wallet'
import type { Askar, KdfMethod } from '@openwallet-foundation/askar-shared'

export enum AskarMultiWalletDatabaseScheme {
/**
Expand All @@ -12,7 +13,48 @@ export enum AskarMultiWalletDatabaseScheme {
ProfilePerWallet = 'ProfilePerWallet',
}

export interface AskarModuleConfigStoreOptions {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Wallet config is now askar specific

/**
* The id of the store, and also the default profile that will be used for the root agent instance.
*
* - When SQLite is used that is not in-memory this will influence the path where the SQLite database is stored.
* - When Postgres is used, this determines the database.
*/
id: string

/**
* The key to open the store
*/
key: string

/**
* Key derivation method to use for opening the store.
*
* - `kdf:argon2i:mod` - most secure
* - `kdf:argon2i:int` - faster, less secure
* - `raw` - no key derivation. Useful if key is stored in e.g. the keychain on-device backed by biometrics.
*
* @default 'kdf:argon2i:mod'
*/
keyDerivationMethod?: `${KdfMethod.Argon2IInt}` | `${KdfMethod.Argon2IMod}` | `${KdfMethod.Raw}`
Copy link
Contributor

Choose a reason for hiding this comment

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

I think if we add the None here, we resolve #2277

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Is it ok to do this in a follow up PR? It will require additional tests to make sure it works and don't want to expand the scope any further


/**
* The backend to use with backend specific configuraiton options.
*
* If not provided SQLite will be used by default
*/
database?: AskarWalletSqliteStorageConfig | AskarWalletPostgresStorageConfig
}

export interface AskarModuleConfigOptions {
/**
* Store configuration used for askar.
*
* If `multiWalletDatabaseScheme` is set to `AskarMultiWalletDatabaseScheme.DatabasePerWallet` a new store will be created
* for each tenant. For performance reasons it is recommended to use `AskarMultiWalletDatabaseScheme.ProfilePerWallet`.
*/
store: AskarModuleConfigStoreOptions

/**
*
* ## Node.JS
Expand Down Expand Up @@ -58,6 +100,20 @@ export interface AskarModuleConfigOptions {
* @default {@link AskarMultiWalletDatabaseScheme.DatabasePerWallet} (for backwards compatibility)
*/
multiWalletDatabaseScheme?: AskarMultiWalletDatabaseScheme

/**
* Whether to enable and register the `AskarKeyManagementService` for key management and cryptographic operations.
*
* @default true
*/
enableKms?: boolean

/**
* Whether to enable and register the `AskarStorageService` for storage
*
* @default true
*/
enableStorage?: boolean
}

/**
Expand All @@ -79,4 +135,16 @@ export class AskarModuleConfig {
public get multiWalletDatabaseScheme() {
return this.options.multiWalletDatabaseScheme ?? AskarMultiWalletDatabaseScheme.DatabasePerWallet
}

public get store() {
return this.options.store
}

public get enableKms() {
return this.options.enableKms
}

public get enableStorage() {
return this.options.enableStorage
}
}
Loading
Loading