Skip to content

Commit

Permalink
Add an authentication-required gRPC service for working with accounts
Browse files Browse the repository at this point in the history
  • Loading branch information
jon-signal authored Oct 25, 2023
1 parent 3d92e5b commit 54bc3bc
Show file tree
Hide file tree
Showing 10 changed files with 1,358 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,8 @@
import org.whispersystems.textsecuregcm.filters.RequestStatisticsFilter;
import org.whispersystems.textsecuregcm.filters.TimestampResponseFilter;
import org.whispersystems.textsecuregcm.grpc.AcceptLanguageInterceptor;
import org.whispersystems.textsecuregcm.grpc.AccountsAnonymousGrpcService;
import org.whispersystems.textsecuregcm.grpc.AccountsGrpcService;
import org.whispersystems.textsecuregcm.grpc.ErrorMappingInterceptor;
import org.whispersystems.textsecuregcm.grpc.ExternalServiceCredentialsAnonymousGrpcService;
import org.whispersystems.textsecuregcm.grpc.ExternalServiceCredentialsGrpcService;
Expand Down Expand Up @@ -650,6 +652,8 @@ public void run(WhisperServerConfiguration config, Environment environment) thro
new BasicCredentialAuthenticationInterceptor(new BaseAccountAuthenticator(accountsManager));

