This checkout is PawletOS's fork (git.oxmc.me/PawletOS/profiled), so its own identity should read PawletOS, not VesperOS: binary/package name, AIDL package+interface (me.oxmc.vesperos.profile -> os.pawlet.profiled), D-Bus service/object/error names, C++ namespace (vesperos::profile -> pawletos::profile), sepolicy types, data paths (/data/system/vesperos -> /data/system/pawletos, /etc/vesperprofiled -> /etc/pawletprofiled), the vendored OpenSSL static-lib module names, and the ZTE protocol string. Also drops generated build output (obj-x86_64-linux-gnu/, debian/.debhelper, debian staging dir, debhelper log/substvars files) that had been committed by mistake, and adds a .gitignore so they don't come back. The stock/upstream vesperprofiled at git.oxmc.me/VesperOS/vesperprofiled is untouched -- this commit only goes to the pawletos remote.
323 lines
12 KiB
C++
323 lines
12 KiB
C++
#include "PawletProfileBinderService.h"
|
|
#include "payloads/PayloadHandler.h"
|
|
|
|
#include <cstring>
|
|
#include <sstream>
|
|
#include <syslog.h>
|
|
|
|
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<uint8_t>& profileData, std::string& outUuid) {
|
|
std::lock_guard<std::mutex> lock(mLock);
|
|
|
|
std::vector<uint8_t> 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<uint8_t>& in_profileData, std::string* _aidl_return) {
|
|
std::lock_guard<std::mutex> lock(mLock);
|
|
|
|
std::vector<uint8_t> 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<std::mutex> 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<std::string>* _aidl_return) {
|
|
std::lock_guard<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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
|