]> nmode's Git Repositories - signal-cli/blob - lib/src/main/java/org/asamk/signal/manager/storage/identities/LegacyIdentityKeyStore.java
a7f6a4aa7e4141adfdda777252bf089f5bf6e7b6
[signal-cli] / lib / src / main / java / org / asamk / signal / manager / storage / identities / LegacyIdentityKeyStore.java
1 package org.asamk.signal.manager.storage.identities;
2
3 import com.fasterxml.jackson.databind.ObjectMapper;
4
5 import org.asamk.signal.manager.api.TrustLevel;
6 import org.asamk.signal.manager.helper.RecipientAddressResolver;
7 import org.asamk.signal.manager.storage.recipients.RecipientId;
8 import org.asamk.signal.manager.storage.recipients.RecipientResolver;
9 import org.asamk.signal.manager.util.IOUtils;
10 import org.signal.libsignal.protocol.IdentityKey;
11 import org.signal.libsignal.protocol.InvalidKeyException;
12 import org.slf4j.Logger;
13 import org.slf4j.LoggerFactory;
14
15 import java.io.File;
16 import java.io.FileInputStream;
17 import java.io.IOException;
18 import java.nio.file.Files;
19 import java.util.Arrays;
20 import java.util.Base64;
21 import java.util.List;
22 import java.util.Objects;
23 import java.util.regex.Pattern;
24
25 public class LegacyIdentityKeyStore {
26
27 private static final Logger logger = LoggerFactory.getLogger(LegacyIdentityKeyStore.class);
28 private static final ObjectMapper objectMapper = org.asamk.signal.manager.storage.Utils.createStorageObjectMapper();
29
30 public static void migrate(
31 final File identitiesPath,
32 final RecipientResolver resolver,
33 final RecipientAddressResolver addressResolver,
34 final IdentityKeyStore identityKeyStore
35 ) {
36 final var identities = getIdentities(identitiesPath, resolver, addressResolver);
37 identityKeyStore.addLegacyIdentities(identities);
38 removeIdentityFiles(identitiesPath);
39 }
40
41 static final Pattern identityFileNamePattern = Pattern.compile("(\\d+)");
42
43 private static List<IdentityInfo> getIdentities(
44 final File identitiesPath, final RecipientResolver resolver, final RecipientAddressResolver addressResolver
45 ) {
46 final var files = identitiesPath.listFiles();
47 if (files == null) {
48 return List.of();
49 }
50 return Arrays.stream(files)
51 .filter(f -> identityFileNamePattern.matcher(f.getName()).matches())
52 .map(f -> resolver.resolveRecipient(Long.parseLong(f.getName())))
53 .filter(Objects::nonNull)
54 .map(recipientId -> loadIdentityLocked(recipientId, addressResolver, identitiesPath))
55 .filter(Objects::nonNull)
56 .toList();
57 }
58
59 private static File getIdentityFile(final RecipientId recipientId, final File identitiesPath) {
60 try {
61 IOUtils.createPrivateDirectories(identitiesPath);
62 } catch (IOException e) {
63 throw new AssertionError("Failed to create identities path", e);
64 }
65 return new File(identitiesPath, String.valueOf(recipientId.id()));
66 }
67
68 private static IdentityInfo loadIdentityLocked(
69 final RecipientId recipientId, RecipientAddressResolver addressResolver, final File identitiesPath
70 ) {
71 final var file = getIdentityFile(recipientId, identitiesPath);
72 if (!file.exists()) {
73 return null;
74 }
75 final var address = addressResolver.resolveRecipientAddress(recipientId);
76 if (address.serviceId().isEmpty()) {
77 return null;
78 }
79 try (var inputStream = new FileInputStream(file)) {
80 var storage = objectMapper.readValue(inputStream, IdentityStorage.class);
81
82 var id = new IdentityKey(Base64.getDecoder().decode(storage.identityKey()));
83 var trustLevel = TrustLevel.fromInt(storage.trustLevel());
84 var added = storage.addedTimestamp();
85
86 final var serviceId = address.serviceId().get();
87 return new IdentityInfo(serviceId.toString(), id, trustLevel, added);
88 } catch (IOException | InvalidKeyException e) {
89 logger.warn("Failed to load identity key: {}", e.getMessage());
90 return null;
91 }
92 }
93
94 private static void removeIdentityFiles(File identitiesPath) {
95 final var files = identitiesPath.listFiles();
96 if (files == null) {
97 return;
98 }
99
100 for (var file : files) {
101 try {
102 Files.delete(file.toPath());
103 } catch (IOException e) {
104 logger.error("Failed to delete identity file {}: {}", file, e.getMessage());
105 }
106 }
107 try {
108 Files.delete(identitiesPath.toPath());
109 } catch (IOException e) {
110 logger.error("Failed to delete identity directory {}: {}", identitiesPath, e.getMessage());
111 }
112 }
113
114 public record IdentityStorage(String identityKey, int trustLevel, long addedTimestamp) {}
115 }