How can we perform code signing using an HSM with module-signer?

Given new requirements for Code Signing (see this write up from Comodo) we are trying to perform our Code Signing using an Hardware Security Module (HSM) setup within Azure Key Vault. The consequence of this is that we (apparently) cannot get a .pfx file (with private key) as we have in the past.

We chose NOT to use the USB Key option as this seems to be unworkable with build pipelines, distributed work force, etc.

However all of this creates challenges using the IA supplied module-signer as it seems to require a .pfx file as input; at least this is how we have done it in the past.

I was able to sign the .modl file directly using a Maven package from Microsoft and instructions for this GitHub repos.

However looking at the code from IA it adds in the .p7b file and a custom file. While I can simply add the .p7b file myself with a custom method, the file is another story. In my testing I was partially successful doing this but I suspect (understandably) that the file being improper (copied an old one) or missing (since I have no way to generate this) is an issue.

With all of that background and given these new requirements for using an HSM for Code Signing does anyone have any suggestions on how we can sign our .modl files for use in Ignition? What have I missed? Is there a way with module-signer? Have others run into this?

You really have two options here:

  1. look at the IA module-signer code to see what constitutes a signed module, then write your own custom code to achieve that interfacing with the HSM/vault.
  2. ignore all of this, stop using a "real" code signing certificate, and just generate your own

Module signing as it exists in Ignition is a pointless half implemented idea. It achieves essentially nothing and does not require the use of a fancy code signing cert from a public CA.

1 Like

Update: there is work in progress to add HSM support to the module-signer util.

1 Like

Good to know! We have self-signed for now.

@pturmel you're using the module-signer in its current form (via -pkcs11-cfg arg) with a Yubikey, right? Or some other hardware?

I use a Yubikey Neo and a NitroKey HSM. It was a bit of a pain to transfer my private key to the Nitrokey, fwiw. Currently using the maven module-signer from a command line via ant. The gradle project I was playing with stalled for lack of priority (my lack of copious free time, natch).

Since the gradle plug-in calls the maven module-signer classes, it just needs additional argument handling. Which, from a brief look, is precisely what Brian has added.

(I use the OpenSC libraries, which seemlessly handles whichever key happens to be plugged in.)

err, do you mean we published the module-signer artifact to Maven Central? or something else?

Yes, I'm using the public module-signer, which has suited my needs since you merged my patch. (But I admit I haven't bothered to update it in years.)

Right, but you just built a JAR locally and execute it as part of your build. I can't see that we actually built and published the artifact anywhere.

I would have thought you did. As your maven plugin calls it, too, I thought.

No, the Maven plugin doesn't sign modules. The Gradle plugin does, but via largely copy/pasted code from module-signer, it seems.

Hmmm. My module-signer folder has this git log --pretty:

commit 1b55e1766299feb4cb100626d43261d7872c8671
Author: Philip J. Turmel <>
Date:   Tue Nov 13 14:26:26 2018 -0500

    Java 11 Support, not backwards compatible

commit 7a3df1e5f313d07c1b6aa105f92908c41d93899a
Author: Kevin Herron <>
Date:   Tue Aug 30 07:20:10 2016 -0700

    Add support for private keys from PKCS11 sources
    This patch was generously provided by Phil Turmel

commit 58d0057557f2536a13914cb3eb37dc5799139cf5
Author: Kevin Herron <>
Date:   Thu May 19 08:48:06 2016 -0700

    Add configuration for jar and assembly plugins

commit b0a0f3cba123052168a807273e27fedd394009fe
Author: Kevin Herron <>
Date:   Fri Mar 11 09:22:50 2016 -0800


{ older commits omitted }

That top commit was a patch from you, IIRC, locally applied.

Hmm. Not so sure. That is 0001-Java-11-Support-not-backwards-compatible.patch

