HTTPS SSH
GPG REMOTE
==========


Motivation
----------

Using GnuPG in a networked environment always poses certain risk that a
remote attacker who is able to compromise one of the client applications
(e.g. MUA, IM client, etc.) could easily leak the private key by calling
``gpg --export-secret-keys``. A common mitigation of such risk are
smartcards, however they are specialized hardware which a) may not be
readily available, or b) could be not trusted for various of reasons.


Overview
--------

GPG Remote is a client-server application allowing to delegate GnuPG
private key operations to a remote server running in a trusted environment.
Server filters client input according to specified rules, and runs GnuPG
operations on behalf of a client.

GPG Remote separates GnuPG execution between a front-end client and a
back-end server. The client tries to replicate GnuPG command line
interface, taking up command line arguments and STDIN data. Internally,
it then parses args input, figures out files which the user may want to
process, packs all that into a request package, and sends it to the server.

The server operating in a trusted environment is tasked to execute ``gpg``
in a safe manner. For this end it uses a whitelist of ``gpg`` command line
options to filter out everything inappropriate of the received client
command line arguments (especially, commands like ``--export-secret-keys``).
Files received from the client are saved into temporary location, and their
paths in command line arguments are updated accordingly. Finally, ``gpg``
is called, and its output (comprised of STDERR, STDOUT, exit code, as well
as newly generates files) is sent back to client.


Installation
------------

Make sure you have Python 3.3.x or later installed on all systems you plan
to use for client and server operation. Both client and server modules are
self-contained, and can be placed anywhere on the system.

Running GPG Remote Client as a drop-in replacement for system-wide ``gpg``
requires ``gpgremote_client.py`` script to be moved to or symlinked from
``/usr/bin/gpg`` path. If both components are running on the same system,
ensure only the server user has read-write access to GnuPG keyring files.

In order to enable passphrase input over a network connection, follow these
steps:

1. Make sure standard ``gpg`` ``pinentry`` application is installed on the
   client.
