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 final static 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 try (var inputStream
= new FileInputStream(file
)) {
76 var storage
= objectMapper
.readValue(inputStream
, IdentityStorage
.class);
78 var id
= new IdentityKey(Base64
.getDecoder().decode(storage
.identityKey()));
79 var trustLevel
= TrustLevel
.fromInt(storage
.trustLevel());
80 var added
= storage
.addedTimestamp();
82 final var serviceId
= addressResolver
.resolveRecipientAddress(recipientId
).getServiceId();
83 return new IdentityInfo(serviceId
, id
, trustLevel
, added
);
84 } catch (IOException
| InvalidKeyException e
) {
85 logger
.warn("Failed to load identity key: {}", e
.getMessage());
90 private static void removeIdentityFiles(File identitiesPath
) {
91 final var files
= identitiesPath
.listFiles();
96 for (var file
: files
) {
98 Files
.delete(file
.toPath());
99 } catch (IOException e
) {
100 logger
.error("Failed to delete identity file {}: {}", file
, e
.getMessage());
104 Files
.delete(identitiesPath
.toPath());
105 } catch (IOException e
) {
106 logger
.error("Failed to delete identity directory {}: {}", identitiesPath
, e
.getMessage());
110 private record IdentityStorage(String identityKey
, int trustLevel
, long addedTimestamp
) {}