1 package org
.asamk
.signal
.manager
.helper
;
3 import org
.asamk
.signal
.manager
.api
.IncorrectPinException
;
4 import org
.asamk
.signal
.manager
.api
.Pair
;
5 import org
.asamk
.signal
.manager
.util
.PinHashing
;
6 import org
.signal
.libsignal
.protocol
.InvalidKeyException
;
7 import org
.slf4j
.Logger
;
8 import org
.slf4j
.LoggerFactory
;
9 import org
.whispersystems
.signalservice
.api
.KbsPinData
;
10 import org
.whispersystems
.signalservice
.api
.KeyBackupService
;
11 import org
.whispersystems
.signalservice
.api
.KeyBackupServicePinException
;
12 import org
.whispersystems
.signalservice
.api
.KeyBackupSystemNoDataException
;
13 import org
.whispersystems
.signalservice
.api
.kbs
.MasterKey
;
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
.LockedException
;
18 import java
.io
.IOException
;
19 import java
.util
.Collection
;
20 import java
.util
.stream
.Stream
;
22 public class PinHelper
{
24 private final static Logger logger
= LoggerFactory
.getLogger(PinHelper
.class);
26 private final KeyBackupService keyBackupService
;
27 private final Collection
<KeyBackupService
> fallbackKeyBackupServices
;
30 final KeyBackupService keyBackupService
, final Collection
<KeyBackupService
> fallbackKeyBackupServices
32 this.keyBackupService
= keyBackupService
;
33 this.fallbackKeyBackupServices
= fallbackKeyBackupServices
;
36 public void setRegistrationLockPin(
37 String pin
, MasterKey masterKey
38 ) throws IOException
{
39 final var pinChangeSession
= keyBackupService
.newPinChangeSession();
40 final var hashedPin
= PinHashing
.hashPin(pin
, pinChangeSession
);
43 pinChangeSession
.setPin(hashedPin
, masterKey
);
44 } catch (UnauthenticatedResponseException e
) {
45 throw new IOException(e
);
47 pinChangeSession
.enableRegistrationLock(masterKey
);
50 public void migrateRegistrationLockPin(String pin
, MasterKey masterKey
) throws IOException
{
51 setRegistrationLockPin(pin
, masterKey
);
53 for (final var keyBackupService
: fallbackKeyBackupServices
) {
55 final var pinChangeSession
= keyBackupService
.newPinChangeSession();
56 pinChangeSession
.removePin();
57 } catch (Exception e
) {
58 logger
.warn("Failed to remove PIN from fallback KBS: {}", e
.getMessage());
63 public void removeRegistrationLockPin() throws IOException
{
64 final var pinChangeSession
= keyBackupService
.newPinChangeSession();
65 pinChangeSession
.disableRegistrationLock();
67 pinChangeSession
.removePin();
68 } catch (UnauthenticatedResponseException e
) {
69 throw new IOException(e
);
73 public KbsPinData
getRegistrationLockData(
74 String pin
, LockedException e
75 ) throws IOException
, IncorrectPinException
{
76 var basicStorageCredentials
= e
.getBasicStorageCredentials();
77 if (basicStorageCredentials
== null) {
82 return getRegistrationLockData(pin
, basicStorageCredentials
);
83 } catch (KeyBackupSystemNoDataException ex
) {
84 throw new IOException(e
);
85 } catch (KeyBackupServicePinException ex
) {
86 throw new IncorrectPinException(ex
.getTriesRemaining());
90 private KbsPinData
getRegistrationLockData(
91 String pin
, String basicStorageCredentials
92 ) throws IOException
, KeyBackupSystemNoDataException
, KeyBackupServicePinException
{
93 var tokenResponsePair
= getTokenResponse(basicStorageCredentials
);
94 final var tokenResponse
= tokenResponsePair
.first();
95 final var keyBackupService
= tokenResponsePair
.second();
97 var registrationLockData
= restoreMasterKey(pin
, basicStorageCredentials
, tokenResponse
, keyBackupService
);
98 if (registrationLockData
== null) {
99 throw new AssertionError("Failed to restore master key");
101 return registrationLockData
;
104 private Pair
<TokenResponse
, KeyBackupService
> getTokenResponse(String basicStorageCredentials
) throws IOException
{
105 final var keyBackupServices
= Stream
.concat(Stream
.of(keyBackupService
), fallbackKeyBackupServices
.stream())
107 for (final var keyBackupService
: keyBackupServices
) {
108 var tokenResponse
= keyBackupService
.getToken(basicStorageCredentials
);
109 if (tokenResponse
!= null && tokenResponse
.getTries() > 0) {
110 return new Pair
<>(tokenResponse
, keyBackupService
);
113 throw new IOException("KBS Account locked, maximum pin attempts reached.");
116 private KbsPinData
restoreMasterKey(
118 String basicStorageCredentials
,
119 TokenResponse tokenResponse
,
120 final KeyBackupService keyBackupService
121 ) throws IOException
, KeyBackupSystemNoDataException
, KeyBackupServicePinException
{
122 if (pin
== null) return null;
124 if (basicStorageCredentials
== null) {
125 throw new AssertionError("Cannot restore KBS key, no storage credentials supplied");
128 var session
= keyBackupService
.newRegistrationSession(basicStorageCredentials
, tokenResponse
);
131 var hashedPin
= PinHashing
.hashPin(pin
, session
);
132 var kbsData
= session
.restorePin(hashedPin
);
133 if (kbsData
== null) {
134 throw new AssertionError("Null not expected");
137 } catch (UnauthenticatedResponseException
| InvalidKeyException e
) {
138 throw new IOException(e
);