mirror of
https://github.com/signalapp/Signal-Android.git
synced 2025-12-23 04:28:35 +00:00
Add pre-alpha support for SVR2.
This commit is contained in:
committed by
Nicholas Tinsley
parent
8cd0ac5451
commit
6cf4dbc78c
@@ -185,6 +185,7 @@ android {
|
|||||||
buildConfigField "String", "SIGNAL_CDSI_URL", "\"https://cdsi.signal.org\""
|
buildConfigField "String", "SIGNAL_CDSI_URL", "\"https://cdsi.signal.org\""
|
||||||
buildConfigField "String", "SIGNAL_SERVICE_STATUS_URL", "\"uptime.signal.org\""
|
buildConfigField "String", "SIGNAL_SERVICE_STATUS_URL", "\"uptime.signal.org\""
|
||||||
buildConfigField "String", "SIGNAL_KEY_BACKUP_URL", "\"https://api.backup.signal.org\""
|
buildConfigField "String", "SIGNAL_KEY_BACKUP_URL", "\"https://api.backup.signal.org\""
|
||||||
|
buildConfigField "String", "SIGNAL_SVR2_URL", "\"https://svr2.staging.signal.org\""
|
||||||
buildConfigField "String", "SIGNAL_SFU_URL", "\"https://sfu.voip.signal.org\""
|
buildConfigField "String", "SIGNAL_SFU_URL", "\"https://sfu.voip.signal.org\""
|
||||||
buildConfigField "String", "SIGNAL_STAGING_SFU_URL", "\"https://sfu.staging.voip.signal.org\""
|
buildConfigField "String", "SIGNAL_STAGING_SFU_URL", "\"https://sfu.staging.voip.signal.org\""
|
||||||
buildConfigField "String[]", "SIGNAL_SFU_INTERNAL_NAMES", "new String[]{\"Test\", \"Staging\", \"Development\"}"
|
buildConfigField "String[]", "SIGNAL_SFU_INTERNAL_NAMES", "new String[]{\"Test\", \"Staging\", \"Development\"}"
|
||||||
@@ -200,6 +201,7 @@ android {
|
|||||||
buildConfigField "String[]", "SIGNAL_CONTENT_PROXY_IPS", content_proxy_ips
|
buildConfigField "String[]", "SIGNAL_CONTENT_PROXY_IPS", content_proxy_ips
|
||||||
buildConfigField "String", "SIGNAL_AGENT", "\"OWA\""
|
buildConfigField "String", "SIGNAL_AGENT", "\"OWA\""
|
||||||
buildConfigField "String", "CDSI_MRENCLAVE", "\"0f6fd79cdfdaa5b2e6337f534d3baf999318b0c462a7ac1f41297a3e4b424a57\""
|
buildConfigField "String", "CDSI_MRENCLAVE", "\"0f6fd79cdfdaa5b2e6337f534d3baf999318b0c462a7ac1f41297a3e4b424a57\""
|
||||||
|
buildConfigField "String", "SVR2_MRENCLAVE", "\"dc9fd472a5a9c871a3c7f76f1af60aa9c1f314abf2e8d1e0c4ba25c8aaa2848c\""
|
||||||
buildConfigField "org.thoughtcrime.securesms.KbsEnclave", "KBS_ENCLAVE", "new org.thoughtcrime.securesms.KbsEnclave(\"e18376436159cda3ad7a45d9320e382e4a497f26b0dca34d8eab0bd0139483b5\", " +
|
buildConfigField "org.thoughtcrime.securesms.KbsEnclave", "KBS_ENCLAVE", "new org.thoughtcrime.securesms.KbsEnclave(\"e18376436159cda3ad7a45d9320e382e4a497f26b0dca34d8eab0bd0139483b5\", " +
|
||||||
"\"3a485adb56e2058ef7737764c738c4069dd62bc457637eafb6bbce1ce29ddb89\", " +
|
"\"3a485adb56e2058ef7737764c738c4069dd62bc457637eafb6bbce1ce29ddb89\", " +
|
||||||
"\"45627094b2ea4a66f4cf0b182858a8dcf4b8479122c3820fe7fd0551a6d4cf5c\")"
|
"\"45627094b2ea4a66f4cf0b182858a8dcf4b8479122c3820fe7fd0551a6d4cf5c\")"
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import org.whispersystems.signalservice.internal.configuration.SignalKeyBackupSe
|
|||||||
import org.whispersystems.signalservice.internal.configuration.SignalServiceConfiguration
|
import org.whispersystems.signalservice.internal.configuration.SignalServiceConfiguration
|
||||||
import org.whispersystems.signalservice.internal.configuration.SignalServiceUrl
|
import org.whispersystems.signalservice.internal.configuration.SignalServiceUrl
|
||||||
import org.whispersystems.signalservice.internal.configuration.SignalStorageUrl
|
import org.whispersystems.signalservice.internal.configuration.SignalStorageUrl
|
||||||
|
import org.whispersystems.signalservice.internal.configuration.SignalSvr2Url
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.util.Optional
|
import java.util.Optional
|
||||||
|
|
||||||
@@ -73,6 +74,7 @@ open class SignalServiceNetworkAccess(context: Context) {
|
|||||||
private const val F_CDN_HOST = "cdn.signal.org.global.prod.fastly.net"
|
private const val F_CDN_HOST = "cdn.signal.org.global.prod.fastly.net"
|
||||||
private const val F_CDN2_HOST = "cdn2.signal.org.global.prod.fastly.net"
|
private const val F_CDN2_HOST = "cdn2.signal.org.global.prod.fastly.net"
|
||||||
private const val F_CDSI_HOST = "cdsi-signal.global.ssl.fastly.net"
|
private const val F_CDSI_HOST = "cdsi-signal.global.ssl.fastly.net"
|
||||||
|
private const val F_SVR2_HOST = "svr2-signal.global.ssl.fastly.net"
|
||||||
private const val F_KBS_HOST = "api.backup.signal.org.global.prod.fastly.net"
|
private const val F_KBS_HOST = "api.backup.signal.org.global.prod.fastly.net"
|
||||||
|
|
||||||
private val GMAPS_CONNECTION_SPEC = ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
|
private val GMAPS_CONNECTION_SPEC = ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
|
||||||
@@ -159,18 +161,19 @@ open class SignalServiceNetworkAccess(context: Context) {
|
|||||||
private val fUrls = arrayOf("https://cdn.sstatic.net", "https://github.githubassets.com", "https://pinterest.com", "https://open.scdn.co", "https://www.redditstatic.com")
|
private val fUrls = arrayOf("https://cdn.sstatic.net", "https://github.githubassets.com", "https://pinterest.com", "https://open.scdn.co", "https://www.redditstatic.com")
|
||||||
|
|
||||||
private val fConfig: SignalServiceConfiguration = SignalServiceConfiguration(
|
private val fConfig: SignalServiceConfiguration = SignalServiceConfiguration(
|
||||||
fUrls.map { SignalServiceUrl(it, F_SERVICE_HOST, fTrustStore, APP_CONNECTION_SPEC) }.toTypedArray(),
|
signalServiceUrls = fUrls.map { SignalServiceUrl(it, F_SERVICE_HOST, fTrustStore, APP_CONNECTION_SPEC) }.toTypedArray(),
|
||||||
mapOf(
|
signalCdnUrlMap = mapOf(
|
||||||
0 to fUrls.map { SignalCdnUrl(it, F_CDN_HOST, fTrustStore, APP_CONNECTION_SPEC) }.toTypedArray(),
|
0 to fUrls.map { SignalCdnUrl(it, F_CDN_HOST, fTrustStore, APP_CONNECTION_SPEC) }.toTypedArray(),
|
||||||
2 to fUrls.map { SignalCdnUrl(it, F_CDN2_HOST, fTrustStore, APP_CONNECTION_SPEC) }.toTypedArray()
|
2 to fUrls.map { SignalCdnUrl(it, F_CDN2_HOST, fTrustStore, APP_CONNECTION_SPEC) }.toTypedArray()
|
||||||
),
|
),
|
||||||
fUrls.map { SignalKeyBackupServiceUrl(it, F_KBS_HOST, fTrustStore, APP_CONNECTION_SPEC) }.toTypedArray(),
|
signalKeyBackupServiceUrls = fUrls.map { SignalKeyBackupServiceUrl(it, F_KBS_HOST, fTrustStore, APP_CONNECTION_SPEC) }.toTypedArray(),
|
||||||
fUrls.map { SignalStorageUrl(it, F_STORAGE_HOST, fTrustStore, APP_CONNECTION_SPEC) }.toTypedArray(),
|
signalStorageUrls = fUrls.map { SignalStorageUrl(it, F_STORAGE_HOST, fTrustStore, APP_CONNECTION_SPEC) }.toTypedArray(),
|
||||||
fUrls.map { SignalCdsiUrl(it, F_CDSI_HOST, fTrustStore, APP_CONNECTION_SPEC) }.toTypedArray(),
|
signalCdsiUrls = fUrls.map { SignalCdsiUrl(it, F_CDSI_HOST, fTrustStore, APP_CONNECTION_SPEC) }.toTypedArray(),
|
||||||
interceptors,
|
signalSvr2Urls = fUrls.map { SignalSvr2Url(it, fTrustStore, F_SVR2_HOST, APP_CONNECTION_SPEC) }.toTypedArray(),
|
||||||
Optional.of(DNS),
|
networkInterceptors = interceptors,
|
||||||
Optional.empty(),
|
dns = Optional.of(DNS),
|
||||||
zkGroupServerPublicParams
|
signalProxy = Optional.empty(),
|
||||||
|
zkGroupServerPublicParams = zkGroupServerPublicParams
|
||||||
)
|
)
|
||||||
|
|
||||||
private val censorshipConfiguration: Map<Int, SignalServiceConfiguration> = mapOf(
|
private val censorshipConfiguration: Map<Int, SignalServiceConfiguration> = mapOf(
|
||||||
@@ -209,18 +212,19 @@ open class SignalServiceNetworkAccess(context: Context) {
|
|||||||
)
|
)
|
||||||
|
|
||||||
open val uncensoredConfiguration: SignalServiceConfiguration = SignalServiceConfiguration(
|
open val uncensoredConfiguration: SignalServiceConfiguration = SignalServiceConfiguration(
|
||||||
arrayOf(SignalServiceUrl(BuildConfig.SIGNAL_URL, serviceTrustStore)),
|
signalServiceUrls = arrayOf(SignalServiceUrl(BuildConfig.SIGNAL_URL, serviceTrustStore)),
|
||||||
mapOf(
|
signalCdnUrlMap = mapOf(
|
||||||
0 to arrayOf(SignalCdnUrl(BuildConfig.SIGNAL_CDN_URL, serviceTrustStore)),
|
0 to arrayOf(SignalCdnUrl(BuildConfig.SIGNAL_CDN_URL, serviceTrustStore)),
|
||||||
2 to arrayOf(SignalCdnUrl(BuildConfig.SIGNAL_CDN2_URL, serviceTrustStore))
|
2 to arrayOf(SignalCdnUrl(BuildConfig.SIGNAL_CDN2_URL, serviceTrustStore))
|
||||||
),
|
),
|
||||||
arrayOf(SignalKeyBackupServiceUrl(BuildConfig.SIGNAL_KEY_BACKUP_URL, serviceTrustStore)),
|
signalKeyBackupServiceUrls = arrayOf(SignalKeyBackupServiceUrl(BuildConfig.SIGNAL_KEY_BACKUP_URL, serviceTrustStore)),
|
||||||
arrayOf(SignalStorageUrl(BuildConfig.STORAGE_URL, serviceTrustStore)),
|
signalStorageUrls = arrayOf(SignalStorageUrl(BuildConfig.STORAGE_URL, serviceTrustStore)),
|
||||||
arrayOf(SignalCdsiUrl(BuildConfig.SIGNAL_CDSI_URL, serviceTrustStore)),
|
signalCdsiUrls = arrayOf(SignalCdsiUrl(BuildConfig.SIGNAL_CDSI_URL, serviceTrustStore)),
|
||||||
interceptors,
|
signalSvr2Urls = arrayOf(SignalSvr2Url(BuildConfig.SIGNAL_SVR2_URL, serviceTrustStore)),
|
||||||
Optional.of(DNS),
|
networkInterceptors = interceptors,
|
||||||
if (SignalStore.proxy().isProxyEnabled) Optional.ofNullable(SignalStore.proxy().proxy) else Optional.empty(),
|
dns = Optional.of(DNS),
|
||||||
zkGroupServerPublicParams
|
signalProxy = if (SignalStore.proxy().isProxyEnabled) Optional.ofNullable(SignalStore.proxy().proxy) else Optional.empty(),
|
||||||
|
zkGroupServerPublicParams = zkGroupServerPublicParams
|
||||||
)
|
)
|
||||||
|
|
||||||
open fun getConfiguration(): SignalServiceConfiguration {
|
open fun getConfiguration(): SignalServiceConfiguration {
|
||||||
@@ -272,20 +276,22 @@ open class SignalServiceNetworkAccess(context: Context) {
|
|||||||
val kbsUrls: Array<SignalKeyBackupServiceUrl> = hostConfigs.map { SignalKeyBackupServiceUrl("${it.baseUrl}/backup", it.host, gTrustStore, it.connectionSpec) }.toTypedArray()
|
val kbsUrls: Array<SignalKeyBackupServiceUrl> = hostConfigs.map { SignalKeyBackupServiceUrl("${it.baseUrl}/backup", it.host, gTrustStore, it.connectionSpec) }.toTypedArray()
|
||||||
val storageUrls: Array<SignalStorageUrl> = hostConfigs.map { SignalStorageUrl("${it.baseUrl}/storage", it.host, gTrustStore, it.connectionSpec) }.toTypedArray()
|
val storageUrls: Array<SignalStorageUrl> = hostConfigs.map { SignalStorageUrl("${it.baseUrl}/storage", it.host, gTrustStore, it.connectionSpec) }.toTypedArray()
|
||||||
val cdsiUrls: Array<SignalCdsiUrl> = hostConfigs.map { SignalCdsiUrl("${it.baseUrl}/cdsi", it.host, gTrustStore, it.connectionSpec) }.toTypedArray()
|
val cdsiUrls: Array<SignalCdsiUrl> = hostConfigs.map { SignalCdsiUrl("${it.baseUrl}/cdsi", it.host, gTrustStore, it.connectionSpec) }.toTypedArray()
|
||||||
|
val svr2Urls: Array<SignalSvr2Url> = hostConfigs.map { SignalSvr2Url("${it.baseUrl}/svr2", gTrustStore, it.host, it.connectionSpec) }.toTypedArray()
|
||||||
|
|
||||||
return SignalServiceConfiguration(
|
return SignalServiceConfiguration(
|
||||||
serviceUrls,
|
signalServiceUrls = serviceUrls,
|
||||||
mapOf(
|
signalCdnUrlMap = mapOf(
|
||||||
0 to cdnUrls,
|
0 to cdnUrls,
|
||||||
2 to cdn2Urls
|
2 to cdn2Urls
|
||||||
),
|
),
|
||||||
kbsUrls,
|
signalKeyBackupServiceUrls = kbsUrls,
|
||||||
storageUrls,
|
signalStorageUrls = storageUrls,
|
||||||
cdsiUrls,
|
signalCdsiUrls = cdsiUrls,
|
||||||
interceptors,
|
signalSvr2Urls = arrayOf(),
|
||||||
Optional.of(DNS),
|
networkInterceptors = interceptors,
|
||||||
Optional.empty(),
|
dns = Optional.of(DNS),
|
||||||
zkGroupServerPublicParams
|
signalProxy = Optional.empty(),
|
||||||
|
zkGroupServerPublicParams = zkGroupServerPublicParams
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ import org.whispersystems.signalservice.api.storage.SignalStorageRecord;
|
|||||||
import org.whispersystems.signalservice.api.storage.StorageId;
|
import org.whispersystems.signalservice.api.storage.StorageId;
|
||||||
import org.whispersystems.signalservice.api.storage.StorageKey;
|
import org.whispersystems.signalservice.api.storage.StorageKey;
|
||||||
import org.whispersystems.signalservice.api.storage.StorageManifestKey;
|
import org.whispersystems.signalservice.api.storage.StorageManifestKey;
|
||||||
|
import org.whispersystems.signalservice.api.svr.SecureValueRecoveryV2;
|
||||||
import org.whispersystems.signalservice.api.util.CredentialsProvider;
|
import org.whispersystems.signalservice.api.util.CredentialsProvider;
|
||||||
import org.whispersystems.signalservice.api.util.Preconditions;
|
import org.whispersystems.signalservice.api.util.Preconditions;
|
||||||
import org.whispersystems.signalservice.internal.ServiceResponse;
|
import org.whispersystems.signalservice.internal.ServiceResponse;
|
||||||
@@ -173,6 +174,10 @@ public class SignalServiceAccountManager {
|
|||||||
return this.pushServiceSocket.getUuidOnlySenderCertificate();
|
return this.pushServiceSocket.getUuidOnlySenderCertificate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public SecureValueRecoveryV2 getSecureValueRecoveryV2(String mrEnclave) {
|
||||||
|
return new SecureValueRecoveryV2(configuration, mrEnclave, pushServiceSocket);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* V1 PINs are no longer used in favor of V2 PINs stored on KBS.
|
* V1 PINs are no longer used in favor of V2 PINs stored on KBS.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -165,7 +165,7 @@ final class CdsiSocket {
|
|||||||
webSocket.close(1000, "OK");
|
webSocket.close(1000, "OK");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} catch (IOException | SgxCommunicationFailureException | AttestationDataException e) {
|
} catch (IOException | AttestationDataException | SgxCommunicationFailureException e) {
|
||||||
Log.w(TAG, e);
|
Log.w(TAG, e);
|
||||||
webSocket.close(1000, "OK");
|
webSocket.close(1000, "OK");
|
||||||
emitter.tryOnError(e);
|
emitter.tryOnError(e);
|
||||||
|
|||||||
@@ -0,0 +1,199 @@
|
|||||||
|
package org.whispersystems.signalservice.api.svr
|
||||||
|
|
||||||
|
import io.reactivex.rxjava3.core.Single
|
||||||
|
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||||
|
import okio.ByteString.Companion.toByteString
|
||||||
|
import org.signal.libsignal.svr2.PinHash
|
||||||
|
import org.signal.svr2.proto.BackupRequest
|
||||||
|
import org.signal.svr2.proto.DeleteRequest
|
||||||
|
import org.signal.svr2.proto.Request
|
||||||
|
import org.signal.svr2.proto.RestoreRequest
|
||||||
|
import org.whispersystems.signalservice.api.crypto.InvalidCiphertextException
|
||||||
|
import org.whispersystems.signalservice.api.kbs.MasterKey
|
||||||
|
import org.whispersystems.signalservice.api.kbs.PinHashUtil
|
||||||
|
import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException
|
||||||
|
import org.whispersystems.signalservice.internal.configuration.SignalServiceConfiguration
|
||||||
|
import org.whispersystems.signalservice.internal.push.PushServiceSocket
|
||||||
|
import java.io.IOException
|
||||||
|
import org.signal.svr2.proto.BackupResponse as ProtoBackupResponse
|
||||||
|
import org.signal.svr2.proto.RestoreResponse as ProtoRestoreResponse
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An interface for working with V2 of the Secure Value Recovery service.
|
||||||
|
*/
|
||||||
|
class SecureValueRecoveryV2(
|
||||||
|
private val serviceConfiguration: SignalServiceConfiguration,
|
||||||
|
private val mrEnclave: String,
|
||||||
|
private val pushServiceSocket: PushServiceSocket
|
||||||
|
) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the provided data on the SVR service with the provided PIN.
|
||||||
|
*
|
||||||
|
* @param pin The user-specified PIN.
|
||||||
|
* @param masterKey The data to set on SVR.
|
||||||
|
*/
|
||||||
|
fun setPin(pin: PinHash, masterKey: MasterKey): Single<BackupResponse> {
|
||||||
|
val data = PinHashUtil.createNewKbsData(pin, masterKey)
|
||||||
|
|
||||||
|
val request = Request(
|
||||||
|
backup = BackupRequest(
|
||||||
|
pin = data.kbsAccessKey.toByteString(),
|
||||||
|
data_ = data.cipherText.toByteString(),
|
||||||
|
maxTries = 10
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return getAuthorization()
|
||||||
|
.flatMap { auth -> Svr2Socket(serviceConfiguration, mrEnclave).makeRequest(auth, request) }
|
||||||
|
.map { response ->
|
||||||
|
when (response.backup?.status) {
|
||||||
|
ProtoBackupResponse.Status.OK -> {
|
||||||
|
BackupResponse.Success
|
||||||
|
}
|
||||||
|
ProtoBackupResponse.Status.REQUEST_INVALID -> {
|
||||||
|
BackupResponse.ApplicationError(InvalidRequestException("BackupResponse returned status code for REQUEST_INVALID"))
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
BackupResponse.ApplicationError(IllegalStateException("Unknown status: ${response.backup?.status}"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onErrorReturn { throwable ->
|
||||||
|
when (throwable) {
|
||||||
|
is NonSuccessfulResponseCodeException -> BackupResponse.ApplicationError(throwable)
|
||||||
|
is IOException -> BackupResponse.NetworkError(throwable)
|
||||||
|
else -> BackupResponse.ApplicationError(throwable)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Restores the user's SVR data from the service. Intended to be called in the situation where the user is not yet registered.
|
||||||
|
* Currently, this will only happen during a reglock challenge. When in this state, the user is not registered, and will instead
|
||||||
|
* be provided credentials in a service response to give the user an opportunity to restore SVR data and generate the reglock proof.
|
||||||
|
*
|
||||||
|
* If the user is already registered, use [restoreDataPostRegistration]
|
||||||
|
*/
|
||||||
|
fun restoreDataPreRegistration(authorization: String, pinHash: PinHash): Single<RestoreResponse> {
|
||||||
|
return restoreData(Single.just(authorization), pinHash)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Restores data from SVR. Only intended to be called if the user is already registered. If the user is not yet registered, use [restoreDataPreRegistration]
|
||||||
|
*/
|
||||||
|
fun restoreDataPostRegistration(pinHash: PinHash): Single<RestoreResponse> {
|
||||||
|
return restoreData(getAuthorization(), pinHash)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes the user's SVR data from the service.
|
||||||
|
*/
|
||||||
|
fun deleteData(): Single<DeleteResponse> {
|
||||||
|
val request = Request(delete = DeleteRequest())
|
||||||
|
|
||||||
|
return getAuthorization()
|
||||||
|
.flatMap { auth -> Svr2Socket(serviceConfiguration, mrEnclave).makeRequest(auth, request) }
|
||||||
|
.map { DeleteResponse.Success as DeleteResponse }
|
||||||
|
.onErrorReturn { throwable ->
|
||||||
|
when (throwable) {
|
||||||
|
is NonSuccessfulResponseCodeException -> DeleteResponse.ApplicationError(throwable)
|
||||||
|
is IOException -> DeleteResponse.NetworkError(throwable)
|
||||||
|
else -> DeleteResponse.ApplicationError(throwable)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun restoreData(authorization: Single<String>, pinHash: PinHash): Single<RestoreResponse> {
|
||||||
|
val request = Request(
|
||||||
|
restore = RestoreRequest(pin = pinHash.accessKey().toByteString())
|
||||||
|
)
|
||||||
|
|
||||||
|
return authorization
|
||||||
|
.flatMap { auth -> Svr2Socket(serviceConfiguration, mrEnclave).makeRequest(auth, request) }
|
||||||
|
.map { response ->
|
||||||
|
when (response.restore?.status) {
|
||||||
|
ProtoRestoreResponse.Status.OK -> {
|
||||||
|
val ciphertext: ByteArray = response.restore.data_.toByteArray()
|
||||||
|
try {
|
||||||
|
val masterKey: MasterKey = PinHashUtil.decryptKbsDataIVCipherText(pinHash, ciphertext).masterKey
|
||||||
|
RestoreResponse.Success(masterKey)
|
||||||
|
} catch (e: InvalidCiphertextException) {
|
||||||
|
RestoreResponse.ApplicationError(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ProtoRestoreResponse.Status.MISSING -> {
|
||||||
|
RestoreResponse.Missing
|
||||||
|
}
|
||||||
|
ProtoRestoreResponse.Status.PIN_MISMATCH -> {
|
||||||
|
RestoreResponse.PinMismatch(response.restore.tries)
|
||||||
|
}
|
||||||
|
ProtoRestoreResponse.Status.REQUEST_INVALID -> {
|
||||||
|
RestoreResponse.ApplicationError(InvalidRequestException("RestoreResponse returned status code for REQUEST_INVALID"))
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
RestoreResponse.ApplicationError(IllegalStateException("Unknown status: ${response.backup?.status}"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onErrorReturn { throwable ->
|
||||||
|
when (throwable) {
|
||||||
|
is NonSuccessfulResponseCodeException -> RestoreResponse.ApplicationError(throwable)
|
||||||
|
is IOException -> RestoreResponse.NetworkError(throwable)
|
||||||
|
else -> RestoreResponse.ApplicationError(throwable)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getAuthorization(): Single<String> {
|
||||||
|
return Single.fromCallable { pushServiceSocket.svr2Authorization }
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Response for setting a PIN. */
|
||||||
|
sealed class BackupResponse {
|
||||||
|
/** Operation completed successfully. */
|
||||||
|
object Success : BackupResponse()
|
||||||
|
|
||||||
|
/** There as a network error. Not a bad response, but rather interference or some other inability to make a network request. */
|
||||||
|
data class NetworkError(val exception: IOException) : BackupResponse()
|
||||||
|
|
||||||
|
/** Something went wrong when making the request that is related to application logic. */
|
||||||
|
data class ApplicationError(val exception: Throwable) : BackupResponse()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Response for restoring data with you PIN. */
|
||||||
|
sealed class RestoreResponse {
|
||||||
|
/** Operation completed successfully. Includes the restored data. */
|
||||||
|
data class Success(val masterKey: MasterKey) : RestoreResponse()
|
||||||
|
|
||||||
|
/** No data was found for this user. Could mean that none ever existed, or that the service deleted the data after too many incorrect PIN guesses. */
|
||||||
|
object Missing : RestoreResponse()
|
||||||
|
|
||||||
|
/** The PIN was incorrect. Includes the number of attempts the user has remaining. */
|
||||||
|
data class PinMismatch(val triesRemaining: Int) : RestoreResponse()
|
||||||
|
|
||||||
|
/** There as a network error. Not a bad response, but rather interference or some other inability to make a network request. */
|
||||||
|
data class NetworkError(val exception: IOException) : RestoreResponse()
|
||||||
|
|
||||||
|
/** Something went wrong when making the request that is related to application logic. */
|
||||||
|
data class ApplicationError(val exception: Throwable) : RestoreResponse()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Response for deleting data. */
|
||||||
|
sealed class DeleteResponse {
|
||||||
|
/** Operation completed successfully. */
|
||||||
|
object Success : DeleteResponse()
|
||||||
|
|
||||||
|
/** There as a network error. Not a bad response, but rather interference or some other inability to make a network request. */
|
||||||
|
data class NetworkError(val exception: IOException) : DeleteResponse()
|
||||||
|
|
||||||
|
/** Something went wrong when making the request that is related to application logic. */
|
||||||
|
data class ApplicationError(val exception: Throwable) : DeleteResponse()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Exception indicating that we received a response from the service that our request was invalid. */
|
||||||
|
class InvalidRequestException(message: String) : Exception(message)
|
||||||
|
}
|
||||||
@@ -0,0 +1,212 @@
|
|||||||
|
package org.whispersystems.signalservice.api.svr
|
||||||
|
|
||||||
|
import io.reactivex.rxjava3.core.Single
|
||||||
|
import io.reactivex.rxjava3.core.SingleEmitter
|
||||||
|
import okhttp3.ConnectionSpec
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
import okhttp3.Request
|
||||||
|
import okhttp3.Response
|
||||||
|
import okhttp3.WebSocket
|
||||||
|
import okhttp3.WebSocketListener
|
||||||
|
import okio.ByteString
|
||||||
|
import okio.ByteString.Companion.toByteString
|
||||||
|
import org.signal.libsignal.attest.AttestationDataException
|
||||||
|
import org.signal.libsignal.protocol.logging.Log
|
||||||
|
import org.signal.libsignal.protocol.util.Pair
|
||||||
|
import org.signal.libsignal.sgxsession.SgxCommunicationFailureException
|
||||||
|
import org.signal.libsignal.svr2.Svr2Client
|
||||||
|
import org.whispersystems.signalservice.api.push.TrustStore
|
||||||
|
import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException
|
||||||
|
import org.whispersystems.signalservice.api.util.Tls12SocketFactory
|
||||||
|
import org.whispersystems.signalservice.api.util.TlsProxySocketFactory
|
||||||
|
import org.whispersystems.signalservice.internal.configuration.SignalServiceConfiguration
|
||||||
|
import org.whispersystems.signalservice.internal.configuration.SignalSvr2Url
|
||||||
|
import org.whispersystems.signalservice.internal.util.BlacklistingTrustManager
|
||||||
|
import org.whispersystems.signalservice.internal.util.Hex
|
||||||
|
import org.whispersystems.signalservice.internal.util.Util
|
||||||
|
import java.io.IOException
|
||||||
|
import java.security.KeyManagementException
|
||||||
|
import java.security.NoSuchAlgorithmException
|
||||||
|
import java.time.Instant
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
import java.util.concurrent.atomic.AtomicReference
|
||||||
|
import javax.net.ssl.SSLContext
|
||||||
|
import javax.net.ssl.SSLSocketFactory
|
||||||
|
import javax.net.ssl.X509TrustManager
|
||||||
|
import org.signal.svr2.proto.Request as Svr2Request
|
||||||
|
import org.signal.svr2.proto.Response as Svr2Response
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the websocket and general lifecycle of an SVR2 request.
|
||||||
|
*/
|
||||||
|
internal class Svr2Socket(
|
||||||
|
configuration: SignalServiceConfiguration,
|
||||||
|
private val mrEnclave: String
|
||||||
|
) {
|
||||||
|
private val svr2Url: SignalSvr2Url = chooseUrl(configuration.signalSvr2Urls)
|
||||||
|
private val okhttp: OkHttpClient = buildOkHttpClient(configuration, svr2Url)
|
||||||
|
|
||||||
|
fun makeRequest(authorization: String, clientRequest: Svr2Request): Single<Svr2Response> {
|
||||||
|
return Single.create { emitter ->
|
||||||
|
val openRequest: Request.Builder = Request.Builder()
|
||||||
|
.url("${svr2Url.url}/v1/$mrEnclave")
|
||||||
|
.addHeader("Authorization", authorization)
|
||||||
|
|
||||||
|
if (svr2Url.hostHeader.isPresent) {
|
||||||
|
openRequest.addHeader("Host", svr2Url.hostHeader.get())
|
||||||
|
Log.w(TAG, "Using alternate host: ${svr2Url.hostHeader.get()}")
|
||||||
|
}
|
||||||
|
|
||||||
|
val webSocket = okhttp.newWebSocket(
|
||||||
|
openRequest.build(),
|
||||||
|
SvrWebSocketListener(
|
||||||
|
mrEnclave = mrEnclave,
|
||||||
|
clientRequest = clientRequest,
|
||||||
|
emitter = emitter
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
emitter.setCancellable { webSocket.close(1000, "OK") }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class SvrWebSocketListener(
|
||||||
|
private val mrEnclave: String,
|
||||||
|
private val clientRequest: Svr2Request,
|
||||||
|
private val emitter: SingleEmitter<Svr2Response>
|
||||||
|
) : WebSocketListener() {
|
||||||
|
|
||||||
|
private val stage = AtomicReference(Stage.WAITING_TO_INITIALIZE)
|
||||||
|
private lateinit var client: Svr2Client
|
||||||
|
|
||||||
|
override fun onOpen(webSocket: WebSocket, response: Response) {
|
||||||
|
Log.d(TAG, "[onOpen]")
|
||||||
|
stage.set(Stage.WAITING_FOR_CONNECTION)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onMessage(webSocket: WebSocket, bytes: ByteString) {
|
||||||
|
Log.d(TAG, "[onMessage] stage: " + stage.get())
|
||||||
|
try {
|
||||||
|
when (stage.get()!!) {
|
||||||
|
Stage.WAITING_TO_INITIALIZE -> {
|
||||||
|
throw IOException("Received a message before we were open!")
|
||||||
|
}
|
||||||
|
|
||||||
|
Stage.WAITING_FOR_CONNECTION -> {
|
||||||
|
client = Svr2Client.create_NOT_FOR_PRODUCTION(Hex.fromStringCondensed(mrEnclave), bytes.toByteArray(), Instant.now())
|
||||||
|
|
||||||
|
Log.d(TAG, "[onMessage] Sending initial handshake...")
|
||||||
|
webSocket.send(client.initialRequest().toByteString())
|
||||||
|
stage.set(Stage.WAITING_FOR_HANDSHAKE)
|
||||||
|
}
|
||||||
|
|
||||||
|
Stage.WAITING_FOR_HANDSHAKE -> {
|
||||||
|
client.completeHandshake(bytes.toByteArray())
|
||||||
|
Log.d(TAG, "[onMessage] Handshake read success. Sending request...")
|
||||||
|
|
||||||
|
val ciphertextBytes = client.establishedSend(clientRequest.encode())
|
||||||
|
webSocket.send(ciphertextBytes.toByteString())
|
||||||
|
|
||||||
|
Log.d(TAG, "[onMessage] Request sent.")
|
||||||
|
stage.set(Stage.WAITING_FOR_RESPONSE)
|
||||||
|
}
|
||||||
|
|
||||||
|
Stage.WAITING_FOR_RESPONSE -> {
|
||||||
|
Log.d(TAG, "[onMessage] Received response for our request.")
|
||||||
|
emitter.onSuccess(Svr2Response.ADAPTER.decode(client.establishedRecv(bytes.toByteArray())))
|
||||||
|
}
|
||||||
|
|
||||||
|
Stage.CLOSED -> {
|
||||||
|
Log.w(TAG, "[onMessage] Received a message after the websocket closed! Ignoring.")
|
||||||
|
}
|
||||||
|
|
||||||
|
Stage.FAILED -> {
|
||||||
|
Log.w(TAG, "[onMessage] Received a message after we entered the failure state! Ignoring.")
|
||||||
|
webSocket.close(1000, "OK")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: IOException) {
|
||||||
|
Log.w(TAG, e)
|
||||||
|
webSocket.close(1000, "OK")
|
||||||
|
emitter.tryOnError(e)
|
||||||
|
} catch (e: AttestationDataException) {
|
||||||
|
Log.w(TAG, e)
|
||||||
|
webSocket.close(1000, "OK")
|
||||||
|
emitter.tryOnError(e)
|
||||||
|
} catch (e: SgxCommunicationFailureException) {
|
||||||
|
Log.w(TAG, e)
|
||||||
|
webSocket.close(1000, "OK")
|
||||||
|
emitter.tryOnError(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onClosing(webSocket: WebSocket, code: Int, reason: String) {
|
||||||
|
Log.i(TAG, "[onClosing] code: $code, reason: $reason")
|
||||||
|
|
||||||
|
if (code == 1000) {
|
||||||
|
emitter.tryOnError(IOException("Websocket was closed with code 1000"))
|
||||||
|
stage.set(Stage.CLOSED)
|
||||||
|
} else {
|
||||||
|
Log.w(TAG, "Remote side is closing with non-normal code $code")
|
||||||
|
webSocket.close(1000, "Remote closed with code $code")
|
||||||
|
stage.set(Stage.FAILED)
|
||||||
|
|
||||||
|
emitter.tryOnError(NonSuccessfulResponseCodeException(code))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) {
|
||||||
|
if (emitter.tryOnError(t)) {
|
||||||
|
Log.w(TAG, "[onFailure] response? " + (response != null), t)
|
||||||
|
stage.set(Stage.FAILED)
|
||||||
|
webSocket.close(1000, "OK")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum class Stage {
|
||||||
|
WAITING_TO_INITIALIZE,
|
||||||
|
WAITING_FOR_CONNECTION,
|
||||||
|
WAITING_FOR_HANDSHAKE,
|
||||||
|
WAITING_FOR_RESPONSE,
|
||||||
|
CLOSED,
|
||||||
|
FAILED
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val TAG = Svr2Socket::class.java.simpleName
|
||||||
|
|
||||||
|
private fun buildOkHttpClient(configuration: SignalServiceConfiguration, svr2Url: SignalSvr2Url): OkHttpClient {
|
||||||
|
val socketFactory = createTlsSocketFactory(svr2Url.trustStore)
|
||||||
|
val builder = OkHttpClient.Builder().sslSocketFactory(Tls12SocketFactory(socketFactory.first()), socketFactory.second()).connectionSpecs(Util.immutableList(ConnectionSpec.RESTRICTED_TLS)).retryOnConnectionFailure(false).readTimeout(30, TimeUnit.SECONDS).connectTimeout(30, TimeUnit.SECONDS)
|
||||||
|
|
||||||
|
for (interceptor in configuration.networkInterceptors) {
|
||||||
|
builder.addInterceptor(interceptor)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (configuration.signalProxy.isPresent) {
|
||||||
|
val proxy = configuration.signalProxy.get()
|
||||||
|
builder.socketFactory(TlsProxySocketFactory(proxy.host, proxy.port, configuration.dns))
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createTlsSocketFactory(trustStore: TrustStore): Pair<SSLSocketFactory, X509TrustManager> {
|
||||||
|
return try {
|
||||||
|
val context = SSLContext.getInstance("TLS")
|
||||||
|
val trustManagers = BlacklistingTrustManager.createFor(trustStore)
|
||||||
|
context.init(null, trustManagers, null)
|
||||||
|
Pair(context.socketFactory, trustManagers[0] as X509TrustManager)
|
||||||
|
} catch (e: NoSuchAlgorithmException) {
|
||||||
|
throw AssertionError(e)
|
||||||
|
} catch (e: KeyManagementException) {
|
||||||
|
throw AssertionError(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun chooseUrl(urls: Array<SignalSvr2Url>): SignalSvr2Url {
|
||||||
|
return urls[(Math.random() * urls.size).toInt()]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,80 +0,0 @@
|
|||||||
package org.whispersystems.signalservice.internal.configuration;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
import okhttp3.Dns;
|
|
||||||
import okhttp3.Interceptor;
|
|
||||||
|
|
||||||
public final class SignalServiceConfiguration {
|
|
||||||
|
|
||||||
private final SignalServiceUrl[] signalServiceUrls;
|
|
||||||
private final Map<Integer, SignalCdnUrl[]> signalCdnUrlMap;
|
|
||||||
private final SignalCdsiUrl[] signalCdsiUrls;
|
|
||||||
private final SignalKeyBackupServiceUrl[] signalKeyBackupServiceUrls;
|
|
||||||
private final SignalStorageUrl[] signalStorageUrls;
|
|
||||||
private final List<Interceptor> networkInterceptors;
|
|
||||||
private final Optional<Dns> dns;
|
|
||||||
private final Optional<SignalProxy> proxy;
|
|
||||||
private final byte[] zkGroupServerPublicParams;
|
|
||||||
|
|
||||||
public SignalServiceConfiguration(SignalServiceUrl[] signalServiceUrls,
|
|
||||||
Map<Integer, SignalCdnUrl[]> signalCdnUrlMap,
|
|
||||||
SignalKeyBackupServiceUrl[] signalKeyBackupServiceUrls,
|
|
||||||
SignalStorageUrl[] signalStorageUrls,
|
|
||||||
SignalCdsiUrl[] signalCdsiUrls,
|
|
||||||
List<Interceptor> networkInterceptors,
|
|
||||||
Optional<Dns> dns,
|
|
||||||
Optional<SignalProxy> proxy,
|
|
||||||
byte[] zkGroupServerPublicParams)
|
|
||||||
{
|
|
||||||
this.signalServiceUrls = signalServiceUrls;
|
|
||||||
this.signalCdnUrlMap = signalCdnUrlMap;
|
|
||||||
this.signalCdsiUrls = signalCdsiUrls;
|
|
||||||
this.signalKeyBackupServiceUrls = signalKeyBackupServiceUrls;
|
|
||||||
this.signalStorageUrls = signalStorageUrls;
|
|
||||||
this.networkInterceptors = networkInterceptors;
|
|
||||||
this.dns = dns;
|
|
||||||
this.proxy = proxy;
|
|
||||||
this.zkGroupServerPublicParams = zkGroupServerPublicParams;
|
|
||||||
}
|
|
||||||
|
|
||||||
public SignalServiceUrl[] getSignalServiceUrls() {
|
|
||||||
return signalServiceUrls;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Map<Integer, SignalCdnUrl[]> getSignalCdnUrlMap() {
|
|
||||||
return signalCdnUrlMap;
|
|
||||||
}
|
|
||||||
|
|
||||||
public SignalCdsiUrl[] getSignalCdsiUrls() {
|
|
||||||
return signalCdsiUrls;
|
|
||||||
}
|
|
||||||
|
|
||||||
public SignalKeyBackupServiceUrl[] getSignalKeyBackupServiceUrls() {
|
|
||||||
return signalKeyBackupServiceUrls;
|
|
||||||
}
|
|
||||||
|
|
||||||
public SignalStorageUrl[] getSignalStorageUrls() {
|
|
||||||
return signalStorageUrls;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<Interceptor> getNetworkInterceptors() {
|
|
||||||
return networkInterceptors;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Optional<Dns> getDns() {
|
|
||||||
return dns;
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] getZkGroupServerPublicParams() {
|
|
||||||
return zkGroupServerPublicParams;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Optional<SignalProxy> getSignalProxy() {
|
|
||||||
return proxy;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package org.whispersystems.signalservice.internal.configuration
|
||||||
|
|
||||||
|
import okhttp3.Dns
|
||||||
|
import okhttp3.Interceptor
|
||||||
|
import java.util.Optional
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines all network configuration needed to connect to the Signal service.
|
||||||
|
*/
|
||||||
|
class SignalServiceConfiguration(
|
||||||
|
val signalServiceUrls: Array<SignalServiceUrl>,
|
||||||
|
val signalCdnUrlMap: Map<Int, Array<SignalCdnUrl>>,
|
||||||
|
val signalKeyBackupServiceUrls: Array<SignalKeyBackupServiceUrl>,
|
||||||
|
val signalStorageUrls: Array<SignalStorageUrl>,
|
||||||
|
val signalCdsiUrls: Array<SignalCdsiUrl>,
|
||||||
|
val signalSvr2Urls: Array<SignalSvr2Url>,
|
||||||
|
val networkInterceptors: List<Interceptor>,
|
||||||
|
val dns: Optional<Dns>,
|
||||||
|
val signalProxy: Optional<SignalProxy>,
|
||||||
|
val zkGroupServerPublicParams: ByteArray
|
||||||
|
)
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package org.whispersystems.signalservice.internal.configuration
|
||||||
|
|
||||||
|
import okhttp3.ConnectionSpec
|
||||||
|
import org.whispersystems.signalservice.api.push.TrustStore
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configuration for reach the SVR2 service.
|
||||||
|
*/
|
||||||
|
class SignalSvr2Url(
|
||||||
|
url: String,
|
||||||
|
trustStore: TrustStore,
|
||||||
|
hostHeader: String? = null,
|
||||||
|
connectionSpec: ConnectionSpec? = null
|
||||||
|
) : SignalUrl(url, hostHeader, trustStore, connectionSpec)
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
package org.whispersystems.signalservice.internal.push;
|
package org.whispersystems.signalservice.internal.push;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
|||||||
@@ -283,6 +283,7 @@ public class PushServiceSocket {
|
|||||||
private static final String REGISTRATION_PATH = "/v1/registration";
|
private static final String REGISTRATION_PATH = "/v1/registration";
|
||||||
|
|
||||||
private static final String CDSI_AUTH = "/v2/directory/auth";
|
private static final String CDSI_AUTH = "/v2/directory/auth";
|
||||||
|
private static final String SVR2_AUTH = "/v2/backup/auth";
|
||||||
|
|
||||||
private static final String REPORT_SPAM = "/v1/messages/report/%s/%s";
|
private static final String REPORT_SPAM = "/v1/messages/report/%s/%s";
|
||||||
|
|
||||||
@@ -425,6 +426,13 @@ public class PushServiceSocket {
|
|||||||
return JsonUtil.fromJsonResponse(body, CdsiAuthResponse.class);
|
return JsonUtil.fromJsonResponse(body, CdsiAuthResponse.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getSvr2Authorization() throws IOException {
|
||||||
|
String body = makeServiceRequest(SVR2_AUTH, "GET", null);
|
||||||
|
AuthCredentials credentials = JsonUtil.fromJsonResponse(body, AuthCredentials.class);
|
||||||
|
|
||||||
|
return credentials.asBasic();
|
||||||
|
}
|
||||||
|
|
||||||
public VerifyAccountResponse changeNumber(@Nonnull ChangePhoneNumberRequest changePhoneNumberRequest)
|
public VerifyAccountResponse changeNumber(@Nonnull ChangePhoneNumberRequest changePhoneNumberRequest)
|
||||||
throws IOException
|
throws IOException
|
||||||
{
|
{
|
||||||
|
|||||||
74
libsignal/service/src/main/protowire/SVR2.proto
Normal file
74
libsignal/service/src/main/protowire/SVR2.proto
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
option java_multiple_files = true;
|
||||||
|
option java_package = "org.signal.svr2.proto";
|
||||||
|
|
||||||
|
message Request {
|
||||||
|
reserved 1; // backupId, only used by server
|
||||||
|
|
||||||
|
oneof inner {
|
||||||
|
BackupRequest backup = 2;
|
||||||
|
RestoreRequest restore = 3;
|
||||||
|
DeleteRequest delete = 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message Response {
|
||||||
|
oneof inner {
|
||||||
|
BackupResponse backup = 1;
|
||||||
|
RestoreResponse restore = 2;
|
||||||
|
DeleteResponse delete = 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// backup
|
||||||
|
//
|
||||||
|
|
||||||
|
message BackupRequest {
|
||||||
|
bytes data = 1; // between 16 and 48 bytes
|
||||||
|
bytes pin = 2; // 32 bytes
|
||||||
|
uint32 maxTries = 3; // in range [1,255]
|
||||||
|
}
|
||||||
|
|
||||||
|
message BackupResponse {
|
||||||
|
enum Status {
|
||||||
|
UNSET = 0; // never returned
|
||||||
|
OK = 1; // successfully set db[backup_id]=data
|
||||||
|
REQUEST_INVALID = 2; // the request was not correctly specified
|
||||||
|
}
|
||||||
|
|
||||||
|
Status status = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// restore
|
||||||
|
//
|
||||||
|
|
||||||
|
message RestoreRequest {
|
||||||
|
bytes pin = 1; // 32 bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
message RestoreResponse {
|
||||||
|
enum Status {
|
||||||
|
UNSET = 0; // never returned
|
||||||
|
OK = 1; // successfully restored, [data] will be set
|
||||||
|
MISSING = 2; // db[backup_id] does not exist
|
||||||
|
PIN_MISMATCH = 3; // pin did not match, tries were decremented
|
||||||
|
REQUEST_INVALID = 4; // the request was not correctly specified, tries were not decremented
|
||||||
|
}
|
||||||
|
|
||||||
|
Status status = 1;
|
||||||
|
bytes data = 2; // between 16 and 48 bytes, if set
|
||||||
|
uint32 tries = 3; // in range [0,255]
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// delete
|
||||||
|
//
|
||||||
|
|
||||||
|
message DeleteRequest {
|
||||||
|
}
|
||||||
|
|
||||||
|
message DeleteResponse {
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user