en | Technical | KeePassRPC detail

Chris Tomlinson edited this page Oct 18, 2016 · 6 revisions
Clone this wiki locally

KeePassRPC technical detail

Introduction

In order to transfer passwords to and from KeePass, KeeFox needs to install a KeePass plugin called KeePassRPC. This plugin can communicate with other applications (not just KeeFox) provided that you allow it.

This document explains the new KeePassRPC protocol (used in KeePassRPC 1.3 and above) and the available options for adjusting the strength of the security to your needs.

This is a technical document which will sometimes use terminology that may be unfamiliar to non-technical users although we've tried to keep it as simple as we can and provide easy definitions and references for the more technical parts of this section. If you want a non-technical introduction, please read the KeePassRPC overview.

Note that the earlier version of the protocol is significantly different. There is a discussion of its security here. The KeePassRPC plugin will continue to accept connections from the earlier version for at least a year in order to facilitate a seamless upgrade from earlier versions of the plugin (and hence earlier versions of KeeFox).

Notable technical terminology used in this section of the manual

Client: The application that wants to connect to KeePass to access the passwords stored in your database. An example of a client is the KeeFox Firefox add-on

Server: The KeePassRPC plugin which allows clients to connect to it (provided that they obey the protocol described below)

RPC - Remote Procedure Call: This is a communication process that allows a computer program to cause an action to execute in another program.

Side note: Technically, KeePassRPC is closer to generic IPC than RPC due to the current limitation of working on only a single computer but the use of the "JSON-RPC" protocol and a networking stack that is capable of remote execution means that it's probably not a huge stretch to use the term RPC.

Protocol overview

The KeePassRPC protocol encapsulates 3 sub-protocols into one but because these sub-protocols have been modified slightly to improve performance and end-user experience I'll describe the entire process here.

Sitting on top of everything is a wrapper that indicates which one of the sub-protocols should be used.

The KeePassRPC protocol can be in one of 3 modes: "setup", "jsonrpc" and "error". While "jsonrpc" is a close match to the standard json-rpc protocol, the "setup" protocol can be based upon either an SRP or shared key mutual authentication protocol, depending upon the status of any currently saved encryption key.

The normal lifetime of the KPRPC protocol covers the entire time from initial connection to transport disconnection (such as when either the client or server are closed down); it starts with a "setup" stage and then moves to "jsonrpc" for the remainder of the connection lifetime (with some notable exceptions) as described below:

1a) "setup": Initial connection establishment through a slightly modified SRP negotiation resulting in a shared secret encryption key.

1b) "setup": Occurs when establishing a new connection between a previously authorised client and server pair. This ensures that both client and server know that they are communicating with each other using the shared key that was established when the user first authorised a connection in stage 1a.

2) "jsonrpc": AES encrypted data flows back and forth as required by the client (e.g. sending the password for a KeePass entry for automatic filling into a web page form)

Step 1a and 1b are differentiated by the presence of a "srp" or "key" object respectively.

The protocol type of "error" is reserved to indicate that the message contains error details that explain why the previous request was not successful. Note that errors specific to the SRP negotiation and mutual shared key handshake protocols are handled as part of the "setup" protocol in order to minimise deviation from the standard SRP and basic shared key mutual assurance protocol specification.

Any error during normal operation which could compromise the security of the communications channel will trigger a full re-authentication via stage 1a (essentially requiring the user to authorise a fresh connection).

All messages are structured in JSON as per the following specification-by-example.

{
    // the sub-protocol being encapsulated in this message
    protocol: "setup", // or "jsonrpc" or "error"

    jsonrpc: // null unless protocol is "jsonrpc"
    {
        message: "string", // The base64 encoded AES encrypted JSONRPC data goes here
        iv: "string", // The base64 encoded AES initialisation vector
        hmac: "string" // The base64 encoded authentication code
    },

    srp: // null unless protocol is "setup" and no valid shared encryption key already exists
    {
        // SRP data goes here
    },

    key: // null unless protocol is "setup" and a shared encryption key already exists
    {
        // Key auth data goes here
    },

    version: 123456789, // the version of the KeePassRPC client or server application
                        // (depending on whether it's the client or server sending
                        // this message)

    // version numbers are the 32 bit representation of 3 separate unsigned bytes
    // which represent the three most significant standard .NET version parameters
    // and are described in the manual section about versions:
    // https://github.com/luckyrat/KeeFox/wiki/en-|-Technical#version-numbering

    clientTypeId: "keefox", // this allows KP to identify which type of client is
                            // making this request. We can only trust it once the
                            // authentication process has completed

    // The strings below allow KeePass to present identity information to the user
    // about which client is making this request. We can only trust it once the
    // authentication process has completed and must therefore caution the user
    // of this limitation. It can be localised by the client in order to display
    // different information to users based upon their client locale.

    clientDisplayName: "KeeFox", // In practice this value won't change across
                                 // languages for KeeFox but maybe some other
                                 // KPRPC clients might want that functionality

    clientDisplayDescription": "KeeFox is a Firefox add-on...",

    error:
    {
        // error code and message from server (e.g. authentication failed, etc.)
        code: 1,
        messageParams: [ "array of strings", "containing specific details" ]
    }
}

