diff --git a/kotlin/android/app/build.gradle b/kotlin/android/app/build.gradle index d30247d7e..d659b746b 100644 --- a/kotlin/android/app/build.gradle +++ b/kotlin/android/app/build.gradle @@ -134,4 +134,6 @@ dependencies { // Add the dependencies for the Crashlytics and Analytics libraries // When using the BoM, you don't specify versions in Firebase library dependencies implementation("com.google.firebase:firebase-crashlytics-ktx") - implementation("com.google.firebase:firebase-analytics-ktx")} + implementation("com.google.firebase:firebase-analytics-ktx") + implementation("com.google.firebase:firebase-installations-ktx") +} diff --git a/rust/Cargo.lock b/rust/Cargo.lock index f0604d058..c6d9c7f8d 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -1821,6 +1821,7 @@ dependencies = [ "parking_lot", "rand", "rand_core", + "ring", "rtnetlink", "serde", "serde_json", diff --git a/rust/connlib/clients/android/connlib/build.gradle.kts b/rust/connlib/clients/android/connlib/build.gradle.kts index 4b4362699..e5ba026b5 100644 --- a/rust/connlib/clients/android/connlib/build.gradle.kts +++ b/rust/connlib/clients/android/connlib/build.gradle.kts @@ -68,7 +68,7 @@ fun copyJniShared(task: Task, buildType: String) = task.apply { jniTargets.forEach { entry -> val soFile = File( project.projectDir.parentFile.parentFile.parentFile.parentFile, - "target/${entry.key}/${buildType}/libconnlib.so" + "target/${entry.key}/$buildType/libconnlib.so", ) val targetDir = File(project.projectDir, "/jniLibs/${entry.value}").apply { if (!exists()) { @@ -77,9 +77,11 @@ fun copyJniShared(task: Task, buildType: String) = task.apply { } copy { - with(copySpec { - from(soFile) - }) + with( + copySpec { + from(soFile) + }, + ) into(targetDir) } } @@ -88,7 +90,7 @@ fun copyJniShared(task: Task, buildType: String) = task.apply { cargo { prebuiltToolchains = true verbose = true - module = "../" + module = "../" libname = "connlib" targets = listOf("arm", "arm64", "x86", "x86_64") features { @@ -108,13 +110,13 @@ tasks.register("copyJniSharedObjectsRelease") { tasks.whenTaskAdded { if (name.startsWith("javaPreCompile")) { - val newTasks = arrayOf ( + val newTasks = arrayOf( tasks.named("cargoBuild"), if (name.endsWith("Debug")) { tasks.named("copyJniSharedObjectsDebug") } else { tasks.named("copyJniSharedObjectsRelease") - } + }, ) dependsOn(*newTasks) } diff --git a/rust/connlib/clients/android/src/lib.rs b/rust/connlib/clients/android/src/lib.rs index 26b30ce19..617c620e2 100644 --- a/rust/connlib/clients/android/src/lib.rs +++ b/rust/connlib/clients/android/src/lib.rs @@ -289,7 +289,7 @@ fn connect( env: &mut JNIEnv, portal_url: JString, portal_token: JString, - external_id: JString, + device_id: JString, callback_handler: GlobalRef, ) -> Result, ConnectError> { let portal_url = String::from(env.get_string(&portal_url).map_err(|source| { @@ -309,17 +309,17 @@ fn connect( vm: env.get_java_vm().map_err(ConnectError::GetJavaVmFailed)?, callback_handler, }; - let external_id = env - .get_string(&external_id) + let device_id = env + .get_string(&device_id) .map_err(|source| ConnectError::StringInvalid { - name: "external_id", + name: "device_id", source, })? .into(); Session::connect( portal_url.as_str(), portal_token, - external_id, + device_id, callback_handler, ) .map_err(Into::into) @@ -335,13 +335,13 @@ pub unsafe extern "system" fn Java_dev_firezone_android_tunnel_TunnelSession_con _class: JClass, portal_url: JString, portal_token: JString, - external_id: JString, + device_id: JString, callback_handler: JObject, ) -> *const Session { let Ok(callback_handler) = env.new_global_ref(callback_handler) else { return std::ptr::null() }; if let Some(result) = catch_and_throw(&mut env, |env| { - connect(env, portal_url, portal_token, external_id, callback_handler) + connect(env, portal_url, portal_token, device_id, callback_handler) }) { match result { Ok(session) => return Box::into_raw(Box::new(session)), diff --git a/rust/connlib/clients/apple/src/lib.rs b/rust/connlib/clients/apple/src/lib.rs index f1567e67a..277a9968e 100644 --- a/rust/connlib/clients/apple/src/lib.rs +++ b/rust/connlib/clients/apple/src/lib.rs @@ -19,7 +19,7 @@ mod ffi { fn connect( portal_url: String, token: String, - external_id: String, + device_id: String, callback_handler: CallbackHandler, ) -> Result; @@ -154,14 +154,14 @@ impl WrappedSession { fn connect( portal_url: String, token: String, - external_id: String, + device_id: String, callback_handler: ffi::CallbackHandler, ) -> Result { init_logging(); Session::connect( portal_url.as_str(), token, - external_id, + device_id, CallbackHandler(callback_handler.into()), ) .map(|session| Self { session }) diff --git a/rust/connlib/clients/headless/src/main.rs b/rust/connlib/clients/headless/src/main.rs index a6e3990fd..eed1751d7 100644 --- a/rust/connlib/clients/headless/src/main.rs +++ b/rust/connlib/clients/headless/src/main.rs @@ -8,7 +8,7 @@ use std::{ }; use firezone_client_connlib::{ - get_external_id, get_user_agent, Callbacks, Error, ResourceDescription, Session, + get_device_id, get_user_agent, Callbacks, Error, ResourceDescription, Session, }; use url::Url; @@ -82,8 +82,8 @@ fn main() -> Result<()> { // TODO: allow passing as arg vars let url = parse_env_var::(URL_ENV_VAR)?; let secret = parse_env_var::(SECRET_ENV_VAR)?; - let external_id = get_external_id(); - let mut session = Session::connect(url, secret, external_id, CallbackHandler).unwrap(); + let device_id = get_device_id(); + let mut session = Session::connect(url, secret, device_id, CallbackHandler).unwrap(); tracing::info!("Started new session"); block_on_ctrl_c(); diff --git a/rust/connlib/gateway/src/main.rs b/rust/connlib/gateway/src/main.rs index f36e3adba..95df94156 100644 --- a/rust/connlib/gateway/src/main.rs +++ b/rust/connlib/gateway/src/main.rs @@ -6,7 +6,7 @@ use std::{ str::FromStr, }; -use firezone_gateway_connlib::{get_external_id, Callbacks, Error, ResourceDescription, Session}; +use firezone_gateway_connlib::{get_device_id, Callbacks, Error, ResourceDescription, Session}; use url::Url; #[derive(Clone)] @@ -66,8 +66,8 @@ fn main() -> Result<()> { // TODO: allow passing as arg vars let url = parse_env_var::(URL_ENV_VAR)?; let secret = parse_env_var::(SECRET_ENV_VAR)?; - let external_id = get_external_id(); - let mut session = Session::connect(url, secret, external_id, CallbackHandler).unwrap(); + let device_id = get_device_id(); + let mut session = Session::connect(url, secret, device_id, CallbackHandler).unwrap(); let (tx, rx) = std::sync::mpsc::channel(); ctrlc::set_handler(move || tx.send(()).expect("Could not send stop signal on channel.")) diff --git a/rust/connlib/libs/client/src/lib.rs b/rust/connlib/libs/client/src/lib.rs index 7209ec0f7..d30802eb9 100644 --- a/rust/connlib/libs/client/src/lib.rs +++ b/rust/connlib/libs/client/src/lib.rs @@ -19,7 +19,7 @@ pub type Session = libs_common::Session< >; pub use libs_common::{ - get_external_id, get_user_agent, messages::ResourceDescription, Callbacks, Error, + get_device_id, get_user_agent, messages::ResourceDescription, Callbacks, Error, }; use messages::Messages; use messages::ReplyMessages; diff --git a/rust/connlib/libs/common/Cargo.toml b/rust/connlib/libs/common/Cargo.toml index 16fcb90ae..d5bd528f9 100644 --- a/rust/connlib/libs/common/Cargo.toml +++ b/rust/connlib/libs/common/Cargo.toml @@ -30,6 +30,7 @@ os_info = { version = "3", default-features = false } rand = { version = "0.8", default-features = false, features = ["std"] } chrono = { workspace = true } parking_lot = "0.12" +ring = "0.16" # Needed for Android logging until tracing is working log = "0.4" diff --git a/rust/connlib/libs/common/src/lib.rs b/rust/connlib/libs/common/src/lib.rs index 05be80496..372c14f5c 100644 --- a/rust/connlib/libs/common/src/lib.rs +++ b/rust/connlib/libs/common/src/lib.rs @@ -27,8 +27,8 @@ pub fn get_user_agent() -> String { format!("{os_type}/{os_version} {lib_name}/{lib_version}") } -/// Returns the SMBios Serial of the device, or a random UUIDv4 if it can't be found. -pub fn get_external_id() -> String { +/// Returns the SMBios Serial of the device or a random UUIDv4 if the SMBios is not available. +pub fn get_device_id() -> String { // smbios fails to build on mobile, but it works for other platforms. #[cfg(not(any(target_os = "ios", target_os = "android")))] match smbioslib::table_load_from_device() { diff --git a/rust/connlib/libs/common/src/session.rs b/rust/connlib/libs/common/src/session.rs index 91d0a9da1..dc7d54447 100644 --- a/rust/connlib/libs/common/src/session.rs +++ b/rust/connlib/libs/common/src/session.rs @@ -4,6 +4,7 @@ use boringtun::x25519::{PublicKey, StaticSecret}; use ip_network::IpNetwork; use rand::{distributions::Alphanumeric, thread_rng, Rng}; use rand_core::OsRng; +use ring::digest::{Context, SHA256}; use std::{ error::Error as StdError, fmt::{Debug, Display}, @@ -213,7 +214,7 @@ where pub fn connect( portal_url: impl TryInto, token: String, - external_id: String, + device_id: String, callbacks: CB, ) -> Result { // TODO: We could use tokio::runtime::current() to get the current runtime @@ -255,7 +256,7 @@ where tx, portal_url.try_into().map_err(|_| Error::UriError)?, token, - external_id, + device_id, this.callbacks.clone(), ); std::thread::spawn(move || { @@ -271,12 +272,13 @@ where runtime_stopper: tokio::sync::mpsc::Sender, portal_url: Url, token: String, - external_id: String, + device_id: String, callbacks: CallbackErrorFacade, ) { runtime.spawn(async move { let private_key = StaticSecret::random_from_rng(OsRng); let name_suffix: String = thread_rng().sample_iter(&Alphanumeric).take(8).map(char::from).collect(); + let external_id = sha256(device_id); let connect_url = fatal_error!( get_websocket_path(portal_url, token, T::socket_path(), &Key(PublicKey::from(&private_key).to_bytes()), &external_id, &name_suffix), @@ -394,6 +396,18 @@ fn set_ws_scheme(url: &mut Url) -> Result<()> { Ok(()) } +fn sha256(input: String) -> String { + let mut ctx = Context::new(&SHA256); + ctx.update(input.as_bytes()); + let digest = ctx.finish(); + + digest + .as_ref() + .iter() + .map(|b| format!("{:02x}", b)) + .collect() +} + fn get_websocket_path( mut url: Url, secret: String, diff --git a/rust/connlib/libs/gateway/src/lib.rs b/rust/connlib/libs/gateway/src/lib.rs index c0c9bbd03..f7956e395 100644 --- a/rust/connlib/libs/gateway/src/lib.rs +++ b/rust/connlib/libs/gateway/src/lib.rs @@ -19,4 +19,4 @@ pub type Session = libs_common::Session< CB, >; -pub use libs_common::{get_external_id, messages::ResourceDescription, Callbacks, Error}; +pub use libs_common::{get_device_id, messages::ResourceDescription, Callbacks, Error}; diff --git a/swift/apple/Firezone/xcconfig/Developer.xcconfig.template b/swift/apple/Firezone/xcconfig/Developer.xcconfig.template index f32287f21..e1fd00e2c 100644 --- a/swift/apple/Firezone/xcconfig/Developer.xcconfig.template +++ b/swift/apple/Firezone/xcconfig/Developer.xcconfig.template @@ -5,7 +5,3 @@ DEVELOPMENT_TEAM = // Should be an app id created at developer.apple.com // with Network Extensions capability. PRODUCT_BUNDLE_IDENTIFIER = - -// If you want to build Connlib with mocks, -// enable it here. -// CONNLIB_MOCK=1 diff --git a/swift/apple/FirezoneNetworkExtension/Adapter.swift b/swift/apple/FirezoneNetworkExtension/Adapter.swift index 334f260b3..70ba5fb15 100644 --- a/swift/apple/FirezoneNetworkExtension/Adapter.swift +++ b/swift/apple/FirezoneNetworkExtension/Adapter.swift @@ -130,7 +130,7 @@ public class Adapter { do { self.state = .startingTunnel( session: try WrappedSession.connect( - self.controlPlaneURLString, self.token, self.getExternalId(), self.callbackHandler), + self.controlPlaneURLString, self.token, self.getDeviceId(), self.callbackHandler), onStarted: completionHandler ) } catch let error { @@ -200,23 +200,25 @@ public class Adapter { } // MARK: Device unique identifiers + extension Adapter { - func getExternalId() -> String { + func getDeviceId() -> String { #if os(iOS) - guard let uuid = UIDevice.current.identifierForVendor?.uuidString else { + guard let extId = UIDevice.current.identifierForVendor?.uuidString else { // Send a blank string, letting either connlib or the portal handle this return "" } - return uuid + #elseif os(macOS) - guard let macBytes = PrimaryMacAddress.copy_mac_address() else { + guard let extId = PrimaryMacAddress.copy_mac_address() as? String else { // Send a blank string, letting either connlib or the portal handle this return "" } - return (macBytes as Data).base64EncodedString() #else #error("Unsupported platform") #endif + + return extId } } @@ -271,7 +273,7 @@ extension Adapter { do { self.state = .startingTunnel( session: try WrappedSession.connect( - controlPlaneURLString, token, self.getExternalId(), self.callbackHandler), + controlPlaneURLString, token, self.getDeviceId(), self.callbackHandler), onStarted: { error in if let error = error { self.logger.error( diff --git a/swift/apple/FirezoneNetworkExtension/PrimaryMacAddress.swift b/swift/apple/FirezoneNetworkExtension/PrimaryMacAddress.swift index e92411196..ffb6d7a90 100644 --- a/swift/apple/FirezoneNetworkExtension/PrimaryMacAddress.swift +++ b/swift/apple/FirezoneNetworkExtension/PrimaryMacAddress.swift @@ -8,14 +8,14 @@ // Believe it or not, this is Apple's recommended way of doing things for macOS // See https://developer.apple.com/documentation/appstorereceipts/validating_receipts_on_the_device#//apple_ref/doc/uid/TP40010573-CH1-SW14 -import IOKit import Foundation +import IOKit import OSLog public class PrimaryMacAddress { // Returns an object with a +1 retain count; the caller needs to release. private static func io_service(named name: String, wantBuiltIn: Bool) -> io_service_t? { - let default_port = kIOMainPortDefault + let defaultPort = kIOMainPortDefault var iterator = io_iterator_t() defer { if iterator != IO_OBJECT_NULL { @@ -23,21 +23,24 @@ public class PrimaryMacAddress { } } - guard let matchingDict = IOBSDNameMatching(default_port, 0, name), - IOServiceGetMatchingServices(default_port, - matchingDict as CFDictionary, - &iterator) == KERN_SUCCESS, - iterator != IO_OBJECT_NULL + guard let matchingDict = IOBSDNameMatching(defaultPort, 0, name), + IOServiceGetMatchingServices( + defaultPort, + matchingDict as CFDictionary, + &iterator) == KERN_SUCCESS, + iterator != IO_OBJECT_NULL else { return nil } var candidate = IOIteratorNext(iterator) while candidate != IO_OBJECT_NULL { - if let cftype = IORegistryEntryCreateCFProperty(candidate, - "IOBuiltin" as CFString, - kCFAllocatorDefault, - 0) { + if let cftype = IORegistryEntryCreateCFProperty( + candidate, + "IOBuiltin" as CFString, + kCFAllocatorDefault, + 0) + { let isBuiltIn = cftype.takeRetainedValue() as! CFBoolean if wantBuiltIn == CFBooleanGetValue(isBuiltIn) { return candidate @@ -55,23 +58,23 @@ public class PrimaryMacAddress { // Prefer built-in network interfaces. // For example, an external Ethernet adaptor can displace // the built-in Wi-Fi as en0. - guard let service = io_service(named: "en0", wantBuiltIn: true) - ?? io_service(named: "en1", wantBuiltIn: true) - ?? io_service(named: "en0", wantBuiltIn: false) + guard + let service = io_service(named: "en0", wantBuiltIn: true) + ?? io_service(named: "en1", wantBuiltIn: true) + ?? io_service(named: "en0", wantBuiltIn: false) else { return nil } defer { IOObjectRelease(service) } - if let cftype = IORegistryEntrySearchCFProperty( service, kIOServicePlane, "IOMACAddress" as CFString, kCFAllocatorDefault, - IOOptionBits(kIORegistryIterateRecursively | kIORegistryIterateParents)) { + IOOptionBits(kIORegistryIterateRecursively | kIORegistryIterateParents)) + { return (cftype as! CFData) } - return nil } }