slackhq/nebula · error

unable to login. error: %w

Error message

unable to login. error: %w

What it means

New() failed to login to the token with the provided PIN; the session is closed and the pkcs11 error is wrapped with %w. The library deliberately rethrows except for pkcs11 error 256 (CKR_USER_ALREADY_LOGGED_IN), which is tolerated. This means the token rejected authentication.

Source

Thrown at pkclient/pkclient_cgo.go:69

	client := &PKClient{
		module: module,
		id:     []byte(id),
		label:  []byte(label),
	}

	client.session, err = slots[slotIdx].OpenWriteSession()
	if err != nil {
		module.Destroy()
		return nil, fmt.Errorf("failed to open session on slot %d", slotId)
	}

	if len(pin) != 0 {
		err = client.session.Login(pin)
		if err != nil {
			// ignore "already logged in"
			if !errors.Is(err, pkcs11.Error(256)) {
				_ = client.session.Close()
				return nil, fmt.Errorf("unable to login. error: %w", err)
			}
		}
	}

	// Make sure the hsm has a private key for deriving
	client.privKeyObj, err = client.findDeriveKey(client.id, client.label, true)
	if err != nil {
		_ = client.Close() //log out, close session, destroy module
		return nil, fmt.Errorf("failed to find private key for deriving: %w", err)
	}

	return client, nil
}

// Close cleans up properly and logs out
func (c *PKClient) Close() error {
	var err error = nil
	if c.session != nil {

View on GitHub (pinned to dd8f660c0a)

Solutions

  1. Verify the PIN is correct for the selected token/slot
  2. If the PIN is locked, unlock/reset it with the vendor tool (or wait per policy)
  3. If no auth is needed, pass an empty pin so Login is skipped
  4. Confirm slotId points at the token the PIN belongs to
  5. Test the PIN independently with pkcs11-tool --login --pin

Example fix

// before
pkcs11_pin: "1234"
// after
pkcs11_pin: "correct-current-pin"
Defensive patterns

Strategy: try-catch

Validate before calling

// verify the PIN by logging in on a scratch session before New()
ctx := pkcs11.New(modulePath); ctx.Initialize()
session, _ := ctx.OpenSession(slot, pkcs11.CKF_SERIAL_SESSION|pkcs11.CKF_RW_SESSION)
if err := ctx.Login(session, pkcs11.CKU_USER, pin); err != nil {
    return fmt.Errorf("PIN rejected: %w", err)
}

Try / catch

client, err := pkclient.New(hsmPath, slot, pin, id, label)
if err != nil {
    var p11err pkcs11.Error
    if errors.As(err, &p11err) && uint(p11err) == 0xA0+0x20 { // CKR_PIN_LOCKED (163/0x000000A0 family)
        return errors.New("PIN locked; unlock with vendor tool before retrying")
    }
    if strings.Contains(err.Error(), "unable to login") {
        return fmt.Errorf("check PIN for slot %d: %w", slot, err)
    }
    return err
}

Prevention

When it happens

Trigger: Calling New() with a non-empty pin when session.Login(pin) returns an error other than pkcs11.Error(256): wrong PIN, CKR_PIN_LOCKED after repeated failures, CKR_USER_PIN_NOT_INITIALIZED, or login to a slot with no user credentials.

Common situations: Expired or mistyped PIN in config; account locked from previous attempts; PIN configured for a different token; HSM requiring SO login first.

Related errors


AI-assisted analysis of slackhq/nebula@dd8f660c0a (2026-09-03). Data as JSON: /api/errors/d39904663a9fe19a. Report an issue: GitHub.