provisioning profiles support

This commit is contained in:
eevee
2025-09-14 01:24:47 +03:00
parent 5f4adec22c
commit 68722c2084
12 changed files with 128 additions and 33 deletions

View File

@@ -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 }
}
);

View File

@@ -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
}
);

View File

@@ -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,

View File

@@ -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,

View File

@@ -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);
@@ -47,14 +53,42 @@ internal class IviRootCommandParametersBinder(
var directoriesToRemove =
bindingContext.ParseResult.GetValueForOption(directoriesToRemoveOption);
IviPackagingInfo? packagingInfo;
IviSigningInfo? signingInfo = null;
IviPackagingInfo? packagingInfo = null;
if (bundleId is null
&& directoriesToRemove is null
&& !enableDocumentsSupport
&& !removeSupportedDevices)
packagingInfo = null;
else
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
};
}

View File

@@ -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; }

View 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; }
}

View 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
};
}
}

View File

@@ -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
}
);

View File

@@ -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
}
);

View File

@@ -3,7 +3,7 @@ using ivinject.Features.Command;
namespace ivinject;
internal class Program
internal static class Program
{
private static readonly RootCommand RootCommand = new IviRootCommand();

View File

@@ -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 wont support anything except for macOS — I couldnt 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)