merging entitlements

This commit is contained in:
eevee
2025-05-04 17:44:53 +03:00
parent 661619ccd7
commit 5f4adec22c
6 changed files with 163 additions and 85 deletions

View File

@@ -1,28 +1,31 @@
using System.Diagnostics;
using System.Text;
using ivinject.Common.Models;
using ivinject.Features.Packaging.Models;
using static ivinject.Features.Packaging.Models.DirectoryNames;
namespace ivinject.Features.Codesigning;
internal static class CodesigningMachOExtensions
{
internal static bool IsMainExecutable(this IviMachOBinary binary, IviDirectoriesInfo directoriesInfo)
{
return !Path.GetRelativePath(
directoriesInfo.BundleDirectory,
binary.FullName
).Contains(FrameworksDirectoryName);
}
internal static async Task<bool> SignAsync(
this IviMachOBinary binary,
string identity,
bool force,
FileInfo? entitlements,
bool preserveEntitlements = false
FileInfo? entitlements = null
)
{
var arguments = new StringBuilder($"-s {identity}");
if (entitlements is not null)
arguments.Append($" --entitlements {entitlements.FullName}");
else if (preserveEntitlements)
arguments.Append(" --preserve-metadata=entitlements");
if (force)
arguments.Append(" -f");
arguments.Append($" \"{binary.FullName}\"");
@@ -63,8 +66,10 @@ internal static class CodesigningMachOExtensions
RedirectStandardError = true
}
);
var error = await process!.StandardError.ReadToEndAsync();
await process.WaitForExitAsync();
await process!.WaitForExitAsync();
return process.ExitCode == 0;
return process.ExitCode == 0 && error.Count(c => c.Equals('\n')) == 1;
}
}

View File

