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