Skip to content

Commit 4d7f24a

Browse files
godofredocCoderDake
authored andcommitted
Migrate verify_codesigned. (flutter#139328)
This is part of the migration of adhoc tests to shard tests. Bug: flutter#139153
1 parent 4e3d3c8 commit 4d7f24a

File tree

4 files changed

+643
-6
lines changed

4 files changed

+643
-6
lines changed

.ci.yaml

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3714,26 +3714,24 @@ targets:
37143714
- name: Mac_x64 verify_binaries_codesigned
37153715
enabled_branches:
37163716
- flutter-\d+\.\d+-candidate\.\d+
3717-
recipe: flutter/flutter
3717+
recipe: flutter/flutter_drone
37183718
presubmit: false
37193719
timeout: 60
37203720
properties:
37213721
tags: >
37223722
["framework", "hostonly", "shard", "mac"]
3723-
validation: verify_binaries_codesigned
3724-
validation_name: Verify x64 binaries codesigned
3723+
shard: verify_binaries_codesigned
37253724

37263725
- name: Mac_arm64 verify_binaries_codesigned
37273726
enabled_branches:
37283727
- flutter-\d+\.\d+-candidate\.\d+
3729-
recipe: flutter/flutter
3728+
recipe: flutter/flutter_drone
37303729
presubmit: false
37313730
timeout: 60
37323731
properties:
37333732
tags: >
37343733
["framework", "hostonly", "shard", "mac"]
3735-
validation: verify_binaries_codesigned
3736-
validation_name: Verify arm64 binaries codesigned
3734+
shard: verify_binaries_codesigned
37373735

37383736
- name: Mac web_tool_tests
37393737
recipe: flutter/flutter_drone

dev/bots/test.dart

Lines changed: 318 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,13 +52,16 @@ import 'dart:core' as system show print;
5252
import 'dart:core' hide print;
5353
import 'dart:io' as system show exit;
5454
import 'dart:io' hide exit;
55+
import 'dart:io' as io;
5556
import 'dart:math' as math;
5657
import 'dart:typed_data';
5758

5859
import 'package:archive/archive.dart';
5960
import 'package:file/file.dart' as fs;
6061
import 'package:file/local.dart';
62+
import 'package:meta/meta.dart';
6163
import 'package:path/path.dart' as path;
64+
import 'package:process/process.dart';
6265

6366
import 'browser.dart';
6467
import 'run_command.dart';
@@ -252,6 +255,7 @@ Future<void> main(List<String> args) async {
252255
'analyze': _runAnalyze,
253256
'fuchsia_precache': _runFuchsiaPrecache,
254257
'docs': _runDocs,
258+
'verify_binaries_codesigned': _runVerifyCodesigned,
255259
kTestHarnessShardName: _runTestHarnessTests, // Used for testing this script; also run as part of SHARD=framework_tests, SUBSHARD=misc.
256260
});
257261
} catch (error, stackTrace) {
@@ -1644,6 +1648,320 @@ Future<void> _runDocs() async {
16441648
);
16451649
}
16461650

