From f18e61a5e81f4a56dcc391b6c1410e43d0ad9e2a Mon Sep 17 00:00:00 2001 From: godofredoc Date: Fri, 5 Jan 2024 16:45:26 -0800 Subject: [PATCH] Remove codesign command from conductor. (#141044) Codesigning is now automated and the codesigning tests have been migrated to shard tests. --- dev/conductor/README.md | 7 - dev/conductor/core/bin/cli.dart | 4 - dev/conductor/core/lib/conductor_core.dart | 1 - dev/conductor/core/lib/src/codesign.dart | 431 ------------ .../core/test/codesign_integration_test.dart | 103 --- dev/conductor/core/test/codesign_test.dart | 615 ------------------ 6 files changed, 1161 deletions(-) delete mode 100644 dev/conductor/core/lib/src/codesign.dart delete mode 100644 dev/conductor/core/test/codesign_integration_test.dart delete mode 100644 dev/conductor/core/test/codesign_test.dart diff --git a/dev/conductor/README.md b/dev/conductor/README.md index 8c9fa10b7a..8899226c38 100644 --- a/dev/conductor/README.md +++ b/dev/conductor/README.md @@ -90,13 +90,6 @@ Once a PR is opened, the user must validate CI builds. If there are regressions output of the failing test), then the user must fix these tests in their local checkout and push their changes again. -### Codesign Engine Binaries - -The user must validate post-submit CI builds for their merged engine PR have -passed. A link to the web dashboard is available via `conductor status`. Once -the post-submit CI builds have all passed, the user must codesign engine -binaries for the **merged** engine commit. - ### Apply Framework Cherrypicks The tool will attempt to auto-apply all framework cherrypicks. However, any diff --git a/dev/conductor/core/bin/cli.dart b/dev/conductor/core/bin/cli.dart index 0e86f13c79..6c2025b47a 100644 --- a/dev/conductor/core/bin/cli.dart +++ b/dev/conductor/core/bin/cli.dart @@ -46,10 +46,6 @@ Future main(List args) async { )).trim(); >[ - CodesignCommand( - checkouts: checkouts, - flutterRoot: _localFlutterRoot, - ), StatusCommand( checkouts: checkouts, ), diff --git a/dev/conductor/core/lib/conductor_core.dart b/dev/conductor/core/lib/conductor_core.dart index f1203be73f..28065d4833 100644 --- a/dev/conductor/core/lib/conductor_core.dart +++ b/dev/conductor/core/lib/conductor_core.dart @@ -6,7 +6,6 @@ export 'src/candidates.dart'; export 'src/clean.dart'; -export 'src/codesign.dart'; export 'src/git.dart'; export 'src/globals.dart'; export 'src/next.dart' hide kStateOption, kYesFlag; diff --git a/dev/conductor/core/lib/src/codesign.dart b/dev/conductor/core/lib/src/codesign.dart deleted file mode 100644 index 2e45cd0f6d..0000000000 --- a/dev/conductor/core/lib/src/codesign.dart +++ /dev/null @@ -1,431 +0,0 @@ -// 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' as io; - -import 'package:args/command_runner.dart'; -import 'package:file/file.dart'; -import 'package:meta/meta.dart' show visibleForTesting; -import 'package:platform/platform.dart'; -import 'package:process/process.dart'; - -import './globals.dart'; -import './repository.dart'; -import './stdio.dart'; - -const List expectedEntitlements = [ - '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', -]; - -const String kVerify = 'verify'; -const String kSignatures = 'signatures'; -const String kRevision = 'revision'; -const String kUpstream = 'upstream'; - - -/// Command to codesign and verify the signatures of cached binaries. -class CodesignCommand extends Command { - CodesignCommand({ - required this.checkouts, - required this.flutterRoot, - FrameworkRepository? framework, - }) : fileSystem = checkouts.fileSystem, - platform = checkouts.platform, - stdio = checkouts.stdio, - processManager = checkouts.processManager { - if (framework != null) { - _framework = framework; - } - argParser.addFlag( - kVerify, - help: 'Only verify expected binaries exist and are codesigned with entitlements.', - ); - argParser.addFlag( - kSignatures, - defaultsTo: true, - help: 'When off, this command will only verify the existence of binaries, and not their\n' - 'signatures or entitlements. Must be used with --verify flag.', - ); - argParser.addOption( - kUpstream, - defaultsTo: FrameworkRepository.defaultUpstream, - help: "The git remote URL to use as the Flutter framework's upstream.", - ); - argParser.addOption( - kRevision, - help: 'The Flutter framework revision to use.', - ); - } - - final Checkouts checkouts; - final FileSystem fileSystem; - final Platform platform; - final ProcessManager processManager; - final Stdio stdio; - - /// Root directory of the Flutter repository. - final Directory flutterRoot; - - FrameworkRepository? _framework; - FrameworkRepository get framework { - return _framework ??= FrameworkRepository( - checkouts, - upstreamRemote: Remote( - name: RemoteName.upstream, - url: argResults![kUpstream] as String, - ), - ); - } - - @override - String get name => 'codesign'; - - @override - String get description => - 'For codesigning and verifying the signatures of engine binaries.'; - - @override - Future run() async { - if (!platform.isMacOS) { - throw ConductorException( - 'Error! Expected operating system "macos", actual operating system is: ' - '"${platform.operatingSystem}"', - ); - } - - if (!(argResults!['verify'] as bool)) { - throw ConductorException( - 'Sorry, but codesigning is not implemented yet. Please pass the ' - '--$kVerify flag to verify signatures.', - ); - } - - String revision; - if (argResults!.wasParsed(kRevision)) { - stdio.printWarning( - 'Warning! When providing an arbitrary revision, the contents of the cache may not ' - 'match the expected binaries in the conductor tool. It is preferred to check out ' - 'the desired revision and run that version of the conductor.\n', - ); - revision = argResults![kRevision] as String; - } else { - revision = ((await processManager.run( - ['git', 'rev-parse', 'HEAD'], - workingDirectory: flutterRoot.path, - )).stdout as String).trim(); - assert(revision.isNotEmpty); - } - - await framework.checkout(revision); - - // Ensure artifacts present - final io.ProcessResult result = await framework.runFlutter( - ['precache', '--android', '--ios', '--macos'], - ); - if (result.exitCode != 0) { - stdio.printError( - 'flutter precache: exitCode: ${result.exitCode}\n' - 'stdout:\n${result.stdout}\nstderr:\n${result.stderr}', - ); - } - - await verifyExist(); - if (argResults![kSignatures] as bool) { - await verifySignatures(); - } - } - - /// Binaries that are expected to be codesigned and have entitlements. - /// - /// This list should be kept in sync with the actual contents of Flutter's - /// cache. - Future> get binariesWithEntitlements async { - final String frameworkCacheDirectory = await framework.cacheDirectory; - return [ - 'artifacts/engine/android-arm-profile/darwin-x64/gen_snapshot', - 'artifacts/engine/android-arm-release/darwin-x64/gen_snapshot', - 'artifacts/engine/android-arm64-profile/darwin-x64/gen_snapshot', - 'artifacts/engine/android-arm64-release/darwin-x64/gen_snapshot', - 'artifacts/engine/android-x64-profile/darwin-x64/gen_snapshot', - 'artifacts/engine/android-x64-release/darwin-x64/gen_snapshot', - 'artifacts/engine/darwin-x64-profile/gen_snapshot', - 'artifacts/engine/darwin-x64-profile/gen_snapshot_arm64', - 'artifacts/engine/darwin-x64-profile/gen_snapshot_x64', - 'artifacts/engine/darwin-x64-release/gen_snapshot', - 'artifacts/engine/darwin-x64-release/gen_snapshot_arm64', - 'artifacts/engine/darwin-x64-release/gen_snapshot_x64', - 'artifacts/engine/darwin-x64/flutter_tester', - 'artifacts/engine/darwin-x64/gen_snapshot', - 'artifacts/engine/darwin-x64/gen_snapshot_arm64', - 'artifacts/engine/darwin-x64/gen_snapshot_x64', - 'artifacts/engine/ios-profile/gen_snapshot_arm64', - 'artifacts/engine/ios-release/gen_snapshot_arm64', - 'artifacts/engine/ios/gen_snapshot_arm64', - 'artifacts/libimobiledevice/idevicescreenshot', - 'artifacts/libimobiledevice/idevicesyslog', - 'artifacts/libimobiledevice/libimobiledevice-1.0.6.dylib', - 'artifacts/libplist/libplist-2.0.3.dylib', - 'artifacts/openssl/libcrypto.1.1.dylib', - 'artifacts/openssl/libssl.1.1.dylib', - 'artifacts/usbmuxd/iproxy', - 'artifacts/usbmuxd/libusbmuxd-2.0.6.dylib', - 'dart-sdk/bin/dart', - 'dart-sdk/bin/dartaotruntime', - 'dart-sdk/bin/utils/gen_snapshot', - 'dart-sdk/bin/utils/wasm-opt', - ] - .map((String relativePath) => - fileSystem.path.join(frameworkCacheDirectory, relativePath)) - .toList(); - } - - /// Binaries that are only expected to be codesigned. - /// - /// This list should be kept in sync with the actual contents of Flutter's - /// cache. - Future> get binariesWithoutEntitlements async { - final String frameworkCacheDirectory = await framework.cacheDirectory; - return [ - 'artifacts/engine/darwin-x64-profile/FlutterMacOS.framework/Versions/A/FlutterMacOS', - 'artifacts/engine/darwin-x64-release/FlutterMacOS.framework/Versions/A/FlutterMacOS', - 'artifacts/engine/darwin-x64/FlutterMacOS.framework/Versions/A/FlutterMacOS', - 'artifacts/engine/darwin-x64/font-subset', - 'artifacts/engine/darwin-x64/impellerc', - 'artifacts/engine/darwin-x64/libpath_ops.dylib', - 'artifacts/engine/darwin-x64/libtessellator.dylib', - 'artifacts/engine/ios-profile/Flutter.xcframework/ios-arm64/Flutter.framework/Flutter', - 'artifacts/engine/ios-profile/Flutter.xcframework/ios-arm64_x86_64-simulator/Flutter.framework/Flutter', - 'artifacts/engine/ios-profile/extension_safe/Flutter.xcframework/ios-arm64/Flutter.framework/Flutter', - 'artifacts/engine/ios-profile/extension_safe/Flutter.xcframework/ios-arm64_x86_64-simulator/Flutter.framework/Flutter', - 'artifacts/engine/ios-release/Flutter.xcframework/ios-arm64/Flutter.framework/Flutter', - 'artifacts/engine/ios-release/Flutter.xcframework/ios-arm64_x86_64-simulator/Flutter.framework/Flutter', - 'artifacts/engine/ios-release/extension_safe/Flutter.xcframework/ios-arm64/Flutter.framework/Flutter', - 'artifacts/engine/ios-release/extension_safe/Flutter.xcframework/ios-arm64_x86_64-simulator/Flutter.framework/Flutter', - 'artifacts/engine/ios/Flutter.xcframework/ios-arm64/Flutter.framework/Flutter', - 'artifacts/engine/ios/Flutter.xcframework/ios-arm64_x86_64-simulator/Flutter.framework/Flutter', - 'artifacts/engine/ios/extension_safe/Flutter.xcframework/ios-arm64/Flutter.framework/Flutter', - 'artifacts/engine/ios/extension_safe/Flutter.xcframework/ios-arm64_x86_64-simulator/Flutter.framework/Flutter', - 'artifacts/ios-deploy/ios-deploy', - ] - .map((String relativePath) => - fileSystem.path.join(frameworkCacheDirectory, relativePath)) - .toList(); - } - - /// Verify the existence of all expected binaries in cache. - /// - /// This function ignores code signatures and entitlements, and is intended to - /// be run on every commit. It should throw if either new binaries are added - /// to the cache or expected binaries removed. In either case, this class' - /// [binariesWithEntitlements] or [binariesWithoutEntitlements] lists should - /// be updated accordingly. - @visibleForTesting - Future verifyExist() async { - final Set foundFiles = {}; - for (final String binaryPath - in await findBinaryPaths(await framework.cacheDirectory)) { - if ((await binariesWithEntitlements).contains(binaryPath)) { - foundFiles.add(binaryPath); - } else if ((await binariesWithoutEntitlements).contains(binaryPath)) { - foundFiles.add(binaryPath); - } else { - throw ConductorException( - 'Found unexpected binary in cache: $binaryPath'); - } - } - - final List allExpectedFiles = - (await binariesWithEntitlements) + (await binariesWithoutEntitlements); - if (foundFiles.length < allExpectedFiles.length) { - final List unfoundFiles = allExpectedFiles - .where( - (String file) => !foundFiles.contains(file), - ) - .toList(); - stdio.printError( - 'Expected binaries not found in cache:\n\n${unfoundFiles.join('\n')}\n\n' - 'If this commit is removing binaries from the cache, this test should be fixed by\n' - 'removing the relevant entry from either the "binariesWithEntitlements" or\n' - '"binariesWithoutEntitlements" getters in dev/tools/lib/codesign.dart.', - ); - throw ConductorException('Did not find all expected binaries!'); - } - - stdio.printStatus('All expected binaries present.'); - } - - /// Verify code signatures and entitlements of all binaries in the cache. - @visibleForTesting - Future verifySignatures() async { - final List unsignedBinaries = []; - final List wrongEntitlementBinaries = []; - final List unexpectedBinaries = []; - for (final String binaryPath - in await findBinaryPaths(await framework.cacheDirectory)) { - bool verifySignature = false; - bool verifyEntitlements = false; - if ((await binariesWithEntitlements).contains(binaryPath)) { - verifySignature = true; - verifyEntitlements = true; - } - if ((await binariesWithoutEntitlements).contains(binaryPath)) { - verifySignature = true; - } - if (!verifySignature && !verifyEntitlements) { - unexpectedBinaries.add(binaryPath); - stdio.printError('Unexpected binary $binaryPath found in cache!'); - continue; - } - stdio.printTrace('Verifying the code signature of $binaryPath'); - final io.ProcessResult codeSignResult = await processManager.run( - [ - 'codesign', - '-vvv', - binaryPath, - ], - ); - if (codeSignResult.exitCode != 0) { - unsignedBinaries.add(binaryPath); - stdio.printError( - 'File "$binaryPath" does not appear to be codesigned.\n' - 'The `codesign` command failed with exit code ${codeSignResult.exitCode}:\n' - '${codeSignResult.stderr}\n', - ); - continue; - } - if (verifyEntitlements) { - stdio.printTrace('Verifying entitlements of $binaryPath'); - if (!(await hasExpectedEntitlements(binaryPath))) { - wrongEntitlementBinaries.add(binaryPath); - } - } - } - - // First print all deviations from expectations - if (unsignedBinaries.isNotEmpty) { - stdio.printError('Found ${unsignedBinaries.length} unsigned binaries:'); - unsignedBinaries.forEach(stdio.printError); - } - - if (wrongEntitlementBinaries.isNotEmpty) { - stdio.printError('Found ${wrongEntitlementBinaries.length} binaries with unexpected entitlements:'); - wrongEntitlementBinaries.forEach(stdio.printError); - } - - if (unexpectedBinaries.isNotEmpty) { - stdio.printError('Found ${unexpectedBinaries.length} unexpected binaries in the cache:'); - unexpectedBinaries.forEach(print); - } - - // Finally, exit on any invalid state - if (unsignedBinaries.isNotEmpty) { - throw ConductorException('Test failed because unsigned binaries detected.'); - } - - if (wrongEntitlementBinaries.isNotEmpty) { - throw ConductorException( - 'Test failed because files found with the wrong entitlements:\n' - '${wrongEntitlementBinaries.join('\n')}', - ); - } - - if (unexpectedBinaries.isNotEmpty) { - throw ConductorException('Test failed because unexpected binaries found in the cache.'); - } - - final String? desiredRevision = argResults![kRevision] as String?; - if (desiredRevision == null) { - stdio.printStatus('Verified that binaries are codesigned and have expected entitlements.'); - } else { - stdio.printStatus( - 'Verified that binaries for commit $desiredRevision are codesigned and have ' - 'expected entitlements.', - ); - } - } - - List? _allBinaryPaths; - - /// Find every binary file in the given [rootDirectory]. - Future> findBinaryPaths(String rootDirectory) async { - if (_allBinaryPaths != null) { - return _allBinaryPaths!; - } - final List allBinaryPaths = []; - final io.ProcessResult result = await processManager.run( - [ - 'find', - rootDirectory, - '-type', - 'f', - ], - ); - final List allFiles = (result.stdout as String) - .split('\n') - .where((String s) => s.isNotEmpty) - .toList(); - - await Future.forEach(allFiles, (String filePath) async { - if (await isBinary(filePath)) { - allBinaryPaths.add(filePath); - } - }); - _allBinaryPaths = allBinaryPaths; - return _allBinaryPaths!; - } - - /// Check mime-type of file at [filePath] to determine if it is binary. - Future isBinary(String filePath) async { - final io.ProcessResult result = await processManager.run( - [ - 'file', - '--mime-type', - '-b', // is binary - filePath, - ], - ); - return (result.stdout as String).contains('application/x-mach-binary'); - } - - /// Check if the binary has the expected entitlements. - Future hasExpectedEntitlements(String binaryPath) async { - final io.ProcessResult entitlementResult = await processManager.run( - [ - 'codesign', - '--display', - '--entitlements', - ':-', - binaryPath, - ], - ); - - if (entitlementResult.exitCode != 0) { - stdio.printError( - '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 = - (await binariesWithEntitlements).contains(binaryPath); - if (output.contains(entitlement) != entitlementExpected) { - stdio.printError( - 'File "$binaryPath" ${entitlementExpected ? 'does not have expected' : 'has unexpected'} ' - 'entitlement $entitlement.', - ); - passes = false; - } - } - return passes; - } -} diff --git a/dev/conductor/core/test/codesign_integration_test.dart b/dev/conductor/core/test/codesign_integration_test.dart deleted file mode 100644 index e065066299..0000000000 --- a/dev/conductor/core/test/codesign_integration_test.dart +++ /dev/null @@ -1,103 +0,0 @@ -// 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. - -// This test clones the framework and downloads pre-built binaries; it sometimes -// times out with the default 5 minutes: https://github.com/flutter/flutter/issues/100937 -@Timeout(Duration(minutes: 10)) -library; - -import 'package:args/command_runner.dart'; -import 'package:conductor_core/src/codesign.dart' show CodesignCommand; -import 'package:conductor_core/src/globals.dart'; -import 'package:conductor_core/src/repository.dart' show Checkouts, FrameworkRepository; -import 'package:file/file.dart'; -import 'package:file/local.dart'; -import 'package:platform/platform.dart'; -import 'package:process/process.dart'; - -import './common.dart'; - -/// Verify all binaries in the Flutter cache are expected by Conductor. -void main() { - test( - 'validate the expected binaries from the conductor codesign command are present in the cache', - () async { - const Platform platform = LocalPlatform(); - const FileSystem fileSystem = LocalFileSystem(); - final Directory tempDir = fileSystem.systemTempDirectory.createTempSync('flutter_conductor_integration_test.'); - const ProcessManager processManager = LocalProcessManager(); - final TestStdio stdio = TestStdio(verbose: true); - final Checkouts checkouts = Checkouts( - fileSystem: fileSystem, - parentDirectory: tempDir, - platform: platform, - processManager: processManager, - stdio: stdio, - ); - - final Directory flutterRoot = _flutterRootFromDartBinary( - fileSystem.file(platform.executable), - ); - - final String currentHead = (processManager.runSync( - ['git', 'rev-parse', 'HEAD'], - workingDirectory: flutterRoot.path, - ).stdout as String).trim(); - - final FrameworkRepository framework = FrameworkRepository.localRepoAsUpstream( - checkouts, - upstreamPath: flutterRoot.path, - initialRef: currentHead, - ); - final CommandRunner runner = CommandRunner('codesign-test', ''); - runner.addCommand( - CodesignCommand( - checkouts: checkouts, - framework: framework, - flutterRoot: flutterRoot, - ), - ); - - try { - await runner.run([ - 'codesign', - '--verify', - // Only verify if the correct binaries are in the cache - '--no-signatures', - ]); - } on ConductorException catch (e) { - print(stdio.error); - print(_fixItInstructions); - fail(e.message); - } on Exception { - print('stdout:\n${stdio.stdout}'); - print('stderr:\n${stdio.error}'); - rethrow; - } - }, onPlatform: { - 'windows': const Skip('codesign command is only supported on macos'), - 'linux': const Skip('codesign command is only supported on macos'), - }); -} - -Directory _flutterRootFromDartBinary(File dartBinary) { - final Directory flutterDartSdkDir = dartBinary.parent.parent; - final Directory flutterCache = flutterDartSdkDir.parent; - final Directory flutterSdkDir = flutterCache.parent.parent; - return flutterSdkDir; -} - -const String _fixItInstructions = ''' -Codesign integration test failed. - -This means that the binary files found in the Flutter cache do not match those -expected by the conductor tool (either an expected file was not found in the -cache or an unexpected file was found in the cache). - -This usually happens either during an engine roll or a change to the caching -logic in flutter_tools. If this is a valid change, then the conductor source -code should be updated, specifically either the [binariesWithEntitlements] or -[binariesWithoutEntitlements] lists, depending on if the file should have macOS -entitlements applied during codesigning. -'''; diff --git a/dev/conductor/core/test/codesign_test.dart b/dev/conductor/core/test/codesign_test.dart deleted file mode 100644 index 423d245228..0000000000 --- a/dev/conductor/core/test/codesign_test.dart +++ /dev/null @@ -1,615 +0,0 @@ -// 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 'package:args/command_runner.dart'; -import 'package:conductor_core/src/codesign.dart'; -import 'package:conductor_core/src/repository.dart'; -import 'package:file/memory.dart'; -import 'package:platform/platform.dart'; - -import './common.dart'; - -void main() { - group('codesign command', () { - const String flutterRoot = '/flutter'; - const String checkoutsParentDirectory = '$flutterRoot/dev/conductor/'; - const String flutterCache = - '${checkoutsParentDirectory}flutter_conductor_checkouts/framework/bin/cache'; - const String flutterBin = - '${checkoutsParentDirectory}flutter_conductor_checkouts/framework/bin/flutter'; - const String revision = 'abcd1234'; - late CommandRunner runner; - late Checkouts checkouts; - late MemoryFileSystem fileSystem; - late FakePlatform platform; - late TestStdio stdio; - late FakeProcessManager processManager; - const List binariesWithEntitlements = [ - '$flutterCache/dart-sdk/bin/dart', - '$flutterCache/dart-sdk/bin/dartaotruntime', - ]; - const List binariesWithoutEntitlements = [ - '$flutterCache/engine/darwin-x64/font-subset', - ]; - const List allBinaries = [ - ...binariesWithEntitlements, - ...binariesWithoutEntitlements, - ]; - - void createRunner({ - String operatingSystem = 'macos', - List? commands, - }) { - stdio = TestStdio(); - fileSystem = MemoryFileSystem.test(); - platform = FakePlatform(operatingSystem: operatingSystem); - processManager = FakeProcessManager.list(commands ?? []); - checkouts = Checkouts( - fileSystem: fileSystem, - parentDirectory: fileSystem.directory(checkoutsParentDirectory), - platform: platform, - processManager: processManager, - stdio: stdio, - ); - final FakeCodesignCommand command = FakeCodesignCommand( - checkouts: checkouts, - binariesWithEntitlements: Future>.value(binariesWithEntitlements), - binariesWithoutEntitlements: Future>.value(binariesWithoutEntitlements), - flutterRoot: fileSystem.directory(flutterRoot), - ); - runner = CommandRunner('codesign-test', '') - ..addCommand(command); - } - - test('throws exception if not run from macos', () async { - createRunner(operatingSystem: 'linux'); - expect( - () async => runner.run(['codesign']), - throwsExceptionWith('Error! Expected operating system "macos"'), - ); - }); - - test('throws exception if verify flag is not provided', () async { - createRunner(); - expect( - () async => runner.run(['codesign']), - throwsExceptionWith( - 'Sorry, but codesigning is not implemented yet. Please pass the --$kVerify flag to verify signatures'), - ); - }); - - test('does not fail if --revision flag not provided', () async { - final List codesignCheckCommands = []; - for (final String bin in binariesWithEntitlements) { - codesignCheckCommands.add( - FakeCommand( - command: ['codesign', '-vvv', bin], - ), - ); - codesignCheckCommands.add( - FakeCommand( - command: ['codesign', '--display', '--entitlements', ':-', bin], - stdout: expectedEntitlements.join('\n'), - ), - ); - } - for (final String bin in binariesWithoutEntitlements) { - codesignCheckCommands.add( - FakeCommand( - command: ['codesign', '-vvv', bin], - ), - ); - } - createRunner(commands: [ - const FakeCommand(command: [ - 'git', - 'rev-parse', - 'HEAD', - ], stdout: revision), - const FakeCommand(command: [ - 'git', - 'clone', - '--origin', - 'upstream', - '--', - FrameworkRepository.defaultUpstream, - '${checkoutsParentDirectory}flutter_conductor_checkouts/framework', - ]), - const FakeCommand(command: [ - 'git', - 'checkout', - FrameworkRepository.defaultBranch, - ]), - const FakeCommand(command: [ - 'git', - 'rev-parse', - 'HEAD', - ], stdout: revision), - const FakeCommand(command: [ - 'git', - 'checkout', - revision, - ]), - const FakeCommand(command: [ - flutterBin, - 'help', - ]), - const FakeCommand(command: [ - flutterBin, - 'help', - ]), - const FakeCommand(command: [ - flutterBin, - 'precache', - '--android', - '--ios', - '--macos', - ]), - FakeCommand( - command: const [ - 'find', - '${checkoutsParentDirectory}flutter_conductor_checkouts/framework/bin/cache', - '-type', - 'f', - ], - stdout: allBinaries.join('\n'), - ), - for (final String bin in allBinaries) - FakeCommand( - command: ['file', '--mime-type', '-b', bin], - stdout: 'application/x-mach-binary', - ), - ...codesignCheckCommands, - ]); - await runner.run(['codesign', '--$kVerify']); - expect(processManager.hasRemainingExpectations, false); - expect(stdio.stdout, contains('Verified that binaries are codesigned and have expected entitlements')); - }); - - test('framework cloned from repo provided by --$kUpstream', () async { - const String upstreamRepo = 'https://githost.org/org/project'; - final List codesignCheckCommands = []; - for (final String bin in binariesWithEntitlements) { - codesignCheckCommands.add( - FakeCommand( - command: ['codesign', '-vvv', bin], - ), - ); - codesignCheckCommands.add( - FakeCommand( - command: ['codesign', '--display', '--entitlements', ':-', bin], - stdout: expectedEntitlements.join('\n'), - ), - ); - } - for (final String bin in binariesWithoutEntitlements) { - codesignCheckCommands.add( - FakeCommand( - command: ['codesign', '-vvv', bin], - ), - ); - } - createRunner(commands: [ - const FakeCommand(command: [ - 'git', - 'clone', - '--origin', - 'upstream', - '--', - upstreamRepo, - '${checkoutsParentDirectory}flutter_conductor_checkouts/framework', - ]), - const FakeCommand(command: [ - 'git', - 'checkout', - FrameworkRepository.defaultBranch, - ]), - const FakeCommand(command: [ - 'git', - 'rev-parse', - 'HEAD', - ], stdout: revision), - const FakeCommand(command: [ - 'git', - 'checkout', - revision, - ]), - const FakeCommand(command: [ - flutterBin, - 'help', - ]), - const FakeCommand(command: [ - flutterBin, - 'help', - ]), - const FakeCommand(command: [ - flutterBin, - 'precache', - '--android', - '--ios', - '--macos', - ]), - FakeCommand( - command: const [ - 'find', - '${checkoutsParentDirectory}flutter_conductor_checkouts/framework/bin/cache', - '-type', - 'f', - ], - stdout: allBinaries.join('\n'), - ), - for (final String bin in allBinaries) - FakeCommand( - command: ['file', '--mime-type', '-b', bin], - stdout: 'application/x-mach-binary', - ), - ...codesignCheckCommands, - ]); - await runner.run([ - 'codesign', - '--$kVerify', - '--$kRevision', - revision, - '--$kUpstream', - upstreamRepo, - ]); - expect(processManager, hasNoRemainingExpectations); - expect(stdio.stdout, contains('Verified that binaries for commit $revision are codesigned and have expected entitlements')); - }); - - test('succeeds if every binary is codesigned and has correct entitlements', () async { - final List codesignCheckCommands = []; - for (final String bin in binariesWithEntitlements) { - codesignCheckCommands.add( - FakeCommand( - command: ['codesign', '-vvv', bin], - ), - ); - codesignCheckCommands.add( - FakeCommand( - command: ['codesign', '--display', '--entitlements', ':-', bin], - stdout: expectedEntitlements.join('\n'), - ), - ); - } - for (final String bin in binariesWithoutEntitlements) { - codesignCheckCommands.add( - FakeCommand( - command: ['codesign', '-vvv', bin], - ), - ); - } - createRunner(commands: [ - const FakeCommand(command: [ - 'git', - 'clone', - '--origin', - 'upstream', - '--', - FrameworkRepository.defaultUpstream, - '${checkoutsParentDirectory}flutter_conductor_checkouts/framework', - ]), - const FakeCommand(command: [ - 'git', - 'checkout', - FrameworkRepository.defaultBranch, - ]), - const FakeCommand(command: [ - 'git', - 'rev-parse', - 'HEAD', - ], stdout: revision), - const FakeCommand(command: [ - 'git', - 'checkout', - revision, - ]), - const FakeCommand(command: [ - flutterBin, - 'help', - ]), - const FakeCommand(command: [ - flutterBin, - 'help', - ]), - const FakeCommand(command: [ - flutterBin, - 'precache', - '--android', - '--ios', - '--macos', - ]), - FakeCommand( - command: const [ - 'find', - '${checkoutsParentDirectory}flutter_conductor_checkouts/framework/bin/cache', - '-type', - 'f', - ], - stdout: allBinaries.join('\n'), - ), - for (final String bin in allBinaries) - FakeCommand( - command: ['file', '--mime-type', '-b', bin], - stdout: 'application/x-mach-binary', - ), - ...codesignCheckCommands, - ]); - await runner.run(['codesign', '--$kVerify', '--$kRevision', revision]); - expect(processManager.hasRemainingExpectations, false); - expect(stdio.stdout, contains('Verified that binaries for commit $revision are codesigned and have expected entitlements')); - }); - - test('fails if a single binary is not codesigned', () async { - final List codesignCheckCommands = []; - codesignCheckCommands.add( - const FakeCommand( - command: ['codesign', '-vvv', '$flutterCache/dart-sdk/bin/dart'], - ), - ); - codesignCheckCommands.add( - FakeCommand( - command: const [ - 'codesign', - '--display', - '--entitlements', - ':-', - '$flutterCache/dart-sdk/bin/dart', - ], - stdout: expectedEntitlements.join('\n'), - ) - ); - // Not signed - codesignCheckCommands.add( - const FakeCommand( - command: ['codesign', '-vvv', '$flutterCache/dart-sdk/bin/dartaotruntime'], - exitCode: 1, - ), - ); - codesignCheckCommands.add( - const FakeCommand( - command: ['codesign', '-vvv', '$flutterCache/engine/darwin-x64/font-subset'], - ), - ); - - createRunner(commands: [ - const FakeCommand(command: [ - 'git', - 'clone', - '--origin', - 'upstream', - '--', - FrameworkRepository.defaultUpstream, - '${checkoutsParentDirectory}flutter_conductor_checkouts/framework', - ]), - const FakeCommand(command: [ - 'git', - 'checkout', - FrameworkRepository.defaultBranch, - ]), - const FakeCommand(command: [ - 'git', - 'rev-parse', - 'HEAD', - ], stdout: revision), - const FakeCommand(command: [ - 'git', - 'checkout', - revision, - ]), - const FakeCommand(command: [ - flutterBin, - 'help', - ]), - const FakeCommand(command: [ - flutterBin, - 'help', - ]), - const FakeCommand(command: [ - flutterBin, - 'precache', - '--android', - '--ios', - '--macos', - ]), - FakeCommand( - command: const [ - 'find', - '${checkoutsParentDirectory}flutter_conductor_checkouts/framework/bin/cache', - '-type', - 'f', - ], - stdout: allBinaries.join('\n'), - ), - for (final String bin in allBinaries) - FakeCommand( - command: ['file', '--mime-type', '-b', bin], - stdout: 'application/x-mach-binary', - ), - ...codesignCheckCommands, - ]); - await expectLater( - () => runner.run(['codesign', '--$kVerify', '--$kRevision', revision]), - throwsExceptionWith('Test failed because unsigned binaries detected.'), - ); - expect(processManager.hasRemainingExpectations, false); - }); - - test('fails if a single binary has the wrong entitlements', () async { - final List codesignCheckCommands = []; - codesignCheckCommands.add( - const FakeCommand( - command: ['codesign', '-vvv', '$flutterCache/dart-sdk/bin/dart'], - ), - ); - codesignCheckCommands.add( - FakeCommand( - command: const ['codesign', '--display', '--entitlements', ':-', '$flutterCache/dart-sdk/bin/dart'], - stdout: expectedEntitlements.join('\n'), - ) - ); - codesignCheckCommands.add( - const FakeCommand( - command: ['codesign', '-vvv', '$flutterCache/dart-sdk/bin/dartaotruntime'], - ), - ); - // No entitlements - codesignCheckCommands.add( - const FakeCommand( - command: ['codesign', '--display', '--entitlements', ':-', '$flutterCache/dart-sdk/bin/dartaotruntime'], - ) - ); - codesignCheckCommands.add( - const FakeCommand( - command: ['codesign', '-vvv', '$flutterCache/engine/darwin-x64/font-subset'], - ), - ); - createRunner(commands: [ - const FakeCommand(command: [ - 'git', - 'clone', - '--origin', - 'upstream', - '--', - FrameworkRepository.defaultUpstream, - '${checkoutsParentDirectory}flutter_conductor_checkouts/framework', - ]), - const FakeCommand(command: [ - 'git', - 'checkout', - FrameworkRepository.defaultBranch, - ]), - const FakeCommand(command: [ - 'git', - 'rev-parse', - 'HEAD', - ], stdout: revision), - const FakeCommand(command: [ - 'git', - 'checkout', - revision, - ]), - const FakeCommand(command: [ - flutterBin, - 'help', - ]), - const FakeCommand(command: [ - flutterBin, - 'help', - ]), - const FakeCommand(command: [ - flutterBin, - 'precache', - '--android', - '--ios', - '--macos', - ]), - FakeCommand( - command: const [ - 'find', - '${checkoutsParentDirectory}flutter_conductor_checkouts/framework/bin/cache', - '-type', - 'f', - ], - stdout: allBinaries.join('\n'), - ), - for (final String bin in allBinaries) - FakeCommand( - command: ['file', '--mime-type', '-b', bin], - stdout: 'application/x-mach-binary', - ), - ...codesignCheckCommands, - ]); - await expectLater( - () => runner.run(['codesign', '--$kVerify', '--$kRevision', revision]), - throwsExceptionWith('Test failed because files found with the wrong entitlements'), - ); - expect(processManager.hasRemainingExpectations, false); - }); - - test('does not check signatures or entitlements if --no-$kSignatures specified', () async { - createRunner(commands: [ - const FakeCommand(command: [ - 'git', - 'clone', - '--origin', - 'upstream', - '--', - FrameworkRepository.defaultUpstream, - '${checkoutsParentDirectory}flutter_conductor_checkouts/framework', - ]), - const FakeCommand(command: [ - 'git', - 'checkout', - FrameworkRepository.defaultBranch, - ]), - const FakeCommand(command: [ - 'git', - 'rev-parse', - 'HEAD', - ], stdout: revision), - const FakeCommand(command: [ - 'git', - 'checkout', - revision, - ]), - const FakeCommand(command: [ - flutterBin, - 'help', - ]), - const FakeCommand(command: [ - flutterBin, - 'help', - ]), - const FakeCommand(command: [ - flutterBin, - 'precache', - '--android', - '--ios', - '--macos', - ]), - FakeCommand( - command: const [ - 'find', - '${checkoutsParentDirectory}flutter_conductor_checkouts/framework/bin/cache', - '-type', - 'f', - ], - stdout: allBinaries.join('\n'), - ), - for (final String bin in allBinaries) - FakeCommand( - command: ['file', '--mime-type', '-b', bin], - stdout: 'application/x-mach-binary', - ), - ]); - await runner.run([ - 'codesign', - '--$kVerify', - '--no-$kSignatures', - '--$kRevision', - revision, - ]); - expect( - processManager.hasRemainingExpectations, - false, - ); - }); - }); -} - -class FakeCodesignCommand extends CodesignCommand { - FakeCodesignCommand({ - required super.checkouts, - required this.binariesWithEntitlements, - required this.binariesWithoutEntitlements, - required super.flutterRoot, - }); - - @override - final Future> binariesWithEntitlements; - - @override - final Future> binariesWithoutEntitlements; -}