final ServerBuilder<?> grpcServer = ServerBuilder.forPort(config.getGrpcPort())
.addService(ServerInterceptors.intercept(new AccountsGrpcService(accountsManager, rateLimiters, usernameHashZkProofVerifier, registrationRecoveryPasswordsManager), basicCredentialAuthenticationInterceptor))
.addService(new AccountsAnonymousGrpcService(accountsManager, rateLimiters))
.addService(ExternalServiceCredentialsGrpcService.createForAllExternalServices(config, rateLimiters))
.addService(ExternalServiceCredentialsAnonymousGrpcService.create(accountsManager, config))
.addService(ServerInterceptors.intercept(new KeysGrpcService(accountsManager, keys, rateLimiters), basicCredentialAuthenticationInterceptor))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
public class AccountController {
public static final int MAXIMUM_USERNAME_HASHES_LIST_LENGTH = 20;
public static final int USERNAME_HASH_LENGTH = 32;
public static final int MAXIMUM_USERNAME_CIPHERTEXT_LENGTH = 128;

private final AccountsManager accounts;
private final RateLimiters rateLimiters;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,6 @@ public record ConfirmUsernameHashRequest(
@Nullable
@JsonSerialize(using = ByteArrayBase64UrlAdapter.Serializing.class)
@JsonDeserialize(using = ByteArrayBase64UrlAdapter.Deserializing.class)
@Size(min = 1, max = 128)
@Size(min = 1, max = AccountController.MAXIMUM_USERNAME_CIPHERTEXT_LENGTH)
byte[] encryptedUsername
) {}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ public record EncryptedUsername(
@JsonSerialize(using = ByteArrayBase64UrlAdapter.Serializing.class)
@JsonDeserialize(using = ByteArrayBase64UrlAdapter.Deserializing.class)
@NotNull
@Size(min = 1, max = 128)
@Size(min = 1, max = EncryptedUsername.MAX_SIZE)
@Schema(type = "string", description = "the URL-safe base64 encoding of the encrypted username")
byte[] usernameLinkEncryptedValue) {

public static final int MAX_SIZE = 128;
}

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,10 @@ public RateLimiter getUsernameLinkLookupLimiter() {
return forDescriptor(For.USERNAME_LINK_LOOKUP_PER_IP);
}

public RateLimiter getUsernameLinkOperationLimiter() {
return forDescriptor(For.USERNAME_LINK_OPERATION);
}

public RateLimiter getUsernameSetLimiter() {
return forDescriptor(For.USERNAME_SET);
}
Expand Down
261 changes: 261 additions & 0 deletions service/src/main/proto/org/signal/chat/account.proto
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,101 @@ package org.signal.chat.account;

import "org/signal/chat/common.proto";

/**
* Provides methods for working with Signal accounts.
*/
service Accounts {
/**
* Returns basic identifiers for the authenticated account.
*/
rpc GetAccountIdentity(GetAccountIdentityRequest) returns (GetAccountIdentityResponse) {}

/**
* Deletes the authenticated account, purging all associated data in the
* process.
*/
rpc DeleteAccount(DeleteAccountRequest) returns (DeleteAccountResponse) {}

/**
* Sets the registration lock secret for the authenticated account. To remove
* a registration lock, please use `ClearRegistrationLock`.
*/
rpc SetRegistrationLock(SetRegistrationLockRequest) returns (SetRegistrationLockResponse) {}

/**
* Removes any registration lock credentials from the authenticated account.
*/
rpc ClearRegistrationLock(ClearRegistrationLockRequest) returns (ClearRegistrationLockResponse) {}

/**
* Attempts to reserve one of multiple given username hashes. Reserved
* usernames may be claimed later via `ConfirmUsernameHash`. This RPC may
* fail with a `RESOURCE_EXHAUSTED` status if a rate limit for modifying
* usernames has been exceeded, in which case a `retry-after` header
* containing an ISO 8601 duration string will be present in the response
* trailers.
*/
rpc ReserveUsernameHash(ReserveUsernameHashRequest) returns (ReserveUsernameHashResponse) {}

/**
* Sets the username hash/encrypted username to a previously-reserved value
* (see `ReserveUsernameHash`). This RPC may fail with a status of
* `FAILED_PRECONDITION` if no reserved username hash was foudn for the given
* account or `NOT_FOUND` if the reservation has lapsed and been claimed by
* another caller. It may also fail with a `RESOURCE_EXHAUSTED` if a rate
* limit for modifying usernames has been exceeded, in which case a
* `retry-after` header containing an ISO 8601 duration string will be present
* in the response trailers.
*/
rpc ConfirmUsernameHash(ConfirmUsernameHashRequest) returns (ConfirmUsernameHashResponse) {}

/**
* Clears the current username hash, ciphertext, and link for the
* authenticated user.
*/
rpc DeleteUsernameHash(DeleteUsernameHashRequest) returns (DeleteUsernameHashResponse) {}

/**
* Generates a new link handle for the given username ciphertext, displacing
* any previously-existing link handle.
*
* This RPC may fail with a status of `FAILED_PRECONDITION` if the
* authenticated account does not have a username. It may also fail with
* `RESOURCE_EXHAUSTED` if a rate limit for modifying username links has been
* exceeded, in which case a `retry-after` header containing an ISO 8601
* duration string will be present in the response trailers.
*/
rpc SetUsernameLink(SetUsernameLinkRequest) returns (SetUsernameLinkResponse) {}

/**
* Clears any username link associated with the authenticated account. This
* RPC may fail with `RESOURCE_EXHAUSTED` if a rate limit for modifying
* username links has been exceeded, in which case a `retry-after` header
* containing an ISO 8601 duration string will be present in the response
* trailers.
*/
rpc DeleteUsernameLink(DeleteUsernameLinkRequest) returns (DeleteUsernameLinkResponse) {}

/**
* Configures "unidentified access" keys and preferences for the authenticated
* account. Other users permitted to interact with this account anonymously
* may take actions like fetching pre-keys and profiles for this account or
* sending sealed-sender messages without providing identifying credentials.
*/
rpc ConfigureUnidentifiedAccess(ConfigureUnidentifiedAccessRequest) returns (ConfigureUnidentifiedAccessResponse) {}

/**
* Sets whether the authenticated account may be discovered by phone number
* via the Contact Discovery Service (CDS).
*/
rpc SetDiscoverableByPhoneNumber(SetDiscoverableByPhoneNumberRequest) returns (SetDiscoverableByPhoneNumberResponse) {}

/**
* Sets the registration recovery password for the authenticated account.
*/
rpc SetRegistrationRecoveryPassword(SetRegistrationRecoveryPasswordRequest) returns (SetRegistrationRecoveryPasswordResponse) {}
}

/**
* Provides methods for looking up Signal accounts. Callers must not provide
* identifying credentials when calling methods in this service.
Expand All @@ -31,6 +126,172 @@ service AccountsAnonymous {
rpc LookupUsernameLink(LookupUsernameLinkRequest) returns (LookupUsernameLinkResponse) {}
}

message GetAccountIdentityRequest {
}

message GetAccountIdentityResponse {
/**
* A set of account identifiers for the authenticated account.
*/
common.AccountIdentifiers account_identifiers = 1;
}

