90 lines
5.1 KiB
Python
90 lines
5.1 KiB
Python
"""Execute production credential persistence with storage/JSON boundary doubles."""
|
|
from policy_harness import ROOT, body, run
|
|
|
|
p = ROOT / 'addons/core/Scripts/Game/ERR/00_Core/ERR_TelemetryCredentials.c'
|
|
methods = '\n'.join(body(p, s) for s in ['static bool Valid(', 'static bool Resolve('])
|
|
for a, b in [
|
|
('out string token', 'string& token'),
|
|
('ref array<ref ERR_TelemetryCredential> entries = {};', 'Entries entries;'),
|
|
('ERR_TelemetryCredential entry', 'Credential* entry'),
|
|
('ERR_TelemetryCredential known', 'Credential* known'),
|
|
('ERR_TelemetryCredential created = new ERR_TelemetryCredential()', 'Credential* created = new Credential()'),
|
|
('JsonLoadContext loaded = new JsonLoadContext()', 'JsonLoadContext loaded'),
|
|
('JsonSaveContext saved = new JsonSaveContext()', 'JsonSaveContext saved'),
|
|
('foreach (Credential* known :', 'for (Credential* known :'),
|
|
('entry.', 'entry->'), ('known.', 'known->'), ('created.', 'created->'),
|
|
('FileIO.', 'FileIO::'), ('UUID.', 'UUID::'), ('ERR_ProfileConfig.', 'Profile::'),
|
|
]:
|
|
methods = methods.replace(a, b)
|
|
run(r'''
|
|
#include <cassert>
|
|
#include <string>
|
|
#include <vector>
|
|
#include <map>
|
|
struct string : std::string {
|
|
using std::string::string; using std::string::operator=;
|
|
string(std::string s):std::string(s){} bool IsEmpty() const{return empty();} int Length()const{return size();}
|
|
};
|
|
struct Credential {string endpoint,serverId,token;};
|
|
struct Entries:std::vector<Credential*> {
|
|
int Count(){return size();} void Insert(Credential* v){push_back(v);} bool operator!(){return false;}
|
|
};
|
|
std::map<std::string,string> files;
|
|
std::map<std::string,Entries> documents;
|
|
bool failSave=false,failCopy=false,failRead=false;int generated=0,serial=0;
|
|
namespace UUID {bool IsUUID(string s){return s=="server"||s=="other";}
|
|
string GenV4(){generated++;return string(36, char('a'+generated%20));}}
|
|
namespace FileIO {
|
|
bool FileExists(string p){return files.count(p);}
|
|
bool CopyFile(string a,string b){if(failCopy)return false;files[b]=files[a];return true;}
|
|
void DeleteFile(string p){files.erase(p);}
|
|
}
|
|
namespace Profile {bool Text(string p,string& text){if(failRead||!files.count(p))return false;text=files[p];return true;}}
|
|
struct JsonLoadContext {
|
|
string raw;
|
|
bool LoadFromFile(string p){raw=files[p];return documents.count(raw);}
|
|
bool ReadValue(string,Entries& e){e=documents[raw];return true;}
|
|
};
|
|
struct JsonSaveContext {
|
|
string raw;
|
|
void EnableTypeDiscriminator(bool){}
|
|
void WriteValue(string,Entries e){raw="document-"+std::to_string(++serial);documents[raw]=e;}
|
|
bool SaveToFile(string p){if(failSave)return false;files[p]=raw;return true;}
|
|
string SaveToString(){return raw;}
|
|
};
|
|
struct Credentials {
|
|
static inline string PATH="credentials.json";
|
|
'''+methods+r'''
|
|
};
|
|
int main(){
|
|
string token;
|
|
assert(Credentials::Resolve("https://one", "server", token));
|
|
assert(token.Length()==72&&generated==2&&files.count(Credentials::PATH));
|
|
string first=token;assert(!files.count(Credentials::PATH+".tmp"));
|
|
token="";assert(Credentials::Resolve("https://one", "server", token));
|
|
assert(token==first&&generated==2); // mission restart reuses the durable secret
|
|
assert(Credentials::Resolve("https://two", "server", token)&&token!=first);
|
|
assert(Credentials::Resolve("https://one", "server", token)&&token==first); // endpoint isolation
|
|
assert(Credentials::Resolve("https://one", "other", token)&&token!=first); // identity isolation
|
|
failSave=true;assert(!Credentials::Resolve("https://save-fail", "server", token)&&token.IsEmpty());failSave=false;
|
|
failCopy=true;assert(!Credentials::Resolve("https://copy-fail", "server", token)&&token.IsEmpty());failCopy=false;
|
|
failRead=true;assert(!Credentials::Resolve("https://verify-fail", "server", token)&&token.IsEmpty());failRead=false;
|
|
assert(Credentials::Resolve("https://one", "server", token)&&token==first);
|
|
auto before=generated;files[Credentials::PATH]="corrupt";
|
|
assert(!Credentials::Resolve("https://one", "server", token)&&token.IsEmpty()&&generated==before);
|
|
assert(files[Credentials::PATH]=="corrupt"); // preserve damaged evidence; do not silently rotate
|
|
files.clear();documents.clear();
|
|
for(int i=0;i<16;i++)assert(Credentials::Resolve("endpoint-"+std::to_string(i),"server",token));
|
|
assert(!Credentials::Resolve("endpoint-overflow","server",token)&&token.IsEmpty());
|
|
}
|
|
''')
|
|
sender = p.with_name('ERR_Telemetry.c')
|
|
pulse = sender.read_text().split('protected void Pulse(', 1)[1].split('protected void Delivered(', 1)[0]
|
|
assert pulse.index('if (!Enabled()) return;') < pulse.index('ERR_TelemetryCredentials.Resolve')
|
|
assert pulse.index('if (m_Dirty && !Save())') < pulse.index('ERR_TelemetryCredentials.Resolve') < pulse.index('m_Context.POST(')
|
|
assert 'if (m_Config.Token.IsEmpty())' in pulse
|
|
assert 'Token' not in body(sender, 'protected void Event(')
|
|
assert 'Credentials' not in body(sender, 'protected void Purge(') # opt-out keeps identity, deletes queued personal data
|
|
assert 'https://co10escape-telemetry.zacekjakub.workers.dev/api/mission-events' in (ROOT/'addons/core/Configs/ERR/Telemetry.conf').read_text()
|
|
print('PASS: durable installation credential, restart/endpoint/identity isolation, write failure, corruption, bounded retention and disabled-send gates')
|