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
.slf4j
.Logger
;
10 import org
.slf4j
.LoggerFactory
;
13 import java
.io
.FileInputStream
;
14 import java
.io
.FileOutputStream
;
15 import java
.io
.IOException
;
16 import java
.nio
.file
.Files
;
17 import java
.util
.Arrays
;
18 import java
.util
.HashMap
;
19 import java
.util
.List
;
21 import java
.util
.Objects
;
22 import java
.util
.UUID
;
23 import java
.util
.regex
.Matcher
;
24 import java
.util
.regex
.Pattern
;
26 public class SenderKeyRecordStore
implements org
.signal
.libsignal
.protocol
.groups
.state
.SenderKeyStore
{
28 private final static Logger logger
= LoggerFactory
.getLogger(SenderKeyRecordStore
.class);
30 private final Map
<Key
, SenderKeyRecord
> cachedSenderKeys
= new HashMap
<>();
32 private final File senderKeysPath
;
34 private final RecipientResolver resolver
;
36 public SenderKeyRecordStore(
37 final File senderKeysPath
, final RecipientResolver resolver
39 this.senderKeysPath
= senderKeysPath
;
40 this.resolver
= resolver
;
44 public SenderKeyRecord
loadSenderKey(final SignalProtocolAddress address
, final UUID distributionId
) {
45 final var key
= getKey(address
, distributionId
);
47 synchronized (cachedSenderKeys
) {
48 return loadSenderKeyLocked(key
);
53 public void storeSenderKey(
54 final SignalProtocolAddress address
, final UUID distributionId
, final SenderKeyRecord
record
56 final var key
= getKey(address
, distributionId
);
58 synchronized (cachedSenderKeys
) {
59 storeSenderKeyLocked(key
, record);
63 long getCreateTimeForKey(final RecipientId selfRecipientId
, final int selfDeviceId
, final UUID distributionId
) {
64 final var key
= getKey(selfRecipientId
, selfDeviceId
, distributionId
);
65 final var senderKeyFile
= getSenderKeyFile(key
);
67 if (!senderKeyFile
.exists()) {
71 return IOUtils
.getFileCreateTime(senderKeyFile
);
74 void deleteSenderKey(final RecipientId recipientId
, final UUID distributionId
) {
75 synchronized (cachedSenderKeys
) {
76 cachedSenderKeys
.clear();
77 final var keys
= getKeysLocked(recipientId
);
78 for (var key
: keys
) {
79 if (key
.distributionId
.equals(distributionId
)) {
80 deleteSenderKeyLocked(key
);
87 synchronized (cachedSenderKeys
) {
88 cachedSenderKeys
.clear();
89 final var files
= senderKeysPath
.listFiles((_file
, s
) -> senderKeyFileNamePattern
.matcher(s
).matches());
94 for (final var file
: files
) {
96 Files
.delete(file
.toPath());
97 } catch (IOException e
) {
98 logger
.error("Failed to delete sender key file {}: {}", file
, e
.getMessage());
104 void deleteAllFor(final RecipientId recipientId
) {
105 synchronized (cachedSenderKeys
) {
106 cachedSenderKeys
.clear();
107 final var keys
= getKeysLocked(recipientId
);
108 for (var key
: keys
) {
109 deleteSenderKeyLocked(key
);
114 void mergeRecipients(RecipientId recipientId
, RecipientId toBeMergedRecipientId
) {
115 synchronized (cachedSenderKeys
) {
116 final var keys
= getKeysLocked(toBeMergedRecipientId
);
117 final var otherHasSenderKeys
= keys
.size() > 0;
118 if (!otherHasSenderKeys
) {
122 logger
.debug("To be merged recipient had sender keys, re-assigning to the new recipient.");
123 for (var key
: keys
) {
124 final var toBeMergedSenderKey
= loadSenderKeyLocked(key
);
125 deleteSenderKeyLocked(key
);
126 if (toBeMergedSenderKey
== null) {
130 final var newKey
= new Key(recipientId
, key
.deviceId(), key
.distributionId
);
131 final var senderKeyRecord
= loadSenderKeyLocked(newKey
);
132 if (senderKeyRecord
!= null) {
135 storeSenderKeyLocked(newKey
, toBeMergedSenderKey
);
141 * @param identifier can be either a serialized uuid or a e164 phone number
143 private RecipientId
resolveRecipient(String identifier
) {
144 return resolver
.resolveRecipient(identifier
);
147 private Key
getKey(final RecipientId recipientId
, int deviceId
, final UUID distributionId
) {
148 return new Key(recipientId
, deviceId
, distributionId
);
151 private Key
getKey(final SignalProtocolAddress address
, final UUID distributionId
) {
152 final var recipientId
= resolveRecipient(address
.getName());
153 return new Key(recipientId
, address
.getDeviceId(), distributionId
);
156 private List
<Key
> getKeysLocked(RecipientId recipientId
) {
157 final var files
= senderKeysPath
.listFiles((_file
, s
) -> s
.startsWith(recipientId
.id() + "_"));
161 return parseFileNames(files
);
164 final Pattern senderKeyFileNamePattern
= Pattern
.compile("([0-9]+)_([0-9]+)_([0-9a-z\\-]+)");
166 private List
<Key
> parseFileNames(final File
[] files
) {
167 return Arrays
.stream(files
)
168 .map(f
-> senderKeyFileNamePattern
.matcher(f
.getName()))
169 .filter(Matcher
::matches
)
171 final var recipientId
= resolver
.resolveRecipient(Long
.parseLong(matcher
.group(1)));
172 if (recipientId
== null) {
175 return new Key(recipientId
, Integer
.parseInt(matcher
.group(2)), UUID
.fromString(matcher
.group(3)));
177 .filter(Objects
::nonNull
)
181 private File
getSenderKeyFile(Key key
) {
183 IOUtils
.createPrivateDirectories(senderKeysPath
);
184 } catch (IOException e
) {
185 throw new AssertionError("Failed to create sender keys path: " + e
.getMessage(), e
);
187 return new File(senderKeysPath
,
188 key
.recipientId().id() + "_" + key
.deviceId() + "_" + key
.distributionId
.toString());
191 private SenderKeyRecord
loadSenderKeyLocked(final Key key
) {
193 final var senderKeyRecord
= cachedSenderKeys
.get(key
);
194 if (senderKeyRecord
!= null) {
195 return senderKeyRecord
;
199 final var file
= getSenderKeyFile(key
);
200 if (!file
.exists()) {
203 try (var inputStream
= new FileInputStream(file
)) {
204 final var senderKeyRecord
= new SenderKeyRecord(inputStream
.readAllBytes());
205 cachedSenderKeys
.put(key
, senderKeyRecord
);
206 return senderKeyRecord
;
207 } catch (IOException
| InvalidMessageException e
) {
208 logger
.warn("Failed to load sender key, resetting sender key: {}", e
.getMessage());
213 private void storeSenderKeyLocked(final Key key
, final SenderKeyRecord senderKeyRecord
) {
214 cachedSenderKeys
.put(key
, senderKeyRecord
);
216 final var file
= getSenderKeyFile(key
);
218 try (var outputStream
= new FileOutputStream(file
)) {
219 outputStream
.write(senderKeyRecord
.serialize());
221 } catch (IOException e
) {
222 logger
.warn("Failed to store sender key, trying to delete file and retry: {}", e
.getMessage());
224 Files
.delete(file
.toPath());
225 try (var outputStream
= new FileOutputStream(file
)) {
226 outputStream
.write(senderKeyRecord
.serialize());
228 } catch (IOException e2
) {
229 logger
.error("Failed to store sender key file {}: {}", file
, e2
.getMessage());
234 private void deleteSenderKeyLocked(final Key key
) {
235 cachedSenderKeys
.remove(key
);
237 final var file
= getSenderKeyFile(key
);
238 if (!file
.exists()) {
242 Files
.delete(file
.toPath());
243 } catch (IOException e
) {
244 logger
.error("Failed to delete sender key file {}: {}", file
, e
.getMessage());
248 private record Key(RecipientId recipientId
, int deviceId
, UUID distributionId
) {}