2 Copyright (C) 2015-2021 AsamK and contributors
4 This program is free software: you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation, either version 3 of the License, or
7 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program. If not, see <http://www.gnu.org/licenses/>.
17 package org
.asamk
.signal
.manager
;
19 import org
.asamk
.signal
.manager
.config
.ServiceConfig
;
20 import org
.asamk
.signal
.manager
.config
.ServiceEnvironment
;
21 import org
.asamk
.signal
.manager
.config
.ServiceEnvironmentConfig
;
22 import org
.asamk
.signal
.manager
.storage
.SignalAccount
;
23 import org
.asamk
.signal
.manager
.util
.KeyUtils
;
24 import org
.slf4j
.Logger
;
25 import org
.slf4j
.LoggerFactory
;
26 import org
.whispersystems
.libsignal
.IdentityKeyPair
;
27 import org
.whispersystems
.libsignal
.util
.KeyHelper
;
28 import org
.whispersystems
.signalservice
.api
.SignalServiceAccountManager
;
29 import org
.whispersystems
.signalservice
.api
.groupsv2
.ClientZkOperations
;
30 import org
.whispersystems
.signalservice
.api
.groupsv2
.GroupsV2Operations
;
31 import org
.whispersystems
.signalservice
.api
.push
.SignalServiceAddress
;
32 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.AuthorizationFailedException
;
33 import org
.whispersystems
.signalservice
.api
.util
.DeviceNameUtil
;
34 import org
.whispersystems
.signalservice
.api
.util
.SleepTimer
;
35 import org
.whispersystems
.signalservice
.api
.util
.UptimeSleepTimer
;
36 import org
.whispersystems
.signalservice
.internal
.util
.DynamicCredentialsProvider
;
39 import java
.io
.IOException
;
41 import java
.util
.concurrent
.TimeoutException
;
43 public class ProvisioningManager
{
45 private final static Logger logger
= LoggerFactory
.getLogger(ProvisioningManager
.class);
47 private final PathConfig pathConfig
;
48 private final ServiceEnvironmentConfig serviceEnvironmentConfig
;
49 private final String userAgent
;
51 private final SignalServiceAccountManager accountManager
;
52 private final IdentityKeyPair tempIdentityKey
;
53 private final int registrationId
;
54 private final String password
;
56 ProvisioningManager(PathConfig pathConfig
, ServiceEnvironmentConfig serviceEnvironmentConfig
, String userAgent
) {
57 this.pathConfig
= pathConfig
;
58 this.serviceEnvironmentConfig
= serviceEnvironmentConfig
;
59 this.userAgent
= userAgent
;
61 tempIdentityKey
= KeyUtils
.generateIdentityKeyPair();
62 registrationId
= KeyHelper
.generateRegistrationId(false);
63 password
= KeyUtils
.createPassword();
64 final SleepTimer timer
= new UptimeSleepTimer();
65 GroupsV2Operations groupsV2Operations
;
67 groupsV2Operations
= new GroupsV2Operations(ClientZkOperations
.create(serviceEnvironmentConfig
.getSignalServiceConfiguration()));
68 } catch (Throwable ignored
) {
69 groupsV2Operations
= null;
71 accountManager
= new SignalServiceAccountManager(serviceEnvironmentConfig
.getSignalServiceConfiguration(),
72 new DynamicCredentialsProvider(null, null, password
, SignalServiceAddress
.DEFAULT_DEVICE_ID
),
75 ServiceConfig
.AUTOMATIC_NETWORK_RETRY
,
79 public static ProvisioningManager
init(
80 File settingsPath
, ServiceEnvironment serviceEnvironment
, String userAgent
82 var pathConfig
= PathConfig
.createDefault(settingsPath
);
84 final var serviceConfiguration
= ServiceConfig
.getServiceEnvironmentConfig(serviceEnvironment
, userAgent
);
86 return new ProvisioningManager(pathConfig
, serviceConfiguration
, userAgent
);
89 public URI
getDeviceLinkUri() throws TimeoutException
, IOException
{
90 var deviceUuid
= accountManager
.getNewDeviceUuid();
92 return new DeviceLinkInfo(deviceUuid
, tempIdentityKey
.getPublicKey().getPublicKey()).createDeviceLinkUri();
95 public Manager
finishDeviceLink(String deviceName
) throws IOException
, TimeoutException
, UserAlreadyExists
{
96 var ret
= accountManager
.getNewDeviceRegistration(tempIdentityKey
);
97 var number
= ret
.getNumber();
99 logger
.info("Received link information from {}, linking in progress ...", number
);
101 if (SignalAccount
.userExists(pathConfig
.getDataPath(), number
) && !canRelinkExistingAccount(number
)) {
102 throw new UserAlreadyExists(number
, SignalAccount
.getFileName(pathConfig
.getDataPath(), number
));
105 var encryptedDeviceName
= deviceName
== null
107 : DeviceNameUtil
.encryptDeviceName(deviceName
, ret
.getIdentity().getPrivateKey());
109 var deviceId
= accountManager
.finishNewDeviceRegistration(ret
.getProvisioningCode(),
113 encryptedDeviceName
);
115 // Create new account with the synced identity
116 var profileKey
= ret
.getProfileKey() == null ? KeyUtils
.createProfileKey() : ret
.getProfileKey();
118 SignalAccount account
= null;
120 account
= SignalAccount
.createOrUpdateLinkedAccount(pathConfig
.getDataPath(),
132 m
= new Manager(account
, pathConfig
, serviceEnvironmentConfig
, userAgent
);
136 } catch (Exception e
) {
137 logger
.error("Failed to check new account state.");
142 m
.requestAllSyncData();
143 } catch (Exception e
) {
144 logger
.error("Failed to request sync messages from linked device.");
148 final var result
= m
;
159 if (account
!= null) {
165 private boolean canRelinkExistingAccount(final String number
) throws UserAlreadyExists
, IOException
{
166 final SignalAccount signalAccount
;
168 signalAccount
= SignalAccount
.load(pathConfig
.getDataPath(), number
, false);
169 } catch (IOException e
) {
170 logger
.debug("Account in use or failed to load.", e
);
174 try (signalAccount
) {
175 if (signalAccount
.isMasterDevice()) {
176 logger
.debug("Account is a master device.");
180 final var m
= new Manager(signalAccount
, pathConfig
, serviceEnvironmentConfig
, userAgent
);
182 m
.checkAccountState();
183 } catch (AuthorizationFailedException ignored
) {
187 logger
.debug("Account is still successfully linked.");