mirror of
https://github.com/AsamK/signal-cli.git
synced 2026-08-12 03:16:22 +00:00
Use upstream provisioning functionality
This commit is contained in:
parent
1e59c6f6cc
commit
b3e3eda4f7
@ -1,9 +1,10 @@
|
||||
[versions]
|
||||
slf4j = "2.0.18"
|
||||
coroutines = "1.10.2"
|
||||
junit = "6.1.2"
|
||||
micronaut-json-schema = "2.1.0"
|
||||
micronaut-core = "5.1.10"
|
||||
signal-service = "2.15.3_unofficial_150"
|
||||
signal-service = "2.15.3_unofficial_151"
|
||||
|
||||
[libraries]
|
||||
bouncycastle = "org.bouncycastle:bcprov-jdk18on:1.85"
|
||||
@ -19,6 +20,7 @@ slf4j-api = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" }
|
||||
slf4j-jul = { module = "org.slf4j:jul-to-slf4j", version.ref = "slf4j" }
|
||||
logback = "ch.qos.logback:logback-classic:1.6.1"
|
||||
|
||||
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines" }
|
||||
signalnetwork = { module = "com.github.turasa:signal-network", version.ref = "signal-service" }
|
||||
sqlite = "org.xerial:sqlite-jdbc:3.53.2.1"
|
||||
hikari = "com.zaxxer:HikariCP:7.1.0"
|
||||
|
||||
@ -30,6 +30,7 @@ dependencies {
|
||||
implementation(libs.slf4j.api)
|
||||
implementation(libs.sqlite)
|
||||
implementation(libs.hikari)
|
||||
compileOnly(libs.kotlinx.coroutines.core)
|
||||
|
||||
testImplementation(libs.junit.jupiter)
|
||||
testImplementation(platform(libs.junit.jupiter.bom))
|
||||
|
||||
@ -17,6 +17,7 @@ import org.asamk.signal.manager.util.Utils;
|
||||
import org.signal.core.models.ServiceId.ACI;
|
||||
import org.signal.core.models.ServiceId.PNI;
|
||||
import org.signal.core.util.Base64;
|
||||
import org.signal.core.util.crypto.DeviceNameCipher;
|
||||
import org.signal.libsignal.protocol.IdentityKeyPair;
|
||||
import org.signal.libsignal.protocol.InvalidKeyException;
|
||||
import org.signal.libsignal.protocol.NoSessionException;
|
||||
@ -40,7 +41,6 @@ import org.whispersystems.signalservice.api.push.UsernameLinkComponents;
|
||||
import org.whispersystems.signalservice.api.push.exceptions.AlreadyVerifiedException;
|
||||
import org.whispersystems.signalservice.api.push.exceptions.AuthorizationFailedException;
|
||||
import org.whispersystems.signalservice.api.push.exceptions.DeprecatedVersionException;
|
||||
import org.whispersystems.signalservice.api.util.DeviceNameUtil;
|
||||
import org.whispersystems.signalservice.internal.push.DeviceLimitExceededException;
|
||||
import org.whispersystems.signalservice.internal.push.KyberPreKeyEntity;
|
||||
import org.whispersystems.signalservice.internal.push.OutgoingPushMessage;
|
||||
@ -48,6 +48,7 @@ import org.whispersystems.signalservice.internal.push.SyncMessage;
|
||||
import org.whispersystems.signalservice.internal.push.exceptions.MismatchedDevicesException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
@ -519,18 +520,22 @@ public class AccountHelper {
|
||||
}
|
||||
|
||||
public void setDeviceName(String deviceName) {
|
||||
final var privateKey = account.getAciIdentityKeyPair().getPrivateKey();
|
||||
final var encryptedDeviceName = DeviceNameUtil.encryptDeviceName(deviceName, privateKey);
|
||||
final var encryptedDeviceName = getEncryptedDeviceName(deviceName);
|
||||
account.setEncryptedDeviceName(encryptedDeviceName);
|
||||
}
|
||||
|
||||
public void setDeviceName(int deviceId, String deviceName) throws IOException {
|
||||
final var privateKey = account.getAciIdentityKeyPair().getPrivateKey();
|
||||
final var encryptedDeviceName = DeviceNameUtil.encryptDeviceName(deviceName, privateKey);
|
||||
final var encryptedDeviceName = getEncryptedDeviceName(deviceName);
|
||||
handleResponseException(dependencies.getLinkDeviceApi().setDeviceName(encryptedDeviceName, deviceId));
|
||||
context.getSyncHelper().sendDeviceNameChange(deviceId);
|
||||
}
|
||||
|
||||
private String getEncryptedDeviceName(final String deviceName) {
|
||||
final var identityKey = account.getAciIdentityKeyPair();
|
||||
return Base64.encodeWithoutPadding(DeviceNameCipher.encryptDeviceName(deviceName.getBytes(StandardCharsets.UTF_8),
|
||||
identityKey));
|
||||
}
|
||||
|
||||
public void refreshDeviceName() throws IOException {
|
||||
final List<DeviceInfo> devices = handleResponseExceptionSuspend(cont -> dependencies.getLinkDeviceApi()
|
||||
.getDevices(cont));
|
||||
|
||||
@ -98,8 +98,10 @@ import org.asamk.signal.manager.util.StickerUtils;
|
||||
import org.signal.core.models.ServiceId;
|
||||
import org.signal.core.models.ServiceId.ACI;
|
||||
import org.signal.core.models.ServiceId.PNI;
|
||||
import org.signal.core.util.Base64;
|
||||
import org.signal.core.util.Hex;
|
||||
import org.signal.core.util.crypto.DeviceName;
|
||||
import org.signal.core.util.crypto.DeviceNameCipher;
|
||||
import org.signal.libsignal.net.LinkedDevice;
|
||||
import org.signal.libsignal.protocol.InvalidMessageException;
|
||||
import org.signal.libsignal.protocol.NoSessionException;
|
||||
import org.signal.libsignal.usernames.BaseUsernameException;
|
||||
@ -120,10 +122,8 @@ import org.whispersystems.signalservice.api.messages.calls.HangupMessage;
|
||||
import org.whispersystems.signalservice.api.messages.calls.IceUpdateMessage;
|
||||
import org.whispersystems.signalservice.api.messages.calls.OfferMessage;
|
||||
import org.whispersystems.signalservice.api.messages.calls.SignalServiceCallMessage;
|
||||
import org.whispersystems.signalservice.api.messages.multidevice.DeviceInfo;
|
||||
import org.whispersystems.signalservice.api.push.ServiceIdType;
|
||||
import org.whispersystems.signalservice.api.push.exceptions.CdsiResourceExhaustedException;
|
||||
import org.whispersystems.signalservice.api.util.DeviceNameUtil;
|
||||
import org.whispersystems.signalservice.api.util.StreamDetails;
|
||||
import org.whispersystems.signalservice.internal.util.Util;
|
||||
|
||||
@ -484,24 +484,26 @@ public class ManagerImpl implements Manager {
|
||||
|
||||
@Override
|
||||
public List<Device> getLinkedDevices() throws IOException {
|
||||
final List<DeviceInfo> devices = handleResponseExceptionSuspend(cont -> dependencies.getLinkDeviceApi()
|
||||
final List<LinkedDevice> devices = handleResponseExceptionSuspend(cont -> dependencies.getLinkDeviceApi()
|
||||
.getDevices(cont));
|
||||
account.setMultiDevice(devices.size() > 1);
|
||||
var identityKey = account.getAciIdentityKeyPair().getPrivateKey();
|
||||
var identityKey = account.getAciIdentityKeyPair();
|
||||
return devices.stream().map(d -> {
|
||||
String deviceName = d.getName();
|
||||
if (deviceName != null) {
|
||||
String deviceName = null;
|
||||
if (d.getEncryptedName() != null && d.getEncryptedName().length > 0) {
|
||||
try {
|
||||
deviceName = DeviceNameUtil.decryptDeviceName(deviceName, identityKey);
|
||||
} catch (IOException e) {
|
||||
deviceName = new String(DeviceNameCipher.decryptDeviceName(DeviceName.ADAPTER.decode(d.getEncryptedName()),
|
||||
identityKey), StandardCharsets.UTF_8);
|
||||
} catch (Exception e) {
|
||||
logger.debug("Failed to decrypt device name, maybe plain text?", e);
|
||||
deviceName = new String(d.getEncryptedName(), StandardCharsets.UTF_8);
|
||||
}
|
||||
}
|
||||
final var createdAt = getPlaintextCreatedAt(d);
|
||||
return new Device(d.getId(),
|
||||
deviceName,
|
||||
createdAt == null ? 0 : createdAt,
|
||||
d.getLastSeen(),
|
||||
d.getLastSeen().toEpochMilli(),
|
||||
d.getId() == account.getDeviceId());
|
||||
}).toList();
|
||||
}
|
||||
@ -521,7 +523,7 @@ public class ManagerImpl implements Manager {
|
||||
}
|
||||
}
|
||||
|
||||
private Long getPlaintextCreatedAt(DeviceInfo d) {
|
||||
private Long getPlaintextCreatedAt(LinkedDevice d) {
|
||||
final var DECRYPTION_INFO = "deviceCreatedAt";
|
||||
var identityKey = account.getAciIdentityKeyPair().getPrivateKey();
|
||||
try {
|
||||
@ -529,8 +531,9 @@ public class ManagerImpl implements Manager {
|
||||
var associatedData = new ByteArrayOutputStream();
|
||||
associatedData.write(d.getId());
|
||||
associatedData.write(ByteBuffer.allocate(4).putInt(d.getRegistrationId()).array());
|
||||
var createdAtPlaintext = identityKey.open(Base64.decode(d.getCreatedAtCiphertext()
|
||||
.getBytes(StandardCharsets.UTF_8)), DECRYPTION_INFO, associatedData.toByteArray());
|
||||
var createdAtPlaintext = identityKey.open(d.getCreatedAtCiphertext(),
|
||||
DECRYPTION_INFO,
|
||||
associatedData.toByteArray());
|
||||
return ByteBuffer.wrap(createdAtPlaintext).getLong();
|
||||
} catch (Exception e) {
|
||||
logger.warn("Failed while reading the protobuf.", e);
|
||||
|
||||
@ -19,34 +19,57 @@ package org.asamk.signal.manager.internal;
|
||||
import org.asamk.signal.manager.Manager;
|
||||
import org.asamk.signal.manager.ProvisioningManager;
|
||||
import org.asamk.signal.manager.Settings;
|
||||
import org.asamk.signal.manager.api.DeviceLinkUrl;
|
||||
import org.asamk.signal.manager.api.UserAlreadyExistsException;
|
||||
import org.asamk.signal.manager.config.ServiceConfig;
|
||||
import org.asamk.signal.manager.config.ServiceEnvironmentConfig;
|
||||
import org.asamk.signal.manager.storage.SignalAccount;
|
||||
import org.asamk.signal.manager.storage.accounts.AccountsStore;
|
||||
import org.asamk.signal.manager.util.KeyUtils;
|
||||
import org.signal.core.models.AccountEntropyPool;
|
||||
import org.signal.core.models.ServiceId.ACI;
|
||||
import org.signal.core.models.ServiceId.PNI;
|
||||
import org.signal.core.models.backup.MediaRootBackupKey;
|
||||
import org.signal.core.util.crypto.DeviceNameCipher;
|
||||
import org.signal.libsignal.protocol.IdentityKey;
|
||||
import org.signal.libsignal.protocol.IdentityKeyPair;
|
||||
import org.signal.libsignal.protocol.ecc.ECPrivateKey;
|
||||
import org.signal.libsignal.zkgroup.profiles.ProfileKey;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
|
||||
import org.whispersystems.signalservice.api.account.DeviceAttributes;
|
||||
import org.whispersystems.signalservice.api.provisioning.ProvisioningSocket;
|
||||
import org.whispersystems.signalservice.api.push.ServiceIdType;
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||
import org.whispersystems.signalservice.api.push.exceptions.AuthorizationFailedException;
|
||||
import org.whispersystems.signalservice.api.registration.ProvisioningApi;
|
||||
import org.whispersystems.signalservice.api.util.DeviceNameUtil;
|
||||
import org.whispersystems.signalservice.internal.push.ProvisioningSocket;
|
||||
import org.whispersystems.signalservice.internal.push.PushServiceSocket;
|
||||
import org.whispersystems.signalservice.internal.util.DynamicCredentialsProvider;
|
||||
import org.whispersystems.signalservice.internal.crypto.SecondaryProvisioningCipher;
|
||||
import org.whispersystems.signalservice.internal.push.ProvisionMessage;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.channels.OverlappingFileLockException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static org.asamk.signal.manager.util.KeyUtils.generatePreKeysForType;
|
||||
import kotlin.ResultKt;
|
||||
import kotlin.Unit;
|
||||
import kotlin.coroutines.Continuation;
|
||||
import kotlin.coroutines.EmptyCoroutineContext;
|
||||
import kotlin.coroutines.intrinsics.IntrinsicsKt;
|
||||
import kotlin.jvm.functions.Function3;
|
||||
import kotlinx.coroutines.BuildersKt;
|
||||
import kotlinx.coroutines.CoroutineScope;
|
||||
|
||||
public class ProvisioningManagerImpl implements ProvisioningManager {
|
||||
import static org.asamk.signal.manager.util.KeyUtils.generatePreKeysForType;
|
||||
import static org.asamk.signal.manager.util.Utils.handleResponseException;
|
||||
|
||||
public class ProvisioningManagerImpl implements ProvisioningManager, Closeable {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(ProvisioningManagerImpl.class);
|
||||
|
||||
@ -56,9 +79,10 @@ public class ProvisioningManagerImpl implements ProvisioningManager {
|
||||
private final Consumer<Manager> newManagerListener;
|
||||
private final AccountsStore accountsStore;
|
||||
|
||||
private final ProvisioningApi provisioningApi;
|
||||
private final IdentityKeyPair tempIdentityKey;
|
||||
private final String password;
|
||||
private final CompletableFuture<String> urlFuture = new CompletableFuture<>();
|
||||
private final CompletableFuture<SecondaryProvisioningCipher.ProvisioningDecryptResult<ProvisionMessage>> messageFuture = new CompletableFuture<>();
|
||||
private final Closeable socketHandle;
|
||||
|
||||
public ProvisioningManagerImpl(
|
||||
PathConfig pathConfig,
|
||||
@ -73,35 +97,58 @@ public class ProvisioningManagerImpl implements ProvisioningManager {
|
||||
this.newManagerListener = newManagerListener;
|
||||
this.accountsStore = accountsStore;
|
||||
|
||||
tempIdentityKey = KeyUtils.generateIdentityKeyPair();
|
||||
final IdentityKeyPair tempIdentityKey = KeyUtils.generateIdentityKeyPair();
|
||||
password = KeyUtils.createPassword();
|
||||
final var credentialsProvider = new DynamicCredentialsProvider(null,
|
||||
null,
|
||||
null,
|
||||
password,
|
||||
SignalServiceAddress.DEFAULT_DEVICE_ID);
|
||||
final var pushServiceSocket = new PushServiceSocket(serviceEnvironmentConfig.signalServiceConfiguration(),
|
||||
credentialsProvider,
|
||||
userAgent,
|
||||
ServiceConfig.AUTOMATIC_NETWORK_RETRY);
|
||||
final var provisioningSocket = new ProvisioningSocket(serviceEnvironmentConfig.signalServiceConfiguration(),
|
||||
userAgent);
|
||||
this.provisioningApi = new ProvisioningApi(pushServiceSocket, provisioningSocket, credentialsProvider);
|
||||
|
||||
socketHandle = ProvisioningSocket.Companion.start(new ProvisioningSocket.Mode.Link(false),
|
||||
tempIdentityKey,
|
||||
serviceEnvironmentConfig.signalServiceConfiguration(),
|
||||
(id, t) -> {
|
||||
urlFuture.completeExceptionally(t);
|
||||
messageFuture.completeExceptionally(t);
|
||||
},
|
||||
new ProvisioningBlock());
|
||||
}
|
||||
|
||||
@Override
|
||||
public URI getDeviceLinkUri() throws TimeoutException, IOException {
|
||||
var deviceUuid = provisioningApi.getNewDeviceUuid();
|
||||
|
||||
return new DeviceLinkUrl(deviceUuid, tempIdentityKey.getPublicKey().getPublicKey()).createDeviceLinkUri();
|
||||
try {
|
||||
var url = urlFuture.get(30, TimeUnit.SECONDS);
|
||||
return new URI(url);
|
||||
} catch (java.util.concurrent.TimeoutException e) {
|
||||
throw new TimeoutException("Timed out waiting for provisioning URL");
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new IOException("Interrupted while waiting for provisioning URL", e);
|
||||
} catch (ExecutionException e) {
|
||||
throw new IOException("Failed to get provisioning URL", e.getCause());
|
||||
} catch (URISyntaxException e) {
|
||||
throw new IOException("Invalid provisioning URL", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String finishDeviceLink(String deviceName) throws IOException, TimeoutException, UserAlreadyExistsException {
|
||||
var ret = provisioningApi.getNewDeviceRegistration(tempIdentityKey);
|
||||
var number = ret.getNumber();
|
||||
var aci = ret.getAci();
|
||||
var pni = ret.getPni();
|
||||
SecondaryProvisioningCipher.ProvisioningDecryptResult<ProvisionMessage> decryptResult;
|
||||
try {
|
||||
decryptResult = messageFuture.get(120, TimeUnit.SECONDS);
|
||||
} catch (java.util.concurrent.TimeoutException e) {
|
||||
throw new TimeoutException("Timed out waiting for provisioning message");
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new IOException("Interrupted while waiting for provisioning message", e);
|
||||
} catch (ExecutionException e) {
|
||||
throw new IOException("Failed to receive provisioning message", e.getCause());
|
||||
}
|
||||
|
||||
if (!(decryptResult instanceof SecondaryProvisioningCipher.ProvisioningDecryptResult.Success<ProvisionMessage> success)) {
|
||||
throw new IOException("Failed to decrypt provisioning message");
|
||||
}
|
||||
var msg = success.getMessage();
|
||||
|
||||
var number = msg.number;
|
||||
var aci = ACI.parseOrThrow(msg.aci, msg.aciBinary);
|
||||
var pni = PNI.parseOrThrow(msg.pni, msg.pniBinary);
|
||||
|
||||
logger.info("Received link information from {}, linking in progress ...", number);
|
||||
|
||||
@ -120,11 +167,28 @@ public class ProvisioningManagerImpl implements ProvisioningManager {
|
||||
accountsStore.updateAccount(accountPath, number, aci);
|
||||
}
|
||||
|
||||
final IdentityKeyPair aciIdentity;
|
||||
final IdentityKeyPair pniIdentity;
|
||||
final ProfileKey profileKey;
|
||||
try {
|
||||
aciIdentity = new IdentityKeyPair(new IdentityKey(msg.aciIdentityKeyPublic.toByteArray()),
|
||||
new ECPrivateKey(msg.aciIdentityKeyPrivate.toByteArray()));
|
||||
pniIdentity = new IdentityKeyPair(new IdentityKey(msg.pniIdentityKeyPublic.toByteArray()),
|
||||
new ECPrivateKey(msg.pniIdentityKeyPrivate.toByteArray()));
|
||||
profileKey = msg.profileKey == null
|
||||
? KeyUtils.createProfileKey()
|
||||
: new ProfileKey(msg.profileKey.toByteArray());
|
||||
} catch (Exception e) {
|
||||
throw new IOException("Invalid key material in provisioning message", e);
|
||||
}
|
||||
|
||||
var encryptedDeviceName = deviceName == null
|
||||
? null
|
||||
: DeviceNameUtil.encryptDeviceName(deviceName, ret.getAciIdentity().getPrivateKey());
|
||||
// Create new account with the synced identity
|
||||
var profileKey = ret.getProfileKey() == null ? KeyUtils.createProfileKey() : ret.getProfileKey();
|
||||
: DeviceNameCipher.encryptDeviceName(deviceName.getBytes(StandardCharsets.UTF_8), aciIdentity);
|
||||
var accountEntropyPool = msg.accountEntropyPool == null ? null : new AccountEntropyPool(msg.accountEntropyPool);
|
||||
var mediaRootBackupKey = msg.mediaRootBackupKey == null
|
||||
? null
|
||||
: new MediaRootBackupKey(msg.mediaRootBackupKey.toByteArray());
|
||||
|
||||
SignalAccount account = null;
|
||||
try {
|
||||
@ -142,22 +206,39 @@ public class ProvisioningManagerImpl implements ProvisioningManager {
|
||||
pni,
|
||||
password,
|
||||
encryptedDeviceName,
|
||||
ret.getAciIdentity(),
|
||||
ret.getPniIdentity(),
|
||||
aciIdentity,
|
||||
pniIdentity,
|
||||
profileKey,
|
||||
ret.getAccountEntropyPool(),
|
||||
ret.getMediaRootBackupKey());
|
||||
accountEntropyPool,
|
||||
mediaRootBackupKey);
|
||||
|
||||
account.getConfigurationStore().setReadReceipts(ret.isReadReceipts());
|
||||
if (Boolean.TRUE.equals(msg.readReceipts)) {
|
||||
account.getConfigurationStore().setReadReceipts(true);
|
||||
}
|
||||
|
||||
final var aciPreKeys = generatePreKeysForType(account.getAccountData(ServiceIdType.ACI));
|
||||
final var pniPreKeys = generatePreKeysForType(account.getAccountData(ServiceIdType.PNI));
|
||||
|
||||
logger.debug("Finishing new device registration");
|
||||
var deviceId = provisioningApi.finishNewDeviceRegistration(ret.getProvisioningCode(),
|
||||
account.getAccountAttributes(null),
|
||||
aciPreKeys,
|
||||
pniPreKeys);
|
||||
final var attrs = account.getAccountAttributes(null);
|
||||
final var deviceAttributes = new DeviceAttributes(attrs.getFetchesMessages(),
|
||||
attrs.getRegistrationId(),
|
||||
attrs.getPniRegistrationId(),
|
||||
attrs.getName(),
|
||||
attrs.getCapabilities());
|
||||
final var unauthAccountManager = SignalServiceAccountManager.createWithStaticCredentials(
|
||||
serviceEnvironmentConfig.signalServiceConfiguration(),
|
||||
null,
|
||||
null,
|
||||
number,
|
||||
SignalServiceAddress.DEFAULT_DEVICE_ID,
|
||||
password,
|
||||
userAgent,
|
||||
ServiceConfig.AUTOMATIC_NETWORK_RETRY,
|
||||
ServiceConfig.GROUP_MAX_SIZE);
|
||||
final var registerResponse = handleResponseException(unauthAccountManager.getRegistrationApi()
|
||||
.registerAsSecondaryDevice(msg.provisioningCode, deviceAttributes, aciPreKeys, pniPreKeys, null));
|
||||
final var deviceId = Integer.parseInt(registerResponse.getDeviceId());
|
||||
|
||||
account.finishLinking(deviceId, aciPreKeys, pniPreKeys);
|
||||
|
||||
@ -203,6 +284,11 @@ public class ProvisioningManagerImpl implements ProvisioningManager {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
socketHandle.close();
|
||||
}
|
||||
|
||||
private boolean canRelinkExistingAccount(final String accountPath) throws IOException {
|
||||
final SignalAccount signalAccount;
|
||||
try {
|
||||
@ -243,4 +329,36 @@ public class ProvisioningManagerImpl implements ProvisioningManager {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private class ProvisioningBlock implements Function3<CoroutineScope, ProvisioningSocket<ProvisionMessage>, Continuation<? super Unit>, Object> {
|
||||
|
||||
@Override
|
||||
public Object invoke(
|
||||
CoroutineScope scope,
|
||||
ProvisioningSocket<ProvisionMessage> socket,
|
||||
Continuation<? super Unit> cont
|
||||
) {
|
||||
Thread.ofVirtual().start(() -> {
|
||||
try {
|
||||
urlFuture.complete(BuildersKt.runBlocking(EmptyCoroutineContext.INSTANCE,
|
||||
(s, c) -> socket.getProvisioningUrl(c)));
|
||||
messageFuture.complete(BuildersKt.runBlocking(EmptyCoroutineContext.INSTANCE,
|
||||
(s, c) -> socket.getProvisioningMessageDecryptResult(c)));
|
||||
cont.resumeWith(Unit.INSTANCE);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
failBoth(new RuntimeException(e), cont);
|
||||
} catch (Throwable t) {
|
||||
failBoth(t, cont);
|
||||
}
|
||||
});
|
||||
return IntrinsicsKt.getCOROUTINE_SUSPENDED();
|
||||
}
|
||||
|
||||
private void failBoth(Throwable t, Continuation<? super Unit> cont) {
|
||||
urlFuture.completeExceptionally(t);
|
||||
messageFuture.completeExceptionally(t);
|
||||
cont.resumeWith(ResultKt.createFailure(t));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -292,7 +292,7 @@ public class SignalAccount implements Closeable {
|
||||
final ACI aci,
|
||||
final PNI pni,
|
||||
final String password,
|
||||
final String encryptedDeviceName,
|
||||
final byte[] encryptedDeviceName,
|
||||
final IdentityKeyPair aciIdentity,
|
||||
final IdentityKeyPair pniIdentity,
|
||||
final ProfileKey profileKey,
|
||||
@ -307,7 +307,7 @@ public class SignalAccount implements Closeable {
|
||||
getRecipientTrustedResolver().resolveSelfRecipientTrusted(getSelfRecipientAddress());
|
||||
this.password = password;
|
||||
this.profileKey = profileKey;
|
||||
this.encryptedDeviceName = encryptedDeviceName;
|
||||
this.encryptedDeviceName = org.signal.core.util.Base64.encodeWithoutPadding(encryptedDeviceName);
|
||||
this.aciAccountData.setIdentityKeyPair(aciIdentity);
|
||||
this.pniAccountData.setIdentityKeyPair(pniIdentity);
|
||||
this.registered = false;
|
||||
|
||||
@ -31,8 +31,6 @@ import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Spliterator;
|
||||
import java.util.Spliterators;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
@ -40,11 +38,9 @@ import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
import kotlin.ResultKt;
|
||||
import kotlin.coroutines.Continuation;
|
||||
import kotlin.coroutines.CoroutineContext;
|
||||
import kotlin.coroutines.EmptyCoroutineContext;
|
||||
import kotlin.coroutines.intrinsics.IntrinsicsKt;
|
||||
import kotlinx.coroutines.BuildersKt;
|
||||
import okio.ByteString;
|
||||
|
||||
public class Utils {
|
||||
@ -167,58 +163,15 @@ public class Utils {
|
||||
return NetworkResultUtil.toBasicLegacy(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bridge Kotlin suspend functions for Java callers.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> T runSuspendBlocking(final Function<Continuation<? super T>, Object> call) {
|
||||
final var value = new AtomicReference<T>();
|
||||
final var throwable = new AtomicReference<Throwable>();
|
||||
final var done = new CountDownLatch(1);
|
||||
|
||||
final var immediate = call.apply(new Continuation<>() {
|
||||
@Override
|
||||
public CoroutineContext getContext() {
|
||||
return EmptyCoroutineContext.INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resumeWith(final Object result) {
|
||||
try {
|
||||
ResultKt.throwOnFailure(result);
|
||||
value.set((T) result);
|
||||
} catch (Throwable t) {
|
||||
throwable.set(t);
|
||||
} finally {
|
||||
done.countDown();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (immediate != IntrinsicsKt.getCOROUTINE_SUSPENDED()) {
|
||||
ResultKt.throwOnFailure(immediate);
|
||||
return (T) immediate;
|
||||
}
|
||||
|
||||
try {
|
||||
done.await();
|
||||
return (T) BuildersKt.runBlocking(EmptyCoroutineContext.INSTANCE,
|
||||
(scope, cont) -> call.apply((Continuation<? super T>) cont));
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new RuntimeException("Interrupted while waiting for suspend function", e);
|
||||
}
|
||||
|
||||
final var t = throwable.get();
|
||||
if (t instanceof RuntimeException e) {
|
||||
throw e;
|
||||
}
|
||||
if (t instanceof Error e) {
|
||||
throw e;
|
||||
}
|
||||
if (t != null) {
|
||||
throw new RuntimeException(t);
|
||||
}
|
||||
|
||||
return value.get();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
|
||||
@ -1 +1 @@
|
||||
0.96.3
|
||||
0.99.1
|
||||
|
||||
@ -8,7 +8,7 @@ public class BaseConfig {
|
||||
public static final String PROJECT_VERSION = BaseConfig.class.getPackage().getImplementationVersion();
|
||||
|
||||
static final String USER_AGENT_SIGNAL_ANDROID = Optional.ofNullable(System.getenv("SIGNAL_CLI_USER_AGENT"))
|
||||
.orElse("Signal-Android/8.15.0");
|
||||
.orElse("Signal-Android/8.21.1");
|
||||
static final String USER_AGENT_SIGNAL_CLI = PROJECT_NAME == null
|
||||
? "signal-cli"
|
||||
: PROJECT_NAME + "/" + PROJECT_VERSION;
|
||||
|
||||
@ -1163,7 +1163,8 @@
|
||||
"type": "java.lang.Integer",
|
||||
"allDeclaredFields": true,
|
||||
"allDeclaredMethods": true,
|
||||
"allDeclaredConstructors": true
|
||||
"allDeclaredConstructors": true,
|
||||
"jniAccessible": true
|
||||
},
|
||||
{
|
||||
"type": "java.lang.Iterable",
|
||||
@ -1198,6 +1199,7 @@
|
||||
},
|
||||
{
|
||||
"type": "java.lang.Object",
|
||||
"jniAccessible": true,
|
||||
"methods": [
|
||||
{
|
||||
"name": "equals",
|
||||
@ -6142,9 +6144,30 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "org.signal.libsignal.internal.LinkedDeviceInternal",
|
||||
"jniAccessible": true,
|
||||
"methods": [
|
||||
{
|
||||
"name": "fromNative",
|
||||
"parameterTypes": [
|
||||
"java.lang.Object",
|
||||
"java.lang.Object",
|
||||
"java.lang.Object",
|
||||
"java.lang.Object",
|
||||
"java.lang.Object"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "org.signal.libsignal.internal.NativeHandleGuard$SimpleOwner",
|
||||
"jniAccessible": true,
|
||||
"fields": [
|
||||
{
|
||||
"name": "nativeHandle"
|
||||
}
|
||||
],
|
||||
"methods": [
|
||||
{
|
||||
"name": "unsafeNativeHandleWithoutGuard",
|
||||
@ -6152,6 +6175,9 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "org.signal.libsignal.net.AuthenticatedChatConnection"
|
||||
},
|
||||
{
|
||||
"type": "org.signal.libsignal.net.CdsiLookupResponse",
|
||||
"jniAccessible": true,
|
||||
@ -8053,6 +8079,31 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "org.whispersystems.signalservice.api.account.DeviceAttributes",
|
||||
"methods": [
|
||||
{
|
||||
"name": "getCapabilities",
|
||||
"parameterTypes": []
|
||||
},
|
||||
{
|
||||
"name": "getFetchesMessages",
|
||||
"parameterTypes": []
|
||||
},
|
||||
{
|
||||
"name": "getName",
|
||||
"parameterTypes": []
|
||||
},
|
||||
{
|
||||
"name": "getPniRegistrationId",
|
||||
"parameterTypes": []
|
||||
},
|
||||
{
|
||||
"name": "getRegistrationId",
|
||||
"parameterTypes": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "org.whispersystems.signalservice.api.groupsv2.CredentialResponse",
|
||||
"allDeclaredFields": true,
|
||||
@ -8179,6 +8230,26 @@
|
||||
"allDeclaredMethods": true,
|
||||
"allDeclaredConstructors": true
|
||||
},
|
||||
{
|
||||
"type": "org.whispersystems.signalservice.api.messages.multidevice.RegisterAsSecondaryDeviceResponse",
|
||||
"fields": [
|
||||
{
|
||||
"name": "deviceId"
|
||||
},
|
||||
{
|
||||
"name": "pni"
|
||||
},
|
||||
{
|
||||
"name": "uuid"
|
||||
}
|
||||
],
|
||||
"methods": [
|
||||
{
|
||||
"name": "<init>",
|
||||
"parameterTypes": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "org.whispersystems.signalservice.api.profiles.SignalServiceProfile",
|
||||
"allDeclaredFields": true,
|
||||
@ -9079,6 +9150,39 @@
|
||||
"allDeclaredMethods": true,
|
||||
"allDeclaredConstructors": true
|
||||
},
|
||||
{
|
||||
"type": "org.whispersystems.signalservice.internal.push.RegisterAsSecondaryDeviceRequest",
|
||||
"methods": [
|
||||
{
|
||||
"name": "getAccountAttributes",
|
||||
"parameterTypes": []
|
||||
},
|
||||
{
|
||||
"name": "getAciPqLastResortPreKey",
|
||||
"parameterTypes": []
|
||||
},
|
||||
{
|
||||
"name": "getAciSignedPreKey",
|
||||
"parameterTypes": []
|
||||
},
|
||||
{
|
||||
"name": "getGcmToken",
|
||||
"parameterTypes": []
|
||||
},
|
||||
{
|
||||
"name": "getPniPqLastResortPreKey",
|
||||
"parameterTypes": []
|
||||
},
|
||||
{
|
||||
"name": "getPniSignedPreKey",
|
||||
"parameterTypes": []
|
||||
},
|
||||
{
|
||||
"name": "getVerificationCode",
|
||||
"parameterTypes": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "org.whispersystems.signalservice.internal.push.RegistrationSessionMetadataJson",
|
||||
"allDeclaredFields": true,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user