Skip to content

Commit

Permalink
feat: pin_derived_eph_pub+priv (#128)
Browse files Browse the repository at this point in the history
Signed-off-by: Berend Sliedrecht <[email protected]>
  • Loading branch information
berendsliedrecht authored Jul 29, 2024
1 parent 4ffd02f commit 62ea105
Show file tree
Hide file tree
Showing 14 changed files with 158 additions and 42 deletions.
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,6 @@ android

# build output
dist/
types/
packages/ui/types

storybook-static
storybook-static
1 change: 1 addition & 0 deletions apps/funke/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const FUNKE_WALLET_INSTANCE_LONG_TERM_AES_KEY_ID = 'FUNKE_WALLET_INSTANCE_LONG_TERM_AES_KEY_ID'
4 changes: 4 additions & 0 deletions apps/funke/crypto/aes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { aes128Gcm } from '@package/agent'
import { FUNKE_WALLET_INSTANCE_LONG_TERM_AES_KEY_ID } from '../constants'

export const funkeAes128Gcm = aes128Gcm(FUNKE_WALLET_INSTANCE_LONG_TERM_AES_KEY_ID)
35 changes: 35 additions & 0 deletions apps/funke/crypto/bPrime.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { type AgentContext, TypedArrayEncoder } from '@credo-ts/core'
import { Key, KeyAlgs, KeyMethod } from '@hyperledger/aries-askar-react-native'
import { kdf } from '@package/secure-store/kdf'
import { funkeAes128Gcm } from './aes'

/**
*
* Derive a key pair based on a numeric pin according to the steps in B'
*
* returns pin_derived_eph_pub + pin_derived_eph_priv
*
* @todo Might be good later to add methods like `signWithPidPin`
*
*/
export const deriveKeypairFromPin = async (agentContext: AgentContext, pin: Array<number>) => {
if (!(await funkeAes128Gcm.aes128GcmHasKey({ agentContext }))) {
throw new Error('No AES key found in storage. Flow is called in an incorrect way!')
}

const pinSecret = await funkeAes128Gcm.aes128GcmEncrypt({
agentContext,
data: new Uint8Array(pin),
})

const pinSeed = await kdf.derive(
TypedArrayEncoder.toUtf8String(new Uint8Array(pin)),
TypedArrayEncoder.toUtf8String(pinSecret)
)

return Key.fromSeed({
seed: new Uint8Array(TypedArrayEncoder.fromHex(pinSeed)),
method: KeyMethod.None,
algorithm: KeyAlgs.EcSecp256r1,
})
}
2 changes: 2 additions & 0 deletions apps/funke/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"@package/agent": "workspace:*",
"@package/app": "workspace:*",
"@package/ui": "workspace:*",
"@package/secure-store": "workspace:*",
"@react-native-community/blur": "^4.3.2",
"@react-native-community/netinfo": "11.3.1",
"@react-native-masked-view/masked-view": "0.3.1",
Expand Down Expand Up @@ -54,6 +55,7 @@
},
"devDependencies": {
"@babel/core": "^7.24.4",
"@credo-ts/core": "0.5.1-alpha.51",
"@tamagui/babel-plugin": "^1.104.2",
"typescript": "*"
}
Expand Down
63 changes: 63 additions & 0 deletions packages/agent/src/crypto/aes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { assertAskarWallet } from '@credo-ts/askar/build/utils/assertAskarWallet'
import type { AgentContext } from '@credo-ts/core'
import { Key, KeyAlgs } from '@hyperledger/aries-askar-react-native'

const aes128GcmGenerateAndStoreKey =
(id: string) =>
async ({ agentContext }: { agentContext: AgentContext }) => {
const wallet = agentContext.wallet
const key = Key.generate(KeyAlgs.AesA128Gcm)
assertAskarWallet(wallet)
await wallet.withSession((session) => session.insertKey({ name: id, key }))
}

const aes128GcmHasKey =
(id: string) =>
async ({ agentContext }: { agentContext: AgentContext }) => {
const wallet = agentContext.wallet
assertAskarWallet(wallet)
const aesKey = await wallet.withSession((session) => session.fetchKey({ name: id }))

return Boolean(aesKey)
}

