Skip to content

Commit 04d4a86

Browse files
committed
iOS,macOS: Skip codesigning files in unsigned_binaries.txt
This updates the code-signing workflow to account for iOS and macOS binaries in the artifact cache that are _expected_ to not be codesigned. In flutter/engine#54414 we started bundling dSYM (debugging symbols) within Flutter.xcframework, a requirement for App Store verification using Xcode 16. We did the same for macOS in flutter/engine#54696. Unlike the framework dylib, dSYM contents are not directly codesigned (though the xcframework containing them is). This skips code-signing for files found in `unsigned_binaries.txt`, which will be added in a followup patch to the framework artifact archive creation scripts in engine: * `sky/tools/create_ios_framework.py` * `sky/tools/create_macos_framework.py` Issue: flutter/flutter#154571
1 parent 8f96724 commit 04d4a86

File tree

2 files changed

+78
-42
lines changed

2 files changed

+78
-42
lines changed

cipd_packages/codesign/lib/src/file_codesign_visitor.dart

Lines changed: 55 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,10 @@ enum CodesignType {
2929
withEntitlements(filename: 'entitlements.txt'),
3030

3131
/// Binaries requiring codesigning that DO NOT use APIs requiring entitlements.
32-
withoutEntitlements(filename: 'without_entitlements.txt');
32+
withoutEntitlements(filename: 'without_entitlements.txt'),
33+
34+
/// Binaries that do not require codesigning.
35+
unsigned(filename: 'unsigned_binaries.txt');
3336

3437
const CodesignType({required this.filename});
3538

@@ -87,6 +90,9 @@ class FileCodesignVisitor {
8790

8891
/// Files that require codesigning that DO NOT use APIs requiring entitlements.
8992
Set<String> withoutEntitlementsFiles = <String>{};
93+
94+
/// Files that do not require codesigning.
95+
Set<String> unsignedBinaryFiles = <String>{};
9096
Set<String> fileConsumed = <String>{};
9197
Set<String> directoriesVisited = <String>{};
9298
Map<String, String> availablePasswords = {
@@ -124,26 +130,30 @@ class FileCodesignVisitor {
124130
static final RegExp _notarytoolStatusCheckPattern = RegExp(r'[ ]*status: ([a-zA-z ]+)');
125131
static final RegExp _notarytoolRequestPattern = RegExp(r'id: ([a-z0-9-]+)');
126132

127-
static const String fixItInstructions = '''
133+
static final String fixItInstructions = '''
128134
Codesign test failed.
129135
130136
We compared binary files in engine artifacts with those listed in
131-
entitlement.txt and withoutEntitlements.txt, and the binary files do not match.
132-
*entitlements.txt is the configuration file encoded in engine artifact zip,
133-
built by BUILD.gn and Ninja, to detail the list of entitlement files.
134-
Either an expected file was not found in *entitlements.txt, or an unexpected
135-
file was found in entitlements.txt.
137+
* ${CodesignType.withEntitlements.filename}
138+
* ${CodesignType.withoutEntitlements.filename}
139+
* ${CodesignType.unsigned.filename}
140+
and the binary files do not match.
141+
142+
These are the configuration files encoded in engine artifact zip that detail
143+
the code-signing requirements of each of the binaries in the archive.
144+
Either an unexpected binary was listed in these files, or one of the expected
145+
binaries listed in these files was not found in the archive.
136146
137147
This usually happens during an engine roll.
138-
If this is a valid change, then BUILD.gn needs to be changed.
139-
Binaries that will run on a macOS host require entitlements, and
140-
binaries that run on an iOS device must NOT have entitlements.
148+
149+
If this is a valid change, then the BUILD.gn or the codesigning configuration
150+
files need to be changed. Binaries that will run on a macOS host require
151+
entitlements, and binaries that run on an iOS device must NOT have entitlements.
141152
For example, if this is a new binary that runs on macOS host, add it
142-
to [entitlements.txt] file inside the zip artifact produced by BUILD.gn.
143-
If this is a new binary that needs to be run on iOS device, add it
144-
to [withoutEntitlements.txt].
145-
If there are obsolete binaries in entitlements configuration files, please delete or
146-
update these file paths accordingly.
153+
to ${CodesignType.withEntitlements.filename} file inside the zip artifact produced by BUILD.gn.
154+
If this is a new binary that needs to be run on iOS device, add it to
155+
${CodesignType.withoutEntitlements.filename}. If there are obsolete binaries in entitlements
156+
configuration files, please delete or update these file paths accordingly.
147157
''';
148158

149159
/// Read a single line of password stored at [passwordFilePath].
@@ -202,8 +212,10 @@ update these file paths accordingly.
202212
// Read codesigning configuration files.
203213
withEntitlementsFiles = await parseCodesignConfig(parentDirectory, CodesignType.withEntitlements);
204214
withoutEntitlementsFiles = await parseCodesignConfig(parentDirectory, CodesignType.withoutEntitlements);
215+
unsignedBinaryFiles = await parseCodesignConfig(parentDirectory, CodesignType.unsigned);
205216
log.info('parsed binaries with entitlements are $withEntitlementsFiles');
206217
log.info('parsed binaries without entitlements are $withoutEntitlementsFiles');
218+
log.info('parsed binaries without codesigning $unsignedBinaryFiles');
207219

208220
// recursively visit extracted files
209221
await visitDirectory(directory: parentDirectory, parentVirtualPath: '');
@@ -240,6 +252,7 @@ update these file paths accordingly.
240252

241253
await cleanupCodesignConfig(directory);
242254

255+
final Set<String> ignoredFiles = Set.from(CodesignType.values.map((CodesignType type) => type.filename));
243256
final List<FileSystemEntity> entities = await directory.list(followLinks: false).toList();
244257
for (FileSystemEntity entity in entities) {
245258
if (entity is io.Link) {
@@ -254,7 +267,7 @@ update these file paths accordingly.
254267
);
255268
continue;
256269
}
257-
if (entity.basename == 'entitlements.txt' || entity.basename == 'without_entitlements.txt') {
270+
if (ignoredFiles.contains(entity.basename)) {
258271
continue;
259272
}
260273
final FileType childType = getFileType(
@@ -306,40 +319,47 @@ update these file paths accordingly.
306319
await newDir.delete(recursive: true);
307320
}
308321

309-
/// Visit and codesign a binary with / without entitlement.
322+
/// Visit and handle code-signing for a binary.
310323
///
311-
/// At this stage, the virtual [entitlementCurrentPath] accumulated through the recursive visit, is compared
312-
/// against the paths extracted from [withEntitlementsFiles], to help determine if this file should be signed
313-
/// with entitlements.
324+
/// At this stage, the virtual [currentFilePath] accumulated through the recursive
325+
/// visit is compared against the paths extracted from the contents of the codesigning
326+
/// config files, to help determine whether or not this file should be codesigned
327+
/// and if so, whether or not it should be signed with entitlements.
314328
Future<void> visitBinaryFile({
315329
required File binaryFile,
316330
required String parentVirtualPath,
317331
}) async {
318332
final String currentFileName = binaryFile.basename;
319-
final String entitlementCurrentPath = joinEntitlementPaths(parentVirtualPath, currentFileName);
320-
321-
if (!withEntitlementsFiles.contains(entitlementCurrentPath) &&
322-
!withoutEntitlementsFiles.contains(entitlementCurrentPath)) {
323-
log.severe('the binary file $currentFileName is causing an issue. \n'
324-
'This file is located at $entitlementCurrentPath in the flutter engine artifact.');
325-
log.severe('The system has detected a binary file at $entitlementCurrentPath. '
326-
'But it is not in the entitlements configuration files you provided. '
333+
final String currentFilePath = joinEntitlementPaths(parentVirtualPath, currentFileName);
334+
335+
if (!withEntitlementsFiles.contains(currentFilePath) &&
336+
!withoutEntitlementsFiles.contains(currentFilePath) &&
337+
!unsignedBinaryFiles.contains(currentFilePath)) {
338+
log.severe('The binary file $currentFileName is causing an issue. \n'
339+
'This file is located at $currentFilePath in the flutter engine artifact.');
340+
log.severe('The system has detected a binary file at $currentFilePath. '
341+
'But it is not in the codesigning configuration files you provided. '
327342
'If this is a new engine artifact, please add it to one of the entitlements.txt files.');
328343
throw CodesignException(fixItInstructions);
329344
}
330-
log.info('signing file at path ${binaryFile.absolute.path}');
331-
log.info('the virtual entitlement path associated with file is $entitlementCurrentPath');
332-
log.info('the decision to sign with entitlement is ${withEntitlementsFiles.contains(entitlementCurrentPath)}');
333-
fileConsumed.add(entitlementCurrentPath);
345+
if (unsignedBinaryFiles.contains(currentFilePath)) {
346+
// No codesigning necessary.
347+
log.info('Not signing file at path ${binaryFile.absolute.path}');
348+
return;
349+
}
350+
log.info('Signing file at path ${binaryFile.absolute.path}');
351+
log.info('The virtual entitlement path associated with file is $currentFilePath');
352+
log.info('The decision to sign with entitlement is ${withEntitlementsFiles.contains(currentFilePath)}');
353+
fileConsumed.add(currentFilePath);
334354
if (dryrun) {
335355
return;
336356
}
337-
await codesignAtPath(binaryOrBundlePath: binaryFile.absolute.path, entitlementCurrentPath: entitlementCurrentPath);
357+
await codesignAtPath(binaryOrBundlePath: binaryFile.absolute.path, currentFilePath: currentFilePath);
338358
}
339359

340360
Future<void> codesignAtPath({
341361
required String binaryOrBundlePath,
342-
String? entitlementCurrentPath,
362+
String? currentFilePath,
343363
}) async {
344364
final List<String> args = <String>[
345365
'/usr/bin/codesign',
@@ -351,7 +371,7 @@ update these file paths accordingly.
351371
binaryOrBundlePath,
352372
'--timestamp', // add a secure timestamp
353373
'--options=runtime', // hardened runtime
354-
if (entitlementCurrentPath != '' && withEntitlementsFiles.contains(entitlementCurrentPath)) ...<String>[
374+
if (currentFilePath != '' && withEntitlementsFiles.contains(currentFilePath)) ...<String>[
355375
'--entitlements',
356376
entitlementsFile.absolute.path,
357377
],

cipd_packages/codesign/test/file_codesign_visitor_test.dart

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -662,9 +662,11 @@ void main() {
662662
codesignVisitor.codesignTeamId = randomString;
663663
codesignVisitor.withEntitlementsFiles = <String>{'root/folder_a/file_a'};
664664
codesignVisitor.withoutEntitlementsFiles = <String>{'root/folder_b/file_b'};
665+
codesignVisitor.unsignedBinaryFiles = <String>{'root/folder_c/file_c'};
665666
fileSystem
666667
..file('${rootDirectory.path}/remote_zip_6/folder_a/file_a').createSync(recursive: true)
667-
..file('${rootDirectory.path}/remote_zip_6/folder_b/file_b').createSync(recursive: true);
668+
..file('${rootDirectory.path}/remote_zip_6/folder_b/file_b').createSync(recursive: true)
669+
..file('${rootDirectory.path}/remote_zip_6/folder_c/file_c').createSync(recursive: true);
668670
final Directory testDirectory = fileSystem.directory('${rootDirectory.path}/remote_zip_6');
669671
processManager.addCommands(<FakeCommand>[
670672
FakeCommand(
@@ -713,6 +715,15 @@ void main() {
713715
'--options=runtime',
714716
],
715717
),
718+
FakeCommand(
719+
command: <String>[
720+
'file',
721+
'--mime-type',
722+
'-b',
723+
'${rootDirectory.absolute.path}/remote_zip_6/folder_c/file_c',
724+
],
725+
stdout: 'application/x-mach-binary',
726+
),
716727
]);
717728
await codesignVisitor.visitDirectory(
718729
directory: testDirectory,
@@ -722,13 +733,18 @@ void main() {
722733
.where((LogRecord record) => record.level == Level.INFO)
723734
.map((LogRecord record) => record.message)
724735
.toList();
725-
expect(messages, contains('signing file at path ${rootDirectory.absolute.path}/remote_zip_6/folder_a/file_a'));
726-
expect(messages, contains('the virtual entitlement path associated with file is root/folder_a/file_a'));
727-
expect(messages, contains('the decision to sign with entitlement is true'));
736+
expect(messages, contains('Signing file at path ${rootDirectory.absolute.path}/remote_zip_6/folder_a/file_a'));
737+
expect(messages, contains('The virtual entitlement path associated with file is root/folder_a/file_a'));
738+
expect(messages, contains('The decision to sign with entitlement is true'));
739+
740+
expect(messages, contains('Signing file at path ${rootDirectory.absolute.path}/remote_zip_6/folder_b/file_b'));
741+
expect(messages, contains('The virtual entitlement path associated with file is root/folder_b/file_b'));
742+
expect(messages, contains('The decision to sign with entitlement is false'));
728743

729-
expect(messages, contains('signing file at path ${rootDirectory.absolute.path}/remote_zip_6/folder_b/file_b'));
730-
expect(messages, contains('the virtual entitlement path associated with file is root/folder_b/file_b'));
731-
expect(messages, contains('the decision to sign with entitlement is false'));
744+
expect(
745+
messages,
746+
contains('Not signing file at path ${rootDirectory.absolute.path}/remote_zip_6/folder_c/file_c'),
747+
);
732748
});
733749
});
734750

0 commit comments

Comments
 (0)