Skip to content

Commit

Permalink
Merge pull request #127 from QuantumEntangledAndy/fix/sim_sub_disc
Browse files Browse the repository at this point in the history
Fix login on new firmware
  • Loading branch information
QuantumEntangledAndy authored Aug 7, 2023
2 parents 2484b87 + a45bcf8 commit a380212
Show file tree
Hide file tree
Showing 18 changed files with 298 additions and 128 deletions.
7 changes: 6 additions & 1 deletion crates/core/src/bc/codex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ impl Encoder<Bc> for BcCodex {
// let context = self.context.read().unwrap();
let buf: Vec<u8> = Default::default();
let enc_protocol: EncryptionProtocol = match self.context.get_encrypted() {
EncryptionProtocol::Aes(_) if item.meta.msg_id == 1 => {
EncryptionProtocol::Aes(_) | EncryptionProtocol::FullAes(_)
if item.meta.msg_id == 1 =>
{
// During login the encyption protocol cannot go higher than BCEncrypt
// even if we support AES. (BUt it can go lower i.e. None)
EncryptionProtocol::BCEncrypt
Expand Down Expand Up @@ -113,6 +115,9 @@ impl Decoder for BcCodex {
0x02 => self.context.set_encrypted(EncryptionProtocol::Aes(
self.context.credentials.make_aeskey(nonce),
)),
0x12 => self.context.set_encrypted(EncryptionProtocol::FullAes(
self.context.credentials.make_aeskey(nonce),
)),
_ => {
return Err(Error::UnknownEncryption(encryption_protocol_byte));
}
Expand Down
55 changes: 38 additions & 17 deletions crates/core/src/bc/de.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,13 @@ fn bc_modern_msg<'a>(
};

let mut in_binary = false;
let mut encrypted_len = None;
// Now we'll take the buffer that Nom gave a ref to and parse it.
let extension = if ext_len > 0 {
log::trace!(
"Extension Txt: {:?}",
String::from_utf8(processed_ext_buf.to_vec()).unwrap_or("Not Text".to_string())
);
// Apply the XML parse function, but throw away the reference to decrypted in the Ok and
// Err case. This error-error-error thing is the same idiom Nom uses internally.
let parsed = Extension::try_parse(processed_ext_buf).map_err(|_| {
Expand All @@ -126,11 +131,13 @@ fn bc_modern_msg<'a>(
})?;
if let Extension {
binary_data: Some(1),
encrypt_len,
..
} = parsed
{
// In binary so tell the current context that we need to treat the payload as binary
in_binary = true;
encrypted_len = encrypt_len;
}
Some(parsed)
} else {
Expand All @@ -149,31 +156,45 @@ fn bc_modern_msg<'a>(
msg_id: 1,
response_code,
..
} if (response_code & 0xff) == 0x00 => EncryptionProtocol::Unencrypted,
BcHeader {
msg_id: 1,
response_code,
..
} if (response_code & 0xff) == 0x01 => EncryptionProtocol::BCEncrypt,
BcHeader {
msg_id: 1,
response_code,
..
} if (response_code & 0xff) == 0x02 => EncryptionProtocol::BCEncrypt, // This is AES but the first packet with the NONCE is BCEcrypt, since the NONCE in this packet is required to build the AES key
BcHeader { msg_id: 1, .. }
if matches!(context.get_encrypted(), EncryptionProtocol::Aes(_)) =>
{
// Maximum encryption level is BCEncrypt during login... Not sure why Reolink did it like that
EncryptionProtocol::BCEncrypt
} if (response_code >> 8) & 0xff == 0xdd => {
// 0xdd means we are setting the encryption method
// Durig login, the max encryption is BcEncrypt since
// the nonce has not been exchanged yet
match response_code & 0xff {
0x00 => EncryptionProtocol::Unencrypted,
_ => EncryptionProtocol::BCEncrypt,
}
}
BcHeader { msg_id: 1, .. } => {
match *context.get_encrypted() {
EncryptionProtocol::Aes(_) | EncryptionProtocol::FullAes(_) => {
// During login max is BcEncrypt
EncryptionProtocol::BCEncrypt
}
n => n,
}
}
_ => *context.get_encrypted(),
};

let processed_payload_buf =
xml_crypto::decrypt(header.channel_id as u32, payload_buf, &encryption_protocol);
if context.in_bin_mode.contains(&(header.msg_num)) || in_binary {
payload = Some(BcPayloads::Binary(payload_buf.to_vec()));
payload = match (context.get_encrypted(), encrypted_len) {
(EncryptionProtocol::FullAes(_), Some(encrypted_len)) => {
log::trace!("Binary: {:X?}", &processed_payload_buf[0..30]);

Some(BcPayloads::Binary(
processed_payload_buf[0..(encrypted_len as usize)].to_vec(),
))
}
_ => Some(BcPayloads::Binary(payload_buf.to_vec())),
};
} else {
log::trace!(
"Payload Txt: {:?}",
String::from_utf8(processed_payload_buf.to_vec()).unwrap_or("Not Text".to_string())
);
let xml = BcXml::try_parse(processed_payload_buf.as_slice()).map_err(|_| {
error!("header.msg_id: {}", header.msg_id);
error!(
Expand Down
5 changes: 5 additions & 0 deletions crates/core/src/bc/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,8 @@ pub enum LegacyMsg {
/// Password for a legacy login
password: String,
},
/// Sent to upgrade to modern and not exposed the MD5 username/password
LoginUpgrade,
/// Any other type of legacy message will be collected here
UnknownMsg,
}
Expand Down Expand Up @@ -198,6 +200,9 @@ pub enum EncryptionProtocol {
/// Latest cameras/firmwares use Aes with the key derived from
/// the camera's password and the negotiated NONCE
Aes([u8; 16]),
/// Same as Aes but the media stream is also encrypted and not just
/// the control commands
FullAes([u8; 16]),
}

#[derive(Debug)]
Expand Down
4 changes: 4 additions & 0 deletions crates/core/src/bc/ser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,10 @@ fn bc_legacy<W: Write>(legacy: &'_ LegacyMsg) -> impl SerializeFn<W> + '_ {
slice(&[0u8; 1772][..]),
))(out)
}
LoginUpgrade => {
// Write nothing as it is header only
slice(&[])(out)
}
UnknownMsg => {
panic!("Cannot serialize an unknown message!");
}
Expand Down
12 changes: 12 additions & 0 deletions crates/core/src/bc/xml.rs
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,15 @@ pub struct Extension {
/// The rfID used in the PIR
#[yaserde(rename = "rfId")]
pub rf_id: Option<u8>,
/// Encrypted binary has this to verify successful decryption
#[yaserde(rename = "checkPos")]
pub check_pos: Option<u32>,
/// Encrypted binary has this to verify successful decryption
#[yaserde(rename = "checkValue")]
pub check_value: Option<u32>,
/// Used in newer encrypted payload packets
#[yaserde(rename = "encryptLen")]
pub encrypt_len: Option<u32>,
}

impl Default for Extension {
Expand All @@ -270,6 +279,9 @@ impl Default for Extension {
token: None,
channel_id: None,
rf_id: None,
check_pos: None,
check_value: None,
encrypt_len: None,
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions crates/core/src/bc/xml_crypto.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ pub fn decrypt(offset: u32, buf: &[u8], encryption_protocol: &EncryptionProtocol
.map(|(key, i)| *i ^ key ^ (offset as u8))
.collect()
}
EncryptionProtocol::Aes(aeskey) => {
EncryptionProtocol::Aes(aeskey) | EncryptionProtocol::FullAes(aeskey) => {
// AES decryption

let mut decrypted = buf.to_vec();
Expand All @@ -41,7 +41,7 @@ pub fn encrypt(offset: u32, buf: &[u8], encryption_protocol: &EncryptionProtocol
// Encrypt is the same as decrypt
decrypt(offset, buf, encryption_protocol)
}
EncryptionProtocol::Aes(aeskey) => {
EncryptionProtocol::Aes(aeskey) | EncryptionProtocol::FullAes(aeskey) => {
// AES encryption
let mut encrypted = buf.to_vec();
Aes128CfbEnc::new(aeskey.into(), IV.into()).encrypt(&mut encrypted);
Expand Down
33 changes: 22 additions & 11 deletions crates/core/src/bc_protocol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ impl BcCamera {
};

let res = tokio::select! {
v = async {
Ok(v) = async {
let uid_local = uid.clone();
info!("{}: Trying local discovery", options.name);
let result = discovery.local(&uid_local, Some(sockets)).await;
Expand All @@ -202,8 +202,9 @@ impl BcCamera {
},
Err(e) => Err(e)
}
}, if allow_local => v,
v = async {
}, if allow_local => Ok(v),
Ok(v) = async {
let mut discovery = Discovery::new().await?;
let reg_result;
// Registration is looped as it seems that reolink
// only updates the registration lazily when someone attempts
Expand All @@ -212,17 +213,25 @@ impl BcCamera {
//
// We loop infinitly and allow the caller to timeout at the
// interval they desire
let mut retry = 0;
const MAX_RETRY: usize = 10;
loop {
tokio::task::yield_now().await;
if let Ok(result) = discovery.get_registration(uid).await {
reg_result = result;
break;
}
log::debug!("Registration failed. Retrying");
if retry >= MAX_RETRY {
return Err(Error::DiscoveryTimeout);
}
log::info!("{}: Registration with reolink servers failed. Retrying: {}/{}", options.name, retry + 1, MAX_RETRY);
retry += 1;
tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
// New discovery to get new client IDs
discovery = Discovery::new().await?;
};
tokio::select! {
v = async {
Ok(v) = async {
let uid_remote = uid.clone();
info!("{}: Trying remote discovery", options.name);
let result = discovery
Expand All @@ -240,8 +249,8 @@ impl BcCamera {
},
Err(e) => Err(e)
}
}, if allow_remote => v,
v = async {
}, if allow_remote => Ok(v),
Ok(v) = async {
let uid_map = uid.clone();
info!("{}: Trying map discovery", options.name);
let result = discovery.map(&reg_result).await;
Expand All @@ -257,8 +266,8 @@ impl BcCamera {
},
Err(e) => Err(e),
}
}, if allow_map => v,
v = async {
}, if allow_map => Ok(v),
Ok(v) = async {
let uid_relay = uid.clone();
info!("{}: Trying relay discovery", options.name);
let result = discovery.relay(&reg_result).await;
Expand All @@ -274,9 +283,11 @@ impl BcCamera {
},
Err(e) => Err(e),
}
}, if allow_relay => v,
}, if allow_relay => Ok(v),
else => Err(Error::DiscoveryTimeout),
}
}, if allow_remote || allow_map || allow_relay => v,
}, if allow_remote || allow_map || allow_relay => Ok(v),
else => Err(Error::DiscoveryTimeout),
}?;

return Ok(res);
Expand Down
1 change: 1 addition & 0 deletions crates/core/src/bc_protocol/connection/bcconn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,7 @@ impl Poller {
if occ_entry.get().is_closed() {
occ_entry.insert(tx);
} else {
// log::error!("Failed to subscribe in bcconn to {:?} for {:?}", msg_num, msg_id);
let _ = tx
.send(Err(Error::SimultaneousSubscription { msg_num }))
.await;
Expand Down
Loading

0 comments on commit a380212

Please sign in to comment.