]> nmode's Git Repositories - signal-cli/blob - lib/src/main/java/org/asamk/signal/manager/ProvisioningManagerImpl.java
Add SignalAccountFiles as a central entry point
[signal-cli] / lib / src / main / java / org / asamk / signal / manager / ProvisioningManagerImpl.java
1 /*
2 Copyright (C) 2015-2022 AsamK and contributors
3
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.
8
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.
13
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/>.
16 */
17 package org.asamk.signal.manager;
18
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.slf4j.Logger;
27 import org.slf4j.LoggerFactory;
28 import org.whispersystems.libsignal.IdentityKeyPair;
29 import org.whispersystems.libsignal.util.KeyHelper;
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.util.DynamicCredentialsProvider;
37
38 import java.io.IOException;
39 import java.net.URI;
40 import java.util.concurrent.TimeoutException;
41 import java.util.function.Consumer;
42
43 class ProvisioningManagerImpl implements ProvisioningManager {
44
45 private final static Logger logger = LoggerFactory.getLogger(ProvisioningManagerImpl.class);
46
47 private final PathConfig pathConfig;
48 private final ServiceEnvironmentConfig serviceEnvironmentConfig;
49 private final String userAgent;
50 private final Consumer<Manager> newManagerListener;
51 private final AccountsStore accountsStore;
52
53 private final SignalServiceAccountManager accountManager;
54 private final IdentityKeyPair tempIdentityKey;
55 private final int registrationId;
56 private final String password;
57
58 ProvisioningManagerImpl(
59 PathConfig pathConfig,
60 ServiceEnvironmentConfig serviceEnvironmentConfig,
61 String userAgent,
62 final Consumer<Manager> newManagerListener,
63 final AccountsStore accountsStore
64 ) {
65 this.pathConfig = pathConfig;
66 this.serviceEnvironmentConfig = serviceEnvironmentConfig;
67 this.userAgent = userAgent;
68 this.newManagerListener = newManagerListener;
69 this.accountsStore = accountsStore;
70
71 tempIdentityKey = KeyUtils.generateIdentityKeyPair();
72 registrationId = KeyHelper.generateRegistrationId(false);
73 password = KeyUtils.createPassword();
74 GroupsV2Operations groupsV2Operations;
75 try {
76 groupsV2Operations = new GroupsV2Operations(ClientZkOperations.create(serviceEnvironmentConfig.getSignalServiceConfiguration()));
77 } catch (Throwable ignored) {
78 groupsV2Operations = null;
79 }
80 accountManager = new SignalServiceAccountManager(serviceEnvironmentConfig.getSignalServiceConfiguration(),
81 new DynamicCredentialsProvider(null, null, password, SignalServiceAddress.DEFAULT_DEVICE_ID),
82 userAgent,
83 groupsV2Operations,
84 ServiceConfig.AUTOMATIC_NETWORK_RETRY);
85 }
86
87 @Override
88 public URI getDeviceLinkUri() throws TimeoutException, IOException {
89 var deviceUuid = accountManager.getNewDeviceUuid();
90
91 return new DeviceLinkInfo(deviceUuid, tempIdentityKey.getPublicKey().getPublicKey()).createDeviceLinkUri();
92 }
93
94 @Override
95 public String finishDeviceLink(String deviceName) throws IOException, TimeoutException, UserAlreadyExistsException {
96 var ret = accountManager.getNewDeviceRegistration(tempIdentityKey);
97 var number = ret.getNumber();
98 var aci = ret.getAci();
99
100 logger.info("Received link information from {}, linking in progress ...", number);
101
102 var accountPath = accountsStore.getPathByAci(aci);
103 if (accountPath == null) {
104 accountPath = accountsStore.getPathByNumber(number);
105 }
106 if (accountPath != null
107 && SignalAccount.accountFileExists(pathConfig.dataPath(), accountPath)
108 && !canRelinkExistingAccount(accountPath)) {
109 throw new UserAlreadyExistsException(number, SignalAccount.getFileName(pathConfig.dataPath(), accountPath));
110 }
111 if (accountPath == null) {
112 accountPath = accountsStore.addAccount(number, aci);
113 } else {
114 accountsStore.updateAccount(accountPath, number, aci);
115 }
116
117 var encryptedDeviceName = deviceName == null
118 ? null
119 : DeviceNameUtil.encryptDeviceName(deviceName, ret.getIdentity().getPrivateKey());
120
121 logger.debug("Finishing new device registration");
122 var deviceId = accountManager.finishNewDeviceRegistration(ret.getProvisioningCode(),
123 false,
124 true,
125 registrationId,
126 encryptedDeviceName);
127
128 // Create new account with the synced identity
129 var profileKey = ret.getProfileKey() == null ? KeyUtils.createProfileKey() : ret.getProfileKey();
130
131 SignalAccount account = null;
132 try {
133 account = SignalAccount.createOrUpdateLinkedAccount(pathConfig.dataPath(),
134 accountPath,
135 number,
136 aci,
137 password,
138 encryptedDeviceName,
139 deviceId,
140 ret.getIdentity(),
141 registrationId,
142 profileKey,
143 TrustNewIdentity.ON_FIRST_USE);
144
145 ManagerImpl m = null;
146 try {
147 final var accountPathFinal = accountPath;
148 m = new ManagerImpl(account,
149 pathConfig,
150 (newNumber, newAci) -> accountsStore.updateAccount(accountPathFinal, newNumber, newAci),
151 serviceEnvironmentConfig,
152 userAgent);
153 account = null;
154
155 logger.debug("Refreshing pre keys");
156 try {
157 m.refreshPreKeys();
158 } catch (Exception e) {
159 logger.error("Failed to refresh pre keys.");
160 }
161
162 logger.debug("Requesting sync data");
163 try {
164 m.requestAllSyncData();
165 } catch (Exception e) {
166 logger.error(
167 "Failed to request sync messages from linked device, data can be requested again with `sendSyncRequest`.");
168 }
169
170 if (newManagerListener != null) {
171 newManagerListener.accept(m);
172 m = null;
173 }
174 return number;
175 } finally {
176 if (m != null) {
177 m.close();
178 }
179 }
180 } finally {
181 if (account != null) {
182 account.close();
183 }
184 }
185 }
186
187 private boolean canRelinkExistingAccount(final String accountPath) throws IOException {
188 final SignalAccount signalAccount;
189 try {
190 signalAccount = SignalAccount.load(pathConfig.dataPath(),
191 accountPath,
192 false,
193 TrustNewIdentity.ON_FIRST_USE);
194 } catch (IOException e) {
195 logger.debug("Account in use or failed to load.", e);
196 return false;
197 }
198
199 try (signalAccount) {
200 if (signalAccount.isMasterDevice()) {
201 logger.debug("Account is a master device.");
202 return false;
203 }
204
205 final var m = new ManagerImpl(signalAccount,
206 pathConfig,
207 (newNumber, newAci) -> accountsStore.updateAccount(accountPath, newNumber, newAci),
208 serviceEnvironmentConfig,
209 userAgent);
210 try (m) {
211 m.checkAccountState();
212 } catch (AuthorizationFailedException ignored) {
213 return true;
214 }
215
216 logger.debug("Account is still successfully linked.");
217 return false;
218 }
219 }
220 }