]> nmode's Git Repositories - signal-cli/blob - lib/src/main/java/org/asamk/signal/manager/storage/senderKeys/LegacySenderKeyRecordStore.java
f5a1fc53e11f8506385b46826100d0f6c1469542
[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 import org.whispersystems.signalservice.api.push.ServiceId;
12
13 import java.io.File;
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;
23
24 public class LegacySenderKeyRecordStore {
25
26 private final static Logger logger = LoggerFactory.getLogger(LegacySenderKeyRecordStore.class);
27
28 public static void migrate(
29 final File senderKeysPath,
30 final RecipientResolver resolver,
31 final RecipientAddressResolver addressResolver,
32 final SenderKeyStore senderKeyStore
33 ) {
34 final var files = senderKeysPath.listFiles();
35 if (files == null) {
36 return;
37 }
38
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()) {
43 return null;
44 }
45 return new Pair<>(new SenderKeyRecordStore.Key(ServiceId.from(uuid.get()),
46 key.deviceId,
47 key.distributionId), record);
48 }).filter(Objects::nonNull).toList();
49
50 senderKeyStore.addLegacySenderKeys(senderKeys);
51 deleteAllSenderKeys(senderKeysPath);
52 }
53
54 private static void deleteAllSenderKeys(File senderKeysPath) {
55 final var files = senderKeysPath.listFiles();
56 if (files == null) {
57 return;
58 }
59
60 for (var file : files) {
61 try {
62 Files.delete(file.toPath());
63 } catch (IOException e) {
64 logger.error("Failed to delete sender key file {}: {}", file, e.getMessage());
65 }
66 }
67 try {
68 Files.delete(senderKeysPath.toPath());
69 } catch (IOException e) {
70 logger.error("Failed to delete sender keys directory {}: {}", senderKeysPath, e.getMessage());
71 }
72 }
73
74 final static Pattern senderKeyFileNamePattern = Pattern.compile("(\\d+)_(\\d+)_([\\da-z\\-]+)");
75
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)
80 .map(matcher -> {
81 final var recipientId = resolver.resolveRecipient(Long.parseLong(matcher.group(1)));
82 if (recipientId == null) {
83 return null;
84 }
85 return new Key(recipientId, Integer.parseInt(matcher.group(2)), UUID.fromString(matcher.group(3)));
86 })
87 .filter(Objects::nonNull)
88 .toList();
89 }
90
91 private static File getSenderKeyFile(Key key, final File senderKeysPath) {
92 return new File(senderKeysPath,
93 key.recipientId().id() + "_" + key.deviceId() + "_" + key.distributionId().toString());
94 }
95
96 private static SenderKeyRecord loadSenderKeyLocked(final Key key, final File senderKeysPath) {
97 final var file = getSenderKeyFile(key, senderKeysPath);
98 if (!file.exists()) {
99 return null;
100 }
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());
105 return null;
106 }
107 }
108
109 record Key(RecipientId recipientId, int deviceId, UUID distributionId) {}
110 }