KeePassRPC technical detail

Introduction

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

This document explains the KeePassRPC protocol. It 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 Connection Overview documentation instead.

Notable technical terminology used in this topic

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

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:

  1. “setup”
    a. Initial connection establishment through a slightly modified SRP negotiation resulting in a shared secret encryption key.
    b. 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.
  1. “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. kprpcClient.js:setupWebsocketSession() - 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 password stretching 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)

Key expiration

You can set an expiry time for the secret key that the client 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.

If you need some extra functionality (e.g. deleting entries from a remote client) you can fork Kee 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 Kee at the same time).

Hi Chris,
I would like to know how the web identification process (URL) and auto-fill of forms data is carried out. Is there a GET with all known URLs and then it compares with visited URL?
My concern is if a site could present another domain name in the form and kee will fill in that information.
In the first instance, I will disable the autofill. But I would like to know the process to know if my concern have fundamentals.

Thank you for your work!

Sebastián

Hello,
Where can I find information about the contents of the JSON-RPC messages to get passwords? I’m trying to write a custom client. Also, is there any sample C# code for a custom client to get me started? Thanks in advance.

Take a look at the methods with a JsonRpcMethod attribute in KeePassRPCService.cs.

There isn’t any specific documentation about those methods or the data types they currently use but you can check out the full details in the keepassrpc source code and see an example of the methods being called by looking at the browser-extension source code (Kee).

Also worth reading Issues related to the creation of new clients : Extending JsonRPC DB handling capabilities. · Issue #134 · kee-org/keepassrpc · GitHub in case your use case also requires some adjustment to the available JSON-RPC methods or DTOs.

I’ve been working on New Entry configuration format · Issue #101 · kee-org/keepassrpc · GitHub for a while now but still have some work to do before I can take that updated configuration format into the main version of KeePassRPC:

  • Implement the HTML type and DOM Selector fields in the form field editor dialog.
  • Bump the version number to 2.0 since there is at least one breaking change as a result of the work.
  • Repeat the implementation in Kee Vault.
  • Thoroughly test the interaction with Kee, including a future version that understands the new configuration format directly (avoiding the need to translate from the new configuration format to the old one current clients understand).

The ongoing progress for the KeePass plugin part of this work is currently at GitHub - kee-org/keepassrpc at feature/entry-model-2023 - albeit in a potentially broken and unfinished state. I might make a bit of progress over the next month but think that March 2024 is a more realistic time frame for the work to be getting close to beta-testing.

Let us know how you get on and whether you can work with the existing protocol or want to suggest changes for incorporation in KeePassRPC v2.