diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8ad9ba4..6a48464 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -180,7 +180,7 @@ jobs: run: echo 'module version_string; enum versionStr = "Official build, branch ${{ github.ref_name }}, commit ${{ github.sha }}";' > source/version_string.d - name: Build - run: dub build -b release-debug --compiler=ldc2 --arch x86_64-apple-darwin + run: dub build -b release-debug -c cli --compiler=ldc2 --arch x86_64-apple-darwin - name: Rename run: mv "${{github.workspace}}/bin/sideloader" "${{github.workspace}}/bin/sideloader-macOS-x86_64" # TODO make an app bundle diff --git a/README.md b/README.md index 5644c62..410b044 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,12 @@ eventually install it with Sideloader on a real device (or maybe even an emulate in the future!) and debug it (with `idevicedebug` or remote `lldb`). _(TODO: add an option to add the entitlement for debugging)_ +## Features + +- Sideload +- Install SideStore (soon) +- iOS version range is unknown. 32-bit support is untested. Please report any issue here!! + ## Notes on platform support ### Linux @@ -94,6 +100,9 @@ cues on the authentication systems for both machines and accounts. - All the people in the SideStore team: testing, help on the machine authentication. - All the people in the AltStore team: help on the account auth, and 2FA (especially kabiroberai's code). +- zhlynn: for its code in zsign. +- indygreg: for its code in rcodesign. +- teryx: [their article about code signature](https://medium.com/csit-tech-blog/demystifying-ios-code-signature-309d52c2ff1d). - Apple Music for Android libraries: giving the opportunity to make all of this work neatly! - Apple's AuthKit and AuthKitWin: giving me the skeleton of the authentication requests @@ -116,5 +125,5 @@ I took 2 years to find a way to overcome the problem that encountered Cydia Impa without resorting to reimplementing the full Windows API. I dedicated a lot of work on this software (alongside my studies). -That is why I am asking you, if you enjoyed my software and if you can afford it, to +That is why I am asking you - if you enjoyed my software and if you can afford it, to give me a small tip via [GitHub Sponsors](https://github.com/sponsors/Dadoum). diff --git a/cli/frontend.d b/cli/frontend.d index 36a2b87..1742495 100644 --- a/cli/frontend.d +++ b/cli/frontend.d @@ -1,198 +1,103 @@ module frontend; +import std.algorithm; +import std.array; +import std.datetime; +import std.format; +import std.path; +import std.stdio; +import std.sumtype; +import std.typecons; +import file = std.file; + import slf4d; import slf4d.default_provider; import slf4d.provider; +import plist; + +import imobiledevice; + +import server.appleaccount; +import server.developersession; + +import sideload; +import sideload.bundle; +import sideload.application; +import sideload.certificateidentity; + import app.frontend; +import main; version = X509; shared class CLIFrontend: Frontend { override string configurationPath() { - getLogger().error("Not implemented."); - return ""; + return expandTilde("./sideloader-config"); } override int run(string[] args) { - import std.algorithm; - import std.array; - import std.datetime; - import std.path; - import std.typecons; - import file = std.file; - - import slf4d; - - import plist; - - import imobiledevice; - - import server.developersession; - - import sideload.bundle; - import sideload.application; - import sideload.certificateidentity; - - import main; - auto log = getLogger(); - // auto app = new Application("~/Téléchargements/SideStore.ipa".expandTilde()); - auto app = new Application("~/Téléchargements/appux/packages/com.yourcompany.appux_0.0.1-1+debug.ipa".expandTilde()); - - // create a certificate for the developer - // auto certIdentity = new CertificateIdentity(configurationPath, null); - - auto team = DeveloperTeam("iOS devel.", "TEAMID"); - - // check if we registered an app id for it (if not create it) - string mainAppBundleId = app.bundleIdentifier(); - string mainAppIdStr = mainAppBundleId ~ "." ~ team.teamId; - string mainAppName = app.bundleName(); - - app.appId = mainAppIdStr; - foreach (plugin; app.plugIns) { - string pluginBundleIdentifier = plugin.bundleIdentifier(); - assertBundle( - pluginBundleIdentifier.startsWith(mainAppBundleId) && - pluginBundleIdentifier.length > mainAppBundleId.length, - "Plug-ins are not formed with the main app bundle identifier" - ); - plugin.appId = mainAppIdStr ~ pluginBundleIdentifier[mainAppBundleId.length..$]; - } - Bundle[] bundlesNeeded = [cast(Bundle) app] ~ app.plugIns; - // Search which App IDs have to be registered (we don't want to start registering App IDs if we don't - // have enough of them to register them all!! otherwise we will waste their precious App IDs) - auto appIdsToRegister = bundlesNeeded; + string appPath; - foreach (bundle; appIdsToRegister) { - log.infoF!"Creating App ID `%s`..."(bundle.appId); + if (args.length != 2) { + log.errorF!"Usage: %s "(args.length ? args[0] : "sideloader"); + return 1; } + appPath = args[1]; - auto bundles = bundlesNeeded.map!((bundle) => tuple(bundle, AppId("", bundle.appId, "ApplicationName", null, DateTime()))).array(); - auto mainBundle = bundles[0]; + if (!(file.exists(configurationPath.buildPath("lib/libstoreservicescore.so")) && file.exists(configurationPath.buildPath("lib/libCoreADI.so")))) { + auto succeeded = downloadAndInstallDeps((progress) { + write(format!"%.2f %% completed\r"(progress * 100)); + stdout.flush(); - // sign the app with all the retrieved material! - foreach (bundlePair; bundles) { - import core.sys.darwin.mach.loader; - import sideload.macho; + return false; + }); - auto bundle = bundlePair[0]; - auto appId = bundlePair[1]; - - auto bundlePath = bundle.bundleDir; - - // set the bundle identifier to the one with the team id to match the provisioning profile - bundle.appInfo["CFBundleIdentifier"] = appId.identifier.pl; - - string executablePath = bundlePath.buildPath(bundle.appInfo["CFBundleExecutable"].str().native()); - MachO[] machOs = MachO.parse(cast(ubyte[]) file.read(executablePath), Architecture.aarch64); - log.infoF!"Mach-Os: %s"(machOs); - - import cms.cms_dec; - auto provisioningProfilePlist = Plist.fromMemory(dataFromCMS( - cast(ubyte[]) file.read("/home/dadoum/Téléchargements/com.SideStore.SideStore.MK7ZNLPN7B.AltWidget.mobileprovision") - )); - - auto entitlements = provisioningProfilePlist["Entitlements"].dict; - - foreach (machO; machOs) { - auto execSegBase = machO.execSegBase; - auto execSegLimit = machO.execSegLimit; - auto execFlags = machO.execFlags(entitlements); - - auto embeddedSignature = new EmbeddedSignature(); - embeddedSignature ~= cast(Blob[]) [ - new CodeDirectoryBlob(new SHA1(), team.teamId, execSegBase, execSegLimit, execFlags), - new RequirementsBlob(), - new EntitlementsBlob(entitlements.toXml()), - new DerEntitlementsBlob(entitlements), - new CodeDirectoryBlob(new SHA2(), team.teamId, execSegBase, execSegLimit, execFlags), - new SignatureBlob(), - ]; - machO.replaceCodeSignature(embeddedSignature); - } - - file.write("/home/dadoum/Téléchargements/Salut", makeMachO(machOs)); - - /* - // fabricate entitlements file!!! - string executablePath = bundlePath.buildPath(bundle.appInfo["CFBundleExecutable"].str().native()); - MachO[] machOs = MachO.parse(cast(ubyte[]) file.read(executablePath)); - - // here is the real signing logic: - // we will sign each of the mach-o contained - // and rebuild them. - foreach (machO; machOs) { - linkedit_data_command signatureCommand = void; - symtab_command symtabCommand = void; - - foreach (command; machO.loadCommands) { - switch (command.cmd) { - case LC_CODE_SIGNATURE: - signatureCommand = command.read!linkedit_data_command(0); - break; - case LC_SYMTAB: - symtabCommand = command.read!symtab_command(0); - break; - default: - break; - } - } - - string entitlementsStr = ""; - if (signatureCommand.cmd) { - // get entitlements!! - SuperBlobHeader superBlob = machO.read!SuperBlobHeader(signatureCommand.dataoff); - auto blobArrayStart = signatureCommand.dataoff + SuperBlobHeader.sizeof; - auto blobArrayEnd = blobArrayStart + superBlob.count * BlobIndex.sizeof; - - for (auto blobArrayIndex = blobArrayStart; blobArrayIndex < blobArrayEnd; blobArrayIndex += BlobIndex.sizeof) { - auto currentBlob = machO.read!BlobIndex(signatureCommand.dataoff + blobArrayIndex); - if (currentBlob.type == CSSLOT_ENTITLEMENTS) { - Blob entitlementsBlob = machO.read!Blob(currentBlob.offset); - entitlementsStr = cast(string) machO.data[signatureCommand.dataoff + currentBlob.offset + Blob.sizeof..signatureCommand.dataoff + currentBlob.offset + entitlementsBlob.length]; - if (entitlementsStr.length) { - log.infoF!"Entitlements: %s"(entitlementsStr); - } - } - } - } + if (!succeeded) { + log.error("Download failed."); + return 1; } + log.info("Download completed."); + } - /* - auto entitlements = Plist.fromMemory(cast(ubyte[]) entitlementsStr).dict(); - entitlements["application-identifier"] = appId.identifier; - entitlements["com.apple.developer.team-identifier"] = team.teamId; - - // create app groups for it if needed - if (auto bundleAppGroups = "com.apple.security.application-groups" in entitlements) { - if (!appId.features[AppIdFeatures.appGroup].boolean().native()) { - // We need to enable app groups then ! - log.infoF!"Updating the app id %s to enable app groups."(appId.identifier); - appId.features = developer.updateAppId!iOS(team, appId, dict(AppIdFeatures.appGroup, true)).unwrap(); - } - - auto appGroups = developer.listApplicationGroups!iOS(team).unwrap(); - foreach (bundleAppGroup; bundleAppGroups.array()) { - string bundleGroupId = bundleAppGroup.str().native(); - auto matchingAppGroups = appGroups.find!((appGroup) => appGroup.identifier == bundleGroupId).array(); - ApplicationGroup appGroup; - if (matchingAppGroups.empty) { - log.infoF!"Creating the app group %s."(bundleGroupId); - appGroup = developer.addApplicationGroup!iOS(team, bundleGroupId, mainAppName).unwrap(); - } else { - appGroup = matchingAppGroups[0]; - } - } + initializeADI(); + + write("Enter your Apple ID: "); + stdout.flush(); + string appleId = readln()[0..$ - 1]; + write("Enter your password (will appear in clear in your terminal): "); + stdout.flush(); + string password = readln()[0..$ - 1]; + + DeveloperSession appleAccount = DeveloperSession.login( + device, + adi, + appleId, + password, + (sendCode, submitCode) { + sendCode(); + write("A code has been sent to your devices, please write it here: "); + stdout.flush(); + string code = readln(); + submitCode(code); + }).match!( + (DeveloperSession session) => session, + (AppleLoginError error) { + auto errorStr = format!"%s (%d)"(error.description, error); + getLogger().errorF!"Apple auth error: %s"(errorStr); + return null; } - - // Write the updated Info.plist with the new bundle identifier. - file.write(bundlePath.buildPath("Info.plist"), bundle.appInfo.toXml()); - file.write(bundlePath.buildPath("embedded.mobileprovision"), profile.encodedProfile); - // */ + ); + + if (appleAccount) { + string udid = iDevice.deviceList()[0].udid; + log.infoF!"Initiating connection the device (UUID: %s)"(udid); + auto device = new iDevice(udid); + sideloadFull(device, appleAccount, new Application(appPath), (progress, action) { + log.infoF!"%s (%.2f%%)"(action, progress * 100); + }); } - return 0; } } diff --git a/dub.json b/dub.json index b2fa520..49edfbe 100644 --- a/dub.json +++ b/dub.json @@ -6,8 +6,9 @@ ], "targetPath": "bin/", + "stringImportPaths": ["resources/"], - "buildRequirements": ["allowWarnings"], + "buildRequirements": ["allowWarnings", "requireBoundsCheck"], "dependencies": { "botan": { @@ -18,9 +19,10 @@ "repository": "git+https://github.com/Dadoum/dynamicloader.git", "version": "32355c1aae76e0a89c123bc082ec2df8cddc2b0f" }, + "intel-intrinsics": "~>1.11.15", "plist-d": { "repository": "git+https://github.com/Dadoum/libplist-d.git", - "version": "f04b7ebf2623ff5e3ad608910c3a3ac56639a21f" + "version": "ad7ce217b56af8e51647ab9dd86157b94ef58325" }, "provision": { "repository": "git+https://github.com/Dadoum/Provision.git", diff --git a/dub.selections.json b/dub.selections.json index 86089a7..7afbcaa 100644 --- a/dub.selections.json +++ b/dub.selections.json @@ -19,10 +19,11 @@ "gtk_d": "1.0.3", "icontheme": "1.2.3", "inilike": "1.2.2", + "intel-intrinsics": "1.11.15", "isfreedesktop": "0.1.1", "memutils": "1.0.9", "plist": "~master", - "plist-d": {"version":"f04b7ebf2623ff5e3ad608910c3a3ac56639a21f","repository":"git+https://github.com/Dadoum/libplist-d.git"}, + "plist-d": {"version":"ad7ce217b56af8e51647ab9dd86157b94ef58325","repository":"git+https://github.com/Dadoum/libplist-d.git"}, "provision": {"version":"a007cb290da1a21e231a1a4a335a047a151ca24f","repository":"git+https://github.com/Dadoum/Provision.git"}, "requests": "2.0.9", "silly": "1.2.0-dev.2", diff --git a/linux/gtk/ui/sideloadergtkapplication.d b/linux/gtk/ui/sideloadergtkapplication.d index a70dc7c..65aa39f 100644 --- a/linux/gtk/ui/sideloadergtkapplication.d +++ b/linux/gtk/ui/sideloadergtkapplication.d @@ -75,11 +75,11 @@ class SideloaderGtkApplication: Application { aboutDialog.addCreditSection("SideStore contributors (no shared code)", ["Riley Testut", "Kabir Oberai", `Joelle "Lonkelle"`, `Nick "nythepegasus"`, `James "JJTech"`, `Joss "bogotesr"`, `naturecodevoid`, `many other, open a GH issue if needed`]); - aboutDialog.addCreditSection("zsign (no shared code)", [`zhlynn`]); - aboutDialog.addCreditSection("OG Cydia Impactor, ldid (no shared code)", [`Jay "saurik" Freeman`]); + aboutDialog.addCreditSection("Help on app signature", [`DebianArch`, `zhlynn (zsign)`, `Jay "saurik" Freeman (ldid)`]); aboutDialog.addCreditSection("Apple Music for Android libraries", [`Apple`]); - aboutDialog.setComments("Don't hesitate to reach me out if I forgot someone in the credits!"); + aboutDialog.setComments("Don't hesitate to reach me out if I forgot someone in the credits! \n" + ~ "Note: most of them are not involved in the development of this software whatsoever. Do not report any issue to them!!"); aboutDialog.show(); }); diff --git a/resources/AppleIncRootCertificate.cer b/resources/AppleIncRootCertificate.cer new file mode 100644 index 0000000..8a9ff24 Binary files /dev/null and b/resources/AppleIncRootCertificate.cer differ diff --git a/resources/AppleWWDRCAG3.cer b/resources/AppleWWDRCAG3.cer new file mode 100644 index 0000000..32f96f8 Binary files /dev/null and b/resources/AppleWWDRCAG3.cer differ diff --git a/source/main.d b/source/main.d index dede5ef..fce3be7 100644 --- a/source/main.d +++ b/source/main.d @@ -43,6 +43,7 @@ int main(string[] args) { Logger log = getLogger(); frontend = native_frontend.makeFrontend(); + log.infoF!"Sideloader, compiled with %s on %s at %s"(__VENDOR__, __DATE__, __TIME__); log.infoF!"Configuration path: %s"(frontend.configurationPath()); if (!file.exists(frontend.configurationPath)) { file.mkdirRecurse(frontend.configurationPath); @@ -50,3 +51,12 @@ int main(string[] args) { return frontend.run(args); } + +static this() { + version (DigitalMars) { + + } else { + static import sse2; + sse2.register(); + } +} diff --git a/source/server/developersession.d b/source/server/developersession.d index 6c9da65..56f8299 100644 --- a/source/server/developersession.d +++ b/source/server/developersession.d @@ -481,17 +481,6 @@ enum AppIdFeatures: string { cloudKitVersion = "cloudKitVersion", } -template Transaction(alias U, alias val) { - static assert(is(__traits(parent, U) == AppIdFeatures)); - enum udas = __traits(getUDAs, U); - static if (udas.length) { - enum propName = udas[0]; - } else { - enum propName = __traits(identifier, U); - } - enum Transaction = AliasSeq!(propName, val); -} - struct AppId { string appIdId; string identifier; diff --git a/source/sideload/applecert.d b/source/sideload/applecert.d new file mode 100644 index 0000000..d2cabec --- /dev/null +++ b/source/sideload/applecert.d @@ -0,0 +1,4 @@ +module sideload.applecert; + +enum ubyte[] appleWWDRG3 = cast(ubyte[]) import("AppleWWDRCAG3.cer"); +enum ubyte[] appleRoot = cast(ubyte[]) import("AppleIncRootCertificate.cer"); diff --git a/source/sideload/application.d b/source/sideload/application.d index 2c25564..abca98a 100644 --- a/source/sideload/application.d +++ b/source/sideload/application.d @@ -4,6 +4,7 @@ import std.algorithm.iteration; import std.algorithm.searching; import std.array; import file = std.file; +import std.parallelism; import std.path; import std.string; import std.zip; @@ -18,45 +19,42 @@ import sideload.plugin; class Application: Bundle { string tempPath; - Bundle[] plugIns = []; this(string path) { - tempPath = file.tempDir().buildPath(baseName(path)); - if (file.exists(tempPath)) { - file.rmdirRecurse(tempPath); - file.mkdir(tempPath); - } else { - file.mkdirRecurse(tempPath); - } - auto ipa = new ZipArchive(file.read(path)); - - foreach (k, v; ipa.directory()) { - auto entryPath = tempPath.buildPath(k); - if (k[$ - 1] != '/') { - auto dirname = dirName(entryPath); - if (!file.exists(dirname)) { - file.mkdirRecurse(dirname); - } - file.write(entryPath, ipa.expand(v)); + if (file.isFile(path)) { + tempPath = file.tempDir().buildPath(baseName(path)); + if (file.exists(tempPath)) { + file.rmdirRecurse(tempPath); + file.mkdir(tempPath); + } else { + file.mkdirRecurse(tempPath); } - } + auto ipa = new ZipArchive(file.read(path)); - auto payloadFolder = tempPath.buildPath("Payload"); - assertBundle(file.exists(payloadFolder), "No Payload folder!"); + foreach (kv; parallel(ipa.directory().byKeyValue())) { + auto k = kv.key; + auto v = kv.value; - auto apps = file.dirEntries(payloadFolder, file.SpanMode.shallow).array; - assertBundle(apps.length == 1, "No or too many application folder!"); + auto entryPath = tempPath.buildPath(k); + if (k[$ - 1] != '/') { + auto dirname = dirName(entryPath); + if (!file.exists(dirname)) { + file.mkdirRecurse(dirname); + } + file.write(entryPath, ipa.expand(v)); + } + } - auto appFolder = apps[0]; + auto payloadFolder = tempPath.buildPath("Payload"); + assertBundle(file.exists(payloadFolder), "No Payload folder!"); - super(appFolder); + auto apps = file.dirEntries(payloadFolder, file.SpanMode.shallow).array; + assertBundle(apps.length == 1, "No or too many application folder!"); - auto plugInsFolder = appFolder.buildPath("PlugIns"); - if (file.exists(plugInsFolder) && file.isDir(plugInsFolder)) { - foreach (pluginFolder; file.dirEntries(plugInsFolder, file.SpanMode.shallow)) { - plugIns ~= new PlugIn(pluginFolder); - } + path = apps[0]; } + + super(path); } /// Fetches a mobileprovision file for the app diff --git a/source/sideload/bundle.d b/source/sideload/bundle.d index 208755e..96720d0 100644 --- a/source/sideload/bundle.d +++ b/source/sideload/bundle.d @@ -1,5 +1,7 @@ module sideload.bundle; +import std.algorithm.iteration; +import std.array; import file = std.file; import std.path; @@ -8,17 +10,43 @@ import plist; class Bundle { PlistDict appInfo; string bundleDir; - string appId; // registered app id for it + + Bundle[] _appExtensions; + Bundle[] _frameworks; + string[] _libraries; this(string bundleDir) { + if (bundleDir[$ - 1] == '/' || bundleDir[$ - 1] == '\\') bundleDir.length -= 1; this.bundleDir = bundleDir; string infoPlistPath = bundleDir.buildPath("Info.plist"); - assertBundle(file.exists(infoPlistPath), "No Info.plist"); + assertBundle(file.exists(infoPlistPath), "No Info.plist here: " ~ infoPlistPath); appInfo = Plist.fromMemory(cast(ubyte[]) file.read(infoPlistPath)).dict(); + + auto plugInsDir = bundleDir.buildPath("PlugIns"); + if (file.exists(plugInsDir)) { + _appExtensions = file.dirEntries(plugInsDir, file.SpanMode.shallow).filter!((f) => f.isDir).map!((f) => new Bundle(f.name)).array; + } else { + _appExtensions = []; + } + + auto frameworksDir = bundleDir.buildPath("Frameworks"); + if (file.exists(frameworksDir)) { + _frameworks = file.dirEntries(frameworksDir, file.SpanMode.shallow).filter!((f) => f.isDir).map!((f) => new Bundle(f.name)).array; + _libraries = file.dirEntries(frameworksDir, file.SpanMode.shallow).filter!((f) => f.isFile).map!((f) => f.name[bundleDir.length + 1..$]).array; + } else { + _frameworks = []; + } } + void bundleIdentifier(string id) => appInfo["CFBundleIdentifier"] = id.pl; string bundleIdentifier() => appInfo["CFBundleIdentifier"].str().native(); + string bundleName() => appInfo["CFBundleName"].str().native(); + + string[] libraries() => _libraries; + Bundle[] frameworks() => _frameworks; + Bundle[] appExtensions() => _appExtensions; + Bundle[] subBundles() => frameworks ~ appExtensions; } void assertBundle(bool condition, string msg, string file = __FILE__, int line = __LINE__) { diff --git a/source/sideload/certificateidentity.d b/source/sideload/certificateidentity.d index ff5cf77..7183a4b 100644 --- a/source/sideload/certificateidentity.d +++ b/source/sideload/certificateidentity.d @@ -23,19 +23,16 @@ import server.developersession; import sideload.bundle; class CertificateIdentity { + RandomNumberGenerator rng = void; RSAPrivateKey privateKey = void; DevelopmentCertificate appleCertificateInfo = void; X509Certificate certificate = void; - string certFile; string keyFile; - this(string configurationPath, DeveloperSession appleAccount) { // Gets the key if it exists, and generates or retrieves the matching certificate. + this(string configurationPath, DeveloperSession appleAccount) { auto log = getLogger(); - auto teams = appleAccount.listTeams().unwrap(); - auto team = teams[0]; - string keyPath = configurationPath.buildPath("keys").buildPath(sha1Of(appleAccount.appleId).toHexString().toLower()); if (!file.exists(keyPath)) { file.mkdirRecurse(keyPath); @@ -43,7 +40,10 @@ class CertificateIdentity { keyFile = keyPath.buildPath("key.pem"); - RandomNumberGenerator rng = RandomNumberGenerator.makeRng(); + rng = RandomNumberGenerator.makeRng(); + + auto teams = appleAccount.listTeams().unwrap(); + auto team = teams[0]; if (file.exists(keyFile)) { log.info("A key has already been generated"); @@ -91,10 +91,7 @@ class CertificateIdentity { certificateReady: log.info("Certificate retrieved successfully."); - certificate = X509Certificate(appleCertificateInfo.certContent, false); - // temporary, remove when real signing is implemented - certFile = keyPath.buildPath("cert.der"); - file.write(certFile, appleCertificateInfo.certContent); + certificate = X509Certificate(Vector!ubyte(appleCertificateInfo.certContent), false); } import server.developersession; diff --git a/source/sideload/macho.d b/source/sideload/macho.d index d57a83c..26733d4 100644 --- a/source/sideload/macho.d +++ b/source/sideload/macho.d @@ -9,30 +9,48 @@ import core.stdc.stdint; import std.algorithm; import std.algorithm.iteration; -import std.bitmanip; +import std.bitmanip; alias readBE = std.bitmanip.bigEndianToNative; +import std.datetime.systime; +import std.exception; import std.format; +import std.parallelism; import std.range; import std.traits; +import std.typecons; import botan.asn1.asn1_obj; import botan.asn1.asn1_str; +import botan.asn1.asn1_time; import botan.asn1.der_enc; +import botan.asn1.oids; +import botan.cert.x509.x509_obj; +import botan.cert.x509.x509cert; import botan.hash.mdx_hash; import botan.hash.sha160; import botan.hash.sha2_32; +import botan.math.bigint.bigint; +import botan.pubkey.pubkey; import memutils.vector; import plist; +import sideload.applecert; +import sideload.certificateidentity; + version (BigEndian) { static assert(false, "Big endian systems are not supported"); } +static this() { + OIDS.setDefaults(); +} + /// Will only parse little-endian on little-endian /// I have code which is more versatile, but since it's useless (almost everything is little-endian now), /// and is way more complex I won't put it here for now. class MachO { + size_t headersize; int cputype; int cpusubtype; uint ncmds; @@ -44,12 +62,14 @@ class MachO { uint64_t execSegBase; uint64_t execSegLimit; + load_command* linkeditCommand; uint64_t execFlags(PlistDict entitlements) { return computeEntitlementsExecSegFlags(entitlements) | (filetype == MH_EXECUTE ? CS_EXECSEG_MAIN_BINARY : 0); } private this(ubyte[] data, size_t headersize, int cputype, int cpusubtype, uint ncmds, uint sizeofcmds, uint filetype) { + this.headersize = headersize; this.cputype = cputype; this.cpusubtype = cpusubtype; this.ncmds = ncmds; @@ -58,16 +78,28 @@ class MachO { this.filetype = filetype; size_t loc = headersize; + linkedit_data_command* codeSigCmd; for (int i = 0; i < ncmds; i++) { auto command = cast(load_command*) data[loc..$].ptr; loc += command.cmdsize; - if (command.cmd == LC_SEGMENT_64) { // 32-bit support? + if (command.cmd == LC_SEGMENT_64) { segment_command_64* segmentCmd = cast(segment_command_64*) command; if (segmentCmd.segname[0..6] == "__TEXT") { execSegBase = segmentCmd.fileoff; execSegLimit = segmentCmd.fileoff + segmentCmd.filesize; + } else if (segmentCmd.segname[0..10] == "__LINKEDIT") { + linkeditCommand = command; + } + } else if (command.cmd == LC_SEGMENT) { + segment_command* segmentCmd = cast(segment_command*) command; + + if (segmentCmd.segname[0..6] == "__TEXT") { + execSegBase = segmentCmd.fileoff; + execSegLimit = segmentCmd.fileoff + segmentCmd.filesize; + } else if (segmentCmd.segname[0..10] == "__LINKEDIT") { + linkeditCommand = command; } } @@ -115,8 +147,33 @@ class MachO { throw new InvalidMachOException(format!"magic: %x"(magic)); } - void replaceCodeSignature(EmbeddedSignature signature) { + size_t codeSignatureOffset() { + linkedit_data_command* codeSigCmd; + foreach (command; commands) { + if (command.cmd == LC_CODE_SIGNATURE) { + codeSigCmd = cast(linkedit_data_command*) command; + break; + } + } + + if (!codeSigCmd) { + return data.length; + } + + return codeSigCmd.dataoff; + } + + + /// Returns whether the segment had to be extended + bool replaceCodeSignature(EmbeddedSignature signature) { auto sig = signature.encode(); + return replaceCodeSignature(sig); + } + + /// ditto + bool replaceCodeSignature(ubyte[] sig) { + size_t sectionSize = pageCeil(sig.length); + linkedit_data_command* codeSigCmd; foreach (command; commands) { if (command.cmd == LC_CODE_SIGNATURE) { @@ -126,27 +183,75 @@ class MachO { } if (!codeSigCmd) { - throw new NeverSignedException(); + auto endCommandsLocation = headersize + sizeofcmds; + + // If adding a linkedit_data_command doesn't cross the page boundary. + if ((pageFloor(endCommandsLocation + linkedit_data_command.sizeof)) > endCommandsLocation) { + throw new SegmentAllocationFailedException(); + } + + codeSigCmd = cast(linkedit_data_command*) data[endCommandsLocation..$].ptr; + commands ~= cast(load_command*) codeSigCmd; + + codeSigCmd.cmd = LC_CODE_SIGNATURE; + codeSigCmd.cmdsize = linkedit_data_command.sizeof; + codeSigCmd.dataoff = cast(uint) data.length; + codeSigCmd.datasize = 0; + + sizeofcmds += linkedit_data_command.sizeof; + ncmds += 1; } - if (sig.length < codeSigCmd.datasize) { + if (sig.length <= codeSigCmd.datasize) { // We can re-use the space. data[codeSigCmd.dataoff..codeSigCmd.dataoff + sig.length] = sig; data[codeSigCmd.dataoff + sig.length..codeSigCmd.dataoff + codeSigCmd.datasize] = 0; - return; + return false; } - if (codeSigCmd.dataoff + codeSigCmd.datasize == data.length) { - // If the segment is at the end of the file, we can remove it without messing alignment - data = data[0..$ - codeSigCmd.datasize]; + // The section should be at the end of the file. + enforce (codeSigCmd.dataoff + codeSigCmd.datasize >= data.length); + data = data[0..codeSigCmd.dataoff]; + + size_t extraVmSize = sectionSize - pageFloor(codeSigCmd.datasize); + size_t extraFileSize = sig.length - codeSigCmd.datasize; + + if (linkeditCommand.cmd == LC_SEGMENT_64) { + // 64-bit + auto linkedit = cast(segment_command_64*) linkeditCommand; + linkedit.filesize += extraFileSize; + linkedit.vmsize += extraVmSize - 0x3000; } else { - // We will zero it and add new segment at the end. - data[codeSigCmd.dataoff..codeSigCmd.dataoff + codeSigCmd.datasize] = 0; + auto linkedit = cast(segment_command*) linkeditCommand; + linkedit.filesize += extraFileSize; + linkedit.vmsize += extraVmSize; } codeSigCmd.dataoff = cast(uint) data.length; codeSigCmd.datasize = cast(uint) sig.length; + data ~= sig; + updateHeader(); + + return true; + } + + void updateHeader() { + if (cputype & CPU_ARCH_ABI64) { + auto header = cast(mach_header_64*) data.ptr; + header.cputype = cputype; + header.cpusubtype = cpusubtype; + header.ncmds = ncmds; + header.sizeofcmds = sizeofcmds; + header.filetype = filetype; + } else { + auto header = cast(mach_header*) data.ptr; + header.cputype = cputype; + header.cpusubtype = cpusubtype; + header.ncmds = ncmds; + header.sizeofcmds = sizeofcmds; + header.filetype = filetype; + } } } @@ -215,36 +320,58 @@ enum Architecture { ubyte[] makeMachO(MachO[] machOs) { auto nMachOs = machOs.length; if (nMachOs == 1) { - return machOs[0].data; + auto machO = machOs[0]; + if (machO.cputype & CPU_ARCH_ABI64) { + auto header = cast(mach_header_64*) machO.data.ptr; + header.cputype = machO.cputype; + header.cpusubtype = machO.cpusubtype; + header.ncmds = machO.ncmds; + header.sizeofcmds = machO.sizeofcmds; + header.filetype = machO.filetype; + } else { + auto header = cast(mach_header*) machO.data.ptr; + header.cputype = machO.cputype; + header.cpusubtype = machO.cpusubtype; + header.ncmds = machO.ncmds; + header.sizeofcmds = machO.sizeofcmds; + header.filetype = machO.filetype; + } + return machO.data; } else if (nMachOs == 0) { return []; } else { // build a fat mach-o file. ubyte[] fatMachO = (cast(ubyte*) new fat_header( - (cast(uint32_t) FAT_MAGIC).nativeToBigEndian(), - (cast(uint32_t) nMachOs).nativeToBigEndian() - ))[0..fat_header.sizeof]; + (cast(uint32_t) FAT_MAGIC), + (cast(uint32_t) nMachOs) + ).nativeToBigEndian())[0..fat_header.sizeof]; ubyte[] machOData; - uint dataOffset = cast(uint) (fat_header.sizeof + nMachOs * fat_arch.sizeof); + uint dataOffset = pageCeil(cast(uint) (fat_header.sizeof + nMachOs * fat_arch.sizeof)); foreach (ref index, machO; machOs) { fatMachO ~= (cast(ubyte*) new fat_arch( - machO.cputype.nativeToBigEndian(), - machO.cpusubtype.nativeToBigEndian(), - dataOffset.nativeToBigEndian(), - (cast(uint32_t) machO.data.length).nativeToBigEndian(), - PAGE_SIZE_LOG2.nativeToBigEndian() - ))[0..fat_arch.sizeof]; - dataOffset += machO.data.length; + machO.cputype, + machO.cpusubtype, + dataOffset, + cast(uint32_t) machO.data.length, + PAGE_SIZE_LOG2 + ).nativeToBigEndian())[0..fat_arch.sizeof]; + dataOffset += pageCeil(machO.data.length); machOData ~= machO.data; + auto alignment = new ubyte[](pageCeil(machOData.length) - machOData.length); + alignment[] = 0; + machOData ~= alignment; } - return fatMachO ~ machOData; + auto alignment = new ubyte[](pageCeil(fatMachO.length) - fatMachO.length); + alignment[] = 0; + return fatMachO ~ alignment ~ machOData; } } enum PAGE_SIZE_LOG2 = 14; +enum PAGE_SIZE = 1 << PAGE_SIZE_LOG2; enum uint CSSLOT_CODEDIRECTORY = 0; enum uint CSSLOT_REQUIREMENTS = 2; @@ -254,6 +381,7 @@ enum uint CSSLOT_ALTERNATE_CODEDIRECTORIES = 0x1000; enum uint CSSLOT_SIGNATURESLOT = 0x10000; enum uint CSMAGIC_BLOBWRAPPER = 0xfade0b01; +enum uint CSMAGIC_REQUIREMENT = 0xfade0c00; enum uint CSMAGIC_REQUIREMENTS = 0xfade0c01; enum uint CSMAGIC_CODEDIRECTORY = 0xfade0c02; enum uint CSMAGIC_EMBEDDED_SIGNATURE = 0xfade0cc0; @@ -263,26 +391,51 @@ enum uint CSMAGIC_EMBEDDED_DER_ENTITLEMENTS = 0xfade7172; interface Blob { uint type(); - ubyte[] encode(); + uint length(); + ubyte[] encode(ubyte[][] previousEncodedBlobs); } enum CODEDIRECTORY_VERSION = 0x20400; class CodeDirectoryBlob: Blob { - uint type() => CSSLOT_CODEDIRECTORY; + uint type() => isAlternate ? CSSLOT_ALTERNATE_CODEDIRECTORIES : CSSLOT_CODEDIRECTORY; + + enum PAGE_SIZE_CODEDIRECTORY_LOG2 = 12; + enum PAGE_SIZE_CODEDIRECTORY = 1 << PAGE_SIZE_CODEDIRECTORY_LOG2; MDxHashFunction hashFunction; + string bundleId; string teamId; - uint64_t execSegBase; - uint64_t execSegLimit; - uint64_t execSegFlags; - this(MDxHashFunction hash, string teamIdentifier, uint64_t execSegBase, uint64_t execSegLimit, uint64_t execSegFlags) { + MachO machO; + PlistDict entitlements; + + string infoPlist; + string codeResources; + + bool isAlternate; + + this( + MDxHashFunction hash, + string bundleIdentifier, + string teamIdentifier, + MachO machO, + PlistDict entitlements, + string infoPlist, + string codeResources, + bool isAlternate = false + ) { hashFunction = hash; + bundleId = bundleIdentifier; teamId = teamIdentifier; - this.execSegBase = execSegBase; - this.execSegLimit = execSegLimit; - this.execSegFlags = execSegFlags; + + this.machO = machO; + this.entitlements = entitlements; + + this.infoPlist = infoPlist; + this.codeResources = codeResources; + + this.isAlternate = isAlternate; } struct CS_CodeDirectory { @@ -321,44 +474,148 @@ class CodeDirectoryBlob: Blob { uint64_t execSegFlags; /* executable segment flags */ //char end_withExecSeg[0]; + /* Version 0x20500 */ + // uint32_t runtime; + // uint32_t preEncryptOffset; + //char end_withPreEncryptOffset[0]; + + /* Version 0x20600, currently unsupported */ + // uint8_t linkageHashType; + // uint8_t linkageTruncated; + // uint16_t spare4; + // uint32_t linkageOffset; + // uint32_t linkageSize; + //char end_withLinkage[0]; + /* followed by dynamic content flagsas located by offset fields above */ } - ubyte[] encode() { + uint length() { + auto hashOutputLength = hashFunction.outputLength(); + auto codeLimit = machO.codeSignatureOffset(); + + return cast(uint) ( + CS_CodeDirectory.sizeof + + bundleId.length + 1 + + teamId.length + 1 + + (7 + (codeLimit / 4096 + !!(codeLimit % 4096))) * hashOutputLength + ); + } + + ubyte[] encode(ubyte[][] previousEncodedBlobs) { + auto execSegBase = machO.execSegBase; + auto execSegLimit = machO.execSegLimit; + auto execFlags = machO.execFlags(entitlements); + + auto getTaskAllow = "get-task-allow" in entitlements; + if (getTaskAllow && getTaskAllow.boolean().native()) { + execFlags |= CS_EXECSEG_ALLOW_UNSIGNED; + } + + auto codeLimit = machO.codeSignatureOffset(); + auto codeSlots = machO.data[0..codeLimit].chunks(4096).array(); + + auto isExecute = machO.filetype == MH_EXECUTE; + + ubyte[] requirementsData; + ubyte[] entitlementsData; + ubyte[] derEntitlementsData; + + foreach (blob; previousEncodedBlobs) { + uint magic = (cast(ubyte[4]) blob[0..4]).readBE!uint(); + if (magic == CSMAGIC_REQUIREMENTS) { + requirementsData = blob; + continue; + } + if (magic == CSMAGIC_EMBEDDED_ENTITLEMENTS) { + entitlementsData = blob; + continue; + } + if (magic == CSMAGIC_EMBEDDED_DER_ENTITLEMENTS) { + derEntitlementsData = blob; + continue; + } + } + + enforce(requirementsData, "Requirements have not been computed before CodeDir!"); + enforce(entitlementsData, "Entitlements have not been computed before CodeDir!"); + enforce(derEntitlementsData, "DerEntitlements have not been computed before CodeDir!"); + + auto hashOutputLength = cast(ubyte) hashFunction.outputLength(); + auto codeDir = new CS_CodeDirectory( - CSMAGIC_CODEDIRECTORY, - CS_CodeDirectory.sizeof + 0, - CODEDIRECTORY_VERSION, - 0, - 0, - 0, - 0, - 0, - 0, - cast(ubyte) hashFunction.hashBlockSize(), - typeid(hashFunction) == typeid(SHA1) ? 1 : 2, - 0, - PAGE_SIZE_LOG2, - 0, - - 0, // we don't use scatter - - CS_CodeDirectory.sizeof, // we will set teamId - - 0, - 0, - - execSegBase, - execSegLimit, - execSegFlags, + /+ magic +/ CSMAGIC_CODEDIRECTORY, + /+ length +/ CS_CodeDirectory.sizeof, + /+ version_ +/ CODEDIRECTORY_VERSION, + /+ flags +/ 0, + /+ hashOffset +/ CS_CodeDirectory.sizeof, + /+ identOffset +/ CS_CodeDirectory.sizeof, + /+ nSpecialSlots +/ 0, + /+ nCodeSlots +/ 0, + /+ codeLimit +/ codeLimit <= uint32_t.max ? cast(uint) codeLimit : 0, + /+ hashSize +/ hashOutputLength, + /+ hashType +/ hashFunction.hashType(), + /+ platform +/ 0, + /+ pageSize +/ PAGE_SIZE_CODEDIRECTORY_LOG2, + /+ spare2 +/ 0, + + /+ scatterOffset +/ 0, // we don't use scatter + + /+ teamOffset +/ CS_CodeDirectory.sizeof, // we will set teamId + + /+ spare3 +/ 0, + /+ codeLimit64 +/ codeLimit > uint32_t.max ? codeLimit : 0, + + /+ execSegBase +/ execSegBase, + /+ execSegLimit +/ execSegLimit, + /+ execSegFlags +/ execFlags, ); + ubyte[] body = []; + codeDir.identOffset += body.length; + body ~= bundleId ~ '\0'; + codeDir.teamOffset += body.length; - body ~= cast(ubyte[]) teamId ~ '\0'; + body ~= teamId ~ '\0'; + + // zsign copy tbh + ubyte[][] specialSlots; + + auto emptyHash = new ubyte[](hashOutputLength); + auto infoPlistHash = hashFunction.process(infoPlist)[].dup; + auto codeResourcesHash = hashFunction.process(codeResources)[].dup; + auto requirementsSectionHash = hashFunction.process(requirementsData)[].dup; + auto entitlementsSectionHash = hashFunction.process(entitlementsData)[].dup; + auto derEntitlementsSectionHash = hashFunction.process(derEntitlementsData)[].dup; + + specialSlots ~= derEntitlementsSectionHash; + specialSlots ~= emptyHash; + specialSlots ~= entitlementsSectionHash; + specialSlots ~= emptyHash; + specialSlots ~= codeResourcesHash; + specialSlots ~= requirementsSectionHash; + specialSlots ~= infoPlistHash; + + codeDir.nSpecialSlots = cast(uint) specialSlots.length; + body ~= specialSlots[].join(); + + codeDir.hashOffset += cast(uint) body.length; + codeDir.nCodeSlots = cast(uint) codeSlots.length; + + ubyte[] slots = new ubyte[](codeSlots.length * hashOutputLength); + + auto hashFunctionLocal = taskPool().workerLocalStorage!MDxHashFunction(cast(MDxHashFunction) hashFunction.clone()); + + foreach (idx, slot; parallel(codeSlots)) { + auto index = idx * hashOutputLength; + slots[index..index + hashOutputLength] = hashFunctionLocal.get().process(slot)[]; + } + + body ~= slots; - codeDir.length = cast(uint) (codeDir.sizeof + body.length); - return (cast(ubyte*) codeDir.nativeToBigEndian())[0..CS_CodeDirectory.sizeof]; + codeDir.length += body.length; + return (cast(ubyte*) codeDir.nativeToBigEndian())[0..CS_CodeDirectory.sizeof] ~ body; } } @@ -369,13 +626,45 @@ T* nativeToBigEndian(T)(return T* struc) if (is(T == struct)) { return struc; } -public alias SHA1 = SHA160; -public alias SHA2 = SHA256; +class Requirement: Blob { + uint type() => CSMAGIC_REQUIREMENT; + uint length() => 0; + ubyte[] encode(ubyte[][] previousEncodedBlobs) { + return []; + } +} + +enum RequirementType: uint { + host = 1, + guest = 2, + designated = 3, + library = 4, + plugin = 5, +} + +// from rcodesign class RequirementsBlob: Blob { uint type() => CSSLOT_REQUIREMENTS; - ubyte[] encode() => std.bitmanip.nativeToBigEndian(CSMAGIC_REQUIREMENTS) ~ std.bitmanip.nativeToBigEndian(12) ~ std.bitmanip.nativeToBigEndian(0); + uint length() => 4 + 4 + 4; + + // Empty requirements set, + ubyte[] encode(ubyte[][] previousEncodedBlobs) => std.bitmanip.nativeToBigEndian(CSMAGIC_REQUIREMENTS) + ~ std.bitmanip.nativeToBigEndian(length) + ~ std.bitmanip.nativeToBigEndian(0); + + // Unfinished implementation of a real requirement set + // Requirement[] requirements; + // + // ubyte[] encode(ubyte[][] previousEncodedBlobs) { + // ubyte[] data = std.bitmanip.nativeToBigEndian(cast(uint) requirements.length) + // ~ std.bitmanip.nativeToBigEndian(0x3) + // ~ std.bitmanip.nativeToBigEndian(0x14); + // + // return std.bitmanip.nativeToBigEndian(CSMAGIC_REQUIREMENTS) + // ~ std.bitmanip.nativeToBigEndian(4 + 4 + data.length); + // } } class EntitlementsBlob: Blob { @@ -386,24 +675,28 @@ class EntitlementsBlob: Blob { } uint type() => CSSLOT_ENTITLEMENTS; + + uint length() => 4 + 4 + cast(uint) entitlements.length; + // magic + length (sizeof(magic) + sizeof(length) + length of the entitlements string) + entitlements string - ubyte[] encode() => std.bitmanip.nativeToBigEndian(CSMAGIC_EMBEDDED_ENTITLEMENTS) - ~ std.bitmanip.nativeToBigEndian(4 + 4 + cast(uint) entitlements.length) - ~ cast(ubyte[]) entitlements; + ubyte[] encode(ubyte[][] previousEncodedBlobs) => std.bitmanip.nativeToBigEndian(CSMAGIC_EMBEDDED_ENTITLEMENTS) + ~ std.bitmanip.nativeToBigEndian(length) + ~ cast(ubyte[]) entitlements; } class DerEntitlementsBlob: Blob { - PlistDict entitlements; + ubyte[] entitlementsDer; this(PlistDict entitlements) { - this.entitlements = entitlements; + auto encoder = DEREncoder(); + entitlementsDer = encodeEntitlements(entitlements, encoder).getContents()[].dup; } ref DEREncoder encodeEntitlements(Plist elem, return ref DEREncoder encoder) { if (PlistBoolean val = cast(PlistBoolean) elem) { encoder.encode(val.native()); } else if (PlistUint val = cast(PlistUint) elem) { - encoder.encode(val.native()); + encoder.encode(cast(size_t) val); } else if (PlistString val = cast(PlistString) elem) { encoder.encode(ASN1String(val.native(), ASN1Tag.UTF8_STRING)); } else if (PlistArray val = cast(PlistArray) elem) { @@ -437,21 +730,189 @@ class DerEntitlementsBlob: Blob { } uint type() => CSSLOT_DER_ENTITLEMENTS; - ubyte[] encode() { - auto encoder = DEREncoder(); - - auto entitlementsDer = encodeEntitlements(entitlements, encoder).getContents()[].dup; + uint length() => 4 + 4 + cast(uint) entitlementsDer.length; + ubyte[] encode(ubyte[][] previousEncodedBlobs) { return std.bitmanip.nativeToBigEndian(CSMAGIC_EMBEDDED_DER_ENTITLEMENTS) - ~ std.bitmanip.nativeToBigEndian(4 + 4 + cast(uint) entitlementsDer.length) + ~ std.bitmanip.nativeToBigEndian(length) ~ entitlementsDer; } } +class DebugBlob: Blob { + uint _type; + ubyte[] _data; + + this(uint type, ubyte[] data) { + _type = type; + _data = data; + } + + uint type() => _type; + uint length() => cast(uint) _data.length; + + ubyte[] encode(ubyte[][] previousEncodedBlobs) => _data; +} + class SignatureBlob: Blob { uint type() => CSSLOT_SIGNATURESLOT; - ubyte[] encode() => std.bitmanip.nativeToBigEndian(CSMAGIC_BLOBWRAPPER) - ~ std.bitmanip.nativeToBigEndian(4 + 4); + + ubyte[] codeDirectory1; + ubyte[] codeDirectory2; + + CertificateIdentity identity; + + MDxHashFunction[] hashers; + + this(CertificateIdentity identity, MDxHashFunction[] hashers) { + this.identity = identity; + + this.hashers = hashers; + } + + ref DEREncoder encodeBlob(return ref DEREncoder der, ubyte[][] codeDirectories) { // made to match as closely as possible zsign + auto rng = identity.rng; + PKSigner signer = PKSigner(identity.privateKey, "EMSA3(SHA-256)"); + + X509Certificate appleWWDRCert = X509Certificate(Vector!ubyte(appleWWDRG3)); + X509Certificate appleRootCA = X509Certificate(Vector!ubyte(appleRoot)); + + enforce(identity.certificate, "Certificate is null!!"); + + ubyte codeDirHashType(ubyte[] codeDir) pure { + return (cast(CodeDirectoryBlob.CS_CodeDirectory*) codeDir.ptr).hashType; + } + + auto signedAttrs = DEREncoder() + // Attribute + .startCons(ASN1Tag.SEQUENCE) + .encode(OIDS.lookup("PKCS9.ContentType")) + .startCons(ASN1Tag.SET) + .encode(OIDS.lookup("CMS.DataContent")) + .endCons() + .endCons() + // Attribute + .startCons(ASN1Tag.SEQUENCE) + .encode(OID("1.2.840.113549.1.9.5")) // SigningTime + .startCons(ASN1Tag.SET) + .encode(X509Time(Clock.currTime())) + .endCons() + .endCons() + // Attribute + .startCons(ASN1Tag.SEQUENCE) + .encode(OIDS.lookup("PKCS9.MessageDigest")) + .startCons(ASN1Tag.SET) + .encode(hashers[2].process(codeDirectories[0]), ASN1Tag.OCTET_STRING) + .endCons() + .endCons() + // Attribute + .startCons(ASN1Tag.SEQUENCE) + .encode(OID("1.2.840.113635.100.9.2")) + .startCons(ASN1Tag.SET) + .startCons(ASN1Tag.SEQUENCE) + .encode(OIDS.lookup("SHA-256")) + // Don't ask me why I wrote that as is, I just want it to not crash... + .encode(hashers[2].process(codeDirectories.filter!((dir) => codeDirHashType(dir) == 2).array()[0]), ASN1Tag.OCTET_STRING) + .endCons() + .endCons() + .endCons() + // Attribute + .startCons(ASN1Tag.SEQUENCE) + .encode(OID("1.2.840.113635.100.9.1")) + .startCons(ASN1Tag.SET) + .encode( + Vector!ubyte( + dict( + "cdhashes", codeDirectories.map!( + (codeDir) => hashers[codeDirHashType(codeDir)].process(codeDir)[0..20].dup.pl + ).array().pl + ).toXml()[0..$-1] + ), + ASN1Tag.OCTET_STRING + ) + .endCons() + .endCons().getContents(); + + auto attrToSign = DEREncoder() + .startCons(ASN1Tag.SET) + .rawBytes(signedAttrs) + .endCons() + .getContents(); + + der + .startCons(ASN1Tag.SEQUENCE).encode(OIDS.lookup("CMS.SignedData")) + .startCons(ASN1Tag.UNIVERSAL, ASN1Tag.PRIVATE) + // SignedData + .startCons(ASN1Tag.SEQUENCE) + // CMSVersion + .encode(size_t(1)) + // Digest algorithms + .startCons(ASN1Tag.SET) + // DigestAlgorithmIdentifier + .startCons(ASN1Tag.SEQUENCE) + .encode(OIDS.lookup("SHA-256")) + .endCons() + .endCons() + // Encapsulated Content Info + .startCons(ASN1Tag.SEQUENCE) + .encode(OIDS.lookup("CMS.DataContent")) + .endCons() + // CertificateList OPTIONAL tagged 0x01 + .startCons(cast(ASN1Tag) 0x0, ASN1Tag.CONTEXT_SPECIFIC) + .encode(appleWWDRCert) + .encode(appleRootCA) + .encode(identity.certificate) + .endCons() + // SignerInfos + .startCons(ASN1Tag.SET) + .startCons(ASN1Tag.SEQUENCE) + // CMSVersion + .encode(size_t(1)) + // IssuerAndSerialNumber ::= SignerIdentifier + .startCons(ASN1Tag.SEQUENCE) + // Name + .rawBytes(identity.certificate.rawIssuerDn()) + // Serial number + .encode(BigInt.decode(identity.certificate.serialNumber())) + .endCons() + // DigestAlgorithmIdentifier + .startCons(ASN1Tag.SEQUENCE) + // Serial number + .encode(OIDS.lookup("SHA-256")) + .endCons() + // SignedAttributes + .startCons(cast(ASN1Tag) 0x0, ASN1Tag.CONTEXT_SPECIFIC) + .rawBytes(signedAttrs) + .endCons() + // SignatureAlgorithmIdentifier + .encode(AlgorithmIdentifier("RSA", false)) + // SignatureValue + .encode(signer.signMessage(attrToSign, rng), ASN1Tag.OCTET_STRING) + .endCons() + .endCons() + .endCons() + .endCons() + .endCons(); + return der; + } + + uint length() => 5000; + + ubyte[] encode(ubyte[][] previousEncodedBlobs) { + auto codeDirectories = previousEncodedBlobs + .filter!((data) => cast(int) data.read!uint() == CSMAGIC_CODEDIRECTORY) + .array(); + + auto encoder = DEREncoder(); + + auto signatureBlob = encodeBlob(encoder, codeDirectories).getContents()[].dup; + + return ( + std.bitmanip.nativeToBigEndian(CSMAGIC_BLOBWRAPPER) + ~ std.bitmanip.nativeToBigEndian(4 + 4 + cast(uint) signatureBlob.length) + ~ signatureBlob + ).padRight(ubyte(0), length()).array(); + } } class EmbeddedSignature { @@ -470,31 +931,59 @@ class EmbeddedSignature { uint offset; } - void opOpAssign(string op: "~")(Blob rhs) { blobs ~= rhs; } - void opOpAssign(string op: "~")(Blob[] rhs) { blobs ~= rhs; } + uint length() { + return cast(uint) ( + SuperBlob.sizeof + + BlobIndex.sizeof * blobs.length + + blobs.map!((b) => b.length).sum() + ); + } ubyte[] encode() { uint offset = cast(uint) (SuperBlob.sizeof + blobs.length * BlobIndex.sizeof); - ubyte[] blobsData; + ubyte[][] blobsData; BlobIndex[] blobIndexes = new BlobIndex[blobs.length]; + size_t codeDirIndex = -1; + foreach (index, blob; blobs) { - auto blobData = blob.encode(); - blobIndexes[index] = BlobIndex(blob.type.nativeToBigEndian(), offset.nativeToBigEndian()); + auto blobData = blob.encode(blobsData); + auto announcedLength = blob.length(); + auto realLength = blobData.length; + enforce(announcedLength == realLength, format!"%s is lying on its size!!! (announced %d but gave %d)"(blob, announcedLength, realLength)); blobsData ~= blobData; + + if (codeDirIndex == -1 && typeid(cast(Object) blob) == typeid(CodeDirectoryBlob)) { + codeDirIndex = index; + } + } + + foreach (index, blobData; blobsData) { + blobIndexes[index] = BlobIndex(blobs[index].type.nativeToBigEndian(), offset.nativeToBigEndian()); offset += blobData.length; } + auto data = blobsData.join; + return (cast(ubyte*) new SuperBlob( - CSMAGIC_EMBEDDED_SIGNATURE.nativeToBigEndian(), - (cast(uint) (SuperBlob.sizeof + blobs.length * BlobIndex.sizeof + blobsData.length)).nativeToBigEndian(), - (cast(uint) blobs.length).nativeToBigEndian() - ))[0..SuperBlob.sizeof] ~ + CSMAGIC_EMBEDDED_SIGNATURE, + cast(uint) (SuperBlob.sizeof + blobs.length * BlobIndex.sizeof + data.length), + cast(uint) blobs.length + ).nativeToBigEndian())[0..SuperBlob.sizeof] ~ (cast(ubyte[]) blobIndexes) ~ - blobsData; + data; + } +} + +ubyte hashType(MDxHashFunction hashFunction) { + if (cast(SHA160) hashFunction) { + return 1; + } else if (cast(SHA256) hashFunction) { + return 2; } + throw new UnknownHashFunction(); } T bigEndianToNative(T)(T val) if (isIntegral!T) { @@ -505,6 +994,16 @@ T bigEndianToNative(T)(T val) if (isIntegral!T) { } } +pragma(inline, true) +T pageCeil(T)(T val) { + return (val + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1); +} + +pragma(inline, true) +T pageFloor(T)(T val) { + return (val) & ~(PAGE_SIZE - 1); +} + alias nativeToBigEndian = bigEndianToNative; enum CPU_ARCH_ABI64 = 0x1000000; @@ -535,15 +1034,21 @@ struct fat_arch { uint32_t align_; } +class UnknownHashFunction: Exception { + this(string filename = __FILE__, size_t line = __LINE__) { + super("An unknown hash function has been provided", filename, line); + } +} + class UnsupportedEntitlementsException: Exception { this(string filename = __FILE__, size_t line = __LINE__) { super("The entitlements contains unsupported tags", filename, line); } } -class NeverSignedException: Exception { +class SegmentAllocationFailedException: Exception { this(string filename = __FILE__, size_t line = __LINE__) { - super("The executable has never been signed before (cannot find any code signature command in the executable)", filename, line); + super("Cannot allocate a code signature segment in the binary", filename, line); } } diff --git a/source/sideload/package.d b/source/sideload/package.d index 3cbc388..f9d3723 100644 --- a/source/sideload/package.d +++ b/source/sideload/package.d @@ -16,24 +16,20 @@ import imobiledevice; import server.developersession; -public import sideload.bundle; public import sideload.application; +public import sideload.bundle; import sideload.certificateidentity; +import sideload.sign; import main; -void sign(DeveloperSession developer, Application app) { - auto teams = developer.listTeams().unwrap(); - auto team = teams[0]; -} - void sideloadFull( iDevice device, DeveloperSession developer, Application app, void delegate(double progress, string action) progressCallback, ) { - enum STEP_COUNT = 10.0; + enum STEP_COUNT = 9.0; auto log = getLogger(); bool isSideStore = app.bundleIdentifier() == "com.SideStore.SideStore"; @@ -63,24 +59,28 @@ void sideloadFull( progressCallback(4 / STEP_COUNT, "Creating App IDs for the application"); string mainAppBundleId = app.bundleIdentifier(); string mainAppIdStr = mainAppBundleId ~ "." ~ team.teamId; + app.bundleIdentifier = mainAppIdStr; string mainAppName = app.bundleName(); + auto listAppIdResponse = developer.listAppIds!iOS(team).unwrap(); - app.appId = mainAppIdStr; - foreach (plugin; app.plugIns) { + auto appExtensions = app.appExtensions(); + + foreach (ref plugin; appExtensions) { string pluginBundleIdentifier = plugin.bundleIdentifier(); assertBundle( pluginBundleIdentifier.startsWith(mainAppBundleId) && pluginBundleIdentifier.length > mainAppBundleId.length, "Plug-ins are not formed with the main app bundle identifier" ); - plugin.appId = mainAppIdStr ~ pluginBundleIdentifier[mainAppBundleId.length..$]; + plugin.bundleIdentifier = mainAppIdStr ~ pluginBundleIdentifier[mainAppBundleId.length..$]; } - Bundle[] bundlesNeeded = [cast(Bundle) app] ~ app.plugIns; + + auto bundlesWithAppID = app ~ appExtensions; // Search which App IDs have to be registered (we don't want to start registering App IDs if we don't // have enough of them to register them all!! otherwise we will waste their precious App IDs) - auto appIdsToRegister = bundlesNeeded.filter!((bundle) => !listAppIdResponse.appIds.canFind!((a) => a.identifier == bundle.appId)).array(); + auto appIdsToRegister = bundlesWithAppID.filter!((bundle) => !listAppIdResponse.appIds.canFind!((a) => a.identifier == bundle.bundleIdentifier())).array(); if (appIdsToRegister.length > listAppIdResponse.availableQuantity) { auto minDate = listAppIdResponse.appIds.map!((appId) => appId.expirationDate).minElement(); @@ -88,11 +88,11 @@ void sideloadFull( } foreach (bundle; appIdsToRegister) { - log.infoF!"Creating App ID `%s`..."(bundle.appId); - developer.addAppId!iOS(team, bundle.appId, bundle.bundleName).unwrap(); + log.infoF!"Creating App ID `%s`..."(bundle.bundleIdentifier); + developer.addAppId!iOS(team, bundle.bundleIdentifier, bundle.bundleName).unwrap(); } listAppIdResponse = developer.listAppIds!iOS(team).unwrap(); - auto appIds = listAppIdResponse.appIds.filter!((appId) => bundlesNeeded.canFind!((bundle) => appId.identifier == bundle.appId)).array(); + auto appIds = listAppIdResponse.appIds.filter!((appId) => bundlesWithAppID.canFind!((bundle) => appId.identifier == bundle.bundleIdentifier())).array(); auto mainAppId = appIds.find!((appId) => appId.identifier == mainAppIdStr)[0]; foreach (ref appId; appIds) { @@ -105,6 +105,11 @@ void sideloadFull( // create an app group for it if needed progressCallback(5 / STEP_COUNT, "Creating an application group"); auto groupIdentifier = "group." ~ mainAppIdStr; + + if (isSideStore) { + app.appInfo["ALTAppGroups"] = [groupIdentifier.pl].pl; + } + auto appGroups = developer.listApplicationGroups!iOS(team).unwrap(); auto matchingAppGroups = appGroups.find!((appGroup) => appGroup.identifier == groupIdentifier).array(); ApplicationGroup appGroup; @@ -114,68 +119,32 @@ void sideloadFull( appGroup = matchingAppGroups[0]; } - progressCallback(6 / STEP_COUNT, "Assign App IDs to the application group"); + progressCallback(6 / STEP_COUNT, "Manage App IDs and groups"); + ProvisioningProfile[string] provisioningProfiles; foreach (appId; appIds) { developer.assignApplicationGroupToAppId!iOS(team, appId, appGroup).unwrap(); + provisioningProfiles[appId.identifier] = developer.downloadTeamProvisioningProfile!iOS(team, mainAppId).unwrap(); } - // fetch the mobileprovision file for it - progressCallback(7 / STEP_COUNT, "Fetching mobileprovision file for the application"); - // auto profile = developer.downloadTeamProvisioningProfile!iOS(team, mainAppId).unwrap(); - // sign the app with all the retrieved material! - progressCallback(8 / STEP_COUNT, "Signing the application bundle"); - // file.write(app.bundleDir.buildPath("embedded.mobileprovision"), profile.encodedProfile); - - import std.process; - - string entitlementsPath = "/tmp/entitlements.xml"; - foreach (bundle; bundlesNeeded) { - auto appId = appIds.find!((appId) => appId.identifier == bundle.appId)[0]; - auto profile = developer.downloadTeamProvisioningProfile!iOS(team, appId).unwrap(); - auto embeddedMobileProvision = bundle.bundleDir.buildPath("embedded.mobileprovision"); - log.debugF!"Mobile provision file: %s"(embeddedMobileProvision); - file.write(embeddedMobileProvision, profile.encodedProfile); - - import cms.cms_dec; - auto provisioningProfilePlist = Plist.fromMemory(dataFromCMS(profile.encodedProfile)); - file.write(entitlementsPath, provisioningProfilePlist["Entitlements"].toXml()); - - auto infoPlistPath = bundle.bundleDir.buildPath("Info.plist"); - auto infoPlist = Plist.fromMemory(cast(ubyte[]) file.read(infoPlistPath)).dict(); - if (isSideStore) { - // TODO discuss about this, because if apple forbids that, it's to avoid fingerprinting. - // TODO change SideKit to not require that - infoPlist["ALTDeviceID"] = deviceUdid.pl; - // TODO remove this, SideStore should read that from their mobileprovision file instead - infoPlist["ALTAppGroups"] = [appGroup.identifier.pl].pl; - } - infoPlist["CFBundleIdentifier"] = appId.identifier.pl; - - file.write(infoPlistPath, infoPlist.toXml()); - - // auto codesignProcess = ["rcodesign", "sign", "-e", entitlementsPath, "--pem-source", certIdentity.keyFile, "--der-source", certIdentity.certFile, bundle.bundleDir]; - auto codesignProcess = ["zsign", "-e", entitlementsPath, "-m", embeddedMobileProvision, "-k", certIdentity.keyFile, "-c", certIdentity.certFile, bundle.bundleDir]; - // auto codesignProcess = ["ldid", "-K" ~ "/home/dadoum/.config/Sideloader/keys/1dad95c5ddb3ceed75aac3d4fcf229f63f9fb811/cert.p12", "-M", "-S" ~ entitlementsPath, bundle.bundleDir]; - log.debugF!"> %s"(codesignProcess.join(' ')); - wait(spawnProcess(codesignProcess)); - file.write(embeddedMobileProvision, profile.encodedProfile); - } + progressCallback(7 / STEP_COUNT, "Signing the application bundle"); + double accumulator = 0; + sign(app, certIdentity, provisioningProfiles, (progress) => progressCallback((7 + (accumulator += progress)) / STEP_COUNT, "Signing the application bundle")); // connect to the device's installation daemon and send to it the signed app - double progress = 9 / STEP_COUNT; + double progress = 8 / STEP_COUNT; progressCallback(progress, "Installing the application on the device"); - auto lockdownClient = new LockdowndClient(device, "sideloader.app_install"); + scope lockdownClient = new LockdowndClient(device, "sideloader.app_install"); // set up clients and proxies auto installationProxyService = lockdownClient.startService("com.apple.mobile.installation_proxy"); - auto installationProxyClient = new InstallationProxyClient(device, installationProxyService); + scope installationProxyClient = new InstallationProxyClient(device, installationProxyService); - auto misagentService = lockdownClient.startService("com.apple.misagent"); - auto misagentClient = new MisagentClient(device, misagentService); + scope misagentService = lockdownClient.startService("com.apple.misagent"); + scope misagentClient = new MisagentClient(device, misagentService); - auto afcService = lockdownClient.startService("com.apple.afc"); - auto afcClient = new AFCClient(device, afcService); + scope afcService = lockdownClient.startService("com.apple.afc"); + scope afcClient = new AFCClient(device, afcService); string stagingDir = "PublicStaging"; @@ -230,7 +199,7 @@ void sideloadFull( } progressCallback( - progress + (status["PercentComplete"].uinteger() / 400.0), + progress + (status["PercentComplete"].uinteger().native() / (400.0 * STEP_COUNT)), format!"Installing the application on the device (%s)"(statusEntry.str().native()) ); } else { diff --git a/source/sideload/sign.d b/source/sideload/sign.d new file mode 100644 index 0000000..8eefa70 --- /dev/null +++ b/source/sideload/sign.d @@ -0,0 +1,345 @@ +module sideload.sign; + +import std.algorithm; +import std.exception; +import std.format; +import file = std.file; +import std.mmfile; +import std.parallelism; +import std.path; +import std.range; +import std.string; +import std.typecons; + +import slf4d; + +import botan.hash.mdx_hash; +import botan.libstate.lookup; + +import plist; + +import cms.cms_dec; + +import server.developersession; + +import sideload.bundle; +import sideload.certificateidentity; +import sideload.macho; + +Tuple!(PlistDict, PlistDict) sign( + Bundle bundle, + CertificateIdentity identity, + ProvisioningProfile[string] provisioningProfiles, + void delegate(double progress) addProgress, + string teamId = null, + MDxHashFunction sha1Hasher = null, + MDxHashFunction sha2Hasher = null, +) { + auto log = getLogger(); + + auto bundleFolder = bundle.bundleDir; + auto bundleId = bundle.bundleIdentifier(); + + PlistDict files = new PlistDict(); + PlistDict files2 = new PlistDict(); + + if (!sha1Hasher) { + sha1Hasher = cast(MDxHashFunction) retrieveHash("SHA-1"); + } + if (!sha2Hasher) { + sha2Hasher = cast(MDxHashFunction) retrieveHash("SHA-256"); + } + + auto sha1HasherParallel = taskPool().workerLocalStorage!MDxHashFunction(cast(MDxHashFunction) sha1Hasher.clone()); + auto sha2HasherParallel = taskPool().workerLocalStorage!MDxHashFunction(cast(MDxHashFunction) sha2Hasher.clone()); + + auto lprojFinder = boyerMooreFinder(".lproj"); + + string infoPlist = bundle.appInfo.toBin(); + + auto profile = bundle.bundleIdentifier() in provisioningProfiles; + ubyte[] profileData; + Plist profilePlist; + + if (profile) { + profileData = profile.encodedProfile; + file.write(bundleFolder.buildPath("embedded.mobileprovision"), profileData); + profilePlist = Plist.fromMemory(dataFromCMS(profileData)); + teamId = profilePlist["TeamIdentifier"].array[0].str().native(); + } + + auto subBundles = bundle.subBundles(); + + size_t stepCount = subBundles.length + 2; + const double stepSize = 1.0 / stepCount; + + auto subBundlesTask = task({ + foreach (subBundle; parallel(subBundles)) { + auto bundleFiles = subBundle.sign( + identity, + provisioningProfiles, + (double progress) => addProgress(progress * stepSize), + teamId, + sha1HasherParallel.get(), + sha2HasherParallel.get() + ); + auto subBundlePath = subBundle.bundleDir; + + auto bundleFiles1 = bundleFiles[0]; + auto bundleFiles2 = bundleFiles[1]; + + auto subFolder = subBundlePath.relativePath(/+ base +/ bundleFolder); + + void reroot(ref PlistDict dict, ref PlistDict subDict) { + auto iter = subDict.iter(); + + string key; + Plist element; + + synchronized { + while (iter.next(element, key)) { + dict[subFolder.buildPath(key)] = element.copy(); + } + } + } + reroot(files, bundleFiles1); + reroot(files2, bundleFiles2); + + void addFile(string subRelativePath) { + ubyte[] sha1 = new ubyte[](20); + ubyte[] sha2 = new ubyte[](32); + + auto localHasher1 = sha1HasherParallel.get(); + auto localHasher2 = sha2HasherParallel.get(); + + auto hashPairs = [tuple(localHasher1, sha1), tuple(localHasher2, sha2)]; + + scope MmFile memoryFile = new MmFile(subBundle.bundleDir.buildPath(subRelativePath)); + ubyte[] fileData = cast(ubyte[]) memoryFile[]; + + foreach (hashCouple; parallel(hashPairs)) { + auto localHasher = hashCouple[0]; + auto sha = hashCouple[1]; + sha[] = localHasher.process(fileData)[]; + } + + synchronized { + files[subFolder.buildPath(subRelativePath)] = sha1.pl; + files2[subFolder.buildPath(subRelativePath)] = dict( + "hash", sha1, + "hash2", sha2 + ); + } + } + addFile("_CodeSignature".buildPath("CodeResources")); + addFile(subBundle.appInfo["CFBundleExecutable"].str().native()); + } + }); + subBundlesTask.executeInNewThread(); + + log.traceF!"Signing bundle %s..."(baseName(bundleFolder)); + + string executable = bundle.appInfo["CFBundleExecutable"].str().native(); + + string codeSignatureFolder = bundleFolder.buildPath("_CodeSignature"); + string codeResourcesFile = codeSignatureFolder.buildPath("CodeResources"); + + if (file.exists(codeSignatureFolder)) { + if (file.exists(codeResourcesFile)) { + file.remove(codeResourcesFile); + } + } else { + file.mkdir(codeSignatureFolder); + } + + file.write(bundleFolder.buildPath("Info.plist"), infoPlist); + + // log.trace("Hashing files..."); + + auto bundleFiles = file.dirEntries(bundleFolder, file.SpanMode.breadth); + // double fileStepSize = stepSize / bundleFiles.length; TODO + + if (bundleFolder[$ - 1] == '/' || bundleFolder[$ - 1] == '\\') bundleFolder.length -= 1; + foreach(idx, absolutePath; parallel(bundleFiles)) { + // scope(exit) addProgress(fileStepSize); + + string basename = baseName(absolutePath); + string relativePath = absolutePath[bundleFolder.length + 1..$]; + + enum frameworksDir = "Frameworks/"; + enum plugInsDir = "PlugIns/"; + + if (!file.isFile(absolutePath) + || relativePath == executable + || (relativePath.startsWith(frameworksDir) && relativePath[frameworksDir.length..$].canFind('/')) + || (relativePath.startsWith(plugInsDir) && relativePath[plugInsDir.length..$].canFind('/')) + ) { + continue; + } + + ubyte[] sha1 = new ubyte[](20); + ubyte[] sha2 = new ubyte[](32); + + auto localHasher1 = sha1HasherParallel.get(); + auto localHasher2 = sha2HasherParallel.get(); + + auto hashPairs = [tuple(localHasher1, sha1), tuple(localHasher2, sha2)]; + + if (file.getSize(absolutePath) > 0) { + scope MmFile memoryFile = new MmFile(absolutePath); + ubyte[] fileData = cast(ubyte[]) memoryFile[]; + + foreach (hashCouple; parallel(hashPairs)) { + auto localHasher = hashCouple[0]; + auto sha = hashCouple[1]; + sha[] = localHasher.process(fileData)[]; + } + } else { + foreach (hashCouple; parallel(hashPairs)) { + auto localHasher = hashCouple[0]; + auto sha = hashCouple[1]; + sha[] = localHasher.process(cast(ubyte[]) [])[]; + } + } + + Plist hashes1 = sha1.pl; + + PlistDict hashes2 = dict( + "hash", sha1, + "hash2", sha2 + ); + + if (lprojFinder.beFound(relativePath) != null) { + hashes1 = dict( + "hash", hashes1, + "optional", true + ); + + hashes2["optional"] = true.pl; + } + + synchronized { + files[relativePath] = hashes1; + files2[relativePath] = hashes2; + } + } + // too lazy yet to add better progress tracking + addProgress(stepSize); + + subBundlesTask.yieldForce(); + + // log.trace("Making CodeResources..."); + string codeResources = dict( + "files", files.copy(), + "files2", files2.copy(), + // Rules have been copied from zsign + "rules", rules(), + "rules2", rules2() + ).toXml(); + file.write(codeResourcesFile, codeResources); + + string executablePath = bundleFolder.buildPath(executable); + PlistDict entitlements = profilePlist ? profilePlist["Entitlements"].dict : new PlistDict(); + + auto fatMachOs = (bundle.libraries() ~ executable).map!((f) { + auto path = bundleFolder.buildPath(f); + return tuple!("path", "machO")(path, MachO.parse(cast(ubyte[]) file.read(path))); + }); + + double machOStepSize = stepSize / fatMachOs.length; + + foreach (fatMachOPair; parallel(fatMachOs)) { + scope(exit) addProgress(machOStepSize); + auto path = fatMachOPair.path; + auto fatMachO = fatMachOPair.machO; + log.traceF!"Signing executable %s..."(path[bundleFolder.dirName.length + 1..$]); + + auto requirementsBlob = new RequirementsBlob(); + auto entitlementsBlob = new EntitlementsBlob(entitlements.toXml()); + auto derEntitlementsBlob = new DerEntitlementsBlob(entitlements); + + foreach (machO; fatMachO) { + auto embeddedSignature = new EmbeddedSignature(); + // TODO: don't recompute entitlements and derentitlements blob each time. + embeddedSignature.blobs = cast(Blob[]) [ + requirementsBlob, + entitlementsBlob, + derEntitlementsBlob, + new CodeDirectoryBlob(sha1HasherParallel.get(), bundleId, teamId, machO, entitlements, infoPlist, codeResources), + new CodeDirectoryBlob(sha2HasherParallel.get(), bundleId, teamId, machO, entitlements, infoPlist, codeResources, true), + new SignatureBlob(identity, [null, sha1HasherParallel.get(), sha2HasherParallel.get()]) + ]; + + machO.replaceCodeSignature(new ubyte[](embeddedSignature.length())); + + auto encodedBlob = embeddedSignature.encode(); + enforce(!machO.replaceCodeSignature(encodedBlob)); + } + + file.write(path, makeMachO(fatMachO)); + } + + return tuple(files, files2); +} + +Plist rules() { + return dict( + "^.*", true, + "^.*\\.lproj/", dict( + "optional", true, + "weight", 1000. + ), + "^.*\\.lproj/locversion.plist$", dict( + "omit", true, + "weight", 1100. + ), + "^Base\\.lproj/", dict( + "weight", 1010. + ), + "^version.plist$", true + ); +} + +Plist rules2() { + return dict( + ".*\\.dSYM($|/)", dict( + "weight", 11. + ), + "^(.*/)?\\.DS_Store$", dict( + "omit", true, + "weight", 2000. + ), + "^.*", true, + "^.*\\.lproj/", dict( + "optional", true, + "weight", 1000. + ), + "^.*\\.lproj/locversion.plist$", dict( + "omit", true, + "weight", 1100. + ), + "^Base\\.lproj/", dict( + "weight", 1010. + ), + "^Info\\.plist$", dict( + "omit", true, + "weight", 20. + ), + "^PkgInfo$", dict( + "omit", true, + "weight", 20. + ), + "^embedded\\.provisionprofile$", dict( + "weight", 20. + ), + "^version\\.plist$", dict( + "weight", 20. + ) + ); +} + +class InvalidApplicationException: Exception { + this(string message, string file = __FILE__, size_t line = __LINE__) { + super(format!"Cannot sign the application : %s"(message)); + } +} diff --git a/source/sse2/package.d b/source/sse2/package.d new file mode 100644 index 0000000..d69b8c4 --- /dev/null +++ b/source/sse2/package.d @@ -0,0 +1,75 @@ +module sse2; + +import botan.constants; +import botan.engine.engine; +import botan.utils.cpuid; + +import sse2.sha1_sse2; +import sse2.sha2_sse2; + +/** +* Engine for implementations that use some kind of SIMD +*/ +final class SHA256SIMDEngine : Engine +{ + public: + string providerName() const { return "aes_isa"; } // HACK: get priority over all the other engines. + + BlockCipher findBlockCipher(in SCANToken request, AlgorithmFactory) const + { + return null; + } + + HashFunction findHash(in SCANToken request, AlgorithmFactory) const + { + static if (BOTAN_HAS_SHA1 && BOTAN_HAS_SIMD_SSE2) { + if (request.algoName == "SHA-160" && CPUID.hasSse2()) + return new SHA160SSE2_2; + } + + static if (BOTAN_HAS_SHA2_32 && BOTAN_HAS_SIMD_SSE2) { + if (request.algoName == "SHA-256" && CPUID.hasSse2()) + return new SHA256SSE2; + } + + return null; + } + + + StreamCipher findStreamCipher(in SCANToken algo_spec, AlgorithmFactory af) const + { return null; } + + MessageAuthenticationCode findMac(in SCANToken algo_spec, AlgorithmFactory af) const + { return null; } + + PBKDF findPbkdf(in SCANToken algo_spec, AlgorithmFactory af) const + { return null; } + + KeyedFilter getCipher(in string algo_spec, CipherDir dir, AlgorithmFactory af) const + { return null; } + + static if (BOTAN_HAS_PUBLIC_KEY_CRYPTO): + + ModularExponentiator modExp(const(BigInt)* n, PowerMod.UsageHints hints) const + { return null; } + + KeyAgreement getKeyAgreementOp(in PrivateKey key, RandomNumberGenerator rng) const + { return null; } + + Signature getSignatureOp(in PrivateKey key, RandomNumberGenerator rng) const + { return null; } + + Verification getVerifyOp(in PublicKey key, RandomNumberGenerator rng) const + { return null; } + + Encryption getEncryptionOp(in PublicKey key, RandomNumberGenerator rng) const + { return null; } + + Decryption getDecryptionOp(in PrivateKey key, RandomNumberGenerator rng) const + { return null; } +} + +void register() { + import botan.libstate.libstate; + globalState().algorithmFactory().addEngine(new SHA256SIMDEngine); +} diff --git a/source/sse2/sha1_sse2.d b/source/sse2/sha1_sse2.d new file mode 100644 index 0000000..14d3711 --- /dev/null +++ b/source/sse2/sha1_sse2.d @@ -0,0 +1,352 @@ +module sse2.sha1_sse2; + +import botan.constants; + +version (GNU) { + enum GDC = true; +} else { + enum GDC = false; +} + +static if (BOTAN_HAS_SHA1 && (BOTAN_HAS_SIMD_SSE2 || GDC)): + +import botan.hash.sha160; +import botan.utils.rotate; +import botan.hash.hash; +import std.format : format; + +import inteli.smmintrin; +import inteli.shaintrin; + +/** +* SHA-160 using SSE2 for the message expansion +*/ +class SHA160SSE2_2 : SHA160 +{ + public: + override HashFunction clone() const { return new SHA160SSE2_2; } + this() + { + super(0); + } // no W needed + + protected: + /* + * SHA-160 Compression Function using SSE for message expansion + */ + override void compressN(const(ubyte)* input_bytes, size_t blocks) + { + const(__m128i) K00_19 = _mm_set1_epi32(0x5A827999); + const(__m128i) K20_39 = _mm_set1_epi32(0x6ED9EBA1); + const(__m128i) K40_59 = _mm_set1_epi32(0x8F1BBCDC); + const(__m128i) K60_79 = _mm_set1_epi32(0xCA62C1D6); + + uint A = m_digest[0], + B = m_digest[1], + C = m_digest[2], + D = m_digest[3], + E = m_digest[4]; + + __m128i* input = cast(__m128i*)(input_bytes); + + foreach (size_t i; 0 .. blocks) + { + union v4si { + uint[4] u32; + __m128i u128; + } + + v4si P0, P1, P2, P3; + + __m128i W0 = _mm_loadu_si128(input); + mixin(prep00_15!(P0, W0)); + + __m128i W1 = _mm_loadu_si128(&input[1]); + mixin(prep00_15!(P1, W1)); + + __m128i W2 = _mm_loadu_si128(&input[2]); + mixin(prep00_15!(P2, W2)); + + __m128i W3 = _mm_loadu_si128(&input[3]); + mixin(prep00_15!(P3, W3)); + + + mixin(` + F1(A, B, C, D, E, ` ~ GET_P_32!(P0, 0) ~ `); + F1(E, A, B, C, D, ` ~ GET_P_32!(P0, 1) ~ `); + F1(D, E, A, B, C, ` ~ GET_P_32!(P0, 2) ~ `); + F1(C, D, E, A, B, ` ~ GET_P_32!(P0, 3) ~ `); + ` ~ prep!(P0, W0, W1, W2, W3, K00_19) ~ ` + + F1(B, C, D, E, A, ` ~ GET_P_32!(P1, 0) ~ `); + F1(A, B, C, D, E, ` ~ GET_P_32!(P1, 1) ~ `); + F1(E, A, B, C, D, ` ~ GET_P_32!(P1, 2) ~ `); + F1(D, E, A, B, C, ` ~ GET_P_32!(P1, 3) ~ `); + ` ~ prep!(P1, W1, W2, W3, W0, K20_39) ~ ` + + F1(C, D, E, A, B, ` ~ GET_P_32!(P2, 0) ~ `); + F1(B, C, D, E, A, ` ~ GET_P_32!(P2, 1) ~ `); + F1(A, B, C, D, E, ` ~ GET_P_32!(P2, 2) ~ `); + F1(E, A, B, C, D, ` ~ GET_P_32!(P2, 3) ~ `); + ` ~ prep!(P2, W2, W3, W0, W1, K20_39) ~ ` + + F1(D, E, A, B, C, ` ~ GET_P_32!(P3, 0) ~ `); + F1(C, D, E, A, B, ` ~ GET_P_32!(P3, 1) ~ `); + F1(B, C, D, E, A, ` ~ GET_P_32!(P3, 2) ~ `); + F1(A, B, C, D, E, ` ~ GET_P_32!(P3, 3) ~ `); + ` ~ prep!(P3, W3, W0, W1, W2, K20_39) ~ ` + + F1(E, A, B, C, D, ` ~ GET_P_32!(P0, 0) ~ `); + F1(D, E, A, B, C, ` ~ GET_P_32!(P0, 1) ~ `); + F1(C, D, E, A, B, ` ~ GET_P_32!(P0, 2) ~ `); + F1(B, C, D, E, A, ` ~ GET_P_32!(P0, 3) ~ `); + ` ~ prep!(P0, W0, W1, W2, W3, K20_39) ~ ` + + F2(A, B, C, D, E, ` ~ GET_P_32!(P1, 0) ~ `); + F2(E, A, B, C, D, ` ~ GET_P_32!(P1, 1) ~ `); + F2(D, E, A, B, C, ` ~ GET_P_32!(P1, 2) ~ `); + F2(C, D, E, A, B, ` ~ GET_P_32!(P1, 3) ~ `); + ` ~ prep!(P1, W1, W2, W3, W0, K20_39) ~ ` + + F2(B, C, D, E, A, ` ~ GET_P_32!(P2, 0) ~ `); + F2(A, B, C, D, E, ` ~ GET_P_32!(P2, 1) ~ `); + F2(E, A, B, C, D, ` ~ GET_P_32!(P2, 2) ~ `); + F2(D, E, A, B, C, ` ~ GET_P_32!(P2, 3) ~ `); + ` ~ prep!(P2, W2, W3, W0, W1, K40_59) ~ ` + + F2(C, D, E, A, B, ` ~ GET_P_32!(P3, 0) ~ `); + F2(B, C, D, E, A, ` ~ GET_P_32!(P3, 1) ~ `); + F2(A, B, C, D, E, ` ~ GET_P_32!(P3, 2) ~ `); + F2(E, A, B, C, D, ` ~ GET_P_32!(P3, 3) ~ `); + ` ~ prep!(P3, W3, W0, W1, W2, K40_59) ~ ` + + F2(D, E, A, B, C, ` ~ GET_P_32!(P0, 0) ~ `); + F2(C, D, E, A, B, ` ~ GET_P_32!(P0, 1) ~ `); + F2(B, C, D, E, A, ` ~ GET_P_32!(P0, 2) ~ `); + F2(A, B, C, D, E, ` ~ GET_P_32!(P0, 3) ~ `); + ` ~ prep!(P0, W0, W1, W2, W3, K40_59) ~ ` + + F2(E, A, B, C, D, ` ~ GET_P_32!(P1, 0) ~ `); + F2(D, E, A, B, C, ` ~ GET_P_32!(P1, 1) ~ `); + F2(C, D, E, A, B, ` ~ GET_P_32!(P1, 2) ~ `); + F2(B, C, D, E, A, ` ~ GET_P_32!(P1, 3) ~ `); + ` ~ prep!(P1, W1, W2, W3, W0, K40_59) ~ ` + + F3(A, B, C, D, E, ` ~ GET_P_32!(P2, 0) ~ `); + F3(E, A, B, C, D, ` ~ GET_P_32!(P2, 1) ~ `); + F3(D, E, A, B, C, ` ~ GET_P_32!(P2, 2) ~ `); + F3(C, D, E, A, B, ` ~ GET_P_32!(P2, 3) ~ `); + ` ~ prep!(P2, W2, W3, W0, W1, K40_59) ~ ` + + F3(B, C, D, E, A, ` ~ GET_P_32!(P3, 0) ~ `); + F3(A, B, C, D, E, ` ~ GET_P_32!(P3, 1) ~ `); + F3(E, A, B, C, D, ` ~ GET_P_32!(P3, 2) ~ `); + F3(D, E, A, B, C, ` ~ GET_P_32!(P3, 3) ~ `); + ` ~ prep!(P3, W3, W0, W1, W2, K60_79) ~ ` + + F3(C, D, E, A, B, ` ~ GET_P_32!(P0, 0) ~ `); + F3(B, C, D, E, A, ` ~ GET_P_32!(P0, 1) ~ `); + F3(A, B, C, D, E, ` ~ GET_P_32!(P0, 2) ~ `); + F3(E, A, B, C, D, ` ~ GET_P_32!(P0, 3) ~ `); + ` ~ prep!(P0, W0, W1, W2, W3, K60_79) ~ ` + + F3(D, E, A, B, C, ` ~ GET_P_32!(P1, 0) ~ `); + F3(C, D, E, A, B, ` ~ GET_P_32!(P1, 1) ~ `); + F3(B, C, D, E, A, ` ~ GET_P_32!(P1, 2) ~ `); + F3(A, B, C, D, E, ` ~ GET_P_32!(P1, 3) ~ `); + ` ~ prep!(P1, W1, W2, W3, W0, K60_79) ~ ` + + F3(E, A, B, C, D, ` ~ GET_P_32!(P2, 0) ~ `); + F3(D, E, A, B, C, ` ~ GET_P_32!(P2, 1) ~ `); + F3(C, D, E, A, B, ` ~ GET_P_32!(P2, 2) ~ `); + F3(B, C, D, E, A, ` ~ GET_P_32!(P2, 3) ~ `); + ` ~ prep!(P2, W2, W3, W0, W1, K60_79) ~ ` + + F4(A, B, C, D, E, ` ~ GET_P_32!(P3, 0) ~ `); + F4(E, A, B, C, D, ` ~ GET_P_32!(P3, 1) ~ `); + F4(D, E, A, B, C, ` ~ GET_P_32!(P3, 2) ~ `); + F4(C, D, E, A, B, ` ~ GET_P_32!(P3, 3) ~ `); + ` ~ prep!(P3, W3, W0, W1, W2, K60_79) ~ ` + + F4(B, C, D, E, A, ` ~ GET_P_32!(P0, 0) ~ `); + F4(A, B, C, D, E, ` ~ GET_P_32!(P0, 1) ~ `); + F4(E, A, B, C, D, ` ~ GET_P_32!(P0, 2) ~ `); + F4(D, E, A, B, C, ` ~ GET_P_32!(P0, 3) ~ `); + + F4(C, D, E, A, B, ` ~ GET_P_32!(P1, 0) ~ `); + F4(B, C, D, E, A, ` ~ GET_P_32!(P1, 1) ~ `); + F4(A, B, C, D, E, ` ~ GET_P_32!(P1, 2) ~ `); + F4(E, A, B, C, D, ` ~ GET_P_32!(P1, 3) ~ `); + + F4(D, E, A, B, C, ` ~ GET_P_32!(P2, 0) ~ `); + F4(C, D, E, A, B, ` ~ GET_P_32!(P2, 1) ~ `); + F4(B, C, D, E, A, ` ~ GET_P_32!(P2, 2) ~ `); + F4(A, B, C, D, E, ` ~ GET_P_32!(P2, 3) ~ `); + + F4(E, A, B, C, D, ` ~ GET_P_32!(P3, 0) ~ `); + F4(D, E, A, B, C, ` ~ GET_P_32!(P3, 1) ~ `); + F4(C, D, E, A, B, ` ~ GET_P_32!(P3, 2) ~ `); + F4(B, C, D, E, A, ` ~ GET_P_32!(P3, 3) ~ `);`); + + A = (m_digest[0] += A); + B = (m_digest[1] += B); + C = (m_digest[2] += C); + D = (m_digest[3] += D); + E = (m_digest[4] += E); + + input += (hashBlockSize / 16); + } + } + +} + + +private: + +/* +* First 16 bytes just need ubyte swapping. Preparing just means +* adding in the round constants. +*/ + +/* + Using SSE4; slower on Core2 and Nehalem + #define GET_P_32(P, i) _mm_extract_epi32(P.u128, i) + + Much slower on all tested platforms + #define GET_P_32(P,i) _mm_cvtsi128_si32(_mm_srli_si128(P.u128, i*4)) +*/ +enum string GET_P_32(alias P, ubyte i) = +BOTAN_FORCE_SSE4 +? `_mm_extract_epi32(` ~ __traits(identifier, P).stringof ~ `.u128, ` ~ i.stringof ~ `)` +: __traits(identifier, P) ~ `.u32[` ~ i.stringof ~ `]`; + +enum string prep00_15(alias P, alias _W) = q{ + { + enum SHUF = _MM_SHUFFLE(2, 3, 0, 1); + %1$s = _mm_shufflehi_epi16!SHUF(%1$s); + %1$s = _mm_shufflelo_epi16!SHUF(%1$s); + %1$s = _mm_or_si128(_mm_slli_epi16(%1$s, 8), _mm_srli_epi16(%1$s, 8)); + %2$s.u128 = _mm_add_epi32(%1$s, K00_19); + } +}.format(__traits(identifier, _W), __traits(identifier, P)); + +/* +For each multiple of 4, t, we want to calculate this: + +W[t+0] = rol(W[t-3] ^ W[t-8] ^ W[t-14] ^ W[t-16], 1); +W[t+1] = rol(W[t-2] ^ W[t-7] ^ W[t-13] ^ W[t-15], 1); +W[t+2] = rol(W[t-1] ^ W[t-6] ^ W[t-12] ^ W[t-14], 1); +W[t+3] = rol(W[t] ^ W[t-5] ^ W[t-11] ^ W[t-13], 1); + +we'll actually calculate this: + +W[t+0] = rol(W[t-3] ^ W[t-8] ^ W[t-14] ^ W[t-16], 1); +W[t+1] = rol(W[t-2] ^ W[t-7] ^ W[t-13] ^ W[t-15], 1); +W[t+2] = rol(W[t-1] ^ W[t-6] ^ W[t-12] ^ W[t-14], 1); +W[t+3] = rol( 0 ^ W[t-5] ^ W[t-11] ^ W[t-13], 1); +W[t+3] ^= rol(W[t+0], 1); + +the parameters are: + +W0 = &W[t-16]; +W1 = &W[t-12]; +W2 = &W[t- 8]; +W3 = &W[t- 4]; + +and on output: +prepared = W0 + K +W0 = W[t]..W[t+3] +*/ + +/* note that there is a step here where i want to do a rol by 1, which +* normally would look like this: +* +* r1 = psrld r0,$31 +* r0 = pslld r0,$1 +* r0 = por r0,r1 +* +* but instead i do this: +* +* r1 = pcmpltd r0,zero +* r0 = paddd r0,r0 +* r0 = psub r0,r1 +* +* because pcmpltd and paddd are availabe in both MMX units on +* efficeon, pentium-m, and opteron but shifts are available in +* only one unit. +*/ +string prep(alias _prep, alias _XW0, alias _XW1, alias _XW2, alias _XW3, alias _K)() +{ + enum prep = __traits(identifier, _prep); + enum XW0 = __traits(identifier, _XW0); + enum XW1 = __traits(identifier, _XW1); + enum XW2 = __traits(identifier, _XW2); + enum XW3 = __traits(identifier, _XW3); + enum K = __traits(identifier, _K); + return `{ + __m128i r0, r1, r2, r3; + + /* load W[t-4] 16-ubyte aligned, and shift */ + r3 = _mm_srli_si128!4(` ~ XW3 ~ `); + r0 = ` ~ XW0 ~ `; + /* get high 64-bits of XW0 into low 64-bits */ + r1 = _mm_shuffle_epi32!(_MM_SHUFFLE(1,0,3,2))(` ~ XW0 ~ `); + /* load high 64-bits of r1 */ + r1 = _mm_unpacklo_epi64(r1, ` ~ XW1 ~ `); + r2 = ` ~ XW2 ~ `; + r0 = _mm_xor_si128(r1, r0); + r2 = _mm_xor_si128(r3, r2); + r0 = _mm_xor_si128(r2, r0); + /* unrotated W[t]..W[t+2] in r0 ... still need W[t+3] */ + + r2 = _mm_slli_si128!12(r0); + r1 = _mm_cmplt_epi32(r0, _mm_setzero_si128()); + r0 = _mm_add_epi32(r0, r0); /* shift left by 1 */ + r0 = _mm_sub_epi32(r0, r1); /* r0 has W[t]..W[t+2] */ + + r3 = _mm_srli_epi32(r2, 30); + r2 = _mm_slli_epi32(r2, 2); + r0 = _mm_xor_si128(r0, r3); + r0 = _mm_xor_si128(r0, r2); /* r0 now has W[t+3] */ + ` ~ XW0 ~ ` = r0; + ` ~ prep ~ `.u128 = _mm_add_epi32(r0, ` ~ K ~ `); + }`; +} + +pure: + +/* +* SHA-160 F1 Function +*/ +void F1(uint A, ref uint B, uint C, uint D, ref uint E, uint msg) +{ + E += (D ^ (B & (C ^ D))) + msg + rotateLeft(A, 5); + B = rotateLeft(B, 30); +} + +/* +* SHA-160 F2 Function +*/ +void F2(uint A, ref uint B, uint C, uint D, ref uint E, uint msg) +{ + E += (B ^ C ^ D) + msg + rotateLeft(A, 5); + B = rotateLeft(B, 30); +} + +/* +* SHA-160 F3 Function +*/ +void F3(uint A, ref uint B, uint C, uint D, ref uint E, uint msg) +{ + E += ((B & C) | ((B | C) & D)) + msg + rotateLeft(A, 5); + B = rotateLeft(B, 30); +} + +/* +* SHA-160 F4 Function +*/ +void F4(uint A, ref uint B, uint C, uint D, ref uint E, uint msg) +{ + E += (B ^ C ^ D) + msg + rotateLeft(A, 5); + B = rotateLeft(B, 30); +} diff --git a/source/sse2/sha2_sse2.d b/source/sse2/sha2_sse2.d new file mode 100644 index 0000000..fa2ad5e --- /dev/null +++ b/source/sse2/sha2_sse2.d @@ -0,0 +1,223 @@ +module sse2.sha2_sse2; + +import botan.constants; + +version (GNU) { + enum GDC = true; +} else { + enum GDC = false; +} + +static if (BOTAN_HAS_SHA2_32 && (BOTAN_HAS_SIMD_SSE2 || GDC)): + +import core.bitop: bswap; +import core.stdc.stdint; + +import botan.hash.sha2_32; +import botan.hash.hash; +import std.format : format; + +import inteli.smmintrin; +import inteli.shaintrin; + +// Copied from botan C++ +class SHA256SSE2: SHA256 { + override HashFunction clone() const { return new SHA256SSE2; } + this() + { + super(); + } // no W needed + + protected: + /* + * SHA-256 Compression Function using SSE for message expansion + */ + override void compressN(const(ubyte)* input_bytes, size_t blocks) + { + enum const(uint32_t)[] K = [ + 0x428A2F98, 0x71374491, 0xB5C0FBCF, 0xE9B5DBA5, 0x3956C25B, 0x59F111F1, 0x923F82A4, 0xAB1C5ED5, + 0xD807AA98, 0x12835B01, 0x243185BE, 0x550C7DC3, 0x72BE5D74, 0x80DEB1FE, 0x9BDC06A7, 0xC19BF174, + 0xE49B69C1, 0xEFBE4786, 0x0FC19DC6, 0x240CA1CC, 0x2DE92C6F, 0x4A7484AA, 0x5CB0A9DC, 0x76F988DA, + 0x983E5152, 0xA831C66D, 0xB00327C8, 0xBF597FC7, 0xC6E00BF3, 0xD5A79147, 0x06CA6351, 0x14292967, + 0x27B70A85, 0x2E1B2138, 0x4D2C6DFC, 0x53380D13, 0x650A7354, 0x766A0ABB, 0x81C2C92E, 0x92722C85, + 0xA2BFE8A1, 0xA81A664B, 0xC24B8B70, 0xC76C51A3, 0xD192E819, 0xD6990624, 0xF40E3585, 0x106AA070, + 0x19A4C116, 0x1E376C08, 0x2748774C, 0x34B0BCB5, 0x391C0CB3, 0x4ED8AA4A, 0x5B9CCA4F, 0x682E6FF3, + 0x748F82EE, 0x78A5636F, 0x84C87814, 0x8CC70208, 0x90BEFFFA, 0xA4506CEB, 0xBEF9A3F7, 0xC67178F2, + ]; + + const __m128i* K_mm = cast(const __m128i*) K.ptr; + + uint32_t* state = m_digest.ptr; + + __m128i* input_mm = cast(__m128i*) input_bytes; + const __m128i MASK = _mm_set_epi64x(ulong(0x0c0d0e0f08090a0b), ulong(0x0405060700010203)); + + // Load initial values + __m128i STATE0 = _mm_loadu_si128(cast(__m128i*) &state[0]); + __m128i STATE1 = _mm_loadu_si128(cast(__m128i*) &state[4]); + + STATE0 = _mm_shuffle_epi32!0xB1(STATE0); // CDAB + STATE1 = _mm_shuffle_epi32!0x1B(STATE1); // EFGH + + __m128i TMP = _mm_alignr_epi8!8(STATE0, STATE1); // ABEF + STATE1 = _mm_blend_epi16!0xF0(STATE1, STATE0); // CDGH + STATE0 = TMP; + + while(blocks > 0) { + // Save current state + const __m128i ABEF_SAVE = STATE0; + const __m128i CDGH_SAVE = STATE1; + + __m128i MSG; + + __m128i TMSG0 = _mm_shuffle_epi8(_mm_loadu_si128(input_mm), MASK); + __m128i TMSG1 = _mm_shuffle_epi8(_mm_loadu_si128(input_mm + 1), MASK); + __m128i TMSG2 = _mm_shuffle_epi8(_mm_loadu_si128(input_mm + 2), MASK); + __m128i TMSG3 = _mm_shuffle_epi8(_mm_loadu_si128(input_mm + 3), MASK); + + // Rounds 0-3 + MSG = _mm_add_epi32(TMSG0, _mm_load_si128(K_mm)); + STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); + STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, _mm_shuffle_epi32!0x0E(MSG)); + + // Rounds 4-7 + MSG = _mm_add_epi32(TMSG1, _mm_load_si128(K_mm + 1)); + STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); + STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, _mm_shuffle_epi32!0x0E(MSG)); + + TMSG0 = _mm_sha256msg1_epu32(TMSG0, TMSG1); + + // Rounds 8-11 + MSG = _mm_add_epi32(TMSG2, _mm_load_si128(K_mm + 2)); + STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); + STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, _mm_shuffle_epi32!0x0E(MSG)); + + TMSG1 = _mm_sha256msg1_epu32(TMSG1, TMSG2); + + // Rounds 12-15 + MSG = _mm_add_epi32(TMSG3, _mm_load_si128(K_mm + 3)); + STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); + STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, _mm_shuffle_epi32!0x0E(MSG)); + + TMSG0 = _mm_add_epi32(TMSG0, _mm_alignr_epi8!4(TMSG3, TMSG2)); + TMSG0 = _mm_sha256msg2_epu32(TMSG0, TMSG3); + TMSG2 = _mm_sha256msg1_epu32(TMSG2, TMSG3); + + // Rounds 16-19 + MSG = _mm_add_epi32(TMSG0, _mm_load_si128(K_mm + 4)); + STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); + STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, _mm_shuffle_epi32!0x0E(MSG)); + + TMSG1 = _mm_add_epi32(TMSG1, _mm_alignr_epi8!4(TMSG0, TMSG3)); + TMSG1 = _mm_sha256msg2_epu32(TMSG1, TMSG0); + TMSG3 = _mm_sha256msg1_epu32(TMSG3, TMSG0); + + // Rounds 20-23 + MSG = _mm_add_epi32(TMSG1, _mm_load_si128(K_mm + 5)); + STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); + STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, _mm_shuffle_epi32!0x0E(MSG)); + + TMSG2 = _mm_add_epi32(TMSG2, _mm_alignr_epi8!4(TMSG1, TMSG0)); + TMSG2 = _mm_sha256msg2_epu32(TMSG2, TMSG1); + TMSG0 = _mm_sha256msg1_epu32(TMSG0, TMSG1); + + // Rounds 24-27 + MSG = _mm_add_epi32(TMSG2, _mm_load_si128(K_mm + 6)); + STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); + STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, _mm_shuffle_epi32!0x0E(MSG)); + + TMSG3 = _mm_add_epi32(TMSG3, _mm_alignr_epi8!4(TMSG2, TMSG1)); + TMSG3 = _mm_sha256msg2_epu32(TMSG3, TMSG2); + TMSG1 = _mm_sha256msg1_epu32(TMSG1, TMSG2); + + // Rounds 28-31 + MSG = _mm_add_epi32(TMSG3, _mm_load_si128(K_mm + 7)); + STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); + STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, _mm_shuffle_epi32!0x0E(MSG)); + + TMSG0 = _mm_add_epi32(TMSG0, _mm_alignr_epi8!4(TMSG3, TMSG2)); + TMSG0 = _mm_sha256msg2_epu32(TMSG0, TMSG3); + TMSG2 = _mm_sha256msg1_epu32(TMSG2, TMSG3); + + // Rounds 32-35 + MSG = _mm_add_epi32(TMSG0, _mm_load_si128(K_mm + 8)); + STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); + STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, _mm_shuffle_epi32!0x0E(MSG)); + + TMSG1 = _mm_add_epi32(TMSG1, _mm_alignr_epi8!4(TMSG0, TMSG3)); + TMSG1 = _mm_sha256msg2_epu32(TMSG1, TMSG0); + TMSG3 = _mm_sha256msg1_epu32(TMSG3, TMSG0); + + // Rounds 36-39 + MSG = _mm_add_epi32(TMSG1, _mm_load_si128(K_mm + 9)); + STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); + STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, _mm_shuffle_epi32!0x0E(MSG)); + + TMSG2 = _mm_add_epi32(TMSG2, _mm_alignr_epi8!4(TMSG1, TMSG0)); + TMSG2 = _mm_sha256msg2_epu32(TMSG2, TMSG1); + TMSG0 = _mm_sha256msg1_epu32(TMSG0, TMSG1); + + // Rounds 40-43 + MSG = _mm_add_epi32(TMSG2, _mm_load_si128(K_mm + 10)); + STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); + STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, _mm_shuffle_epi32!0x0E(MSG)); + + TMSG3 = _mm_add_epi32(TMSG3, _mm_alignr_epi8!4(TMSG2, TMSG1)); + TMSG3 = _mm_sha256msg2_epu32(TMSG3, TMSG2); + TMSG1 = _mm_sha256msg1_epu32(TMSG1, TMSG2); + + // Rounds 44-47 + MSG = _mm_add_epi32(TMSG3, _mm_load_si128(K_mm + 11)); + STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); + STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, _mm_shuffle_epi32!0x0E(MSG)); + + TMSG0 = _mm_add_epi32(TMSG0, _mm_alignr_epi8!4(TMSG3, TMSG2)); + TMSG0 = _mm_sha256msg2_epu32(TMSG0, TMSG3); + TMSG2 = _mm_sha256msg1_epu32(TMSG2, TMSG3); + + // Rounds 48-51 + MSG = _mm_add_epi32(TMSG0, _mm_load_si128(K_mm + 12)); + STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); + STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, _mm_shuffle_epi32!0x0E(MSG)); + + TMSG1 = _mm_add_epi32(TMSG1, _mm_alignr_epi8!4(TMSG0, TMSG3)); + TMSG1 = _mm_sha256msg2_epu32(TMSG1, TMSG0); + TMSG3 = _mm_sha256msg1_epu32(TMSG3, TMSG0); + + // Rounds 52-55 + MSG = _mm_add_epi32(TMSG1, _mm_load_si128(K_mm + 13)); + STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); + STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, _mm_shuffle_epi32!0x0E(MSG)); + + TMSG2 = _mm_add_epi32(TMSG2, _mm_alignr_epi8!4(TMSG1, TMSG0)); + TMSG2 = _mm_sha256msg2_epu32(TMSG2, TMSG1); + + // Rounds 56-59 + MSG = _mm_add_epi32(TMSG2, _mm_load_si128(K_mm + 14)); + STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); + STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, _mm_shuffle_epi32!0x0E(MSG)); + + TMSG3 = _mm_add_epi32(TMSG3, _mm_alignr_epi8!4(TMSG2, TMSG1)); + TMSG3 = _mm_sha256msg2_epu32(TMSG3, TMSG2); + + // Rounds 60-63 + MSG = _mm_add_epi32(TMSG3, _mm_load_si128(K_mm + 15)); + STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); + STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, _mm_shuffle_epi32!0x0E(MSG)); + + // Add values back to state + STATE0 = _mm_add_epi32(STATE0, ABEF_SAVE); + STATE1 = _mm_add_epi32(STATE1, CDGH_SAVE); + + input_mm += 4; + blocks--; + } + + STATE0 = _mm_shuffle_epi32!0x1B(STATE0); // FEBA + STATE1 = _mm_shuffle_epi32!0xB1(STATE1); // DCHG + + // Save state + _mm_storeu_si128(cast(__m128i*) &state[0], _mm_blend_epi16!0xF0(STATE0, STATE1)); // DCBA + _mm_storeu_si128(cast(__m128i*) &state[4], _mm_alignr_epi8!8(STATE1, STATE0)); // ABEF + } +} diff --git a/windows/winforms/ui/sideloaderform.d b/windows/winforms/ui/sideloaderform.d index ca3b9d6..8f04c6e 100644 --- a/windows/winforms/ui/sideloaderform.d +++ b/windows/winforms/ui/sideloaderform.d @@ -3,6 +3,7 @@ module ui.sideloaderform; import file = std.file; import std.format; import std.path; +import std.process; import slf4d; @@ -25,6 +26,7 @@ class SideloaderForm: Form { private MenuItem toolStripSeparator1; private MenuItem manageAppIDsMenuItem; private MenuItem manageCertificatesMenuItem; + private Button donateButton; private Button installAppButton; private ImageList deviceImageList; private ListView deviceListView; @@ -41,6 +43,7 @@ class SideloaderForm: Form { this.toolStripSeparator1 = new MenuItem(); this.manageAppIDsMenuItem = new MenuItem(); this.manageCertificatesMenuItem = new MenuItem(); + this.donateButton = new Button(); this.installAppButton = new Button(); this.deviceImageList = new ImageList(); this.deviceListView = new ListView(); @@ -115,6 +118,17 @@ Thanks to people behind: libimobiledevice, libplist, Botan, Botan D port, SideSt this.manageCertificatesMenuItem.text = "Manage certificates"; this.manageCertificatesMenuItem.enabled = false; // + // donateButton + // + // this.donateButton.anchor = (cast(AnchorStyles)((AnchorStyles.TOP | AnchorStyles.RIGHT))); + this.donateButton.click ~= &donateButton_Clicked; + this.donateButton.location = Point(652, 438); + this.donateButton.name = "donateButton"; + this.donateButton.size = Size(140, 32); + // this.donateButton.tabIndex = 1; + this.donateButton.text = "Donate"; + // this.donateButton.useVisualStyleBackColor = true; + // // installAppButton // // this.installAppButton.anchor = (cast(AnchorStyles)((AnchorStyles.TOP | AnchorStyles.RIGHT))); @@ -158,6 +172,7 @@ Thanks to people behind: libimobiledevice, libplist, Botan, Botan D port, SideSt this.clientSize = Size(800, 500); this.controls.add(this.deviceInfoButton); this.controls.add(this.deviceListView); + this.controls.add(this.donateButton); this.controls.add(this.installAppButton); this.formBorderStyle = FormBorderStyle.FIXED_DIALOG; this.menu = this.mainMenuStrip; @@ -178,4 +193,8 @@ Thanks to people behind: libimobiledevice, libplist, Botan, Botan D port, SideSt frontend.initializeADI(); } } + + void donateButton_Clicked(Control c, EventArgs e) { + browse("https://github.com/sponsors/Dadoum"); + } }