1 package org
.asamk
.signal
.manager
.storage
.identities
;
3 import com
.fasterxml
.jackson
.databind
.ObjectMapper
;
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
;
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
;
25 public class LegacyIdentityKeyStore
{
27 private static final Logger logger
= LoggerFactory
.getLogger(LegacyIdentityKeyStore
.class);
28 private static final ObjectMapper objectMapper
= org
.asamk
.signal
.manager
.storage
.Utils
.createStorageObjectMapper();
30 public static void migrate(
31 final File identitiesPath
,
32 final RecipientResolver resolver
,
33 final RecipientAddressResolver addressResolver
,
34 final IdentityKeyStore identityKeyStore
36 final var identities
= getIdentities(identitiesPath
, resolver
, addressResolver
);
37 identityKeyStore
.addLegacyIdentities(identities
);
38 removeIdentityFiles(identitiesPath
);
41 static final Pattern identityFileNamePattern
= Pattern
.compile("(\\d+)");
43 private static List
<IdentityInfo
> getIdentities(
44 final File identitiesPath
, final RecipientResolver resolver
, final RecipientAddressResolver addressResolver
46 final var files
= identitiesPath
.listFiles();
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
)
59 private static File
getIdentityFile(final RecipientId recipientId
, final File identitiesPath
) {
61 IOUtils
.createPrivateDirectories(identitiesPath
);
62 } catch (IOException e
) {
63 throw new AssertionError("Failed to create identities path", e
);
65 return new File(identitiesPath
, String
.valueOf(recipientId
.id()));
68 private static IdentityInfo
loadIdentityLocked(
69 final RecipientId recipientId
, RecipientAddressResolver addressResolver
, final File identitiesPath
71 final var file
= getIdentityFile(recipientId
, identitiesPath
);
75 final var address
= addressResolver
.resolveRecipientAddress(recipientId
);
76 if (address
.serviceId().isEmpty()) {
79 try (var inputStream
= new FileInputStream(file
)) {
80 var storage
= objectMapper
.readValue(inputStream
, IdentityStorage
.class);
82 var id
= new IdentityKey(Base64
.getDecoder().decode(storage
.identityKey()));
83 var trustLevel
= TrustLevel
.fromInt(storage
.trustLevel());
84 var added
= storage
.addedTimestamp();
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());
94 private static void removeIdentityFiles(File identitiesPath
) {
95 final var files
= identitiesPath
.listFiles();
100 for (var file
: files
) {
102 Files
.delete(file
.toPath());
103 } catch (IOException e
) {
104 logger
.error("Failed to delete identity file {}: {}", file
, e
.getMessage());
108 Files
.delete(identitiesPath
.toPath());
109 } catch (IOException e
) {
110 logger
.error("Failed to delete identity directory {}: {}", identitiesPath
, e
.getMessage());
114 public record IdentityStorage(String identityKey
, int trustLevel
, long addedTimestamp
) {}