Support for v2 registration lock

This commit is contained in:
Moxie Marlinspike
2019-06-07 15:19:11 -07:00
parent 4fdbe9b9ff
commit 11902dec3c
22 changed files with 538 additions and 127 deletions

View File

@@ -156,6 +156,11 @@ public class WhisperServerConfiguration extends Configuration {
@JsonProperty
private RecaptchaConfiguration recaptcha;
@Valid
@NotNull
@JsonProperty
private SecureStorageServiceConfiguration storageService;
private Map<String, String> transparentDataIndex = new HashMap<>();
public RecaptchaConfiguration getRecaptchaConfiguration() {
@@ -194,6 +199,10 @@ public class WhisperServerConfiguration extends Configuration {
return directory;
}
public SecureStorageServiceConfiguration getSecureStorageServiceConfiguration() {
return storageService;
}
public AccountDatabaseCrawlerConfiguration getAccountDatabaseCrawlerConfiguration() {
return accountDatabaseCrawler;
}

View File

@@ -29,7 +29,7 @@ import org.jdbi.v3.core.Jdbi;
import org.whispersystems.dispatch.DispatchManager;
import org.whispersystems.textsecuregcm.auth.AccountAuthenticator;
import org.whispersystems.textsecuregcm.auth.CertificateGenerator;
import org.whispersystems.textsecuregcm.auth.DirectoryCredentialsGenerator;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialGenerator;
import org.whispersystems.textsecuregcm.auth.DisabledPermittedAccount;
import org.whispersystems.textsecuregcm.auth.DisabledPermittedAccountAuthenticator;
import org.whispersystems.textsecuregcm.auth.TurnTokenGenerator;
@@ -44,6 +44,7 @@ import org.whispersystems.textsecuregcm.controllers.KeysController;
import org.whispersystems.textsecuregcm.controllers.MessageController;
import org.whispersystems.textsecuregcm.controllers.ProfileController;
import org.whispersystems.textsecuregcm.controllers.ProvisioningController;
import org.whispersystems.textsecuregcm.controllers.SecureStorageController;
import org.whispersystems.textsecuregcm.controllers.TransparentDataController;
import org.whispersystems.textsecuregcm.controllers.VoiceVerificationController;
import org.whispersystems.textsecuregcm.limits.RateLimiters;
@@ -204,6 +205,12 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
AccountAuthenticator accountAuthenticator = new AccountAuthenticator(accountsManager);
DisabledPermittedAccountAuthenticator disabledPermittedAccountAuthenticator = new DisabledPermittedAccountAuthenticator(accountsManager);
ExternalServiceCredentialGenerator directoryCredentialsGenerator = new ExternalServiceCredentialGenerator(config.getDirectoryConfiguration().getDirectoryClientConfiguration().getUserAuthenticationTokenSharedSecret(),
config.getDirectoryConfiguration().getDirectoryClientConfiguration().getUserAuthenticationTokenUserIdSecret(),
true);
ExternalServiceCredentialGenerator storageCredentialsGenerator = new ExternalServiceCredentialGenerator(config.getSecureStorageServiceConfiguration().getUserAuthenticationTokenSharedSecret(), new byte[0], false);
ApnFallbackManager apnFallbackManager = new ApnFallbackManager(pushSchedulerClient, apnSender, accountsManager);
TwilioSmsSender twilioSmsSender = new TwilioSmsSender(config.getTwilioConfiguration());
SmsSender smsSender = new SmsSender(twilioSmsSender);
@@ -212,8 +219,7 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
TurnTokenGenerator turnTokenGenerator = new TurnTokenGenerator(config.getTurnConfiguration());
RecaptchaClient recaptchaClient = new RecaptchaClient(config.getRecaptchaConfiguration().getSecret());
DirectoryCredentialsGenerator directoryCredentialsGenerator = new DirectoryCredentialsGenerator(config.getDirectoryConfiguration().getDirectoryClientConfiguration().getUserAuthenticationTokenSharedSecret(),
config.getDirectoryConfiguration().getDirectoryClientConfiguration().getUserAuthenticationTokenUserIdSecret());
DirectoryReconciliationClient directoryReconciliationClient = new DirectoryReconciliationClient(config.getDirectoryConfiguration().getDirectoryServerConfiguration());
ActiveUserCounter activeUserCounter = new ActiveUserCounter(config.getMetricsFactory(), cacheClient);
@@ -247,13 +253,14 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
DisabledPermittedAccount.class, disabledPermittedAccountAuthFilter)));
environment.jersey().register(new PolymorphicAuthValueFactoryProvider.Binder<>(ImmutableSet.of(Account.class, DisabledPermittedAccount.class)));
environment.jersey().register(new AccountController(pendingAccountsManager, accountsManager, abusiveHostRules, rateLimiters, smsSender, directoryQueue, messagesManager, turnTokenGenerator, config.getTestDevices(), recaptchaClient, gcmSender, apnSender));
environment.jersey().register(new AccountController(pendingAccountsManager, accountsManager, abusiveHostRules, rateLimiters, smsSender, directoryQueue, messagesManager, turnTokenGenerator, config.getTestDevices(), recaptchaClient, gcmSender, apnSender, storageCredentialsGenerator));
environment.jersey().register(new DeviceController(pendingDevicesManager, accountsManager, messagesManager, directoryQueue, rateLimiters, config.getMaxDevices()));
environment.jersey().register(new DirectoryController(rateLimiters, directory, directoryCredentialsGenerator));
environment.jersey().register(new ProvisioningController(rateLimiters, pushSender));
environment.jersey().register(new CertificateController(new CertificateGenerator(config.getDeliveryCertificate().getCertificate(), config.getDeliveryCertificate().getPrivateKey(), config.getDeliveryCertificate().getExpiresDays())));
environment.jersey().register(new VoiceVerificationController(config.getVoiceVerificationConfiguration().getUrl(), config.getVoiceVerificationConfiguration().getLocales()));
environment.jersey().register(new TransparentDataController(accountsManager, config.getTransparentDataIndex()));
environment.jersey().register(new SecureStorageController(storageCredentialsGenerator));
environment.jersey().register(attachmentControllerV1);
environment.jersey().register(attachmentControllerV2);
environment.jersey().register(keysController);

