1 package org
.asamk
.signal
.manager
.storage
.senderKeys
;
3 import org
.asamk
.signal
.manager
.storage
.recipients
.RecipientId
;
4 import org
.asamk
.signal
.manager
.storage
.recipients
.RecipientResolver
;
5 import org
.asamk
.signal
.manager
.util
.IOUtils
;
6 import org
.signal
.libsignal
.protocol
.InvalidMessageException
;
7 import org
.signal
.libsignal
.protocol
.SignalProtocolAddress
;
8 import org
.signal
.libsignal
.protocol
.groups
.state
.SenderKeyRecord
;
9 import org
.signal
.libsignal
.protocol
.groups
.state
.SenderKeyStore
;
10 import org
.slf4j
.Logger
;
11 import org
.slf4j
.LoggerFactory
;
14 import java
.io
.FileInputStream
;
15 import java
.io
.FileOutputStream
;
16 import java
.io
.IOException
;
17 import java
.nio
.file
.Files
;
18 import java
.util
.Arrays
;
19 import java
.util
.HashMap
;
20 import java
.util
.List
;
22 import java
.util
.Objects
;
23 import java
.util
.UUID
;
24 import java
.util
.regex
.Matcher
;
25 import java
.util
.regex
.Pattern
;
27 public class SenderKeyRecordStore
implements SenderKeyStore
{
29 private final static Logger logger
= LoggerFactory
.getLogger(SenderKeyRecordStore
.class);
31 private final Map
<Key
, SenderKeyRecord
> cachedSenderKeys
= new HashMap
<>();
33 private final File senderKeysPath
;
35 private final RecipientResolver resolver
;
37 public SenderKeyRecordStore(
38 final File senderKeysPath
, final RecipientResolver resolver
40 this.senderKeysPath
= senderKeysPath
;
41 this.resolver
= resolver
;
45 public SenderKeyRecord
loadSenderKey(final SignalProtocolAddress address
, final UUID distributionId
) {
46 final var key
= getKey(address
, distributionId
);
48 synchronized (cachedSenderKeys
) {
49 return loadSenderKeyLocked(key
);
54 public void storeSenderKey(
55 final SignalProtocolAddress address
, final UUID distributionId
, final SenderKeyRecord
record
57 final var key
= getKey(address
, distributionId
);
59 synchronized (cachedSenderKeys
) {
60 storeSenderKeyLocked(key
, record);
64 long getCreateTimeForKey(final RecipientId selfRecipientId
, final int selfDeviceId
, final UUID distributionId
) {
65 final var key
= getKey(selfRecipientId
, selfDeviceId
, distributionId
);
66 final var senderKeyFile
= getSenderKeyFile(key
);
68 if (!senderKeyFile
.exists()) {
72 return IOUtils
.getFileCreateTime(senderKeyFile
);
75 void deleteSenderKey(final RecipientId recipientId
, final UUID distributionId
) {
76 synchronized (cachedSenderKeys
) {
77 cachedSenderKeys
.clear();
78 final var keys
= getKeysLocked(recipientId
);
79 for (var key
: keys
) {
80 if (key
.distributionId
.equals(distributionId
)) {
81 deleteSenderKeyLocked(key
);
88 synchronized (cachedSenderKeys
) {
89 cachedSenderKeys
.clear();
90 final var files
= senderKeysPath
.listFiles((_file
, s
) -> senderKeyFileNamePattern
.matcher(s
).matches());
95 for (final var file
: files
) {
97 Files
.delete(file
.toPath());
98 } catch (IOException e
) {
99 logger
.error("Failed to delete sender key file {}: {}", file
, e
.getMessage());
105 void deleteAllFor(final RecipientId recipientId
) {
106 synchronized (cachedSenderKeys
) {
107 cachedSenderKeys
.clear();
108 final var keys
= getKeysLocked(recipientId
);
109 for (var key
: keys
) {
110 deleteSenderKeyLocked(key
);
115 void mergeRecipients(RecipientId recipientId
, RecipientId toBeMergedRecipientId
) {
116 synchronized (cachedSenderKeys
) {
117 final var keys
= getKeysLocked(toBeMergedRecipientId
);
118 final var otherHasSenderKeys
= keys
.size() > 0;
119 if (!otherHasSenderKeys
) {
123 logger
.debug("To be merged recipient had sender keys, re-assigning to the new recipient.");
124 for (var key
: keys
) {
125 final var toBeMergedSenderKey
= loadSenderKeyLocked(key
);
126 deleteSenderKeyLocked(key
);
127 if (toBeMergedSenderKey
== null) {
131 final var newKey
= new Key(recipientId
, key
.deviceId(), key
.distributionId
);
132 final var senderKeyRecord
= loadSenderKeyLocked(newKey
);
133 if (senderKeyRecord
!= null) {
136 storeSenderKeyLocked(newKey
, toBeMergedSenderKey
);
142 * @param identifier can be either a serialized uuid or an e164 phone number
144 private RecipientId
resolveRecipient(String identifier
) {
145 return resolver
.resolveRecipient(identifier
);
148 private Key
getKey(final RecipientId recipientId
, int deviceId
, final UUID distributionId
) {
149 return new Key(recipientId
, deviceId
, distributionId
);
152 private Key
getKey(final SignalProtocolAddress address
, final UUID distributionId
) {
153 final var recipientId
= resolveRecipient(address
.getName());
154 return new Key(recipientId
, address
.getDeviceId(), distributionId
);
157 private List
<Key
> getKeysLocked(RecipientId recipientId
) {
158 final var files
= senderKeysPath
.listFiles((_file
, s
) -> s
.startsWith(recipientId
.id() + "_"));
162 return parseFileNames(files
);
165 final Pattern senderKeyFileNamePattern
= Pattern
.compile("(\\d+)_(\\d+)_([\\da-z\\-]+)");
167 private List
<Key
> parseFileNames(final File
[] files
) {
168 return Arrays
.stream(files
)
169 .map(f
-> senderKeyFileNamePattern
.matcher(f
.getName()))
170 .filter(Matcher
::matches
)
172 final var recipientId
= resolver
.resolveRecipient(Long
.parseLong(matcher
.group(1)));
173 if (recipientId
== null) {
176 return new Key(recipientId
, Integer
.parseInt(matcher
.group(2)), UUID
.fromString(matcher
.group(3)));
178 .filter(Objects
::nonNull
)
182 private File
getSenderKeyFile(Key key
) {
184 IOUtils
.createPrivateDirectories(senderKeysPath
);
185 } catch (IOException e
) {
186 throw new AssertionError("Failed to create sender keys path: " + e
.getMessage(), e
);
188 return new File(senderKeysPath
,
189 key
.recipientId().id() + "_" + key
.deviceId() + "_" + key
.distributionId
.toString());
192 private SenderKeyRecord
loadSenderKeyLocked(final Key key
) {
194 final var senderKeyRecord
= cachedSenderKeys
.get(key
);
195 if (senderKeyRecord
!= null) {
196 return senderKeyRecord
;
200 final var file
= getSenderKeyFile(key
);
201 if (!file
.exists()) {
204 try (var inputStream
= new FileInputStream(file
)) {
205 final var senderKeyRecord
= new SenderKeyRecord(inputStream
.readAllBytes());
206 cachedSenderKeys
.put(key
, senderKeyRecord
);
207 return senderKeyRecord
;
208 } catch (IOException
| InvalidMessageException e
) {
209 logger
.warn("Failed to load sender key, resetting sender key: {}", e
.getMessage());
214 private void storeSenderKeyLocked(final Key key
, final SenderKeyRecord senderKeyRecord
) {
215 cachedSenderKeys
.put(key
, senderKeyRecord
);
217 final var file
= getSenderKeyFile(key
);
219 try (var outputStream
= new FileOutputStream(file
)) {
220 outputStream
.write(senderKeyRecord
.serialize());
222 } catch (IOException e
) {
223 logger
.warn("Failed to store sender key, trying to delete file and retry: {}", e
.getMessage());
225 Files
.delete(file
.toPath());
226 try (var outputStream
= new FileOutputStream(file
)) {
227 outputStream
.write(senderKeyRecord
.serialize());
229 } catch (IOException e2
) {
230 logger
.error("Failed to store sender key file {}: {}", file
, e2
.getMessage());
235 private void deleteSenderKeyLocked(final Key key
) {
236 cachedSenderKeys
.remove(key
);
238 final var file
= getSenderKeyFile(key
);
239 if (!file
.exists()) {
243 Files
.delete(file
.toPath());
244 } catch (IOException e
) {
245 logger
.error("Failed to delete sender key file {}: {}", file
, e
.getMessage());
249 private record Key(RecipientId recipientId
, int deviceId
, UUID distributionId
) {}