mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-05-01 14:13:22 +01:00
Add some initial backupV2 network infrastructure.
This commit is contained in:
committed by
Cody Henthorne
parent
e17b07bb12
commit
6230a7553d
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.signalservice.api
|
||||
|
||||
import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException
|
||||
import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException
|
||||
import java.io.IOException
|
||||
|
||||
/**
|
||||
* A helper class that wraps the result of a network request, turning common exceptions
|
||||
* into sealed classes, with optional request chaining.
|
||||
*
|
||||
* This was designed to be a middle ground between the heavy reliance on specific exceptions
|
||||
* in old network code (which doesn't translate well to kotlin not having checked exceptions)
|
||||
* and plain rx, which still doesn't free you from having to catch exceptions and translate
|
||||
* things to sealed classes yourself.
|
||||
*
|
||||
* If you have a very complicated network request with lots of different possible response types
|
||||
* based on specific errors, this isn't for you. You're likely better off writing your own
|
||||
* sealed class. However, for the majority of requests which just require getting a model from
|
||||
* the success case and the status code of the error, this can be quite convenient.
|
||||
*/
|
||||
sealed class NetworkResult<T> {
|
||||
companion object {
|
||||
/**
|
||||
* A convenience method to capture the common case of making a request.
|
||||
* Perform the network action in the [fetch] lambda, returning your result.
|
||||
* Common exceptions will be caught and translated to errors.
|
||||
*/
|
||||
fun <T> fromFetch(fetch: () -> T): NetworkResult<T> = try {
|
||||
Success(fetch())
|
||||
} catch (e: NonSuccessfulResponseCodeException) {
|
||||
StatusCodeError(e.code, e)
|
||||
} catch (e: IOException) {
|
||||
NetworkError(e)
|
||||
} catch (e: Throwable) {
|
||||
ApplicationError(e)
|
||||
}
|
||||
}
|
||||
|
||||
/** Indicates the request was successful */
|
||||
data class Success<T>(val result: T) : NetworkResult<T>()
|
||||
|
||||
/** Indicates a generic network error occurred before we were able to process a response. */
|
||||
data class NetworkError<T>(val throwable: Throwable? = null) : NetworkResult<T>()
|
||||
|
||||
/** Indicates we got a response, but it was a non-2xx response. */
|
||||
data class StatusCodeError<T>(val code: Int, val throwable: Throwable? = null) : NetworkResult<T>()
|
||||
|
||||
/** Indicates that the application somehow failed in a way unrelated to network activity. Usually a runtime crash. */
|
||||
data class ApplicationError<T>(val throwable: Throwable) : NetworkResult<T>()
|
||||
|
||||
/**
|
||||
* Returns the result if successful, otherwise turns the result back into an exception and throws it.
|
||||
*/
|
||||
fun successOrThrow(): T {
|
||||
when (this) {
|
||||
is Success -> return result
|
||||
is NetworkError -> throw throwable ?: PushNetworkException("Network error")
|
||||
is StatusCodeError -> throw throwable ?: NonSuccessfulResponseCodeException(this.code)
|
||||
is ApplicationError -> throw throwable
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes the output of one [NetworkResult] and transforms it into another if the operation is successful.
|
||||
* If it's a failure, the original failure will be propagated. Useful for changing the type of a result.
|
||||
*/
|
||||
fun <R> map(transform: (T) -> R): NetworkResult<R> {
|
||||
return when (this) {
|
||||
is Success -> Success(transform(this.result))
|
||||
is NetworkError -> NetworkError(throwable)
|
||||
is StatusCodeError -> StatusCodeError(code, throwable)
|
||||
is ApplicationError -> ApplicationError(throwable)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes the output of one [NetworkResult] and passes it as the input to another if the operation is successful.
|
||||
* If it's a failure, the original failure will be propagated. Useful for chaining operations together.
|
||||
*/
|
||||
fun <R> then(result: (T) -> NetworkResult<R>): NetworkResult<R> {
|
||||
return when (this) {
|
||||
is Success -> result(this.result)
|
||||
is NetworkError -> NetworkError(throwable)
|
||||
is StatusCodeError -> StatusCodeError(code, throwable)
|
||||
is ApplicationError -> ApplicationError(throwable)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,7 @@ import org.whispersystems.signalservice.api.account.ChangePhoneNumberRequest;
|
||||
import org.whispersystems.signalservice.api.account.PniKeyDistributionRequest;
|
||||
import org.whispersystems.signalservice.api.account.PreKeyCollection;
|
||||
import org.whispersystems.signalservice.api.account.PreKeyUpload;
|
||||
import org.whispersystems.signalservice.api.archive.ArchiveApi;
|
||||
import org.whispersystems.signalservice.api.crypto.ProfileCipher;
|
||||
import org.whispersystems.signalservice.api.crypto.ProfileCipherOutputStream;
|
||||
import org.whispersystems.signalservice.api.groupsv2.ClientZkOperations;
|
||||
@@ -834,6 +835,10 @@ public class SignalServiceAccountManager {
|
||||
return new GroupsV2Api(pushServiceSocket, groupsV2Operations);
|
||||
}
|
||||
|
||||
public ArchiveApi getArchiveApi() {
|
||||
return ArchiveApi.create(pushServiceSocket, configuration.getBackupServerPublicParams(), credentials.getAci());
|
||||
}
|
||||
|
||||
public AuthCredentials getPaymentsAuthorization() throws IOException {
|
||||
return pushServiceSocket.getPaymentsAuthorization();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.signalservice.api.archive
|
||||
|
||||
import org.signal.libsignal.protocol.ecc.Curve
|
||||
import org.signal.libsignal.protocol.ecc.ECPrivateKey
|
||||
import org.signal.libsignal.protocol.ecc.ECPublicKey
|
||||
import org.signal.libsignal.zkgroup.GenericServerPublicParams
|
||||
import org.signal.libsignal.zkgroup.backups.BackupAuthCredential
|
||||
import org.signal.libsignal.zkgroup.backups.BackupAuthCredentialRequestContext
|
||||
import org.signal.libsignal.zkgroup.backups.BackupAuthCredentialResponse
|
||||
import org.whispersystems.signalservice.api.NetworkResult
|
||||
import org.whispersystems.signalservice.api.backup.BackupKey
|
||||
import org.whispersystems.signalservice.api.push.ServiceId.ACI
|
||||
import org.whispersystems.signalservice.internal.push.PushServiceSocket
|
||||
|
||||
/**
|
||||
* Class to interact with various archive-related endpoints.
|
||||
* Why is it called archive instead of backup? Because SVR took the "backup" endpoint namespace first :)
|
||||
*/
|
||||
class ArchiveApi(
|
||||
private val pushServiceSocket: PushServiceSocket,
|
||||
private val backupServerPublicParams: GenericServerPublicParams,
|
||||
private val aci: ACI
|
||||
) {
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun create(pushServiceSocket: PushServiceSocket, backupServerPublicParams: ByteArray, aci: ACI): ArchiveApi {
|
||||
return ArchiveApi(
|
||||
pushServiceSocket,
|
||||
GenericServerPublicParams(backupServerPublicParams),
|
||||
aci
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a set of credentials one can use to authorize other requests.
|
||||
*
|
||||
* You'll receive a set of credentials spanning 7 days. Cache them and store them for later use.
|
||||
* It's important that (at least in the common case) you do not request credentials on-the-fly.
|
||||
* Instead, request them in advance on a regular schedule. This is because the purpose of these
|
||||
* credentials is to keep the caller anonymous, but that doesn't help if this authenticated request
|
||||
* happens right before all of the unauthenticated ones, as that would make it easier to correlate
|
||||
* traffic.
|
||||
*/
|
||||
fun getServiceCredentials(currentTime: Long): NetworkResult<ArchiveServiceCredentialsResponse> {
|
||||
return NetworkResult.fromFetch {
|
||||
pushServiceSocket.getArchiveCredentials(currentTime)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that you reserve a backupId on the service. This must be done before any other
|
||||
* backup-related calls. You only need to do it once, but repeated calls are safe.
|
||||
*/
|
||||
fun triggerBackupIdReservation(backupKey: BackupKey): NetworkResult<Unit> {
|
||||
return NetworkResult.fromFetch {
|
||||
val backupRequestContext = BackupAuthCredentialRequestContext.create(backupKey.value, aci.rawUuid)
|
||||
pushServiceSocket.setArchiveBackupId(backupRequestContext.request)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a public key on the service derived from your [BackupKey]. This key is used to prevent
|
||||
* unauthorized users from changing your backup data. You only need to do it once, but repeated
|
||||
* calls are safe.
|
||||
*/
|
||||
fun setPublicKey(backupKey: BackupKey, serviceCredential: ArchiveServiceCredential): NetworkResult<Unit> {
|
||||
return NetworkResult.fromFetch {
|
||||
val zkCredential = getZkCredential(backupKey, serviceCredential)
|
||||
val presentationData = CredentialPresentationData.from(backupKey, zkCredential, backupServerPublicParams)
|
||||
pushServiceSocket.setArchivePublicKey(presentationData.publicKey, presentationData.toArchiveCredentialPresentation())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches an upload form you can use to upload your main message backup file to cloud storage.
|
||||
*/
|
||||
fun getMessageBackupUploadForm(backupKey: BackupKey, serviceCredential: ArchiveServiceCredential): NetworkResult<ArchiveMessageBackupUploadFormResponse> {
|
||||
return NetworkResult.fromFetch {
|
||||
val zkCredential = getZkCredential(backupKey, serviceCredential)
|
||||
val presentationData = CredentialPresentationData.from(backupKey, zkCredential, backupServerPublicParams)
|
||||
pushServiceSocket.getArchiveMessageBackupUploadForm(presentationData.toArchiveCredentialPresentation())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches metadata about your current backup.
|
||||
* Will return a [NetworkResult.StatusCodeError] with status code 404 if you haven't uploaded a
|
||||
* backup yet.
|
||||
*/
|
||||
fun getBackupInfo(backupKey: BackupKey, serviceCredential: ArchiveServiceCredential): NetworkResult<ArchiveGetBackupInfoResponse> {
|
||||
return NetworkResult.fromFetch {
|
||||
val zkCredential = getZkCredential(backupKey, serviceCredential)
|
||||
val presentationData = CredentialPresentationData.from(backupKey, zkCredential, backupServerPublicParams)
|
||||
pushServiceSocket.getArchiveBackupInfo(presentationData.toArchiveCredentialPresentation())
|
||||
}
|
||||
}
|
||||
|
||||
private fun getZkCredential(backupKey: BackupKey, serviceCredential: ArchiveServiceCredential): BackupAuthCredential {
|
||||
val backupAuthResponse = BackupAuthCredentialResponse(serviceCredential.credential)
|
||||
val backupRequestContext = BackupAuthCredentialRequestContext.create(backupKey.value, aci.rawUuid)
|
||||
|
||||
return backupRequestContext.receiveResponse(
|
||||
backupAuthResponse,
|
||||
backupServerPublicParams,
|
||||
10
|
||||
)
|
||||
}
|
||||
|
||||
private class CredentialPresentationData(
|
||||
val privateKey: ECPrivateKey,
|
||||
val presentation: ByteArray,
|
||||
val signedPresentation: ByteArray
|
||||
) {
|
||||
val publicKey: ECPublicKey = privateKey.publicKey()
|
||||
|
||||
companion object {
|
||||
fun from(backupKey: BackupKey, credential: BackupAuthCredential, backupServerPublicParams: GenericServerPublicParams): CredentialPresentationData {
|
||||
val privateKey: ECPrivateKey = Curve.decodePrivatePoint(backupKey.value)
|
||||
val presentation: ByteArray = credential.present(backupServerPublicParams).serialize()
|
||||
val signedPresentation: ByteArray = privateKey.calculateSignature(presentation)
|
||||
|
||||
return CredentialPresentationData(privateKey, presentation, signedPresentation)
|
||||
}
|
||||
}
|
||||
|
||||
fun toArchiveCredentialPresentation(): ArchiveCredentialPresentation {
|
||||
return ArchiveCredentialPresentation(
|
||||
presentation = presentation,
|
||||
signedPresentation = signedPresentation
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.signalservice.api.archive
|
||||
|
||||
/**
|
||||
* Acts as credentials for various archive operations.
|
||||
*/
|
||||
class ArchiveCredentialPresentation(
|
||||
val presentation: ByteArray,
|
||||
val signedPresentation: ByteArray
|
||||
)
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.signalservice.api.archive
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
|
||||
/**
|
||||
* Represents the response when fetching the archive backup info.
|
||||
*/
|
||||
data class ArchiveGetBackupInfoResponse(
|
||||
@JsonProperty
|
||||
val cdn: Int?,
|
||||
@JsonProperty
|
||||
val backupDir: String?,
|
||||
@JsonProperty
|
||||
val backupName: String?,
|
||||
@JsonProperty
|
||||
val usedSpace: Long?
|
||||
)
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.signalservice.api.archive
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
|
||||
/**
|
||||
* Represents the response body when we ask for a message backup upload form.
|
||||
*/
|
||||
data class ArchiveMessageBackupUploadFormResponse(
|
||||
@JsonProperty
|
||||
val cdn: Int,
|
||||
@JsonProperty
|
||||
val key: String,
|
||||
@JsonProperty
|
||||
val headers: Map<String, String>,
|
||||
@JsonProperty
|
||||
val signedUploadLocation: String
|
||||
)
|
||||
@@ -0,0 +1,15 @@
|
||||
package org.whispersystems.signalservice.api.archive
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
|
||||
/**
|
||||
* Represents an individual credential for an archive operation. Note that is isn't the final
|
||||
* credential you will actually use -- that's [org.signal.libsignal.zkgroup.backups.BackupAuthCredential].
|
||||
* But you use these to make those.
|
||||
*/
|
||||
class ArchiveServiceCredential(
|
||||
@JsonProperty
|
||||
val credential: ByteArray,
|
||||
@JsonProperty
|
||||
val redemptionTime: Long
|
||||
)
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.signalservice.api.archive
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
|
||||
/**
|
||||
* Represents the result of fetching archive credentials.
|
||||
* See [ArchiveServiceCredential].
|
||||
*/
|
||||
class ArchiveServiceCredentialsResponse(
|
||||
@JsonProperty
|
||||
val credentials: Array<ArchiveServiceCredential>
|
||||
)
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.signalservice.api.archive
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.fasterxml.jackson.core.JsonGenerator
|
||||
import com.fasterxml.jackson.databind.JsonSerializer
|
||||
import com.fasterxml.jackson.databind.SerializerProvider
|
||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize
|
||||
import org.signal.core.util.Base64
|
||||
import org.signal.libsignal.zkgroup.backups.BackupAuthCredentialRequest
|
||||
|
||||
/**
|
||||
* Represents the request body when setting the archive backupId.
|
||||
*/
|
||||
class ArchiveSetBackupIdRequest(
|
||||
@JsonProperty
|
||||
@JsonSerialize(using = BackupAuthCredentialRequestSerializer::class)
|
||||
val backupAuthCredentialRequest: BackupAuthCredentialRequest
|
||||
) {
|
||||
class BackupAuthCredentialRequestSerializer : JsonSerializer<BackupAuthCredentialRequest>() {
|
||||
override fun serialize(value: BackupAuthCredentialRequest, gen: JsonGenerator, serializers: SerializerProvider) {
|
||||
gen.writeString(Base64.encodeWithPadding(value.serialize()))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.signalservice.api.archive
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.fasterxml.jackson.core.JsonGenerator
|
||||
import com.fasterxml.jackson.databind.JsonSerializer
|
||||
import com.fasterxml.jackson.databind.SerializerProvider
|
||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize
|
||||
import org.signal.core.util.Base64
|
||||
import org.signal.libsignal.protocol.ecc.ECPublicKey
|
||||
|
||||
/**
|
||||
* Represents the request body when setting the archive public key.
|
||||
*/
|
||||
class ArchiveSetPublicKeyRequest(
|
||||
@JsonProperty
|
||||
@JsonSerialize(using = PublicKeySerializer::class)
|
||||
val backupIdPublicKey: ECPublicKey
|
||||
) {
|
||||
class PublicKeySerializer : JsonSerializer<ECPublicKey>() {
|
||||
override fun serialize(value: ECPublicKey, gen: JsonGenerator, serializers: SerializerProvider) {
|
||||
gen.writeString(Base64.encodeWithPadding(value.serialize()))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,5 +17,6 @@ class SignalServiceConfiguration(
|
||||
val dns: Optional<Dns>,
|
||||
val signalProxy: Optional<SignalProxy>,
|
||||
val zkGroupServerPublicParams: ByteArray,
|
||||
val genericServerPublicParams: ByteArray
|
||||
val genericServerPublicParams: ByteArray,
|
||||
val backupServerPublicParams: ByteArray
|
||||
)
|
||||
|
||||
@@ -20,6 +20,7 @@ import org.signal.libsignal.protocol.util.Pair;
|
||||
import org.signal.libsignal.usernames.BaseUsernameException;
|
||||
import org.signal.libsignal.usernames.Username;
|
||||
import org.signal.libsignal.zkgroup.VerificationFailedException;
|
||||
import org.signal.libsignal.zkgroup.backups.BackupAuthCredentialRequest;
|
||||
import org.signal.libsignal.zkgroup.calllinks.CreateCallLinkCredentialRequest;
|
||||
import org.signal.libsignal.zkgroup.calllinks.CreateCallLinkCredentialResponse;
|
||||
import org.signal.libsignal.zkgroup.profiles.ClientZkProfileOperations;
|
||||
@@ -42,6 +43,12 @@ import org.whispersystems.signalservice.api.account.ChangePhoneNumberRequest;
|
||||
import org.whispersystems.signalservice.api.account.PniKeyDistributionRequest;
|
||||
import org.whispersystems.signalservice.api.account.PreKeyCollection;
|
||||
import org.whispersystems.signalservice.api.account.PreKeyUpload;
|
||||
import org.whispersystems.signalservice.api.archive.ArchiveCredentialPresentation;
|
||||
import org.whispersystems.signalservice.api.archive.ArchiveServiceCredentialsResponse;
|
||||
import org.whispersystems.signalservice.api.archive.ArchiveGetBackupInfoResponse;
|
||||
import org.whispersystems.signalservice.api.archive.ArchiveMessageBackupUploadFormResponse;
|
||||
import org.whispersystems.signalservice.api.archive.ArchiveSetBackupIdRequest;
|
||||
import org.whispersystems.signalservice.api.archive.ArchiveSetPublicKeyRequest;
|
||||
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess;
|
||||
import org.whispersystems.signalservice.api.groupsv2.CredentialResponse;
|
||||
import org.whispersystems.signalservice.api.groupsv2.GroupsV2AuthorizationString;
|
||||
@@ -85,7 +92,6 @@ import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException
|
||||
import org.whispersystems.signalservice.api.push.exceptions.RangeException;
|
||||
import org.whispersystems.signalservice.api.push.exceptions.RateLimitException;
|
||||
import org.whispersystems.signalservice.api.push.exceptions.RegistrationRetryException;
|
||||
import org.whispersystems.signalservice.api.push.exceptions.RemoteAttestationResponseExpiredException;
|
||||
import org.whispersystems.signalservice.api.push.exceptions.ResumeLocationInvalidException;
|
||||
import org.whispersystems.signalservice.api.push.exceptions.ServerRejectedException;
|
||||
import org.whispersystems.signalservice.api.push.exceptions.TokenNotAcceptedException;
|
||||
@@ -108,9 +114,6 @@ import org.whispersystems.signalservice.internal.configuration.SignalCdnUrl;
|
||||
import org.whispersystems.signalservice.internal.configuration.SignalProxy;
|
||||
import org.whispersystems.signalservice.internal.configuration.SignalServiceConfiguration;
|
||||
import org.whispersystems.signalservice.internal.configuration.SignalUrl;
|
||||
import org.whispersystems.signalservice.internal.contacts.entities.KeyBackupRequest;
|
||||
import org.whispersystems.signalservice.internal.contacts.entities.KeyBackupResponse;
|
||||
import org.whispersystems.signalservice.internal.contacts.entities.TokenResponse;
|
||||
import org.whispersystems.signalservice.internal.crypto.AttachmentDigest;
|
||||
import org.whispersystems.signalservice.internal.push.exceptions.DonationProcessorError;
|
||||
import org.whispersystems.signalservice.internal.push.exceptions.DonationReceiptCredentialError;
|
||||
@@ -303,6 +306,12 @@ public class PushServiceSocket {
|
||||
|
||||
private static final String BACKUP_AUTH_CHECK = "/v2/backup/auth/check";
|
||||
|
||||
private static final String ARCHIVE_CREDENTIALS = "/v1/archives/auth?redemptionStartSeconds=%d&redemptionEndSeconds=%d";
|
||||
private static final String ARCHIVE_BACKUP_ID = "/v1/archives/backupid";
|
||||
private static final String ARCHIVE_PUBLIC_KEY = "/v1/archives/keys";
|
||||
private static final String ARCHIVE_INFO = "/v1/archives";
|
||||
private static final String ARCHIVE_MESSAGE_UPLOAD_FORM = "/v1/archives/upload/form";
|
||||
|
||||
private static final String CALL_LINK_CREATION_AUTH = "/v1/call-link/create-auth";
|
||||
private static final String SERVER_DELIVERED_TIMESTAMP_HEADER = "X-Signal-Timestamp";
|
||||
|
||||
@@ -471,6 +480,48 @@ public class PushServiceSocket {
|
||||
return credentials;
|
||||
}
|
||||
|
||||
public ArchiveServiceCredentialsResponse getArchiveCredentials(long currentTime) throws IOException {
|
||||
long secondsRoundedToNearestDay = TimeUnit.DAYS.toSeconds(TimeUnit.MILLISECONDS.toDays(currentTime));
|
||||
long endTimeInSeconds = secondsRoundedToNearestDay + TimeUnit.DAYS.toSeconds(7);
|
||||
|
||||
String response = makeServiceRequest(String.format(Locale.US, ARCHIVE_CREDENTIALS, secondsRoundedToNearestDay, endTimeInSeconds), "GET", null);
|
||||
|
||||
return JsonUtil.fromJson(response, ArchiveServiceCredentialsResponse.class);
|
||||
}
|
||||
|
||||
public void setArchiveBackupId(BackupAuthCredentialRequest request) throws IOException {
|
||||
String body = JsonUtil.toJson(new ArchiveSetBackupIdRequest(request));
|
||||
makeServiceRequest(ARCHIVE_BACKUP_ID, "PUT", body);
|
||||
}
|
||||
|
||||
public void setArchivePublicKey(ECPublicKey publicKey, ArchiveCredentialPresentation credentialPresentation) throws IOException {
|
||||
Map<String, String> headers = new HashMap<>();
|
||||
headers.put("X-Signal-ZK-Auth", Base64.encodeWithPadding(credentialPresentation.getPresentation()));
|
||||
headers.put("X-Signal-ZK-Auth-Signature", Base64.encodeWithPadding(credentialPresentation.getSignedPresentation()));
|
||||
|
||||
String body = JsonUtil.toJson(new ArchiveSetPublicKeyRequest(publicKey));
|
||||
makeServiceRequestWithoutAuthentication(ARCHIVE_PUBLIC_KEY, "PUT", body, headers, NO_HANDLER);
|
||||
}
|
||||
|
||||
public ArchiveGetBackupInfoResponse getArchiveBackupInfo(ArchiveCredentialPresentation credentialPresentation) throws IOException {
|
||||
Map<String, String> headers = new HashMap<>();
|
||||
headers.put("X-Signal-ZK-Auth", Base64.encodeWithPadding(credentialPresentation.getPresentation()));
|
||||
headers.put("X-Signal-ZK-Auth-Signature", Base64.encodeWithPadding(credentialPresentation.getSignedPresentation()));
|
||||
|
||||
String response = makeServiceRequestWithoutAuthentication(ARCHIVE_INFO, "GET", null, headers, NO_HANDLER);
|
||||
return JsonUtil.fromJson(response, ArchiveGetBackupInfoResponse.class);
|
||||
}
|
||||
|
||||
public ArchiveMessageBackupUploadFormResponse getArchiveMessageBackupUploadForm(ArchiveCredentialPresentation credentialPresentation) throws IOException {
|
||||
Map<String, String> headers = new HashMap<>();
|
||||
headers.put("X-Signal-ZK-Auth", Base64.encodeWithPadding(credentialPresentation.getPresentation()));
|
||||
headers.put("X-Signal-ZK-Auth-Signature", Base64.encodeWithPadding(credentialPresentation.getSignedPresentation()));
|
||||
|
||||
String response = makeServiceRequestWithoutAuthentication(ARCHIVE_MESSAGE_UPLOAD_FORM, "GET", null, headers, NO_HANDLER);
|
||||
return JsonUtil.fromJson(response, ArchiveMessageBackupUploadFormResponse.class);
|
||||
}
|
||||
|
||||
|
||||
public VerifyAccountResponse changeNumber(@Nonnull ChangePhoneNumberRequest changePhoneNumberRequest)
|
||||
throws IOException
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user