feat(hololake): connect enterprise device challenge flow
This commit is contained in:
parent
be31c4136b
commit
6b23f710e1
19 changed files with 1220 additions and 14 deletions
|
|
@ -91,6 +91,29 @@ version = "1.5.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
|
||||
|
||||
[[package]]
|
||||
name = "aws-lc-rs"
|
||||
version = "1.18.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b281d307588d634de920874890732659e2e7672f72b5e10e81badc1a8a83621e"
|
||||
dependencies = [
|
||||
"aws-lc-sys",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aws-lc-sys"
|
||||
version = "0.45.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9bff6c3b54fad79a2e60b8102caf565819711497c1f5f092f49508e2f5c31b27"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"cmake",
|
||||
"dunce",
|
||||
"fs_extra",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.21.7"
|
||||
|
|
@ -282,6 +305,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "0ad534f4357a5264cce5019c989cf66a4f0dc4e0d1b1d15f8aacec0ff7360273"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"jobserver",
|
||||
"libc",
|
||||
"shlex",
|
||||
]
|
||||
|
||||
|
|
@ -318,6 +343,23 @@ version = "1.0.4"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||
|
||||
[[package]]
|
||||
name = "cfg_aliases"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527"
|
||||
|
||||
[[package]]
|
||||
name = "chacha20"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "65c35e4b699c7e15ccbe7ee35c005e4fc0a278d22238a2857e6ce2dadeda1b06"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures 0.3.1",
|
||||
"rand_core 0.10.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.45"
|
||||
|
|
@ -330,6 +372,15 @@ dependencies = [
|
|||
"windows-link 0.2.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cmake"
|
||||
version = "0.1.58"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "combine"
|
||||
version = "4.6.8"
|
||||
|
|
@ -405,6 +456,15 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.5.1"
|
||||
|
|
@ -904,6 +964,12 @@ dependencies = [
|
|||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fs_extra"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c"
|
||||
|
||||
[[package]]
|
||||
name = "futf"
|
||||
version = "0.1.5"
|
||||
|
|
@ -921,6 +987,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1121,8 +1188,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"js-sys",
|
||||
"libc",
|
||||
"wasi 0.11.1+wasi-snapshot-preview1",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1144,8 +1213,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"js-sys",
|
||||
"libc",
|
||||
"r-efi 6.0.0",
|
||||
"rand_core 0.10.1",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1333,6 +1405,7 @@ dependencies = [
|
|||
"base64 0.22.1",
|
||||
"chrono",
|
||||
"regex",
|
||||
"reqwest",
|
||||
"ring",
|
||||
"security-framework",
|
||||
"serde",
|
||||
|
|
@ -1806,6 +1879,16 @@ dependencies = [
|
|||
"syn 2.0.119",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jobserver"
|
||||
version = "0.1.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3"
|
||||
dependencies = [
|
||||
"getrandom 0.4.3",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.104"
|
||||
|
|
@ -1938,6 +2021,12 @@ version = "0.4.34"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6"
|
||||
|
||||
[[package]]
|
||||
name = "lru-slab"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154"
|
||||
|
||||
[[package]]
|
||||
name = "mac"
|
||||
version = "0.1.1"
|
||||
|
|
@ -2787,6 +2876,63 @@ dependencies = [
|
|||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quinn"
|
||||
version = "0.11.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c1a41e437b6bbd489372cd4971de128e85c855f56c57f283d20ff016cf7c0a8"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"cfg_aliases",
|
||||
"pin-project-lite",
|
||||
"quinn-proto",
|
||||
"quinn-udp",
|
||||
"rustc-hash",
|
||||
"rustls",
|
||||
"socket2",
|
||||
"thiserror 2.0.20",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"web-time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quinn-proto"
|
||||
version = "0.11.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04759210543be93709136e28212294a659ef5001836ff4eab4d663e4529bba83"
|
||||
dependencies = [
|
||||
"aws-lc-rs",
|
||||
"bytes",
|
||||
"getrandom 0.4.3",
|
||||
"lru-slab",
|
||||
"rand 0.10.2",
|
||||
"rand_pcg 0.10.2",
|
||||
"ring",
|
||||
"rustc-hash",
|
||||
"rustls",
|
||||
"rustls-pki-types",
|
||||
"slab",
|
||||
"thiserror 2.0.20",
|
||||
"tinyvec",
|
||||
"tracing",
|
||||
"web-time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quinn-udp"
|
||||
version = "0.5.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "35a133f956daabe89a61a685c2649f13d82d5aa4bd5d12d1277e1072a21c0694"
|
||||
dependencies = [
|
||||
"cfg_aliases",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"socket2",
|
||||
"tracing",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.47"
|
||||
|
|
@ -2819,7 +2965,7 @@ dependencies = [
|
|||
"rand_chacha 0.2.2",
|
||||
"rand_core 0.5.1",
|
||||
"rand_hc",
|
||||
"rand_pcg",
|
||||
"rand_pcg 0.2.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -2833,6 +2979,17 @@ dependencies = [
|
|||
"rand_core 0.6.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80"
|
||||
dependencies = [
|
||||
"chacha20",
|
||||
"getrandom 0.4.3",
|
||||
"rand_core 0.10.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.2.2"
|
||||
|
|
@ -2871,6 +3028,12 @@ dependencies = [
|
|||
"getrandom 0.2.17",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69"
|
||||
|
||||
[[package]]
|
||||
name = "rand_hc"
|
||||
version = "0.2.0"
|
||||
|
|
@ -2889,6 +3052,15 @@ dependencies = [
|
|||
"rand_core 0.5.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_pcg"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "caa0f4137e1c0a72f4c651489402276c8e8e1cf081f3b0ba156d2cbeef09e86a"
|
||||
dependencies = [
|
||||
"rand_core 0.10.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "raw-window-handle"
|
||||
version = "0.6.2"
|
||||
|
|
@ -2972,6 +3144,7 @@ checksum = "219c5811de6525e5416c7d5d53bb656d3afdbc6c5af816e0802bcfa42dbdc1c3"
|
|||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"bytes",
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"http",
|
||||
|
|
@ -2984,6 +3157,7 @@ dependencies = [
|
|||
"log",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"quinn",
|
||||
"rustls",
|
||||
"rustls-pki-types",
|
||||
"rustls-platform-verifier",
|
||||
|
|
@ -3075,6 +3249,7 @@ version = "0.23.43"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0283386ce02abc0151e1761d08802dfe86c173b0b494af5cbc086574e453da06"
|
||||
dependencies = [
|
||||
"aws-lc-rs",
|
||||
"once_cell",
|
||||
"ring",
|
||||
"rustls-pki-types",
|
||||
|
|
@ -3101,6 +3276,7 @@ version = "1.15.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96"
|
||||
dependencies = [
|
||||
"web-time",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
|
|
@ -3137,6 +3313,7 @@ version = "0.103.15"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f3c3cf1d8b1e7d4927e2d154c3fcb02979afb9939629c62cd9048d4f07b60ac2"
|
||||
dependencies = [
|
||||
"aws-lc-rs",
|
||||
"ring",
|
||||
"rustls-pki-types",
|
||||
"untrusted",
|
||||
|
|
@ -3469,7 +3646,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"cpufeatures 0.2.17",
|
||||
"digest",
|
||||
]
|
||||
|
||||
|
|
@ -4758,6 +4935,16 @@ dependencies = [
|
|||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "web-time"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "web_atoms"
|
||||
version = "0.2.6"
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ regex = "1"
|
|||
chrono = { version = "0.4", default-features = false, features = ["clock"] }
|
||||
base64 = "0.22"
|
||||
ring = "0.17"
|
||||
reqwest = { version = "0.13", default-features = false, features = ["blocking", "json", "rustls"] }
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
security-framework = "3"
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ use crate::storage;
|
|||
|
||||
const KEYCHAIN_SERVICE: &str = "world.guanghu.hololake.enterprise-device";
|
||||
const KEYCHAIN_ACCOUNT: &str = "responsibility-entrance-ed25519-v1";
|
||||
const SESSION_KEYCHAIN_SERVICE: &str = "world.guanghu.hololake.enterprise-session";
|
||||
const GATE_BASE: &str = "https://guanghu.chat/api/hololake/enterprise/device-gate";
|
||||
const ALLOWED_DOMAINS: [&str; 4] = ["DOMAIN-MAIN", "DOMAIN-SUB", "DOMAIN-ZERO", "DOMAIN-ZS"];
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
|
|
@ -52,11 +54,24 @@ pub struct EnterpriseEntranceSnapshot {
|
|||
pub state: String,
|
||||
pub device: Option<EnterpriseDeviceProof>,
|
||||
pub binding: Option<EnterpriseBindingRequest>,
|
||||
pub session: Option<EnterpriseSession>,
|
||||
pub enterprise_server_embedded: bool,
|
||||
pub server_authorized: bool,
|
||||
pub next_action: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct EnterpriseSession {
|
||||
pub schema: String,
|
||||
pub state: String,
|
||||
pub domain_id: String,
|
||||
pub repository: String,
|
||||
pub repository_url: String,
|
||||
pub expires_unix_ms: u128,
|
||||
pub token_storage: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct EnterpriseChallengeProof {
|
||||
|
|
@ -84,6 +99,14 @@ fn used_challenges_path(app: &AppHandle) -> Result<std::path::PathBuf, String> {
|
|||
Ok(storage::root(app)?.join("enterprise-entrance/used-challenges.json"))
|
||||
}
|
||||
|
||||
fn enrollment_path(app: &AppHandle) -> Result<std::path::PathBuf, String> {
|
||||
Ok(storage::root(app)?.join("enterprise-entrance/enrollment-receipt.json"))
|
||||
}
|
||||
|
||||
fn session_path(app: &AppHandle) -> Result<std::path::PathBuf, String> {
|
||||
Ok(storage::root(app)?.join("enterprise-entrance/session.json"))
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
fn load_key_bytes() -> Result<Option<Vec<u8>>, String> {
|
||||
match security_framework::passwords::get_generic_password(KEYCHAIN_SERVICE, KEYCHAIN_ACCOUNT) {
|
||||
|
|
@ -99,6 +122,12 @@ fn save_key_bytes(value: &[u8]) -> Result<(), String> {
|
|||
.map_err(|error| format!("ENTERPRISE_DEVICE_KEYCHAIN_WRITE_FAILED: {error}"))
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
fn save_session_token(account: &str, value: &[u8]) -> Result<(), String> {
|
||||
security_framework::passwords::set_generic_password(SESSION_KEYCHAIN_SERVICE, account, value)
|
||||
.map_err(|error| format!("ENTERPRISE_SESSION_KEYCHAIN_WRITE_FAILED: {error}"))
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
fn load_key_bytes() -> Result<Option<Vec<u8>>, String> {
|
||||
Err("ENTERPRISE_DEVICE_KEYCHAIN_UNAVAILABLE_ON_PLATFORM".into())
|
||||
|
|
@ -109,6 +138,41 @@ fn save_key_bytes(_value: &[u8]) -> Result<(), String> {
|
|||
Err("ENTERPRISE_DEVICE_KEYCHAIN_UNAVAILABLE_ON_PLATFORM".into())
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
fn save_session_token(_account: &str, _value: &[u8]) -> Result<(), String> {
|
||||
Err("ENTERPRISE_SESSION_KEYCHAIN_UNAVAILABLE_ON_PLATFORM".into())
|
||||
}
|
||||
|
||||
fn post_json(
|
||||
path: &str,
|
||||
payload: serde_json::Value,
|
||||
basic: Option<(&str, &str)>,
|
||||
) -> Result<serde_json::Value, String> {
|
||||
let client = reqwest::blocking::Client::builder()
|
||||
.timeout(std::time::Duration::from_secs(15))
|
||||
.build()
|
||||
.map_err(|_| "ENTERPRISE_GATE_CLIENT_INIT_FAILED".to_string())?;
|
||||
let mut request = client.post(format!("{GATE_BASE}{path}")).json(&payload);
|
||||
if let Some((username, password)) = basic {
|
||||
request = request.basic_auth(username, Some(password));
|
||||
}
|
||||
let response = request
|
||||
.send()
|
||||
.map_err(|_| "ENTERPRISE_GATE_UNREACHABLE".to_string())?;
|
||||
let status = response.status();
|
||||
let value: serde_json::Value = response
|
||||
.json()
|
||||
.map_err(|_| "ENTERPRISE_GATE_RESPONSE_INVALID".to_string())?;
|
||||
if !status.is_success() {
|
||||
return Err(value
|
||||
.get("error")
|
||||
.and_then(serde_json::Value::as_str)
|
||||
.unwrap_or("ENTERPRISE_GATE_REJECTED")
|
||||
.to_string());
|
||||
}
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
fn load_or_create_key() -> Result<Ed25519KeyPair, String> {
|
||||
let bytes = if let Some(value) = load_key_bytes()? {
|
||||
value
|
||||
|
|
@ -225,7 +289,19 @@ pub fn snapshot(app: &AppHandle) -> Result<EnterpriseEntranceSnapshot, String> {
|
|||
} else {
|
||||
None
|
||||
};
|
||||
let state = if binding.is_some() {
|
||||
let stored_session = session_path(app)?;
|
||||
let session: Option<EnterpriseSession> = if stored_session.exists() {
|
||||
let value: EnterpriseSession = storage::read_json(&stored_session)?;
|
||||
(value.expires_unix_ms > storage::now_unix_ms()).then_some(value)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let enrolled = enrollment_path(app)?.exists();
|
||||
let state = if session.is_some() {
|
||||
"ENTERPRISE_SESSION_VERIFIED"
|
||||
} else if enrolled {
|
||||
"DEVICE_ENROLLED_SERVER_CHALLENGE_REQUIRED"
|
||||
} else if binding.is_some() {
|
||||
"LOCAL_PROOF_READY_SERVER_CHALLENGE_REQUIRED"
|
||||
} else if device.is_some() {
|
||||
"LOCAL_DEVICE_KEY_READY_BINDING_REQUIRED"
|
||||
|
|
@ -237,12 +313,142 @@ pub fn snapshot(app: &AppHandle) -> Result<EnterpriseEntranceSnapshot, String> {
|
|||
state: state.into(),
|
||||
device,
|
||||
binding,
|
||||
session,
|
||||
enterprise_server_embedded: false,
|
||||
server_authorized: false,
|
||||
next_action: "企业服务器并发任务结束后获取一次性挑战;本机证明不等于登录成功。".into(),
|
||||
server_authorized: state == "ENTERPRISE_SESSION_VERIFIED",
|
||||
next_action: match state {
|
||||
"ENTERPRISE_SESSION_VERIFIED" => "企业责任会话已验证;只可进入返回的单一责任仓库。",
|
||||
"DEVICE_ENROLLED_SERVER_CHALLENGE_REQUIRED" => {
|
||||
"设备已登记;可以领取一次性挑战并验证当前会话。"
|
||||
}
|
||||
_ => "先完成设备和责任绑定;本机证明不等于登录成功。",
|
||||
}
|
||||
.into(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn enroll_device(
|
||||
app: &AppHandle,
|
||||
username: &str,
|
||||
password: &str,
|
||||
) -> Result<serde_json::Value, String> {
|
||||
let username = username.trim();
|
||||
if !Regex::new(r"^[A-Za-z0-9_-]{1,40}$")
|
||||
.map_err(|e| e.to_string())?
|
||||
.is_match(username)
|
||||
|| password.is_empty()
|
||||
|| password.len() > 512
|
||||
{
|
||||
return Err("ENTERPRISE_ACCOUNT_CREDENTIALS_INVALID".into());
|
||||
}
|
||||
let device: EnterpriseDeviceProof = storage::read_json(&device_path(app)?)
|
||||
.map_err(|_| "ENTERPRISE_DEVICE_PROOF_REQUIRED".to_string())?;
|
||||
let binding: EnterpriseBindingRequest = storage::read_json(&binding_path(app)?)
|
||||
.map_err(|_| "ENTERPRISE_BINDING_REQUIRED".to_string())?;
|
||||
let response = post_json(
|
||||
"/enroll",
|
||||
serde_json::json!({
|
||||
"request_id": binding.request_id,
|
||||
"human_number": binding.responsible_human_id,
|
||||
"persona_id": binding.persona_id,
|
||||
"domain_id": binding.domain_id,
|
||||
"node_id": device.node_id,
|
||||
"key_id": device.key_id,
|
||||
"public_key": device.public_key,
|
||||
"fingerprint_sha256": device.auxiliary_machine_fingerprint_sha256,
|
||||
"idempotency_key": format!("{}-{}", binding.request_id, device.key_id),
|
||||
}),
|
||||
Some((username, password)),
|
||||
)?;
|
||||
storage::write_json(&enrollment_path(app)?, &response)?;
|
||||
Ok(serde_json::json!({
|
||||
"ok": true,
|
||||
"state": "DEVICE_ENROLLED",
|
||||
"keyId": device.key_id,
|
||||
"serverAuthorized": false
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn authenticate(app: &AppHandle) -> Result<EnterpriseSession, String> {
|
||||
if !enrollment_path(app)?.exists() {
|
||||
return Err("ENTERPRISE_DEVICE_ENROLLMENT_REQUIRED".into());
|
||||
}
|
||||
let device: EnterpriseDeviceProof = storage::read_json(&device_path(app)?)?;
|
||||
let binding: EnterpriseBindingRequest = storage::read_json(&binding_path(app)?)?;
|
||||
let response = post_json(
|
||||
"/challenges",
|
||||
serde_json::json!({"key_id":device.key_id,"request_id":binding.request_id}),
|
||||
None,
|
||||
)?;
|
||||
let challenge = response
|
||||
.get("challenge")
|
||||
.ok_or_else(|| "ENTERPRISE_CHALLENGE_MISSING".to_string())?;
|
||||
let challenge_id = challenge
|
||||
.get("challenge_id")
|
||||
.and_then(serde_json::Value::as_str)
|
||||
.ok_or_else(|| "ENTERPRISE_CHALLENGE_ID_MISSING".to_string())?;
|
||||
let nonce = challenge
|
||||
.get("nonce")
|
||||
.and_then(serde_json::Value::as_str)
|
||||
.ok_or_else(|| "ENTERPRISE_CHALLENGE_NONCE_MISSING".to_string())?;
|
||||
let expires = challenge
|
||||
.get("expires_unix_ms")
|
||||
.and_then(serde_json::Value::as_u64)
|
||||
.ok_or_else(|| "ENTERPRISE_CHALLENGE_EXPIRY_MISSING".to_string())?
|
||||
as u128;
|
||||
let proof = sign_challenge(app, &binding.request_id, challenge_id, nonce, expires)?;
|
||||
let verified = post_json(
|
||||
"/challenges/verify",
|
||||
serde_json::json!({"challenge_id":proof.challenge_id,"signature":proof.signature}),
|
||||
None,
|
||||
)?;
|
||||
let value = verified
|
||||
.get("session")
|
||||
.ok_or_else(|| "ENTERPRISE_SESSION_MISSING".to_string())?;
|
||||
let token = value
|
||||
.get("token")
|
||||
.and_then(serde_json::Value::as_str)
|
||||
.ok_or_else(|| "ENTERPRISE_SESSION_TOKEN_MISSING".to_string())?;
|
||||
let scope = value
|
||||
.get("scope")
|
||||
.and_then(serde_json::Value::as_str)
|
||||
.unwrap_or("");
|
||||
let session = EnterpriseSession {
|
||||
schema: "hololake.enterprise-session/v1".into(),
|
||||
state: "ENTERPRISE_SESSION_VERIFIED".into(),
|
||||
domain_id: value
|
||||
.get("domain_id")
|
||||
.and_then(serde_json::Value::as_str)
|
||||
.unwrap_or("")
|
||||
.into(),
|
||||
repository: value
|
||||
.get("repository")
|
||||
.and_then(serde_json::Value::as_str)
|
||||
.unwrap_or("")
|
||||
.into(),
|
||||
repository_url: value
|
||||
.get("repository_url")
|
||||
.and_then(serde_json::Value::as_str)
|
||||
.unwrap_or("")
|
||||
.into(),
|
||||
expires_unix_ms: value
|
||||
.get("expires_unix_ms")
|
||||
.and_then(serde_json::Value::as_u64)
|
||||
.unwrap_or(0) as u128,
|
||||
token_storage: "MACOS_KEYCHAIN".into(),
|
||||
};
|
||||
if scope != "ONE_DOMAIN_ONE_REPOSITORY"
|
||||
|| session.domain_id != binding.domain_id
|
||||
|| session.repository.is_empty()
|
||||
|| session.expires_unix_ms <= storage::now_unix_ms()
|
||||
{
|
||||
return Err("ENTERPRISE_SESSION_SCOPE_INVALID".into());
|
||||
}
|
||||
save_session_token(&device.key_id, token.as_bytes())?;
|
||||
storage::write_json(&session_path(app)?, &session)?;
|
||||
Ok(session)
|
||||
}
|
||||
|
||||
pub fn sign_challenge(
|
||||
app: &AppHandle,
|
||||
request_id: &str,
|
||||
|
|
|
|||
|
|
@ -501,6 +501,22 @@ fn sign_enterprise_challenge(
|
|||
enterprise_entrance::sign_challenge(&app, &request_id, &challenge_id, &nonce, expires_unix_ms)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn enroll_enterprise_device(
|
||||
app: AppHandle,
|
||||
username: String,
|
||||
password: String,
|
||||
) -> Result<serde_json::Value, String> {
|
||||
enterprise_entrance::enroll_device(&app, &username, &password)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn authenticate_enterprise_entrance(
|
||||
app: AppHandle,
|
||||
) -> Result<enterprise_entrance::EnterpriseSession, String> {
|
||||
enterprise_entrance::authenticate(&app)
|
||||
}
|
||||
|
||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||
pub fn run() {
|
||||
tauri::Builder::default()
|
||||
|
|
@ -577,11 +593,13 @@ pub fn run() {
|
|||
delete_trial_persona,
|
||||
compile_tcs_agent_proposal,
|
||||
approve_agent_proposal,
|
||||
reject_agent_proposal
|
||||
,enterprise_entrance_snapshot
|
||||
,prepare_enterprise_device
|
||||
,prepare_enterprise_binding
|
||||
,sign_enterprise_challenge
|
||||
reject_agent_proposal,
|
||||
enterprise_entrance_snapshot,
|
||||
prepare_enterprise_device,
|
||||
prepare_enterprise_binding,
|
||||
sign_enterprise_challenge,
|
||||
enroll_enterprise_device,
|
||||
authenticate_enterprise_entrance
|
||||
])
|
||||
.run(tauri::generate_context!())
|
||||
.expect("HoloLake runtime failed")
|
||||
|
|
|
|||
Loading…
Reference in a new issue