Internet-Draft E2EE Messaging Private User Discovery October 2023
Hogben & Olumofin Expires 12 April 2024 [Page]
Workgroup:
More Instant Messaging Interoperability (mimi)
Internet-Draft:
draft-party-mimi-user-private-discovery-02
Published:
Intended Status:
Informational
Expires:
Authors:
G. Hogben
Google
F. Olumofin
Google

Interoperable Private Identity Discovery for E2EE Messaging

Abstract

This document specifies how users can find each other privately when using end-to-end encrypted messaging services. Users can retrieve the key materials and message delivery endpoints of other users without revealing their social graphs to the key material service hosts. Users can search for phone numbers or user IDs, either individually or in batches, using private information retrieval (PIR). Our specification is based on a state-of-the-art lattice-based homomorphic PIR scheme, which provides a reasonable tradeoff between privacy and cost in a keyword-based sparse PIR setting.

About This Document

This note is to be removed before publishing as an RFC.

The latest revision of this draft can be found at https://datatracker.ietf.org/doc/giles-interop-user-private-discovery/. Status information for this document may be found at https://datatracker.ietf.org/doc/draft-party-mimi-user-private-discovery/.

Discussion of this document takes place on the mimi Working Group mailing list (mailto:mimi@ietf.org), which is archived at https://mailarchive.ietf.org/arch/browse/mimi/. Subscribe at https://www.ietf.org/mailman/listinfo/mimi/.

Source for this draft and an issue tracker can be found at https://github.com/femigolu/giles-interop-user-private-discovery.

Status of This Memo

This Internet-Draft is submitted in full conformance with the provisions of BCP 78 and BCP 79.

Internet-Drafts are working documents of the Internet Engineering Task Force (IETF). Note that other groups may also distribute working documents as Internet-Drafts. The list of current Internet-Drafts is at https://datatracker.ietf.org/drafts/current/.

Internet-Drafts are draft documents valid for a maximum of six months and may be updated, replaced, or obsoleted by other documents at any time. It is inappropriate to use Internet-Drafts as reference material or to cite them other than as "work in progress."

This Internet-Draft will expire on 12 April 2024.

Table of Contents

1. Terminology

The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in BCP 14 [RFC2119] [RFC8174] when, and only when, they appear in all capitals, as shown here.

Glossary of terms:

2. Introduction

Outline of design for message delivery bridge and key distribution server discovery mechanisms for interoperable E2EE messaging clients. A DNS-like resolver service stores UserID <-> service pairs that map to key distribution and message delivery endpoints (e.g. Platform1 Bridges). Each service is responsible for an "authoritative name server" that covers its own users and this can be replicated/cached by other providers and retrieved by sender clients using a privacy preserving protocol to prevent social graph leakage.

2.1. Functional Requirements

For a given messaging service identity handle (phone number or alphanumeric UserID):

  1. Endpoint discovery: discover receiver service IDs to retrieve public key material and send message payload e.g. Platform1.org/send/, Platform1.org/kds

  2. Default service discovery: Discover optional default receiver service ID user preference for a given PN/UserID (e.g. default:Platform1.org)

  3. Global uniqueness: Fully-qualified service identifiers should be globally unique

  4. Key verification (P1): Provide an independently trusted party to assert and verify the association between a public key and a UserID

2.2. Privacy Requirements

  1. Social graph: Resolver or discovery services should not learn the PN/UserID a client is querying for (i.e. who is sending a message to who)

  2. Querying user identity: A resolver service should not default to sharing the querying user identity with other resolver services when it requires their help for discovery

  3. Metadata: Resolver service should not learn the exact timing of when a message is sent

2.3. Other Non-functional Requirements

  1. No single entity should be financially responsible for resolving all identity queries (e.g. even within a geographical region)

  2. Costs for each participating entity of storing and querying key records should be proportional to their number of participating users

  3. Performance should support each client querying each of their contacts at least once every 24 hours

2.4. Non-requirements

  1. Hiding service reachability: This is the link between a UserID and a service. All major E2EE messaging services already publish unACL'd reachability information without opt-out i.e. +16501234567, reachable on Messages, Whatsapp, Telegram (not including name or any other info). Therefore this should not be a goal (and would not be feasible to implement).

  2. Hiding the value of UserIDs or public keys: e.g. the existence of the PN, +16501234567

  3. Hiding the association between a public key and a UserID: e.g. PN +16501234567 has pubkey x

  4. Contact lookup by name (or anything except username)

