Let server generate copyToMedia IVs
We include the IV in the encrypted payload, so we can let the server choose them instead of the client
This commit is contained in:
		
							parent
							
								
									a5f60b1522
								
							
						
					
					
						commit
						f2cb04817b
					
				| 
						 | 
					@ -19,7 +19,6 @@ import java.util.Optional;
 | 
				
			||||||
import java.util.concurrent.CompletableFuture;
 | 
					import java.util.concurrent.CompletableFuture;
 | 
				
			||||||
import java.util.concurrent.CompletionStage;
 | 
					import java.util.concurrent.CompletionStage;
 | 
				
			||||||
import java.util.concurrent.ExecutorService;
 | 
					import java.util.concurrent.ExecutorService;
 | 
				
			||||||
import java.util.concurrent.Executors;
 | 
					 | 
				
			||||||
import java.util.concurrent.ScheduledExecutorService;
 | 
					import java.util.concurrent.ScheduledExecutorService;
 | 
				
			||||||
import javax.annotation.Nullable;
 | 
					import javax.annotation.Nullable;
 | 
				
			||||||
import javax.validation.constraints.NotNull;
 | 
					import javax.validation.constraints.NotNull;
 | 
				
			||||||
| 
						 | 
					@ -129,7 +128,7 @@ public class Cdn3RemoteStorageManager implements RemoteStorageManager {
 | 
				
			||||||
   * Serialized copy request for cdn3 storage manager
 | 
					   * Serialized copy request for cdn3 storage manager
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
  record Cdn3CopyRequest(
 | 
					  record Cdn3CopyRequest(
 | 
				
			||||||
      String encryptionKey, String hmacKey, String iv,
 | 
					      String encryptionKey, String hmacKey,
 | 
				
			||||||
      SourceDescriptor source, int expectedSourceLength,
 | 
					      SourceDescriptor source, int expectedSourceLength,
 | 
				
			||||||
      String dst) {
 | 
					      String dst) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -137,7 +136,6 @@ public class Cdn3RemoteStorageManager implements RemoteStorageManager {
 | 
				
			||||||
        String dst) {
 | 
					        String dst) {
 | 
				
			||||||
      this(Base64.getEncoder().encodeToString(parameters.aesEncryptionKey().getEncoded()),
 | 
					      this(Base64.getEncoder().encodeToString(parameters.aesEncryptionKey().getEncoded()),
 | 
				
			||||||
          Base64.getEncoder().encodeToString(parameters.hmacSHA256Key().getEncoded()),
 | 
					          Base64.getEncoder().encodeToString(parameters.hmacSHA256Key().getEncoded()),
 | 
				
			||||||
          Base64.getEncoder().encodeToString(parameters.iv().getIV()),
 | 
					 | 
				
			||||||
          source, expectedSourceLength, dst);
 | 
					          source, expectedSourceLength, dst);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,24 +1,22 @@
 | 
				
			||||||
package org.whispersystems.textsecuregcm.backup;
 | 
					package org.whispersystems.textsecuregcm.backup;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import javax.crypto.spec.IvParameterSpec;
 | 
					 | 
				
			||||||
import javax.crypto.spec.SecretKeySpec;
 | 
					import javax.crypto.spec.SecretKeySpec;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
public record MediaEncryptionParameters(
 | 
					public record MediaEncryptionParameters(
 | 
				
			||||||
    SecretKeySpec aesEncryptionKey,
 | 
					    SecretKeySpec aesEncryptionKey,
 | 
				
			||||||
    SecretKeySpec hmacSHA256Key,
 | 
					    SecretKeySpec hmacSHA256Key) {
 | 
				
			||||||
    IvParameterSpec iv) {
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  public MediaEncryptionParameters(byte[] encryptionKey, byte[] macKey, byte[] iv) {
 | 
					  public MediaEncryptionParameters(byte[] encryptionKey, byte[] macKey) {
 | 
				
			||||||
    this(
 | 
					    this(
 | 
				
			||||||
        new SecretKeySpec(encryptionKey, "AES"),
 | 
					        new SecretKeySpec(encryptionKey, "AES"),
 | 
				
			||||||
        new SecretKeySpec(macKey, "HmacSHA256"),
 | 
					        new SecretKeySpec(macKey, "HmacSHA256"));
 | 
				
			||||||
        new IvParameterSpec(iv));
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  public int outputSize(final int inputSize) {
 | 
					  public int outputSize(final int inputSize) {
 | 
				
			||||||
    // AES-256 has 16-byte block size, and always adds a block if the plaintext is a multiple of the block size
 | 
					    // AES-256 has 16-byte block size, and always adds a block if the plaintext is a multiple of the block size
 | 
				
			||||||
    final int numBlocks = (inputSize + 16) / 16;
 | 
					    final int numBlocks = (inputSize + 16) / 16;
 | 
				
			||||||
 | 
					    // 16-byte IV will be generated and prepended to the ciphertext
 | 
				
			||||||
    // IV + AES-256 encrypted data + HmacSHA256
 | 
					    // IV + AES-256 encrypted data + HmacSHA256
 | 
				
			||||||
    return this.iv().getIV().length + (numBlocks * 16) + 32;
 | 
					    return 16 + (numBlocks * 16) + 32;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -512,19 +512,13 @@ public class ArchiveController {
 | 
				
			||||||
      @JsonDeserialize(using = ByteArrayAdapter.Deserializing.class)
 | 
					      @JsonDeserialize(using = ByteArrayAdapter.Deserializing.class)
 | 
				
			||||||
      @NotNull
 | 
					      @NotNull
 | 
				
			||||||
      @ExactlySize(32)
 | 
					      @ExactlySize(32)
 | 
				
			||||||
      byte[] encryptionKey,
 | 
					      byte[] encryptionKey) {
 | 
				
			||||||
 | 
					 | 
				
			||||||
      @Schema(description = "A 16-byte IV for AES, encoded in standard padded base64", implementation = String.class)
 | 
					 | 
				
			||||||
      @JsonDeserialize(using = ByteArrayAdapter.Deserializing.class)
 | 
					 | 
				
			||||||
      @NotNull
 | 
					 | 
				
			||||||
      @ExactlySize(16)
 | 
					 | 
				
			||||||
      byte[] iv) {
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    CopyParameters toCopyParameters() {
 | 
					    CopyParameters toCopyParameters() {
 | 
				
			||||||
      return new CopyParameters(
 | 
					      return new CopyParameters(
 | 
				
			||||||
          sourceAttachment.cdn(), sourceAttachment.key(),
 | 
					          sourceAttachment.cdn(), sourceAttachment.key(),
 | 
				
			||||||
          objectLength,
 | 
					          objectLength,
 | 
				
			||||||
          new MediaEncryptionParameters(encryptionKey, hmacKey, iv),
 | 
					          new MediaEncryptionParameters(encryptionKey, hmacKey),
 | 
				
			||||||
          mediaId);
 | 
					          mediaId);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -85,8 +85,7 @@ public class BackupManagerTest {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private static final MediaEncryptionParameters COPY_ENCRYPTION_PARAM = new MediaEncryptionParameters(
 | 
					  private static final MediaEncryptionParameters COPY_ENCRYPTION_PARAM = new MediaEncryptionParameters(
 | 
				
			||||||
      TestRandomUtil.nextBytes(32),
 | 
					      TestRandomUtil.nextBytes(32),
 | 
				
			||||||
      TestRandomUtil.nextBytes(32),
 | 
					      TestRandomUtil.nextBytes(32));
 | 
				
			||||||
      TestRandomUtil.nextBytes(16));
 | 
					 | 
				
			||||||
  private static final CopyParameters COPY_PARAM = new CopyParameters(
 | 
					  private static final CopyParameters COPY_PARAM = new CopyParameters(
 | 
				
			||||||
      3, "abc", 100,
 | 
					      3, "abc", 100,
 | 
				
			||||||
      COPY_ENCRYPTION_PARAM, TestRandomUtil.nextBytes(15));
 | 
					      COPY_ENCRYPTION_PARAM, TestRandomUtil.nextBytes(15));
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -39,7 +39,6 @@ public class Cdn3RemoteStorageManagerTest {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private static final byte[] HMAC_KEY = TestRandomUtil.nextBytes(32);
 | 
					  private static final byte[] HMAC_KEY = TestRandomUtil.nextBytes(32);
 | 
				
			||||||
  private static final byte[] AES_KEY = TestRandomUtil.nextBytes(32);
 | 
					  private static final byte[] AES_KEY = TestRandomUtil.nextBytes(32);
 | 
				
			||||||
  private static final byte[] IV = TestRandomUtil.nextBytes(16);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @RegisterExtension
 | 
					  @RegisterExtension
 | 
				
			||||||
  private static final WireMockExtension wireMock = WireMockExtension.newInstance()
 | 
					  private static final WireMockExtension wireMock = WireMockExtension.newInstance()
 | 
				
			||||||
| 
						 | 
					@ -66,7 +65,7 @@ public class Cdn3RemoteStorageManagerTest {
 | 
				
			||||||
  @ParameterizedTest
 | 
					  @ParameterizedTest
 | 
				
			||||||
  @ValueSource(ints = {2, 3})
 | 
					  @ValueSource(ints = {2, 3})
 | 
				
			||||||
  public void copy(final int sourceCdn) throws JsonProcessingException {
 | 
					  public void copy(final int sourceCdn) throws JsonProcessingException {
 | 
				
			||||||
    final MediaEncryptionParameters encryptionParameters = new MediaEncryptionParameters(AES_KEY, HMAC_KEY, IV);
 | 
					    final MediaEncryptionParameters encryptionParameters = new MediaEncryptionParameters(AES_KEY, HMAC_KEY);
 | 
				
			||||||
    final String scheme = switch (sourceCdn) {
 | 
					    final String scheme = switch (sourceCdn) {
 | 
				
			||||||
      case 2 -> "gcs";
 | 
					      case 2 -> "gcs";
 | 
				
			||||||
      case 3 -> "r2";
 | 
					      case 3 -> "r2";
 | 
				
			||||||
| 
						 | 
					@ -99,7 +98,7 @@ public class Cdn3RemoteStorageManagerTest {
 | 
				
			||||||
            2,
 | 
					            2,
 | 
				
			||||||
            "a/test/source",
 | 
					            "a/test/source",
 | 
				
			||||||
            100,
 | 
					            100,
 | 
				
			||||||
            new MediaEncryptionParameters(AES_KEY, HMAC_KEY, IV),
 | 
					            new MediaEncryptionParameters(AES_KEY, HMAC_KEY),
 | 
				
			||||||
            "a/destination").toCompletableFuture());
 | 
					            "a/destination").toCompletableFuture());
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -111,7 +110,7 @@ public class Cdn3RemoteStorageManagerTest {
 | 
				
			||||||
            2,
 | 
					            2,
 | 
				
			||||||
            "a/test/source",
 | 
					            "a/test/source",
 | 
				
			||||||
            100,
 | 
					            100,
 | 
				
			||||||
            new MediaEncryptionParameters(AES_KEY, HMAC_KEY, IV),
 | 
					            new MediaEncryptionParameters(AES_KEY, HMAC_KEY),
 | 
				
			||||||
            "a/destination").toCompletableFuture());
 | 
					            "a/destination").toCompletableFuture());
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -122,7 +121,7 @@ public class Cdn3RemoteStorageManagerTest {
 | 
				
			||||||
            0,
 | 
					            0,
 | 
				
			||||||
            "a/test/source",
 | 
					            "a/test/source",
 | 
				
			||||||
            100,
 | 
					            100,
 | 
				
			||||||
            new MediaEncryptionParameters(AES_KEY, HMAC_KEY, IV),
 | 
					            new MediaEncryptionParameters(AES_KEY, HMAC_KEY),
 | 
				
			||||||
            "a/destination").toCompletableFuture());
 | 
					            "a/destination").toCompletableFuture());
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -272,8 +272,7 @@ public class ArchiveControllerTest {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @ParameterizedTest
 | 
					  @ParameterizedTest
 | 
				
			||||||
  @MethodSource
 | 
					  @MethodSource
 | 
				
			||||||
  public void setBackupIdException(final Exception ex, final boolean sync, final int expectedStatus)
 | 
					  public void setBackupIdException(final Exception ex, final boolean sync, final int expectedStatus) {
 | 
				
			||||||
      throws RateLimitExceededException {
 | 
					 | 
				
			||||||
    if (sync) {
 | 
					    if (sync) {
 | 
				
			||||||
      when(backupAuthManager.commitBackupId(any(), any(), any())).thenThrow(ex);
 | 
					      when(backupAuthManager.commitBackupId(any(), any(), any())).thenThrow(ex);
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
| 
						 | 
					@ -393,16 +392,14 @@ public class ArchiveControllerTest {
 | 
				
			||||||
                100,
 | 
					                100,
 | 
				
			||||||
                mediaIds[0],
 | 
					                mediaIds[0],
 | 
				
			||||||
                TestRandomUtil.nextBytes(32),
 | 
					                TestRandomUtil.nextBytes(32),
 | 
				
			||||||
                TestRandomUtil.nextBytes(32),
 | 
					                TestRandomUtil.nextBytes(32)),
 | 
				
			||||||
                TestRandomUtil.nextBytes(16)),
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            new ArchiveController.CopyMediaRequest(
 | 
					            new ArchiveController.CopyMediaRequest(
 | 
				
			||||||
                new RemoteAttachment(3, "def"),
 | 
					                new RemoteAttachment(3, "def"),
 | 
				
			||||||
                200,
 | 
					                200,
 | 
				
			||||||
                mediaIds[1],
 | 
					                mediaIds[1],
 | 
				
			||||||
                TestRandomUtil.nextBytes(32),
 | 
					                TestRandomUtil.nextBytes(32),
 | 
				
			||||||
                TestRandomUtil.nextBytes(32),
 | 
					                TestRandomUtil.nextBytes(32))
 | 
				
			||||||
                TestRandomUtil.nextBytes(16))
 | 
					 | 
				
			||||||
        ))));
 | 
					        ))));
 | 
				
			||||||
    assertThat(r.getStatus()).isEqualTo(207);
 | 
					    assertThat(r.getStatus()).isEqualTo(207);
 | 
				
			||||||
    final ArchiveController.CopyMediaBatchResponse copyResponse = r.readEntity(
 | 
					    final ArchiveController.CopyMediaBatchResponse copyResponse = r.readEntity(
 | 
				
			||||||
| 
						 | 
					@ -438,8 +435,7 @@ public class ArchiveControllerTest {
 | 
				
			||||||
            100,
 | 
					            100,
 | 
				
			||||||
            mediaId,
 | 
					            mediaId,
 | 
				
			||||||
            TestRandomUtil.nextBytes(32),
 | 
					            TestRandomUtil.nextBytes(32),
 | 
				
			||||||
            TestRandomUtil.nextBytes(32),
 | 
					            TestRandomUtil.nextBytes(32))
 | 
				
			||||||
            TestRandomUtil.nextBytes(16))
 | 
					 | 
				
			||||||
        ).toList();
 | 
					        ).toList();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Response r = resources.getJerseyTest()
 | 
					    Response r = resources.getJerseyTest()
 | 
				
			||||||
| 
						 | 
					@ -454,7 +450,7 @@ public class ArchiveControllerTest {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    assertThat(copyResponse.responses()).hasSize(4);
 | 
					    assertThat(copyResponse.responses()).hasSize(4);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    final ArchiveController.CopyMediaBatchResponse.Entry r1 = copyResponse.responses().get(0);
 | 
					    final ArchiveController.CopyMediaBatchResponse.Entry r1 = copyResponse.responses().getFirst();
 | 
				
			||||||
    assertThat(r1.cdn()).isEqualTo(1);
 | 
					    assertThat(r1.cdn()).isEqualTo(1);
 | 
				
			||||||
    assertThat(r1.mediaId()).isEqualTo(mediaIds[0]);
 | 
					    assertThat(r1.mediaId()).isEqualTo(mediaIds[0]);
 | 
				
			||||||
    assertThat(r1.status()).isEqualTo(200);
 | 
					    assertThat(r1.status()).isEqualTo(200);
 | 
				
			||||||
| 
						 | 
					@ -494,16 +490,14 @@ public class ArchiveControllerTest {
 | 
				
			||||||
                1,
 | 
					                1,
 | 
				
			||||||
                mediaIds[0],
 | 
					                mediaIds[0],
 | 
				
			||||||
                TestRandomUtil.nextBytes(32),
 | 
					                TestRandomUtil.nextBytes(32),
 | 
				
			||||||
                TestRandomUtil.nextBytes(32),
 | 
					                TestRandomUtil.nextBytes(32)),
 | 
				
			||||||
                TestRandomUtil.nextBytes(16)),
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            new ArchiveController.CopyMediaRequest(
 | 
					            new ArchiveController.CopyMediaRequest(
 | 
				
			||||||
                new RemoteAttachment(3, "def"),
 | 
					                new RemoteAttachment(3, "def"),
 | 
				
			||||||
                -1,
 | 
					                -1,
 | 
				
			||||||
                mediaIds[1],
 | 
					                mediaIds[1],
 | 
				
			||||||
                TestRandomUtil.nextBytes(32),
 | 
					                TestRandomUtil.nextBytes(32),
 | 
				
			||||||
                TestRandomUtil.nextBytes(32),
 | 
					                TestRandomUtil.nextBytes(32))
 | 
				
			||||||
                TestRandomUtil.nextBytes(16))
 | 
					 | 
				
			||||||
        ))));
 | 
					        ))));
 | 
				
			||||||
    assertThat(r.getStatus()).isEqualTo(422);
 | 
					    assertThat(r.getStatus()).isEqualTo(422);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
