From db5b4c4dc0cffb21b3a98fd857d7241450dc58ea Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ma=C5=82gorzata=20Olsz=C3=B3wka?=
 <Malgorzata.Olszowka@stunnel.org>
Date: Sun, 8 Sep 2024 19:23:38 +0200
Subject: [PATCH] Add the "-engineCtrl" option to control hardware and CNG
 engines (#405)

Documentation updated for CNG engine 1.1 compatibility.
---
 NEWS.md        |  2 ++
 README.md      | 23 +++++++++++++++++++++++
 osslsigncode.c | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++
 osslsigncode.h |  9 +++++++++
 4 files changed, 84 insertions(+)

diff --git a/NEWS.md b/NEWS.md
index efbe0b4..7c608af 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -2,6 +2,8 @@
 
 ### 2.10 (unreleased)
 
+- added compatiblity with the CNG engine version 1.1 or later
+- added the "-engineCtrl" option to control hardware and CNG engines
 - improved unauthenticated blob support (thanks to Asger Hautop Drewsen)
 - added the '-blobFile' option to specify a file containing the blob content
 
diff --git a/README.md b/README.md
index a076cfa..4b024eb 100644
--- a/README.md
+++ b/README.md
@@ -142,6 +142,29 @@ An example of using osslsigncode with SoftHSM:
     -in yourapp.exe -out yourapp-signed.exe
 ```
 
+You can use a certificate and key stored in the Windows Certificate Store with
+the CNG engine version 1.1 or later. For more information, refer to
+
+  https://www.stunnel.org/cng-engine.html
+
+A non-commercial edition of CNG engine is available for testing, personal,
+educational, or research purposes.
+
+To use the CNG engine with osslsigncode, ensure that the `cng.dll` library is
+placed in the same directory as the `osslsigncode.exe` executable.
+
+Below is an example of how to use osslsigncode with the CNG engine:
+```
+  osslsigncode sign \
+    -pkcs11engine cng \
+    -pkcs11cert osslsigncode_cert \
+    -key osslsigncode_cert \
+    -engineCtrl store_flags:0 \
+    -engineCtrl store_name:MY \
+    -engineCtrl PIN:yourpass \
+    -in yourapp.exe -out yourapp-signed.exe
+```
+
 You can check that the signed file is correct by right-clicking
 on it in Windows and choose Properties --> Digital Signatures,
 and then choose the signature from the list, and click on
diff --git a/osslsigncode.c b/osslsigncode.c
index e6ec51f..aa945f8 100644
--- a/osslsigncode.c
+++ b/osslsigncode.c
@@ -206,6 +206,14 @@ ASN1_SEQUENCE(MsCtlContent) = {
 
 IMPLEMENT_ASN1_FUNCTIONS(MsCtlContent)
 
+
+ASN1_SEQUENCE(EngineControl) = {
+    ASN1_SIMPLE(EngineControl, cmd, ASN1_OCTET_STRING),
+    ASN1_SIMPLE(EngineControl, param, ASN1_OCTET_STRING)
+} ASN1_SEQUENCE_END(EngineControl)
+
+IMPLEMENT_ASN1_FUNCTIONS(EngineControl)
+
 /* Prototypes */
 static ASN1_INTEGER *create_nonce(int bits);
 static char *clrdp_url_get_x509(X509 *cert);
@@ -224,6 +232,7 @@ static int PKCS7_compare(const PKCS7 *const *a, const PKCS7 *const *b);
 static PKCS7 *pkcs7_get_sigfile(FILE_FORMAT_CTX *ctx);
 static void print_cert(X509 *cert, int i);
 static int x509_store_load_crlfile(X509_STORE *store, char *cafile, char *crlfile);
+static void engine_control_set(GLOBAL_OPTIONS *options, const char *arg);
 
 
 /*
@@ -3433,6 +3442,7 @@ static void free_options(GLOBAL_OPTIONS *options)
     options->xcerts = NULL;
     sk_X509_CRL_pop_free(options->crls, X509_CRL_free);
     options->crls = NULL;
+    sk_EngineControl_pop_free(options->engine_ctrls, EngineControl_free);
 }
 
 /*
@@ -3459,6 +3469,7 @@ static void usage(const char *argv0, const char *cmd)
         printf("%1s[ sign ] ( -pkcs12 <pkcs12file>\n", "");
         printf("%13s | ( -certs <certfile> | -spc <certfile> ) -key <keyfile>\n", "");
         printf("%13s | [ -pkcs11engine <engine> ] [ -login ] -pkcs11module <module>\n", "");
+        printf("%13s | [ -engineCtrl <command[:parameter]> ]\n", "");
         printf("%15s ( -pkcs11cert <pkcs11 cert id> | -certs <certfile> ) -key <pkcs11 key id> )\n", "");
 #if OPENSSL_VERSION_NUMBER>=0x30000000L
         printf("%12s[ -nolegacy ]\n", "");
@@ -3596,6 +3607,7 @@ static void help_for(const char *argv0, const char *cmd)
     const char *cmds_pkcs11cert[] = {"sign", NULL};
     const char *cmds_pkcs11engine[] = {"sign", NULL};
     const char *cmds_pkcs11module[] = {"sign", NULL};
+    const char *cmds_engineCtrl[] = {"sign", NULL};
     const char *cmds_login[] = {"sign", NULL};
     const char *cmds_pkcs12[] = {"sign", NULL};
     const char *cmds_readpass[] = {"sign", NULL};
@@ -3734,6 +3746,8 @@ static void help_for(const char *argv0, const char *cmd)
         printf("%-24s= PKCS#11 engine\n", "-pkcs11engine");
     if (on_list(cmd, cmds_pkcs11module))
         printf("%-24s= PKCS#11 module\n", "-pkcs11module");
+    if (on_list(cmd, cmds_engineCtrl))
+        printf("%-24s= control hardware engine\n", "-engineCtrl");
     if (on_list(cmd, cmds_login))
         printf("%-24s= force login to the token\n", "-login");
     if (on_list(cmd, cmds_pkcs12))
@@ -4191,6 +4205,8 @@ static ENGINE *engine_pkcs11(void)
  */
 static int read_token(GLOBAL_OPTIONS *options, ENGINE *engine)
 {
+    int i;
+
     if (options->p11module && !ENGINE_ctrl_cmd_string(engine, "MODULE_PATH", options->p11module, 0)) {
         fprintf(stderr, "Failed to set pkcs11 engine MODULE_PATH to '%s'\n", options->p11module);
         ENGINE_free(engine);
@@ -4211,6 +4227,20 @@ static int read_token(GLOBAL_OPTIONS *options, ENGINE *engine)
         ENGINE_free(engine);
         return 0; /* FAILED */
     }
+    for (i = 0; i < sk_EngineControl_num(options->engine_ctrls); i++) {
+        EngineControl *engine_ctrl = sk_EngineControl_value(options->engine_ctrls, i);
+        const u_char *cmd = ASN1_STRING_get0_data(engine_ctrl->cmd);
+        const u_char *param = ASN1_STRING_get0_data(engine_ctrl->param);
+
+        if (param)
+            printf("Executing engine control command %s:%s\n", cmd, param);
+        else
+            printf("Executing engine control command %s\n", cmd);
+
+        if(!ENGINE_ctrl_cmd_string(engine, (const char *)cmd, (const char *)param, 0)) {
+            fprintf(stderr, "Failed to execute the engine control command\n");
+        }
+    }
     /*
      * ENGINE_init() returned a functional reference, so free the structural
      * reference from ENGINE_by_id().
@@ -4479,6 +4509,7 @@ static int main_configure(int argc, char **argv, GLOBAL_OPTIONS *options)
 /* Use legacy PKCS#12 container with RC2-40-CBC private key and certificate encryption algorithm */
     options->legacy = 1;
 #endif /* OPENSSL_VERSION_NUMBER>=0x30000000L */
+    options->engine_ctrls = sk_EngineControl_new_null();
 
     if (cmd == CMD_HELP) {
         return 0; /* FAILED */
@@ -4553,6 +4584,12 @@ static int main_configure(int argc, char **argv, GLOBAL_OPTIONS *options)
                 return 0; /* FAILED */
             }
             options->p11module = *(++argv);
+        } else if (!strcmp(*argv, "-engineCtrl")) {
+            if (--argc < 1) {
+                usage(argv0, "all");
+                return 0; /* FAILED */
+            }
+            engine_control_set(options, *(++argv));
         } else if ((cmd == CMD_SIGN) && !strcmp(*argv, "-login")) {
             options->login = 1;
 #endif /* OPENSSL_NO_ENGINE */
@@ -4860,6 +4897,19 @@ static int main_configure(int argc, char **argv, GLOBAL_OPTIONS *options)
     return 1; /* OK */
 }
 
+static void engine_control_set(GLOBAL_OPTIONS *options, const char *arg)
+{
+    EngineControl *engine_ctrl = EngineControl_new();
+    char *tmp_str=strchr(arg, ':');
+
+    if(tmp_str) {
+        *tmp_str++='\0';
+        ASN1_STRING_set(engine_ctrl->param, tmp_str, (int)strlen(tmp_str));
+    }
+    ASN1_STRING_set(engine_ctrl->cmd, arg, (int)strlen(arg));
+    sk_EngineControl_push(options->engine_ctrls, engine_ctrl);
+}
+
 int main(int argc, char **argv)
 {
     FILE_FORMAT_CTX *ctx = NULL;
diff --git a/osslsigncode.h b/osslsigncode.h
index 8d97d00..ce56e73 100644
--- a/osslsigncode.h
+++ b/osslsigncode.h
@@ -244,6 +244,14 @@ typedef enum {
 
 typedef unsigned char u_char;
 
+typedef struct {
+    ASN1_OCTET_STRING *cmd;
+    ASN1_OCTET_STRING *param;
+} EngineControl;
+
+DECLARE_ASN1_FUNCTIONS(EngineControl)
+DEFINE_STACK_OF(EngineControl)
+
 typedef struct {
     char *infile;
     char *outfile;
@@ -307,6 +315,7 @@ typedef struct {
     char *tsa_keyfile;
     time_t tsa_time;
     int nested_number;
+    STACK_OF(EngineControl) *engine_ctrls;
 } GLOBAL_OPTIONS;
 
 /*