Skip to content

Commit af99cee

Browse files
authored
[msbuild] Enable signing by default for all platforms. Fixes #18469. (#20824)
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 46acde9 commit af99cee

File tree

14 files changed

+243
-181
lines changed

14 files changed

+243
-181
lines changed

docs/building-apps/build-properties.md

Lines changed: 54 additions & 2 deletions
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
@@ -316,8 +335,7 @@ Default: true
316335

317336
If code signing is enabled.
318337

319-
Typically the build will automatically determine whether code signing is
320-
required; this automatic detection can be overridden with this property.
338+
Code signing is enabled by default for all platforms; this can be overridden with this property.
321339

322340
## EnableDefaultCodesignEntitlements
323341

@@ -866,6 +884,40 @@ only scan libraries with the `[LinkWith]` attribute for Objective-C classes:
866884
</PropertyGroup>
867885
```
868886

887+
## SdkIsSimulator
888+
889+
This property is a read-only property (setting it will have no effect) that
890+
specifies whether we're building for a simulator or not.
891+
892+
It is only set after [imports and
893+
properties](https://learn.microsoft.com/visualstudio/msbuild/build-process-overview#evaluate-imports-and-properties)
894+
have been evaluated. This means the property is not set while evaluating the
895+
properties in the project file, so this will _not_ work:
896+
897+
```xml
898+
<PropertyGroup>
899+
<EnableCodeSigning Condition="'$(SdkIsSimulator)' == 'true'">false</EnableCodeSigning>
900+
</PropertyGroup>
901+
```
902+
903+
However, the either of the following works:
904+
905+
```xml
906+
<ItemGroup>
907+
<!-- item groups (and their conditions) are evaluated after properties have been evaluated -->
908+
<CustomEntitlements Condition="'$(SdkIsSimulator)' == 'true'" Include="com.apple.simulator-entitlement" Type="Boolean" Value="true" />
909+
<CodesignConfigureDependsOn>$(CodesignConfigureDependsOn);ConfigureSimulatorSigning</CodesignConfigureDependsOn>
910+
</ItemGroup>
911+
<!-- targets are executed after properties have been evaluated -->
912+
<Target Name="ConfigureSimulatorSigning">
913+
<PropertyGroup>
914+
<EnableCodeSigning Condition="'$(SdkIsSimulator) == 'true'">false</EnableCodeSigning>
915+
</PropertyGroup>
916+
</Target>
917+
```
918+
919+
Note: this property will always be `false` on macOS and Mac Catalyst.
920+
869921
## SkipStaticLibraryValidation
870922

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

dotnet/targets/Xamarin.Shared.Sdk.props

Lines changed: 2 additions & 0 deletions
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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2178,7 +2178,7 @@
21782178
<Target Name="_ComputeCodesignItems"
21792179
Outputs="$(_CodesignItemsPath)"
21802180
>
2181-
<ItemGroup Condition="'$(_RequireCodeSigning)' == 'true' And '$(BundleCreateDump)' == 'true'">
2181+
<ItemGroup Condition="'$(EnableCodeSigning)' == 'true' And '$(BundleCreateDump)' == 'true'">
21822182
<!-- The 'createdump' executable must be signed. -->
21832183
<!-- Ref: https://github.com/dotnet/macios/issues/13417 -->
21842184
<_CreateDumpExecutableToSign Include="@(_CreateDumpExecutable -> '$(_DylibPublishDir)%(RelativePath)')" KeepMetadata="false">

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

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -126,8 +126,11 @@ bool Validate (SignInfo info)
126126
return true;
127127
}
128128

129-
bool StampFileNeedsUpdate (ITaskItem item)
129+
bool StampFileNeedsUpdate (ITaskItem? item)
130130
{
131+
if (item is null)
132+
return false;
133+
131134
var stampFile = GetCodesignStampFile (item);
132135
if (!File.Exists (stampFile)) {
133136
Log.LogMessage (MessageImportance.Low, "The stamp file '{0}' does not exist, so the item '{1}' needs to be codesigned.", stampFile, item.ItemSpec);
@@ -143,9 +146,12 @@ bool StampFileNeedsUpdate (ITaskItem item)
143146
}
144147

145148
// 'sortedItems' is sorted by length of path, longest first.
146-
bool NeedsCodesign (ITaskItem [] sortedItems, int index, string stampFileContents)
149+
bool NeedsCodesign (ITaskItem? [] sortedItems, int index, string stampFileContents)
147150
{
148151
var item = sortedItems [index];
152+
if (item is null)
153+
return false;
154+
149155
var stampFile = GetCodesignStampFile (item);
150156
if (StampFileNeedsUpdate (item))
151157
return true;
@@ -159,14 +165,15 @@ bool NeedsCodesign (ITaskItem [] sortedItems, int index, string stampFileContent
159165
var resolvedStampFile = Path.GetFullPath (PathUtils.ResolveSymbolicLinks (stampFile));
160166

161167
for (var i = 0; i < index; i++) {
162-
if (sortedItems [i] is null)
168+
var sortedItem = sortedItems [i];
169+
if (sortedItem is null)
163170
continue; // this item does not need to be signed
164-
if (sortedItems [i].ItemSpec.StartsWith (itemPath, StringComparison.OrdinalIgnoreCase)) {
165-
if (StampFileNeedsUpdate (sortedItems [i])) {
166-
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);
171+
if (sortedItem.ItemSpec.StartsWith (itemPath, StringComparison.OrdinalIgnoreCase)) {
172+
if (StampFileNeedsUpdate (sortedItem)) {
173+
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);
167174
return true; // there's an item inside this directory that needs to be signed, so this directory must be signed too
168175
}
169-
Log.LogMessage (MessageImportance.Low, "The item '{0}' contains '{1}', which must be signed, which means that the item must be signed too; however this other item has an up-to-date signature.", item.ItemSpec, sortedItems [i].ItemSpec);
176+
Log.LogMessage (MessageImportance.Low, "The item '{0}' contains '{1}', which must be signed, which means that the item must be signed too; however this other item has an up-to-date signature.", item.ItemSpec, sortedItem.ItemSpec);
170177
}
171178
}
172179

@@ -438,17 +445,22 @@ bool ExecuteUnsafe ()
438445
}
439446

440447
// first sort all the items by path length, longest path first.
441-
resourcesToSign = resourcesToSign.OrderBy (v => v.ItemSpec.Length).Reverse ().ToArray ();
448+
ITaskItem? [] sortedResources = resourcesToSign.OrderBy (v => v.ItemSpec.Length).Reverse ().ToArray ();
442449

443450
// remove items that are up-to-date
444451
var itemsToSign = new List<SignInfo> ();
445-
for (var i = 0; i < resourcesToSign.Length; i++) {
446-
var item = resourcesToSign [i];
452+
for (var i = 0; i < sortedResources.Length; i++) {
453+
var item = sortedResources [i];
454+
if (item is null)
455+
continue;
447456
var info = new SignInfo (item);
448457
if (!Validate (info))
449458
continue;
450-
if (NeedsCodesign (resourcesToSign, i, info.GetStampFileContents (this)))
459+
if (NeedsCodesign (sortedResources, i, info.GetStampFileContents (this))) {
451460
itemsToSign.Add (info);
461+
} else {
462+
sortedResources [i] = null;
463+
}
452464
}
453465

454466
if (Log.HasLoggedErrors)

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

Lines changed: 52 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,21 @@ protected string EntitlementBundlePath {
151151
}
152152
}
153153

154+
bool IsDeviceOrDesktop {
155+
get {
156+
switch (Platform) {
157+
case ApplePlatform.iOS:
158+
case ApplePlatform.TVOS:
159+
return !SdkIsSimulator;
160+
case ApplePlatform.MacOSX:
161+
case ApplePlatform.MacCatalyst:
162+
return true;
163+
default:
164+
throw new InvalidOperationException (string.Format (MSBStrings.InvalidPlatform, Platform));
165+
}
166+
}
167+
}
168+
154169
PString MergeEntitlementString (PString pstr, MobileProvision? profile, bool expandWildcards, string? key)
155170
{
156171
string TeamIdentifierPrefix;
@@ -159,7 +174,7 @@ PString MergeEntitlementString (PString pstr, MobileProvision? profile, bool exp
159174
if (string.IsNullOrEmpty (pstr.Value))
160175
return (PString) pstr.Clone ();
161176

162-
if (profile is null) {
177+
if (profile is null && IsDeviceOrDesktop) {
163178
if (!warnedTeamIdentifierPrefix && pstr.Value.Contains ("$(TeamIdentifierPrefix)")) {
164179
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);
165180
warnedTeamIdentifierPrefix = true;
@@ -537,33 +552,51 @@ public override bool Execute ()
537552

538553
compiled = GetCompiledEntitlements (profile, templates);
539554

540-
ValidateAppEntitlements (profile, compiled);
541-
542-
archived = GetArchivedExpandedEntitlements (templates, compiled);
543-
544-
try {
545-
Directory.CreateDirectory (Path.GetDirectoryName (CompiledEntitlements!.ItemSpec));
546-
WriteXcent (compiled, CompiledEntitlements.ItemSpec);
547-
} catch (Exception ex) {
548-
Log.LogError (MSBStrings.E0114, CompiledEntitlements, ex.Message);
549-
return false;
550-
}
551-
552-
SaveArchivedExpandedEntitlements (archived);
553-
554555
/* 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,
555556
* 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,
556557
* 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. */
557-
var compiledEntitlementsFullPath = new TaskItem (Path.GetFullPath (CompiledEntitlements!.ItemSpec));
558+
var compiledEntitlementsFullPath = Path.GetFullPath (CompiledEntitlements!.ItemSpec);
559+
var compiledEntitlementsFullPathItem = new TaskItem (compiledEntitlementsFullPath);
560+
561+
Directory.CreateDirectory (Path.GetDirectoryName (compiledEntitlementsFullPath));
558562

559563
if (BundleEntitlementsInExecutable) {
560-
if (compiled.Count > 0) {
561-
EntitlementsInExecutable = compiledEntitlementsFullPath;
564+
// Any entitlements the app desires are stored inside the executable for simulator builds,
565+
// and then the executable is signed with a placeholder signature ('-') + just a single
566+
// entitlement (com.apple.security.get-task-allow). One consequence of storing entitlements
567+
// this way is that no provisioning profile will be needed to sign the executable.
568+
var simulatedEntitlements = compiled;
569+
var simulatedXcent = Path.ChangeExtension (compiledEntitlementsFullPath, "").TrimEnd ('.') + "-Simulated.xcent";
570+
try {
571+
WriteXcent (simulatedEntitlements, simulatedXcent);
572+
} catch (Exception ex) {
573+
Log.LogError (MSBStrings.E0114, simulatedXcent, ex.Message);
574+
return false;
562575
}
576+
577+
EntitlementsInExecutable = new TaskItem (simulatedXcent);
578+
579+
// No matter what, I've only been able to make Xcode apply a single entitlement to simulator builds: com.apple.security.get-task-allow
580+
compiled = new PDictionary ();
581+
compiled.Add ("com.apple.security.get-task-allow", new PBoolean (true));
563582
} else {
564-
EntitlementsInSignature = compiledEntitlementsFullPath;
583+
archived = GetArchivedExpandedEntitlements (templates, compiled);
565584
}
566585

586+
ValidateAppEntitlements (profile, compiled);
587+
588+
try {
589+
WriteXcent (compiled, compiledEntitlementsFullPath);
590+
} catch (Exception ex) {
591+
Log.LogError (MSBStrings.E0114, compiledEntitlementsFullPathItem, ex.Message);
592+
return false;
593+
}
594+
595+
if (archived is not null)
596+
SaveArchivedExpandedEntitlements (archived);
597+
598+
EntitlementsInSignature = compiledEntitlementsFullPathItem;
599+
567600
return !Log.HasLoggedErrors;
568601
}
569602

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

Lines changed: 5 additions & 3 deletions
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)