]> nmode's Git Repositories - signal-cli/blob - lib/src/main/java/org/asamk/signal/manager/util/ProfileUtils.java
Implement configuration handling
[signal-cli] / lib / src / main / java / org / asamk / signal / manager / util / ProfileUtils.java
1 package org.asamk.signal.manager.util;
2
3 import org.asamk.signal.manager.storage.recipients.Profile;
4 import org.signal.zkgroup.profiles.ProfileKey;
5 import org.whispersystems.libsignal.util.Pair;
6 import org.whispersystems.signalservice.api.crypto.InvalidCiphertextException;
7 import org.whispersystems.signalservice.api.crypto.ProfileCipher;
8 import org.whispersystems.signalservice.api.profiles.SignalServiceProfile;
9
10 import java.util.Base64;
11 import java.util.HashSet;
12
13 public class ProfileUtils {
14
15 public static Profile decryptProfile(
16 final ProfileKey profileKey, final SignalServiceProfile encryptedProfile
17 ) {
18 var profileCipher = new ProfileCipher(profileKey);
19 try {
20 var name = decrypt(encryptedProfile.getName(), profileCipher);
21 var about = trimZeros(decrypt(encryptedProfile.getAbout(), profileCipher));
22 var aboutEmoji = trimZeros(decrypt(encryptedProfile.getAboutEmoji(), profileCipher));
23
24 final var nameParts = splitName(name);
25 return new Profile(System.currentTimeMillis(),
26 nameParts.first(),
27 nameParts.second(),
28 about,
29 aboutEmoji,
30 getUnidentifiedAccessMode(encryptedProfile, profileCipher),
31 getCapabilities(encryptedProfile));
32 } catch (InvalidCiphertextException e) {
33 return null;
34 }
35 }
36
37 public static Profile.UnidentifiedAccessMode getUnidentifiedAccessMode(
38 final SignalServiceProfile encryptedProfile, final ProfileCipher profileCipher
39 ) {
40 if (encryptedProfile.isUnrestrictedUnidentifiedAccess()) {
41 return Profile.UnidentifiedAccessMode.UNRESTRICTED;
42 }
43
44 if (encryptedProfile.getUnidentifiedAccess() != null && profileCipher != null) {
45 final var unidentifiedAccessVerifier = Base64.getDecoder().decode(encryptedProfile.getUnidentifiedAccess());
46 if (profileCipher.verifyUnidentifiedAccess(unidentifiedAccessVerifier)) {
47 return Profile.UnidentifiedAccessMode.ENABLED;
48 }
49 }
50
51 return Profile.UnidentifiedAccessMode.DISABLED;
52 }
53
54 public static HashSet<Profile.Capability> getCapabilities(final SignalServiceProfile encryptedProfile) {
55 final var capabilities = new HashSet<Profile.Capability>();
56 if (encryptedProfile.getCapabilities().isGv1Migration()) {
57 capabilities.add(Profile.Capability.gv1Migration);
58 }
59 if (encryptedProfile.getCapabilities().isGv2()) {
60 capabilities.add(Profile.Capability.gv2);
61 }
62 if (encryptedProfile.getCapabilities().isStorage()) {
63 capabilities.add(Profile.Capability.storage);
64 }
65 if (encryptedProfile.getCapabilities().isSenderKey()) {
66 capabilities.add(Profile.Capability.senderKey);
67 }
68 if (encryptedProfile.getCapabilities().isAnnouncementGroup()) {
69 capabilities.add(Profile.Capability.announcementGroup);
70 }
71
72 return capabilities;
73 }
74
75 private static String decrypt(
76 final String encryptedName, final ProfileCipher profileCipher
77 ) throws InvalidCiphertextException {
78 try {
79 return encryptedName == null
80 ? null
81 : new String(profileCipher.decrypt(Base64.getDecoder().decode(encryptedName)));
82 } catch (IllegalArgumentException e) {
83 return null;
84 }
85 }
86
87 private static Pair<String, String> splitName(String name) {
88 if (name == null) {
89 return new Pair<>(null, null);
90 }
91 String[] parts = name.split("\0");
92
93 switch (parts.length) {
94 case 0:
95 return new Pair<>(null, null);
96 case 1:
97 return new Pair<>(parts[0], null);
98 default:
99 return new Pair<>(parts[0], parts[1]);
100 }
101 }
102
103 static String trimZeros(String str) {
104 if (str == null) {
105 return null;
106 }
107
108 int pos = str.indexOf(0);
109 return pos == -1 ? str : str.substring(0, pos);
110 }
111 }