Repository: YehudaKremer/msix

Issue:
[BUG] Error information: "Error: SignerSign() failed." (-2146869243/0x80096005)
### :information_source: Info

<!--- Please enter what version of MSIX you were using so we can identify if this is a problem with a certain version of the package --->

**Version: `v3.16.8`**

### :speech_balloon: Description

Running `dart run msix:create` got following error message, which seems to be caused by [`sign_tool.dart#L234`](https://github.com/YehudaKremer/msix/blob/4a0a525e2ca6263d23b5655f07bdc045c2cc00d4/lib/src/sign_tool.dart#L234) not specifying **timestamp digest algorithm** (`/td SHA256`) with ['/tr', 'http://timestamp.digicert.com'].

```shell
# not work
dart run msix:create
# work!
dart run msix:create --signtool-options "/td SHA256"
``` 

Quotes below refs from ['Troubleshooting Timestamping Problems'](https://knowledge.digicert.com/solution/troubleshooting-timestamping-problems).

> Ensure that you use the correct timestamp switch in your signing command:
>
> - SignTool: /tr http://timestamp.digicert.com /td SHA256

**Failed Logs:**

```log
$ dart run msix:create -v 
...
[    16.943s] packing msix files...
[    16.943s] packing
[    18.761s] cleaning temporary files
[    19.101s] signing
[    20.737s] The following certificate was selected:
    Issued to: Msix Testing
    Issued by: Msix Testing
    Expires:   Sat Aug 03 22:57:09 2295
    SHA1 hash: 028BC9922D198EE83D776AA19CB8E82897691E0C

Done Adding Additional Store
Error information: "Error: SignerSign() failed." (-2146869243/0x80096005)

Unhandled exception:
SignTool Error: An unexpected internal error has occurred.

#0      ProcessResultExtensions.exitOnError (package:msix/src/method_extensions.dart:61:7)
#1      SignTool.sign (package:msix/src/sign_tool.dart:247:9)
<asynchronous suspension>
#2      Msix._packMsixFiles (package:msix/msix.dart:149:7)
<asynchronous suspension>
#3      Msix._createMsix (package:msix/msix.dart:103:5)
<asynchronous suspension>
#4      Msix.create (package:msix/msix.dart:59:5)
<asynchronous suspension>
#5      main (file:///D:/tmp/pub/hosted/pub.dev/msix-3.16.8/bin/create.dart:4:3)
<asynchronous suspension>
```

### :scroll: Pubspec.yaml

```yaml
# ...
msix_config:
  display_name:  Demo
  identity_name: Example.Demo
  enable_at_startup: false
  logo_path: ./assets/logo.png
  capabilities: internetClient
```



Discussion: Quick solution: Use this command to create
`dart run msix:create --signtool-options "/td SHA256"`
> Quick solution: Use this command to create `dart run msix:create --signtool-options "/td SHA256"`

That alone does not work for me. Looking at the source code, it looks like we should additionally provide the `/fd` and `/tr` options, although `/fd` alone (no `/td` or `/tr`) was enough to work for me. For completeness, see the snippet that worked for me below.

Note: If you were previously specifying your cert path and password with `-c` and `-p`, you will need to move those arguments to `--signtool-options`. Your full usage might look something like:

`dart run msix:create --signtool-options="/fd SHA256 /tr http://timestamp.digicert.com /td SHA256 /f $CERTIFICATE_PATH /p $CERTIFICATE_PASSWORD"`
Neither answer worked for me.
> dart run msix:create --signtool-options "/td SHA256"

This is the fix. Or better still add to the `mix_config` section of your pubspec.yaml file:
```
signtool_options: /td SHA256
```

Fix patch:
diff --git a/lib/src/sign_tool.dart b/lib/src/sign_tool.dart
index 8a1ad95..35353c1 100644
--- a/lib/src/sign_tool.dart
+++ b/lib/src/sign_tool.dart
@@ -204,47 +204,52 @@ class SignTool {
   Future<void> sign() async {
     _logger.trace('signing');
 
-    String signtoolPath = p.join(_config.msixToolkitPath, 'signtool.exe');
-    List<String> signtoolOptions = _config.signToolOptions ?? ['/v'];
+    String signToolPath = p.join(_config.msixToolkitPath, 'signtool.exe');
+    final signToolOptions = getSignToolOptions();
+    bool isFullSignToolCommand =
+        signToolOptions[0].toLowerCase().contains('signtool');
+
+    // ignore: avoid_single_cascade_in_expression_statements
+    await Process.run(
+        isFullSignToolCommand ? signToolOptions[0] : signToolPath, [
+      if (!isFullSignToolCommand) 'sign',
+      ...signToolOptions.skip(isFullSignToolCommand ? 1 : 0),
+      _config.msixPath,
+    ])
+      ..exitOnError();
+  }
+
+  /// Returns the options necessary for [sign].
+  ///
+  /// This method accounts for whether the config already has signtool options
+  /// that [isCustomSignCommand] and the config's certificate type.
+  List<String> getSignToolOptions() {
+    List<String> signToolOptions = _config.signToolOptions ?? ['/v'];
 
     if (isCustomSignCommand(_config.signToolOptions)) {
-      signtoolOptions = _config.signToolOptions!;
+      signToolOptions = _config.signToolOptions!;
     } else if (_config.certificatePath != null) {
       switch (extension(_config.certificatePath!).toLowerCase()) {
         case '.pfx':
-          signtoolOptions.addAll([
-            '/fd',
-            'SHA256',
-            '/f',
-            _config.certificatePath!,
-            '/p',
-            _config.certificatePassword!
-          ]);
+          signToolOptions.addAll(['/p', _config.certificatePassword!]);
           break;
         default:
-          signtoolOptions.addAll([
-            '/a',
-            '/fd',
-            'SHA256',
-            '/f',
-            _config.certificatePath!,
-          ]);
+          signToolOptions.addAll(['/a']);
       }
 
-      signtoolOptions.addAll(['/tr', 'http://timestamp.digicert.com']);
+      signToolOptions.addAll([
+        '/fd',
+        'SHA256',
+        '/td',
+        'SHA256',
+        '/tr',
+        'http://timestamp.digicert.com',
+        '/f',
+        _config.certificatePath!,
+      ]);
     }
 
-    bool isFullSigntoolCommand =
-        signtoolOptions[0].toLowerCase().contains('signtool');
-
-    // ignore: avoid_single_cascade_in_expression_statements
-    await Process.run(
-        isFullSigntoolCommand ? signtoolOptions[0] : signtoolPath, [
-      if (!isFullSigntoolCommand) 'sign',
-      ...signtoolOptions.skip(isFullSigntoolCommand ? 1 : 0),
-      _config.msixPath,
-    ])
-      ..exitOnError();
+    return signToolOptions;
   }
 
   static isCustomSignCommand(List<String>? signToolOptions) =>


Test patch:
diff --git a/test/sign_tool_test.dart b/test/sign_tool_test.dart
new file mode 100644
index 0000000..8f5268d
--- /dev/null
+++ b/test/sign_tool_test.dart
@@ -0,0 +1,60 @@
+import 'package:cli_util/cli_logging.dart';
+import 'package:get_it/get_it.dart';
+import 'package:msix/src/configuration.dart';
+import 'package:msix/src/sign_tool.dart';
+import 'package:test/test.dart';
+
+final getIt = GetIt.instance;
+
+void main() {
+  getIt.registerSingleton<Logger>(Logger.standard());
+  getIt.registerSingleton<Configuration>(Configuration([]));
+
+  List<String>? originalSignToolOptions;
+  String? originalCertificatePath;
+  String? originalCertificatePassword;
+  setUp(() {
+    final config = getIt<Configuration>();
+    originalSignToolOptions = config.signToolOptions;
+    originalCertificatePath = config.certificatePath;
+    originalCertificatePassword = config.certificatePassword;
+  });
+
+  tearDown(() {
+    final config = getIt<Configuration>();
+    config.signToolOptions = originalSignToolOptions;
+    config.certificatePath = originalCertificatePath;
+    config.certificatePassword = originalCertificatePassword;
+  });
+
+  group('`SignTool.signToolOptions` with null config sign tool options', () {
+    final config = getIt<Configuration>();
+    config.signToolOptions = null;
+    config.certificatePassword = 'password1234';
+
+    final signTool = SignTool();
+    test('`.pfx` certificate', () {
+      config.certificatePath = 'cert.pfx';
+      final options = signTool.getSignToolOptions().join(' ');
+
+      expect(options.contains('/v'), isTrue);
+      expect(options.contains('/f ${config.certificatePath}'), isTrue);
+      expect(options.contains('/p ${config.certificatePassword}'), isTrue);
+      expect(options.contains('/fd SHA256'), isTrue);
+      expect(options.contains('/tr http://timestamp.digicert.com'), isTrue);
+      expect(options.contains('/td SHA256'), isTrue);
+    });
+
+    test('Non-`.pfx` certificate', () {
+      config.certificatePath = 'cert.pem';
+      final options = signTool.getSignToolOptions().join(' ');
+
+      expect(options.contains('/v'), isTrue);
+      expect(options.contains('/f ${config.certificatePath}'), isTrue);
+      expect(options.contains('/a'), isTrue);
+      expect(options.contains('/fd SHA256'), isTrue);
+      expect(options.contains('/td SHA256'), isTrue);
+      expect(options.contains('/tr http://timestamp.digicert.com'), isTrue);
+    });
+  });
+}