1 package org
.asamk
.signal
.manager
.storage
.senderKeys
;
3 import org
.asamk
.signal
.manager
.api
.Pair
;
4 import org
.asamk
.signal
.manager
.helper
.RecipientAddressResolver
;
5 import org
.asamk
.signal
.manager
.storage
.recipients
.RecipientId
;
6 import org
.asamk
.signal
.manager
.storage
.recipients
.RecipientResolver
;
7 import org
.signal
.libsignal
.protocol
.InvalidMessageException
;
8 import org
.signal
.libsignal
.protocol
.groups
.state
.SenderKeyRecord
;
9 import org
.slf4j
.Logger
;
10 import org
.slf4j
.LoggerFactory
;
11 import org
.whispersystems
.signalservice
.api
.push
.ServiceId
;
14 import java
.io
.FileInputStream
;
15 import java
.io
.IOException
;
16 import java
.nio
.file
.Files
;
17 import java
.util
.Arrays
;
18 import java
.util
.List
;
19 import java
.util
.Objects
;
20 import java
.util
.UUID
;
21 import java
.util
.regex
.Matcher
;
22 import java
.util
.regex
.Pattern
;
24 public class LegacySenderKeyRecordStore
{
26 private final static Logger logger
= LoggerFactory
.getLogger(LegacySenderKeyRecordStore
.class);
28 public static void migrate(
29 final File senderKeysPath
,
30 final RecipientResolver resolver
,
31 final RecipientAddressResolver addressResolver
,
32 final SenderKeyStore senderKeyStore
34 final var files
= senderKeysPath
.listFiles();
39 final var senderKeys
= parseFileNames(files
, resolver
).stream().map(key
-> {
40 final var record = loadSenderKeyLocked(key
, senderKeysPath
);
41 final var uuid
= addressResolver
.resolveRecipientAddress(key
.recipientId
).uuid();
42 if (record == null || uuid
.isEmpty()) {
45 return new Pair
<>(new SenderKeyRecordStore
.Key(ServiceId
.from(uuid
.get()),
47 key
.distributionId
), record);
48 }).filter(Objects
::nonNull
).toList();
50 senderKeyStore
.addLegacySenderKeys(senderKeys
);
51 deleteAllSenderKeys(senderKeysPath
);
54 private static void deleteAllSenderKeys(File senderKeysPath
) {
55 final var files
= senderKeysPath
.listFiles();
60 for (var file
: files
) {
62 Files
.delete(file
.toPath());
63 } catch (IOException e
) {
64 logger
.error("Failed to delete sender key file {}: {}", file
, e
.getMessage());
68 Files
.delete(senderKeysPath
.toPath());
69 } catch (IOException e
) {
70 logger
.error("Failed to delete sender keys directory {}: {}", senderKeysPath
, e
.getMessage());
74 final static Pattern senderKeyFileNamePattern
= Pattern
.compile("(\\d+)_(\\d+)_([\\da-z\\-]+)");
76 private static List
<Key
> parseFileNames(final File
[] files
, final RecipientResolver resolver
) {
77 return Arrays
.stream(files
)
78 .map(f
-> senderKeyFileNamePattern
.matcher(f
.getName()))
79 .filter(Matcher
::matches
)
81 final var recipientId
= resolver
.resolveRecipient(Long
.parseLong(matcher
.group(1)));
82 if (recipientId
== null) {
85 return new Key(recipientId
, Integer
.parseInt(matcher
.group(2)), UUID
.fromString(matcher
.group(3)));
87 .filter(Objects
::nonNull
)
91 private static File
getSenderKeyFile(Key key
, final File senderKeysPath
) {
92 return new File(senderKeysPath
,
93 key
.recipientId().id() + "_" + key
.deviceId() + "_" + key
.distributionId().toString());
96 private static SenderKeyRecord
loadSenderKeyLocked(final Key key
, final File senderKeysPath
) {
97 final var file
= getSenderKeyFile(key
, senderKeysPath
);
101 try (var inputStream
= new FileInputStream(file
)) {
102 return new SenderKeyRecord(inputStream
.readAllBytes());
103 } catch (IOException
| InvalidMessageException e
) {
104 logger
.warn("Failed to load sender key, resetting sender key: {}", e
.getMessage());
109 record Key(RecipientId recipientId
, int deviceId
, UUID distributionId
) {}