]> nmode's Git Repositories - signal-cli/blob - lib/src/main/java/org/asamk/signal/manager/util/ProfileUtils.java
Update libsignal-service
[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.api.Pair;
4 import org.asamk.signal.manager.api.Profile;
5 import org.signal.libsignal.protocol.IdentityKey;
6 import org.signal.libsignal.protocol.InvalidKeyException;
7 import org.signal.libsignal.protocol.ecc.ECPublicKey;
8 import org.signal.libsignal.zkgroup.profiles.ProfileKey;
9 import org.slf4j.Logger;
10 import org.slf4j.LoggerFactory;
11 import org.whispersystems.signalservice.api.crypto.InvalidCiphertextException;
12 import org.whispersystems.signalservice.api.crypto.ProfileCipher;
13 import org.whispersystems.signalservice.api.profiles.SignalServiceProfile;
14 import org.whispersystems.signalservice.internal.push.PaymentAddress;
15
16 import java.io.IOException;
17 import java.util.Base64;
18 import java.util.HashSet;
19
20 public class ProfileUtils {
21
22 private static final Logger logger = LoggerFactory.getLogger(ProfileUtils.class);
23
24 public static Profile decryptProfile(
25 final ProfileKey profileKey, final SignalServiceProfile encryptedProfile
26 ) {
27 var profileCipher = new ProfileCipher(profileKey);
28 IdentityKey identityKey = null;
29 try {
30 identityKey = new IdentityKey(Base64.getDecoder().decode(encryptedProfile.getIdentityKey()), 0);
31 } catch (InvalidKeyException e) {
32 logger.debug("Failed to decode identity key in profile, can't verify payment address", e);
33 }
34
35 try {
36 var name = decrypt(encryptedProfile.getName(), profileCipher);
37 var about = trimZeros(decrypt(encryptedProfile.getAbout(), profileCipher));
38 var aboutEmoji = trimZeros(decrypt(encryptedProfile.getAboutEmoji(), profileCipher));
39
40 final var nameParts = splitName(name);
41 return new Profile(System.currentTimeMillis(),
42 nameParts.first(),
43 nameParts.second(),
44 about,
45 aboutEmoji,
46 encryptedProfile.getAvatar(),
47 identityKey == null || encryptedProfile.getPaymentAddress() == null
48 ? null
49 : decryptAndVerifyMobileCoinAddress(encryptedProfile.getPaymentAddress(),
50 profileCipher,
51 identityKey.getPublicKey()),
52 getUnidentifiedAccessMode(encryptedProfile, profileCipher),
53 getCapabilities(encryptedProfile));
54 } catch (InvalidCiphertextException e) {
55 logger.debug("Failed to decrypt profile for {}", encryptedProfile.getServiceId(), e);
56 return null;
57 }
58 }
59
60 public static Profile.UnidentifiedAccessMode getUnidentifiedAccessMode(
61 final SignalServiceProfile encryptedProfile, final ProfileCipher profileCipher
62 ) {
63 if (encryptedProfile.isUnrestrictedUnidentifiedAccess()) {
64 return Profile.UnidentifiedAccessMode.UNRESTRICTED;
65 }
66
67 if (encryptedProfile.getUnidentifiedAccess() != null && profileCipher != null) {
68 final var unidentifiedAccessVerifier = Base64.getDecoder().decode(encryptedProfile.getUnidentifiedAccess());
69 if (profileCipher.verifyUnidentifiedAccess(unidentifiedAccessVerifier)) {
70 return Profile.UnidentifiedAccessMode.ENABLED;
71 }
72 }
73
74 return Profile.UnidentifiedAccessMode.DISABLED;
75 }
76
77 public static HashSet<Profile.Capability> getCapabilities(final SignalServiceProfile encryptedProfile) {
78 final var capabilities = new HashSet<Profile.Capability>();
79 if (encryptedProfile.getCapabilities().isStorage()) {
80 capabilities.add(Profile.Capability.storage);
81 }
82
83 return capabilities;
84 }
85
86 private static String decrypt(
87 final String encryptedName, final ProfileCipher profileCipher
88 ) throws InvalidCiphertextException {
89 try {
90 return encryptedName == null
91 ? null
92 : new String(profileCipher.decrypt(Base64.getDecoder().decode(encryptedName)));
93 } catch (IllegalArgumentException e) {
94 return null;
95 }
96 }
97
98 private static byte[] decryptAndVerifyMobileCoinAddress(
99 final byte[] encryptedPaymentAddress, final ProfileCipher profileCipher, final ECPublicKey publicKey
100 ) throws InvalidCiphertextException {
101 byte[] decrypted;
102 try {
103 decrypted = profileCipher.decryptWithLength(encryptedPaymentAddress);
104 } catch (IOException e) {
105 logger.debug("Failed to decrypt payment address", e);
106 return null;
107 }
108
109 PaymentAddress paymentAddress;
110 try {
111 paymentAddress = PaymentAddress.ADAPTER.decode(decrypted);
112 } catch (IOException e) {
113 logger.debug("Failed to parse payment address", e);
114 return null;
115 }
116
117 return PaymentUtils.verifyPaymentsAddress(paymentAddress, publicKey);
118 }
119
120 private static Pair<String, String> splitName(String name) {
121 if (name == null) {
122 return new Pair<>(null, null);
123 }
124 String[] parts = name.split("\0");
125
126 return switch (parts.length) {
127 case 0 -> new Pair<>(null, null);
128 case 1 -> new Pair<>(parts[0], null);
129 default -> new Pair<>(parts[0], parts[1]);
130 };
131 }
132
133 static String trimZeros(String str) {
134 if (str == null) {
135 return null;
136 }
137
138 int pos = str.indexOf(0);
139 return pos == -1 ? str : str.substring(0, pos);
140 }
141 }