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
;
19 import org
.asamk
.signal
.manager
.api
.UserAlreadyExistsException
;
20 import org
.asamk
.signal
.manager
.config
.ServiceConfig
;
21 import org
.asamk
.signal
.manager
.config
.ServiceEnvironmentConfig
;
22 import org
.asamk
.signal
.manager
.storage
.SignalAccount
;
23 import org
.asamk
.signal
.manager
.storage
.accounts
.AccountsStore
;
24 import org
.asamk
.signal
.manager
.storage
.identities
.TrustNewIdentity
;
25 import org
.asamk
.signal
.manager
.util
.KeyUtils
;
26 import org
.signal
.libsignal
.protocol
.IdentityKeyPair
;
27 import org
.signal
.libsignal
.protocol
.util
.KeyHelper
;
28 import org
.slf4j
.Logger
;
29 import org
.slf4j
.LoggerFactory
;
30 import org
.whispersystems
.signalservice
.api
.SignalServiceAccountManager
;
31 import org
.whispersystems
.signalservice
.api
.groupsv2
.ClientZkOperations
;
32 import org
.whispersystems
.signalservice
.api
.groupsv2
.GroupsV2Operations
;
33 import org
.whispersystems
.signalservice
.api
.push
.SignalServiceAddress
;
34 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.AuthorizationFailedException
;
35 import org
.whispersystems
.signalservice
.api
.util
.DeviceNameUtil
;
36 import org
.whispersystems
.signalservice
.internal
.push
.ConfirmCodeMessage
;
37 import org
.whispersystems
.signalservice
.internal
.util
.DynamicCredentialsProvider
;
39 import java
.io
.IOException
;
41 import java
.nio
.channels
.OverlappingFileLockException
;
42 import java
.util
.concurrent
.TimeoutException
;
43 import java
.util
.function
.Consumer
;
45 class ProvisioningManagerImpl
implements ProvisioningManager
{
47 private final static Logger logger
= LoggerFactory
.getLogger(ProvisioningManagerImpl
.class);
49 private final PathConfig pathConfig
;
50 private final ServiceEnvironmentConfig serviceEnvironmentConfig
;
51 private final String userAgent
;
52 private final Consumer
<Manager
> newManagerListener
;
53 private final AccountsStore accountsStore
;
55 private final SignalServiceAccountManager accountManager
;
56 private final IdentityKeyPair tempIdentityKey
;
57 private final int registrationId
;
58 private final int pniRegistrationId
;
59 private final String password
;
61 ProvisioningManagerImpl(
62 PathConfig pathConfig
,
63 ServiceEnvironmentConfig serviceEnvironmentConfig
,
65 final Consumer
<Manager
> newManagerListener
,
66 final AccountsStore accountsStore
68 this.pathConfig
= pathConfig
;
69 this.serviceEnvironmentConfig
= serviceEnvironmentConfig
;
70 this.userAgent
= userAgent
;
71 this.newManagerListener
= newManagerListener
;
72 this.accountsStore
= accountsStore
;
74 tempIdentityKey
= KeyUtils
.generateIdentityKeyPair();
75 registrationId
= KeyHelper
.generateRegistrationId(false);
76 pniRegistrationId
= KeyHelper
.generateRegistrationId(false);
77 password
= KeyUtils
.createPassword();
78 GroupsV2Operations groupsV2Operations
;
80 groupsV2Operations
= new GroupsV2Operations(ClientZkOperations
.create(serviceEnvironmentConfig
.getSignalServiceConfiguration()),
81 ServiceConfig
.GROUP_MAX_SIZE
);
82 } catch (Throwable ignored
) {
83 groupsV2Operations
= null;
85 accountManager
= new SignalServiceAccountManager(serviceEnvironmentConfig
.getSignalServiceConfiguration(),
86 new DynamicCredentialsProvider(null, null, null, password
, SignalServiceAddress
.DEFAULT_DEVICE_ID
),
89 ServiceConfig
.AUTOMATIC_NETWORK_RETRY
);
93 public URI
getDeviceLinkUri() throws TimeoutException
, IOException
{
94 var deviceUuid
= accountManager
.getNewDeviceUuid();
96 return new DeviceLinkInfo(deviceUuid
, tempIdentityKey
.getPublicKey().getPublicKey()).createDeviceLinkUri();
100 public String
finishDeviceLink(String deviceName
) throws IOException
, TimeoutException
, UserAlreadyExistsException
{
101 var ret
= accountManager
.getNewDeviceRegistration(tempIdentityKey
);
102 var number
= ret
.getNumber();
103 var aci
= ret
.getAci();
104 var pni
= ret
.getPni();
106 logger
.info("Received link information from {}, linking in progress ...", number
);
108 var accountPath
= accountsStore
.getPathByAci(aci
);
109 if (accountPath
== null) {
110 accountPath
= accountsStore
.getPathByNumber(number
);
112 if (accountPath
!= null
113 && SignalAccount
.accountFileExists(pathConfig
.dataPath(), accountPath
)
114 && !canRelinkExistingAccount(accountPath
)) {
115 throw new UserAlreadyExistsException(number
, SignalAccount
.getFileName(pathConfig
.dataPath(), accountPath
));
117 if (accountPath
== null) {
118 accountPath
= accountsStore
.addAccount(number
, aci
);
120 accountsStore
.updateAccount(accountPath
, number
, aci
);
123 var encryptedDeviceName
= deviceName
== null
125 : DeviceNameUtil
.encryptDeviceName(deviceName
, ret
.getAciIdentity().getPrivateKey());
127 logger
.debug("Finishing new device registration");
128 var deviceId
= accountManager
.finishNewDeviceRegistration(ret
.getProvisioningCode(),
129 new ConfirmCodeMessage(false, true, registrationId
, pniRegistrationId
, encryptedDeviceName
, null));
131 // Create new account with the synced identity
132 var profileKey
= ret
.getProfileKey() == null ? KeyUtils
.createProfileKey() : ret
.getProfileKey();
134 SignalAccount account
= null;
136 account
= SignalAccount
.createOrUpdateLinkedAccount(pathConfig
.dataPath(),
139 serviceEnvironmentConfig
.getType(),
145 ret
.getAciIdentity(),
146 ret
.getPniIdentity(),
150 TrustNewIdentity
.ON_FIRST_USE
);
152 ManagerImpl m
= null;
154 m
= new ManagerImpl(account
,
156 new AccountFileUpdaterImpl(accountsStore
, accountPath
),
157 serviceEnvironmentConfig
,
161 logger
.debug("Refreshing pre keys");
164 } catch (Exception e
) {
165 logger
.error("Failed to refresh pre keys.", e
);
168 logger
.debug("Requesting sync data");
170 m
.requestAllSyncData();
171 } catch (Exception e
) {
173 "Failed to request sync messages from linked device, data can be requested again with `sendSyncRequest`.",
177 if (newManagerListener
!= null) {
178 newManagerListener
.accept(m
);
188 if (account
!= null) {
194 private boolean canRelinkExistingAccount(final String accountPath
) throws IOException
{
195 final SignalAccount signalAccount
;
197 signalAccount
= SignalAccount
.load(pathConfig
.dataPath(),
200 TrustNewIdentity
.ON_FIRST_USE
);
201 } catch (IOException e
) {
202 logger
.debug("Account in use or failed to load.", e
);
204 } catch (OverlappingFileLockException e
) {
205 logger
.debug("Account in use.", e
);
209 try (signalAccount
) {
210 if (signalAccount
.isPrimaryDevice()) {
211 logger
.debug("Account is a primary device.");
214 if (signalAccount
.isRegistered()
215 && signalAccount
.getServiceEnvironment() != null
216 && signalAccount
.getServiceEnvironment() != serviceEnvironmentConfig
.getType()) {
217 logger
.debug("Account is registered in another environment: {}.",
218 signalAccount
.getServiceEnvironment());
222 final var m
= new ManagerImpl(signalAccount
,
224 new AccountFileUpdaterImpl(accountsStore
, accountPath
),
225 serviceEnvironmentConfig
,
228 m
.checkAccountState();
229 } catch (AuthorizationFailedException ignored
) {
233 logger
.debug("Account is still successfully linked.");