#include "PawletProfileBinderService.h" #include "payloads/PayloadHandler.h" #include #include #include namespace pawletos::profile { using ::ndk::ScopedAStatus; // Service-specific error codes returned via ScopedAStatus — mirrors // PawletProfileService's D-Bus error domain (kErrNotFound etc.) one-for-one // so a future shared client-side error mapping stays simple. namespace { constexpr int kErrNotFound = 1; constexpr int kErrSignature = 2; constexpr int kErrParse = 3; constexpr int kErrLocked = 4; constexpr int kErrWrongPassword = 5; constexpr int kErrSigningRequired = 6; } // namespace ScopedAStatus PawletProfileBinderService::serviceError(const char* name, const std::string& message) { syslog(LOG_WARNING, "pawletprofiled: %s: %s", name, message.c_str()); int code = 0; if (strcmp(name, "NotFound") == 0) code = kErrNotFound; else if (strcmp(name, "Signature") == 0) code = kErrSignature; else if (strcmp(name, "Parse") == 0) code = kErrParse; else if (strcmp(name, "Locked") == 0) code = kErrLocked; else if (strcmp(name, "WrongPassword") == 0) code = kErrWrongPassword; else if (strcmp(name, "SigningRequired") == 0) code = kErrSigningRequired; return ScopedAStatus::fromServiceSpecificErrorWithMessage(code, message.c_str()); } PawletProfileBinderService::PawletProfileBinderService() { syslog(LOG_INFO, "pawletprofiled: Binder service starting"); mStore.load(); } // ── installProfileDirect (preinstalled profiles, same as D-Bus version) ──── bool PawletProfileBinderService::installProfileDirect( const std::vector& profileData, std::string& outUuid) { std::lock_guard lock(mLock); std::vector yamlBytes; bool isSigned = false; SignatureVerifier::TrustLevel trust = SignatureVerifier::TrustLevel::UNSIGNED; if (mVerifier.isCmsWrapped(profileData)) { auto result = mVerifier.verify(profileData, yamlBytes); if (result == SignatureVerifier::VerifyResult::INVALID) { syslog(LOG_ERR, "[installProfileDirect] signature invalid"); return false; } isSigned = true; trust = (result == SignatureVerifier::VerifyResult::TRUSTED) ? SignatureVerifier::TrustLevel::TRUSTED : SignatureVerifier::TrustLevel::UNVERIFIED; } else { yamlBytes = profileData; } ParsedProfile profile; if (!mParser.parse(yamlBytes, profile)) { syslog(LOG_ERR, "[installProfileDirect] parse failed"); return false; } ParsedProfile existing; if (mStore.load(profile.uuid, existing)) { syslog(LOG_INFO, "[installProfileDirect] already installed uuid=%s, skipping", profile.uuid.c_str()); outUuid = profile.uuid; return true; } for (const auto& payload : profile.payloads) { bool sigRequired = payload.type == "mdm" || payload.type == "removal-password" || payload.type == "kiosk" || payload.type == "asam"; if (sigRequired && !isSigned) { syslog(LOG_ERR, "[installProfileDirect] type=%s requires signed profile", payload.type.c_str()); return false; } } profile.trustLevel = trust; if (!mStore.save(profile)) { syslog(LOG_ERR, "[installProfileDirect] save failed uuid=%s", profile.uuid.c_str()); return false; } applyProfile(profile); outUuid = profile.uuid; syslog(LOG_INFO, "[installProfileDirect] installed uuid=%s", profile.uuid.c_str()); return true; } // ── installProfile ────────────────────────────────────────────────────── ScopedAStatus PawletProfileBinderService::installProfile( const std::vector& in_profileData, std::string* _aidl_return) { std::lock_guard lock(mLock); std::vector yamlBytes; bool isSigned = false; SignatureVerifier::TrustLevel trust = SignatureVerifier::TrustLevel::UNSIGNED; if (mVerifier.isCmsWrapped(in_profileData)) { auto result = mVerifier.verify(in_profileData, yamlBytes); if (result == SignatureVerifier::VerifyResult::INVALID) { return serviceError("Signature", "Profile signature is invalid or tampered."); } isSigned = true; trust = (result == SignatureVerifier::VerifyResult::TRUSTED) ? SignatureVerifier::TrustLevel::TRUSTED : SignatureVerifier::TrustLevel::UNVERIFIED; } else { yamlBytes = in_profileData; } ParsedProfile profile; if (!mParser.parse(yamlBytes, profile)) { return serviceError("Parse", "Profile YAML is malformed or schema-invalid."); } for (const auto& payload : profile.payloads) { bool sigRequired = payload.type == "mdm" || payload.type == "removal-password" || payload.type == "kiosk" || payload.type == "asam"; if (sigRequired && !isSigned) { return serviceError("SigningRequired", "Payload type '" + payload.type + "' requires a signed profile."); } } profile.trustLevel = trust; if (!mStore.save(profile)) { return serviceError("Parse", "Failed to persist profile."); } applyProfile(profile); syslog(LOG_INFO, "pawletprofiled: installed profile uuid=%s", profile.uuid.c_str()); *_aidl_return = profile.uuid; return ScopedAStatus::ok(); } // ── removeProfile ──────────────────────────────────────────────────────── ScopedAStatus PawletProfileBinderService::removeProfile( const std::string& in_uuid, const std::string& in_removalPassword) { std::lock_guard lock(mLock); ParsedProfile profile; if (!mStore.load(in_uuid, profile)) { return serviceError("NotFound", "Profile not found: " + in_uuid); } if (profile.lifecycle.removal == "locked") { return serviceError("Locked", "This profile can only be removed by the MDM server."); } if (profile.lifecycle.removal == "password") { if (in_removalPassword.empty() || !mStore.checkRemovalPassword(in_uuid, in_removalPassword)) { return serviceError("WrongPassword", "Incorrect removal password."); } } revertProfile(profile); mStore.remove(in_uuid); syslog(LOG_INFO, "pawletprofiled: removed profile uuid=%s", in_uuid.c_str()); return ScopedAStatus::ok(); } // ── listProfiles ───────────────────────────────────────────────────────── ScopedAStatus PawletProfileBinderService::listProfiles(std::vector* _aidl_return) { std::lock_guard lock(mLock); *_aidl_return = mStore.listUuids(); return ScopedAStatus::ok(); } // ── getProfileInfo ─────────────────────────────────────────────────────── ScopedAStatus PawletProfileBinderService::getProfileInfo( const std::string& in_uuid, std::string* _aidl_return) { std::lock_guard lock(mLock); ParsedProfile profile; if (!mStore.load(in_uuid, profile)) { return serviceError("NotFound", "Not found: " + in_uuid); } std::ostringstream j; j << "{" << "\"uuid\":\"" << profile.uuid << "\"," << "\"id\":\"" << profile.id << "\"," << "\"name\":\"" << profile.meta.name << "\"," << "\"organization\":\"" << profile.meta.organization << "\"," << "\"scope\":\"" << profile.scope << "\"," << "\"removal\":\"" << profile.lifecycle.removal << "\"," << "\"trusted\":" << (profile.trustLevel != SignatureVerifier::TrustLevel::UNSIGNED ? "true" : "false") << "," << "\"payloadCount\":" << profile.payloads.size() << "}"; *_aidl_return = j.str(); return ScopedAStatus::ok(); } // ── isDeviceManaged ────────────────────────────────────────────────────── ScopedAStatus PawletProfileBinderService::isDeviceManaged(bool* _aidl_return) { std::lock_guard lock(mLock); bool managed = false; for (const auto& uuid : mStore.listUuids()) { ParsedProfile p; if (mStore.load(uuid, p)) for (const auto& pl : p.payloads) if (pl.type == "mdm") { managed = true; break; } if (managed) break; } *_aidl_return = managed; return ScopedAStatus::ok(); } // ── getMdmServerUrl ────────────────────────────────────────────────────── ScopedAStatus PawletProfileBinderService::getMdmServerUrl(std::string* _aidl_return) { std::lock_guard lock(mLock); std::string url; for (const auto& uuid : mStore.listUuids()) { ParsedProfile p; if (!mStore.load(uuid, p)) continue; for (const auto& pl : p.payloads) { if (pl.type == "mdm") { auto it = pl.fields.find("server-url"); if (it != pl.fields.end()) url = it->second; if (url.size() >= 2 && url.front() == '"') url = url.substr(1, url.size() - 2); break; } } if (!url.empty()) break; } *_aidl_return = url; return ScopedAStatus::ok(); } // ── isSupervised ───────────────────────────────────────────────────────── ScopedAStatus PawletProfileBinderService::isSupervised(bool* _aidl_return) { std::lock_guard lock(mLock); bool supervised = false; for (const auto& uuid : mStore.listUuids()) { ParsedProfile p; if (!mStore.load(uuid, p)) continue; for (const auto& pl : p.payloads) if (pl.type == "kiosk" || pl.type == "asam") { supervised = true; break; } if (supervised) break; } *_aidl_return = supervised; return ScopedAStatus::ok(); } // ── getPayloadsOfType ──────────────────────────────────────────────────── ScopedAStatus PawletProfileBinderService::getPayloadsOfType( const std::string& in_payloadType, std::string* _aidl_return) { std::lock_guard lock(mLock); std::ostringstream j; j << "["; bool first = true; for (const auto& uuid : mStore.listUuids()) { ParsedProfile p; if (!mStore.load(uuid, p)) continue; for (const auto& pl : p.payloads) { if (pl.type != in_payloadType) continue; if (!first) j << ","; first = false; j << "{\"profileUuid\":\"" << p.uuid << "\"," << "\"payloadUuid\":\"" << pl.uuid << "\""; for (const auto& [k, v] : pl.fields) j << ",\"" << k << "\":" << v; j << "}"; } } j << "]"; *_aidl_return = j.str(); return ScopedAStatus::ok(); } // ── applyProfile / revertProfile ───────────────────────────────────────── void PawletProfileBinderService::applyProfile(const ParsedProfile& profile) { for (const auto& payload : profile.payloads) { auto* handler = PayloadHandlerRegistry::get(payload.type); if (handler) { if (!handler->apply(payload)) syslog(LOG_WARNING, "pawletprofiled: handler failed type=%s uuid=%s", payload.type.c_str(), payload.uuid.c_str()); } else { syslog(LOG_WARNING, "pawletprofiled: no handler for type=%s", payload.type.c_str()); } } } void PawletProfileBinderService::revertProfile(const ParsedProfile& profile) { for (auto it = profile.payloads.rbegin(); it != profile.payloads.rend(); ++it) { auto* handler = PayloadHandlerRegistry::get(it->type); if (handler) handler->revert(*it); } } } // namespace pawletos::profile