diff --git a/CHANGELOG.md b/CHANGELOG.md index 77de51a..2015e3a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1 +1,13 @@ +7 August 2025 - `Feature` Enhanced End-User Security in TonConnect: + - Added `/verify` endpoint for connection verification with statuses: `ok`, `danger`, `warning`, `unknown` + - Introduced encrypted `request_source` metadata in bridge messages containing origin, IP, User-Agent, timestamp, and client ID + - Added BridgeRequestSource struct for request source tracking and verification + - Updated bridge message format to include optional `request_source` field + - Updated bridge message format to include `connect_source` field + - Enhanced wallet workflows with connection and transaction verification processes + - Added comprehensive wallet implementation guidelines for security features + - Implemented phased rollout approach for verification status responses + - Maintained full backward compatibility with existing dApps and bridges + - Added user interface guidelines for verification marks and security warnings + 7 March 2023 - `Feature` format of `DeviceInfo` of `ConnectEventSuccess` changed; Parameter `maxMessages` added to the Feature `SendTransaction`. \ No newline at end of file diff --git a/bridge.md b/bridge.md index fb0fccc..be8c8c8 100644 --- a/bridge.md +++ b/bridge.md @@ -42,7 +42,7 @@ Sending message from client A to client B. Bridge returns error if ttl is too hi ```tsx request - POST /message?client_id=?to=&ttl=300&topic= + POST /message?client_id=?to=&ttl=300[&topic=][&no_request_source=true] body: ``` @@ -50,24 +50,134 @@ request The `topic` [optional] query parameter can be used by the bridge to deliver the push notification to the wallet. If the parameter is given, it must correspond to the RPC method called inside the encrypted `message`. +The `no_request_source` [optional] query parameter can be used to disable request source metadata forwarding and encryption. When set to `true`, the bridge will not include the `request_source` field in the BridgeMessage. This parameter should be set for messages from `wallet` to `dapp`, since the `dapp` side doesn't need this information. + Bridge buffers messages up to TTL (in secs), but removes them as soon as the recipient receives the message. If the TTL exceeds the hard limit of the bridge server, it should respond with HTTP 400. Bridges should support at least 300 seconds TTL. -When the bridge receives a message `base64_encoded_message` from client `A` addressed to client `B`, it generates a message `BridgeMessage`: +## Bridge Security Verification + +For enhanced security, the bridge implements verification mechanisms to help wallets confirm the true source of connection and transaction requests. + +### Request Source Metadata + +When the bridge receives a message from client A to client B, it collects request source data: + +```go +type BridgeRequestSource struct { + Origin string `json:"origin"` // protocol + domain (e.g., "https://app.ton.org") + IP string `json:"ip"` // sender IP address + Time string `json:"time"` // unixtime + UserAgent string `json:"user_agent"` // HTTP User-Agent header +} +``` + +### Connect Source Metadata + +```go +type BridgeConnectSource struct { + IP string `json:"ip"` // receiver IP address +} +``` + +When bridge sends a message to the client, it adds the `connect_source` field to the message. This field contains the BridgeConnectSource struct with the IP address of the client that receives the message. The client should be able to verify this IP address against the IP address of the client that sent the message. We're adding this field to the message to minimize cases of IP mismatch due to different proxies used by the client and the bridge. + +### Message Processing with Verification + +When the bridge receives a message `base64_encoded_message` from client `A` addressed to client `B`, it: + +1. Collects request source metadata into `BridgeRequestSource` struct +2. Serializes the struct to JSON +3. Encrypts it using the recipient wallet's Curve25519 public key with: + ``` + naclbox.SealAnonymous(nil, data, receiverSessionPublicKey, rand.Reader) + ``` +4. Base64 encodes the encrypted bytes +5. Generates a message `BridgeMessage`: ```js { "from": , - "message": + "message": , + "request_source": , // Information about the sender + "connect_source": // Information about the receiver } ``` -and sends it to the client B via SSE connection -```js -resB.write(BridgeMessage) +Pseudocode for the bridge: +```go +resB.send(struct{ + From: req.from, + Message: req.message, + RequestSource: base64_encode(encrypt(struct{ + Origin: req.origin, + IP: req.ip, + Time: req.time, + UserAgent: req.user_agent, + })), + ConnectSource: struct{ + IP: resB.ip, + }, +}) +``` + +### Connect Verification Endpoint + +Bridge provides a verification endpoint for connection requests: + +```tsx +request + POST /verify + + body: { + "type": "connect", + "client_id": "", + "origin": "" + } +``` + +### IP Address Endpoint + +Bridge provides an endpoint for wallets to obtain their current IP address for validation purposes: + +```tsx +request + POST /myip +``` + +```tsx +response + { + "ip": "" + } +``` + +This endpoint returns the IP address from which the request originated, allowing wallets to compare their current IP with the IP address stored in request source metadata for security validation. + +**Response statuses:** + +- **Phase 1** (first 6 months): `ok`, `unknown` +- **Phase 2** (after 6 months): `ok`, `danger`, `warning` + +```tsx +response + { + "status": "ok" | "danger" | "warning" | "unknown" + } ``` +**Status meanings:** +- `ok`: Request verified and matches expected source +- `danger`: Strong indication of fraudulent activity +- `warning`: Suspicious activity or mismatched details +- `unknown`: Cannot verify (default for new or untracked origins) + +**Bridge Implementation:** +- On SSE connect store connection metadata for a short period(~5 minutes): origin, IP address, client ID, timestamp +- Compare verification requests against stored data +- Implement rate limiting and abuse detection + ### Heartbeat To keep the connection, bridge server should periodically send a "heartbeat" message to the SSE channel. Client should ignore such messages. diff --git a/wallet-guidelines.md b/wallet-guidelines.md index 32e6768..96bd9c9 100644 --- a/wallet-guidelines.md +++ b/wallet-guidelines.md @@ -61,3 +61,106 @@ Multiple network accounts can be created for one key pair. Implement this functi We recommend wallets provide the ability to disconnect session with a specified dapp because the dapp may have an incomplete UI. +## Security Verification + +### Connection Verification Implementation + +1) **Implement `/verify` endpoint integration** + + For HTTP Bridge connections, wallets MUST implement connection verification: + + - Extract origin and client_id from connection requests + - Send POST request to `${bridgeUrl}/verify` with connection details + - Process verification status and display appropriate UI to users + +2) **Verification Status Handling** + + Wallets MUST handle all verification statuses appropriately: + + - `ok` → Show that message is coming from the shown source, or show nothing + - `ok` + whitelisted domain → Show that message is coming from the shown source AND add source is verified + - `danger` → Show strong warning, recommend declining connection + - `warning` → Show caution message, let user decide + - `unknown` → Proceed without special indicators (default behavior) + +3) **Rollout Phase Awareness** + + - **Phase 1 (first 6 months)**: Only `ok` and `unknown` statuses returned + - **Phase 2 (after 6 months)**: Full status set including `danger` and `warning` + +### Transaction Verification Implementation + +1) **Request Source Metadata Processing** + + Wallets MUST implement transaction source verification: + + - Decrypt `request_source` field from BridgeMessage using session private key + - Parse BridgeRequestSource JSON containing origin, IP, User-Agent, timestamp, client_id + - Obtain current IP address from `connect_source` field in BridgeMessage + - Compare metadata against current user information + - Display warnings for any mismatches + +2) **Mismatch Detection and Warnings** + + Display appropriate warnings for detected mismatches: + + - **Different origin**: Strong fraud warning - likely impersonation attack + - **Different IP/User-Agent**: Network change warning - could be legitimate or suspicious + - **Significant time gaps**: Message is not relevant anymore + - **Missing metadata**: Should not occure after rollout. Messages without metadata should be rejected + +### User Interface Guidelines + +1) **Verification Status Messages** + + Wallets should be able to display following statuses: + - **Verification Mark (ok + whitelisted)**: + "✅ Verified dApp — confirmed request from a trusted source" + + - **Ok**: + "No message" + + - **Danger Warning**: + "⚠️ This request could not be verified and may be fraudulent. Do not proceed unless you are certain of the source." + + - **Warning Message**: + "⚠️ This request's details differ from your current connection. This could be due to a network change or other unusual event. Proceed with caution." + +2) **Transaction Source Display** + + In transaction confirmation dialogs, wallets SHOULD display: + + - Request source information (origin, timestamp) + - Any detected mismatches with stored connection data + - Clear warnings for security concerns + - Country of the request origin(from ip) + - Information about request user agent in the human readable format(e.g. Chrome 113 from macOS) + +3) **User Education** + + Wallets SHOULD educate users about: + + - Meaning of verification marks and warnings + - How to identify legitimate vs suspicious connection patterns + - Best practices for safe dApp interaction + - When to decline suspicious requests + +### Security Best Practices + +1) **Metadata Storage** + + - Store connection metadata securely and encrypted + - Implement proper session management and cleanup + - Limit metadata retention to necessary duration + +2) **Verification Logic** + + - Implement proper cryptographic verification of encrypted metadata + - Handle edge cases and error conditions gracefully + +3) **User Safety** + + - Default to more restrictive behavior when verification fails + - Provide clear actionable guidance to users + - Never suppress security warnings to improve user experience + diff --git a/workflows.md b/workflows.md index fd4ae18..0ac92ac 100644 --- a/workflows.md +++ b/workflows.md @@ -6,8 +6,9 @@ 1. App initiates SSE connection with bridge; 2. App passes connection info to the wallet via universal link or deeplink or QR code; 3. Wallet connects to the bridge with given parameters, and save connection info locally; -4. Wallet sends account information to the app using bridge; -5. App receives message and save connection info locally; +4. Wallet verifies connection source via `/verify` endpoint; +5. Wallet sends account information to the app using bridge; +6. App receives message and save connection info locally; ### Reconnection with http bridge 1. App reads connection info from localstorage @@ -22,9 +23,10 @@ ### Making ordinary requests and responses 1. App and wallet are in a connected state 2. App generates request and sends it to the bridge -3. Bridge forwards message to the wallet -4. Wallet generates response and sends it to the bridge -5. Bridge forwards message to the app +3. Bridge forwards message to the wallet with encrypted request source metadata +4. Wallet decrypts and verifies request source metadata +5. Wallet generates response and sends it to the bridge +6. Bridge forwards message to the app ## Details @@ -67,7 +69,32 @@ App is not yet in the connected state, and may restart the whole process at any ### Wallet establishes connection -Wallet opens up a link or QR code, reads plaintext app’s **Client ID** (A from parameter “**id”**) and [InitialRequest](requests-responses.md#initiating-connection) (from parameter **“r”**). +Wallet opens up a link or QR code, reads plaintext app's **Client ID** (A from parameter "**id"**) and [InitialRequest](requests-responses.md#initiating-connection) (from parameter **"r"**). + +### Connection Verification Process + +**For HTTP Bridge connections:** + +1. **Extract connection details**: Wallet extracts origin, client ID from the connection request +2. **Call verification endpoint**: Wallet sends POST request to `${bridgeUrl}/verify`: + ```json + { + "type": "connect", + "client_id": "", + "origin": "" + } + ``` +3. **Process verification response**: + - `ok` + whitelisted domain → show verification mark ✅ + - `danger` → display strong warning, recommend declining + - `warning` → display caution message + - `unknown` → proceed without special indicators + +**User Interface Guidelines:** +- **Verification Mark (ok + whitelisted)**: "✅ Verified dApp — confirmed request from a trusted source" +- **ok**: No message for default case +- **Danger Warning**: "⚠️ This request could not be verified and may be fraudulent. Do not proceed unless you are certain of the source." +- **Warning Message**: "⚠️ This request's details differ from expected. This could be due to a network change or other unusual event. Proceed with caution." Wallet computes the [InitialResponse](requests-responses.md#initiating-connection). @@ -93,16 +120,38 @@ When the user performs an action in the app, it may request confirmation from th App generates a [request](requests-responses.md#messages). -App encrypts it to the wallet’s key B (see below). +App encrypts it to the wallet's key B (see below). App sends the encrypted message to B over the [Bridge](bridge.md). -App shows “pending confirmation” UI to let user know to open the wallet. +**Bridge processes message with verification**: +1. Bridge collects request source metadata (origin, IP, User-Agent, timestamp, client ID) +2. Bridge encrypts metadata using wallet's Curve25519 public key +3. Bridge includes encrypted `request_source` in the BridgeMessage + +App shows "pending confirmation" UI to let user know to open the wallet. Wallet receives the encrypted message through the Bridge. Wallet decrypts the message and is now assured that it came from the app with ID **A.** -Wallet shows the confirmation dialog to the user, signs transaction and [replies](requests-responses.md#messages) over the bridge with user’s decision: “Ok, sent” or “User cancelled”. +### Transaction Verification Process + +**Request Source Verification:** +1. **Decrypt metadata**: Wallet decrypts the `request_source` field from BridgeMessage using its private key +2. **Parse metadata**: Extract BridgeRequestSource with origin, IP, User-Agent, timestamp, client ID +3. **Verify IP address**: Verify that the `connect_source` field matches the IP address of the client that sent the message +3. **Compare with stored connection**: Verify metadata matches stored connection details +4. **Display warnings for mismatches**: + - Different origin → potential fraud warning + - Different IP/User-Agent → network change warning + - Significant time gaps → reject transaction + +**User Interface for Transaction Verification:** +- Show request source details in transaction confirmation +- Highlight any mismatches between connection and transaction metadata +- Provide clear warnings for potential security issues + +Wallet shows the confirmation dialog to the user, signs transaction and [replies](requests-responses.md#messages) over the bridge with user's decision: "Ok, sent" or "User cancelled". App receives the encrypted message, decrypts it and closes the “pending confirmation” UI.