// Package cloudconfig baut die CloudConfigurationDetails.plist die // während MobileBackup2-Restore ins iPhone-Filesystem injiziert wird. // // Schema verifiziert empirisch aus TechLockdown's extracted Manifest.db // + live cloud-config dumps. Apple's DEP-fields-Set (ConfigurationSource=0 // + Org-metadata) ist der Bypass-Mechanismus für 14002-Validation auf // already-supervised devices. package cloudconfig import ( "bytes" "fmt" "os" "strings" "howett.net/plist" ) // SkipSetupAll = TL's 31-key SkipSetup-Liste + "Intelligence" (iOS 18+ Apple-AI-Setup-Step // den TL's altes binary noch nicht skipped). Empirisch verifiziert 2026-05-28: ohne // Intelligence-Key zeigt iOS 26.x den Apple-Intelligence-Screen vor Kamera/SOS. // Siehe memory: project_supervise_tl_parity_achieved. var SkipSetupAll = []string{ "Android", "Appearance", "AppleID", "AppStore", "Biometric", "Diagnostics", "DisplayTone", "FileVault", "HomeButtonSensitivity", "iCloudDiagnostics", "iCloudStorage", "iMessageAndFaceTime", "Intelligence", // iOS 18+ Apple-Intelligence-Setup "Location", "OnBoarding", "Passcode", "Payment", "Privacy", "Registration", "Restore", "RestoreCompleted", "UpdateCompleted", "ScreenTime", "ScreenSaver", "SIMSetup", "Siri", "SoftwareUpdate", "TapToSetup", "TOS", "WatchMigration", "Zoom", "Welcome", } // BuildOptions sind die runtime-vars für CloudConfigurationDetails. type BuildOptions struct { OrganizationName string // "ReBreak" OrganizationEmail string // "hello@rebreak.org" SupervisorCert []byte // DER-encoded cert from our identity SkipSetup []string // default SkipSetupAll if nil } // Build encoded eine vollständige CloudConfigurationDetails.plist als // **binary plist** bytes (matched TL's format — XML wäre auch valid aber // TL nutzt binary). // // Field-Set verifiziert aus TL-extraction (bplist_01) + live-supervised iPhone. func Build(opts BuildOptions) ([]byte, error) { if opts.OrganizationName == "" { opts.OrganizationName = "ReBreak" } if opts.OrganizationEmail == "" { opts.OrganizationEmail = "hello@rebreak.org" } if opts.SkipSetup == nil { opts.SkipSetup = SkipSetupAll } if profile := os.Getenv("REBREAK_CC_SKIPSETUP_PROFILE"); profile != "" { switch profile { case "camera_sos": // Kandidaten für die verbleibenden letzten Setup-Panels (Kamera/SOS/Intelligence). opts.SkipSetup = mergeSkipSetup(opts.SkipSetup, "CameraButton", "CameraControl", "EmergencySOS", "SOS", "Safety", "Intelligence", "AppleIntelligence", "AI") } } if extra := os.Getenv("REBREAK_CC_SKIPSETUP_EXTRA"); extra != "" { parts := strings.Split(extra, ",") clean := make([]string, 0, len(parts)) for _, p := range parts { k := strings.TrimSpace(p) if k != "" { clean = append(clean, k) } } if len(clean) > 0 { opts.SkipSetup = mergeSkipSetup(opts.SkipSetup, clean...) } } configurationSource := int64(0) configurationWasApplied := true cloudConfigurationUIComplete := true postSetupProfileWasInstalled := true if v, ok := envBool("REBREAK_CC_CONFIGURATION_WAS_APPLIED"); ok { configurationWasApplied = v } if v, ok := envBool("REBREAK_CC_UI_COMPLETE"); ok { cloudConfigurationUIComplete = v } if v, ok := envBool("REBREAK_CC_POST_SETUP_PROFILE"); ok { postSetupProfileWasInstalled = v } if v, ok := envInt64("REBREAK_CC_CONFIGURATION_SOURCE"); ok { configurationSource = v } // AutoAdvanceSetup: 2026-05-28 09:25 empirisch verifiziert: // true → iPhone triggert iOS-Install-Screen (mini backup) + skipt // Setup-Assistant → landet bei Apple Intelligence/Kamera. // ABER: viele User-Settings (Sprache etc) gehen verloren. // false → KEIN install-screen, iPhone bootet normal → Setup-Assistant // zeigt Sprache/Region/Anrede + "teilweise eingerichtet"-Dialog. // // TL's extrahierte plist hat false ABER zeigt install-screen — d.h. TL // triggert das Verhalten anders (vermutlich via Restore-Options). Wir // behalten true bis wir den TL-Mechanismus für Settings-Preservation // gefunden haben (RestoreSystemFiles=false ist eine Kandidaten-Hypothese). autoAdvanceSetup := true if v, ok := envBool("REBREAK_CC_AUTO_ADVANCE"); ok { autoAdvanceSetup = v } // 2026-05-28 EMPIRISCH-VERIFIZIERT: TL's CloudConfigurationDetails enthält // KEIN `SupervisorHostCertificates`-Field (weder in Embed-Template noch in // Runtime-Output via MCInstall.GetCloudConfiguration). Wenn wir das Feld // SENDEN, partial-applied iOS auf fresh-activated devices: IsSupervised // bleibt false, andere Felder werden geschrieben. Ohne das Feld: full apply. // // Cert wird trotzdem in cert.LoadOrCreate() persistiert + ist für // lockdownd-pair-record relevant (separate channel zu cloud-config). // Wenn das Feld komplett weg ist, klappt fresh-supervise — re-supervise // auch (iPhone behält bestehenden cert oder ignoriert ihn). _ = opts.SupervisorCert // marked-unused — wir nutzen ihn nicht mehr in der plist // Wir bauen das dict in der Reihenfolge die TL nutzt (helps with iOS // validation falls Apple ordering-sensitive ist). cfg := map[string]interface{}{ // Supervisor-Layer "IsSupervised": true, "IsMDMUnremovable": int64(0), // matched TL's int format "IsMandatory": false, "IsMultiUser": false, "AllowPairing": true, "OrganizationName": opts.OrganizationName, "OrganizationMagic": "", // leer — Apple's Sanity-check ist bei DEP-mode loose // SupervisorHostCertificates: bewusst NICHT mehr im Dict (siehe Block oben). "SkipSetup": toInterfaceSlice(opts.SkipSetup), // DEP-mode (the magic that bypasses 14002) "ConfigurationSource": configurationSource, "ConfigurationURL": "", "ConfigurationWasApplied": configurationWasApplied, "CloudConfigurationUIComplete": cloudConfigurationUIComplete, "PostSetupProfileWasInstalled": postSetupProfileWasInstalled, "AutoAdvanceSetup": autoAdvanceSetup, "AwaitDeviceConfigured": false, // DEP-Org-Metadata (TL pattern — most "N/A" except email) "OrganizationAddress": "N/A", "OrganizationAddressLine1": "N/A", "OrganizationAddressLine2": "N/A", "OrganizationCity": "N/A", "OrganizationCountry": "N/A", "OrganizationDepartment": "N/A", "OrganizationEmail": opts.OrganizationEmail, "OrganizationPhone": "N/A", "OrganizationSupportPhone": "N/A", "OrganizationZipCode": "N/A", } var buf bytes.Buffer enc := plist.NewEncoderForFormat(&buf, plist.BinaryFormat) if err := enc.Encode(cfg); err != nil { return nil, fmt.Errorf("cloudconfig: encode: %w", err) } return buf.Bytes(), nil } func envBool(name string) (bool, bool) { v, ok := os.LookupEnv(name) if !ok { return false, false } switch v { case "1", "true", "TRUE", "True": return true, true case "0", "false", "FALSE", "False": return false, true default: return false, false } } func envInt64(name string) (int64, bool) { v, ok := os.LookupEnv(name) if !ok { return 0, false } var parsed int64 if _, err := fmt.Sscanf(v, "%d", &parsed); err != nil { return 0, false } return parsed, true } func toInterfaceSlice(ss []string) []interface{} { out := make([]interface{}, len(ss)) for i, s := range ss { out[i] = s } return out } func mergeSkipSetup(base []string, extra ...string) []string { seen := make(map[string]struct{}, len(base)+len(extra)) out := make([]string, 0, len(base)+len(extra)) for _, k := range base { if _, ok := seen[k]; ok { continue } seen[k] = struct{}{} out = append(out, k) } for _, k := range extra { if _, ok := seen[k]; ok { continue } seen[k] = struct{}{} out = append(out, k) } return out }