]> nmode's Git Repositories - signal-cli/blob - src/main/java/org/asamk/signal/manager/Utils.java
Bump version
[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.net.URI;
31 import java.net.URLConnection;
32 import java.net.URLDecoder;
33 import java.net.URLEncoder;
34 import java.nio.charset.StandardCharsets;
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 static String getFileMimeType(File file, String defaultMimeType) 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 return defaultMimeType;
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, "application/octet-stream");
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,
85 mime,
86 attachmentSize,
87 Optional.of(attachmentFile.getName()),
88 false,
89 false,
90 preview,
91 0,
92 0,
93 uploadTimestamp,
94 caption,
95 blurHash,
96 null,
97 null,
98 resumableUploadSpec);
99 }
100
101 static StreamDetails createStreamDetailsFromFile(File file) throws IOException {
102 InputStream stream = new FileInputStream(file);
103 final long size = file.length();
104 String mime = Files.probeContentType(file.toPath());
105 if (mime == null) {
106 mime = "application/octet-stream";
107 }
108 return new StreamDetails(stream, mime, size);
109 }
110
111 static CertificateValidator getCertificateValidator() {
112 try {
113 ECPublicKey unidentifiedSenderTrustRoot = Curve.decodePoint(Base64.decode(ServiceConfig.UNIDENTIFIED_SENDER_TRUST_ROOT),
114 0);
115 return new CertificateValidator(unidentifiedSenderTrustRoot);
116 } catch (InvalidKeyException | IOException e) {
117 throw new AssertionError(e);
118 }
119 }
120
121 private static Map<String, String> getQueryMap(String query) {
122 String[] params = query.split("&");
123 Map<String, String> map = new HashMap<>();
124 for (String param : params) {
125 final String[] paramParts = param.split("=");
126 String name = URLDecoder.decode(paramParts[0], StandardCharsets.UTF_8);
127 String value = URLDecoder.decode(paramParts[1], StandardCharsets.UTF_8);
128 map.put(name, value);
129 }
130 return map;
131 }
132
133 static String createDeviceLinkUri(DeviceLinkInfo info) {
134 return "tsdevice:/?uuid="
135 + URLEncoder.encode(info.deviceIdentifier, StandardCharsets.UTF_8)
136 + "&pub_key="
137 + URLEncoder.encode(Base64.encodeBytesWithoutPadding(info.deviceKey.serialize()),
138 StandardCharsets.UTF_8);
139 }
140
141 static DeviceLinkInfo parseDeviceLinkUri(URI linkUri) throws IOException, InvalidKeyException {
142 Map<String, String> query = getQueryMap(linkUri.getRawQuery());
143 String deviceIdentifier = query.get("uuid");
144 String publicKeyEncoded = query.get("pub_key");
145
146 if (isEmpty(deviceIdentifier) || isEmpty(publicKeyEncoded)) {
147 throw new RuntimeException("Invalid device link uri");
148 }
149
150 ECPublicKey deviceKey = Curve.decodePoint(Base64.decode(publicKeyEncoded), 0);
151
152 return new DeviceLinkInfo(deviceIdentifier, deviceKey);
153 }
154
155 static SignalServiceEnvelope loadEnvelope(File file) throws IOException {
156 try (FileInputStream f = new FileInputStream(file)) {
157 DataInputStream in = new DataInputStream(f);
158 int version = in.readInt();
159 if (version > 4) {
160 return null;
161 }
162 int type = in.readInt();
163 String source = in.readUTF();
164 UUID sourceUuid = null;
165 if (version >= 3) {
166 sourceUuid = UuidUtil.parseOrNull(in.readUTF());
167 }
168 int sourceDevice = in.readInt();
169 if (version == 1) {
170 // read legacy relay field
171 in.readUTF();
172 }
173 long timestamp = in.readLong();
174 byte[] content = null;
175 int contentLen = in.readInt();
176 if (contentLen > 0) {
177 content = new byte[contentLen];
178 in.readFully(content);
179 }
180 byte[] legacyMessage = null;
181 int legacyMessageLen = in.readInt();
182 if (legacyMessageLen > 0) {
183 legacyMessage = new byte[legacyMessageLen];
184 in.readFully(legacyMessage);
185 }
186 long serverReceivedTimestamp = 0;
187 String uuid = null;
188 if (version >= 2) {
189 serverReceivedTimestamp = in.readLong();
190 uuid = in.readUTF();
191 if ("".equals(uuid)) {
192 uuid = null;
193 }
194 }
195 long serverDeliveredTimestamp = 0;
196 if (version >= 4) {
197 serverDeliveredTimestamp = in.readLong();
198 }
199 Optional<SignalServiceAddress> addressOptional = sourceUuid == null && source.isEmpty()
200 ? Optional.absent()
201 : Optional.of(new SignalServiceAddress(sourceUuid, source));
202 return new SignalServiceEnvelope(type,
203 addressOptional,
204 sourceDevice,
205 timestamp,
206 legacyMessage,
207 content,
208 serverReceivedTimestamp,
209 serverDeliveredTimestamp,
210 uuid);
211 }
212 }
213
214 static void storeEnvelope(SignalServiceEnvelope envelope, File file) throws IOException {
215 try (FileOutputStream f = new FileOutputStream(file)) {
216 try (DataOutputStream out = new DataOutputStream(f)) {
217 out.writeInt(4); // version
218 out.writeInt(envelope.getType());
219 out.writeUTF(envelope.getSourceE164().isPresent() ? envelope.getSourceE164().get() : "");
220 out.writeUTF(envelope.getSourceUuid().isPresent() ? envelope.getSourceUuid().get() : "");
221 out.writeInt(envelope.getSourceDevice());
222 out.writeLong(envelope.getTimestamp());
223 if (envelope.hasContent()) {
224 out.writeInt(envelope.getContent().length);
225 out.write(envelope.getContent());
226 } else {
227 out.writeInt(0);
228 }
229 if (envelope.hasLegacyMessage()) {
230 out.writeInt(envelope.getLegacyMessage().length);
231 out.write(envelope.getLegacyMessage());
232 } else {
233 out.writeInt(0);
234 }
235 out.writeLong(envelope.getServerReceivedTimestamp());
236 String uuid = envelope.getUuid();
237 out.writeUTF(uuid == null ? "" : uuid);
238 out.writeLong(envelope.getServerDeliveredTimestamp());
239 }
240 }
241 }
242
243 static File retrieveAttachment(SignalServiceAttachmentStream stream, File outputFile) throws IOException {
244 InputStream input = stream.getInputStream();
245
246 try (OutputStream output = new FileOutputStream(outputFile)) {
247 byte[] buffer = new byte[4096];
248 int read;
249
250 while ((read = input.read(buffer)) != -1) {
251 output.write(buffer, 0, read);
252 }
253 } catch (FileNotFoundException e) {
254 e.printStackTrace();
255 return null;
256 }
257 return outputFile;
258 }
259
260 static String computeSafetyNumber(
261 SignalServiceAddress ownAddress,
262 IdentityKey ownIdentityKey,
263 SignalServiceAddress theirAddress,
264 IdentityKey theirIdentityKey
265 ) {
266 int version;
267 byte[] ownId;
268 byte[] theirId;
269
270 if (ServiceConfig.capabilities.isUuid() && ownAddress.getUuid().isPresent() && theirAddress.getUuid()
271 .isPresent()) {
272 // Version 2: UUID user
273 version = 2;
274 ownId = UuidUtil.toByteArray(ownAddress.getUuid().get());
275 theirId = UuidUtil.toByteArray(theirAddress.getUuid().get());
276 } else {
277 // Version 1: E164 user
278 version = 1;
279 if (!ownAddress.getNumber().isPresent() || !theirAddress.getNumber().isPresent()) {
280 return "INVALID ID";
281 }
282 ownId = ownAddress.getNumber().get().getBytes();
283 theirId = theirAddress.getNumber().get().getBytes();
284 }
285
286 Fingerprint fingerprint = new NumericFingerprintGenerator(5200).createFor(version,
287 ownId,
288 ownIdentityKey,
289 theirId,
290 theirIdentityKey);
291 return fingerprint.getDisplayableFingerprint().getDisplayText();
292 }
293
294 static class DeviceLinkInfo {
295
296 final String deviceIdentifier;
297 final ECPublicKey deviceKey;
298
299 DeviceLinkInfo(final String deviceIdentifier, final ECPublicKey deviceKey) {
300 this.deviceIdentifier = deviceIdentifier;
301 this.deviceKey = deviceKey;
302 }
303 }
304 }