1 package org
.asamk
.signal
.manager
;
3 import org
.apache
.http
.util
.TextUtils
;
4 import org
.asamk
.signal
.AttachmentInvalidException
;
5 import org
.signal
.libsignal
.metadata
.certificate
.CertificateValidator
;
6 import org
.whispersystems
.libsignal
.IdentityKey
;
7 import org
.whispersystems
.libsignal
.InvalidKeyException
;
8 import org
.whispersystems
.libsignal
.ecc
.Curve
;
9 import org
.whispersystems
.libsignal
.ecc
.ECPublicKey
;
10 import org
.whispersystems
.libsignal
.fingerprint
.Fingerprint
;
11 import org
.whispersystems
.libsignal
.fingerprint
.NumericFingerprintGenerator
;
12 import org
.whispersystems
.libsignal
.util
.guava
.Optional
;
13 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachment
;
14 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachmentStream
;
15 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceEnvelope
;
16 import org
.whispersystems
.signalservice
.api
.push
.SignalServiceAddress
;
17 import org
.whispersystems
.signalservice
.api
.util
.InvalidNumberException
;
18 import org
.whispersystems
.signalservice
.api
.util
.PhoneNumberFormatter
;
19 import org
.whispersystems
.signalservice
.internal
.util
.Base64
;
23 import java
.net
.URLDecoder
;
24 import java
.net
.URLEncoder
;
25 import java
.nio
.file
.Files
;
30 static List
<SignalServiceAttachment
> getSignalServiceAttachments(List
<String
> attachments
) throws AttachmentInvalidException
{
31 List
<SignalServiceAttachment
> SignalServiceAttachments
= null;
32 if (attachments
!= null) {
33 SignalServiceAttachments
= new ArrayList
<>(attachments
.size());
34 for (String attachment
: attachments
) {
36 SignalServiceAttachments
.add(createAttachment(new File(attachment
)));
37 } catch (IOException e
) {
38 throw new AttachmentInvalidException(attachment
, e
);
42 return SignalServiceAttachments
;
45 static SignalServiceAttachmentStream
createAttachment(File attachmentFile
) throws IOException
{
46 InputStream attachmentStream
= new FileInputStream(attachmentFile
);
47 final long attachmentSize
= attachmentFile
.length();
48 String mime
= Files
.probeContentType(attachmentFile
.toPath());
50 mime
= "application/octet-stream";
52 // TODO mabybe add a parameter to set the voiceNote, preview, width, height and caption option
53 Optional
<byte[]> preview
= Optional
.absent();
54 Optional
<String
> caption
= Optional
.absent();
55 return new SignalServiceAttachmentStream(attachmentStream
, mime
, attachmentSize
, Optional
.of(attachmentFile
.getName()), false, preview
, 0, 0, caption
, null);
58 static CertificateValidator
getCertificateValidator() {
60 ECPublicKey unidentifiedSenderTrustRoot
= Curve
.decodePoint(Base64
.decode(BaseConfig
.UNIDENTIFIED_SENDER_TRUST_ROOT
), 0);
61 return new CertificateValidator(unidentifiedSenderTrustRoot
);
62 } catch (InvalidKeyException
| IOException e
) {
63 throw new AssertionError(e
);
67 private static Map
<String
, String
> getQueryMap(String query
) {
68 String
[] params
= query
.split("&");
69 Map
<String
, String
> map
= new HashMap
<>();
70 for (String param
: params
) {
72 final String
[] paramParts
= param
.split("=");
74 name
= URLDecoder
.decode(paramParts
[0], "utf-8");
75 } catch (UnsupportedEncodingException e
) {
80 value
= URLDecoder
.decode(paramParts
[1], "utf-8");
81 } catch (UnsupportedEncodingException e
) {
89 static String
createDeviceLinkUri(DeviceLinkInfo info
) {
91 return "tsdevice:/?uuid=" + URLEncoder
.encode(info
.deviceIdentifier
, "utf-8") + "&pub_key=" + URLEncoder
.encode(Base64
.encodeBytesWithoutPadding(info
.deviceKey
.serialize()), "utf-8");
92 } catch (UnsupportedEncodingException e
) {
98 static DeviceLinkInfo
parseDeviceLinkUri(URI linkUri
) throws IOException
, InvalidKeyException
{
99 Map
<String
, String
> query
= getQueryMap(linkUri
.getRawQuery());
100 String deviceIdentifier
= query
.get("uuid");
101 String publicKeyEncoded
= query
.get("pub_key");
103 if (TextUtils
.isEmpty(deviceIdentifier
) || TextUtils
.isEmpty(publicKeyEncoded
)) {
104 throw new RuntimeException("Invalid device link uri");
107 ECPublicKey deviceKey
= Curve
.decodePoint(Base64
.decode(publicKeyEncoded
), 0);
109 return new DeviceLinkInfo(deviceIdentifier
, deviceKey
);
112 static Set
<SignalServiceAddress
> getSignalServiceAddresses(Collection
<String
> recipients
, String localNumber
) {
113 Set
<SignalServiceAddress
> recipientsTS
= new HashSet
<>(recipients
.size());
114 for (String recipient
: recipients
) {
116 recipientsTS
.add(getPushAddress(recipient
, localNumber
));
117 } catch (InvalidNumberException e
) {
118 System
.err
.println("Failed to add recipient \"" + recipient
+ "\": " + e
.getMessage());
119 System
.err
.println("Aborting sending.");
126 static String
canonicalizeNumber(String number
, String localNumber
) throws InvalidNumberException
{
127 return PhoneNumberFormatter
.formatNumber(number
, localNumber
);
130 private static SignalServiceAddress
getPushAddress(String number
, String localNumber
) throws InvalidNumberException
{
131 String e164number
= canonicalizeNumber(number
, localNumber
);
132 return new SignalServiceAddress(e164number
);
135 static SignalServiceEnvelope
loadEnvelope(File file
) throws IOException
{
136 try (FileInputStream f
= new FileInputStream(file
)) {
137 DataInputStream
in = new DataInputStream(f
);
138 int version
= in.readInt();
142 int type
= in.readInt();
143 String source
= in.readUTF();
144 int sourceDevice
= in.readInt();
146 // read legacy relay field
149 long timestamp
= in.readLong();
150 byte[] content
= null;
151 int contentLen
= in.readInt();
152 if (contentLen
> 0) {
153 content
= new byte[contentLen
];
154 in.readFully(content
);
156 byte[] legacyMessage
= null;
157 int legacyMessageLen
= in.readInt();
158 if (legacyMessageLen
> 0) {
159 legacyMessage
= new byte[legacyMessageLen
];
160 in.readFully(legacyMessage
);
162 long serverTimestamp
= 0;
165 serverTimestamp
= in.readLong();
167 if ("".equals(uuid
)) {
171 return new SignalServiceEnvelope(type
, source
, sourceDevice
, timestamp
, legacyMessage
, content
, serverTimestamp
, uuid
);
175 static void storeEnvelope(SignalServiceEnvelope envelope
, File file
) throws IOException
{
176 try (FileOutputStream f
= new FileOutputStream(file
)) {
177 try (DataOutputStream out
= new DataOutputStream(f
)) {
178 out
.writeInt(2); // version
179 out
.writeInt(envelope
.getType());
180 out
.writeUTF(envelope
.getSource());
181 out
.writeInt(envelope
.getSourceDevice());
182 out
.writeLong(envelope
.getTimestamp());
183 if (envelope
.hasContent()) {
184 out
.writeInt(envelope
.getContent().length
);
185 out
.write(envelope
.getContent());
189 if (envelope
.hasLegacyMessage()) {
190 out
.writeInt(envelope
.getLegacyMessage().length
);
191 out
.write(envelope
.getLegacyMessage());
195 out
.writeLong(envelope
.getServerTimestamp());
196 String uuid
= envelope
.getUuid();
197 out
.writeUTF(uuid
== null ?
"" : uuid
);
202 static File
retrieveAttachment(SignalServiceAttachmentStream stream
, File outputFile
) throws IOException
{
203 InputStream input
= stream
.getInputStream();
205 try (OutputStream output
= new FileOutputStream(outputFile
)) {
206 byte[] buffer
= new byte[4096];
209 while ((read
= input
.read(buffer
)) != -1) {
210 output
.write(buffer
, 0, read
);
212 } catch (FileNotFoundException e
) {
219 static String
computeSafetyNumber(String ownUsername
, IdentityKey ownIdentityKey
, String theirUsername
, IdentityKey theirIdentityKey
) {
220 Fingerprint fingerprint
= new NumericFingerprintGenerator(5200).createFor(ownUsername
, ownIdentityKey
, theirUsername
, theirIdentityKey
);
221 return fingerprint
.getDisplayableFingerprint().getDisplayText();
224 static class DeviceLinkInfo
{
226 final String deviceIdentifier
;
227 final ECPublicKey deviceKey
;
229 DeviceLinkInfo(final String deviceIdentifier
, final ECPublicKey deviceKey
) {
230 this.deviceIdentifier
= deviceIdentifier
;
231 this.deviceKey
= deviceKey
;