diff --git a/.env b/.env index 49d411774..f3f9aff7a 100644 --- a/.env +++ b/.env @@ -1,2 +1 @@ -TRIGGERED_REF=v4.0.1 - +TRIGGERED_REF=v5.0.0 diff --git a/.github/workflows/gitlab.yml b/.github/workflows/gitlab.yml index 3d2b6b0cb..280d3d5ed 100644 --- a/.github/workflows/gitlab.yml +++ b/.github/workflows/gitlab.yml @@ -18,12 +18,12 @@ jobs: trigger-gitlab-pipeline: needs: read-triggered-ref - uses: NordSecurity/trigger-gitlab-pipeline/.github/workflows/trigger-gitlab-pipeline.yml@94a5d4a0ac0d6353948f0493c894dbd44b179bde + uses: NordSecurity/trigger-gitlab-pipeline/.github/workflows/trigger-gitlab-pipeline.yml@25e94a4be8c887376dc98ec536132594738ec32a secrets: - ci-api-v4-url: ${{ secrets.CI_API_V4_URL }} - access-token: ${{ secrets.GITLAB_API_TOKEN }} - trigger-token: ${{ secrets.TOKEN }} - project-id: ${{ secrets.PROJECT_ID }} + ci-api-v4-url: ${{ secrets.CODEHUB_CI_API_V4_URL }} + access-token: ${{ secrets.CODEHUB_GITLAB_API_TOKEN }} + trigger-token: ${{ secrets.CODEHUB_TOKEN }} + project-id: ${{ secrets.CODEHUB_PROJECT_ID }} with: cancel-outdated-pipelines: ${{ github.ref_name != 'main' }} triggered-ref: ${{ needs.read-triggered-ref.outputs.value }} # defined in .env file diff --git a/.github/workflows/gitlab_schedule.yml b/.github/workflows/gitlab_schedule.yml index 94cc819aa..2da4c62a7 100644 --- a/.github/workflows/gitlab_schedule.yml +++ b/.github/workflows/gitlab_schedule.yml @@ -52,12 +52,12 @@ jobs: trigger-gitlab-pipeline: needs: [read-triggered-ref, prepare-sha] - uses: NordSecurity/trigger-gitlab-pipeline/.github/workflows/trigger-gitlab-pipeline.yml@94a5d4a0ac0d6353948f0493c894dbd44b179bde + uses: NordSecurity/trigger-gitlab-pipeline/.github/workflows/trigger-gitlab-pipeline.yml@25e94a4be8c887376dc98ec536132594738ec32a secrets: - ci-api-v4-url: ${{ secrets.CI_API_V4_URL }} - access-token: ${{ secrets.GITLAB_API_TOKEN }} - trigger-token: ${{ secrets.TOKEN }} - project-id: ${{ secrets.PROJECT_ID }} + ci-api-v4-url: ${{ secrets.CODEHUB_CI_API_V4_URL }} + access-token: ${{ secrets.CODEHUB_GITLAB_API_TOKEN }} + trigger-token: ${{ secrets.CODEHUB_TOKEN }} + project-id: ${{ secrets.CODEHUB_PROJECT_ID }} with: schedule: true cancel-outdated-pipelines: false diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index d841e40a3..f4e5ed97d 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,3 +1,8 @@ +default: + tags: + - docker + - linux + prepare-trigger-env: stage: .pre script: diff --git a/.unreleased/LLT-6080_interface_filter_windows b/.unreleased/LLT-6080_interface_filter_windows deleted file mode 100644 index 6ebd5a5d1..000000000 --- a/.unreleased/LLT-6080_interface_filter_windows +++ /dev/null @@ -1 +0,0 @@ -Ability to pass an interface list in windows, for them to be excluded, when binding to "default interface" \ No newline at end of file diff --git a/.unreleased/LLT-6651 b/.unreleased/LLT-6442_adjustments_to_codehub similarity index 100% rename from .unreleased/LLT-6651 rename to .unreleased/LLT-6442_adjustments_to_codehub diff --git a/.unreleased/LLT-6657_add_openwrt_reconnect_tests b/.unreleased/LLT-6473_update_nordlynx_in_nat-lab similarity index 100% rename from .unreleased/LLT-6657_add_openwrt_reconnect_tests rename to .unreleased/LLT-6473_update_nordlynx_in_nat-lab diff --git a/.unreleased/LLT-6733_openwrt_wait_for_cleanup b/.unreleased/LLT-6733_openwrt_wait_for_cleanup deleted file mode 100644 index e69de29bb..000000000 diff --git a/.unreleased/LLT-6738_config_tuneup b/.unreleased/LLT-6738_config_tuneup deleted file mode 100644 index e69de29bb..000000000 diff --git a/.unreleased/LLT-6755_rename_commands b/.unreleased/LLT-6755_rename_commands deleted file mode 100644 index e69de29bb..000000000 diff --git a/.unreleased/LLT-6760_add_openwrt_ui_check b/.unreleased/LLT-6760_add_openwrt_ui_check deleted file mode 100644 index e69de29bb..000000000 diff --git a/.unreleased/LLT-6763 b/.unreleased/LLT-6763 deleted file mode 100644 index e69de29bb..000000000 diff --git a/.unreleased/LLT-6694_select_race b/.unreleased/LLT-6843-install-uv similarity index 100% rename from .unreleased/LLT-6694_select_race rename to .unreleased/LLT-6843-install-uv diff --git a/.unreleased/LLT-6972 b/.unreleased/LLT-6972 new file mode 100644 index 000000000..568583d4c --- /dev/null +++ b/.unreleased/LLT-6972 @@ -0,0 +1 @@ +Fix `tcli` tool auth to follow basic authorization standard diff --git a/.unreleased/allow-derpcli-and-interderp-build-from-source b/.unreleased/allow-derpcli-and-interderp-build-from-source deleted file mode 100644 index e69de29bb..000000000 diff --git a/.unreleased/bump_libtelio_build_to_v4.0.1 b/.unreleased/bump_libtelio_build_to_v4.0.1 deleted file mode 100644 index e69de29bb..000000000 diff --git a/.unreleased/LLT-6707_nvl_cleanup_quit b/.unreleased/bump_natlab_openwrt_image_tag similarity index 100% rename from .unreleased/LLT-6707_nvl_cleanup_quit rename to .unreleased/bump_natlab_openwrt_image_tag diff --git a/.unreleased/bump_rust_build_utils b/.unreleased/bump_rust_build_utils deleted file mode 100644 index e69de29bb..000000000 diff --git a/.unreleased/fix_conntrack_fail_message b/.unreleased/fix_conntrack_fail_message deleted file mode 100644 index e69de29bb..000000000 diff --git a/.unreleased/fix_end_proto_check_on_build_script b/.unreleased/fix_end_proto_check_on_build_script deleted file mode 100644 index e69de29bb..000000000 diff --git a/.unreleased/fix_upnp_interval_bug b/.unreleased/fix_upnp_interval_bug deleted file mode 100644 index e69de29bb..000000000 diff --git a/.unreleased/jj_offset_natlab_shards b/.unreleased/jj_offset_natlab_shards deleted file mode 100644 index e69de29bb..000000000 diff --git a/.unreleased/make_docker_client_services_depend_on_their_respective_gateways b/.unreleased/make_docker_client_services_depend_on_their_respective_gateways deleted file mode 100644 index e69de29bb..000000000 diff --git a/.unreleased/remove_get_wg_socket b/.unreleased/remove_get_wg_socket deleted file mode 100644 index e69de29bb..000000000 diff --git a/.unreleased/windows_logs_even_after_stop_failure b/.unreleased/windows_logs_even_after_stop_failure deleted file mode 100644 index e69de29bb..000000000 diff --git a/Cargo.lock b/Cargo.lock index 1a19bd40e..d8bfb22c9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4768,7 +4768,7 @@ dependencies = [ [[package]] name = "telio" -version = "6.1.0" +version = "6.2.1" dependencies = [ "anyhow", "async-trait", diff --git a/Cargo.toml b/Cargo.toml index a325e4690..c6d0f7ff1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "telio" -version = "6.1.0" +version = "6.2.1" authors = ["info@nordvpn.com"] edition = "2018" license = "GPL-3.0-only" diff --git a/Justfile b/Justfile index 1ded4dd43..3fa507330 100644 --- a/Justfile +++ b/Justfile @@ -106,7 +106,7 @@ _unused-install: _rust_stable-install _rust-from-toolchain-file-install cargo +{{ rust_stable }} install --version 0.2.0 cargo-unused-features --locked _deny-install: - cargo install --locked cargo-deny@0.15.1 + cargo install --locked cargo-deny@0.18.9 _nightly-install: rustup toolchain add {{ nightly }} diff --git a/changelog.md b/changelog.md index 7ed3f5b36..f484fd203 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,12 @@ +### v6.2.1 +### **** +--- +* LLT-6935: Fix StatefulFirewall::set_ip_addresses method, so the internal IP address vector (and thus firewall chain) won't grow indefinitely + +
+ ### v6.2.0-rc0 ### **Pink Soup** --- diff --git a/ci/constpointer-fix-v1.12.6.patch b/ci/constpointer-fix-v1.12.6.patch new file mode 100644 index 000000000..d46fd92bd --- /dev/null +++ b/ci/constpointer-fix-v1.12.6.patch @@ -0,0 +1,1000 @@ +diff --git a/src/agreement.rs b/src/agreement.rs +index 73b92f4ce12..5b85d74a94c 100644 +--- a/src/agreement.rs ++++ b/src/agreement.rs +@@ -62,7 +62,6 @@ use crate::ec::verify_evp_key_nid; + use crate::ec::{encoding, evp_key_generate}; + use crate::error::{KeyRejected, Unspecified}; + use crate::hex; +-use crate::ptr::ConstPointer; + pub use ephemeral::{agree_ephemeral, EphemeralPrivateKey}; + + use crate::aws_lc::{ +@@ -416,7 +415,9 @@ impl PrivateKey { + } + KeyInner::X25519(priv_key) => { + let mut buffer = [0u8; MAX_PUBLIC_KEY_LEN]; +- let out_len = priv_key.marshal_raw_public_to_buffer(&mut buffer)?; ++ let out_len = priv_key ++ .as_const() ++ .marshal_raw_public_to_buffer(&mut buffer)?; + Ok(PublicKey { + inner_key: self.inner_key.clone(), + public_key: buffer, +@@ -448,9 +449,11 @@ impl AsDer> for PrivateKey { + + let mut outp = null_mut::(); + let ec_key = { +- ConstPointer::new(unsafe { +- EVP_PKEY_get0_EC_KEY(*self.inner_key.get_evp_pkey().as_const()) +- })? ++ self.inner_key ++ .get_evp_pkey() ++ .project_const_lifetime(unsafe { ++ |evp_pkey| EVP_PKEY_get0_EC_KEY(*evp_pkey.as_const()) ++ })? + }; + let length = usize::try_from(unsafe { aws_lc::i2d_ECPrivateKey(*ec_key, &mut outp) }) + .map_err(|_| Unspecified)?; +@@ -476,6 +479,7 @@ impl AsDer> for PrivateKey { + Ok(Pkcs8V1Der::new( + self.inner_key + .get_evp_pkey() ++ .as_const() + .marshal_rfc5208_private_key(Version::V1)?, + )) + } +@@ -509,7 +513,9 @@ impl AsBigEndian> for PrivateKey { + return Err(Unspecified); + } + let evp_pkey = self.inner_key.get_evp_pkey(); +- Ok(Curve25519SeedBin::new(evp_pkey.marshal_raw_private_key()?)) ++ Ok(Curve25519SeedBin::new( ++ evp_pkey.as_const().marshal_raw_private_key()?, ++ )) + } + } + +@@ -576,7 +582,7 @@ impl AsDer> for PublicKey { + | KeyInner::ECDH_P384(evp_pkey) + | KeyInner::ECDH_P521(evp_pkey) + | KeyInner::X25519(evp_pkey) => { +- let der = evp_pkey.marshal_rfc5280_public_key()?; ++ let der = evp_pkey.as_const().marshal_rfc5280_public_key()?; + Ok(PublicKeyX509Der::from(Buffer::new(der))) + } + } +diff --git a/src/bn.rs b/src/bn.rs +index 6b54192b0ec..dc1296301d7 100644 +--- a/src/bn.rs ++++ b/src/bn.rs +@@ -49,7 +49,7 @@ impl TryFrom for DetachableLcPtr { + } + } + +-impl ConstPointer { ++impl ConstPointer<'_, BIGNUM> { + pub(crate) fn to_be_bytes(&self) -> Vec { + unsafe { + let bn_bytes = BN_num_bytes(**self); +diff --git a/src/cipher.rs b/src/cipher.rs +index 7d83747035d..4c806252ff6 100644 +--- a/src/cipher.rs ++++ b/src/cipher.rs +@@ -285,21 +285,23 @@ pub enum OperatingMode { + + impl OperatingMode { + fn evp_cipher(&self, algorithm: &Algorithm) -> ConstPointer { +- ConstPointer::new(match (self, algorithm.id) { +- (OperatingMode::CBC, AlgorithmId::Aes128) => unsafe { EVP_aes_128_cbc() }, +- (OperatingMode::CTR, AlgorithmId::Aes128) => unsafe { EVP_aes_128_ctr() }, +- (OperatingMode::CFB128, AlgorithmId::Aes128) => unsafe { EVP_aes_128_cfb128() }, +- (OperatingMode::ECB, AlgorithmId::Aes128) => unsafe { EVP_aes_128_ecb() }, +- (OperatingMode::CBC, AlgorithmId::Aes192) => unsafe { EVP_aes_192_cbc() }, +- (OperatingMode::CTR, AlgorithmId::Aes192) => unsafe { EVP_aes_192_ctr() }, +- (OperatingMode::CFB128, AlgorithmId::Aes192) => unsafe { EVP_aes_192_cfb128() }, +- (OperatingMode::ECB, AlgorithmId::Aes192) => unsafe { EVP_aes_192_ecb() }, +- (OperatingMode::CBC, AlgorithmId::Aes256) => unsafe { EVP_aes_256_cbc() }, +- (OperatingMode::CTR, AlgorithmId::Aes256) => unsafe { EVP_aes_256_ctr() }, +- (OperatingMode::CFB128, AlgorithmId::Aes256) => unsafe { EVP_aes_256_cfb128() }, +- (OperatingMode::ECB, AlgorithmId::Aes256) => unsafe { EVP_aes_256_ecb() }, +- }) +- .unwrap() ++ unsafe { ++ ConstPointer::new_static(match (self, algorithm.id) { ++ (OperatingMode::CBC, AlgorithmId::Aes128) => EVP_aes_128_cbc(), ++ (OperatingMode::CTR, AlgorithmId::Aes128) => EVP_aes_128_ctr(), ++ (OperatingMode::CFB128, AlgorithmId::Aes128) => EVP_aes_128_cfb128(), ++ (OperatingMode::ECB, AlgorithmId::Aes128) => EVP_aes_128_ecb(), ++ (OperatingMode::CBC, AlgorithmId::Aes192) => EVP_aes_192_cbc(), ++ (OperatingMode::CTR, AlgorithmId::Aes192) => EVP_aes_192_ctr(), ++ (OperatingMode::CFB128, AlgorithmId::Aes192) => EVP_aes_192_cfb128(), ++ (OperatingMode::ECB, AlgorithmId::Aes192) => EVP_aes_192_ecb(), ++ (OperatingMode::CBC, AlgorithmId::Aes256) => EVP_aes_256_cbc(), ++ (OperatingMode::CTR, AlgorithmId::Aes256) => EVP_aes_256_ctr(), ++ (OperatingMode::CFB128, AlgorithmId::Aes256) => EVP_aes_256_cfb128(), ++ (OperatingMode::ECB, AlgorithmId::Aes256) => EVP_aes_256_ecb(), ++ }) ++ .unwrap() ++ } + } + } + +diff --git a/src/digest.rs b/src/digest.rs +index b46d22334ff..8a071fb07fb 100644 +--- a/src/digest.rs ++++ b/src/digest.rs +@@ -342,7 +342,7 @@ pub const MAX_CHAINING_LEN: usize = MAX_OUTPUT_LEN; + /// Match digest types for `EVP_MD` functions. + pub(crate) fn match_digest_type(algorithm_id: &AlgorithmID) -> ConstPointer { + unsafe { +- ConstPointer::new(match algorithm_id { ++ ConstPointer::new_static(match algorithm_id { + AlgorithmID::SHA1 => EVP_sha1(), + AlgorithmID::SHA224 => EVP_sha224(), + AlgorithmID::SHA256 => EVP_sha256(), +diff --git a/src/ec.rs b/src/ec.rs +index 08557fc5395..3b6c8c8da9b 100644 +--- a/src/ec.rs ++++ b/src/ec.rs +@@ -3,10 +3,6 @@ + // Modifications copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + // SPDX-License-Identifier: Apache-2.0 OR ISC + +-use crate::ec::signature::AlgorithmID; +-// TODO: Uncomment when MSRV >= 1.64 +-use std::os::raw::c_int; +- + #[cfg(feature = "fips")] + use crate::aws_lc::EC_KEY_check_fips; + #[cfg(not(feature = "fips"))] +@@ -18,11 +14,16 @@ use crate::aws_lc::{ + NID_X9_62_prime256v1, NID_secp224r1, NID_secp256k1, NID_secp384r1, NID_secp521r1, EC_GROUP, + EC_KEY, EVP_PKEY, EVP_PKEY_EC, + }; ++use crate::ec::signature::AlgorithmID; + use crate::error::{KeyRejected, Unspecified}; + #[cfg(feature = "fips")] + use crate::fips::indicator_check; + use crate::ptr::{ConstPointer, LcPtr}; + use crate::signature::Signature; ++// TODO: Uncomment when MSRV >= 1.64 ++//use core::ffi::c_int; ++use std::os::raw::c_int; ++use std::ptr::null; + + pub(crate) mod encoding; + pub(crate) mod key_pair; +@@ -40,7 +41,8 @@ fn verify_ec_key_nid( + ec_key: &ConstPointer, + expected_curve_nid: i32, + ) -> Result<(), KeyRejected> { +- let ec_group = ConstPointer::new(unsafe { EC_KEY_get0_group(**ec_key) })?; ++ let ec_group = ++ ec_key.project_const_lifetime(unsafe { |ec_key| EC_KEY_get0_group(**ec_key) })?; + let key_nid = unsafe { EC_GROUP_get_curve_name(*ec_group) }; + + if key_nid != expected_curve_nid { +@@ -55,7 +57,8 @@ pub(crate) fn verify_evp_key_nid( + evp_pkey: &ConstPointer, + expected_curve_nid: i32, + ) -> Result<(), KeyRejected> { +- let ec_key = ConstPointer::new(unsafe { EVP_PKEY_get0_EC_KEY(**evp_pkey) })?; ++ let ec_key = ++ evp_pkey.project_const_lifetime(unsafe { |evp_pkey| EVP_PKEY_get0_EC_KEY(**evp_pkey) })?; + verify_ec_key_nid(&ec_key, expected_curve_nid)?; + + Ok(()) +@@ -66,7 +69,8 @@ pub(crate) fn validate_evp_key( + evp_pkey: &ConstPointer, + expected_curve_nid: i32, + ) -> Result<(), KeyRejected> { +- let ec_key = ConstPointer::new(unsafe { EVP_PKEY_get0_EC_KEY(**evp_pkey) })?; ++ let ec_key = ++ evp_pkey.project_const_lifetime(unsafe { |evp_pkey| EVP_PKEY_get0_EC_KEY(**evp_pkey) })?; + verify_ec_key_nid(&ec_key, expected_curve_nid)?; + + #[cfg(not(feature = "fips"))] +@@ -96,18 +100,20 @@ pub(crate) fn evp_key_generate(nid: c_int) -> Result, Unspecifie + + #[inline] + #[allow(non_upper_case_globals)] +-pub(crate) fn ec_group_from_nid(nid: i32) -> Result, Unspecified> { +- Ok(ConstPointer::new(match nid { +- NID_secp224r1 => Ok(unsafe { EC_group_p224() }), +- NID_X9_62_prime256v1 => Ok(unsafe { EC_group_p256() }), +- NID_secp384r1 => Ok(unsafe { EC_group_p384() }), +- NID_secp521r1 => Ok(unsafe { EC_group_p521() }), +- NID_secp256k1 => Ok(unsafe { EC_group_secp256k1() }), +- _ => { +- // OPENSSL_PUT_ERROR(EC, EC_R_UNKNOWN_GROUP); +- Err(Unspecified) +- } +- }?)?) ++pub(crate) fn ec_group_from_nid(nid: i32) -> Result, Unspecified> { ++ Ok(unsafe { ++ ConstPointer::new_static(match nid { ++ NID_secp224r1 => EC_group_p224(), ++ NID_X9_62_prime256v1 => EC_group_p256(), ++ NID_secp384r1 => EC_group_p384(), ++ NID_secp521r1 => EC_group_p521(), ++ NID_secp256k1 => EC_group_secp256k1(), ++ _ => { ++ // OPENSSL_PUT_ERROR(EC, EC_R_UNKNOWN_GROUP); ++ null() ++ } ++ })? ++ }) + } + + #[inline] +@@ -116,10 +122,12 @@ fn ecdsa_asn1_to_fixed(alg_id: &'static AlgorithmID, sig: &[u8]) -> Result Result, Unspecified> { + let pub_key_size = if compressed { +- compressed_public_key_size_bytes(evp_pkey.key_size_bits()) ++ compressed_public_key_size_bytes(evp_pkey.as_const().key_size_bits()) + } else { +- uncompressed_public_key_size_bytes(evp_pkey.key_size_bits()) ++ uncompressed_public_key_size_bytes(evp_pkey.as_const().key_size_bits()) + }; + let mut cbb = LcCBB::new(pub_key_size); + marshal_sec1_public_point_into_cbb(&mut cbb, evp_pkey, compressed)?; +@@ -159,9 +159,13 @@ pub(crate) mod sec1 { + evp_pkey: &LcPtr, + compressed: bool, + ) -> Result<(), Unspecified> { +- let ec_key = ConstPointer::new(unsafe { EVP_PKEY_get0_EC_KEY(*evp_pkey.as_const()) })?; +- let ec_group = ConstPointer::new(unsafe { EC_KEY_get0_group(*ec_key) })?; +- let ec_point = ConstPointer::new(unsafe { EC_KEY_get0_public_key(*ec_key) })?; ++ let ec_key = evp_pkey.project_const_lifetime(unsafe { ++ |evp_pkey| EVP_PKEY_get0_EC_KEY(*evp_pkey.as_const()) ++ })?; ++ let ec_group = ++ ec_key.project_const_lifetime(unsafe { |ec_key| EC_KEY_get0_group(**ec_key) })?; ++ let ec_point = ++ ec_key.project_const_lifetime(unsafe { |ec_key| EC_KEY_get0_public_key(**ec_key) })?; + + let point_conversion_form = if compressed { + point_conversion_form_t::POINT_CONVERSION_COMPRESSED +@@ -186,8 +190,11 @@ pub(crate) mod sec1 { + pub(crate) fn marshal_sec1_private_key( + evp_pkey: &LcPtr, + ) -> Result, Unspecified> { +- let ec_key = ConstPointer::new(unsafe { EVP_PKEY_get0_EC_KEY(*evp_pkey.as_const()) })?; +- let ec_group = ConstPointer::new(unsafe { EC_KEY_get0_group(*ec_key) })?; ++ let ec_key = evp_pkey.project_const_lifetime(unsafe { ++ |evp_pkey| EVP_PKEY_get0_EC_KEY(*evp_pkey.as_const()) ++ })?; ++ let ec_group = ++ ec_key.project_const_lifetime(unsafe { |ec_key| EC_KEY_get0_group(**ec_key) })?; + let nid = unsafe { EC_GROUP_get_curve_name(*ec_group) }; + #[allow(non_upper_case_globals)] + let key_size: usize = match nid { +@@ -196,7 +203,8 @@ pub(crate) mod sec1 { + NID_secp521r1 => Ok(66usize), + _ => Err(Unspecified), + }?; +- let private_bn = ConstPointer::new(unsafe { EC_KEY_get0_private_key(*ec_key) })?; ++ let private_bn = ++ ec_key.project_const_lifetime(unsafe { |ec_key| EC_KEY_get0_private_key(**ec_key) })?; + + let mut cbb = LcCBB::new(key_size); + if 1 != unsafe { BN_bn2cbb_padded(cbb.as_mut_ptr(), key_size, *private_bn) } { +@@ -215,7 +223,7 @@ pub(crate) mod rfc5915 { + use crate::cbs::build_CBS; + use crate::ec::ec_group_from_nid; + use crate::error::{KeyRejected, Unspecified}; +- use crate::ptr::{ConstPointer, LcPtr}; ++ use crate::ptr::LcPtr; + + pub(crate) fn parse_rfc5915_private_key( + key_bytes: &[u8], +@@ -234,8 +242,10 @@ pub(crate) mod rfc5915 { + pub(crate) fn marshal_rfc5915_private_key( + evp_pkey: &LcPtr, + ) -> Result, Unspecified> { +- let ec_key = ConstPointer::new(unsafe { EVP_PKEY_get0_EC_KEY(*evp_pkey.as_const()) })?; +- let mut cbb = LcCBB::new(evp_pkey.key_size_bytes()); ++ let ec_key = evp_pkey.project_const_lifetime(unsafe { ++ |evp_pkey| EVP_PKEY_get0_EC_KEY(*evp_pkey.as_const()) ++ })?; ++ let mut cbb = LcCBB::new(evp_pkey.as_const().key_size_bytes()); + let enc_flags = unsafe { EC_KEY_get_enc_flags(*ec_key) }; + if 1 != unsafe { EC_KEY_marshal_private_key(cbb.as_mut_ptr(), *ec_key, enc_flags) } { + return Err(Unspecified); +diff --git a/src/ec/key_pair.rs b/src/ec/key_pair.rs +index 718cd80732d..5f4720960db 100644 +--- a/src/ec/key_pair.rs ++++ b/src/ec/key_pair.rs +@@ -128,7 +128,9 @@ impl EcdsaKeyPair { + /// + pub fn to_pkcs8v1(&self) -> Result { + Ok(Document::new( +- self.evp_pkey.marshal_rfc5208_private_key(Version::V1)?, ++ self.evp_pkey ++ .as_const() ++ .marshal_rfc5208_private_key(Version::V1)?, + )) + } + +diff --git a/src/ec/signature.rs b/src/ec/signature.rs +index b72b15af69a..dc83bdee62f 100644 +--- a/src/ec/signature.rs ++++ b/src/ec/signature.rs +@@ -121,7 +121,7 @@ impl AsDer> for PublicKey { + /// # Errors + /// Returns an error if the public key fails to marshal to X.509. + fn as_der(&self) -> Result, Unspecified> { +- let der = self.evp_pkey.marshal_rfc5280_public_key()?; ++ let der = self.evp_pkey.as_const().marshal_rfc5280_public_key()?; + Ok(PublicKeyX509Der::new(der)) + } + } +diff --git a/src/ed25519.rs b/src/ed25519.rs +index 71d9751c906..f61ebf533a3 100644 +--- a/src/ed25519.rs ++++ b/src/ed25519.rs +@@ -153,7 +153,7 @@ impl AsDer> for PublicKey { + // 2:d=1 hl=2 l= 5 cons: SEQUENCE + // 4:d=2 hl=2 l= 3 prim: OBJECT :ED25519 + // 9:d=1 hl=2 l= 33 prim: BIT STRING +- let der = self.evp_pkey.marshal_rfc5280_public_key()?; ++ let der = self.evp_pkey.as_const().marshal_rfc5280_public_key()?; + Ok(PublicKeyX509Der::from(Buffer::new(der))) + } + } +@@ -182,7 +182,9 @@ impl Ed25519KeyPair { + let evp_pkey = generate_key()?; + + let mut public_key = [0u8; ED25519_PUBLIC_KEY_LEN]; +- let out_len: usize = evp_pkey.marshal_raw_public_to_buffer(&mut public_key)?; ++ let out_len: usize = evp_pkey ++ .as_const() ++ .marshal_raw_public_to_buffer(&mut public_key)?; + debug_assert_eq!(public_key.len(), out_len); + + Ok(Self { +@@ -219,7 +221,9 @@ impl Ed25519KeyPair { + pub fn generate_pkcs8(_rng: &dyn SecureRandom) -> Result { + let evp_pkey = generate_key()?; + Ok(Document::new( +- evp_pkey.marshal_rfc5208_private_key(Version::V2)?, ++ evp_pkey ++ .as_const() ++ .marshal_rfc5208_private_key(Version::V2)?, + )) + } + +@@ -230,7 +234,9 @@ impl Ed25519KeyPair { + /// + pub fn to_pkcs8(&self) -> Result { + Ok(Document::new( +- self.evp_pkey.marshal_rfc5208_private_key(Version::V2)?, ++ self.evp_pkey ++ .as_const() ++ .marshal_rfc5208_private_key(Version::V2)?, + )) + } + +@@ -251,7 +257,9 @@ impl Ed25519KeyPair { + pub fn generate_pkcs8v1(_rng: &dyn SecureRandom) -> Result { + let evp_pkey = generate_key()?; + Ok(Document::new( +- evp_pkey.marshal_rfc5208_private_key(Version::V1)?, ++ evp_pkey ++ .as_const() ++ .marshal_rfc5208_private_key(Version::V1)?, + )) + } + +@@ -262,7 +270,9 @@ impl Ed25519KeyPair { + /// + pub fn to_pkcs8v1(&self) -> Result { + Ok(Document::new( +- self.evp_pkey.marshal_rfc5208_private_key(Version::V1)?, ++ self.evp_pkey ++ .as_const() ++ .marshal_rfc5208_private_key(Version::V1)?, + )) + } + +@@ -307,7 +317,9 @@ impl Ed25519KeyPair { + let evp_pkey = LcPtr::::parse_raw_private_key(seed, EVP_PKEY_ED25519)?; + + let mut derived_public_key = [0u8; ED25519_PUBLIC_KEY_LEN]; +- let out_len: usize = evp_pkey.marshal_raw_public_to_buffer(&mut derived_public_key)?; ++ let out_len: usize = evp_pkey ++ .as_const() ++ .marshal_raw_public_to_buffer(&mut derived_public_key)?; + debug_assert_eq!(derived_public_key.len(), out_len); + + Ok(Self { +@@ -360,10 +372,12 @@ impl Ed25519KeyPair { + fn parse_pkcs8(pkcs8: &[u8]) -> Result { + let evp_pkey = LcPtr::::parse_rfc5208_private_key(pkcs8, EVP_PKEY_ED25519)?; + +- evp_pkey.validate_as_ed25519()?; ++ evp_pkey.as_const().validate_as_ed25519()?; + + let mut public_key = [0u8; ED25519_PUBLIC_KEY_LEN]; +- let out_len: usize = evp_pkey.marshal_raw_public_to_buffer(&mut public_key)?; ++ let out_len: usize = evp_pkey ++ .as_const() ++ .marshal_raw_public_to_buffer(&mut public_key)?; + debug_assert_eq!(public_key.len(), out_len); + + Ok(Self { +@@ -406,7 +420,11 @@ impl Ed25519KeyPair { + /// Currently the function cannot fail, but it might in future implementations. + pub fn seed(&self) -> Result, Unspecified> { + Ok(Seed { +- bytes: self.evp_pkey.marshal_raw_private_key()?.into_boxed_slice(), ++ bytes: self ++ .evp_pkey ++ .as_const() ++ .marshal_raw_private_key()? ++ .into_boxed_slice(), + phantom: PhantomData, + }) + } +@@ -419,7 +437,9 @@ impl AsDer> for Ed25519KeyPair { + /// `error::Unspecified` on internal error. + fn as_der(&self) -> Result, crate::error::Unspecified> { + Ok(Pkcs8V1Der::new( +- self.evp_pkey.marshal_rfc5208_private_key(Version::V1)?, ++ self.evp_pkey ++ .as_const() ++ .marshal_rfc5208_private_key(Version::V1)?, + )) + } + } +@@ -431,7 +451,9 @@ impl AsDer> for Ed25519KeyPair { + /// `error::Unspecified` on internal error. + fn as_der(&self) -> Result, crate::error::Unspecified> { + Ok(Pkcs8V2Der::new( +- self.evp_pkey.marshal_rfc5208_private_key(Version::V2)?, ++ self.evp_pkey ++ .as_const() ++ .marshal_rfc5208_private_key(Version::V2)?, + )) + } + } +diff --git a/src/evp_pkey.rs b/src/evp_pkey.rs +index 2e30972cf40..402145086ff 100644 +--- a/src/evp_pkey.rs ++++ b/src/evp_pkey.rs +@@ -43,7 +43,7 @@ impl EVP_PKEY_CTX_consumer for T where T: Fn(*mut EVP_PKEY_CTX) -> Result<(), + #[allow(non_upper_case_globals, clippy::type_complexity)] + pub(crate) const No_EVP_PKEY_CTX_consumer: Option Result<(), ()>> = None; + +-impl LcPtr { ++impl ConstPointer<'_, EVP_PKEY> { + pub(crate) fn validate_as_ed25519(&self) -> Result<(), KeyRejected> { + const ED25519_KEY_TYPE: c_int = EVP_PKEY_ED25519; + const ED25519_MIN_BITS: c_int = 253; +@@ -79,7 +79,7 @@ impl LcPtr { + // EVP_PKEY_X448 = 961; + // EVP_PKEY_ED448 = 960; + pub(crate) fn id(&self) -> i32 { +- unsafe { EVP_PKEY_id(*self.as_const()) } ++ unsafe { EVP_PKEY_id(**self) } + } + + pub(crate) fn key_size_bytes(&self) -> usize { +@@ -87,30 +87,22 @@ impl LcPtr { + } + + pub(crate) fn key_size_bits(&self) -> usize { +- unsafe { EVP_PKEY_bits(*self.as_const()) } +- .try_into() +- .unwrap() ++ unsafe { EVP_PKEY_bits(**self) }.try_into().unwrap() + } + + pub(crate) fn signature_size_bytes(&self) -> usize { +- unsafe { EVP_PKEY_size(*self.as_const()) } +- .try_into() +- .unwrap() ++ unsafe { EVP_PKEY_size(**self) }.try_into().unwrap() + } + + #[allow(dead_code)] + pub(crate) fn get_ec_key(&self) -> Result, KeyRejected> { +- unsafe { +- ConstPointer::new(EVP_PKEY_get0_EC_KEY(*self.as_const())) +- .map_err(|()| KeyRejected::wrong_algorithm()) +- } ++ self.project_const_lifetime(unsafe { |evp_pkey| EVP_PKEY_get0_EC_KEY(**evp_pkey) }) ++ .map_err(|()| KeyRejected::wrong_algorithm()) + } + + pub(crate) fn get_rsa(&self) -> Result, KeyRejected> { +- unsafe { +- ConstPointer::new(EVP_PKEY_get0_RSA(*self.as_const())) +- .map_err(|()| KeyRejected::wrong_algorithm()) +- } ++ self.project_const_lifetime(unsafe { |evp_pkey| EVP_PKEY_get0_RSA(**evp_pkey) }) ++ .map_err(|()| KeyRejected::wrong_algorithm()) + } + + pub(crate) fn marshal_rfc5280_public_key(&self) -> Result, Unspecified> { +@@ -118,43 +110,27 @@ impl LcPtr { + // size in bytes for keys ranging from 2048-bit to 4096-bit. So size the initial capacity to be roughly + // 500% as a conservative estimate to avoid needing to reallocate for any key in that range. + let mut cbb = LcCBB::new(self.key_size_bytes() * 5); +- if 1 != unsafe { EVP_marshal_public_key(cbb.as_mut_ptr(), *self.as_const()) } { ++ if 1 != unsafe { EVP_marshal_public_key(cbb.as_mut_ptr(), **self) } { + return Err(Unspecified); + } + cbb.into_vec() + } + +- pub(crate) fn parse_rfc5280_public_key( +- bytes: &[u8], +- evp_pkey_type: c_int, +- ) -> Result { +- let mut cbs = cbs::build_CBS(bytes); +- // Also checks the validity of the key +- let evp_pkey = LcPtr::new(unsafe { EVP_parse_public_key(&mut cbs) }) +- .map_err(|()| KeyRejected::invalid_encoding())?; +- evp_pkey +- .id() +- .eq(&evp_pkey_type) +- .then_some(evp_pkey) +- .ok_or(KeyRejected::wrong_algorithm()) +- } +- + pub(crate) fn marshal_rfc5208_private_key( + &self, + version: Version, + ) -> Result, Unspecified> { +- let key_size_bytes = TryInto::::try_into(unsafe { EVP_PKEY_bits(*self.as_const()) }) +- .expect("fit in usize") +- / 8; ++ let key_size_bytes = ++ TryInto::::try_into(unsafe { EVP_PKEY_bits(**self) }).expect("fit in usize") / 8; + let mut cbb = LcCBB::new(key_size_bytes * 5); + match version { + Version::V1 => { +- if 1 != unsafe { EVP_marshal_private_key(cbb.as_mut_ptr(), *self.as_const()) } { ++ if 1 != unsafe { EVP_marshal_private_key(cbb.as_mut_ptr(), **self) } { + return Err(Unspecified); + } + } + Version::V2 => { +- if 1 != unsafe { EVP_marshal_private_key_v2(cbb.as_mut_ptr(), *self.as_const()) } { ++ if 1 != unsafe { EVP_marshal_private_key_v2(cbb.as_mut_ptr(), **self) } { + return Err(Unspecified); + } + } +@@ -162,32 +138,9 @@ impl LcPtr { + cbb.into_vec() + } + +- pub(crate) fn parse_rfc5208_private_key( +- bytes: &[u8], +- evp_pkey_type: c_int, +- ) -> Result { +- let mut cbs = cbs::build_CBS(bytes); +- // Also checks the validity of the key +- let evp_pkey = LcPtr::new(unsafe { EVP_parse_private_key(&mut cbs) }) +- .map_err(|()| KeyRejected::invalid_encoding())?; +- evp_pkey +- .id() +- .eq(&evp_pkey_type) +- .then_some(evp_pkey) +- .ok_or(KeyRejected::wrong_algorithm()) +- } +- +- #[allow(non_snake_case)] +- pub(crate) fn create_EVP_PKEY_CTX(&self) -> Result, ()> { +- // The only modification made by EVP_PKEY_CTX_new to `priv_key` is to increment its +- // refcount. The modification is made while holding a global lock: +- // https://github.com/aws/aws-lc/blob/61503f7fe72457e12d3446853a5452d175560c49/crypto/refcount_lock.c#L29 +- LcPtr::new(unsafe { EVP_PKEY_CTX_new(*self.as_mut_unsafe(), null_mut()) }) +- } +- + pub(crate) fn marshal_raw_private_key(&self) -> Result, Unspecified> { + let mut size = 0; +- if 1 != unsafe { EVP_PKEY_get_raw_private_key(*self.as_const(), null_mut(), &mut size) } { ++ if 1 != unsafe { EVP_PKEY_get_raw_private_key(**self, null_mut(), &mut size) } { + return Err(Unspecified); + } + let mut buffer = vec![0u8; size]; +@@ -201,9 +154,7 @@ impl LcPtr { + buffer: &mut [u8], + ) -> Result { + let mut key_len = buffer.len(); +- if 1 == unsafe { +- EVP_PKEY_get_raw_private_key(*self.as_const(), buffer.as_mut_ptr(), &mut key_len) +- } { ++ if 1 == unsafe { EVP_PKEY_get_raw_private_key(**self, buffer.as_mut_ptr(), &mut key_len) } { + Ok(key_len) + } else { + Err(Unspecified) +@@ -213,7 +164,7 @@ impl LcPtr { + #[allow(dead_code)] + pub(crate) fn marshal_raw_public_key(&self) -> Result, Unspecified> { + let mut size = 0; +- if 1 != unsafe { EVP_PKEY_get_raw_public_key(*self.as_const(), null_mut(), &mut size) } { ++ if 1 != unsafe { EVP_PKEY_get_raw_public_key(**self, null_mut(), &mut size) } { + return Err(Unspecified); + } + let mut buffer = vec![0u8; size]; +@@ -231,13 +182,55 @@ impl LcPtr { + // `EVP_PKEY_get_raw_public_key` writes the total length + // to `encapsulate_key_size` in the event that the buffer we provide is larger then + // required. +- EVP_PKEY_get_raw_public_key(*self.as_const(), buffer.as_mut_ptr(), &mut key_len) ++ EVP_PKEY_get_raw_public_key(**self, buffer.as_mut_ptr(), &mut key_len) + } { + Ok(key_len) + } else { + Err(Unspecified) + } + } ++} ++ ++impl LcPtr { ++ pub(crate) fn parse_rfc5280_public_key( ++ bytes: &[u8], ++ evp_pkey_type: c_int, ++ ) -> Result { ++ let mut cbs = cbs::build_CBS(bytes); ++ // Also checks the validity of the key ++ let evp_pkey = LcPtr::new(unsafe { EVP_parse_public_key(&mut cbs) }) ++ .map_err(|()| KeyRejected::invalid_encoding())?; ++ evp_pkey ++ .as_const() ++ .id() ++ .eq(&evp_pkey_type) ++ .then_some(evp_pkey) ++ .ok_or(KeyRejected::wrong_algorithm()) ++ } ++ ++ pub(crate) fn parse_rfc5208_private_key( ++ bytes: &[u8], ++ evp_pkey_type: c_int, ++ ) -> Result { ++ let mut cbs = cbs::build_CBS(bytes); ++ // Also checks the validity of the key ++ let evp_pkey = LcPtr::new(unsafe { EVP_parse_private_key(&mut cbs) }) ++ .map_err(|()| KeyRejected::invalid_encoding())?; ++ evp_pkey ++ .as_const() ++ .id() ++ .eq(&evp_pkey_type) ++ .then_some(evp_pkey) ++ .ok_or(KeyRejected::wrong_algorithm()) ++ } ++ ++ #[allow(non_snake_case)] ++ pub(crate) fn create_EVP_PKEY_CTX(&self) -> Result, ()> { ++ // The only modification made by EVP_PKEY_CTX_new to `priv_key` is to increment its ++ // refcount. The modification is made while holding a global lock: ++ // https://github.com/aws/aws-lc/blob/61503f7fe72457e12d3446853a5452d175560c49/crypto/refcount_lock.c#L29 ++ LcPtr::new(unsafe { EVP_PKEY_CTX_new(*self.as_mut_unsafe(), null_mut()) }) ++ } + + pub(crate) fn parse_raw_private_key( + bytes: &[u8], +diff --git a/src/kem.rs b/src/kem.rs +index 52ed528a3b2..90c11eda96c 100644 +--- a/src/kem.rs ++++ b/src/kem.rs +@@ -360,6 +360,7 @@ where + let mut encapsulate_bytes = vec![0u8; self.algorithm.encapsulate_key_size()]; + let encapsulate_key_size = self + .evp_pkey ++ .as_const() + .marshal_raw_public_to_buffer(&mut encapsulate_bytes)?; + + debug_assert_eq!(encapsulate_key_size, encapsulate_bytes.len()); +diff --git a/src/ptr.rs b/src/ptr.rs +index 9f714d5dfd8..b8f9248f3d8 100644 +--- a/src/ptr.rs ++++ b/src/ptr.rs +@@ -1,14 +1,14 @@ + // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + // SPDX-License-Identifier: Apache-2.0 OR ISC + +-use core::ops::Deref; +- + use crate::aws_lc::{ + BN_free, ECDSA_SIG_free, EC_GROUP_free, EC_KEY_free, EC_POINT_free, EVP_AEAD_CTX_free, + EVP_CIPHER_CTX_free, EVP_PKEY_CTX_free, EVP_PKEY_free, OPENSSL_free, RSA_free, BIGNUM, + ECDSA_SIG, EC_GROUP, EC_KEY, EC_POINT, EVP_AEAD_CTX, EVP_CIPHER_CTX, EVP_PKEY, EVP_PKEY_CTX, + RSA, + }; ++use core::ops::Deref; ++use std::marker::PhantomData; + + pub(crate) type LcPtr = ManagedPointer<*mut T>; + pub(crate) type DetachableLcPtr = DetachablePointer<*mut T>; +@@ -40,12 +40,33 @@ impl Drop for ManagedPointer

{ + } + } + ++impl<'a, P: Pointer> From<&'a ManagedPointer

> for ConstPointer<'a, P::T> { ++ fn from(ptr: &'a ManagedPointer

) -> ConstPointer<'a, P::T> { ++ ConstPointer { ++ ptr: ptr.pointer.as_const_ptr(), ++ _lifetime: PhantomData, ++ } ++ } ++} ++ + impl ManagedPointer

{ + #[inline] + pub fn as_const(&self) -> ConstPointer { +- ConstPointer { +- ptr: self.pointer.as_const_ptr(), ++ self.into() ++ } ++ ++ pub fn project_const_lifetime<'a, C>( ++ &'a self, ++ f: unsafe fn(&'a Self) -> *const C, ++ ) -> Result, ()> { ++ let ptr = unsafe { f(self) }; ++ if ptr.is_null() { ++ return Err(()); + } ++ Ok(ConstPointer { ++ ptr, ++ _lifetime: PhantomData, ++ }) + } + + #[inline] +@@ -133,20 +154,40 @@ impl Drop for DetachablePointer

{ + } + + #[derive(Debug)] +-pub(crate) struct ConstPointer { ++pub(crate) struct ConstPointer<'a, T> { + ptr: *const T, ++ _lifetime: PhantomData<&'a T>, ++} ++ ++impl ConstPointer<'static, T> { ++ pub unsafe fn new_static(ptr: *const T) -> Result { ++ if ptr.is_null() { ++ return Err(()); ++ } ++ Ok(ConstPointer { ++ ptr, ++ _lifetime: PhantomData, ++ }) ++ } + } + +-impl ConstPointer { +- pub fn new(ptr: *const T) -> Result, ()> { ++impl ConstPointer<'_, T> { ++ pub fn project_const_lifetime<'a, C>( ++ &'a self, ++ f: unsafe fn(&'a Self) -> *const C, ++ ) -> Result, ()> { ++ let ptr = unsafe { f(self) }; + if ptr.is_null() { + return Err(()); + } +- Ok(ConstPointer { ptr }) ++ Ok(ConstPointer { ++ ptr, ++ _lifetime: PhantomData, ++ }) + } + } + +-impl Deref for ConstPointer { ++impl Deref for ConstPointer<'_, T> { + type Target = *const T; + + fn deref(&self) -> &Self::Target { +diff --git a/src/rsa/encoding.rs b/src/rsa/encoding.rs +index 11538c87cd9..193596386f5 100644 +--- a/src/rsa/encoding.rs ++++ b/src/rsa/encoding.rs +@@ -21,7 +21,11 @@ pub(in crate::rsa) mod rfc8017 { + let mut pubkey_bytes = null_mut::(); + let mut outlen: usize = 0; + if 1 != unsafe { +- RSA_public_key_to_bytes(&mut pubkey_bytes, &mut outlen, *pubkey.get_rsa()?) ++ RSA_public_key_to_bytes( ++ &mut pubkey_bytes, ++ &mut outlen, ++ *pubkey.as_const().get_rsa()?, ++ ) + } { + return Err(Unspecified); + } +@@ -85,7 +89,7 @@ pub(in crate::rsa) mod rfc5280 { + pub(in crate::rsa) fn encode_public_key_der( + key: &LcPtr, + ) -> Result, Unspecified> { +- let der = key.marshal_rfc5280_public_key()?; ++ let der = key.as_const().marshal_rfc5280_public_key()?; + Ok(PublicKeyX509Der::from(Buffer::new(der))) + } + +diff --git a/src/rsa/encryption.rs b/src/rsa/encryption.rs +index d31187754e3..3dc58b3316f 100644 +--- a/src/rsa/encryption.rs ++++ b/src/rsa/encryption.rs +@@ -44,7 +44,7 @@ impl PrivateDecryptingKey { + if !is_rsa_key(key) { + return Err(Unspecified); + } +- match key.key_size_bits() { ++ match key.as_const().key_size_bits() { + 2048..=8192 => Ok(()), + _ => Err(Unspecified), + } +@@ -105,13 +105,13 @@ impl PrivateDecryptingKey { + /// Returns the RSA signature size in bytes. + #[must_use] + pub fn key_size_bytes(&self) -> usize { +- self.0.signature_size_bytes() ++ self.0.as_const().signature_size_bytes() + } + + /// Returns the RSA key size in bits. + #[must_use] + pub fn key_size_bits(&self) -> usize { +- self.0.key_size_bits() ++ self.0.as_const().key_size_bits() + } + + /// Retrieves the `PublicEncryptingKey` corresponding with this `PrivateDecryptingKey`. +@@ -133,7 +133,7 @@ impl Debug for PrivateDecryptingKey { + impl AsDer> for PrivateDecryptingKey { + fn as_der(&self) -> Result, Unspecified> { + Ok(Pkcs8V1Der::new( +- self.0.marshal_rfc5208_private_key(Version::V1)?, ++ self.0.as_const().marshal_rfc5208_private_key(Version::V1)?, + )) + } + } +@@ -157,7 +157,7 @@ impl PublicEncryptingKey { + if !is_rsa_key(key) { + return Err(Unspecified); + } +- match key.key_size_bits() { ++ match key.as_const().key_size_bits() { + 2048..=8192 => Ok(()), + _ => Err(Unspecified), + } +@@ -174,13 +174,13 @@ impl PublicEncryptingKey { + /// Returns the RSA signature size in bytes. + #[must_use] + pub fn key_size_bytes(&self) -> usize { +- self.0.signature_size_bytes() ++ self.0.as_const().signature_size_bytes() + } + + /// Returns the RSA key size in bits. + #[must_use] + pub fn key_size_bits(&self) -> usize { +- self.0.key_size_bits() ++ self.0.as_const().key_size_bits() + } + } + +diff --git a/src/rsa/key.rs b/src/rsa/key.rs +index 1abc483f88e..b43c5d4a6e2 100644 +--- a/src/rsa/key.rs ++++ b/src/rsa/key.rs +@@ -16,8 +16,6 @@ use crate::encoding::{AsDer, Pkcs8V1Der}; + use crate::error::{KeyRejected, Unspecified}; + #[cfg(feature = "ring-io")] + use crate::io; +-#[cfg(feature = "ring-io")] +-use crate::ptr::ConstPointer; + use crate::ptr::{DetachableLcPtr, LcPtr}; + use crate::rsa::PublicEncryptingKey; + use crate::sealed::Sealed; +@@ -176,7 +174,7 @@ impl KeyPair { + if !is_rsa_key(key) { + return Err(KeyRejected::unspecified()); + } +- match key.key_size_bits() { ++ match key.as_const().key_size_bits() { + 2048..=8192 => Ok(()), + _ => Err(KeyRejected::unspecified()), + } +@@ -231,7 +229,7 @@ impl KeyPair { + #[must_use] + pub fn public_modulus_len(&self) -> usize { + // This was already validated to be an RSA key so this can't fail +- match self.evp_pkey.get_rsa() { ++ match self.evp_pkey.as_const().get_rsa() { + Ok(rsa) => { + // https://github.com/awslabs/aws-lc/blob/main/include/openssl/rsa.h#L99 + unsafe { RSA_size(*rsa) as usize } +@@ -261,7 +259,9 @@ impl crate::signature::KeyPair for KeyPair { + impl AsDer> for KeyPair { + fn as_der(&self) -> Result, Unspecified> { + Ok(Pkcs8V1Der::new( +- self.evp_pkey.marshal_rfc5208_private_key(Version::V1)?, ++ self.evp_pkey ++ .as_const() ++ .marshal_rfc5208_private_key(Version::V1)?, + )) + } + } +@@ -292,10 +292,13 @@ impl PublicKey { + let key = encoding::rfc8017::encode_public_key_der(evp_pkey)?; + #[cfg(feature = "ring-io")] + { ++ let evp_pkey = evp_pkey.as_const(); + let pubkey = evp_pkey.get_rsa()?; +- let modulus = ConstPointer::new(unsafe { RSA_get0_n(*pubkey) })?; ++ let modulus = ++ pubkey.project_const_lifetime(unsafe { |pubkey| RSA_get0_n(**pubkey) })?; + let modulus = modulus.to_be_bytes().into_boxed_slice(); +- let exponent = ConstPointer::new(unsafe { RSA_get0_e(*pubkey) })?; ++ let exponent = ++ pubkey.project_const_lifetime(unsafe { |pubkey| RSA_get0_e(**pubkey) })?; + let exponent = exponent.to_be_bytes().into_boxed_slice(); + Ok(PublicKey { + key, +@@ -478,12 +481,13 @@ pub(super) fn generate_rsa_key(size: c_int) -> Result, Unspecifi + #[must_use] + pub(super) fn is_valid_fips_key(key: &LcPtr) -> bool { + // This should always be an RSA key and must-never panic. +- let rsa_key = key.get_rsa().expect("RSA EVP_PKEY"); ++ let evp_pkey = key.as_const(); ++ let rsa_key = evp_pkey.get_rsa().expect("RSA EVP_PKEY"); + + 1 == unsafe { RSA_check_fips(*rsa_key as *mut RSA) } + } + + pub(super) fn is_rsa_key(key: &LcPtr) -> bool { +- let id = key.id(); ++ let id = key.as_const().id(); + id == EVP_PKEY_RSA || id == EVP_PKEY_RSA_PSS + } +diff --git a/src/rsa/signature.rs b/src/rsa/signature.rs +index d1b18edf78d..164386c5b8f 100644 +--- a/src/rsa/signature.rs ++++ b/src/rsa/signature.rs +@@ -109,7 +109,7 @@ impl RsaParameters { + /// `error::Unspecified` on parse error. + pub fn public_modulus_len(public_key: &[u8]) -> Result { + let rsa = encoding::rfc8017::decode_public_key_der(public_key)?; +- Ok(unsafe { RSA_bits(*rsa.get_rsa()?) }) ++ Ok(unsafe { RSA_bits(*rsa.as_const().get_rsa()?) }) + } + + #[must_use] +@@ -222,7 +222,7 @@ pub(crate) fn verify_rsa_signature( + signature: &[u8], + allowed_bit_size: &RangeInclusive, + ) -> Result<(), Unspecified> { +- if !allowed_bit_size.contains(&public_key.key_size_bits().try_into()?) { ++ if !allowed_bit_size.contains(&public_key.as_const().key_size_bits().try_into()?) { + return Err(Unspecified); + } + diff --git a/ci/env.py b/ci/env.py index 6652d8138..7c191b05a 100755 --- a/ci/env.py +++ b/ci/env.py @@ -3,14 +3,15 @@ import os LIBTELIO_ENV_MOOSE_RELEASE_TAG = "v17.0.0-libtelioApp" -LIBTELIO_ENV_NAT_LAB_DEPS_TAG = "v0.0.32" +LIBTELIO_ENV_NAT_LAB_DEPS_TAG = "v0.3.0" LIBTELIO_ENV_ANDROID_BUILDER_TAG = "v7.2.2" LIBTELIO_ENV_LINUX_BUILDER_TAG = "v7.2.3" LIBTELIO_ENV_WINDOWS_BUILDER_TAG = "v7.2.2" LIBTELIO_ENV_UNIFFI_GENERATORS_TAG = "v0.28.3-4" -LIBTELIO_ENV_NAT_LAB_WINDOWS_VM_TAG = "v0.0.7" -LIBTELIO_ENV_NAT_LAB_MACOS_VM_TAG = "v0.0.8" -LIBTELIO_ENV_NAT_LAB_LINUX_VM_TAG = "v0.0.6" +LIBTELIO_ENV_NAT_LAB_WINDOWS_VM_TAG = "v0.0.8" +LIBTELIO_ENV_NAT_LAB_MACOS_VM_TAG = "v0.0.9" +LIBTELIO_ENV_NAT_LAB_LINUX_VM_TAG = "v0.2.2" +LIBTELIO_ENV_OPENWRT_BUILDER_TAG = "24.10.4" def set_sh(): diff --git a/ci/fetch_artifacts.py b/ci/fetch_artifacts.py index f687158ef..e1e744959 100644 --- a/ci/fetch_artifacts.py +++ b/ci/fetch_artifacts.py @@ -104,7 +104,7 @@ def _get_latest_tag(self): raise Exception("No suitable build tag was found") def _get_remote_path(self) -> str: - LIBTELIO_BUILD_PROJECT_ID = 6299 + LIBTELIO_BUILD_PROJECT_ID = 2386 libtelio_env_sec_gitlab_repository = os.environ.get( "LIBTELIO_ENV_SEC_GITLAB_REPOSITORY", None ) diff --git a/ci/moose_utils.py b/ci/moose_utils.py index 95be71031..54c357e02 100644 --- a/ci/moose_utils.py +++ b/ci/moose_utils.py @@ -116,7 +116,7 @@ def set_cargo_dependencies(): MOOSELIBTELIOAPP_DEP = ( r"\nmooselibtelioapp = { " - f'git = "https://{libtelio_env_sec_gitlab_repository}/low-level-hacks/moose/moose-events",' + f'git = "https://{libtelio_env_sec_gitlab_repository}/nord-projects/nordvpn/infra/llt/moose/moose-events",' f' tag = "{LIBTELIO_ENV_MOOSE_RELEASE_TAG}" }}' ) diff --git a/clis/tcli/src/nord.rs b/clis/tcli/src/nord.rs index a27f10f33..bee210d5f 100644 --- a/clis/tcli/src/nord.rs +++ b/clis/tcli/src/nord.rs @@ -1,3 +1,4 @@ +use base64::prelude::{Engine, BASE64_STANDARD}; use reqwest::{ blocking::{Client, Response}, header, @@ -92,6 +93,11 @@ struct MeshDev { } impl Nord { + fn format_auth_token(token: &String) -> String { + let creds = format!("token:{}", token); + format!("Basic {}", BASE64_STANDARD.encode(creds)) + } + pub fn start_login() -> Result { let chalenge = b"asdfasdf".to_vec(); let sha256 = hex::encode(Sha256::digest(&chalenge)); @@ -120,7 +126,7 @@ impl Nord { .json()?; let creds: Creds = client .get(&format!("{}/users/services/credentials", API_BASE)) - .header(header::AUTHORIZATION, format!("token:{}", &login.token)) + .header(header::AUTHORIZATION, Self::format_auth_token(&login.token)) .send()? .checked()? .json()?; @@ -143,7 +149,7 @@ impl Nord { let creds: Creds = client .get(&format!("{}/users/services/credentials", API_BASE)) - .header(header::AUTHORIZATION, format!("token:{}", &login.token)) + .header(header::AUTHORIZATION, Self::format_auth_token(&login.token)) .send()? .checked()? .json()?; @@ -159,10 +165,11 @@ impl Nord { let login = LoginInfo { token: token.to_string(), }; + let client = Client::new(); let creds: Creds = client .get(&format!("{}/users/services/credentials", API_BASE)) - .header(header::AUTHORIZATION, format!("token:{}", &login.token)) + .header(header::AUTHORIZATION, Self::format_auth_token(&login.token)) .send()? .checked()? .json()?; @@ -221,7 +228,7 @@ impl Nord { .post(&format!("{}/meshnet/machines", API_BASE)) .header( header::AUTHORIZATION, - format!("Bearer token:{}", &self.login.token), + Self::format_auth_token(&self.login.token), ) .header(header::CONTENT_TYPE, "application/json") .header(header::ACCEPT, "application/json") @@ -245,7 +252,7 @@ impl Nord { .get(&format!("{}/meshnet/machines/{}/map", API_BASE, id)) .header( header::AUTHORIZATION, - format!("Bearer token:{}", &self.login.token), + Self::format_auth_token(&self.login.token), ) .header(header::ACCEPT, "application/json") .send()? diff --git a/crates/telio-firewall/src/firewall.rs b/crates/telio-firewall/src/firewall.rs index 2a3456add..506df14dc 100644 --- a/crates/telio-firewall/src/firewall.rs +++ b/crates/telio-firewall/src/firewall.rs @@ -883,8 +883,10 @@ impl Firewall for StatefullFirewall { } fn set_ip_addresses(&self, ip_addrs: Vec) { - self.ip_addresses.write().extend_from_slice(&ip_addrs); - self.recreate_chain(); + if ip_addrs != *self.ip_addresses.read() { + *self.ip_addresses.write().as_mut() = ip_addrs; + self.recreate_chain(); + } } } @@ -1450,6 +1452,33 @@ pub mod tests { } } + fn ip_address_set_multiple_times() { + let ip_vec = vec![ + StdIpAddr::V4(StdIpv4Addr::new(127, 0, 0, 1)), + StdIpAddr::V6(StdIpv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)), + ]; + let new_ip_vec = vec![ + StdIpAddr::V4(StdIpv4Addr::new(192, 168, 1, 1)), + StdIpAddr::V6(StdIpv6Addr::new(0xfd74, 0x656c, 0x696f, 0, 0, 0, 0, 1)), + ]; + + let fw = StatefullFirewall::new(false, &FeatureFirewall::default()); + + assert_eq!(fw.ip_addresses.read().len(), 0); + + fw.set_ip_addresses(ip_vec.clone()); + assert_eq!(fw.ip_addresses.read().len(), 2); + assert_eq!(*fw.ip_addresses.read(), ip_vec); + + fw.set_ip_addresses(ip_vec.clone()); + assert_eq!(fw.ip_addresses.read().len(), 2); + assert_eq!(*fw.ip_addresses.read(), ip_vec); + + fw.set_ip_addresses(new_ip_vec.clone()); + assert_eq!(fw.ip_addresses.read().len(), 2); + assert_eq!(*fw.ip_addresses.read(), new_ip_vec); + } + #[rustfmt::skip] #[test] fn ipv6_blocked() { diff --git a/deny.toml b/deny.toml index 656fb2f85..348783a58 100644 --- a/deny.toml +++ b/deny.toml @@ -5,15 +5,19 @@ targets = [] [advisories] db-path = "~/.cargo/advisory-db" db-urls = ["https://github.com/rustsec/advisory-db"] -vulnerability = "deny" -unmaintained = "warn" yanked = "warn" -notice = "warn" -ignore = [] +ignore = [ + "RUSTSEC-2024-0370", # proc-macro-error is unmaintained + "RUSTSEC-2024-0381", # pqcrypto-kyber is unmaintained + "RUSTSEC-2024-0436", # paste is unmaintained + "RUSTSEC-2024-0375", # atty is unmaintained + "RUSTSEC-2025-0052", # async-std is unmaintained + "RUSTSEC-2021-0145", # atty has potential unaligned read + "RUSTSEC-2025-0141", # bincode is unmaintained +] [licenses] -unlicensed = "deny" allow = [ "BSD-2-Clause", "MPL-2.0", @@ -21,15 +25,16 @@ allow = [ "Unicode-DFS-2016", "Unicode-3.0", "OpenSSL", + "Apache-2.0", + "MIT", + "ISC", + "BSD-3-Clause", + "Zlib" ] -deny = [] -copyleft = "deny" -allow-osi-fsf-free = "both" -default = "deny" confidence-threshold = 0.8 exceptions = [ - { allow = ["GPL-3.0"], name = "telio", version = "*" }, - { allow = ["GPL-3.0"], name = "llt-proto", version = "*" }, + { allow = ["GPL-3.0-only"], name = "telio", version = "*" }, + { allow = ["GPL-3.0-only"], name = "llt-proto", version = "*" }, ] [licenses.private] diff --git a/nat-lab/Dockerfile b/nat-lab/Dockerfile index 4b129e537..24b5a2fb1 100644 --- a/nat-lab/Dockerfile +++ b/nat-lab/Dockerfile @@ -1,14 +1,13 @@ ARG LIBTELIO_ENV_SEC_CONTAINER_REGISTRY ARG LIBTELIO_ENV_NAT_LAB_DEPS_TAG -FROM ${LIBTELIO_ENV_SEC_CONTAINER_REGISTRY}/low-level-hacks/vpn/client/libtelio-build/natlab-deps-common:${LIBTELIO_ENV_NAT_LAB_DEPS_TAG} +FROM ${LIBTELIO_ENV_SEC_CONTAINER_REGISTRY}/nord-projects/nordvpn/infra/llt/automation/docker/libtelio/nat_lab:${LIBTELIO_ENV_NAT_LAB_DEPS_TAG} LABEL org.opencontainers.image.authors="info@nordsec.com" LABEL org.opencontainers.image.source="https://github.com/NordSecurity/libtelio/blob/main/nat-lab/Dockerfile" COPY --chmod=0755 bin/ /opt/bin/ COPY --chmod=0755 data/custom_debs /opt/custom_debs -RUN curl -LsSf https://astral.sh/uv/install.sh | sh RUN sh -c ". ~/.bashrc && uv add --directory /opt/bin grpcio==1.73.1 protobuf==6.30.2 aiohttp==3.12.14 cryptography==45.0.5 blake3==1.0.5" # If custom deb is found, install it. This command doesn't fail if it doesn't find anything. diff --git a/nat-lab/bin/nordlynx b/nat-lab/bin/nordlynx index b37cc3498..786e4f105 100755 --- a/nat-lab/bin/nordlynx +++ b/nat-lab/bin/nordlynx @@ -2,64 +2,34 @@ set -euxo pipefail +up() { + local service="$1" + echo "$service: enable + start" + if systemctl enable --now "$service" && systemctl is-active --quiet "$service"; then + echo "OK: $service" + else + echo "FAIL: $service" + systemctl status "$s" --no-pager || true + exit 1 + fi +} + # This is a workaround for 6.x kernel bug, which can cause NULL-deref when # iptables are executed too early at boot. # More details: # https://lore.kernel.org/netdev/20240731213046.6194-3-pablo@netfilter.org/T/#m597226ed32420a20bca5be58cf1c21073f06083b sleep 5 -sysctl net.ipv4.ip_forward=1 -sysctl net.ipv4.conf.all.rp_filter=0 -sysctl net.ipv4.conf.default.rp_filter=0 -sysctl net.ipv6.conf.all.disable_ipv6=0 -sysctl net.ipv6.conf.default.disable_ipv6=0 -sysctl net.ipv6.conf.all.forwarding=1 - -mkdir -p /etc/nordlynx -ip link add dev nordlynx0 type nordlynx -ip addr add 10.5.0.1/16 dev nordlynx0 -ip addr add 100.64.0.1/10 dev nordlynx0 -ip link set mtu 1420 dev nordlynx0 -ip link set dev nordlynx0 up - -nlx set nordlynx0 listen-port 1023 - -iptables -A FORWARD -i nordlynx0 -j ACCEPT; -iptables -A FORWARD -o nordlynx0 -j ACCEPT; -iptables -t nat -A POSTROUTING -o enp0s3 -j MASQUERADE - # Backup IP tables iptables-save -f iptables_backup ip6tables-save -f ip6tables_backup -# courtesy sleep so it would be really all up and running -sleep 2 - -ip addr s > /var/log/ipaddr.show -nlx > /var/log/nlx.log -/opt/fakefm/fakefm/main -s testing123 > /var/log/fakefm.log 2>&1 & -/opt/nlx-radius/nlx-radius/target/release/nlx-radius > /var/log/nordlynx.log 2>&1 & - -# run PQ upgrader -cat > config.yml << EOL -bind: - port: 6480 -logging: - output: file - level: Debug - file: /var/log/pq-upgrader.log -dynamic_configuration: - enabled: false - port: 7771 -limits: - peer: - session_length: 5 - rate: 10 - burst: 5 -EOL - -pushd /opt/pq-upgrader/pq-upgr/upgrader -./upgrader > /var/log/upgrader.log 2>&1 & -popd +echo "Setting up services..." +systemctl daemon-reload +up nlx-quick@nordlynx0 +up nlx-radius +up fakefm.service +up pq-upgrader +up nlx-ns touch /ready diff --git a/nat-lab/bin/windows-client b/nat-lab/bin/windows-client index 2cb57bc70..3cc99946e 100755 --- a/nat-lab/bin/windows-client +++ b/nat-lab/bin/windows-client @@ -13,6 +13,15 @@ last_rc=1 while [ $SECONDS -lt $end ]; do if output=$(python3 /run/qga.py powershell -Command '$svc = Get-Service -Name "sshd" -ErrorAction SilentlyContinue; if ($null -eq $svc) { Write-Output "OpenSSH SSH Server (sshd) service not found"; exit 3 } elseif ($svc.Status -eq "Running") { Write-Output "OpenSSH SSH Server is running"; exit 0 } else { Write-Output ("OpenSSH SSH Server status: " + $svc.Status); exit 2 }'); then echo "$output" + + # Enable Windows Error Reporting crash dumps + echo "Configuring WER crash dumps..." + python3 /run/qga.py powershell -Command 'New-Item -Path "HKLM:\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps" -Force' + python3 /run/qga.py powershell -Command 'Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps" -Name "DumpFolder" -Value "C:\CrashDumps" -Type ExpandString' + python3 /run/qga.py powershell -Command 'Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps" -Name "DumpType" -Value 2 -Type DWord' + python3 /run/qga.py powershell -Command 'New-Item -Path "C:\CrashDumps" -ItemType Directory -Force' + echo "WER crash dumps configured" + exit 0 else last_rc=$? diff --git a/nat-lab/docker-compose.yml b/nat-lab/docker-compose.yml index 631b7c221..24131da7c 100644 --- a/nat-lab/docker-compose.yml +++ b/nat-lab/docker-compose.yml @@ -14,7 +14,7 @@ services: nlx-01: &common-nlx hostname: nlx-01 privileged: true - image: ${LIBTELIO_ENV_SEC_CONTAINER_REGISTRY}/low-level-hacks/third-party-build/dockur_linux:${LIBTELIO_ENV_NAT_LAB_LINUX_VM_TAG} + image: ${LIBTELIO_ENV_SEC_CONTAINER_REGISTRY}/nord-projects/nordvpn/infra/llt/automation/docker/libtelio/dockur_linux:${LIBTELIO_ENV_NAT_LAB_LINUX_VM_TAG} devices: - "/dev/net/tun" - "/dev/kvm" @@ -107,7 +107,7 @@ services: windows-client-01: &windows-client hostname: windows-client-01 - image: ${LIBTELIO_ENV_SEC_CONTAINER_REGISTRY}/low-level-hacks/third-party-build/dockur_windows:${LIBTELIO_ENV_NAT_LAB_WINDOWS_VM_TAG} + image: ${LIBTELIO_ENV_SEC_CONTAINER_REGISTRY}/nord-projects/nordvpn/infra/llt/automation/docker/libtelio/dockur_windows:${LIBTELIO_ENV_NAT_LAB_WINDOWS_VM_TAG} privileged: true devices: - "/dev/net/tun" @@ -191,7 +191,7 @@ services: mac-client-01: &mac-client hostname: mac-client-01 - image: ${LIBTELIO_ENV_SEC_CONTAINER_REGISTRY}/low-level-hacks/third-party-build/dockur_macos:${LIBTELIO_ENV_NAT_LAB_MACOS_VM_TAG} + image: ${LIBTELIO_ENV_SEC_CONTAINER_REGISTRY}/nord-projects/nordvpn/infra/llt/automation/docker/libtelio/dockur_macos:${LIBTELIO_ENV_NAT_LAB_MACOS_VM_TAG} privileged: true devices: - "/dev/net/tun" @@ -483,7 +483,7 @@ services: fullcone-gw-01: &common-fullcone-gw hostname: fullcone-gw-01 privileged: true - image: ${LIBTELIO_ENV_SEC_CONTAINER_REGISTRY}/low-level-hacks/third-party-build/dockur_linux:${LIBTELIO_ENV_NAT_LAB_LINUX_VM_TAG} + image: ${LIBTELIO_ENV_SEC_CONTAINER_REGISTRY}/nord-projects/nordvpn/infra/llt/automation/docker/libtelio/dockur_linux:${LIBTELIO_ENV_NAT_LAB_LINUX_VM_TAG} devices: - "/dev/net/tun" - "/dev/kvm" @@ -665,8 +665,7 @@ services: context: . dockerfile: openwrt.Dockerfile args: - LIBTELIO_ENV_SEC_CONTAINER_REGISTRY: $LIBTELIO_ENV_SEC_CONTAINER_REGISTRY - LIBTELIO_ENV_NAT_LAB_DEPS_TAG: $LIBTELIO_ENV_NAT_LAB_DEPS_TAG + SEC_CONTAINER_REGISTRY: $LIBTELIO_ENV_SEC_CONTAINER_REGISTRY cap_drop: - ALL cap_add: diff --git a/nat-lab/openwrt.Dockerfile b/nat-lab/openwrt.Dockerfile index e340229a8..ac0d9cebb 100644 --- a/nat-lab/openwrt.Dockerfile +++ b/nat-lab/openwrt.Dockerfile @@ -1,6 +1,6 @@ -ARG LIBTELIO_ENV_SEC_CONTAINER_REGISTRY +ARG SEC_CONTAINER_REGISTRY -FROM ${LIBTELIO_ENV_SEC_CONTAINER_REGISTRY}/low-level-hacks/third-party-build/openwrt_image/natlab-openwrt-24.10.2-x86-64:v0.0.2 +FROM ${SEC_CONTAINER_REGISTRY}/nord-projects/nordvpn/infra/llt/third-party-build/openwrt_image/natlab-openwrt-24.10.4-x86-64:v0.3.0 ENV QEMU_CONFIG_TIMEOUT="300" @@ -13,7 +13,7 @@ RUN mkdir -p /var/lib/qemu-image WORKDIR /var/lib/qemu-image RUN mkdir -p /var/lib/qemu && \ - gunzip -c openwrt-24.10.2-x86-64-generic-ext4-combined.img.gz > /var/lib/qemu/image.raw + gunzip -c openwrt-24.10.4-x86-64-generic-ext4-combined.img.gz > /var/lib/qemu/image.raw RUN mkdir -p /usr/local/share/vmconfig/container.d /usr/local/share/vmconfig/vm.d RUN mkdir -p /var/lib/vmconfig/container.d /var/lib/vmconfig/vm.d diff --git a/nat-lab/tests/helpers.py b/nat-lab/tests/helpers.py index 4214b10a6..d0f2a64ce 100644 --- a/nat-lab/tests/helpers.py +++ b/nat-lab/tests/helpers.py @@ -604,10 +604,12 @@ async def print_network_state(connection: Connection) -> None: ip_r, ) - ip_tables_log = await connection.create_process(["iptables", "-L"]).execute() + ip_tables_log = await connection.create_process( + ["iptables", "--wait", "--list"] + ).execute() ip_tables = ip_tables_log.get_stdout().strip() log.debug( - "--- Log of iptables -L command ---\n %s", + "--- Log of iptables --list command ---\n %s", ip_tables, ) diff --git a/nat-lab/tests/nordvpnlite.py b/nat-lab/tests/nordvpnlite.py index 206edc2d4..f2e742c2e 100644 --- a/nat-lab/tests/nordvpnlite.py +++ b/nat-lab/tests/nordvpnlite.py @@ -114,7 +114,7 @@ async def assert_match_daemon_start(self, stdout: str): class NordVpnLite: - START_TIMEOUT_S = 3 + START_TIMEOUT_S = 10 SOCKET_CHECK_INTERVAL_S = 0.5 NORDVPNLITE_CMD_CHECK_INTERVAL_S = 10 # TODO (LLT-6693): revert back to 1 diff --git a/nat-lab/tests/telio.py b/nat-lab/tests/telio.py index 6e4b18624..1e3414d30 100644 --- a/nat-lab/tests/telio.py +++ b/nat-lab/tests/telio.py @@ -514,7 +514,7 @@ async def on_stderr(stderr: str) -> None: async with AsyncExitStack() as exit_stack: await exit_stack.enter_async_context(make_tcpdump([self._connection])) - if isinstance(self._connection, DockerConnection): + if isinstance(self._connection, DockerConnection) or self._connection.target_os == TargetOS.Windows: await self.clear_core_dumps() await self.clear_system_log() @@ -569,7 +569,7 @@ async def on_stderr(stderr: str) -> None: "[%s] Test cleanup: Stopping tcpdump and collecting core dumps", self._node.name, ) - if isinstance(self._connection, DockerConnection): + if isinstance(self._connection, DockerConnection) or self._connection.target_os == TargetOS.Windows: await self.collect_core_dumps() log.info( @@ -1381,18 +1381,28 @@ async def flush_logs(self) -> None: # - have set the NATLAB_SAVE_LOGS environment variable # - want to have natlab automatically collect core dumps for you def get_coredump_folder(self) -> tuple[str, str]: + if self._connection.target_os == TargetOS.Windows: + return "C:\\CrashDumps", "" return "/var/crash", "core-" def should_skip_core_dump_collection(self) -> bool: return ( os.environ.get("NATLAB_SAVE_LOGS") is None - or self._connection.target_os != TargetOS.Linux + or self._connection.target_os not in (TargetOS.Linux, TargetOS.Windows) ) async def clear_core_dumps(self): if self.should_skip_core_dump_collection(): return + if self._connection.target_os == TargetOS.Windows: + await self._connection.create_process( + ["powershell", "-Command", + "Remove-Item -Path 'C:\\CrashDumps\\*' -Force -ErrorAction SilentlyContinue"], + quiet=True, + ).execute() + return + coredump_folder, _ = self.get_coredump_folder() # clear the existing system core dumps @@ -1408,21 +1418,43 @@ async def collect_core_dumps(self): if self.should_skip_core_dump_collection(): return + coredump_dir = "coredumps" + os.makedirs(coredump_dir, exist_ok=True) + test_name = get_current_test_case_and_parameters()[0] or "" + + if self._connection.target_os == TargetOS.Windows: + try: + process = await self._connection.create_process( + ["powershell", "-Command", + "Get-ChildItem -Path 'C:\\CrashDumps' -Filter '*.dmp' | Select-Object -ExpandProperty FullName"], + quiet=True, + ).execute() + dump_files = [f.strip() for f in process.get_stdout().strip().splitlines() if f.strip()] + except ProcessExecError as e: + log.warning("[%s] Failed to list Windows crash dumps: %s", self._node.name, e) + dump_files = [] + + for i, dump_path in enumerate(dump_files): + file_name = dump_path.rsplit("\\", 1)[-1] + log.info("[%s] Collecting Windows crash dump: %s", self._node.name, dump_path) + await self._connection.download(dump_path, coredump_dir) + # Rename to include test name + downloaded = os.path.join(coredump_dir, file_name) + if os.path.exists(downloaded): + os.rename(downloaded, os.path.join(coredump_dir, f"{test_name}_{file_name}_{i}.dmp")) + return + coredump_folder, file_prefix = self.get_coredump_folder() dump_files = await find_files( self._connection, coredump_folder, f"{file_prefix}*" ) - coredump_dir = "coredumps" - os.makedirs(coredump_dir, exist_ok=True) - should_copy_coredumps = len(dump_files) > 0 # if we collected some core dumps, copy them if isinstance(self._connection, DockerConnection) and should_copy_coredumps: container_name = container_id(self._connection.tag) - test_name = get_current_test_case_and_parameters()[0] or "" for i, file_path in enumerate(dump_files): file_name = file_path.rsplit("/", 1)[-1] core_dump_destination = ( diff --git a/nat-lab/tests/test_core_api.py b/nat-lab/tests/test_core_api.py index 5e81c2d65..beec4b35c 100644 --- a/nat-lab/tests/test_core_api.py +++ b/nat-lab/tests/test_core_api.py @@ -106,15 +106,18 @@ async def clean_up_machines(connection: Connection): authorization_header=BEARER_AUTHORIZATION_HEADER, ) - for machine in machines: - await send_https_request( - connection, - f"{CORE_API_URL}/v1/meshnet/machines/{machine['identifier']}", - "DELETE", - CORE_API_CA_CERTIFICATE_PATH, - expect_response=False, - authorization_header=BEARER_AUTHORIZATION_HEADER, - ) + # Check if machines is a valid list response + if isinstance(machines, list): + for machine in machines: + if "identifier" in machine: + await send_https_request( + connection, + f"{CORE_API_URL}/v1/meshnet/machines/{machine['identifier']}", + "DELETE", + CORE_API_CA_CERTIFICATE_PATH, + expect_response=False, + authorization_header=BEARER_AUTHORIZATION_HEADER, + ) async def register_vpn_server_key( @@ -239,7 +242,10 @@ async def test_get_all_machines(registered_machines, machine_data): assert isinstance(response_data, list) assert len(response_data) == 2 - for machine, data in zip(registered_machines, machine_data): + for machine, data in zip(response_data, machine_data): + assert ( + "identifier" in machine + ), f"Machine response missing 'identifier': {machine}" verify_uuid(machine["identifier"]) assert machine["public_key"] == data.public_key assert machine["os"] == data.os @@ -299,6 +305,9 @@ async def test_delete_registered_machine(registered_machines, machine_data): ) machine = registered_machines[0] + assert ( + "identifier" in machine + ), f"Machine response missing 'identifier': {machine}" await send_https_request( connection, diff --git a/nat-lab/tests/test_events_link_state.py b/nat-lab/tests/test_events_link_state.py index 976b3881b..f170d5bf4 100644 --- a/nat-lab/tests/test_events_link_state.py +++ b/nat-lab/tests/test_events_link_state.py @@ -391,13 +391,13 @@ def __init__(self, conn: Connection): async def __aenter__(self): proc = self._conn.create_process([ "iptables", - "-I", + "--insert", "INPUT", - "-p", + "--protocol", "icmp", "--icmp-type", "echo-request", - "-j", + "--jump", "DROP", ]) await proc.execute() @@ -405,13 +405,13 @@ async def __aenter__(self): async def __aexit__(self, *_): proc = self._conn.create_process([ "iptables", - "-D", + "--delete", "INPUT", - "-p", + "--protocol", "icmp", "--icmp-type", "echo-request", - "-j", + "--jump", "DROP", ]) await proc.execute() diff --git a/nat-lab/tests/test_notification_center.py b/nat-lab/tests/test_notification_center.py index 18797c9f5..61e403edb 100644 --- a/nat-lab/tests/test_notification_center.py +++ b/nat-lab/tests/test_notification_center.py @@ -178,6 +178,13 @@ async def test_nc_register(): payload, authorization_header=BEARER_AUTHORIZATION_HEADER, ) + + # Check if the response contains an error + if isinstance(https_process_stdout, dict) and "errors" in https_process_stdout: + raise AssertionError( + f"API returned error: {https_process_stdout['errors']}" + ) + response = MachineResponse(**https_process_stdout) identifier = response.identifier diff --git a/nat-lab/tests/utils/router/linux_router.py b/nat-lab/tests/utils/router/linux_router.py index 98ff9bca7..5fd148786 100644 --- a/nat-lab/tests/utils/router/linux_router.py +++ b/nat-lab/tests/utils/router/linux_router.py @@ -251,16 +251,17 @@ async def create_exit_node_route(self) -> None: await self._connection.create_process( [ "iptables", - "-t", + "--wait", # Wait for xtables lock + "--table", "nat", - "-A", + "--append", "POSTROUTING", - "-s", + "--source", "100.64.0.0/10", "!", - "-o", + "--out-interface", self._interface_name, - "-j", + "--jump", "MASQUERADE", ], quiet=True, @@ -270,16 +271,17 @@ async def create_exit_node_route(self) -> None: await self._connection.create_process( [ "ip6tables", - "-t", + "--wait", # Wait for xtables lock + "--table", "nat", - "-A", + "--append", "POSTROUTING", - "-s", + "--source", config.LIBTELIO_IPV6_WG_SUBNET + "::/64", "!", - "-o", + "--out-interface", self._interface_name, - "-j", + "--jump", "MASQUERADE", ], quiet=True, @@ -291,16 +293,17 @@ async def delete_exit_node_route(self) -> None: await self._connection.create_process( [ "iptables", - "-t", + "--wait", # Wait for xtables lock + "--table", "nat", - "-D", + "--delete", "POSTROUTING", - "-s", + "--source", "100.64.0.0/10", "!", - "-o", + "--out-interface", self._interface_name, - "-j", + "--jump", "MASQUERADE", ], quiet=True, @@ -315,16 +318,17 @@ async def delete_exit_node_route(self) -> None: await self._connection.create_process( [ "ip6tables", - "-t", + "--wait", # Wait for xtables lock + "--table", "nat", - "-D", + "--delete", "POSTROUTING", - "-s", + "--source", config.LIBTELIO_IPV6_WG_SUBNET + "::/64", "!", - "-o", + "--out-interface", self._interface_name, - "-j", + "--jump", "MASQUERADE", ], quiet=True, @@ -349,13 +353,14 @@ async def disable_path(self, address: str) -> AsyncIterator: await self._connection.create_process( [ iptables_string, - "-t", + "--wait", # Wait for xtables lock + "--table", "filter", - "-A", + "--append", "INPUT", - "-s", + "--source", address, - "-j", + "--jump", "DROP", ], quiet=True, @@ -363,13 +368,14 @@ async def disable_path(self, address: str) -> AsyncIterator: await self._connection.create_process( [ iptables_string, - "-t", + "--wait", # Wait for xtables lock + "--table", "filter", - "-A", + "--append", "OUTPUT", - "-d", + "--destination", address, - "-j", + "--jump", "DROP", ], quiet=True, @@ -381,13 +387,14 @@ async def disable_path(self, address: str) -> AsyncIterator: await self._connection.create_process( [ iptables_string, - "-t", + "--wait", # Wait for xtables lock + "--table", "filter", - "-D", + "--delete", "INPUT", - "-s", + "--source", address, - "-j", + "--jump", "DROP", ], quiet=True, @@ -395,13 +402,14 @@ async def disable_path(self, address: str) -> AsyncIterator: await self._connection.create_process( [ iptables_string, - "-t", + "--wait", # Wait for xtables lock + "--table", "filter", - "-D", + "--delete", "OUTPUT", - "-d", + "--destination", address, - "-j", + "--jump", "DROP", ], quiet=True, @@ -419,15 +427,16 @@ async def break_tcp_conn_to_host(self, address: str) -> AsyncIterator: await self._connection.create_process( [ iptables_string, - "-t", + "--wait", # Wait for xtables lock + "--table", "filter", - "-A", + "--append", "OUTPUT", "--destination", address, - "-p", + "--protocol", "tcp", - "-j", + "--jump", "REJECT", "--reject-with", "tcp-reset", @@ -441,15 +450,16 @@ async def break_tcp_conn_to_host(self, address: str) -> AsyncIterator: await self._connection.create_process( [ iptables_string, - "-t", + "--wait", # Wait for xtables lock + "--table", "filter", - "-D", + "--delete", "OUTPUT", "--destination", address, - "-p", + "--protocol", "tcp", - "-j", + "--jump", "REJECT", "--reject-with", "tcp-reset", @@ -469,15 +479,16 @@ async def break_udp_conn_to_host(self, address: str) -> AsyncIterator: await self._connection.create_process( [ iptables_string, - "-t", + "--wait", # Wait for xtables lock + "--table", "filter", - "-A", + "--append", "OUTPUT", "--destination", address, - "-p", + "--protocol", "udp", - "-j", + "--jump", "REJECT", "--reject-with", "icmp-host-unreachable", @@ -491,15 +502,16 @@ async def break_udp_conn_to_host(self, address: str) -> AsyncIterator: await self._connection.create_process( [ iptables_string, - "-t", + "--wait", # Wait for xtables lock + "--table", "filter", - "-D", + "--delete", "OUTPUT", "--destination", address, - "-p", + "--protocol", "udp", - "-j", + "--jump", "REJECT", "--reject-with", "icmp-host-unreachable", @@ -514,13 +526,14 @@ async def block_udp_port(self, port: int) -> AsyncIterator: await self._connection.create_process( [ "iptables", - "-A", + "--wait", # Wait for xtables lock + "--append", "OUTPUT", - "-p", + "--protocol", "udp", "--sport", str(port), - "-j", + "--jump", "DROP", ], quiet=True, @@ -532,13 +545,14 @@ async def block_udp_port(self, port: int) -> AsyncIterator: await self._connection.create_process( [ "iptables", - "-D", + "--wait", # Wait for xtables lock + "--delete", "OUTPUT", - "-p", + "--protocol", "udp", "--sport", str(port), - "-j", + "--jump", "DROP", ], quiet=True, @@ -549,13 +563,14 @@ async def block_tcp_port(self, port: int) -> AsyncIterator: await self._connection.create_process( [ "iptables", - "-A", + "--wait", # Wait for xtables lock + "--append", "OUTPUT", - "-p", + "--protocol", "tcp", "--dport", str(port), - "-j", + "--jump", "DROP", ], quiet=True, @@ -567,13 +582,14 @@ async def block_tcp_port(self, port: int) -> AsyncIterator: await self._connection.create_process( [ "iptables", - "-D", + "--wait", # Wait for xtables lock + "--delete", "OUTPUT", - "-p", + "--protocol", "tcp", "--dport", str(port), - "-j", + "--jump", "DROP", ], quiet=True, diff --git a/nat-lab/tests/utils/tcpdump.py b/nat-lab/tests/utils/tcpdump.py index 5362eb1c6..08a9c94ef 100644 --- a/nat-lab/tests/utils/tcpdump.py +++ b/nat-lab/tests/utils/tcpdump.py @@ -19,7 +19,7 @@ TargetOS.Mac: "/var/root/dump.pcap", TargetOS.Windows: "C:\\workspace\\dump.pcap", } -TCPDUMP_START_EVENT_TIMEOUT_S = 3 +TCPDUMP_START_EVENT_TIMEOUT_S = 10 class TcpDump: diff --git a/nat-lab/tests/utils/vm/openwrt_vm_util.py b/nat-lab/tests/utils/vm/openwrt_vm_util.py index a7dc9b948..a870a8555 100644 --- a/nat-lab/tests/utils/vm/openwrt_vm_util.py +++ b/nat-lab/tests/utils/vm/openwrt_vm_util.py @@ -1,9 +1,10 @@ import asyncssh +import os from config import get_root_path from utils.connection import Connection from utils.process import ProcessExecError -DIST_PATH = "dist/openwrt/" +DIST_PATH = f"dist/openwrt/{os.getenv('TELIO_BIN_PROFILE')}/x86_64/" NATLAB_DATA_PATH = "nat-lab/data/" LOCAL_BIN_DIR = "/tmp/" OWR_CERT_PATH = "/etc/ssl/server_certificate/"