1 package org
.asamk
.signal
.manager
.util
;
3 import com
.google
.protobuf
.InvalidProtocolBufferException
;
5 import org
.asamk
.signal
.manager
.api
.Pair
;
6 import org
.asamk
.signal
.manager
.api
.Profile
;
7 import org
.signal
.libsignal
.protocol
.IdentityKey
;
8 import org
.signal
.libsignal
.protocol
.InvalidKeyException
;
9 import org
.signal
.libsignal
.protocol
.ecc
.ECPublicKey
;
10 import org
.signal
.libsignal
.zkgroup
.profiles
.ProfileKey
;
11 import org
.slf4j
.Logger
;
12 import org
.slf4j
.LoggerFactory
;
13 import org
.whispersystems
.signalservice
.api
.crypto
.InvalidCiphertextException
;
14 import org
.whispersystems
.signalservice
.api
.crypto
.ProfileCipher
;
15 import org
.whispersystems
.signalservice
.api
.profiles
.SignalServiceProfile
;
16 import org
.whispersystems
.signalservice
.internal
.push
.SignalServiceProtos
;
18 import java
.io
.IOException
;
19 import java
.util
.Base64
;
20 import java
.util
.HashSet
;
22 public class ProfileUtils
{
24 private final static Logger logger
= LoggerFactory
.getLogger(ProfileUtils
.class);
26 public static Profile
decryptProfile(
27 final ProfileKey profileKey
, final SignalServiceProfile encryptedProfile
29 var profileCipher
= new ProfileCipher(profileKey
);
30 IdentityKey identityKey
= null;
32 identityKey
= new IdentityKey(Base64
.getDecoder().decode(encryptedProfile
.getIdentityKey()), 0);
33 } catch (InvalidKeyException e
) {
34 logger
.debug("Failed to decode identity key in profile, can't verify payment address", e
);
38 var name
= decrypt(encryptedProfile
.getName(), profileCipher
);
39 var about
= trimZeros(decrypt(encryptedProfile
.getAbout(), profileCipher
));
40 var aboutEmoji
= trimZeros(decrypt(encryptedProfile
.getAboutEmoji(), profileCipher
));
42 final var nameParts
= splitName(name
);
43 return new Profile(System
.currentTimeMillis(),
48 encryptedProfile
.getAvatar(),
49 identityKey
== null || encryptedProfile
.getPaymentAddress() == null
51 : decryptAndVerifyMobileCoinAddress(encryptedProfile
.getPaymentAddress(),
53 identityKey
.getPublicKey()),
54 getUnidentifiedAccessMode(encryptedProfile
, profileCipher
),
55 getCapabilities(encryptedProfile
));
56 } catch (InvalidCiphertextException e
) {
57 logger
.debug("Failed to decrypt profile for {}", encryptedProfile
.getServiceId(), e
);
62 public static Profile
.UnidentifiedAccessMode
getUnidentifiedAccessMode(
63 final SignalServiceProfile encryptedProfile
, final ProfileCipher profileCipher
65 if (encryptedProfile
.isUnrestrictedUnidentifiedAccess()) {
66 return Profile
.UnidentifiedAccessMode
.UNRESTRICTED
;
69 if (encryptedProfile
.getUnidentifiedAccess() != null && profileCipher
!= null) {
70 final var unidentifiedAccessVerifier
= Base64
.getDecoder().decode(encryptedProfile
.getUnidentifiedAccess());
71 if (profileCipher
.verifyUnidentifiedAccess(unidentifiedAccessVerifier
)) {
72 return Profile
.UnidentifiedAccessMode
.ENABLED
;
76 return Profile
.UnidentifiedAccessMode
.DISABLED
;
79 public static HashSet
<Profile
.Capability
> getCapabilities(final SignalServiceProfile encryptedProfile
) {
80 final var capabilities
= new HashSet
<Profile
.Capability
>();
81 if (encryptedProfile
.getCapabilities().isGv1Migration()) {
82 capabilities
.add(Profile
.Capability
.gv1Migration
);
84 if (encryptedProfile
.getCapabilities().isStorage()) {
85 capabilities
.add(Profile
.Capability
.storage
);
87 if (encryptedProfile
.getCapabilities().isSenderKey()) {
88 capabilities
.add(Profile
.Capability
.senderKey
);
90 if (encryptedProfile
.getCapabilities().isAnnouncementGroup()) {
91 capabilities
.add(Profile
.Capability
.announcementGroup
);
97 private static String
decrypt(
98 final String encryptedName
, final ProfileCipher profileCipher
99 ) throws InvalidCiphertextException
{
101 return encryptedName
== null
103 : new String(profileCipher
.decrypt(Base64
.getDecoder().decode(encryptedName
)));
104 } catch (IllegalArgumentException e
) {
109 private static byte[] decryptAndVerifyMobileCoinAddress(
110 final byte[] encryptedPaymentAddress
, final ProfileCipher profileCipher
, final ECPublicKey publicKey
111 ) throws InvalidCiphertextException
{
114 decrypted
= profileCipher
.decryptWithLength(encryptedPaymentAddress
);
115 } catch (IOException e
) {
116 logger
.debug("Failed to decrypt payment address", e
);
120 SignalServiceProtos
.PaymentAddress paymentAddress
;
122 paymentAddress
= SignalServiceProtos
.PaymentAddress
.parseFrom(decrypted
);
123 } catch (InvalidProtocolBufferException e
) {
124 logger
.debug("Failed to parse payment address", e
);
128 return PaymentUtils
.verifyPaymentsAddress(paymentAddress
, publicKey
);
131 private static Pair
<String
, String
> splitName(String name
) {
133 return new Pair
<>(null, null);
135 String
[] parts
= name
.split("\0");
137 return switch (parts
.length
) {
138 case 0 -> new Pair
<>(null, null);
139 case 1 -> new Pair
<>(parts
[0], null);
140 default -> new Pair
<>(parts
[0], parts
[1]);
144 static String
trimZeros(String str
) {
149 int pos
= str
.indexOf(0);
150 return pos
== -1 ? str
: str
.substring(0, pos
);