Skip to content

Commit

Permalink
Merge branch 'main' into feat/openid4vc-issued-state-per-cred
Browse files Browse the repository at this point in the history
  • Loading branch information
auer-martin authored Apr 11, 2024
2 parents 2d56b2d + f54b90b commit 2e47655
Show file tree
Hide file tree
Showing 10 changed files with 68 additions and 14 deletions.
1 change: 1 addition & 0 deletions packages/core/src/agent/Agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ export class Agent<AgentModules extends AgentModulesInput = any> extends BaseAge
.receiveMessage(e.payload.message, {
connection: e.payload.connection,
contextCorrelationId: e.payload.contextCorrelationId,
receivedAt: e.payload.receivedAt,
})
.catch((error) => {
this.logger.error('Failed to process message', { error })
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/agent/Dispatcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ class Dispatcher {
payload: {
message,
connection,
receivedAt: messageContext.receivedAt,
},
})
}
Expand Down
2 changes: 2 additions & 0 deletions packages/core/src/agent/Events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export interface AgentMessageReceivedEvent extends BaseEvent {
message: unknown
connection?: ConnectionRecord
contextCorrelationId?: string
receivedAt?: Date
}
}

Expand All @@ -41,6 +42,7 @@ export interface AgentMessageProcessedEvent extends BaseEvent {
payload: {
message: AgentMessage
connection?: ConnectionRecord
receivedAt?: Date
}
}

