[codesign + recipe ] read passwords from a file (#2277)

diff --git a/codesign/bin/codesign.dart b/codesign/bin/codesign.dart
index 05a85ba..053282c 100644
--- a/codesign/bin/codesign.dart
+++ b/codesign/bin/codesign.dart
@@ -15,12 +15,11 @@
 const String kHelpFlag = 'help';
 const String kDryrunFlag = 'dryrun';
 const String kCodesignCertNameOption = 'codesign-cert-name';
-const String kCodesignUserNameOption = 'codesign-username';
-const String kAppSpecificPasswordOption = 'app-specific-password';
-const String kCodesignAppStoreIdOption = 'codesign-appstore-id';
-const String kCodesignTeamIdOption = 'codesign-team-id';
 const String kGcsDownloadPathOption = 'gcs-download-path';
 const String kGcsUploadPathOption = 'gcs-upload-path';
+const String kAppSpecificPasswordOption = 'app-specific-password-file-path';
+const String kCodesignAppstoreIDOption = 'codesign-appstore-id-file-path';
+const String kCodesignTeamIDOption = 'codesign-team-id-file-path';
 
 /// Perform Mac code signing based on file paths.
 ///
@@ -34,12 +33,33 @@
 /// For [kGcsDownloadPathOption] and [kGcsUploadPathOption], they are required parameter to specify the google cloud bucket paths.
 /// [kGcsDownloadPathOption] is the google cloud bucket prefix to download the remote artifacts,
 /// [kGcsUploadPathOption] is the cloud bucket prefix to upload codesigned artifact to.
-/// For example, supply '--gcs-download-path=gs://flutter_infra_release/ios-usb-dependencies/unsigned/libimobiledevice/<commit>/libimobiledevice.zip',
-/// and code sign app will download the artifact at 'flutter_infra_release/ios-usb-dependencies/unsigned/libimobiledevice/<commit>/libimobiledevice.zip' on google cloud storage
+/// For example, supply
+/// '--gcs-download-path=gs://flutter_infra_release/ios-usb-dependencies/unsigned/libimobiledevice/<commit>/libimobiledevice.zip',
+/// and code sign app will download the artifact at
+/// 'flutter_infra_release/ios-usb-dependencies/unsigned/libimobiledevice/<commit>/libimobiledevice.zip'
+/// on google cloud storage
+///
+/// For [kAppSpecificPasswordOption], [kCodesignAppstoreIDOption] and [kCodesignTeamIDOption],
+/// they are file paths of the password files in the file system.
+/// Each of the file paths stores a single line of sensitive password.
+/// sensitive passwords include <CODESIGN_APPSTORE_ID>, <CODESIGN_TEAM_ID>, and <APP_SPECIFIC_PASSWORD>.
+/// For example, if a user supplies --app-specific-password-file-path=/tmp/passwords.txt,
+/// then we would be expecting a password file located at /tmp/passwords.txt.
+/// The password file should contain the password name APP-SPECIFIC-PASSWORD and its value, deliminated by a single colon.
+/// The content of a password file would look similar to:
+/// APP-SPECIFIC-PASSWORD:789
+///
+/// [kCodesignCertNameOption] is public information. For codesigning flutter artifacts,
+/// a user can provide values for this variable as shown in the example below.
 ///
 /// Usage:
 /// ```shell
-/// dart run bin/codesign.dart --[no-]dryrun --gcs-download-path=gs://flutter_infra_release/flutter/<commit>/android-arm-profile/artifacts.zip
+/// dart run bin/codesign.dart --[no-]dryrun
+/// --codesign-cert-name="FLUTTER.IO LLC"
+/// --codesign-team-id-file-path=/a/b/c.txt
+/// --codesign-appstore-id-file-path=/a/b/b.txt
+/// --app-specific-password-file-path=/a/b/a.txt
+/// --gcs-download-path=gs://flutter_infra_release/flutter/<commit>/android-arm-profile/artifacts.zip
 /// --gcs-upload-path=gs://flutter_infra_release/flutter/<commit>/android-arm-profile/artifacts.zip
 /// ```
 Future<void> main(List<String> args) async {
@@ -57,18 +77,6 @@
           'the name of the certificate for flutter, for example, is: FLUTTER.IO LLC',
     )
     ..addOption(
-      kAppSpecificPasswordOption,
-      help: 'Unique password specifically for codesigning the given application.',
-    )
-    ..addOption(
-      kCodesignAppStoreIdOption,
-      help: 'Apple developer account email used for authentication with notary service.',
-    )
-    ..addOption(
-      kCodesignTeamIdOption,
-      help: 'Team-id is used by notary service for xcode version 13+.',
-    )
-    ..addOption(
       kGcsDownloadPathOption,
       help: 'The google cloud bucket path to download the artifact from\n'
           'e.g. supply `--gcs-download-path=gs://flutter_infra_release/ios-usb-dependencies/unsigned/ios-deploy/<commit>/ios-deploy.zip`'
@@ -80,6 +88,21 @@
           'e.g. supply `--gcs-upload-path=gs://flutter_infra_release/ios-usb-dependencies/ios-deploy/<commit>/ios-deploy.zip`'
           ' if you would like to codesign ios-deploy.zip, which has a google cloud bucket path of flutter_infra_release/ios-usb-dependencies/ios-deploy/<commit>/ios-deploy.zip to be uploaded to',
     )
+    ..addOption(
+      kAppSpecificPasswordOption,
+      help:
+          'The file path of a password file in file system. The password file stores the sensitive password <APP-SPECIFIC-PASSWORD> \n',
+    )
+    ..addOption(
+      kCodesignAppstoreIDOption,
+      help:
+          'The file path of a password file in file system. The password file stores the sensitive password <CODESIGN_APPSTORE_ID> \n',
+    )
+    ..addOption(
+      kCodesignTeamIDOption,
+      help:
+          'The file path of a password file in file system. The password file stores the sensitive password <CODESIGN_TEAM_ID> \n',
+    )
     ..addFlag(
       kDryrunFlag,
       defaultsTo: true,
@@ -90,14 +113,12 @@
 
   const Platform platform = LocalPlatform();
 
-  final String codesignCertName = getValueFromEnvOrArgs(kCodesignCertNameOption, argResults, platform.environment)!;
-  final String codesignUserName = getValueFromEnvOrArgs(kCodesignUserNameOption, argResults, platform.environment)!;
-  final String appSpecificPassword =
-      getValueFromEnvOrArgs(kAppSpecificPasswordOption, argResults, platform.environment)!;
-  final String codesignAppstoreId = getValueFromEnvOrArgs(kCodesignAppStoreIdOption, argResults, platform.environment)!;
-  final String codesignTeamId = getValueFromEnvOrArgs(kCodesignTeamIdOption, argResults, platform.environment)!;
-  final String gCloudDownloadPath = getValueFromEnvOrArgs(kGcsDownloadPathOption, argResults, platform.environment)!;
-  final String gCloudUploadPath = getValueFromEnvOrArgs(kGcsUploadPathOption, argResults, platform.environment)!;
+  final String codesignCertName = getValueFromArgs(kCodesignCertNameOption, argResults)!;
+  final String gCloudDownloadPath = getValueFromArgs(kGcsDownloadPathOption, argResults)!;
+  final String gCloudUploadPath = getValueFromArgs(kGcsUploadPathOption, argResults)!;
+  final String appSpecificPasswordFilePath = getValueFromArgs(kAppSpecificPasswordOption, argResults)!;
+  final String codesignAppstoreIDFilePath = getValueFromArgs(kCodesignAppstoreIDOption, argResults)!;
+  final String codesignTeamIDFilePath = getValueFromArgs(kCodesignTeamIDOption, argResults)!;
 
   final bool dryrun = argResults[kDryrunFlag] as bool;
 
@@ -118,12 +139,11 @@
 
   return FileCodesignVisitor(
     codesignCertName: codesignCertName,
-    codesignUserName: codesignUserName,
-    appSpecificPassword: appSpecificPassword,
-    codesignAppstoreId: codesignAppstoreId,
-    codesignTeamId: codesignTeamId,
     fileSystem: fileSystem,
     rootDirectory: rootDirectory,
+    appSpecificPasswordFilePath: appSpecificPasswordFilePath,
+    codesignAppstoreIDFilePath: codesignAppstoreIDFilePath,
+    codesignTeamIDFilePath: codesignTeamIDFilePath,
     processManager: processManager,
     dryrun: dryrun,
     gcsDownloadPath: gCloudDownloadPath,
diff --git a/codesign/lib/src/file_codesign_visitor.dart b/codesign/lib/src/file_codesign_visitor.dart
index 8d05f39..8581ef7 100644
--- a/codesign/lib/src/file_codesign_visitor.dart
+++ b/codesign/lib/src/file_codesign_visitor.dart
@@ -26,16 +26,15 @@
 class FileCodesignVisitor {
   FileCodesignVisitor({
     required this.codesignCertName,
-    required this.codesignUserName,
-    required this.appSpecificPassword,
-    required this.codesignAppstoreId,
-    required this.codesignTeamId,
     required this.fileSystem,
     required this.rootDirectory,
     required this.processManager,
     required this.gcsDownloadPath,
     required this.gcsUploadPath,
     required this.googleCloudStorage,
+    required this.appSpecificPasswordFilePath,
+    required this.codesignAppstoreIDFilePath,
+    required this.codesignTeamIDFilePath,
     this.dryrun = true,
     this.notarizationTimerDuration = const Duration(seconds: 5),
   }) {
@@ -53,19 +52,30 @@
   final GoogleCloudStorage googleCloudStorage;
 
   final String codesignCertName;
-  final String codesignUserName;
-  final String appSpecificPassword;
-  final String codesignAppstoreId;
-  final String codesignTeamId;
   final String gcsDownloadPath;
   final String gcsUploadPath;
+  final String appSpecificPasswordFilePath;
+  final String codesignAppstoreIDFilePath;
+  final String codesignTeamIDFilePath;
   final bool dryrun;
   final Duration notarizationTimerDuration;
 
+  // 'Apple developer account email used for authentication with notary service.'
+  late String codesignAppstoreId;
+  // Unique password of the apple developer account.'
+  late String appSpecificPassword;
+  // Team-id is used by notary service for xcode version 13+.
+  late String codesignTeamId;
+
   Set<String> fileWithEntitlements = <String>{};
   Set<String> fileWithoutEntitlements = <String>{};
   Set<String> fileConsumed = <String>{};
   Set<String> directoriesVisited = <String>{};
+  Map<String, String> availablePasswords = {
+    'CODESIGN_APPSTORE_ID': '',
+    'CODESIGN_TEAM_ID': '',
+    'APP_SPECIFIC_PASSWORD': ''
+  };
 
   late final File entitlementsFile;
   late final Directory remoteDownloadsDir;
@@ -119,8 +129,50 @@
 update these file paths accordingly.
 ''';
 
+  /// Read a single line of password stored at [passwordFilePath].
+  ///
+  /// The password file should provide the password name and value, deliminated by a single colon.
+  /// The content of a password file would look similar to:
+  /// CODESIGN_APPSTORE_ID:123
+  Future<void> readPassword(String passwordFilePath) async {
+    if (!(await fileSystem.file(passwordFilePath).exists())) {
+      throw CodesignException('$passwordFilePath not found \n'
+          'make sure you have provided codesign credentials in a file \n');
+    }
+
+    String passwordLine = await fileSystem.file(passwordFilePath).readAsString();
+    List<String> parsedPasswordLine = passwordLine.split(":");
+    if (parsedPasswordLine.length != 2) {
+      throw CodesignException('$passwordFilePath is not correctly formatted. \n'
+          'please double check formatting \n');
+    }
+    String passwordName = parsedPasswordLine[0];
+    String passwordValue = parsedPasswordLine[1];
+    if (!availablePasswords.containsKey(passwordName)) {
+      throw CodesignException('$passwordName is not a password we can process. \n'
+          'please double check passwords.txt \n');
+    }
+    availablePasswords[passwordName] = passwordValue;
+    return;
+  }
+
   /// The entrance point of examining and code signing an engine artifact.
   Future<void> validateAll() async {
+    for (String passwordFilePath in [
+      codesignAppstoreIDFilePath,
+      codesignTeamIDFilePath,
+      appSpecificPasswordFilePath,
+    ]) {
+      await readPassword(passwordFilePath);
+    }
+    if (availablePasswords.containsValue('')) {
+      throw CodesignException('certian passwords are missing. \n'
+          'make sure you have provided <CODESIGN_APPSTORE_ID>, <CODESIGN_TEAM_ID>, and <APP_SPECIFIC_PASSWORD>');
+    }
+    codesignAppstoreId = availablePasswords['CODESIGN_APPSTORE_ID']!;
+    codesignTeamId = availablePasswords['CODESIGN_TEAM_ID']!;
+    appSpecificPassword = availablePasswords['APP_SPECIFIC_PASSWORD']!;
+
     await processRemoteZip();
 
     if (dryrun) {
diff --git a/codesign/lib/src/utils.dart b/codesign/lib/src/utils.dart
index 1c34a04..400ebce 100644
--- a/codesign/lib/src/utils.dart
+++ b/codesign/lib/src/utils.dart
@@ -89,39 +89,22 @@
   String toString() => 'Exception: $message';
 }
 
-/// Translate CLI arg names to env variable names.
+/// Return the command line argument by parsing [argResults].
 ///
-/// For example, 'state-file' -> 'STATE_FILE'.
-String fromArgToEnvName(String argName) {
-  return argName.toUpperCase().replaceAll('-', '_');
-}
-
-/// Either return the value from [env] or fall back to [argResults].
-///
-/// If the key does not exist in either the environment or CLI args, throws a
-/// [ConductorException].
-///
-/// The environment is favored over CLI args since the latter can have a default
-/// value, which the environment should be able to override.
-String? getValueFromEnvOrArgs(
+/// If the key does not exist in CLI args, throws a [CodesignException].
+String? getValueFromArgs(
   String name,
-  ArgResults argResults,
-  Map<String, String> env, {
+  ArgResults argResults, {
   bool allowNull = false,
 }) {
-  final String envName = fromArgToEnvName(name);
-  if (env[envName] != null) {
-    return env[envName];
-  }
   final String? argValue = argResults[name] as String?;
   if (argValue != null) {
     return argValue;
   }
-
   if (allowNull) {
     return null;
   }
-  throw CodesignException('Expected either the CLI arg --$name or the environment variable $envName '
+  throw CodesignException('Expected either the CLI arg --$name '
       'to be provided!');
 }
 
diff --git a/codesign/test/file_codesign_visitor_test.dart b/codesign/test/file_codesign_visitor_test.dart
index 29294ff..6a596d7 100644
--- a/codesign/test/file_codesign_visitor_test.dart
+++ b/codesign/test/file_codesign_visitor_test.dart
@@ -17,6 +17,9 @@
 
 void main() {
   const String randomString = 'abcd1234';
+  const String appSpecificPasswordFilePath = '/tmp/passwords.txt';
+  const String codesignAppstoreIDFilePath = '/tmp/appID.txt';
+  const String codesignTeamIDFilePath = '/tmp/teamID.txt';
   final MemoryFileSystem fileSystem = MemoryFileSystem.test();
   final List<LogRecord> records = <LogRecord>[];
 
@@ -26,6 +29,113 @@
 
   Directory rootDirectory = fileSystem.systemTempDirectory.createTempSync('conductor_codesign');
 
+  group('test reading in passwords: ', () {
+    setUp(() {
+      processManager = FakeProcessManager.list(<FakeCommand>[]);
+      googleCloudStorage = GoogleCloudStorage(
+        processManager: processManager,
+        rootDirectory: rootDirectory,
+      );
+      codesignVisitor = cs.FileCodesignVisitor(
+        codesignCertName: randomString,
+        googleCloudStorage: googleCloudStorage,
+        fileSystem: fileSystem,
+        appSpecificPasswordFilePath: appSpecificPasswordFilePath,
+        codesignAppstoreIDFilePath: codesignAppstoreIDFilePath,
+        codesignTeamIDFilePath: codesignTeamIDFilePath,
+        processManager: processManager,
+        rootDirectory: rootDirectory,
+        gcsDownloadPath: 'gs://flutter/$randomString/$randomString',
+        gcsUploadPath: 'gs://flutter/$randomString/$randomString',
+        notarizationTimerDuration: const Duration(seconds: 0),
+        dryrun: false,
+      );
+      codesignVisitor.directoriesVisited.clear();
+      records.clear();
+      log.onRecord.listen((LogRecord record) => records.add(record));
+    });
+
+    test('incorrectly formatted password file throws exception', () async {
+      fileSystem.file(appSpecificPasswordFilePath)
+        ..createSync(recursive: true)
+        ..writeAsStringSync(
+          'file_a',
+          mode: FileMode.write,
+          encoding: utf8,
+        );
+
+      expect(
+        () async {
+          await codesignVisitor.readPassword(appSpecificPasswordFilePath);
+          fileSystem.file(appSpecificPasswordFilePath).deleteSync();
+        },
+        throwsA(
+          isA<CodesignException>(),
+        ),
+      );
+    });
+
+    test('unknown password name throws an exception', () async {
+      fileSystem.file(codesignTeamIDFilePath)
+        ..createSync(recursive: true, exclusive: true)
+        ..writeAsStringSync(
+          'dart:dart',
+          mode: FileMode.write,
+          encoding: utf8,
+        );
+
+      expect(
+        () async {
+          await codesignVisitor.readPassword(codesignTeamIDFilePath);
+          await fileSystem.file(codesignTeamIDFilePath).delete();
+        },
+        throwsA(
+          isA<CodesignException>(),
+        ),
+      );
+    });
+
+    test('lacking required passwords throws exception', () async {
+      codesignVisitor.availablePasswords = {
+        'CODESIGN_APPSTORE_ID': '',
+        'CODESIGN_TEAM_ID': '',
+        'APP-SPECIFIC-PASSWORD': ''
+      };
+      fileSystem.file(codesignAppstoreIDFilePath)
+        ..createSync(recursive: true)
+        ..writeAsStringSync(
+          'CODESIGN_APPSTORE_ID:123',
+          mode: FileMode.write,
+          encoding: utf8,
+        );
+
+      expect(
+        () async {
+          await codesignVisitor.validateAll();
+          await fileSystem.file(codesignAppstoreIDFilePath).delete();
+        },
+        throwsA(
+          isA<CodesignException>(),
+        ),
+      );
+    });
+
+    test('providing correctly formatted password returns normally', () async {
+      fileSystem.file(appSpecificPasswordFilePath)
+        ..createSync(recursive: true, exclusive: true)
+        ..writeAsStringSync(
+          'APP_SPECIFIC_PASSWORD:123',
+          mode: FileMode.write,
+          encoding: utf8,
+        );
+
+      expect(() async {
+        await codesignVisitor.readPassword(appSpecificPasswordFilePath);
+        await fileSystem.file(appSpecificPasswordFilePath).delete();
+      }, returnsNormally);
+    });
+  });
+
   group('test utils function to join virtual entitlement path: ', () {
     test('omits slash for the first path', () async {
       expect(joinEntitlementPaths("", randomString), randomString);
@@ -45,12 +155,11 @@
       );
       codesignVisitor = cs.FileCodesignVisitor(
         codesignCertName: randomString,
-        codesignUserName: randomString,
-        appSpecificPassword: randomString,
-        codesignAppstoreId: randomString,
-        codesignTeamId: randomString,
         googleCloudStorage: googleCloudStorage,
         fileSystem: fileSystem,
+        appSpecificPasswordFilePath: appSpecificPasswordFilePath,
+        codesignAppstoreIDFilePath: codesignAppstoreIDFilePath,
+        codesignTeamIDFilePath: codesignTeamIDFilePath,
         processManager: processManager,
         rootDirectory: rootDirectory,
         gcsDownloadPath: 'gs://flutter/$randomString/$randomString',
@@ -58,6 +167,9 @@
         notarizationTimerDuration: const Duration(seconds: 0),
         dryrun: false,
       );
+      codesignVisitor.appSpecificPassword = randomString;
+      codesignVisitor.codesignAppstoreId = randomString;
+      codesignVisitor.codesignTeamId = randomString;
       codesignVisitor.directoriesVisited.clear();
       records.clear();
       log.onRecord.listen((LogRecord record) => records.add(record));
@@ -311,18 +423,20 @@
       );
       codesignVisitor = cs.FileCodesignVisitor(
         codesignCertName: randomString,
-        codesignUserName: randomString,
-        appSpecificPassword: randomString,
-        codesignAppstoreId: randomString,
-        codesignTeamId: randomString,
         googleCloudStorage: googleCloudStorage,
         fileSystem: fileSystem,
+        appSpecificPasswordFilePath: appSpecificPasswordFilePath,
+        codesignAppstoreIDFilePath: codesignAppstoreIDFilePath,
+        codesignTeamIDFilePath: codesignTeamIDFilePath,
         processManager: processManager,
         rootDirectory: rootDirectory,
         gcsDownloadPath: 'gs://flutter/$randomString/FILEPATH',
         gcsUploadPath: 'gs://flutter/$randomString/FILEPATH',
         notarizationTimerDuration: Duration.zero,
       );
+      codesignVisitor.appSpecificPassword = randomString;
+      codesignVisitor.codesignAppstoreId = randomString;
+      codesignVisitor.codesignTeamId = randomString;
       codesignVisitor.directoriesVisited.clear();
       records.clear();
       log.onRecord.listen((LogRecord record) => records.add(record));
@@ -582,12 +696,11 @@
     test('visitBinary codesigns binary with / without entitlement', () async {
       codesignVisitor = cs.FileCodesignVisitor(
         codesignCertName: randomString,
-        codesignUserName: randomString,
-        appSpecificPassword: randomString,
-        codesignAppstoreId: randomString,
-        codesignTeamId: randomString,
         googleCloudStorage: googleCloudStorage,
         fileSystem: fileSystem,
+        appSpecificPasswordFilePath: appSpecificPasswordFilePath,
+        codesignAppstoreIDFilePath: codesignAppstoreIDFilePath,
+        codesignTeamIDFilePath: codesignTeamIDFilePath,
         processManager: processManager,
         rootDirectory: rootDirectory,
         gcsDownloadPath: 'flutter/$randomString/FILEPATH',
@@ -595,6 +708,9 @@
         dryrun: false,
         notarizationTimerDuration: const Duration(seconds: 0),
       );
+      codesignVisitor.appSpecificPassword = randomString;
+      codesignVisitor.codesignAppstoreId = randomString;
+      codesignVisitor.codesignTeamId = randomString;
       codesignVisitor.fileWithEntitlements = <String>{'root/folder_a/file_a'};
       codesignVisitor.fileWithoutEntitlements = <String>{'root/folder_b/file_b'};
       fileSystem
@@ -672,17 +788,19 @@
       );
       codesignVisitor = cs.FileCodesignVisitor(
         codesignCertName: randomString,
-        codesignUserName: randomString,
-        appSpecificPassword: randomString,
-        codesignAppstoreId: randomString,
-        codesignTeamId: randomString,
         gcsDownloadPath: 'flutter/$randomString/FILEPATH',
         gcsUploadPath: 'flutter/$randomString/FILEPATH',
         googleCloudStorage: googleCloudStorage,
         fileSystem: fileSystem,
+        appSpecificPasswordFilePath: appSpecificPasswordFilePath,
+        codesignAppstoreIDFilePath: codesignAppstoreIDFilePath,
+        codesignTeamIDFilePath: codesignTeamIDFilePath,
         processManager: processManager,
         rootDirectory: rootDirectory,
       );
+      codesignVisitor.appSpecificPassword = randomString;
+      codesignVisitor.codesignAppstoreId = randomString;
+      codesignVisitor.codesignTeamId = randomString;
       codesignVisitor.directoriesVisited.clear();
       records.clear();
       log.onRecord.listen((LogRecord record) => records.add(record));
@@ -775,17 +893,19 @@
       );
       codesignVisitor = cs.FileCodesignVisitor(
         codesignCertName: randomString,
-        codesignUserName: randomString,
-        appSpecificPassword: randomString,
-        codesignAppstoreId: randomString,
-        codesignTeamId: randomString,
         gcsDownloadPath: 'flutter/$randomString/FILEPATH',
         gcsUploadPath: 'flutter/$randomString/FILEPATH',
         googleCloudStorage: googleCloudStorage,
         fileSystem: fileSystem,
+        appSpecificPasswordFilePath: appSpecificPasswordFilePath,
+        codesignAppstoreIDFilePath: codesignAppstoreIDFilePath,
+        codesignTeamIDFilePath: codesignTeamIDFilePath,
         processManager: processManager,
         rootDirectory: rootDirectory,
       );
+      codesignVisitor.appSpecificPassword = randomString;
+      codesignVisitor.codesignAppstoreId = randomString;
+      codesignVisitor.codesignTeamId = randomString;
       codesignVisitor.directoriesVisited.clear();
       records.clear();
       log.onRecord.listen((LogRecord record) => records.add(record));
@@ -1095,21 +1215,32 @@
       );
       codesignVisitor = cs.FileCodesignVisitor(
         codesignCertName: randomString,
-        codesignUserName: randomString,
-        appSpecificPassword: randomString,
-        codesignAppstoreId: randomString,
-        codesignTeamId: randomString,
         gcsDownloadPath: 'gs://ios-usb-dependencies/unsigned/libimobiledevice/$randomString/libimobiledevice.zip',
         gcsUploadPath: 'gs://ios-usb-dependencies/libimobiledevice/$randomString/libimobiledevice.zip',
         googleCloudStorage: googleCloudStorage,
         fileSystem: fileSystem,
+        appSpecificPasswordFilePath: appSpecificPasswordFilePath,
+        codesignAppstoreIDFilePath: codesignAppstoreIDFilePath,
+        codesignTeamIDFilePath: codesignTeamIDFilePath,
         processManager: processManager,
         rootDirectory: rootDirectory,
         notarizationTimerDuration: const Duration(seconds: 0),
       );
+      codesignVisitor.appSpecificPassword = randomString;
+      codesignVisitor.codesignAppstoreId = randomString;
+      codesignVisitor.codesignTeamId = randomString;
       codesignVisitor.directoriesVisited.clear();
       records.clear();
       log.onRecord.listen((LogRecord record) => records.add(record));
+      fileSystem.file(codesignAppstoreIDFilePath)
+        ..createSync(recursive: true)
+        ..writeAsStringSync('CODESIGN_APPSTORE_ID:$randomString');
+      fileSystem.file(codesignTeamIDFilePath)
+        ..createSync(recursive: true)
+        ..writeAsStringSync('CODESIGN_TEAM_ID:$randomString');
+      fileSystem.file(appSpecificPasswordFilePath)
+        ..createSync(recursive: true)
+        ..writeAsStringSync('APP_SPECIFIC_PASSWORD:$randomString');
     });
 
     test('codesign optional switches artifacts when dryrun is false', () async {
@@ -1261,19 +1392,21 @@
       ]);
       codesignVisitor = cs.FileCodesignVisitor(
         codesignCertName: randomString,
-        codesignUserName: randomString,
-        appSpecificPassword: randomString,
-        codesignAppstoreId: randomString,
-        codesignTeamId: randomString,
         gcsDownloadPath: 'gs://ios-usb-dependencies/unsigned/libimobiledevice/$randomString/libimobiledevice.zip',
         gcsUploadPath: 'gs://ios-usb-dependencies/libimobiledevice/$randomString/libimobiledevice.zip',
         googleCloudStorage: googleCloudStorage,
         fileSystem: fileSystem,
+        appSpecificPasswordFilePath: appSpecificPasswordFilePath,
+        codesignAppstoreIDFilePath: codesignAppstoreIDFilePath,
+        codesignTeamIDFilePath: codesignTeamIDFilePath,
         processManager: processManager,
         rootDirectory: rootDirectory,
         notarizationTimerDuration: const Duration(seconds: 0),
         dryrun: false,
       );
+      codesignVisitor.appSpecificPassword = randomString;
+      codesignVisitor.codesignAppstoreId = randomString;
+      codesignVisitor.codesignTeamId = randomString;
       codesignVisitor.directoriesVisited.clear();
       await codesignVisitor.validateAll();
       final Set<String> messages = records