Skip to content

Latest commit

 

History

History
609 lines (461 loc) · 27.2 KB

5.Connections.md

File metadata and controls

609 lines (461 loc) · 27.2 KB

5. Connections

Connection is a communication channel between two parties. During the connection establishing process, both parts exchange their public keys. This exchange results in both parties recording the other's public key, thereby forming a unique pairwise encryption channel. That keys will be used for messages encryption during later communications between parties. Mobile SDK can form an unlimited number of connections in this way.

There are two roles in the connection establishing process: Inviter and Invitee.

  • The Inviter is the party that initiates the connection protocol with an Invitation message and transfer it in some usual way (SMS, QR, etc.). Verity SDK can be used as an Inviter.
  • The Invitee is the party that accepts the invitation to establish the connection. Mobile SDK represents the Invitee party. Bellow in this document we will explain which steps need to be taken in order to accept a connection invitation on the Invitee side using Mobile SDK.

NOTE: library should be initialized before using credentials API. See initialization documentation

To complete this section read through the following sections:

  1. Establishing connections
  2. Reusing Existing Connections
  3. Deleting Connections

Establishing connections

Aries Connection and Out-of-Band protocols consist of several messages exchange:

  • Inviter prepares Connection Invitation message and shares it with an Invitee some ual way (SMS, QR, etc.)
  • Invitee sends Connection Request message to Inviter
  • Inviter handles Connection Request message and sends Connection Response message to Invitee
  • Invitee handles Connection Response message and sends Connection Ack message (if requested) to Inviter

Steps overview

In order to accept a Connection Invitation an Invitee (client) need to take the following steps:

  1. Check type of invitation: aries connection and aries out-of-band formats are supported.
  2. Ensure that connection has not been established yet.
  3. If there is no existing connection:
    3.1. Depending on the invitation type call corresponding method to create SDK state object. 3.2. Await Connection is completed.
  4. If there is an existing connection:
    4.1. Reuse existing connection.

NOTE: The library should be initialized before forming a connection. See initialization documentation

0. Connection Application object

As we mentioned before in the Storage document SDK does not save state objects, so the application must care about it. It is up to the developer regarding what data to store in the application.
On of the possible formats may match the following structure:

{
    "pwDid" - string, // connection pairwise DID - uniquely identifies pairwise connection
    "serialized" - string, // serialized representation of SDK Connection state object

    // metadata to show on the UI
    "name" - string, // inviter name
    "logo" - string, // (optional) inviter logo (optional - maybe missed)
    "goal" - string, //  A text defining the goal of the connection establishing (optional - maybe missed)
    
    // optionally
    "timestamp" - int // time of establishing of the connection (it can be shown on the UI and used for sorting) 
    "status" - string, // connection status (pending / completed)
    "invitation" - string // original invitation was used to establish the connection, 
                             It will simply check for already accepted invitations 
}

Later in this document, we will show how to get each of these fields.

1. Identify the type of received invitation

Aries Connection invitation

The value of @type field must end with connections/1.0/invitation.

To send this type of invitation from the Verity application, you need to use this API call with this body payload.

Connection Invitation Example

{
    // type of the message
    "@type": "did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/connections/1.0/invitation",
    // id of the message
    "@id": "467f6449-7d1f-4a9f-ada7-09d6444af083",
    // name of the Inviter
    "label": "Acme",
    // URL with the Inviter logo (Optional)
    "profileUrl": "https://s3.us-east-2.amazonaws.com/public-demo-artifacts/demo-icons/cbACME.png",
    // Endpoint of the Inviter Agnet
    "serviceEndpoint": "http://vas.evernym.com:80/agency/msg",
    // Public key of the Inviter to encrypt messages  
    "recipientKeys": [
        "9NR9NYh5z5HHu6nLHnQWXczUqRwrdoL4KBUWvo2fE6vj"
    ],
    // Keys defining the Inviter Agent (Optional)
    "routingKeys": [
        "9NR9NYh5z5HHu6nLHnQWXczUqRwrdoL4KBUWvo2fE6vj",
        "3mo3P6XzDzBvuktCgDQarACzzeV7zxrSExnicpuH7t83"
    ]
}

More information about Aries Connection invitation and related protocol you can find here.

Aries Out-of-band invitation

The value of @type field must end with /out-of-band/1.0/invitation.

To send this type of invitation from the Verity application without a handshake protocol, you need to use this API call with this body payload.

This type of invitation can also be sent with a handshake protocol from the Verity application by using the out-of-band with a request attached. This is done by adding "by_invitation": tru to a body payload of the Present Proof API call or Issue Credential API call.

