diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/grpc/AccountsAnonymousGrpcService.java b/service/src/main/java/org/whispersystems/textsecuregcm/grpc/AccountsAnonymousGrpcService.java index 2bf9f8fa3..634e79e96 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/grpc/AccountsAnonymousGrpcService.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/grpc/AccountsAnonymousGrpcService.java @@ -15,7 +15,6 @@ import org.signal.chat.account.LookupUsernameLinkRequest; import org.signal.chat.account.LookupUsernameLinkResponse; import org.signal.chat.account.SimpleAccountsAnonymousGrpc; import org.signal.chat.errors.NotFound; -import org.whispersystems.textsecuregcm.controllers.AccountController; import org.whispersystems.textsecuregcm.controllers.RateLimitExceededException; import org.whispersystems.textsecuregcm.identity.AciServiceIdentifier; import org.whispersystems.textsecuregcm.identity.ServiceIdentifier; @@ -52,12 +51,6 @@ public class AccountsAnonymousGrpcService extends SimpleAccountsAnonymousGrpc.Ac public LookupUsernameHashResponse lookupUsernameHash(final LookupUsernameHashRequest request) throws RateLimitExceededException { - if (request.getUsernameHash().size() != AccountController.USERNAME_HASH_LENGTH) { - throw GrpcExceptions.fieldViolation("username_hash", - String.format("Illegal username hash length; expected %d bytes, but got %d bytes", - AccountController.USERNAME_HASH_LENGTH, request.getUsernameHash().size())); - } - RateLimitUtil.rateLimitByRemoteAddress(rateLimiters.getUsernameLookupLimiter()); return accountsManager.getByUsernameHash(request.getUsernameHash().toByteArray()).join() diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/grpc/AccountsGrpcService.java b/service/src/main/java/org/whispersystems/textsecuregcm/grpc/AccountsGrpcService.java index 7be76b73c..815ca91df 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/grpc/AccountsGrpcService.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/grpc/AccountsGrpcService.java @@ -45,7 +45,6 @@ import org.whispersystems.textsecuregcm.auth.grpc.AuthenticatedDevice; import org.whispersystems.textsecuregcm.auth.grpc.AuthenticationUtil; import org.whispersystems.textsecuregcm.controllers.AccountController; import org.whispersystems.textsecuregcm.controllers.RateLimitExceededException; -import org.whispersystems.textsecuregcm.entities.EncryptedUsername; import org.whispersystems.textsecuregcm.identity.AciServiceIdentifier; import org.whispersystems.textsecuregcm.identity.IdentityType; import org.whispersystems.textsecuregcm.identity.PniServiceIdentifier; @@ -103,10 +102,6 @@ public class AccountsGrpcService extends SimpleAccountsGrpc.AccountsImplBase { @Override public SetRegistrationLockResponse setRegistrationLock(final SetRegistrationLockRequest request) { - if (request.getRegistrationLock().isEmpty()) { - throw GrpcExceptions.fieldViolation("registration_lock", "Registration lock secret must not be empty"); - } - // In the previous REST-based API, clients would send hex strings directly. For backward compatibility, we // convert the registration lock secret to a lowercase hex string before turning it into a salted hash. final SaltedTokenHash credentials = @@ -131,16 +126,6 @@ public class AccountsGrpcService extends SimpleAccountsGrpc.AccountsImplBase { throws RateLimitExceededException { final AuthenticatedDevice authenticatedDevice = AuthenticationUtil.requireAuthenticatedDevice(); - if (request.getUsernameHashesCount() == 0) { - throw GrpcExceptions.fieldViolation("username_hashes", "List of username hashes must not be empty"); - } - - if (request.getUsernameHashesCount() > AccountController.MAXIMUM_USERNAME_HASHES_LIST_LENGTH) { - throw GrpcExceptions.fieldViolation("username_hashes", - String.format("List of username hashes may have at most %d elements, but actually had %d", - AccountController.MAXIMUM_USERNAME_HASHES_LIST_LENGTH, request.getUsernameHashesCount())); - } - final List usernameHashes = new ArrayList<>(request.getUsernameHashesCount()); for (final ByteString usernameHash : request.getUsernameHashesList()) { @@ -175,30 +160,6 @@ public class AccountsGrpcService extends SimpleAccountsGrpc.AccountsImplBase { throws RateLimitExceededException { final AuthenticatedDevice authenticatedDevice = AuthenticationUtil.requireAuthenticatedDevice(); - if (request.getUsernameHash().isEmpty()) { - throw GrpcExceptions.fieldViolation("username_hash", "Username hash must not be empty"); - } - - if (request.getUsernameHash().size() != AccountController.USERNAME_HASH_LENGTH) { - throw GrpcExceptions.fieldViolation("username_hash", - String.format("Username hash length must be %d bytes, but was actually %d", - AccountController.USERNAME_HASH_LENGTH, request.getUsernameHash().size())); - } - - if (request.getZkProof().isEmpty()) { - throw GrpcExceptions.fieldViolation("zk_proof", "Zero-knowledge proof must not be empty"); - } - - if (request.getUsernameCiphertext().isEmpty()) { - throw GrpcExceptions.fieldViolation("username_ciphertext", "Username ciphertext must not be empty"); - } - - if (request.getUsernameCiphertext().size() > AccountController.MAXIMUM_USERNAME_CIPHERTEXT_LENGTH) { - throw GrpcExceptions.fieldViolation("username_ciphertext", - String.format("Username ciphertext length must at most %d bytes, but was actually %d", - AccountController.MAXIMUM_USERNAME_CIPHERTEXT_LENGTH, request.getUsernameCiphertext().size())); - } - try { usernameHashZkProofVerifier.verifyProof(request.getZkProof().toByteArray(), request.getUsernameHash().toByteArray()); } catch (final BaseUsernameException e) { @@ -242,11 +203,6 @@ public class AccountsGrpcService extends SimpleAccountsGrpc.AccountsImplBase { throws RateLimitExceededException { final AuthenticatedDevice authenticatedDevice = AuthenticationUtil.requireAuthenticatedDevice(); - if (request.getUsernameCiphertext().isEmpty() || request.getUsernameCiphertext().size() > EncryptedUsername.MAX_SIZE) { - throw GrpcExceptions.fieldViolation("username_ciphertext", - String.format("Username ciphertext must not be empty and must be shorter than %d bytes", EncryptedUsername.MAX_SIZE)); - } - rateLimiters.getUsernameLinkOperationLimiter().validate(authenticatedDevice.accountIdentifier()); final Account account = getAuthenticatedAccount(); @@ -304,10 +260,6 @@ public class AccountsGrpcService extends SimpleAccountsGrpc.AccountsImplBase { @Override public SetRegistrationRecoveryPasswordResponse setRegistrationRecoveryPassword(final SetRegistrationRecoveryPasswordRequest request) { - if (request.getRegistrationRecoveryPassword().isEmpty()) { - throw GrpcExceptions.fieldViolation("registration_recovery_password", "Registration recovery password must not be empty"); - } - registrationRecoveryPasswordsManager.store(getAuthenticatedAccount().getIdentifier(IdentityType.PNI), request.getRegistrationRecoveryPassword().toByteArray()) .join(); diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/grpc/ExternalServiceCredentialsAnonymousGrpcService.java b/service/src/main/java/org/whispersystems/textsecuregcm/grpc/ExternalServiceCredentialsAnonymousGrpcService.java index 76f65fcbc..32cd60249 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/grpc/ExternalServiceCredentialsAnonymousGrpcService.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/grpc/ExternalServiceCredentialsAnonymousGrpcService.java @@ -53,9 +53,6 @@ public class ExternalServiceCredentialsAnonymousGrpcService extends @Override public CheckSvrCredentialsResponse checkSvrCredentials(final CheckSvrCredentialsRequest request) { final List tokens = request.getPasswordsList(); - if (tokens.size() > 10) { - throw GrpcExceptions.fieldViolation("passwordsList", "At most 10 passwords may be provided"); - } final List credentials = ExternalServiceCredentialsSelector.check( tokens, svrCredentialsGenerator, diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/grpc/KeysGrpcService.java b/service/src/main/java/org/whispersystems/textsecuregcm/grpc/KeysGrpcService.java index b33c3046b..8c4ca7ebc 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/grpc/KeysGrpcService.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/grpc/KeysGrpcService.java @@ -119,10 +119,6 @@ public class KeysGrpcService extends SimpleKeysGrpc.KeysImplBase { @Override public SetPreKeyResponse setOneTimeEcPreKeys(final SetOneTimeEcPreKeysRequest request) { - if (request.getPreKeysList().isEmpty()) { - throw GrpcExceptions.fieldViolation("pre_keys", "pre_keys must be non-empty"); - } - final AuthenticatedDevice authenticatedDevice = AuthenticationUtil.requireAuthenticatedDevice(); storeOneTimePreKeys(authenticatedDevice.accountIdentifier(), @@ -136,10 +132,6 @@ public class KeysGrpcService extends SimpleKeysGrpc.KeysImplBase { @Override public SetPreKeyResponse setOneTimeKemSignedPreKeys(final SetOneTimeKemSignedPreKeysRequest request) { - if (request.getPreKeysList().isEmpty()) { - throw GrpcExceptions.fieldViolation("pre_keys", "pre_keys must be non-empty"); - } - final AuthenticatedDevice authenticatedDevice = AuthenticationUtil.requireAuthenticatedDevice(); storeOneTimePreKeys(authenticatedDevice.accountIdentifier(), diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/grpc/ServiceIdentifierUtil.java b/service/src/main/java/org/whispersystems/textsecuregcm/grpc/ServiceIdentifierUtil.java index 35d2f9f89..dc8af3c81 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/grpc/ServiceIdentifierUtil.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/grpc/ServiceIdentifierUtil.java @@ -19,8 +19,11 @@ public class ServiceIdentifierUtil { } public static ServiceIdentifier fromGrpcServiceIdentifier(final org.signal.chat.common.ServiceIdentifier serviceIdentifier) { - final UUID uuid; + if (serviceIdentifier == null) { + throw GrpcExceptions.invalidArguments("invalid service identifier"); + } + final UUID uuid; try { uuid = UUIDUtil.fromByteString(serviceIdentifier.getUuid()); } catch (final IllegalArgumentException e) { diff --git a/service/src/main/proto/org/signal/chat/account.proto b/service/src/main/proto/org/signal/chat/account.proto index 304c1411d..cd77c274e 100644 --- a/service/src/main/proto/org/signal/chat/account.proto +++ b/service/src/main/proto/org/signal/chat/account.proto @@ -6,6 +6,7 @@ package org.signal.chat.account; import "org/signal/chat/common.proto"; import "org/signal/chat/errors.proto"; +import "org/signal/chat/require.proto"; // Provides methods for working with Signal accounts. service Accounts { @@ -87,7 +88,7 @@ message DeleteAccountResponse { message SetRegistrationLockRequest { // The new registration lock secret for the authenticated account. - bytes registration_lock = 1; + bytes registration_lock = 1 [(require.exactlySize) = 32]; } message SetRegistrationLockResponse { @@ -100,8 +101,9 @@ message ClearRegistrationLockResponse { } message ReserveUsernameHashRequest { - // A prioritized list of username hashes to attempt to reserve. - repeated bytes username_hashes = 1; + // A prioritized list of username hashes to attempt to reserve. Each hash must + // be exactly 32 bytes. + repeated bytes username_hashes = 1 [(require.size) = {min: 1, max: 20}]; } message UsernameNotAvailable {} @@ -119,15 +121,15 @@ message ReserveUsernameHashResponse { message ConfirmUsernameHashRequest { // The username hash to claim for the authenticated account. - bytes username_hash = 1; + bytes username_hash = 1 [(require.exactlySize) = 32]; // A zero-knowledge proof that the given username hash was generated by the // Signal username algorithm. - bytes zk_proof = 2; + bytes zk_proof = 2 [(require.nonEmpty) = true]; // The ciphertext of the chosen username for use in public-facing contexts // (e.g. links and QR codes). - bytes username_ciphertext = 3; + bytes username_ciphertext = 3 [(require.size) = {min: 1, max: 128}]; } message ConfirmUsernameHashResponse { @@ -160,7 +162,7 @@ message DeleteUsernameHashResponse { message SetUsernameLinkRequest { // The username ciphertext for which to generate a new link handle. - bytes username_ciphertext = 1; + bytes username_ciphertext = 1 [(require.size) = {min: 1, max: 128}]; // If true and the account already had an encrypted username stored, the // existing link handle will be reused. Otherwise a new link handle will be @@ -214,7 +216,7 @@ message SetDiscoverableByPhoneNumberResponse { message SetRegistrationRecoveryPasswordRequest { // The new registration recovery password for the authenticated account. - bytes registration_recovery_password = 1; + bytes registration_recovery_password = 1 [(require.exactlySize) = 32]; } message SetRegistrationRecoveryPasswordResponse { @@ -233,7 +235,7 @@ message CheckAccountExistenceResponse { message LookupUsernameHashRequest { // A 32-byte username hash for which to find an account. - bytes username_hash = 1; + bytes username_hash = 1 [(require.exactlySize) = 32]; } message LookupUsernameHashResponse { @@ -249,7 +251,7 @@ message LookupUsernameHashResponse { message LookupUsernameLinkRequest { // The link handle for which to find an encrypted username. Link handles are // 16-byte representations of UUIDs. - bytes username_link_handle = 1; + bytes username_link_handle = 1 [(require.exactlySize) = 16]; } message LookupUsernameLinkResponse { diff --git a/service/src/main/proto/org/signal/chat/backups.proto b/service/src/main/proto/org/signal/chat/backups.proto index 1dac97005..91e93f017 100644 --- a/service/src/main/proto/org/signal/chat/backups.proto +++ b/service/src/main/proto/org/signal/chat/backups.proto @@ -202,18 +202,18 @@ service BackupsAnonymous { message SignedPresentation { // Presentation of a BackupAuthCredential previously retrieved from // GetBackupAuthCredentials on the authenticated channel - bytes presentation = 1; + bytes presentation = 1 [(require.nonEmpty) = true]; // The presentation signed with the private key corresponding to the public // key set with SetPublicKey - bytes presentation_signature = 2; + bytes presentation_signature = 2 [(require.nonEmpty) = true]; } message SetPublicKeyRequest { SignedPresentation signed_presentation = 1; // The public key, serialized in libsignal's elliptic-curve public key format. - bytes public_key = 2; + bytes public_key = 2 [(require.nonEmpty) = true]; } message SetPublicKeyResponse { @@ -422,7 +422,7 @@ message CopyMediaRequest { SignedPresentation signed_presentation = 1; // Items to copy - repeated CopyMediaItem items = 2; + repeated CopyMediaItem items = 2 [(require.size) = {min: 1, max: 1000}]; } message CopyMediaResponse { @@ -465,7 +465,7 @@ message ListMediaRequest { optional string cursor = 2; // If provided, the maximum number of entries to return in a page - uint32 limit = 3 [(require.range) = {min: 0, max: 10000}]; + uint32 limit = 3 [(require.range) = {min: 1, max: 10000}]; } message ListMediaResponse { message ListEntry { @@ -529,13 +529,13 @@ message DeleteMediaItem { uint32 cdn = 1; // The media_id of the object to delete - bytes media_id = 2; + bytes media_id = 2 [(require.exactlySize) = 15]; } message DeleteMediaRequest { SignedPresentation signed_presentation = 1; - repeated DeleteMediaItem items = 2; + repeated DeleteMediaItem items = 2 [(require.size) = {min: 1, max: 1000}]; } message DeleteMediaResponse { diff --git a/service/src/main/proto/org/signal/chat/common.proto b/service/src/main/proto/org/signal/chat/common.proto index f196af185..d0ba1a1d6 100644 --- a/service/src/main/proto/org/signal/chat/common.proto +++ b/service/src/main/proto/org/signal/chat/common.proto @@ -9,6 +9,8 @@ option java_multiple_files = true; package org.signal.chat.common; +import "org/signal/chat/require.proto"; + enum IdentityType { IDENTITY_TYPE_UNSPECIFIED = 0; IDENTITY_TYPE_ACI = 1; @@ -20,7 +22,7 @@ message ServiceIdentifier { IdentityType identity_type = 1; // The UUID of the identity represented by this service identifier. - bytes uuid = 2; + bytes uuid = 2 [(require.exactlySize) = 16]; } message AccountIdentifiers { @@ -36,35 +38,40 @@ message AccountIdentifiers { } message EcPreKey { - // A locally-unique identifier for this key. + // A locally-unique identifier for this key, which will be provided by + // peers using this key to encrypt messages so the private key can be looked + // up. uint64 key_id = 1; - // The serialized form of the public key. - bytes public_key = 2; + // The public key, serialized in libsignal's elliptic-curve public key format. + bytes public_key = 2 [(require.nonEmpty) = true]; } message EcSignedPreKey { - // A locally-unique identifier for this key. + // A locally-unique identifier for this key, which will be provided by + // peers using this key to encrypt messages so the private key can be looked + // up. uint64 key_id = 1; - // The serialized form of the public key. - bytes public_key = 2; + // The public key, serialized in libsignal's elliptic-curve public key format. + bytes public_key = 2 [(require.nonEmpty) = true]; // A signature of the public key, verifiable with the identity key for the // account/identity associated with this pre-key. - bytes signature = 3; + bytes signature = 3 [(require.nonEmpty) = true]; } message KemSignedPreKey { - // A locally-unique identifier for this key. + // An locally-unique identifier for this key, which will be provided by peers + // using this key to encrypt messages so the private key can be looked up. uint64 key_id = 1; - // The serialized form of the public key. - bytes public_key = 2; + // The public key, serialized in libsignal's Kyber1024 public key format. + bytes public_key = 2 [(require.nonEmpty) = true]; // A signature of the public key, verifiable with the identity key for the // account/identity associated with this pre-key. - bytes signature = 3; + bytes signature = 3 [(require.nonEmpty) = true]; } enum DeviceCapability { @@ -87,6 +94,6 @@ message ZkCredential { /* * The ZK credential, using libsignal's serialization */ - bytes credential = 2; + bytes credential = 2 [(require.nonEmpty) = true]; } diff --git a/service/src/main/proto/org/signal/chat/credentials.proto b/service/src/main/proto/org/signal/chat/credentials.proto index 596e2a48e..59ccb0cb0 100644 --- a/service/src/main/proto/org/signal/chat/credentials.proto +++ b/service/src/main/proto/org/signal/chat/credentials.proto @@ -7,6 +7,8 @@ syntax = "proto3"; option java_multiple_files = true; +import "org/signal/chat/require.proto"; + package org.signal.chat.credentials; // Provides methods for obtaining and verifying credentials for "external" services @@ -69,7 +71,7 @@ message CheckSvrCredentialsRequest { // A list of credentials from previously made calls to `ExternalServiceCredentials.GetExternalServiceCredentials()` // for `EXTERNAL_SERVICE_TYPE_SVR`. This list may contain credentials generated by different users. Up to 10 credentials // can be checked. - repeated string passwords = 2; + repeated string passwords = 2 [(require.nonEmpty) = true, (require.size) = {max: 10}]; } // For each of the credentials tokens in the `CheckSvrCredentialsRequest` contains the result of the check. diff --git a/service/src/main/proto/org/signal/chat/device.proto b/service/src/main/proto/org/signal/chat/device.proto index 977231dde..90eeb7ca1 100644 --- a/service/src/main/proto/org/signal/chat/device.proto +++ b/service/src/main/proto/org/signal/chat/device.proto @@ -83,7 +83,7 @@ message RemoveDeviceRequest { message SetDeviceNameRequest { // A sequence of bytes that encodes an encrypted human-readable name for this // device. - bytes name = 1 [(require.size) = {min: 1, max: 256}]; + bytes name = 1 [(require.size) = {min: 1, max: 225}]; // The identifier for the device for which to set a name. uint32 id = 2; diff --git a/service/src/main/proto/org/signal/chat/keys.proto b/service/src/main/proto/org/signal/chat/keys.proto index 7c107c004..62699af21 100644 --- a/service/src/main/proto/org/signal/chat/keys.proto +++ b/service/src/main/proto/org/signal/chat/keys.proto @@ -13,6 +13,7 @@ import "google/protobuf/empty.proto"; import "org/signal/chat/common.proto"; import "org/signal/chat/errors.proto"; +import "org/signal/chat/require.proto"; // Provides methods for working with pre-keys. service Keys { @@ -175,7 +176,7 @@ message SetOneTimeEcPreKeysRequest { common.IdentityType identity_type = 1; // The unsigned EC pre-keys to be stored. - repeated common.EcPreKey pre_keys = 2; + repeated common.EcPreKey pre_keys = 2 [(require.size) = {min: 1, max: 100}]; } message SetOneTimeKemSignedPreKeysRequest { @@ -184,7 +185,7 @@ message SetOneTimeKemSignedPreKeysRequest { common.IdentityType identity_type = 1; // The KEM pre-keys to be stored. - repeated common.KemSignedPreKey pre_keys = 2; + repeated common.KemSignedPreKey pre_keys = 2 [(require.size) = {min: 1, max: 100}]; } message SetEcSignedPreKeyRequest { @@ -192,7 +193,7 @@ message SetEcSignedPreKeyRequest { common.IdentityType identity_type = 1; // The signed EC pre-key itself. - common.EcSignedPreKey signed_pre_key = 2; + common.EcSignedPreKey signed_pre_key = 2 [(require.present) = true]; } message SetKemLastResortPreKeyRequest { @@ -200,7 +201,7 @@ message SetKemLastResortPreKeyRequest { common.IdentityType identity_type = 1; // The signed KEM pre-key itself. - common.KemSignedPreKey signed_pre_key = 2; + common.KemSignedPreKey signed_pre_key = 2 [(require.present) = true]; } message SetPreKeyResponse { @@ -210,7 +211,7 @@ message CheckIdentityKeyRequest { // The service identifier of the account for which we want to check the associated identity key fingerprint. common.ServiceIdentifier target_identifier = 1; // The most significant 4 bytes of the SHA-256 hash of the identity key associated with the target account/identity type. - bytes fingerprint = 2; + bytes fingerprint = 2 [(require.exactlySize) = 4]; } message CheckIdentityKeyResponse { diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/grpc/AccountsGrpcServiceTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/grpc/AccountsGrpcServiceTest.java index 91f58bea2..97f1fc501 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/grpc/AccountsGrpcServiceTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/grpc/AccountsGrpcServiceTest.java @@ -211,7 +211,7 @@ class AccountsGrpcServiceTest extends SimpleBaseGrpcTest authenticatedServiceStub().setRegistrationLock(SetRegistrationLockRequest.newBuilder() - .setRegistrationLock(ByteString.copyFrom(TestRandomUtil.nextBytes(16))) + .setRegistrationLock(ByteString.copyFrom(TestRandomUtil.nextBytes(32))) .build())); verify(accountsManager, never()).update(any(), any()); diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/grpc/BackupsAnonymousGrpcServiceTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/grpc/BackupsAnonymousGrpcServiceTest.java index bb2bae0cc..fd8554feb 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/grpc/BackupsAnonymousGrpcServiceTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/grpc/BackupsAnonymousGrpcServiceTest.java @@ -11,6 +11,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.when; +import com.google.common.collect.Iterators; import com.google.protobuf.ByteString; import io.grpc.Status; import io.grpc.StatusRuntimeException; @@ -26,13 +27,16 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; import java.util.stream.IntStream; import java.util.stream.Stream; +import org.apache.commons.collections4.IteratorUtils; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.CsvSource; import org.junit.jupiter.params.provider.EnumSource; import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.ValueSource; import org.junitpioneer.jupiter.cartesian.CartesianTest; import org.mockito.Mock; import org.signal.chat.backup.BackupsAnonymousGrpc; @@ -380,6 +384,60 @@ class BackupsAnonymousGrpcServiceTest extends assertThat(response.getCdnCredentials().getHeadersMap()).containsExactlyEntriesOf(Map.of("key", "value")); } + @ParameterizedTest + @ValueSource(ints = {0, 1001}) + void copyMediaInvalidRequest(final int count) { + final SignedPresentation sp = SignedPresentation.newBuilder() + .setPresentation(ByteString.copyFrom(TestRandomUtil.nextBytes(10))) + .setPresentationSignature(ByteString.copyFromUtf8("aaa")).build(); + + final CopyMediaItem validItem = CopyMediaItem.newBuilder() + .setSourceAttachmentCdn(3) + .setSourceKey("abc") + .setObjectLength(100) + .setMediaId(ByteString.copyFrom(TestRandomUtil.nextBytes(15))) + .setHmacKey(ByteString.copyFrom(TestRandomUtil.nextBytes(32))) + .setEncryptionKey(ByteString.copyFrom(TestRandomUtil.nextBytes(32))) + .build(); + + GrpcTestUtils.assertStatusInvalidArgument(() -> IteratorUtils.toList(unauthenticatedServiceStub().copyMedia( + CopyMediaRequest.newBuilder() + .setSignedPresentation(sp) + .addAllItems(IntStream.range(0, count).mapToObj(_ -> validItem).toList()) + .build()))); + } + + + @ParameterizedTest + @ValueSource(ints = {0, 1001}) + void deleteMediaInvalidRequest(final int count) { + final SignedPresentation sp = SignedPresentation.newBuilder() + .setPresentation(ByteString.copyFrom(TestRandomUtil.nextBytes(10))) + .setPresentationSignature(ByteString.copyFromUtf8("aaa")).build(); + + final DeleteMediaItem validItem = DeleteMediaItem.newBuilder() + .setCdn(3) + .setMediaId(ByteString.copyFrom(TestRandomUtil.nextBytes(15))) + .build(); + + GrpcTestUtils.assertStatusInvalidArgument(() -> IteratorUtils.toList(unauthenticatedServiceStub().deleteMedia( + DeleteMediaRequest.newBuilder() + .setSignedPresentation(sp) + .addAllItems(IntStream.range(0, count).mapToObj(_ -> validItem).toList()) + .build()))); + } + + @ParameterizedTest + @ValueSource(ints = {0, 10001}) + void listMediaInvalidLimit(int count) { + GrpcTestUtils.assertStatusInvalidArgument(() -> unauthenticatedServiceStub().listMedia( + ListMediaRequest.newBuilder() + .setSignedPresentation(signedPresentation(presentation)) + .setLimit(count) + .build())); + } + + private static AuthenticatedBackupUser backupUser(final byte[] backupId, final BackupCredentialType credentialType, final BackupLevel backupLevel) { return new AuthenticatedBackupUser(backupId, credentialType, backupLevel, "myBackupDir", "myMediaDir", null); diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/grpc/ExternalServiceCredentialsAnonymousGrpcServiceTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/grpc/ExternalServiceCredentialsAnonymousGrpcServiceTest.java index 50779182f..a37c603dc 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/grpc/ExternalServiceCredentialsAnonymousGrpcServiceTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/grpc/ExternalServiceCredentialsAnonymousGrpcServiceTest.java @@ -18,6 +18,8 @@ import java.util.UUID; import java.util.stream.IntStream; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import org.mockito.Mock; import org.mockito.Mockito; import org.signal.chat.credentials.AuthCheckResult; @@ -127,11 +129,12 @@ class ExternalServiceCredentialsAnonymousGrpcServiceTest extends ), day(25)); } - @Test - public void testTooManyPasswords() { + @ParameterizedTest + @ValueSource(ints = {0, 11}) + public void testInvalidPasswordCount(int count) { final CheckSvrCredentialsRequest request = CheckSvrCredentialsRequest.newBuilder() .setNumber(USER_E164) - .addAllPasswords(IntStream.range(0, 12).mapToObj(i -> token(UUID.randomUUID(), day(10))).toList()) + .addAllPasswords(IntStream.range(0, count).mapToObj(i -> token(UUID.randomUUID(), day(10))).toList()) .build(); final StatusRuntimeException status = assertThrows(StatusRuntimeException.class, () -> unauthenticatedServiceStub().checkSvrCredentials(request)); diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/grpc/KeysGrpcServiceTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/grpc/KeysGrpcServiceTest.java index 9a59b939f..606c03672 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/grpc/KeysGrpcServiceTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/grpc/KeysGrpcServiceTest.java @@ -149,7 +149,7 @@ class KeysGrpcServiceTest extends SimpleBaseGrpcTest preKeys = new ArrayList<>(); - for (int keyId = 0; keyId < 100; keyId++) { + for (int keyId = 1; keyId <= 100; keyId++) { preKeys.add(new ECPreKey(keyId, ECKeyPair.generate().getPublicKey())); } @@ -220,7 +220,7 @@ class KeysGrpcServiceTest extends SimpleBaseGrpcTest preKeys = new ArrayList<>(); - for (int keyId = 0; keyId < 100; keyId++) { + for (int keyId = 1; keyId <= 100; keyId++) { preKeys.add(KeysHelper.signedKEMPreKey(keyId, identityKeyPair)); }