View File

@@ -1,4 +1,4 @@
/**
/*
* Copyright (C) 2013 Open WhisperSystems
*
* This program is free software: you can redistribute it and/or modify
@@ -17,18 +17,14 @@
package org.whispersystems.textsecuregcm.auth;
import org.apache.commons.codec.binary.Hex;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
public class AuthenticationCredentials {
private final Logger logger = LoggerFactory.getLogger(AuthenticationCredentials.class);
private final String hashedAuthenticationToken;
private final String salt;
@@ -38,7 +34,7 @@ public class AuthenticationCredentials {
}
public AuthenticationCredentials(String authenticationToken) {
this.salt = Math.abs(new SecureRandom().nextInt()) + "";
this.salt = String.valueOf(Math.abs(new SecureRandom().nextInt()));
this.hashedAuthenticationToken = getHashedValue(salt, authenticationToken);
}
@@ -52,19 +48,13 @@ public class AuthenticationCredentials {
public boolean verify(String authenticationToken) {
String theirValue = getHashedValue(salt, authenticationToken);
logger.debug("Comparing: " + theirValue + " , " + this.hashedAuthenticationToken);
return theirValue.equals(this.hashedAuthenticationToken);
return MessageDigest.isEqual(theirValue.getBytes(StandardCharsets.UTF_8), this.hashedAuthenticationToken.getBytes(StandardCharsets.UTF_8));
}
private static String getHashedValue(String salt, String token) {
Logger logger = LoggerFactory.getLogger(AuthenticationCredentials.class);
logger.debug("Getting hashed token: " + salt + " , " + token);
try {
return new String(Hex.encodeHex(MessageDigest.getInstance("SHA1").digest((salt + token).getBytes("UTF-8"))));
} catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
return new String(Hex.encodeHex(MessageDigest.getInstance("SHA1").digest((salt + token).getBytes(StandardCharsets.UTF_8))));
} catch (NoSuchAlgorithmException e) {
throw new AssertionError(e);
}
}

View File

@@ -1,6 +1,5 @@
package org.whispersystems.textsecuregcm.auth;
import com.google.common.base.Optional;
import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Hex;
import org.slf4j.Logger;
@@ -14,27 +13,29 @@ import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.concurrent.TimeUnit;
public class DirectoryCredentialsGenerator {
public class ExternalServiceCredentialGenerator {
private final Logger logger = LoggerFactory.getLogger(DirectoryCredentialsGenerator.class);
private final Logger logger = LoggerFactory.getLogger(ExternalServiceCredentialGenerator.class);
private final byte[] key;
private final byte[] userIdKey;
private final boolean usernameDerivation;
public DirectoryCredentialsGenerator(byte[] key, byte[] userIdKey) {
this.key = key;
this.userIdKey = userIdKey;
public ExternalServiceCredentialGenerator(byte[] key, byte[] userIdKey, boolean usernameDerivation) {
this.key = key;
this.userIdKey = userIdKey;
this.usernameDerivation = usernameDerivation;
}
public DirectoryCredentials generateFor(String number) {
public ExternalServiceCredentials generateFor(String number) {
Mac mac = getMacInstance();
String username = getUserId(number, mac);
String username = getUserId(number, mac, usernameDerivation);
long currentTimeSeconds = System.currentTimeMillis() / 1000;
String prefix = username + ":" + currentTimeSeconds;
String output = Hex.encodeHexString(Util.truncate(getHmac(key, prefix.getBytes(), mac), 10));
String token = prefix + ":" + output;
return new DirectoryCredentials(username, token);
return new ExternalServiceCredentials(username, token);
}
@@ -46,7 +47,7 @@ public class DirectoryCredentialsGenerator {
return false;
}
if (!getUserId(number, mac).equals(parts[0])) {
if (!getUserId(number, mac, usernameDerivation).equals(parts[0])) {
return false;
}
@@ -57,8 +58,9 @@ public class DirectoryCredentialsGenerator {
return isValidSignature(parts[0] + ":" + parts[1], parts[2], mac);
}
private String getUserId(String number, Mac mac) {
return Hex.encodeHexString(Util.truncate(getHmac(userIdKey, number.getBytes(), mac), 10));
private String getUserId(String number, Mac mac, boolean usernameDerivation) {
if (usernameDerivation) return Hex.encodeHexString(Util.truncate(getHmac(userIdKey, number.getBytes(), mac), 10));
else return number;
}
private boolean isValidTime(String timeString, long currentTimeMillis) {

View File

@@ -3,7 +3,7 @@ package org.whispersystems.textsecuregcm.auth;
import com.fasterxml.jackson.annotation.JsonProperty;
public class DirectoryCredentials {
public class ExternalServiceCredentials {
@JsonProperty
private String username;
@@ -11,12 +11,12 @@ public class DirectoryCredentials {
@JsonProperty
private String password;
public DirectoryCredentials(String username, String password) {
public ExternalServiceCredentials(String username, String password) {
this.username = username;
this.password = password;
}
public DirectoryCredentials() {}
public ExternalServiceCredentials() {}
public String getUsername() {
return username;

View File

@@ -0,0 +1,18 @@
package org.whispersystems.textsecuregcm.configuration;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Hex;
import org.hibernate.validator.constraints.NotEmpty;
public class SecureStorageServiceConfiguration {
@NotEmpty
@JsonProperty
private String userAuthenticationTokenSharedSecret;
public byte[] getUserAuthenticationTokenSharedSecret() throws DecoderException {
return Hex.decodeHex(userAuthenticationTokenSharedSecret.toCharArray());
}
}

View File

@@ -26,6 +26,8 @@ import org.slf4j.LoggerFactory;
import org.whispersystems.textsecuregcm.auth.AuthenticationCredentials;
import org.whispersystems.textsecuregcm.auth.AuthorizationHeader;
import org.whispersystems.textsecuregcm.auth.DisabledPermittedAccount;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialGenerator;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentials;
import org.whispersystems.textsecuregcm.auth.InvalidAuthorizationHeaderException;
import org.whispersystems.textsecuregcm.auth.StoredVerificationCode;
import org.whispersystems.textsecuregcm.auth.TurnToken;
@@ -34,6 +36,7 @@ import org.whispersystems.textsecuregcm.entities.AccountAttributes;
import org.whispersystems.textsecuregcm.entities.ApnRegistrationId;
import org.whispersystems.textsecuregcm.entities.DeviceName;
import org.whispersystems.textsecuregcm.entities.GcmRegistrationId;
import org.whispersystems.textsecuregcm.entities.DeprecatedPin;
import org.whispersystems.textsecuregcm.entities.RegistrationLock;
import org.whispersystems.textsecuregcm.entities.RegistrationLockFailure;
import org.whispersystems.textsecuregcm.limits.RateLimiters;
@@ -95,18 +98,19 @@ public class AccountController {
private final Meter captchaFailureMeter = metricRegistry.meter(name(AccountController.class, "captcha_failure" ));
private final PendingAccountsManager pendingAccounts;
private final AccountsManager accounts;
private final AbusiveHostRules abusiveHostRules;
private final RateLimiters rateLimiters;
private final SmsSender smsSender;
private final DirectoryQueue directoryQueue;
private final MessagesManager messagesManager;
private final TurnTokenGenerator turnTokenGenerator;
private final Map<String, Integer> testDevices;
private final RecaptchaClient recaptchaClient;
private final GCMSender gcmSender;
private final APNSender apnSender;
private final PendingAccountsManager pendingAccounts;
private final AccountsManager accounts;
private final AbusiveHostRules abusiveHostRules;
private final RateLimiters rateLimiters;
private final SmsSender smsSender;
private final DirectoryQueue directoryQueue;
private final MessagesManager messagesManager;
private final TurnTokenGenerator turnTokenGenerator;
private final Map<String, Integer> testDevices;
private final RecaptchaClient recaptchaClient;
private final GCMSender gcmSender;
private final APNSender apnSender;
private final ExternalServiceCredentialGenerator storageServiceCredentialGenerator;
public AccountController(PendingAccountsManager pendingAccounts,
AccountsManager accounts,
@@ -119,20 +123,22 @@ public class AccountController {
Map<String, Integer> testDevices,
RecaptchaClient recaptchaClient,
GCMSender gcmSender,
APNSender apnSender)
APNSender apnSender,
ExternalServiceCredentialGenerator storageServiceCredentialGenerator)
{
this.pendingAccounts = pendingAccounts;
this.accounts = accounts;
this.abusiveHostRules = abusiveHostRules;
this.rateLimiters = rateLimiters;
this.smsSender = smsSenderFactory;
this.directoryQueue = directoryQueue;
this.messagesManager = messagesManager;
this.testDevices = testDevices;
this.turnTokenGenerator = turnTokenGenerator;
this.recaptchaClient = recaptchaClient;
this.gcmSender = gcmSender;
this.apnSender = apnSender;
this.pendingAccounts = pendingAccounts;
this.accounts = accounts;
this.abusiveHostRules = abusiveHostRules;
this.rateLimiters = rateLimiters;
this.smsSender = smsSenderFactory;
this.directoryQueue = directoryQueue;
this.messagesManager = messagesManager;
this.testDevices = testDevices;
this.turnTokenGenerator = turnTokenGenerator;
this.recaptchaClient = recaptchaClient;
this.gcmSender = gcmSender;
this.apnSender = apnSender;
this.storageServiceCredentialGenerator = storageServiceCredentialGenerator;
}
@Timed
@@ -260,25 +266,42 @@ public class AccountController {
Optional<Account> existingAccount = accounts.get(number);
if (existingAccount.isPresent() &&
existingAccount.get().getPin().isPresent() &&
if (existingAccount.isPresent() &&
(existingAccount.get().getPin().isPresent() || existingAccount.get().getRegistrationLock().isPresent()) &&
System.currentTimeMillis() - existingAccount.get().getLastSeen() < TimeUnit.DAYS.toMillis(7))
{
rateLimiters.getVerifyLimiter().clear(number);
long timeRemaining = TimeUnit.DAYS.toMillis(7) - (System.currentTimeMillis() - existingAccount.get().getLastSeen());
long timeRemaining = TimeUnit.DAYS.toMillis(7) - (System.currentTimeMillis() - existingAccount.get().getLastSeen());
Optional<ExternalServiceCredentials> credentials = existingAccount.get().getRegistrationLock().isPresent() &&
existingAccount.get().getRegistrationLockSalt().isPresent() ?
Optional.of(storageServiceCredentialGenerator.generateFor(number)) :
Optional.empty();
if (accountAttributes.getPin() == null) {
if (Util.isEmpty(accountAttributes.getPin()) &&
Util.isEmpty(accountAttributes.getRegistrationLock()))
{
throw new WebApplicationException(Response.status(423)
.entity(new RegistrationLockFailure(timeRemaining))
.entity(new RegistrationLockFailure(timeRemaining, credentials.orElse(null)))
.build());
}
rateLimiters.getPinLimiter().validate(number);
if (!MessageDigest.isEqual(existingAccount.get().getPin().get().getBytes(), accountAttributes.getPin().getBytes())) {
boolean pinMatches;
if (existingAccount.get().getRegistrationLock().isPresent() && existingAccount.get().getRegistrationLockSalt().isPresent()) {
pinMatches = new AuthenticationCredentials(existingAccount.get().getRegistrationLock().get(),
existingAccount.get().getRegistrationLockSalt().get()).verify(accountAttributes.getRegistrationLock());
} else if (existingAccount.get().getPin().isPresent()) {
pinMatches = MessageDigest.isEqual(existingAccount.get().getPin().get().getBytes(), accountAttributes.getPin().getBytes());
} else {
throw new AssertionError("Invalid registration lock state");
}
if (!pinMatches) {
throw new WebApplicationException(Response.status(423)
.entity(new RegistrationLockFailure(timeRemaining))
.entity(new RegistrationLockFailure(timeRemaining, credentials.orElse(null)))
.build());
}
@@ -382,12 +405,37 @@ public class AccountController {
}
}
@Timed
@PUT
@Produces(MediaType.APPLICATION_JSON)
@Path("/registration_lock")
public void setRegistrationLock(@Auth Account account, @Valid RegistrationLock accountLock) {
AuthenticationCredentials credentials = new AuthenticationCredentials(accountLock.getRegistrationLock());
account.setRegistrationLock(credentials.getHashedAuthenticationToken());
account.setRegistrationLockSalt(credentials.getSalt());
account.setPin(null);
accounts.update(account);
}
@Timed
@DELETE
@Path("/registration_lock")
public void removeRegistrationLock(@Auth Account account) {
account.setRegistrationLock(null);
account.setRegistrationLockSalt(null);
accounts.update(account);
}
@Timed
@PUT
@Produces(MediaType.APPLICATION_JSON)
@Path("/pin/")
public void setPin(@Auth Account account, @Valid RegistrationLock accountLock) {
public void setPin(@Auth Account account, @Valid DeprecatedPin accountLock) {
account.setPin(accountLock.getPin());
account.setRegistrationLock(null);
account.setRegistrationLockSalt(null);
accounts.update(account);
}
@@ -436,7 +484,18 @@ public class AccountController {
device.setSignalingKey(attributes.getSignalingKey());
device.setUserAgent(userAgent);
account.setPin(attributes.getPin());
if (!Util.isEmpty(attributes.getPin())) {
account.setPin(attributes.getPin());
} else if (!Util.isEmpty(attributes.getRegistrationLock())) {
AuthenticationCredentials credentials = new AuthenticationCredentials(attributes.getRegistrationLock());
account.setRegistrationLock(credentials.getHashedAuthenticationToken());
account.setRegistrationLockSalt(credentials.getSalt());
} else {
account.setPin(null);
account.setRegistrationLock(null);
account.setRegistrationLockSalt(null);
}
account.setUnidentifiedAccessKey(attributes.getUnidentifiedAccessKey());
account.setUnrestrictedUnidentifiedAccess(attributes.isUnrestrictedUnidentifiedAccess());

View File

@@ -23,7 +23,7 @@ import com.codahale.metrics.SharedMetricRegistries;
import com.codahale.metrics.annotation.Timed;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.textsecuregcm.auth.DirectoryCredentialsGenerator;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialGenerator;
import org.whispersystems.textsecuregcm.entities.ClientContact;
import org.whispersystems.textsecuregcm.entities.ClientContactTokens;
import org.whispersystems.textsecuregcm.entities.ClientContacts;
@@ -86,17 +86,17 @@ public class DirectoryController {
}
}};
private final RateLimiters rateLimiters;
private final DirectoryManager directory;
private final DirectoryCredentialsGenerator userTokenGenerator;
private final RateLimiters rateLimiters;
private final DirectoryManager directory;
private final ExternalServiceCredentialGenerator directoryServiceTokenGenerator;
public DirectoryController(RateLimiters rateLimiters,
DirectoryManager directory,
DirectoryCredentialsGenerator userTokenGenerator)
ExternalServiceCredentialGenerator userTokenGenerator)
{
this.directory = directory;
this.rateLimiters = rateLimiters;
this.userTokenGenerator = userTokenGenerator;
this.directory = directory;
this.rateLimiters = rateLimiters;
this.directoryServiceTokenGenerator = userTokenGenerator;
}
@Timed
@@ -104,7 +104,7 @@ public class DirectoryController {
@Path("/auth")
@Produces(MediaType.APPLICATION_JSON)
public Response getAuthToken(@Auth Account account) {
return Response.ok().entity(userTokenGenerator.generateFor(account.getNumber())).build();
return Response.ok().entity(directoryServiceTokenGenerator.generateFor(account.getNumber())).build();
}
@PUT

View File

@@ -0,0 +1,31 @@
package org.whispersystems.textsecuregcm.controllers;
import com.codahale.metrics.annotation.Timed;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialGenerator;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentials;
import org.whispersystems.textsecuregcm.storage.Account;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import io.dropwizard.auth.Auth;
@Path("/v1/storage")
public class SecureStorageController {
private final ExternalServiceCredentialGenerator storageServiceCredentialGenerator;
public SecureStorageController(ExternalServiceCredentialGenerator storageServiceCredentialGenerator) {
this.storageServiceCredentialGenerator = storageServiceCredentialGenerator;
}
@Timed
@GET
@Path("/auth")
@Produces(MediaType.APPLICATION_JSON)
public ExternalServiceCredentials getAuth(@Auth Account account) {
return storageServiceCredentialGenerator.generateFor(account.getNumber());
}
}

View File

@@ -19,7 +19,6 @@ package org.whispersystems.textsecuregcm.entities;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.annotations.VisibleForTesting;
import org.hibernate.validator.constraints.Length;
import org.hibernate.validator.constraints.NotEmpty;
public class AccountAttributes {
@@ -36,15 +35,12 @@ public class AccountAttributes {
@Length(max = 204, message = "This field must be less than 50 characters")
private String name;
@JsonProperty
private boolean voice;
@JsonProperty
private boolean video;
@JsonProperty
private String pin;
@JsonProperty
private String registrationLock;
@JsonProperty
private byte[] unidentifiedAccessKey;
@@ -55,18 +51,17 @@ public class AccountAttributes {
@VisibleForTesting
public AccountAttributes(String signalingKey, boolean fetchesMessages, int registrationId, String pin) {
this(signalingKey, fetchesMessages, registrationId, null, false, false, pin);
this(signalingKey, fetchesMessages, registrationId, null, pin, null);
}
@VisibleForTesting
public AccountAttributes(String signalingKey, boolean fetchesMessages, int registrationId, String name, boolean voice, boolean video, String pin) {
this.signalingKey = signalingKey;
this.fetchesMessages = fetchesMessages;
this.registrationId = registrationId;
this.name = name;
this.voice = voice;
this.video = video;
this.pin = pin;
public AccountAttributes(String signalingKey, boolean fetchesMessages, int registrationId, String name, String pin, String registrationLock) {
this.signalingKey = signalingKey;
this.fetchesMessages = fetchesMessages;
this.registrationId = registrationId;
this.name = name;
this.pin = pin;
this.registrationLock = registrationLock;
}
public String getSignalingKey() {
@@ -85,18 +80,14 @@ public class AccountAttributes {
return name;
}
public boolean getVoice() {
return voice;
}
public boolean getVideo() {
return video;
}
public String getPin() {
return pin;
}
public String getRegistrationLock() {
return registrationLock;
}
public byte[] getUnidentifiedAccessKey() {
return unidentifiedAccessKey;
}

View File

@@ -0,0 +1,26 @@
package org.whispersystems.textsecuregcm.entities;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.annotations.VisibleForTesting;
import org.hibernate.validator.constraints.Length;
import org.hibernate.validator.constraints.NotEmpty;
public class DeprecatedPin {
@JsonProperty
@NotEmpty
@Length(min=4,max=20)
private String pin;
public DeprecatedPin() {}
@VisibleForTesting
public DeprecatedPin(String pin) {
this.pin = pin;
}
public String getPin() {
return pin;
}
}

View File

@@ -8,19 +8,19 @@ import org.hibernate.validator.constraints.NotEmpty;
public class RegistrationLock {
@JsonProperty
@Length(min=64,max=64)
@NotEmpty
@Length(min=4,max=20)
private String pin;
private String registrationLock;
public RegistrationLock() {}
@VisibleForTesting
public RegistrationLock(String pin) {
this.pin = pin;
public RegistrationLock(String registrationLock) {
this.registrationLock = registrationLock;
}
public String getPin() {
return pin;
public String getRegistrationLock() {
return registrationLock;
}
}

View File

@@ -2,20 +2,30 @@ package org.whispersystems.textsecuregcm.entities;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentials;
public class RegistrationLockFailure {
@JsonProperty
private long timeRemaining;
@JsonProperty
private ExternalServiceCredentials storageCredentials;
public RegistrationLockFailure() {}
public RegistrationLockFailure(long timeRemaining) {
this.timeRemaining = timeRemaining;
public RegistrationLockFailure(long timeRemaining, ExternalServiceCredentials storageCredentials) {
this.timeRemaining = timeRemaining;
this.storageCredentials = storageCredentials;
}
@JsonIgnore
public long getTimeRemaining() {
return timeRemaining;
}
@JsonIgnore
public ExternalServiceCredentials getStorageCredentials() {
return storageCredentials;
}
}

View File

@@ -53,6 +53,12 @@ public class Account implements Principal {
@JsonProperty
private String pin;
@JsonProperty
private String registrationLock;
@JsonProperty
private String registrationLockSalt;
@JsonProperty("uak")
private byte[] unidentifiedAccessKey;
@@ -209,6 +215,22 @@ public class Account implements Principal {
this.pin = pin;
}
public void setRegistrationLock(String registrationLock) {
this.registrationLock = registrationLock;
}
public Optional<String> getRegistrationLock() {
return Optional.ofNullable(registrationLock);
}
public void setRegistrationLockSalt(String registrationLockSalt) {
this.registrationLockSalt = registrationLockSalt;
}
public Optional<String> getRegistrationLockSalt() {
return Optional.ofNullable(registrationLockSalt);
}
public Optional<byte[]> getUnidentifiedAccessKey() {
return Optional.ofNullable(unidentifiedAccessKey);
}
@@ -238,4 +260,5 @@ public class Account implements Principal {
public boolean implies(Subject subject) {
return false;
}
}