Out-of-band Invitation Example:

{
    // type of the message
    "@type": "https://didcomm.org/out-of-band/1.0/invitation",
    // id of the message
    "@id": "9NR9NYh5z5HHu6nLHnQWXczUqRwrdoL4KBUWvo2fE6vj",
    // name of the Inviter
    "label": "Faber College",
    // invitation goal code defined by the Inviter (Optional)
    "goal_code": "issue-vc",
    // a text defining the goal of the connection establishing (Optional)
    "goal": "To issue a Faber College Graduate credential",
    // Protocols can be used to establish a connection
    "handshake_protocols": [
        "https://didcomm.org/didexchange/1.0",
        "https://didcomm.org/connections/1.0"
    ],
    // An additional message attached to the invitation (Optional)
    "request~attach": [
      {
          "@id": "request-0",
          "mime-type": "application/json",
          "data": {
              "json": "<json of protocol message>"
          }
      }
    ],
    // Service of the Inviter Agent
    "service": [
        {
            "id": "#inline",
            "type": "did-communication",
            // Endpoint of the Inviter Agnet
            "serviceEndpoint": "http://vas.evernym.com:80/agency/msg",
            // Public key of the Inviter to encrypt messages  
            "recipientKeys": [
                "9NR9NYh5z5HHu6nLHnQWXczUqRwrdoL4KBUWvo2fE6vj"
            ],
            // Keys defining the Inviter Agent (Optional)
            "routingKeys": [
                "9NR9NYh5z5HHu6nLHnQWXczUqRwrdoL4KBUWvo2fE6vj",
                "3mo3P6XzDzBvuktCgDQarACzzeV7zxrSExnicpuH7t83"
            ]
        },
    ]
}

More information about Aries Out-Of-Band invitation and related protocol you can find here.

The following data can be taken from the Invitation to fill the application Connection object we defined before:

  • invite["label"] - Name of the inviter (name field of application Connection object)
  • invite["profileUrl"] - URL with inviter logo (optional - maybe missed in the invitation) (logo field of application Connection object)
  • invite["goal"] - A text defining the goal of the connection establishing (optional - maybe missed in the invitation) (goal field of application Connection object)

URL invitation format

Both invitation types can also be represented in form of URL:

  • Aries Connection invitation - https://<domain>/<path>?c_i=[Base-64 encoded invitation]
  • Aries Out-Of-Band invitation - https://<domain>/<path>?oob=[Base-64 encoded invitation]
  • Invitation without Base-64 encoded - https://<domain>/<path>

In this case, you need to decode Base-64 string first.

iOS

[sdkApi resolveMessageByUrl:invite
                 completion:^(NSError *error, NSString *parsedInvite) {
     // ...
 }];

Android

int parsedInvite = UtilsApi.vcxResolveMessageByUrl(invite).get();

The result value should match the formats we described above.

URL invitation format example:

http://vas.evernym.com:80/agency/msg?c_i=eyJsYWJlbCI6IkFjbWUiLCJzZXJ2aWNlRW5kcG9pbnQiOiJodHRwOi8vdmFzLmV2ZXJueW0uY29tOjgwL2FnZW5jeS9tc2ciLCJyZWNpcGllbnRLZXlzIjpbIjNhVkhWZEFxZnBKSmVROG1mdm5UV0Y0MVpoYUxidVVLQXJ3UlVCRldlWjNRIl0sInJvdXRpbmdLZXlzIjpbIjNhVkhWZEFxZnBKSmVROG1mdm5UV0Y0MVpoYUxidVVLQXJ3UlVCRldlWjNRIiwiM21vM1A2WHpEekJ2dWt0Q2dEUWFyQUN6emVWN3p4clNFeG5pY3B1SDd0ODMiXSwicHJvZmlsZVVybCI6Imh0dHBzOi8vczMudXMtZWFzdC0yLmFtYXpvbmF3cy5jb20vcHVibGljLWRlbW8tYXJ0aWZhY3RzL2RlbW8taWNvbnMvY2JBQ01FLnBuZyIsIkB0eXBlIjoiZGlkOnNvdjpCekNic05ZaE1yakhpcVpEVFVBU0hnO3NwZWMvY29ubmVjdGlvbnMvMS4wL2ludml0YXRpb24iLCJAaWQiOiI4YWE1ZTZjOS1mZjZkLTQ0NDUtOWU1Ni1iNDU1MjQxZTVlZGIifQ==

