1use crate::{crypto::*, protobuf as p, MAX_MESSAGE_BYTES};
3use anyhow::{bail, ensure, Result};
4use openssl::{nid::Nid, symm::Mode};
5use zeroize::Zeroize;
6
7pub struct PublicProfile {
8 pub version: u16,
9 pub point: Vec<u8>,
10 pub signature_pem: String,
11}
12pub fn profile_for_region(region: u32) -> Result<PublicProfile> {
13 let version = match region {
14 1 => 10014,
15 2 => 50005,
16 _ => bail!("Unsupported business certificate region"),
17 };
18 let pins: serde_json::Value =
19 serde_json::from_str(include_str!("../fixtures/business-public-keys.json"))?;
20 let pin = pins["profiles"]
21 .as_array()
22 .unwrap()
23 .iter()
24 .find(|p| p["certVersion"].as_u64() == Some(version))
25 .ok_or_else(|| anyhow::anyhow!("Missing business pin"))?;
26 Ok(PublicProfile {
27 version: version as u16,
28 point: hex::decode(pin["ecdhPointHex"].as_str().unwrap())?,
29 signature_pem: pin["ecdsaPEM"].as_str().unwrap().to_string(),
30 })
31}
32pub struct Prelogin {
33 pub request: Vec<u8>,
34 local: Ecdh,
35 plain: Vec<u8>,
36 derived: Vec<u8>,
37 encrypted_payload: Vec<u8>,
38 signature_pem: String,
39 has_auth_key: bool,
40}
41impl Drop for Prelogin {
42 fn drop(&mut self) {
43 self.plain.zeroize();
44 self.derived.zeroize();
45 }
46}
47impl Prelogin {
48 pub fn prepare(plain: &[u8], profile: &PublicProfile, auth_key: &[u8]) -> Result<Self> {
49 ensure!(
50 plain.len() <= MAX_MESSAGE_BYTES,
51 "Prelogin plaintext too large"
52 );
53 ensure!(
54 profile.point.len() == 65 && profile.point[0] == 4,
55 "Invalid business pin"
56 );
57 ensure!(
58 auth_key.is_empty() || auth_key.len() >= 24,
59 "Invalid saved Hybrid auth key"
60 );
61 let local = Ecdh::generate(Nid::X9_62_PRIME256V1)?;
62 let point = local.public()?;
63 let prefix = [b"1415".as_slice(), &point].concat();
64 let aad = sha256(&prefix);
65 let mut shared = sha256(&local.derive(&profile.point)?);
66 let mut random32 = random::<32>()?;
67 let encrypted_random = envelope_seal(&random32, &shared[..24], &aad, random()?)?;
68 shared.zeroize();
69 let auth_block = if auth_key.is_empty() {
70 vec![]
71 } else {
72 envelope_seal(&random32, &auth_key[..24], &aad, random()?)?
73 };
74 let mut extracted = hmac(b"security hdkf expand", &random32)?;
75 let derived = expand(&extracted, &aad, 56)?;
76 random32.zeroize();
77 extracted.zeroize();
78 let payload_aad = sha256(&[prefix, encrypted_random.clone(), auth_block.clone()].concat());
79 let encrypted_payload = envelope_seal(plain, &derived[..24], &payload_aad, random()?)?;
80 let request = p::message(&[
81 p::uint(1, 1),
82 p::bytes(2, &p::message(&[p::uint(1, 415), p::bytes(2, &point)])?),
83 p::bytes(3, &encrypted_random),
84 p::bytes(4, &auth_block),
85 p::bytes(5, &encrypted_payload),
86 ])?;
87 Ok(Self {
88 request,
89 local,
90 plain: plain.to_vec(),
91 derived,
92 encrypted_payload,
93 signature_pem: profile.signature_pem.clone(),
94 has_auth_key: !auth_key.is_empty(),
95 })
96 }
97 pub fn decrypt_reply(&self, reply: &[u8]) -> Result<Vec<u8>> {
98 let f = p::decode(reply)?;
99 let key = p::decode(p::data(&f, 1)?)?;
100 ensure!(p::scalar(&key, 1)? == 415, "Unsupported business curve");
101 let point = p::data(&key, 2)?;
102 let credential = p::scalar(&f, 2)?;
103 ensure!(
104 point.len() == 65
105 && point[0] == 4
106 && [1, 2].contains(&credential)
107 && (credential != 2 || self.has_auth_key),
108 "Unsupported business credential"
109 );
110 let ciphertext = p::data(&f, 3)?;
111 if credential == 1 {
112 verify_signature(
113 &self.signature_pem,
114 &sha256(&[self.encrypted_payload.as_slice(), ciphertext].concat()),
115 p::data(&f, 4)?,
116 )?;
117 }
118 let mut shared = sha256(&self.local.derive(point)?);
119 let aad = sha256(
120 &[
121 &self.derived[24..],
122 &self.plain,
123 b"415",
124 point,
125 credential.to_string().as_bytes(),
126 ]
127 .concat(),
128 );
129 let plain = envelope_open(ciphertext, &shared[..24], &aad);
130 shared.zeroize();
131 plain
132 }
133}
134pub struct PacketPolicy<'a> {
135 pub client_version: u32,
136 pub uin: u32,
137 pub cookie: &'a [u8],
138 pub cgi: u16,
139 pub original_length: usize,
140 pub pre_encryption_length: usize,
141 pub key_version: u16,
142 pub cert_version: u16,
143 pub checksum: u32,
144 pub compression: u8,
145 pub encryption: u8,
146 pub notice_id: u32,
147 pub sequence: u16,
148}
149pub fn pack(policy: PacketPolicy<'_>, encrypted: &[u8]) -> Result<Vec<u8>> {
150 ensure!(
151 policy.cookie.len() <= 15 && policy.compression <= 3 && policy.encryption <= 15,
152 "Invalid business policy"
153 );
154 ensure!(
155 policy.original_length <= MAX_MESSAGE_BYTES
156 && policy.pre_encryption_length <= MAX_MESSAGE_BYTES,
157 "Business payload too large"
158 );
159 let tail = [
160 policy.client_version.to_be_bytes().to_vec(),
161 policy.uin.to_be_bytes().to_vec(),
162 policy.cookie.to_vec(),
163 p::varint(policy.cgi as u64),
164 p::varint(policy.original_length as u64),
165 p::varint(policy.pre_encryption_length as u64),
166 p::varint(policy.key_version as u64),
167 p::varint(policy.cert_version as u64),
168 p::varint(policy.checksum as u64),
169 vec![0xff],
170 p::varint(policy.notice_id as u64),
171 vec![0, 0],
172 p::varint(0),
173 policy.sequence.to_be_bytes().to_vec(),
174 ]
175 .concat();
176 let length = tail.len() + 3;
177 ensure!(length <= 63, "Unsupported extended business header");
178 let flags = ((length as u16) << 2)
179 | policy.compression as u16
180 | ((policy.cookie.len() as u16) << 8)
181 | ((policy.encryption as u16) << 12);
182 Ok([
183 vec![0xbf],
184 flags.to_le_bytes().to_vec(),
185 tail,
186 encrypted.to_vec(),
187 ]
188 .concat())
189}
190pub fn prelogin_packet(
191 prelogin: &Prelogin,
192 cgi: u16,
193 key_version: u16,
194 cookie: &[u8],
195) -> Result<Vec<u8>> {
196 pack(
197 PacketPolicy {
198 client_version: 0,
199 uin: 0,
200 cookie,
201 cgi,
202 original_length: prelogin.plain.len(),
203 pre_encryption_length: prelogin.plain.len(),
204 key_version,
205 cert_version: 0,
206 checksum: 0,
207 compression: 2,
208 encryption: 12,
209 notice_id: crate::notice::notice_id(&prelogin.request)?,
210 sequence: u16::from_be_bytes(random()?),
211 },
212 &prelogin.request,
213 )
214}
215pub struct Envelope {
216 pub return_code: i32,
217 pub cgi: u64,
218 pub original_length: usize,
219 pub pre_encryption_length: usize,
220 pub compression: u8,
221 pub encryption: u8,
222 pub cookie: Vec<u8>,
223 pub payload: Vec<u8>,
224}
225pub fn split(input: &[u8]) -> Result<Envelope> {
226 ensure!(input.len() >= 10, "Truncated business response");
227 let base = usize::from(input[0] == 0xbf);
228 let flags = u16::from_le_bytes(input[base..base + 2].try_into()?);
229 let header = ((flags >> 2) & 63) as usize;
230 let cookie_len = ((flags >> 8) & 15) as usize;
231 let cookie_offset = base + 10;
232 ensure!(
233 header >= cookie_offset + cookie_len && header <= input.len(),
234 "Invalid business boundary"
235 );
236 let return_code = i32::from_be_bytes(input[base + 2..base + 6].try_into()?);
237 let mut offset = cookie_offset + cookie_len;
238 let cgi = p::read_varint(&input[..header], &mut offset)?;
239 let original_length = usize::try_from(p::read_varint(&input[..header], &mut offset)?)?;
240 let pre_encryption_length = usize::try_from(p::read_varint(&input[..header], &mut offset)?)?;
241 p::read_varint(&input[..header], &mut offset)?;
242 p::read_varint(&input[..header], &mut offset)?;
243 if base == 0 {
244 ensure!(offset == header, "Unknown business response extension");
245 }
246 ensure!(
247 original_length <= MAX_MESSAGE_BYTES && pre_encryption_length <= MAX_MESSAGE_BYTES,
248 "Response too large"
249 );
250 Ok(Envelope {
251 return_code,
252 cgi,
253 original_length,
254 pre_encryption_length,
255 compression: (flags & 3) as u8,
256 encryption: (flags >> 12) as u8,
257 cookie: input[cookie_offset..cookie_offset + cookie_len].to_vec(),
258 payload: input[header..].to_vec(),
259 })
260}
261pub fn checksum(uin: u32, payload: &[u8]) -> u32 {
262 let mut input = (payload.len() as u32).to_be_bytes().to_vec();
263 input.extend(md5(&uin.to_be_bytes()));
264 let prefix = md5(&input);
265 let mut a = 1u32;
266 let mut b = 0u32;
267 for byte in prefix.iter().chain(payload) {
268 a = (a + *byte as u32) % 65521;
269 b = (b + a) % 65521;
270 }
271 (b << 16) | a
272}
273pub struct AuthenticatedPacket {
274 pub packet: Vec<u8>,
275 cgi: u16,
276 mode: u8,
277 key: Vec<u8>,
278}
279impl Drop for AuthenticatedPacket {
280 fn drop(&mut self) {
281 self.key.zeroize();
282 }
283}
284impl AuthenticatedPacket {
285 pub fn prepare(state: &crate::AccountState, cgi: u16, plain: &[u8]) -> Result<Self> {
286 state.validate()?;
287 ensure!(
288 plain.len() <= MAX_MESSAGE_BYTES,
289 "Authenticated request too large"
290 );
291 let mode = if !state.request_key.is_empty() && !state.response_key.is_empty() {
292 13
293 } else {
294 5
295 };
296 let zipped = if mode == 5 {
297 deflate(plain)?
298 } else {
299 plain.to_vec()
300 };
301 let compressed = mode == 5 && zipped.len() < plain.len();
302 let body = if compressed { zipped.as_slice() } else { plain };
303 let key = if mode == 13 {
304 ensure!(
305 state.request_key.len() == 24 && state.response_key.len() == 24,
306 "Invalid main session keys"
307 );
308 state.response_key.clone()
309 } else {
310 ensure!(state.session_key.len() == 16, "Invalid origin session key");
311 state.session_key.clone()
312 };
313 let encrypted = if mode == 13 {
314 envelope_seal(body, &state.request_key, b"", random()?)?
315 } else {
316 cbc(body, &key, Mode::Encrypt)?
317 };
318 let packet = pack(
319 PacketPolicy {
320 client_version: state.device.client_version as u32,
321 uin: state.uin,
322 cookie: &state.cookie,
323 cgi,
324 original_length: plain.len(),
325 pre_encryption_length: body.len(),
326 key_version: 0,
327 cert_version: 39,
328 checksum: checksum(state.uin, plain),
329 compression: if compressed { 1 } else { 2 },
330 encryption: mode,
331 notice_id: crate::notice::notice_id(&encrypted)?,
332 sequence: u16::from_be_bytes(random()?),
333 },
334 &encrypted,
335 )?;
336 Ok(Self {
337 packet,
338 cgi,
339 mode,
340 key,
341 })
342 }
343 pub fn decode_verified_transport_reply(&self, input: &[u8]) -> Result<(Vec<u8>, Vec<u8>)> {
345 let envelope = split(input)?;
346 ensure!(
347 envelope.return_code == 0,
348 "Native envelope returned {}",
349 envelope.return_code
350 );
351 ensure!(
352 envelope.cgi == self.cgi as u64
353 && envelope.encryption == self.mode
354 && [1, 2].contains(&envelope.compression),
355 "Unsupported authenticated response policy"
356 );
357 let body = if self.mode == 13 {
358 envelope_open(&envelope.payload, &self.key, b"")?
359 } else {
360 cbc(&envelope.payload, &self.key, Mode::Decrypt)?
361 };
362 ensure!(
363 body.len() == envelope.pre_encryption_length,
364 "Authenticated body length mismatch"
365 );
366 let plain = if envelope.compression == 1 {
367 inflate(&body)?
368 } else {
369 body
370 };
371 ensure!(
372 plain.len() == envelope.original_length,
373 "Authenticated plaintext length mismatch"
374 );
375 Ok((plain, envelope.cookie))
376 }
377}
378pub fn account_keys(inner: &Ecdh, auth: &[u8]) -> Result<(Vec<u8>, Vec<u8>, Vec<u8>)> {
379 let f = p::decode(auth)?;
380 let flags = match p::optional(&f, 19)? {
381 Some(p::Value::Varint(v)) => *v,
382 None => 0,
383 _ => bail!("Invalid auth flags"),
384 };
385 let raw = p::unbuffer(p::data(&f, 3)?)?;
386 let session = if flags & 4 != 0 {
387 let e = p::decode(p::data(&f, 2)?)?;
388 ensure!(p::scalar(&e, 1)? == 713, "Unsupported account curve");
389 let point = p::unbuffer(p::data(&e, 2)?)?;
390 ensure!(point.len() == 57 && point[0] == 4, "Invalid account point");
391 let mut secret = inner.derive(&point)?;
392 ensure!(secret.len() == 28, "Invalid P224 secret");
393 let mut key = md5(&secret);
394 secret.zeroize();
395 let result = cbc(&raw, &key, Mode::Decrypt);
396 key.zeroize();
397 result?
398 } else {
399 raw
400 };
401 ensure!(
402 !session.is_empty() && session.len() <= 65536,
403 "Invalid derived session key"
404 );
405 let get = |n| -> Result<Vec<u8>> {
406 Ok(match p::optional_data(&f, n)? {
407 Some(b) => p::unbuffer(b)?,
408 None => vec![],
409 })
410 };
411 Ok((session, get(23)?, get(24)?))
412}
413
414#[cfg(test)]
415mod tests {
416 use super::*;
417 #[test]
418 fn independent_checksum_vectors() {
419 for (uin, payload, expected) in [
420 (0, vec![], 0x4e970913),
421 (1, b"a".to_vec(), 0x39d20759),
422 (0x12345678, b"abc".to_vec(), 0x69430a30),
423 (u32::MAX, (0..=255).collect(), 0xf74e868e),
424 ] {
425 assert_eq!(checksum(uin, &payload), expected);
426 }
427 }
428 #[test]
429 fn prelogin_profiles_validate() {
430 for region in [1, 2] {
431 let profile = profile_for_region(region).unwrap();
432 assert!(
433 Prelogin::prepare(b"synthetic", &profile, &[])
434 .unwrap()
435 .request
436 .len()
437 > 100
438 );
439 }
440 assert!(profile_for_region(3).is_err());
441 }
442 #[test]
443 fn encrypted_envelope_rejects_aad() {
444 let key = [3; 24];
445 let aad = sha256(b"a");
446 let e = envelope_seal(b"synthetic", &key, &aad, [4; 12]).unwrap();
447 assert_eq!(envelope_open(&e, &key, &aad).unwrap(), b"synthetic");
448 assert!(envelope_open(&e, &key, &sha256(b"b")).is_err());
449 }
450 #[test]
451 fn synthetic_signed_peer_and_saved_credential() {
452 use openssl::{
453 ec::{EcGroup, EcKey},
454 hash::MessageDigest,
455 pkey::PKey,
456 sign::Signer,
457 };
458 let static_key = Ecdh::generate(Nid::X9_62_PRIME256V1).unwrap();
459 let signing = PKey::from_ec_key(
460 EcKey::generate(&EcGroup::from_curve_name(Nid::X9_62_PRIME256V1).unwrap()).unwrap(),
461 )
462 .unwrap();
463 let profile = PublicProfile {
464 version: 1,
465 point: static_key.public().unwrap(),
466 signature_pem: String::from_utf8(signing.public_key_to_pem().unwrap()).unwrap(),
467 };
468 for credential in [1, 2] {
469 let auth = if credential == 2 {
470 vec![11; 380]
471 } else {
472 vec![]
473 };
474 let request = b"synthetic login";
475 let local = Prelogin::prepare(request, &profile, &auth).unwrap();
476 let f = p::decode(&local.request).unwrap();
477 let key = p::decode(p::data(&f, 2).unwrap()).unwrap();
478 let point = p::data(&key, 2).unwrap();
479 let prefix = [b"1415".as_slice(), point].concat();
480 let aad = sha256(&prefix);
481 let random = envelope_open(
482 p::data(&f, 3).unwrap(),
483 &sha256(&static_key.derive(point).unwrap())[..24],
484 &aad,
485 )
486 .unwrap();
487 let derived =
488 expand(&hmac(b"security hdkf expand", &random).unwrap(), &aad, 56).unwrap();
489 assert_eq!(
490 envelope_open(
491 p::data(&f, 5).unwrap(),
492 &derived[..24],
493 &sha256(
494 &[
495 prefix,
496 p::data(&f, 3).unwrap().to_vec(),
497 p::data(&f, 4).unwrap().to_vec()
498 ]
499 .concat()
500 )
501 )
502 .unwrap(),
503 request
504 );
505 if credential == 2 {
506 assert_eq!(
507 envelope_open(p::data(&f, 4).unwrap(), &auth[..24], &aad).unwrap(),
508 random
509 );
510 }
511 let ephemeral = Ecdh::generate(Nid::X9_62_PRIME256V1).unwrap();
512 let server_point = ephemeral.public().unwrap();
513 let response_aad = sha256(
514 &[
515 &derived[24..],
516 request,
517 b"415",
518 &server_point,
519 credential.to_string().as_bytes(),
520 ]
521 .concat(),
522 );
523 let ciphertext = envelope_seal(
524 b"synthetic authenticated reply",
525 &sha256(&ephemeral.derive(point).unwrap())[..24],
526 &response_aad,
527 [4; 12],
528 )
529 .unwrap();
530 let mut fields = vec![
531 p::bytes(
532 1,
533 &p::message(&[p::uint(1, 415), p::bytes(2, &server_point)]).unwrap(),
534 ),
535 p::uint(2, credential),
536 p::bytes(3, &ciphertext),
537 ];
538 if credential == 1 {
539 let mut signer = Signer::new(MessageDigest::sha256(), &signing).unwrap();
540 signer
541 .update(&sha256(&[p::data(&f, 5).unwrap(), &ciphertext].concat()))
542 .unwrap();
543 fields.push(p::bytes(4, &signer.sign_to_vec().unwrap()));
544 }
545 let response = p::message(&fields).unwrap();
546 assert_eq!(
547 local.decrypt_reply(&response).unwrap(),
548 b"synthetic authenticated reply"
549 );
550 let mut corrupt = response;
551 let n = corrupt.len();
552 corrupt[n - 1] ^= 1;
553 assert!(local.decrypt_reply(&corrupt).is_err());
554 }
555 }
556}