@@ -1,9 +1,12 @@
using System.Collections.Concurrent;
using Claunia.PropertyList;
using ivinject.Common;
using ivinject.Common.Models;
using ivinject.Features.Codesigning.Models;
using ivinject.Features.Command.Models;
using ivinject.Features.Packaging;
using ivinject.Features.Packaging.Models;
using Microsoft.Extensions.Logging;
using static ivinject.Features.Packaging.Models.DirectoryNames;
using static ivinject.Common.Models.BinaryHeaders;
namespace ivinject.Features.Codesigning;
@@ -11,12 +14,20 @@ namespace ivinject.Features.Codesigning;
internal class CodesigningManager(ILogger logger) : IDisposable
{
private IviPackageInfo _packageInfo = null!;
private IviDirectoriesInfo DirectoriesInfo => _packageInfo.DirectoriesInfo;
//
private readonly List<IviMachOBinary> _allBinaries = [];
private List<IviMachOBinary> Binaries => _allBinaries[1..];
private IviMachOBinary MainBinary => _allBinaries[0];
private List<IviMachOBinary> Binaries => _allBinaries[1..];
private List<IviMachOBinary> MainExecutables =>
_allBinaries.Where(binary => binary.IsMainExecutable(DirectoriesInfo)).ToList();
private FileInfo? _savedMainEntitlements;
//
private readonly ConcurrentDictionary<IviMachOBinary, FileInfo> _savedEntitlements = [];
private readonly List<FileInfo> _mergedEntitlementFiles = [];
internal void UpdateWithPackage(IviPackageInfo packageInfo)
{
@@ -73,87 +84,145 @@ internal class CodesigningManager(ILogger logger) : IDisposable
};
}
internal async Task SaveMainBinaryEntitlementsAsync()
internal async Task<bool> SaveMainExecutablesEntitlementsAsync()
{
var tempFile = Path.GetTempFileName();
if (await MainBinary.DumpEntitlementsAsync(tempFile))
{
logger.LogInformation("Saved {} entitlements", MainBinary.Name);
_savedMainEntitlements = new FileInfo(tempFile);
return;
}
logger.LogWarning(
"Unable to save {} entitlements. The binary is likely unsigned.",
MainBinary.Name
);
}
internal async Task<bool> SignAsync(string identity, bool isAdHocSigning, FileInfo? entitlements)
{
var mainExecutablesCount = 0;
var signingResults = await Task.WhenAll(
Binaries.Select(async binary =>
var dumpingResults = await Task.WhenAll(
MainExecutables.Select(async binary =>
{
var isMainExecutable = !Path.GetRelativePath(
_packageInfo.DirectoriesInfo.BundleDirectory,
binary.FullName
).Contains(FrameworksDirectoryName);
var tempFile = Path.GetTempFileName();
var tempFileInfo = new FileInfo(tempFile);
if (isMainExecutable)
mainExecutablesCount++;
return await binary.SignAsync(
identity,
isAdHocSigning,
isMainExecutable
? entitlements
: null,
isMainExecutable && entitlements is null
);
if (!await binary.DumpEntitlementsAsync(tempFile))
{
tempFileInfo.Delete();
return false;
}
_savedEntitlements[binary] = tempFileInfo;
return true;
}
)
);
return dumpingResults.All(result => result);
}
private async Task<FileInfo> MergeEntitlementsAsync(
string binaryName,
FileInfo binaryEntitlements,
FileInfo profileEntitlements
)
{
var binaryEntitlementsDictionary = (NSDictionary)PropertyListParser.Parse(binaryEntitlements);
var profileEntitlementsDictionary = (NSDictionary)PropertyListParser.Parse(profileEntitlements);
var profileTeamId = ((NSString)profileEntitlementsDictionary["com.apple.developer.team-identifier"]).Content;
var finalEntitlements = new NSDictionary();
foreach (var entitlementKey in binaryEntitlementsDictionary.Keys)
{
if (!profileEntitlementsDictionary.TryGetValue(entitlementKey, out var profileEntitlement))
{
logger.LogWarning(
"Matching entitlement for {} ({}) was not found",
entitlementKey,
binaryName
);
continue;
}
if (entitlementKey == "keychain-access-groups")
{
var binaryAccessGroups = (NSArray)binaryEntitlementsDictionary[entitlementKey];
var finalAccessGroups = new NSArray();
foreach (var accessGroup in binaryAccessGroups)
{
var group = profileTeamId + ((NSString)accessGroup).Content[10..];
finalAccessGroups.Add(new NSString(group));
}
finalEntitlements[entitlementKey] = finalAccessGroups;
logger.LogInformation("Mapped keychain access groups for {}: {}", binaryName, finalAccessGroups);
continue;
}
finalEntitlements[entitlementKey] = profileEntitlement;
}
var finalEntitlementsPath = Path.GetTempFileName();
await finalEntitlements.SaveToFileAsync(finalEntitlementsPath);
var fileInfo = new FileInfo(finalEntitlementsPath);
_mergedEntitlementFiles.Add(fileInfo);
return fileInfo;
}
private async Task<bool> SignBinary(IviMachOBinary binary, IviSigningInfo signingInfo)
{
var identity = signingInfo.Identity;
var entitlements = signingInfo.Entitlements;
if (!binary.IsMainExecutable(DirectoriesInfo))
return await binary.SignAsync(identity);
if (!_savedEntitlements.TryGetValue(binary, out var signingEntitlements))
return await binary.SignAsync(identity, entitlements);
if (!signingInfo.IsAdHocSigning)
signingEntitlements = await MergeEntitlementsAsync(
binary.Name,
signingEntitlements,
entitlements!
);
return await binary.SignAsync(identity, signingEntitlements);
}
internal async Task<bool> SignPackageAsync(IviSigningInfo signingInfo)
{
var signingResults = await Task.WhenAll(
Binaries.Select(async binary => await SignBinary(binary, signingInfo))
);
if (signingResults.Any(result => !result))
return false;
if (!await MainBinary.SignAsync(identity, false, _savedMainEntitlements ?? entitlements))
if (!await SignBinary(MainBinary, signingInfo))
return false;
logger.LogInformation(
"Signed {} binaries ({} main executables) with the specified identity",
_allBinaries.Count,
mainExecutablesCount + 1 // App main executable
MainExecutables.Count
);
return true;
}
internal async Task<bool> RemoveSignatureAsync(bool allBinaries)
internal async Task<bool> RemoveSignatureAsync()
{
if (allBinaries)
{
var removingResults = await Task.WhenAll(
_allBinaries.Select(binary => binary.RemoveSignatureAsync())
);
if (removingResults.Any(result => !result))
return false;
logger.LogInformation("Signature removed from {} binaries", _allBinaries.Count);
return true;
}
if (!await MainBinary.RemoveSignatureAsync())
var removingResults = await Task.WhenAll(
_allBinaries.Select(binary => binary.RemoveSignatureAsync())
);
if (removingResults.Any(result => !result))
return false;
logger.LogInformation("Removed {} signature", MainBinary.Name);
logger.LogInformation("Signature removed from {} binaries", _allBinaries.Count);
return true;
}
public void Dispose() => _savedMainEntitlements?.Delete();
public void Dispose()
{
foreach (var fileInfo in _savedEntitlements.Values)
fileInfo.Delete();
foreach (var fileInfo in _mergedEntitlementFiles)
fileInfo.Delete();
}
}

View File

@@ -76,21 +76,25 @@ internal class IviRootCommandProcessor
if (hasIdentity && !isAdHocSigning && !hasEntitlements)
CriticalError("Entitlements are required for non ad hoc identity signing.");
if (isAdHocSigning && !hasEntitlements)
await _codesigningManager.SaveMainBinaryEntitlementsAsync();
if (!await _codesigningManager.RemoveSignatureAsync(!hasIdentity || hasEntitlements))
if (hasIdentity)
{
if (!await _codesigningManager.SaveMainExecutablesEntitlementsAsync())
{
_logger.LogError(
"Unable to save entitlements for one or more binaries. The package is likely unsigned, and all specified entitlements will be applied."
);
}
}
if (!await _codesigningManager.RemoveSignatureAsync())
CriticalError("Unable to remove signature from one or more binaries.");
await _injectionManager.InsertLoadCommandsAsync();
if (hasIdentity)
{
if (!await _codesigningManager.SignAsync(
signingInfo!.Identity,
isAdHocSigning,
signingInfo.Entitlements))
if (!await _codesigningManager.SignPackageAsync(signingInfo!))
CriticalError("Unable to sign one or more binaries.");
}
}

