---
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
```
