@@ -52,13 +52,16 @@ import 'dart:core' as system show print;
5252import 'dart:core' hide print;
5353import 'dart:io' as system show exit;
5454import 'dart:io' hide exit;
55+ import 'dart:io' as io;
5556import 'dart:math' as math;
5657import 'dart:typed_data' ;
5758
5859import 'package:archive/archive.dart' ;
5960import 'package:file/file.dart' as fs;
6061import 'package:file/local.dart' ;
62+ import 'package:meta/meta.dart' ;
6163import 'package:path/path.dart' as path;
64+ import 'package:process/process.dart' ;
6265
6366import 'browser.dart' ;
6467import '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