http://vas.evernym.com:80/agency/msg?oob=eyJAdHlwZSI6Imh0dHBzOi8vZGlkY29tbS5vcmcvb3V0LW9mLWJhbmQvMS4wL2ludml0YXRpb24iLCJAaWQiOiI2OTIxMmEzYS1kMDY4LTRmOWQtYTJkZC00NzQxYmNhODlhZjMiLCJsYWJlbCI6IkZhYmVyIENvbGxlZ2UiLCAiZ29hbF9jb2RlIjoiaXNzdWUtdmMiLCJnb2FsIjoiVG8gaXNzdWUgYSBGYWJlciBDb2xsZWdlIEdyYWR1YXRlIGNyZWRlbnRpYWwiLCJoYW5kc2hha2VfcHJvdG9jb2xzIjpbImh0dHBzOi8vZGlkY29tbS5vcmcvZGlkZXhjaGFuZ2UvMS4wIiwiaHR0cHM6Ly9kaWRjb21tLm9yZy9jb25uZWN0aW9ucy8xLjAiXSwic2VydmljZSI6WyJkaWQ6c292OkxqZ3BTVDJyanNveFllZ1FEUm03RUwiXX0

https://vty.im/cnf4m

2. Create Connection state object using received Invitation.

Successful execution will return a connection handle. This handle points to created Connection state object and should be used for later operations (sending messages through the connection) related to this connection.

iOS

  • For aries connection invitation:

    [appDelegate.sdkApi connectionCreateWithInvite: invitationId
            inviteDetails: inviteDetails
            completion:^(NSError *error, NSInteger connectionHandle) {
                // ...
            }];
  • For aries out-of-band invitation:

    [appDelegate.sdkApi connectionCreateWithOutofbandInvite: invitationId
            invite: inviteDetails
            completion:^(NSError *error, NSInteger connectionHandle) {
                // ...
            }];

Android

  • For aries connection invitation:

    int connectionHandle = ConnectionApi.vcxCreateConnectionWithInvite(invitationId, invitationDetails).get();
  • For aries out-of-band invitation:

    int connectionHandle = ConnectionApi.vcxCreateConnectionWithOutofbandInvite(invitationId, invitationDetails).get();

3. Check whether connection has been already established.

The previous step only creates a Connection state object but not actually accept the invitation. Before accepting a connection invitation you need to check that it has not been used yet. In order to check does connection already exists, we need to retrieve invitations that were used during the creation of existing connections and compare them with the received invitation.

  1. There are two ways of getting original invitation for existing connections:

    • Using SDK:
      1.1 Deserialize connection state object

       #### iOS
        ```objC
        [appDelegate.sdkApi connectionDeserialize:serializedConnection
                completion:^(NSError *error, NSInteger connectionHandle) {
                    // ...
                }];
        ```
      
       #### Android
        ```java
        int connectionHandle = ConnectionApi.connectionDeserialize(serializedConnection).get();
        ```
      

      1.2 Get invitation

       #### iOS
        ```objC
        [appDelegate.sdkApi getConnectionInviteDetails:connectionHandle
                                           abbreviated:FALSE
                                            completion:^(NSError *error, NSInteger connectionInvite) {
                                               // ...
                                           }];
        ```
      
       #### Android
        ```java
        String connectionInvite = ConnectionApi.connectionInviteDetails(handle, abbreviated).get();
        ```
      
    • Using an original invitation stored in the application connection object.

  2. Compare invitations - invitations match when one of the following fields is the same for both of them:

    • @id field (means exactly the same invitation)
    • public_did field (invitation from same Inviter)
    • recipient_keys[0] field (invitation from same Inviter)
  3. If you find that connection already exists for the received invitation you would better reuse the existing connection rather than establish a new one. You can read how to reuse existing connection in the section.

4. Accept connection invitation.

If there is no existing connection, and you want to accept the invitation to establish a connection you need to create Pairwise Cloud Agent and send Connection Request message.

  • This Agent will be used for one particular connection, so a new Pairwise Cloud Agent will be created for every connection you establish.

  • Connection Request message contains:

    • Public key of the Invitee which Inviter will be used to encrypt all following messages.
    • Cloud Agent public endpoint.
    • Pairwise Cloud Agent public keys which will be used to encrypt sending messages.
  • All communication between Inviter <-> Invitee and Invite/Inviter <-> Pairwise Cloud Agent happens in encrypted way.

  • The Invitee private keys to decrypt messages from Inviter or Cloud Agent are stored into Mobile SDK Wallet in encrypted form and cannot be read.

  • Pairwise Cloud Agent receives messages in encrypted form, so only the Invitee can read the message content.

  1. Send Connection Request message

    iOS

    [appDelegate.sdkApi connectionConnect: connectionHandle
                           connectionType: connType
            completion:^(NSError *error, NSInteger inviteDetails) {
                // ...
            }];

    Android

    ConnectionApi.vcxConnectionConnect(connectionHandle, connType).get();
  2. Fetch Connection pwDid. This field will be unique among all established connections. This field will be also needed during the handling of messages received from the Cloud Agent (in order to associate messages with serialized connections).

    iOS

    [appDelegate.sdkApi connectionGetPwDid:connectionHandle
                                completion:^(NSError *error, NSString *pwDid) {
        // ...
    }];

    Android

    String pwDid = ConnectionApi.connectionGetPwDid(connectionHandle).get();

