org.whispersystems.signalservice.api.crypto.SignalServiceCipher Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of signal-service-java Show documentation
Show all versions of signal-service-java Show documentation
Signal Service communication library for Java
The newest version!
/*
* Copyright (C) 2014-2016 Open Whisper Systems
*
* Licensed according to the LICENSE file in this repository.
*/
package org.whispersystems.signalservice.api.crypto;
import com.google.protobuf.ByteString;
import com.google.protobuf.InvalidProtocolBufferException;
import org.signal.libsignal.metadata.InvalidMetadataMessageException;
import org.signal.libsignal.metadata.InvalidMetadataVersionException;
import org.signal.libsignal.metadata.ProtocolDuplicateMessageException;
import org.signal.libsignal.metadata.ProtocolInvalidKeyException;
import org.signal.libsignal.metadata.ProtocolInvalidKeyIdException;
import org.signal.libsignal.metadata.ProtocolInvalidMessageException;
import org.signal.libsignal.metadata.ProtocolInvalidVersionException;
import org.signal.libsignal.metadata.ProtocolLegacyMessageException;
import org.signal.libsignal.metadata.ProtocolNoSessionException;
import org.signal.libsignal.metadata.ProtocolUntrustedIdentityException;
import org.signal.libsignal.metadata.SealedSessionCipher;
import org.signal.libsignal.metadata.SealedSessionCipher.DecryptionResult;
import org.signal.libsignal.metadata.SelfSendException;
import org.signal.libsignal.metadata.certificate.CertificateValidator;
import org.whispersystems.libsignal.DuplicateMessageException;
import org.whispersystems.libsignal.IdentityKey;
import org.whispersystems.libsignal.InvalidKeyException;
import org.whispersystems.libsignal.InvalidKeyIdException;
import org.whispersystems.libsignal.InvalidMessageException;
import org.whispersystems.libsignal.InvalidVersionException;
import org.whispersystems.libsignal.LegacyMessageException;
import org.whispersystems.libsignal.NoSessionException;
import org.whispersystems.libsignal.SessionCipher;
import org.whispersystems.libsignal.SignalProtocolAddress;
import org.whispersystems.libsignal.UntrustedIdentityException;
import org.whispersystems.libsignal.logging.Log;
import org.whispersystems.libsignal.protocol.CiphertextMessage;
import org.whispersystems.libsignal.protocol.PreKeySignalMessage;
import org.whispersystems.libsignal.protocol.SignalMessage;
import org.whispersystems.libsignal.state.SignalProtocolStore;
import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.api.messages.SignalServiceAttachment;
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer;
import org.whispersystems.signalservice.api.messages.SignalServiceContent;
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage.Preview;
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage.Sticker;
import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope;
import org.whispersystems.signalservice.api.messages.SignalServiceGroup;
import org.whispersystems.signalservice.api.messages.SignalServiceReceiptMessage;
import org.whispersystems.signalservice.api.messages.SignalServiceTypingMessage;
import org.whispersystems.signalservice.api.messages.calls.AnswerMessage;
import org.whispersystems.signalservice.api.messages.calls.BusyMessage;
import org.whispersystems.signalservice.api.messages.calls.HangupMessage;
import org.whispersystems.signalservice.api.messages.calls.IceUpdateMessage;
import org.whispersystems.signalservice.api.messages.calls.OfferMessage;
import org.whispersystems.signalservice.api.messages.calls.SignalServiceCallMessage;
import org.whispersystems.signalservice.api.messages.multidevice.BlockedListMessage;
import org.whispersystems.signalservice.api.messages.multidevice.ConfigurationMessage;
import org.whispersystems.signalservice.api.messages.multidevice.ReadMessage;
import org.whispersystems.signalservice.api.messages.multidevice.RequestMessage;
import org.whispersystems.signalservice.api.messages.multidevice.SentTranscriptMessage;
import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage;
import org.whispersystems.signalservice.api.messages.multidevice.StickerPackOperationMessage;
import org.whispersystems.signalservice.api.messages.multidevice.VerifiedMessage;
import org.whispersystems.signalservice.api.messages.multidevice.VerifiedMessage.VerifiedState;
import org.whispersystems.signalservice.api.messages.multidevice.ViewOnceOpenMessage;
import org.whispersystems.signalservice.api.messages.shared.SharedContact;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import org.whispersystems.signalservice.api.util.UuidUtil;
import org.whispersystems.signalservice.internal.push.OutgoingPushMessage;
import org.whispersystems.signalservice.internal.push.PushTransportDetails;
import org.whispersystems.signalservice.internal.push.SignalServiceProtos;
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.AttachmentPointer;
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.Content;
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.DataMessage;
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.Envelope.Type;
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.ReceiptMessage;
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.SyncMessage;
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.TypingMessage;
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.Verified;
import org.whispersystems.signalservice.internal.push.UnsupportedDataMessageException;
import org.whispersystems.util.Base64;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import static org.whispersystems.signalservice.internal.push.SignalServiceProtos.CallMessage;
import static org.whispersystems.signalservice.internal.push.SignalServiceProtos.GroupContext.Type.DELIVER;
/**
* This is used to decrypt received {@link SignalServiceEnvelope}s.
*
* @author Moxie Marlinspike
*/
public class SignalServiceCipher {
@SuppressWarnings("unused")
private static final String TAG = SignalServiceCipher.class.getSimpleName();
private final SignalProtocolStore signalProtocolStore;
private final SignalServiceAddress localAddress;
private final CertificateValidator certificateValidator;
public SignalServiceCipher(SignalServiceAddress localAddress,
SignalProtocolStore signalProtocolStore,
CertificateValidator certificateValidator)
{
this.signalProtocolStore = signalProtocolStore;
this.localAddress = localAddress;
this.certificateValidator = certificateValidator;
}
public OutgoingPushMessage encrypt(SignalProtocolAddress destination,
Optional unidentifiedAccess,
byte[] unpaddedMessage)
throws UntrustedIdentityException, InvalidKeyException
{
if (unidentifiedAccess.isPresent()) {
SealedSessionCipher sessionCipher = new SealedSessionCipher(signalProtocolStore, localAddress.getUuid().orNull(), localAddress.getNumber().orNull(), 1);
PushTransportDetails transportDetails = new PushTransportDetails(sessionCipher.getSessionVersion(destination));
byte[] ciphertext = sessionCipher.encrypt(destination, unidentifiedAccess.get().getUnidentifiedCertificate(), transportDetails.getPaddedMessageBody(unpaddedMessage));
String body = Base64.encodeBytes(ciphertext);
int remoteRegistrationId = sessionCipher.getRemoteRegistrationId(destination);
return new OutgoingPushMessage(Type.UNIDENTIFIED_SENDER_VALUE, destination.getDeviceId(), remoteRegistrationId, body);
} else {
SessionCipher sessionCipher = new SessionCipher(signalProtocolStore, destination);
PushTransportDetails transportDetails = new PushTransportDetails(sessionCipher.getSessionVersion());
CiphertextMessage message = sessionCipher.encrypt(transportDetails.getPaddedMessageBody(unpaddedMessage));
int remoteRegistrationId = sessionCipher.getRemoteRegistrationId();
String body = Base64.encodeBytes(message.serialize());
int type;
switch (message.getType()) {
case CiphertextMessage.PREKEY_TYPE: type = Type.PREKEY_BUNDLE_VALUE; break;
case CiphertextMessage.WHISPER_TYPE: type = Type.CIPHERTEXT_VALUE; break;
default: throw new AssertionError("Bad type: " + message.getType());
}
return new OutgoingPushMessage(type, destination.getDeviceId(), remoteRegistrationId, body);
}
}
/**
* Decrypt a received {@link SignalServiceEnvelope}
*
* @param envelope The received SignalServiceEnvelope
*
* @return a decrypted SignalServiceContent
*/
public SignalServiceContent decrypt(SignalServiceEnvelope envelope)
throws InvalidMetadataMessageException, InvalidMetadataVersionException,
ProtocolInvalidKeyIdException, ProtocolLegacyMessageException,
ProtocolUntrustedIdentityException, ProtocolNoSessionException,
ProtocolInvalidVersionException, ProtocolInvalidMessageException,
ProtocolInvalidKeyException, ProtocolDuplicateMessageException,
SelfSendException, UnsupportedDataMessageException
{
try {
if (envelope.hasLegacyMessage()) {
Plaintext plaintext = decrypt(envelope, envelope.getLegacyMessage());
DataMessage message = DataMessage.parseFrom(plaintext.getData());
return new SignalServiceContent(createSignalServiceMessage(plaintext.getMetadata(), message),
plaintext.getMetadata().getSender(),
plaintext.getMetadata().getSenderDevice(),
plaintext.getMetadata().getTimestamp(),
plaintext.getMetadata().isNeedsReceipt());
} else if (envelope.hasContent()) {
Plaintext plaintext = decrypt(envelope, envelope.getContent());
Content message = Content.parseFrom(plaintext.getData());
if (message.hasDataMessage()) {
return new SignalServiceContent(createSignalServiceMessage(plaintext.getMetadata(), message.getDataMessage()),
plaintext.getMetadata().getSender(),
plaintext.getMetadata().getSenderDevice(),
plaintext.getMetadata().getTimestamp(),
plaintext.getMetadata().isNeedsReceipt());
} else if (message.hasSyncMessage() && localAddress.matches(plaintext.getMetadata().getSender())) {
return new SignalServiceContent(createSynchronizeMessage(plaintext.getMetadata(), message.getSyncMessage()),
plaintext.getMetadata().getSender(),
plaintext.getMetadata().getSenderDevice(),
plaintext.getMetadata().getTimestamp(),
plaintext.getMetadata().isNeedsReceipt());
} else if (message.hasCallMessage()) {
return new SignalServiceContent(createCallMessage(message.getCallMessage()),
plaintext.getMetadata().getSender(),
plaintext.getMetadata().getSenderDevice(),
plaintext.getMetadata().getTimestamp(),
plaintext.getMetadata().isNeedsReceipt());
} else if (message.hasReceiptMessage()) {
return new SignalServiceContent(createReceiptMessage(plaintext.getMetadata(), message.getReceiptMessage()),
plaintext.getMetadata().getSender(),
plaintext.getMetadata().getSenderDevice(),
plaintext.getMetadata().getTimestamp(),
plaintext.getMetadata().isNeedsReceipt());
} else if (message.hasTypingMessage()) {
return new SignalServiceContent(createTypingMessage(plaintext.getMetadata(), message.getTypingMessage()),
plaintext.getMetadata().getSender(),
plaintext.getMetadata().getSenderDevice(),
plaintext.getMetadata().getTimestamp(),
false);
}
}
return null;
} catch (InvalidProtocolBufferException e) {
throw new InvalidMetadataMessageException(e);
}
}
private Plaintext decrypt(SignalServiceEnvelope envelope, byte[] ciphertext)
throws InvalidMetadataMessageException, InvalidMetadataVersionException,
ProtocolDuplicateMessageException, ProtocolUntrustedIdentityException,
ProtocolLegacyMessageException, ProtocolInvalidKeyException,
ProtocolInvalidVersionException, ProtocolInvalidMessageException,
ProtocolInvalidKeyIdException, ProtocolNoSessionException,
SelfSendException
{
try {
byte[] paddedMessage;
Metadata metadata;
int sessionVersion;
if (!envelope.hasSource() && !envelope.isUnidentifiedSender()) {
throw new ProtocolInvalidMessageException(new InvalidMessageException("Non-UD envelope is missing a source!"), null, 0);
}
if (envelope.isPreKeySignalMessage()) {
SignalProtocolAddress sourceAddress = getPreferredProtocolAddress(signalProtocolStore, envelope.getSourceAddress(), envelope.getSourceDevice());
SessionCipher sessionCipher = new SessionCipher(signalProtocolStore, sourceAddress);
paddedMessage = sessionCipher.decrypt(new PreKeySignalMessage(ciphertext));
metadata = new Metadata(envelope.getSourceAddress(), envelope.getSourceDevice(), envelope.getTimestamp(), false);
sessionVersion = sessionCipher.getSessionVersion();
} else if (envelope.isSignalMessage()) {
SignalProtocolAddress sourceAddress = getPreferredProtocolAddress(signalProtocolStore, envelope.getSourceAddress(), envelope.getSourceDevice());
SessionCipher sessionCipher = new SessionCipher(signalProtocolStore, sourceAddress);
paddedMessage = sessionCipher.decrypt(new SignalMessage(ciphertext));
metadata = new Metadata(envelope.getSourceAddress(), envelope.getSourceDevice(), envelope.getTimestamp(), false);
sessionVersion = sessionCipher.getSessionVersion();
} else if (envelope.isUnidentifiedSender()) {
SealedSessionCipher sealedSessionCipher = new SealedSessionCipher(signalProtocolStore, localAddress.getUuid().orNull(), localAddress.getNumber().orNull(), 1);
DecryptionResult result = sealedSessionCipher.decrypt(certificateValidator, ciphertext, envelope.getServerTimestamp());
SignalServiceAddress resultAddress = new SignalServiceAddress(UuidUtil.parse(result.getSenderUuid().orNull()), result.getSenderE164());
SignalProtocolAddress protocolAddress = getPreferredProtocolAddress(signalProtocolStore, resultAddress, result.getDeviceId());
paddedMessage = result.getPaddedMessage();
metadata = new Metadata(resultAddress, result.getDeviceId(), envelope.getTimestamp(), true);
sessionVersion = sealedSessionCipher.getSessionVersion(protocolAddress);
} else {
throw new InvalidMetadataMessageException("Unknown type: " + envelope.getType());
}
PushTransportDetails transportDetails = new PushTransportDetails(sessionVersion);
byte[] data = transportDetails.getStrippedPaddingMessageBody(paddedMessage);
return new Plaintext(metadata, data);
} catch (DuplicateMessageException e) {
throw new ProtocolDuplicateMessageException(e, envelope.getSourceIdentifier(), envelope.getSourceDevice());
} catch (LegacyMessageException e) {
throw new ProtocolLegacyMessageException(e, envelope.getSourceIdentifier(), envelope.getSourceDevice());
} catch (InvalidMessageException e) {
throw new ProtocolInvalidMessageException(e, envelope.getSourceIdentifier(), envelope.getSourceDevice());
} catch (InvalidKeyIdException e) {
throw new ProtocolInvalidKeyIdException(e, envelope.getSourceIdentifier(), envelope.getSourceDevice());
} catch (InvalidKeyException e) {
throw new ProtocolInvalidKeyException(e, envelope.getSourceIdentifier(), envelope.getSourceDevice());
} catch (UntrustedIdentityException e) {
throw new ProtocolUntrustedIdentityException(e, envelope.getSourceIdentifier(), envelope.getSourceDevice());
} catch (InvalidVersionException e) {
throw new ProtocolInvalidVersionException(e, envelope.getSourceIdentifier(), envelope.getSourceDevice());
} catch (NoSessionException e) {
throw new ProtocolNoSessionException(e, envelope.getSourceIdentifier(), envelope.getSourceDevice());
}
}
private static SignalProtocolAddress getPreferredProtocolAddress(SignalProtocolStore store, SignalServiceAddress address, int sourceDevice) {
SignalProtocolAddress uuidAddress = address.getUuid().isPresent() ? new SignalProtocolAddress(address.getUuid().get().toString(), sourceDevice) : null;
SignalProtocolAddress e164Address = address.getNumber().isPresent() ? new SignalProtocolAddress(address.getNumber().get(), sourceDevice) : null;
if (uuidAddress != null && store.containsSession(uuidAddress)) {
return uuidAddress;
} else if (e164Address != null && store.containsSession(e164Address)) {
return e164Address;
} else {
return new SignalProtocolAddress(address.getIdentifier(), sourceDevice);
}
}
private SignalServiceDataMessage createSignalServiceMessage(Metadata metadata, DataMessage content)
throws ProtocolInvalidMessageException, UnsupportedDataMessageException
{
SignalServiceGroup groupInfo = createGroupInfo(content);
List attachments = new LinkedList<>();
boolean endSession = ((content.getFlags() & DataMessage.Flags.END_SESSION_VALUE ) != 0);
boolean expirationUpdate = ((content.getFlags() & DataMessage.Flags.EXPIRATION_TIMER_UPDATE_VALUE) != 0);
boolean profileKeyUpdate = ((content.getFlags() & DataMessage.Flags.PROFILE_KEY_UPDATE_VALUE ) != 0);
SignalServiceDataMessage.Quote quote = createQuote(content);
List sharedContacts = createSharedContacts(content);
List previews = createPreviews(content);
Sticker sticker = createSticker(content);
if (content.getRequiredProtocolVersion() > DataMessage.ProtocolVersion.CURRENT.getNumber()) {
throw new UnsupportedDataMessageException(DataMessage.ProtocolVersion.CURRENT.getNumber(),
content.getRequiredProtocolVersion(),
metadata.getSender().getIdentifier(),
metadata.getSenderDevice(),
Optional.fromNullable(groupInfo));
}
for (AttachmentPointer pointer : content.getAttachmentsList()) {
attachments.add(createAttachmentPointer(pointer));
}
if (content.hasTimestamp() && content.getTimestamp() != metadata.getTimestamp()) {
throw new ProtocolInvalidMessageException(new InvalidMessageException("Timestamps don't match: " + content.getTimestamp() + " vs " + metadata.getTimestamp()),
metadata.getSender().getIdentifier(),
metadata.getSenderDevice());
}
return new SignalServiceDataMessage(metadata.getTimestamp(),
groupInfo,
attachments,
content.getBody(),
endSession,
content.getExpireTimer(),
expirationUpdate,
content.hasProfileKey() ? content.getProfileKey().toByteArray() : null,
profileKeyUpdate,
quote,
sharedContacts,
previews,
sticker,
content.getIsViewOnce());
}
private SignalServiceSyncMessage createSynchronizeMessage(Metadata metadata, SyncMessage content)
throws ProtocolInvalidMessageException, ProtocolInvalidKeyException, UnsupportedDataMessageException
{
if (content.hasSent()) {
SyncMessage.Sent sentContent = content.getSent();
SignalServiceDataMessage dataMessage = createSignalServiceMessage(metadata, sentContent.getMessage());
Optional address = SignalServiceAddress.isValidAddress(sentContent.getDestinationUuid(), sentContent.getDestinationE164())
? Optional.of(new SignalServiceAddress(UuidUtil.parseOrNull(sentContent.getDestinationUuid()), sentContent.getDestinationE164()))
: Optional.absent();
Map unidentifiedStatuses = new HashMap<>();
if (!address.isPresent() && !dataMessage.getGroupInfo().isPresent()) {
throw new ProtocolInvalidMessageException(new InvalidMessageException("SyncMessage missing both destination and group ID!"), null, 0);
}
for (SyncMessage.Sent.UnidentifiedDeliveryStatus status : sentContent.getUnidentifiedStatusList()) {
if (SignalServiceAddress.isValidAddress(status.getDestinationUuid(), status.getDestinationE164())) {
SignalServiceAddress recipient = new SignalServiceAddress(UuidUtil.parseOrNull(status.getDestinationUuid()), status.getDestinationE164());
unidentifiedStatuses.put(recipient, status.getUnidentified());
} else {
Log.w(TAG, "Encountered an invalid UnidentifiedDeliveryStatus in a SentTranscript! Ignoring.");
}
}
return SignalServiceSyncMessage.forSentTranscript(new SentTranscriptMessage(address,
sentContent.getTimestamp(),
dataMessage,
sentContent.getExpirationStartTimestamp(),
unidentifiedStatuses,
sentContent.getIsRecipientUpdate()));
}
if (content.hasRequest()) {
return SignalServiceSyncMessage.forRequest(new RequestMessage(content.getRequest()));
}
if (content.getReadList().size() > 0) {
List readMessages = new LinkedList<>();
for (SyncMessage.Read read : content.getReadList()) {
if (SignalServiceAddress.isValidAddress(read.getSenderUuid(), read.getSenderE164())) {
SignalServiceAddress address = new SignalServiceAddress(UuidUtil.parseOrNull(read.getSenderUuid()), read.getSenderE164());
readMessages.add(new ReadMessage(address, read.getTimestamp()));
} else {
Log.w(TAG, "Encountered an invalid ReadMessage! Ignoring.");
}
}
return SignalServiceSyncMessage.forRead(readMessages);
}
if (content.hasViewOnceOpen()) {
if (SignalServiceAddress.isValidAddress(content.getViewOnceOpen().getSenderUuid(), content.getViewOnceOpen().getSenderE164())) {
SignalServiceAddress address = new SignalServiceAddress(UuidUtil.parseOrNull(content.getViewOnceOpen().getSenderUuid()), content.getViewOnceOpen().getSenderE164());
ViewOnceOpenMessage timerRead = new ViewOnceOpenMessage(address, content.getViewOnceOpen().getTimestamp());
return SignalServiceSyncMessage.forViewOnceOpen(timerRead);
} else {
throw new ProtocolInvalidMessageException(new InvalidMessageException("ViewOnceOpen message has no sender!"), null, 0);
}
}
if (content.hasVerified()) {
if (SignalServiceAddress.isValidAddress(content.getVerified().getDestinationUuid(), content.getVerified().getDestinationE164())) {
try {
Verified verified = content.getVerified();
SignalServiceAddress destination = new SignalServiceAddress(UuidUtil.parseOrNull(verified.getDestinationUuid()), verified.getDestinationE164());
IdentityKey identityKey = new IdentityKey(verified.getIdentityKey().toByteArray(), 0);
VerifiedState verifiedState;
if (verified.getState() == Verified.State.DEFAULT) {
verifiedState = VerifiedState.DEFAULT;
} else if (verified.getState() == Verified.State.VERIFIED) {
verifiedState = VerifiedState.VERIFIED;
} else if (verified.getState() == Verified.State.UNVERIFIED) {
verifiedState = VerifiedState.UNVERIFIED;
} else {
throw new ProtocolInvalidMessageException(new InvalidMessageException("Unknown state: " + verified.getState().getNumber()),
metadata.getSender().getIdentifier(), metadata.getSenderDevice());
}
return SignalServiceSyncMessage.forVerified(new VerifiedMessage(destination, identityKey, verifiedState, System.currentTimeMillis()));
} catch (InvalidKeyException e) {
throw new ProtocolInvalidKeyException(e, metadata.getSender().getIdentifier(), metadata.getSenderDevice());
}
} else {
throw new ProtocolInvalidMessageException(new InvalidMessageException("Verified message has no sender!"), null, 0);
}
}
if (content.getStickerPackOperationList().size() > 0) {
List operations = new LinkedList<>();
for (SyncMessage.StickerPackOperation operation : content.getStickerPackOperationList()) {
byte[] packId = operation.hasPackId() ? operation.getPackId().toByteArray() : null;
byte[] packKey = operation.hasPackKey() ? operation.getPackKey().toByteArray() : null;
StickerPackOperationMessage.Type type = null;
if (operation.hasType()) {
switch (operation.getType()) {
case INSTALL: type = StickerPackOperationMessage.Type.INSTALL; break;
case REMOVE: type = StickerPackOperationMessage.Type.REMOVE; break;
}
}
operations.add(new StickerPackOperationMessage(packId, packKey, type));
}
return SignalServiceSyncMessage.forStickerPackOperations(operations);
}
if (content.hasBlocked()) {
List numbers = content.getBlocked().getNumbersList();
List uuids = content.getBlocked().getUuidsList();
List addresses = new ArrayList<>(numbers.size() + uuids.size());
List groupIds = new ArrayList<>(content.getBlocked().getGroupIdsList().size());
for (String e164 : numbers) {
Optional address = SignalServiceAddress.fromRaw(null, e164);
if (address.isPresent()) {
addresses.add(address.get());
}
}
for (String uuid : uuids) {
Optional address = SignalServiceAddress.fromRaw(uuid, null);
if (address.isPresent()) {
addresses.add(address.get());
}
}
for (ByteString groupId : content.getBlocked().getGroupIdsList()) {
groupIds.add(groupId.toByteArray());
}
return SignalServiceSyncMessage.forBlocked(new BlockedListMessage(addresses, groupIds));
}
if (content.hasConfiguration()) {
Boolean readReceipts = content.getConfiguration().hasReadReceipts() ? content.getConfiguration().getReadReceipts() : null;
Boolean unidentifiedDeliveryIndicators = content.getConfiguration().hasUnidentifiedDeliveryIndicators() ? content.getConfiguration().getUnidentifiedDeliveryIndicators() : null;
Boolean typingIndicators = content.getConfiguration().hasTypingIndicators() ? content.getConfiguration().getTypingIndicators() : null;
Boolean linkPreviews = content.getConfiguration().hasLinkPreviews() ? content.getConfiguration().getLinkPreviews() : null;
return SignalServiceSyncMessage.forConfiguration(new ConfigurationMessage(Optional.fromNullable(readReceipts),
Optional.fromNullable(unidentifiedDeliveryIndicators),
Optional.fromNullable(typingIndicators),
Optional.fromNullable(linkPreviews)));
}
if (content.hasFetchLatest() && content.getFetchLatest().hasType()) {
switch (content.getFetchLatest().getType()) {
case LOCAL_PROFILE: return SignalServiceSyncMessage.forFetchLatest(SignalServiceSyncMessage.FetchType.LOCAL_PROFILE);
case STORAGE_MANIFEST: return SignalServiceSyncMessage.forFetchLatest(SignalServiceSyncMessage.FetchType.STORAGE_MANIFEST);
}
}
return SignalServiceSyncMessage.empty();
}
private SignalServiceCallMessage createCallMessage(CallMessage content) {
if (content.hasOffer()) {
CallMessage.Offer offerContent = content.getOffer();
return SignalServiceCallMessage.forOffer(new OfferMessage(offerContent.getId(), offerContent.getDescription()));
} else if (content.hasAnswer()) {
CallMessage.Answer answerContent = content.getAnswer();
return SignalServiceCallMessage.forAnswer(new AnswerMessage(answerContent.getId(), answerContent.getDescription()));
} else if (content.getIceUpdateCount() > 0) {
List iceUpdates = new LinkedList<>();
for (CallMessage.IceUpdate iceUpdate : content.getIceUpdateList()) {
iceUpdates.add(new IceUpdateMessage(iceUpdate.getId(), iceUpdate.getSdpMid(), iceUpdate.getSdpMLineIndex(), iceUpdate.getSdp()));
}
return SignalServiceCallMessage.forIceUpdates(iceUpdates);
} else if (content.hasHangup()) {
CallMessage.Hangup hangup = content.getHangup();
return SignalServiceCallMessage.forHangup(new HangupMessage(hangup.getId()));
} else if (content.hasBusy()) {
CallMessage.Busy busy = content.getBusy();
return SignalServiceCallMessage.forBusy(new BusyMessage(busy.getId()));
}
return SignalServiceCallMessage.empty();
}
private SignalServiceReceiptMessage createReceiptMessage(Metadata metadata, ReceiptMessage content) {
SignalServiceReceiptMessage.Type type;
if (content.getType() == ReceiptMessage.Type.DELIVERY) type = SignalServiceReceiptMessage.Type.DELIVERY;
else if (content.getType() == ReceiptMessage.Type.READ) type = SignalServiceReceiptMessage.Type.READ;
else type = SignalServiceReceiptMessage.Type.UNKNOWN;
return new SignalServiceReceiptMessage(type, content.getTimestampList(), metadata.getTimestamp());
}
private SignalServiceTypingMessage createTypingMessage(Metadata metadata, TypingMessage content) throws ProtocolInvalidMessageException {
SignalServiceTypingMessage.Action action;
if (content.getAction() == TypingMessage.Action.STARTED) action = SignalServiceTypingMessage.Action.STARTED;
else if (content.getAction() == TypingMessage.Action.STOPPED) action = SignalServiceTypingMessage.Action.STOPPED;
else action = SignalServiceTypingMessage.Action.UNKNOWN;
if (content.hasTimestamp() && content.getTimestamp() != metadata.getTimestamp()) {
throw new ProtocolInvalidMessageException(new InvalidMessageException("Timestamps don't match: " + content.getTimestamp() + " vs " + metadata.getTimestamp()),
metadata.getSender().getIdentifier(),
metadata.getSenderDevice());
}
return new SignalServiceTypingMessage(action, content.getTimestamp(),
content.hasGroupId() ? Optional.of(content.getGroupId().toByteArray()) :
Optional.absent());
}
private SignalServiceDataMessage.Quote createQuote(DataMessage content) {
if (!content.hasQuote()) return null;
List attachments = new LinkedList<>();
for (DataMessage.Quote.QuotedAttachment attachment : content.getQuote().getAttachmentsList()) {
attachments.add(new SignalServiceDataMessage.Quote.QuotedAttachment(attachment.getContentType(),
attachment.getFileName(),
attachment.hasThumbnail() ? createAttachmentPointer(attachment.getThumbnail()) : null));
}
if (SignalServiceAddress.isValidAddress(content.getQuote().getAuthorUuid(), content.getQuote().getAuthorE164())) {
SignalServiceAddress address = new SignalServiceAddress(UuidUtil.parseOrNull(content.getQuote().getAuthorUuid()), content.getQuote().getAuthorE164());
return new SignalServiceDataMessage.Quote(content.getQuote().getId(),
address,
content.getQuote().getText(),
attachments);
} else {
Log.w(TAG, "Quote was missing an author! Returning null.");
return null;
}
}
private List createPreviews(DataMessage content) {
if (content.getPreviewCount() <= 0) return null;
List results = new LinkedList<>();
for (DataMessage.Preview preview : content.getPreviewList()) {
SignalServiceAttachment attachment = null;
if (preview.hasImage()) {
attachment = createAttachmentPointer(preview.getImage());
}
results.add(new Preview(preview.getUrl(),
preview.getTitle(),
Optional.fromNullable(attachment)));
}
return results;
}
private Sticker createSticker(DataMessage content) {
if (!content.hasSticker() ||
!content.getSticker().hasPackId() ||
!content.getSticker().hasPackKey() ||
!content.getSticker().hasStickerId() ||
!content.getSticker().hasData())
{
return null;
}
DataMessage.Sticker sticker = content.getSticker();
return new Sticker(sticker.getPackId().toByteArray(),
sticker.getPackKey().toByteArray(),
sticker.getStickerId(),
createAttachmentPointer(sticker.getData()));
}
private List createSharedContacts(DataMessage content) {
if (content.getContactCount() <= 0) return null;
List results = new LinkedList<>();
for (DataMessage.Contact contact : content.getContactList()) {
SharedContact.Builder builder = SharedContact.newBuilder()
.setName(SharedContact.Name.newBuilder()
.setDisplay(contact.getName().getDisplayName())
.setFamily(contact.getName().getFamilyName())
.setGiven(contact.getName().getGivenName())
.setMiddle(contact.getName().getMiddleName())
.setPrefix(contact.getName().getPrefix())
.setSuffix(contact.getName().getSuffix())
.build());
if (contact.getAddressCount() > 0) {
for (DataMessage.Contact.PostalAddress address : contact.getAddressList()) {
SharedContact.PostalAddress.Type type = SharedContact.PostalAddress.Type.HOME;
switch (address.getType()) {
case WORK: type = SharedContact.PostalAddress.Type.WORK; break;
case HOME: type = SharedContact.PostalAddress.Type.HOME; break;
case CUSTOM: type = SharedContact.PostalAddress.Type.CUSTOM; break;
}
builder.withAddress(SharedContact.PostalAddress.newBuilder()
.setCity(address.getCity())
.setCountry(address.getCountry())
.setLabel(address.getLabel())
.setNeighborhood(address.getNeighborhood())
.setPobox(address.getPobox())
.setPostcode(address.getPostcode())
.setRegion(address.getRegion())
.setStreet(address.getStreet())
.setType(type)
.build());
}
}
if (contact.getNumberCount() > 0) {
for (DataMessage.Contact.Phone phone : contact.getNumberList()) {
SharedContact.Phone.Type type = SharedContact.Phone.Type.HOME;
switch (phone.getType()) {
case HOME: type = SharedContact.Phone.Type.HOME; break;
case WORK: type = SharedContact.Phone.Type.WORK; break;
case MOBILE: type = SharedContact.Phone.Type.MOBILE; break;
case CUSTOM: type = SharedContact.Phone.Type.CUSTOM; break;
}
builder.withPhone(SharedContact.Phone.newBuilder()
.setLabel(phone.getLabel())
.setType(type)
.setValue(phone.getValue())
.build());
}
}
if (contact.getEmailCount() > 0) {
for (DataMessage.Contact.Email email : contact.getEmailList()) {
SharedContact.Email.Type type = SharedContact.Email.Type.HOME;
switch (email.getType()) {
case HOME: type = SharedContact.Email.Type.HOME; break;
case WORK: type = SharedContact.Email.Type.WORK; break;
case MOBILE: type = SharedContact.Email.Type.MOBILE; break;
case CUSTOM: type = SharedContact.Email.Type.CUSTOM; break;
}
builder.withEmail(SharedContact.Email.newBuilder()
.setLabel(email.getLabel())
.setType(type)
.setValue(email.getValue())
.build());
}
}
if (contact.hasAvatar()) {
builder.setAvatar(SharedContact.Avatar.newBuilder()
.withAttachment(createAttachmentPointer(contact.getAvatar().getAvatar()))
.withProfileFlag(contact.getAvatar().getIsProfile())
.build());
}
if (contact.hasOrganization()) {
builder.withOrganization(contact.getOrganization());
}
results.add(builder.build());
}
return results;
}
private SignalServiceAttachmentPointer createAttachmentPointer(AttachmentPointer pointer) {
return new SignalServiceAttachmentPointer(pointer.getId(),
pointer.getContentType(),
pointer.getKey().toByteArray(),
pointer.hasSize() ? Optional.of(pointer.getSize()) : Optional.absent(),
pointer.hasThumbnail() ? Optional.of(pointer.getThumbnail().toByteArray()): Optional.absent(),
pointer.getWidth(), pointer.getHeight(),
pointer.hasDigest() ? Optional.of(pointer.getDigest().toByteArray()) : Optional.absent(),
pointer.hasFileName() ? Optional.of(pointer.getFileName()) : Optional.absent(),
(pointer.getFlags() & AttachmentPointer.Flags.VOICE_MESSAGE_VALUE) != 0,
pointer.hasCaption() ? Optional.of(pointer.getCaption()) : Optional.absent(),
pointer.hasBlurHash() ? Optional.of(pointer.getBlurHash()) : Optional.absent());
}
private SignalServiceGroup createGroupInfo(DataMessage content) throws ProtocolInvalidMessageException {
if (!content.hasGroup()) return null;
SignalServiceGroup.Type type;
switch (content.getGroup().getType()) {
case DELIVER: type = SignalServiceGroup.Type.DELIVER; break;
case UPDATE: type = SignalServiceGroup.Type.UPDATE; break;
case QUIT: type = SignalServiceGroup.Type.QUIT; break;
case REQUEST_INFO: type = SignalServiceGroup.Type.REQUEST_INFO; break;
default: type = SignalServiceGroup.Type.UNKNOWN; break;
}
if (content.getGroup().getType() != DELIVER) {
String name = null;
List members = null;
SignalServiceAttachmentPointer avatar = null;
if (content.getGroup().hasName()) {
name = content.getGroup().getName();
}
if (content.getGroup().getMembersCount() > 0) {
members = new ArrayList<>(content.getGroup().getMembersCount());
for (SignalServiceProtos.GroupContext.Member member : content.getGroup().getMembersList()) {
if (SignalServiceAddress.isValidAddress(member.getUuid(), member.getE164())) {
members.add(new SignalServiceAddress(UuidUtil.parseOrNull(member.getUuid()), member.getE164()));
} else {
throw new ProtocolInvalidMessageException(new InvalidMessageException("GroupContext.Member had no address!"), null, 0);
}
}
} else if (content.getGroup().getMembersE164Count() > 0) {
members = new ArrayList<>(content.getGroup().getMembersE164Count());
for (String member : content.getGroup().getMembersE164List()) {
members.add(new SignalServiceAddress(null, member));
}
}
if (content.getGroup().hasAvatar()) {
AttachmentPointer pointer = content.getGroup().getAvatar();
avatar = new SignalServiceAttachmentPointer(pointer.getId(),
pointer.getContentType(),
pointer.getKey().toByteArray(),
Optional.of(pointer.getSize()),
Optional.absent(), 0, 0,
Optional.fromNullable(pointer.hasDigest() ? pointer.getDigest().toByteArray() : null),
Optional.absent(),
false,
Optional.absent(),
Optional.absent());
}
return new SignalServiceGroup(type, content.getGroup().getId().toByteArray(), name, members, avatar);
}
return new SignalServiceGroup(content.getGroup().getId().toByteArray());
}
private static class Metadata {
private final SignalServiceAddress sender;
private final int senderDevice;
private final long timestamp;
private final boolean needsReceipt;
private Metadata(SignalServiceAddress sender, int senderDevice, long timestamp, boolean needsReceipt) {
this.sender = sender;
this.senderDevice = senderDevice;
this.timestamp = timestamp;
this.needsReceipt = needsReceipt;
}
public SignalServiceAddress getSender() {
return sender;
}
public int getSenderDevice() {
return senderDevice;
}
public long getTimestamp() {
return timestamp;
}
public boolean isNeedsReceipt() {
return needsReceipt;
}
}
private static class Plaintext {
private final Metadata metadata;
private final byte[] data;
private Plaintext(Metadata metadata, byte[] data) {
this.metadata = metadata;
this.data = data;
}
public Metadata getMetadata() {
return metadata;
}
public byte[] getData() {
return data;
}
}
}