mirror of
https://github.com/signalapp/libsignal.git
synced 2026-05-15 03:16:21 +02:00
protocol: Throw SessionNotFound for an expired unacknowledged session
For the most part this should happen transparently without any explicit adoption, like the previous change, but for Java code the NoSessionException is now properly declared on SessionCipher.encrypt. (This was always technically possible, but clients were expected to have previously checked for session validity before using SessionCipher; now that there's an expiration involved, that's not strictly possible.)
This commit is contained in:
@@ -5,6 +5,7 @@
|
||||
|
||||
package org.signal.libsignal.metadata;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
@@ -63,7 +64,8 @@ public class SealedSessionCipher {
|
||||
paddedPlaintext,
|
||||
addressGuard.nativeHandle(),
|
||||
this.signalProtocolStore,
|
||||
this.signalProtocolStore);
|
||||
this.signalProtocolStore,
|
||||
Instant.now().toEpochMilli());
|
||||
UnidentifiedSenderMessageContent content =
|
||||
new UnidentifiedSenderMessageContent(
|
||||
message,
|
||||
@@ -78,7 +80,7 @@ public class SealedSessionCipher {
|
||||
SignalProtocolAddress destinationAddress, UnidentifiedSenderMessageContent content)
|
||||
throws InvalidKeyException, UntrustedIdentityException {
|
||||
try (NativeHandleGuard addressGuard = new NativeHandleGuard(destinationAddress);
|
||||
NativeHandleGuard contentGuard = new NativeHandleGuard(content); ) {
|
||||
NativeHandleGuard contentGuard = new NativeHandleGuard(content)) {
|
||||
return Native.SealedSessionCipher_Encrypt(
|
||||
addressGuard.nativeHandle(), contentGuard.nativeHandle(), this.signalProtocolStore);
|
||||
}
|
||||
|
||||
@@ -693,6 +693,7 @@ public class SealedSessionCipherTest extends TestCase {
|
||||
InvalidMessageException,
|
||||
InvalidMetadataMessageException,
|
||||
InvalidMetadataVersionException,
|
||||
NoSessionException,
|
||||
ProtocolDuplicateMessageException,
|
||||
ProtocolInvalidKeyException,
|
||||
ProtocolInvalidKeyIdException,
|
||||
|
||||
@@ -11,6 +11,8 @@ import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
@@ -28,6 +30,7 @@ import org.signal.libsignal.protocol.message.PreKeySignalMessage;
|
||||
import org.signal.libsignal.protocol.message.SignalMessage;
|
||||
import org.signal.libsignal.protocol.state.IdentityKeyStore;
|
||||
import org.signal.libsignal.protocol.state.PreKeyBundle;
|
||||
import org.signal.libsignal.protocol.state.SessionRecord;
|
||||
import org.signal.libsignal.protocol.state.SignalProtocolStore;
|
||||
import org.signal.libsignal.protocol.util.Medium;
|
||||
import org.signal.libsignal.protocol.util.Pair;
|
||||
@@ -243,6 +246,49 @@ public class SessionBuilderTest {
|
||||
assertNotNull(bobStore.loadSession(ALICE_ADDRESS).getAliceBaseKey());
|
||||
assertEquals(originalMessage, new String(plaintext));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExpiresUnacknowledgedSessions()
|
||||
throws InvalidKeyException,
|
||||
InvalidVersionException,
|
||||
InvalidMessageException,
|
||||
InvalidKeyIdException,
|
||||
DuplicateMessageException,
|
||||
LegacyMessageException,
|
||||
UntrustedIdentityException,
|
||||
NoSessionException {
|
||||
SignalProtocolStore aliceStore = new TestInMemorySignalProtocolStore();
|
||||
SessionBuilder aliceSessionBuilder = new SessionBuilder(aliceStore, BOB_ADDRESS);
|
||||
|
||||
final SignalProtocolStore bobStore = new TestInMemorySignalProtocolStore();
|
||||
|
||||
PreKeyBundle bobPreKey = bundleFactory.createBundle(bobStore);
|
||||
|
||||
aliceSessionBuilder.process(bobPreKey, Instant.EPOCH);
|
||||
|
||||
SessionRecord initialSession = aliceStore.loadSession(BOB_ADDRESS);
|
||||
assertTrue(initialSession.hasSenderChain(Instant.EPOCH));
|
||||
assertFalse(initialSession.hasSenderChain(Instant.EPOCH.plus(90, ChronoUnit.DAYS)));
|
||||
|
||||
String originalMessage = "Good, fast, cheap: pick two";
|
||||
SessionCipher aliceSessionCipher = new SessionCipher(aliceStore, BOB_ADDRESS);
|
||||
CiphertextMessage outgoingMessage =
|
||||
aliceSessionCipher.encrypt(originalMessage.getBytes(), Instant.EPOCH);
|
||||
|
||||
assertTrue(outgoingMessage.getType() == CiphertextMessage.PREKEY_TYPE);
|
||||
|
||||
SessionRecord updatedSession = aliceStore.loadSession(BOB_ADDRESS);
|
||||
assertTrue(updatedSession.hasSenderChain(Instant.EPOCH));
|
||||
assertFalse(updatedSession.hasSenderChain(Instant.EPOCH.plus(90, ChronoUnit.DAYS)));
|
||||
|
||||
try {
|
||||
aliceSessionCipher.encrypt(
|
||||
originalMessage.getBytes(), Instant.EPOCH.plus(90, ChronoUnit.DAYS));
|
||||
fail("should have expired");
|
||||
} catch (NoSessionException e) {
|
||||
// Expected
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class VersionAgnostic {
|
||||
@@ -313,7 +359,8 @@ public class SessionBuilderTest {
|
||||
InvalidMessageException,
|
||||
DuplicateMessageException,
|
||||
LegacyMessageException,
|
||||
InvalidKeyIdException {
|
||||
InvalidKeyIdException,
|
||||
NoSessionException {
|
||||
SignalProtocolStore aliceStore = new TestInMemorySignalProtocolStore();
|
||||
SessionBuilder aliceSessionBuilder = new SessionBuilder(aliceStore, BOB_ADDRESS);
|
||||
|
||||
@@ -362,7 +409,8 @@ public class SessionBuilderTest {
|
||||
InvalidVersionException,
|
||||
InvalidMessageException,
|
||||
DuplicateMessageException,
|
||||
LegacyMessageException {
|
||||
LegacyMessageException,
|
||||
NoSessionException {
|
||||
SignalProtocolStore aliceStore = new TestNoSignedPreKeysStore();
|
||||
SessionBuilder aliceSessionBuilder = new SessionBuilder(aliceStore, BOB_ADDRESS);
|
||||
|
||||
@@ -398,7 +446,8 @@ public class SessionBuilderTest {
|
||||
InvalidVersionException,
|
||||
InvalidMessageException,
|
||||
DuplicateMessageException,
|
||||
LegacyMessageException {
|
||||
LegacyMessageException,
|
||||
NoSessionException {
|
||||
SignalProtocolStore aliceStore = new TestBadSignedPreKeysStore();
|
||||
SessionBuilder aliceSessionBuilder = new SessionBuilder(aliceStore, BOB_ADDRESS);
|
||||
|
||||
|
||||
@@ -470,7 +470,7 @@ public final class Native {
|
||||
|
||||
public static native byte[] SessionCipher_DecryptPreKeySignalMessage(long message, long protocolAddress, SessionStore sessionStore, IdentityKeyStore identityKeyStore, PreKeyStore prekeyStore, SignedPreKeyStore signedPrekeyStore, KyberPreKeyStore kyberPrekeyStore);
|
||||
public static native byte[] SessionCipher_DecryptSignalMessage(long message, long protocolAddress, SessionStore sessionStore, IdentityKeyStore identityKeyStore);
|
||||
public static native CiphertextMessage SessionCipher_EncryptMessage(byte[] ptext, long protocolAddress, SessionStore sessionStore, IdentityKeyStore identityKeyStore);
|
||||
public static native CiphertextMessage SessionCipher_EncryptMessage(byte[] ptext, long protocolAddress, SessionStore sessionStore, IdentityKeyStore identityKeyStore, long now);
|
||||
|
||||
public static native void SessionRecord_ArchiveCurrentState(long sessionRecord);
|
||||
public static native boolean SessionRecord_CurrentRatchetKeyMatches(long s, long key);
|
||||
|
||||
@@ -97,6 +97,8 @@ public class SessionBuilder {
|
||||
* Build a new session from a {@link org.signal.libsignal.protocol.state.PreKeyBundle} retrieved
|
||||
* from a server.
|
||||
*
|
||||
* <p>You should only use this overload if you need to test session expiration explicitly.
|
||||
*
|
||||
* @param preKey A PreKey for the destination recipient, retrieved from a server.
|
||||
* @param now The current time, used later to check if the session is stale.
|
||||
* @throws InvalidKeyException when the {@link org.signal.libsignal.protocol.state.PreKeyBundle}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
package org.signal.libsignal.protocol;
|
||||
|
||||
import java.time.Instant;
|
||||
import org.signal.libsignal.internal.Native;
|
||||
import org.signal.libsignal.internal.NativeHandleGuard;
|
||||
import org.signal.libsignal.protocol.message.CiphertextMessage;
|
||||
@@ -70,11 +71,35 @@ public class SessionCipher {
|
||||
*
|
||||
* @param paddedMessage The plaintext message bytes, optionally padded to a constant multiple.
|
||||
* @return A ciphertext message encrypted to the recipient+device tuple.
|
||||
* @throws NoSessionException if there is no established session for this contact, or if an
|
||||
* unacknowledged session has expired
|
||||
* @throws UntrustedIdentityException when the {@link IdentityKey} of the sender is out of date.
|
||||
*/
|
||||
public CiphertextMessage encrypt(byte[] paddedMessage) throws UntrustedIdentityException {
|
||||
public CiphertextMessage encrypt(byte[] paddedMessage)
|
||||
throws NoSessionException, UntrustedIdentityException {
|
||||
return encrypt(paddedMessage, Instant.now());
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypt a message.
|
||||
*
|
||||
* <p>You should only use this overload if you need to test session expiration explicitly.
|
||||
*
|
||||
* @param paddedMessage The plaintext message bytes, optionally padded to a constant multiple.
|
||||
* @return A ciphertext message encrypted to the recipient+device tuple.
|
||||
* @throws NoSessionException if there is no established session for this contact, or if an
|
||||
* unacknowledged session has expired
|
||||
* @throws UntrustedIdentityException when the {@link IdentityKey} of the sender is out of date.
|
||||
*/
|
||||
public CiphertextMessage encrypt(byte[] paddedMessage, Instant now)
|
||||
throws NoSessionException, UntrustedIdentityException {
|
||||
try (NativeHandleGuard remoteAddress = new NativeHandleGuard(this.remoteAddress)) {
|
||||
return Native.SessionCipher_EncryptMessage(
|
||||
paddedMessage, remoteAddress.nativeHandle(), sessionStore, identityKeyStore);
|
||||
paddedMessage,
|
||||
remoteAddress.nativeHandle(),
|
||||
sessionStore,
|
||||
identityKeyStore,
|
||||
now.toEpochMilli());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
package org.signal.libsignal.protocol.state;
|
||||
|
||||
import java.time.Instant;
|
||||
import org.signal.libsignal.internal.Native;
|
||||
import org.signal.libsignal.internal.NativeHandleGuard;
|
||||
import org.signal.libsignal.protocol.IdentityKey;
|
||||
@@ -14,9 +15,6 @@ import org.signal.libsignal.protocol.InvalidMessageException;
|
||||
import org.signal.libsignal.protocol.ecc.ECKeyPair;
|
||||
import org.signal.libsignal.protocol.ecc.ECPublicKey;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.Instant;
|
||||
|
||||
/**
|
||||
* A SessionRecord encapsulates the state of an ongoing session.
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user