Unverified Commit 899f4234 authored by Jenn Magder's avatar Jenn Magder Committed by GitHub

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
parent d242d136
...@@ -1688,7 +1688,7 @@ const List<String> expectedEntitlements = <String>[ ...@@ -1688,7 +1688,7 @@ const List<String> expectedEntitlements = <String>[
/// ///
/// This list should be kept in sync with the actual contents of Flutter's /// This list should be kept in sync with the actual contents of Flutter's
/// cache. /// cache.
Future<List<String>> binariesWithEntitlements(String flutterRoot) async { List<String> binariesWithEntitlements(String flutterRoot) {
return <String> [ return <String> [
'artifacts/engine/android-arm-profile/darwin-x64/gen_snapshot', 'artifacts/engine/android-arm-profile/darwin-x64/gen_snapshot',
'artifacts/engine/android-arm-release/darwin-x64/gen_snapshot', 'artifacts/engine/android-arm-release/darwin-x64/gen_snapshot',
...@@ -1729,7 +1729,7 @@ Future<List<String>> binariesWithEntitlements(String flutterRoot) async { ...@@ -1729,7 +1729,7 @@ Future<List<String>> binariesWithEntitlements(String flutterRoot) async {
/// ///
/// This list should be kept in sync with the actual contents of Flutter's /// This list should be kept in sync with the actual contents of Flutter's
/// cache. /// cache.
Future<List<String>> binariesWithoutEntitlements(String flutterRoot) async { List<String> binariesWithoutEntitlements(String flutterRoot) {
return <String>[ return <String>[
'artifacts/engine/darwin-x64-profile/FlutterMacOS.framework/Versions/A/FlutterMacOS', 'artifacts/engine/darwin-x64-profile/FlutterMacOS.framework/Versions/A/FlutterMacOS',
'artifacts/engine/darwin-x64-release/FlutterMacOS.framework/Versions/A/FlutterMacOS', 'artifacts/engine/darwin-x64-release/FlutterMacOS.framework/Versions/A/FlutterMacOS',
...@@ -1755,6 +1755,22 @@ Future<List<String>> binariesWithoutEntitlements(String flutterRoot) async { ...@@ -1755,6 +1755,22 @@ Future<List<String>> binariesWithoutEntitlements(String flutterRoot) async {
.map((String relativePath) => path.join(flutterRoot, 'bin', 'cache', relativePath)).toList(); .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<String> signedXcframeworks(String flutterRoot) {
return <String>[
'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. /// Verify the existence of all expected binaries in cache.
/// ///
/// This function ignores code signatures and entitlements, and is intended to /// This function ignores code signatures and entitlements, and is intended to
...@@ -1769,13 +1785,11 @@ Future<void> verifyExist( ...@@ -1769,13 +1785,11 @@ Future<void> verifyExist(
final Set<String> foundFiles = <String>{}; final Set<String> foundFiles = <String>{};
final String cacheDirectory = path.join(flutterRoot, 'bin', 'cache'); final String cacheDirectory = path.join(flutterRoot, 'bin', 'cache');
for (final String binaryPath for (final String binaryPath
in await findBinaryPaths(cacheDirectory, processManager: processManager)) { in await findBinaryPaths(cacheDirectory, processManager: processManager)) {
if ((await binariesWithEntitlements(flutterRoot)).contains(binaryPath)) { if (binariesWithEntitlements(flutterRoot).contains(binaryPath)) {
foundFiles.add(binaryPath); foundFiles.add(binaryPath);
} else if ((await binariesWithoutEntitlements(flutterRoot)).contains(binaryPath)) { } else if (binariesWithoutEntitlements(flutterRoot).contains(binaryPath)) {
foundFiles.add(binaryPath); foundFiles.add(binaryPath);
} else { } else {
throw Exception( throw Exception(
...@@ -1783,7 +1797,7 @@ Future<void> verifyExist( ...@@ -1783,7 +1797,7 @@ Future<void> verifyExist(
} }
} }
final List<String> allExpectedFiles = await binariesWithEntitlements(flutterRoot) + await binariesWithoutEntitlements(flutterRoot); final List<String> allExpectedFiles = binariesWithEntitlements(flutterRoot) + binariesWithoutEntitlements(flutterRoot);
if (foundFiles.length < allExpectedFiles.length) { if (foundFiles.length < allExpectedFiles.length) {
final List<String> unfoundFiles = allExpectedFiles final List<String> unfoundFiles = allExpectedFiles
.where( .where(
...@@ -1807,71 +1821,76 @@ Future<void> verifySignatures( ...@@ -1807,71 +1821,76 @@ Future<void> verifySignatures(
String flutterRoot, String flutterRoot,
{@visibleForTesting ProcessManager processManager = const LocalProcessManager()} {@visibleForTesting ProcessManager processManager = const LocalProcessManager()}
) async { ) async {
final List<String> unsignedBinaries = <String>[]; final List<String> unsignedFiles = <String>[];
final List<String> wrongEntitlementBinaries = <String>[]; final List<String> wrongEntitlementBinaries = <String>[];
final List<String> unexpectedBinaries = <String>[]; final List<String> unexpectedFiles = <String>[];
final String cacheDirectory = path.join(flutterRoot, 'bin', 'cache'); final String cacheDirectory = path.join(flutterRoot, 'bin', 'cache');
for (final String binaryPath final List<String> binariesAndXcframeworks =
in await findBinaryPaths(cacheDirectory, processManager: processManager)) { (await findBinaryPaths(cacheDirectory, processManager: processManager)) + (await findXcframeworksPaths(cacheDirectory, processManager: processManager));
for (final String pathToCheck in binariesAndXcframeworks) {
bool verifySignature = false; bool verifySignature = false;
bool verifyEntitlements = false; bool verifyEntitlements = false;
if ((await binariesWithEntitlements(flutterRoot)).contains(binaryPath)) { if (binariesWithEntitlements(flutterRoot).contains(pathToCheck)) {
verifySignature = true; verifySignature = true;
verifyEntitlements = true; verifyEntitlements = true;
} }
if ((await binariesWithoutEntitlements(flutterRoot)).contains(binaryPath)) { if (binariesWithoutEntitlements(flutterRoot).contains(pathToCheck)) {
verifySignature = true;
}
if (signedXcframeworks(flutterRoot).contains(pathToCheck)) {
verifySignature = true; verifySignature = true;
} }
if (!verifySignature && !verifyEntitlements) { if (!verifySignature && !verifyEntitlements) {
unexpectedBinaries.add(binaryPath); unexpectedFiles.add(pathToCheck);
print('Unexpected binary $binaryPath found in cache!'); print('Unexpected binary or xcframework $pathToCheck found in cache!');
continue; continue;
} }
print('Verifying the code signature of $binaryPath'); print('Verifying the code signature of $pathToCheck');
final io.ProcessResult codeSignResult = await processManager.run( final io.ProcessResult codeSignResult = await processManager.run(
<String>[ <String>[
'codesign', 'codesign',
'-vvv', '-vvv',
binaryPath, pathToCheck,
], ],
); );
if (codeSignResult.exitCode != 0) { if (codeSignResult.exitCode != 0) {
unsignedBinaries.add(binaryPath); unsignedFiles.add(pathToCheck);
print( 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' 'The `codesign` command failed with exit code ${codeSignResult.exitCode}:\n'
'${codeSignResult.stderr}\n', '${codeSignResult.stderr}\n',
); );
continue; continue;
} }
if (verifyEntitlements) { if (verifyEntitlements) {
print('Verifying entitlements of $binaryPath'); print('Verifying entitlements of $pathToCheck');
if (!(await hasExpectedEntitlements(binaryPath, flutterRoot, processManager: processManager))) { if (!(await hasExpectedEntitlements(pathToCheck, flutterRoot, processManager: processManager))) {
wrongEntitlementBinaries.add(binaryPath); wrongEntitlementBinaries.add(pathToCheck);
} }
} }
} }
// First print all deviations from expectations // First print all deviations from expectations
if (unsignedBinaries.isNotEmpty) { if (unsignedFiles.isNotEmpty) {
print('Found ${unsignedBinaries.length} unsigned binaries:'); print('Found ${unsignedFiles.length} unsigned files:');
unsignedBinaries.forEach(print); unsignedFiles.forEach(print);
} }
if (wrongEntitlementBinaries.isNotEmpty) { if (wrongEntitlementBinaries.isNotEmpty) {
print('Found ${wrongEntitlementBinaries.length} binaries with unexpected entitlements:'); print('Found ${wrongEntitlementBinaries.length} files with unexpected entitlements:');
wrongEntitlementBinaries.forEach(print); wrongEntitlementBinaries.forEach(print);
} }
if (unexpectedBinaries.isNotEmpty) { if (unexpectedFiles.isNotEmpty) {
print('Found ${unexpectedBinaries.length} unexpected binaries in the cache:'); print('Found ${unexpectedFiles.length} unexpected files in the cache:');
unexpectedBinaries.forEach(print); unexpectedFiles.forEach(print);
} }
// Finally, exit on any invalid state // Finally, exit on any invalid state
if (unsignedBinaries.isNotEmpty) { if (unsignedFiles.isNotEmpty) {
throw Exception('Test failed because unsigned binaries detected.'); throw Exception('Test failed because unsigned files detected.');
} }
if (wrongEntitlementBinaries.isNotEmpty) { if (wrongEntitlementBinaries.isNotEmpty) {
...@@ -1881,10 +1900,10 @@ Future<void> verifySignatures( ...@@ -1881,10 +1900,10 @@ Future<void> verifySignatures(
); );
} }
if (unexpectedBinaries.isNotEmpty) { if (unexpectedFiles.isNotEmpty) {
throw Exception('Test failed because unexpected binaries found in the cache.'); 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]. /// Find every binary file in the given [rootDirectory].
...@@ -1915,6 +1934,30 @@ Future<List<String>> findBinaryPaths( ...@@ -1915,6 +1934,30 @@ Future<List<String>> findBinaryPaths(
return allBinaryPaths; return allBinaryPaths;
} }
/// Find every xcframework in the given [rootDirectory].
Future<List<String>> findXcframeworksPaths(
String rootDirectory,
{@visibleForTesting ProcessManager processManager = const LocalProcessManager()
}) async {
final io.ProcessResult result = await processManager.run(
<String>[
'find',
rootDirectory,
'-type',
'd',
'-name',
'*xcframework',
],
);
final List<String> 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. /// Check mime-type of file at [filePath] to determine if it is binary.
Future<bool> isBinary( Future<bool> isBinary(
String filePath, String filePath,
...@@ -1959,7 +2002,7 @@ Future<bool> hasExpectedEntitlements( ...@@ -1959,7 +2002,7 @@ Future<bool> hasExpectedEntitlements(
final String output = entitlementResult.stdout as String; final String output = entitlementResult.stdout as String;
for (final String entitlement in expectedEntitlements) { for (final String entitlement in expectedEntitlements) {
final bool entitlementExpected = final bool entitlementExpected =
(await binariesWithEntitlements(flutterRoot)).contains(binaryPath); binariesWithEntitlements(flutterRoot).contains(binaryPath);
if (output.contains(entitlement) != entitlementExpected) { if (output.contains(entitlement) != entitlementExpected) {
print( print(
'File "$binaryPath" ${entitlementExpected ? 'does not have expected' : 'has unexpected'} ' 'File "$binaryPath" ${entitlementExpected ? 'does not have expected' : 'has unexpected'} '
......
...@@ -11,9 +11,11 @@ import './common.dart'; ...@@ -11,9 +11,11 @@ import './common.dart';
void main() async { void main() async {
const String flutterRoot = '/a/b/c'; const String flutterRoot = '/a/b/c';
final List<String> allExpectedFiles = await binariesWithEntitlements(flutterRoot) + await binariesWithoutEntitlements(flutterRoot); final List<String> allExpectedFiles = binariesWithEntitlements(flutterRoot) + binariesWithoutEntitlements(flutterRoot);
final String allFilesStdout = allExpectedFiles.join('\n'); final String allFilesStdout = allExpectedFiles.join('\n');
final List<String> withEntitlements = await binariesWithEntitlements(flutterRoot); final List<String> allExpectedXcframeworks = signedXcframeworks(flutterRoot);
final String allXcframeworksStdout = allExpectedXcframeworks.join('\n');
final List<String> withEntitlements = binariesWithEntitlements(flutterRoot);
group('verifyExist', () { group('verifyExist', () {
test('Not all files found', () async { test('Not all files found', () async {
...@@ -74,8 +76,8 @@ void main() async { ...@@ -74,8 +76,8 @@ void main() async {
}); });
}); });
group('findBinaryPaths', () { group('find paths', () {
test('All files found', () async { test('All binary files found', () async {
final List<FakeCommand> commandList = <FakeCommand>[]; final List<FakeCommand> commandList = <FakeCommand>[];
final FakeCommand findCmd = FakeCommand( final FakeCommand findCmd = FakeCommand(
command: const <String>[ command: const <String>[
...@@ -117,7 +119,26 @@ void main() async { ...@@ -117,7 +119,26 @@ void main() async {
final ProcessManager processManager = FakeProcessManager.list(commandList); final ProcessManager processManager = FakeProcessManager.list(commandList);
final List<String> foundFiles = await findBinaryPaths('$flutterRoot/bin/cache', processManager: processManager); final List<String> foundFiles = await findBinaryPaths('$flutterRoot/bin/cache', processManager: processManager);
expect(foundFiles, <String>[]); expect(foundFiles, <String>[]);
}); });
test('All xcframeworks files found', () async {
final List<FakeCommand> commandList = <FakeCommand>[
FakeCommand(
command: const <String>[
'find',
'$flutterRoot/bin/cache',
'-type',
'd',
'-name',
'*xcframework',
],
stdout: allXcframeworksStdout,
)
];
final ProcessManager processManager = FakeProcessManager.list(commandList);
final List<String> foundFiles = await findXcframeworksPaths('$flutterRoot/bin/cache', processManager: processManager);
expect(foundFiles, allExpectedXcframeworks);
});
group('isBinary', () { group('isBinary', () {
test('isTrue', () async { test('isTrue', () async {
...@@ -223,6 +244,19 @@ void main() async { ...@@ -223,6 +244,19 @@ void main() async {
) )
); );
} }
commandList.add(
FakeCommand(
command: const <String>[
'find',
'$flutterRoot/bin/cache',
'-type',
'd',
'-name',
'*xcframework',
],
stdout: allXcframeworksStdout,
),
);
for (final String expectedFile in allExpectedFiles) { for (final String expectedFile in allExpectedFiles) {
commandList.add( commandList.add(
FakeCommand( FakeCommand(
...@@ -248,6 +282,18 @@ void main() async { ...@@ -248,6 +282,18 @@ void main() async {
); );
} }
} }
for (final String expectedXcframework in allExpectedXcframeworks) {
commandList.add(
FakeCommand(
command: <String>[
'codesign',
'-vvv',
expectedXcframework,
],
)
);
}
final ProcessManager processManager = FakeProcessManager.list(commandList); final ProcessManager processManager = FakeProcessManager.list(commandList);
await expectLater(verifySignatures(flutterRoot, processManager: processManager), completes); await expectLater(verifySignatures(flutterRoot, processManager: processManager), completes);
}); });
...@@ -276,6 +322,19 @@ void main() async { ...@@ -276,6 +322,19 @@ void main() async {
) )
); );
} }
commandList.add(
FakeCommand(
command: const <String>[
'find',
'$flutterRoot/bin/cache',
'-type',
'd',
'-name',
'*xcframework',
],
stdout: allXcframeworksStdout,
),
);
for (final String expectedFile in allExpectedFiles) { for (final String expectedFile in allExpectedFiles) {
commandList.add( commandList.add(
FakeCommand( FakeCommand(
...@@ -300,6 +359,17 @@ void main() async { ...@@ -300,6 +359,17 @@ void main() async {
); );
} }
} }
for (final String expectedXcframework in allExpectedXcframeworks) {
commandList.add(
FakeCommand(
command: <String>[
'codesign',
'-vvv',
expectedXcframework,
],
)
);
}
final ProcessManager processManager = FakeProcessManager.list(commandList); final ProcessManager processManager = FakeProcessManager.list(commandList);
expect( expect(
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment