2 Copyright (C) 2015-2022 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
.internal
;
19 import org
.asamk
.signal
.manager
.Manager
;
20 import org
.asamk
.signal
.manager
.ProvisioningManager
;
21 import org
.asamk
.signal
.manager
.Settings
;
22 import org
.asamk
.signal
.manager
.api
.DeviceLinkUrl
;
23 import org
.asamk
.signal
.manager
.api
.UserAlreadyExistsException
;
24 import org
.asamk
.signal
.manager
.config
.ServiceConfig
;
25 import org
.asamk
.signal
.manager
.config
.ServiceEnvironmentConfig
;
26 import org
.asamk
.signal
.manager
.storage
.SignalAccount
;
27 import org
.asamk
.signal
.manager
.storage
.accounts
.AccountsStore
;
28 import org
.asamk
.signal
.manager
.util
.KeyUtils
;
29 import org
.signal
.libsignal
.protocol
.IdentityKeyPair
;
30 import org
.slf4j
.Logger
;
31 import org
.slf4j
.LoggerFactory
;
32 import org
.whispersystems
.signalservice
.api
.groupsv2
.ClientZkOperations
;
33 import org
.whispersystems
.signalservice
.api
.push
.ServiceIdType
;
34 import org
.whispersystems
.signalservice
.api
.push
.SignalServiceAddress
;
35 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.AuthorizationFailedException
;
36 import org
.whispersystems
.signalservice
.api
.registration
.ProvisioningApi
;
37 import org
.whispersystems
.signalservice
.api
.util
.DeviceNameUtil
;
38 import org
.whispersystems
.signalservice
.internal
.push
.ProvisioningSocket
;
39 import org
.whispersystems
.signalservice
.internal
.push
.PushServiceSocket
;
40 import org
.whispersystems
.signalservice
.internal
.util
.DynamicCredentialsProvider
;
42 import java
.io
.IOException
;
44 import java
.nio
.channels
.OverlappingFileLockException
;
45 import java
.util
.concurrent
.TimeoutException
;
46 import java
.util
.function
.Consumer
;
48 import static org
.asamk
.signal
.manager
.util
.KeyUtils
.generatePreKeysForType
;
50 public class ProvisioningManagerImpl
implements ProvisioningManager
{
52 private static final Logger logger
= LoggerFactory
.getLogger(ProvisioningManagerImpl
.class);
54 private final PathConfig pathConfig
;
55 private final ServiceEnvironmentConfig serviceEnvironmentConfig
;
56 private final String userAgent
;
57 private final Consumer
<Manager
> newManagerListener
;
58 private final AccountsStore accountsStore
;
60 private final ProvisioningApi provisioningApi
;
61 private final IdentityKeyPair tempIdentityKey
;
62 private final String password
;
64 public ProvisioningManagerImpl(
65 PathConfig pathConfig
,
66 ServiceEnvironmentConfig serviceEnvironmentConfig
,
68 final Consumer
<Manager
> newManagerListener
,
69 final AccountsStore accountsStore
71 this.pathConfig
= pathConfig
;
72 this.serviceEnvironmentConfig
= serviceEnvironmentConfig
;
73 this.userAgent
= userAgent
;
74 this.newManagerListener
= newManagerListener
;
75 this.accountsStore
= accountsStore
;
77 tempIdentityKey
= KeyUtils
.generateIdentityKeyPair();
78 password
= KeyUtils
.createPassword();
79 final var clientZkOperations
= ClientZkOperations
.create(serviceEnvironmentConfig
.signalServiceConfiguration());
80 final var credentialsProvider
= new DynamicCredentialsProvider(null,
84 SignalServiceAddress
.DEFAULT_DEVICE_ID
);
85 final var pushServiceSocket
= new PushServiceSocket(serviceEnvironmentConfig
.signalServiceConfiguration(),
88 clientZkOperations
.getProfileOperations(),
89 ServiceConfig
.AUTOMATIC_NETWORK_RETRY
);
90 final var provisioningSocket
= new ProvisioningSocket(serviceEnvironmentConfig
.signalServiceConfiguration(),
92 this.provisioningApi
= new ProvisioningApi(pushServiceSocket
, provisioningSocket
, credentialsProvider
);
96 public URI
getDeviceLinkUri() throws TimeoutException
, IOException
{
97 var deviceUuid
= provisioningApi
.getNewDeviceUuid();
99 return new DeviceLinkUrl(deviceUuid
, tempIdentityKey
.getPublicKey().getPublicKey()).createDeviceLinkUri();
103 public String
finishDeviceLink(String deviceName
) throws IOException
, TimeoutException
, UserAlreadyExistsException
{
104 var ret
= provisioningApi
.getNewDeviceRegistration(tempIdentityKey
);
105 var number
= ret
.getNumber();
106 var aci
= ret
.getAci();
107 var pni
= ret
.getPni();
109 logger
.info("Received link information from {}, linking in progress ...", number
);
111 var accountPath
= accountsStore
.getPathByAci(aci
);
112 if (accountPath
== null) {
113 accountPath
= accountsStore
.getPathByNumber(number
);
115 final var accountExists
= accountPath
!= null && SignalAccount
.accountFileExists(pathConfig
.dataPath(),
117 if (accountExists
&& !canRelinkExistingAccount(accountPath
)) {
118 throw new UserAlreadyExistsException(number
, SignalAccount
.getFileName(pathConfig
.dataPath(), accountPath
));
120 if (accountPath
== null) {
121 accountPath
= accountsStore
.addAccount(number
, aci
);
123 accountsStore
.updateAccount(accountPath
, number
, aci
);
126 var encryptedDeviceName
= deviceName
== null
128 : DeviceNameUtil
.encryptDeviceName(deviceName
, ret
.getAciIdentity().getPrivateKey());
129 // Create new account with the synced identity
130 var profileKey
= ret
.getProfileKey() == null ? KeyUtils
.createProfileKey() : ret
.getProfileKey();
132 SignalAccount account
= null;
134 if (!accountExists
) {
135 account
= SignalAccount
.createLinkedAccount(pathConfig
.dataPath(),
137 serviceEnvironmentConfig
.type(),
140 account
= SignalAccount
.load(pathConfig
.dataPath(), accountPath
, true, Settings
.DEFAULT
);
143 account
.setProvisioningData(number
,
148 ret
.getAciIdentity(),
149 ret
.getPniIdentity(),
152 ret
.getAccountEntropyPool(),
153 ret
.getMediaRootBackupKey());
155 account
.getConfigurationStore().setReadReceipts(ret
.isReadReceipts());
157 final var aciPreKeys
= generatePreKeysForType(account
.getAccountData(ServiceIdType
.ACI
));
158 final var pniPreKeys
= generatePreKeysForType(account
.getAccountData(ServiceIdType
.PNI
));
160 logger
.debug("Finishing new device registration");
161 var deviceId
= provisioningApi
.finishNewDeviceRegistration(ret
.getProvisioningCode(),
162 account
.getAccountAttributes(null),
166 account
.finishLinking(deviceId
, aciPreKeys
, pniPreKeys
);
168 ManagerImpl m
= null;
170 m
= new ManagerImpl(account
,
172 new AccountFileUpdaterImpl(accountsStore
, accountPath
),
173 serviceEnvironmentConfig
,
177 logger
.debug("Refreshing pre keys");
180 } catch (Exception e
) {
181 logger
.error("Failed to refresh pre keys.", e
);
184 logger
.debug("Requesting sync data");
186 m
.requestAllSyncData();
187 } catch (Exception e
) {
189 "Failed to request sync messages from linked device, data can be requested again with `sendSyncRequest`.",
193 if (newManagerListener
!= null) {
194 newManagerListener
.accept(m
);
204 if (account
!= null) {
210 private boolean canRelinkExistingAccount(final String accountPath
) throws IOException
{
211 final SignalAccount signalAccount
;
213 signalAccount
= SignalAccount
.load(pathConfig
.dataPath(), accountPath
, false, Settings
.DEFAULT
);
214 } catch (IOException e
) {
215 logger
.debug("Account in use or failed to load.", e
);
217 } catch (OverlappingFileLockException e
) {
218 logger
.debug("Account in use.", e
);
222 try (signalAccount
) {
223 if (signalAccount
.isPrimaryDevice()) {
224 logger
.debug("Account is a primary device.");
227 if (signalAccount
.isRegistered()
228 && signalAccount
.getServiceEnvironment() != null
229 && signalAccount
.getServiceEnvironment() != serviceEnvironmentConfig
.type()) {
230 logger
.debug("Account is registered in another environment: {}.",
231 signalAccount
.getServiceEnvironment());
235 final var m
= new ManagerImpl(signalAccount
,
237 new AccountFileUpdaterImpl(accountsStore
, accountPath
),
238 serviceEnvironmentConfig
,
241 m
.checkAccountState();
242 } catch (AuthorizationFailedException ignored
) {
246 logger
.debug("Account is still successfully linked.");