Skip to content

Commit aebf607

Browse files
Schmiddiiirubdos
authored andcommitted
Fix DeviceInfo::created
This somewhat recently changed, which lead to errors deserializing device IDs. This field now needs to be decrypted. This commit adds that decryption, and also replaces primitive types by DeviceId in many places if appropriate.
1 parent 54981f4 commit aebf607

File tree

12 files changed

+168
-39
lines changed

12 files changed

+168
-39
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ readme = "README.md"
99
[dependencies]
1010
libsignal-protocol = { git = "https://github.com/signalapp/libsignal", tag = "v0.86.6" }
1111
libsignal-core = { git = "https://github.com/signalapp/libsignal", tag = "v0.86.6" }
12+
signal-crypto = { git = "https://github.com/signalapp/libsignal", tag = "v0.86.6" }
1213
zkgroup = { git = "https://github.com/signalapp/libsignal", tag = "v0.86.6" }
1314

1415
aes = "0.8"

src/account_manager.rs

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use base64::prelude::*;
2+
use libsignal_core::DeviceId;
23
use phonenumber::PhoneNumber;
34
use rand::{CryptoRng, Rng};
45
use reqwest::Method;
@@ -457,8 +458,14 @@ impl AccountManager {
457458
},
458459
}
459460
}),
460-
created: i.created,
461+
registration_id: i.registration_id,
461462
last_seen: i.last_seen,
463+
created_at: decrypt_device_created_at_from_device_info(
464+
i.id,
465+
i.registration_id,
466+
&i.created_at_ciphertext,
467+
&aci_identity_keypair,
468+
)?,
462469
})
463470
})
464471
.collect()
@@ -991,6 +998,33 @@ fn decrypt_device_name_from_device_info(
991998
crate::decrypt_device_name(aci.private_key(), &name)
992999
}
9931000

1001+
// Analogous to https://github.com/signalapp/Signal-Android/blob/d88a862e0985cc2bbc463c5f504f5bb4e91ad4fc/app/src/main/java/org/thoughtcrime/securesms/linkdevice/LinkDeviceRepository.kt#L121.
1002+
fn decrypt_device_created_at_from_device_info(
1003+
id: DeviceId,
1004+
registration_id: i32,
1005+
string: &str,
1006+
aci: &IdentityKeyPair,
1007+
) -> Result<chrono::DateTime<chrono::Utc>, ServiceError> {
1008+
use signal_crypto::SimpleHpkeReceiver;
1009+
1010+
let mut associated_data = [0; 5];
1011+
associated_data[0] = id.into();
1012+
associated_data[1..].copy_from_slice(&registration_id.to_be_bytes());
1013+
1014+
let data = BASE64_RELAXED.decode(string)?;
1015+
1016+
let result =
1017+
aci.private_key()
1018+
.open(b"deviceCreatedAt", &associated_data, &data)?;
1019+
1020+
let timestamp = i64::from_be_bytes(result.try_into().map_err(|_| {
1021+
ServiceError::DecryptDeviceInfoFieldError("created-at")
1022+
})?);
1023+
1024+
chrono::DateTime::<chrono::Utc>::from_timestamp_millis(timestamp)
1025+
.ok_or(ServiceError::DecryptDeviceInfoFieldError("created-at"))
1026+
}
1027+
9941028
pub fn decrypt_device_name(
9951029
private_key: &PrivateKey,
9961030
device_name: &DeviceName,
@@ -1001,7 +1035,7 @@ pub fn decrypt_device_name(
10011035
ciphertext: Some(ciphertext),
10021036
} = device_name
10031037
else {
1004-
return Err(ServiceError::InvalidDeviceName);
1038+
return Err(ServiceError::DecryptDeviceInfoFieldError("name"));
10051039
};
10061040

10071041
let synthetic_iv: [u8; 16] = synthetic_iv[..synthetic_iv.len().min(16)]

src/cipher.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -418,7 +418,7 @@ where
418418
use crate::proto::envelope::Type;
419419
Ok(OutgoingPushMessage {
420420
r#type: Type::UnidentifiedSender as u32,
421-
destination_device_id: address.device_id().into(),
421+
destination_device_id: address.device_id(),
422422
destination_registration_id,
423423
content: BASE64_RELAXED.encode(message),
424424
})
@@ -446,7 +446,7 @@ where
446446
} as u32;
447447
Ok(OutgoingPushMessage {
448448
r#type: message_type,
449-
destination_device_id: address.device_id().into(),
449+
destination_device_id: address.device_id(),
450450
destination_registration_id,
451451
content: body,
452452
})