const aes128GcmGetKey =
(id: string) =>
async ({ agentContext }: { agentContext: AgentContext }) => {
const wallet = agentContext.wallet
assertAskarWallet(wallet)
const aesKey = await wallet.withSession((session) => session.fetchKey({ name: id }))

if (!aesKey) {
throw new Error(`AES-128-GCM key not found with id: ${id}`)
}

return aesKey.key
}

const aes128GcmEncrypt =
(id: string) =>
async ({
data,
agentContext,
nonce = new Uint8Array(12).fill(1),
}: {
data: Uint8Array
agentContext: AgentContext
nonce?: Uint8Array
}) => {
const wallet = agentContext.wallet
assertAskarWallet(wallet)

const key = await aes128GcmGetKey(id)({ agentContext })

const { ciphertextWithTag } = key.aeadEncrypt({ nonce, message: data })

return ciphertextWithTag
}

export const aes128Gcm = (id: string) => ({
aes128GcmGenerateAndStoreKey: aes128GcmGenerateAndStoreKey(id),
aes128GcmHasKey: aes128GcmHasKey(id),
aes128GcmEncrypt: aes128GcmEncrypt(id),
})
1 change: 1 addition & 0 deletions packages/agent/src/crypto/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './aes'
1 change: 1 addition & 0 deletions packages/agent/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,4 @@ export {
formatDifPexCredentialsForRequest,
} from './format/formatPresentation'
export * from './mediation'
export * from './crypto'
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import argon2 from 'react-native-argon2'

/**
* Derive key from pin and salt.
* Derive a hash from pin and salt. (which can be used to seed a key)
*
* Configuration based on recommended parameters defined in RFC 9106
* @see https://www.rfc-editor.org/rfc/rfc9106.html#name-parameter-choice
*
* returns a hex-encoded derived hash
*
*/
export const deriveWalletKey = async (pin: string, salt: string) => {
const derive = async (pin: string, salt: string): Promise<string> => {
const { rawHash } = await argon2(pin, salt, {
hashLength: 32,
mode: 'argon2id',
Expand All @@ -24,6 +27,12 @@ export const deriveWalletKey = async (pin: string, salt: string) => {
* @see https://github.com/LinusU/react-native-get-random-values
* @see https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues
*/
export function generateSalt(): string {
return crypto.getRandomValues(new Uint8Array(32)).join('')
}
const generateSalt = () => crypto.getRandomValues(new Uint8Array(32)).join('')

/**
* Derive key from pin and salt.
*
* Configuration based on recommended parameters defined in RFC 9106
* @see https://www.rfc-editor.org/rfc/rfc9106.html#name-parameter-choice
*/
export const kdf = { derive, generateSalt }
6 changes: 3 additions & 3 deletions packages/secure-store/secureUnlock.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { WalletUnlockError } from './error/WalletUnlockError'
import { kdf } from './kdf'
import { getSalt, storeSalt } from './secure-unlock/saltStore'
import { deriveWalletKey, generateSalt } from './secure-unlock/walletKeyDerivation'
import {
canUseBiometryBackedWalletKey,
getWalletKeyUsingBiometrics,
Expand All @@ -25,7 +25,7 @@ export async function createSaltForPin(returnExisting = false) {
if (existingSalt) return existingSalt
}

const salt = generateSalt()
const salt = kdf.generateSalt()
await storeSalt(salt, version)

return salt
Expand All @@ -37,7 +37,7 @@ export async function getWalletKeyUsingPin(pin: string) {
throw new WalletUnlockError('Error unlocking wallet. No salt configured')
}

const walletKey = await deriveWalletKey(pin, salt)
const walletKey = await kdf.derive(pin, salt)
return walletKey
}

Expand Down
3 changes: 2 additions & 1 deletion packages/ui/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
"include": ["src"],
"compilerOptions": {
"composite": true,
"jsx": "react-jsx"
"jsx": "react-jsx",
"typeRoots": ["types"]
},
"references": []
}
Loading

0 comments on commit 62ea105

Please sign in to comment.