// 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" "howett.net/plist" ) // SkipSetupAll matched TL's 31-key SkipSetup-Array. Diese Keys skippen // Setup-Assistant-Steps die sonst nach Restore + Reboot triggern würden. var SkipSetupAll = []string{ "Android", "Appearance", "AppleID", "AppStore", "Biometric", "Diagnostics", "DisplayTone", "FileVault", "HomeButtonSensitivity", "iCloudDiagnostics", "iCloudStorage", "iMessageAndFaceTime", "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 } // 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": int64(0), "ConfigurationURL": "", "ConfigurationWasApplied": true, "CloudConfigurationUIComplete": true, "PostSetupProfileWasInstalled": true, "AutoAdvanceSetup": false, "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 toInterfaceSlice(ss []string) []interface{} { out := make([]interface{}, len(ss)) for i, s := range ss { out[i] = s } return out }