Skip to content

Commit

Permalink
fix: handle invalid pin when sharing or after onboarding
Browse files Browse the repository at this point in the history
Signed-off-by: Berend Sliedrecht <[email protected]>
  • Loading branch information
Berend Sliedrecht committed Nov 26, 2024
1 parent a2025d7 commit ffaa2d0
Show file tree
Hide file tree
Showing 15 changed files with 142 additions and 48 deletions.
3 changes: 1 addition & 2 deletions apps/easypid/src/app/authenticate.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Redirect, useLocalSearchParams, useNavigation } from 'expo-router'
import { Redirect, useLocalSearchParams } from 'expo-router'

import { TypedArrayEncoder, WalletInvalidKeyError } from '@credo-ts/core'
import { initializeAppAgent, useSecureUnlock } from '@easypid/agent'
Expand All @@ -25,7 +25,6 @@ export default function Authenticate() {
const pinInputRef = useRef<PinDotsInputRef>(null)
const [isInitializingAgent, setIsInitializingAgent] = useState(false)
const [isAllowedToUnlockWithFaceId, setIsAllowedToUnlockWithFaceId] = useState(false)
const navigation = useNavigation()
const isLoading =
secureUnlock.state === 'acquired-wallet-key' || (secureUnlock.state === 'locked' && secureUnlock.isUnlocking)

Expand Down
60 changes: 52 additions & 8 deletions apps/easypid/src/crypto/WalletServiceProviderClient.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,49 @@
import type { SecureEnvironment } from '@animo-id/expo-secure-environment'
import { AskarModule } from '@credo-ts/askar'
import {
Agent,
CredoWebCrypto,
type JwsProtectedHeaderOptions,
JwsService,
JwtPayload,
KeyDerivationMethod,
TypedArrayEncoder,
WalletInvalidKeyError,
getJwkFromKey,
} from '@credo-ts/core'
import type { EasyPIDAppAgent } from 'packages/agent/src'
import { agentDependencies } from '@credo-ts/react-native'
import { ariesAskar } from '@hyperledger/aries-askar-react-native'
import type { EasyPIDAppAgent } from '@package/agent'
import { secureWalletKey } from 'packages/secure-store/secureUnlock'
import { InvalidPinError } from './error'
import { deriveKeypairFromPin } from './pin'

// TODO: should auto reset after X seconds
let __pin: Array<number> | undefined
export const setWalletServiceProviderPin = (pin?: Array<number>) => {
export const setWalletServiceProviderPin = async (pin: Array<number>) => {
const pinString = pin.join('')
const walletKeyVersion = secureWalletKey.getWalletKeyVersion()
const walletKey = await secureWalletKey.getWalletKeyUsingPin(pinString, walletKeyVersion)
const walletId = `easypid-wallet-${walletKeyVersion}`
const agent = new Agent({
config: {
label: 'pin_test_agent',
walletConfig: { id: walletId, key: walletKey, keyDerivationMethod: KeyDerivationMethod.Raw },
},
modules: {
askar: new AskarModule({ ariesAskar }),
},
dependencies: agentDependencies,
})

try {
await agent.initialize()
} catch (e) {
if (e instanceof WalletInvalidKeyError) {
throw new InvalidPinError()
}
throw e
}
__pin = pin
}

Expand All @@ -30,10 +61,6 @@ export class WalletServiceProviderClient implements SecureEnvironment {
private agent: EasyPIDAppAgent
) {}

public async register() {
await this.post('register-wallet', {})
}

private async post<T>(path: string, claims: Record<string, unknown>): Promise<T> {
const pin = getWalletServiceProviderPin()
if (!pin)
Expand Down Expand Up @@ -73,6 +100,22 @@ export class WalletServiceProviderClient implements SecureEnvironment {
return parsedData
}

public async register() {
await this.post('register-wallet', {})
}

public async batchGenerateKeyPair(keyIds: string[]): Promise<Record<string, Uint8Array>> {
const { publicKeys } = await this.post<{ publicKeys: Record<string, Array<number>> }>('batch-create-key', {
keyIds,
keyType: 'P256',
})

return Object.entries(publicKeys).reduce(
(prev, [keyId, publicKey]) => ({ ...prev, [keyId]: new Uint8Array(publicKey) }),
{}
)
}

public async sign(keyId: string, message: Uint8Array): Promise<Uint8Array> {
const { signature } = await this.post<{ signature: Array<number> }>('sign', {
data: new Array(...message),
Expand All @@ -86,8 +129,9 @@ export class WalletServiceProviderClient implements SecureEnvironment {
return new Uint8Array(signature)
}

public async generateKeypair(id: string): Promise<void> {
await this.post('create-key', { keyType: 'P256', keyId: id })
public async generateKeypair(id: string): Promise<Uint8Array> {
const { publicKey } = await this.post<{ publicKey: Array<number> }>('create-key', { keyType: 'P256', keyId: id })
return new Uint8Array(publicKey)
}

public async getPublicBytesForKeyId(keyId: string): Promise<Uint8Array> {
Expand Down
3 changes: 3 additions & 0 deletions apps/easypid/src/crypto/error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export class InvalidPinError extends Error {
public message = 'Invalid PIN entered'
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useCanUseSecureEnclave } from '@easypid/hooks/useCanUseSecureEnclave'
import { Button, HeroIcons, Paragraph, Spinner, XStack, YStack, useToastController } from '@package/ui'
import { isLocalSecureEnvironmentSupported } from '@animo-id/expo-secure-environment'
import { Button, HeroIcons, Spinner, XStack, YStack, useToastController } from '@package/ui'
import { useImageScaler } from 'packages/app/src/hooks'
import React, { useState } from 'react'
import { Linking, Platform } from 'react-native'
Expand All @@ -11,7 +11,6 @@ interface OnboardingDataProtectionProps {

export function OnboardingDataProtection({ goToNextStep }: OnboardingDataProtectionProps) {
const toast = useToastController()
const canUseSecureEnclave = useCanUseSecureEnclave()
const [shouldUseCloudHsm, setShouldUseCloudHsm] = useState(true)

const { height, onLayout } = useImageScaler()
Expand All @@ -27,7 +26,7 @@ export function OnboardingDataProtection({ goToNextStep }: OnboardingDataProtect
const onToggleCloudHsm = () => {
const newShouldUseCloudHsm = !shouldUseCloudHsm

if (newShouldUseCloudHsm === false && !canUseSecureEnclave) {
if (newShouldUseCloudHsm === false && !isLocalSecureEnvironmentSupported()) {
toast.show(`You device does not support on-device ${Platform.OS === 'ios' ? 'Secure Enclave' : 'Strongbox'}.`, {
message: 'Only Cloud HSM supported for PID cryptogrpahic keys.',
customData: {
Expand Down
16 changes: 15 additions & 1 deletion apps/easypid/src/features/pid/FunkePidSetupScreen.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { sendCommand } from '@animo-id/expo-ausweis-sdk'
import { type SdJwtVcHeader, SdJwtVcRecord } from '@credo-ts/core'
import { useSecureUnlock } from '@easypid/agent'
import { InvalidPinError } from '@easypid/crypto/error'
import type { PidSdJwtVcAttributes } from '@easypid/hooks'
import { ReceivePidUseCaseCFlow } from '@easypid/use-cases/ReceivePidUseCaseCFlow'
import type {
Expand Down Expand Up @@ -190,7 +191,20 @@ export function FunkePidSetupScreen() {
throw new Error('Retry')
}

if (shouldUseCloudHsm) setWalletServiceProviderPin(pin.split('').map(Number))
if (shouldUseCloudHsm) {
try {
await setWalletServiceProviderPin(pin.split('').map(Number))
} catch (e) {
if (e instanceof InvalidPinError) {
toast.show(e.message, {
customData: {
preset: 'danger',
},
})
}
throw e
}
}
await onIdCardStart({ walletPin: pin, allowSimulatorCard: allowSimulatorCard })
}

Expand Down
1 change: 0 additions & 1 deletion apps/easypid/src/features/pid/PidWalletPinSlide.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { Heading, Paragraph, YStack } from '@package/ui'
import { PinDotsInput, type PinDotsInputRef, useWizard } from 'packages/app/src'
import { useRef, useState } from 'react'
import { useSafeAreaInsets } from 'react-native-safe-area-context'

interface PidWalletPinSlideProps {
title: string
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {

import { useAppAgent } from '@easypid/agent'

import { InvalidPinError } from '@easypid/crypto/error'
import { SlideWizard, usePushToWallet } from '@package/app'
import { useToastController } from '@package/ui'
import { useCallback, useEffect, useState } from 'react'
Expand Down Expand Up @@ -274,7 +275,18 @@ export function FunkeCredentialNotificationScreen() {
return
}
// TODO: maybe provide to shareProof method?
setWalletServiceProviderPin(pin.split('').map(Number))
try {
await setWalletServiceProviderPin(pin.split('').map(Number))
} catch (e) {
if (e instanceof InvalidPinError) {
toast.show(e.message, { customData: { preset: 'danger' } })
setIsSharingPresentation(false)
return { result: { title: e.message }, redirectToWallet: false }
}

setErrorReason('Presentation information could not be extracted.')
return
}
}

try {
Expand Down Expand Up @@ -314,6 +326,7 @@ export function FunkeCredentialNotificationScreen() {
resolvedAuthorizationRequest,
resolvedCredentialOffer,
shouldUsePinForPresentation,
toast.show,
]
)

Expand Down Expand Up @@ -414,7 +427,12 @@ export function FunkeCredentialNotificationScreen() {
step: 'pin-enter',
progress: 82.5,
screen: (
<PinSlide key="pin-enter" isLoading={isSharingPresentation} onPinComplete={onPresentationAccept} />
<PinSlide
key="pin-enter"
pinRef
isLoading={isSharingPresentation}
onPinComplete={onPresentationAccept}

Check failure on line 434 in apps/easypid/src/features/receive/FunkeCredentialNotificationScreen.tsx

View workflow job for this annotation

GitHub Actions / Validate

Type '(pin?: string) => Promise<{ result: { title: string; }; redirectToWallet: boolean; } | undefined>' is not assignable to type '(pin: string) => Promise<void> | Promise<PresentationRequestResult>'.
/>
),
}
: undefined,
Expand Down
13 changes: 11 additions & 2 deletions apps/easypid/src/features/scan/FunkeQrScannerScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,13 @@ import { FadeIn, FadeOut, LinearTransition, useAnimatedStyle, withTiming } from
import { useSafeAreaInsets } from 'react-native-safe-area-context'

import easypidLogo from '../../../assets/icon-rounded.png'
import { checkMdocPermissions, getMdocQrCode, requestMdocPermissions, waitForDeviceRequest } from '../proximity'
import {
checkMdocPermissions,
getMdocQrCode,
requestMdocPermissions,
shutdownDataTransfer,
waitForDeviceRequest,
} from '../proximity'

const unsupportedUrlPrefixes = ['_oob=']

Expand Down Expand Up @@ -58,7 +64,10 @@ export function FunkeQrScannerScreen({ credentialDataHandlerOptions }: QrScanner
}
}, [showMyQrCode])

const onCancel = () => back()
const onCancel = () => {
back()
shutdownDataTransfer()
}

const onScan = async (scannedData: string) => {
if (isProcessing || !isFocused) return
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { useLocalSearchParams } from 'expo-router'
import React, { useEffect, useState, useCallback } from 'react'

import { useAppAgent } from '@easypid/agent'
import { InvalidPinError } from '@easypid/crypto/error'
import { analyzeVerification } from '@easypid/use-cases/ValidateVerification'
import type { VerificationAnalysisResponse } from '@easypid/use-cases/ValidateVerification'
import { usePushToWallet } from '@package/app/src/hooks/usePushToWallet'
Expand Down Expand Up @@ -114,7 +115,25 @@ export function FunkeOpenIdPresentationNotificationScreen() {
}
}
// TODO: maybe provide to shareProof method?
setWalletServiceProviderPin(pin.split('').map(Number))
try {
await setWalletServiceProviderPin(pin.split('').map(Number))
} catch (e) {
if (e instanceof InvalidPinError) {
return {
status: 'error',
result: {
title: 'Authentication Failed',
},
}
}

return {
status: 'error',
result: {
title: 'Authentication failed',
},
}
}
}

try {
Expand Down
2 changes: 2 additions & 0 deletions apps/easypid/src/features/share/slides/PinSlide.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export const PinSlide = ({ onPinComplete, isLoading }: PinSlideProps) => {
const pinRef = useRef<PinDotsInputRef>(null)

const onPinEnterComplete = (pin: string) => {
console.log(pinRef)
setIsSubmitting(true)

onPinComplete(pin)
Expand All @@ -27,6 +28,7 @@ export const PinSlide = ({ onPinComplete, isLoading }: PinSlideProps) => {
customData: { preset: r.redirectToWallet ? 'danger' : 'warning' },
})

console.log(pinRef)
pinRef.current?.shake()
pinRef.current?.clear()

Expand Down
16 changes: 2 additions & 14 deletions apps/easypid/src/hooks/useCanUseSecureEnclave.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,5 @@
import { generateKeypair } from '@animo-id/expo-secure-environment'
import { useEffect, useState } from 'react'
import { Platform } from 'react-native'
import { isLocalSecureEnvironmentSupported } from '@animo-id/expo-secure-environment'

export function useCanUseSecureEnclave() {
if (Platform.OS === 'ios') return true

const [canUseSecureEnclave, setCanUseSecureEnclave] = useState<boolean>()

useEffect(() => {
generateKeypair('123', false)
.then(() => setCanUseSecureEnclave(true))
.catch(() => setCanUseSecureEnclave(false))
}, [])

return canUseSecureEnclave
return isLocalSecureEnvironmentSupported()
}
1 change: 0 additions & 1 deletion packages/agent/src/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ import { agentDependencies } from '@credo-ts/react-native'
import { anoncreds } from '@hyperledger/anoncreds-react-native'
import { ariesAskar } from '@hyperledger/aries-askar-react-native'
import { indyVdr } from '@hyperledger/indy-vdr-react-native'
import { DidWebAnonCredsRegistry } from 'credo-ts-didweb-anoncreds'

import { bdrPidIssuerCertificate, pidSchemes } from '../../../apps/easypid/src/constants'
import { indyNetworks } from './indyNetworks'
Expand Down
1 change: 1 addition & 0 deletions packages/agent/src/openid4vc/credentialBindingResolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export function getCredentialBindingResolver({
pidSchemes?: { sdJwtVcVcts: Array<string>; msoMdocDoctypes: Array<string> }
resolvedCredentialOffer: OpenId4VciResolvedCredentialOffer
}): OpenId4VciCredentialBindingResolver {
const keys = []
return async ({
supportedDidMethods,
keyTypes,
Expand Down
Loading

0 comments on commit ffaa2d0

Please sign in to comment.