1651+
// Verifies binaries are codesigned.
1652+
Future<void> _runVerifyCodesigned() async {
1653+
printProgress('${green}Running binaries codesign verification$reset');
1654+
await runCommand(
1655+
'flutter',
1656+
<String>[
1657+
'precache',
1658+
'--android',
1659+
'--ios',
1660+
'--macos'
1661+
],
1662+
workingDirectory: flutterRoot,
1663+
);
1664+
1665+
await verifyExist(flutterRoot);
1666+
await verifySignatures(flutterRoot);
1667+
}
1668+
1669+
const List<String> expectedEntitlements = <String>[
1670+
'com.apple.security.cs.allow-jit',
1671+
'com.apple.security.cs.allow-unsigned-executable-memory',
1672+
'com.apple.security.cs.allow-dyld-environment-variables',
1673+
'com.apple.security.network.client',
1674+
'com.apple.security.network.server',
1675+
'com.apple.security.cs.disable-library-validation',
1676+
];
1677+
1678+
/// Binaries that are expected to be codesigned and have entitlements.
1679+
///
1680+
/// This list should be kept in sync with the actual contents of Flutter's
1681+
/// cache.
1682+
Future<List<String>> binariesWithEntitlements(String flutterRoot) async {
1683+
return <String> [
1684+
'artifacts/engine/android-arm-profile/darwin-x64/gen_snapshot',
1685+
'artifacts/engine/android-arm-release/darwin-x64/gen_snapshot',
1686+
'artifacts/engine/android-arm64-profile/darwin-x64/gen_snapshot',
1687+
'artifacts/engine/android-arm64-release/darwin-x64/gen_snapshot',
1688+
'artifacts/engine/android-x64-profile/darwin-x64/gen_snapshot',
1689+
'artifacts/engine/android-x64-release/darwin-x64/gen_snapshot',
1690+
'artifacts/engine/darwin-x64-profile/gen_snapshot',
1691+
'artifacts/engine/darwin-x64-profile/gen_snapshot_arm64',
1692+
'artifacts/engine/darwin-x64-profile/gen_snapshot_x64',
1693+
'artifacts/engine/darwin-x64-release/gen_snapshot',
1694+
'artifacts/engine/darwin-x64-release/gen_snapshot_arm64',
1695+
'artifacts/engine/darwin-x64-release/gen_snapshot_x64',
1696+
'artifacts/engine/darwin-x64/flutter_tester',
1697+
'artifacts/engine/darwin-x64/gen_snapshot',
1698+
'artifacts/engine/darwin-x64/gen_snapshot_arm64',
1699+
'artifacts/engine/darwin-x64/gen_snapshot_x64',
1700+
'artifacts/engine/ios-profile/gen_snapshot_arm64',
1701+
'artifacts/engine/ios-release/gen_snapshot_arm64',
1702+
'artifacts/engine/ios/gen_snapshot_arm64',
1703+
'artifacts/libimobiledevice/idevicescreenshot',
1704+
'artifacts/libimobiledevice/idevicesyslog',
1705+
'artifacts/libimobiledevice/libimobiledevice-1.0.6.dylib',
1706+
'artifacts/libplist/libplist-2.0.3.dylib',
1707+
'artifacts/openssl/libcrypto.1.1.dylib',
1708+
'artifacts/openssl/libssl.1.1.dylib',
1709+
'artifacts/usbmuxd/iproxy',
1710+
'artifacts/usbmuxd/libusbmuxd-2.0.6.dylib',
1711+
'dart-sdk/bin/dart',
1712+
'dart-sdk/bin/dartaotruntime',
1713+
'dart-sdk/bin/utils/gen_snapshot',
1714+
'dart-sdk/bin/utils/wasm-opt',
1715+
]
1716+
.map((String relativePath) => path.join(flutterRoot, 'bin', 'cache', relativePath)).toList();
1717+
}
1718+
1719+
/// Binaries that are only expected to be codesigned.
1720+
///
1721+
/// This list should be kept in sync with the actual contents of Flutter's
1722+
/// cache.
1723+
Future<List<String>> binariesWithoutEntitlements(String flutterRoot) async {
1724+
return <String>[
1725+
'artifacts/engine/darwin-x64-profile/FlutterMacOS.framework/Versions/A/FlutterMacOS',
1726+
'artifacts/engine/darwin-x64-release/FlutterMacOS.framework/Versions/A/FlutterMacOS',
1727+
'artifacts/engine/darwin-x64/FlutterMacOS.framework/Versions/A/FlutterMacOS',
1728+
'artifacts/engine/darwin-x64/font-subset',
1729+
'artifacts/engine/darwin-x64/impellerc',
1730+
'artifacts/engine/darwin-x64/libpath_ops.dylib',
1731+
'artifacts/engine/darwin-x64/libtessellator.dylib',
1732+
'artifacts/engine/ios-profile/Flutter.xcframework/ios-arm64/Flutter.framework/Flutter',
1733+
'artifacts/engine/ios-profile/Flutter.xcframework/ios-arm64_x86_64-simulator/Flutter.framework/Flutter',
1734+
'artifacts/engine/ios-profile/extension_safe/Flutter.xcframework/ios-arm64/Flutter.framework/Flutter',
1735+
'artifacts/engine/ios-profile/extension_safe/Flutter.xcframework/ios-arm64_x86_64-simulator/Flutter.framework/Flutter',
1736+
'artifacts/engine/ios-release/Flutter.xcframework/ios-arm64/Flutter.framework/Flutter',
1737+
'artifacts/engine/ios-release/Flutter.xcframework/ios-arm64_x86_64-simulator/Flutter.framework/Flutter',
1738+
'artifacts/engine/ios-release/extension_safe/Flutter.xcframework/ios-arm64/Flutter.framework/Flutter',
1739+
'artifacts/engine/ios-release/extension_safe/Flutter.xcframework/ios-arm64_x86_64-simulator/Flutter.framework/Flutter',
1740+
'artifacts/engine/ios/Flutter.xcframework/ios-arm64/Flutter.framework/Flutter',
1741+
'artifacts/engine/ios/Flutter.xcframework/ios-arm64_x86_64-simulator/Flutter.framework/Flutter',
1742+
'artifacts/engine/ios/extension_safe/Flutter.xcframework/ios-arm64/Flutter.framework/Flutter',
1743+
'artifacts/engine/ios/extension_safe/Flutter.xcframework/ios-arm64_x86_64-simulator/Flutter.framework/Flutter',
1744+
'artifacts/ios-deploy/ios-deploy',
1745+
]
1746+
.map((String relativePath) => path.join(flutterRoot, 'bin', 'cache', relativePath)).toList();
1747+
}
1748+
1749+
/// Verify the existence of all expected binaries in cache.
1750+
///
1751+
/// This function ignores code signatures and entitlements, and is intended to
1752+
/// be run on every commit. It should throw if either new binaries are added
1753+
/// to the cache or expected binaries removed. In either case, this class'
1754+
/// [binariesWithEntitlements] or [binariesWithoutEntitlements] lists should
1755+
/// be updated accordingly.
1756+
Future<void> verifyExist(
1757+
String flutterRoot,
1758+
{@visibleForTesting ProcessManager processManager = const LocalProcessManager()
1759+
}) async {
1760+
final Set<String> foundFiles = <String>{};
1761+
final String cacheDirectory = path.join(flutterRoot, 'bin', 'cache');
1762+
1763+
1764+
1765+
for (final String binaryPath
1766+
in await findBinaryPaths(cacheDirectory, processManager: processManager)) {
1767+
if ((await binariesWithEntitlements(flutterRoot)).contains(binaryPath)) {
1768+
foundFiles.add(binaryPath);
1769+
} else if ((await binariesWithoutEntitlements(flutterRoot)).contains(binaryPath)) {
1770+
foundFiles.add(binaryPath);
1771+
} else {
1772+
throw Exception(
1773+
'Found unexpected binary in cache: $binaryPath');
1774+
}
1775+
}
1776+
1777+
final List<String> allExpectedFiles = await binariesWithEntitlements(flutterRoot) + await binariesWithoutEntitlements(flutterRoot);
1778+
if (foundFiles.length < allExpectedFiles.length) {
1779+
final List<String> unfoundFiles = allExpectedFiles
1780+
.where(
1781+
(String file) => !foundFiles.contains(file),
1782+
)
1783+
.toList();
1784+
print(
1785+
'Expected binaries not found in cache:\n\n${unfoundFiles.join('\n')}\n\n'
1786+
'If this commit is removing binaries from the cache, this test should be fixed by\n'
1787+
'removing the relevant entry from either the "binariesWithEntitlements" or\n'
1788+
'"binariesWithoutEntitlements" getters in dev/tools/lib/codesign.dart.',
1789+
);
1790+
throw Exception('Did not find all expected binaries!');
1791+
}
1792+
1793+
print('All expected binaries present.');
1794+
}
1795+
1796+
/// Verify code signatures and entitlements of all binaries in the cache.
1797+
Future<void> verifySignatures(
1798+
String flutterRoot,
1799+
{@visibleForTesting ProcessManager processManager = const LocalProcessManager()}
1800+
) async {
1801+
final List<String> unsignedBinaries = <String>[];
1802+
final List<String> wrongEntitlementBinaries = <String>[];
1803+
final List<String> unexpectedBinaries = <String>[];
1804+
final String cacheDirectory = path.join(flutterRoot, 'bin', 'cache');
1805+
1806+
for (final String binaryPath
1807+
in await findBinaryPaths(cacheDirectory, processManager: processManager)) {
1808+
bool verifySignature = false;
1809+
bool verifyEntitlements = false;
1810+
if ((await binariesWithEntitlements(flutterRoot)).contains(binaryPath)) {
1811+
verifySignature = true;
1812+
verifyEntitlements = true;
1813+
}
1814+
if ((await binariesWithoutEntitlements(flutterRoot)).contains(binaryPath)) {
1815+
verifySignature = true;
1816+
}
1817+
if (!verifySignature && !verifyEntitlements) {
1818+
unexpectedBinaries.add(binaryPath);
1819+
print('Unexpected binary $binaryPath found in cache!');
1820+
continue;
1821+
}
1822+
print('Verifying the code signature of $binaryPath');
1823+
final io.ProcessResult codeSignResult = await processManager.run(
1824+
<String>[
1825+
'codesign',
1826+
'-vvv',
1827+
binaryPath,
1828+
],
1829+
);
1830+
if (codeSignResult.exitCode != 0) {
1831+
unsignedBinaries.add(binaryPath);
1832+
print(
1833+
'File "$binaryPath" does not appear to be codesigned.\n'
1834+
'The `codesign` command failed with exit code ${codeSignResult.exitCode}:\n'
1835+
'${codeSignResult.stderr}\n',
1836+
);
1837+
continue;
1838+
}
1839+
if (verifyEntitlements) {
1840+
print('Verifying entitlements of $binaryPath');
1841+
if (!(await hasExpectedEntitlements(binaryPath, flutterRoot, processManager: processManager))) {
1842+
wrongEntitlementBinaries.add(binaryPath);
1843+
}
1844+
}
1845+
}
1846+
1847+
// First print all deviations from expectations
1848+
if (unsignedBinaries.isNotEmpty) {
1849+
print('Found ${unsignedBinaries.length} unsigned binaries:');
1850+
unsignedBinaries.forEach(print);
1851+
}
1852+
1853+
if (wrongEntitlementBinaries.isNotEmpty) {
1854+
print('Found ${wrongEntitlementBinaries.length} binaries with unexpected entitlements:');
1855+
wrongEntitlementBinaries.forEach(print);
1856+
}
1857+
1858+
if (unexpectedBinaries.isNotEmpty) {
1859+
print('Found ${unexpectedBinaries.length} unexpected binaries in the cache:');
1860+
unexpectedBinaries.forEach(print);
1861+
}
1862+
1863+
// Finally, exit on any invalid state
1864+
if (unsignedBinaries.isNotEmpty) {
1865+
throw Exception('Test failed because unsigned binaries detected.');
1866+
}
1867+
1868+
if (wrongEntitlementBinaries.isNotEmpty) {
1869+
throw Exception(
1870+
'Test failed because files found with the wrong entitlements:\n'
1871+
'${wrongEntitlementBinaries.join('\n')}',
1872+
);
1873+
}
1874+
1875+
if (unexpectedBinaries.isNotEmpty) {
1876+
throw Exception('Test failed because unexpected binaries found in the cache.');
1877+
}
1878+
print('Verified that binaries are codesigned and have expected entitlements.');
1879+
}
1880+
1881+
/// Find every binary file in the given [rootDirectory].
1882+
Future<List<String>> findBinaryPaths(
1883+
String rootDirectory,
1884+
{@visibleForTesting ProcessManager processManager = const LocalProcessManager()
1885+
}) async {
1886+
final List<String> allBinaryPaths = <String>[];
1887+
final io.ProcessResult result = await processManager.run(
1888+
<String>[
1889+
'find',
1890+
rootDirectory,
1891+
'-type',
1892+
'f',
1893+
],
1894+
);
1895+
final List<String> allFiles = (result.stdout as String)
1896+
.split('\n')
1897+
.where((String s) => s.isNotEmpty)
1898+
.toList();
1899+
1900+
await Future.forEach(allFiles, (String filePath) async {
1901+
if (await isBinary(filePath, processManager: processManager)) {
1902+
allBinaryPaths.add(filePath);
1903+
print('Found: $filePath\n');
1904+
}
1905+
});
1906+
return allBinaryPaths;
1907+
}
1908+
1909+
/// Check mime-type of file at [filePath] to determine if it is binary.
1910+
Future<bool> isBinary(
1911+
String filePath,
1912+
{@visibleForTesting ProcessManager processManager = const LocalProcessManager()}
1913+
) async {
1914+
final io.ProcessResult result = await processManager.run(
1915+
<String>[
1916+
'file',
1917+
'--mime-type',
1918+
'-b', // is binary
1919+
filePath,
1920+
],
1921+
);
1922+
return (result.stdout as String).contains('application/x-mach-binary');
1923+
}
1924+
1925+
/// Check if the binary has the expected entitlements.
1926+
Future<bool> hasExpectedEntitlements(
1927+
String binaryPath,
1928+
String flutterRoot,
1929+
{@visibleForTesting ProcessManager processManager = const LocalProcessManager()}
1930+
) async {
1931+
final io.ProcessResult entitlementResult = await processManager.run(
1932+
<String>[
1933+
'codesign',
1934+
'--display',
1935+
'--entitlements',
1936+
':-',
1937+
binaryPath,
1938+
],
1939+
);
1940+
1941+
if (entitlementResult.exitCode != 0) {
1942+
print(
1943+
'The `codesign --entitlements` command failed with exit code ${entitlementResult.exitCode}:\n'
1944+
'${entitlementResult.stderr}\n',
1945+
);
1946+
return false;
1947+
}
1948+
1949+
bool passes = true;
1950+
final String output = entitlementResult.stdout as String;
1951+
for (final String entitlement in expectedEntitlements) {
1952+
final bool entitlementExpected =
1953+
(await binariesWithEntitlements(flutterRoot)).contains(binaryPath);
1954+
if (output.contains(entitlement) != entitlementExpected) {
1955+
print(
1956+
'File "$binaryPath" ${entitlementExpected ? 'does not have expected' : 'has unexpected'} '
1957+
'entitlement $entitlement.',
1958+
);
1959+
passes = false;
1960+
}
1961+
}
1962+
return passes;
1963+
}
1964+
16471965
/// Runs the skp_generator from the flutter/tests repo.
16481966
///
16491967
/// See also the customer_tests shard.

0 commit comments

Comments
 (0)