Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: using legacy enc key #74

Merged
merged 1 commit into from
Mar 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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