import org.asamk.signal.manager.api.PinLockedException;
import org.asamk.signal.manager.api.RateLimitException;
import org.asamk.signal.manager.api.UpdateProfile;
+import org.asamk.signal.manager.api.VerificationMethodNotAvailableException;
import org.asamk.signal.manager.config.ServiceConfig;
import org.asamk.signal.manager.config.ServiceEnvironmentConfig;
import org.asamk.signal.manager.helper.AccountFileUpdater;
import org.asamk.signal.manager.storage.SignalAccount;
import org.asamk.signal.manager.util.KeyUtils;
import org.asamk.signal.manager.util.NumberVerificationUtils;
-import org.asamk.signal.manager.util.Utils;
import org.signal.libsignal.usernames.BaseUsernameException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
import org.whispersystems.signalservice.api.account.PreKeyCollection;
-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.ServiceId.ACI;
import org.whispersystems.signalservice.api.push.ServiceId.PNI;
import org.whispersystems.signalservice.api.push.ServiceIdType;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import org.whispersystems.signalservice.api.push.exceptions.AlreadyVerifiedException;
import org.whispersystems.signalservice.api.push.exceptions.DeprecatedVersionException;
+import org.whispersystems.signalservice.api.svr.SecureValueRecovery;
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.util.KeyUtils.generatePreKeysForType;
+import static org.asamk.signal.manager.util.Utils.handleResponseException;
+
public class RegistrationManagerImpl implements RegistrationManager {
- private final static Logger logger = LoggerFactory.getLogger(RegistrationManagerImpl.class);
+ private static final Logger logger = LoggerFactory.getLogger(RegistrationManagerImpl.class);
private SignalAccount account;
private final PathConfig pathConfig;
private final String userAgent;
private final Consumer<Manager> newManagerListener;
- private final SignalServiceAccountManager accountManager;
+ private final SignalServiceAccountManager unauthenticatedAccountManager;
private final PinHelper pinHelper;
private final AccountFileUpdater accountFileUpdater;
this.userAgent = userAgent;
this.newManagerListener = newManagerListener;
- GroupsV2Operations groupsV2Operations;
- try {
- groupsV2Operations = new GroupsV2Operations(ClientZkOperations.create(serviceEnvironmentConfig.signalServiceConfiguration()),
- ServiceConfig.GROUP_MAX_SIZE);
- } catch (Throwable ignored) {
- groupsV2Operations = null;
- }
- this.accountManager = new SignalServiceAccountManager(serviceEnvironmentConfig.signalServiceConfiguration(),
- new DynamicCredentialsProvider(
- // Using empty UUID, because registering doesn't work otherwise
- null, null, account.getNumber(), account.getPassword(), SignalServiceAddress.DEFAULT_DEVICE_ID),
+ this.unauthenticatedAccountManager = SignalServiceAccountManager.createWithStaticCredentials(
+ serviceEnvironmentConfig.signalServiceConfiguration(),
+ // Using empty UUID, because registering doesn't work otherwise
+ null,
+ null,
+ account.getNumber(),
+ SignalServiceAddress.DEFAULT_DEVICE_ID,
+ account.getPassword(),
userAgent,
- groupsV2Operations,
- ServiceConfig.AUTOMATIC_NETWORK_RETRY);
- final var keyBackupService = accountManager.getKeyBackupService(ServiceConfig.getIasKeyStore(),
- serviceEnvironmentConfig.keyBackupConfig().enclaveName(),
- serviceEnvironmentConfig.keyBackupConfig().serviceId(),
- serviceEnvironmentConfig.keyBackupConfig().mrenclave(),
- 10);
- final var fallbackKeyBackupServices = serviceEnvironmentConfig.fallbackKeyBackupConfigs()
+ ServiceConfig.AUTOMATIC_NETWORK_RETRY,
+ ServiceConfig.GROUP_MAX_SIZE);
+ final var secureValueRecovery = serviceEnvironmentConfig.svr2Mrenclaves()
.stream()
- .map(config -> accountManager.getKeyBackupService(ServiceConfig.getIasKeyStore(),
- config.enclaveName(),
- config.serviceId(),
- config.mrenclave(),
- 10))
+ .map(mr -> (SecureValueRecovery) this.unauthenticatedAccountManager.getSecureValueRecoveryV2(mr))
.toList();
- final var secureValueRecoveryV2 = accountManager.getSecureValueRecoveryV2(serviceEnvironmentConfig.svr2Mrenclave());
- this.pinHelper = new PinHelper(keyBackupService, fallbackKeyBackupServices, secureValueRecoveryV2);
+ this.pinHelper = new PinHelper(secureValueRecovery);
}
@Override
public void register(
- boolean voiceVerification, String captcha
- ) throws IOException, CaptchaRequiredException, NonNormalizedPhoneNumberException, RateLimitException {
+ boolean voiceVerification,
+ String captcha,
+ final boolean forceRegister
+ ) throws IOException, CaptchaRequiredException, NonNormalizedPhoneNumberException, RateLimitException, VerificationMethodNotAvailableException {
if (account.isRegistered()
&& account.getServiceEnvironment() != null
&& account.getServiceEnvironment() != serviceEnvironmentConfig.type()) {
}
try {
- if (account.getAci() != null && attemptReactivateAccount()) {
+ if (!forceRegister) {
+ if (account.isRegistered()) {
+ throw new IOException("Account is already registered");
+ }
+
+ if (account.getAci() != null && attemptReactivateAccount()) {
+ return;
+ }
+ }
+
+ final var recoveryPassword = account.getRecoveryPassword();
+ if (recoveryPassword != null && account.isPrimaryDevice() && attemptReregisterAccount(recoveryPassword)) {
return;
}
- String sessionId = NumberVerificationUtils.handleVerificationSession(accountManager,
+ final var registrationApi = unauthenticatedAccountManager.getRegistrationApi();
+ logger.trace("Creating verification session");
+ String sessionId = NumberVerificationUtils.handleVerificationSession(registrationApi,
account.getSessionId(account.getNumber()),
id -> account.setSessionId(account.getNumber(), id),
voiceVerification,
captcha);
- NumberVerificationUtils.requestVerificationCode(accountManager, sessionId, voiceVerification);
+ logger.trace("Requesting verification code");
+ NumberVerificationUtils.requestVerificationCode(registrationApi, sessionId, voiceVerification);
+ logger.debug("Successfully requested verification code");
+ account.setRegistered(false);
} catch (DeprecatedVersionException e) {
logger.debug("Signal-Server returned deprecated version exception", e);
throw e;
@Override
public void verifyAccount(
- String verificationCode, String pin
+ String verificationCode,
+ String pin
) throws IOException, PinLockedException, IncorrectPinException {
+ if (account.isRegistered()) {
+ throw new IOException("Account is already registered");
+ }
+
if (account.getPniIdentityKeyPair() == null) {
account.setPniIdentityKeyPair(KeyUtils.generateIdentityKeyPair());
}
- final var aciPreKeys = generatePreKeysForType(ServiceIdType.ACI);
- final var pniPreKeys = generatePreKeysForType(ServiceIdType.PNI);
+ final var aciPreKeys = generatePreKeysForType(account.getAccountData(ServiceIdType.ACI));
+ final var pniPreKeys = generatePreKeysForType(account.getAccountData(ServiceIdType.PNI));
final var result = NumberVerificationUtils.verifyNumber(account.getSessionId(account.getNumber()),
verificationCode,
pin,
pin = null;
}
- //accountManager.setGcmId(Optional.of(GoogleCloudMessaging.getInstance(this).register(REGISTRATION_ID)));
- final var aci = ACI.parseOrThrow(response.getUuid());
- final var pni = PNI.parseOrThrow(response.getPni());
- account.finishRegistration(aci, pni, masterKey, pin, aciPreKeys, pniPreKeys);
- accountFileUpdater.updateAccountIdentifiers(account.getNumber(), aci);
-
- ManagerImpl m = null;
- try {
- m = new ManagerImpl(account, pathConfig, accountFileUpdater, 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.updateProfile(UpdateProfile.newBuilder().build());
- } catch (NoClassDefFoundError e) {
- logger.warn("Failed to set default profile: {}", e.getMessage());
- }
-
- try {
- m.refreshCurrentUsername();
- } catch (IOException | BaseUsernameException e) {
- logger.warn("Failed to refresh current username", e);
- }
-
- if (newManagerListener != null) {
- newManagerListener.accept(m);
- m = null;
- }
- } finally {
- if (m != null) {
- m.close();
- }
- }
+ finishAccountRegistration(response, pin, masterKey, aciPreKeys, pniPreKeys);
}
@Override
return account.isRegistered();
}
+ private boolean attemptReregisterAccount(final String recoveryPassword) {
+ try {
+ if (account.getPniIdentityKeyPair() == null) {
+ account.setPniIdentityKeyPair(KeyUtils.generateIdentityKeyPair());
+ }
+
+ final var aciPreKeys = generatePreKeysForType(account.getAccountData(ServiceIdType.ACI));
+ final var pniPreKeys = generatePreKeysForType(account.getAccountData(ServiceIdType.PNI));
+ final var registrationApi = unauthenticatedAccountManager.getRegistrationApi();
+ final var response = handleResponseException(registrationApi.registerAccount(null,
+ recoveryPassword,
+ account.getAccountAttributes(null),
+ aciPreKeys,
+ pniPreKeys,
+ null,
+ true));
+ finishAccountRegistration(response,
+ account.getRegistrationLockPin(),
+ account.getPinBackedMasterKey(),
+ aciPreKeys,
+ pniPreKeys);
+ logger.info("Reregistered existing account, verify is not necessary.");
+ return true;
+ } catch (IOException e) {
+ logger.debug("Failed to reregister account with recovery password", e);
+ }
+ return false;
+ }
+
private boolean attemptReactivateAccount() {
try {
- final var accountManager = new SignalServiceAccountManager(serviceEnvironmentConfig.signalServiceConfiguration(),
- account.getCredentialsProvider(),
+ final var dependencies = new SignalDependencies(serviceEnvironmentConfig,
userAgent,
+ account.getCredentialsProvider(),
+ account.getSignalServiceDataStore(),
null,
- ServiceConfig.AUTOMATIC_NETWORK_RETRY);
- accountManager.setAccountAttributes(account.getAccountAttributes(null));
+ new ReentrantSignalSessionLock());
+ handleResponseException(dependencies.getAccountApi()
+ .setAccountAttributes(account.getAccountAttributes(null)));
account.setRegistered(true);
logger.info("Reactivated existing account, verify is not necessary.");
if (newManagerListener != null) {
final PreKeyCollection aciPreKeys,
final PreKeyCollection pniPreKeys
) throws IOException {
+ final var registrationApi = unauthenticatedAccountManager.getRegistrationApi();
try {
- Utils.handleResponseException(accountManager.verifyAccount(verificationCode, sessionId));
+ handleResponseException(registrationApi.verifyAccount(sessionId, verificationCode));
} catch (AlreadyVerifiedException e) {
// Already verified so can continue registering
}
- return Utils.handleResponseException(accountManager.registerAccount(sessionId,
+ return handleResponseException(registrationApi.registerAccount(sessionId,
null,
account.getAccountAttributes(registrationLock),
aciPreKeys,
true));
}
- private PreKeyCollection generatePreKeysForType(ServiceIdType serviceIdType) {
- final var accountData = account.getAccountData(serviceIdType);
- final var keyPair = accountData.getIdentityKeyPair();
- final var preKeyMetadata = accountData.getPreKeyMetadata();
+ private void finishAccountRegistration(
+ final VerifyAccountResponse response,
+ final String pin,
+ final MasterKey masterKey,
+ final PreKeyCollection aciPreKeys,
+ final PreKeyCollection pniPreKeys
+ ) throws IOException {
+ //accountManager.setGcmId(Optional.of(GoogleCloudMessaging.getInstance(this).register(REGISTRATION_ID)));
+ final var aci = ACI.parseOrThrow(response.getUuid());
+ final var pni = PNI.parseOrThrow(response.getPni());
+ account.finishRegistration(aci, pni, masterKey, pin, aciPreKeys, pniPreKeys);
+ accountFileUpdater.updateAccountIdentifiers(account.getNumber(), aci);
- final var nextSignedPreKeyId = preKeyMetadata.getNextSignedPreKeyId();
- final var signedPreKey = KeyUtils.generateSignedPreKeyRecord(nextSignedPreKeyId, keyPair);
+ ManagerImpl m = null;
+ try {
+ m = new ManagerImpl(account, pathConfig, accountFileUpdater, serviceEnvironmentConfig, userAgent);
+ account = null;
- final var privateKey = keyPair.getPrivateKey();
- final var kyberPreKeyIdOffset = preKeyMetadata.getKyberPreKeyIdOffset();
- final var lastResortKyberPreKey = KeyUtils.generateKyberPreKeyRecord(kyberPreKeyIdOffset, privateKey);
+ m.refreshPreKeys();
+ if (response.isStorageCapable()) {
+ m.syncRemoteStorage();
+ }
+ // Set an initial empty profile so user can be added to groups
+ try {
+ m.updateProfile(UpdateProfile.newBuilder().build());
+ } catch (NoClassDefFoundError e) {
+ logger.warn("Failed to set default profile: {}", e.getMessage());
+ }
- return new PreKeyCollection(keyPair.getPublicKey(), signedPreKey, lastResortKyberPreKey);
+ try {
+ m.refreshCurrentUsername();
+ } catch (IOException | BaseUsernameException e) {
+ logger.warn("Failed to refresh current username", e);
+ }
+
+ if (newManagerListener != null) {
+ newManagerListener.accept(m);
+ m = null;
+ }
+ } finally {
+ if (m != null) {
+ m.close();
+ }
+ }
}
@Override