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