]> nmode's Git Repositories - signal-cli/blob - src/main/java/org/asamk/signal/manager/Utils.java
4012674f1c76219296fa7e71f7433351c860dd90
[signal-cli] / src / main / java / org / asamk / signal / manager / Utils.java
1 package org.asamk.signal.manager;
2
3 import org.asamk.signal.AttachmentInvalidException;
4 import org.signal.libsignal.metadata.certificate.CertificateValidator;
5 import org.whispersystems.libsignal.IdentityKey;
6 import org.whispersystems.libsignal.InvalidKeyException;
7 import org.whispersystems.libsignal.ecc.Curve;
8 import org.whispersystems.libsignal.ecc.ECPublicKey;
9 import org.whispersystems.libsignal.fingerprint.Fingerprint;
10 import org.whispersystems.libsignal.fingerprint.NumericFingerprintGenerator;
11 import org.whispersystems.libsignal.util.guava.Optional;
12 import org.whispersystems.signalservice.api.messages.SignalServiceAttachment;
13 import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentStream;
14 import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope;
15 import org.whispersystems.signalservice.api.push.SignalServiceAddress;
16 import org.whispersystems.signalservice.api.util.InvalidNumberException;
17 import org.whispersystems.signalservice.api.util.PhoneNumberFormatter;
18 import org.whispersystems.signalservice.internal.util.Base64;
19
20 import java.io.*;
21 import java.net.URI;
22 import java.net.URLDecoder;
23 import java.net.URLEncoder;
24 import java.nio.file.Files;
25 import java.util.*;
26
27 import static org.whispersystems.signalservice.internal.util.Util.isEmpty;
28
29 class Utils {
30
31 static List<SignalServiceAttachment> getSignalServiceAttachments(List<String> attachments) throws AttachmentInvalidException {
32 List<SignalServiceAttachment> SignalServiceAttachments = null;
33 if (attachments != null) {
34 SignalServiceAttachments = new ArrayList<>(attachments.size());
35 for (String attachment : attachments) {
36 try {
37 SignalServiceAttachments.add(createAttachment(new File(attachment)));
38 } catch (IOException e) {
39 throw new AttachmentInvalidException(attachment, e);
40 }
41 }
42 }
43 return SignalServiceAttachments;
44 }
45
46 static SignalServiceAttachmentStream createAttachment(File attachmentFile) throws IOException {
47 InputStream attachmentStream = new FileInputStream(attachmentFile);
48 final long attachmentSize = attachmentFile.length();
49 String mime = Files.probeContentType(attachmentFile.toPath());
50 if (mime == null) {
51 mime = "application/octet-stream";
52 }
53 // TODO mabybe add a parameter to set the voiceNote, preview, width, height and caption option
54 Optional<byte[]> preview = Optional.absent();
55 Optional<String> caption = Optional.absent();
56 return new SignalServiceAttachmentStream(attachmentStream, mime, attachmentSize, Optional.of(attachmentFile.getName()), false, preview, 0, 0, caption, null);
57 }
58
59 static CertificateValidator getCertificateValidator() {
60 try {
61 ECPublicKey unidentifiedSenderTrustRoot = Curve.decodePoint(Base64.decode(BaseConfig.UNIDENTIFIED_SENDER_TRUST_ROOT), 0);
62 return new CertificateValidator(unidentifiedSenderTrustRoot);
63 } catch (InvalidKeyException | IOException e) {
64 throw new AssertionError(e);
65 }
66 }
67
68 private static Map<String, String> getQueryMap(String query) {
69 String[] params = query.split("&");
70 Map<String, String> map = new HashMap<>();
71 for (String param : params) {
72 String name = null;
73 final String[] paramParts = param.split("=");
74 try {
75 name = URLDecoder.decode(paramParts[0], "utf-8");
76 } catch (UnsupportedEncodingException e) {
77 // Impossible
78 }
79 String value = null;
80 try {
81 value = URLDecoder.decode(paramParts[1], "utf-8");
82 } catch (UnsupportedEncodingException e) {
83 // Impossible
84 }
85 map.put(name, value);
86 }
87 return map;
88 }
89
90 static String createDeviceLinkUri(DeviceLinkInfo info) {
91 try {
92 return "tsdevice:/?uuid=" + URLEncoder.encode(info.deviceIdentifier, "utf-8") + "&pub_key=" + URLEncoder.encode(Base64.encodeBytesWithoutPadding(info.deviceKey.serialize()), "utf-8");
93 } catch (UnsupportedEncodingException e) {
94 // Shouldn't happen
95 return null;
96 }
97 }
98
99 static DeviceLinkInfo parseDeviceLinkUri(URI linkUri) throws IOException, InvalidKeyException {
100 Map<String, String> query = getQueryMap(linkUri.getRawQuery());
101 String deviceIdentifier = query.get("uuid");
102 String publicKeyEncoded = query.get("pub_key");
103
104 if (isEmpty(deviceIdentifier) || isEmpty(publicKeyEncoded)) {
105 throw new RuntimeException("Invalid device link uri");
106 }
107
108 ECPublicKey deviceKey = Curve.decodePoint(Base64.decode(publicKeyEncoded), 0);
109
110 return new DeviceLinkInfo(deviceIdentifier, deviceKey);
111 }
112
113 static Set<SignalServiceAddress> getSignalServiceAddresses(Collection<String> recipients, String localNumber) {
114 Set<SignalServiceAddress> recipientsTS = new HashSet<>(recipients.size());
115 for (String recipient : recipients) {
116 try {
117 recipientsTS.add(getPushAddress(recipient, localNumber));
118 } catch (InvalidNumberException e) {
119 System.err.println("Failed to add recipient \"" + recipient + "\": " + e.getMessage());
120 System.err.println("Aborting sending.");
121 return null;
122 }
123 }
124 return recipientsTS;
125 }
126
127 static String canonicalizeNumber(String number, String localNumber) throws InvalidNumberException {
128 return PhoneNumberFormatter.formatNumber(number, localNumber);
129 }
130
131 private static SignalServiceAddress getPushAddress(String number, String localNumber) throws InvalidNumberException {
132 String e164number = canonicalizeNumber(number, localNumber);
133 return new SignalServiceAddress(e164number);
134 }
135
136 static SignalServiceEnvelope loadEnvelope(File file) throws IOException {
137 try (FileInputStream f = new FileInputStream(file)) {
138 DataInputStream in = new DataInputStream(f);
139 int version = in.readInt();
140 if (version > 2) {
141 return null;
142 }
143 int type = in.readInt();
144 String source = in.readUTF();
145 int sourceDevice = in.readInt();
146 if (version == 1) {
147 // read legacy relay field
148 in.readUTF();
149 }
150 long timestamp = in.readLong();
151 byte[] content = null;
152 int contentLen = in.readInt();
153 if (contentLen > 0) {
154 content = new byte[contentLen];
155 in.readFully(content);
156 }
157 byte[] legacyMessage = null;
158 int legacyMessageLen = in.readInt();
159 if (legacyMessageLen > 0) {
160 legacyMessage = new byte[legacyMessageLen];
161 in.readFully(legacyMessage);
162 }
163 long serverTimestamp = 0;
164 String uuid = null;
165 if (version == 2) {
166 serverTimestamp = in.readLong();
167 uuid = in.readUTF();
168 if ("".equals(uuid)) {
169 uuid = null;
170 }
171 }
172 return new SignalServiceEnvelope(type, source, sourceDevice, timestamp, legacyMessage, content, serverTimestamp, uuid);
173 }
174 }
175
176 static void storeEnvelope(SignalServiceEnvelope envelope, File file) throws IOException {
177 try (FileOutputStream f = new FileOutputStream(file)) {
178 try (DataOutputStream out = new DataOutputStream(f)) {
179 out.writeInt(2); // version
180 out.writeInt(envelope.getType());
181 out.writeUTF(envelope.getSource());
182 out.writeInt(envelope.getSourceDevice());
183 out.writeLong(envelope.getTimestamp());
184 if (envelope.hasContent()) {
185 out.writeInt(envelope.getContent().length);
186 out.write(envelope.getContent());
187 } else {
188 out.writeInt(0);
189 }
190 if (envelope.hasLegacyMessage()) {
191 out.writeInt(envelope.getLegacyMessage().length);
192 out.write(envelope.getLegacyMessage());
193 } else {
194 out.writeInt(0);
195 }
196 out.writeLong(envelope.getServerTimestamp());
197 String uuid = envelope.getUuid();
198 out.writeUTF(uuid == null ? "" : uuid);
199 }
200 }
201 }
202
203 static File retrieveAttachment(SignalServiceAttachmentStream stream, File outputFile) throws IOException {
204 InputStream input = stream.getInputStream();
205
206 try (OutputStream output = new FileOutputStream(outputFile)) {
207 byte[] buffer = new byte[4096];
208 int read;
209
210 while ((read = input.read(buffer)) != -1) {
211 output.write(buffer, 0, read);
212 }
213 } catch (FileNotFoundException e) {
214 e.printStackTrace();
215 return null;
216 }
217 return outputFile;
218 }
219
220 static String computeSafetyNumber(String ownUsername, IdentityKey ownIdentityKey, String theirUsername, IdentityKey theirIdentityKey) {
221 Fingerprint fingerprint = new NumericFingerprintGenerator(5200).createFor(ownUsername, ownIdentityKey, theirUsername, theirIdentityKey);
222 return fingerprint.getDisplayableFingerprint().getDisplayText();
223 }
224
225 static class DeviceLinkInfo {
226
227 final String deviceIdentifier;
228 final ECPublicKey deviceKey;
229
230 DeviceLinkInfo(final String deviceIdentifier, final ECPublicKey deviceKey) {
231 this.deviceIdentifier = deviceIdentifier;
232 this.deviceKey = deviceKey;
233 }
234 }
235 }