Validate that sourceAttachments are valid base64 strings
This commit is contained in:
parent
7e353f8ea0
commit
b5f9564e13
|
@ -65,6 +65,7 @@ import org.whispersystems.textsecuregcm.util.ByteArrayBase64UrlAdapter;
|
||||||
import org.whispersystems.textsecuregcm.util.ECPublicKeyAdapter;
|
import org.whispersystems.textsecuregcm.util.ECPublicKeyAdapter;
|
||||||
import org.whispersystems.textsecuregcm.util.ExactlySize;
|
import org.whispersystems.textsecuregcm.util.ExactlySize;
|
||||||
import org.whispersystems.textsecuregcm.util.Util;
|
import org.whispersystems.textsecuregcm.util.Util;
|
||||||
|
import org.whispersystems.textsecuregcm.util.ValidBase64URLString;
|
||||||
import org.whispersystems.websocket.auth.Mutable;
|
import org.whispersystems.websocket.auth.Mutable;
|
||||||
import org.whispersystems.websocket.auth.ReadOnly;
|
import org.whispersystems.websocket.auth.ReadOnly;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
|
@ -464,13 +465,16 @@ public class ArchiveController {
|
||||||
@Schema(description = "The attachment cdn")
|
@Schema(description = "The attachment cdn")
|
||||||
@NotNull
|
@NotNull
|
||||||
Integer cdn,
|
Integer cdn,
|
||||||
|
|
||||||
@NotBlank
|
@NotBlank
|
||||||
|
@ValidBase64URLString
|
||||||
@Schema(description = "The attachment key")
|
@Schema(description = "The attachment key")
|
||||||
String key) {}
|
String key) {}
|
||||||
|
|
||||||
public record CopyMediaRequest(
|
public record CopyMediaRequest(
|
||||||
@Schema(description = "The object on the attachment CDN to copy")
|
@Schema(description = "The object on the attachment CDN to copy")
|
||||||
@NotNull
|
@NotNull
|
||||||
|
@Valid
|
||||||
RemoteAttachment sourceAttachment,
|
RemoteAttachment sourceAttachment,
|
||||||
|
|
||||||
@Schema(description = "The length of the source attachment before the encryption applied by the copy operation")
|
@Schema(description = "The length of the source attachment before the encryption applied by the copy operation")
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2024 Signal Messenger, LLC
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.whispersystems.textsecuregcm.util;
|
||||||
|
|
||||||
|
import javax.validation.Constraint;
|
||||||
|
import javax.validation.ConstraintValidator;
|
||||||
|
import javax.validation.ConstraintValidatorContext;
|
||||||
|
import javax.validation.Payload;
|
||||||
|
import java.lang.annotation.Documented;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
import java.util.Base64;
|
||||||
|
import java.util.HexFormat;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import static java.lang.annotation.ElementType.*;
|
||||||
|
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constraint annotation that requires annotated entity is a valid url-base64 encoded string.
|
||||||
|
*/
|
||||||
|
@Target({ FIELD, PARAMETER, METHOD })
|
||||||
|
@Retention(RUNTIME)
|
||||||
|
@Constraint(validatedBy = ValidBase64URLString.Validator.class)
|
||||||
|
@Documented
|
||||||
|
public @interface ValidBase64URLString {
|
||||||
|
|
||||||
|
String message() default "value is not a valid base64 string";
|
||||||
|
|
||||||
|
Class<?>[] groups() default { };
|
||||||
|
|
||||||
|
Class<? extends Payload>[] payload() default { };
|
||||||
|
|
||||||
|
class Validator implements ConstraintValidator<ValidBase64URLString, String> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isValid(final String value, final ConstraintValidatorContext context) {
|
||||||
|
if (Objects.isNull(value)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Base64.getUrlDecoder().decode(value);
|
||||||
|
return true;
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -594,6 +594,27 @@ public class ArchiveControllerTest {
|
||||||
assertThat(response.getStatus()).isEqualTo(204);
|
assertThat(response.getStatus()).isEqualTo(204);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void invalidSourceAttachmentKey() throws VerificationFailedException {
|
||||||
|
final BackupAuthCredentialPresentation presentation = backupAuthTestUtil.getPresentation(
|
||||||
|
BackupLevel.MEDIA, backupKey, aci);
|
||||||
|
when(backupManager.authenticateBackupUser(any(), any()))
|
||||||
|
.thenReturn(CompletableFuture.completedFuture(backupUser(presentation.getBackupId(), BackupLevel.MEDIA)));
|
||||||
|
final Response r = resources.getJerseyTest()
|
||||||
|
.target("v1/archives/media")
|
||||||
|
.request()
|
||||||
|
.header("X-Signal-ZK-Auth", Base64.getEncoder().encodeToString(presentation.serialize()))
|
||||||
|
.header("X-Signal-ZK-Auth-Signature", "aaa")
|
||||||
|
.put(Entity.json(new ArchiveController.CopyMediaRequest(
|
||||||
|
new ArchiveController.RemoteAttachment(3, "invalid/urlBase64"),
|
||||||
|
100,
|
||||||
|
TestRandomUtil.nextBytes(15),
|
||||||
|
TestRandomUtil.nextBytes(32),
|
||||||
|
TestRandomUtil.nextBytes(32),
|
||||||
|
TestRandomUtil.nextBytes(16))));
|
||||||
|
assertThat(r.getStatus()).isEqualTo(422);
|
||||||
|
}
|
||||||
|
|
||||||
private static AuthenticatedBackupUser backupUser(byte[] backupId, BackupLevel backupLevel) {
|
private static AuthenticatedBackupUser backupUser(byte[] backupId, BackupLevel backupLevel) {
|
||||||
return new AuthenticatedBackupUser(backupId, backupLevel, "myBackupDir", "myMediaDir");
|
return new AuthenticatedBackupUser(backupId, backupLevel, "myBackupDir", "myMediaDir");
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue