]> nmode's Git Repositories - signal-cli/blob - lib/src/main/java/org/asamk/signal/manager/ProvisioningManager.java
Return URI instead of String
[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.signal.zkgroup.InvalidInputException;
25 import org.signal.zkgroup.profiles.ProfileKey;
26 import org.slf4j.Logger;
27 import org.slf4j.LoggerFactory;
28 import org.whispersystems.libsignal.IdentityKeyPair;
29 import org.whispersystems.libsignal.InvalidKeyException;
30 import org.whispersystems.libsignal.util.KeyHelper;
31 import org.whispersystems.signalservice.api.SignalServiceAccountManager;
32 import org.whispersystems.signalservice.api.groupsv2.ClientZkOperations;
33 import org.whispersystems.signalservice.api.groupsv2.GroupsV2Operations;
34 import org.whispersystems.signalservice.api.push.SignalServiceAddress;
35 import org.whispersystems.signalservice.api.util.SleepTimer;
36 import org.whispersystems.signalservice.api.util.UptimeSleepTimer;
37 import org.whispersystems.signalservice.internal.util.DynamicCredentialsProvider;
38
39 import java.io.File;
40 import java.io.IOException;
41 import java.net.URI;
42 import java.util.concurrent.TimeoutException;
43
44 public class ProvisioningManager {
45
46 private final static Logger logger = LoggerFactory.getLogger(ProvisioningManager.class);
47
48 private final PathConfig pathConfig;
49 private final ServiceEnvironmentConfig serviceEnvironmentConfig;
50 private final String userAgent;
51
52 private final SignalServiceAccountManager accountManager;
53 private final IdentityKeyPair identityKey;
54 private final int registrationId;
55 private final String password;
56
57 ProvisioningManager(PathConfig pathConfig, ServiceEnvironmentConfig serviceEnvironmentConfig, String userAgent) {
58 this.pathConfig = pathConfig;
59 this.serviceEnvironmentConfig = serviceEnvironmentConfig;
60 this.userAgent = userAgent;
61
62 identityKey = KeyUtils.generateIdentityKeyPair();
63 registrationId = KeyHelper.generateRegistrationId(false);
64 password = KeyUtils.createPassword();
65 final SleepTimer timer = new UptimeSleepTimer();
66 GroupsV2Operations groupsV2Operations;
67 try {
68 groupsV2Operations = new GroupsV2Operations(ClientZkOperations.create(serviceEnvironmentConfig.getSignalServiceConfiguration()));
69 } catch (Throwable ignored) {
70 groupsV2Operations = null;
71 }
72 accountManager = new SignalServiceAccountManager(serviceEnvironmentConfig.getSignalServiceConfiguration(),
73 new DynamicCredentialsProvider(null, null, password, SignalServiceAddress.DEFAULT_DEVICE_ID),
74 userAgent,
75 groupsV2Operations,
76 ServiceConfig.AUTOMATIC_NETWORK_RETRY,
77 timer);
78 }
79
80 public static ProvisioningManager init(
81 File settingsPath, ServiceEnvironment serviceEnvironment, String userAgent
82 ) {
83 var pathConfig = PathConfig.createDefault(settingsPath);
84
85 final var serviceConfiguration = ServiceConfig.getServiceEnvironmentConfig(serviceEnvironment, userAgent);
86
87 return new ProvisioningManager(pathConfig, serviceConfiguration, userAgent);
88 }
89
90 public URI getDeviceLinkUri() throws TimeoutException, IOException {
91 var deviceUuid = accountManager.getNewDeviceUuid();
92
93 return new DeviceLinkInfo(deviceUuid, identityKey.getPublicKey().getPublicKey()).createDeviceLinkUri();
94 }
95
96 public Manager finishDeviceLink(String deviceName) throws IOException, InvalidKeyException, TimeoutException, UserAlreadyExists {
97 var ret = accountManager.finishNewDeviceRegistration(identityKey, false, true, registrationId, deviceName);
98
99 var username = ret.getNumber();
100 // TODO do this check before actually registering
101 if (SignalAccount.userExists(pathConfig.getDataPath(), username)) {
102 throw new UserAlreadyExists(username, SignalAccount.getFileName(pathConfig.getDataPath(), username));
103 }
104
105 // Create new account with the synced identity
106 var profileKeyBytes = ret.getProfileKey();
107 ProfileKey profileKey;
108 if (profileKeyBytes == null) {
109 profileKey = KeyUtils.createProfileKey();
110 } else {
111 try {
112 profileKey = new ProfileKey(profileKeyBytes);
113 } catch (InvalidInputException e) {
114 throw new IOException("Received invalid profileKey", e);
115 }
116 }
117
118 SignalAccount account = null;
119 try {
120 account = SignalAccount.createLinkedAccount(pathConfig.getDataPath(),
121 username,
122 ret.getUuid(),
123 password,
124 ret.getDeviceId(),
125 ret.getIdentity(),
126 registrationId,
127 profileKey);
128 account.save();
129
130 Manager m = null;
131 try {
132 m = new Manager(account, pathConfig, serviceEnvironmentConfig, userAgent);
133
134 try {
135 m.refreshPreKeys();
136 } catch (Exception e) {
137 logger.error("Failed to refresh prekeys.");
138 throw e;
139 }
140
141 try {
142 m.requestSyncGroups();
143 m.requestSyncContacts();
144 m.requestSyncBlocked();
145 m.requestSyncConfiguration();
146 m.requestSyncKeys();
147 } catch (Exception e) {
148 logger.error("Failed to request sync messages from linked device.");
149 throw e;
150 }
151
152 account.save();
153
154 final var result = m;
155 account = null;
156 m = null;
157
158 return result;
159 } finally {
160 if (m != null) {
161 m.close();
162 }
163 }
164 } finally {
165 if (account != null) {
166 account.close();
167 }
168 }
169 }
170 }