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