URI Handling

URIs are a convenient method for endpoints to convey connectivity information in any out-of-band medium. This defines a process for handling any URI to automatically detect associated keys and paths, as well as standard practices for embedding that information in any generated URIs.

Once a URI is successfully resolved and a link is established to an endpoint the key and path information should be stored and used in place of the URI, a successful resolution only needs to be performed once.

Generation

New URIs that are generated by applications for the sole purpose of establishing a new link take the minimal form of: link://host:port/?csid=base32

  • link:// - defaults to link but may be any app-specific name for registering custom URL handlers
  • host:port - the host is often an IP address, and the port defaults to 42424 if not included
  • csid=base32 - (optional) a key/value pair for each of the sender's public keys

A generated URI will often include additional pathname or query string values as needed by the application.

Examples:

chat://127.0.0.1:55772/?cs1a=aof7baqdudm3mmjgexy5yqxj3m23pcsupy

Router / Peer URI

A common architecture includes a designated router that facilitates the connection process with peers. The router can generate an authorative base URI for peers to re-use and advertises it to them in a path request. This allows a peer to be reachable via a URI but still remain private and not share any of its identity information (hashname, keys, or paths).

The router may include its own keys in the query string but may not attach a #fragment so that a peer can use the fragment part to include additional data before distributing the URI. The router must always include a value in the base URI to validate the request and internally map it to the peer it was generated for, it should never embed or expose the hashname or other specific details about the peer in the base URI.

The peer endpoint must generate a fragment value that it can use to validate incoming requests and the recipient can use to verify the peer's hashname. This fragment is always a base32 encoded byte string of at least 16 bytes in length when decoded. The last 8 bytes are always the SipHash digest of all of the previous bytes, using the first 16 bytes of the peer's hashname as the key input to the digest (identical to the chat channel). The 8+ variable bytes in the fragment must be generated by the peer such that they are unique for every URI it shares.

When the URI is resolved and the peer must always respond with a URI handshake and the connecting party must verify that the hashname is the correct key to generate the digest in the fragment. This ensures that only that peer can correctly link from a specific URI and that the router cannot redirect to another party.

Example URI that uses a router as the base and includes the peer fragment:

link://127.0.0.1/?sid=1zm3hv7g&cs1a=aof7baqdudm3mmjgexy5yqxj3m23pcsupy#jpnzr4n33kwqbgpw3mduf7takvczpx2gafzjc2ppfc4yrxkltzsa

When a URI is processed that contains a fragment it generates a new peer request to the router that includes the "uri":"..." (without the fragment) for the router to validate and resolve to the right peer. The peer request must still also include a full URI handshake to be forwarded directly to the peer for its own validation.

Processing:

  1. detect included keys in the query string and derive hashname
  2. [optional] fallback to discover keys via WebFinger
  3. fallback to resolve the canonical hostname to discover keys via DNS SRV
  4. fallback to discover keys via HTTPS well-known
  5. generate paths for all supported transports with any resolved IP and port
  6. create a link with the keys and path(s) including a URI handshake
  7. if there's a fragment hashname, issue a peer request over the link to it including a URI handshake
  8. process any response URI handshake with a validating fragment as the final resolution

Embedded Keys

The Cipher Set keys for an endpoint may be included in the query string of any existing URI.

Each CSID is included as an individual key/value pair where the key is the format cs?? (cs1a, cs2a, etc) and the value is always the base32 encoded key bytes.

Embedded Paths

The current paths may also be included in the query string of any existing URI. Each available path has its JSON object base32 encoded as the value and is included with a common paths key, multiple paths have the same key.

When the paths are able to be generated from the hostname in the URI it is not necessary to include them in the query string.

Example paths:

[
    {
        "url": "http://192.168.0.36:42424",
        "type": "http"
    },
    {
        "ip": "192.168.0.36",
        "port": 42424,
        "type": "udp4"
    },
    {
        "ip": "fe80::bae8:56ff:fe43:3de4",
        "port": 42424,
        "type": "tcp6"
    }
]
URL: proto://host/path?key=value&paths=pmrhk4tmei5ce2duorydulzpge4telrrgy4c4mbogm3dunbsgqzdiirmej2hs4dfei5ce2duoryce7i&paths=pmrgs4bchirdcojsfyytmobogaxdgnrcfqrha33soqrdunbsgqzdilbcor4xazjchirhkzdqgqrh2&paths=pmrgs4bchirgmzjyga5duytbmu4dunjwmztduztfgqztum3emu2celbcobxxe5bchi2denbsgqwce5dzobsseorcorrxanrcpu

Generated Paths

The canonical hostname (or IP and port) of a URI or the resolved SRV port and IP should be treated as a potential path for all available transports for an endpoint. Handshakes should be sent to the given address in every transport supported that can use an IP and port, including UDP, TCP, TLS, and HTTP(S).

When an endpoint's keys cannot be included directly in the URI they may be discovered via automated techniques from other parts of the URI.

SRV records always resolve to a hashname-prefixed host, with TXT records returning all of the keys.

  • _link._udp.example.com. 86400 IN SRV 0 5 42424 uvabrvfqacyvgcu8kbrrmk9apjbvgvn2wjechqr3vf9c1zm3hv7g.example.com.
  • uvabrvfqacyvgcu8kbrrmk9apjbvgvn2wjechqr3vf9c1zm3hv7g IN A 1.2.3.4
  • uvabrvfqacyvgcu8kbrrmk9apjbvgvn2wjechqr3vf9c1zm3hv7g IN TXT "1a=base32"
  • uvabrvfqacyvgcu8kbrrmk9apjbvgvn2wjechqr3vf9c1zm3hv7g IN TXT "2a=base32"
  • uvabrvfqacyvgcu8kbrrmk9apjbvgvn2wjechqr3vf9c1zm3hv7g IN TXT "2a2=base32"
  • uvabrvfqacyvgcu8kbrrmk9apjbvgvn2wjechqr3vf9c1zm3hv7g IN TXT "3a=base32"

If a key's base32 encoding is larger than 250 characters (TXT limit), it is broken into multiple TXT records with the CSID being numerically increased so that it can be consistently reassembled.

No other DNS record type is supported, only SRV records resulting in one or more A and TXT records.

Use WebFinger against the canonical hostname, passing the given URI in as the resource and a rel value of http://telehash.org/link. If successful, it will result in a valid href that must return the standard JSON link description format.

GET https://example.com/.well-known/webfinger?resource=http://example.com/~user1&ref=http://telehash.org/link
{
  "subject": "http://example.com/~user1",
  "links" : [
    {
      "rel" : "http://telehash.org/link",
      "href" : "https://www.example.com/~user1/link.json"
    }
  ]
}

GET https://example.com/~user1/link.json
{
  "keys":{...},
  "paths":[...]
}

Return the standard JSON link description format under the root /.well-known/ path:

GET https://example.com/.well-known/link.json
{
  "keys":{...},
  "paths":[...]
}

## URI Handshake

When a URI is the source of a new link, a `"type":"uri"` [handshake](e3x/handshake.md#uri) should be sent including the original URI.

Example:

```json
{
  "type":"uri",
  "uri":"https://example.com/link?ref=42#u8kbrrmk9apjbvgvn2wjechqr3vf9c1"
}