]> nmode's Git Repositories - signal-cli/blob - src/main/java/org/asamk/signal/manager/Utils.java
Update dependencies
[signal-cli] / src / main / java / org / asamk / signal / manager / Utils.java
1 package org.asamk.signal.manager;
2
3 import org.signal.libsignal.metadata.certificate.CertificateValidator;
4 import org.whispersystems.libsignal.IdentityKey;
5 import org.whispersystems.libsignal.InvalidKeyException;
6 import org.whispersystems.libsignal.ecc.Curve;
7 import org.whispersystems.libsignal.ecc.ECPublicKey;
8 import org.whispersystems.libsignal.fingerprint.Fingerprint;
9 import org.whispersystems.libsignal.fingerprint.NumericFingerprintGenerator;
10 import org.whispersystems.libsignal.util.guava.Optional;
11 import org.whispersystems.signalservice.api.messages.SignalServiceAttachment;
12 import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentStream;
13 import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope;
14 import org.whispersystems.signalservice.api.push.SignalServiceAddress;
15 import org.whispersystems.signalservice.api.util.StreamDetails;
16 import org.whispersystems.signalservice.api.util.UuidUtil;
17 import org.whispersystems.signalservice.internal.push.http.ResumableUploadSpec;
18 import org.whispersystems.util.Base64;
19
20 import java.io.BufferedInputStream;
21 import java.io.DataInputStream;
22 import java.io.DataOutputStream;
23 import java.io.File;
24 import java.io.FileInputStream;
25 import java.io.FileNotFoundException;
26 import java.io.FileOutputStream;
27 import java.io.IOException;
28 import java.io.InputStream;
29 import java.io.OutputStream;
30 import java.io.UnsupportedEncodingException;
31 import java.net.URI;
32 import java.net.URLConnection;
33 import java.net.URLDecoder;
34 import java.net.URLEncoder;
35 import java.nio.file.Files;
36 import java.util.ArrayList;
37 import java.util.HashMap;
38 import java.util.List;
39 import java.util.Map;
40 import java.util.UUID;
41
42 import static org.whispersystems.signalservice.internal.util.Util.isEmpty;
43
44 class Utils {
45
46 static List<SignalServiceAttachment> getSignalServiceAttachments(List<String> attachments) throws AttachmentInvalidException {
47 List<SignalServiceAttachment> signalServiceAttachments = null;
48 if (attachments != null) {
49 signalServiceAttachments = new ArrayList<>(attachments.size());
50 for (String attachment : attachments) {
51 try {
52 signalServiceAttachments.add(createAttachment(new File(attachment)));
53 } catch (IOException e) {
54 throw new AttachmentInvalidException(attachment, e);
55 }
56 }
57 }
58 return signalServiceAttachments;
59 }
60
61 private static String getFileMimeType(File file) throws IOException {
62 String mime = Files.probeContentType(file.toPath());
63 if (mime == null) {
64 try (InputStream bufferedStream = new BufferedInputStream(new FileInputStream(file))) {
65 mime = URLConnection.guessContentTypeFromStream(bufferedStream);
66 }
67 }
68 if (mime == null) {
69 mime = "application/octet-stream";
70 }
71 return mime;
72 }
73
74 static SignalServiceAttachmentStream createAttachment(File attachmentFile) throws IOException {
75 InputStream attachmentStream = new FileInputStream(attachmentFile);
76 final long attachmentSize = attachmentFile.length();
77 final String mime = getFileMimeType(attachmentFile);
78 // TODO mabybe add a parameter to set the voiceNote, borderless, preview, width, height and caption option
79 final long uploadTimestamp = System.currentTimeMillis();
80 Optional<byte[]> preview = Optional.absent();
81 Optional<String> caption = Optional.absent();
82 Optional<String> blurHash = Optional.absent();
83 final Optional<ResumableUploadSpec> resumableUploadSpec = Optional.absent();
84 return new SignalServiceAttachmentStream(attachmentStream, mime, attachmentSize, Optional.of(attachmentFile.getName()), false, false, preview, 0, 0, uploadTimestamp, caption, blurHash, null, null, resumableUploadSpec);
85 }
86
87 static StreamDetails createStreamDetailsFromFile(File file) throws IOException {
88 InputStream stream = new FileInputStream(file);
89 final long size = file.length();
90 String mime = Files.probeContentType(file.toPath());
91 if (mime == null) {
92 mime = "application/octet-stream";
93 }
94 return new StreamDetails(stream, mime, size);
95 }
96
97 static CertificateValidator getCertificateValidator() {
98 try {
99 ECPublicKey unidentifiedSenderTrustRoot = Curve.decodePoint(Base64.decode(ServiceConfig.UNIDENTIFIED_SENDER_TRUST_ROOT), 0);
100 return new CertificateValidator(unidentifiedSenderTrustRoot);
101 } catch (InvalidKeyException | IOException e) {
102 throw new AssertionError(e);
103 }
104 }
105
106 private static Map<String, String> getQueryMap(String query) {
107 String[] params = query.split("&");
108 Map<String, String> map = new HashMap<>();
109 for (String param : params) {
110 String name = null;
111 final String[] paramParts = param.split("=");
112 try {
113 name = URLDecoder.decode(paramParts[0], "utf-8");
114 } catch (UnsupportedEncodingException e) {
115 // Impossible
116 }
117 String value = null;
118 try {
119 value = URLDecoder.decode(paramParts[1], "utf-8");
120 } catch (UnsupportedEncodingException e) {
121 // Impossible
122 }
123 map.put(name, value);
124 }
125 return map;
126 }
127
128 static String createDeviceLinkUri(DeviceLinkInfo info) {
129 try {
130 return "tsdevice:/?uuid=" + URLEncoder.encode(info.deviceIdentifier, "utf-8") + "&pub_key=" + URLEncoder.encode(Base64.encodeBytesWithoutPadding(info.deviceKey.serialize()), "utf-8");
131 } catch (UnsupportedEncodingException e) {
132 // Shouldn't happen
133 return null;
134 }
135 }
136
137 static DeviceLinkInfo parseDeviceLinkUri(URI linkUri) throws IOException, InvalidKeyException {
138 Map<String, String> query = getQueryMap(linkUri.getRawQuery());
139 String deviceIdentifier = query.get("uuid");
140 String publicKeyEncoded = query.get("pub_key");
141
142 if (isEmpty(deviceIdentifier) || isEmpty(publicKeyEncoded)) {
143 throw new RuntimeException("Invalid device link uri");
144 }
145
146 ECPublicKey deviceKey = Curve.decodePoint(Base64.decode(publicKeyEncoded), 0);
147
148 return new DeviceLinkInfo(deviceIdentifier, deviceKey);
149 }
150
151 static SignalServiceEnvelope loadEnvelope(File file) throws IOException {
152 try (FileInputStream f = new FileInputStream(file)) {
153 DataInputStream in = new DataInputStream(f);
154 int version = in.readInt();
155 if (version > 4) {
156 return null;
157 }
158 int type = in.readInt();
159 String source = in.readUTF();
160 UUID sourceUuid = null;
161 if (version >= 3) {
162 sourceUuid = UuidUtil.parseOrNull(in.readUTF());
163 }
164 int sourceDevice = in.readInt();
165 if (version == 1) {
166 // read legacy relay field
167 in.readUTF();
168 }
169 long timestamp = in.readLong();
170 byte[] content = null;
171 int contentLen = in.readInt();
172 if (contentLen > 0) {
173 content = new byte[contentLen];
174 in.readFully(content);
175 }
176 byte[] legacyMessage = null;
177 int legacyMessageLen = in.readInt();
178 if (legacyMessageLen > 0) {
179 legacyMessage = new byte[legacyMessageLen];
180 in.readFully(legacyMessage);
181 }
182 long serverReceivedTimestamp = 0;
183 String uuid = null;
184 if (version >= 2) {
185 serverReceivedTimestamp = in.readLong();
186 uuid = in.readUTF();
187 if ("".equals(uuid)) {
188 uuid = null;
189 }
190 }
191 long serverDeliveredTimestamp = 0;
192 if (version >= 4) {
193 serverDeliveredTimestamp = in.readLong();
194 }
195 Optional<SignalServiceAddress> addressOptional = sourceUuid == null && source.isEmpty()
196 ? Optional.absent()
197 : Optional.of(new SignalServiceAddress(sourceUuid, source));
198 return new SignalServiceEnvelope(type, addressOptional, sourceDevice, timestamp, legacyMessage, content, serverReceivedTimestamp, serverDeliveredTimestamp, uuid);
199 }
200 }
201
202 static void storeEnvelope(SignalServiceEnvelope envelope, File file) throws IOException {
203 try (FileOutputStream f = new FileOutputStream(file)) {
204 try (DataOutputStream out = new DataOutputStream(f)) {
205 out.writeInt(4); // version
206 out.writeInt(envelope.getType());
207 out.writeUTF(envelope.getSourceE164().isPresent() ? envelope.getSourceE164().get() : "");
208 out.writeUTF(envelope.getSourceUuid().isPresent() ? envelope.getSourceUuid().get() : "");
209 out.writeInt(envelope.getSourceDevice());
210 out.writeLong(envelope.getTimestamp());
211 if (envelope.hasContent()) {
212 out.writeInt(envelope.getContent().length);
213 out.write(envelope.getContent());
214 } else {
215 out.writeInt(0);
216 }
217 if (envelope.hasLegacyMessage()) {
218 out.writeInt(envelope.getLegacyMessage().length);
219 out.write(envelope.getLegacyMessage());
220 } else {
221 out.writeInt(0);
222 }
223 out.writeLong(envelope.getServerReceivedTimestamp());
224 String uuid = envelope.getUuid();
225 out.writeUTF(uuid == null ? "" : uuid);
226 out.writeLong(envelope.getServerDeliveredTimestamp());
227 }
228 }
229 }
230
231 static File retrieveAttachment(SignalServiceAttachmentStream stream, File outputFile) throws IOException {
232 InputStream input = stream.getInputStream();
233
234 try (OutputStream output = new FileOutputStream(outputFile)) {
235 byte[] buffer = new byte[4096];
236 int read;
237
238 while ((read = input.read(buffer)) != -1) {
239 output.write(buffer, 0, read);
240 }
241 } catch (FileNotFoundException e) {
242 e.printStackTrace();
243 return null;
244 }
245 return outputFile;
246 }
247
248 static String computeSafetyNumber(SignalServiceAddress ownAddress, IdentityKey ownIdentityKey, SignalServiceAddress theirAddress, IdentityKey theirIdentityKey) {
249 int version;
250 byte[] ownId;
251 byte[] theirId;
252
253 if (ServiceConfig.capabilities.isUuid()
254 && ownAddress.getUuid().isPresent() && theirAddress.getUuid().isPresent()) {
255 // Version 2: UUID user
256 version = 2;
257 ownId = UuidUtil.toByteArray(ownAddress.getUuid().get());
258 theirId = UuidUtil.toByteArray(theirAddress.getUuid().get());
259 } else {
260 // Version 1: E164 user
261 version = 1;
262 if (!ownAddress.getNumber().isPresent() || !theirAddress.getNumber().isPresent()) {
263 return "INVALID ID";
264 }
265 ownId = ownAddress.getNumber().get().getBytes();
266 theirId = theirAddress.getNumber().get().getBytes();
267 }
268
269 Fingerprint fingerprint = new NumericFingerprintGenerator(5200).createFor(version, ownId, ownIdentityKey, theirId, theirIdentityKey);
270 return fingerprint.getDisplayableFingerprint().getDisplayText();
271 }
272
273 static class DeviceLinkInfo {
274
275 final String deviceIdentifier;
276 final ECPublicKey deviceKey;
277
278 DeviceLinkInfo(final String deviceIdentifier, final ECPublicKey deviceKey) {
279 this.deviceIdentifier = deviceIdentifier;
280 this.deviceKey = deviceKey;
281 }
282 }
283 }