Expand Down
21 changes: 15 additions & 6 deletions packages/core/src/agent/MessageReceiver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,13 @@ export class MessageReceiver {
session,
connection,
contextCorrelationId,
}: { session?: TransportSession; connection?: ConnectionRecord; contextCorrelationId?: string } = {}
receivedAt,
}: {
session?: TransportSession
connection?: ConnectionRecord
contextCorrelationId?: string
receivedAt?: Date
} = {}
) {
this.logger.debug(`Agent received message`)

Expand All @@ -93,9 +99,9 @@ export class MessageReceiver {

try {
if (this.isEncryptedMessage(inboundMessage)) {
await this.receiveEncryptedMessage(agentContext, inboundMessage as EncryptedMessage, session)
await this.receiveEncryptedMessage(agentContext, inboundMessage as EncryptedMessage, session, receivedAt)
} else if (this.isPlaintextMessage(inboundMessage)) {
await this.receivePlaintextMessage(agentContext, inboundMessage, connection)
await this.receivePlaintextMessage(agentContext, inboundMessage, connection, receivedAt)
} else {
throw new CredoError('Unable to parse incoming message: unrecognized format')
}
Expand All @@ -108,17 +114,19 @@ export class MessageReceiver {
private async receivePlaintextMessage(
agentContext: AgentContext,
plaintextMessage: PlaintextMessage,
connection?: ConnectionRecord
connection?: ConnectionRecord,
receivedAt?: Date
) {
const message = await this.transformAndValidate(agentContext, plaintextMessage)
const messageContext = new InboundMessageContext(message, { connection, agentContext })
const messageContext = new InboundMessageContext(message, { connection, agentContext, receivedAt })
await this.dispatcher.dispatch(messageContext)
}

private async receiveEncryptedMessage(
agentContext: AgentContext,
encryptedMessage: EncryptedMessage,
session?: TransportSession
session?: TransportSession,
receivedAt?: Date
) {
const decryptedMessage = await this.decryptMessage(agentContext, encryptedMessage)
const { plaintextMessage, senderKey, recipientKey } = decryptedMessage
Expand All @@ -140,6 +148,7 @@ export class MessageReceiver {
senderKey,
recipientKey,
agentContext,
receivedAt,
})

// We want to save a session if there is a chance of returning outbound message via inbound transport.
Expand Down
3 changes: 3 additions & 0 deletions packages/core/src/agent/models/InboundMessageContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export interface MessageContextParams {
senderKey?: Key
recipientKey?: Key
agentContext: AgentContext
receivedAt?: Date
}

export class InboundMessageContext<T extends AgentMessage = AgentMessage> {
Expand All @@ -19,6 +20,7 @@ export class InboundMessageContext<T extends AgentMessage = AgentMessage> {
public sessionId?: string
public senderKey?: Key
public recipientKey?: Key
public receivedAt: Date
public readonly agentContext: AgentContext

public constructor(message: T, context: MessageContextParams) {
Expand All @@ -28,6 +30,7 @@ export class InboundMessageContext<T extends AgentMessage = AgentMessage> {
this.connection = context.connection
this.sessionId = context.sessionId
this.agentContext = context.agentContext
this.receivedAt = context.receivedAt ?? new Date()
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ export class V2MessagePickupProtocol extends BaseMessagePickupProtocol {
(msg) =>
new Attachment({
id: msg.id,
lastmodTime: msg.receivedAt,
data: {
json: msg.encryptedMessage,
},
Expand Down Expand Up @@ -190,6 +191,7 @@ export class V2MessagePickupProtocol extends BaseMessagePickupProtocol {
(msg) =>
new Attachment({
id: msg.id,
lastmodTime: msg.receivedAt,
data: {
json: msg.encryptedMessage,
},
Expand Down Expand Up @@ -323,6 +325,7 @@ export class V2MessagePickupProtocol extends BaseMessagePickupProtocol {
payload: {
message: attachment.getDataAsJson<EncryptedMessage>(),
contextCorrelationId: messageContext.agentContext.contextCorrelationId,
receivedAt: attachment.lastmodTime,
},
})
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
import type { EncryptedMessage } from '../../../types'

/**
* Basic representation of an encrypted message in a Message Pickup Queue
* - id: Message Pickup repository's specific queued message id (unrelated to DIDComm message id)
* - receivedAt: reception time (i.e. time when the message has been added to the queue)
* - encryptedMessage: packed message
*/
export type QueuedMessage = {
id: string
receivedAt?: Date
encryptedMessage: EncryptedMessage
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { OpenId4VcIssuanceRequest } from './requestContext'
import type { AgentContext } from '@credo-ts/core'
import type { JWTSignerCallback } from '@sphereon/oid4vci-common'
import type { AccessTokenRequest, JWTSignerCallback } from '@sphereon/oid4vci-common'
import type { NextFunction, Response, Router } from 'express'

import { getJwkFromKey, CredoError, JwsService, JwtPayload, getJwkClassFromKeyType, Key } from '@credo-ts/core'
Expand Down Expand Up @@ -89,7 +89,7 @@ function getJwtSignerCallback(
const jwk = getJwkFromKey(signerPublicKey)
const signedJwt = await jwsService.createJwsCompact(agentContext, {
protectedHeaderOptions: { ...jwt.header, jwk, alg },
payload: new JwtPayload(jwt.payload),
payload: JwtPayload.fromJson(jwt.payload),
key: signerPublicKey,
})

Expand All @@ -106,11 +106,15 @@ export function handleTokenRequest(config: OpenId4VciAccessTokenEndpointConfig)
const requestContext = getRequestContext(request)
const { agentContext, issuer } = requestContext

if (request.body.grant_type !== GrantTypes.PRE_AUTHORIZED_CODE) {
return response.status(400).json({
error: TokenErrorResponse.invalid_request,
error_description: PRE_AUTHORIZED_CODE_REQUIRED_ERROR,
})
const body = request.body as AccessTokenRequest
if (body.grant_type !== GrantTypes.PRE_AUTHORIZED_CODE) {
return sendErrorResponse(
response,
agentContext.config.logger,
400,
TokenErrorResponse.invalid_request,
PRE_AUTHORIZED_CODE_REQUIRED_ERROR
)
}

const openId4VcIssuerService = agentContext.dependencyManager.resolve(OpenId4VcIssuerService)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,12 @@ export function configureCredentialEndpoint(router: Router, config: OpenId4VciCr
const { agentContext, issuer } = getRequestContext(request)
const openId4VcIssuerService = agentContext.dependencyManager.resolve(OpenId4VcIssuerService)

let preAuthorizedCode: string

// Verify the access token (should at some point be moved to a middleware function or something)
try {
await verifyAccessToken(agentContext, issuer, request.headers.authorization)
preAuthorizedCode = (await verifyAccessToken(agentContext, issuer, request.headers.authorization))
.preAuthorizedCode
} catch (error) {
return sendErrorResponse(response, agentContext.config.logger, 401, 'unauthorized', error)
}
Expand All @@ -44,6 +47,19 @@ export function configureCredentialEndpoint(router: Router, config: OpenId4VciCr
credentialRequest,
})

if (issuanceSession?.preAuthorizedCode !== preAuthorizedCode) {
agentContext.config.logger.warn(
`Credential request used access token with for credential offer with different pre-authorized code than was used for the issuance session ${issuanceSession?.id}`
)
return sendErrorResponse(
response,
agentContext.config.logger,
401,
'unauthorized',
'Access token is not valid for this credential request'
)
}

if (!issuanceSession) {
const cNonce = getCNonceFromCredentialRequest(credentialRequest)
agentContext.config.logger.warn(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,12 @@ export async function verifyAccessToken(
if (accessToken.payload.iss !== issuerMetadata.issuerUrl) {
throw new CredoError('Access token was not issued by the expected issuer')
}

if (typeof accessToken.payload.additionalClaims.preAuthorizedCode !== 'string') {
throw new CredoError('No preAuthorizedCode present in access token')
}

return {
preAuthorizedCode: accessToken.payload.additionalClaims.preAuthorizedCode,
}
}

0 comments on commit 2e47655

Please sign in to comment.