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
.storage
.recipients
.RecipientId
;
7 import org
.asamk
.signal
.manager
.storage
.recipients
.RecipientResolver
;
8 import org
.asamk
.signal
.manager
.util
.IOUtils
;
9 import org
.signal
.libsignal
.protocol
.IdentityKey
;
10 import org
.signal
.libsignal
.protocol
.InvalidKeyException
;
11 import org
.slf4j
.Logger
;
12 import org
.slf4j
.LoggerFactory
;
15 import java
.io
.FileInputStream
;
16 import java
.io
.IOException
;
17 import java
.nio
.file
.Files
;
18 import java
.util
.Arrays
;
19 import java
.util
.Base64
;
20 import java
.util
.List
;
21 import java
.util
.Objects
;
22 import java
.util
.regex
.Pattern
;
24 public class LegacyIdentityKeyStore
{
26 private final static Logger logger
= LoggerFactory
.getLogger(LegacyIdentityKeyStore
.class);
27 private static final ObjectMapper objectMapper
= org
.asamk
.signal
.manager
.storage
.Utils
.createStorageObjectMapper();
29 public static void migrate(
30 final File identitiesPath
, final RecipientResolver resolver
, final IdentityKeyStore identityKeyStore
32 final var identities
= getIdentities(identitiesPath
, resolver
);
33 identityKeyStore
.addLegacyIdentities(identities
);
34 removeIdentityFiles(identitiesPath
);
37 static final Pattern identityFileNamePattern
= Pattern
.compile("(\\d+)");
39 private static List
<IdentityInfo
> getIdentities(final File identitiesPath
, final RecipientResolver resolver
) {
40 final var files
= identitiesPath
.listFiles();
44 return Arrays
.stream(files
)
45 .filter(f
-> identityFileNamePattern
.matcher(f
.getName()).matches())
46 .map(f
-> resolver
.resolveRecipient(Long
.parseLong(f
.getName())))
47 .filter(Objects
::nonNull
)
48 .map(recipientId
-> loadIdentityLocked(recipientId
, identitiesPath
))
49 .filter(Objects
::nonNull
)
53 private static File
getIdentityFile(final RecipientId recipientId
, final File identitiesPath
) {
55 IOUtils
.createPrivateDirectories(identitiesPath
);
56 } catch (IOException e
) {
57 throw new AssertionError("Failed to create identities path", e
);
59 return new File(identitiesPath
, String
.valueOf(recipientId
.id()));
62 private static IdentityInfo
loadIdentityLocked(final RecipientId recipientId
, final File identitiesPath
) {
63 final var file
= getIdentityFile(recipientId
, identitiesPath
);
67 try (var inputStream
= new FileInputStream(file
)) {
68 var storage
= objectMapper
.readValue(inputStream
, IdentityStorage
.class);
70 var id
= new IdentityKey(Base64
.getDecoder().decode(storage
.identityKey()));
71 var trustLevel
= TrustLevel
.fromInt(storage
.trustLevel());
72 var added
= storage
.addedTimestamp();
74 return new IdentityInfo(recipientId
, id
, trustLevel
, added
);
75 } catch (IOException
| InvalidKeyException e
) {
76 logger
.warn("Failed to load identity key: {}", e
.getMessage());
81 private static void removeIdentityFiles(File identitiesPath
) {
82 final var files
= identitiesPath
.listFiles();
87 for (var file
: files
) {
89 Files
.delete(file
.toPath());
90 } catch (IOException e
) {
91 logger
.error("Failed to delete identity file {}: {}", file
, e
.getMessage());
95 Files
.delete(identitiesPath
.toPath());
96 } catch (IOException e
) {
97 logger
.error("Failed to delete identity directory {}: {}", identitiesPath
, e
.getMessage());
101 private record IdentityStorage(String identityKey
, int trustLevel
, long addedTimestamp
) {}