2. Install [``pyassuan``](https://pypi.python.org/pypi/pyassuan/) library
   on both client and server systems.
3. Ensure ``gpg-agent`` is properly configured and running on the server,
   and path to bundled GPG Remote ``pinentry.py`` is passed to ``gpg-agent``
   using ``--pinentry-program`` option (see ``man gpg-agent`` for details).

If "panic" rules support is required (see the corresponsing section below),
install [``pbkdf2``](https://pypi.python.org/pypi/pbkdf2) Python module on
the server system.


Configuration
-------------

The client reads configuration data (specifically, server listening
host:port) from ``gpgremote_client.conf`` file located in ``~/.gnupg``
directory unless path is overridden with ``GNUPGHOME`` environment variable.

By default server reads its configuration from ``gpgremote_server.conf``
file located in ``~/.gnupg`` (the path can be overridden with ``GNUPGHOME``
environment variable). However, specific path can be provided with
``-c``/``--config`` option to server invocation. Most server parameters
can be reconfigured from the command line as well (``-h``/``--help`` will
print all available options).


Whitelist
---------

The second part of server configuration is ``gpg`` options whitelist
defined in ``whitelist.conf`` in the same directory as server config file.
The syntax is simple, yet configuring the whitelist correctly is critical
to server security (see _Security considerations_ section for details).

1. Lines not starting with a dash sign are ignored.
2. A single set of options per line.
3. A set is either a single option (in long or short form), or a
   space-separated long and short forms (in arbitrary amount and order).
4. Non dash-prefixed words in a set (if any) have special meaning:

    * A bracketed word is considered a wildcard parameter turning options
      in a set into parameterized ones.
    * An unbracketed word is a whitelisted parameter value, and, as such,
      options in a set can be passed with this value only. Multiple
      whitelisted values must be provided on the same line (quoting /
      space-escaping is supported).
    * If a word is bracketed ``#NO_FILES``, it means no files should be
      expected in arguments list for this options set (see _Security
      considerations_ section below).


One-time passwords
------------------

An extra security measure for private key protection is _one-time passwords
(OTP)_. When enabled, it will require user to enter a short random string
(from a pre-generated list) along with private key passphrase. This will
thwart adversary's attempts to use private key over GPG Remote by passing a
sniffed passphrase. (Please note: bundled GPG Remote ``pinentry.py`` must
be used, see _Installation_ section above for detailed requirements.)

To use this feature, enable it in server configuration file or with
``--otp`` startup option. Then run the server with ``--gen-otp`` option and
enter the number of one-time passwords to generate. The longer the list,
the later it will has to be replenished, but the wider will be the window
of opportunity for the attacker if OTP list gets compromised. Note: the
list can be regenerated at any moment, and any passwords left in it will be
invalidated; regenerating OTP list does not requires server restart.

Once OTP is enabled, a next password from the list will be required each
time a private key passphrase is prompted - an OTP must be appended to
the end of passphrase (without spaces or other delimiters). An entered OTP
is invalidated, i.e. if the passphrase is mistyped, the next OTP will be
requested on each retry. Once OTP list is depleted, any private key
operation will fail until the new list is generated.

Please note that ``gpg-agent`` passphrase caching bypasses OTP: while the
passphrase is cached, the key could be used without user interaction.


"Panic" rules
-------------

It is possible to configure an arbitrary amount of so called "panic" rules.
These rules can be used to execute specific shell commands on the server in
the event a predefined passphrase is entered in ``pinentry`` dialog.
(Please note: bundled GPG Remote ``pinentry.py`` must be used, see
_Installation_ section above for detailed requirements.)

Each rule is specified as an entry in the server configuration file. Entry
name must begin with ``panic_`` prefix followed by a unique name. Entry
value consits of a space-sperated security token and shell command(s) in
regular notation (i.e. no quoting or escaping is necessary), or a special
command (see below). Security token is a PBKDF2 hash string generated from
a passphrase that should trigger a specific rule. Running server with
``--gen-token`` option will help to generate a token for a particular
passphrase.

A single passphrase can trigger any amount of rules if all of them use the
same passphrase protection (but not necesserily the same token literatim).
A triggered command is silently executed by the server-side ``pinentry``
process with access permissions of ``gpg-agent`` parent user prior to
resending the entered passphrase to ``gpg-agent``. Matched rules are
executed in the order they are defined in the configuration file.

The following environment variables are passed to "panic" shell commands:

* ``GPG_REMOTE_PID``: PID of the GPG Remote server process.
* ``GPG_REMOTE_KEYRINGS``: Space-separated list of paths to non-empty
  ``gpg`` keyring files.

The following special commands may be used instead of shell commands in
"panic" rule definitions. Please note that a single special command only
can be specified for any rule:

* ``STOP``: Stop GPG Remote server gracefully. Server will send the client
  a general error message, finish processing of any concurrent requests,
  clean up all the data received from the client, and exit.
* ``KILL``: Terminate GPG Remote server immediately. Server will send
  ``SIGKILL`` signal to itself without performing any cleanup procedures.

Please take into account that ``gpg-agent`` reads the private key in memory
_before_ spawning ``pinentry``, and simply running ``rm``/``wipe`` to
delete private keyring files will not destroy the key immediately - it is
necessary to terminate the running GPG Remote server process (using ``STOP``
or ``KILL`` special commands) to prevent sending ``gpg`` operation results
back to the client. Use rules chaining (by assigning the same security
token / passphrase to multiple rules) to run multiple commands when needed.


Security considerations
-----------------------

Communication channel authentication/encryption is out of the scope of this
application. The user may employ SSH or VPN tunnelling to create a trusted
channel for client-server communication.

The threat model and main attack scenario is a client-side remote attacker
(e.g. compromised network application) exfiltrating ``gpg`` private keys.
The server mitigates this risk by using ``gpg`` command line options
whitelist.

Note that even if keyring modifying options (e.g. ``--delete-key``,
``--import``) are not whitelisted, client user would still be able to add
keys to the keyring by simply sending them to STDIN (``gpg`` processes it
contextually). If this should be avoided, it's up to the server
administrator to run the server as a user without write access to ``gpg``
keyring files. Remember that default ``gpg`` keyrings can be overridden
with ``--no-default-keyring``, ``--secret-keyring`` and ``--keyring``
options.

Another potential risk to the server is its local files exfiltration. In
the naive case the user could ask the server to run ``gpg -o - --enarmor
[path_to_local_file]``, and the server would happily send that file contents
in STDOUT. In order to protect against such attacks the server makes sure
the number of filename arguments is equal to the number of files received
in client request package. (These complications are necessary as simply
refusing to process requests containing server local filepaths would lead
to information leakage about server filesystem contents.) However, it
requires correct configuration of the server whitelist in respect to
options parameter specification: in case an option accepts parameters,
its set MUST include parameter wildcard/value, otherwise the server might
become vulnerable to the described attack.

Note also that a number of ``gpg`` command line options (namely,
``--list-keys``, ``--list-sigs``, etc.) receive arbitrary amount of
non-file arguments. This case is supported with special ``[#NO_FILES]``
placeholder. If such an option is provided by the client, the server strips
out any ``-o``/``--output`` options, and prevents sending any files back to
the client.

Files received from the client (which may contain sensitive cleartext data)
are written by the server to a temporary location. By default it is a
system-wide temp directory (commonly, ``/tmp``), but in case this directory
is unsafe, it can be overridden using ``TEMP`` environment variable, or
``--temp`` command line option for server invocation. (Note that files
aren't written directly to tempdir, but to temporary subdirectories with
0700 access mode, i.e. accessable only by GPG Remote server user).

As neither client nor server employ any semantic analysis of command line
arguments (i.e. does not understand the meaning of options and commands),
the client assumes an option parameter or trailing argument named as an
existing client local file to be a file indended for ``gpg`` processing,
and optimistically sends it to the server. Note that client unconditionally
writes out all files received from the server (on the assumption it has
write access to a given path) without asking for overwrite if the same file
exist.

The client may try to cause DoS on the server by sending it excessively
huge input(s). This scenario is addressed with server resources management
parameters: ``size_limit``, ``threads`` and ``queue``. The first limits the
size of client request package and, as a result, memory usage. The second
limits the number of CPU threads used for requests processing (each request
is single-threaded). Note that the total amount of RAM the server might use
is around ``S * T * 2``, where ``S`` is the package size limit and ``T`` is
the threads count (i.e. with default ``S=1GB`` and ``T=2`` maximum RAM
usage would be 4 GB). Finally, the ``queue`` value is the amount of
requests that can be queued for processing. If the value is higher than
threads count then the remaining requests will wait until active ones are
finished. Awaiting requests does not take up additional resources except
for a socket connection.

When remote passphrase input is used, an entered passphrase never touches
long-lived server process memory. However it remains in the client memory
for the whole duration of ``gpg`` execution, and during that period it's
subject to a risk of memory swapping. Make sure client swapping device is
encrypted, disabled, or other protective measures are employed.

One-time passwords (OTP) is a mere access control mechanism enforced by GPG
Remote. As such, it does not affects any cryptographic material, and must
not be expected to deliver specific cryptographic properties, e.g. PFS.

If "panic" rules are configured on the server with high hashing iterations
count, an adversary can potentially deduce this fact from a delay of ``gpg``
output as user passphrase must be matched to each unique security token.
It is also possible to detect "panic" rules execution if the executed
command takes a long time to complete.


Technical details
-----------------

Communication protocol is a simple two-step request-response. Package
format for both directions is as follows:

``<len_p> | <len_j> | JSON(<header>, <type>, <fields>, <files_meta>) |
[binary]``

* ``len_p`` (8 bytes): Overall package length.
* ``len_j`` (8 bytes): JSON packet length.
* ``header`` (list): ``auth`` token (optional) and application ``version``.
* ``type`` (str): Package identifier.
* ``fields`` (list): Arbitrary data fields.
* ``files_meta`` (dict): ``File_pathname->file_len`` mapping.
* ``binary`` (bytes): Concatenated files data (optional).

If authentication token is provided, it is expected to be a HMAC-SHA256 hex
digest of all JSON-packed metadata, and is calculated as follows: the
metadata elements (except for ``auth``) are packed as a flat list in the
above mentioned order into a JSON-encoded string, which is passed to HMAC
context. Authentication is currently used for server<>``pinentry`` IPC
only. As such, binary data is not authenticated.

Remote passphrase input is implemented using custom ``pinentry.py`` shim
application. It employs the following communication steps (``pinentry``
actor below is the custom ``pinentry.py`` shim application unless otherwise
stated):

1. ``server>gpg-agent``: uses ``PINENTRY_USER_DATA`` environment variable
   (which is passed over ``gpg > gpg-agent > pinentry`` execution stack) to
   provide ``pinentry`` with IPC communication details including IPC socket
   and session authentication key.
2. ``gpg-agent>pinentry``: calls ``pinentry`` with ``PINENTRY_USER_DATA``
   environment variable and initiates Assuan protocol.
3. ``pinentry>server``: initiates IPC protocol and asks for client network
   connection data.
4. ``server>pinentry``: sends opened client network socket directly to the
   custom ``pinentry`` over IPC channel (UNIX socket).
5. ``pinentry>client``: uses the provided network connection to send the
   client all the required ``pinentry`` data (text strings and startup
   parameters) got from ``gpg-agent`` at step 2.
6. ``client``: runs standard ``pinentry`` to aquire user response data (in
   the form of Assuan protocol response).
7. ``client>pinentry``: sends user response data.
8. ``pinentry``: executes "panic" commands if any are triggered by the
   client passphrase.
9. ``pinentry>gpg-agent``: replays user response in Assuan protocol
   exchange.

If wrong passphrase is entered, steps 2-9 are performed again up to the
number of retries required by ``gpg-agent``.

It should be noted that although IPC UNIX socket (used for
server<>``pinentry`` communication) access is not resticted (in order to
allow running server and ``gpg-agent`` under different users), server
verifies authenticity of packages received using the session auth key it
provides ``pinentry`` process with.

The one-time passwords (OTP) list is stored on the server in plaintext.
Each line of OTP list file is a colon-delimited ID number and the actual
password string.

"Panic" rules security token is a PBKDF2[SHA-1, HMAC] output with effective
entropy limit of 256 bits. 64-bit salt value is used. Token format is a
string of colon-delimited Base62-encoded elements: iterations count
(in bytes representation), salt, hash.

Default server listening port (29797) was produced in Python as follows:

```
#!python

int.from_bytes(b'gpgremote', 'big') % 2 ** 16
```

(Although it has been noted only ``b'te'`` bytestring has any effect in
such procedure.)


Issues, limitations
-------------------

* Interactive console UI operations (e.g. key generation, key edit, etc.)
  are not supported.

* Client does not support reading input from TTY, data must be piped to
  STDIN.

* Passing file descriptors and implementing other forms of advanced IPC
  interaction with ``gpg`` is not supported.

* No environment variables are passed from the client. If ``gpg`` must be
  invoked with specific environment (e.g. ``LANG``), start GPG Remote
  Server with all the necessary variables instead.

* If GnuPG 2.x or higher is used without custom Pinentry, secret key
  operations would spawn standard Pinentry dialog on the server side which
  will prevent ``gpg`` process from terminating. This might be a feature if
  both GPG Remote server and client are running on the same system,
  otherwise it's up to the server administrator to disable ``gpg-agent``
  server-side (for example, by downgrading to GnuPG 1.4.x or starting
  ``gpg-agent`` with ``--batch`` option).


ToDo
----

* One-time passwords support.
* Minimize memory footprint.


Version history
---------------

* 2015-03-18 - ``v1.3``
    - Added support for one-time passwords.
    - Fixed a case with pinentry and stdin pipe.

* 2015-03-16 - ``v1.2``
    - Passphrase confirmation while generating "panic" security token.
    - Minor aesthetic and code documentation cleanups.
    - First stable release.

* 2015-02-17 - ``v1.2b``
    - Updated minimum Python version requirement to 3.3 (it was mistakenly
      lower).
    - Raised default logging verbosity to info level.
    - Matched "panic" rules are executed in the defined order.
    - New "panic" rules security token format replacing ``crypt(3)`` one.
      Output length limit is 256 bits now instead of 192 bits.
    - Optimized security token matching scheme (speed-wise) if the same
      token is used for multiple rules.
    - Changed Server<>Pinentry IPC interface and protocol.
    - Set IPC message size limit to 64 KB (was a possible DoS scenario).
    - Special "panic" commands to properly terminate server.
    - Fixed IPC socket permissions which prevented running server and
      ``gpg-agent`` under different users.
    - Fixed error handling if ``gpg`` executable cannot be found.
    - Code cleanup and reorganization.

* 2015-02-06 - ``v1.1b1``
    - Fixed 'ttyname' Assuan option update on the client side.
    - Honour PINENTRY_USER_DATA="USE_CURSES=1" environment variable.
    - Support for "panic" commands.

* 2015-02-05 - ``v1.0b1``
    - Graceful server shutdown on SIGTERM.
    - Custom Pinentry to support passphrase input over a network.
    - Updated timeout defaults to make them compatible with passphrase
      input.
    - Code cleanup.

* 2015-01-27 - ``v0.9b2``
    - Fixed ``--output -`` case.
    - Versioned protocol.
    - Config parser updates.
    - More unittest coverage.
    - ``README`` file updates.

* 2015-01-23 - ``v0.9b1``
    - First beta release.


License
-------

See ``COPYING``.


Author
------

Vlad "SATtva" Miller

sattva@vladmiller.info

http://vladmiller.info

``0x8443620A``