Stage 1a

Stage 1a is an SRP negotiation with a small tweak: the password is randomly selected by the server and presented to the user, rather than being chosen by the user in an initial registration phase. The user then acts as a side-channel to securely supply the password to the client.

We use SRP because of its desirable security properties and patent-free design. In this initial protocol design we have no need for the server to persist the computed password hash. A few minor changes will be required if we want to persist the password for any reason in future.

SRP allows the user to enter a relatively weak password to authenticate the link between the KeePassRPC client and server while protecting this password from MITM attacks and brute force guessing.

For background information it may be worth looking at the protocol currently on wikipedia but note that our implementation has some subtle differences - this mostly stems from the areas of the official SRP protocol that are a little under-specified.

With reference to the SRP-6a protocol, the main steps are:

  1. User -> Host: I, A = g^a (identifies self, a = random number)
  2. Host -> User: s, B = kv + g^b (sends salt, b = random number)

  3. Both: u = H(A, B)

  4. User: x = H(s, p) (user enters password)

  5. User: S = (B - kg^x) ^ (a + ux) (computes session key)
  6. User: K = H(S)

  7. Host: S = (Av^u) ^ b (computes session key)

  8. Host: K = H(S)

  9. User -> Host: M = H(A, B, S)*

  10. Host -> User: M2 = H(A, M, S)*

* These steps are unspecified so I've used an implementation that I think works and allows one hash operation to be delayed until after mutually authenticated (DOS protection).

The KeePassRPC implementation groups these steps together and arranges them as indicated below.

  1. json.js:setup() - This client function = SRP step 1

    • The initilisation of the SRPc class on the client defines many of the variables that are needed in later steps.
      • k = H2(N,g) where H2 is based on SHA-1 as per the SRP demo.
    • Client also sends a "securityLevel" int to the server so that the server can reject the client if its security level is too low
  2. KeePassRPCClient.cs:SRPIdentifyToServer() - This server function = SRP step 2, 3 and 7

    • Same initialisation variables as on the client are setup here
    • Server also sends a "securityLevel" int to the client so that the client can reject the server if its security level is too low
    • The server chooses and displays the password at this stage, along with unverified information about the client to aid the user.
  3. identifyToClient() - This client function = SRP step 3, 4, 5 and ends with 9

    • Also calculates M1 and M2 to save a network roundtrip.
  4. KeePassRPCClient.cs:SRPProofToServer() - This server function = SRP step 10

  5. SRP steps 6 and 8 are only calculated when the respective parties first try to use the shared key (e.g. for encryption) - we're less susceptible to DOS attack this way.

H() is SHA-256

The password verifier (v) is also SHA-256 with no PBKDF applied (not needed since we use SRP to establish and store a secret key rather than store the hash).

Note that due to complexities with representing case-sensitive variable names and large integer values through the JSON transport we are forced to occasionally use variants of the notation described in the protocol design but this should have no significant effect on the behaviour of the implementation provided that consistency between client and server data representation is maintained.

Stage 1b

The stage 1b protocol is an example of mutual authentication. It's a simple protocol, almost identical to the example protocol on this challenge-response authentication page.

  1. Client sends a username and declares its current security level to the Server
  2. Server sends a unique challenge value sc to the client
  3. Client generates unique challenge value cc
  4. Client computes cr = hash("1" + secret + sc + cc)
  5. Client sends cr and cc to the server
  6. Server calculates the expected value of cr and ensures the client responded correctly
  7. Server computes sr = hash("0" + secret + sc + cc)
  8. Server sends sr
  9. Client calculates the expected value of sr and ensures the server responded correctly

where

  • sc is the server generated challenge
  • cc is the client generated challenge
  • cr is the client response
  • sr is the server response
  • secret is the key that is shared between client and server (server picks from multiple client keys via the client-supplied username)
  • hash is SHA-256

We also check the security level required by client and server just in case the levels have been changed (if they have changed, we'll go into a full SRP re-authentication procedure as per stage 1a above)

Stage 2

The stage 2 protocol is JSON-RPC. As mentioned in the example JSON message above, we encrypt the contents of a standard JSON-RPC message using AES and deliver the cipher text, IV and HMAC. Each JSON-RPC message is encrypted using a different IV and we calculate the HMAC of the cipher text + IV.

SHA-1 is deemed sufficient for the HMAC algorithm due to the difficulty of constructing a collision within the lifespan of a message travelling between client and server. While SHA-256 would offer greater assurance against unexpected future breaks in the hash algorithm, the extra assurance offered is not currently sufficient to warrant the extra computational overhead of SHA-256.

Errors

1) Setup errors: These are usually problems with the security protocols being used to establish a connection (e.g. user entered the wrong password)