View File

@@ -31,6 +31,6 @@ internal static class InfoPlistDictionaryExtensions
internal static string BundleExecutable(this NSDictionary dictionary) =>
((NSString)dictionary[CoreFoundationBundleExecutableKey]).Content;
internal static async Task SaveToFile(this NSDictionary dictionary, string filePath) =>
internal static async Task SaveToFileAsync(this NSDictionary dictionary, string filePath) =>
await File.WriteAllTextAsync(filePath, dictionary.ToXmlPropertyList());
}

View File

@@ -77,7 +77,7 @@ internal partial class PackageManager
var newBundleId = bundleId.Replace(packageBundleId, customBundleId);
dictionary[CoreFoundationBundleIdentifierKey] = new NSString(newBundleId);
await dictionary.SaveToFile(file);
await dictionary.SaveToFileAsync(file);
replacedCount++;
}
@@ -94,7 +94,7 @@ internal partial class PackageManager
if (packagingInfo.EnableDocumentsSupport)
EnableDocumentSupport();
await _infoDictionary.SaveToFile(_infoDictionaryFile.FullName);
await _infoDictionary.SaveToFileAsync(_infoDictionaryFile.FullName);
if (packagingInfo.CustomBundleId is not { } customBundleId)
return;

View File

@@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
@@ -18,8 +18,8 @@
<ItemGroup>
<PackageReference Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="9.0.0-rc.2.24473.5" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="9.0.0-rc.2.24473.5" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="10.0.0-preview.3.25171.5" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="10.0.0-preview.3.25171.5" />
<PackageReference Include="plist-cil" Version="2.2.0" />
</ItemGroup>