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