summaryrefslogtreecommitdiff
path: root/doc/dev/rpc-book/src/rpc-cookie-sketch.md
blob: 4099e8c15bb367850fd5bc9845e4202664b7a3f3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
# RPC cookie authentication

This is based on the Tor control port's cookie authentication mechanism.

It's meant for use over a connection to a TCP port on localhost.

We try to provide the property that if a client and a server successfully
complete this process, then each one knows that the other was able to read
a given secure cookie file on the filesystem.


## Preliminaries

Let `P` be the 32-byte string
"====== arti-rpc-cookie-v1 ======".

Let MAC(a,b,c,...) be TupleHash,
the Keccak-based cryptographic digest function
described in Section 5 of [NIST SP 800-185],
using the output length `L = 256 bits`,
and the customization string `S = "arti-rpc-cookie-v1"`

> NOTE: Do not substitute any other hash function without cryptographic
> analysis!  In particular, we rely on TupleHash(K,a,b,c,d,...)
> instantiating a proper message-authentication-code over a unique
> encoding of the tuple `(a,b,c,d,...)`.

The client and server begin by knowing the location of a "cookie file."
That file contains the 32-byte fixed string `P`, followed by a 32-byte secret
`cookie` generated by the server. The server generates this file at startup.
Before connecting, the client reads this file,
and determines the value of `cookie`.

> Both parties need to make sure that the file isn't writeable by any
> untrusted user.  This is out-of-scope for this document.

The RPC client treats failures to read a cookie file
the same as a failure to read a connect file.

> To recap those rules:
>
> If the client cannot read the cookie file because of `EACCESS` or `ENOENT`
> or local equivalent,
> then the client *declines* the connect point.
> If the client fails to read the cookie file for some other reason,
> then the client *aborts* its connection to RPC.

If the cookie file is malformed, the client also *aborts*.
(A cookie file is malformed if it does not begin with `P`,
or if it is not exactly 64 bytes long.)

If the RPC server cannot write the cookie file,
it is a fatal error regardless of the reason.

Strings are represented in UTF-8 without a trailing NUL byte.

[NIST SP 800-185]: https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-185.pdf

## The protocol

At the start of the process,
the client knows this value from the connect point:
  - `socket`: The address at which to connect to the server.

At the start of the process,
the client and server additionally know this value from the connect point:
  - `socket_canonical`:
     The address at which the server is actually listening.
     If absent, defaults to the value of `socket` from the connect point.
     If the address is a unix path containing shell-style expansions,
     then this value is taken _after_ shell expansion.

> Note that this protocol will only succeed
> if the value of `socket_canonical` seen by the client
> is exactly the same string as
> the value of `socket_canonical` seen by the server.

The client and server know this value from the cookie file:
  - `cookie`: The value of the cookie.

1. The client connects to the server at `socket`.

   The client generates a random 32-byte nonce `client_nonce`,
   and the server generates a random 32-byte nonce `server_nonce`.
   These nonces MUST NOT be reused.

2. The client sends `client_nonce`.

3. The server computes
   `server_mac = MAC(cookie, "Server", socket_canonical, client_nonce)`
   and sends (`server_mac`, `socket_canonical`, `server_nonce`).
   (See below for the encoding.)

4. The client computes `server_mac`,
   and verifies that its value matches the one
   provided by the server.  If it does not match, it aborts the protocol.
   If it does match, the client computes
   `client_mac = MAC(cookie, "Client", socket_canonincal, server_nonce)`,
   and sends `client_mac` to the server.

5. The server computes `client_mac`, and verifies that its value matches the one
   provided by the client.  If it does not match, this connection attempt aborts.
   Otherwise, the parties are authenticated.

## In Arti-RPC.

This protocol is selected from an RPC connect point as discussed
in `rpc-connect-sketch.md`.

The client's message in step 2 is sent by invoking the `auth:cookie_begin` method,
implemented on the connection object.
It expects a single `client_nonce` parameter.

The server's message in step 3 is embedded in the server's response to that
method, in a set of fields: `server_addr`, `server_mac`, and `server_nonce.`
Additionally, the response includes an object ID in a `cookie_auth` field
This object holds the in-progress authentication state, and can be used
for a single `auth:cookie_continue` command.

The client's message in step 4 is sent by invoking the
`auth:cookie_continue` method,
directed to the object ID received in the `cookie_auth` field.

All binary values are encoded as hexadecimal strings before sending in JSON.