mirror of
https://github.com/signalapp/libsignal.git
synced 2024-09-20 03:52:17 +02:00
Get registration IDs from sessions for Sealed Sender v2
The app-visible change is that sealedSenderMultiRecipientEncrypt now takes a SessionStore as well. Sessions will be looked up in bulk using a new SessionStore API, 'loadExistingSessions' or 'getExistingSessions`. The registration ID is then loaded from each session and included in the resulting SSv2 payload. The implementation is a bit of a divergence from some other APIs in libsignal-client in that the "look up in bulk" step is performed in the Java, Swift, or TypeScript layer, with the resulting sessions passed down to Rust. Why? Because otherwise we'd pass a list of addresses into Rust, which would have to turn them back into a Java, Swift, or TypeScript array to call the SessionStore method. This would be (1) a bunch of extra work to implement, and (2) a waste of CPU when we already /have/ a list of addresses in the correct format: the argument to sealedSenderMultiRecipientEncrypt. This is an example of "the boundaries between the Rust and Java/Swift/TypeScript parts of the library don't have to be perfect; they're internal to the overall product". In this case, we've taken that a little further than usual: usually we try to make the libsignal-protocol API as convenient as possible as well, but here it had to be a bit lower-level to satisfy the needs of the app language wrappers. (Specifically, callers need to fetch the list of SessionRecords themselves.) P.S. Why doesn't v1 of sealed sender include registration IDs? Because for SSv1, libsignal-client isn't producing the entire request body to upload to the server; it's only producing the message content that will be decrypted by the recipient. With SSv2, the serialized message the recipient downloads has both shared and per-recipient data in it, which the server must assemble from the uploaded request. Because of this, SSv2's encrypt API might as well produce the entire request.
This commit is contained in:
parent
92e239c28f
commit
6f9083175e
@ -178,7 +178,7 @@ public final class Native {
|
||||
|
||||
public static native long SealedSessionCipher_DecryptToUsmc(byte[] ctext, IdentityKeyStore identityStore, Object ctx);
|
||||
public static native byte[] SealedSessionCipher_Encrypt(long destination, long content, IdentityKeyStore identityKeyStore, Object ctx);
|
||||
public static native byte[] SealedSessionCipher_MultiRecipientEncrypt(long[] recipients, long content, IdentityKeyStore identityKeyStore, Object ctx);
|
||||
public static native byte[] SealedSessionCipher_MultiRecipientEncrypt(long[] recipients, long[] recipientSessions, long content, IdentityKeyStore identityKeyStore, Object ctx);
|
||||
public static native byte[] SealedSessionCipher_MultiRecipientMessageForSingleRecipient(byte[] encodedMultiRecipientMessage);
|
||||
|
||||
public static native long SenderCertificate_Deserialize(byte[] data);
|
||||
|
@ -21,6 +21,7 @@ import org.whispersystems.libsignal.protocol.CiphertextMessage;
|
||||
import org.whispersystems.libsignal.protocol.PreKeySignalMessage;
|
||||
import org.whispersystems.libsignal.protocol.SenderKeyMessage;
|
||||
import org.whispersystems.libsignal.protocol.SignalMessage;
|
||||
import org.whispersystems.libsignal.state.SessionRecord;
|
||||
import org.whispersystems.libsignal.state.SignalProtocolStore;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
|
||||
@ -77,16 +78,28 @@ public class SealedSessionCipher {
|
||||
}
|
||||
|
||||
public byte[] multiRecipientEncrypt(List<SignalProtocolAddress> recipients, UnidentifiedSenderMessageContent content)
|
||||
throws InvalidKeyException, UntrustedIdentityException
|
||||
throws InvalidKeyException, NoSessionException, UntrustedIdentityException
|
||||
{
|
||||
long[] recipientHandles = new long[recipients.size()];
|
||||
List<SessionRecord> recipientSessions =
|
||||
this.signalProtocolStore.loadExistingSessions(recipients);
|
||||
|
||||
long[] recipientSessionHandles = new long[recipientSessions.size()];
|
||||
int i = 0;
|
||||
for (SessionRecord nextSession : recipientSessions) {
|
||||
recipientSessionHandles[i] = nextSession.nativeHandle();
|
||||
i++;
|
||||
}
|
||||
|
||||
long[] recipientHandles = new long[recipients.size()];
|
||||
i = 0;
|
||||
for (SignalProtocolAddress nextRecipient : recipients) {
|
||||
recipientHandles[i] = nextRecipient.nativeHandle();
|
||||
i++;
|
||||
}
|
||||
|
||||
return Native.SealedSessionCipher_MultiRecipientEncrypt(
|
||||
recipientHandles,
|
||||
recipientSessionHandles,
|
||||
content.nativeHandle(),
|
||||
this.signalProtocolStore,
|
||||
null);
|
||||
|
@ -145,7 +145,7 @@ public class SessionRecord {
|
||||
theirBaseKey.nativeHandle()));
|
||||
}
|
||||
|
||||
long nativeHandle() {
|
||||
public long nativeHandle() {
|
||||
return this.handle;
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,7 @@
|
||||
*/
|
||||
package org.whispersystems.libsignal.state;
|
||||
|
||||
import org.whispersystems.libsignal.NoSessionException;
|
||||
import org.whispersystems.libsignal.SignalProtocolAddress;
|
||||
|
||||
import java.util.List;
|
||||
@ -32,6 +33,15 @@ public interface SessionStore {
|
||||
*/
|
||||
public SessionRecord loadSession(SignalProtocolAddress address);
|
||||
|
||||
/**
|
||||
* Returns the {@link SessionRecord}s corresponding to the given addresses.
|
||||
*
|
||||
* @param addresses The name and device ID of each remote client.
|
||||
* @return the SessionRecords corresponding to each recipientId + deviceId tuple.
|
||||
* @throws NoSessionException if any address does not have an active session.
|
||||
*/
|
||||
public List<SessionRecord> loadExistingSessions(List<SignalProtocolAddress> addresses) throws NoSessionException;
|
||||
|
||||
/**
|
||||
* Returns all known devices with active sessions for a recipient
|
||||
*
|
||||
|
@ -5,6 +5,7 @@
|
||||
*/
|
||||
package org.whispersystems.libsignal.state.impl;
|
||||
|
||||
import org.whispersystems.libsignal.NoSessionException;
|
||||
import org.whispersystems.libsignal.SignalProtocolAddress;
|
||||
import org.whispersystems.libsignal.state.SessionRecord;
|
||||
import org.whispersystems.libsignal.state.SessionStore;
|
||||
@ -34,6 +35,23 @@ public class InMemorySessionStore implements SessionStore {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized List<SessionRecord> loadExistingSessions(List<SignalProtocolAddress> addresses) throws NoSessionException {
|
||||
List<SessionRecord> resultSessions = new LinkedList<>();
|
||||
for (SignalProtocolAddress remoteAddress : addresses) {
|
||||
byte[] serialized = sessions.get(remoteAddress);
|
||||
if (serialized == null) {
|
||||
throw new NoSessionException("no session for " + remoteAddress);
|
||||
}
|
||||
try {
|
||||
resultSessions.add(new SessionRecord(serialized));
|
||||
} catch (IOException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
return resultSessions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized List<Integer> getSubDeviceSessions(String name) {
|
||||
List<Integer> deviceIds = new LinkedList<>();
|
||||
|
@ -9,6 +9,7 @@ import org.whispersystems.libsignal.SignalProtocolAddress;
|
||||
import org.whispersystems.libsignal.IdentityKey;
|
||||
import org.whispersystems.libsignal.IdentityKeyPair;
|
||||
import org.whispersystems.libsignal.InvalidKeyIdException;
|
||||
import org.whispersystems.libsignal.NoSessionException;
|
||||
import org.whispersystems.libsignal.groups.state.InMemorySenderKeyStore;
|
||||
import org.whispersystems.libsignal.groups.state.SenderKeyRecord;
|
||||
import org.whispersystems.libsignal.state.SignalProtocolStore;
|
||||
@ -82,6 +83,11 @@ public class InMemorySignalProtocolStore implements SignalProtocolStore {
|
||||
return sessionStore.loadSession(address);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SessionRecord> loadExistingSessions(List<SignalProtocolAddress> addresses) throws NoSessionException {
|
||||
return sessionStore.loadExistingSessions(addresses);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Integer> getSubDeviceSessions(String name) {
|
||||
return sessionStore.getSubDeviceSessions(name);
|
||||
|
2
node/Native.d.ts
vendored
2
node/Native.d.ts
vendored
@ -99,7 +99,7 @@ export function SealedSenderDecryptionResult_Message(obj: Wrapper<SealedSenderDe
|
||||
export function SealedSender_DecryptMessage(message: Buffer, trustRoot: Wrapper<PublicKey>, timestamp: number, localE164: string | null, localUuid: string, localDeviceId: number, sessionStore: SessionStore, identityStore: IdentityKeyStore, prekeyStore: PreKeyStore, signedPrekeyStore: SignedPreKeyStore): Promise<SealedSenderDecryptionResult>;
|
||||
export function SealedSender_DecryptToUsmc(ctext: Buffer, identityStore: IdentityKeyStore, ctx: null): Promise<UnidentifiedSenderMessageContent>;
|
||||
export function SealedSender_Encrypt(destination: Wrapper<ProtocolAddress>, content: Wrapper<UnidentifiedSenderMessageContent>, identityKeyStore: IdentityKeyStore, ctx: null): Promise<Buffer>;
|
||||
export function SealedSender_MultiRecipientEncrypt(recipients: Wrapper<ProtocolAddress>[], content: Wrapper<UnidentifiedSenderMessageContent>, identityKeyStore: IdentityKeyStore, ctx: null): Promise<Buffer>;
|
||||
export function SealedSender_MultiRecipientEncrypt(recipients: Wrapper<ProtocolAddress>[], recipientSessions: Wrapper<SessionRecord>[], content: Wrapper<UnidentifiedSenderMessageContent>, identityKeyStore: IdentityKeyStore, ctx: null): Promise<Buffer>;
|
||||
export function SealedSender_MultiRecipientMessageForSingleRecipient(encodedMultiRecipientMessage: Buffer): Buffer;
|
||||
export function SenderCertificate_Deserialize(buffer: Buffer): SenderCertificate;
|
||||
export function SenderCertificate_GetCertificate(obj: Wrapper<SenderCertificate>): Buffer;
|
||||
|
@ -1008,6 +1008,9 @@ export abstract class SessionStore implements Native.SessionStore {
|
||||
record: SessionRecord
|
||||
): Promise<void>;
|
||||
abstract getSession(name: ProtocolAddress): Promise<SessionRecord | null>;
|
||||
abstract getExistingSessions(
|
||||
addresses: ProtocolAddress[]
|
||||
): Promise<SessionRecord[]>;
|
||||
}
|
||||
|
||||
export abstract class IdentityKeyStore implements Native.IdentityKeyStore {
|
||||
@ -1316,13 +1319,16 @@ export function sealedSenderEncrypt(
|
||||
return NativeImpl.SealedSender_Encrypt(address, content, identityStore, null);
|
||||
}
|
||||
|
||||
export function sealedSenderMultiRecipientEncrypt(
|
||||
export async function sealedSenderMultiRecipientEncrypt(
|
||||
content: UnidentifiedSenderMessageContent,
|
||||
recipients: ProtocolAddress[],
|
||||
identityStore: IdentityKeyStore
|
||||
identityStore: IdentityKeyStore,
|
||||
sessionStore: SessionStore
|
||||
): Promise<Buffer> {
|
||||
return NativeImpl.SealedSender_MultiRecipientEncrypt(
|
||||
const recipientSessions = await sessionStore.getExistingSessions(recipients);
|
||||
return await NativeImpl.SealedSender_MultiRecipientEncrypt(
|
||||
recipients,
|
||||
recipientSessions,
|
||||
content,
|
||||
identityStore,
|
||||
null
|
||||
|
@ -21,7 +21,7 @@ SignalClient.initLogger(
|
||||
);
|
||||
|
||||
class InMemorySessionStore extends SignalClient.SessionStore {
|
||||
private state = new Map();
|
||||
private state = new Map<string, Buffer>();
|
||||
async saveSession(
|
||||
name: SignalClient.ProtocolAddress,
|
||||
record: SignalClient.SessionRecord
|
||||
@ -33,14 +33,27 @@ class InMemorySessionStore extends SignalClient.SessionStore {
|
||||
name: SignalClient.ProtocolAddress
|
||||
): Promise<SignalClient.SessionRecord | null> {
|
||||
const idx = name.name() + '::' + name.deviceId();
|
||||
if (this.state.has(idx)) {
|
||||
const serialized = this.state.get(idx);
|
||||
if (serialized) {
|
||||
return Promise.resolve(
|
||||
SignalClient.SessionRecord.deserialize(this.state.get(idx))
|
||||
SignalClient.SessionRecord.deserialize(serialized)
|
||||
);
|
||||
} else {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
}
|
||||
async getExistingSessions(
|
||||
addresses: SignalClient.ProtocolAddress[]
|
||||
): Promise<SignalClient.SessionRecord[]> {
|
||||
return addresses.map(address => {
|
||||
const idx = address.name() + '::' + address.deviceId();
|
||||
const serialized = this.state.get(idx);
|
||||
if (!serialized) {
|
||||
throw 'no session for ' + idx;
|
||||
}
|
||||
return SignalClient.SessionRecord.deserialize(serialized);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class InMemoryIdentityKeyStore extends SignalClient.IdentityKeyStore {
|
||||
@ -1251,7 +1264,8 @@ describe('SignalClient', () => {
|
||||
const aSealedSenderMessage = await SignalClient.sealedSenderMultiRecipientEncrypt(
|
||||
aUsmc,
|
||||
[bAddress],
|
||||
aKeys
|
||||
aKeys,
|
||||
aSess
|
||||
);
|
||||
|
||||
const bSealedSenderMessage = SignalClient.sealedSenderMultiRecipientMessageForSingleRecipient(
|
||||
|
@ -49,6 +49,10 @@ def translate_to_ts(typ):
|
||||
assert(typ.endswith(']'))
|
||||
return 'Wrapper<' + translate_to_ts(typ[3:-1]) + '>[]'
|
||||
|
||||
if typ.startswith('&['):
|
||||
assert(typ.endswith(']'))
|
||||
return 'Wrapper<' + translate_to_ts(typ[2:-1]) + '>[]'
|
||||
|
||||
if typ.startswith('&'):
|
||||
return 'Wrapper<' + typ[1:] + '>'
|
||||
|
||||
|
@ -874,6 +874,42 @@ macro_rules! node_bridge_handle {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: This is necessarily a cloning API.
|
||||
// We should try to avoid cloning data when possible.
|
||||
impl<'storage> node::AsyncArgTypeInfo<'storage>
|
||||
for &'storage [$typ] {
|
||||
type ArgType = node::JsArray;
|
||||
type StoredType = node::DefaultFinalize<Vec<$typ>>;
|
||||
fn save_async_arg(
|
||||
cx: &mut node::FunctionContext,
|
||||
array: node::Handle<Self::ArgType>,
|
||||
) -> node::NeonResult<Self::StoredType> {
|
||||
let len = array.len(cx);
|
||||
let result = (0..len)
|
||||
.map(|i| {
|
||||
let element = neon::object::Object::get(*array, cx, i)?;
|
||||
let wrapper = element.downcast_or_throw::<node::JsObject, _>(cx)?;
|
||||
let value_box = neon::object::Object::get(
|
||||
*wrapper,
|
||||
cx,
|
||||
node::NATIVE_HANDLE_PROPERTY
|
||||
)?;
|
||||
let value_box: node::Handle<node::DefaultJsBox<std::cell::RefCell<$typ>>> =
|
||||
value_box.downcast_or_throw(cx)?;
|
||||
let cell: &std::cell::RefCell<_> = &***value_box;
|
||||
let result = cell.borrow().clone();
|
||||
Ok(result)
|
||||
})
|
||||
.collect::<node::NeonResult<_>>()?;
|
||||
Ok(node::DefaultFinalize(result))
|
||||
}
|
||||
fn load_async_arg(
|
||||
stored: &'storage mut Self::StoredType,
|
||||
) -> Self {
|
||||
&stored.0
|
||||
}
|
||||
}
|
||||
};
|
||||
( $typ:ty $(, mut = $_:tt)?) => {
|
||||
paste! {
|
||||
|
@ -982,10 +982,11 @@ async fn SealedSessionCipher_Encrypt<E: Env>(
|
||||
Ok(env.buffer(ctext))
|
||||
}
|
||||
|
||||
#[bridge_fn_buffer(jni = "SealedSessionCipher_1MultiRecipientEncrypt")]
|
||||
#[bridge_fn_buffer(jni = "SealedSessionCipher_1MultiRecipientEncrypt", node = false)]
|
||||
async fn SealedSender_MultiRecipientEncrypt<E: Env>(
|
||||
env: E,
|
||||
recipients: &[&ProtocolAddress],
|
||||
recipient_sessions: &[&SessionRecord],
|
||||
content: &UnidentifiedSenderMessageContent,
|
||||
identity_key_store: &mut dyn IdentityKeyStore,
|
||||
ctx: Context,
|
||||
@ -993,6 +994,30 @@ async fn SealedSender_MultiRecipientEncrypt<E: Env>(
|
||||
let mut rng = rand::rngs::OsRng;
|
||||
let ctext = sealed_sender_multi_recipient_encrypt(
|
||||
recipients,
|
||||
recipient_sessions,
|
||||
content,
|
||||
identity_key_store,
|
||||
ctx,
|
||||
&mut rng,
|
||||
)
|
||||
.await?;
|
||||
Ok(env.buffer(ctext))
|
||||
}
|
||||
|
||||
// Node can't support the `&[&Foo]` type, so we clone the sessions instead.
|
||||
#[bridge_fn_buffer(ffi = false, jni = false, node = "SealedSender_MultiRecipientEncrypt")]
|
||||
async fn SealedSender_MultiRecipientEncryptNode<E: Env>(
|
||||
env: E,
|
||||
recipients: &[&ProtocolAddress],
|
||||
recipient_sessions: &[SessionRecord],
|
||||
content: &UnidentifiedSenderMessageContent,
|
||||
identity_key_store: &mut dyn IdentityKeyStore,
|
||||
ctx: Context,
|
||||
) -> Result<E::Buffer> {
|
||||
let mut rng = rand::rngs::OsRng;
|
||||
let ctext = sealed_sender_multi_recipient_encrypt(
|
||||
recipients,
|
||||
&recipient_sessions.iter().collect::<Vec<&SessionRecord>>(),
|
||||
content,
|
||||
identity_key_store,
|
||||
ctx,
|
||||
|
@ -5,8 +5,8 @@
|
||||
|
||||
use crate::{
|
||||
message_encrypt, CiphertextMessageType, Context, Direction, IdentityKeyStore, KeyPair,
|
||||
PreKeySignalMessage, PreKeyStore, PrivateKey, ProtocolAddress, PublicKey, Result, SessionStore,
|
||||
SignalMessage, SignalProtocolError, SignedPreKeyStore, HKDF,
|
||||
PreKeySignalMessage, PreKeyStore, PrivateKey, ProtocolAddress, PublicKey, Result,
|
||||
SessionRecord, SessionStore, SignalMessage, SignalProtocolError, SignedPreKeyStore, HKDF,
|
||||
};
|
||||
|
||||
use crate::crypto;
|
||||
@ -870,11 +870,18 @@ mod sealed_sender_v2 {
|
||||
|
||||
pub async fn sealed_sender_multi_recipient_encrypt<R: Rng + CryptoRng>(
|
||||
destinations: &[&ProtocolAddress],
|
||||
destination_sessions: &[&SessionRecord],
|
||||
usmc: &UnidentifiedSenderMessageContent,
|
||||
identity_store: &mut dyn IdentityKeyStore,
|
||||
ctx: Context,
|
||||
rng: &mut R,
|
||||
) -> Result<Vec<u8>> {
|
||||
if destinations.len() != destination_sessions.len() {
|
||||
return Err(SignalProtocolError::InvalidArgument(
|
||||
"must have the same number of destination sessions as addresses".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let m: [u8; 32] = rng.gen();
|
||||
let keys = sealed_sender_v2::DerivedKeys::calculate(&m);
|
||||
let e_pub = keys.e.public_key()?;
|
||||
@ -903,7 +910,7 @@ pub async fn sealed_sender_multi_recipient_encrypt<R: Rng + CryptoRng>(
|
||||
.expect("cannot fail encoding to Vec");
|
||||
|
||||
let our_identity = identity_store.get_identity_key_pair(ctx).await?;
|
||||
for destination in destinations {
|
||||
for (destination, session) in destinations.iter().zip(destination_sessions) {
|
||||
let their_uuid = Uuid::parse_str(destination.name()).map_err(|_| {
|
||||
SignalProtocolError::InvalidArgument(format!(
|
||||
"multi-recipient sealed sender requires UUID recipients (not {})",
|
||||
@ -916,6 +923,17 @@ pub async fn sealed_sender_multi_recipient_encrypt<R: Rng + CryptoRng>(
|
||||
.await?
|
||||
.ok_or_else(|| SignalProtocolError::SessionNotFound(format!("{}", destination)))?;
|
||||
|
||||
let their_registration_id = session.remote_registration_id()?;
|
||||
let their_registration_id = u16::try_from(their_registration_id).map_err(|_| {
|
||||
SignalProtocolError::InvalidState(
|
||||
"remote_registration_id",
|
||||
format!(
|
||||
"{} has too-high registration ID {:#X}",
|
||||
destination, their_registration_id
|
||||
),
|
||||
)
|
||||
})?;
|
||||
|
||||
let c_i = sealed_sender_v2::apply_agreement_xor(
|
||||
&keys.e,
|
||||
their_identity.public_key(),
|
||||
@ -934,8 +952,7 @@ pub async fn sealed_sender_multi_recipient_encrypt<R: Rng + CryptoRng>(
|
||||
serialized.extend_from_slice(their_uuid.as_bytes());
|
||||
prost::encode_length_delimiter(destination.device_id() as usize, &mut serialized)
|
||||
.expect("cannot fail encoding to Vec");
|
||||
// Provide a placeholder registration ID for now.
|
||||
serialized.extend_from_slice(&0u16.to_be_bytes());
|
||||
serialized.extend_from_slice(&their_registration_id.to_be_bytes());
|
||||
serialized.extend_from_slice(&c_i);
|
||||
serialized.extend_from_slice(&at_i);
|
||||
}
|
||||
|
@ -195,6 +195,23 @@ impl InMemSessionStore {
|
||||
sessions: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Bulk version of [SessionStore::load_session]
|
||||
///
|
||||
/// Useful for [crate::sealed_sender_multi_recipient_encrypt].
|
||||
pub fn load_existing_sessions(
|
||||
&self,
|
||||
addresses: &[&ProtocolAddress],
|
||||
) -> Result<Vec<&SessionRecord>> {
|
||||
addresses
|
||||
.iter()
|
||||
.map(|address| {
|
||||
self.sessions
|
||||
.get(address)
|
||||
.ok_or_else(|| SignalProtocolError::SessionNotFound(address.to_string()))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for InMemSessionStore {
|
||||
|
@ -328,8 +328,12 @@ fn group_sealed_sender() -> Result<(), SignalProtocolError> {
|
||||
Some([42].to_vec()),
|
||||
)?;
|
||||
|
||||
let recipients = [&bob_uuid_address, &carol_uuid_address];
|
||||
let alice_ctext = sealed_sender_multi_recipient_encrypt(
|
||||
&[&bob_uuid_address, &carol_uuid_address],
|
||||
&recipients,
|
||||
&alice_store
|
||||
.session_store
|
||||
.load_existing_sessions(&recipients)?,
|
||||
&alice_usmc,
|
||||
&mut alice_store.identity_store,
|
||||
None,
|
||||
|
@ -496,8 +496,12 @@ fn test_sealed_sender_multi_recipient() -> Result<(), SignalProtocolError> {
|
||||
None,
|
||||
)?;
|
||||
|
||||
let recipients = [&bob_uuid_address];
|
||||
let alice_ctext = sealed_sender_multi_recipient_encrypt(
|
||||
&[&bob_uuid_address],
|
||||
&recipients,
|
||||
&alice_store
|
||||
.session_store
|
||||
.load_existing_sessions(&recipients)?,
|
||||
&alice_usmc,
|
||||
&mut alice_store.identity_store,
|
||||
None,
|
||||
@ -546,8 +550,12 @@ fn test_sealed_sender_multi_recipient() -> Result<(), SignalProtocolError> {
|
||||
None,
|
||||
)?;
|
||||
|
||||
let recipients = [&bob_uuid_address];
|
||||
let alice_ctext = sealed_sender_multi_recipient_encrypt(
|
||||
&[&bob_uuid_address],
|
||||
&recipients,
|
||||
&alice_store
|
||||
.session_store
|
||||
.load_existing_sessions(&recipients)?,
|
||||
&alice_usmc,
|
||||
&mut alice_store.identity_store,
|
||||
None,
|
||||
@ -602,8 +610,12 @@ fn test_sealed_sender_multi_recipient() -> Result<(), SignalProtocolError> {
|
||||
None,
|
||||
)?;
|
||||
|
||||
let recipients = [&bob_uuid_address];
|
||||
let alice_ctext = sealed_sender_multi_recipient_encrypt(
|
||||
&[&bob_uuid_address],
|
||||
&recipients,
|
||||
&alice_store
|
||||
.session_store
|
||||
.load_existing_sessions(&recipients)?,
|
||||
&alice_usmc,
|
||||
&mut alice_store.identity_store,
|
||||
None,
|
||||
|
@ -94,6 +94,15 @@ public class InMemorySignalProtocolStore: IdentityKeyStore, PreKeyStore, SignedP
|
||||
return sessionMap[address]
|
||||
}
|
||||
|
||||
public func loadExistingSessions(for addresses: [ProtocolAddress], context: StoreContext) throws -> [SessionRecord] {
|
||||
return try addresses.map { address in
|
||||
if let session = sessionMap[address] {
|
||||
return session
|
||||
}
|
||||
throw SignalError.sessionNotFound("\(address)")
|
||||
}
|
||||
}
|
||||
|
||||
public func storeSession(_ record: SessionRecord, for address: ProtocolAddress, context: StoreContext) throws {
|
||||
sessionMap[address] = record
|
||||
}
|
||||
|
@ -37,6 +37,7 @@ public protocol SignedPreKeyStore: AnyObject {
|
||||
|
||||
public protocol SessionStore: AnyObject {
|
||||
func loadSession(for address: ProtocolAddress, context: StoreContext) throws -> SessionRecord?
|
||||
func loadExistingSessions(for addresses: [ProtocolAddress], context: StoreContext) throws -> [SessionRecord]
|
||||
func storeSession(_ record: SessionRecord, for address: ProtocolAddress, context: StoreContext) throws
|
||||
}
|
||||
|
||||
|
@ -150,13 +150,17 @@ public func sealedSenderEncrypt(_ content: UnidentifiedSenderMessageContent,
|
||||
public func sealedSenderMultiRecipientEncrypt(_ content: UnidentifiedSenderMessageContent,
|
||||
for recipients: [ProtocolAddress],
|
||||
identityStore: IdentityKeyStore,
|
||||
sessionStore: SessionStore,
|
||||
context: StoreContext) throws -> [UInt8] {
|
||||
let sessions = try sessionStore.loadExistingSessions(for: recipients, context: context)
|
||||
return try context.withOpaquePointer { context in
|
||||
try withIdentityKeyStore(identityStore) { ffiIdentityStore in
|
||||
try invokeFnReturningArray {
|
||||
signal_sealed_sender_multi_recipient_encrypt($0, $1,
|
||||
recipients.map { $0.nativeHandle },
|
||||
recipients.count,
|
||||
sessions.map { $0.nativeHandle },
|
||||
sessions.count,
|
||||
content.nativeHandle,
|
||||
ffiIdentityStore, context)
|
||||
}
|
||||
|
@ -899,6 +899,8 @@ SignalFfiError *signal_sealed_sender_multi_recipient_encrypt(const unsigned char
|
||||
size_t *out_len,
|
||||
const SignalProtocolAddress *const *recipients,
|
||||
size_t recipients_len,
|
||||
const SignalSessionRecord *const *recipient_sessions,
|
||||
size_t recipient_sessions_len,
|
||||
const SignalUnidentifiedSenderMessageContent *content,
|
||||
const SignalIdentityKeyStore *identity_key_store,
|
||||
void *ctx);
|
||||
|
@ -283,6 +283,7 @@ class SessionTests: TestCaseBase {
|
||||
let a_ctext = try! sealedSenderMultiRecipientEncrypt(a_usmc,
|
||||
for: [bob_address],
|
||||
identityStore: alice_store,
|
||||
sessionStore: alice_store,
|
||||
context: NullContext())
|
||||
|
||||
let b_ctext = try! sealedSenderMultiRecipientMessageForSingleRecipient(a_ctext)
|
||||
|
Loading…
Reference in New Issue
Block a user