slackhq/nebula · critical

ErrAsymmetricCipherKeys

ErrAsymmetricCipherKeys

Error message

noise produced only one cipher key

What it means

ErrAsymmetricCipherKeys is returned by ProcessPacket when a Noise handshake message that should finalize key derivation yields only one of the two transport cipher keys (encryption or decryption key is nil). A correct handshake always produces a key pair for both directions, so this indicates a pattern/engine inconsistency. The machine is marked failed.

Source

Thrown at handshake/errors.go:19

package handshake

import "errors"

var (
	ErrInitiateOnResponder     = errors.New("initiate called on responder")
	ErrInitiateAlreadyCalled   = errors.New("initiate already called")
	ErrInitiateNotCalled       = errors.New("initiate must be called before ProcessPacket for initiators")
	ErrPacketTooShort          = errors.New("packet too short")
	ErrPublicKeyMismatch       = errors.New("public key mismatch between certificate and handshake")
	ErrIncompleteHandshake     = errors.New("handshake completed without receiving required content")
	ErrMachineFailed           = errors.New("handshake machine has failed")
	ErrUnknownSubtype          = errors.New("unknown handshake subtype")
	ErrMissingContent          = errors.New("expected handshake content but message was empty")
	ErrUnexpectedContent       = errors.New("received unexpected handshake content")
	ErrInvalidRemoteIndex      = errors.New("peer sent an invalid index in handshake payload")
	ErrIndexAllocation         = errors.New("failed to allocate local index")
	ErrNoCredential            = errors.New("no handshake credential available for cert version")
	ErrAsymmetricCipherKeys    = errors.New("noise produced only one cipher key")
	ErrMultiMessageUnsupported = errors.New("multi-message handshake patterns are not yet supported by the manager")
	ErrSubtypeMismatch         = errors.New("packet subtype does not match handshake machine subtype")
)

View on GitHub (pinned to dd8f660c0a)

Solutions

  1. Verify both peers use a handshake pattern whose message count matches the implementation's expectations
  2. Upgrade the library in case of a Noise key-derivation bug in your version
  3. Restart the handshake with a fresh Machine; never reuse partially completed machines

Example fix

// before: pattern declared with fewer messages than needed
pattern := handshake.PatternIK1 // only 1 message; final stage has no 2nd key

// after
pattern := handshake.PatternIK // full pattern produces both cipher keys
Defensive patterns

Strategy: try-catch

Validate before calling

if patternMsgCount(pattern) != expectedStages {
    return fmt.Errorf("pattern %v produces asymmetric keys: wrong stage count", pattern)
}

Type guard

func keysSymmetric(res handshake.CipherResults) bool {
    return res.EKey != nil && res.DKey != nil
}

Try / catch

res, _, err := m.ProcessPacket(out, pkt)
if errors.Is(err, handshake.ErrAsymmetricCipherKeys) {
    // key derivation is broken: abort and restart from a fresh machine
    conn.Close()
    log.Printf("noise produced asymmetric cipher keys with %s", conn.RemoteAddr())
    return
}

Prevention

When it happens

Trigger: In ProcessPacket, after the final handshake stage, eKey == nil || dKey == nil (handshake/machine.go:249) or ek == nil || dk == nil (machine.go:267) — NoiseCipherResults returned fewer than two cipher keys.

Common situations: Bug or mismatch in the handshake pattern implementation (wrong message count for the chosen pattern); peers using different pattern definitions so one side finalizes too early; corrupted handshake state from reusing a machine.

Related errors


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