Connection security

There are a few security options for each end of the connection. Naturally you should select the highest security level that you can but there are lower security options available for those who can't justify the extra time required to use the higher security options.

We strongly recommend NEVER using the low security options unless it's the only way that you can get your computer to establish a connection between KeeFox and KeePassRPC (if that is the case you should ask for help on the forum and be prepared to re-install some faulty parts of your computer).

The table below explains where the secret key is stored.

KeeFox (Firefox) KeePassRPC (KeePass)
High Not stored Not stored*
Medium Firefox password storage file (may be unencrypted)** KeePass config file (Windows users: encrypted - strength depends on your windows logon password; Linux/Mac users: protected only by standard filesystem permission ACLs)
Low about:config (unencrypted) KeePass config file (unencrypted)

* A new password is randomly generated every time KeeFox connects to KeePassRPC - possibly too intrusive for many users but you might decide that the extra security is worth the added inconvenience in your situation.

** This storage location is not encrypted by default - you can enable the Firefox master password feature to increase security.

The username is a randomly generated UUID which is stored in Firefox about:config unless high security mode is enabled (in which case it is never stored because we might as well use a new username each time alongside the new password).

Note that security levels can be spoofed so they should be used as a convenience for the end-user rather than a guaranteed contract.

Key expiration

You can set an expiry time for the secret key that KeeFox uses.

The "Authorisation expiry time" configures the maximum time that your authorisation (via the random password) with KeePassRPC will be valid. Set a value in hours from 1 to 43800 (5 years). The default setting is 8760 (1 year). Set this value in the KeePassRPC options window.

Transport protocol

The KeePassRPC protocol does not specify the use of a particular transport but it will only work if the transport layer meets a few requirements.

The connection must be:

  1. reliable
  2. ordered
  3. error-checked
  4. bi-directional

The current implementation of the KeePassRPC plugin uses web sockets which benefit from the same widespread support and easy addressing of HTTP but with the additional bi-directional communication feature.

An unsecured web socket channel is used because the nature of having two open source applications establish a connection prevents the use of secure asynchronous certificates (unless we want to ask the user to create and pay for valid certificates).

An alternative transport protocol could involve establishing connection security at the transport layer but it is un-necessary.

Encapsulation format

The protocol transfers messages in the JSON format. There are lots of reasons for that and I can't foresee it being a problem but it is theoretically possible to use an alternative encapsulation method - please get in touch to discuss before implementing an alternative because it will take a bit of effort for us to ensure that multiple message encapsulation methods can work side-by-side. In practice, I expect that the benefits of different encapsulation formats will only be realised alongside the use of an alternative transport protocol.

Upgrading from KeePassRPC < 1.3

When a client that has previously offered support for KeePassRPC 1.2 or lower wants to upgrade to using v1.3 it needs to follow this protocol:

  1. Client tries to connect to the server using the v1.3 protocol
  2. The connection attempt will fail if the version of KeePassRPC is less than 1.3. If the connection succeeds, the v1.3 protocol will kick in as described above but otherwise continue with this upgrade protocol
  3. Client tries to connect to the server using the v1.2 protocol
  4. The server sees that the client version is higher than the version it will accept so it sends a rejection message to the client
  5. Client receives rejection message and prompts user to install the new version of KeePassRPC

Using KeePassRPC with different clients

If you want to write a client to communicate with KeePass, you can probably use KeePassRPC with no modifications - just bundle the plgx file from the latest version of the KeeFox add-on.

If you need some extra functionality (e.g. deleting entries from a remote client) you can fork KeeFox and make changes to the KeePassRPC plugin code. If you're going to make the plugin available to anyone other than yourself, please make sure to get in touch (e.g. via a pull request) to find out whether we'd like to incorporate your work into the official KeePassRPC plugin. Don't worry if it doesn't fit with our plans though - you'll just need to change a few lines to ensure your plugin gets given unique identification information to avoid possible version conflicts in future (e.g. if someone uses your client and KeeFox at the same time).

Source code

Server source code is contained within the KeePassRPC folder

Client source code can be seen in several KeeFox addon files. jsonrpcClient is an object that contains everything needed to pass data between KeeFox and KPRPC; it is defined through a chain of prototype extensions contained within several separate files.

While the interaction points between these different files are unspecified, in practice there are only a few (fairly logical) places where the different layers interact; if desirable, we would consider further specification and/or refactoring work to produce more clarity in this area. The files involved in the definition of jsonrpcClient are:

  • jsonrpcClient.js
  • kprpcClient.js
  • kprpcClientLegacy.js
  • session.js
  • sessionLegacy.js

All files are JavaScript modules (often denoted with a .jsm extension but Visual Studio doesn't understand that so we're stuck with .js)

TODO:? include table listing imports? sjcl.js, biginteger.js, SRP.js, utils.js