| // Copyright 2014 The Flutter Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| import 'dart:io'; | |
| import 'package:path/path.dart' as path; | |
| String get repoRoot => path.normalize(path.join(path.dirname(Platform.script.toFilePath()), '..', '..')); | |
| String get cacheDirectory => path.normalize(path.join(repoRoot, 'bin', 'cache')); | |
| /// Check mime-type of file at [filePath] to determine if it is binary | |
| bool isBinary(String filePath) { | |
| final ProcessResult result = Process.runSync( | |
| 'file', | |
| <String>[ | |
| '--mime-type', | |
| '-b', // is binary | |
| filePath, | |
| ], | |
| ); | |
| return (result.stdout as String).contains('application/x-mach-binary'); | |
| } | |
| /// Find every binary file in the given [rootDirectory] | |
| List<String> findBinaryPaths([String rootDirectory]) { | |
| rootDirectory ??= cacheDirectory; | |
| final ProcessResult result = Process.runSync( | |
| 'find', | |
| <String>[ | |
| rootDirectory, | |
| '-type', | |
| 'f', | |
| '-perm', | |
| '+111', // is executable | |
| ], | |
| ); | |
| final List<String> allFiles = (result.stdout as String).split('\n').where((String s) => s.isNotEmpty).toList(); | |
| return allFiles.where(isBinary).toList(); | |
| } | |
| /// Given the path to a stamp file, read the contents. | |
| /// | |
| /// Will throw if the file doesn't exist. | |
| String readStamp(String filePath) { | |
| final File file = File(filePath); | |
| if (!file.existsSync()) { | |
| throw 'Error! Stamp file $filePath does not exist!'; | |
| } | |
| return file.readAsStringSync().trim(); | |
| } | |
| /// Return whether or not the flutter cache is up to date. | |
| bool checkCacheIsCurrent() { | |
| try { | |
| final String dartSdkStamp = readStamp(path.join(cacheDirectory, 'engine-dart-sdk.stamp')); | |
| final String engineVersion = readStamp(path.join(repoRoot, 'bin', 'internal', 'engine.version')); | |
| return dartSdkStamp == engineVersion; | |
| } catch (e) { | |
| print(e); | |
| return false; | |
| } | |
| } | |
| List<String> get binariesWithEntitlements => List<String>.unmodifiable(<String>[ | |
| 'ideviceinfo', | |
| 'idevicename', | |
| 'idevicescreenshot', | |
| 'idevicesyslog', | |
| 'libimobiledevice.6.dylib', | |
| 'libplist.3.dylib', | |
| 'iproxy', | |
| 'libusbmuxd.4.dylib', | |
| 'libssl.1.0.0.dylib', | |
| 'libcrypto.1.0.0.dylib', | |
| 'libzip.5.0.dylib', | |
| 'libzip.5.dylib', | |
| 'gen_snapshot', | |
| 'dart', | |
| 'flutter_tester', | |
| 'gen_snapshot_arm64', | |
| 'gen_snapshot_armv7', | |
| ]); | |
| List<String> get expectedEntitlements => List<String>.unmodifiable(<String>[ | |
| 'com.apple.security.cs.allow-jit', | |
| 'com.apple.security.cs.allow-unsigned-executable-memory', | |
| 'com.apple.security.cs.allow-dyld-environment-variables', | |
| 'com.apple.security.network.client', | |
| 'com.apple.security.network.server', | |
| 'com.apple.security.cs.disable-library-validation', | |
| ]); | |
| /// Check if the binary has the expected entitlements. | |
| bool hasExpectedEntitlements(String binaryPath) { | |
| try { | |
| final ProcessResult entitlementResult = Process.runSync( | |
| 'codesign', | |
| <String>[ | |
| '--display', | |
| '--entitlements', | |
| ':-', | |
| binaryPath, | |
| ], | |
| ); | |
| if (entitlementResult.exitCode != 0) { | |
| print('The `codesign --entitlements` command failed with exit code ${entitlementResult.exitCode}:\n' | |
| '${entitlementResult.stderr}\n'); | |
| return false; | |
| } | |
| bool passes = true; | |
| final String output = entitlementResult.stdout as String; | |
| for (final String entitlement in expectedEntitlements) { | |
| final bool entitlementExpected = binariesWithEntitlements.contains(path.basename(binaryPath)); | |
| if (output.contains(entitlement) != entitlementExpected) { | |
| print('File "$binaryPath" ${entitlementExpected ? 'does not have expected' : 'has unexpected'} entitlement $entitlement.'); | |
| passes = false; | |
| } | |
| } | |
| return passes; | |
| } catch (e) { | |
| print(e); | |
| return false; | |
| } | |
| } | |
| void main() { | |
| if (!Platform.isMacOS) { | |
| print('Error! Expected operating system "macos", actual operating system ' | |
| 'is: "${Platform.operatingSystem}"'); | |
| exit(1); | |
| } | |
| if (!checkCacheIsCurrent()) { | |
| print( | |
| 'Warning! Your cache is either not present or not matching your flutter\n' | |
| 'version. Run a `flutter` command to update your cache, and re-try this\n' | |
| 'test.'); | |
| exit(1); | |
| } | |
| final List<String> unsignedBinaries = <String>[]; | |
| final List<String> wrongEntitlementBinaries = <String>[]; | |
| for (final String binaryPath in findBinaryPaths(cacheDirectory)) { | |
| print('Verifying the code signature of $binaryPath'); | |
| final ProcessResult codeSignResult = Process.runSync( | |
| 'codesign', | |
| <String>[ | |
| '-vvv', | |
| binaryPath, | |
| ], | |
| ); | |
| if (codeSignResult.exitCode != 0) { | |
| unsignedBinaries.add(binaryPath); | |
| print('File "$binaryPath" does not appear to be codesigned.\n' | |
| 'The `codesign` command failed with exit code ${codeSignResult.exitCode}:\n' | |
| '${codeSignResult.stderr}\n'); | |
| continue; | |
| } else { | |
| print('Verifying entitlements of $binaryPath'); | |
| if (!hasExpectedEntitlements(binaryPath)) { | |
| wrongEntitlementBinaries.add(binaryPath); | |
| } | |
| } | |
| } | |
| if (unsignedBinaries.isNotEmpty) { | |
| print('Found ${unsignedBinaries.length} unsigned binaries:'); | |
| unsignedBinaries.forEach(print); | |
| } | |
| if (wrongEntitlementBinaries.isNotEmpty) { | |
| print('Found ${wrongEntitlementBinaries.length} binaries with unexpected entitlements:'); | |
| wrongEntitlementBinaries.forEach(print); | |
| } | |
| if (unsignedBinaries.isNotEmpty) { | |
| // TODO(jmagman): Also exit if `wrongEntitlementBinaries.isNotEmpty` after https://github.com/flutter/flutter/issues/46704 is done. | |
| exit(1); | |
| } | |
| print('Verified that binaries are codesigned and have expected entitlements.'); | |
| } |