A circuit is a virtual connection between two PSYC nodes. Packets between any two entities that are leaves of the nodes are sent over the circuit. There should normally be only one circuit between two nodes.
The circuit provides:
- bidirectional reliable message delivery
- verification of sender address
In addition, it may provide
- encryption and compression between the root entities
Overview of packet delivery process
- TODO: This specification does not discuss relaying, that is when a sender trusts an intermediate node to forward the message to a destination, and the intermediate node has a reason to trust the sender in order to implement such relaying operation. Since even simple notification applications depend on this behaviour, it should be documented in the Spec somewhere, and taken in consideration here:
When a packet is sent to a non-local address, the following steps happen:
- the packet is queued until delivery
- the _target uniform is parsed and the hostname/port is extracted
- the hostname is resolved to obtain an IP address
- if there is no circuit to that IP address, a new circuit is opened
- if a trusted public key or X.509 certificate for the host is available, a TLS connection is initiated on the circuit
- when either type of connection is established, an empty packet is sent by the initiator, this establishes the circuit
- after opening an unencrypted circuit (or when reusing an unencrypted circuit), the initiator requests sender address verification from the accepting side. In the same step, it may require additional verification information from the accepting side
- the accepting side attempts to verify the sender address of the initiating side and answers the verification request for the root _source and _target addresses
- the initiating side receives the verification message. If the result is positive, the initiating side may start its own verification process, if it requested and required the accepting side to provide further information
- the initiating side begins to push any queued packets.
- if the accepting side receives any non circuit establishment packet from the initiating side, it may conclude that the initiating side has successful verified the sender address of the accepting side and may start to use the circuit in the reverse direction.
- TODO: Specification on when and how errors are returned to sender and queue is dismantled (Check the source, Luke)...
When a PSYC uniform has to be mapped to a host and port pair and no port is given in the uniform, a DNS SRV lookup SHOULD be performed to retrieve the real host and port number. The lookup is performed on _psyc._tcp.hostname (See also: RFC2782).
NOTE: The DNS SRV lookup SHOULD NOT return more than one host:port pair, as PSYC currently does not support mapping of hostnames to multiple IPs. Multiple hostnames of a PSYC node SHOULD NOT resolve to multiple ip:port combinations. These things have to be taken care of by the administrators of the PSYC nodes at the moment.
When UDP is selected as a transport, _psyc._udp.hostname MAY be consulted.
The resulting IP number and port of the resolution process may point to an already existing circuit. That circuit SHOULD be reused. If the root of the uniform hasn't already been verified as being a valid _target for that circuit, a _request_authorization is necessary.
SRV lookup may be circumvented by the user by allowing him to override the physical host and port numbers explicitly. This is different from restricting the user to provide a different uniform containing the port number.
A minimal application MAY leave out DNS resolution altogether, if the implications are understood and accepted. Best practice for SRV usage in 2008 is to support it in all PSYC applications, but to not deploy SRV usage in actual installations, if possible.
The endpoints of a circuit are defined by the two IP addresses and port numbers they are connecting.
Should no public port number be available, the receiving peer port number is used, prefixed by a minus sign (thus, a negative port number). Such an endpoint is only routable while the connection is established, after that any messages that wish to be routed to an address with a negative port number should trigger a _error_network_connect_invalid_port error.
This means, that a PSYC node that wants to deliver a packet for host A has to resolve its IP address and look whether it already has a circuit to that host.
If a circuit exists, but hostname A hasn't been authorized on it yet a _request_authorization request needs to be issued.
If no circuit exists yet, a new circuit has to be established.
Immediately after a TCP or TLS connection is established, the initiator sends an empty greeting packet (consisting of a pipe and a line feed, aka "|\n"). This establishes a circuit. The initiator should not wait for the the accepting side to reply to this packet as explained below, but may proceed requesting circuit features from the accepting side, or transmitting content that doesn't require any particular circuit features. The accepting side must reply to the empty greeting packet by answering with an empty packet, to acknowledge the establishment of the circuit.
Sender address verification
- Sender address verification serves the purpose of giving a recipient server time to check the validity of a sender (traditionally that would be an asynchronous DNS request, but in future it could be a synchronous calculation of trust metrics) before allowing him to send. Why do this? Because an attacker might send large packets that the recipient would need to queue until she knows they are invalid, creating an opportunity for a denial of service attack. There may be circumstances that make this level of paranoia unnecessary. Also, the receiving server could tell the sending server by which names she already expects him to send (Hello foo.bar, foo.baz, bar.tender).
The protocol flow is as follows: The initiating side sends tagged _request_authorization with the entity variable _uniform_source containing a single source root uniforms that the sender wants to get authorization for. The _uniform_target entity variable contains a singletarget root uniforms that the initiator presumes to be hosted on the recipient server.
| :_source :_target :_tag sometagasexplainedinexplanationofrequestresponsetagging :_uniform_source psyc://example.org :_uniform_target psyc://example.com _request_authorization |
This message may occur at any time. First, the receiving side checks if the root uniform given in the _uniform_target entity variable is indeed hosted on it. If that is not the case, it sends back an _error_invalid_uniform_target as follows:
:_source :_target :_tag_relay sometagasexplainedinexplanationofrequestresponsetagging :_uniform_source psyc://example.org :_uniform_target psyc://example.com _error_invalid_uniform_target |
The receiving side additionally verifies that the root uniform contained in the _uniform source variable is permitted to send on that circuit. The preferred method for this is that the initiating side has provided a trusted certificate which contains the identity; another method is a reverse DNS lookup that takes into account the existence of SRV records. If that check fails, the receiving entity answers with a _error_invalid_uniform_source as follows:
:_source :_target :_tag_relay sometagasexplainedinexplanationofrequestresponsetagging :_uniform_source psyc://example.org/ :_uniform_target psyc://example.com/ _error_invalid_uniform_source |
In both cases, the _uniform_source and _uniform_target entity variables should be sent back as-is.
If both _uniform_source and _uniform_target are considered acceptable, the receiving entity sends back a confirmation:
| :_source :_target :_tag_reply sometagasexplainedinexplanationofrequestresponsetagging :_uniform_source psyc://example.org/ :_uniform_target psyc://example.com/ _status_authorization |
Note: if a client sends a packet to the accepting side, the accepting side may consider the hostname portion of _target to be verified. It may also consider the hostname of each target to be acceptable.
TODO: Fuer den fall das die seite wo der initiator hinconnected, dieser schon connected ist, wird ein _failure_redirect geantwortet, und in _source_redirect die eigentliche uniform der destination einem mitgeteilt, zu der man evtl. schon verbunden ist. Man muss dann erst gucken ob man dorthin schon ne verbindung hat, und dort die _request_verification fuer den target host (welcher uns ebend redirected hat) nochmal machen mit _request_verification und _list_targets (aber mit leerem _list_sources, fuer den fall in dem wir das schon ausgehandelt haben dort).
Encryption using TLS
Immediately after establishing the TCP connection, the initiating side enables TLS. A TLSv1 client handshake MUST be sent. Backward compatibility to clients sending SSLv2 client hellos is only permitted on connections to leaves, however SSLv2 MUST NOT be negotiated. The initiating entity should use the TLS server name indication specified in RFC 4366. This may even be done on the default PSYC port, as the receiving side can use the following heuristic to determine if the initiating side is using TLS: The incoming connection is considered to be TLS if the first received byte is 0x16.
After the handshake the initiator and the recipient MUST also check the TLS certificates. If they are not valid and trusted, either side MAY break the connection. In case of an untrusted certificate, the certificate SHOULD be logged and the administrator SHOULD be notified of the failed communication attempt. See RFC 6125 for BCP regarding this check.
Note: the usage of a TLS-DHE ciphersuite with ephemeral Diffie-Hellman parameters is recommended.
Requesting a circuit feature
Same as TCP, client should not offer the _compression module, as this is handled by TLS already.
Recipient handling of a feature request
Same as TCP, server should ignore the _compression module if offered by the client.
Sender address verification
Sender address verification relies on X.509. The sender may use any host names contained in the subjectAltName/dNSName extension. The same applies for the receiving side. Use of a host name not listed MUST lead to an immediate termination of the circuit. DNS and its associated verification methods MUST NOT be used.
A circuit may be initiated by connecting to a uniform which isn't the actual canonical uniform of the recipient. For example connecting to psyc://example.org:4404 may actually be answered by an entity that carries psyc://example.net as its canonical name. The canonical name obviously has to be checked for validity.
Should this procedure have created two circuits to the same node, it makes sense to dismantle one again.
Outgoing uniforms (as typed by a user or clicked on a link) should not be stored persistently (except to cache the knowledge, which actual circuit they belong to, in case the user insists on using them). Entities should always perform a subscription procedure before communicating, therefore they will always be directed to the canonical uniform before completing the subscription procedure.
Canonical uniforms may at a later time be deprecated, in this case they should issue permanent redirects to the new canonical uniform. See the paragraph on redirection.
As a simplification in host authorization we presume that there are no malicious alternate nodes on the same host of a legitimate node willing to impersonate such legitimate node. That is also why processes on localhost are usually given full trust. Thus, any process on a host can produce messages in the name of other entities residing on that host.
Still, there can be several nodes on the same host using different canonical uniforms (different domains or even just different port numbers) as long as they co-exist peacefully.