There is a complete implementation of this protocol in libpEpAdapter


  1. use .add() in config_passphrase() to add configured passphrases to the cache
  2. use .add_stored() in config_passphrase_for_new_keys() to add the stored passphrase to the cache
  3. call API functions like encrypt_message() or decrypt_message() using .api()

Sample for implementing config_passphrase()

status = ::config_passphrase(session(), passphrase_cache.add(passphrase));

Sample for implementing config_passphrase_for_new_keys()

status = config_passphrase_for_new_keys(session(), enable, passphrase_cache.add_stored(passphrase));

Sample for calling encrypt_message()

status = passphrase_cache.api(::encrypt_message, session(), src, extra, dst, enc_format, flags);

This is calling encrypt_message() first with an empty passphrase and then for each passphrase in the cache until one passphrase works or the cache is running out of passphrases.


Passphrases are Unicode Strings with a maximum size of 250 Code Points.

p≡p API

Passphrases are delivered by the application using config_passphrase(passphrase). These calls from the application feed the Passphrase cache.

The application has the option to enable passphrases for newly generated keys. The API call for this is config_passphrase_for_new_keys(). In case it does the passphrase MAY be added to the cache.

Passphrase cache

A p≡p adapter must implement a passphrase cache for 20 passphrases. When the 21th passphrase arrives it is replacing the 1st one.

Retry API calls with different passphrases

An API call is tried with an empty passphrase first. Then it is tried for all not expired passphrases in the cache in sequence from newest to oldest.

The API calls to intercept are all calls to sign, the calls to encrypt (because this is always implying signing) and the calls to decrypt. Additionally, any API call that may modify the key material itself can trigger the need for a passphrase. Because it is not the adapter’s nor the app’s job to determine which functions here may be impacted (and these may indeed change should we start supporting other key types), we now instead presume that any function which requires an extant session, with the exception of release() and the config_*() functions.

Thus, it is easier to make a list of the API functions which do not require this support:

  • init()
  • release()

All config_*() functions do not, too:

  • config_passive_mode()
  • config_unencrypted_subject()
  • config_use_only_own_private_keys()
  • config_service_log()
  • config_cipher_suite()
  • config_passphrase()
  • config_passphrase_for_new_keys()

And this function is an exception, do not wrap it either:

  • outgoing_message_rating_preview()

Any other function which has a session as its argument should be wrapped appropriately.

ensure_passphrase callback

N.B. Krista has no idea exactly what information is needed here or what the adapter is supposed to do, though libpEpAdapter already has some implementation for this - consider this an information dump as Volker requested from the engine standpoint, and someone else can rerwrite as necessary

Sometimes the engine cannot exit from a call and ask the adapter/app to fix the passphrase and try again. This is specifically a problem when we iterate internally in the engine over a list of keys that may need to sign something (usually a key renewal or revocation) and need to make sure we can decrypt the key material for every key to complete the iteration, and there is the possibility that we have multiple disjoint passphrases required to do so.

So instead we require a callback from the caller that will ensure, upon return, that in the session it is called for, the passphrase is correct for the key it is called on, or that we get the appropriate PEP_*PASSPHRASE* error if the adapter/app are unable to resolve it.

The callback, from pEpEngine.h, looks like this (Volker has apparently put an implementation of this into libpEpAdapter, but I don’t know how this impacts other adapters, so I’ll put this here anyway):

// ensure_passphrase() - callee ensures correct password for (signing) key is configured in the session on
//                       return, or returns error when it is not found
//  parameters:
//.     session (in)      session for which the guarantee is made
//      fpr (in)          fpr to check
//  return value:
//      PEP_STATUS_OK passphrase is configured and ready to use
//      If the caller runs out of passphrases to try, PEP_*PASSWORD* errors 
//      are acceptable.
//.     Other errors if, e.g., the key is not found
//  caveat:
//      The callee is responsible for iterating through passwords
//      to ensure signing/encryption can occur successfully. 
typedef PEP_STATUS (*ensure_passphrase_t)(PEP_SESSION session, const char* fpr);

It is passed in as the 4th argument to init() (see pEpEngine header) all sessions, unless you can guarantee you never need passphrases or that there is only one passphrase and it is always set correctly.

The following is the list of API calls which call (or whose call graph calls) this callback:

  • key_reset()
  • key_reset_own_grouped_keys()
  • key_reset_user()
  • key_reset_identity()
  • disable_identity_for_sync()
  • decrypt_message()
  • get_message_trustwords()
  • own_message_private_key_details()

Also note (this is mostly to Volker) that the sync calls resetOwnGroupedKeys() and resetOwnKeysUngrouped() are impacted. I don’t know how the adapters are supposed to handle that.

Cache expiry

Passphrases expire. The expiry time is compile-time configurable. The default is 10 minutes after last successful use on an API call.


When a passphrase is required but no passphrase is known yet (empty cache, call with empty passphrase fails) a p≡p API call returns PEP_PASSPHRASE_REQUIRED.

When a passphrase is required but all provided passphrases in the cache are wrong a p≡p API call returns PEP_WRONG_PASSPHRASE.

See also