From 1b55e1766299feb4cb100626d43261d7872c8671 Mon Sep 17 00:00:00 2001
Message-Id: <>
From: "Philip J. Turmel" <>
Date: Tue, 13 Nov 2018 14:26:26 -0500
Subject: [PATCH] Java 11 Support, not backwards compatible

 pom.xml                                                      | 5 ++---
 .../com/inductiveautomation/ignitionsdk/    | 5 +++--
 2 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/pom.xml b/pom.xml
index ee564d5..4bfe5c0 100644
--- a/pom.xml
+++ b/pom.xml
@@ -27,10 +27,9 @@
-                <version>3.1</version>
+                <version>3.8.0</version>
-                    <source>1.8</source>
-                    <target>1.8</target>
+                    <release>11</release>
diff --git a/src/main/java/com/inductiveautomation/ignitionsdk/ b/src/main/java/com/inductiveautomation/ignitionsdk/
index a727fe2..b4621e7 100644
--- a/src/main/java/com/inductiveautomation/ignitionsdk/
+++ b/src/main/java/com/inductiveautomation/ignitionsdk/
@@ -18,6 +18,7 @@ import;
 import java.util.Arrays;
 import java.util.Base64;
 import java.util.Properties;
+import java.util.Set;
 import org.apache.commons.cli.CommandLine;
 import org.apache.commons.cli.CommandLineParser;
@@ -26,7 +27,6 @@ import org.apache.commons.cli.Option;
 import org.apache.commons.cli.Options;
 public class ModuleSigner {
@@ -123,7 +123,8 @@ public class ModuleSigner {
             String aliasPwd = commandLine.getOptionValue(OPT_ALIAS_PWD, "");
             if (commandLine.hasOption(OPT_PKCS11_CFG)) {
-                Provider p = new SunPKCS11(commandLine.getOptionValue(OPT_PKCS11_CFG));
+                Provider p = Security.getProvider("SunPKCS11");
+                p = p.configure(commandLine.getOptionValue(OPT_PKCS11_CFG));
                 keyStore = KeyStore.getInstance("PKCS11");
                 keyStore.load(null, keyStorePwd.toCharArray());

When I looked at this last year, the gradle plug-in depended on module-signer-tools and called its signing implementation. It did not have its own implementation. Just had a thin wrapper to collect the necessary arguments.

Ah, you're right:

I'm looking in the wrong place. It's published to our Nexus repository, not Maven Central.

That's all I thought it needed too way back when I first looked more closely at your GH issue and the code ... not so. The Gradle plugin calls a method in module-signer that sidesteps Kevin's PKCS#11 enhancement--cracking open the keystore (file- or HSM-based) and grabbing the private key.

The Gradle plugin's always opened + loaded the keystore and fetched the private key from it, only passing off to module-signer for the actual signing operation.

        val keyStore: KeyStore = getKeyStore()
        keyStore.load(keyStoreFile.inputStream(), keystorePassword.toCharArray())

        val privateKey: RSAPrivateKey = keyStore.getKey(certAlias, certPassword.toCharArray()) as RSAPrivateKey

        ModuleSigner(privateKey, cert.inputStream())
            .signModule(PrintStream(OutputStream.nullOutputStream()), unsignedModule, outFile)

Not the end of the world though. There's a bit of logic duplication that will be refactored back to DRY down the road, knock on wood.

I guess it would be fair to say for the signing operation itself, it is a thin wrapper. Fetching and unlocking the cert from the keystore, it's got its own implementation. Always has unless I am reading the git blame incorrectly.

Yeah, go look at the ModuleSigner class's main() function. The branch taken for PKCS11 is almost trivial (line 124). The actual module signer class is handed a private key "placeholder" produced from the PKCS logic.

You need a similar 4-line-or-so branch to assemble the PKCS args => keystore instead of the regular keystore.

Yep that's in my fork already. That's what I've been testing. Should have a draft PR up soon.

The other day I think I linked to a full diff of my fork against the upstream master branch but maybe not, I also had a few individual commit links in there. Maybe that made the extent of my WIP unclear.

1 Like

Expanding on Kevin's link above a bit, here's my fork vs upstream master.

That commit history will get rebased before I file a PR so the commit history will shrink and make more sense.

1 Like