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

feat: OpenID Federation usage in the app #234

Merged
merged 1 commit 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
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@
"@credo-ts/openid4vc": "catalog:",
"@credo-ts/question-answer": "catalog:",
"@credo-ts/react-native": "catalog:",
"@sphereon/pex-models": "catalog:"
"@sphereon/pex-models": "catalog:",
"@openid-federation/core": "catalog:"
},
"patchedDependencies": {
"@hyperledger/[email protected]": "patches/@[email protected]",
Expand Down
80 changes: 76 additions & 4 deletions packages/agent/src/invitation/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,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
Expand Down Expand Up @@ -398,18 +453,27 @@ export const getCredentialsForProofRequest = async ({
agent,
data,
uri,
allowUntrustedFederation = true, // TODO: True for now
}: {
agent: EitherAgent
// Either data or uri can be provided
data?: string
uri?: string
allowUntrustedFederation?: boolean
}) => {
let requestUri: string

if (data) {
let requestData = data

const { entityId = undefined, data: fromFederationData = null } = allowUntrustedFederation
? await extractEntityIdFromAuthorizationRequest({ data: requestData, uri })
: {}
requestData = fromFederationData ?? requestData

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 {
Expand All @@ -422,7 +486,15 @@ export const getCredentialsForProofRequest = async ({
requestUri,
})

const resolved = await 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 formattedSubmission: FormattedSubmission
if (resolved.presentationExchange) {
Expand Down Expand Up @@ -451,7 +523,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
? {
Expand Down
1 change: 1 addition & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions pnpm-workspace.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down