keystore
========

KeyStore holds *unencrypted* secrets on behalf of users in memory for
a short time (of the order of a SSO session lifespan). User secrets
can be *opened* with a password (used to decrypt the key, which is
stored encrypted in a database), *queried* by presenting a suitable
authentication token, and *closed* (wiped and forgotten).

The database can provide multiple versions of the encrypted key (to
support multiple decryption passwords), in which case we'll try
them all sequentially until one of them decrypts successfully with
the provided password.

In order to query the KeyStore, you need to present a valid SSO
token for the user whose secrets you would like to obtain.

# API

The server exports an API over HTTP/HTTPS. All requests should be made
using the POST method and a Content-Type of *application/json*. The
request body should contain a JSON-encoded object. Responses will be
similarly JSON-encoded.

`/api/open` (*OpenRequest*)

Retrieve the encrypted key for a user, decrypt it with the provided
password, and store it in memory.

OpenRequest is an object with the following attributes:

* `username`
* `password` to decrypt the user's key with
* `ttl` (seconds) time after which the credentials are automatically
  forgotten

If the user has no encrypted keys in the database, the request will
still return successfully: no action will be performed, and no errors
will be returned.

`/api/get` (*GetRequest*) -> *GetResponse*

Retrieve the key for a user. GetRequest must contain the following
attributes:

* `username` whose key you wish to retrieve
* `sso_ticket` with a valid SSO ticket for the *keystore* service

If the request is successfully authenticated, GetResponse will contain
a single attribute *key*.

`/api/close` (*CloseRequest*)

Forget the key for a given user.

# Dovecot integration

The final consumer for user encryption keys is the Dovecot
service. The *dovecot-keylookupd* daemon can read the user public and
private keys from the database, and serve the *unencrypted* keys to
Dovecot using its [dict proxy
protocol](https://wiki2.dovecot.org/AuthDatabase/Dict).

*NOTE* that passdb lookups using *dovecot-keylookupd* contain the
cleartext password as part of the key, which may be logged in case of
error! This is currently a huge limitation of this solution, but there
seems to be no workaround that does not involve switching to a
fork()-based solution (like the checkpassword script). That might be a
better solution long-term.

TODO: explain the lookup protocol.

# Configuration

The *keystored* daemon loads its configuration from a YAML-encoded
file, */etc/keystore/config.yml* by default. It can contain the
following attributes:

* `sso_public_key_file`: path to the SSO Ed25519 public key
* `sso_service`: SSO service for this application
* `sso_domain`: SSO domain
* `backend`: backend configuration
  * `type`: backend type, one of *ldap* or *sql*
  * `params`: backend parameters, type-specific (see *Backend
    configuration*, below)
* `http_server`: HTTP server configuration
  * `tls`: contains the server-side TLS configuration:
    * `cert`: path to the server certificate
    * `key`: path to the server's private key
    * `ca`: path to the CA used to validate clients
    * `acl`: specifies TLS-based access controls, a list of entries
      with the following attributes:
      * `path`: regular expression to match the request URL path
      * `cn`: regular expression that must match the CommonName part
        of the subject of the client certificate
  * `max_inflight_requests`: maximum number of in-flight requests to
    allow before server-side throttling kicks in

The *dovecot-keylookupd* daemon uses a similar configuration, read by
default from */etc/keystore/dovecot.yml*:

* `backend`: backend configuration
  * `type`: backend type, one of *ldap* or *sql*
  * `params`: backend parameters, type-specific (see *Backend
    configuration*, below)
* `keystore`: configures the connection to the keystore service
  * `url`: URL for the keystore service
  * `sharded`: if true, requests to the keystore service will be
    partitioned according to the user's *shard* attribute
  * `tls`: client TLS configuration
    * `cert`: path to the client certificate
    * `key`: path to the private key
    * `ca`: path to the CA used to validate the server
* `shard`: shard identifier for the local host. Must be set if
  keystore.sharded is true.

## Backend configuration

The keystore servers can talk to a LDAP or a SQL database. In both
cases it is possible to adapt to the database schema by defining the
exact queries to use. All we need to do is to retrieve the public and
private parts of the user encryption key.

The *ldap* database backend understands the following configuration
parameters:

* `uri`: LDAP server URI
* `bind_dn`: bind DN (for simple bind, SASL is not supported)
* `bind_pw`: bind password
* `bind_pw_file`: bind password (load from this file), in
  alternative to *bind_pw*
* `query`: Parameters for the LDAP search query
  * `search_base`: base DN for the search
  * `search_filter`: search filter. The filter string may contain a
    literal `%s` token somewhere, that will be replaced with the
    (escaped) username.
  * `scope`: search scope, one of *sub* (default), *one* or *base*
  * `public_key_attr`: attribute that contains the user's public key
  * `private_key_attr`: attribute that contains the user's encrypted
    key(s)

The *sql* database backend requires the following parameters:

* `driver`: SQL driver, one of *sqlite3*, *mysql* or *postgres*
* `db_uri`: database URI (a.k.a. DSN), whose exact syntax will depend
  on the chosen driver. Check out the documentation for the
  database/sql [sqlite](https://github.com/mattn/go-sqlite3),
  [mysql](https://github.com/go-sql-driver/mysql) and
  [postgres](https://godoc.org/github.com/lib/pq) drivers.
* `queries`: map with the known queries. All SQL queries take one
  parameter (the user name), and return one or more rows with a single
  column. Use the `?` placeholder for the parameter. Known queries:
  * `get_user_public_key`: must return a single row with the public
    key
  * `get_user_private_keys`: must return one or more rows with the
    user's private keys (copies of the same key encrypted with
    different passwords).