Skip to content

Commit 1107d8d

Browse files
committed
[msbuild] Enable signing by default for all platforms. Fixes #18469.
Enable signing by default for all platforms, because that's what Xcode does. This fixes an issue for simulator builds, where certain features don't work unless the app is signed (#18469). It also simplifies and unifies our code to detect signing identities, so that it's much more shared/equal beteween platforms. However, simulator builds have a few peculiarities: * The placeholder code signing certificate ("-", or what Xcode calls "Sign to Run Locally") is always used. * Any entitlements the app requests will be embedded in the native executable in a Mach-O section named `__ents_der`. * The actual app signature only demands the "com.apple.security.get-task-allow" entitlement (which seems to be allowed without a provisioning profile when using the placeholder code signing certificate). * No provisioning profiles are used. In order to provide a fairly decent way of restoring old behavior (not signing simulator builds), I created a public property to determine whether we're building for the simulator (SdkIsSimulator), and added documentation on how to use this new property to disable code signing. Fixes #18469.
1 parent 9e1ee56 commit 1107d8d

File tree

13 files changed

+232
-169
lines changed

13 files changed

+232
-169
lines changed

docs/building-apps/build-properties.md

+54-2
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 for instance be used to disable code signing for simulator builds:
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
@@ -295,8 +314,7 @@ Default: true
295314

296315
If code signing is enabled.
297316

298-
Typically the build will automatically determine whether code signing is
299-
required; this automatic detection can be overridden with this property.
317+
Code signing is enabled by default for all platforms; this can be overridden with this property.
300318

301319
## EnableDefaultCodesignEntitlements
302320

@@ -832,6 +850,40 @@ only scan libraries with the `[LinkWith]` attribute for Objective-C classes:
832850
</PropertyGroup>
833851
```
834852

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

837889
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>

dotnet/targets/Xamarin.Shared.Sdk.targets

+1-1
Original file line numberDiff line numberDiff line change
@@ -2004,7 +2004,7 @@
20042004
<Target Name="_ComputeCodesignItems"
20052005
Outputs="$(_CodesignItemsPath)"
20062006
>
2007-
<ItemGroup Condition="'$(_RequireCodeSigning)' == 'true' And '$(BundleCreateDump)' == 'true'">
2007+
<ItemGroup Condition="'$(EnableCodeSigning)' == 'true' And '$(BundleCreateDump)' == 'true'">
20082008
<!-- The 'createdump' executable must be signed. -->
20092009
<!-- Ref: https://github.com/dotnet/macios/issues/13417 -->
20102010
<_CreateDumpExecutableToSign Include="@(_CreateDumpExecutable -> '$(_DylibPublishDir)%(RelativePath)')" KeepMetadata="false">

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

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

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

148-
if (profile is null) {
163+
if (profile is null && IsDeviceOrDesktop) {
149164
if (!warnedTeamIdentifierPrefix && pstr.Value.Contains ("$(TeamIdentifierPrefix)")) {
150165
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);
151166
warnedTeamIdentifierPrefix = true;
@@ -455,7 +470,7 @@ public override bool Execute ()
455470
MobileProvision? profile;
456471
PDictionary template;
457472
PDictionary compiled;
458-
PDictionary archived;
473+
PDictionary? archived = null;
459474
string path;
460475

461476
switch (SdkPlatform) {
@@ -507,34 +522,50 @@ public override bool Execute ()
507522

508523
compiled = GetCompiledEntitlements (profile, template);
509524

510-
ValidateAppEntitlements (profile, compiled);
525+
/* 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,
526+
* 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,
527+
* 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. */
528+
var compiledEntitlementsFullPath = Path.GetFullPath (CompiledEntitlements!.ItemSpec);
529+
var compiledEntitlementsFullPathItem = new TaskItem (compiledEntitlementsFullPath);
530+
531+
Directory.CreateDirectory (Path.GetDirectoryName (compiledEntitlementsFullPath));
532+
533+
if (SdkIsSimulator) {
534+
// Any entitlements the app desires are stored inside the executable for simulator builds,
535+
// and then the executable is signed with a placeholder signature ('-') + just a single
536+
// entitlement (com.apple.security.get-task-allow). One consequence of storing entitlements
537+
// this way is that no provisioning profile will be needed to sign the executable.
538+
var simulatedEntitlements = compiled;
539+
var simulatedXcent = Path.ChangeExtension (compiledEntitlementsFullPath, "").TrimEnd ('.') + "-Simulated.xcent";
540+
try {
541+
WriteXcent (simulatedEntitlements, simulatedXcent);
542+
} catch (Exception ex) {
543+
Log.LogError (MSBStrings.E0114, simulatedXcent, ex.Message);
544+
return false;
545+
}
546+
547+
EntitlementsInExecutable = new TaskItem (simulatedXcent);
511548

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

514558
try {
515-
Directory.CreateDirectory (Path.GetDirectoryName (CompiledEntitlements!.ItemSpec));
516-
WriteXcent (compiled, CompiledEntitlements.ItemSpec);
559+
WriteXcent (compiled, compiledEntitlementsFullPath);
517560
} catch (Exception ex) {
518-
Log.LogError (MSBStrings.E0114, CompiledEntitlements, ex.Message);
561+
Log.LogError (MSBStrings.E0114, compiledEntitlementsFullPathItem, ex.Message);
519562
return false;
520563
}
521564

522-
SaveArchivedExpandedEntitlements (archived);
565+
if (archived is not null)
566+
SaveArchivedExpandedEntitlements (archived);
523567

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-
}
568+
EntitlementsInSignature = compiledEntitlementsFullPathItem;
538569

539570
return !Log.HasLoggedErrors;
540571
}

msbuild/Xamarin.MacDev.Tasks/Tasks/ComputeCodesignItems.cs

+5-3
Original file line numberDiff line numberDiff line change
@@ -67,11 +67,13 @@ public override bool Execute ()
6767

6868
// Add the app bundles themselves
6969
foreach (var bundle in CodesignBundle) {
70-
// An app bundle is signed if either 'RequireCodeSigning' is true
70+
// An app bundle is signed if either 'EnableCodeSigning' is true
7171
// or a 'CodesignSigningKey' has been provided.
72-
var requireCodeSigning = bundle.GetMetadata ("RequireCodeSigning");
72+
var enableCodeSigning = bundle.GetMetadata ("EnableCodeSigning");
73+
if (string.IsNullOrEmpty (enableCodeSigning))
74+
enableCodeSigning = bundle.GetMetadata ("RequireCodeSigning");
7375
var codesignSigningKey = bundle.GetMetadata ("CodesignSigningKey");
74-
if (!string.Equals (requireCodeSigning, "true") && string.IsNullOrEmpty (codesignSigningKey))
76+
if (!string.Equals (enableCodeSigning, "true") && string.IsNullOrEmpty (codesignSigningKey))
7577
continue;
7678

7779
// Create a new item for the app bundle, and copy any metadata over.

0 commit comments

Comments
 (0)