slackhq/nebula · error

pkcs11 module gave us a nil CKA_PUBLIC_KEY_INFO, and looking

Error message

pkcs11 module gave us a nil CKA_PUBLIC_KEY_INFO, and looking up the public key also failed: %w

What it means

GetPubKey prefers the CKA_PUBLIC_KEY_INFO attribute of the client's key object to reconstruct the public key. When that attribute is nil, it falls back to locating the public key object via findDeriveKey and wrapping that failure with this error, indicating both paths to obtain the public key failed.

Source

Thrown at pkclient/pkclient_cgo.go:213

	if tmpKey == nil || len(tmpKey) == 0 {
		return nil, fmt.Errorf("got an empty secret key")
	}
	secret := make([]byte, NoiseKeySize)
	copy(secret[:], tmpKey[:NoiseKeySize])
	return secret, nil
}

func (c *PKClient) GetPubKey() ([]byte, error) {
	d, err := c.privKeyObj.Attribute(pkcs11.CKA_PUBLIC_KEY_INFO)
	if err != nil {
		return nil, err
	}
	if d != nil && len(d) > 0 {
		return formatPubkeyFromPublicKeyInfoAttr(d)
	}
	c.pubKeyObj, err = c.findDeriveKey(c.id, c.label, false)
	if err != nil {
		return nil, fmt.Errorf("pkcs11 module gave us a nil CKA_PUBLIC_KEY_INFO, and looking up the public key also failed: %w", err)
	}
	d, err = c.pubKeyObj.Attribute(pkcs11.CKA_EC_POINT)
	if err != nil {
		return nil, fmt.Errorf("pkcs11 module gave us a nil CKA_PUBLIC_KEY_INFO, and reading CKA_EC_POINT also failed: %w", err)
	}
	if d == nil || len(d) < 1 {
		return nil, fmt.Errorf("pkcs11 module gave us a nil or empty CKA_EC_POINT")
	}
	switch len(d) {
	case 65: //length of 0x04 + len(X) + len(Y)
		return d, nil
	case 67: //as above, DER-encoded IIRC?
		return d[2:], nil
	default:
		return nil, fmt.Errorf("unknown public key length: %d", len(d))
	}
}

View on GitHub (pinned to dd8f660c0a)

Solutions

  1. Store/enable CKA_PUBLIC_KEY_INFO on the key object (or re-import with it) so the primary path works.
  2. Ensure a matching public key object with the same CKA_ID/CKA_LABEL exists on the token.
  3. Check findDeriveKey's search template attributes (CKA_CLASS=CKO_PUBLIC_KEY, CKA_DERIVE) match how the key was provisioned.
  4. Regenerate the keypair on the HSM with full public object attributes.

Example fix

// before: provision private key only
pkcs11-tool --keypairgen --id 01 --label nebula --key-type EC:prime256v1 --usage-derive
// after: also verify public object exists
pkcs11-tool --list-objects --type pubkey --id 01
Defensive patterns

Strategy: validation

Validate before calling

// Before GetPubKey: confirm both key objects exist on the token
out, _ := exec.Command("pkcs11-tool", "--module", hsmPath, "--slot", slot,
  "--login", "--pin", pin, "--list-objects").Output()
needPriv := strings.Contains(string(out), "Private Key")
needPub := strings.Contains(string(out), "Public Key")
if !needPriv || !needPub { return errors.New("token missing private or public key object") }

Try / catch

pub, err := client.GetPubKey()
if err != nil {
  if strings.Contains(err.Error(), "nil CKA_PUBLIC_KEY_INFO") {
    return fmt.Errorf("HSM not fully provisioned (missing CKA_PUBLIC_KEY_INFO/public object): %w", err)
  }
  return err
}

Prevention

When it happens

Trigger: Calling GetPubKey (used by ca and signCert) on a token whose private key has no CKA_PUBLIC_KEY_INFO attribute AND no matching public key object can be found by id/label.

Common situations: Minimal HSM provisioning that only stored the private key without the public key object; vendor module that doesn't expose CKA_PUBLIC_KEY_INFO; id/label mismatch preventing the public key lookup; pkcs11-backed certificates where the public half was never stored.

Related errors


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