message DeleteAccountRequest {
}

message DeleteAccountResponse {
}

message SetRegistrationLockRequest {
/**
* The new registration lock secret for the authenticated account.
*/
bytes registration_lock = 1;
}

message SetRegistrationLockResponse {
}

message ClearRegistrationLockRequest {
}

message ClearRegistrationLockResponse {
}

message ReserveUsernameHashRequest {
/**
* A prioritized list of username hashes to attempt to reserve.
*/
repeated bytes username_hashes = 1;
}

message ReserveUsernameHashResponse {
oneof response {
/**
* The first username hash that was available (and actually reserved).
*/
bytes username_hash = 1;

/**
* An error indicating why a username hash could not be reserved.
*/
ReserveUsernameHashError error = 2;
}
}

message ReserveUsernameHashError {
ReserveUsernameHashErrorType error_type = 1;
}

enum ReserveUsernameHashErrorType {
RESERVE_USERNAME_HASH_ERROR_TYPE_UNSPECIFIED = 0;

/**
* Indicates that, of all of the candidate hashes provided, none were
* available. Callers may generate a new set of hashes and and retry.
*/
RESERVE_USERNAME_HASH_ERROR_TYPE_NO_HASHES_AVAILABLE = 1;
}

message ConfirmUsernameHashRequest {
/**
* The username hash to claim for the authenticated account.
*/
bytes username_hash = 1;

/**
* A zero-knowledge proof that the given username hash was generated by the
* Signal username algorithm.
*/
bytes zk_proof = 2;

/**
* The ciphertext of the chosen username for use in public-facing contexts
* (e.g. links and QR codes).
*/
bytes username_ciphertext = 3;
}

message ConfirmUsernameHashResponse {
/**
* The newly-confirmed username hash.
*/
bytes username_hash = 1;

/**
* The server-generated username link handle for the newly-confirmed username.
*/
bytes username_link_handle = 2;
}

message DeleteUsernameHashRequest {
}

message DeleteUsernameHashResponse {
}

message SetUsernameLinkRequest {
/**
* The username ciphertext for which to generate a new link handle.
*/
bytes username_ciphertext = 1;
}

message SetUsernameLinkResponse {
/**
* A new link handle for the given username ciphertext.
*/
bytes username_link_handle = 1;
}

message DeleteUsernameLinkRequest {
}

message DeleteUsernameLinkResponse {
}

message ConfigureUnidentifiedAccessRequest {
/**
* The key that other users must provide to interact with this account
* anonymously (i.e. to retrieve keys or profiles or to send messages) unless
* unrestricted unidentified access is permitted. Must be present if
* unrestricted unidentified access is not allowed.
*/
bytes unidentified_access_key = 1;

/**
* If `true`, any user may interact with this account anonymously without
* providing an unidentified access key. Otherwise, users must provide the
* given unidentified access key to interact with this account anonymously.
*/
bool allow_unrestricted_unidentified_access = 2;
}

message ConfigureUnidentifiedAccessResponse {
}

message SetDiscoverableByPhoneNumberRequest {
/**
* If true, the authenticated account may be discovered by phone number via
* the Contact Discovery Service (CDS). Otherwise, other users must discover
* this account by other means (i.e. by username).
*/
bool discoverable_by_phone_number = 1;
}

message SetDiscoverableByPhoneNumberResponse {
}

message SetRegistrationRecoveryPasswordRequest {
/**
* The new registration recovery password for the authenticated account.
*/
bytes registration_recovery_password = 1;
}

message SetRegistrationRecoveryPasswordResponse {
}

message CheckAccountExistenceRequest {
/**
* The service identifier of an account that may or may not exist.
Expand Down
12 changes: 12 additions & 0 deletions service/src/main/proto/org/signal/chat/common.proto
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,20 @@ message ServiceIdentifier {
}

message AccountIdentifiers {
/**
* A list of service identifiers for the identified account.
*/
repeated ServiceIdentifier service_identifiers = 1;

/**
* The phone number associated with the identified account.
*/
string e164 = 2;

/**
* The username hash (if any) associated with the identified account. May be
* empty if no username is associated with the identified account.
*/
bytes username_hash = 3;
}

Expand Down
Loading

0 comments on commit 54bc3bc

Please sign in to comment.