diff --git a/apps/easypid/eas.json b/apps/easypid/eas.json index 1c00851d..03e4c2a4 100644 --- a/apps/easypid/eas.json +++ b/apps/easypid/eas.json @@ -30,7 +30,8 @@ "autoIncrement": true, "distribution": "store", "android": { - "buildType": "app-bundle" + "buildType": "app-bundle", + "resourceClass": "large" } } }, diff --git a/apps/easypid/index.ts b/apps/easypid/index.ts index 67000ccb..fc8a77fb 100644 --- a/apps/easypid/index.ts +++ b/apps/easypid/index.ts @@ -1 +1,2 @@ +import 'fast-text-encoding' import 'expo-router/entry' diff --git a/apps/easypid/package.json b/apps/easypid/package.json index 1dbca974..7b80551b 100644 --- a/apps/easypid/package.json +++ b/apps/easypid/package.json @@ -14,6 +14,7 @@ "@animo-id/expo-ausweis-sdk": "catalog:", "@animo-id/expo-mdoc-data-transfer": "catalog:", "@animo-id/expo-secure-environment": "catalog:", + "@animo-id/mdoc": "catalog:", "@credo-ts/core": "catalog:", "@expo-google-fonts/open-sans": "^0.2.3", "@expo-google-fonts/raleway": "^0.2.3", @@ -26,7 +27,6 @@ "@package/secure-store": "workspace:*", "@package/ui": "workspace:*", "@package/utils": "workspace:*", - "@animo-id/mdoc": "catalog:", "@react-native-community/blur": "^4.3.2", "@react-native-community/netinfo": "11.3.1", "@react-native-masked-view/masked-view": "0.3.1", @@ -51,6 +51,7 @@ "expo-system-ui": "~3.0.6", "expo-updates": "~0.25.16", "expo-web-browser": "~13.0.3", + "fast-text-encoding": "^1.0.6", "react": "catalog:", "react-native": "catalog:", "react-native-argon2": "^2.0.1", diff --git a/apps/easypid/src/app/(app)/_layout.tsx b/apps/easypid/src/app/(app)/_layout.tsx index 4980d4cd..9bfc7ca8 100644 --- a/apps/easypid/src/app/(app)/_layout.tsx +++ b/apps/easypid/src/app/(app)/_layout.tsx @@ -1,4 +1,4 @@ -import { Redirect, Stack, useGlobalSearchParams, useLocalSearchParams, usePathname, useRouter } from 'expo-router' +import { Redirect, Stack, useGlobalSearchParams, usePathname, useRouter } from 'expo-router' import { TypedArrayEncoder } from '@credo-ts/core' import { useSecureUnlock } from '@easypid/agent' @@ -10,7 +10,6 @@ import { type CredentialDataHandlerOptions, DeeplinkHandler, useHaptics } from ' import { HeroIcons, IconContainer } from '@package/ui' import { useEffect, useState } from 'react' import { useTheme } from 'tamagui' -import { WithBackgroundPidRefresh } from '../../features/pid/WithBackPidRefresh' const jsonRecordIds = [activityStorage.recordId] @@ -32,17 +31,19 @@ export default function AppLayout() { // It could be that the onboarding is cut of mid-process, and e.g. the user closes the app // if this is the case we will redo the onboarding const [hasFinishedOnboarding] = useHasFinishedOnboarding() - const [resetWalletState, setResetWalletState] = useState<'resetting' | 'reset'>() + const [hasResetWallet, setHasResetWallet] = useState(false) const shouldResetWallet = secureUnlock.state !== 'not-configured' && secureUnlock.state !== 'initializing' && !hasFinishedOnboarding const isWalletLocked = secureUnlock.state === 'locked' || secureUnlock.state === 'acquired-wallet-key' useEffect(() => { - if (resetWalletState || !shouldResetWallet) return + // Reset state + if (hasResetWallet && !shouldResetWallet) setHasResetWallet(false) + if (!shouldResetWallet || hasResetWallet) return - setResetWalletState('resetting') - resetWallet(secureUnlock).then(() => setResetWalletState('reset')) - }, [secureUnlock, resetWalletState, shouldResetWallet]) + setHasResetWallet(true) + resetWallet(secureUnlock) + }, [secureUnlock, hasResetWallet, shouldResetWallet]) // If we are intializing and the wallet was opened using a deeplinkg we will be redirected // to the authentication screen. We first save the redirection url and use that when navigation @@ -66,7 +67,7 @@ export default function AppLayout() { } // This should show the splash screen - if (secureUnlock.state === 'initializing' || (shouldResetWallet && resetWalletState !== 'reset')) { + if (secureUnlock.state === 'initializing' || shouldResetWallet) { return null } @@ -97,48 +98,46 @@ export default function AppLayout() { return ( - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + ) diff --git a/apps/easypid/src/app/(app)/federation.tsx b/apps/easypid/src/app/(app)/federation.tsx new file mode 100644 index 00000000..70a8a0db --- /dev/null +++ b/apps/easypid/src/app/(app)/federation.tsx @@ -0,0 +1,18 @@ +import { FunkeFederationDetailScreen } from '@easypid/features/wallet/FunkeFederationDetailScreen' +import type { TrustedEntity } from '@package/agent' +import { useLocalSearchParams } from 'expo-router' + +export default function Screen() { + const { entityId, trustedEntities, name, logo } = useLocalSearchParams() + + const trustedEntitiesArray = JSON.parse(decodeURIComponent(trustedEntities as string)) as Array + + return ( + + ) +} diff --git a/apps/easypid/src/app/(app)/issuer.tsx b/apps/easypid/src/app/(app)/issuer.tsx deleted file mode 100644 index 12e186cf..00000000 --- a/apps/easypid/src/app/(app)/issuer.tsx +++ /dev/null @@ -1,8 +0,0 @@ -import { FunkeIssuerDetailScreen } from '@easypid/features/wallet/FunkeIssuerDetailScreen' -import { useLocalSearchParams } from 'expo-router' - -export default function Screen() { - const { host } = useLocalSearchParams() - - return -} diff --git a/apps/easypid/src/app/+native-intent.tsx b/apps/easypid/src/app/+native-intent.tsx index 82366ec1..60e24862 100644 --- a/apps/easypid/src/app/+native-intent.tsx +++ b/apps/easypid/src/app/+native-intent.tsx @@ -1,3 +1,5 @@ +import 'fast-text-encoding' + import { parseInvitationUrl } from '@package/agent' import { deeplinkSchemes } from '@package/app' import * as Haptics from 'expo-haptics' diff --git a/apps/easypid/src/app/_layout.tsx b/apps/easypid/src/app/_layout.tsx index 68169ce7..0fcb2642 100644 --- a/apps/easypid/src/app/_layout.tsx +++ b/apps/easypid/src/app/_layout.tsx @@ -1,3 +1,5 @@ +import 'fast-text-encoding' + import { BackgroundLockProvider, NoInternetToastProvider, Provider, useTransparentNavigationBar } from '@package/app' import { SecureUnlockProvider } from '@package/secure-store/secureUnlock' import { DefaultTheme, ThemeProvider } from '@react-navigation/native' diff --git a/apps/easypid/src/constants.ts b/apps/easypid/src/constants.ts index 38fd84e7..b86b4810 100644 --- a/apps/easypid/src/constants.ts +++ b/apps/easypid/src/constants.ts @@ -17,12 +17,16 @@ const animoFunkeRelyingPartyCertificate = const ubiqueRootCertificate = 'MIIBZjCCAQygAwIBAgIGAZGJt173MAoGCCqGSM49BAMCMB8xHTAbBgNVBAMMFGh0dHBzOi8vYXV0aG9yaXR5LmNoMB4XDTI0MDgyNTEzMjYyMVoXDTI1MDgyNTEzMjYyMVowHzEdMBsGA1UEAwwUaHR0cHM6Ly9hdXRob3JpdHkuY2gwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAScIjAmHrkp3TC6bisgaqmszbKkpY0iGTdHF2rcRemJCV+ikotDt7G+ApwG0m6fxt8aBJHeJ2mssLvZBmZj5LtWozQwMjAfBgNVHREEGDAWghRodHRwczovL2F1dGhvcml0eS5jaDAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMCA0gAMEUCIQCpQsxyQx/5knqhGnDCiAo6MpQmTCd7vA9WehF4/1P8/QIgEnAtFVTP1uThuTEna1RD4Ji35+z1h8pDoMyLPd3Uaig=' +const ubiqueIssuer = + 'MIICsTCCAlegAwIBAgIUeN7cTJgPmbK39asN8Wf3VLOSCTAwCgYIKoZIzj0EAwIwgcYxCzAJBgNVBAYTAkRFMR0wGwYDVQQIDBRHZW1laW5kZSBNdXN0ZXJzdGFkdDEUMBIGA1UEBwwLTXVzdGVyc3RhZHQxHTAbBgNVBAoMFEdlbWVpbmRlIE11c3RlcnN0YWR0MQswCQYDVQQLDAJJVDEpMCcGA1UEAwwgaXNzdWFuY2UuZ2VtZWluZGUtbXVzdGVyc3RhZHQuZGUxKzApBgkqhkiG9w0BCQEWHHRlc3RAZ2VtZWluZGUtbXVzdGVyc3RhZHQuZGUwHhcNMjQxMTE1MDgzNzA4WhcNMzQxMTEzMDgzNzA4WjCBxjELMAkGA1UEBhMCREUxHTAbBgNVBAgMFEdlbWVpbmRlIE11c3RlcnN0YWR0MRQwEgYDVQQHDAtNdXN0ZXJzdGFkdDEdMBsGA1UECgwUR2VtZWluZGUgTXVzdGVyc3RhZHQxCzAJBgNVBAsMAklUMSkwJwYDVQQDDCBpc3N1YW5jZS5nZW1laW5kZS1tdXN0ZXJzdGFkdC5kZTErMCkGCSqGSIb3DQEJARYcdGVzdEBnZW1laW5kZS1tdXN0ZXJzdGFkdC5kZTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABDYXt8M+5E1ADj5N2Rv/zIwBlvkTlt3gsscrKP4owg6km9Ejv5bHqDWY+nQi29ezNH2tkhGrKe0ZsmeH9ZqUsI+jITAfMB0GA1UdDgQWBBRSW2AGYj1dJ5Nz84/XojDDjH00XzAKBggqhkjOPQQDAgNIADBFAiBJ7ohG3x9iBlbTeSLnJGTFdwfw10mM9sd1J/TpoijcfAIhALgJgE/w3/J7jJMvZq+EiUT8DkhKTTUNhN74uA+bL4v6' + export const trustedX509Certificates = [ + ubiqueIssuer, bdrPidIssuerCertificate, animoFunkeRelyingPartyCertificate, ubiqueRootCertificate, oldAnimoFunkeRelyingPartyCertificate, - 'MIIBKDCBzqADAgECAhAyWHL4SEss2wMO1QQybg/fMAoGCCqGSM49BAMCMA0xCzAJBgNVBAYTAk5MMB4XDTcwMDEwMTAwMDAwMFoXDTI1MTEyMjA4MjIxMlowDTELMAkGA1UEBhMCTkwwOTATBgcqhkjOPQIBBggqhkjOPQMBBwMiAALcD1XzKepFxWMAOqV+ln1fybBt7DRO5CV0f9A6mRp2xaMwMC4wLAYDVR0RBCUwI4IhMDRkNy0yMTctMTIzLTE4LTI2Lm5ncm9rLWZyZWUuYXBwMAoGCCqGSM49BAMCA0kAMEYCIQDWjkAm/iLhGWcgKILW48f43vEUByvJd2R4lxdTdK9w+wIhALcZIgrH2h9SoXHjuI9ktOMbfVHxt59iq+lOKsC4yOUQ', + 'MIIBJzCBz6ADAgECAhA0WLLsSm0Hf5R2/q7neHUKMAoGCCqGSM49BAMCMA0xCzAJBgNVBAYTAk5MMB4XDTcwMDEwMTAwMDAwMFoXDTI1MTEyMjA4MjIxMlowDTELMAkGA1UEBhMCTkwwOTATBgcqhkjOPQIBBggqhkjOPQMBBwMiAALcD1XzKepFxWMAOqV+ln1fybBt7DRO5CV0f9A6mRp2xaMxMC8wLQYDVR0RBCYwJIIiNGFjNS0xMDktMzctMTUwLTE5OC5uZ3Jvay1mcmVlLmFwcDAKBggqhkjOPQQDAgNHADBEAiAEqDL6WHBelM4YW3L0k2criU+Za/FlDEuAJKuY+LiY/AIgR0qGuW9qu4wUo/kcJ75mv+jAwV25ABmYAnbUX/7u5lI=', ] // https://gitlab.opencode.de/bmi/eudi-wallet/eidas-2.0-architekturkonzept/-/blob/main/architecture-proposal.md#pid-contents diff --git a/apps/easypid/src/features/pid/WithBackPidRefresh.tsx b/apps/easypid/src/features/pid/WithBackPidRefresh.tsx deleted file mode 100644 index ff00fcaa..00000000 --- a/apps/easypid/src/features/pid/WithBackPidRefresh.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import type { PropsWithChildren } from 'react' -import { useBackgroundPidRefresh } from '../../hooks/useBackgroundPidRefresh' - -export function WithBackgroundPidRefresh({ children }: PropsWithChildren) { - // Refresh PID once it reaches 1 - // useBackgroundPidRefresh(1) - - return children -} diff --git a/apps/easypid/src/features/proximity/mdocProximity.ts b/apps/easypid/src/features/proximity/mdocProximity.ts index 4d228e96..a0280542 100644 --- a/apps/easypid/src/features/proximity/mdocProximity.ts +++ b/apps/easypid/src/features/proximity/mdocProximity.ts @@ -12,6 +12,7 @@ import { import { TypedArrayEncoder } from '@credo-ts/core' import { getMdocContext } from '@credo-ts/core/build/modules/mdoc/MdocContext' import type { EasyPIDAppAgent, FormattedSubmission, MdocRecord } from '@package/agent' +import { handleBatchCredential } from '@package/agent/src/batch' import { type Permission, PermissionsAndroid, Platform } from 'react-native' const requireMdocDataTransfer = () => @@ -47,6 +48,7 @@ export const checkMdocPermissions = async () => { export const getMdocQrCode = async () => { const mdt = requireMdocDataTransfer().mdocDataTransfer.instance() + mdt.enableNfc() const qrData = await mdt.startQrEngagement() return qrData } @@ -82,12 +84,22 @@ export const shareDeviceResponse = async (options: ShareDeviceResponseOptions) = throw new Error('Not all requirements are satisfied') } - const issuerSignedDocuments = options.submission.entries.map((e) => { - if (!e.isSatisfied) throw new Error(`Requirement for doctype ${e.inputDescriptorId} not satisfied`) + if (options.submission.entries.length > 1) { + throw new Error('Only one mdoc supported at the moment due to only being able to sign with one device key') + } + + const issuerSignedDocuments = await Promise.all( + options.submission.entries.map(async (e) => { + if (!e.isSatisfied) throw new Error(`Requirement for doctype ${e.inputDescriptorId} not satisfied`) + + const credential = e.credentials[0].credential.record as MdocRecord - const credential = e.credentials[0].credential.record as MdocRecord - return parseIssuerSigned(TypedArrayEncoder.fromBase64(credential.base64Url), credential.getTags().docType) - }) + // Optionally handle batch issuance + const credentialRecord = await handleBatchCredential(options.agent, credential) + + return parseIssuerSigned(TypedArrayEncoder.fromBase64(credentialRecord.base64Url), credential.getTags().docType) + }) + ) const mdoc = new MDoc(issuerSignedDocuments) @@ -118,3 +130,8 @@ export const shareDeviceResponse = async (options: ShareDeviceResponseOptions) = await mdt.sendDeviceResponse(deviceResponse.encode()) } + +export const shutdownDataTransfer = () => { + const mdt = requireMdocDataTransfer().mdocDataTransfer.instance() + mdt.shutdown() +} diff --git a/apps/easypid/src/features/receive/FunkeCredentialNotificationScreen.tsx b/apps/easypid/src/features/receive/FunkeCredentialNotificationScreen.tsx index a41d0cb8..9107e014 100644 --- a/apps/easypid/src/features/receive/FunkeCredentialNotificationScreen.tsx +++ b/apps/easypid/src/features/receive/FunkeCredentialNotificationScreen.tsx @@ -74,11 +74,14 @@ export function FunkeCredentialNotificationScreen() { // TODO: where to transform? // Combine oid4vci issuer metadata and openid fed into one pipeline. If openid it's trusted const issuerMetadata = resolvedCredentialOffer?.metadata.credentialIssuer - const configuration = - resolvedCredentialOffer?.offeredCredentialConfigurations[ - // TODO: handle empty configuration ids - resolvedCredentialOffer.credentialOfferPayload.credential_configuration_ids[0] - ] + // We want the first supported configuration id + // TODO: handle empty configuration ids + const configurationId = resolvedCredentialOffer?.offeredCredentialConfigurations + ? Object.keys(resolvedCredentialOffer.offeredCredentialConfigurations)[0] + : undefined + const configuration = configurationId + ? resolvedCredentialOffer?.offeredCredentialConfigurations[configurationId] + : undefined const credentialDisplay = getCredentialDisplayWithDefaults( configuration && issuerMetadata @@ -122,13 +125,17 @@ export function FunkeCredentialNotificationScreen() { async ( resolvedCredentialOffer: OpenId4VciResolvedCredentialOffer, tokenResponse: OpenId4VciRequestTokenResponse, + configurationId: string, resolvedAuthorizationRequest?: OpenId4VciResolvedAuthorizationRequest ) => { const credentialResponses = await receiveCredentialFromOpenId4VciOffer({ agent, resolvedCredentialOffer, + credentialConfigurationIdsToRequest: [configurationId], accessToken: tokenResponse, clientId: resolvedAuthorizationRequest ? authorization.clientId : undefined, + // Always request batch for non pid credentials + requestBatch: true, }) const credentialRecord = credentialResponses[0].credential @@ -164,7 +171,7 @@ export function FunkeCredentialNotificationScreen() { const acquireCredentialsAuth = useCallback( async (authorizationCode: string) => { - if (!resolvedCredentialOffer || !resolvedAuthorizationRequest) { + if (!resolvedCredentialOffer || !resolvedAuthorizationRequest || !configurationId) { setErrorReason('Credential information could not be extracted') return } @@ -179,7 +186,7 @@ export function FunkeCredentialNotificationScreen() { 'codeVerifier' in resolvedAuthorizationRequest ? resolvedAuthorizationRequest.codeVerifier : undefined, }) - await retrieveCredentials(resolvedCredentialOffer, tokenResponse, resolvedAuthorizationRequest) + await retrieveCredentials(resolvedCredentialOffer, tokenResponse, configurationId, resolvedAuthorizationRequest) } catch (error) { agent.config.logger.error(`Couldn't receive credential from OpenID4VCI offer`, { error, @@ -187,12 +194,12 @@ export function FunkeCredentialNotificationScreen() { setErrorReason('Error while retrieving credentials') } }, - [resolvedCredentialOffer, resolvedAuthorizationRequest, retrieveCredentials, agent] + [resolvedCredentialOffer, resolvedAuthorizationRequest, retrieveCredentials, agent, configurationId] ) const acquireCredentialsPreAuth = useCallback( async (txCode?: string) => { - if (!resolvedCredentialOffer) { + if (!resolvedCredentialOffer || !configurationId) { setErrorReason('Credential information could not be extracted') return } @@ -203,7 +210,7 @@ export function FunkeCredentialNotificationScreen() { resolvedCredentialOffer, txCode, }) - await retrieveCredentials(resolvedCredentialOffer, tokenResponse) + await retrieveCredentials(resolvedCredentialOffer, tokenResponse, configurationId) } catch (error) { agent.config.logger.error(`Couldn't receive credential from OpenID4VCI offer`, { error, @@ -211,7 +218,7 @@ export function FunkeCredentialNotificationScreen() { setErrorReason('Error while retrieving credentials') } }, - [resolvedCredentialOffer, agent, retrieveCredentials] + [resolvedCredentialOffer, agent, retrieveCredentials, configurationId] ) const parsePresentationRequestUrl = useCallback( @@ -219,7 +226,6 @@ export function FunkeCredentialNotificationScreen() { getCredentialsForProofRequest({ agent, uri: oid4vpRequestUrl, - allowUntrustedCertificates: true, }) .then(setCredentialsForRequest) .catch((error) => { @@ -276,7 +282,6 @@ export function FunkeCredentialNotificationScreen() { agent, resolvedRequest: credentialsForRequest, selectedCredentials: {}, - allowUntrustedCertificate: true, }) const { authorizationCode } = await acquireAuthorizationCodeUsingPresentation({ @@ -347,7 +352,6 @@ export function FunkeCredentialNotificationScreen() { logo={credentialDisplay.issuer.logo} entityId={issuerMetadata?.credential_issuer as string} lastInteractionDate={activities[0]?.date} - approvalsCount={0} onContinue={onCheckCardContinue} /> ), diff --git a/apps/easypid/src/features/receive/slides/VerifyPartySlide.tsx b/apps/easypid/src/features/receive/slides/VerifyPartySlide.tsx index 7bc8909b..538d8096 100644 --- a/apps/easypid/src/features/receive/slides/VerifyPartySlide.tsx +++ b/apps/easypid/src/features/receive/slides/VerifyPartySlide.tsx @@ -1,4 +1,4 @@ -import type { DisplayImage } from '@package/agent' +import type { DisplayImage, TrustedEntity } from '@package/agent' import { Circle, @@ -25,8 +25,8 @@ interface VerifyPartySlideProps { logo?: DisplayImage backgroundColor?: string lastInteractionDate?: string - approvalsCount?: number onContinue?: () => Promise + trustedEntities?: Array } export const VerifyPartySlide = ({ @@ -36,8 +36,8 @@ export const VerifyPartySlide = ({ logo, backgroundColor, lastInteractionDate, - approvalsCount, onContinue, + trustedEntities, }: VerifyPartySlideProps) => { const router = useRouter() const { onNext, onCancel } = useWizard() @@ -54,7 +54,9 @@ export const VerifyPartySlide = ({ } const onPressVerifiedIssuer = withHaptics(() => { - router.push(`/issuer?entityId=${entityId}`) + router.push( + `/federation?name=${encodeURIComponent(name ?? '')}&logo=${encodeURIComponent(logo?.url ?? '')}&entityId=${encodeURIComponent(entityId)}&trustedEntities=${encodeURIComponent(JSON.stringify(trustedEntities ?? []))}` + ) }) const onPressInteraction = withHaptics(() => { @@ -93,15 +95,20 @@ export const VerifyPartySlide = ({ - {approvalsCount ? ( + {trustedEntities && trustedEntities.length > 0 ? ( ) : ( - + )} { + shutdownDataTransfer() + pushToWallet('replace') + } + const addActivity = async (status: ActivityStatus) => { if (!submission) return await addSharedActivityForCredentialsForRequest( @@ -116,6 +122,7 @@ export function FunkeMdocOfflineSharingScreen({ hostName: undefined, logo: undefined, name: 'Unknown party', + trustedEntities: [], }, }, status @@ -129,7 +136,7 @@ export function FunkeMdocOfflineSharingScreen({ submission={submission} onAccept={onProofAccept} onDecline={onProofDecline} - onComplete={() => pushToWallet('replace')} + onComplete={onProofComplete} /> ) } diff --git a/apps/easypid/src/features/share/FunkeOpenIdPresentationNotificationScreen.tsx b/apps/easypid/src/features/share/FunkeOpenIdPresentationNotificationScreen.tsx index 80598d6e..d533cc0b 100644 --- a/apps/easypid/src/features/share/FunkeOpenIdPresentationNotificationScreen.tsx +++ b/apps/easypid/src/features/share/FunkeOpenIdPresentationNotificationScreen.tsx @@ -12,7 +12,6 @@ import React, { useEffect, useState, useMemo, useCallback } from 'react' import { useAppAgent } from '@easypid/agent' import { analyzeVerification } from '@easypid/use-cases/ValidateVerification' import type { VerificationAnalysisResponse } from '@easypid/use-cases/ValidateVerification' -import { getOpenIdFedIssuerMetadata } from '@easypid/utils/issuer' import { usePushToWallet } from '@package/app/src/hooks/usePushToWallet' import { setWalletServiceProviderPin } from '../../crypto/WalletServiceProviderClient' import { useShouldUsePinForSubmission } from '../../hooks/useShouldUsePinForPresentation' @@ -33,15 +32,8 @@ export function FunkeOpenIdPresentationNotificationScreen() { const { activities } = useActivities({ filters: { entityId: credentialsForRequest?.verifier.entityId ?? 'NO MATCH' }, }) - const shouldUsePin = useShouldUsePinForSubmission(credentialsForRequest) - - // TODO: this should be returnd by getCredentialsForProofRequest - // TODO: addSharedActivityForCredentialsForRequest should take into account fed display metadata - const fedDisplayData = useMemo( - () => credentialsForRequest && getOpenIdFedIssuerMetadata(credentialsForRequest.verifier.entityId), - [credentialsForRequest] - ) const lastInteractionDate = activities?.[0]?.date + const shouldUsePin = useShouldUsePinForSubmission(credentialsForRequest) useEffect(() => { if (credentialsForRequest) return @@ -50,7 +42,6 @@ export function FunkeOpenIdPresentationNotificationScreen() { agent, data: params.data, uri: params.uri, - allowUntrustedCertificates: true, }) .then(setCredentialsForRequest) .catch((error) => { @@ -124,7 +115,6 @@ export function FunkeOpenIdPresentationNotificationScreen() { agent, resolvedRequest: credentialsForRequest, selectedCredentials: {}, - allowUntrustedCertificate: true, }) await addSharedActivityForCredentialsForRequest(agent, credentialsForRequest, 'success') @@ -187,8 +177,8 @@ export function FunkeOpenIdPresentationNotificationScreen() { entityId={credentialsForRequest?.verifier.entityId as string} verifierName={credentialsForRequest?.verifier.name} logo={credentialsForRequest?.verifier.logo} + trustedEntities={credentialsForRequest?.verifier.trustedEntities} lastInteractionDate={lastInteractionDate} - approvalsCount={fedDisplayData?.approvals.length} onComplete={() => pushToWallet('replace')} verificationAnalysis={verificationAnalysis} /> diff --git a/apps/easypid/src/features/share/FunkePresentationNotificationScreen.tsx b/apps/easypid/src/features/share/FunkePresentationNotificationScreen.tsx index 89428fc6..c41b773e 100644 --- a/apps/easypid/src/features/share/FunkePresentationNotificationScreen.tsx +++ b/apps/easypid/src/features/share/FunkePresentationNotificationScreen.tsx @@ -1,4 +1,4 @@ -import type { DisplayImage, FormattedSubmission } from '@package/agent' +import type { DisplayImage, FormattedSubmission, TrustedEntity } from '@package/agent' import type { VerificationAnalysisResult } from '@easypid/use-cases/ValidateVerification' import { type SlideStep, SlideWizard } from '@package/app' @@ -14,9 +14,8 @@ interface FunkePresentationNotificationScreenProps { verifierName?: string logo?: DisplayImage lastInteractionDate?: string - approvalsCount?: number verificationAnalysis: VerificationAnalysisResult - + trustedEntities?: Array submission?: FormattedSubmission usePin: boolean isAccepting: boolean @@ -30,7 +29,6 @@ export function FunkePresentationNotificationScreen({ verifierName, logo, lastInteractionDate, - approvalsCount, usePin, onAccept, onDecline, @@ -38,6 +36,7 @@ export function FunkePresentationNotificationScreen({ submission, onComplete, verificationAnalysis, + trustedEntities, }: FunkePresentationNotificationScreenProps) { return ( ), }, diff --git a/apps/easypid/src/features/wallet/FunkeFederationDetailScreen.tsx b/apps/easypid/src/features/wallet/FunkeFederationDetailScreen.tsx new file mode 100644 index 00000000..60396b4d --- /dev/null +++ b/apps/easypid/src/features/wallet/FunkeFederationDetailScreen.tsx @@ -0,0 +1,104 @@ +import type { TrustedEntity } from '@package/agent' +import { + Circle, + FlexPage, + Heading, + HeroIcons, + IconContainer, + Image, + MessageBox, + Paragraph, + ScrollView, + type ScrollViewRefType, + XStack, + YStack, +} from '@package/ui' +import { TextBackButton, useScrollViewPosition } from 'packages/app/src' +import React, { useRef } from 'react' +import { useSafeAreaInsets } from 'react-native-safe-area-context' + +interface FunkeFederationDetailScreenProps { + name: string + logo?: string + entityId?: string + trustedEntities?: Array +} + +export function FunkeFederationDetailScreen({ + name, + logo, + entityId, + trustedEntities = [], +}: FunkeFederationDetailScreenProps) { + const { handleScroll, isScrolledByOffset, scrollEventThrottle } = useScrollViewPosition() + const { bottom } = useSafeAreaInsets() + const scrollViewRef = useRef(null) + + return ( + + + + + About this party + } + /> + + {logo ? ( + + + + ) : ( + + + + )} + {name} + + + + Trusted by + + {trustedEntities.length > 0 ? ( + <>A list of organizations and whether they have approved {name}. + ) : ( + <>There are no organizations that have approved {name}. + )} + + + + {trustedEntities.map((entity) => { + return ( + + {entity.logo_uri && ( + + + + )} + + + {entity.organization_name} + + } /> + + + ) + })} + + + + + + + + + ) +} diff --git a/apps/easypid/src/features/wallet/FunkeIssuerDetailScreen.tsx b/apps/easypid/src/features/wallet/FunkeIssuerDetailScreen.tsx deleted file mode 100644 index 95ed1a50..00000000 --- a/apps/easypid/src/features/wallet/FunkeIssuerDetailScreen.tsx +++ /dev/null @@ -1,127 +0,0 @@ -import { getOpenIdFedIssuerMetadata } from '@easypid/utils/issuer' -import { - Circle, - FlexPage, - Heading, - HeroIcons, - Image, - MessageBox, - Paragraph, - ScrollView, - type ScrollViewRefType, - Stack, - XStack, - YStack, - useToastController, -} from '@package/ui' -import { useRouter } from 'expo-router' -import { TextBackButton, useHaptics, useScrollViewPosition } from 'packages/app/src' -import { useRef } from 'react' -import { Linking } from 'react-native' -import { useSafeAreaInsets } from 'react-native-safe-area-context' - -interface FunkeIssuerDetailScreenProps { - host: string -} - -export function FunkeIssuerDetailScreen({ host }: FunkeIssuerDetailScreenProps) { - const toast = useToastController() - const router = useRouter() - const { withHaptics, errorHaptic } = useHaptics() - const data = getOpenIdFedIssuerMetadata(host) - - if (!data) { - router.back() - errorHaptic() - return toast.show('Currently unavailable.', { - customData: { - preset: 'warning', - }, - }) - } - - const { handleScroll, isScrolledByOffset, scrollEventThrottle } = useScrollViewPosition() - const { bottom } = useSafeAreaInsets() - const scrollViewRef = useRef(null) - - const openDomain = withHaptics(() => { - Linking.openURL(`https://${host}`) - }) - - return ( - - - - - About this party - } - /> - - - - - - {data.display.name} - - {host} - - - - - - Approvals - A list of entities that have approved {data.display.name}. - - - {data.approvals.map((approval) => ( - - - - - - {approval.name} - Owned by {approval.ownedBy.display.name} - - - ))} - - - - - Trust marks - Certifications that verify {data.display.name}'s security and quality standards. - - - {data.certifications.map((certification) => ( - - {certification} - - ))} - - - - - - - - - ) -} diff --git a/apps/easypid/src/hooks/useBackgroundPidRefresh.ts b/apps/easypid/src/hooks/useBackgroundPidRefresh.ts deleted file mode 100644 index 5b8725d6..00000000 --- a/apps/easypid/src/hooks/useBackgroundPidRefresh.ts +++ /dev/null @@ -1,67 +0,0 @@ -import type { MdocRecord, SdJwtVcRecord } from '@credo-ts/core' -import { getBatchCredentialMetadata } from '@package/agent/src/openid4vc/batchMetadata' -import { useHasInternetConnection } from '@package/app' -import { useEffect, useMemo, useState } from 'react' -import { type AppAgent, useAppAgent } from '../agent' -import { useShouldUseCloudHsm } from '../features/onboarding/useShouldUseCloudHsm' -import { RefreshPidUseCase } from '../use-cases/RefreshPidUseCase.ts' -import { usePidCredential } from './usePidCredential' - -async function refreshPid({ agent, sdJwt, mdoc }: { agent: AppAgent; sdJwt?: SdJwtVcRecord; mdoc?: MdocRecord }) { - console.log('refreshing PID') - const useCase = await RefreshPidUseCase.initialize({ - agent, - }) - - await useCase.retrieveCredentialsUsingExistingRecords({ - sdJwt, - mdoc, - }) -} - -export function useBackgroundPidRefresh(batchThreshold: number) { - const { sdJwt, mdoc } = usePidCredential() - const [isRefreshing, setIsRefreshing] = useState(false) - const hasInternet = useHasInternetConnection() - const shouldUseCloudHsm = useShouldUseCloudHsm() - const { agent } = useAppAgent() - - const { shouldRefreshMdoc, shouldRefreshSdJwt } = useMemo(() => { - if (!shouldUseCloudHsm) return {} - - let shouldRefreshSdJwt = false - if (sdJwt) { - const sdJwtBatch = getBatchCredentialMetadata(sdJwt.record) - if (sdJwtBatch) { - shouldRefreshSdJwt = sdJwtBatch.additionalCredentials.length <= batchThreshold - } - } - - let shouldRefreshMdoc = false - if (mdoc) { - const mdocBatch = getBatchCredentialMetadata(mdoc.record) - if (mdocBatch) { - shouldRefreshMdoc = mdocBatch.additionalCredentials.length <= batchThreshold - } - } - - return { - shouldRefreshSdJwt, - shouldRefreshMdoc, - } - }, [sdJwt, mdoc, batchThreshold, shouldUseCloudHsm]) - - useEffect(() => { - if (isRefreshing || !hasInternet || !shouldUseCloudHsm) return - - if (shouldRefreshMdoc || shouldRefreshSdJwt) { - setIsRefreshing(true) - - refreshPid({ - agent, - sdJwt: shouldRefreshSdJwt ? sdJwt?.record : undefined, - mdoc: shouldRefreshMdoc ? mdoc?.record : undefined, - }).finally(() => setIsRefreshing(false)) - } - }, [shouldRefreshMdoc, shouldRefreshSdJwt, hasInternet, agent, isRefreshing, mdoc, sdJwt, shouldUseCloudHsm]) -} diff --git a/apps/easypid/src/use-cases/ReceivePidUseCaseFlow.ts b/apps/easypid/src/use-cases/ReceivePidUseCaseFlow.ts index d6974bc4..4e4022b2 100644 --- a/apps/easypid/src/use-cases/ReceivePidUseCaseFlow.ts +++ b/apps/easypid/src/use-cases/ReceivePidUseCaseFlow.ts @@ -1,13 +1,13 @@ import { AusweisAuthFlow, type AusweisAuthFlowOptions, sendCommand } from '@animo-id/expo-ausweis-sdk' import type { MdocRecord } from '@credo-ts/core' import type { AppAgent } from '@easypid/agent' -import { - type OpenId4VciRequestTokenResponse, - type OpenId4VciResolvedCredentialOffer, - type OpenId4VciResolvedOauth2RedirectAuthorizationRequest, - type SdJwtVcRecord, - acquireAuthorizationCodeAccessToken, +import type { + OpenId4VciRequestTokenResponse, + OpenId4VciResolvedCredentialOffer, + OpenId4VciResolvedOauth2RedirectAuthorizationRequest, + SdJwtVcRecord, } from '@package/agent' +import { acquireAuthorizationCodeAccessToken } from '@package/agent/src/invitation/handler' export interface ReceivePidUseCaseFlowOptions extends Pick { diff --git a/apps/easypid/src/use-cases/RefreshPidUseCase.ts.ts b/apps/easypid/src/use-cases/RefreshPidUseCase.ts similarity index 92% rename from apps/easypid/src/use-cases/RefreshPidUseCase.ts.ts rename to apps/easypid/src/use-cases/RefreshPidUseCase.ts index 46f545f0..8ac91059 100644 --- a/apps/easypid/src/use-cases/RefreshPidUseCase.ts.ts +++ b/apps/easypid/src/use-cases/RefreshPidUseCase.ts @@ -1,17 +1,18 @@ import { ClaimFormat, MdocRecord, getJwkFromJson } from '@credo-ts/core' +import { SdJwtVcRecord } from '@credo-ts/core' import type { AppAgent } from '@easypid/agent' +import type { OpenId4VciRequestTokenResponse, OpenId4VciResolvedCredentialOffer } from '@package/agent' import { - type OpenId4VciRequestTokenResponse, - type OpenId4VciResolvedCredentialOffer, - SdJwtVcRecord, acquireRefreshTokenAccessToken, - getRefreshCredentialMetadata, receiveCredentialFromOpenId4VciOffer, resolveOpenId4VciOffer, - setRefreshCredentialMetadata, - updateCredential, -} from '@package/agent' +} from '@package/agent/src/invitation/handler' import { getBatchCredentialMetadata, setBatchCredentialMetadata } from '@package/agent/src/openid4vc/batchMetadata' +import { + getRefreshCredentialMetadata, + setRefreshCredentialMetadata, +} from '@package/agent/src/openid4vc/refreshMetadata' +import { updateCredential } from '@package/agent/src/storage/credential' import { pidSchemes } from '../constants' import { ReceivePidUseCaseFlow } from './ReceivePidUseCaseFlow' import { C_PRIME_SD_JWT_MDOC_OFFER } from './bdrPidIssuerOffers' @@ -55,9 +56,11 @@ export class RefreshPidUseCase { public async retrieveCredentialsUsingExistingRecords({ sdJwt, mdoc, + batchSize = 2, }: { sdJwt?: SdJwtVcRecord mdoc?: MdocRecord + batchSize?: number }) { const existingRefreshMetadata = (sdJwt ? getRefreshCredentialMetadata(sdJwt) : undefined) ?? @@ -94,7 +97,7 @@ export class RefreshPidUseCase { resolvedCredentialOffer: this.resolvedCredentialOffer, credentialConfigurationIdsToRequest, clientId: RefreshPidUseCase.CLIENT_ID, - requestBatch: 2, + requestBatch: batchSize, pidSchemes, }) diff --git a/apps/easypid/src/utils/issuer.ts b/apps/easypid/src/utils/issuer.ts deleted file mode 100644 index 15d8ac69..00000000 --- a/apps/easypid/src/utils/issuer.ts +++ /dev/null @@ -1,52 +0,0 @@ -const DEFAULT_FUNKE_HOST = 'funke.animo.id' - -const FUNKE_ISSUER_DATA = { - host: DEFAULT_FUNKE_HOST, - display: { - name: 'Animo', - logo: { - url: 'https://i.imgur.com/5kxpKzA.png', - altText: 'Animo Solutions logo', - }, - }, - approvals: [ - { - id: '7c82e94d-d650-4230-9586-cc5e0bec1d88', - name: 'Dutch Gov eID list', - ownedBy: { - did: 'did:web:dutch.gov', - display: { - name: 'Dutch Gov', - logo: { - url: 'https://i.imgur.com/lqfQV5g.png', - altText: 'Dutch Gov logo', - }, - isGovernment: true, - }, - }, - }, - { - id: '1e5b0c77-891f-4db6-b5b0-b174e7b38d30', - name: 'Registered Businesses', - ownedBy: { - did: 'did:web:registered.businesses', - display: { - name: 'Registered Businesses', - logo: { - url: 'https://i.imgur.com/eoXTZS5.jpeg', - altText: 'Registered Businesses logo', - }, - }, - }, - }, - ], - certifications: ['eIDAS compliant', 'ISO/IEC 27001'], -} - -export const getOpenIdFedIssuerMetadata = (host: string) => { - if (host === FUNKE_ISSUER_DATA.host) { - return FUNKE_ISSUER_DATA - } - - return null -} diff --git a/apps/easypid/src/utils/resetWallet.ts b/apps/easypid/src/utils/resetWallet.ts index 157730c5..12c0f54e 100644 --- a/apps/easypid/src/utils/resetWallet.ts +++ b/apps/easypid/src/utils/resetWallet.ts @@ -12,6 +12,7 @@ import { import { removeShouldUseCloudHsm } from '../features/onboarding/useShouldUseCloudHsm' export async function resetWallet(secureUnlock: SecureUnlockReturn) { + console.log('Resetting wallet') if (secureUnlock.state === 'unlocked') { const agent = secureUnlock.context.agent secureUnlock.lock() diff --git a/package.json b/package.json index c90c0070..60155880 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,9 @@ "@credo-ts/indy-vdr": "catalog:", "@credo-ts/openid4vc": "catalog:", "@credo-ts/question-answer": "catalog:", - "@credo-ts/react-native": "catalog:" + "@credo-ts/react-native": "catalog:", + "@sphereon/pex-models": "catalog:", + "@openid-federation/core": "catalog:" }, "patchedDependencies": { "@hyperledger/indy-vdr-react-native@0.2.2": "patches/@hyperledger__indy-vdr-react-native@0.2.2.patch", diff --git a/packages/agent/src/agent.ts b/packages/agent/src/agent.ts index f3c77341..a7fb6f60 100644 --- a/packages/agent/src/agent.ts +++ b/packages/agent/src/agent.ts @@ -13,6 +13,7 @@ import { Agent, AutoAcceptCredential, AutoAcceptProof, + ClaimFormat, ConnectionsModule, CredentialsModule, DidsModule, @@ -23,6 +24,7 @@ import { KeyDidRegistrar, KeyDidResolver, LogLevel, + Mdoc, MediationRecipientModule, MediatorPickupStrategy, ProofsModule, @@ -30,6 +32,7 @@ import { V2ProofProtocol, WebDidResolver, WsOutboundTransport, + X509Module, } from '@credo-ts/core' import { IndyVdrAnonCredsRegistry, @@ -45,6 +48,7 @@ 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' import { appLogger } from './logger' @@ -80,16 +84,43 @@ export const initializeEasyPIDAgent = async ({ modules: { ariesAskar: askarModule, openId4VcHolder: new OpenId4VcHolderModule(), + x509: new X509Module({ + getTrustedCertificatesForVerification: (agentContext, { certificateChain, verification }) => { + if (verification.type === 'credential') { + // Only allow BDR certificate for PID credentials for now + if ( + verification.credential instanceof Mdoc && + pidSchemes.msoMdocDoctypes.includes(verification.credential.docType) + ) { + return [bdrPidIssuerCertificate] + } + + if ( + verification.credential.claimFormat === ClaimFormat.SdJwtVc && + pidSchemes.sdJwtVcVcts.includes(verification.credential.payload.vct as string) + ) { + return [bdrPidIssuerCertificate] + } + + // If not PID, we allow any certificate for now + return [certificateChain[0].toString('pem')] + } + + // Allow any actor for auth requests for now + if (verification.type === 'oauth2SecuredAuthorizationRequest') { + return [certificateChain[0].toString('pem')] + } + + return undefined + }, + trustedCertificates: + trustedX509Certificates.length > 0 ? (trustedX509Certificates as [string, ...string[]]) : undefined, + }), }, }) await agent.initialize() - // Register the trusted x509 certificates - for (const trustedCertificate of trustedX509Certificates) { - agent.x509.addTrustedCertificate(trustedCertificate) - } - return agent } diff --git a/packages/agent/src/batch.ts b/packages/agent/src/batch.ts index b99e5e02..cec7c4cf 100644 --- a/packages/agent/src/batch.ts +++ b/packages/agent/src/batch.ts @@ -1,44 +1,94 @@ import { Mdoc, MdocRecord, SdJwtVcRecord, W3cCredentialRecord } from '@credo-ts/core' -import type { EitherAgent } from './agent' +import type { AppAgent } from '../../../apps/easypid/src/agent' +import { RefreshPidUseCase } from '../../../apps/easypid/src/use-cases/RefreshPidUseCase' +import type { EasyPIDAppAgent, EitherAgent } from './agent' +import { getCredentialCategoryMetadata } from './credentialCategoryMetadata' import { decodeW3cCredential } from './format/credentialEncoding' import { getBatchCredentialMetadata } from './openid4vc/batchMetadata' +import { getRefreshCredentialMetadata } from './openid4vc/refreshMetadata' import { updateCredential } from './storage' +export async function refreshPid({ + agent, + sdJwt, + mdoc, + batchSize, +}: { agent: AppAgent; sdJwt?: SdJwtVcRecord; mdoc?: MdocRecord; batchSize?: number }) { + console.log('refreshing PID') + const useCase = await RefreshPidUseCase.initialize({ + agent, + }) + + await useCase.retrieveCredentialsUsingExistingRecords({ + sdJwt, + mdoc, + batchSize, + }) +} + /** * If available, takes a credential from the batch. * * @todo: what if batch is gone? */ -export async function handleBatchCredential( +export async function handleBatchCredential( agent: EitherAgent, - credentialRecord: W3cCredentialRecord | SdJwtVcRecord | MdocRecord -) { + credentialRecord: CredentialRecord +): Promise { const batchMetadata = getBatchCredentialMetadata(credentialRecord) + if (!batchMetadata) return credentialRecord - if (batchMetadata) { - const batchCredential = batchMetadata.additionalCredentials.pop() + // TODO: maybe we should also store the main credential in the additional credentials (and rename it) + // As right now the main one is mostly for display + const batchCredential = batchMetadata.additionalCredentials.pop() - if (batchCredential) { - // Store the record with the used credential removed. Even if the presentation fails we remove it, as we want to be careful - // if the presentation was shared - await updateCredential(agent, credentialRecord) + // Store the record with the used credential removed. Even if the presentation fails we remove it, as we want to be careful + // if the presentation was shared + if (batchCredential) await updateCredential(agent, credentialRecord) - if (credentialRecord instanceof MdocRecord) { - return new MdocRecord({ - mdoc: Mdoc.fromBase64Url(batchCredential as string), - }) - } - if (credentialRecord instanceof SdJwtVcRecord) { - return new SdJwtVcRecord({ - compactSdJwtVc: batchCredential as string, - }) - } - if (credentialRecord instanceof W3cCredentialRecord) { - return new W3cCredentialRecord({ - tags: { expandedTypes: [] }, - credential: decodeW3cCredential(batchCredential), + // Try to refresh the pid when we run out + // TODO: we should probably move this somewhere else at some point + const categoryMetadata = getCredentialCategoryMetadata(credentialRecord) + const refreshMetadata = getRefreshCredentialMetadata(credentialRecord) + if ( + categoryMetadata?.credentialCategory === 'DE-PID' && + refreshMetadata && + batchMetadata.additionalCredentials.length === 0 + ) { + refreshPid({ + agent: agent as EasyPIDAppAgent, + sdJwt: credentialRecord.type === 'SdJwtVcRecord' ? credentialRecord : undefined, + mdoc: credentialRecord.type === 'MdocRecord' ? credentialRecord : undefined, + // Get a batch of 5 for a single record type + batchSize: 5, + }) + .catch((error) => { + // TODO: we should handle the case where the refresh token is expired + agent.config.logger.error('Error refreshing pid', { + error, }) - } + }) + .then(() => { + agent.config.logger.debug('Successfully refreshed PID') + }) + } + + if (batchCredential) { + if (credentialRecord instanceof MdocRecord) { + return new MdocRecord({ + mdoc: Mdoc.fromBase64Url(batchCredential as string), + }) as CredentialRecord + } + if (credentialRecord instanceof SdJwtVcRecord) { + return new SdJwtVcRecord({ + compactSdJwtVc: batchCredential as string, + }) as CredentialRecord + } + if (credentialRecord instanceof W3cCredentialRecord) { + return new W3cCredentialRecord({ + tags: { expandedTypes: [] }, + credential: decodeW3cCredential(batchCredential), + }) as CredentialRecord } } diff --git a/packages/agent/src/format/formatPresentation.ts b/packages/agent/src/format/formatPresentation.ts index c7955bc1..26fe7192 100644 --- a/packages/agent/src/format/formatPresentation.ts +++ b/packages/agent/src/format/formatPresentation.ts @@ -207,27 +207,30 @@ export function formatDcqlCredentialsForRequest(dcqlQueryResult: DcqlQueryResult const credentialForDisplay = getCredentialForDisplay(match.record) let disclosed: FormattedSubmissionEntrySatisfiedCredential['disclosed'] - if (match.output.credentialFormat === 'vc+sd-jwt') { + if (match.output.credential_format === 'vc+sd-jwt') { if (match.record.type !== 'SdJwtVcRecord') throw new Error('Expected SdJwtRecord') if (queryCredential.format !== 'vc+sd-jwt') { throw new Error(`Expected queryr credential format ${queryCredential.format} to be vc+sd-jwt`) } - const disclosedDecoded = applyLimitdisclosureForSdJwtRequestedPayload( - match.record.compactSdJwtVc, - match.output.claims - ) + // TODO: remove once selective disclosure in credo tested + // const disclosedDecoded = applyLimitdisclosureForSdJwtRequestedPayload( + // match.record.compactSdJwtVc, + // match.output.claims + // ) - const { attributes, metadata } = getAttributesAndMetadataForSdJwtPayload(disclosedDecoded.prettyClaims) + // Creod already applied selective disclosure on payload + const { attributes, metadata } = getAttributesAndMetadataForSdJwtPayload(match.output.claims) disclosed = { attributes, metadata, paths: getDisclosedAttributePathArrays(attributes, 2), } - } else if (match.output.credentialFormat === 'mso_mdoc') { + } else if (match.output.credential_format === 'mso_mdoc') { if (match.record.type !== 'MdocRecord') throw new Error('Expected MdocRecord') + // TODO: check if fixed now // FIXME: the disclosed payload here doesn't have the correct encoding anymore // once we serialize input?? disclosed = { diff --git a/packages/agent/src/index.ts b/packages/agent/src/index.ts index a9bc1c07..a025bbf9 100644 --- a/packages/agent/src/index.ts +++ b/packages/agent/src/index.ts @@ -1,5 +1,4 @@ import 'react-native-get-random-values' -import 'fast-text-encoding' import { Buffer } from '@credo-ts/core' export { diff --git a/packages/agent/src/invitation/handler.ts b/packages/agent/src/invitation/handler.ts index bd282b47..0944cd6e 100644 --- a/packages/agent/src/invitation/handler.ts +++ b/packages/agent/src/invitation/handler.ts @@ -44,7 +44,6 @@ import { filter, first, firstValueFrom, merge, timeout } from 'rxjs' import { Oauth2Client, getAuthorizationServerMetadataFromList } from '@animo-id/oauth2' import q from 'query-string' -import { handleBatchCredential } from '../batch' import { credentialRecordFromCredential, encodeCredential } from '../format/credentialEncoding' import { type FormattedSubmission, @@ -56,6 +55,8 @@ import { getCredentialBindingResolver } from '../openid4vc/credentialBindingReso import { extractOpenId4VcCredentialMetadata, setOpenId4VcCredentialMetadata } from '../openid4vc/displayMetadata' import { BiometricAuthenticationError } from './error' import { fetchInvitationDataUrl } from './fetchInvitation' +import { TRUSTED_ENTITIES } from './trustedEntities' +import type { TrustedEntity } from './trustedEntities' export async function resolveOpenId4VciOffer({ agent, @@ -326,6 +327,61 @@ export const receiveCredentialFromOpenId4VciOffer = async ({ } } +const extractEntityIdFromJwt = (jwt: string): string | null => { + const jwtPayload = Jwt.fromSerializedJwt(jwt).payload + + if (jwtPayload?.additionalClaims?.client_id_scheme !== 'entity_id') return null + + const clientId = jwtPayload?.additionalClaims?.client_id + if (!clientId || typeof clientId !== 'string') return null + + return clientId +} + +/** + * This is a temp method to allow for untrusted certificates to still work with the wallet. + */ +export const extractEntityIdFromAuthorizationRequest = async ({ + data, + uri, +}: { data?: string; uri?: string }): Promise<{ data: string | null; entityId: string | null }> => { + try { + if (data) { + return { + data, + entityId: extractEntityIdFromJwt(data), + } + } + + if (uri) { + const query = q.parseUrl(uri).query + if (query.request_uri && typeof query.request_uri === 'string') { + const result = await fetchInvitationDataUrl(query.request_uri) + + if ( + result.success && + result.result.type === 'openid-authorization-request' && + typeof result.result.data === 'string' + ) { + return { + data: result.result.data, + entityId: extractEntityIdFromJwt(result.result.data), + } + } + } else if (query.request && typeof query.request === 'string') { + return { + data: query.request, + entityId: extractEntityIdFromJwt(query.request), + } + } + } + } catch (error) { + console.error(error) + } + + return { data: null, entityId: null } +} + const extractCertificateFromJwt = (jwt: string) => { const jwtHeader = Jwt.fromSerializedJwt(jwt).header return Array.isArray(jwtHeader.x5c) && typeof jwtHeader.x5c[0] === 'string' ? jwtHeader.x5c[0] : null @@ -394,32 +450,32 @@ export async function withTrustedCertificate( } export type CredentialsForProofRequest = Awaited> + +export type GetCredentialsForProofRequestOptions = { + agent: EitherAgent + data?: string + uri?: string + allowUntrustedFederation?: boolean +} + export const getCredentialsForProofRequest = async ({ agent, data, uri, - allowUntrustedCertificates = false, -}: { - agent: EitherAgent - // Either data or uri can be provided - data?: string - uri?: string - allowUntrustedCertificates?: boolean -}) => { + allowUntrustedFederation = true, +}: GetCredentialsForProofRequestOptions) => { let requestUri: string + let requestData = data - const { certificate = null, data: newData = null } = allowUntrustedCertificates - ? await extractCertificateFromAuthorizationRequest({ data, uri }) + const { entityId = undefined, data: fromFederationData = null } = allowUntrustedFederation + ? await extractEntityIdFromAuthorizationRequest({ data: requestData, uri }) : {} + requestData = fromFederationData ?? requestData - if (newData) { - // FIXME: Credo only support request string, but we already parsed it before. So we construct an request here - // but in the future we need to support the parsed request in Credo directly - requestUri = `openid://?request=${encodeURIComponent(newData)}` - } else if (data) { + if (requestData) { // FIXME: Credo only support request string, but we already parsed it before. So we construct an request here // but in the future we need to support the parsed request in Credo directly - requestUri = `openid://?request=${encodeURIComponent(data)}` + requestUri = `openid://?request=${encodeURIComponent(requestData)}` } else if (uri) { requestUri = uri } else { @@ -432,10 +488,32 @@ export const getCredentialsForProofRequest = async ({ requestUri, }) - // Temp solution to add and remove the trusted certificate - const resolved = await withTrustedCertificate(agent, certificate, () => - agent.modules.openId4VcHolder.resolveSiopAuthorizationRequest(requestUri) - ) + const resolved = await agent.modules.openId4VcHolder.resolveSiopAuthorizationRequest(requestUri, { + ...(entityId ? { federation: { trustedEntityIds: [entityId] } } : {}), + }) + + // TODO: Remove me when the new credo-ts version is used + if (resolved.authorizationRequest.payload) { + resolved.authorizationRequest.payload.client_metadata = + resolved.authorizationRequest.authorizationRequestPayload.client_metadata + } + + let trustedEntities: Array = [] + if (entityId) { + const resolvedChains = await agent.modules.openId4VcHolder.resolveOpenIdFederationChains({ + entityId: entityId, + trustAnchorEntityIds: TRUSTED_ENTITIES, + }) + + trustedEntities = resolvedChains + .map((chain) => ({ + entity_id: chain.trustAnchorEntityConfiguration.sub, + organization_name: + chain.trustAnchorEntityConfiguration.metadata?.federation_entity?.organization_name ?? 'Unknown entity', + logo_uri: chain.trustAnchorEntityConfiguration.metadata?.federation_entity?.logo_uri, + })) + .filter((entity, index, self) => self.findIndex((e) => e.entity_id === entity.entity_id) === index) + } let formattedSubmission: FormattedSubmission if (resolved.presentationExchange) { @@ -464,7 +542,7 @@ export const getCredentialsForProofRequest = async ({ hostName: resolved.authorizationRequest.responseURI ? getHostNameFromUrl(resolved.authorizationRequest.responseURI) : undefined, - entityId: resolved.authorizationRequest.payload?.iss as string, + entityId: entityId ?? (resolved.authorizationRequest.payload?.iss as string), logo: clientMetadata?.logo_uri ? { @@ -472,98 +550,12 @@ export const getCredentialsForProofRequest = async ({ } : undefined, name: clientMetadata?.client_name, + trustedEntities, }, formattedSubmission, } as const } -export const shareProof = async ({ - agent, - resolvedRequest, - selectedCredentials, - allowUntrustedCertificate = false, -}: { - agent: EitherAgent - resolvedRequest: CredentialsForProofRequest - selectedCredentials: { [inputDescriptorId: string]: string } - allowUntrustedCertificate?: boolean -}) => { - const { authorizationRequest } = resolvedRequest - if ( - !resolvedRequest.credentialsForRequest?.areRequirementsSatisfied && - !resolvedRequest.queryResult?.canBeSatisfied - ) { - throw new Error('Requirements from proof request are not satisfied') - } - - // Map all requirements and entries to a credential record. If a credential record for an - // input descriptor has been provided in `selectedCredentials` we will use that. Otherwise - // it will pick the first available credential. - const presentationExchangeCredentials = resolvedRequest.credentialsForRequest - ? Object.fromEntries( - await Promise.all( - resolvedRequest.credentialsForRequest.requirements.flatMap((requirement) => - requirement.submissionEntry.slice(0, requirement.needsCount).map(async (entry) => { - const credentialId = selectedCredentials[entry.inputDescriptorId] - const credential = - entry.verifiableCredentials.find((vc) => vc.credentialRecord.id === credentialId) ?? - entry.verifiableCredentials[0] - - // Optionally use a batch credential - const credentialRecord = await handleBatchCredential(agent, credential.credentialRecord) - - return [entry.inputDescriptorId, [credentialRecord]] as [string, (typeof credentialRecord)[]] - }) - ) - ) - ) - : undefined - - // TODO: support credential selection for DCQL - const dcqlCredentials = resolvedRequest.queryResult - ? agent.modules.openId4VcHolder.selectCredentialsForDcqlRequest(resolvedRequest.queryResult) - : undefined - - try { - // Temp solution to add and remove the trusted certificate - const certificate = - authorizationRequest.jwt && allowUntrustedCertificate ? extractCertificateFromJwt(authorizationRequest.jwt) : null - - const result = await withTrustedCertificate(agent, certificate, () => - agent.modules.openId4VcHolder.acceptSiopAuthorizationRequest({ - authorizationRequest, - presentationExchange: presentationExchangeCredentials - ? { - credentials: presentationExchangeCredentials, - } - : undefined, - dcql: dcqlCredentials - ? { - credentials: dcqlCredentials, - } - : undefined, - }) - ) - - // if redirect_uri is provided, open it in the browser - // Even if the response returned an error, we must open this uri - if (result.redirectUri) { - await Linking.openURL(result.redirectUri) - } - - if (result.serverResponse.status < 200 || result.serverResponse.status > 299) { - throw new Error( - `Error while accepting authorization request. ${JSON.stringify(result.serverResponse.body, null, 2)}` - ) - } - - return result - } catch (error) { - // Handle biometric authentication errors - throw BiometricAuthenticationError.tryParseFromError(error) ?? error - } -} - /** * @todo we probably need a way to cancel this method, if the qr scanner is .e.g dismissed. */ diff --git a/packages/agent/src/invitation/index.ts b/packages/agent/src/invitation/index.ts index 4908fe99..f5b5f7bb 100644 --- a/packages/agent/src/invitation/index.ts +++ b/packages/agent/src/invitation/index.ts @@ -35,8 +35,10 @@ export { acquirePreAuthorizedAccessToken, resolveOpenId4VciOffer, getCredentialsForProofRequest, - shareProof, withTrustedCertificate, acquireAuthorizationCodeUsingPresentation, } from './handler' +export { shareProof } from './shareProof' export * from './error' + +export type { TrustedEntity } from './trustedEntities' diff --git a/packages/agent/src/invitation/shareProof.ts b/packages/agent/src/invitation/shareProof.ts new file mode 100644 index 00000000..c1cc91d5 --- /dev/null +++ b/packages/agent/src/invitation/shareProof.ts @@ -0,0 +1,95 @@ +import { Linking } from 'react-native' +import type { EitherAgent } from '../agent' +import { handleBatchCredential } from '../batch' +import { BiometricAuthenticationError } from './error' +import type { CredentialsForProofRequest } from './handler' + +export const shareProof = async ({ + agent, + resolvedRequest, + selectedCredentials, +}: { + agent: EitherAgent + resolvedRequest: CredentialsForProofRequest + selectedCredentials: { [inputDescriptorId: string]: string } +}) => { + const { authorizationRequest } = resolvedRequest + if ( + !resolvedRequest.credentialsForRequest?.areRequirementsSatisfied && + !resolvedRequest.queryResult?.canBeSatisfied + ) { + throw new Error('Requirements from proof request are not satisfied') + } + + // Map all requirements and entries to a credential record. If a credential record for an + // input descriptor has been provided in `selectedCredentials` we will use that. Otherwise + // it will pick the first available credential. + const presentationExchangeCredentials = resolvedRequest.credentialsForRequest + ? Object.fromEntries( + await Promise.all( + resolvedRequest.credentialsForRequest.requirements.flatMap((requirement) => + requirement.submissionEntry.slice(0, requirement.needsCount).map(async (entry) => { + const credentialId = selectedCredentials[entry.inputDescriptorId] + const credential = + entry.verifiableCredentials.find((vc) => vc.credentialRecord.id === credentialId) ?? + entry.verifiableCredentials[0] + + // Optionally use a batch credential + const credentialRecord = await handleBatchCredential(agent, credential.credentialRecord) + + return [entry.inputDescriptorId, [credentialRecord]] as [string, (typeof credentialRecord)[]] + }) + ) + ) + ) + : undefined + + // TODO: support credential selection for DCQL + const dcqlCredentials = resolvedRequest.queryResult + ? Object.fromEntries( + await Promise.all( + Object.entries( + agent.modules.openId4VcHolder.selectCredentialsForDcqlRequest(resolvedRequest.queryResult) + ).map(async ([queryCredentialId, credential]) => { + // Optionally use a batch credential + const credentialRecord = await handleBatchCredential(agent, credential.credentialRecord) + + return [queryCredentialId, { ...credential, credentialRecord }] + }) + ) + ) + : undefined + + try { + const result = await agent.modules.openId4VcHolder.acceptSiopAuthorizationRequest({ + authorizationRequest, + presentationExchange: presentationExchangeCredentials + ? { + credentials: presentationExchangeCredentials, + } + : undefined, + dcql: dcqlCredentials + ? { + credentials: dcqlCredentials, + } + : undefined, + }) + + // if redirect_uri is provided, open it in the browser + // Even if the response returned an error, we must open this uri + if (result.redirectUri) { + await Linking.openURL(result.redirectUri) + } + + if (result.serverResponse.status < 200 || result.serverResponse.status > 299) { + throw new Error( + `Error while accepting authorization request. ${JSON.stringify(result.serverResponse.body, null, 2)}` + ) + } + + return result + } catch (error) { + // Handle biometric authentication errors + throw BiometricAuthenticationError.tryParseFromError(error) ?? error + } +} diff --git a/packages/agent/src/invitation/trustedEntities.ts b/packages/agent/src/invitation/trustedEntities.ts new file mode 100644 index 00000000..0cd3b226 --- /dev/null +++ b/packages/agent/src/invitation/trustedEntities.ts @@ -0,0 +1,15 @@ +const BASE_URL = 'https://funke.animo.id/siop' + +export const TRUSTED_ENTITIES = [ + `${BASE_URL}/0193687b-0c27-7b82-a686-ff857dc6bbb3`, + `${BASE_URL}/0193687f-20d8-720a-9139-ed939ba510fa`, + `${BASE_URL}/019368ed-3787-7669-b7f4-8c012238e90d`, + `${BASE_URL}/01936907-56a3-7007-a61f-44bff8b5d175`, + `${BASE_URL}/01936903-8879-733f-8eaf-6f2fa862099c`, +] satisfies [string, ...string[]] + +export type TrustedEntity = { + entity_id: string + organization_name: string + logo_uri?: string +} diff --git a/packages/ui/src/components/InfoButton.tsx b/packages/ui/src/components/InfoButton.tsx index 08a6c8da..caa9932d 100644 --- a/packages/ui/src/components/InfoButton.tsx +++ b/packages/ui/src/components/InfoButton.tsx @@ -21,6 +21,10 @@ const infoButtonVariants = { icon: , accent: '$danger-500', }, + info: { + icon: , + accent: '$grey-500', + }, // States expired: { @@ -95,7 +99,7 @@ export function InfoButton({ )} - + {title} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a8818f4a..1f7a5802 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -10,8 +10,8 @@ catalogs: specifier: 0.0.1-alpha.14 version: 0.0.1-alpha.14 '@animo-id/expo-mdoc-data-transfer': - specifier: 0.0.3-alpha.7 - version: 0.0.3-alpha.7 + specifier: 0.0.3-alpha.8 + version: 0.0.3-alpha.8 '@animo-id/expo-secure-environment': specifier: 0.1.0-alpha.11 version: 0.1.0-alpha.11 @@ -53,7 +53,7 @@ overrides: '@animo-id/oid4vci': 0.1.4 '@animo-id/oauth2': 0.1.4 '@animo-id/oauth2-utils': 0.1.4 - dcql: 0.2.13 + dcql: 0.2.17 '@credo-ts/anoncreds': https://gitpkg.vercel.app/animo/aries-framework-javascript/packages/anoncreds?funke '@credo-ts/askar': https://gitpkg.vercel.app/animo/aries-framework-javascript/packages/askar?funke '@credo-ts/node': https://gitpkg.vercel.app/animo/aries-framework-javascript/packages/node?funke @@ -63,6 +63,8 @@ overrides: '@credo-ts/openid4vc': https://gitpkg.vercel.app/animo/aries-framework-javascript/packages/openid4vc?funke '@credo-ts/question-answer': https://gitpkg.vercel.app/animo/aries-framework-javascript/packages/question-answer?funke '@credo-ts/react-native': https://gitpkg.vercel.app/animo/aries-framework-javascript/packages/react-native?funke + '@sphereon/pex-models': 2.3.2 + '@openid-federation/core': 0.1.1-alpha.17 patchedDependencies: '@credo-ts/askar@0.5.13': @@ -96,7 +98,7 @@ importers: version: 0.0.1-alpha.14(expo@51.0.39(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0)))(react-native@0.74.5(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.2.79)(react@18.3.1))(react@18.3.1) '@animo-id/expo-mdoc-data-transfer': specifier: 'catalog:' - version: 0.0.3-alpha.7(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@react-native-community/cli-server-api@13.6.9)(@types/react@18.2.79)(expo@51.0.39(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0)))(react@18.3.1) + version: 0.0.3-alpha.8(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@react-native-community/cli-server-api@13.6.9)(@types/react@18.2.79)(expo@51.0.39(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0)))(react@18.3.1) '@animo-id/expo-secure-environment': specifier: 'catalog:' version: 0.1.0-alpha.11(expo@51.0.39(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0)))(react-native@0.74.5(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.2.79)(react@18.3.1))(react@18.3.1) @@ -211,6 +213,9 @@ importers: expo-web-browser: specifier: ~13.0.3 version: 13.0.3(expo@51.0.39(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))) + fast-text-encoding: + specifier: ^1.0.6 + version: 1.0.6 react: specifier: 'catalog:' version: 18.3.1 @@ -585,7 +590,7 @@ importers: version: https://gitpkg.vercel.app/animo/aries-framework-javascript/packages/anoncreds?funke(@hyperledger/anoncreds-shared@0.2.4)(expo@51.0.39(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0)))(react-native@0.76.3(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@react-native-community/cli-server-api@13.6.9)(@types/react@18.2.79)(react@18.3.1))(typescript@5.3.3)(web-streams-polyfill@3.3.3) '@credo-ts/askar': specifier: https://gitpkg.vercel.app/animo/aries-framework-javascript/packages/askar?funke - version: https://gitpkg.vercel.app/animo/aries-framework-javascript/packages/askar?funke(patch_hash=zbu2rcss5evxukkhh5w5venkba)(@hyperledger/aries-askar-shared@0.2.3)(expo@51.0.39(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0)))(react-native@0.76.3(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@react-native-community/cli-server-api@13.6.9)(@types/react@18.2.79)(react@18.3.1))(typescript@5.3.3)(web-streams-polyfill@3.3.3) + version: https://gitpkg.vercel.app/animo/aries-framework-javascript/packages/askar?funke(patch_hash=zbu2rcss5evxukkhh5w5venkba)(@animo-id/expo-secure-environment@0.1.0-alpha.11(expo@51.0.39(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0)))(react-native@0.76.3(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@react-native-community/cli-server-api@13.6.9)(@types/react@18.2.79)(react@18.3.1))(react@18.3.1))(@hyperledger/aries-askar-shared@0.2.3)(expo@51.0.39(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0)))(react-native@0.76.3(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@react-native-community/cli-server-api@13.6.9)(@types/react@18.2.79)(react@18.3.1))(typescript@5.3.3)(web-streams-polyfill@3.3.3) '@credo-ts/cheqd': specifier: https://gitpkg.vercel.app/animo/aries-framework-javascript/packages/cheqd?funke version: https://gitpkg.vercel.app/animo/aries-framework-javascript/packages/cheqd?funke(@hyperledger/anoncreds-shared@0.2.4)(expo@51.0.39(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0)))(react-native@0.76.3(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@react-native-community/cli-server-api@13.6.9)(@types/react@18.2.79)(react@18.3.1))(typescript@5.3.3)(web-streams-polyfill@3.3.3) @@ -812,8 +817,8 @@ packages: react: '*' react-native: '*' - '@animo-id/expo-mdoc-data-transfer@0.0.3-alpha.7': - resolution: {integrity: sha512-owHGj02XzGeBsHLi8ZJyN8AiiS4rgO22oFlKuZPyrmylUemz15CzB2tnIa8W1KKM7MnCrWXENFV5a6VC/gLqag==} + '@animo-id/expo-mdoc-data-transfer@0.0.3-alpha.8': + resolution: {integrity: sha512-UmjgDagxpxcY+jeS2gHFEP5krKrgICx72WLzfSwsRIdDVXnPehKofjQegQ2IQ0rechrW+XgBxA1TEriXIbyy+g==} peerDependencies: expo: '>= 51' react: '*' @@ -1739,7 +1744,7 @@ packages: resolution: {tarball: https://gitpkg.vercel.app/animo/aries-framework-javascript/packages/askar?funke} version: 0.5.13 peerDependencies: - '@animo-id/expo-secure-environment': ^0.0.1-alpha.0 + '@animo-id/expo-secure-environment': ^0.1.0-alpha.11 '@hyperledger/aries-askar-shared': ^0.2.3 peerDependenciesMeta: '@animo-id/expo-secure-environment': @@ -2570,8 +2575,8 @@ packages: resolution: {integrity: sha512-q9CRWjpHCMIh5sVyefoD1cA7PkvILqCZsnSOEUUivORLjxCO/Irmue2DprETiNgEqktDBZaM1Bi+jrarx1XdCg==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - '@openid-federation/core@0.1.1-alpha.13': - resolution: {integrity: sha512-QC4DSbiJ7eWstLs1O3XrX/yKFgaj+3ch8cA4N/02BywVNmkiYgW9qXhcvY50ULINuCeYdqIMIqCuHbaTa0A1hw==} + '@openid-federation/core@0.1.1-alpha.17': + resolution: {integrity: sha512-Bn5JaQzQnrQ2koPisiITHN69eOEewvs26EPyD4tp5XoR6Kdh9sy2f3w4hYV4MSaDwRorpRF7ttN6PGK1piMTng==} '@peculiar/asn1-cms@2.3.13': resolution: {integrity: sha512-joqu8A7KR2G85oLPq+vB+NFr2ro7Ls4ol13Zcse/giPSzUNN0n2k3v8kMpf6QdGUhI13e5SzQYN8AKP8sJ8v4w==} @@ -3467,8 +3472,8 @@ packages: version: 0.16.0 engines: {node: '>=18'} - '@sphereon/pex-models@2.3.1': - resolution: {integrity: sha512-SByU4cJ0XYA6VZQ/L6lsSiRcFtBPHbFioCeQ4GP7/W/jQ+PSBD7uK2oTnKQ9/0iEiMK/6JYqhKgLs4a9UX3UTQ==} + '@sphereon/pex-models@2.3.2': + resolution: {integrity: sha512-foFxfLkRwcn/MOp/eht46Q7wsvpQGlO7aowowIIb5Tz9u97kYZ2kz6K2h2ODxWuv5CRA7Q0MY8XUBGE2lfOhOQ==} '@sphereon/pex@5.0.0-unstable.24': resolution: {integrity: sha512-CZc+kr8cJqPsFSpg4kHyamr5oB5xLVP2E5eJ0pbetOfOE2uSxqk0/A8zGazcPhU1zZILrO51hD4vW/hJRgtKJQ==} @@ -5529,8 +5534,8 @@ packages: dayjs@1.11.13: resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==} - dcql@0.2.13: - resolution: {integrity: sha512-XfePsSz9ULj9HH3VFNguzK/xlFnliKDX2iUDb1tIrn97S+TfftcFo+jipw16m9jPlWLhhBx48QniF0D8KotIWA==} + dcql@0.2.17: + resolution: {integrity: sha512-YKNJR2anEiWooUCg7cJt/QmSFxpBS+SJQurcsNA60+8qUrjOuroh1Wd+lka/yOAV2VUdRzdNY6ISouxTV6SUaQ==} debug@2.6.9: resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} @@ -9570,16 +9575,16 @@ packages: resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} hasBin: true - valibot@0.37.0: - resolution: {integrity: sha512-FQz52I8RXgFgOHym3XHYSREbNtkgSjF9prvMFH1nBsRyfL6SfCzoT1GuSDTlbsuPubM7/6Kbw0ZMQb8A+V+VsQ==} + valibot@0.42.1: + resolution: {integrity: sha512-3keXV29Ar5b//Hqi4MbSdV7lfVp6zuYLZuA9V1PvQUsXqogr+u5lvLPLk3A4f74VUXDnf/JfWMN6sB+koJ/FFw==} peerDependencies: typescript: '>=5' peerDependenciesMeta: typescript: optional: true - valibot@0.42.1: - resolution: {integrity: sha512-3keXV29Ar5b//Hqi4MbSdV7lfVp6zuYLZuA9V1PvQUsXqogr+u5lvLPLk3A4f74VUXDnf/JfWMN6sB+koJ/FFw==} + valibot@1.0.0-beta.8: + resolution: {integrity: sha512-OPAwJZtowb0j91b+bd77+ny7D1VVzsCzD7Jl9waLUlMprTsfI9Y3HHbW3hAQD7wKDKHsmGEesuiYWaYvcZL2wg==} peerDependencies: typescript: '>=5' peerDependenciesMeta: @@ -9898,9 +9903,9 @@ snapshots: transitivePeerDependencies: - supports-color - '@animo-id/expo-mdoc-data-transfer@0.0.3-alpha.7(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@react-native-community/cli-server-api@13.6.9)(@types/react@18.2.79)(expo@51.0.39(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0)))(react@18.3.1)': + '@animo-id/expo-mdoc-data-transfer@0.0.3-alpha.8(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@react-native-community/cli-server-api@13.6.9)(@types/react@18.2.79)(expo@51.0.39(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0)))(react@18.3.1)': dependencies: - '@expo/config-plugins': 8.0.10 + '@expo/config-plugins': 8.0.11 expo: 51.0.39(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0)) react: 18.3.1 react-native: 0.76.3(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@react-native-community/cli-server-api@13.6.9)(@types/react@18.2.79)(react@18.3.1) @@ -9923,6 +9928,16 @@ snapshots: react: 18.3.1 react-native: 0.74.5(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.2.79)(react@18.3.1) + '@animo-id/expo-secure-environment@0.1.0-alpha.11(expo@51.0.39(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0)))(react-native@0.76.3(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@react-native-community/cli-server-api@13.6.9)(@types/react@18.2.79)(react@18.3.1))(react@18.3.1)': + dependencies: + '@peculiar/asn1-ecc': 2.3.14 + '@peculiar/asn1-schema': 2.3.13 + '@peculiar/asn1-x509': 2.3.13 + expo: 51.0.39(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0)) + react: 18.3.1 + react-native: 0.76.3(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@react-native-community/cli-server-api@13.6.9)(@types/react@18.2.79)(react@18.3.1) + optional: true + '@animo-id/mdoc@0.2.39': dependencies: compare-versions: 6.1.1 @@ -9955,7 +9970,7 @@ snapshots: '@sd-jwt/decode': 0.7.2 '@sd-jwt/present': 0.7.2 '@sd-jwt/types': 0.7.2 - '@sphereon/pex-models': 2.3.1 + '@sphereon/pex-models': 2.3.2 '@sphereon/ssi-types': 0.30.2-next.135 ajv: 8.17.1 ajv-formats: 2.1.1(ajv@8.17.1) @@ -11122,7 +11137,7 @@ snapshots: '@astronautlabs/jsonpath': 1.1.2 '@credo-ts/core': https://gitpkg.vercel.app/animo/aries-framework-javascript/packages/core?funke(expo@51.0.39(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0)))(react-native@0.76.3(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@react-native-community/cli-server-api@13.6.9)(@types/react@18.2.79)(react@18.3.1))(typescript@5.3.3)(web-streams-polyfill@3.3.3) '@hyperledger/anoncreds-shared': 0.2.4 - '@sphereon/pex-models': 2.3.1 + '@sphereon/pex-models': 2.3.2 big-integer: 1.6.52 bn.js: 5.2.1 class-transformer: 0.5.1 @@ -11137,7 +11152,7 @@ snapshots: - typescript - web-streams-polyfill - '@credo-ts/askar@https://gitpkg.vercel.app/animo/aries-framework-javascript/packages/askar?funke(patch_hash=zbu2rcss5evxukkhh5w5venkba)(@hyperledger/aries-askar-shared@0.2.3)(expo@51.0.39(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0)))(react-native@0.76.3(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@react-native-community/cli-server-api@13.6.9)(@types/react@18.2.79)(react@18.3.1))(typescript@5.3.3)(web-streams-polyfill@3.3.3)': + '@credo-ts/askar@https://gitpkg.vercel.app/animo/aries-framework-javascript/packages/askar?funke(patch_hash=zbu2rcss5evxukkhh5w5venkba)(@animo-id/expo-secure-environment@0.1.0-alpha.11(expo@51.0.39(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0)))(react-native@0.76.3(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@react-native-community/cli-server-api@13.6.9)(@types/react@18.2.79)(react@18.3.1))(react@18.3.1))(@hyperledger/aries-askar-shared@0.2.3)(expo@51.0.39(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0)))(react-native@0.76.3(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@react-native-community/cli-server-api@13.6.9)(@types/react@18.2.79)(react@18.3.1))(typescript@5.3.3)(web-streams-polyfill@3.3.3)': dependencies: '@credo-ts/core': https://gitpkg.vercel.app/animo/aries-framework-javascript/packages/core?funke(expo@51.0.39(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0)))(react-native@0.76.3(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@react-native-community/cli-server-api@13.6.9)(@types/react@18.2.79)(react@18.3.1))(typescript@5.3.3)(web-streams-polyfill@3.3.3) '@hyperledger/aries-askar-shared': 0.2.3 @@ -11146,6 +11161,8 @@ snapshots: class-validator: 0.14.1 rxjs: 7.8.1 tsyringe: 4.8.0 + optionalDependencies: + '@animo-id/expo-secure-environment': 0.1.0-alpha.11(expo@51.0.39(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0)))(react-native@0.76.3(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@react-native-community/cli-server-api@13.6.9)(@types/react@18.2.79)(react@18.3.1))(react@18.3.1) transitivePeerDependencies: - domexception - encoding @@ -11204,7 +11221,7 @@ snapshots: '@sd-jwt/sd-jwt-vc': 0.7.2 '@sd-jwt/types': 0.7.2 '@sd-jwt/utils': 0.7.2 - '@sphereon/pex-models': 2.3.1 + '@sphereon/pex-models': 2.3.2 '@sphereon/ssi-types': 0.30.2-next.135 '@stablelib/ed25519': 1.0.3 '@types/ws': 8.5.12 @@ -11214,7 +11231,7 @@ snapshots: buffer: 6.0.3 class-transformer: 0.5.1 class-validator: 0.14.1 - dcql: 0.2.13(typescript@5.3.3) + dcql: 0.2.17(typescript@5.3.3) did-resolver: 4.1.0 lru_map: 0.4.1 luxon: 3.5.0 @@ -11259,7 +11276,7 @@ snapshots: '@sd-jwt/sd-jwt-vc': 0.7.2 '@sd-jwt/types': 0.7.2 '@sd-jwt/utils': 0.7.2 - '@sphereon/pex-models': 2.3.1 + '@sphereon/pex-models': 2.3.2 '@sphereon/ssi-types': 0.30.2-next.135 '@stablelib/ed25519': 1.0.3 '@types/ws': 8.5.12 @@ -11269,7 +11286,7 @@ snapshots: buffer: 6.0.3 class-transformer: 0.5.1 class-validator: 0.14.1 - dcql: 0.2.13(typescript@5.3.3) + dcql: 0.2.17(typescript@5.3.3) did-resolver: 4.1.0 lru_map: 0.4.1 luxon: 3.5.0 @@ -11312,7 +11329,7 @@ snapshots: '@animo-id/oauth2': 0.1.4(typescript@5.3.3) '@animo-id/oid4vci': 0.1.4(typescript@5.3.3) '@credo-ts/core': https://gitpkg.vercel.app/animo/aries-framework-javascript/packages/core?funke(expo@51.0.39(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0)))(react-native@0.76.3(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@react-native-community/cli-server-api@13.6.9)(@types/react@18.2.79)(react@18.3.1))(typescript@5.3.3)(web-streams-polyfill@3.3.3) - '@openid-federation/core': 0.1.1-alpha.13 + '@openid-federation/core': 0.1.1-alpha.17 '@sphereon/did-auth-siop': https://gitpkg.vercel.app/animo/OID4VC/packages/siop-oid4vp?funke(typescript@5.3.3) '@sphereon/oid4vc-common': https://gitpkg.vercel.app/animo/OID4VC/packages/common?funke '@sphereon/ssi-types': 0.30.2-next.135 @@ -12456,7 +12473,7 @@ snapshots: dependencies: semver: 7.6.3 - '@openid-federation/core@0.1.1-alpha.13': + '@openid-federation/core@0.1.1-alpha.17': dependencies: buffer: 6.0.3 zod: 3.23.8 @@ -13693,10 +13710,10 @@ snapshots: '@sphereon/jarm': https://gitpkg.vercel.app/animo/OID4VC/packages/jarm?funke(typescript@5.3.3) '@sphereon/oid4vc-common': https://gitpkg.vercel.app/animo/OID4VC/packages/common?funke '@sphereon/pex': 5.0.0-unstable.24 - '@sphereon/pex-models': 2.3.1 + '@sphereon/pex-models': 2.3.2 '@sphereon/ssi-types': 0.30.2-next.279 cross-fetch: 4.0.0 - dcql: 0.2.13(typescript@5.3.3) + dcql: 0.2.17(typescript@5.3.3) debug: 4.3.7 events: 3.3.0 jwt-decode: 4.0.0 @@ -13733,7 +13750,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@sphereon/pex-models@2.3.1': {} + '@sphereon/pex-models@2.3.2': {} '@sphereon/pex@5.0.0-unstable.24': dependencies: @@ -13741,7 +13758,7 @@ snapshots: '@sd-jwt/decode': 0.7.2 '@sd-jwt/present': 0.7.2 '@sd-jwt/types': 0.7.2 - '@sphereon/pex-models': 2.3.1 + '@sphereon/pex-models': 2.3.2 '@sphereon/ssi-types': 0.30.2-next.129 ajv: 8.17.1 ajv-formats: 2.1.1(ajv@8.17.1) @@ -16966,9 +16983,9 @@ snapshots: dayjs@1.11.13: {} - dcql@0.2.13(typescript@5.3.3): + dcql@0.2.17(typescript@5.3.3): dependencies: - valibot: 0.37.0(typescript@5.3.3) + valibot: 1.0.0-beta.8(typescript@5.3.3) transitivePeerDependencies: - typescript @@ -21849,11 +21866,11 @@ snapshots: uuid@9.0.1: {} - valibot@0.37.0(typescript@5.3.3): + valibot@0.42.1(typescript@5.3.3): optionalDependencies: typescript: 5.3.3 - valibot@0.42.1(typescript@5.3.3): + valibot@1.0.0-beta.8(typescript@5.3.3): optionalDependencies: typescript: 5.3.3 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index af86d649..6fd53585 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -17,6 +17,7 @@ catalog: "@credo-ts/question-answer": https://gitpkg.vercel.app/animo/aries-framework-javascript/packages/question-answer?funke "@credo-ts/react-native": https://gitpkg.vercel.app/animo/aries-framework-javascript/packages/react-native?funke "@credo-ts/react-hooks": 0.6.1 + "@openid-federation/core": 0.1.1-alpha.17 "@hyperledger/anoncreds-react-native": ^0.2.4 "@hyperledger/aries-askar-react-native": ^0.2.3 "@hyperledger/indy-vdr-react-native": ^0.2.0 @@ -24,14 +25,15 @@ catalog: "@sphereon/did-auth-siop": https://gitpkg.vercel.app/animo/OID4VC/packages/siop-oid4vp?funke "@sphereon/jarm": https://gitpkg.vercel.app/animo/OID4VC/packages/jarm?funke "@sphereon/oid4vc-common": https://gitpkg.vercel.app/animo/OID4VC/packages/common?funke - dcql: 0.2.13 + dcql: 0.2.17 "@animo-id/expo-ausweis-sdk": 0.0.1-alpha.14 "@animo-id/oid4vci": 0.1.4 "@animo-id/oauth2": 0.1.4 "@animo-id/oauth2-utils": 0.1.4 "@animo-id/expo-secure-environment": 0.1.0-alpha.11 - "@animo-id/expo-mdoc-data-transfer": 0.0.3-alpha.7 + "@animo-id/expo-mdoc-data-transfer": 0.0.3-alpha.8 "@animo-id/mdoc": 0.2.39 "@unimodules/react-native-adapter": "./noop" "@unimodules/core": "./noop" - expo: ~51.0.39 \ No newline at end of file + expo: ~51.0.39 + "@sphereon/pex-models": 2.3.2