]> nmode's Git Repositories - signal-cli/blob - lib/src/main/java/org/asamk/signal/manager/storage/senderKeys/LegacySenderKeyRecordStore.java
d17941567a93a1e0358cc32a23a0f4b46e088171
[signal-cli] / lib / src / main / java / org / asamk / signal / manager / storage / senderKeys / LegacySenderKeyRecordStore.java
1 package org.asamk.signal.manager.storage.senderKeys;
2
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
12 import java.io.File;
13 import java.io.FileInputStream;
14 import java.io.IOException;
15 import java.nio.file.Files;
16 import java.util.Arrays;
17 import java.util.List;
18 import java.util.Objects;
19 import java.util.UUID;
20 import java.util.regex.Matcher;
21 import java.util.regex.Pattern;
22
23 public class LegacySenderKeyRecordStore {
24
25 private final static Logger logger = LoggerFactory.getLogger(LegacySenderKeyRecordStore.class);
26
27 public static void migrate(
28 final File senderKeysPath,
29 final RecipientResolver resolver,
30 final RecipientAddressResolver addressResolver,
31 final SenderKeyStore senderKeyStore
32 ) {
33 final var files = senderKeysPath.listFiles();
34 if (files == null) {
35 return;
36 }
37
38 final var senderKeys = parseFileNames(files, resolver).stream().map(key -> {
39 final var record = loadSenderKeyLocked(key, senderKeysPath);
40 final var serviceId = addressResolver.resolveRecipientAddress(key.recipientId).serviceId();
41 if (record == null || serviceId.isEmpty()) {
42 return null;
43 }
44 return new Pair<>(new SenderKeyRecordStore.Key(serviceId.get(), key.deviceId, key.distributionId), record);
45 }).filter(Objects::nonNull).toList();
46
47 senderKeyStore.addLegacySenderKeys(senderKeys);
48 deleteAllSenderKeys(senderKeysPath);
49 }
50
51 private static void deleteAllSenderKeys(File senderKeysPath) {
52 final var files = senderKeysPath.listFiles();
53 if (files == null) {
54 return;
55 }
56
57 for (var file : files) {
58 try {
59 Files.delete(file.toPath());
60 } catch (IOException e) {
61 logger.error("Failed to delete sender key file {}: {}", file, e.getMessage());
62 }
63 }
64 try {
65 Files.delete(senderKeysPath.toPath());
66 } catch (IOException e) {
67 logger.error("Failed to delete sender keys directory {}: {}", senderKeysPath, e.getMessage());
68 }
69 }
70
71 final static Pattern senderKeyFileNamePattern = Pattern.compile("(\\d+)_(\\d+)_([\\da-z\\-]+)");
72
73 private static List<Key> parseFileNames(final File[] files, final RecipientResolver resolver) {
74 return Arrays.stream(files)
75 .map(f -> senderKeyFileNamePattern.matcher(f.getName()))
76 .filter(Matcher::matches)
77 .map(matcher -> {
78 final var recipientId = resolver.resolveRecipient(Long.parseLong(matcher.group(1)));
79 if (recipientId == null) {
80 return null;
81 }
82 return new Key(recipientId, Integer.parseInt(matcher.group(2)), UUID.fromString(matcher.group(3)));
83 })
84 .filter(Objects::nonNull)
85 .toList();
86 }
87
88 private static File getSenderKeyFile(Key key, final File senderKeysPath) {
89 return new File(senderKeysPath,
90 key.recipientId().id() + "_" + key.deviceId() + "_" + key.distributionId().toString());
91 }
92
93 private static SenderKeyRecord loadSenderKeyLocked(final Key key, final File senderKeysPath) {
94 final var file = getSenderKeyFile(key, senderKeysPath);
95 if (!file.exists()) {
96 return null;
97 }
98 try (var inputStream = new FileInputStream(file)) {
99 return new SenderKeyRecord(inputStream.readAllBytes());
100 } catch (IOException | InvalidMessageException e) {
101 logger.warn("Failed to load sender key, resetting sender key: {}", e.getMessage());
102 return null;
103 }
104 }
105
106 record Key(RecipientId recipientId, int deviceId, UUID distributionId) {}
107 }