Skip to content

Commit 077fa56

Browse files
committed
More flexible PCS support
1 parent aaca77a commit 077fa56

File tree

7 files changed

+256
-69
lines changed

7 files changed

+256
-69
lines changed

cloudkit-derive/src/lib.rs

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use syn::{parse_macro_input, Data, DeriveInput, LitStr};
1212
struct CloudKitRecordAttributes {
1313
r#type: String,
1414
encrypted: Flag,
15+
rename_all: Option<String>,
1516
}
1617

1718
#[derive(deluxe::ExtractAttributes)]
@@ -22,11 +23,29 @@ struct CloudKitAttributes {
2223
unencrypted: Flag,
2324
}
2425

26+
fn snake_to_camel(s: &str) -> String {
27+
let mut camel = String::new();
28+
let mut upper_next = false;
29+
30+
for c in s.chars() {
31+
if c == '_' {
32+
upper_next = true;
33+
} else if upper_next {
34+
camel.push_str(&c.to_uppercase().to_string());
35+
upper_next = false;
36+
} else {
37+
camel.push(c);
38+
}
39+
}
40+
41+
camel
42+
}
43+
2544
#[proc_macro_derive(CloudKitRecord, attributes(cloudkit_record, cloudkit))]
2645
pub fn cloudkitrecord_derive(input: TokenStream) -> TokenStream {
2746
let mut input = parse_macro_input!(input as DeriveInput);
2847

29-
let CloudKitRecordAttributes { r#type, encrypted: record_encrypted } = deluxe::extract_attributes(&mut input).unwrap();
48+
let CloudKitRecordAttributes { r#type, encrypted: record_encrypted, rename_all } = deluxe::extract_attributes(&mut input).unwrap();
3049

3150
let name = input.ident;
3251

@@ -48,7 +67,18 @@ pub fn cloudkitrecord_derive(input: TokenStream) -> TokenStream {
4867
}
4968

5069
let ident = field.ident.unwrap();
51-
let name = rename.unwrap_or_else(|| ident.to_string());
70+
let name = rename.unwrap_or_else(|| {
71+
if let Some(rename_all) = &rename_all {
72+
let name = ident.to_string();
73+
return if rename_all == "camelCase" {
74+
snake_to_camel(&name)
75+
} else {
76+
panic!("unknown rename {}", rename_all)
77+
}
78+
}
79+
80+
ident.to_string()
81+
});
5282
let name_lit = LitStr::new(&name, Span::call_site());
5383
if is_encrypted {
5484
fields.push(quote! {

cloudkit-proto/src/cloudkit.proto

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -906,6 +906,7 @@ message RequestOperation {
906906

907907
message EncryptedValue {
908908
optional int64 signedValue = 3;
909+
optional Date dateValue = 5;
909910
optional string stringValue = 6;
910911
}
911912

@@ -983,6 +984,17 @@ message RequestOperation {
983984
optional uint32 permission = 15;
984985
optional bytes pcsKey = 24;
985986
}
987+
988+
// the abuse of the cloudkit file continues, this should really go in a PCS file, but it only has two messages...
989+
message ProtoPCSPrivateKey {
990+
required bytes key = 1;
991+
optional bytes public = 2;
992+
}
993+
994+
message ProtoPCSKey {
995+
required ProtoPCSPrivateKey encryptionKey = 1;
996+
optional ProtoPCSPrivateKey signingKey = 2;
997+
}
986998

987999
message RequestedFields {
9881000
repeated Record.Field.Identifier fields = 1;

cloudkit-proto/src/lib.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,30 @@ impl CloudKitEncryptedValue for String {
205205
}
206206
}
207207

208+
impl CloudKitEncryptedValue for SystemTime {
209+
fn to_value_encrypted(&self, encryptor: &impl CloudKitEncryptor, context: &[u8]) -> Option<record::field::Value> {
210+
let apple_epoch = SystemTime::UNIX_EPOCH + Duration::from_secs(978307200);
211+
let duration = self.duration_since(apple_epoch).expect("Before Apple Epoch?").as_secs_f64();
212+
213+
Some(record::field::Value {
214+
r#type: Some(FieldType::DateType as i32),
215+
bytes_value: Some(encryptor.encrypt_data(&record::field::EncryptedValue {
216+
date_value: Some(Date { time: Some(duration) }),
217+
..Default::default()
218+
}.encode_to_vec(), context)),
219+
is_encrypted: Some(true),
220+
..Default::default()
221+
})
222+
}
223+
224+
fn from_value_encrypted(value: &record::field::Value, encryptor: &impl CloudKitEncryptor, context: &[u8]) -> Option<Self> {
225+
let d = record::field::EncryptedValue::decode(&encryptor.decrypt_data(value.bytes_value.as_ref().unwrap(), context)[..]).unwrap().date_value.unwrap();
226+
let secs = d.time.expect("Date misses time??");
227+
let apple_epoch = SystemTime::UNIX_EPOCH + Duration::from_secs(978307200);
228+
Some(apple_epoch + Duration::from_secs_f64(secs))
229+
}
230+
}
231+
208232
pub fn encode_hex(bytes: &[u8]) -> String {
209233
use std::fmt::Write;
210234
let mut s = String::with_capacity(bytes.len() * 2);

src/cloudkit.rs

Lines changed: 84 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -42,15 +42,16 @@ pub struct FetchedRecords {
4242
}
4343

4444
impl FetchedRecords {
45-
pub fn get_record<R: CloudKitRecord>(&self, record_id: &str, key: Option<&PCSKey>) -> R {
45+
pub fn get_record<R: CloudKitRecord>(&self, record_id: &str, key: Option<&PCSKeys>) -> R {
4646
self.responses.iter().find_map(|response| {
4747
let r = response.record_retrieve_response.as_ref().expect("No retrieve response?").record.as_ref().expect("No record?");
4848
if r.record_identifier.as_ref().expect("No record id?").value.as_ref().expect("No identifier").name.as_ref().expect("No name?") == record_id {
4949
let got_type = r.r#type.as_ref().expect("no TYpe").name.as_ref().expect("No ta");
5050
if got_type.as_str() != R::record_type() {
5151
panic!("Wrong record type, got {} expected {}", got_type, R::record_type());
5252
}
53-
Some(R::from_record_encrypted(&r.record_field, key.map(|k| (k, r.record_identifier.as_ref().unwrap()))))
53+
let key = key.map(|k| pcs_key_for_record(r, k).expect("PCS key failed"));
54+
Some(R::from_record_encrypted(&r.record_field, key.as_ref().map(|k| (k, r.record_identifier.as_ref().unwrap()))))
5455
} else { None }
5556
}).expect("No record found?")
5657
}
@@ -98,6 +99,13 @@ pub trait CloudKitOp {
9899
}
99100
}
100101

102+
pub fn pcs_key_for_record(record: &Record, keys: &PCSKeys) -> Result<PCSKey, PushError> {
103+
let Some(protection) = &record.protection_info else {
104+
return Ok(keys.default_record_key.clone().unwrap())
105+
};
106+
keys.decode_record_protection(protection)
107+
}
108+
101109
pub struct UploadAssetOperation(pub cloudkit_proto::AssetUploadTokenRetrieveRequest);
102110
impl CloudKitOp for UploadAssetOperation {
103111
type Response = cloudkit_proto::AssetUploadTokenRetrieveResponse;
@@ -178,15 +186,35 @@ impl CloudKitOp for SaveRecordOperation {
178186
}
179187

180188
impl SaveRecordOperation {
181-
pub fn new<R: CloudKitRecord>(id: RecordIdentifier, record: R, key: Option<&PCSKey>, update: bool) -> Self {
189+
pub fn new<R: CloudKitRecord>(id: RecordIdentifier, record: R, key: Option<&PCSKeys>, update: bool) -> Self {
190+
let mut pcs_key_id: Option<Vec<u8>> = None;
191+
let mut protection_info: Option<ProtectionInfo> = None;
192+
let mut pcs_key: Option<PCSKey> = None;
193+
194+
if let Some(key) = key {
195+
if let Some(global) = &key.default_record_key {
196+
pcs_key_id = Some(global.key_id().unwrap()[..4].to_vec());
197+
pcs_key = Some(global.clone());
198+
} else {
199+
// create a key for this record
200+
let record_protection = PCSShareProtection::create(&key.zone_keys[0], &[]).unwrap();
201+
let der = rasn::der::encode(&record_protection).unwrap();
202+
protection_info = Some(ProtectionInfo {
203+
protection_info_tag: Some(encode_hex(&sha1(&der)).to_uppercase()),
204+
protection_info: Some(der),
205+
});
206+
}
207+
}
208+
182209
Self(cloudkit_proto::RecordSaveRequest {
183210
record: Some(cloudkit_proto::Record {
184211
record_identifier: Some(id.clone()),
185212
r#type: Some(cloudkit_proto::record::Type {
186213
name: Some(R::record_type().to_string())
187214
}),
188-
record_field: record.to_record_encrypted(key.map(|k| (k, &id))),
189-
pcs_key: key.map(|k| k.key_id().unwrap()[..4].to_vec()),
215+
record_field: record.to_record_encrypted(pcs_key.as_ref().map(|k| (k, &id))),
216+
pcs_key: pcs_key_id,
217+
protection_info,
190218
..Default::default()
191219
}),
192220
merge: Some(true),
@@ -203,14 +231,15 @@ pub struct FetchedRecord {
203231
}
204232

205233
impl FetchedRecord {
206-
pub fn get_record<R: CloudKitRecord>(&self, key: Option<&PCSKey>) -> R {
234+
pub fn get_record<R: CloudKitRecord>(&self, key: Option<&PCSKeys>) -> R {
207235
let r = self.response.record_retrieve_response.as_ref().expect("No retrieve response?").record.as_ref().expect("No record?");
208236

209237
let got_type = r.r#type.as_ref().expect("no TYpe").name.as_ref().expect("No ta");
210238
if got_type.as_str() != R::record_type() {
211239
panic!("Wrong record type, got {} expected {}", got_type, R::record_type());
212240
}
213-
R::from_record_encrypted(&r.record_field, key.map(|k| (k, r.record_identifier.as_ref().unwrap())))
241+
let key = key.map(|k| pcs_key_for_record(r, k).expect("no PCS key"));
242+
R::from_record_encrypted(&r.record_field, key.as_ref().map(|k| (k, r.record_identifier.as_ref().unwrap())))
214243
}
215244

216245
pub fn get_id(&self) -> String {
@@ -546,15 +575,17 @@ impl CloudKitOp for ZoneSaveOperation {
546575
}
547576

548577
impl ZoneSaveOperation {
549-
pub fn new(zone: RecordZoneIdentifier, pcs_key: Option<&CompactECKey<Private>>) -> Result<Self, PushError> {
578+
pub fn new(zone: RecordZoneIdentifier, pcs_key: Option<&CompactECKey<Private>>, with_record: bool) -> Result<Self, PushError> {
550579
let mut protection_info: Option<ProtectionInfo> = None;
551580
let mut record_protection_info: Option<ProtectionInfo> = None;
552581
if let Some(pcs_key) = pcs_key {
553582
let zone_key = CompactECKey::new()?;
554-
let record_protection = PCSShareProtection::create(&zone_key, &[])?;
555-
let main_protection = PCSShareProtection::create(pcs_key, &[zone_key])?;
556-
557-
record_protection_info = Some(ProtectionInfo { protection_info: Some(rasn::der::encode(&record_protection).unwrap()), protection_info_tag: None });
583+
let main_protection = PCSShareProtection::create(pcs_key, &[zone_key.clone()])?;
584+
585+
if with_record {
586+
let record_protection = PCSShareProtection::create(&zone_key, &[])?;
587+
record_protection_info = Some(ProtectionInfo { protection_info: Some(rasn::der::encode(&record_protection).unwrap()), protection_info_tag: None });
588+
}
558589
let main_encoded = rasn::der::encode(&main_protection).unwrap();
559590
protection_info = Some(ProtectionInfo {
560591
protection_info_tag: Some(encode_hex(&sha1(&main_encoded)).to_uppercase()),
@@ -715,11 +746,38 @@ pub struct QueryResult<T: CloudKitRecord> {
715746
pub result: T,
716747
}
717748

749+
#[derive(Clone)]
750+
pub struct PCSKeys {
751+
zone_keys: Vec<CompactECKey<Private>>,
752+
default_record_key: Option<PCSKey>,
753+
}
754+
755+
impl PCSKeys {
756+
fn new(keys: Vec<CompactECKey<Private>>) -> Self {
757+
Self {
758+
zone_keys: keys,
759+
default_record_key: None,
760+
}
761+
}
762+
763+
fn decode_record_protection(&self, protection: &ProtectionInfo) -> Result<PCSKey, PushError> {
764+
let record_protection: PCSShareProtection = rasn::der::decode(protection.protection_info()).expect("Bad record protection?");
765+
let mut big_num = BigNumContext::new()?;
766+
let record_key = CompactECKey::decompress(record_protection.decode_key_public()?.try_into().expect("Decode key not compact!"));
767+
768+
let item = self.zone_keys.iter().find(|k| matches!(record_key.public_key().eq(&record_key.group(), &k.public_key(), &mut big_num), Ok(true))).expect("Record key not found!");
769+
770+
let (key, _record_keys) = record_protection.decode(item).unwrap();
771+
772+
Ok(key)
773+
}
774+
}
775+
718776
pub struct CloudKitOpenContainer<'t, T: AnisetteProvider> {
719777
container: CloudKitContainer<'t>,
720778
pub user_id: String,
721779
pub client: Arc<CloudKitClient<T>>,
722-
pub keys: Mutex<HashMap<String, PCSKey>>,
780+
pub keys: Mutex<HashMap<String, PCSKeys>>,
723781
}
724782

725783
impl<'t, T: AnisetteProvider> Deref for CloudKitOpenContainer<'t, T> {
@@ -744,14 +802,14 @@ impl<'t, T: AnisetteProvider> CloudKitOpenContainer<'t, T> {
744802
}
745803
}
746804

747-
pub async fn get_zone_encryption_config(&self, zone: &cloudkit_proto::RecordZoneIdentifier, client: &KeychainClient<T>, service: &PCSService<'_>) -> Result<PCSKey, PushError> {
805+
pub async fn get_zone_encryption_config(&self, zone: &cloudkit_proto::RecordZoneIdentifier, client: &KeychainClient<T>, pcs_service: &PCSService<'_>) -> Result<PCSKeys, PushError> {
748806
let mut cached_keys = self.keys.lock().await;
749807
let zone_name = zone.value.as_ref().unwrap().name().to_string();
750808
if let Some(key) = cached_keys.get(&zone_name) {
751809
return Ok(key.clone());
752810
}
753811

754-
client.sync_keychain(&[&service.zone, "ProtectedCloudStorage"]).await?;
812+
client.sync_keychain(&[&pcs_service.zone, "ProtectedCloudStorage"]).await?;
755813

756814
let zone = match self.perform(&CloudKitSession::new(), FetchZoneOperation::new(zone.clone())).await {
757815
Ok(data) => data.target_zone.unwrap(),
@@ -764,9 +822,9 @@ impl<'t, T: AnisetteProvider> CloudKitOpenContainer<'t, T> {
764822
}),
765823
..
766824
})) => {
767-
let service = PCSPrivateKey::get_service_key(client, service, self.client.config.as_ref()).await?;
825+
let service = PCSPrivateKey::get_service_key(client, pcs_service, self.client.config.as_ref()).await?;
768826

769-
let request = ZoneSaveOperation::new(zone.clone(), Some(&service.key())).unwrap();
827+
let request = ZoneSaveOperation::new(zone.clone(), Some(&service.key()), pcs_service.global_record).unwrap();
770828
let zone = request.0.clone().zone.unwrap();
771829
self.perform(&CloudKitSession::new(), request).await.unwrap();
772830
zone
@@ -777,20 +835,17 @@ impl<'t, T: AnisetteProvider> CloudKitOpenContainer<'t, T> {
777835

778836
let data = client.state.read().await;
779837

780-
let (_parent_key, keys) = zone_protection.decrypt_with_keychain(&data)?;
838+
let (_parent_key, keys) = zone_protection.decrypt_with_keychain(&data, pcs_service)?;
781839

782-
let record_protection: PCSShareProtection = rasn::der::decode(zone.record_protection_info.as_ref().unwrap().protection_info()).expect("Bad record protection?");
783-
784-
let mut big_num = BigNumContext::new()?;
785-
let record_key = CompactECKey::decompress(record_protection.decode_key_public()?.try_into().expect("Decode key not compact!"));
786-
787-
let item = keys.iter().find(|k| matches!(record_key.public_key().eq(&record_key.group(), &k.public_key(), &mut big_num), Ok(true))).expect("Record key not found!");
788-
789-
let (key, _record_keys) = record_protection.decode(item).unwrap();
790-
791-
cached_keys.insert(zone_name, key.clone());
840+
let mut keys = PCSKeys::new(keys);
841+
842+
if let Some(record_protection_info) = &zone.record_protection_info {
843+
keys.default_record_key = Some(keys.decode_record_protection(record_protection_info)?);
844+
}
845+
846+
cached_keys.insert(zone_name, keys.clone());
792847

793-
Ok(key)
848+
Ok(keys)
794849
}
795850

796851
pub fn build_request<Op: CloudKitOp>(&self, operation: &Op, config: &dyn OSConfig, is_first: bool, uuid: String, isolation_level: IsolationLevel) -> Vec<u8> {

src/imessage/cloud_messages.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ use cloudkit_proto::RecordIdentifier;
2626
use log::info;
2727
use uuid::Uuid;
2828
use crate::cloud_messages::cloudmessagesp::{ChatProto, MessageProto, MessageProto2, MessageProto3, MessageProto4};
29-
use crate::cloudkit::{record_identifier, CloudKitSession, CloudKitUploadRequest, DeleteRecordOperation, FetchRecordChangesOperation, FetchRecordOperation, FetchedRecords, SaveRecordOperation, ZoneDeleteOperation, ALL_ASSETS, NO_ASSETS};
29+
use crate::cloudkit::{pcs_key_for_record, record_identifier, CloudKitSession, CloudKitUploadRequest, DeleteRecordOperation, FetchRecordChangesOperation, FetchRecordOperation, FetchedRecords, SaveRecordOperation, ZoneDeleteOperation, ALL_ASSETS, NO_ASSETS};
3030
use crate::mmcs::{prepare_put_v2, PreparedPut};
3131
use crate::pcs::{get_boundary_key, PCSKey, PCSService};
3232
use bitflags::bitflags;
@@ -43,6 +43,8 @@ pub const MESSAGES_SERVICE: PCSService = PCSService {
4343
zone: "Engram",
4444
r#type: 55,
4545
keychain_type: 55,
46+
v2: false,
47+
global_record: true,
4648
};
4749

4850
pub mod cloudmessagesp {
@@ -477,7 +479,7 @@ impl<P: AnisetteProvider> CloudMessagesClient<P> {
477479
};
478480
if record.r#type.as_ref().unwrap().name() != T::record_type() { continue }
479481

480-
let item = T::from_record_encrypted(&record.record_field, Some((&key, record.record_identifier.as_ref().unwrap())));
482+
let item = T::from_record_encrypted(&record.record_field, Some((&pcs_key_for_record(&record, &key)?, record.record_identifier.as_ref().unwrap())));
481483

482484
results.insert(identifier, Some(item));
483485
}

src/keychain.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -913,7 +913,7 @@ impl KeychainClientState {
913913
}
914914
}
915915

916-
const KEYCHAIN_ZONES: &[&str] = &[
916+
pub const KEYCHAIN_ZONES: &[&str] = &[
917917
"AutoUnlock",
918918
"SecureObjectSync",
919919
"SE-PTC",

0 commit comments

Comments
 (0)