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: did rotate #1699

Merged
merged 11 commits into from
Jan 29, 2024
6 changes: 5 additions & 1 deletion packages/core/src/agent/__tests__/Agent.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { getAgentOptions } from '../../../tests/helpers'
import { InjectionSymbols } from '../../constants'
import { BasicMessageRepository, BasicMessageService } from '../../modules/basic-messages'
import { BasicMessagesApi } from '../../modules/basic-messages/BasicMessagesApi'
import { DidRotateService } from '../../modules/connections'
import { ConnectionsApi } from '../../modules/connections/ConnectionsApi'
import { ConnectionRepository } from '../../modules/connections/repository/ConnectionRepository'
import { ConnectionService } from '../../modules/connections/services/ConnectionService'
Expand Down Expand Up @@ -158,6 +159,7 @@ describe('Agent', () => {
expect(container.resolve(ConnectionsApi)).toBeInstanceOf(ConnectionsApi)
expect(container.resolve(ConnectionService)).toBeInstanceOf(ConnectionService)
expect(container.resolve(ConnectionRepository)).toBeInstanceOf(ConnectionRepository)
expect(container.resolve(DidRotateService)).toBeInstanceOf(DidRotateService)
expect(container.resolve(TrustPingService)).toBeInstanceOf(TrustPingService)

expect(container.resolve(ProofsApi)).toBeInstanceOf(ProofsApi)
Expand Down Expand Up @@ -197,6 +199,7 @@ describe('Agent', () => {
expect(container.resolve(ConnectionService)).toBe(container.resolve(ConnectionService))
expect(container.resolve(ConnectionRepository)).toBe(container.resolve(ConnectionRepository))
expect(container.resolve(TrustPingService)).toBe(container.resolve(TrustPingService))
expect(container.resolve(DidRotateService)).toBe(container.resolve(DidRotateService))

expect(container.resolve(ProofsApi)).toBe(container.resolve(ProofsApi))
expect(container.resolve(ProofRepository)).toBe(container.resolve(ProofRepository))
Expand Down Expand Up @@ -247,6 +250,7 @@ describe('Agent', () => {
'https://didcomm.org/issue-credential/2.0',
'https://didcomm.org/present-proof/2.0',
'https://didcomm.org/didexchange/1.1',
'https://didcomm.org/did-rotate/1.0',
'https://didcomm.org/discover-features/1.0',
'https://didcomm.org/discover-features/2.0',
'https://didcomm.org/messagepickup/1.0',
Expand All @@ -256,6 +260,6 @@ describe('Agent', () => {
'https://didcomm.org/revocation_notification/2.0',
])
)
expect(protocols.length).toEqual(13)
expect(protocols.length).toEqual(14)
})
})
122 changes: 120 additions & 2 deletions packages/core/src/modules/connections/ConnectionsApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { DidResolverService } from '../dids'
import { DidRepository } from '../dids/repository'
import { OutOfBandService } from '../oob/OutOfBandService'
import { RoutingService } from '../routing/services/RoutingService'
import { getMediationRecordForDidDocument } from '../routing/services/helpers'

import { ConnectionsModuleConfig } from './ConnectionsModuleConfig'
import { DidExchangeProtocol } from './DidExchangeProtocol'
Expand All @@ -28,8 +29,13 @@ import {
TrustPingMessageHandler,
TrustPingResponseMessageHandler,
ConnectionProblemReportHandler,
DidRotateHandler,
DidRotateAckHandler,
DidRotateProblemReportHandler,
HangupHandler,
} from './handlers'
import { HandshakeProtocol } from './models'
import { DidRotateService } from './services'
import { ConnectionService } from './services/ConnectionService'
import { TrustPingService } from './services/TrustPingService'

Expand All @@ -47,6 +53,7 @@ export class ConnectionsApi {

private didExchangeProtocol: DidExchangeProtocol
private connectionService: ConnectionService
private didRotateService: DidRotateService
private outOfBandService: OutOfBandService
private messageSender: MessageSender
private trustPingService: TrustPingService
Expand All @@ -59,6 +66,7 @@ export class ConnectionsApi {
messageHandlerRegistry: MessageHandlerRegistry,
didExchangeProtocol: DidExchangeProtocol,
connectionService: ConnectionService,
didRotateService: DidRotateService,
outOfBandService: OutOfBandService,
trustPingService: TrustPingService,
routingService: RoutingService,
Expand All @@ -70,6 +78,7 @@ export class ConnectionsApi {
) {
this.didExchangeProtocol = didExchangeProtocol
this.connectionService = connectionService
this.didRotateService = didRotateService
this.outOfBandService = outOfBandService
this.trustPingService = trustPingService
this.routingService = routingService
Expand All @@ -96,8 +105,8 @@ export class ConnectionsApi {
) {
const { protocol, label, alias, imageUrl, autoAcceptConnection, ourDid } = config

if (ourDid && !config.routing) {
throw new AriesFrameworkError('If an external did is specified, routing configuration must be defined as well')
if (ourDid && config.routing) {
throw new AriesFrameworkError(`'routing' is disallowed when defining 'ourDid'`)
}

const routing =
Expand Down Expand Up @@ -278,6 +287,74 @@ export class ConnectionsApi {
return message
}

/**
* Rotate the DID used for a given connection, notifying the other party immediately.
*
* If `toDid` is not specified, a new peer did will be created. Optionally, routing
* configuration can be set.
*
* Note: any did created or imported in agent wallet can be used as `toDid`, as long as
* there are valid DIDComm services in its DID Document.
*
* @param options connectionId and optional target did and routing configuration
* @returns object containing the new did
*/
public async rotate(options: { connectionId: string; toDid?: string; routing?: Routing }) {
const { connectionId, toDid } = options
const connection = await this.connectionService.getById(this.agentContext, connectionId)

if (toDid && options.routing) {
throw new AriesFrameworkError(`'routing' is disallowed when defining 'toDid'`)
}

let routing = options.routing
if (!toDid && !routing) {
routing = await this.routingService.getRouting(this.agentContext, {})
}

const message = await this.didRotateService.createRotate(this.agentContext, {
connection,
toDid,
routing,
})

const outboundMessageContext = new OutboundMessageContext(message, {
agentContext: this.agentContext,
connection,
})

await this.messageSender.sendMessage(outboundMessageContext)

return { newDid: message.toDid }
}

/**
* Terminate a connection by sending a hang-up message to the other party. The connection record itself and any
* keys used for mediation will only be deleted if `deleteAfterHangup` flag is set.
*
* @param options connectionId
*/
public async hangup(options: { connectionId: string; deleteAfterHangup?: boolean }) {
const connection = await this.connectionService.getById(this.agentContext, options.connectionId)

const connectionBeforeHangup = connection.clone()

// Create Hangup message and update did in connection record
const message = await this.didRotateService.createHangup(this.agentContext, { connection })

const outboundMessageContext = new OutboundMessageContext(message, {
agentContext: this.agentContext,
connection: connectionBeforeHangup,
})

await this.messageSender.sendMessage(outboundMessageContext)

// After hang-up message submission, delete connection if required
if (options.deleteAfterHangup) {
await this.deleteById(connection.id)
}
}

public async returnWhenIsConnected(connectionId: string, options?: { timeoutMs: number }): Promise<ConnectionRecord> {
return this.connectionService.returnWhenIsConnected(this.agentContext, connectionId, options?.timeoutMs)
}
Expand Down Expand Up @@ -394,6 +471,39 @@ export class ConnectionsApi {
return this.connectionService.deleteById(this.agentContext, connectionId)
}

/**
* Remove relationship of a connection with any previous did (either ours or theirs), preventing it from accepting
* messages from them. This is usually called when a DID Rotation flow has been succesful and we are sure that no
* more messages with older keys will arrive.
*
* It will remove routing keys from mediator if applicable.
*
* Note: this will not actually delete any DID from the wallet.
*
* @param connectionId
*/
public async removePreviousDids(options: { connectionId: string }) {
const connection = await this.connectionService.getById(this.agentContext, options.connectionId)

for (const previousDid of connection.previousDids) {
const did = await this.didResolverService.resolve(this.agentContext, previousDid)
if (!did.didDocument) continue
const mediatorRecord = await getMediationRecordForDidDocument(this.agentContext, did.didDocument)

if (mediatorRecord) {
await this.routingService.removeRouting(this.agentContext, {
recipientKeys: did.didDocument.recipientKeys,
mediatorId: mediatorRecord.id,
})
}
}

connection.previousDids = []
connection.previousTheirDids = []

await this.connectionService.update(this.agentContext, connection)
}

public async findAllByOutOfBandId(outOfBandId: string) {
return this.connectionService.findAllByOutOfBandId(this.agentContext, outOfBandId)
}
Expand Down Expand Up @@ -460,5 +570,13 @@ export class ConnectionsApi {
messageHandlerRegistry.registerMessageHandler(
new DidExchangeCompleteHandler(this.didExchangeProtocol, this.outOfBandService)
)

messageHandlerRegistry.registerMessageHandler(new DidRotateHandler(this.didRotateService, this.connectionService))

messageHandlerRegistry.registerMessageHandler(new DidRotateAckHandler(this.didRotateService))

messageHandlerRegistry.registerMessageHandler(new HangupHandler(this.didRotateService))

messageHandlerRegistry.registerMessageHandler(new DidRotateProblemReportHandler(this.didRotateService))
}
}
9 changes: 7 additions & 2 deletions packages/core/src/modules/connections/ConnectionsModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ import { Protocol } from '../../agent/models'
import { ConnectionsApi } from './ConnectionsApi'
import { ConnectionsModuleConfig } from './ConnectionsModuleConfig'
import { DidExchangeProtocol } from './DidExchangeProtocol'
import { ConnectionRole, DidExchangeRole } from './models'
import { ConnectionRole, DidExchangeRole, DidRotateRole } from './models'
import { ConnectionRepository } from './repository'
import { ConnectionService, TrustPingService } from './services'
import { ConnectionService, DidRotateService, TrustPingService } from './services'

export class ConnectionsModule implements Module {
public readonly config: ConnectionsModuleConfig
Expand All @@ -32,6 +32,7 @@ export class ConnectionsModule implements Module {
// Services
dependencyManager.registerSingleton(ConnectionService)
dependencyManager.registerSingleton(DidExchangeProtocol)
dependencyManager.registerSingleton(DidRotateService)
dependencyManager.registerSingleton(TrustPingService)

// Repositories
Expand All @@ -46,6 +47,10 @@ export class ConnectionsModule implements Module {
new Protocol({
id: 'https://didcomm.org/didexchange/1.1',
roles: [DidExchangeRole.Requester, DidExchangeRole.Responder],
}),
new Protocol({
id: 'https://didcomm.org/did-rotate/1.0',
roles: [DidRotateRole.RotatingParty, DidRotateRole.ObservingParty],
})
)
}
Expand Down
21 changes: 21 additions & 0 deletions packages/core/src/modules/connections/ConnectionsModuleConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,27 @@ export interface ConnectionsModuleConfigOptions {
* @default PeerDidNumAlgo.GenesisDoc
*/
peerNumAlgoForDidExchangeRequests?: PeerDidNumAlgo

/**
* Peer did num algo to use for DID rotation (RFC 0794).
*
* @default PeerDidNumAlgo.ShortFormAndLongForm
*/
peerNumAlgoForDidRotation?: PeerDidNumAlgo.MultipleInceptionKeyWithoutDoc | PeerDidNumAlgo.ShortFormAndLongForm
}

export class ConnectionsModuleConfig {
#autoAcceptConnections?: boolean
#peerNumAlgoForDidExchangeRequests?: PeerDidNumAlgo
#peerNumAlgoForDidRotation?: PeerDidNumAlgo.MultipleInceptionKeyWithoutDoc | PeerDidNumAlgo.ShortFormAndLongForm

private options: ConnectionsModuleConfigOptions

public constructor(options?: ConnectionsModuleConfigOptions) {
this.options = options ?? {}
this.#autoAcceptConnections = this.options.autoAcceptConnections
this.#peerNumAlgoForDidExchangeRequests = this.options.peerNumAlgoForDidExchangeRequests
this.#peerNumAlgoForDidRotation = this.options.peerNumAlgoForDidRotation
}

/** See {@link ConnectionsModuleConfigOptions.autoAcceptConnections} */
Expand All @@ -56,4 +65,16 @@ export class ConnectionsModuleConfig {
public set peerNumAlgoForDidExchangeRequests(peerNumAlgoForDidExchangeRequests: PeerDidNumAlgo) {
this.#peerNumAlgoForDidExchangeRequests = peerNumAlgoForDidExchangeRequests
}

/** See {@link ConnectionsModuleConfigOptions.peerNumAlgoForDidRotation} */
public get peerNumAlgoForDidRotation() {
return this.#peerNumAlgoForDidRotation ?? PeerDidNumAlgo.ShortFormAndLongForm
}

/** See {@link ConnectionsModuleConfigOptions.peerNumAlgoForDidRotation} */
public set peerNumAlgoForDidRotation(
peerNumAlgoForDidRotation: PeerDidNumAlgo.MultipleInceptionKeyWithoutDoc | PeerDidNumAlgo.ShortFormAndLongForm
) {
this.#peerNumAlgoForDidRotation = peerNumAlgoForDidRotation
}
}
Loading
Loading