mirror of
https://github.com/signalapp/Signal-Android.git
synced 2025-12-24 13:08:46 +00:00
Perform individual decryptions inside a database transaction.
Required a lot of random locking work to prevent deadlocking, but overall this results in about a 2x speed increase for decryptions.
This commit is contained in:
committed by
Cody Henthorne
parent
d56607a686
commit
28f3ded4bd
@@ -21,7 +21,7 @@ dependencies {
|
||||
api 'com.googlecode.libphonenumber:libphonenumber:8.12.17'
|
||||
api 'com.fasterxml.jackson.core:jackson-databind:2.9.9.2'
|
||||
|
||||
api 'org.whispersystems:signal-client-java:0.1.6'
|
||||
api 'org.whispersystems:signal-client-java:0.1.7'
|
||||
api 'com.squareup.okhttp3:okhttp:3.12.10'
|
||||
implementation 'org.threeten:threetenbp:1.3.6'
|
||||
|
||||
|
||||
@@ -14,11 +14,11 @@ import org.whispersystems.libsignal.SessionBuilder;
|
||||
import org.whispersystems.libsignal.SignalProtocolAddress;
|
||||
import org.whispersystems.libsignal.logging.Log;
|
||||
import org.whispersystems.libsignal.state.PreKeyBundle;
|
||||
import org.whispersystems.libsignal.state.SignalProtocolStore;
|
||||
import org.whispersystems.libsignal.util.Pair;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
import org.whispersystems.signalservice.api.crypto.AttachmentCipherOutputStream;
|
||||
import org.whispersystems.signalservice.api.crypto.SignalServiceCipher;
|
||||
import org.whispersystems.signalservice.api.crypto.SignalSessionBuilder;
|
||||
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess;
|
||||
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair;
|
||||
import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
|
||||
@@ -120,6 +120,7 @@ public class SignalServiceMessageSender {
|
||||
|
||||
private final PushServiceSocket socket;
|
||||
private final SignalServiceProtocolStore store;
|
||||
private final SignalSessionLock sessionLock;
|
||||
private final SignalServiceAddress localAddress;
|
||||
private final Optional<EventListener> eventListener;
|
||||
|
||||
@@ -144,6 +145,7 @@ public class SignalServiceMessageSender {
|
||||
public SignalServiceMessageSender(SignalServiceConfiguration urls,
|
||||
UUID uuid, String e164, String password,
|
||||
SignalServiceProtocolStore store,
|
||||
SignalSessionLock sessionLock,
|
||||
String signalAgent,
|
||||
boolean isMultiDevice,
|
||||
Optional<SignalServiceMessagePipe> pipe,
|
||||
@@ -153,12 +155,13 @@ public class SignalServiceMessageSender {
|
||||
ExecutorService executor,
|
||||
boolean automaticNetworkRetry)
|
||||
{
|
||||
this(urls, new StaticCredentialsProvider(uuid, e164, password), store, signalAgent, isMultiDevice, pipe, unidentifiedPipe, eventListener, clientZkProfileOperations, executor, 0, automaticNetworkRetry);
|
||||
this(urls, new StaticCredentialsProvider(uuid, e164, password), store, sessionLock, signalAgent, isMultiDevice, pipe, unidentifiedPipe, eventListener, clientZkProfileOperations, executor, 0, automaticNetworkRetry);
|
||||
}
|
||||
|
||||
public SignalServiceMessageSender(SignalServiceConfiguration urls,
|
||||
CredentialsProvider credentialsProvider,
|
||||
SignalServiceProtocolStore store,
|
||||
SignalSessionLock sessionLock,
|
||||
String signalAgent,
|
||||
boolean isMultiDevice,
|
||||
Optional<SignalServiceMessagePipe> pipe,
|
||||
@@ -171,6 +174,7 @@ public class SignalServiceMessageSender {
|
||||
{
|
||||
this.socket = new PushServiceSocket(urls, credentialsProvider, signalAgent, clientZkProfileOperations, automaticNetworkRetry);
|
||||
this.store = store;
|
||||
this.sessionLock = sessionLock;
|
||||
this.localAddress = new SignalServiceAddress(credentialsProvider.getUuid(), credentialsProvider.getE164());
|
||||
this.pipe = new AtomicReference<>(pipe);
|
||||
this.unidentifiedPipe = new AtomicReference<>(unidentifiedPipe);
|
||||
@@ -1549,7 +1553,7 @@ public class SignalServiceMessageSender {
|
||||
throws IOException, InvalidKeyException, UntrustedIdentityException
|
||||
{
|
||||
SignalProtocolAddress signalProtocolAddress = new SignalProtocolAddress(recipient.getIdentifier(), deviceId);
|
||||
SignalServiceCipher cipher = new SignalServiceCipher(localAddress, store, null);
|
||||
SignalServiceCipher cipher = new SignalServiceCipher(localAddress, store, sessionLock, null);
|
||||
|
||||
if (!store.containsSession(signalProtocolAddress)) {
|
||||
try {
|
||||
@@ -1558,7 +1562,7 @@ public class SignalServiceMessageSender {
|
||||
for (PreKeyBundle preKey : preKeys) {
|
||||
try {
|
||||
SignalProtocolAddress preKeyAddress = new SignalProtocolAddress(recipient.getIdentifier(), preKey.getDeviceId());
|
||||
SessionBuilder sessionBuilder = new SessionBuilder(store, preKeyAddress);
|
||||
SignalSessionBuilder sessionBuilder = new SignalSessionBuilder(sessionLock, new SessionBuilder(store, preKeyAddress));
|
||||
sessionBuilder.process(preKey);
|
||||
} catch (org.whispersystems.libsignal.UntrustedIdentityException e) {
|
||||
throw new UntrustedIdentityException("Untrusted identity key!", recipient.getIdentifier(), preKey.getIdentityKey());
|
||||
@@ -1598,7 +1602,7 @@ public class SignalServiceMessageSender {
|
||||
PreKeyBundle preKey = socket.getPreKey(recipient, missingDeviceId);
|
||||
|
||||
try {
|
||||
SessionBuilder sessionBuilder = new SessionBuilder(store, new SignalProtocolAddress(recipient.getIdentifier(), missingDeviceId));
|
||||
SignalSessionBuilder sessionBuilder = new SignalSessionBuilder(sessionLock, new SessionBuilder(store, new SignalProtocolAddress(recipient.getIdentifier(), missingDeviceId)));
|
||||
sessionBuilder.process(preKey);
|
||||
} catch (org.whispersystems.libsignal.UntrustedIdentityException e) {
|
||||
throw new UntrustedIdentityException("Untrusted identity key!", recipient.getIdentifier(), preKey.getIdentityKey());
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
package org.whispersystems.signalservice.api;
|
||||
|
||||
import java.io.Closeable;
|
||||
|
||||
/**
|
||||
* An interface to allow the injection of a lock that will be used to keep interactions with
|
||||
* ecryptions/decryptions thread-safe.
|
||||
*/
|
||||
public interface SignalSessionLock {
|
||||
|
||||
Lock acquire();
|
||||
|
||||
interface Lock extends Closeable {
|
||||
@Override
|
||||
void close();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package org.whispersystems.signalservice.api.crypto;
|
||||
|
||||
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.SelfSendException;
|
||||
import org.signal.libsignal.metadata.certificate.CertificateValidator;
|
||||
import org.signal.libsignal.metadata.certificate.SenderCertificate;
|
||||
import org.whispersystems.libsignal.InvalidKeyException;
|
||||
import org.whispersystems.libsignal.SignalProtocolAddress;
|
||||
import org.whispersystems.signalservice.api.SignalSessionLock;
|
||||
|
||||
/**
|
||||
* A thread-safe wrapper around {@link SealedSessionCipher}.
|
||||
*/
|
||||
public class SignalSealedSessionCipher {
|
||||
|
||||
private final SignalSessionLock lock;
|
||||
private final SealedSessionCipher cipher;
|
||||
|
||||
public SignalSealedSessionCipher(SignalSessionLock lock, SealedSessionCipher cipher) {
|
||||
this.lock = lock;
|
||||
this.cipher = cipher;
|
||||
}
|
||||
|
||||
public byte[] encrypt(SignalProtocolAddress destinationAddress, SenderCertificate senderCertificate, byte[] paddedPlaintext) throws InvalidKeyException, org.whispersystems.libsignal.UntrustedIdentityException {
|
||||
try (SignalSessionLock.Lock unused = lock.acquire()) {
|
||||
return cipher.encrypt(destinationAddress, senderCertificate, paddedPlaintext);
|
||||
}
|
||||
}
|
||||
|
||||
public SealedSessionCipher.DecryptionResult decrypt(CertificateValidator validator, byte[] ciphertext, long timestamp) throws InvalidMetadataMessageException, InvalidMetadataVersionException, ProtocolInvalidMessageException, ProtocolInvalidKeyException, ProtocolNoSessionException, ProtocolLegacyMessageException, ProtocolInvalidVersionException, ProtocolDuplicateMessageException, ProtocolInvalidKeyIdException, ProtocolUntrustedIdentityException, SelfSendException {
|
||||
try (SignalSessionLock.Lock unused = lock.acquire()) {
|
||||
return cipher.decrypt(validator, ciphertext, timestamp);
|
||||
}
|
||||
}
|
||||
|
||||
public int getSessionVersion(SignalProtocolAddress remoteAddress) {
|
||||
try (SignalSessionLock.Lock unused = lock.acquire()) {
|
||||
return cipher.getSessionVersion(remoteAddress);
|
||||
}
|
||||
}
|
||||
|
||||
public int getRemoteRegistrationId(SignalProtocolAddress remoteAddress) {
|
||||
try (SignalSessionLock.Lock unused = lock.acquire()) {
|
||||
return cipher.getRemoteRegistrationId(remoteAddress);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -37,6 +37,7 @@ 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.SignalSessionLock;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceContent;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceMetadata;
|
||||
@@ -63,14 +64,17 @@ public class SignalServiceCipher {
|
||||
private static final String TAG = SignalServiceCipher.class.getSimpleName();
|
||||
|
||||
private final SignalProtocolStore signalProtocolStore;
|
||||
private final SignalSessionLock sessionLock;
|
||||
private final SignalServiceAddress localAddress;
|
||||
private final CertificateValidator certificateValidator;
|
||||
|
||||
public SignalServiceCipher(SignalServiceAddress localAddress,
|
||||
SignalProtocolStore signalProtocolStore,
|
||||
SignalSessionLock sessionLock,
|
||||
CertificateValidator certificateValidator)
|
||||
{
|
||||
this.signalProtocolStore = signalProtocolStore;
|
||||
this.sessionLock = sessionLock;
|
||||
this.localAddress = localAddress;
|
||||
this.certificateValidator = certificateValidator;
|
||||
}
|
||||
@@ -81,15 +85,15 @@ public class SignalServiceCipher {
|
||||
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);
|
||||
SignalSealedSessionCipher sessionCipher = new SignalSealedSessionCipher(sessionLock, 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);
|
||||
SignalSessionCipher sessionCipher = new SignalSessionCipher(sessionLock, new SessionCipher(signalProtocolStore, destination));
|
||||
PushTransportDetails transportDetails = new PushTransportDetails(sessionCipher.getSessionVersion());
|
||||
CiphertextMessage message = sessionCipher.encrypt(transportDetails.getPaddedMessageBody(unpaddedMessage));
|
||||
int remoteRegistrationId = sessionCipher.getRemoteRegistrationId();
|
||||
@@ -173,23 +177,23 @@ public class SignalServiceCipher {
|
||||
|
||||
if (envelope.isPreKeySignalMessage()) {
|
||||
SignalProtocolAddress sourceAddress = getPreferredProtocolAddress(signalProtocolStore, envelope.getSourceAddress(), envelope.getSourceDevice());
|
||||
SessionCipher sessionCipher = new SessionCipher(signalProtocolStore, sourceAddress);
|
||||
SignalSessionCipher sessionCipher = new SignalSessionCipher(sessionLock, new SessionCipher(signalProtocolStore, sourceAddress));
|
||||
|
||||
paddedMessage = sessionCipher.decrypt(new PreKeySignalMessage(ciphertext));
|
||||
metadata = new SignalServiceMetadata(envelope.getSourceAddress(), envelope.getSourceDevice(), envelope.getTimestamp(), envelope.getServerReceivedTimestamp(), envelope.getServerDeliveredTimestamp(), false);
|
||||
sessionVersion = sessionCipher.getSessionVersion();
|
||||
} else if (envelope.isSignalMessage()) {
|
||||
SignalProtocolAddress sourceAddress = getPreferredProtocolAddress(signalProtocolStore, envelope.getSourceAddress(), envelope.getSourceDevice());
|
||||
SessionCipher sessionCipher = new SessionCipher(signalProtocolStore, sourceAddress);
|
||||
SignalSessionCipher sessionCipher = new SignalSessionCipher(sessionLock, new SessionCipher(signalProtocolStore, sourceAddress));
|
||||
|
||||
paddedMessage = sessionCipher.decrypt(new SignalMessage(ciphertext));
|
||||
metadata = new SignalServiceMetadata(envelope.getSourceAddress(), envelope.getSourceDevice(), envelope.getTimestamp(), envelope.getServerReceivedTimestamp(), envelope.getServerDeliveredTimestamp(), 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.getServerReceivedTimestamp());
|
||||
SignalServiceAddress resultAddress = new SignalServiceAddress(UuidUtil.parse(result.getSenderUuid().orNull()), result.getSenderE164());
|
||||
SignalProtocolAddress protocolAddress = getPreferredProtocolAddress(signalProtocolStore, resultAddress, result.getDeviceId());
|
||||
SignalSealedSessionCipher sealedSessionCipher = new SignalSealedSessionCipher(sessionLock, new SealedSessionCipher(signalProtocolStore, localAddress.getUuid().orNull(), localAddress.getNumber().orNull(), 1));
|
||||
DecryptionResult result = sealedSessionCipher.decrypt(certificateValidator, ciphertext, envelope.getServerReceivedTimestamp());
|
||||
SignalServiceAddress resultAddress = new SignalServiceAddress(UuidUtil.parse(result.getSenderUuid().orNull()), result.getSenderE164());
|
||||
SignalProtocolAddress protocolAddress = getPreferredProtocolAddress(signalProtocolStore, resultAddress, result.getDeviceId());
|
||||
|
||||
paddedMessage = result.getPaddedMessage();
|
||||
metadata = new SignalServiceMetadata(resultAddress, result.getDeviceId(), envelope.getTimestamp(), envelope.getServerReceivedTimestamp(), envelope.getServerDeliveredTimestamp(), true);
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package org.whispersystems.signalservice.api.crypto;
|
||||
|
||||
import org.whispersystems.libsignal.InvalidKeyException;
|
||||
import org.whispersystems.libsignal.SessionBuilder;
|
||||
import org.whispersystems.libsignal.UntrustedIdentityException;
|
||||
import org.whispersystems.libsignal.state.PreKeyBundle;
|
||||
import org.whispersystems.signalservice.api.SignalSessionLock;
|
||||
|
||||
/**
|
||||
* A thread-safe wrapper around {@link SessionBuilder}.
|
||||
*/
|
||||
public class SignalSessionBuilder {
|
||||
|
||||
private final SignalSessionLock lock;
|
||||
private final SessionBuilder builder;
|
||||
|
||||
public SignalSessionBuilder(SignalSessionLock lock, SessionBuilder builder) {
|
||||
this.lock = lock;
|
||||
this.builder = builder;
|
||||
}
|
||||
|
||||
public void process(PreKeyBundle preKey) throws InvalidKeyException, UntrustedIdentityException {
|
||||
try (SignalSessionLock.Lock unused = lock.acquire()) {
|
||||
builder.process(preKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package org.whispersystems.signalservice.api.crypto;
|
||||
|
||||
import org.whispersystems.libsignal.DuplicateMessageException;
|
||||
import org.whispersystems.libsignal.InvalidKeyException;
|
||||
import org.whispersystems.libsignal.InvalidKeyIdException;
|
||||
import org.whispersystems.libsignal.InvalidMessageException;
|
||||
import org.whispersystems.libsignal.LegacyMessageException;
|
||||
import org.whispersystems.libsignal.NoSessionException;
|
||||
import org.whispersystems.libsignal.SessionCipher;
|
||||
import org.whispersystems.libsignal.UntrustedIdentityException;
|
||||
import org.whispersystems.libsignal.protocol.CiphertextMessage;
|
||||
import org.whispersystems.libsignal.protocol.PreKeySignalMessage;
|
||||
import org.whispersystems.libsignal.protocol.SignalMessage;
|
||||
import org.whispersystems.signalservice.api.SignalSessionLock;
|
||||
|
||||
/**
|
||||
* A thread-safe wrapper around {@link SessionCipher}.
|
||||
*/
|
||||
public class SignalSessionCipher {
|
||||
|
||||
private final SignalSessionLock lock;
|
||||
private final SessionCipher cipher;
|
||||
|
||||
public SignalSessionCipher(SignalSessionLock lock, SessionCipher cipher) {
|
||||
this.lock = lock;
|
||||
this.cipher = cipher;
|
||||
}
|
||||
|
||||
public CiphertextMessage encrypt(byte[] paddedMessage) throws org.whispersystems.libsignal.UntrustedIdentityException {
|
||||
try (SignalSessionLock.Lock unused = lock.acquire()) {
|
||||
return cipher.encrypt(paddedMessage);
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] decrypt(PreKeySignalMessage ciphertext) throws DuplicateMessageException, LegacyMessageException, InvalidMessageException, InvalidKeyIdException, InvalidKeyException, org.whispersystems.libsignal.UntrustedIdentityException {
|
||||
try (SignalSessionLock.Lock unused = lock.acquire()) {
|
||||
return cipher.decrypt(ciphertext);
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] decrypt(SignalMessage ciphertext) throws InvalidMessageException, DuplicateMessageException, LegacyMessageException, NoSessionException, UntrustedIdentityException {
|
||||
try (SignalSessionLock.Lock unused = lock.acquire()) {
|
||||
return cipher.decrypt(ciphertext);
|
||||
}
|
||||
}
|
||||
|
||||
public int getRemoteRegistrationId() {
|
||||
try (SignalSessionLock.Lock unused = lock.acquire()) {
|
||||
return cipher.getRemoteRegistrationId();
|
||||
}
|
||||
}
|
||||
|
||||
public int getSessionVersion() {
|
||||
try (SignalSessionLock.Lock unused = lock.acquire()) {
|
||||
return cipher.getSessionVersion();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -30,7 +30,7 @@ dependencyVerification {
|
||||
['org.threeten:threetenbp:1.3.6',
|
||||
'f4c23ffaaed717c3b99c003e0ee02d6d66377fd47d866fec7d971bd8644fc1a7'],
|
||||
|
||||
['org.whispersystems:signal-client-java:0.1.6',
|
||||
'34f79d5dd063c70d93570734b6e5649ad6176d9288e6beb382b2d62692e8952c'],
|
||||
['org.whispersystems:signal-client-java:0.1.7',
|
||||
'59dd701f9564c2130177ddaca374d14ce54927075955288f85ff8f55565a78f0'],
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user