Skip to content

Commit c4dc73b

Browse files
Fix pubkey derivation and zeroize TWString/TWData (#4484)
* Normalisation and derivation fixes * Minor * Removes dependency on ed25519-bip32 * FMT * Clippy * Add iv check * Use ios 18.0 in CI * Use ios 18.6 * Add switch xcode step * Fix pubkey derivation and zeroize TWString/TWData * Clippy * Add xcode switch in kotlin sample CI * Fix pubkey derivation * Fix C++ zerozing, use sodium comparison and minor fixes * FMT * Remove switch step * Fix issues * Adds remaining fixes * Fix tests * Minor fixes * Update code coverage threshold for now * Use memzero * Fix --------- Co-authored-by: Sergei Boiko <[email protected]>
1 parent d191810 commit c4dc73b

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+510
-182
lines changed

rust/Cargo.lock

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

rust/coverage.stats

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
94.1
1+
94.0

rust/tw_crypto/src/crypto_aes_cbc/mod.rs

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,10 @@ fn aes_cbc_encrypt_impl<E: KeyIvInit + BlockEncryptMut>(
3535
if iv.len() != IV_SIZE {
3636
return Err(StreamCipherError);
3737
}
38-
let key = if key.len() > key_size {
39-
&key[0..key_size]
40-
} else {
41-
key
42-
};
38+
if key.len() < key_size {
39+
return Err(StreamCipherError);
40+
}
41+
let key = &key[0..key_size];
4342
let data = padding_mode.pad(data);
4443
let mut blocks = blocks::<E>(&data);
4544
let mut cipher = E::new(key.into(), iv.into());
@@ -55,19 +54,21 @@ fn aes_cbc_decrypt_impl<D: KeyIvInit + BlockDecryptMut>(
5554
key_size: usize,
5655
padding_mode: PaddingMode,
5756
) -> Result<Vec<u8>, StreamCipherError> {
58-
let key = if key.len() > key_size {
59-
&key[0..key_size]
60-
} else {
61-
key
62-
};
57+
if iv.len() != IV_SIZE {
58+
return Err(StreamCipherError);
59+
}
60+
if key.len() < key_size {
61+
return Err(StreamCipherError);
62+
}
63+
let key = &key[0..key_size];
6364
if data.len() % BLOCK_SIZE_AES != 0 {
6465
return Err(StreamCipherError);
6566
}
6667
let mut blocks = blocks::<D>(data);
6768
let mut cipher = D::new(key.into(), iv.into());
6869
cipher.decrypt_blocks_mut(&mut blocks[..]);
6970
let buffer = blocks.concat();
70-
Ok(padding_mode.unpad(&buffer))
71+
padding_mode.unpad(&buffer).map_err(|_| StreamCipherError)
7172
}
7273

7374
pub fn aes_cbc_encrypt_128(

rust/tw_crypto/src/crypto_aes_cbc/padding.rs

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ pub type TWFFIAESPaddingMode = u32;
1818
#[derive(Clone, Copy, Debug)]
1919
pub struct InvalidPaddingMode;
2020

21+
#[derive(Clone, Copy, Debug)]
22+
pub struct InvalidPadding;
23+
2124
impl TryFrom<u32> for PaddingMode {
2225
type Error = InvalidPaddingMode;
2326

@@ -64,22 +67,28 @@ impl PaddingMode {
6467
padded
6568
}
6669

67-
pub fn unpad(&self, data: &[u8]) -> Vec<u8> {
70+
pub fn unpad(&self, data: &[u8]) -> Result<Vec<u8>, InvalidPadding> {
6871
if data.is_empty() {
69-
return data.to_vec();
72+
return Ok(data.to_vec());
7073
}
7174
match self {
7275
PaddingMode::PKCS7 => {
7376
let padding_len = data[data.len() - 1] as usize;
74-
if padding_len <= data.len() {
75-
data[..data.len() - padding_len].to_vec()
76-
} else {
77-
data.to_vec()
77+
if padding_len == 0 || padding_len > BLOCK_SIZE_AES || padding_len > data.len() {
78+
return Err(InvalidPadding);
79+
}
80+
// Check that all padding bytes are equal to padding_len
81+
if !data[data.len() - padding_len..]
82+
.iter()
83+
.all(|&b| b as usize == padding_len)
84+
{
85+
return Err(InvalidPadding);
7886
}
87+
Ok(data[..data.len() - padding_len].to_vec())
7988
},
8089
// Zero padding can be ambiguous, so we return the original data
8190
// and the caller can strip the padding if needed
82-
PaddingMode::Zero => data.to_vec(),
91+
PaddingMode::Zero => Ok(data.to_vec()),
8392
}
8493
}
8594
}

rust/tw_crypto/src/crypto_aes_ctr/mod.rs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,20 +13,24 @@ type Aes128Ctr64BE = Ctr64BE<Aes128>;
1313
type Aes192Ctr64BE = Ctr64BE<Aes192>;
1414
type Aes256Ctr64BE = Ctr64BE<Aes256>;
1515

16+
const MAX_DATA_SIZE: usize = 10 * 1024 * 1024; // 10 MB
17+
1618
fn aes_ctr_process<C: KeyIvInit + StreamCipher>(
1719
data: &[u8],
1820
iv: &[u8],
1921
key: &[u8],
2022
key_size: usize,
2123
) -> Result<Vec<u8>, StreamCipherError> {
24+
if data.len() > MAX_DATA_SIZE {
25+
return Err(StreamCipherError);
26+
}
2227
if iv.len() != IV_SIZE {
2328
return Err(StreamCipherError);
2429
}
25-
let key = if key.len() > key_size {
26-
&key[0..key_size]
27-
} else {
28-
key
29-
};
30+
if key.len() < key_size {
31+
return Err(StreamCipherError);
32+
}
33+
let key = &key[0..key_size];
3034
let mut cipher = C::new(key.into(), iv.into());
3135
let mut data_vec = data.to_vec();
3236
cipher.try_apply_keystream(&mut data_vec)?;

rust/tw_crypto/src/crypto_hd_node/ecdsa/nist256p1/mod.rs

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
// terms governing use, modification, and redistribution, is contained in the
55
// file LICENSE at the root of the source code distribution tree.
66

7-
use bip32::ChildNumber;
7+
use bip32::{ChainCode, ChildNumber};
88
use tw_hash::H256;
99
use tw_keypair::traits::DerivableKeyTrait;
1010
use tw_keypair::{ecdsa::nist256p1, tw::Curve};
@@ -37,9 +37,28 @@ impl BIP32PrivateKey for nist256p1::PrivateKey {
3737
}
3838

3939
impl BIP32PublicKey for nist256p1::PublicKey {
40-
fn derive_child(&self, other: &[u8], _child_number: ChildNumber) -> Result<Self> {
41-
let other = H256::try_from(other).map_err(|_| Error::InvalidKeyData)?;
42-
<nist256p1::PublicKey as DerivableKeyTrait>::derive_child(self, other)
43-
.map_err(|_| Error::DerivationFailed)
40+
fn curve() -> Curve {
41+
Curve::Nist256p1
42+
}
43+
44+
fn derive_child(
45+
&self,
46+
chain_code: &ChainCode,
47+
child_number: ChildNumber,
48+
) -> Result<(Self, ChainCode)> {
49+
let (tweak, chain_code) = self.derive_tweak(chain_code, child_number)?;
50+
// We should technically loop here if the tweak is zero or overflows
51+
// the order of the underlying elliptic curve group, incrementing the
52+
// index, however per "Child key derivation (CKD) functions":
53+
// https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#child-key-derivation-ckd-functions
54+
//
55+
// > "Note: this has probability lower than 1 in 2^127."
56+
//
57+
// ...so instead, we simply return an error if this were ever to happen,
58+
// as the chances of it happening are vanishingly small.
59+
let tweak = H256::try_from(&tweak[..]).map_err(|_| Error::InvalidKeyData)?;
60+
let public_key = <nist256p1::PublicKey as DerivableKeyTrait>::derive_child(self, tweak)
61+
.map_err(|_| Error::DerivationFailed)?;
62+
Ok((public_key, chain_code))
4463
}
4564
}

rust/tw_crypto/src/crypto_hd_node/ecdsa/secp256k1/mod.rs

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
// terms governing use, modification, and redistribution, is contained in the
55
// file LICENSE at the root of the source code distribution tree.
66

7-
use bip32::ChildNumber;
7+
use bip32::{ChainCode, ChildNumber};
88
use tw_hash::H256;
99
use tw_keypair::traits::DerivableKeyTrait;
1010
use tw_keypair::{ecdsa::secp256k1, tw::Curve};
@@ -37,9 +37,28 @@ impl BIP32PrivateKey for secp256k1::PrivateKey {
3737
}
3838

3939
impl BIP32PublicKey for secp256k1::PublicKey {
40-
fn derive_child(&self, other: &[u8], _child_number: ChildNumber) -> Result<Self> {
41-
let other = H256::try_from(other).map_err(|_| Error::InvalidKeyData)?;
42-
<secp256k1::PublicKey as DerivableKeyTrait>::derive_child(self, other)
43-
.map_err(|_| Error::DerivationFailed)
40+
fn curve() -> Curve {
41+
Curve::Secp256k1
42+
}
43+
44+
fn derive_child(
45+
&self,
46+
chain_code: &ChainCode,
47+
child_number: ChildNumber,
48+
) -> Result<(Self, ChainCode)> {
49+
let (tweak, chain_code) = self.derive_tweak(chain_code, child_number)?;
50+
// We should technically loop here if the tweak is zero or overflows
51+
// the order of the underlying elliptic curve group, incrementing the
52+
// index, however per "Child key derivation (CKD) functions":
53+
// https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#child-key-derivation-ckd-functions
54+
//
55+
// > "Note: this has probability lower than 1 in 2^127."
56+
//
57+
// ...so instead, we simply return an error if this were ever to happen,
58+
// as the chances of it happening are vanishingly small.
59+
let tweak = H256::try_from(&tweak[..]).map_err(|_| Error::InvalidKeyData)?;
60+
let public_key = <secp256k1::PublicKey as DerivableKeyTrait>::derive_child(self, tweak)
61+
.map_err(|_| Error::DerivationFailed)?;
62+
Ok((public_key, chain_code))
4463
}
4564
}

rust/tw_crypto/src/crypto_hd_node/ed25519/cardano/mod.rs

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ mod v2;
1111

1212
use std::str::FromStr;
1313

14-
use bip32::{ChildNumber, DerivationPath};
14+
use bip32::{ChainCode, ChildNumber, DerivationPath};
1515
use derivation::DerivationScheme;
1616
use key::{XPRV_SIZE, XPUB_SIZE};
1717
use tw_keypair::{ed25519, tw::Curve};
@@ -38,7 +38,7 @@ impl BIP32PrivateKey for ed25519::cardano::ExtendedPrivateKey {
3838
let bytes = self.to_zeroizing_vec();
3939
let bytes: [u8; XPRV_SIZE] = bytes.as_slice()[..XPRV_SIZE]
4040
.try_into()
41-
.expect("Should not fail");
41+
.map_err(|_| Error::InvalidKeyData)?;
4242
let bip32_xpr = key::XPrv::from_bytes_verified(bytes).map_err(|_| Error::InvalidKeyData)?;
4343
let child: key::XPrv = bip32_xpr.derive(DerivationScheme::V2, child_number.0);
4444
Self::try_from(child.as_ref()).map_err(|_| Error::InvalidKeyData)
@@ -58,14 +58,26 @@ impl BIP32PrivateKey for ed25519::cardano::ExtendedPrivateKey {
5858
}
5959

6060
impl BIP32PublicKey for ed25519::cardano::ExtendedPublicKey {
61-
fn derive_child(&self, _other: &[u8], child_number: ChildNumber) -> Result<Self> {
61+
fn curve() -> Curve {
62+
Curve::Ed25519ExtendedCardano
63+
}
64+
65+
fn derive_child(
66+
&self,
67+
_chain_code: &ChainCode,
68+
child_number: ChildNumber,
69+
) -> Result<(Self, ChainCode)> {
6270
let bytes = self.to_vec();
63-
let bytes: [u8; XPUB_SIZE] = bytes[..XPUB_SIZE].try_into().expect("Should not fail");
71+
let bytes: [u8; XPUB_SIZE] = bytes[..XPUB_SIZE]
72+
.try_into()
73+
.map_err(|_| Error::InvalidKeyData)?;
6474
let bip32_xpub = key::XPub::from_bytes(bytes);
6575
let child: key::XPub = bip32_xpub
6676
.derive(DerivationScheme::V2, child_number.0)
6777
.map_err(|_| Error::DerivationFailed)?;
68-
Self::try_from(child.as_ref()).map_err(|_| Error::InvalidKeyData)
78+
let public_key = Self::try_from(child.as_ref()).map_err(|_| Error::InvalidKeyData)?;
79+
let chaincode = *child.chain_code();
80+
Ok((public_key, chaincode))
6981
}
7082
}
7183

rust/tw_crypto/src/crypto_hd_node/ed25519/mod.rs

Lines changed: 43 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
pub mod cardano;
88
pub mod waves;
99

10-
use bip32::ChildNumber;
10+
use bip32::{ChainCode, ChildNumber};
1111
use tw_keypair::{ed25519, tw::Curve};
1212

1313
use crate::crypto_hd_node::error::{Error, Result};
@@ -40,9 +40,28 @@ impl BIP32PrivateKey for ed25519::sha512::PrivateKey {
4040
}
4141

4242
impl BIP32PublicKey for ed25519::sha512::PublicKey {
43-
fn derive_child(&self, other: &[u8], child_number: ChildNumber) -> Result<Self> {
43+
fn curve() -> Curve {
44+
Curve::Ed25519
45+
}
46+
47+
fn derive_child(
48+
&self,
49+
chain_code: &ChainCode,
50+
child_number: ChildNumber,
51+
) -> Result<(Self, ChainCode)> {
52+
let (tweak, chain_code) = self.derive_tweak(chain_code, child_number)?;
53+
// We should technically loop here if the tweak is zero or overflows
54+
// the order of the underlying elliptic curve group, incrementing the
55+
// index, however per "Child key derivation (CKD) functions":
56+
// https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#child-key-derivation-ckd-functions
57+
//
58+
// > "Note: this has probability lower than 1 in 2^127."
59+
//
60+
// ...so instead, we simply return an error if this were ever to happen,
61+
// as the chances of it happening are vanishingly small.
4462
if child_number.is_hardened() {
45-
Self::try_from(other).map_err(|_| Error::InvalidKeyData)
63+
let public_key = Self::try_from(&tweak[..]).map_err(|_| Error::InvalidKeyData)?;
64+
Ok((public_key, chain_code))
4665
} else {
4766
Err(Error::InvalidChildNumber)
4867
}
@@ -74,9 +93,28 @@ impl BIP32PrivateKey for ed25519::blake2b::PrivateKey {
7493
}
7594

7695
impl BIP32PublicKey for ed25519::blake2b::PublicKey {
77-
fn derive_child(&self, other: &[u8], child_number: ChildNumber) -> Result<Self> {
96+
fn curve() -> Curve {
97+
Curve::Ed25519Blake2bNano
98+
}
99+
100+
fn derive_child(
101+
&self,
102+
chain_code: &ChainCode,
103+
child_number: ChildNumber,
104+
) -> Result<(Self, ChainCode)> {
105+
let (tweak, chain_code) = self.derive_tweak(chain_code, child_number)?;
106+
// We should technically loop here if the tweak is zero or overflows
107+
// the order of the underlying elliptic curve group, incrementing the
108+
// index, however per "Child key derivation (CKD) functions":
109+
// https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#child-key-derivation-ckd-functions
110+
//
111+
// > "Note: this has probability lower than 1 in 2^127."
112+
//
113+
// ...so instead, we simply return an error if this were ever to happen,
114+
// as the chances of it happening are vanishingly small.
78115
if child_number.is_hardened() {
79-
Self::try_from(other).map_err(|_| Error::InvalidKeyData)
116+
let public_key = Self::try_from(&tweak[..]).map_err(|_| Error::InvalidKeyData)?;
117+
Ok((public_key, chain_code))
80118
} else {
81119
Err(Error::InvalidChildNumber)
82120
}

rust/tw_crypto/src/crypto_hd_node/ed25519/waves/mod.rs

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
// terms governing use, modification, and redistribution, is contained in the
55
// file LICENSE at the root of the source code distribution tree.
66

7-
use bip32::ChildNumber;
7+
use bip32::{ChainCode, ChildNumber};
88
use tw_keypair::{ed25519, tw::Curve};
99

1010
use crate::crypto_hd_node::error::{Error, Result};
@@ -37,9 +37,28 @@ impl BIP32PrivateKey for ed25519::waves::PrivateKey {
3737
}
3838

3939
impl BIP32PublicKey for ed25519::waves::PublicKey {
40-
fn derive_child(&self, other: &[u8], child_number: ChildNumber) -> Result<Self> {
40+
fn curve() -> Curve {
41+
Curve::Curve25519Waves
42+
}
43+
44+
fn derive_child(
45+
&self,
46+
chain_code: &ChainCode,
47+
child_number: ChildNumber,
48+
) -> Result<(Self, ChainCode)> {
49+
let (tweak, chain_code) = self.derive_tweak(chain_code, child_number)?;
50+
// We should technically loop here if the tweak is zero or overflows
51+
// the order of the underlying elliptic curve group, incrementing the
52+
// index, however per "Child key derivation (CKD) functions":
53+
// https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#child-key-derivation-ckd-functions
54+
//
55+
// > "Note: this has probability lower than 1 in 2^127."
56+
//
57+
// ...so instead, we simply return an error if this were ever to happen,
58+
// as the chances of it happening are vanishingly small.
4159
if child_number.is_hardened() {
42-
Self::try_from(other).map_err(|_| Error::InvalidKeyData)
60+
let public_key = Self::try_from(&tweak[..]).map_err(|_| Error::InvalidKeyData)?;
61+
Ok((public_key, chain_code))
4362
} else {
4463
Err(Error::InvalidChildNumber)
4564
}

0 commit comments

Comments
 (0)