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 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154
|
---
authors: Markus Rudy (@burgerdev)
state: committed
---
# RFD 001: GLOME Login v2
## Objective
Make the GLOME Login Protocol unambiguous.
## Background
See also [google/glome#62](https://github.com/google/glome/issues/62).
- The ambiguous interpretation of `prefix7` may lead to a change of server authorization behaviour that cannot be controlled by the client (e.g. a new key is added to the server whose index conflicts with a public key prefix of an existing one).
- It's currently legal to have colon (`:`) and slash (`/`) characters in all message fields, which may cause ambiguity in parsing and, ultimately, lead to authorization of unintended messages.
- The protocol gives advice to "maximize the human readability of the URL", which conflicts with an unambiguous presentation of said characters in percent-encoded form.
## Requirements
- There must be a well-defined interpretation of the GLOME Login handshake
that does not depend on the public keys a server holds.
- There must be a well-defined, bijective conversion from the message embedded
in a GLOME Login URL to the message being authorized.
- Subject to the preceding requirements, the URL layout should be optimized for
human readability (e.g. don't encode
[unreserved characters](https://www.rfc-editor.org/rfc/rfc3986#section-2.3))
and brevity.
- Assuming humans will have to read the message to be authorized much more often than parse the involved keys.
## Design ideas
- The `prefix-type` bit determines interpretation of `prefix7`:
* 0: `prefix7` is matched with the high byte of the server's public key
* 1: `prefix7` is an index into the server's public keys.
- The GLOME Login challenge is a URI path.
- Completely specify the encoding and decoding of the message part.
- Include detailed instructions for server and client into the protocol.
- Publish the result as GLOME Login v2, as it is incompatible with v1 URLs.
### `prefix7`
The most significant bit of a 256bit X25519 public key should not be interpreted by the Diffie-Hellman key exchange [RFC7748]. We use this fact to define a `prefix7` config that is somewhat self-configuring: `prefix-type` is 0, `prefix7` is the high byte of the server's public key, and thus the 8bit prefix is, too. Alternatively, if indices are to be used, `prefix-type` is 1 and `prefix7` is the index between 0 and 127, inclusive. Note that this is a large amount of public keys, even in case of automatic rotation - if this is a concern, `prefixN` can be used to verify (or pick) the public key on the server side.
Note that this is incompatible with _all_ subsets of v1: indices need to be taken `mod 128`, and public key prefixes are now taken from the MSB, not the LSB.
[RFC7748]: https://www.rfc-editor.org/rfc/rfc7748#section-5
### URI
Prior versions of GLOME assumed that the challenge would always be rendered as a URL. This is not true in many cases: for example, a URL challenge does not make too much sense for a response generated with the `glome` cli. On the other hand, presenting the challenge as a URL works reasonably well in practice, so we don't want to change the challenge format in an incompatible way. Thus, a challenge in v2 is what used to be the URL path in v1.
```abnf
challenge = "v2/" handshake-segment "/" message "/"
```
A URI path can still be prefixed with scheme and host to build a URL. Subsequent sections describe how a challenge is encoded to a valid URI path and how to compute the tag over that encoding.
### Message
New restrictions to make message encoding unambiguous:
- `hostid-type` and `hostid` must not contain the `:` character.
- `hostid-type`, `hostid` and `action` should not contain any characters that would be escaped in a URI path segment (as detailed below). Differing from previous protocol versions, `/` is discouraged.
#### URI Path Segments
The URI specification [RFC 3986](https://www.ietf.org/rfc/rfc3986.html#section-3.3) defines a path segment as
```abnf
segment = *pchar
pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "="
pct-encoded = "%" HEXDIG HEXDIG
```
where HEXDIG should refer to a digit or an uppercase letter A-F. This matches the definition in <https://url.spec.whatwg.org/#url-path-segment-string>, which supposedly supersedes the RFC.
Thus, define `EscapePathSegment` as a function that escapes all characters that are not unreserved, sub delimiters, `:` or `@`. See the Appendix for how this function can be implemented in some of the major programming languages.
#### Message Encoding
Constructing a message takes three parameters: `hostid`, `action` and (optionally) `hostid-type`. These are encoded into a `message` - a URI (sub)path - using `EscapePathSegment` as follows.
```abnf
message = host-segment "/" action-segment
host-segment = EscapePathSegment( [hostid-type ":"] hostid )
action-segment = EscapePathSegment(action)
```
The `hostid-type` prefix is added if and only if the `hostid-type` of the message is not empty.
Note that this voids some of the existing recommendations for 'good' actions: `shell/root`, for example, would have to be escaped and thus be less readable. Instead, using URI sub-delimiters as in `shell=root` should be recommended. This format would interact nicely with a host-identity-based authorization scheme working with key-value pairs.
#### Message Decoding
Given a URI path, strip the path prefix up to including the `/` after the handshake message. Split the remaining string on the character `/` and keep only the first and second element, denoted `host-segment` and `action-segment`; or fail if there are less than two elements. Replace all percent-encoded octets in the `host-segment` with their raw, unencoded form. Split the result at the character `:`. If there is one element, assign that element to `hostid` and assign the literal string `hostname` to `hostid-type`; if there are two elements assign the first one to `hostid-type` and the second to `hostid`; if there are more than two elements, fail. Replace all percent-encoded octets in the `action-segment` with their raw, unencoded form, and assign the result to `action`.
#### Message Tagging
The tag for a message is produced by passing the **encoded message** string into `glome_tag`.
## Alternatives considered
### Allow unescaped slashes in action
- Allow an action to span more than one path segment.
- This prevents us from having an unambiguous encoding: `xxx/yyy%2Fzzz` vs. `xxx/yyy/zzz`.
### Calculate the tag on the unescaped message
- Tag the message before URL escaping.
- This would have the benefit of decoupling the tagging from the transport (here, URL segments).
- However, we need to encode the message into a byte array before we can tag it. This encoding must be unambiguous as well, simply concatenating the triple won't cut it.
## Appendix
### URI Path Escaping APIs
#### Python
```python
urllib.parse.quote(segment, safe=":@!$&'()*+,;=")
```
#### Golang
:'-( <https://github.com/golang/go/issues/27559>
#### C
GLib:
```c
g_uri_escape_string(segment, ":@!$&'()*+,;=", /*allow_utf8=*/false);
```
#### Java
Guava:
```java
com.google.common.net.UrlEscapers.urlPathSegmentEscaper().escape(segment)
```
#### OCaml
Uri:
```ocaml
Uri.pct_encode segment
```
|