Skip to content

Commit 208c359

Browse files
committed
Keychain fixes
1 parent 449bf93 commit 208c359

File tree

7 files changed

+130
-49
lines changed

7 files changed

+130
-49
lines changed

Cargo.lock

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ regex = "1.9.3"
2020
tokio-rustls = "0.24.1"
2121
rustls = "0.21.6"
2222
rustls-pemfile = "1.0.3"
23-
rand = "0.8.5"
23+
rand = { version = "0.8.5", features = ["min_const_gen"] }
2424
libflate = "2.0.0"
2525
thiserror = "1.0.47"
2626
async-recursion = "1.0.4"

src/auth.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -731,7 +731,7 @@ impl<P: AnisetteProvider> CircleClientSession<P> {
731731
return Err(PushError::WrongStep(request.step))
732732
}
733733

734-
let step2: CircleStep1 = rasn::der::decode(&base64_decode(request.pake.as_ref().unwrap())).expect("failed to decode circlestep1");
734+
let step2: CircleStep1 = rasn::der::decode(&base64_decode(request.pake.as_ref().expect("No Pake!"))).expect("failed to decode circlestep1");
735735
let body: CircleStep1Body = rasn::der::decode(step2.body.as_ref()).expect("failed to decode circlestep1body");
736736

737737
let verifier: SrpClientVerifier<Sha256> = self.srp_client
@@ -758,6 +758,10 @@ impl<P: AnisetteProvider> CircleClientSession<P> {
758758

759759
pub async fn handle_circle_request(&mut self, request: &IdmsCircleMessage) -> Result<Option<LoginState>, PushError> {
760760
if let Some(ec) = &request.ec {
761+
if *ec == -9003 {
762+
// bad password
763+
return Err(PushError::Bad2FaCode);
764+
}
761765
return Err(PushError::IdmsCircleError(*ec))
762766
}
763767
let Some(pake) = &request.pake else { return Err(PushError::IdmsCircleError(50)) };

src/cloudkit.rs

Lines changed: 53 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -186,41 +186,50 @@ impl CloudKitOp for SaveRecordOperation {
186186
}
187187

188188
impl SaveRecordOperation {
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-
}
189+
// new with a *custom* record protection entry
190+
pub fn new_protected<R: CloudKitRecord>(id: RecordIdentifier, record: R, key: &PCSKeys, update: Option<String>) -> (Self, String) {
191+
// create a key for this record
192+
let record_protection = PCSShareProtection::create(&key.zone_keys[0], &[]).unwrap();
193+
let der = rasn::der::encode(&record_protection).unwrap();
194+
let tag = encode_hex(&sha1(&der)).to_uppercase();
195+
let protection_info = Some(ProtectionInfo {
196+
protection_info_tag: Some(tag.clone()),
197+
protection_info: Some(der),
198+
});
199+
let pcs_key = key.decode_record_protection(protection_info.as_ref().unwrap()).expect("Failed to decode record protection");
200+
201+
(Self(cloudkit_proto::RecordSaveRequest {
202+
record: Some(cloudkit_proto::Record {
203+
record_identifier: Some(id.clone()),
204+
r#type: Some(cloudkit_proto::record::Type {
205+
name: Some(R::record_type().to_string())
206+
}),
207+
record_field: record.to_record_encrypted(Some((&pcs_key, &id))),
208+
protection_info,
209+
..Default::default()
210+
}),
211+
merge: Some(true),
212+
save_semantics: Some(if update.is_some() { 3 } else { 2 }),
213+
record_protection_info_tag: update,
214+
zone_protection_info_tag: key.zone_protection_tag.clone(),
215+
}), tag)
216+
}
208217

218+
pub fn new<R: CloudKitRecord>(id: RecordIdentifier, record: R, key: Option<&PCSKeys>, update: bool) -> Self {
209219
Self(cloudkit_proto::RecordSaveRequest {
210220
record: Some(cloudkit_proto::Record {
211221
record_identifier: Some(id.clone()),
212222
r#type: Some(cloudkit_proto::record::Type {
213223
name: Some(R::record_type().to_string())
214224
}),
215-
record_field: record.to_record_encrypted(pcs_key.as_ref().map(|k| (k, &id))),
216-
pcs_key: pcs_key_id,
217-
protection_info,
225+
record_field: record.to_record_encrypted(key.map(|k| (k.default_record_key.as_ref().expect("No default record key?"), &id))),
226+
pcs_key: key.map(|k| k.default_record_key.as_ref().expect("No default record key?").key_id().unwrap()[..4].to_vec()),
218227
..Default::default()
219228
}),
220229
merge: Some(true),
221230
save_semantics: Some(if update { 3 } else { 2 }),
222-
record_protection_info_tag: None,
223-
zone_protection_info_tag: None,
231+
record_protection_info_tag: key.and_then(|k| k.record_prot_tag.clone()),
232+
zone_protection_info_tag: key.and_then(|k| k.zone_protection_tag.clone()),
224233
})
225234
}
226235
}
@@ -469,6 +478,15 @@ impl FetchRecordChangesOperation {
469478
}
470479
}
471480

