mirror of
https://github.com/whoeevee/ivinject.git
synced 2026-01-08 23:25:03 +00:00
provisioning profiles support
This commit is contained in:
@@ -28,7 +28,6 @@ internal class IviMachOBinary(string fileName)
|
||||
|
||||
return size switch
|
||||
{
|
||||
|
||||
< 1024 => $"{size:F0} bytes",
|
||||
_ when size >> 10 < 1024 => $"{size / (float)1024:F1} KB",
|
||||
_ when size >> 20 < 1024 => $"{(size >> 10) / (float)1024:F1} MB",
|
||||
@@ -43,7 +42,7 @@ internal class IviMachOBinary(string fileName)
|
||||
new ProcessStartInfo
|
||||
{
|
||||
FileName = "otool",
|
||||
Arguments = $"-l \"{FullName}\"",
|
||||
ArgumentList = { "-l", FullName },
|
||||
RedirectStandardOutput = true
|
||||
}
|
||||
);
|
||||
@@ -58,7 +57,7 @@ internal class IviMachOBinary(string fileName)
|
||||
new ProcessStartInfo
|
||||
{
|
||||
FileName = "lipo",
|
||||
Arguments = $"-thin arm64 \"{FullName}\" -output \"{FullName}\""
|
||||
ArgumentList = { "-thin", "arm64", FullName, "-output", FullName }
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ internal static class CodesigningMachOExtensions
|
||||
new ProcessStartInfo
|
||||
{
|
||||
FileName = "codesign",
|
||||
Arguments = $"--remove-signature \"{binary.FullName}\""
|
||||
ArgumentList = { "--remove-signature", binary.FullName }
|
||||
}
|
||||
);
|
||||
|
||||
@@ -62,7 +62,7 @@ internal static class CodesigningMachOExtensions
|
||||
new ProcessStartInfo
|
||||
{
|
||||
FileName = "codesign",
|
||||
Arguments = $"-d --entitlements {outputFilePath} --xml \"{binary.FullName}\"",
|
||||
ArgumentList = { "-d", "--entitlements", outputFilePath, "--xml", binary.FullName },
|
||||
RedirectStandardError = true
|
||||
}
|
||||
);
|
||||
|
||||
@@ -195,6 +195,9 @@ internal class CodesigningManager(ILogger logger) : IDisposable
|
||||
if (!await SignBinary(MainBinary, signingInfo))
|
||||
return false;
|
||||
|
||||
if (signingInfo.IsFromProvisioningProfile)
|
||||
signingInfo.Entitlements!.Delete();
|
||||
|
||||
logger.LogInformation(
|
||||
"Signed {} binaries ({} main executables) with the specified identity",
|
||||
_allBinaries.Count,
|
||||
|
||||
@@ -50,6 +50,16 @@ internal class IviRootCommand : RootCommand
|
||||
AllowMultipleArgumentsPerToken = true
|
||||
};
|
||||
|
||||
private readonly Option<FileInfo> _provisioningProfileOption = new(
|
||||
"--profile",
|
||||
"Provisioning profile to extract entitlements, signing identity, and bundle ID"
|
||||
);
|
||||
|
||||
private readonly Option<bool> _profileBundleIdOption = new(
|
||||
"--profile-bundle-id",
|
||||
"Replace the bundle ID with the one in the provisioning profile"
|
||||
);
|
||||
|
||||
private readonly Option<string> _codesignIdentityOption = new(
|
||||
"--sign",
|
||||
"The identity for code signing (use \"-\" for ad hoc, a.k.a. fake signing)"
|
||||
@@ -63,7 +73,7 @@ internal class IviRootCommand : RootCommand
|
||||
//
|
||||
|
||||
private readonly Option<string> _customBundleIdOption = new(
|
||||
"--bundleId",
|
||||
"--bundle-id",
|
||||
"The custom identifier that will be applied to application bundles"
|
||||
);
|
||||
|
||||
@@ -86,9 +96,11 @@ internal class IviRootCommand : RootCommand
|
||||
internal IviRootCommand() : base("The most demure iOS app injector and signer")
|
||||
{
|
||||
_itemsOption.AddAlias("-i");
|
||||
|
||||
_provisioningProfileOption.AddAlias("-p");
|
||||
_codesignIdentityOption.AddAlias("-s");
|
||||
_compressionLevelOption.AddAlias("--level");
|
||||
_codesignEntitlementsOption.AddAlias("-e");
|
||||
_compressionLevelOption.AddAlias("--level");
|
||||
|
||||
_customBundleIdOption.AddAlias("-b");
|
||||
_enableDocumentsSupportOption.AddAlias("-d");
|
||||
@@ -101,6 +113,8 @@ internal class IviRootCommand : RootCommand
|
||||
AddOption(_compressionLevelOption);
|
||||
|
||||
AddOption(_itemsOption);
|
||||
AddOption(_provisioningProfileOption);
|
||||
AddOption(_profileBundleIdOption);
|
||||
AddOption(_codesignIdentityOption);
|
||||
AddOption(_codesignEntitlementsOption);
|
||||
|
||||
@@ -120,6 +134,8 @@ internal class IviRootCommand : RootCommand
|
||||
_overwriteOutputOption,
|
||||
_compressionLevelOption,
|
||||
_itemsOption,
|
||||
_provisioningProfileOption,
|
||||
_profileBundleIdOption,
|
||||
_codesignIdentityOption,
|
||||
_codesignEntitlementsOption,
|
||||
_customBundleIdOption,
|
||||
|
||||
@@ -12,6 +12,8 @@ internal class IviRootCommandParametersBinder(
|
||||
Option<bool> overwriteOutputOption,
|
||||
Option<CompressionLevel> compressionLevelOption,
|
||||
Option<IEnumerable<FileInfo>> itemsOption,
|
||||
Option<FileInfo> provisioningProfileOption,
|
||||
Option<bool> profileBundleIdOption,
|
||||
Option<string> codesignIdentityOption,
|
||||
Option<FileInfo> codesignEntitlementsOption,
|
||||
Option<string> customBundleIdOption,
|
||||
@@ -33,12 +35,16 @@ internal class IviRootCommandParametersBinder(
|
||||
|
||||
var items =
|
||||
bindingContext.ParseResult.GetValueForOption(itemsOption);
|
||||
var provisioningProfile =
|
||||
bindingContext.ParseResult.GetValueForOption(provisioningProfileOption);
|
||||
var profileBundleId =
|
||||
bindingContext.ParseResult.GetValueForOption(profileBundleIdOption);
|
||||
var codesignIdentity =
|
||||
bindingContext.ParseResult.GetValueForOption(codesignIdentityOption);
|
||||
var codesignEntitlements =
|
||||
bindingContext.ParseResult.GetValueForOption(codesignEntitlementsOption);
|
||||
|
||||
var bundleId =
|
||||
var customBundleId =
|
||||
bindingContext.ParseResult.GetValueForOption(customBundleIdOption);
|
||||
var enableDocumentsSupport =
|
||||
bindingContext.ParseResult.GetValueForOption(enableDocumentsSupportOption);
|
||||
@@ -46,15 +52,43 @@ internal class IviRootCommandParametersBinder(
|
||||
bindingContext.ParseResult.GetValueForOption(removeSupportedDevicesOption);
|
||||
var directoriesToRemove =
|
||||
bindingContext.ParseResult.GetValueForOption(directoriesToRemoveOption);
|
||||
|
||||
IviPackagingInfo? packagingInfo;
|
||||
|
||||
if (bundleId is null
|
||||
&& directoriesToRemove is null
|
||||
&& !enableDocumentsSupport
|
||||
&& !removeSupportedDevices)
|
||||
packagingInfo = null;
|
||||
else
|
||||
IviSigningInfo? signingInfo = null;
|
||||
IviPackagingInfo? packagingInfo = null;
|
||||
|
||||
var profileInfo = provisioningProfile is not null
|
||||
? ProvisioningProfileParser.Parse(provisioningProfile)
|
||||
: null;
|
||||
|
||||
if (profileInfo is not null)
|
||||
{
|
||||
signingInfo = new IviSigningInfo
|
||||
{
|
||||
Identity = profileInfo.Identity,
|
||||
Entitlements = profileInfo.Entitlements,
|
||||
IsFromProvisioningProfile = true
|
||||
};
|
||||
}
|
||||
else if (codesignIdentity is not null)
|
||||
{
|
||||
signingInfo = new IviSigningInfo
|
||||
{
|
||||
Identity = codesignIdentity,
|
||||
Entitlements = codesignEntitlements
|
||||
};
|
||||
}
|
||||
|
||||
var bundleId = customBundleId;
|
||||
|
||||
if (profileInfo is not null && profileBundleId)
|
||||
bundleId = profileInfo.BundleId;
|
||||
|
||||
if (bundleId is not null
|
||||
|| directoriesToRemove is not null
|
||||
|| enableDocumentsSupport
|
||||
|| removeSupportedDevices
|
||||
)
|
||||
{
|
||||
packagingInfo = new IviPackagingInfo
|
||||
{
|
||||
CustomBundleId = bundleId,
|
||||
@@ -62,6 +96,7 @@ internal class IviRootCommandParametersBinder(
|
||||
RemoveSupportedDevices = removeSupportedDevices,
|
||||
DirectoriesToRemove = directoriesToRemove ?? []
|
||||
};
|
||||
}
|
||||
|
||||
return new IviParameters
|
||||
{
|
||||
@@ -70,13 +105,7 @@ internal class IviRootCommandParametersBinder(
|
||||
OverwriteOutput = overwriteOutput,
|
||||
CompressionLevel = compressionLevel,
|
||||
InjectionEntries = items?.Select(item => new IviInjectionEntry(item)) ?? [],
|
||||
SigningInfo = codesignIdentity is null
|
||||
? null
|
||||
: new IviSigningInfo
|
||||
{
|
||||
Identity = codesignIdentity,
|
||||
Entitlements = codesignEntitlements
|
||||
},
|
||||
SigningInfo = signingInfo,
|
||||
PackagingInfo = packagingInfo
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ namespace ivinject.Features.Command.Models;
|
||||
|
||||
internal class IviSigningInfo
|
||||
{
|
||||
internal bool IsFromProvisioningProfile { get; init; }
|
||||
internal required string Identity { get; init; }
|
||||
internal bool IsAdHocSigning => Identity == "-";
|
||||
internal FileInfo? Entitlements { get; init; }
|
||||
|
||||
8
Features/Command/Models/ProfileInfo.cs
Normal file
8
Features/Command/Models/ProfileInfo.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace ivinject.Features.Command.Models;
|
||||
|
||||
internal class ProfileInfo
|
||||
{
|
||||
internal required FileInfo Entitlements { get; init; }
|
||||
internal required string Identity { get; set; }
|
||||
internal required string BundleId { get; set; }
|
||||
}
|
||||
39
Features/Command/ProvisioningProfileParser.cs
Normal file
39
Features/Command/ProvisioningProfileParser.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
using System.Diagnostics;
|
||||
using Claunia.PropertyList;
|
||||
using ivinject.Features.Command.Models;
|
||||
|
||||
namespace ivinject.Features.Command;
|
||||
|
||||
internal static class ProvisioningProfileParser
|
||||
{
|
||||
internal static ProfileInfo Parse(FileInfo profile)
|
||||
{
|
||||
using var process = Process.Start(
|
||||
new ProcessStartInfo
|
||||
{
|
||||
FileName = "security",
|
||||
ArgumentList = { "cms", "-D", "-i", profile.FullName },
|
||||
RedirectStandardOutput = true
|
||||
}
|
||||
);
|
||||
|
||||
process!.WaitForExit();
|
||||
var dictionary = (NSDictionary)PropertyListParser.Parse(process.StandardOutput.BaseStream);
|
||||
|
||||
var entitlements = (NSDictionary)dictionary["Entitlements"];
|
||||
var teamIdentifier = ((NSString)entitlements["com.apple.developer.team-identifier"]).Content;
|
||||
var bundleId = ((NSString)entitlements["application-identifier"]).Content[11..];
|
||||
|
||||
var entitlementsFile = Path.GetTempFileName();
|
||||
File.WriteAllText(entitlementsFile, entitlements.ToXmlPropertyList());
|
||||
|
||||
var entitlementsFileInfo = new FileInfo(entitlementsFile);
|
||||
|
||||
return new ProfileInfo
|
||||
{
|
||||
BundleId = bundleId,
|
||||
Identity = teamIdentifier,
|
||||
Entitlements = entitlementsFileInfo
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,7 @@ internal class DebManager(IviInjectionEntry debEntry) : IDisposable
|
||||
new ProcessStartInfo
|
||||
{
|
||||
FileName = "tar",
|
||||
Arguments = $"-xf {debEntry.FullName} --directory={_tempPath}"
|
||||
ArgumentList = { "-xf", debEntry.FullName, $"--directory={_tempPath}" }
|
||||
}
|
||||
);
|
||||
|
||||
@@ -31,7 +31,7 @@ internal class DebManager(IviInjectionEntry debEntry) : IDisposable
|
||||
new ProcessStartInfo
|
||||
{
|
||||
FileName = "tar",
|
||||
Arguments = $"-xf {dataArchive}",
|
||||
ArgumentList = { "-xf", dataArchive },
|
||||
WorkingDirectory = _tempPath
|
||||
}
|
||||
);
|
||||
|
||||
@@ -13,7 +13,7 @@ internal static class DependencyExtensions
|
||||
new ProcessStartInfo
|
||||
{
|
||||
FileName = "otool",
|
||||
Arguments = $"-L \"{binary.FullName}\"",
|
||||
ArgumentList = { "-L", binary.FullName },
|
||||
RedirectStandardOutput = true
|
||||
}
|
||||
);
|
||||
@@ -35,7 +35,7 @@ internal static class DependencyExtensions
|
||||
new ProcessStartInfo
|
||||
{
|
||||
FileName = "install_name_tool",
|
||||
Arguments = $"-change {oldPath} {newPath} \"{binary.FullName}\"",
|
||||
ArgumentList = { "-change", oldPath, newPath, binary.FullName },
|
||||
RedirectStandardError = true
|
||||
}
|
||||
);
|
||||
@@ -52,7 +52,7 @@ internal static class DependencyExtensions
|
||||
new ProcessStartInfo
|
||||
{
|
||||
FileName = "install_name_tool",
|
||||
Arguments = $"-add_rpath {rPath} \"{binary.FullName}\"",
|
||||
ArgumentList = { "-add_rpath", rPath, binary.FullName },
|
||||
RedirectStandardOutput = true
|
||||
}
|
||||
);
|
||||
@@ -70,7 +70,7 @@ internal static class DependencyExtensions
|
||||
new ProcessStartInfo
|
||||
{
|
||||
FileName = "insert-dylib",
|
||||
Arguments = $"{dependency} \"{binary.FullName}\" --all-yes --inplace",
|
||||
ArgumentList = { dependency, binary.FullName, "--all-yes", "--inplace" },
|
||||
RedirectStandardOutput = true
|
||||
}
|
||||
);
|
||||
|
||||
@@ -3,7 +3,7 @@ using ivinject.Features.Command;
|
||||
|
||||
namespace ivinject;
|
||||
|
||||
internal class Program
|
||||
internal static class Program
|
||||
{
|
||||
private static readonly RootCommand RootCommand = new IviRootCommand();
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ Thus, feature requests and bug reports are not accepted. You probably would like
|
||||
## The Demureness
|
||||
- **ivinject is an entirely different project, written in C# with .NET 9. The code architecture and quality are significantly better. Compiled with NativeAOT, it produces native binaries, offering incredible speed and low resource usage, without needing anything like venv to run.**
|
||||
|
||||
- ivinject is not just an injector but also a signer. You can specify a code signing identity and a file with entitlements that will be written into the main executables. It signs the code properly according to Apple's technotes, passing codesign verification with the `--strict` option. It only supports developer certificates (.p12 and .mobileprovision).
|
||||
- ivinject is not just an injector but also a signer. You can specify a code signing identity and a file with entitlements that will be written into the main executables. It signs the code properly according to Apple's technotes, passing codesign verification with the `--strict` option. It only supports already created provisioning profiles (you can specify the `.mobileprovision` file, or entitlements and identity manually).
|
||||
|
||||
- ivinject does not and won’t support anything except for macOS — I couldn’t care less about other platforms.
|
||||
|
||||
@@ -15,6 +15,6 @@ Thus, feature requests and bug reports are not accepted. You probably would like
|
||||
|
||||
## Prerequisites
|
||||
* Make sure Xcode is installed
|
||||
* Install insert-dylib (`brew install --HEAD samdmarshall/formulae/insert-dylib`)
|
||||
* Install insert-dylib (see `samdmarshall/formulae/insert-dylib`)
|
||||
* Copy the contents of `KnownFrameworks` to `~/.ivinject`
|
||||
* For code signing, the identity needs to be added to Keychain, and the provisioning profile must be installed on the device (you can also add it to the app package by specifying `embedded.mobileprovision` in items)
|
||||
* For code signing, the identity needs to be added to Keychain, and the provisioning profile must be installed on the device (you can also add it to the app package by renaming it to `embedded.mobileprovision` and specifying it in the injection items)
|
||||
Reference in New Issue
Block a user