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