Support for UUID based addressing

This commit is contained in:
Moxie Marlinspike
2019-06-20 19:25:15 -07:00
parent 0f8cb7ea6d
commit 7a3a385569
51 changed files with 1379 additions and 695 deletions

View File

@@ -33,6 +33,7 @@ import org.whispersystems.textsecuregcm.auth.StoredVerificationCode;
import org.whispersystems.textsecuregcm.auth.TurnToken;
import org.whispersystems.textsecuregcm.auth.TurnTokenGenerator;
import org.whispersystems.textsecuregcm.entities.AccountAttributes;
import org.whispersystems.textsecuregcm.entities.AccountCreationResult;
import org.whispersystems.textsecuregcm.entities.ApnRegistrationId;
import org.whispersystems.textsecuregcm.entities.DeviceName;
import org.whispersystems.textsecuregcm.entities.GcmRegistrationId;
@@ -78,6 +79,7 @@ import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import static com.codahale.metrics.MetricRegistry.name;
@@ -245,17 +247,21 @@ public class AccountController {
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@Path("/code/{verification_code}")
public void verifyAccount(@PathParam("verification_code") String verificationCode,
@HeaderParam("Authorization") String authorizationHeader,
@HeaderParam("X-Signal-Agent") String userAgent,
@Valid AccountAttributes accountAttributes)
public AccountCreationResult verifyAccount(@PathParam("verification_code") String verificationCode,
@HeaderParam("Authorization") String authorizationHeader,
@HeaderParam("X-Signal-Agent") String userAgent,
@Valid AccountAttributes accountAttributes)
throws RateLimitExceededException
{
try {
AuthorizationHeader header = AuthorizationHeader.fromFullHeader(authorizationHeader);
String number = header.getNumber();
String number = header.getIdentifier().getNumber();
String password = header.getPassword();
if (number == null) {
throw new WebApplicationException(400);
}
rateLimiters.getVerifyLimiter().validate(number);
Optional<StoredVerificationCode> storedVerificationCode = pendingAccounts.getCodeForNumber(number);
@@ -308,9 +314,11 @@ public class AccountController {
rateLimiters.getPinLimiter().clear(number);
}
createAccount(number, password, userAgent, accountAttributes);
Account account = createAccount(number, password, userAgent, accountAttributes);
metricRegistry.meter(name(AccountController.class, "verify", Util.getCountryCode(number))).mark();
return new AccountCreationResult(account.getUuid());
} catch (InvalidAuthorizationHeaderException e) {
logger.info("Bad Authorization Header", e);
throw new WebApplicationException(Response.status(401).build());
@@ -502,6 +510,13 @@ public class AccountController {
accounts.update(account);
}
@GET
@Path("/whoami")
@Produces(MediaType.APPLICATION_JSON)
public AccountCreationResult whoAmI(@Auth Account account) {
return new AccountCreationResult(account.getUuid());
}
private CaptchaRequirement requiresCaptcha(String number, String transport, String forwardedFor,
String requester,
Optional<String> captchaToken,
@@ -576,7 +591,7 @@ public class AccountController {
return false;
}
private void createAccount(String number, String password, String userAgent, AccountAttributes accountAttributes) {
private Account createAccount(String number, String password, String userAgent, AccountAttributes accountAttributes) {
Device device = new Device();
device.setId(Device.MASTER_ID);
device.setAuthenticationCredentials(new AuthenticationCredentials(password));
@@ -591,6 +606,7 @@ public class AccountController {
Account account = new Account();
account.setNumber(number);
account.setUuid(UUID.randomUUID());
account.addDevice(device);
account.setPin(accountAttributes.getPin());
account.setUnidentifiedAccessKey(accountAttributes.getUnidentifiedAccessKey());
@@ -608,6 +624,8 @@ public class AccountController {
messagesManager.clear(number);
pendingAccounts.remove(number);
return account;
}
@VisibleForTesting protected

View File

@@ -164,9 +164,11 @@ public class DeviceController {
{
try {
AuthorizationHeader header = AuthorizationHeader.fromFullHeader(authorizationHeader);
String number = header.getNumber();
String number = header.getIdentifier().getNumber();
String password = header.getPassword();
if (number == null) throw new WebApplicationException(400);
rateLimiters.getVerifyDeviceLimiter().validate(number);
Optional<StoredVerificationCode> storedVerificationCode = pendingDevices.getCodeForNumber(number);

View File

@@ -19,6 +19,7 @@ package org.whispersystems.textsecuregcm.controllers;
import com.codahale.metrics.annotation.Timed;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.textsecuregcm.auth.AmbiguousIdentifier;
import org.whispersystems.textsecuregcm.auth.Anonymous;
import org.whispersystems.textsecuregcm.auth.DisabledPermittedAccount;
import org.whispersystems.textsecuregcm.auth.OptionalAccess;
@@ -115,11 +116,11 @@ public class KeysController {
@Timed
@GET
@Path("/{number}/{device_id}")
@Path("/{identifier}/{device_id}")
@Produces(MediaType.APPLICATION_JSON)
public Optional<PreKeyResponse> getDeviceKeys(@Auth Optional<Account> account,
@HeaderParam(OptionalAccess.UNIDENTIFIED) Optional<Anonymous> accessKey,
@PathParam("number") String number,
@PathParam("identifier") AmbiguousIdentifier targetName,
@PathParam("device_id") String deviceId)
throws RateLimitExceededException
{
@@ -127,13 +128,13 @@ public class KeysController {
throw new WebApplicationException(Response.Status.UNAUTHORIZED);
}
Optional<Account> target = accounts.get(number);
Optional<Account> target = accounts.get(targetName);
OptionalAccess.verify(account, accessKey, target, deviceId);
assert(target.isPresent());
if (account.isPresent()) {
rateLimiters.getPreKeysLimiter().validate(account.get().getNumber() + "__" + number + "." + deviceId);
rateLimiters.getPreKeysLimiter().validate(account.get().getNumber() + "__" + target.get().getNumber() + "." + deviceId);
}
List<KeyRecord> targetKeys = getLocalKeys(target.get(), deviceId);

View File

@@ -23,6 +23,7 @@ import com.codahale.metrics.annotation.Timed;
import com.google.protobuf.ByteString;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.textsecuregcm.auth.AmbiguousIdentifier;
import org.whispersystems.textsecuregcm.auth.Anonymous;
import org.whispersystems.textsecuregcm.auth.OptionalAccess;
import org.whispersystems.textsecuregcm.entities.IncomingMessage;
@@ -109,7 +110,7 @@ public class MessageController {
@Produces(MediaType.APPLICATION_JSON)
public SendMessageResponse sendMessage(@Auth Optional<Account> source,
@HeaderParam(OptionalAccess.UNIDENTIFIED) Optional<Anonymous> accessKey,
@PathParam("destination") String destinationName,
@PathParam("destination") AmbiguousIdentifier destinationName,
@Valid IncomingMessageList messages)
throws RateLimitExceededException
{
@@ -117,18 +118,18 @@ public class MessageController {
throw new WebApplicationException(Response.Status.UNAUTHORIZED);
}
if (source.isPresent() && !source.get().getNumber().equals(destinationName)) {
if (source.isPresent() && !source.get().isFor(destinationName)) {
rateLimiters.getMessagesLimiter().validate(source.get().getNumber() + "__" + destinationName);
}
if (source.isPresent() && !source.get().getNumber().equals(destinationName)) {
if (source.isPresent() && !source.get().isFor(destinationName)) {
identifiedMeter.mark();
} else {
} else if (!source.isPresent()) {
unidentifiedMeter.mark();
}
try {
boolean isSyncMessage = source.isPresent() && source.get().getNumber().equals(destinationName);
boolean isSyncMessage = source.isPresent() && source.get().isFor(destinationName);
Optional<Account> destination;
@@ -246,6 +247,7 @@ public class MessageController {
if (source.isPresent()) {
messageBuilder.setSource(source.get().getNumber())
.setSourceUuid(source.get().getUuid().toString())
.setSourceDevice((int)source.get().getAuthenticatedDevice().get().getId());
}

View File

@@ -10,6 +10,7 @@ import com.codahale.metrics.annotation.Timed;
import org.apache.commons.codec.binary.Base64;
import org.hibernate.validator.constraints.Length;
import org.hibernate.validator.valuehandling.UnwrapValidatedValue;
import org.whispersystems.textsecuregcm.auth.AmbiguousIdentifier;
import org.whispersystems.textsecuregcm.auth.Anonymous;
import org.whispersystems.textsecuregcm.auth.OptionalAccess;
import org.whispersystems.textsecuregcm.auth.UnidentifiedAccessChecksum;
@@ -79,10 +80,10 @@ public class ProfileController {
@Timed
@GET
@Produces(MediaType.APPLICATION_JSON)
@Path("/{number}")
@Path("/{identifier}")
public Profile getProfile(@Auth Optional<Account> requestAccount,
@HeaderParam(OptionalAccess.UNIDENTIFIED) Optional<Anonymous> accessKey,
@PathParam("number") String number,
@PathParam("identifier") AmbiguousIdentifier identifier,
@QueryParam("ca") boolean useCaCertificate)
throws RateLimitExceededException
{
@@ -94,7 +95,7 @@ public class ProfileController {
rateLimiters.getProfileLimiter().validate(requestAccount.get().getNumber());
}
Optional<Account> accountProfile = accountsManager.get(number);
Optional<Account> accountProfile = accountsManager.get(identifier);
OptionalAccess.verify(requestAccount, accessKey, accountProfile);
//noinspection ConstantConditions,OptionalGetWithoutIsPresent

View File

@@ -1,42 +0,0 @@
package org.whispersystems.textsecuregcm.controllers;
import org.whispersystems.textsecuregcm.storage.Account;
import org.whispersystems.textsecuregcm.storage.AccountsManager;
import org.whispersystems.textsecuregcm.storage.PublicAccount;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import java.util.Map;
import java.util.Optional;
@Path("/v1/transparency/")
public class TransparentDataController {
private final AccountsManager accountsManager;
private final Map<String, String> transparentDataIndex;
public TransparentDataController(AccountsManager accountsManager,
Map<String, String> transparentDataIndex)
{
this.accountsManager = accountsManager;
this.transparentDataIndex = transparentDataIndex;
}
@GET
@Path("/account/{id}")
@Produces(MediaType.APPLICATION_JSON)
public Optional<PublicAccount> getAccount(@PathParam("id") String id) {
String index = transparentDataIndex.get(id);
if (index != null) {
return accountsManager.get(index).map(PublicAccount::new);
}
return Optional.empty();
}
}