| 
						 | 
					@ -573,7 +567,7 @@ public class ArchiveControllerTest {
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @Test
 | 
					  @Test
 | 
				
			||||||
  public void mediaUploadForm() throws RateLimitExceededException, VerificationFailedException {
 | 
					  public void mediaUploadForm() throws VerificationFailedException {
 | 
				
			||||||
    final BackupAuthCredentialPresentation presentation =
 | 
					    final BackupAuthCredentialPresentation presentation =
 | 
				
			||||||
        backupAuthTestUtil.getPresentation(BackupLevel.PAID, messagesBackupKey, aci);
 | 
					        backupAuthTestUtil.getPresentation(BackupLevel.PAID, messagesBackupKey, aci);
 | 
				
			||||||
    when(backupManager.authenticateBackupUser(any(), any()))
 | 
					    when(backupManager.authenticateBackupUser(any(), any()))
 | 
				
			||||||
| 
						 | 
					@ -675,8 +669,7 @@ public class ArchiveControllerTest {
 | 
				
			||||||
            100,
 | 
					            100,
 | 
				
			||||||
            TestRandomUtil.nextBytes(15),
 | 
					            TestRandomUtil.nextBytes(15),
 | 
				
			||||||
            TestRandomUtil.nextBytes(32),
 | 
					            TestRandomUtil.nextBytes(32),
 | 
				
			||||||
            TestRandomUtil.nextBytes(32),
 | 
					            TestRandomUtil.nextBytes(32))));
 | 
				
			||||||
            TestRandomUtil.nextBytes(16))));
 | 
					 | 
				
			||||||
    assertThat(r.getStatus()).isEqualTo(422);
 | 
					    assertThat(r.getStatus()).isEqualTo(422);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue