]> nmode's Git Repositories - signal-cli/blob - lib/src/main/java/org/asamk/signal/manager/helper/PinHelper.java
c734323ff092c94a44a8313ff11956176bd96faa
[signal-cli] / lib / src / main / java / org / asamk / signal / manager / helper / PinHelper.java
1 package org.asamk.signal.manager.helper;
2
3 import org.asamk.signal.manager.api.IncorrectPinException;
4 import org.asamk.signal.manager.api.Pair;
5 import org.signal.libsignal.protocol.InvalidKeyException;
6 import org.slf4j.Logger;
7 import org.slf4j.LoggerFactory;
8 import org.whispersystems.signalservice.api.KeyBackupService;
9 import org.whispersystems.signalservice.api.KeyBackupServicePinException;
10 import org.whispersystems.signalservice.api.SvrNoDataException;
11 import org.whispersystems.signalservice.api.SvrPinData;
12 import org.whispersystems.signalservice.api.kbs.MasterKey;
13 import org.whispersystems.signalservice.api.kbs.PinHashUtil;
14 import org.whispersystems.signalservice.internal.contacts.crypto.UnauthenticatedResponseException;
15 import org.whispersystems.signalservice.internal.contacts.entities.TokenResponse;
16 import org.whispersystems.signalservice.internal.push.AuthCredentials;
17 import org.whispersystems.signalservice.internal.push.LockedException;
18
19 import java.io.IOException;
20 import java.util.Collection;
21 import java.util.stream.Stream;
22
23 public class PinHelper {
24
25 private final static Logger logger = LoggerFactory.getLogger(PinHelper.class);
26
27 private final KeyBackupService keyBackupService;
28 private final Collection<KeyBackupService> fallbackKeyBackupServices;
29
30 public PinHelper(
31 final KeyBackupService keyBackupService, final Collection<KeyBackupService> fallbackKeyBackupServices
32 ) {
33 this.keyBackupService = keyBackupService;
34 this.fallbackKeyBackupServices = fallbackKeyBackupServices;
35 }
36
37 public void setRegistrationLockPin(
38 String pin, MasterKey masterKey
39 ) throws IOException {
40 final var pinChangeSession = keyBackupService.newPinChangeSession();
41 final var hashedPin = PinHashUtil.hashPin(pin, pinChangeSession.hashSalt());
42
43 try {
44 pinChangeSession.setPin(hashedPin, masterKey);
45 } catch (UnauthenticatedResponseException e) {
46 throw new IOException(e);
47 }
48 pinChangeSession.enableRegistrationLock(masterKey);
49 }
50
51 public void migrateRegistrationLockPin(String pin, MasterKey masterKey) throws IOException {
52 setRegistrationLockPin(pin, masterKey);
53
54 for (final var keyBackupService : fallbackKeyBackupServices) {
55 try {
56 final var pinChangeSession = keyBackupService.newPinChangeSession();
57 pinChangeSession.removePin();
58 } catch (Exception e) {
59 logger.warn("Failed to remove PIN from fallback KBS: {}", e.getMessage());
60 }
61 }
62 }
63
64 public void removeRegistrationLockPin() throws IOException {
65 final var pinChangeSession = keyBackupService.newPinChangeSession();
66 pinChangeSession.disableRegistrationLock();
67 try {
68 pinChangeSession.removePin();
69 } catch (UnauthenticatedResponseException e) {
70 throw new IOException(e);
71 }
72 }
73
74 public SvrPinData getRegistrationLockData(
75 String pin, LockedException e
76 ) throws IOException, IncorrectPinException {
77 var basicStorageCredentials = e.getSvr1Credentials();
78 if (basicStorageCredentials == null) {
79 return null;
80 }
81
82 try {
83 return getRegistrationLockData(pin, basicStorageCredentials);
84 } catch (SvrNoDataException ex) {
85 throw new IOException(e);
86 } catch (KeyBackupServicePinException ex) {
87 throw new IncorrectPinException(ex.getTriesRemaining());
88 }
89 }
90
91 private SvrPinData getRegistrationLockData(
92 String pin, AuthCredentials authCredentials
93 ) throws IOException, SvrNoDataException, KeyBackupServicePinException {
94 final var basicStorageCredentials = authCredentials.asBasic();
95 var tokenResponsePair = getTokenResponse(basicStorageCredentials);
96 final var tokenResponse = tokenResponsePair.first();
97 final var keyBackupService = tokenResponsePair.second();
98
99 var registrationLockData = restoreMasterKey(pin, basicStorageCredentials, tokenResponse, keyBackupService);
100 if (registrationLockData == null) {
101 throw new AssertionError("Failed to restore master key");
102 }
103 return registrationLockData;
104 }
105
106 private Pair<TokenResponse, KeyBackupService> getTokenResponse(String basicStorageCredentials) throws IOException {
107 final var keyBackupServices = Stream.concat(Stream.of(keyBackupService), fallbackKeyBackupServices.stream())
108 .toList();
109 for (final var keyBackupService : keyBackupServices) {
110 var tokenResponse = keyBackupService.getToken(basicStorageCredentials);
111 if (tokenResponse != null && tokenResponse.getTries() > 0) {
112 return new Pair<>(tokenResponse, keyBackupService);
113 }
114 }
115 throw new IOException("KBS Account locked, maximum pin attempts reached.");
116 }
117
118 private SvrPinData restoreMasterKey(
119 String pin,
120 String basicStorageCredentials,
121 TokenResponse tokenResponse,
122 final KeyBackupService keyBackupService
123 ) throws IOException, SvrNoDataException, KeyBackupServicePinException {
124 if (pin == null) return null;
125
126 if (basicStorageCredentials == null) {
127 throw new AssertionError("Cannot restore KBS key, no storage credentials supplied");
128 }
129
130 var session = keyBackupService.newRegistrationSession(basicStorageCredentials, tokenResponse);
131
132 try {
133 var hashedPin = PinHashUtil.hashPin(pin, session.hashSalt());
134 var kbsData = session.restorePin(hashedPin);
135 if (kbsData == null) {
136 throw new AssertionError("Null not expected");
137 }
138 return kbsData;
139 } catch (UnauthenticatedResponseException | InvalidKeyException e) {
140 throw new IOException(e);
141 }
142 }
143 }