From 899f4234641df844db5baacb2bb90b24bd65cba2 Mon Sep 17 00:00:00 2001 From: Jenn Magder Date: Thu, 1 Feb 2024 13:25:53 -0800 Subject: [PATCH] Test codesigning xcframeworks in artifacts (#142666) On the beta branch: ``` Verifying the code signature of /Users/m/Projects/flutter/bin/cache/artifacts/engine/ios-profile/extension_safe/Flutter.xcframework Verifying the code signature of /Users/m/Projects/flutter/bin/cache/artifacts/engine/ios-profile/Flutter.xcframework Verifying the code signature of /Users/m/Projects/flutter/bin/cache/artifacts/engine/ios/extension_safe/Flutter.xcframework Verifying the code signature of /Users/m/Projects/flutter/bin/cache/artifacts/engine/ios/Flutter.xcframework Verifying the code signature of /Users/m/Projects/flutter/bin/cache/artifacts/engine/ios-release/extension_safe/Flutter.xcframework Verifying the code signature of /Users/m/Projects/flutter/bin/cache/artifacts/engine/ios-release/Flutter.xcframework ``` Fixes https://github.com/flutter/flutter/issues/140934 --- dev/bots/test.dart | 113 +++++++++++++++++++++---------- dev/bots/test/codesign_test.dart | 80 ++++++++++++++++++++-- 2 files changed, 153 insertions(+), 40 deletions(-) diff --git a/dev/bots/test.dart b/dev/bots/test.dart index 5c886534a63..89651575b09 100644 --- a/dev/bots/test.dart +++ b/dev/bots/test.dart @@ -1688,7 +1688,7 @@ const List expectedEntitlements = [ /// /// This list should be kept in sync with the actual contents of Flutter's /// cache. -Future> binariesWithEntitlements(String flutterRoot) async { +List binariesWithEntitlements(String flutterRoot) { return [ 'artifacts/engine/android-arm-profile/darwin-x64/gen_snapshot', 'artifacts/engine/android-arm-release/darwin-x64/gen_snapshot', @@ -1729,7 +1729,7 @@ Future> binariesWithEntitlements(String flutterRoot) async { /// /// This list should be kept in sync with the actual contents of Flutter's /// cache. -Future> binariesWithoutEntitlements(String flutterRoot) async { +List binariesWithoutEntitlements(String flutterRoot) { return [ 'artifacts/engine/darwin-x64-profile/FlutterMacOS.framework/Versions/A/FlutterMacOS', 'artifacts/engine/darwin-x64-release/FlutterMacOS.framework/Versions/A/FlutterMacOS', @@ -1755,6 +1755,22 @@ Future> binariesWithoutEntitlements(String flutterRoot) async { .map((String relativePath) => path.join(flutterRoot, 'bin', 'cache', relativePath)).toList(); } +/// xcframeworks that are expected to be codesigned. +/// +/// This list should be kept in sync with the actual contents of Flutter's +/// cache. +List signedXcframeworks(String flutterRoot) { + return [ + 'artifacts/engine/ios-profile/Flutter.xcframework', + 'artifacts/engine/ios-profile/extension_safe/Flutter.xcframework', + 'artifacts/engine/ios-release/Flutter.xcframework', + 'artifacts/engine/ios-release/extension_safe/Flutter.xcframework', + 'artifacts/engine/ios/Flutter.xcframework', + 'artifacts/engine/ios/extension_safe/Flutter.xcframework', + ] + .map((String relativePath) => path.join(flutterRoot, 'bin', 'cache', relativePath)).toList(); +} + /// Verify the existence of all expected binaries in cache. /// /// This function ignores code signatures and entitlements, and is intended to @@ -1769,13 +1785,11 @@ Future verifyExist( final Set foundFiles = {}; final String cacheDirectory = path.join(flutterRoot, 'bin', 'cache'); - - for (final String binaryPath in await findBinaryPaths(cacheDirectory, processManager: processManager)) { - if ((await binariesWithEntitlements(flutterRoot)).contains(binaryPath)) { + if (binariesWithEntitlements(flutterRoot).contains(binaryPath)) { foundFiles.add(binaryPath); - } else if ((await binariesWithoutEntitlements(flutterRoot)).contains(binaryPath)) { + } else if (binariesWithoutEntitlements(flutterRoot).contains(binaryPath)) { foundFiles.add(binaryPath); } else { throw Exception( @@ -1783,7 +1797,7 @@ Future verifyExist( } } - final List allExpectedFiles = await binariesWithEntitlements(flutterRoot) + await binariesWithoutEntitlements(flutterRoot); + final List allExpectedFiles = binariesWithEntitlements(flutterRoot) + binariesWithoutEntitlements(flutterRoot); if (foundFiles.length < allExpectedFiles.length) { final List unfoundFiles = allExpectedFiles .where( @@ -1807,71 +1821,76 @@ Future verifySignatures( String flutterRoot, {@visibleForTesting ProcessManager processManager = const LocalProcessManager()} ) async { - final List unsignedBinaries = []; + final List unsignedFiles = []; final List wrongEntitlementBinaries = []; - final List unexpectedBinaries = []; + final List unexpectedFiles = []; final String cacheDirectory = path.join(flutterRoot, 'bin', 'cache'); - for (final String binaryPath - in await findBinaryPaths(cacheDirectory, processManager: processManager)) { + final List binariesAndXcframeworks = + (await findBinaryPaths(cacheDirectory, processManager: processManager)) + (await findXcframeworksPaths(cacheDirectory, processManager: processManager)); + + for (final String pathToCheck in binariesAndXcframeworks) { bool verifySignature = false; bool verifyEntitlements = false; - if ((await binariesWithEntitlements(flutterRoot)).contains(binaryPath)) { + if (binariesWithEntitlements(flutterRoot).contains(pathToCheck)) { verifySignature = true; verifyEntitlements = true; } - if ((await binariesWithoutEntitlements(flutterRoot)).contains(binaryPath)) { + if (binariesWithoutEntitlements(flutterRoot).contains(pathToCheck)) { + verifySignature = true; + } + if (signedXcframeworks(flutterRoot).contains(pathToCheck)) { verifySignature = true; } if (!verifySignature && !verifyEntitlements) { - unexpectedBinaries.add(binaryPath); - print('Unexpected binary $binaryPath found in cache!'); + unexpectedFiles.add(pathToCheck); + print('Unexpected binary or xcframework $pathToCheck found in cache!'); continue; } - print('Verifying the code signature of $binaryPath'); + print('Verifying the code signature of $pathToCheck'); final io.ProcessResult codeSignResult = await processManager.run( [ 'codesign', '-vvv', - binaryPath, + pathToCheck, ], ); if (codeSignResult.exitCode != 0) { - unsignedBinaries.add(binaryPath); + unsignedFiles.add(pathToCheck); print( - 'File "$binaryPath" does not appear to be codesigned.\n' + 'File "$pathToCheck" does not appear to be codesigned.\n' 'The `codesign` command failed with exit code ${codeSignResult.exitCode}:\n' '${codeSignResult.stderr}\n', ); continue; } if (verifyEntitlements) { - print('Verifying entitlements of $binaryPath'); - if (!(await hasExpectedEntitlements(binaryPath, flutterRoot, processManager: processManager))) { - wrongEntitlementBinaries.add(binaryPath); + print('Verifying entitlements of $pathToCheck'); + if (!(await hasExpectedEntitlements(pathToCheck, flutterRoot, processManager: processManager))) { + wrongEntitlementBinaries.add(pathToCheck); } } } // First print all deviations from expectations - if (unsignedBinaries.isNotEmpty) { - print('Found ${unsignedBinaries.length} unsigned binaries:'); - unsignedBinaries.forEach(print); + if (unsignedFiles.isNotEmpty) { + print('Found ${unsignedFiles.length} unsigned files:'); + unsignedFiles.forEach(print); } if (wrongEntitlementBinaries.isNotEmpty) { - print('Found ${wrongEntitlementBinaries.length} binaries with unexpected entitlements:'); + print('Found ${wrongEntitlementBinaries.length} files with unexpected entitlements:'); wrongEntitlementBinaries.forEach(print); } - if (unexpectedBinaries.isNotEmpty) { - print('Found ${unexpectedBinaries.length} unexpected binaries in the cache:'); - unexpectedBinaries.forEach(print); + if (unexpectedFiles.isNotEmpty) { + print('Found ${unexpectedFiles.length} unexpected files in the cache:'); + unexpectedFiles.forEach(print); } // Finally, exit on any invalid state - if (unsignedBinaries.isNotEmpty) { - throw Exception('Test failed because unsigned binaries detected.'); + if (unsignedFiles.isNotEmpty) { + throw Exception('Test failed because unsigned files detected.'); } if (wrongEntitlementBinaries.isNotEmpty) { @@ -1881,10 +1900,10 @@ Future verifySignatures( ); } - if (unexpectedBinaries.isNotEmpty) { - throw Exception('Test failed because unexpected binaries found in the cache.'); + if (unexpectedFiles.isNotEmpty) { + throw Exception('Test failed because unexpected files found in the cache.'); } - print('Verified that binaries are codesigned and have expected entitlements.'); + print('Verified that files are codesigned and have expected entitlements.'); } /// Find every binary file in the given [rootDirectory]. @@ -1915,6 +1934,30 @@ Future> findBinaryPaths( return allBinaryPaths; } +/// Find every xcframework in the given [rootDirectory]. +Future> findXcframeworksPaths( + String rootDirectory, + {@visibleForTesting ProcessManager processManager = const LocalProcessManager() + }) async { + final io.ProcessResult result = await processManager.run( + [ + 'find', + rootDirectory, + '-type', + 'd', + '-name', + '*xcframework', + ], + ); + final List allXcframeworkPaths = LineSplitter.split(result.stdout as String) + .where((String s) => s.isNotEmpty) + .toList(); + for (final String path in allXcframeworkPaths) { + print('Found: $path\n'); + } + return allXcframeworkPaths; +} + /// Check mime-type of file at [filePath] to determine if it is binary. Future isBinary( String filePath, @@ -1959,7 +2002,7 @@ Future hasExpectedEntitlements( final String output = entitlementResult.stdout as String; for (final String entitlement in expectedEntitlements) { final bool entitlementExpected = - (await binariesWithEntitlements(flutterRoot)).contains(binaryPath); + binariesWithEntitlements(flutterRoot).contains(binaryPath); if (output.contains(entitlement) != entitlementExpected) { print( 'File "$binaryPath" ${entitlementExpected ? 'does not have expected' : 'has unexpected'} ' diff --git a/dev/bots/test/codesign_test.dart b/dev/bots/test/codesign_test.dart index 7d2e5ff3186..fa606ccc2c5 100644 --- a/dev/bots/test/codesign_test.dart +++ b/dev/bots/test/codesign_test.dart @@ -11,9 +11,11 @@ import './common.dart'; void main() async { const String flutterRoot = '/a/b/c'; - final List allExpectedFiles = await binariesWithEntitlements(flutterRoot) + await binariesWithoutEntitlements(flutterRoot); + final List allExpectedFiles = binariesWithEntitlements(flutterRoot) + binariesWithoutEntitlements(flutterRoot); final String allFilesStdout = allExpectedFiles.join('\n'); - final List withEntitlements = await binariesWithEntitlements(flutterRoot); + final List allExpectedXcframeworks = signedXcframeworks(flutterRoot); + final String allXcframeworksStdout = allExpectedXcframeworks.join('\n'); + final List withEntitlements = binariesWithEntitlements(flutterRoot); group('verifyExist', () { test('Not all files found', () async { @@ -74,8 +76,8 @@ void main() async { }); }); - group('findBinaryPaths', () { - test('All files found', () async { + group('find paths', () { + test('All binary files found', () async { final List commandList = []; final FakeCommand findCmd = FakeCommand( command: const [ @@ -117,7 +119,26 @@ void main() async { final ProcessManager processManager = FakeProcessManager.list(commandList); final List foundFiles = await findBinaryPaths('$flutterRoot/bin/cache', processManager: processManager); expect(foundFiles, []); - }); + }); + + test('All xcframeworks files found', () async { + final List commandList = [ + FakeCommand( + command: const [ + 'find', + '$flutterRoot/bin/cache', + '-type', + 'd', + '-name', + '*xcframework', + ], + stdout: allXcframeworksStdout, + ) + ]; + final ProcessManager processManager = FakeProcessManager.list(commandList); + final List foundFiles = await findXcframeworksPaths('$flutterRoot/bin/cache', processManager: processManager); + expect(foundFiles, allExpectedXcframeworks); + }); group('isBinary', () { test('isTrue', () async { @@ -223,6 +244,19 @@ void main() async { ) ); } + commandList.add( + FakeCommand( + command: const [ + 'find', + '$flutterRoot/bin/cache', + '-type', + 'd', + '-name', + '*xcframework', + ], + stdout: allXcframeworksStdout, + ), + ); for (final String expectedFile in allExpectedFiles) { commandList.add( FakeCommand( @@ -248,6 +282,18 @@ void main() async { ); } } + + for (final String expectedXcframework in allExpectedXcframeworks) { + commandList.add( + FakeCommand( + command: [ + 'codesign', + '-vvv', + expectedXcframework, + ], + ) + ); + } final ProcessManager processManager = FakeProcessManager.list(commandList); await expectLater(verifySignatures(flutterRoot, processManager: processManager), completes); }); @@ -276,6 +322,19 @@ void main() async { ) ); } + commandList.add( + FakeCommand( + command: const [ + 'find', + '$flutterRoot/bin/cache', + '-type', + 'd', + '-name', + '*xcframework', + ], + stdout: allXcframeworksStdout, + ), + ); for (final String expectedFile in allExpectedFiles) { commandList.add( FakeCommand( @@ -300,6 +359,17 @@ void main() async { ); } } + for (final String expectedXcframework in allExpectedXcframeworks) { + commandList.add( + FakeCommand( + command: [ + 'codesign', + '-vvv', + expectedXcframework, + ], + ) + ); + } final ProcessManager processManager = FakeProcessManager.list(commandList); expect(