Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: issues #232

Merged
merged 4 commits into from
Nov 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 8 additions & 6 deletions apps/easypid/src/app/(app)/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,17 +32,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
Expand All @@ -66,7 +68,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
}

Expand Down
2 changes: 1 addition & 1 deletion apps/easypid/src/app/+native-intent.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { parseInvitationUrl } from '@package/agent'
import { deeplinkSchemes } from '@package/app'
import * as Haptics from 'expo-haptics'
import { Platform } from 'react-native'
import { router } from 'expo-router'
import { Platform } from 'react-native'

export async function redirectSystemPath({ path, initial }: { path: string; initial: boolean }) {
const isRecognizedDeeplink = deeplinkSchemes.some((scheme) => path.startsWith(scheme))
Expand Down
6 changes: 5 additions & 1 deletion apps/easypid/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -122,11 +125,13 @@ 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,
})
Expand Down Expand Up @@ -164,7 +169,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
}
Expand All @@ -179,20 +184,20 @@ 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,
})
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
}
Expand All @@ -203,23 +208,22 @@ 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,
})
setErrorReason('Error while retrieving credentials')
}
},
[resolvedCredentialOffer, agent, retrieveCredentials]
[resolvedCredentialOffer, agent, retrieveCredentials, configurationId]
)

const parsePresentationRequestUrl = useCallback(
(oid4vpRequestUrl: string) =>
getCredentialsForProofRequest({
agent,
uri: oid4vpRequestUrl,
allowUntrustedCertificates: true,
})
.then(setCredentialsForRequest)
.catch((error) => {
Expand Down Expand Up @@ -276,7 +280,6 @@ export function FunkeCredentialNotificationScreen() {
agent,
resolvedRequest: credentialsForRequest,
selectedCredentials: {},
allowUntrustedCertificate: true,
})

const { authorizationCode } = await acquireAuthorizationCodeUsingPresentation({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ export function FunkeOpenIdPresentationNotificationScreen() {
agent,
data: params.data,
uri: params.uri,
allowUntrustedCertificates: true,
})
.then(setCredentialsForRequest)
.catch((error) => {
Expand Down Expand Up @@ -93,7 +92,6 @@ export function FunkeOpenIdPresentationNotificationScreen() {
agent,
resolvedRequest: credentialsForRequest,
selectedCredentials: {},
allowUntrustedCertificate: true,
})

await addSharedActivityForCredentialsForRequest(agent, credentialsForRequest, 'success')
Expand Down
1 change: 1 addition & 0 deletions apps/easypid/src/utils/resetWallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
import { removeShouldUseCloudHsm } from '../features/onboarding/useShouldUseCloudHsm'

export async function resetWallet(secureUnlock: SecureUnlockReturn<SecureUnlockContext>) {
console.log('Resetting wallet')
if (secureUnlock.state === 'unlocked') {
const agent = secureUnlock.context.agent
secureUnlock.lock()
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@
"@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:"
},
"patchedDependencies": {
"@hyperledger/[email protected]": "patches/@[email protected]",
Expand Down
41 changes: 36 additions & 5 deletions packages/agent/src/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
Agent,
AutoAcceptCredential,
AutoAcceptProof,
ClaimFormat,
ConnectionsModule,
CredentialsModule,
DidsModule,
Expand All @@ -23,13 +24,15 @@ import {
KeyDidRegistrar,
KeyDidResolver,
LogLevel,
Mdoc,
MediationRecipientModule,
MediatorPickupStrategy,
ProofsModule,
V2CredentialProtocol,
V2ProofProtocol,
WebDidResolver,
WsOutboundTransport,
X509Module,
} from '@credo-ts/core'
import {
IndyVdrAnonCredsRegistry,
Expand All @@ -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'

Expand Down Expand Up @@ -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
}

Expand Down
17 changes: 10 additions & 7 deletions packages/agent/src/format/formatPresentation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down
Loading