]> nmode's Git Repositories - signal-cli/blob - src/main/java/org/asamk/signal/manager/RegistrationManager.java
Refactor register and verify
[signal-cli] / src / main / java / org / asamk / signal / manager / RegistrationManager.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.helper.PinHelper;
20 import org.asamk.signal.manager.storage.SignalAccount;
21 import org.asamk.signal.manager.util.KeyUtils;
22 import org.signal.zkgroup.profiles.ProfileKey;
23 import org.whispersystems.libsignal.IdentityKeyPair;
24 import org.whispersystems.libsignal.util.KeyHelper;
25 import org.whispersystems.libsignal.util.guava.Optional;
26 import org.whispersystems.signalservice.api.KbsPinData;
27 import org.whispersystems.signalservice.api.KeyBackupService;
28 import org.whispersystems.signalservice.api.KeyBackupServicePinException;
29 import org.whispersystems.signalservice.api.KeyBackupSystemNoDataException;
30 import org.whispersystems.signalservice.api.SignalServiceAccountManager;
31 import org.whispersystems.signalservice.api.push.SignalServiceAddress;
32 import org.whispersystems.signalservice.api.util.SleepTimer;
33 import org.whispersystems.signalservice.api.util.UptimeSleepTimer;
34 import org.whispersystems.signalservice.api.util.UuidUtil;
35 import org.whispersystems.signalservice.internal.configuration.SignalServiceConfiguration;
36 import org.whispersystems.signalservice.internal.push.LockedException;
37 import org.whispersystems.signalservice.internal.push.VerifyAccountResponse;
38 import org.whispersystems.signalservice.internal.util.DynamicCredentialsProvider;
39
40 import java.io.File;
41 import java.io.IOException;
42 import java.util.Locale;
43
44 public class RegistrationManager implements AutoCloseable {
45
46 private SignalAccount account;
47 private final PathConfig pathConfig;
48 private final SignalServiceConfiguration serviceConfiguration;
49 private final String userAgent;
50
51 private final SignalServiceAccountManager accountManager;
52 private final PinHelper pinHelper;
53
54 public RegistrationManager(
55 SignalAccount account,
56 PathConfig pathConfig,
57 SignalServiceConfiguration serviceConfiguration,
58 String userAgent
59 ) {
60 this.account = account;
61 this.pathConfig = pathConfig;
62 this.serviceConfiguration = serviceConfiguration;
63 this.userAgent = userAgent;
64
65 final SleepTimer timer = new UptimeSleepTimer();
66 this.accountManager = new SignalServiceAccountManager(serviceConfiguration, new DynamicCredentialsProvider(
67 // Using empty UUID, because registering doesn't work otherwise
68 null,
69 account.getUsername(),
70 account.getPassword(),
71 account.getSignalingKey(),
72 SignalServiceAddress.DEFAULT_DEVICE_ID), userAgent, null, timer);
73 final KeyBackupService keyBackupService = ServiceConfig.createKeyBackupService(accountManager);
74 this.pinHelper = new PinHelper(keyBackupService);
75 }
76
77 public static RegistrationManager init(
78 String username, File settingsPath, SignalServiceConfiguration serviceConfiguration, String userAgent
79 ) throws IOException {
80 PathConfig pathConfig = PathConfig.createDefault(settingsPath);
81
82 if (!SignalAccount.userExists(pathConfig.getDataPath(), username)) {
83 IdentityKeyPair identityKey = KeyUtils.generateIdentityKeyPair();
84 int registrationId = KeyHelper.generateRegistrationId(false);
85
86 ProfileKey profileKey = KeyUtils.createProfileKey();
87 SignalAccount account = SignalAccount.create(pathConfig.getDataPath(),
88 username,
89 identityKey,
90 registrationId,
91 profileKey);
92 account.save();
93
94 return new RegistrationManager(account, pathConfig, serviceConfiguration, userAgent);
95 }
96
97 SignalAccount account = SignalAccount.load(pathConfig.getDataPath(), username);
98
99 return new RegistrationManager(account, pathConfig, serviceConfiguration, userAgent);
100 }
101
102 public void register(boolean voiceVerification, String captcha) throws IOException {
103 if (account.getPassword() == null) {
104 account.setPassword(KeyUtils.createPassword());
105 }
106
107 if (voiceVerification) {
108 accountManager.requestVoiceVerificationCode(Locale.getDefault(),
109 Optional.fromNullable(captcha),
110 Optional.absent());
111 } else {
112 accountManager.requestSmsVerificationCode(false, Optional.fromNullable(captcha), Optional.absent());
113 }
114
115 account.setRegistered(false);
116 account.save();
117 }
118
119 public void verifyAccount(
120 String verificationCode, String pin
121 ) throws IOException, KeyBackupSystemNoDataException, KeyBackupServicePinException {
122 verificationCode = verificationCode.replace("-", "");
123 if (account.getSignalingKey() == null) {
124 account.setSignalingKey(KeyUtils.createSignalingKey());
125 }
126 VerifyAccountResponse response;
127 try {
128 response = verifyAccountWithCode(verificationCode, pin, null);
129 account.setPinMasterKey(null);
130 } catch (LockedException e) {
131 if (pin == null) {
132 throw e;
133 }
134
135 KbsPinData registrationLockData = pinHelper.getRegistrationLockData(pin, e);
136 if (registrationLockData == null) {
137 throw e;
138 }
139
140 String registrationLock = registrationLockData.getMasterKey().deriveRegistrationLock();
141 try {
142 response = verifyAccountWithCode(verificationCode, null, registrationLock);
143 } catch (LockedException _e) {
144 throw new AssertionError("KBS Pin appeared to matched but reg lock still failed!");
145 }
146 account.setPinMasterKey(registrationLockData.getMasterKey());
147 }
148
149 // TODO response.isStorageCapable()
150 //accountManager.setGcmId(Optional.of(GoogleCloudMessaging.getInstance(this).register(REGISTRATION_ID)));
151
152 account.setDeviceId(SignalServiceAddress.DEFAULT_DEVICE_ID);
153 account.setMultiDevice(false);
154 account.setRegistered(true);
155 account.setUuid(UuidUtil.parseOrNull(response.getUuid()));
156 account.setRegistrationLockPin(pin);
157 account.getSignalProtocolStore()
158 .saveIdentity(account.getSelfAddress(),
159 account.getSignalProtocolStore().getIdentityKeyPair().getPublicKey(),
160 TrustLevel.TRUSTED_VERIFIED);
161
162 try (Manager m = new Manager(account, pathConfig, serviceConfiguration, userAgent)) {
163
164 m.refreshPreKeys();
165
166 m.close(false);
167 }
168
169 account.save();
170 }
171
172 private VerifyAccountResponse verifyAccountWithCode(
173 final String verificationCode, final String legacyPin, final String registrationLock
174 ) throws IOException {
175 return accountManager.verifyAccountWithCode(verificationCode,
176 account.getSignalingKey(),
177 account.getSignalProtocolStore().getLocalRegistrationId(),
178 true,
179 legacyPin,
180 registrationLock,
181 account.getSelfUnidentifiedAccessKey(),
182 account.isUnrestrictedUnidentifiedAccess(),
183 ServiceConfig.capabilities,
184 account.isDiscoverableByPhoneNumber());
185 }
186
187 @Override
188 public void close() throws Exception {
189 if (account != null) {
190 account.close();
191 account = null;
192 }
193 }
194 }