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>[
///
/// This list should be kept in sync with the actual contents of Flutter's
/// cache.
Future<List<String>> binariesWithEntitlements(String flutterRoot) async {
List<String> binariesWithEntitlements(String flutterRoot) {
return <String> [
'artifacts/engine/android-arm-profile/darwin-x64/gen_snapshot',
'artifacts/engine/android-arm-release/darwin-x64/gen_snapshot',
......@@ -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
/// cache.
Future<List<String>> binariesWithoutEntitlements(String flutterRoot) async {
List<String> binariesWithoutEntitlements(String flutterRoot) {
return <String>[
'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<List<String>> 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<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.
///
/// This function ignores code signatures and entitlements, and is intended to
......@@ -1769,13 +1785,11 @@ Future<void> verifyExist(
final Set<String> foundFiles = <String>{};
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<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) {
final List<String> unfoundFiles = allExpectedFiles
.where(
......@@ -1807,71 +1821,76 @@ Future<void> verifySignatures(
String flutterRoot,
{@visibleForTesting ProcessManager processManager = const LocalProcessManager()}
) async {
final List<String> unsignedBinaries = <String>[];
final List<String> unsignedFiles = <String>[];
final List<String> wrongEntitlementBinaries = <String>[];
final List<String> unexpectedBinaries = <String>[];
final List<String> unexpectedFiles = <String>[];
final String cacheDirectory = path.join(flutterRoot, 'bin', 'cache');
for (final String binaryPath
in await findBinaryPaths(cacheDirectory, processManager: processManager)) {
final List<String> 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(
<String>[
'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<void> 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<List<String>> findBinaryPaths(
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.
Future<bool> isBinary(
String filePath,
......@@ -1959,7 +2002,7 @@ Future<bool> 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'} '
......
......@@ -11,9 +11,11 @@ import './common.dart';
void main() async {
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 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', () {
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<FakeCommand> commandList = <FakeCommand>[];
final FakeCommand findCmd = FakeCommand(
command: const <String>[
......@@ -119,6 +121,25 @@ void main() async {
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', () {
test('isTrue', () async {
final List<FakeCommand> commandList = <FakeCommand>[];
......@@ -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) {
commandList.add(
FakeCommand(
......@@ -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);
await expectLater(verifySignatures(flutterRoot, processManager: processManager), completes);
});
......@@ -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) {
commandList.add(
FakeCommand(
......@@ -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);
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