3. Proposed solution

3.1. Key distribution

P1 Client P1 Client P1 Front End P1 Front End P1 Name Server P1 Name Server Authoritative P2 Name Server Authoritative P2 Name Server P2 KDS P2 KDS P2 Client P2 Client Request P2 Name Records Replicate P2 Name Records PIR Query PN/UserID PIR Query PN/UserID Supported service IDs + default service Service IDs & default service Query PN/UserID Return Public Key Bundle (Signed by provider or 3P CA) Return Public Key Bundle Encrypt Message Send Encrypted Message via messaging providers

Taking Platform1 client sending to a Platform2 user as an example:

  1. Platform1 name server replicates authoritative Platform2 NS records. There will need to be a shared list of participating services and name server endpoints.

  2. Platform1 client sends key bundle request to Platform1 front end (PIR encrypted PN/UserID)

  3. Platform1 FE gets supported key distribution service IDs, version number + default service=Platform2 via PIR protocol from its own name server.

  4. Platform1 FE queries Platform2 KDS to retrieve public keys.

  • 4.1 Platform1 Client first sends (query and session key) encrypted with Platform2 public key to Platform1 FE.

  • 4.2 Platform1 FE sends encrypted query to Platform2 KDS

  • 4.3 Platform2 KDS decrypts query and session key, encrypts response with session key

  • 4.4 Platform2 KDS sends encrypted response to Platform1 FE

  • 4.5 Platform1 FE forwards to Platform1 client

  1. Platform 1 Client and Platform 2 Client exchange messages through their respective messaging providers.

This provides E2EE interop while only disclosing to gateway service which services a phone number is registered to. In all other respects, gateway services learn no more information than in the non-interop case.

3.2. Resolver registration

Each service is responsible for registering user enrollments with the resolver.

3.3. Preferred service integrity

While the preferred service is public, the user should control its value/integrity. As well as ensuring user control, it also prevents spoofing attacks where an attacker A could create an account on a new service that B does not have an account on, and then set it to B's preferred service (see cross-service identity spoofing below). Therefore to prevent anyone but the user modifying the default service value, records must be signed with the user's private key and verified by the sender against their public key. For multiple key pairs across different services, the last key pair to sign the default service bit must be used to change the default.

Client Client Service UI Service UI Resolver Resolver Register Preferred Service + Signature Query PN/UserID Return supported service IDs + default service preference + signature Verify default service pref signature

3.4. Cross-service identity spoofing

Today, a messaging service may support one or more ways of identifying a user including email address, phone number, or service specific user name.

Messaging interoperability introduces a new problem that traditionally has been resolvable at the service level: cross-service identity spoofing, where a user on a given E2EE may or may not be addressable at the same ID on another service due to a lack of global uniqueness constraints across providers.

As a result, a user may be registered at multiple services with the same handles, e.g. if Bob's email is bob@example.com and his phone number is 555-111-2222 and he is registered with Signal and iMessage, he would be addressable at bob@example.com:iMessage, 555-111-2222:iMessage, and 555-111-2222:Signal. In this case, the same userId on iMessage and Signal is acceptable as the phone number can map to only one individual who proves their identity by validating ownership of the SIM card.

On services where a user can log in with a username alone, however e.g. Threema and FooService, the challenge becomes:

  • Alice messages Bob at Bob's preferred service (bob@Threema)

  • Eve messages Alice impersonating Bob using bob@FooService

  • Alice needs some indicator or UI to know that bob@Threema isn't bob@FooSercice and that when bob@FooService messages, it should not be assumed that bob@FooService is bob@Threema.

Options for solving this are: 1. Storing the supported services for a contact in Contacts and if a recipient receives a message from an unknown sender, to treat it as spam or otherwise untrusted from the start. 2. Requiring the fully qualified username for services that rely on usernames only - e.g. bob@threema.com vs bob.

4. Privacy of resolver lookup queries

Resolver lookup queries leak the user's social graph - i.e. who is communicating with whom, since the IP address of the querying client can be tied to user identity, especially when operating over a mobile data network. Therefore we propose to use Private Information Retrieval (PIR) to perform the resolver queries. We have evaluated multiple alternative schemes. The proposed solution is based on the Public Key PIR framework by Patel et al[PIRFramework] with sharded databases. This framework is applicable with any standard PIR scheme such as the open source implementation here. Cost estimates suggest this is feasible even for very large resolver databases (10 billion records).

