Skip to content

Commit 7f4b088

Browse files
committed
[msbuild] Sign simulator apps by default. Fixes #18469.
When building for the simulator: * Signing is enabled by default. * Any entitlements the app requests will be embedded in the native executable in an `__ents_der` Mach-O section. * The actual app signature only demands the "com.apple.security.get-task-allow" entitlement. * No provisioning profiles are used. Also: * Unify the code to detect signing identity, so that it's as equal as possible between our platforms. Fixes #18469.
1 parent add3587 commit 7f4b088

File tree

11 files changed

+215
-127
lines changed

11 files changed

+215
-127
lines changed

docs/building-apps/build-properties.md

+53
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,25 @@ The path to the `codesign_allocate` tool.
156156

157157
By default this value is auto-detected.
158158

159+
## CodesignConfigureDependsOn
160+
161+
This is an extension point for the build: a developer can add any targets to
162+
this property to execute those targets before the build looks at any of the
163+
codesigning properties.
164+
165+
This can be used to
166+
167+
```xml
168+
<PropertyGroup>
169+
<CodesignConfigureDependsOn>$(CodesignConfigureDependsOn);DisableCodesignInSimulator</CodesignConfigureDependsOn>
170+
</PropertyGroup>
171+
<Target Name="DisableCodesignInSimulator" Condition="'$(SdkIsSimulator)' == 'true'">
172+
<PropertyGroup>
173+
<EnableCodeSigning>false</EnableCodeSigning>
174+
</PropertyGroup>
175+
</Target>
176+
```
177+
159178
## CodesignDependsOn
160179