5. Serialize Connection object and store in the application storage.

Theoretical, processing of the Connection Request message on the Inviter side may take a while and User can just close the application before completion.
It's better to store the connection state machine (received after sending Connection Request) into the applicaiton storage to avoid data loss.

If you wish, you can skip storing for now and do it only after the connection will be completed (after the step 6).

  1. Serialize Connection object

    iOS

    [appDelegate.sdkApi connectionSerialize:connectionHandle
            completion:^(NSError *error, NSString *serializedConnection)) {
                // ...
            }];

    Android

        String serializedConnection = ConnectionApi.connectionSerialize(connectionHandle).get();
  2. Save serialized sdk connection state object and pwDid into your application connection object.

  3. The status of the connection is considered as pending at this step.

  4. Every time in the future you want to perform some operations using the created connection you firstly need to fetch Connection object from the storage and deserialize SDK object from its serialized representation (receive a new handle).

6. Await Connection is completed.

The connection will be completed when Connection Response message is received back from the Inviter. This message contains public keys which will be used by the Inviter for messages exchange. Inviter may change original keys put into Invitation as it was transferred using some public way.

  1. Fetch Connection record from the storage and deserialize pending Connection state object

    iOS

    [appDelegate.sdkApi connectionDeserialize:serializedConnection
            completion:^(NSError *error, NSInteger connectionHandle) {
                // ...
            }];

    Android

    int existingConnectionHandle = ConnectionApi.connectionDeserialize(serializedConnection).get();
  2. Await connection completion. There are two ways regarding how to await Connection Response message depending on the messages receiving strategy you use:

    1. Polling - call connectionUpdateState function in loop until returned state is not equal 4 (Accepted).

      iOS

      while(1) {
          [appDelegate.sdkApi connectionUpdateState:connectionHandle
              completion:^(NSError *error, NSInteger state) {
                      if (state == 4){
                          break;
                      }
          }
      }

      Android

      int state = -1;
      while(state != 4){
          state = ConnectionApi.vcxConnectionUpdateState(connectionHandle).get();
      }

      These functions do the following things internally:

      • Downloads all pending messages from the Cloud Agent related to the connection
      • Find matching Connection Response message
      • If message found -> updates state machine with it and update message status on the Cloud Agent as read.
    2. Push notifications - as push notification doesn't contain the type of received message we can download all messages and analyze them:

      • Use common downloadPendingMessages function to download messages.

      • Find matching Connection Response message. The following conditions must met:

        • value of ["decryptedPayload"]["@type"] field must ends with response
        • value of pairwiseDID field must match to the pwDid received after sending Connection Request
      • Update Connection state object with received Connection Response message:

        iOS

            [appDelegate.sdkApi connectionUpdateStateWithMessage:connectionHandle
                                                         message:message
                                                      completion:^(NSError *error, NSInteger state) {
                // ....
            }

        Android

        int state = ConnectionApi.vcxConnectionUpdateStateWithMessage(connectionHandle, message).get();
      • Update status of the message as read. See messages documentation for message update information.

        iOS

        [appDelegate.sdkApi updateMessages:messageStatus
                pwdidsJson:handledMessage
                completion:^(NSError *error) {
                    // ...
                }];

        Android

        UtilsApi.vcxUpdateMessages(messageStatus, handledMessage).get()  

7. Serialize Connection object and store in the application storage.

Once we get the connection completed (it means that internal representation of SDK connection state object changed) we need to update the corresponding record in the application storage (or add a new record if you skipped step 5) to contain the latest serialized connection state.

  1. Serialize Connection object

    iOS

    [appDelegate.sdkApi connectionSerialize:connectionHandle
            completion:^(NSError *error, NSString *serializedConnection)) {
                // ...
            }];

    Android

        String serializedConnection = ConnectionApi.connectionSerialize(connectionHandle).get();
  2. Update serialized value from the corresponding record stored in the application storage.

  3. The status of the connection is considered as established at this step.

  4. Every time in the future you want to perform some operations using the created connection you firstly need to fetch Connection object from the storage and next deserialize SDK object from its serialized representation (receive a new handle).

Reusing Existing Connections

With adding more and more connections, its getting hard for user to remember which connections already established. There is potential that some of the new connections user tries to establish already exists in list of connections (e.g. QR code was scanned multiple types). In this case we should not create new connection and try to reuse existing one.

Aries Connection invitation

Aries Connection Invitations match when one of the following fields is the same for a new invitation and one of existing:

  • @id field (means exactly the same invitation)
  • public_did field (invitation from same Inviter)
  • recipient_keys[0] field (invitation from same Inviter)

A regular Aries connection protocol does not contain reusing or redirection steps. If you detected that connection already exists you can optionally show an information message to User and just use the found connection instead of creating a new one.

Aries Out-Of-Band Connection invitation

In contrast to Aries Connection, Aries Out-of-Band protocol describes the protocol allowing as establishing new connections and as reusing of existing. According to Out-Of-Band protocol when we detected a duplicate connection, we need to awake an existing connection and use it instead of establishing a new one.

Aries Out-Of-Band Invitations match when one of the following fields is the same for a new invitation and one of existing:

  • public_did field (invitation from same Inviter)
  • recipient_keys[0] field (invitation from same Inviter)

Note: If @id field matched it means that you have exactly the same invitations, but in this case, you more likely not need to follow reusing steps and instead simply show the information message to the User.

The steps need to be taken by the app also depends on the format of received Out-of-Band invitation. If you want to get guidelines on how to handle all possible Out-of-Band invitation cases go to the document

For this section we assume accomplishing the following facts:

  1. Connection matching to the provided invitation is already exists (either public_did or recipient_keys[0] are the same).
  2. Out-of-Band invitation contains handshake_protocols
  3. Out-of-Band invitation does not request~attach or it's an empty array.
Steps
  1. Deserialize existing Connection state object which matched the invitation

    iOS

    [appDelegate.sdkApi connectionDeserialize:serializedConnection
            completion:^(NSError *error, NSInteger connectionHandle) {
                // ...
            }];

    Android

    int existingConnectionHandle = ConnectionApi.connectionDeserialize(serializedConnection).get();
  2. Send Connection Reuse message. By sending this message you ensure that the connection is still alive and can be used.

    iOS

    [appDelegate.sdkApi connectionSendReuse:existingConnectionHandle
            invite:newInvite
            withCompletion:^(NSError *error) {
                // ...
            }];

    Android

     ConnectionApi.connectionSendReuse(existingConnectionHandle, newInvite).get();
  3. Await for handshake-reuse-accepted message received back from the remote side.

    • Use common downloadPendingMessages function to download messages.

    • Find matching Handshake Rese Accepted message. The following conditions must met:

      • value of ["decryptedPayload"]["@type"] field must ends with handshake-reuse-accepted
    • value of ["decryptedPayload"]["~thread"]["pthid"] field must match to the value of @id field from Connection Invitation.

    • After receiving of handshake-reuse-accepted message, the connection is considered as reused and can be used instead of creating a new one.

    • Update status of the message as read. See messages documentation for message update information.

Deleting Connections

It may happen that after some time a User wants to delete connections that are no longer need to him. In order to delete a connection you need to perform the next steps:

  1. Delete a Pairwise Cloud Agent associated with the Connection, so that it cannot be used anymore for sending or receiving messages.

    1.1. Deserialize existing Connection state object

    #### iOS
     ```objC
     [appDelegate.sdkApi connectionDeserialize:serializedConnection
             completion:^(NSError *error, NSInteger connectionHandle) {
                 // ...
             }];
     ```
    
    #### Android
     ```java
     int connectionHandle = ConnectionApi.connectionDeserialize(serializedConnection).get();
     ```
    

    1.2. Delete Pairwise Cloud Agent associated with the Connection

    #### iOS
     ```objC
     [appDelegate.sdkApi deleteConnection:connectionHandle
             withCompletion:^(NSError *error) {
                 // ...
             }];
     ```
    
    #### Android
     ```java
      ConnectionApi.deleteConnection(connectionHandle).get();
     ```
    
  2. Delete connection data from the application storage.

Expected errors with establishing connection

Connection already exists

It is expected use cases when user on mobile device tries to establish connection which already exists (user already connected before tries again). In that case, we don't need to reset connection (delete existing connection and create new connection with same pair). You can reuse existing connection.

You can find more details about this use case here.

Next Step

Now your application is able to established connections with other parties. You are ready to read how to receive verifiable credentials.