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