github.com/hyperledger/aries-framework-go@v0.3.2/pkg/client/legacyconnection/client.go (about) 1 /* 2 Copyright Avast Software. All Rights Reserved. 3 4 SPDX-License-Identifier: Apache-2.0 5 */ 6 7 package legacyconnection 8 9 import ( 10 "encoding/json" 11 "errors" 12 "fmt" 13 14 "github.com/btcsuite/btcutil/base58" 15 "github.com/google/uuid" 16 17 "github.com/hyperledger/aries-framework-go/pkg/didcomm/common/service" 18 "github.com/hyperledger/aries-framework-go/pkg/didcomm/protocol/legacyconnection" 19 "github.com/hyperledger/aries-framework-go/pkg/didcomm/protocol/mediator" 20 "github.com/hyperledger/aries-framework-go/pkg/didcomm/transport" 21 "github.com/hyperledger/aries-framework-go/pkg/doc/did" 22 "github.com/hyperledger/aries-framework-go/pkg/kms" 23 "github.com/hyperledger/aries-framework-go/pkg/store/connection" 24 "github.com/hyperledger/aries-framework-go/spi/storage" 25 ) 26 27 // InvitationMsgType defines the connection invite message type. 28 const InvitationMsgType = legacyconnection.InvitationMsgType 29 30 // ErrConnectionNotFound is returned when connection not found. 31 var ErrConnectionNotFound = errors.New("connection not found") 32 33 type options struct { 34 routerConnections []string 35 routerConnectionID string 36 } 37 38 func applyOptions(args ...Opt) *options { 39 opts := &options{} 40 41 for i := range args { 42 args[i](opts) 43 } 44 45 return opts 46 } 47 48 // Opt represents option function. 49 type Opt func(*options) 50 51 // InvOpt represents option for the CreateInvitation function. 52 type InvOpt Opt 53 54 // WithRouterConnectionID allows you to specify the router connection ID. 55 func WithRouterConnectionID(conn string) InvOpt { 56 return func(opts *options) { 57 opts.routerConnectionID = conn 58 } 59 } 60 61 // WithRouterConnections allows you to specify the router connections. 62 func WithRouterConnections(conns ...string) Opt { 63 return func(opts *options) { 64 for _, conn := range conns { 65 // filters out empty connections 66 if conn != "" { 67 opts.routerConnections = append(opts.routerConnections, conn) 68 } 69 } 70 } 71 } 72 73 // provider contains dependencies for the legacy-connection protocol and is typically created by using aries.Context(). 74 type provider interface { 75 Service(id string) (interface{}, error) 76 KMS() kms.KeyManager 77 ServiceEndpoint() string 78 StorageProvider() storage.Provider 79 ProtocolStateStorageProvider() storage.Provider 80 MediaTypeProfiles() []string 81 } 82 83 // Client enable access to legacyconnection api. 84 type Client struct { 85 service.Event 86 legacyconnectionSvc protocolService 87 routeSvc mediator.ProtocolService 88 kms kms.KeyManager 89 serviceEndpoint string 90 connectionStore *connection.Recorder 91 mediaTypeProfiles []string 92 } 93 94 // protocolService defines legacy-connection service. 95 type protocolService interface { 96 // DIDComm service 97 service.DIDComm 98 99 // Accepts/Approves connection request 100 AcceptConnectionRequest(connectionID, publicDID, label string, routerConnections []string) error 101 102 // Accepts/Approves connection invitation 103 AcceptInvitation(connectionID, publicDID, label string, routerConnections []string) error 104 105 // CreateImplicitInvitation creates implicit invitation. Inviter DID is required, invitee DID is optional. 106 // If invitee DID is not provided new peer DID will be created for implicit invitation connection request. 107 CreateImplicitInvitation(inviterLabel, inviterDID, inviteeLabel, 108 inviteeDID string, routerConnections []string) (string, error) 109 110 // CreateConnection saves the connection record. 111 CreateConnection(*connection.Record, *did.Doc) error 112 } 113 114 // New return new instance of legacyconnection client. 115 func New(ctx provider) (*Client, error) { 116 svc, err := ctx.Service(legacyconnection.LegacyConnection) 117 if err != nil { 118 return nil, err 119 } 120 121 legacyconnectionSvc, ok := svc.(protocolService) 122 if !ok { 123 return nil, errors.New("cast service to legacyconnection Service failed") 124 } 125 126 s, err := ctx.Service(mediator.Coordination) 127 if err != nil { 128 return nil, err 129 } 130 131 routeSvc, ok := s.(mediator.ProtocolService) 132 if !ok { 133 return nil, errors.New("cast service to Route Service failed") 134 } 135 136 connectionStore, err := connection.NewRecorder(ctx) 137 if err != nil { 138 return nil, err 139 } 140 141 mtp := ctx.MediaTypeProfiles() 142 if len(mtp) == 0 { 143 mtp = []string{transport.MediaTypeRFC0019EncryptedEnvelope} 144 } 145 146 return &Client{ 147 Event: legacyconnectionSvc, 148 legacyconnectionSvc: legacyconnectionSvc, 149 routeSvc: routeSvc, 150 kms: ctx.KMS(), 151 serviceEndpoint: ctx.ServiceEndpoint(), 152 connectionStore: connectionStore, 153 mediaTypeProfiles: mtp, 154 }, nil 155 } 156 157 // CreateInvitation creates an invitation. New key pair will be generated and did:key encoded public key will be 158 // used as basis for invitation. This invitation will be stored so client can cross-reference this invitation during 159 // connection protocol. 160 func (c *Client) CreateInvitation(label string, args ...InvOpt) (*Invitation, error) { 161 opts := &options{} 162 163 for i := range args { 164 args[i](opts) 165 } 166 167 _, pubKey, err := c.kms.CreateAndExportPubKeyBytes(kms.ED25519Type) 168 if err != nil { 169 return nil, fmt.Errorf("createInvitation: failed to extract public SigningKey bytes from handle: %w", err) 170 } 171 172 recKey := base58.Encode(pubKey) 173 174 var ( 175 serviceEndpoint = c.serviceEndpoint 176 routingKeys []string 177 ) 178 179 if opts.routerConnectionID != "" { 180 // get the route configs 181 serviceEndpoint, routingKeys, err = mediator.GetRouterConfig(c.routeSvc, 182 opts.routerConnectionID, c.serviceEndpoint) 183 if err != nil { 184 return nil, fmt.Errorf("createInvitation: getRouterConfig: %w", err) 185 } 186 187 if err = mediator.AddKeyToRouter(c.routeSvc, opts.routerConnectionID, recKey); err != nil { 188 return nil, fmt.Errorf("createInvitation: AddKeyToRouter: %w", err) 189 } 190 } 191 192 invitation := &legacyconnection.Invitation{ 193 ID: uuid.New().String(), 194 Label: label, 195 RecipientKeys: []string{recKey}, 196 ServiceEndpoint: serviceEndpoint, 197 Type: legacyconnection.InvitationMsgType, 198 RoutingKeys: routingKeys, 199 } 200 201 err = c.connectionStore.SaveInvitation(invitation.ID, invitation) 202 if err != nil { 203 return nil, fmt.Errorf("createInvitation: failed to save invitation: %w", err) 204 } 205 206 return &Invitation{invitation}, nil 207 } 208 209 // CreateInvitationWithDID creates an invitation with specified public DID. This invitation will be stored 210 // so client can cross reference this invitation during connection protocol. 211 func (c *Client) CreateInvitationWithDID(label, publicDID string) (*Invitation, error) { 212 invitation := &legacyconnection.Invitation{ 213 ID: uuid.New().String(), 214 Label: label, 215 DID: publicDID, 216 Type: legacyconnection.InvitationMsgType, 217 } 218 219 err := c.connectionStore.SaveInvitation(invitation.ID, invitation) 220 if err != nil { 221 return nil, fmt.Errorf("createInvitationWithDID: failed to save invitation with DID: %w", err) 222 } 223 224 return &Invitation{invitation}, nil 225 } 226 227 // HandleInvitation handle incoming invitation and returns the connectionID that can be used to query the state 228 // of legacy connection protocol. Upon successful completion of protocol, connection details will be used 229 // for securing communication between agents. 230 func (c *Client) HandleInvitation(invitation *Invitation) (string, error) { 231 payload, err := json.Marshal(invitation) 232 if err != nil { 233 return "", fmt.Errorf("handleInvitation: failed marshal invitation: %w", err) 234 } 235 236 msg, err := service.ParseDIDCommMsgMap(payload) 237 if err != nil { 238 return "", fmt.Errorf("handleInvitation: failed to create DIDCommMsg: %w", err) 239 } 240 241 connectionID, err := c.legacyconnectionSvc.HandleInbound(msg, service.EmptyDIDCommContext()) 242 if err != nil { 243 return "", fmt.Errorf("handleInvitation: failed from legacyconnection service handle: %w", err) 244 } 245 246 return connectionID, nil 247 } 248 249 // AcceptInvitation accepts/approves connection invitation. This call is not used if auto execute is setup 250 // for this client (see package example for more details about how to setup auto execute). 251 func (c *Client) AcceptInvitation(connectionID, publicDID, label string, args ...Opt) error { 252 opts := applyOptions(args...) 253 254 if err := c.legacyconnectionSvc.AcceptInvitation(connectionID, publicDID, label, opts.routerConnections); err != nil { 255 return fmt.Errorf("legacyconnection client - accept connection invitation: %w", err) 256 } 257 258 return nil 259 } 260 261 // AcceptConnectionRequest accepts/approves connection request. This call is not used if auto execute is setup 262 // for this client (see package example for more details about how to setup auto execute). 263 func (c *Client) AcceptConnectionRequest(connectionID, publicDID, label string, args ...Opt) error { 264 err := c.legacyconnectionSvc.AcceptConnectionRequest(connectionID, publicDID, label, 265 applyOptions(args...).routerConnections) 266 if err != nil { 267 return fmt.Errorf("legacyconnection client - accept connection request: %w", err) 268 } 269 270 return nil 271 } 272 273 // CreateImplicitInvitation enables invitee to create and send connection request using inviter public DID. 274 func (c *Client) CreateImplicitInvitation(inviterLabel, inviterDID string, args ...Opt) (string, error) { 275 return c.legacyconnectionSvc.CreateImplicitInvitation(inviterLabel, inviterDID, 276 "", "", applyOptions(args...).routerConnections) 277 } 278 279 // CreateImplicitInvitationWithDID enables invitee to create implicit invitation using inviter and invitee public DID. 280 func (c *Client) CreateImplicitInvitationWithDID(inviter, invitee *DIDInfo) (string, error) { 281 if inviter == nil || invitee == nil { 282 return "", errors.New("missing inviter and/or invitee public DID(s)") 283 } 284 285 return c.legacyconnectionSvc.CreateImplicitInvitation(inviter.Label, inviter.DID, invitee.Label, invitee.DID, nil) 286 } 287 288 // QueryConnections queries connections matching given criteria(parameters). 289 func (c *Client) QueryConnections(request *QueryConnectionsParams) ([]*Connection, error) { //nolint: gocyclo 290 // TODO https://github.com/hyperledger/aries-framework-go/issues/655 - query all connections from all criteria and 291 // also results needs to be paged. 292 records, err := c.connectionStore.QueryConnectionRecords() 293 if err != nil { 294 return nil, fmt.Errorf("failed query connections: %w", err) 295 } 296 297 var result []*Connection 298 299 for _, record := range records { 300 if request.State != "" && request.State != record.State { 301 continue 302 } 303 304 if request.InvitationID != "" && request.InvitationID != record.InvitationID { 305 continue 306 } 307 308 if request.ParentThreadID != "" && request.ParentThreadID != record.ParentThreadID { 309 continue 310 } 311 312 if request.MyDID != "" && request.MyDID != record.MyDID { 313 continue 314 } 315 316 if request.TheirDID != "" && request.TheirDID != record.TheirDID { 317 continue 318 } 319 320 result = append(result, &Connection{Record: record}) 321 } 322 323 return result, nil 324 } 325 326 // GetConnection fetches single connection record for given id. 327 func (c *Client) GetConnection(connectionID string) (*Connection, error) { 328 conn, err := c.connectionStore.GetConnectionRecord(connectionID) 329 if err != nil { 330 if errors.Is(err, storage.ErrDataNotFound) { 331 return nil, ErrConnectionNotFound 332 } 333 334 return nil, fmt.Errorf("cannot fetch state from store: connectionid=%s err=%w", connectionID, err) 335 } 336 337 return &Connection{ 338 conn, 339 }, nil 340 } 341 342 // GetConnectionAtState fetches connection record for connection id at particular state. 343 func (c *Client) GetConnectionAtState(connectionID, stateID string) (*Connection, error) { 344 conn, err := c.connectionStore.GetConnectionRecordAtState(connectionID, stateID) 345 if err != nil { 346 if errors.Is(err, storage.ErrDataNotFound) { 347 return nil, ErrConnectionNotFound 348 } 349 350 return nil, fmt.Errorf("cannot fetch state from store: connectionid=%s err=%w", connectionID, err) 351 } 352 353 return &Connection{ 354 conn, 355 }, nil 356 } 357 358 // CreateConnection creates a new connection between myDID and theirDID and returns the connectionID. 359 func (c *Client) CreateConnection(myDID string, theirDID *did.Doc, options ...ConnectionOption) (string, error) { 360 conn := &Connection{&connection.Record{ 361 ConnectionID: uuid.New().String(), 362 State: connection.StateNameCompleted, 363 TheirDID: theirDID.ID, 364 MyDID: myDID, 365 Namespace: connection.MyNSPrefix, 366 }} 367 368 for i := range options { 369 options[i](conn) 370 } 371 372 destination, err := service.CreateDestination(theirDID) 373 if err != nil { 374 return "", fmt.Errorf("createConnection: failed to create destination: %w", err) 375 } 376 377 conn.ServiceEndPoint = destination.ServiceEndpoint 378 conn.RecipientKeys = destination.RecipientKeys 379 conn.RoutingKeys = destination.RoutingKeys 380 conn.MediaTypeProfiles = destination.MediaTypeProfiles 381 382 err = c.legacyconnectionSvc.CreateConnection(conn.Record, theirDID) 383 if err != nil { 384 return "", fmt.Errorf("createConnection: err: %w", err) 385 } 386 387 return conn.ConnectionID, nil 388 } 389 390 // RemoveConnection removes connection record for given id. 391 func (c *Client) RemoveConnection(connectionID string) error { 392 err := c.connectionStore.RemoveConnection(connectionID) 393 if err != nil { 394 return fmt.Errorf("cannot remove connection from the store: err=%w", err) 395 } 396 397 return nil 398 } 399 400 // ConnectionOption allows you to customize details of the connection record. 401 type ConnectionOption func(*Connection) 402 403 // WithTheirLabel sets TheirLabel on the connection record. 404 func WithTheirLabel(l string) ConnectionOption { 405 return func(c *Connection) { 406 c.TheirLabel = l 407 } 408 } 409 410 // WithThreadID sets ThreadID on the connection record. 411 func WithThreadID(thid string) ConnectionOption { 412 return func(c *Connection) { 413 c.ThreadID = thid 414 } 415 } 416 417 // WithParentThreadID sets ParentThreadID on the connection record. 418 func WithParentThreadID(pthid string) ConnectionOption { 419 return func(c *Connection) { 420 c.ParentThreadID = pthid 421 } 422 } 423 424 // WithInvitationID sets InvitationID on the connection record. 425 func WithInvitationID(id string) ConnectionOption { 426 return func(c *Connection) { 427 c.InvitationID = id 428 } 429 } 430 431 // WithInvitationDID sets InvitationDID on the connection record. 432 func WithInvitationDID(didID string) ConnectionOption { 433 return func(c *Connection) { 434 c.InvitationDID = didID 435 } 436 } 437 438 // WithImplicit sets Implicit on the connection record. 439 func WithImplicit(i bool) ConnectionOption { 440 return func(c *Connection) { 441 c.Implicit = i 442 } 443 }