mirror of
https://github.com/AsamK/signal-cli
synced 2025-08-29 02:20:39 +00:00
Extract ProvisioningManager and RegistrationManager interfaces
This commit is contained in:
parent
332780b1a6
commit
00cda598c8
4 changed files with 471 additions and 429 deletions
|
@ -1,38 +1,7 @@
|
||||||
/*
|
|
||||||
Copyright (C) 2015-2021 AsamK and contributors
|
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
package org.asamk.signal.manager;
|
package org.asamk.signal.manager;
|
||||||
|
|
||||||
import org.asamk.signal.manager.config.ServiceConfig;
|
import org.asamk.signal.manager.config.ServiceConfig;
|
||||||
import org.asamk.signal.manager.config.ServiceEnvironment;
|
import org.asamk.signal.manager.config.ServiceEnvironment;
|
||||||
import org.asamk.signal.manager.config.ServiceEnvironmentConfig;
|
|
||||||
import org.asamk.signal.manager.storage.SignalAccount;
|
|
||||||
import org.asamk.signal.manager.storage.identities.TrustNewIdentity;
|
|
||||||
import org.asamk.signal.manager.util.KeyUtils;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.whispersystems.libsignal.IdentityKeyPair;
|
|
||||||
import org.whispersystems.libsignal.util.KeyHelper;
|
|
||||||
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
|
|
||||||
import org.whispersystems.signalservice.api.groupsv2.ClientZkOperations;
|
|
||||||
import org.whispersystems.signalservice.api.groupsv2.GroupsV2Operations;
|
|
||||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
|
||||||
import org.whispersystems.signalservice.api.push.exceptions.AuthorizationFailedException;
|
|
||||||
import org.whispersystems.signalservice.api.util.DeviceNameUtil;
|
|
||||||
import org.whispersystems.signalservice.internal.util.DynamicCredentialsProvider;
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -40,54 +9,15 @@ import java.net.URI;
|
||||||
import java.util.concurrent.TimeoutException;
|
import java.util.concurrent.TimeoutException;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
public class ProvisioningManager {
|
public interface ProvisioningManager {
|
||||||
|
|
||||||
private final static Logger logger = LoggerFactory.getLogger(ProvisioningManager.class);
|
static ProvisioningManager init(
|
||||||
|
|
||||||
private final PathConfig pathConfig;
|
|
||||||
private final ServiceEnvironmentConfig serviceEnvironmentConfig;
|
|
||||||
private final String userAgent;
|
|
||||||
private final Consumer<Manager> newManagerListener;
|
|
||||||
|
|
||||||
private final SignalServiceAccountManager accountManager;
|
|
||||||
private final IdentityKeyPair tempIdentityKey;
|
|
||||||
private final int registrationId;
|
|
||||||
private final String password;
|
|
||||||
|
|
||||||
ProvisioningManager(
|
|
||||||
PathConfig pathConfig,
|
|
||||||
ServiceEnvironmentConfig serviceEnvironmentConfig,
|
|
||||||
String userAgent,
|
|
||||||
final Consumer<Manager> newManagerListener
|
|
||||||
) {
|
|
||||||
this.pathConfig = pathConfig;
|
|
||||||
this.serviceEnvironmentConfig = serviceEnvironmentConfig;
|
|
||||||
this.userAgent = userAgent;
|
|
||||||
this.newManagerListener = newManagerListener;
|
|
||||||
|
|
||||||
tempIdentityKey = KeyUtils.generateIdentityKeyPair();
|
|
||||||
registrationId = KeyHelper.generateRegistrationId(false);
|
|
||||||
password = KeyUtils.createPassword();
|
|
||||||
GroupsV2Operations groupsV2Operations;
|
|
||||||
try {
|
|
||||||
groupsV2Operations = new GroupsV2Operations(ClientZkOperations.create(serviceEnvironmentConfig.getSignalServiceConfiguration()));
|
|
||||||
} catch (Throwable ignored) {
|
|
||||||
groupsV2Operations = null;
|
|
||||||
}
|
|
||||||
accountManager = new SignalServiceAccountManager(serviceEnvironmentConfig.getSignalServiceConfiguration(),
|
|
||||||
new DynamicCredentialsProvider(null, null, password, SignalServiceAddress.DEFAULT_DEVICE_ID),
|
|
||||||
userAgent,
|
|
||||||
groupsV2Operations,
|
|
||||||
ServiceConfig.AUTOMATIC_NETWORK_RETRY);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ProvisioningManager init(
|
|
||||||
File settingsPath, ServiceEnvironment serviceEnvironment, String userAgent
|
File settingsPath, ServiceEnvironment serviceEnvironment, String userAgent
|
||||||
) {
|
) {
|
||||||
return init(settingsPath, serviceEnvironment, userAgent, null);
|
return init(settingsPath, serviceEnvironment, userAgent, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ProvisioningManager init(
|
static ProvisioningManager init(
|
||||||
File settingsPath,
|
File settingsPath,
|
||||||
ServiceEnvironment serviceEnvironment,
|
ServiceEnvironment serviceEnvironment,
|
||||||
String userAgent,
|
String userAgent,
|
||||||
|
@ -97,113 +27,10 @@ public class ProvisioningManager {
|
||||||
|
|
||||||
final var serviceConfiguration = ServiceConfig.getServiceEnvironmentConfig(serviceEnvironment, userAgent);
|
final var serviceConfiguration = ServiceConfig.getServiceEnvironmentConfig(serviceEnvironment, userAgent);
|
||||||
|
|
||||||
return new ProvisioningManager(pathConfig, serviceConfiguration, userAgent, newManagerListener);
|
return new ProvisioningManagerImpl(pathConfig, serviceConfiguration, userAgent, newManagerListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
public URI getDeviceLinkUri() throws TimeoutException, IOException {
|
URI getDeviceLinkUri() throws TimeoutException, IOException;
|
||||||
var deviceUuid = accountManager.getNewDeviceUuid();
|
|
||||||
|
|
||||||
return new DeviceLinkInfo(deviceUuid, tempIdentityKey.getPublicKey().getPublicKey()).createDeviceLinkUri();
|
String finishDeviceLink(String deviceName) throws IOException, TimeoutException, UserAlreadyExists;
|
||||||
}
|
|
||||||
|
|
||||||
public String finishDeviceLink(String deviceName) throws IOException, TimeoutException, UserAlreadyExists {
|
|
||||||
var ret = accountManager.getNewDeviceRegistration(tempIdentityKey);
|
|
||||||
var number = ret.getNumber();
|
|
||||||
|
|
||||||
logger.info("Received link information from {}, linking in progress ...", number);
|
|
||||||
|
|
||||||
if (SignalAccount.userExists(pathConfig.dataPath(), number) && !canRelinkExistingAccount(number)) {
|
|
||||||
throw new UserAlreadyExists(number, SignalAccount.getFileName(pathConfig.dataPath(), number));
|
|
||||||
}
|
|
||||||
|
|
||||||
var encryptedDeviceName = deviceName == null
|
|
||||||
? null
|
|
||||||
: DeviceNameUtil.encryptDeviceName(deviceName, ret.getIdentity().getPrivateKey());
|
|
||||||
|
|
||||||
logger.debug("Finishing new device registration");
|
|
||||||
var deviceId = accountManager.finishNewDeviceRegistration(ret.getProvisioningCode(),
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
registrationId,
|
|
||||||
encryptedDeviceName);
|
|
||||||
|
|
||||||
// Create new account with the synced identity
|
|
||||||
var profileKey = ret.getProfileKey() == null ? KeyUtils.createProfileKey() : ret.getProfileKey();
|
|
||||||
|
|
||||||
SignalAccount account = null;
|
|
||||||
try {
|
|
||||||
account = SignalAccount.createOrUpdateLinkedAccount(pathConfig.dataPath(),
|
|
||||||
number,
|
|
||||||
ret.getAci(),
|
|
||||||
password,
|
|
||||||
encryptedDeviceName,
|
|
||||||
deviceId,
|
|
||||||
ret.getIdentity(),
|
|
||||||
registrationId,
|
|
||||||
profileKey,
|
|
||||||
TrustNewIdentity.ON_FIRST_USE);
|
|
||||||
|
|
||||||
ManagerImpl m = null;
|
|
||||||
try {
|
|
||||||
m = new ManagerImpl(account, pathConfig, serviceEnvironmentConfig, userAgent);
|
|
||||||
account = null;
|
|
||||||
|
|
||||||
logger.debug("Refreshing pre keys");
|
|
||||||
try {
|
|
||||||
m.refreshPreKeys();
|
|
||||||
} catch (Exception e) {
|
|
||||||
logger.error("Failed to refresh pre keys.");
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.debug("Requesting sync data");
|
|
||||||
try {
|
|
||||||
m.requestAllSyncData();
|
|
||||||
} catch (Exception e) {
|
|
||||||
logger.error(
|
|
||||||
"Failed to request sync messages from linked device, data can be requested again with `sendSyncRequest`.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (newManagerListener != null) {
|
|
||||||
newManagerListener.accept(m);
|
|
||||||
m = null;
|
|
||||||
}
|
|
||||||
return number;
|
|
||||||
} finally {
|
|
||||||
if (m != null) {
|
|
||||||
m.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
if (account != null) {
|
|
||||||
account.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean canRelinkExistingAccount(final String number) throws IOException {
|
|
||||||
final SignalAccount signalAccount;
|
|
||||||
try {
|
|
||||||
signalAccount = SignalAccount.load(pathConfig.dataPath(), number, false, TrustNewIdentity.ON_FIRST_USE);
|
|
||||||
} catch (IOException e) {
|
|
||||||
logger.debug("Account in use or failed to load.", e);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
try (signalAccount) {
|
|
||||||
if (signalAccount.isMasterDevice()) {
|
|
||||||
logger.debug("Account is a master device.");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
final var m = new ManagerImpl(signalAccount, pathConfig, serviceEnvironmentConfig, userAgent);
|
|
||||||
try (m) {
|
|
||||||
m.checkAccountState();
|
|
||||||
} catch (AuthorizationFailedException ignored) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.debug("Account is still successfully linked.");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,190 @@
|
||||||
|
/*
|
||||||
|
Copyright (C) 2015-2021 AsamK and contributors
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package org.asamk.signal.manager;
|
||||||
|
|
||||||
|
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.identities.TrustNewIdentity;
|
||||||
|
import org.asamk.signal.manager.util.KeyUtils;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.whispersystems.libsignal.IdentityKeyPair;
|
||||||
|
import org.whispersystems.libsignal.util.KeyHelper;
|
||||||
|
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
|
||||||
|
import org.whispersystems.signalservice.api.groupsv2.ClientZkOperations;
|
||||||
|
import org.whispersystems.signalservice.api.groupsv2.GroupsV2Operations;
|
||||||
|
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||||
|
import org.whispersystems.signalservice.api.push.exceptions.AuthorizationFailedException;
|
||||||
|
import org.whispersystems.signalservice.api.util.DeviceNameUtil;
|
||||||
|
import org.whispersystems.signalservice.internal.util.DynamicCredentialsProvider;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.util.concurrent.TimeoutException;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
public class ProvisioningManagerImpl implements ProvisioningManager {
|
||||||
|
|
||||||
|
private final static Logger logger = LoggerFactory.getLogger(ProvisioningManagerImpl.class);
|
||||||
|
|
||||||
|
private final PathConfig pathConfig;
|
||||||
|
private final ServiceEnvironmentConfig serviceEnvironmentConfig;
|
||||||
|
private final String userAgent;
|
||||||
|
private final Consumer<Manager> newManagerListener;
|
||||||
|
|
||||||
|
private final SignalServiceAccountManager accountManager;
|
||||||
|
private final IdentityKeyPair tempIdentityKey;
|
||||||
|
private final int registrationId;
|
||||||
|
private final String password;
|
||||||
|
|
||||||
|
ProvisioningManagerImpl(
|
||||||
|
PathConfig pathConfig,
|
||||||
|
ServiceEnvironmentConfig serviceEnvironmentConfig,
|
||||||
|
String userAgent,
|
||||||
|
final Consumer<Manager> newManagerListener
|
||||||
|
) {
|
||||||
|
this.pathConfig = pathConfig;
|
||||||
|
this.serviceEnvironmentConfig = serviceEnvironmentConfig;
|
||||||
|
this.userAgent = userAgent;
|
||||||
|
this.newManagerListener = newManagerListener;
|
||||||
|
|
||||||
|
tempIdentityKey = KeyUtils.generateIdentityKeyPair();
|
||||||
|
registrationId = KeyHelper.generateRegistrationId(false);
|
||||||
|
password = KeyUtils.createPassword();
|
||||||
|
GroupsV2Operations groupsV2Operations;
|
||||||
|
try {
|
||||||
|
groupsV2Operations = new GroupsV2Operations(ClientZkOperations.create(serviceEnvironmentConfig.getSignalServiceConfiguration()));
|
||||||
|
} catch (Throwable ignored) {
|
||||||
|
groupsV2Operations = null;
|
||||||
|
}
|
||||||
|
accountManager = new SignalServiceAccountManager(serviceEnvironmentConfig.getSignalServiceConfiguration(),
|
||||||
|
new DynamicCredentialsProvider(null, null, password, SignalServiceAddress.DEFAULT_DEVICE_ID),
|
||||||
|
userAgent,
|
||||||
|
groupsV2Operations,
|
||||||
|
ServiceConfig.AUTOMATIC_NETWORK_RETRY);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public URI getDeviceLinkUri() throws TimeoutException, IOException {
|
||||||
|
var deviceUuid = accountManager.getNewDeviceUuid();
|
||||||
|
|
||||||
|
return new DeviceLinkInfo(deviceUuid, tempIdentityKey.getPublicKey().getPublicKey()).createDeviceLinkUri();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String finishDeviceLink(String deviceName) throws IOException, TimeoutException, UserAlreadyExists {
|
||||||
|
var ret = accountManager.getNewDeviceRegistration(tempIdentityKey);
|
||||||
|
var number = ret.getNumber();
|
||||||
|
|
||||||
|
logger.info("Received link information from {}, linking in progress ...", number);
|
||||||
|
|
||||||
|
if (SignalAccount.userExists(pathConfig.dataPath(), number) && !canRelinkExistingAccount(number)) {
|
||||||
|
throw new UserAlreadyExists(number, SignalAccount.getFileName(pathConfig.dataPath(), number));
|
||||||
|
}
|
||||||
|
|
||||||
|
var encryptedDeviceName = deviceName == null
|
||||||
|
? null
|
||||||
|
: DeviceNameUtil.encryptDeviceName(deviceName, ret.getIdentity().getPrivateKey());
|
||||||
|
|
||||||
|
logger.debug("Finishing new device registration");
|
||||||
|
var deviceId = accountManager.finishNewDeviceRegistration(ret.getProvisioningCode(),
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
registrationId,
|
||||||
|
encryptedDeviceName);
|
||||||
|
|
||||||
|
// Create new account with the synced identity
|
||||||
|
var profileKey = ret.getProfileKey() == null ? KeyUtils.createProfileKey() : ret.getProfileKey();
|
||||||
|
|
||||||
|
SignalAccount account = null;
|
||||||
|
try {
|
||||||
|
account = SignalAccount.createOrUpdateLinkedAccount(pathConfig.dataPath(),
|
||||||
|
number,
|
||||||
|
ret.getAci(),
|
||||||
|
password,
|
||||||
|
encryptedDeviceName,
|
||||||
|
deviceId,
|
||||||
|
ret.getIdentity(),
|
||||||
|
registrationId,
|
||||||
|
profileKey,
|
||||||
|
TrustNewIdentity.ON_FIRST_USE);
|
||||||
|
|
||||||
|
ManagerImpl m = null;
|
||||||
|
try {
|
||||||
|
m = new ManagerImpl(account, pathConfig, serviceEnvironmentConfig, userAgent);
|
||||||
|
account = null;
|
||||||
|
|
||||||
|
logger.debug("Refreshing pre keys");
|
||||||
|
try {
|
||||||
|
m.refreshPreKeys();
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("Failed to refresh pre keys.");
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.debug("Requesting sync data");
|
||||||
|
try {
|
||||||
|
m.requestAllSyncData();
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error(
|
||||||
|
"Failed to request sync messages from linked device, data can be requested again with `sendSyncRequest`.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newManagerListener != null) {
|
||||||
|
newManagerListener.accept(m);
|
||||||
|
m = null;
|
||||||
|
}
|
||||||
|
return number;
|
||||||
|
} finally {
|
||||||
|
if (m != null) {
|
||||||
|
m.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (account != null) {
|
||||||
|
account.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean canRelinkExistingAccount(final String number) throws IOException {
|
||||||
|
final SignalAccount signalAccount;
|
||||||
|
try {
|
||||||
|
signalAccount = SignalAccount.load(pathConfig.dataPath(), number, false, TrustNewIdentity.ON_FIRST_USE);
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.debug("Account in use or failed to load.", e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try (signalAccount) {
|
||||||
|
if (signalAccount.isMasterDevice()) {
|
||||||
|
logger.debug("Account is a master device.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
final var m = new ManagerImpl(signalAccount, pathConfig, serviceEnvironmentConfig, userAgent);
|
||||||
|
try (m) {
|
||||||
|
m.checkAccountState();
|
||||||
|
} catch (AuthorizationFailedException ignored) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.debug("Account is still successfully linked.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,19 +1,3 @@
|
||||||
/*
|
|
||||||
Copyright (C) 2015-2021 AsamK and contributors
|
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
package org.asamk.signal.manager;
|
package org.asamk.signal.manager;
|
||||||
|
|
||||||
import org.asamk.signal.manager.api.CaptchaRequiredException;
|
import org.asamk.signal.manager.api.CaptchaRequiredException;
|
||||||
|
@ -21,92 +5,25 @@ import org.asamk.signal.manager.api.IncorrectPinException;
|
||||||
import org.asamk.signal.manager.api.PinLockedException;
|
import org.asamk.signal.manager.api.PinLockedException;
|
||||||
import org.asamk.signal.manager.config.ServiceConfig;
|
import org.asamk.signal.manager.config.ServiceConfig;
|
||||||
import org.asamk.signal.manager.config.ServiceEnvironment;
|
import org.asamk.signal.manager.config.ServiceEnvironment;
|
||||||
import org.asamk.signal.manager.config.ServiceEnvironmentConfig;
|
|
||||||
import org.asamk.signal.manager.helper.PinHelper;
|
|
||||||
import org.asamk.signal.manager.storage.SignalAccount;
|
import org.asamk.signal.manager.storage.SignalAccount;
|
||||||
import org.asamk.signal.manager.storage.identities.TrustNewIdentity;
|
import org.asamk.signal.manager.storage.identities.TrustNewIdentity;
|
||||||
import org.asamk.signal.manager.util.KeyUtils;
|
import org.asamk.signal.manager.util.KeyUtils;
|
||||||
import org.asamk.signal.manager.util.Utils;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.whispersystems.libsignal.util.KeyHelper;
|
import org.whispersystems.libsignal.util.KeyHelper;
|
||||||
import org.whispersystems.libsignal.util.guava.Optional;
|
|
||||||
import org.whispersystems.signalservice.api.KbsPinData;
|
|
||||||
import org.whispersystems.signalservice.api.KeyBackupServicePinException;
|
|
||||||
import org.whispersystems.signalservice.api.KeyBackupSystemNoDataException;
|
|
||||||
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
|
|
||||||
import org.whispersystems.signalservice.api.groupsv2.ClientZkOperations;
|
|
||||||
import org.whispersystems.signalservice.api.groupsv2.GroupsV2Operations;
|
|
||||||
import org.whispersystems.signalservice.api.kbs.MasterKey;
|
|
||||||
import org.whispersystems.signalservice.api.push.ACI;
|
|
||||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
|
||||||
import org.whispersystems.signalservice.internal.ServiceResponse;
|
|
||||||
import org.whispersystems.signalservice.internal.push.LockedException;
|
|
||||||
import org.whispersystems.signalservice.internal.push.RequestVerificationCodeResponse;
|
|
||||||
import org.whispersystems.signalservice.internal.push.VerifyAccountResponse;
|
|
||||||
import org.whispersystems.signalservice.internal.util.DynamicCredentialsProvider;
|
|
||||||
|
|
||||||
import java.io.Closeable;
|
import java.io.Closeable;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import static org.asamk.signal.manager.config.ServiceConfig.capabilities;
|
public interface RegistrationManager extends Closeable {
|
||||||
|
|
||||||
public class RegistrationManager implements Closeable {
|
static RegistrationManager init(
|
||||||
|
|
||||||
private final static Logger logger = LoggerFactory.getLogger(RegistrationManager.class);
|
|
||||||
|
|
||||||
private SignalAccount account;
|
|
||||||
private final PathConfig pathConfig;
|
|
||||||
private final ServiceEnvironmentConfig serviceEnvironmentConfig;
|
|
||||||
private final String userAgent;
|
|
||||||
private final Consumer<Manager> newManagerListener;
|
|
||||||
|
|
||||||
private final SignalServiceAccountManager accountManager;
|
|
||||||
private final PinHelper pinHelper;
|
|
||||||
|
|
||||||
private RegistrationManager(
|
|
||||||
SignalAccount account,
|
|
||||||
PathConfig pathConfig,
|
|
||||||
ServiceEnvironmentConfig serviceEnvironmentConfig,
|
|
||||||
String userAgent,
|
|
||||||
Consumer<Manager> newManagerListener
|
|
||||||
) {
|
|
||||||
this.account = account;
|
|
||||||
this.pathConfig = pathConfig;
|
|
||||||
this.serviceEnvironmentConfig = serviceEnvironmentConfig;
|
|
||||||
this.userAgent = userAgent;
|
|
||||||
this.newManagerListener = newManagerListener;
|
|
||||||
|
|
||||||
GroupsV2Operations groupsV2Operations;
|
|
||||||
try {
|
|
||||||
groupsV2Operations = new GroupsV2Operations(ClientZkOperations.create(serviceEnvironmentConfig.getSignalServiceConfiguration()));
|
|
||||||
} catch (Throwable ignored) {
|
|
||||||
groupsV2Operations = null;
|
|
||||||
}
|
|
||||||
this.accountManager = new SignalServiceAccountManager(serviceEnvironmentConfig.getSignalServiceConfiguration(),
|
|
||||||
new DynamicCredentialsProvider(
|
|
||||||
// Using empty UUID, because registering doesn't work otherwise
|
|
||||||
null, account.getAccount(), account.getPassword(), SignalServiceAddress.DEFAULT_DEVICE_ID),
|
|
||||||
userAgent,
|
|
||||||
groupsV2Operations,
|
|
||||||
ServiceConfig.AUTOMATIC_NETWORK_RETRY);
|
|
||||||
final var keyBackupService = accountManager.getKeyBackupService(ServiceConfig.getIasKeyStore(),
|
|
||||||
serviceEnvironmentConfig.getKeyBackupConfig().getEnclaveName(),
|
|
||||||
serviceEnvironmentConfig.getKeyBackupConfig().getServiceId(),
|
|
||||||
serviceEnvironmentConfig.getKeyBackupConfig().getMrenclave(),
|
|
||||||
10);
|
|
||||||
this.pinHelper = new PinHelper(keyBackupService);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static RegistrationManager init(
|
|
||||||
String number, File settingsPath, ServiceEnvironment serviceEnvironment, String userAgent
|
String number, File settingsPath, ServiceEnvironment serviceEnvironment, String userAgent
|
||||||
) throws IOException {
|
) throws IOException {
|
||||||
return init(number, settingsPath, serviceEnvironment, userAgent, null);
|
return init(number, settingsPath, serviceEnvironment, userAgent, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static RegistrationManager init(
|
static RegistrationManager init(
|
||||||
String number,
|
String number,
|
||||||
File settingsPath,
|
File settingsPath,
|
||||||
ServiceEnvironment serviceEnvironment,
|
ServiceEnvironment serviceEnvironment,
|
||||||
|
@ -128,176 +45,21 @@ public class RegistrationManager implements Closeable {
|
||||||
profileKey,
|
profileKey,
|
||||||
TrustNewIdentity.ON_FIRST_USE);
|
TrustNewIdentity.ON_FIRST_USE);
|
||||||
|
|
||||||
return new RegistrationManager(account, pathConfig, serviceConfiguration, userAgent, newManagerListener);
|
return new RegistrationManagerImpl(account,
|
||||||
|
pathConfig,
|
||||||
|
serviceConfiguration,
|
||||||
|
userAgent,
|
||||||
|
newManagerListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
var account = SignalAccount.load(pathConfig.dataPath(), number, true, TrustNewIdentity.ON_FIRST_USE);
|
var account = SignalAccount.load(pathConfig.dataPath(), number, true, TrustNewIdentity.ON_FIRST_USE);
|
||||||
|
|
||||||
return new RegistrationManager(account, pathConfig, serviceConfiguration, userAgent, newManagerListener);
|
return new RegistrationManagerImpl(account, pathConfig, serviceConfiguration, userAgent, newManagerListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void register(boolean voiceVerification, String captcha) throws IOException, CaptchaRequiredException {
|
void register(boolean voiceVerification, String captcha) throws IOException, CaptchaRequiredException;
|
||||||
captcha = captcha == null ? null : captcha.replace("signalcaptcha://", "");
|
|
||||||
if (account.getAci() != null) {
|
|
||||||
try {
|
|
||||||
final var accountManager = new SignalServiceAccountManager(serviceEnvironmentConfig.getSignalServiceConfiguration(),
|
|
||||||
new DynamicCredentialsProvider(account.getAci(),
|
|
||||||
account.getAccount(),
|
|
||||||
account.getPassword(),
|
|
||||||
account.getDeviceId()),
|
|
||||||
userAgent,
|
|
||||||
null,
|
|
||||||
ServiceConfig.AUTOMATIC_NETWORK_RETRY);
|
|
||||||
accountManager.setAccountAttributes(account.getEncryptedDeviceName(),
|
|
||||||
null,
|
|
||||||
account.getLocalRegistrationId(),
|
|
||||||
true,
|
|
||||||
null,
|
|
||||||
account.getPinMasterKey() == null ? null : account.getPinMasterKey().deriveRegistrationLock(),
|
|
||||||
account.getSelfUnidentifiedAccessKey(),
|
|
||||||
account.isUnrestrictedUnidentifiedAccess(),
|
|
||||||
capabilities,
|
|
||||||
account.isDiscoverableByPhoneNumber());
|
|
||||||
account.setRegistered(true);
|
|
||||||
logger.info("Reactivated existing account, verify is not necessary.");
|
|
||||||
if (newManagerListener != null) {
|
|
||||||
final var m = new ManagerImpl(account, pathConfig, serviceEnvironmentConfig, userAgent);
|
|
||||||
account = null;
|
|
||||||
newManagerListener.accept(m);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
} catch (IOException e) {
|
|
||||||
logger.debug("Failed to reactivate account");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
final ServiceResponse<RequestVerificationCodeResponse> response;
|
|
||||||
if (voiceVerification) {
|
|
||||||
response = accountManager.requestVoiceVerificationCode(Utils.getDefaultLocale(),
|
|
||||||
Optional.fromNullable(captcha),
|
|
||||||
Optional.absent(),
|
|
||||||
Optional.absent());
|
|
||||||
} else {
|
|
||||||
response = accountManager.requestSmsVerificationCode(false,
|
|
||||||
Optional.fromNullable(captcha),
|
|
||||||
Optional.absent(),
|
|
||||||
Optional.absent());
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
handleResponseException(response);
|
|
||||||
} catch (org.whispersystems.signalservice.api.push.exceptions.CaptchaRequiredException e) {
|
|
||||||
throw new CaptchaRequiredException(e.getMessage(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void verifyAccount(
|
void verifyAccount(
|
||||||
String verificationCode, String pin
|
String verificationCode, String pin
|
||||||
) throws IOException, PinLockedException, IncorrectPinException {
|
) throws IOException, PinLockedException, IncorrectPinException;
|
||||||
verificationCode = verificationCode.replace("-", "");
|
|
||||||
VerifyAccountResponse response;
|
|
||||||
MasterKey masterKey;
|
|
||||||
try {
|
|
||||||
response = verifyAccountWithCode(verificationCode, null);
|
|
||||||
|
|
||||||
masterKey = null;
|
|
||||||
pin = null;
|
|
||||||
} catch (LockedException e) {
|
|
||||||
if (pin == null) {
|
|
||||||
throw new PinLockedException(e.getTimeRemaining());
|
|
||||||
}
|
|
||||||
|
|
||||||
KbsPinData registrationLockData;
|
|
||||||
try {
|
|
||||||
registrationLockData = pinHelper.getRegistrationLockData(pin, e);
|
|
||||||
} catch (KeyBackupSystemNoDataException ex) {
|
|
||||||
throw new IOException(e);
|
|
||||||
} catch (KeyBackupServicePinException ex) {
|
|
||||||
throw new IncorrectPinException(ex.getTriesRemaining());
|
|
||||||
}
|
|
||||||
if (registrationLockData == null) {
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
|
|
||||||
var registrationLock = registrationLockData.getMasterKey().deriveRegistrationLock();
|
|
||||||
try {
|
|
||||||
response = verifyAccountWithCode(verificationCode, registrationLock);
|
|
||||||
} catch (LockedException _e) {
|
|
||||||
throw new AssertionError("KBS Pin appeared to matched but reg lock still failed!");
|
|
||||||
}
|
|
||||||
masterKey = registrationLockData.getMasterKey();
|
|
||||||
}
|
|
||||||
|
|
||||||
//accountManager.setGcmId(Optional.of(GoogleCloudMessaging.getInstance(this).register(REGISTRATION_ID)));
|
|
||||||
account.finishRegistration(ACI.parseOrNull(response.getUuid()), masterKey, pin);
|
|
||||||
|
|
||||||
ManagerImpl m = null;
|
|
||||||
try {
|
|
||||||
m = new ManagerImpl(account, pathConfig, serviceEnvironmentConfig, userAgent);
|
|
||||||
account = null;
|
|
||||||
|
|
||||||
m.refreshPreKeys();
|
|
||||||
if (response.isStorageCapable()) {
|
|
||||||
m.retrieveRemoteStorage();
|
|
||||||
}
|
|
||||||
// Set an initial empty profile so user can be added to groups
|
|
||||||
try {
|
|
||||||
m.setProfile(null, null, null, null, null);
|
|
||||||
} catch (NoClassDefFoundError e) {
|
|
||||||
logger.warn("Failed to set default profile: {}", e.getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (newManagerListener != null) {
|
|
||||||
newManagerListener.accept(m);
|
|
||||||
m = null;
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
if (m != null) {
|
|
||||||
m.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private VerifyAccountResponse verifyAccountWithCode(
|
|
||||||
final String verificationCode, final String registrationLock
|
|
||||||
) throws IOException {
|
|
||||||
final ServiceResponse<VerifyAccountResponse> response;
|
|
||||||
if (registrationLock == null) {
|
|
||||||
response = accountManager.verifyAccount(verificationCode,
|
|
||||||
account.getLocalRegistrationId(),
|
|
||||||
true,
|
|
||||||
account.getSelfUnidentifiedAccessKey(),
|
|
||||||
account.isUnrestrictedUnidentifiedAccess(),
|
|
||||||
ServiceConfig.capabilities,
|
|
||||||
account.isDiscoverableByPhoneNumber());
|
|
||||||
} else {
|
|
||||||
response = accountManager.verifyAccountWithRegistrationLockPin(verificationCode,
|
|
||||||
account.getLocalRegistrationId(),
|
|
||||||
true,
|
|
||||||
registrationLock,
|
|
||||||
account.getSelfUnidentifiedAccessKey(),
|
|
||||||
account.isUnrestrictedUnidentifiedAccess(),
|
|
||||||
ServiceConfig.capabilities,
|
|
||||||
account.isDiscoverableByPhoneNumber());
|
|
||||||
}
|
|
||||||
handleResponseException(response);
|
|
||||||
return response.getResult().get();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void close() throws IOException {
|
|
||||||
if (account != null) {
|
|
||||||
account.close();
|
|
||||||
account = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleResponseException(final ServiceResponse<?> response) throws IOException {
|
|
||||||
final var throwableOptional = response.getExecutionError().or(response.getApplicationError());
|
|
||||||
if (throwableOptional.isPresent()) {
|
|
||||||
if (throwableOptional.get() instanceof IOException) {
|
|
||||||
throw (IOException) throwableOptional.get();
|
|
||||||
} else {
|
|
||||||
throw new IOException(throwableOptional.get());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,263 @@
|
||||||
|
/*
|
||||||
|
Copyright (C) 2015-2021 AsamK and contributors
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package org.asamk.signal.manager;
|
||||||
|
|
||||||
|
import org.asamk.signal.manager.api.CaptchaRequiredException;
|
||||||
|
import org.asamk.signal.manager.api.IncorrectPinException;
|
||||||
|
import org.asamk.signal.manager.api.PinLockedException;
|
||||||
|
import org.asamk.signal.manager.config.ServiceConfig;
|
||||||
|
import org.asamk.signal.manager.config.ServiceEnvironmentConfig;
|
||||||
|
import org.asamk.signal.manager.helper.PinHelper;
|
||||||
|
import org.asamk.signal.manager.storage.SignalAccount;
|
||||||
|
import org.asamk.signal.manager.util.Utils;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.whispersystems.libsignal.util.guava.Optional;
|
||||||
|
import org.whispersystems.signalservice.api.KbsPinData;
|
||||||
|
import org.whispersystems.signalservice.api.KeyBackupServicePinException;
|
||||||
|
import org.whispersystems.signalservice.api.KeyBackupSystemNoDataException;
|
||||||
|
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
|
||||||
|
import org.whispersystems.signalservice.api.groupsv2.ClientZkOperations;
|
||||||
|
import org.whispersystems.signalservice.api.groupsv2.GroupsV2Operations;
|
||||||
|
import org.whispersystems.signalservice.api.kbs.MasterKey;
|
||||||
|
import org.whispersystems.signalservice.api.push.ACI;
|
||||||
|
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||||
|
import org.whispersystems.signalservice.internal.ServiceResponse;
|
||||||
|
import org.whispersystems.signalservice.internal.push.LockedException;
|
||||||
|
import org.whispersystems.signalservice.internal.push.RequestVerificationCodeResponse;
|
||||||
|
import org.whispersystems.signalservice.internal.push.VerifyAccountResponse;
|
||||||
|
import org.whispersystems.signalservice.internal.util.DynamicCredentialsProvider;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import static org.asamk.signal.manager.config.ServiceConfig.capabilities;
|
||||||
|
|
||||||
|
public class RegistrationManagerImpl implements RegistrationManager {
|
||||||
|
|
||||||
|
private final static Logger logger = LoggerFactory.getLogger(RegistrationManagerImpl.class);
|
||||||
|
|
||||||
|
private SignalAccount account;
|
||||||
|
private final PathConfig pathConfig;
|
||||||
|
private final ServiceEnvironmentConfig serviceEnvironmentConfig;
|
||||||
|
private final String userAgent;
|
||||||
|
private final Consumer<Manager> newManagerListener;
|
||||||
|
|
||||||
|
private final SignalServiceAccountManager accountManager;
|
||||||
|
private final PinHelper pinHelper;
|
||||||
|
|
||||||
|
RegistrationManagerImpl(
|
||||||
|
SignalAccount account,
|
||||||
|
PathConfig pathConfig,
|
||||||
|
ServiceEnvironmentConfig serviceEnvironmentConfig,
|
||||||
|
String userAgent,
|
||||||
|
Consumer<Manager> newManagerListener
|
||||||
|
) {
|
||||||
|
this.account = account;
|
||||||
|
this.pathConfig = pathConfig;
|
||||||
|
this.serviceEnvironmentConfig = serviceEnvironmentConfig;
|
||||||
|
this.userAgent = userAgent;
|
||||||
|
this.newManagerListener = newManagerListener;
|
||||||
|
|
||||||
|
GroupsV2Operations groupsV2Operations;
|
||||||
|
try {
|
||||||
|
groupsV2Operations = new GroupsV2Operations(ClientZkOperations.create(serviceEnvironmentConfig.getSignalServiceConfiguration()));
|
||||||
|
} catch (Throwable ignored) {
|
||||||
|
groupsV2Operations = null;
|
||||||
|
}
|
||||||
|
this.accountManager = new SignalServiceAccountManager(serviceEnvironmentConfig.getSignalServiceConfiguration(),
|
||||||
|
new DynamicCredentialsProvider(
|
||||||
|
// Using empty UUID, because registering doesn't work otherwise
|
||||||
|
null, account.getAccount(), account.getPassword(), SignalServiceAddress.DEFAULT_DEVICE_ID),
|
||||||
|
userAgent,
|
||||||
|
groupsV2Operations,
|
||||||
|
ServiceConfig.AUTOMATIC_NETWORK_RETRY);
|
||||||
|
final var keyBackupService = accountManager.getKeyBackupService(ServiceConfig.getIasKeyStore(),
|
||||||
|
serviceEnvironmentConfig.getKeyBackupConfig().getEnclaveName(),
|
||||||
|
serviceEnvironmentConfig.getKeyBackupConfig().getServiceId(),
|
||||||
|
serviceEnvironmentConfig.getKeyBackupConfig().getMrenclave(),
|
||||||
|
10);
|
||||||
|
this.pinHelper = new PinHelper(keyBackupService);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void register(boolean voiceVerification, String captcha) throws IOException, CaptchaRequiredException {
|
||||||
|
captcha = captcha == null ? null : captcha.replace("signalcaptcha://", "");
|
||||||
|
if (account.getAci() != null) {
|
||||||
|
try {
|
||||||
|
final var accountManager = new SignalServiceAccountManager(serviceEnvironmentConfig.getSignalServiceConfiguration(),
|
||||||
|
new DynamicCredentialsProvider(account.getAci(),
|
||||||
|
account.getAccount(),
|
||||||
|
account.getPassword(),
|
||||||
|
account.getDeviceId()),
|
||||||
|
userAgent,
|
||||||
|
null,
|
||||||
|
ServiceConfig.AUTOMATIC_NETWORK_RETRY);
|
||||||
|
accountManager.setAccountAttributes(account.getEncryptedDeviceName(),
|
||||||
|
null,
|
||||||
|
account.getLocalRegistrationId(),
|
||||||
|
true,
|
||||||
|
null,
|
||||||
|
account.getPinMasterKey() == null ? null : account.getPinMasterKey().deriveRegistrationLock(),
|
||||||
|
account.getSelfUnidentifiedAccessKey(),
|
||||||
|
account.isUnrestrictedUnidentifiedAccess(),
|
||||||
|
capabilities,
|
||||||
|
account.isDiscoverableByPhoneNumber());
|
||||||
|
account.setRegistered(true);
|
||||||
|
logger.info("Reactivated existing account, verify is not necessary.");
|
||||||
|
if (newManagerListener != null) {
|
||||||
|
final var m = new ManagerImpl(account, pathConfig, serviceEnvironmentConfig, userAgent);
|
||||||
|
account = null;
|
||||||
|
newManagerListener.accept(m);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.debug("Failed to reactivate account");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
final ServiceResponse<RequestVerificationCodeResponse> response;
|
||||||
|
if (voiceVerification) {
|
||||||
|
response = accountManager.requestVoiceVerificationCode(Utils.getDefaultLocale(),
|
||||||
|
Optional.fromNullable(captcha),
|
||||||
|
Optional.absent(),
|
||||||
|
Optional.absent());
|
||||||
|
} else {
|
||||||
|
response = accountManager.requestSmsVerificationCode(false,
|
||||||
|
Optional.fromNullable(captcha),
|
||||||
|
Optional.absent(),
|
||||||
|
Optional.absent());
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
handleResponseException(response);
|
||||||
|
} catch (org.whispersystems.signalservice.api.push.exceptions.CaptchaRequiredException e) {
|
||||||
|
throw new CaptchaRequiredException(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void verifyAccount(
|
||||||
|
String verificationCode, String pin
|
||||||
|
) throws IOException, PinLockedException, IncorrectPinException {
|
||||||
|
verificationCode = verificationCode.replace("-", "");
|
||||||
|
VerifyAccountResponse response;
|
||||||
|
MasterKey masterKey;
|
||||||
|
try {
|
||||||
|
response = verifyAccountWithCode(verificationCode, null);
|
||||||
|
|
||||||
|
masterKey = null;
|
||||||
|
pin = null;
|
||||||
|
} catch (LockedException e) {
|
||||||
|
if (pin == null) {
|
||||||
|
throw new PinLockedException(e.getTimeRemaining());
|
||||||
|
}
|
||||||
|
|
||||||
|
KbsPinData registrationLockData;
|
||||||
|
try {
|
||||||
|
registrationLockData = pinHelper.getRegistrationLockData(pin, e);
|
||||||
|
} catch (KeyBackupSystemNoDataException ex) {
|
||||||
|
throw new IOException(e);
|
||||||
|
} catch (KeyBackupServicePinException ex) {
|
||||||
|
throw new IncorrectPinException(ex.getTriesRemaining());
|
||||||
|
}
|
||||||
|
if (registrationLockData == null) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
|
||||||
|
var registrationLock = registrationLockData.getMasterKey().deriveRegistrationLock();
|
||||||
|
try {
|
||||||
|
response = verifyAccountWithCode(verificationCode, registrationLock);
|
||||||
|
} catch (LockedException _e) {
|
||||||
|
throw new AssertionError("KBS Pin appeared to matched but reg lock still failed!");
|
||||||
|
}
|
||||||
|
masterKey = registrationLockData.getMasterKey();
|
||||||
|
}
|
||||||
|
|
||||||
|
//accountManager.setGcmId(Optional.of(GoogleCloudMessaging.getInstance(this).register(REGISTRATION_ID)));
|
||||||
|
account.finishRegistration(ACI.parseOrNull(response.getUuid()), masterKey, pin);
|
||||||
|
|
||||||
|
ManagerImpl m = null;
|
||||||
|
try {
|
||||||
|
m = new ManagerImpl(account, pathConfig, serviceEnvironmentConfig, userAgent);
|
||||||
|
account = null;
|
||||||
|
|
||||||
|
m.refreshPreKeys();
|
||||||
|
if (response.isStorageCapable()) {
|
||||||
|
m.retrieveRemoteStorage();
|
||||||
|
}
|
||||||
|
// Set an initial empty profile so user can be added to groups
|
||||||
|
try {
|
||||||
|
m.setProfile(null, null, null, null, null);
|
||||||
|
} catch (NoClassDefFoundError e) {
|
||||||
|
logger.warn("Failed to set default profile: {}", e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newManagerListener != null) {
|
||||||
|
newManagerListener.accept(m);
|
||||||
|
m = null;
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (m != null) {
|
||||||
|
m.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private VerifyAccountResponse verifyAccountWithCode(
|
||||||
|
final String verificationCode, final String registrationLock
|
||||||
|
) throws IOException {
|
||||||
|
final ServiceResponse<VerifyAccountResponse> response;
|
||||||
|
if (registrationLock == null) {
|
||||||
|
response = accountManager.verifyAccount(verificationCode,
|
||||||
|
account.getLocalRegistrationId(),
|
||||||
|
true,
|
||||||
|
account.getSelfUnidentifiedAccessKey(),
|
||||||
|
account.isUnrestrictedUnidentifiedAccess(),
|
||||||
|
ServiceConfig.capabilities,
|
||||||
|
account.isDiscoverableByPhoneNumber());
|
||||||
|
} else {
|
||||||
|
response = accountManager.verifyAccountWithRegistrationLockPin(verificationCode,
|
||||||
|
account.getLocalRegistrationId(),
|
||||||
|
true,
|
||||||
|
registrationLock,
|
||||||
|
account.getSelfUnidentifiedAccessKey(),
|
||||||
|
account.isUnrestrictedUnidentifiedAccess(),
|
||||||
|
ServiceConfig.capabilities,
|
||||||
|
account.isDiscoverableByPhoneNumber());
|
||||||
|
}
|
||||||
|
handleResponseException(response);
|
||||||
|
return response.getResult().get();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
if (account != null) {
|
||||||
|
account.close();
|
||||||
|
account = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleResponseException(final ServiceResponse<?> response) throws IOException {
|
||||||
|
final var throwableOptional = response.getExecutionError().or(response.getApplicationError());
|
||||||
|
if (throwableOptional.isPresent()) {
|
||||||
|
if (throwableOptional.get() instanceof IOException) {
|
||||||
|
throw (IOException) throwableOptional.get();
|
||||||
|
} else {
|
||||||
|
throw new IOException(throwableOptional.get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue