Skip to content

Commit

Permalink
fix: using legacy enc key (#74)
Browse files Browse the repository at this point in the history
  • Loading branch information
XuNeal authored Mar 8, 2024
1 parent b9297b4 commit 624a092
Show file tree
Hide file tree
Showing 5 changed files with 201 additions and 91 deletions.
170 changes: 89 additions & 81 deletions token-core/tcx-keystore/src/identity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,107 +113,116 @@ impl Identity {
}

pub fn encrypt_ipfs(&self, plaintext: &str) -> Result<String> {
let iv: [u8; 16] = random_u8_16();
self.encrypt_ipfs_wth_timestamp_iv(plaintext, unix_timestamp(), &iv)
encrypt_ipfs_with_enc_key(&self.enc_key, plaintext)
}
fn encrypt_ipfs_wth_timestamp_iv(

pub fn decrypt_ipfs(&self, ciphertext: &str) -> Result<String> {
decrypt_ipfs_with_enc_key(ciphertext, &self.ipfs_id, &self.enc_key)
}

pub fn sign_authentication_message(
&self,
plaintext: &str,
timestamp: u64,
iv: &[u8; 16],
access_time: u64,
device_token: &str,
unlocker: &Unlocker,
) -> Result<String> {
let mut header = Vec::new();

header.write_u8(0x03)?;
header.write_all(&timestamp.to_le_bytes()[..4])?;
header.write_all(iv)?;
let enc_auth_key = unlocker.decrypt_enc_pair(&self.enc_auth_key)?;
let mut signature = Secp256k1PrivateKey::from_slice(&enc_auth_key)?.sign_recoverable(
&keccak256(format!("{}.{}.{}", access_time, self.identifier, device_token).as_bytes()),
)?;
signature[64] += 27;
Ok(signature.to_0x_hex())
}
}

let enc_key = Vec::from_hex(&self.enc_key)?;
pub fn decrypt_ipfs_with_enc_key(ciphertext: &str, ipfs_id: &str, enc_key: &str) -> Result<String> {
let ciphertext = Vec::<u8>::from_hex(ciphertext)?;
if ciphertext.len() <= 21 {
return Err(Error::InvalidEncryptionData.into());
}

let ciphertext = encrypt_pkcs7(plaintext.as_bytes(), &enc_key[0..16], iv)?;
let hash = keccak256(&[header.clone(), merkle_hash(&ciphertext).to_vec()].concat());
let mut signature = Secp256k1PrivateKey::from_slice(&enc_key)?.sign_recoverable(&hash)?;
//ETH-compatible ec_recover, in chain_id = 1 case, v = 27 + rec_id
signature[64] += 27;
let mut rdr = Cursor::new(&ciphertext);
let mut header = vec![];

let var_len = VarInt(ciphertext.len() as u64);
let version = rdr.read_u8()?;
if version != 0x03 {
return Err(Error::UnsupportEncryptionDataVersion.into());
}
header.write_u8(version)?;
header.write_u32::<LittleEndian>(rdr.read_u32::<LittleEndian>()?)?;

let mut payload = vec![];
payload.write_all(&header)?;
var_len.consensus_encode(&mut payload)?;
payload.write_all(&ciphertext)?;
payload.write_all(&signature)?;
let mut iv = [0u8; 16];
rdr.read_exact(&mut iv)?;
header.write_all(&iv)?;

Ok(payload.to_hex())
let var_len = VarInt::consensus_decode(&mut rdr)?;
if var_len.0 as usize != ciphertext.len() - 21 - 65 - var_len.len() {
return Err(Error::InvalidEncryptionData.into());
}

pub fn decrypt_ipfs(&self, ciphertext: &str) -> Result<String> {
let ciphertext = Vec::<u8>::from_hex(ciphertext)?;
if ciphertext.len() <= 21 {
return Err(Error::InvalidEncryptionData.into());
}
let mut enc_data = vec![0u8; var_len.0 as usize];
rdr.read_exact(&mut enc_data)?;

let mut rdr = Cursor::new(&ciphertext);
let mut header = vec![];
let mut signature = [0u8; 64];
rdr.read_exact(&mut signature)?;

let version = rdr.read_u8()?;
if version != 0x03 {
return Err(Error::UnsupportEncryptionDataVersion.into());
}
header.write_u8(version)?;
header.write_u32::<LittleEndian>(rdr.read_u32::<LittleEndian>()?)?;
let recover_id = RecoveryId::from_i32(rdr.read_u8()? as i32 - 27)?;

let mut iv = [0u8; 16];
rdr.read_exact(&mut iv)?;
header.write_all(&iv)?;
let hash = keccak256(
[header, merkle_hash(&enc_data).to_vec()]
.concat()
.as_slice(),
);

let var_len = VarInt::consensus_decode(&mut rdr)?;
if var_len.0 as usize != ciphertext.len() - 21 - 65 - var_len.len() {
return Err(Error::InvalidEncryptionData.into());
}
let message = Message::from_slice(&hash)?;
let sig = RecoverableSignature::from_compact(&signature, recover_id)?;
let pub_key = Secp256k1::new().recover_ecdsa(&message, &sig)?;
let calculated_ipfs_id = Identity::calculate_ipfs_id(&pub_key);

let mut enc_data = vec![0u8; var_len.0 as usize];
rdr.read_exact(&mut enc_data)?;
if ipfs_id != calculated_ipfs_id {
return Err(Error::InvalidEncryptionDataSignature.into());
}

let mut signature = [0u8; 64];
rdr.read_exact(&mut signature)?;
let enc_key = Vec::from_hex(&enc_key)?;
let plaintext = decrypt_pkcs7(&enc_data, &enc_key[..16], &iv)?;

let recover_id = RecoveryId::from_i32(rdr.read_u8()? as i32 - 27)?;
Ok(String::from_utf8(plaintext)?)
}

let hash = keccak256(
[header, merkle_hash(&enc_data).to_vec()]
.concat()
.as_slice(),
);
pub fn encrypt_ipfs_with_enc_key(enc_key: &str, plaintext: &str) -> Result<String> {
let iv: [u8; 16] = random_u8_16();
encrypt_ipfs_wth_timestamp_iv(&enc_key, plaintext, unix_timestamp(), &iv)
}

let message = Message::from_slice(&hash)?;
let sig = RecoverableSignature::from_compact(&signature, recover_id)?;
let pub_key = Secp256k1::new().recover_ecdsa(&message, &sig)?;
let ipfs_id = Self::calculate_ipfs_id(&pub_key);
fn encrypt_ipfs_wth_timestamp_iv(
enc_key: &str,
plaintext: &str,
timestamp: u64,
iv: &[u8; 16],
) -> Result<String> {
let mut header = Vec::new();

if self.ipfs_id != ipfs_id {
return Err(Error::InvalidEncryptionDataSignature.into());
}
header.write_u8(0x03)?;
header.write_all(&timestamp.to_le_bytes()[..4])?;
header.write_all(iv)?;

let enc_key = Vec::from_hex(&self.enc_key)?;
let plaintext = decrypt_pkcs7(&enc_data, &enc_key[..16], &iv)?;
let enc_key = Vec::from_hex_auto(&enc_key)?;

Ok(String::from_utf8(plaintext)?)
}
let ciphertext = encrypt_pkcs7(plaintext.as_bytes(), &enc_key[0..16], iv)?;
let hash = keccak256(&[header.clone(), merkle_hash(&ciphertext).to_vec()].concat());
let mut signature = Secp256k1PrivateKey::from_slice(&enc_key)?.sign_recoverable(&hash)?;
//ETH-compatible ec_recover, in chain_id = 1 case, v = 27 + rec_id
signature[64] += 27;

pub fn sign_authentication_message(
&self,
access_time: u64,
device_token: &str,
unlocker: &Unlocker,
) -> Result<String> {
let enc_auth_key = unlocker.decrypt_enc_pair(&self.enc_auth_key)?;
let mut signature = Secp256k1PrivateKey::from_slice(&enc_auth_key)?.sign_recoverable(
&keccak256(format!("{}.{}.{}", access_time, self.identifier, device_token).as_bytes()),
)?;
signature[64] += 27;
Ok(signature.to_0x_hex())
}
let var_len = VarInt(ciphertext.len() as u64);

let mut payload = vec![];
payload.write_all(&header)?;
var_len.consensus_encode(&mut payload)?;
payload.write_all(&ciphertext)?;
payload.write_all(&signature)?;

Ok(payload.to_hex())
}

#[cfg(test)]
Expand All @@ -224,6 +233,7 @@ mod test {
use tcx_constants::CurveType;
use tcx_crypto::Key;

use crate::identity::encrypt_ipfs_wth_timestamp_iv;
use crate::{keystore::IdentityNetwork, Error, HdKeystore, Metadata, PrivateKeystore};

#[test]
Expand Down Expand Up @@ -313,9 +323,7 @@ mod test {
for t in test_cases {
let iv: [u8; 16] = Vec::from_hex(t.1).unwrap().try_into().unwrap();
assert_eq!(
identity
.encrypt_ipfs_wth_timestamp_iv(t.0, unix_timestamp, &iv)
.unwrap(),
encrypt_ipfs_wth_timestamp_iv(&identity.enc_key, t.0, unix_timestamp, &iv).unwrap(),
t.2
);
assert_eq!(identity.decrypt_ipfs(t.2).unwrap(), t.0);
Expand Down
51 changes: 51 additions & 0 deletions token-core/tcx-migration/src/legacy_ipfs.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
use anyhow::anyhow;
use serde::{Deserialize, Serialize};
use std::fs;
use std::io::Read;
use tcx_constants::{coin_info_from_param, CurveType};
use tcx_crypto::{Crypto, EncPair, Key};
use tcx_keystore::identity::Identity;
use tcx_keystore::keystore::{IdentityNetwork, Keystore, Metadata, Store};
use tcx_keystore::{
fingerprint_from_private_key, fingerprint_from_seed, mnemonic_to_seed, Address, HdKeystore,
PrivateKeystore, Result, Source,
};
use tcx_primitive::{PrivateKey, Secp256k1PrivateKey, TypedPublicKey};

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct IpfsInfo {
pub identifier: String,
pub ipfs_id: String,
pub enc_key: String,
}

pub fn read_legacy_ipfs_info(filepath: &str) -> Result<IpfsInfo> {
let mut identify_file = fs::File::open(&filepath)?;

let mut json_str = String::new();
identify_file.read_to_string(&mut json_str)?;
let ipfs_info: IpfsInfo = serde_json::from_str(&json_str)?;
Ok(ipfs_info)
}

#[cfg(test)]
mod tests {
use super::read_legacy_ipfs_info;
#[test]
fn test_scan_tcx_legacy_keystores() {
let ipfs_info = read_legacy_ipfs_info("../test-data/wallets/identity.json").unwrap();
assert_eq!(
ipfs_info.identifier,
"im18MDKM8hcTykvMmhLnov9m2BaFqsdjoA7cwNg"
);
assert_eq!(
ipfs_info.ipfs_id,
"QmSTTidyfa4np9ak9BZP38atuzkCHy4K59oif23f4dNAGU"
);
assert_eq!(
ipfs_info.enc_key,
"9513617c9b398edebfb46080a8f0cf6cab6763866bb06daa63503722bea78907"
);
}
}
1 change: 1 addition & 0 deletions token-core/tcx-migration/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
pub mod keystore_upgrade;
pub mod legacy_ipfs;
pub mod migration;
39 changes: 29 additions & 10 deletions token-core/tcx/src/handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use std::str::FromStr;
use tcx_eos::encode_eos_wif;
use tcx_eth2::transaction::{SignBlsToExecutionChangeParam, SignBlsToExecutionChangeResult};
use tcx_keystore::keystore::IdentityNetwork;
use tcx_migration::legacy_ipfs;

use tcx_common::{FromHex, ToHex};
use tcx_primitive::{
Expand All @@ -17,12 +18,12 @@ use tcx_primitive::{

use tcx_btc_kin::WIFDisplay;
use tcx_keystore::{
fingerprint_from_mnemonic, fingerprint_from_private_key, Keystore, KeystoreGuard,
fingerprint_from_mnemonic, fingerprint_from_private_key, identity, Keystore, KeystoreGuard,
SignatureParameters, Signer,
};
use tcx_keystore::{Account, HdKeystore, Metadata, PrivateKeystore, Source};

use anyhow::anyhow;
use anyhow::{anyhow, ensure};
use tcx_crypto::{XPUB_COMMON_IV, XPUB_COMMON_KEY_128};
use tcx_filecoin::KeyInfo;

Expand Down Expand Up @@ -1098,12 +1099,21 @@ pub(crate) fn encrypt_data_to_ipfs(data: &[u8]) -> Result<Vec<u8>> {
let param = EncryptDataToIpfsParam::decode(data).expect("EncryptDataToIpfsParam");

let map = KEYSTORE_MAP.read();
let Some(identity_ks) = map.values().find(|ks| ks.identity().identifier == param.identifier) else {
return Err(anyhow::anyhow!("identity_not_found"));
let cipher_text = if let Some(identity_ks) = map
.values()
.find(|ks| ks.identity().identifier == param.identifier)
{
identity_ks.identity().encrypt_ipfs(&param.content)?
} else {
let legacy_identify_path = &format!("{}/identity.json", LEGACY_WALLET_FILE_DIR.read());
let legacy_ipfs_info = legacy_ipfs::read_legacy_ipfs_info(&legacy_identify_path)?;
ensure!(
legacy_ipfs_info.identifier == param.identifier,
"wallet_not_found"
);
identity::encrypt_ipfs_with_enc_key(&legacy_ipfs_info.enc_key, &param.content)?
};

let cipher_text = identity_ks.identity().encrypt_ipfs(&param.content)?;

let output = EncryptDataToIpfsResult {
identifier: param.identifier.to_string(),
encrypted: cipher_text,
Expand All @@ -1116,12 +1126,21 @@ pub(crate) fn decrypt_data_from_ipfs(data: &[u8]) -> Result<Vec<u8>> {
let param = DecryptDataFromIpfsParam::decode(data).expect("DecryptDataFromIpfsParam");

let map = KEYSTORE_MAP.read();
let Some(identity_ks) = map.values().find(|ks| ks.identity().identifier == param.identifier) else {
return Err(anyhow::anyhow!("identity_not_found"));
let content = if let Some(identity_ks) = map
.values()
.find(|ks| ks.identity().identifier == param.identifier)
{
identity_ks.identity().decrypt_ipfs(&param.encrypted)?
} else {
let legacy_identify_path = &format!("{}/identity.json", LEGACY_WALLET_FILE_DIR.read());
let legacy_ipfs_data = legacy_ipfs::read_legacy_ipfs_info(&legacy_identify_path)?;
identity::decrypt_ipfs_with_enc_key(
&param.encrypted,
&legacy_ipfs_data.ipfs_id,
&legacy_ipfs_data.enc_key,
)?
};

let content = identity_ks.identity().decrypt_ipfs(&param.encrypted)?;

let output = DecryptDataFromIpfsResult {
identifier: param.identifier.to_string(),
content,
Expand Down
31 changes: 31 additions & 0 deletions token-core/tcx/tests/other_test.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::fs;

use common::run_test;
use serial_test::serial;

Expand Down Expand Up @@ -243,6 +245,35 @@ pub fn test_ipfs_encrypt_and_decrypt() {
})
}

#[test]
#[serial]
pub fn test_ipfs_encrypt_and_decrypt_before_migrate() {
run_test(|| {
fs::copy(
"../test-data/wallets/identity.json",
"/tmp/imtoken/wallets/identity.json",
)
.unwrap();
let content = "imToken".to_string();
let param = EncryptDataToIpfsParam {
identifier: "im18MDKM8hcTykvMmhLnov9m2BaFqsdjoA7cwNg".to_string(),
content: content.clone(),
};
let ret = call_api("encrypt_data_to_ipfs", param).unwrap();
let resp: EncryptDataToIpfsResult =
EncryptDataToIpfsResult::decode(ret.as_slice()).unwrap();
assert!(!resp.encrypted.is_empty());
let param = DecryptDataFromIpfsParam {
identifier: "im18MDKM8hcTykvMmhLnov9m2BaFqsdjoA7cwNg".to_string(),
encrypted: resp.encrypted,
};
let ret = call_api("decrypt_data_from_ipfs", param).unwrap();
let resp: DecryptDataFromIpfsResult =
DecryptDataFromIpfsResult::decode(ret.as_slice()).unwrap();
assert_eq!(content, resp.content);
})
}

#[test]
#[serial]
pub fn test_sign_authentication_message() {
Expand Down

0 comments on commit 624a092

Please sign in to comment.