]> nmode's Git Repositories - signal-cli/blob - src/main/java/cli/Manager.java
acaa1a0c76492560c7866f568f0abb56c59458b3
[signal-cli] / src / main / java / cli / Manager.java
1 /**
2 * Copyright (C) 2015 AsamK
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 cli;
18
19 import org.apache.commons.io.IOUtils;
20 import org.json.JSONObject;
21 import org.whispersystems.libaxolotl.IdentityKeyPair;
22 import org.whispersystems.libaxolotl.InvalidKeyException;
23 import org.whispersystems.libaxolotl.InvalidVersionException;
24 import org.whispersystems.libaxolotl.state.PreKeyRecord;
25 import org.whispersystems.libaxolotl.state.SignedPreKeyRecord;
26 import org.whispersystems.libaxolotl.util.KeyHelper;
27 import org.whispersystems.libaxolotl.util.Medium;
28 import org.whispersystems.libaxolotl.util.guava.Optional;
29 import org.whispersystems.textsecure.api.TextSecureAccountManager;
30 import org.whispersystems.textsecure.api.TextSecureMessagePipe;
31 import org.whispersystems.textsecure.api.TextSecureMessageReceiver;
32 import org.whispersystems.textsecure.api.TextSecureMessageSender;
33 import org.whispersystems.textsecure.api.crypto.TextSecureCipher;
34 import org.whispersystems.textsecure.api.messages.TextSecureContent;
35 import org.whispersystems.textsecure.api.messages.TextSecureEnvelope;
36 import org.whispersystems.textsecure.api.push.TextSecureAddress;
37 import org.whispersystems.textsecure.api.push.TrustStore;
38 import org.whispersystems.textsecure.api.util.InvalidNumberException;
39 import org.whispersystems.textsecure.api.util.PhoneNumberFormatter;
40
41 import java.io.*;
42 import java.util.List;
43 import java.util.concurrent.TimeUnit;
44 import java.util.concurrent.TimeoutException;
45
46 public class Manager {
47 private final static String URL = "https://textsecure-service.whispersystems.org";
48 private final static TrustStore TRUST_STORE = new WhisperTrustStore();
49
50 private final static String settingsPath = System.getProperty("user.home") + "/.config/textsecure";
51
52 private String username;
53 private String password;
54 private String signalingKey;
55
56 private boolean registered = false;
57
58 private JsonAxolotlStore axolotlStore;
59 TextSecureAccountManager accountManager;
60
61 public Manager(String username) {
62 this.username = username;
63 }
64
65 private String getFileName() {
66 String path = settingsPath + "/data";
67 new File(path).mkdirs();
68 return path + "/" + username;
69 }
70
71 public boolean userExists() {
72 File f = new File(getFileName());
73 if (!f.exists() || f.isDirectory()) {
74 return false;
75 }
76 return true;
77 }
78
79 public boolean userHasKeys() {
80 return axolotlStore != null;
81 }
82
83 public void load() throws IOException, InvalidKeyException {
84 JSONObject in = new JSONObject(IOUtils.toString(new FileInputStream(getFileName())));
85 username = in.getString("username");
86 password = in.getString("password");
87 if (in.has("signalingKey")) {
88 signalingKey = in.getString("signalingKey");
89 }
90 axolotlStore = new JsonAxolotlStore(in.getJSONObject("axolotlStore"));
91 registered = in.getBoolean("registered");
92 accountManager = new TextSecureAccountManager(URL, TRUST_STORE, username, password);
93 }
94
95 public void save() {
96 String out = new JSONObject().put("username", username)
97 .put("password", password)
98 .put("signalingKey", signalingKey)
99 .put("axolotlStore", axolotlStore.getJson())
100 .put("registered", registered).toString();
101 try {
102 OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(getFileName()));
103 writer.write(out);
104 writer.flush();
105 writer.close();
106 } catch (Exception e) {
107 System.out.println("Saving file error: " + e.getMessage());
108 return;
109 }
110 }
111
112 public void createNewIdentity() {
113 IdentityKeyPair identityKey = KeyHelper.generateIdentityKeyPair();
114 int registrationId = KeyHelper.generateRegistrationId(false);
115 axolotlStore = new JsonAxolotlStore(identityKey, registrationId);
116 registered = false;
117 }
118
119 public boolean isRegistered() {
120 return registered;
121 }
122
123 public void register(boolean voiceVerication) throws IOException {
124 password = Util.getSecret(18);
125
126 accountManager = new TextSecureAccountManager(URL, TRUST_STORE, username, password);
127
128 if (voiceVerication)
129 accountManager.requestVoiceVerificationCode();
130 else
131 accountManager.requestSmsVerificationCode();
132
133 registered = false;
134 }
135
136 public void verifyAccount(String verificationCode) throws IOException {
137 verificationCode = verificationCode.replace("-", "");
138 signalingKey = Util.getSecret(52);
139 accountManager.verifyAccount(verificationCode, signalingKey, false, axolotlStore.getLocalRegistrationId());
140
141 //accountManager.setGcmId(Optional.of(GoogleCloudMessaging.getInstance(this).register(REGISTRATION_ID)));
142 registered = true;
143
144 int start = 0;
145 List<PreKeyRecord> oneTimePreKeys = KeyHelper.generatePreKeys(start, 100);
146 for (int i = start; i < oneTimePreKeys.size(); i++) {
147 axolotlStore.storePreKey(i, oneTimePreKeys.get(i));
148 }
149
150 PreKeyRecord lastResortKey = KeyHelper.generateLastResortPreKey();
151 axolotlStore.storePreKey(Medium.MAX_VALUE, lastResortKey);
152
153 int signedPreKeyId = 0;
154 SignedPreKeyRecord signedPreKeyRecord;
155 try {
156 signedPreKeyRecord = KeyHelper.generateSignedPreKey(axolotlStore.getIdentityKeyPair(), signedPreKeyId);
157 axolotlStore.storeSignedPreKey(signedPreKeyId, signedPreKeyRecord);
158 } catch (InvalidKeyException e) {
159 // Should really not happen
160 System.out.println("invalid key");
161 return;
162 }
163 accountManager.setPreKeys(axolotlStore.getIdentityKeyPair().getPublicKey(), lastResortKey, signedPreKeyRecord, oneTimePreKeys);
164 }
165
166 public TextSecureMessageSender getMessageSender() {
167 return new TextSecureMessageSender(URL, TRUST_STORE, username, password,
168 axolotlStore, Optional.<TextSecureMessageSender.EventListener>absent());
169 }
170
171 public TextSecureContent decryptMessage(TextSecureEnvelope envelope) {
172 TextSecureCipher cipher = new TextSecureCipher(new TextSecureAddress(username), axolotlStore);
173 try {
174 return cipher.decrypt(envelope);
175 } catch (Exception e) {
176 // TODO handle all exceptions
177 e.printStackTrace();
178 return null;
179 }
180 }
181
182 public void handleEndSession(String source) {
183 axolotlStore.deleteAllSessions(source);
184 }
185
186 public interface ReceiveMessageHandler {
187 void handleMessage(TextSecureEnvelope envelope);
188 }
189
190 public void receiveMessages(int timeoutSeconds, boolean returnOnTimeout, ReceiveMessageHandler handler) throws IOException {
191 TextSecureMessageReceiver messageReceiver = new TextSecureMessageReceiver(URL, TRUST_STORE, username, password, signalingKey);
192 TextSecureMessagePipe messagePipe = null;
193
194 try {
195 messagePipe = messageReceiver.createMessagePipe();
196
197 while (true) {
198 TextSecureEnvelope envelope;
199 try {
200 envelope = messagePipe.read(timeoutSeconds, TimeUnit.SECONDS);
201 handler.handleMessage(envelope);
202 } catch (TimeoutException e) {
203 if (returnOnTimeout)
204 return;
205 } catch (InvalidVersionException e) {
206 System.out.println("Ignoring error: " + e.getMessage());
207 }
208 save();
209 }
210 } finally {
211 if (messagePipe != null)
212 messagePipe.shutdown();
213 }
214 }
215
216 public String canonicalizeNumber(String number) throws InvalidNumberException {
217 String localNumber = username;
218 return PhoneNumberFormatter.formatNumber(number, localNumber);
219 }
220
221 protected TextSecureAddress getPushAddress(String number) throws InvalidNumberException {
222 String e164number = canonicalizeNumber(number);
223 return new TextSecureAddress(e164number);
224 }
225 }