481+
pub fn should_reset(error: Option<&PushError>) -> bool {
482+
matches!(error, Some(PushError::CloudKitError(cloudkit_proto::response_operation::Result { error: Some(cloudkit_proto::response_operation::result::Error {
483+
client_error: Some(cloudkit_proto::response_operation::result::error::Client {
484+
r#type: Some(errortype)
485+
}),
486+
..
487+
}), .. })) if *errortype == cloudkit_proto::response_operation::result::error::client::Code::FullResetNeeded as i32)
488+
}
489+
472490
pub struct FunctionInvokeOperation(pub cloudkit_proto::FunctionInvokeRequest);
473491
impl CloudKitOp for FunctionInvokeOperation {
474492
type Response = Vec<u8>;
@@ -749,16 +767,12 @@ pub struct QueryResult<T: CloudKitRecord> {
749767
#[derive(Clone)]
750768
pub struct PCSKeys {
751769
zone_keys: Vec<CompactECKey<Private>>,
770+
zone_protection_tag: Option<String>,
752771
default_record_key: Option<PCSKey>,
772+
pub record_prot_tag: Option<String>,
753773
}
754774

755775
impl PCSKeys {
756-
fn new(keys: Vec<CompactECKey<Private>>) -> Self {
757-
Self {
758-
zone_keys: keys,
759-
default_record_key: None,
760-
}
761-
}
762776

763777
fn decode_record_protection(&self, protection: &ProtectionInfo) -> Result<PCSKey, PushError> {
764778
let record_protection: PCSShareProtection = rasn::der::decode(protection.protection_info()).expect("Bad record protection?");
@@ -837,8 +851,15 @@ impl<'t, T: AnisetteProvider> CloudKitOpenContainer<'t, T> {
837851

838852
let (_parent_key, keys) = zone_protection.decrypt_with_keychain(&data, pcs_service)?;
839853

840-
let mut keys = PCSKeys::new(keys);
841-
854+
let mut keys = PCSKeys {
855+
zone_keys: keys,
856+
zone_protection_tag: zone.protection_info.as_ref().unwrap().protection_info_tag.clone(),
857+
default_record_key: None,
858+
record_prot_tag: if let Some(record_protection_info) = &zone.record_protection_info {
859+
record_protection_info.protection_info_tag.clone()
860+
} else { None },
861+
};
862+
842863
if let Some(record_protection_info) = &zone.record_protection_info {
843864
keys.default_record_key = Some(keys.decode_record_protection(record_protection_info)?);
844865
}

src/error.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,4 +175,6 @@ pub enum PushError {
175175
ShareKeyNotFound,
176176
#[error("BatchError {0}")]
177177
BatchError(Arc<PushError>),
178+
#[error("Invalid 2fa code!")]
179+
Bad2FaCode,
178180
}

src/keychain.rs

Lines changed: 54 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use std::{collections::{BTreeMap, HashMap}, io::{Cursor, Read}, sync::Arc};
22

33
use aes_gcm::{AesGcm, Nonce};
44
use cloudkit_derive::CloudKitRecord;
5-
use cloudkit_proto::{ot_bottle::OtAuthenticatedCiphertext, record::{reference, Field, Reference}, request_operation::header::IsolationLevel, view_keys::ViewKey, Bottle, CloudKitRecord, CuttlefishChange, CuttlefishChanges, CuttlefishEstablshRequest, CuttlefishFetchChangesRequest, CuttlefishFetchChangesResponse, CuttlefishFetchRecoverableTlkSharesRequest, CuttlefishFetchRecoverableTlkSharesResponse, CuttlefishFetchViableBottleRequest, CuttlefishFetchViableBottleResponse, CuttlefishJoinWithVoucherRequest, CuttlefishJoinWithVoucherResponse, CuttlefishPeer, CuttlefishResetRequest, CuttlefishResetResponse, CuttlefishSerializedKey, CuttlefishUpdateTrustRequest, CuttlefishUpdateTrustResponse, EscrowData, EscrowMeta, FunctionInvokeResponse, OtBottle, OtInternalBottle, OtPrivateKey, PeerDynamicInfo, PeerPermanentInfo, PeerStableInfo, Record, RecordZoneIdentifier, ResponseOperation, SignedInfo, TlkShare, ViewKeys, Voucher};
5+
use cloudkit_proto::{ot_bottle::OtAuthenticatedCiphertext, record::{reference, Field, Reference}, request_operation::header::IsolationLevel, response_operation, view_keys::ViewKey, Bottle, CloudKitRecord, CuttlefishChange, CuttlefishChanges, CuttlefishEstablshRequest, CuttlefishFetchChangesRequest, CuttlefishFetchChangesResponse, CuttlefishFetchRecoverableTlkSharesRequest, CuttlefishFetchRecoverableTlkSharesResponse, CuttlefishFetchViableBottleRequest, CuttlefishFetchViableBottleResponse, CuttlefishJoinWithVoucherRequest, CuttlefishJoinWithVoucherResponse, CuttlefishPeer, CuttlefishResetRequest, CuttlefishResetResponse, CuttlefishSerializedKey, CuttlefishUpdateTrustRequest, CuttlefishUpdateTrustResponse, EscrowData, EscrowMeta, FunctionInvokeResponse, OtBottle, OtInternalBottle, OtPrivateKey, PeerDynamicInfo, PeerPermanentInfo, PeerStableInfo, Record, RecordZoneIdentifier, ResponseOperation, SignedInfo, TlkShare, ViewKeys, Voucher};
66
use deku::{DekuContainerWrite, DekuRead, DekuUpdate, DekuWrite};
77
use hkdf::Hkdf;
88
use icloud_auth::AppleAccount;
@@ -25,7 +25,7 @@ use aes_gcm::KeyInit;
2525
use aes_siv::{siv::CmacSiv, Aes256SivAead};
2626

2727
use cloudkit_proto::CuttlefishEstablishResponse;
28-
use crate::{cloudkit::{record_identifier, SaveRecordOperation, ZoneDeleteOperation, ZoneSaveOperation}, pcs::PCSKey};
28+
use crate::{cloudkit::{record_identifier, should_reset, SaveRecordOperation, ZoneDeleteOperation, ZoneSaveOperation}, pcs::PCSKey};
2929
use aes::{cipher::{consts::{U12, U16, U32}, Unsigned}, Aes128, Aes256};
3030
use sha2::{digest::FixedOutputReset, Digest, Sha256, Sha384};
3131
use srp::{client::{SrpClient, SrpClientVerifier}, groups::G_2048, server::SrpServer};
@@ -1048,16 +1048,36 @@ impl<P: AnisetteProvider> KeychainClient<P> {
10481048
}
10491049

10501050
pub async fn is_in_clique(&self) -> bool {
1051-
let _ = self.sync_changes().await;
1051+
let _ = self.sync_trust().await;
10521052
self.state.read().await.user_identity.as_ref().map(|u| u.is_in_clique()).unwrap_or(false)
10531053
}
10541054

1055-
pub async fn sync_changes(&self) -> Result<(), PushError> {
1056-
info!("Syncing changes!");
1055+
async fn sync_changes(&self) -> Result<(), PushError> {
10571056
let token = self.state.read().await.state_token.clone();
1058-
let CuttlefishFetchChangesResponse { changes: Some(changes) } = self.invoke_cuttlefish("fetchChanges", CuttlefishFetchChangesRequest {
1057+
1058+
let result = self.invoke_cuttlefish("fetchChanges", CuttlefishFetchChangesRequest {
10591059
sync_token: token
1060-
}).await? else { return Ok(()) };
1060+
}).await;
1061+
let changes: CuttlefishFetchChangesResponse = match result {
1062+
Ok(changes) => changes,
1063+
Err(PushError::CloudKitError(e)) => {
1064+
info!("result {e:?}");
1065+
if matches!(&e.error, Some(error) if error.error_description() == ".changeTokenExpired") {
1066+
info!("Change token reset, locking");
1067+
// someone reset our clique...
1068+
let mut lock = self.state.write().await;
1069+
info!("Change token reset, locked");
1070+
lock.state_token = None;
1071+
lock.state.clear();
1072+
self.invoke_cuttlefish("fetchChanges", CuttlefishFetchChangesRequest {
1073+
sync_token: None
1074+
}).await?
1075+
} else { return Err(PushError::CloudKitError(e)) }
1076+
}
1077+
Err(e) => return Err(e),
1078+
};
1079+
1080+
let CuttlefishFetchChangesResponse { changes: Some(changes) } = changes else { return Ok(()) };
10611081

10621082
let mut state = self.state.write().await;
10631083
self.apply_changes(changes, &mut state);
@@ -1136,9 +1156,16 @@ impl<P: AnisetteProvider> KeychainClient<P> {
11361156
let security_container = self.get_security_container().await?;
11371157

11381158
let mut state = self.state.write().await;
1139-
let item = security_container.perform_operations_checked(&CloudKitSession::new(),
1159+
let mut result = security_container.perform_operations_checked(&CloudKitSession::new(),
11401160
&zones.iter().map(|zone| FetchRecordChangesOperation::new(security_container.private_zone(zone.to_string()),
1141-
state.items.get(*zone).and_then(|z| z.change_tag.clone()).map(|z| z.into()), &ALL_ASSETS)).collect::<Vec<_>>(), IsolationLevel::Zone).await?;
1161+
state.items.get(*zone).and_then(|z| z.change_tag.clone()).map(|z| z.into()), &ALL_ASSETS)).collect::<Vec<_>>(), IsolationLevel::Zone).await;
1162+
if should_reset(result.as_ref().err()) {
1163+
state.items.clear();
1164+
result = security_container.perform_operations_checked(&CloudKitSession::new(),
1165+
&zones.iter().map(|zone| FetchRecordChangesOperation::new(security_container.private_zone(zone.to_string()),
1166+
state.items.get(*zone).and_then(|z| z.change_tag.clone()).map(|z| z.into()), &ALL_ASSETS)).collect::<Vec<_>>(), IsolationLevel::Zone).await;
1167+
}
1168+
let item = result?;
11421169

11431170
let state = &mut *state;
11441171

@@ -1291,6 +1318,16 @@ impl<P: AnisetteProvider> KeychainClient<P> {
12911318
info!("Syncing trust!");
12921319

12931320
let mut state = self.state.write().await;
1321+
1322+
if !state.state.contains_key(&state.user_identity.as_ref().unwrap().identifier) {
1323+
info!("We are not in the clique!");
1324+
state.user_identity.as_mut().unwrap().current_state = PeerDynamicInfo {
1325+
clock: Some(0),
1326+
..Default::default()
1327+
};
1328+
return Ok(());
1329+
}
1330+
12941331
if self.fast_forward_trust(&mut state)? {
12951332
let identity = state.user_identity.as_ref().unwrap();
12961333
if let CuttlefishUpdateTrustResponse { changes: Some(changes) } = self.invoke_cuttlefish("updateTrust", CuttlefishUpdateTrustRequest {
@@ -1784,9 +1821,14 @@ impl<P: AnisetteProvider> KeychainClient<P> {
17841821
let machine_id = anisette_lock.get_headers().await?.get("X-Apple-I-MD-M").unwrap().clone();
17851822
drop(anisette_lock);
17861823

1787-
// TODO: delete old bottles
1788-
// self.delete(&format!("com.apple.icdp.record.{}", user_identity_ref.identifier.clone())).await?;
1789-
self.enroll(password, &bottle_label, &machine_id, &escrow_bottle.timestamp, bottle_id, escrowed_signing_key, &plist_to_bin(&escrow_bottle)?).await?;
1824+
if let Err(e) = self.enroll(password, &bottle_label, &machine_id, &escrow_bottle.timestamp, bottle_id.clone(), escrowed_signing_key.clone(), &plist_to_bin(&escrow_bottle)?).await {
1825+
if let PushError::EscrowError(_) = &e {
1826+
self.delete(&bottle_label).await?;
1827+
self.enroll(password, &bottle_label, &machine_id, &escrow_bottle.timestamp, bottle_id, escrowed_signing_key, &plist_to_bin(&escrow_bottle)?).await?;
1828+
} else {
1829+
return Err(e)
1830+
}
1831+
}
17901832

17911833
Ok(bottle)
17921834
}

src/relay.rs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,19 @@ impl RelayConfig {
5050
data = data.header("X-Beeper-Access-Token", token.clone());
5151
}
5252

53-
let result: VersionsResp = data.send().await?.json().await?;
53+
let result = data.send().await?;
54+
55+
match result.status().as_u16() {
56+
200 => {},
57+
404 => {
58+
return Err(PushError::DeviceNotFound)
59+
},
60+
_status => {
61+
return Err(PushError::RelayError(_status, result.text().await?))
62+
}
63+
}
64+
65+
let result: VersionsResp = result.json().await?;
5466

5567
Ok(result.versions)
5668
}

0 commit comments

Comments
 (0)