phase 1 – super simple

Phase 1 is something basic and functional. It uses super simple discovery via https from other LEAP providers. For legacy, we attach the public key for outgoing emails.

  • strict rules for keys (“transitional key validation” document)
  • the provider is not signing keys
  • keys are not uploaded to key server
  • client looks up all public keys via nickserver on the provider
  • include openpgp header with url. only urls on the same domain are allowed.
  • key refreshing: if the email itself has a new key (via header or attachment), it will refresh. but not otherwise.
  • attach the sender’s public key to the email if other party hasn’t sent encrypted message.

Phase 1.5 – slight improvement

  • use email signatures for detecting sender key fingerprint
    • when dkim header is present for “From”, e.g. let server validate
    • then query on this fingerprint to get the key.

Phase 2 – proxy requests and keyservers

A much better system, although one that still relies on x.509. This phase allows for proxy requests to look up and refresh keys. This allows for the possibility of easy network perspective auditing of the user’s key by the key manager. Also, instead of attaching public key to outgoing emails, we will use legacy keyservers. The openpgp header sucks, we will come up with something better for phase 2.

  • run our own testing keyservers disconnected from network
  • user public keys uploaded to openpgp keyservers
  • stop attaching keys to outgoing email
  • allow client to proxy a request through any other nicknym server.
    • every proxy attempt includes a longish timeout (which require nickserver and key manager be written async).
  • a better header
    • x-public-key-validation: nicknym; webfinger
    • e.g. a header that just lists well defined protocols
  • network perspective via proxy nicknym requests
  • a proper key refreshing system, privacy preserving.
  • key manager correctly handles forward signing of new keys for both user and provider keys.

Phase 3 – signed responses

Phase 3 moves away from reliance on trusting x.509.

  • provider endorsement keys:
    • providers have endorsement keys
    • should be multiple endorsement keys. in case one is lost backup key can be used.
    • provider’s endorsement key signs all user keys.
  • key manager supports regular rotation of user’s keys
  • the response to every request is signed, especially 404 responses.
    these signatures are not with openpgp keys, but something very fast and appropriate for real time use, like a ed25519 signature.

Phase 4 – the future


phase 1

  • attaching public key might be a very weird user experience for people. what is this suspicious looking file? what name should we give the attachment? “email-encryption-key-for-user@domain.asc”
  • use of OpenPGP header is an email bug that allows the sender to track when the recipient opened the message. We plan to discontinue use of OpenPGP header shortly.
  • use of OpenPGP header allows for a user to publish a key for someone else on the system

phase 2

  • proxy requests open a big hole for the possibility of DDoS. to migitate this, there should be a long timeout for all proxy requests.
  • Tor or some high latency protocol would be better than a single one hop proxy.
  • not sure when we should disable support for openpgp header
  • not sure who will be responsible for uploading user keys to keyservers, the provider or the client.

Security Concerns

  • The length of the response leaks data about which key is being returned.

Nicknym API v2

Goal: Support for a minimal viable solution, where two users on LEAP providers always send encrypted email to each other all the time. This includes support for renewing expiring keys, and updating keys. It does not yet include support for provider endorsements of keys. So, this is still a pure TOFU model where a lot of trust is placed on the user’s provider.


  • Check https://domain/provider.json
    • If and only if that request returns a 200 does the provider support nicknym. For now, how about nickserver keeps in memory a list of which providers do not support nicknym. When restarted, it will then need to probe provider.json again.
    • For some domains, perhaps we can hard code them in the nickserver config.
    • For nickserver URL, parse the JSON and use key nicknym_uri
  • Require HTTPS
  • Only GET supported


  • address=EMAIL_ADDRESS
  • fingerprint=FULL_FINGERPRINT


LEAP providers currently are required to provide a /provider.json at the root of their domain. This is a little complicated, since the main domain for a provider might not be controlled at all by the LEAP platform. So, we generate a provider.json for them that they can copy in place, if needed. Because of this, we cannot require https://domain/.well-known/nicknym for the nickserver URL. Maybe some day, but for now it makes everything much easier if we only require providers to put the provider.json file on the domain.

Nickserver Logic

address requests

if domain in address matches nickserver's domain
  return authoritative response
  proxy request to foreign nickserver

fingerprint requests

if fingerprint is in database of user keys
  return authoritative response
  proxy request to SKS keyservers

Keymanager Logic

key update logic

query own nickserver by fingerprint
if result and key still has the right uid and fingerprint
  update key
  query own nickserver by address
  if result
    if key fingerprint is unchanged
      update key
      apply validation rules to decide what to do with this new key

private key expiry

The key manager needs to extend the expiration of the user’s key in a timely manner, to minimize the possibility that the user has been offline and let their key lapse.

Updating the key leaks the information the user was running bitmask on the given time. So too many updates may deliver a detailed profile of what days the user is logging in.

The expiration of a key is stored in the self signature of that key. Changing the expiry does not increase the size of the key. You can pick whatever expiry you want, but doing so does update the signature on the key which includes the exact timestamp of when the signature was made (including timezone, it appears). Perhaps another reason to plan to migrate off gpg. Here is the pgpdump output:

Old: Signature Packet(tag 2)(317 bytes)
	Ver 4 - new
	Sig type - Positive certification of a User ID and Public Key packet(0x13).
	Hashed Sub: signature creation time(sub 2)(4 bytes)
		Time - Thu Aug 25 13:49:17 PDT 2016
	Hashed Sub: key expiration time(sub 9)(4 bytes)
		Time - Thu Sep 15 13:49:13 PDT 2016

