# Connect Protocol ## Requirements ### Technical The technical requirements for the **Connect Protocol** include: - Enable a secure channel to be established between a decentralized web app (DWA) and a Web5 Identity Agent over an untrusted network. - Use DIDs and associated key material for all cryptographic operations (i.e., sign/verify and encrypt/decrypt). - Reduce the value / consequences of a compromised app DID private key by limiting the authorized permissions scope and making it easy to revoke and reinitialize a DWA instance. - Prevent man-in-the-middle (MiTM) attacks and other forms of spoofing. - Transport agnostic design that can establish secure channels over common protocols, including `web5://` (deep link), HTTP/HTTPS, and WS/WSS. _Nice to have_ features that aren't required for the MVP: - Support DWAs connecting to headless Web5 agents by authorizing the connect attempt from another authorized device. ### User Experience User experience goals: - Leverage [Jakob's Law](https://lawsofux.com/jakobs-law/) and the design principle of familiarity. - Users will transfer expectations they have built around one familiar product to another that appears similar. - By leveraging existing mental models, we can create superior user experiences in which the users can focus on their tasks rather than on learning new models. - Specifically, design the UX/UI to mimic similar authorization/device linking processes: - authorizing a TV to access media content using your Netflix account. - authorizing a new computing device to access your Apple ID. - authorizing a browser session using your Google Account. ## Connection Sequence ### Summary The basic idea is to bootstrap a secure channel using a `did:key` identifier generated for the DWA and a challenge nonce generated by a Web5 Identity Agent. Once established, the secure channel is used to transmit authentic and confidential data between the app and agent. - DWA initiates a connection using `web5.connect()`. - DWA posts a permissions request to a Connect Relay server. - Using either QR Code or deep link, DWA transmits its unique DID, a random nonce, and the Connect Relay URL its using to the IDA. - IDA fetches the DWA's permissions request from the Connect Relay using the provided URL. - End user selects the Identities (e.g., Social, Career, Family, etc.) for the DWA to use and authorizes the permissions request. - IDA posts the selected Identity's DIDs and permissions grants encrypted using the DWA's public key and PIN as ADD (additional authenticated data) to the Connect Relay. - DWA fetches the DIDs and permissions grants from the Connect Relay and decrypts the values using its private key + the PIN the user enters from the IDA. #### Actors | Icon | Name | Goal | | ---- | ---- | ---- | | ๐Ÿ‘ฉโ€๐Ÿ’ป | Alice | The authorized user, attempting to connect a DWeb App. | | ๐Ÿฆนโ€โ™€๏ธ | Eve | An attacker attempting to disrupt or compromise the connection. | #### Devices | Icon | Name | Role | | ---- | ---- | ---- | | ๐Ÿ“ฑ | Alice's Mobile | <p></p><ul><li>Alice's first device</li><li>Has an Identity Agent</li><li>Manages Alice's DIDs: `did:ion:EiSocial`, `did:ion:EiCareer`, `did:ion:EiFamily`</li></ul> | | ๐Ÿ–ฅ | Alice's Desktop | <p></p><ul><li>Alice's second device</li><li>Has an Identity Agent</li><li>Does not participate in the example flow</li><li>Included as a reminder that users may have multiple IDAs</li></ul> | | ๐Ÿ’ป | Alice's Laptop | <p></p><ul><li>Alice's third device</li><li>[dinger.xyz]() in Chrome is requesting to `connect()`</li><li>Has a key pair and `did:key:zLCDinger`</ul> | | ๐Ÿ˜ˆ | Evil Server | <p></p><ul><li>Eve's compromised Connect Relay server that observes all traffic</li></ul> | ### Pre-Connect The [dinger.xyz]() DWA developer pre-defines a Connect Relay server URL that will be used to facilitate all Connect Requests. ```typescript const connectRelayUrl = 'https://dwn.trustme.org/connect'; ``` For this example, we'll assume that ๐Ÿฆนโ€ Eve has compromised the ๐Ÿ˜ˆ Connect Relay server and is able to observe all traffic to demonstrate that the Connect Protocol works in adversarial environments. When [dinger.xyz]() loads for the first time in Alice's laptop's Chrome web browser, the DWA will generate an Ed25519 signing key pair and associated DID (`did:key:zLCDinger`), which are stored locally in browser storage. The DWA also deterministically generates a unique **`Connect ID`** and **`Connect Key`** derived from the `did:key:zLCDinger` public key. > **:question: Why Ed25519?:** > The `did:key` method we're using for App DIDs is a simple scheme in which the identifier is an encoded version of a public key. This enables any party that receives a `did:key` identifier to resolve its public key (locally, without any network lookup), but it also means that only a single key can be referenced. A best practice is to use different keys for signing and key agreement, so ideally we'd have two keys but the `did:key` identifier only encodes one. > > To solve this conundrum, we can leverage two different cryptographic protocols, Ed25519 and X25519, that use the same underlying Curve25519 elliptic curve for different purposes (digital signatures and key exchange, respectively), and the knowledge that an X25519 key pair can be deterministically computed from a given Ed25519 key pair. As a result, we can generate a single Ed25519 key pair and share its `did:key` identifier with another party, who can then decode the Ed25519 public key and compute the X25519 public key. In this way, a single `did:key` identifier can be resolved to two keys: one used for key exchange as part of the Connect Protocol and the other used for signing DWN messages. ### Step 1: DWA initiates Connect The DWA constructs an array of permission request objects based on the scope and conditions needed, either statically pre-defined or dynamically generated during a connection request. ```typescript const permissionsRequests = [ { "scope": { "protocol": "https://protocol.org/DirectMessage" }, "conditions": { "delegation": false, "publication": false, "sharedAccess": true, "encryption": "required", "attestation": "prohibited" } } ] ``` A Connect Request is created, which includes the origin name (useful when presenting and tracking the request in the IDA) and permissions request. The connect request is encrypted using the DWA's **`Connect Key`** and a randomly generated **`Nonce`**. This ensures that not even a compromised ๐Ÿ˜ˆ Connect Relay will know the nature of Alice's Connect Request. ```typescript const connectRequest = JSON.stringify({ did: 'did:example:app', origin: 'dinger.xyz', permissionRequests: [ PermissionsRequest1, PermissionsRequest2, ... ] }); ``` ### Step 2: DWA posts the connect request to the Connect Relay The DWA constructs a JSON RPC request: ```typescript { method: connect.createRequest, params: { message : connectRequest, // Encrypted ciphertext uuid : connectId // Connect ID } } ``` and posts it to the Connect Relay: ```typescript const response = await fetch(connectUrl, fetchOptions); ``` At this point, the DWA begins polling the Connect Relay by periodically submitting HTTP POST requests with a JSON RPC request of method `connect.getGrant`. ### Step 3: DWA transmits Connect details to IDA At a minimum, the DWA needs to transmit the following data to the IDA: ```typescript { temporaryDid : temporaryDid, // Ephemeral DID generated for each Connect attempt nonce : nonce, // Used to encrypt the request posted to the Connect Relay url : connectUrl // https://dwn.trustme.org/connect } ``` A Web5 Connect link is constructed from this data: ```typescript const baseUrl = 'web5://connect'; const urlObject = new URL(base); const temporaryDid = 'did:key:abc123'; const nonce = new Uint8Array([/* random uint8 values */]); const encodedNonce = Convert.uint8Array(nonce).toBase64Url(); const connectRelayUrl = 'https://dwn.tbddev.org/dwn0/connect'; const params = new URLSearchParams(); params.append('temporaryDid', temporaryDid); params.append('nonce', encodedNonce); params.append('url', connectRelayUrl); urlObject.search = params.toString(); const connectLink = urlObj.toString(); console.log(connectLink); // web5://connect?temporaryDid=did:key:abc123&nonce=VGhlIHF1aWNrJy&url=https://dwn.tbddev.org/dwn0/connect ``` The link is presented to the end user as either a QR Code to scan or deep link to tap/activate, depending on the types of devices involved (i.e., mobile:desktop, desktop:mobile, mobile:mobile, desktop:desktop). ### Step 4: IDA fetches the DWAโ€™s request from the Connect Relay After decoding and parsing the connect data, the IDA will derive the **`Connect ID`** and **`Connect Key`** from the `did:key:abc123` public key. It will then construct a JSON RPC request using the **`Connect ID`**: ```typescript { method: connect.getRequest, params: { uuid: connectId } } ``` and post it to the Connect Relay to fetch the DWA's Connect Request: ```typescript const response = await fetch(connectUrl, fetchOptions); ``` Once received, the DWA's Connect Request is decrypted using the derived **`Connect Key`** and processed. > **:lock: Note:** > Implementers should ensure that the IDA will only interact with ONE connect request for a given DWA at a time. In effect, this secure channel is locked to the (appDid, origin, connectUrl) combination until the Connect Protocol completes successfully, is rejected, or times out. New connect requests MUST use new, randomly generated DID and keys. The IDA SHOULD track DIDs that have already been seen, and reject new requests involving them. UI is raised to the end user to authorize the connection request, including the requested permissions, and to select the ION DID they wish to use with the DWA. > **:lock: Note:** > At this point, the IDA has verified that the request retrieved from the Connect Relay came from the connecting DWA because even a compromised ๐Ÿฆนโ€โ™€๏ธ๐Ÿ˜ˆ Connect Relay wouldn't have known the correct **`Connect Key`** to use to encrypt a spoofed Connect Request. ### Step 5: IDA posts the identity grant and challenge to the Connect Relay The IDA needs to transmit the following data securely to the DWA via the Connect Relay: ```typescript const identityGrants = { grants: [ { did: socialDid, // User selected did:ion:social permissions: [permissionsGrant] }, { did: careerDid, // User selected did:ion:career permissions: [permissionsGrant] } ] } ``` The IDA uses the ECDH-ES-XC20PKW cryptographic protocol to perform key exchange and key wrapping by: - deriving the DWA's X25519 key agreement public key - generating an X25519 ephemeral key pair - using the ephemeral key pair and DWA's X25519 public key to compute the shared secret - encrypting the `identityGrants` object using the shared key - constructing a JWE containing the identity grant data (selected ION DID, delegation JWS, and permissions grant. > **:lock: Note:** > This approach simultaneously provides confidentiality, integrity, and authenticity assurances on the data. Since the message is signed, we can prove that it came from an entity in control of Alice's `did:ion:EiSocial` private key. Because the entire payload is asymmetrically encrypted against the public key of the DWA, we know that no one else can tamper with or decrypt the message. The IDA then constructs a `connect.createGrant` JSON RPC request containing the JWE and posts it to the Connect Relay using the same **`Connect ID`**. ### Step 6: DWA fetches the identity grant and challenge from the Connect Relay Up until this point, the DWA has been periodically polling the Connect Relay with `connect.getGrant` messages. After the IDA posts the identity grant and challenge value, the Connect Relay will return this payload in response to the next poll from the DWA. Upon retrieving the identity grant, the DWA will first decode and parse the JWE and then decrypt its payload by: - deriving the DWA's X25519 key agreement private key - using the DWA's X25519 private key and the provided ephemeral public key to compute the shared secret - decrypting the `identityGrants` object using the shared key The DWA now has the user selected DID (`did:ion:EiSocial`), permissions grant, and challenge value sent by the IDA. > **:lock: Note:** > At this point, Alice can be reasonably confident that the DID, permissions grant, and challenge the DWA received came from Alice's IDA. The reason is that an ๐Ÿฆนโ€โ™€๏ธ attacker would have to be in possession of the DWA's public key in order to asymmetrically encrypt a spoofed identity grant, and the DWA's public key was only briefly revealed during the QR code / deep link interaction between the DWA and IDA. > > The one remaining attack vector to address is an eavesdropping scenario in which a malicious ๐Ÿฆนโ€โ™€๏ธ actor coordinating with the compromised ๐Ÿ˜ˆ Connect Relay is physically present to scan the QR code, and thereby, learn the DWA's public key. However unlikely this might be, to mitigate this attack, the final step is to authenticate the IDA. > > A careful reader might notice that we have not addressed the scenario in which the deep link triggered on a mobile or desktop computing device is intercepted by an attacker. If malware with elevated privileges to eavesdrop in this manner is present on the device, the game was already over. ### Step 7: End user validates the challenge PIN out of band Steps 1-6 establish a secure channel between the DWA and IDA but leaves open the potential for an eavesdropping attack. To validate that the DWA is interacting with the end user's IDA, we go out of band and have the human verify a code. The user enters the PIN from the IDA into the DWA's PIN entry UI. The DWA uses the PIN to decrypt the payload. ## Connect Relay ### Method Names - `connect.createRequest`: This method is used when the DWA posts the connect request to the Connect Relay. - `connect.getRequest`: This method is used when the IDA fetches the DWA's connect request from the Connect Relay. - `connect.createGrant`: This method is used when the IDA posts the DID the user selected, a permissions grant, and a challenge PIN to the Connect Relay. - `connect.getGrant`: This method is used when the DWA fetches the DID, permissions grant, and PIN from the Connect Relay and decrypts the values using its private key. ### Attack Mitigation To minimize server resource demand (CPU / memory) and mitigate denial of service (DoS) attacks, several countermeasures are employed by the Connect Relay. This is only a preliminary list which attempts to balance simplicity with effectiveness. The design is expected to evolve based on experience gained from deployment and usage in the wild. - The Connect Relay will only accept a single `connect.createRequest` message for each `connectId`. Since this value is deterministically derived from the requesting DWA's public key, a connecting app should have no legitimate reason to have more than one request active at any given time. Subsequent `connect.createRequest` messages with the same `connectId` will be logged and silently discarded. - The Connect Relay will only accept a `connect.createGrant` message if it refers to a `connectId` that was recently initiated with a `connect.createRequest` message. - The Connect Relay will discard `createRequest` and `createGrant` messages after 5 minutes. This value may need to be modified based on real world tests of usability. Future countermeasures that will NOT be part of the MVP implementation: - A step can be added before a `connect.createRequest` will be accepted by the Connect Relay which requires the DWA device to complete some non-trivial amount of computation (proof of work) before obtaining a token. A valid token will then be required with all `connect.createRequest` messages. - Rate limiting can be imposed to limit the number of `connect.createRequest` or `connect.createGrant` messages that will be accepted from a single source IP address. ## Post MVP Action Items - After `Web5.connect()` is called, the DWA User Agent will check to see if one or more Identities is already present, and if so, will should skip Connect and instead prompt the user to select an identity to use with the DWA. - It should be possible, once subscriptions have been implemented, for a Desktop Identity App to display a QR code that a DWA on a mobile device can scan, have the DWA post a message to the Desktop Identity App's publicly addressable DWN endpoint, and trigger the initiation of Connect. We need to test and see how reliable it is to scan from within a DWA, but if it turns out to be widely available and robust, it could eliminate the need to manually transfer information between a mobile DWA and Desktop IDA.