Skip to content

Commit

Permalink
feat: did rotate (openwallet-foundation#1699)
Browse files Browse the repository at this point in the history
Signed-off-by: Ariel Gentile <[email protected]>
  • Loading branch information
genaris authored Jan 29, 2024
1 parent a5b569b commit adc7d4e
Show file tree
Hide file tree
Showing 33 changed files with 1,267 additions and 108 deletions.
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

0 comments on commit adc7d4e

Please sign in to comment.