4.1. Proposed protocols

A private information retrieval protocol enables a client holding an index (or keyword) to retrieve the database record corresponding to that index from a remote server. PIR schemes have communication complexities sublinear in the database size and they provide access privacy for clients which precludes the server from being able to learn any information about either the query index or the record retrieved. A standard single-server PIR scheme provides clients with algorithms to generate a query and decrypt a response from the server. It also provides an algorithm for the server to compute a response.

The Public Key PIR framework [PIRFramework] can be wrapped around any standard lattice-based PIR scheme. This framework consists of server database setup, client key initialization, client query generation, server response computation, and client response decryption sub-protocols. All operations are over a set of integers with a plaintext modulus. The appendix provides the protocol messages in protocol buffer format.

4.1.1. Server database setup

  • Sharding: If the database is over 220 records, sub-divide it into shards of ~1 million unique records each, which is a good balance for privacy and costs. Performing PIR over the databases gives stronger privacy but is more costly. Similarly, running PIR queries over the shards is less costly but gives weaker privacy.

    • Sample a hash key Ks for sharding.

    • Using Ks, shard the large database of r records into ⌈r/220 shards based on the hash prefix of the record's unique identifier.

    • N.B. The hash key will be provided to clients to determine the shard to query.

  • Set partitioning boundaries for each shard D: Given a n key-value pairs shard D = {(k1,v1),...,(kn,vn)}, then

    • Compute the number of database partitions as b = n/d1. Where d1 is the desired size for each partition. A value of 128 for d1 works well.

    • Sample a partitioning boundary hash key K1 to generate a target partition for each shard key.

    • Compute the hash F1(K1,ki) for each record identifier ki.

    • Sort the hash values alphanumerically and then divide the list into b partitions P1,...,Pb.

    • Store the b partition boundaries beginning hash values B0, ..., Bb. Note that B0 = 0, and Bb = |U|-1 where U is the rage for F1(K1,ki).

    • N.B. The partition boundaries will be provided to clients and can be stored efficiently (e.g., ~11KB for n = 220, d1 = 128, |U| = 264).

  • Transform each shard: Sample two hash keys K2 and Kr where K2 will be used to generate a row vector within each partition, and Kr is used to generate a representation for the transformed database as F(Kr,ki)||v.

  • N.B. F(K,k) is the output value from hashing k with key K and || is a concatenation.

  • For each partition Pi

    • Construct a |Pi| x d1 Platform1 Mi by appending a random row vector from the bit vector derived from (F2(K2,k||1),...,F2(K2,k||d1)).

    • Construct a |Pi| vector yi by appending Fr(Kr,k)||v for each (k,v) in Pi.

    • Solve for ei that satisfies Miei = yi.

  • Construct the transformed d1 x b Platform1 as E = [e1 … eb].

  • The Platform1 E is the transformed Platform1 for shard D.

  • The clients must download parameters (K1,K2,Kr) to query each shard, plus Ks to inform the server of the target shard for a query.

This protocol is completed by the server without any client participation and before answering any client query. Note that a shard must be re-transformed after an update. Shard transform only takes a few minutes.

4.1.2. Client key initialization

  • The client generates a per-key unique identifier (UID), private key and public key using a fully homomorphic encryption (FHE) scheme relying on hardness assumptions similar to Ring Learning with Errors problem.

  • The client persists the UID and private key into its local key store, and uploads query-independent parameters UID and public key to the server. These later parameters will enable the server to perform cryptographic operations (i.e., FHE) efficiently.

  • The server needs to maintain an up-to-date mapping of UID to public key for all clients.

  • Each client completes this offline initialization protocol before running any query. It also needs to perform it periodically (e.g., weekly or monthly) to prevent server linkability of private queries to the user over an extended period.

  • The client downloads query parameters from the server:

    • Sharding hash key Ks to inform the server of the target shard for a query.

  • Sets of parameters (K1,K2,Kr,B0, …, Bb) for each shard.

4.1.3. Client query generation

  • The client creates a query to retrieve the value corresponding to a database key k as follows:

  • Select a standard PIR algorithm with server-supported implementation as the underlying PIR scheme.

  • Compute d = Fs(Ks,k) to identify the shard to query.

  • Compute j = F1(K1,k) to learn which partition contains the desired entry from the downloaded partition boundaries for the shard.

  • Generate z vector v of length d1 , … , dz . Compute a d1-length random bit vector v1 from (F2(K2,k||1),...,F2(K2,k||d1)). Compute v2 as a zero bit vector of d2 length with only the bit set at ⌊j/⌈n/d1d2⌉⌋. Similarly compute v3 , … , vz.

  • Finally use the underlying PIR scheme and the private key to encrypt the z vector v.

  • Send v, d and the UID to the server.

  • N.B. The dimension dz is typically small; a size of 2 or 4 works well.

4.1.4. Server response computation

  • The server retrieves the public key for the client's UID, and computes the ciphertext of the value corresponding to the key of interest for the shard d, as follows.

  • Take the transformed shard E as a d1 x ⌈n/d1 Platform1 E1, use the underlying PIR response answering algorithm to compute v1.E1, and rearrange the resulting ⌈n/d1 vector as a d2 x ⌈n/d1d2 Platform1 E2.

  • Next, compute v2.E2, and rearrange the resulting ⌈n/d1d2 vector as a d3 x ⌈n/d1d2d3 Platform1 E3.

  • The server similarly repeats the computation for the remaining dimensions v3 ,… , vz.

  • The end result is a ciphertext r of the database value corresponding to k. The server sends r back to the client.

4.1.5. Client response decryption

  • The client uses the underlying PIR response decryption algorithm and private key to decrypt the response r as kr||v. If Fr(Kr,k) == kr then returns v otherwise returns null (key not found).

4.2. FHE key requirements

  • At least 128-bit of security

    • ElGamal, NIST P-224r1 curve and a 4 bytes plaintext size for fast decryption.

    • Gentry-Ramzan, used a 2048-bit modulus

    • Damgård-Jurik, used 1160-bit primes

4.3. Cost estimates

In these estimates, we propose using shards of size ~1 million of identifiers. For 1.28 TB (10 billion records), breaking this down into 10,000 shards each of size 1 million records gives a cost estimate for each query as below:

Table 1
Parameter Cost estimate
PIR Public Key Size Per Device, including metadata (storage required) 14 MB
Upload Bandwidth Per Query 14 KB
Download Bandwidth Per Query 21 KB
Client Time Per Query 0.1s
Server Time Per Query (Single Thread) 0.8-1s

Note on some assumptions for feasibility:

  1. Resolver queries will be cached (vs requiring a roundtrip for every message) and asynchronous with message sending, therefore 1s latency is acceptable.

  2. It is acceptable for key changes to be communicated reactively on decrypt failure.

  3. Group messaging E2EE is bootstrapped using individual users' public keys and for V1, group state will be stored by the initiating user's service provider. Therefore no additional discovery mechanism is required.

5. IANA Considerations

This document has no IANA actions.

5.1. Appendix

The protocol messages are specified in protobuffer format in this appendix.

syntax = "proto3";

package mimi.discovery.pir;

// PirParameterRequest represents a request from a client to a server to
// fetch database and cryptographic parameters for querying a database.

message PirParameterRequest {
  // The protocol version for PIR.
  uint32 protocol_version = 1;

  // Used to prevent replays; request fingerprint.
  string nonce = 2;

  // The unique identifier of the client.
  string uid = 3;

  // The PIR scheme that the client wants to use.
  PirScheme pir_scheme = 4;

  // The public key of the client.
  repeated bytes public_key = 5;
}

// PirParameterResponse represents a server's response to PirParameterRequest
// and it contains database and cryptographic parameters to use for querying the
// database using PIR.

message PirParameterResponse {
  // The protocol version for PIR.
  uint32 protocol_version = 1;

  // Used to prevent replays; request fingerprint.
  string nonce = 2;

  // The unique identifier of the client.
  string uid = 3;

  // Status of the request.
  PirStatus response_status = 4;

  // The PIR scheme negotiated from the client request.
  optional PirScheme pir_scheme = 5;

  // A map of server-supported PIR schemes to their parameters.
  map<PirScheme, PirParams> pir_schemes = 6;

  // The shard hash key to identify subdatabase containing a keyword.
  HashKey shard_key = 7;

  // parameters for sub databases
  repeated PirSubDbParameter sub_db_param = 8;
}

