diff --git a/packages/shorebird_cli/lib/src/commands/patch/android_patcher.dart b/packages/shorebird_cli/lib/src/commands/patch/android_patcher.dart index a2d76dcc..453994ef 100644 --- a/packages/shorebird_cli/lib/src/commands/patch/android_patcher.dart +++ b/packages/shorebird_cli/lib/src/commands/patch/android_patcher.dart @@ -8,6 +8,7 @@ import 'package:shorebird_cli/src/artifact_manager.dart'; import 'package:shorebird_cli/src/code_push_client_wrapper.dart'; import 'package:shorebird_cli/src/code_signer.dart'; import 'package:shorebird_cli/src/commands/patch/patcher.dart'; +import 'package:shorebird_cli/src/common_arguments.dart'; import 'package:shorebird_cli/src/doctor.dart'; import 'package:shorebird_cli/src/extensions/arg_results.dart'; import 'package:shorebird_cli/src/extensions/file.dart'; @@ -36,7 +37,7 @@ class AndroidPatcher extends Patcher { @override Future assertArgsAreValid() async { - argResults.file('private-key-path')?.assertExists(); + argResults.file(CommonArguments.privateKeyArgName)?.assertExists(); } @override @@ -157,7 +158,7 @@ Looked in: final patchArtifact = File(patchArtifactPath); final hash = sha256.convert(await patchArtifact.readAsBytes()).toString(); - final privateKeyFile = argResults.file('private-key-path'); + final privateKeyFile = argResults.file(CommonArguments.privateKeyArgName); final hashSignature = privateKeyFile != null ? codeSigner.sign( message: hash, diff --git a/packages/shorebird_cli/lib/src/commands/patch/ios_patcher.dart b/packages/shorebird_cli/lib/src/commands/patch/ios_patcher.dart index d8bcb1eb..e1d52750 100644 --- a/packages/shorebird_cli/lib/src/commands/patch/ios_patcher.dart +++ b/packages/shorebird_cli/lib/src/commands/patch/ios_patcher.dart @@ -9,9 +9,12 @@ import 'package:shorebird_cli/src/archive_analysis/archive_differ.dart'; import 'package:shorebird_cli/src/artifact_builder.dart'; import 'package:shorebird_cli/src/artifact_manager.dart'; import 'package:shorebird_cli/src/code_push_client_wrapper.dart'; +import 'package:shorebird_cli/src/code_signer.dart'; import 'package:shorebird_cli/src/commands/patch/patcher.dart'; +import 'package:shorebird_cli/src/common_arguments.dart'; import 'package:shorebird_cli/src/doctor.dart'; import 'package:shorebird_cli/src/executables/executables.dart'; +import 'package:shorebird_cli/src/extensions/arg_results.dart'; import 'package:shorebird_cli/src/logger.dart'; import 'package:shorebird_cli/src/patch_diff_checker.dart'; import 'package:shorebird_cli/src/platform.dart'; @@ -210,12 +213,22 @@ class IosPatcher extends Patcher { } final patchFileSize = patchFile.statSync().size; + final privateKeyFile = argResults.file(CommonArguments.privateKeyArgName); + final hash = sha256.convert(patchBuildFile.readAsBytesSync()).toString(); + final hashSignature = privateKeyFile != null + ? codeSigner.sign( + message: hash, + privateKeyPemFile: privateKeyFile, + ) + : null; + return { Arch.arm64: PatchArtifactBundle( arch: 'aarch64', path: patchFile.path, - hash: sha256.convert(patchBuildFile.readAsBytesSync()).toString(), + hash: hash, size: patchFileSize, + hashSignature: hashSignature, ), }; } diff --git a/packages/shorebird_cli/lib/src/commands/patch/patch_command.dart b/packages/shorebird_cli/lib/src/commands/patch/patch_command.dart index e274a939..8d6f4f94 100644 --- a/packages/shorebird_cli/lib/src/commands/patch/patch_command.dart +++ b/packages/shorebird_cli/lib/src/commands/patch/patch_command.dart @@ -8,6 +8,7 @@ import 'package:shorebird_cli/src/cache.dart'; import 'package:shorebird_cli/src/code_push_client_wrapper.dart'; import 'package:shorebird_cli/src/command.dart'; import 'package:shorebird_cli/src/commands/patch/patch.dart'; +import 'package:shorebird_cli/src/common_arguments.dart'; import 'package:shorebird_cli/src/config/config.dart'; import 'package:shorebird_cli/src/deployment_track.dart'; import 'package:shorebird_cli/src/extensions/arg_results.dart'; @@ -94,7 +95,7 @@ of the iOS app that is using this module.''', help: 'Validate but do not upload the patch.', ) ..addOption( - 'private-key-path', + CommonArguments.privateKeyArgName, hide: true, help: ''' The path for a private key file that will be used to sign the patch artifact. diff --git a/packages/shorebird_cli/lib/src/commands/release/release_command.dart b/packages/shorebird_cli/lib/src/commands/release/release_command.dart index 73c52492..67d21b51 100644 --- a/packages/shorebird_cli/lib/src/commands/release/release_command.dart +++ b/packages/shorebird_cli/lib/src/commands/release/release_command.dart @@ -7,6 +7,7 @@ import 'package:shorebird_cli/src/cache.dart'; import 'package:shorebird_cli/src/code_push_client_wrapper.dart'; import 'package:shorebird_cli/src/command.dart'; import 'package:shorebird_cli/src/commands/release/release.dart'; +import 'package:shorebird_cli/src/common_arguments.dart'; import 'package:shorebird_cli/src/config/config.dart'; import 'package:shorebird_cli/src/extensions/arg_results.dart'; import 'package:shorebird_cli/src/logger.dart'; @@ -116,7 +117,7 @@ of the iOS app that is using this module.''', allowed: Arch.values.map((arch) => arch.targetPlatformCliArg), ) ..addOption( - 'public-key-path', + CommonArguments.publicKeyArgName, hide: true, help: ''' The path for a public key file that will be used to validate patch signatures. diff --git a/packages/shorebird_cli/lib/src/common_arguments.dart b/packages/shorebird_cli/lib/src/common_arguments.dart new file mode 100644 index 00000000..15669ca9 --- /dev/null +++ b/packages/shorebird_cli/lib/src/common_arguments.dart @@ -0,0 +1,6 @@ +/// A class that houses the name of arguments that are shared between different +/// commands and layers. +class CommonArguments { + static const String publicKeyArgName = 'public-key-path'; + static const String privateKeyArgName = 'private-key-path'; +} diff --git a/packages/shorebird_cli/lib/src/extensions/arg_results.dart b/packages/shorebird_cli/lib/src/extensions/arg_results.dart index 2a689f31..1fe6a22a 100644 --- a/packages/shorebird_cli/lib/src/extensions/arg_results.dart +++ b/packages/shorebird_cli/lib/src/extensions/arg_results.dart @@ -3,6 +3,7 @@ import 'dart:io'; import 'package:args/args.dart'; import 'package:collection/collection.dart'; +import 'package:shorebird_cli/src/common_arguments.dart'; import 'package:shorebird_cli/src/extensions/file.dart'; extension OptionFinder on ArgResults { @@ -44,18 +45,16 @@ extension OptionFinder on ArgResults { } } -const _publicKeyArgName = 'public-key-path'; - extension CodeSign on ArgResults { /// Asserts that either there is no public key argument /// or that the path received exists. void assertAbsentOrValidPublicKey() { - file(_publicKeyArgName)?.assertExists(); + file(CommonArguments.publicKeyArgName)?.assertExists(); } /// Read the public key file and encode it to base64 if any. String? get encodedPublicKey { - final publicKeyFile = file(_publicKeyArgName); + final publicKeyFile = file(CommonArguments.publicKeyArgName); return publicKeyFile != null ? base64Encode(publicKeyFile.readAsBytesSync()) diff --git a/packages/shorebird_cli/test/src/commands/patch/android_patcher_test.dart b/packages/shorebird_cli/test/src/commands/patch/android_patcher_test.dart index 384af69b..f08a9ea0 100644 --- a/packages/shorebird_cli/test/src/commands/patch/android_patcher_test.dart +++ b/packages/shorebird_cli/test/src/commands/patch/android_patcher_test.dart @@ -11,6 +11,7 @@ import 'package:shorebird_cli/src/artifact_manager.dart'; import 'package:shorebird_cli/src/code_push_client_wrapper.dart'; import 'package:shorebird_cli/src/code_signer.dart'; import 'package:shorebird_cli/src/commands/patch/patch.dart'; +import 'package:shorebird_cli/src/common_arguments.dart'; import 'package:shorebird_cli/src/doctor.dart'; import 'package:shorebird_cli/src/engine_config.dart'; import 'package:shorebird_cli/src/logger.dart'; @@ -466,7 +467,7 @@ Looked in: ), )..createSync(); - when(() => argResults['private-key-path']) + when(() => argResults[CommonArguments.privateKeyArgName]) .thenReturn(privateKey.path); when( diff --git a/packages/shorebird_cli/test/src/commands/patch/ios_patcher_test.dart b/packages/shorebird_cli/test/src/commands/patch/ios_patcher_test.dart index 4d1ea937..77776f17 100644 --- a/packages/shorebird_cli/test/src/commands/patch/ios_patcher_test.dart +++ b/packages/shorebird_cli/test/src/commands/patch/ios_patcher_test.dart @@ -8,7 +8,9 @@ import 'package:shorebird_cli/src/archive_analysis/archive_analysis.dart'; import 'package:shorebird_cli/src/artifact_builder.dart'; import 'package:shorebird_cli/src/artifact_manager.dart'; import 'package:shorebird_cli/src/code_push_client_wrapper.dart'; +import 'package:shorebird_cli/src/code_signer.dart'; import 'package:shorebird_cli/src/commands/patch/patch.dart'; +import 'package:shorebird_cli/src/common_arguments.dart'; import 'package:shorebird_cli/src/doctor.dart'; import 'package:shorebird_cli/src/engine_config.dart'; import 'package:shorebird_cli/src/executables/aot_tools.dart'; @@ -43,6 +45,7 @@ void main() { late ArtifactBuilder artifactBuilder; late ArtifactManager artifactManager; late CodePushClientWrapper codePushClientWrapper; + late CodeSigner codeSigner; late Doctor doctor; late EngineConfig engineConfig; late Directory flutterDirectory; @@ -69,6 +72,7 @@ void main() { artifactBuilderRef.overrideWith(() => artifactBuilder), artifactManagerRef.overrideWith(() => artifactManager), codePushClientWrapperRef.overrideWith(() => codePushClientWrapper), + codeSignerRef.overrideWith(() => codeSigner), doctorRef.overrideWith(() => doctor), engineConfigRef.overrideWith(() => engineConfig), iosRef.overrideWith(() => ios), @@ -102,6 +106,7 @@ void main() { artifactBuilder = MockArtifactBuilder(); artifactManager = MockArtifactManager(); codePushClientWrapper = MockCodePushClientWrapper(); + codeSigner = MockCodeSigner(); doctor = MockDoctor(); engineConfig = MockEngineConfig(); ios = MockIos(); @@ -935,6 +940,56 @@ void main() { ).called(1); }); }); + + group('when a private key is provided', () { + setUp(() { + final privateKey = File( + p.join( + Directory.systemTemp.createTempSync().path, + 'test-private.pem', + ), + )..createSync(); + + when(() => argResults[CommonArguments.privateKeyArgName]) + .thenReturn(privateKey.path); + + when( + () => codeSigner.sign( + message: any(named: 'message'), + privateKeyPemFile: any(named: 'privateKeyPemFile'), + ), + ).thenAnswer((invocation) { + final message = + invocation.namedArguments[#message] as String; + return '$message-signature'; + }); + }); + + test( + '''returns patch artifact bundles with proper hash signatures''', + () async { + final result = await runWithOverrides( + () => patcher.createPatchArtifacts( + appId: appId, + releaseId: releaseId, + releaseArtifact: releaseArtifactFile, + ), + ); + + // Hash the patch artifacts and append '-signature' to get the + // expected signatures, per the mock of [codeSigner.sign] + // above. + const expectedSignature = + '''e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855-signature'''; + + expect( + result.values.first.hashSignature, + equals( + expectedSignature, + ), + ); + }); + }); }); }); diff --git a/packages/shorebird_cli/test/src/commands/release/android_releaser_test.dart b/packages/shorebird_cli/test/src/commands/release/android_releaser_test.dart index b39149f7..e0a0ccdc 100644 --- a/packages/shorebird_cli/test/src/commands/release/android_releaser_test.dart +++ b/packages/shorebird_cli/test/src/commands/release/android_releaser_test.dart @@ -10,6 +10,7 @@ import 'package:shorebird_cli/src/artifact_builder.dart'; import 'package:shorebird_cli/src/code_push_client_wrapper.dart'; import 'package:shorebird_cli/src/code_signer.dart'; import 'package:shorebird_cli/src/commands/release/android_releaser.dart'; +import 'package:shorebird_cli/src/common_arguments.dart'; import 'package:shorebird_cli/src/doctor.dart'; import 'package:shorebird_cli/src/engine_config.dart'; import 'package:shorebird_cli/src/logger.dart'; @@ -228,7 +229,7 @@ void main() { 'public-key.der', ), )..writeAsStringSync('public key'); - when(() => argResults['public-key-path']) + when(() => argResults[CommonArguments.publicKeyArgName]) .thenReturn(publicKeyFile.path); }); @@ -243,7 +244,7 @@ void main() { group('when a public key is provided but it does not exists', () { setUp(() { when(() => argResults['artifact']).thenReturn('apk'); - when(() => argResults['public-key-path']) + when(() => argResults[CommonArguments.publicKeyArgName]) .thenReturn('non-existing-key.der'); }); @@ -435,7 +436,7 @@ void main() { 'patch-signing-public-key.der', ), )..writeAsStringSync('public key'); - when(() => argResults['public-key-path']) + when(() => argResults[CommonArguments.publicKeyArgName]) .thenReturn(patchSigningPublicKeyFile.path); when( diff --git a/packages/shorebird_cli/test/src/commands/release/ios_releaser_test.dart b/packages/shorebird_cli/test/src/commands/release/ios_releaser_test.dart index 05037fa9..95c157e2 100644 --- a/packages/shorebird_cli/test/src/commands/release/ios_releaser_test.dart +++ b/packages/shorebird_cli/test/src/commands/release/ios_releaser_test.dart @@ -10,6 +10,7 @@ import 'package:shorebird_cli/src/artifact_builder.dart'; import 'package:shorebird_cli/src/artifact_manager.dart'; import 'package:shorebird_cli/src/code_push_client_wrapper.dart'; import 'package:shorebird_cli/src/commands/release/ios_releaser.dart'; +import 'package:shorebird_cli/src/common_arguments.dart'; import 'package:shorebird_cli/src/doctor.dart'; import 'package:shorebird_cli/src/executables/xcodebuild.dart'; import 'package:shorebird_cli/src/logger.dart'; @@ -254,7 +255,7 @@ void main() { 'public-key.der', ), )..writeAsStringSync('public key'); - when(() => argResults['public-key-path']) + when(() => argResults[CommonArguments.publicKeyArgName]) .thenReturn(publicKeyFile.path); }); @@ -268,7 +269,7 @@ void main() { group('when the provided public key is a nonexistent file', () { setUp(() { - when(() => argResults['public-key-path']) + when(() => argResults[CommonArguments.publicKeyArgName]) .thenReturn('non-existing-key.der'); }); @@ -331,7 +332,7 @@ void main() { 'patch-signing-public-key.der', ), )..writeAsStringSync('public key'); - when(() => argResults['public-key-path']) + when(() => argResults[CommonArguments.publicKeyArgName]) .thenReturn(patchSigningPublicKeyFile.path); when( diff --git a/packages/shorebird_cli/test/src/commands/release/release_command_test.dart b/packages/shorebird_cli/test/src/commands/release/release_command_test.dart index 30226cad..5f351998 100644 --- a/packages/shorebird_cli/test/src/commands/release/release_command_test.dart +++ b/packages/shorebird_cli/test/src/commands/release/release_command_test.dart @@ -6,6 +6,7 @@ import 'package:scoped_deps/scoped_deps.dart'; import 'package:shorebird_cli/src/cache.dart'; import 'package:shorebird_cli/src/code_push_client_wrapper.dart'; import 'package:shorebird_cli/src/commands/release/release.dart'; +import 'package:shorebird_cli/src/common_arguments.dart'; import 'package:shorebird_cli/src/config/config.dart'; import 'package:shorebird_cli/src/logger.dart'; import 'package:shorebird_cli/src/release_type.dart'; @@ -500,7 +501,8 @@ $exception''', keyName, ), )..writeAsStringSync('KEY'); - when(() => argResults['public-key-path']).thenReturn(file.path); + when(() => argResults[CommonArguments.publicKeyArgName]) + .thenReturn(file.path); }); test('completes successfully', () async {