Resolving CKR_USER_NOT_LOGGED_IN When Signing Ignition Modules

Jason IP5 min read
Other ManufacturerOther TopicTroubleshooting
Licensed PE Working through this on a live machine? A Maine-licensed engineer can take it from here — included with IMD hardware, by the hour for everything else. Book an engineer

Problem Overview: CKR_USER_NOT_LOGGED_IN on Multi-File Module Signing

The module-signer utility (and the inductiveautomation/module-signer code repository) and the newer ignition-module-tools gradle-module-plugin rely on PKCS#11 to drive USB hardware security modules (HSMs) such as the Sectigo USB eToken and the Yubico YubiKey 5 NFC. When signing a .zip archive that contains more than one module artifact (for example, a *.jar plus a module.xml), the first entry signs successfully. The second entry fails with the PKCS#11 error code CKR_USER_NOT_LOGGED_IN (hex 0x00000102), thrown from the SunPKCS#11 provider inside the signer.

The behavior is independent of file order, file count beyond one, or the specific files chosen. Both Sectigo eTokens and YubiKey 5 tokens in their default configuration exhibit the same symptom, which points to a session/PIN caching limitation of the underlying PIV-style firmware rather than a bug in the signer code itself.

Root Cause: PIV Slot PIN Challenge Policy

PKCS#11 tokens based on the PIV specification expose multiple certificate slots, each with its own PIN challenge policy. The most commonly used slots and their behavior on YubiKey 5 hardware (firmware 5.4+ and later) are documented at developers.yubico.com/PIV/Introduction/Certificate_slots.html:

Slot ID (hex) Name PIN Re-prompt Behavior
0x9a Authentication Caches the PIN across multiple signing operations within a session
0x9b Management No signing operation
0x9c Digital Signature Requires a fresh PIN for every signing operation
0x9d Key Management Caches the PIN across multiple operations
0x9e Card Authentication Behavior depends on device configuration
0x82–0x95 Retired keys Device-specific; not standardized

The module-signer opens a single PKCS#11 session, logs in once, and then iterates over every entry in the .zip archive. When the signing certificate is installed in slot 0x9c (the default on YubiKey 5 NFC and on many Sectigo eToken profiles), the firmware invalidates the PIN cache after each C_Sign call. The next signing operation therefore triggers CKR_USER_NOT_LOGGED_IN because the Java SunPKCS#11 provider has not re-applied the PIN. Java's PKCS#11 wrapper does not implement the CKF_PROTECTED_AUTHENTICATION_PATH PIN callback mechanism by default, so the second sign fails.

Critical: Verify which certificate slot holds your code-signing certificate. On a Sectigo eToken, use the Safenet Authentication Client (SAC) to inspect the slot/CKA_LABEL of the signing cert. On YubiKey, use ykman piv info 0x9a, 0x9c, and 0x9d from the YubiKey Manager CLI.

Solution 1: Install the Signing Certificate in Slot 0x9a (Recommended)

Because slot 0x9a (Authentication) keeps the PIN cached across multiple operations in a single session, installing the module-signing certificate in this slot allows module-signer to iterate through the archive without re-authentication failures. Steps:

  1. Export the existing code-signing certificate and private key from the eToken or YubiKey, or re-issue the certificate from your CA. Sectigo Code Signing certificates issued on a Safenet token can be re-installed per the procedure shown in the Sectigo Code Signing USB Token setup video.
  2. On a YubiKey 5 NFC, move the certificate to slot 0x9a with:
    ykman piv import-certificate 9a cert.pem
  3. On a Sectigo eToken, use the SafeNet Authentication Client to import the certificate into the Authentication slot, not the Signature slot.
  4. Update the signer configuration so that pkcs11.config points to the correct slot, e.g. slot=0x9a for the SunPKCS#11 provider, or pass --slot 0x9a to module-signer if the flag is exposed in your build.
  5. Re-run the build. Each file in the .zip should now sign without raising CKR_USER_NOT_LOGGED_IN.

Solution 2: Upgrade to the New Gradle Module Plugin (ignition-module-tools)

Inductive Automation merged a PR (#43) that adds native PKCS#11 support to the Gradle module plugin. This release is the official path forward and supersedes the original module-signer for any green-field project. Configuration steps:

  1. Use Gradle 7.6 or later (required for the plugin to run reliably).
  2. Add the Inductive public Nexus repository to your settings.gradle.kts:
    pluginManagement {
      repositories {
        maven {
          name = "publicNexus"
          url = uri("https://nexus.inductiveautomation.com/repository/public/")
        }
        gradlePluginPortal()
      }
    }
  3. Apply the plugin in build.gradle.kts:
    plugins {
      id("com.inductiveautomation.ignition-module") version "<latest>"
    }
  4. Configure the HSM via the plugin's signing extension, selecting slot 0x9a or 0x9d so the PIN remains cached across files.

Solution 3: Implement a Custom PKCS#11 PIN Callback

For deployments that must use slot 0x9c (for example, where the corporate CA policy mandates the Digital Signature slot), add a CK_C_INITIALIZE_ARGS-aware callback that re-applies the PIN before each C_Sign call. Reference implementation notes appear in the ignition-module-tools fork and the upstream discussion at keystore-explorer#42. This path is the most complex and should only be used when slot reassignment is impossible.

Diagnostic Procedure

  1. Enable PKCS#11 tracing by setting -Djava.security.debug=sunpkcs11 on the JVM that runs the signer.
  2. Capture the hex return codes. CKR_USER_NOT_LOGGED_IN = 0x00000102, CKR_PIN_INVALID = 0x000000a0, CKR_SESSION_HANDLE_INVALID = 0x000000b3.
  3. Run pkcs11-tool --module /usr/lib/opensc-pkcs11.so --list-slots --list-objects (from OpenSC) to confirm which slot the signing cert lives in.
  4. Sign a single-file .zip to validate the credential, then sign the multi-file archive to reproduce the fault before applying the slot change.

Verification

  • Successful signing produces a .modl file whose module.xml shows a valid <signature> block. Verify with module-signer --verify MyModule.modl.
  • Re-run the full multi-file build three consecutive times. No CKR_USER_NOT_LOGGED_IN should appear in stderr.
  • Confirm the loaded module displays as signed inside the Ignition Gateway status page.

Cross-Reference Sources

What does the error CKR_USER_NOT_LOGGED_IN mean in PKCS#11?

It is hex 0x00000102. It is returned by the token when a cryptographic operation is attempted on a session whose PIN has been invalidated or never supplied. On PIV devices it commonly appears after the first sign on slot 0x9c because that slot clears the PIN cache after every operation.

Why does signing a single file work but two files fail?

module-signer opens one PKCS#11 session and logs in once. Slot 0x9c re-prompts for the PIN on every C_Sign call, so the second file finds the session logged out. Slots 0x9a and 0x9d cache the PIN for the entire session and therefore sign all files in a single login.

Can I keep using slot 0x9c without changing slots?

Yes, but only by adding a custom PIN callback handler that re-applies the PIN before each C_Sign. The new ignition-module-tools Gradle plugin includes this capability; the legacy module-signer does not.

Which HSMs are known to be affected?

Yubico YubiKey 5 NFC in default slot 0x9c, Sectigo USB eTokens distributed through the Safenet program, and Nitrokey devices when configured with strict PIN policies. Devices configured to use slot 0x9a or 0x9d are not affected.

What is the minimum Gradle version for the new module plugin?

Gradle 7.6 or later. Earlier versions intermittently fail to resolve the plugin from nexus.inductiveautomation.com or crash when invoking the PKCS#11 provider.

Back to blog