// PirSubDbParameter represents a subdatabase partitioning parameters

message PirSubDbParameter {
  // The hash key of the partition boundary.
  HashKey partion_boundary_hash_key = 1;

  // The hash key of the row vector.
  HashKey row_vector_hash_key = 2;

  // The hash key of the record representation.
  HashKey record_representation_hash_key = 3;

  // The beginning hash values for the partition
  // boundaries for a single shard.
  repeated PartitionBoundaries partition_boundaries = 4;

  // The size of the database.
  uint32 database_size = 5;

  // The size of the largest record.
  uint32 record_size = 6;
}

// PirRequest represents a sparse PIR request to a server for retrieving a
// database record.

message PirRequest {
  // The protocol version for PIR.
  uint32 protocol_version = 1;

  // Used to prevent replays; request fingerprint.
  string nonce = 2;

  // The unique identifier of the client.
  string uid = 3;

  // The PIR scheme that the client wants to use.
  optional PirScheme pir_scheme = 4;

  // The shard ID of the record to be retrieved.
  uint32 shard = 5;

  // The encrypted vector of the query.
  repeated bytes encrypted_vector = 6;
}

// PirResponse represents a PIR server's response to a PirRequest.

message PirResponse {
  // The protocol version for PIR.
  uint32 protocol_version = 1;

  // Used to prevent replays; request fingerprint.
  string nonce = 2;

  // The unique identifier of the client.
  string uid = 3;

  // Status of the request.
  PirStatus response_status = 4;

  // The encrypted response.
  repeated bytes encrypted_response = 5;
}

// PartitionBoundaries represents the beginning hash values for the partition
// boundaries for a single shard.

message PartitionBoundaries {
  // The hash values of the partition boundaries.
  repeated string boundary = 1;
}

// HashKey represents a cryptographic hash key.

message HashKey {
  // The hash key.
  repeated bytes hash_key = 1;
}

// PirParams represents the parameters of a PIR scheme instance.

message PirParams {
  // The number of levels of recursion used in the PIR scheme.
  optional int32 levels_of_recursion = 1;

  // The modulus used in the PIR scheme.
  repeated bytes modulus = 2;

  // The plaintext modulus used in the PIR scheme.
  repeated bytes plaintext_modulus = 3;

  // Other parameters used in the specific PIR scheme.
  repeated bytes other_params = 4;

  // Reserved values.
  reserved 5, 6, 7;
}

// PirScheme represents an indexed-based PIR scheme .

enum PirScheme {
  // Scheme not specified
  PIR_SCHEME_UNSPECIFIED = 0;

  // XPIR scheme based on the Ring Learning with Errors (Ring-LWE) problem.
  PIR_SCHEME_RLWE_XPIR = 1;

  // Add more PIR schemes that we might want to support.
}

// PirStatus represents the PIR server response codes.

enum PirStatus {
  // The PIR scheme selected by the client is supported.
  PIR_SCHEME_MATCHED = 0;

  // The PIR scheme selected by the client is not supported, but the server
  // suggests a list of supported schemes.
  PIR_SCHEME_SUGGESTED = 1;

  // There are problems with the request.
  PIR_REQUEST_FAILURE = 2;

  // The response was successfully computed from the request.
  PIR_RESPONSE_SUCCESS = 3;

  // The response computation failed.
  PIR_RESPONSE_FAILURE = 4;
}

6. Normative References

[PIRFramework]
Patel, S., Seo, J. Y., and K. Yeo, "Don't be Dense: Efficient Keyword PIR for Sparse Databases", 32nd USENIX Security Symposium, USENIX Security 2023 , n.d..
[RFC2119]
Bradner, S., "Key words for use in RFCs to Indicate Requirement Levels", BCP 14, RFC 2119, DOI 10.17487/RFC2119, , <https://www.rfc-editor.org/rfc/rfc2119>.
[RFC8174]
Leiba, B., "Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words", BCP 14, RFC 8174, DOI 10.17487/RFC8174, , <https://www.rfc-editor.org/rfc/rfc8174>.

Acknowledgments

The technical description of the private information retrieval framework is based on Sarvar Patel, Joon Young Seo and Kevin Yeo's USENIX Security '23 paper titled "Don't be Dense: Efficient Keyword PIR for Sparse Databases ".

Authors' Addresses

Giles Hogben
Google
Femi Olumofin
Google