src/provisioning/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -332,7 +332,7 @@ pub async fn link_device<
332332
NewDeviceRegistration {
333333
phone_number,
334334
service_ids: ServiceIds { aci, pni },
335-
device_id: device_id.try_into()?,
335+
device_id,
336336
registration_id,
337337
pni_registration_id,
338338
aci_private_key,

src/push_service/error.rs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,8 +100,8 @@ pub enum ServiceError {
100100
#[error("Not found.")]
101101
NotFoundError,
102102

103-
#[error("invalid device name")]
104-
InvalidDeviceName,
103+
#[error("failed to decrypt field from device info: {0}")]
104+
DecryptDeviceInfoFieldError(&'static str),
105105

106106
#[error("Unknown CDN version {0}")]
107107
UnknownCdnVersion(u32),
@@ -112,12 +112,23 @@ pub enum ServiceError {
112112
#[error(transparent)]
113113
Curve(#[from] CurveError),
114114

115+
// HpkeError does not implement StdError, so we need a manual display,
116+
// and manual From impl.
117+
#[error("Cryptographic error: {0}")]
118+
Hpke(signal_crypto::HpkeError),
119+
115120
// FingerprintError does not implement StdError, so we need a manual display,
116121
// and manual From impl.
117122
#[error("Fingerprint error: {0}")]
118123
Fingerprint(FingerprintError),
119124
}
120125

126+
impl From<signal_crypto::HpkeError> for ServiceError {
127+
fn from(value: signal_crypto::HpkeError) -> Self {
128+
ServiceError::Hpke(value)
129+
}
130+
}
131+
121132
impl From<FingerprintError> for ServiceError {
122133
fn from(value: FingerprintError) -> Self {
123134
ServiceError::Fingerprint(value)

src/push_service/linking.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1+
use libsignal_core::DeviceId;
12
use reqwest::Method;
23
use serde::{Deserialize, Serialize};
34
use uuid::Uuid;
45

56
use crate::{
6-
configuration::Endpoint, websocket::registration::DeviceActivationRequest,
7+
configuration::Endpoint, utils::serde_device_id,
8+
websocket::registration::DeviceActivationRequest,
79
};
810

911
use super::{
@@ -50,7 +52,8 @@ pub struct LinkResponse {
5052
#[serde(rename = "uuid")]
5153
pub aci: Uuid,
5254
pub pni: Uuid,
53-
pub device_id: u32,
55+
#[serde(with = "serde_device_id")]
56+
pub device_id: DeviceId,
5457
}
5558

5659
#[derive(Debug, Serialize)]

src/push_service/mod.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@ use std::{sync::LazyLock, time::Duration};
33
use crate::{
44
configuration::{Endpoint, ServiceCredentials},
55
prelude::ServiceConfiguration,
6+
utils::serde_device_id_vec,
67
websocket::{SignalWebSocket, WebSocketType},
78
};
89

10+
use libsignal_core::DeviceId;
911
use protobuf::ProtobufResponseExt;
1012
use reqwest::{Method, RequestBuilder};
1113
use reqwest_websocket::RequestBuilderExt;
@@ -57,14 +59,17 @@ pub enum AvatarWrite<C> {
5759
#[derive(Debug, Deserialize)]
5860
#[serde(rename_all = "camelCase")]
5961
pub struct MismatchedDevices {
60-
pub missing_devices: Vec<u32>,
61-
pub extra_devices: Vec<u32>,
62+
#[serde(with = "serde_device_id_vec")]
63+
pub missing_devices: Vec<DeviceId>,
64+
#[serde(with = "serde_device_id_vec")]
65+
pub extra_devices: Vec<DeviceId>,
6266
}
6367

6468
#[derive(Debug, Deserialize)]
6569
#[serde(rename_all = "camelCase")]
6670
pub struct StaleDevices {
67-
pub stale_devices: Vec<u32>,
71+
#[serde(with = "serde_device_id_vec")]
72+
pub stale_devices: Vec<DeviceId>,
6873
}
6974

7075
#[derive(Clone)]

src/sender.rs

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ use crate::{
2929
service_address::ServiceIdExt,
3030
session_store::SessionStoreExt,
3131
unidentified_access::UnidentifiedAccess,
32-
utils::serde_service_id,
32+
utils::{serde_device_id, serde_service_id},
3333
websocket::{self, SignalWebSocket},
3434
};
3535

@@ -39,7 +39,8 @@ pub use crate::proto::ContactDetails;
3939
#[serde(rename_all = "camelCase")]
4040
pub struct OutgoingPushMessage {
4141
pub r#type: u32,
42-
pub destination_device_id: u32,
42+
#[serde(with = "serde_device_id")]
43+
pub destination_device_id: DeviceId,
4344
pub destination_registration_id: u32,
4445
pub content: String,
4546
}
@@ -631,9 +632,8 @@ where
631632
);
632633
self.protocol_store
633634
.delete_service_addr_device_session(
634-
&recipient.to_protocol_address(
635-
(*extra_device_id).try_into()?,
636-
)?,
635+
&recipient
636+
.to_protocol_address(*extra_device_id)?,
637637
)
638638
.await?;
639639
}
@@ -643,9 +643,8 @@ where
643643
"creating session with missing device {}",
644644
missing_device_id
645645
);
646-
let remote_address = recipient.to_protocol_address(
647-
(*missing_device_id).try_into()?,
648-
)?;
646+
let remote_address = recipient
647+
.to_protocol_address(*missing_device_id)?;
649648
let pre_key = self
650649
.identified_ws
651650
.get_pre_key(&recipient, *missing_device_id)
@@ -677,9 +676,8 @@ where
677676
);
678677
self.protocol_store
679678
.delete_service_addr_device_session(
680-
&recipient.to_protocol_address(
681-
(*extra_device_id).try_into()?,
682-
)?,
679+
&recipient
680+
.to_protocol_address(*extra_device_id)?,
683681
)
684682
.await?;
685683
}
@@ -993,7 +991,7 @@ where
993991
);
994992
let pre_keys = match self
995993
.identified_ws
996-
.get_pre_keys(recipient, device_id.into())
994+
.get_pre_keys(recipient, device_id)
997995
.await
998996
{
999997
Ok(ok) => {

src/utils.rs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -335,3 +335,55 @@ pub mod serde_aci {
335335
.ok_or_else(|| serde::de::Error::custom("invalid ACI string"))
336336
}
337337
}
338+
339+
pub mod serde_device_id {
340+
use libsignal_core::DeviceId;
341+
use serde::{Deserialize, Deserializer, Serializer};
342+
343+
pub fn serialize<S>(id: &DeviceId, serializer: S) -> Result<S::Ok, S::Error>
344+
where
345+
S: Serializer,
346+
{
347+
serializer.serialize_u8(u8::from(*id))
348+
}
349+
350+
pub fn deserialize<'de, D>(deserializer: D) -> Result<DeviceId, D::Error>
351+
where
352+
D: Deserializer<'de>,
353+
{
354+
DeviceId::try_from(u8::deserialize(deserializer)?)
355+
.map_err(|_| serde::de::Error::custom("invalid device id"))
356+
}
357+
}
358+
359+
pub mod serde_device_id_vec {
360+
use libsignal_core::DeviceId;
361+
use serde::{ser::SerializeSeq, Deserialize, Deserializer, Serializer};
362+
363+
pub fn serialize<S>(
364+
ids: &Vec<DeviceId>,
365+
serializer: S,
366+
) -> Result<S::Ok, S::Error>
367+
where
368+
S: Serializer,
369+
{
370+
let mut seq = serializer.serialize_seq(Some(ids.len()))?;
371+
for id in ids {
372+
seq.serialize_element(&u8::from(*id))?;
373+
}
374+
seq.end()
375+
}
376+
377+
pub fn deserialize<'de, D>(
378+
deserializer: D,
379+
) -> Result<Vec<DeviceId>, D::Error>
380+
where
381+
D: Deserializer<'de>,
382+
{
383+
Vec::<u8>::deserialize(deserializer)?
384+
.into_iter()
385+
.map(DeviceId::try_from)
386+
.collect::<Result<Vec<_>, _>>()
387+
.map_err(|_| serde::de::Error::custom("invalid device id"))
388+
}
389+
}

src/websocket/account.rs

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,25 +5,42 @@ use serde::{Deserialize, Serialize};
55
use uuid::Uuid;
66

77
use crate::{
8-
content::ServiceError, utils::serde_optional_base64,
9-
utils::serde_phone_number, websocket,
8+
content::ServiceError,
9+
utils::serde_optional_base64,
10+
utils::{serde_device_id, serde_phone_number},
11+
websocket,
1012
};
1113

1214
use super::SignalWebSocket;
1315

1416
#[derive(Debug, Serialize, Deserialize)]
1517
#[serde(rename_all = "camelCase")]
1618
pub struct DeviceId {
17-
pub device_id: u32,
19+
#[serde(with = "serde_device_id")]
20+
pub device_id: libsignal_core::DeviceId,
1821
}
1922

2023
#[derive(Debug, Serialize, Deserialize)]
2124
#[serde(rename_all = "camelCase")]
2225
pub struct DeviceInfo {
23-
pub id: u8,
26+
#[serde(with = "serde_device_id")]
27+
pub id: libsignal_core::DeviceId,
28+
pub registration_id: i32,
2429
pub name: Option<String>,
2530
#[serde(with = "chrono::serde::ts_milliseconds")]
26-
pub created: DateTime<Utc>,
31+
pub created_at: DateTime<Utc>,
32+
#[serde(with = "chrono::serde::ts_milliseconds")]
33+
pub last_seen: DateTime<Utc>,
34+
}
35+
36+
#[derive(Debug, Serialize, Deserialize)]
37+
#[serde(rename_all = "camelCase")]
38+
pub(crate) struct DeviceInfoEncrypted {
39+
#[serde(with = "serde_device_id")]
40+
pub id: libsignal_core::DeviceId,
41+
pub name: Option<String>,
42+
pub registration_id: i32,
43+
pub created_at_ciphertext: String,
2744
#[serde(with = "chrono::serde::ts_milliseconds")]
2845
pub last_seen: DateTime<Utc>,
2946
}
@@ -95,10 +112,12 @@ impl SignalWebSocket<websocket::Identified> {
95112
/// Fetches a list of all devices tied to the authenticated account.
96113
///
97114
/// This list include the device that sends the request.
98-
pub async fn devices(&mut self) -> Result<Vec<DeviceInfo>, ServiceError> {
115+
pub(crate) async fn devices(
116+
&mut self,
117+
) -> Result<Vec<DeviceInfoEncrypted>, ServiceError> {
99118
#[derive(serde::Deserialize)]
100119
struct DeviceInfoList {
101-
devices: Vec<DeviceInfo>,
120+
devices: Vec<DeviceInfoEncrypted>,
102121
}
103122

104123
let devices: DeviceInfoList = self

0 commit comments

Comments
 (0)