Conclusions drawn from doing the following:

gpg --edit-key KEY expire
apt install pgpdump
gpg --export KEY | pgpdump
gpg --export KEY | wc --bytes


  • NEW_KEY_EXPIRY=6 months
  • UPDATE_KEY_EXPIRY=3 months
  • new keys have an expiration date 6 months in the future
  • extend the key expiry by 3 months, 2 month before it expires
  • extend the key expiry immediately if we detect it has already expired
  • upload the key using the same mechanism we use for the original key upload
  • the expiry should be updated on both the primary signing key and the encryption subkey

Response codes

  • 200 – here is the key, signed with an endorsing key of the provider.
  • 304 – not modified, for refreshing a key given a fingerprint or (email address + if-modified-since)
  • 400 – error processing request
  • 404 – user or key never existed
  • 409 – the user exists, but the key has not yet been endorsed
  • 410 – user or key no longer exists
  • 500 – unknown internal problem
  • 502 – bad gateway. proxy request failed.
  • 504 – gateway timeout. proxy request timed out.


  • No provider endorsement support yet.
  • The length of the response leaks data about which key is being returned.
  • Keymanager will be repeatedly downloading the same keys over and over just to check if there is a change. Perhaps in the future the nickserver can return a hash of the entire key, so that keymanager can check if there are changes without downloading the whole thing. This makes more sense if we support a batching mode.
  • Updates will be slow
  • Updates will leak metadata, both to the user’s provider and to a network observer.

Nicknym API v3


  • JSON responses. I think we must support multiple keys, and ability to fetch old keys. Need to specify a better JSON format that supports this.
  • Signed responses: For keys that are endorsed, a signature from a long term OpenPGP provider key seems fine (likely on a separate machine). However, when a query results in a miss and there is no record, the provider should return a signed response to the actual query. I sure don’t want to do this with a long RSA key. Maybe a temporary 25529 key for signing ‘not found’ replies… I don’t like all the heavyweight stuff that DNSSEC imposes.

Abuse prevention

If we allow proxied requests, how do we prevent anyone from using the nickserver in an amplification attack?

  • Rate limit how often a particular IP may make a request?

Provider keys

Nicknym relies on provider endorsement of keys, but how do we know what provider key to trust?

For now: TOFU with permanent public key pinning

  • Fetch key over HTTPS, relying on valid CA signed cert.
  • Store this key forever for that provider.

Options for the future:

  • A central trust root, perhaps distributed with the bitmask client, in the form of the keys of all the known nicknym providers.
  • Actual public key pinning, where we support backup keys

Capability detection

A domain supports nicknym if:

  • GET https://domain/.well-known/key-discovery return status 200
  • The response contains the line “nicknym” (maybe don’t bother checking this for now)
  • For now, nickserver should store in memory if .well-known is supported by foreign domain and don’t try again if we know it is not supported. When nickserver is restarted, it will then need to check again.
  • TODO: a better system of retry

The reason to use a different URL for detecting if nicknym is supported or not is that we don’t want to be fooled into thinking the provider doesn’t support nicknym just because the nickserver is malfunctioning or is down. By use .well-known/key-discovery we can make it a static file. Also, maybe we can add other key discovery methods in the future.

The reason to not use DNS or a different port or a different subdomain is that we don’t want to leak to a network observer what we are doing.

More on /.well-known/


other provider is leap provider.

Sending first email.

  1. lookup key by username from other provider

Key expired => update => nothing changed

  1. get key update from other provider, note nothing changed
  2. stop using key ?
  3. send unencrypted email ? maybe not

key expired => update => expiry prolonged

  1. get key update from other provider, notice expiry update
  2. update the key
  3. keep using the key

key revoked => no new key available

  1. lookup if new key is available
  2. if not, send unencrypted

key revoked => new key available

  1. lookup if new key is available
  2. if so, save new key
  3. send encrypted to new key

provider’s signature on key has been revoked

  1. record that provider endorsement has been revoked
  2. only singed by previous key is accepted ??? maybe not
  3. in case of lost account you need to wait for the expiration date

key unchanged => new key available

  1. do nothing, we always use existing key unless it expires or is revoked, or user manually verifies fingerprint on new key.

other provider is non-leap provider

Sending first email

  1. failing lookup key by username from other provider
  2. send unencrypted

Key expired => update => nothing changed

  1. get key update from sks, note nothing changed
  2. stop using key ?

key expired => update => expiry prolonged

  1. check key for updates
  2. update key if expiry has extended
  3. keep using key

key revoked => no new key available

  1. check key for updates
  2. if revoked, check to see if a new key is available
  3. if we can find any key that is signed by the revoked key, then register that new key.
  4. otherwise, disable the revoked key
  5. are their any keys available?
  6. if not, send unencrypted

key revoked => new key available

  1. check key for updates
  2. if revoked, check to see if a new key is available
  3. if we can find any key that is signed by the revoked key, then register that new key.
  4. otherwise, disable the revoked key
  5. are their any keys available?
  6. if yes, send encrypted

key unchanged => new key available => signed by previous key

  1. do nothing, we always keep the prior key unless expired, revoked, or superseded by manual fingerprint verification.