161180
This is an extension point for the build: a developer can add any targets to
@@ -832,6 +851,40 @@ only scan libraries with the `[LinkWith]` attribute for Objective-C classes:
832851
</PropertyGroup>
833852
```
834853

854+
## SdkIsSimulator
855+
856+
This property is a read-only property (setting it will have no effect) that
857+
specifies whether we're building for a simulator or not.
858+
859+
It is only set after [imports and
860+
properties](https://learn.microsoft.com/visualstudio/msbuild/build-process-overview#evaluate-imports-and-properties)
861+
have been evaluated. This means the property is not set while evaluating the
862+
properties in the project file, so this will _not_ work:
863+
864+
```xml
865+
<PropertyGroup>
866+
<EnableCodeSigning Condition="'$(SdkIsSimulator)'">false</EnableCodeSigning>
867+
</PropertyGroup>
868+
```
869+
870+
However, the either of the following works:
871+
872+
```xml
873+
<ItemGroup>
874+
<!-- item groups (and their conditions) are evaluated after properties have been evaluated -->
875+
<CustomEntitlements Condition="'$(SdkIsSimulator)' == 'true'" Include="com.apple.simulator-entitlement" Type="Boolean" Value="true" />
876+
<CodesignConfigureDependsOn>$(CodesignConfigureDependsOn);ConfigureSimulatorSigning</CodesignConfigureDependsOn>
877+
</ItemGroup>
878+
<!-- targets are executed after properties have been evaluated -->
879+
<Target Name="ConfigureSimulatorSigning">
880+
<PropertyGroup>
881+
<EnableCodeSigning Condition="'$(SdkIsSimulator)'">false</EnableCodeSigning>
882+
</PropertyGroup>
883+
</Target>
884+
```
885+
886+
Note: this property will always be `false` on macOS and Mac Catalyst.
887+
835888
## SkipStaticLibraryValidation
836889

837890
Hot Restart doesn't support linking with static libraries, so by default we'll

dotnet/targets/Xamarin.Shared.Sdk.props

+2
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,8 @@
134134
<PropertyGroup>
135135
<_SdkIsSimulator Condition="$(RuntimeIdentifier.Contains('simulator')) Or $(RuntimeIdentifiers.Contains('simulator'))">true</_SdkIsSimulator>
136136
<_SdkIsSimulator Condition="'$(_SdkIsSimulator)' == ''">false</_SdkIsSimulator>
137+
<!-- Set a public property that specifies if we're building for the simulator. -->
138+
<SdkIsSimulator>$(_SdkIsSimulator)</SdkIsSimulator>
137139
</PropertyGroup>
138140

139141
<PropertyGroup>

msbuild/Xamarin.MacDev.Tasks/Tasks/Codesign.cs

+17-8
Original file line numberDiff line numberDiff line change
@@ -127,9 +127,12 @@ bool Validate (SignInfo info)
127127
}
128128

129129
// 'sortedItems' is sorted by length of path, longest first.
130-
bool NeedsCodesign (ITaskItem [] sortedItems, int index, string stampFileContents)
130+
bool NeedsCodesign (ITaskItem? [] sortedItems, int index, string stampFileContents)
131131
{
132132
var item = sortedItems [index];
133+
if (item is null)
134+
return false;
135+
133136
var stampFile = GetCodesignStampFile (item);
134137
if (!File.Exists (stampFile)) {
135138
Log.LogMessage (MessageImportance.Low, "The stamp file '{0}' does not exist, so the item '{1}' needs to be codesigned.", stampFile, item.ItemSpec);
@@ -150,10 +153,11 @@ bool NeedsCodesign (ITaskItem [] sortedItems, int index, string stampFileContent
150153
var resolvedStampFile = Path.GetFullPath (PathUtils.ResolveSymbolicLinks (stampFile));
151154

152155
for (var i = 0; i < index; i++) {
153-
if (sortedItems [i] is null)
156+
var sortedItem = sortedItems [i];
157+
if (sortedItem is null)
154158
continue; // this item does not need to be signed
155-
if (sortedItems [i].ItemSpec.StartsWith (itemPath, StringComparison.OrdinalIgnoreCase)) {
156-
Log.LogMessage (MessageImportance.Low, "The item '{0}' contains '{1}', which must be signed, which means that the item must be signed too.", item.ItemSpec, sortedItems [i].ItemSpec);
159+
if (sortedItem.ItemSpec.StartsWith (itemPath, StringComparison.OrdinalIgnoreCase)) {
160+
Log.LogMessage (MessageImportance.Low, "The item '{0}' contains '{1}', which must be signed, which means that the item must be signed too.", item.ItemSpec, sortedItem.ItemSpec);
157161
return true; // there's an item inside this directory that needs to be signed, so this directory must be signed too
158162
}
159163
}
@@ -416,17 +420,22 @@ bool ExecuteUnsafe ()
416420
}
417421

418422
// first sort all the items by path length, longest path first.
419-
resourcesToSign = resourcesToSign.OrderBy (v => v.ItemSpec.Length).Reverse ().ToArray ();
423+
ITaskItem?[] sortedResources = resourcesToSign.OrderBy (v => v.ItemSpec.Length).Reverse ().ToArray ();
420424

421425
// remove items that are up-to-date
422426
var itemsToSign = new List<SignInfo> ();
423-
for (var i = 0; i < resourcesToSign.Length; i++) {
424-
var item = resourcesToSign [i];
427+
for (var i = 0; i < sortedResources.Length; i++) {
428+
var item = sortedResources [i];
429+
if (item is null)
430+
continue;
425431
var info = new SignInfo (item);
426432
if (!Validate (info))
427433
continue;
428-
if (NeedsCodesign (resourcesToSign, i, info.GetStampFileContents (this)))
434+
if (NeedsCodesign (sortedResources, i, info.GetStampFileContents (this))) {
429435
itemsToSign.Add (info);
436+
} else {
437+
sortedResources [i] = null;
438+
}
430439
}
431440

432441
if (Log.HasLoggedErrors)

msbuild/Xamarin.MacDev.Tasks/Tasks/CompileEntitlements.cs

+54-22
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,22 @@ protected string EntitlementBundlePath {
137137
}
138138
}
139139

140+
bool IsDeviceOrDesktop {
141+
get {
142+
switch (Platform) {
143+
case ApplePlatform.iOS:
144+
case ApplePlatform.TVOS:
145+
case ApplePlatform.WatchOS:
146+
return !SdkIsSimulator;
147+
case ApplePlatform.MacOSX:
148+
case ApplePlatform.MacCatalyst:
149+
return true;
150+
default:
151+
throw new InvalidOperationException (string.Format (MSBStrings.InvalidPlatform, Platform));
152+
}
153+
}
154+
}
155+
140156
PString MergeEntitlementString (PString pstr, MobileProvision? profile, bool expandWildcards, string? key)
141157
{
142158
string TeamIdentifierPrefix;
@@ -145,7 +161,7 @@ PString MergeEntitlementString (PString pstr, MobileProvision? profile, bool exp
145161
if (string.IsNullOrEmpty (pstr.Value))
146162
return (PString) pstr.Clone ();
147163

148-
if (profile is null) {
164+
if (profile is null && IsDeviceOrDesktop) {
149165
if (!warnedTeamIdentifierPrefix && pstr.Value.Contains ("$(TeamIdentifierPrefix)")) {
150166
Log.LogWarning (null, null, null, Entitlements, 0, 0, 0, 0, MSBStrings.W0108b /* Cannot expand $(TeamIdentifierPrefix) in Entitlements.plist without a provisioning profile for key '{0}' with value '{1}' */, key, pstr.Value);
151167
warnedTeamIdentifierPrefix = true;
@@ -455,7 +471,7 @@ public override bool Execute ()
455471
MobileProvision? profile;
456472
PDictionary template;
457473
PDictionary compiled;
458-
PDictionary archived;
474+
PDictionary? archived = null;
459475
string path;
460476

461477
switch (SdkPlatform) {
@@ -507,34 +523,50 @@ public override bool Execute ()
507523

508524
compiled = GetCompiledEntitlements (profile, template);
509525

510-
ValidateAppEntitlements (profile, compiled);
526+
/* The path to the entitlements must be resolved to the full path, because we might want to reference it from a containing project that just references this project,
527+
* and in that case it becomes a bit complicated to resolve to a full path on disk when building remotely from Windows. Instead just resolve to a full path here,
528+
* and use that from now on. This has to be done from a task, so that we get the full path on the mac when executed remotely from Windows. */
529+
var compiledEntitlementsFullPath = Path.GetFullPath (CompiledEntitlements!.ItemSpec);
530+
var compiledEntitlementsFullPathItem = new TaskItem (compiledEntitlementsFullPath);
531+
532+
Directory.CreateDirectory (Path.GetDirectoryName (compiledEntitlementsFullPath));
533+
534+
if (SdkIsSimulator) {
535+
// Any entitlements the app desires are stored inside the executable for simulator builds,
536+
// and then the executable is signed with a placeholder signature ('-') + just a single
537+
// entitlement (com.apple.security.get-task-allow). One consequence of storing entitlements
538+
// this way is that no provisioning profile will be needed to sign the executable.
539+
var simulatedEntitlements = compiled;
540+
var simulatedXcent = Path.ChangeExtension (compiledEntitlementsFullPath, "").TrimEnd ('.') + "-Simulated.xcent";
541+
try {
542+
WriteXcent (simulatedEntitlements, simulatedXcent);
543+
} catch (Exception ex) {
544+
Log.LogError (MSBStrings.E0114, simulatedXcent, ex.Message);
545+
return false;
546+
}
547+
548+
EntitlementsInExecutable = new TaskItem (simulatedXcent);
511549

512-
archived = GetArchivedExpandedEntitlements (template, compiled);
550+
// No matter what, I've only been able to make Xcode apply a single entitlement to simulator builds: com.apple.security.get-task-allow
551+
compiled = new PDictionary ();
552+
compiled.Add ("com.apple.security.get-task-allow", new PBoolean (true));
553+
} else {
554+
archived = GetArchivedExpandedEntitlements (template, compiled);
555+
}
556+
557+
ValidateAppEntitlements (profile, compiled);
513558

514559
try {
515-
Directory.CreateDirectory (Path.GetDirectoryName (CompiledEntitlements!.ItemSpec));
516-
WriteXcent (compiled, CompiledEntitlements.ItemSpec);
560+
WriteXcent (compiled, compiledEntitlementsFullPath);
517561
} catch (Exception ex) {
518-
Log.LogError (MSBStrings.E0114, CompiledEntitlements, ex.Message);
562+
Log.LogError (MSBStrings.E0114, compiledEntitlementsFullPathItem, ex.Message);
519563
return false;
520564
}
521565

522-
SaveArchivedExpandedEntitlements (archived);
566+
if (archived is not null)
567+
SaveArchivedExpandedEntitlements (archived);
523568

524-
/* The path to the entitlements must be resolved to the full path, because we might want to reference it from a containing project that just references this project,
525-
* and in that case it becomes a bit complicated to resolve to a full path on disk when building remotely from Windows. Instead just resolve to a full path here,
526-
* and use that from now on. This has to be done from a task, so that we get the full path on the mac when executed remotely from Windows. */
527-
var compiledEntitlementsFullPath = new TaskItem (Path.GetFullPath (CompiledEntitlements!.ItemSpec));
528-
529-
if (Platform == Utils.ApplePlatform.MacCatalyst) {
530-
EntitlementsInSignature = compiledEntitlementsFullPath;
531-
} else if (SdkIsSimulator) {
532-
if (compiled.Count > 0) {
533-
EntitlementsInExecutable = compiledEntitlementsFullPath;
534-
}
535-
} else {
536-
EntitlementsInSignature = compiledEntitlementsFullPath;
537-
}
569+
EntitlementsInSignature = compiledEntitlementsFullPathItem;
538570

539571
return !Log.HasLoggedErrors;
540572
}

msbuild/Xamarin.MacDev.Tasks/Tasks/DetectSigningIdentity.cs

+14-75
Original file line numberDiff line numberDiff line change
@@ -588,88 +588,27 @@ bool ExecuteImpl ()
588588
identity.BundleId = BundleIdentifier;
589589
DetectedAppId = BundleIdentifier; // default value that can be changed below
590590

591+
// If a code signing key has been pre-detected, it overrides any custom detection on our side.
592+
if (!string.IsNullOrEmpty (DetectedCodeSigningKey))
593+
return !Log.HasLoggedErrors;
594+
591595
// If the developer chooses to use the placeholder codesigning key, accept that.
592596
if (SigningKey == "-") {
593597
DetectedCodeSigningKey = SigningKey;
594598
return !Log.HasLoggedErrors;
595599
}
596600

597-
if (Platform == ApplePlatform.MacOSX) {
598-
if (!RequireCodeSigning || !string.IsNullOrEmpty (DetectedCodeSigningKey)) {
599-
return !Log.HasLoggedErrors;
600-
}
601-
} else if (Platform == ApplePlatform.MacCatalyst) {
602-
var doesNotNeedCodeSigningCertificate = !RequireCodeSigning || !string.IsNullOrEmpty (DetectedCodeSigningKey);
603-
if (RequireProvisioningProfile)
604-
doesNotNeedCodeSigningCertificate = false;
605-
if (doesNotNeedCodeSigningCertificate) {
606-
DetectedCodeSigningKey = "-";
607-
608-
return !Log.HasLoggedErrors;
609-
}
610-
} else {
611-
// Framework is either iOS or tvOS
612-
if (SdkIsSimulator) {
613-
if (AppleSdkSettings.XcodeVersion.Major >= 8 && RequireProvisioningProfile) {
614-
// Note: Starting with Xcode 8.0, we need to codesign iOS Simulator builds that enable Entitlements
615-
// in order for them to run. The "-" key is a special value allowed by the codesign utility that
616-
// allows us to get away with not having an actual codesign key.
617-
DetectedCodeSigningKey = "-";
618-
619-
if (!IsAutoCodeSignProfile (ProvisioningProfile)) {
620-
identity.Profile = MobileProvisionIndex.GetMobileProvision (platform, ProvisioningProfile);
621-
622-
if (identity.Profile is null) {
623-
Log.LogError (MSBStrings.E0140, PlatformName, ProvisioningProfile);
624-
return false;
625-
}
626-
627-
identity.AppId = ConstructValidAppId (identity.Profile, identity.BundleId);
628-
if (identity.AppId is null) {
629-
Log.LogError (MSBStrings.E0141, identity.BundleId, ProvisioningProfile);
630-
return false;
631-
}
632-
633-
provisioningProfileName = identity.Profile.Name;
634-
635-
DetectedProvisioningProfile = identity.Profile.Uuid;
636-
DetectedDistributionType = identity.Profile.DistributionType.ToString ();
637-
} else {
638-
certs = new X509Certificate2 [0];
639-
640-
if ((profiles = GetProvisioningProfiles (platform, type, identity, certs)) is null)
641-
return false;
642-
643-
if ((pairs = GetCodeSignIdentityPairs (profiles, certs)) is null)
644-
return false;
645-
646-
var match = GetBestMatch (pairs, identity);
647-
identity.Profile = match.Profile;
648-
identity.AppId = match.AppId;
649-
650-
if (identity.Profile is not null) {
651-
DetectedDistributionType = identity.Profile.DistributionType.ToString ();
652-
DetectedProvisioningProfile = identity.Profile.Uuid;
653-
provisioningProfileName = identity.Profile.Name;
654-
}
655-
656-
DetectedAppId = identity.AppId;
657-
}
658-
} else {
659-
// Note: Do not codesign. Codesigning seems to break the iOS Simulator in older versions of Xcode.
660-
DetectedCodeSigningKey = null;
661-
}
662-
663-
return !Log.HasLoggedErrors;
664-
}
665-
666-
if (!SdkIsSimulator && !RequireCodeSigning) {
667-
// The "-" key is a special value allowed by the codesign utility that
668-
// allows us to get away with not having an actual codesign key.
669-
DetectedCodeSigningKey = "-";
601+
// If no code signing is required and no provisioning profile is required, we can get away
602+
// with using the placeholder codesign key.
603+
if (!RequireCodeSigning && !RequireProvisioningProfile) {
604+
DetectedCodeSigningKey = "-";
605+
return !Log.HasLoggedErrors;
606+
}
670607

671-
return !Log.HasLoggedErrors;
672-
}
608+
// If we're building for the simulator, always use the placeholder codesign key.
609+
if (SdkIsSimulator) {
610+
DetectedCodeSigningKey = "-";
611+
return !Log.HasLoggedErrors;
673612
}
674613

675614
// Note: if we make it this far, we absolutely need a codesigning certificate

msbuild/Xamarin.MacDev.Tasks/Tasks/LinkNativeCode.cs

+30-1
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ bool ExecuteUnsafe ()
202202
foreach (var obj in ObjectFiles)
203203
arguments.Add (Path.GetFullPath (obj.ItemSpec));
204204

205-
arguments.AddRange (GetEmbedEntitlementsInExecutableLinkerFlags (EntitlementsInExecutable));
205+
arguments.AddRange (GetEmbedEntitlementsWithDerInExecutableLinkerFlags (EntitlementsInExecutable));
206206

207207
arguments.Add ("-o");
208208
arguments.Add (Path.GetFullPath (OutputFile));
@@ -242,6 +242,20 @@ bool ExecuteUnsafe ()
242242
return !Log.HasLoggedErrors;
243243
}
244244

245+
IEnumerable<string> GetEmbedEntitlementsWithDerInExecutableLinkerFlags (string entitlements)
246+
{
247+
var rv = GetEmbedEntitlementsInExecutableLinkerFlags (entitlements).ToList ();
248+
if (rv.Count > 0) {
249+
rv.AddRange (new string [] {
250+
"-Xlinker", "-sectcreate",
251+
"-Xlinker", "__TEXT",
252+
"-Xlinker", "__ents_der",
253+
"-Xlinker", ConvertEntitlementsToDerEntitlements (Path.GetFullPath (entitlements)),
254+
});
255+
}
256+
return rv;
257+
}
258+
245259
public static string [] GetEmbedEntitlementsInExecutableLinkerFlags (string entitlements)
246260
{
247261
if (string.IsNullOrEmpty (entitlements))
@@ -258,6 +272,21 @@ public static string [] GetEmbedEntitlementsInExecutableLinkerFlags (string enti
258272
};
259273
}
260274

275+
string ConvertEntitlementsToDerEntitlements (string entitlements)
276+
{
277+
var derEntitlements = entitlements + ".der";
278+
var arguments = new List<string> () {
279+
"derq",
280+
"query",
281+
"-f", "xml",
282+
"-i", entitlements,
283+
"-o", derEntitlements,
284+
"--raw",
285+
};
286+
ExecuteAsync ("xcrun", arguments, sdkDevPath: SdkDevPath).Wait ();
287+
return derEntitlements;
288+
}
289+
261290
static bool EntitlementsRequireLinkerFlags (string path)
262291
{
263292
try {

0 commit comments

Comments
 (0)