PALISADE v1.2 Negative Test Vectors

This document defines negative test vectors for PALISADE v1.2, ensuring implementations correctly reject invalid inputs and maintain fail-closed behavior. These vectors complement the positive end-to-end test vector in e2e.md.

Common Test Parameters

All negative vectors use the same base parameters as the positive E2E vector:

ComponentValue
Protocol Version0x12 (PALISADE v1.2)
KEMML-KEM-768 (0x0011)
SignatureML-DSA-65 (0x0021)
AEADChaCha20-Poly1305 (0x0001)
HashSHA3-256
HKDFHKDF-SHA3-256
PublicHeader30 bytes (magic + version + flags + length + RID)
RID24 bytes (8 steering + 2 epoch + 14 privacy)

WARNING

This document contains deterministic cryptographic material for testing only. DO NOT USE THESE VALUES IN PRODUCTION.

Error Code Policy

Error codes referenced in this document (e.g., ErrorPacketTooLarge 0x0305) are semantic identifiers for internal logging, counters, and telemetry. On-wire error responses are only sent where the specification explicitly allows them (e.g., during handshake for ErrorInvalidVersion). For data plane errors, implementations MUST drop packets silently and MAY increment the corresponding error counter for monitoring purposes.

1. Handshake Tampering / Transcript Hash Mismatch

1.1 Purpose

This vector ensures implementations:

  • Reject invalid handshakes where the transcript hash does not match the signature
  • Fail closed without installing traffic keys
  • Transition to ABORT/CLOSED state on signature verification failure

1.2 Normative Requirements

Implementations MUST:

  • Reject the handshake at the signature verification step
  • NOT install application keys or accept application packets
  • Transition to an ABORT/CLOSED terminal state
  • Zeroize any ephemeral secrets already derived

1.3 Baseline (Correct) Values

From the positive E2E vector, using PALISADE v1.2 wire format:

ClientHello:
  version:       0x12                  ; single byte, PALISADE v1.2
  client_nonce:  01 02 03 ... 20       ; 32 bytes
  K_c:           [1184 bytes]          ; ML-KEM-768 public key (actual size)

ServerHello:
  version:       0x12                  ; single byte, negotiated v1.2
  server_nonce:  A1 A2 A3 ... C0       ; 32 bytes
  CT_c:          [1088 bytes]          ; ML-KEM-768 ciphertext (actual size)
  server_signature: [3293 bytes]       ; ML-DSA-65 signature over transcript_hash

The correct transcript hash:

transcript_hash_correct = SHA3-256(canonical_ClientHello || canonical_ServerHello_without_signature)

Canonical Encoding Rules

(per End-To-End Test Vector Section 3):

  • For transcript hashing, only field contents are included; length prefixes are not hashed
  • Fields are concatenated in wire order
  • No padding or alignment bytes

Note: For complete field definitions and canonical byte encodings, see End-To-End Test Vector Sections 3-4.

1.4 Injected Fault

An attacker modifies one bit in the KEM ciphertext CT_c in transit:

CT_c'[0] = CT_c[0] XOR 0x01
CT_c'[1..] = CT_c[1..]

All other bytes are unchanged, including:

  • Server certificate
  • server_signature (unchanged, still signs the original transcript)

This creates a transcript mismatch because ServerHello_without_signature is now different.

1.5 Resulting Transcript Hash

The client computes:

transcript_hash_bad = SHA3-256(canonical_ClientHello || canonical_ServerHello_with_CT_c')

By construction: transcript_hash_bad != transcript_hash_correct

1.6 Required Client Behavior

Verification Step

During signature verification, the client MUST verify:

Verify(server_public_key, transcript_hash_bad, server_signature)

This verification MUST FAIL because server_signature is a signature over transcript_hash_correct, not transcript_hash_bad.

Failure Handling

Upon signature verification failure, the client MUST:

  1. Abort the handshake immediately
  2. Transition to ABORT/CLOSED state
  3. Zeroize any ephemeral secrets (including KEM decapsulation outputs)
  4. NOT proceed to HKDF key derivation
  5. NOT accept or emit any application-data packets

Error Code: ErrorSignatureVerifyFailed (0x0106)

1.7 Required Server Behavior

The server may remain unaware of the failure (tampering is indistinguishable from packet loss). However, if the server receives:

  • An invalid HandshakeFinished, or
  • Any out-of-context application packet

The server MUST reject and close the connection.

1.8 Validation Checklist

An implementation passes if:

  • Signature verification fails at the expected step
  • No traffic keys are installed
  • Connection transitions to ABORT/CLOSED
  • Any subsequent application packet is rejected
  • Key material is zeroized

2. Application Packet Replay

2.1 Purpose

This vector ensures implementations:

  • Enforce replay protection using (epoch_id, seq) pairs
  • Reject duplicate packets even if they decrypt correctly
  • Accept sequence 0 for regular packets (only early data requires seq >= 1)
  • Maintain correct state under adversarial packet duplication

2.2 Normative Requirements

Implementations MUST:

  • Reject any packet whose (epoch_id, seq) has already been accepted
  • Reject replays even if the packet is otherwise valid and authenticates correctly
  • Accept sequence 0 for regular packets (not early data)
  • NOT deliver replayed payloads to the application layer

2.3 Preconditions

A successful handshake has completed using the positive E2E vector:

  • Current epoch: epoch_id = 0x00000000
  • Replay window: Initialized, accepts sequence 0 and above
  • State: ESTABLISHED

2.4 Test Case A: First Packet with Sequence 0

Verify that sequence 0 is valid for regular packets. This test uses the canonical 78-byte packet format from End-To-End Test Vector Section 8.5.

PublicHeader (30 bytes)

51 50                             ; magic "QP"
12                                ; version 0x12
00                                ; flags (data, key_phase=0)
00 4E                             ; length = 78 (30 header + 32 ciphertext + 16 tag)
[24 bytes RID]                    ; Routing Identifier

EncryptedHeader (16 bytes plaintext, before encryption)

epoch_id:     00 00 00 00                   ; epoch 0
seq:          00 00 00 00 00 00 00 00       ; sequence 0 (VALID)
padding_len:  00 00
stream_id:    00
hdr_flags:    00

Payload: 16 bytes (e.g., "Hello PALISADE!")

Wire Format: 78 bytes total = 30 (PublicHeader) + 32 (ciphertext: 16 EncryptedHeader + 16 payload) + 16 (AEAD tag)

Expected Behavior: ACCEPT (sequence 0 is valid for regular packets)

2.5 Test Case B: Replay Attack

Packet P: First packet with (epoch=0, seq=0)

Transmission Sequence

  1. Deliver P (original) → ACCEPT
  2. Deliver P again (replay) → REJECT

No bytes are modified between deliveries.

2.6 Required Receiver Behavior

First Delivery (ACCEPT)

  1. Validate and parse the PublicHeader
  2. Verify (epoch_id=0, seq=0) is within window and not previously seen
  3. Perform AEAD decryption/verification
  4. Deliver payload to application
  5. Mark (epoch_id=0, seq=0) as seen/accepted

Second Delivery (REJECT)

  1. Detect that (epoch_id=0, seq=0) has already been accepted
  2. Reject the packet before delivering decrypted payload

The receiver MAY:

  • Drop silently, or
  • Emit ErrorReplayDetected (0x0303) diagnostic counter/log

The receiver MUST NOT:

  • Deliver payload a second time
  • Advance any application-level state based on the replay
  • Trigger rekey/migration based on replayed packets

Rejection Point

To minimize CPU usage and DoS risk, reject replays:

  • Prior to AEAD decryption when possible (based on (epoch_id, seq) tracking)
  • Otherwise immediately after header validation

2.7 Extended Test: Out-of-Order with Replay

Test that out-of-order acceptance does not break replay tracking:

Delivery Sequence

  1. Packet with (epoch=0, seq=2) → ACCEPT
  2. Packet with (epoch=0, seq=0) → ACCEPT (reordered, within window)
  3. Packet with (epoch=0, seq=1) → ACCEPT (reordered, within window)
  4. Replay of (epoch=0, seq=0) → REJECT

2.8 Validation Checklist

An implementation passes if:

  • Sequence 0 is accepted for the first packet
  • Replay of any (epoch, seq) pair is rejected
  • Replay detection does not depend on timestamp presence
  • (epoch_id, seq) tracking remains correct under reordering
  • No state corruption occurs from replayed packets

3. Length-Field Overflow / Size Cap Violation

3.1 Purpose

This vector ensures implementations:

  • Enforce packet size limits before allocation or decryption
  • Reject packets exceeding MTU (default 1500 bytes)
  • Reject packets below minimum size (62 bytes)
  • Fail fast without buffer overflow or resource exhaustion

3.2 Normative Requirements

Per Section 8.5 of the specification:

Implementations MUST:

  • Reject packets with total length < 62 bytes (MIN_PACKET_SIZE)
  • Reject packets with total length > configured MTU (default 1500 bytes)
  • Perform size checks before AEAD decryption
  • Drop violating packets silently (no error response)

Minimum Size Calculation

MIN_PACKET_SIZE      = PublicHeader (30) + EncryptedHeader (16) + AEAD tag (16) = 62 bytes
MIN_CONTROL_SIZE     = PublicHeader (30) + EncryptedHeader (16) + ControlFrame header (4) + AEAD tag (16) = 66 bytes

Note: EncryptedHeader (16 bytes plaintext) is encrypted. ControlFrame header is 4 bytes (1 ctrl_type + 3 length).

3.3 Negative Case A: Packet Too Small

Packet Construction

Total packet size: 61 bytes (one byte under minimum)

PublicHeader (30 bytes):
  51 50                           ; magic
  12                              ; version
  00                              ; flags
  00 3D                           ; length = 61
  [24 bytes RID]

Remaining: 31 bytes (should be at least 32: 16 ciphertext + 16 tag)

Required Behavior

  • Reject immediately after parsing PublicHeader
  • Do NOT attempt AEAD decryption
  • Drop silently (no error response)

3.4 Negative Case B: Packet Exceeds MTU

Packet Construction

Total packet size: 1501 bytes (one byte over default MTU)

PublicHeader (30 bytes):
  51 50                           ; magic
  12                              ; version
  00                              ; flags
  05 DD                           ; length = 1501
  [24 bytes RID]

Remaining: 1471 bytes of payload/padding

Required Behavior

  • Reject immediately after parsing length field
  • Do NOT attempt to read full payload
  • Do NOT attempt AEAD decryption
  • Drop silently
  • Error: ErrorPacketTooLarge (0x0305)

3.5 Negative Case C: Length Field Mismatch

Packet Construction

Actual UDP datagram size: 100 bytes

PublicHeader claims:
  length = 0x0FFF (4095 bytes)

Required Behavior

  • Detect mismatch between claimed length and actual datagram size
  • Reject packet
  • Do NOT attempt to read beyond datagram bounds

3.6 Negative Case D: Reserved Flag Bits Set

Per Section 8.1.1, reserved flag bits (1, 3-7) must be zero.

Packet Construction

PublicHeader (30 bytes):
  51 50                           ; magic
  12                              ; version
  FA                              ; flags = 0xFA (reserved bits 1, 3-7 set)
  00 50                           ; length = 80
  [24 bytes RID]

[Valid ciphertext follows]

Required Behavior

  • Check (flags & 0xFA) != 0 → TRUE
  • Reject packet immediately
  • Do NOT attempt AEAD decryption
  • Drop silently

3.7 Negative Case E: hdr_flags Non-Zero (Post-Decryption)

Per Section 8.2, hdr_flags in EncryptedHeader must be 0x00.

Packet Construction

After successful AEAD decryption, EncryptedHeader contains:

epoch_id:     00 00 00 00
seq:          00 00 00 00 00 00 00 00
padding_len:  00 00
stream_id:    00
hdr_flags:    01                  ; INVALID (must be 0x00)

Required Behavior

  • After successful AEAD decryption, validate hdr_flags == 0x00
  • Reject packet if hdr_flags != 0x00
  • Do NOT deliver payload to application

3.8 Negative Case F: AEAD Authentication Failure

This test explicitly verifies AEAD decryption failure handling (currently implicit in other tests).

Packet Construction

(78 bytes, canonical format):

PublicHeader (30 bytes):
  51 50                           ; magic
  12                              ; version
  00                              ; flags (valid)
  00 4E                           ; length = 78 (valid)
  [24 bytes RID]                  ; valid RID

Ciphertext (32 bytes):
  [valid-looking ciphertext]

Tag (16 bytes):
  DE AD BE EF CA FE BA BE         ; CORRUPTED: 1 bit flipped from valid tag
  12 34 56 78 9A BC DE F0

Required Behavior

  • Attempt AEAD decryption (all pre-checks pass)
  • AEAD authentication fails due to corrupted tag
  • Drop silently
  • MAY increment counter ErrorAEADDecryptFailed (0x0300)
  • Do NOT deliver any plaintext to application
  • Do NOT modify session state

3.9 Negative Case G: padding_len Overflow (Post-Decryption)

This tests a classic parser bug: padding_len claiming more bytes than available.

Packet Construction

(78 bytes total):

After successful AEAD decryption, plaintext (32 bytes) contains:

EncryptedHeader (16 bytes):
  epoch_id:     00 00 00 00
  seq:          00 00 00 00 00 00 00 01
  padding_len:  00 20                 ; INVALID: claims 32 bytes padding
  stream_id:    00
  hdr_flags:    00

Payload (16 bytes):
  [16 bytes of application data]

Condition: padding_len (32) > available payload bytes (16)

Required Behavior

  • AEAD decryption succeeds
  • Parse EncryptedHeader, extract padding_len = 32
  • Detect: padding_len > (plaintext_len - 16) → INVALID
  • Reject packet post-decryption
  • Do NOT deliver payload to application
  • Do NOT crash or read out of bounds
  • Drop silently

3.10 Negative Case H: stream_id Validation (STREAM_DUMMY)

Per Section 8.2, stream_id = 0xFF is reserved as STREAM_DUMMY.

Packet Construction

After successful AEAD decryption, EncryptedHeader contains:

epoch_id:     00 00 00 00
seq:          00 00 00 00 00 00 00 01
padding_len:  00 00
stream_id:    FF                  ; STREAM_DUMMY (reserved)
hdr_flags:    00

Required Behavior

  • AEAD decryption succeeds
  • Parse EncryptedHeader, extract stream_id = 0xFF
  • Recognize as STREAM_DUMMY (reserved for padding/dummy traffic)
  • Do NOT deliver payload to application layer
  • Do NOT treat as error (this is valid wire format for dummy traffic)
  • MAY use for traffic shaping/padding purposes

Note: STREAM_DUMMY is not an error condition—it's a valid mechanism for traffic shaping. This test verifies implementations recognize it and do NOT deliver to application.

Important: STREAM_DUMMY packets still consume replay window entries and sequence numbers. They are not "out of band" traffic—they are fully validated packets that simply have no application payload to deliver.

3.11 Validation Checklist

An implementation passes if:

  • Packets < 62 bytes are rejected before decryption
  • Packets > MTU are rejected before decryption
  • Length field mismatches are detected and rejected
  • Reserved flag bits cause immediate rejection
  • hdr_flags != 0x00 causes post-decryption rejection
  • AEAD authentication failure causes silent drop
  • padding_len overflow is detected post-decryption
  • stream_id = 0xFF (STREAM_DUMMY) is not delivered to application
  • No buffer overflow, over-read, or unbounded allocation occurs
  • Implementation remains stable under repeated invalid inputs

4. Early Data Limit Violation

4.1 Purpose

This vector ensures implementations:

  • Enforce max_early_data_bytes from the resumption ticket, not from client offer
  • Reject excess 0-RTT data
  • Reject early data outside the early-data phase
  • Treat early data as replayable and non-authoritative

4.2 Normative Requirements

Per the specification and security invariants:

Implementations MUST:

  • Enforce max_early_data_bytes from the ticket (authoritative)
  • Ignore any client-offered early data limits (non-authoritative)
  • Reject early data that exceeds the ticket-defined limit
  • Reject early data packets received outside the early-data phase
  • NOT allow early data to trigger epoch transitions, rekeys, or migrations

4.3 Preconditions

Ticket Contents (Authoritative)

Resumption Ticket T:
  max_early_data_bytes = 1024
  ticket_id = <opaque>
  allowed_operations = [idempotent only]

The value 1024 is authoritative and MUST be enforced.

ClientHelloResume Offer (Non-Authoritative)

Client claims:
  client_offered_max_early_data_bytes = 65535

This value MUST be ignored by the server.

4.4 Negative Case A: Early Data Exceeds Ticket Limit

Client Behavior (Input to System Under Test)

  1. Client sends ClientHelloResume referencing ticket T
  2. Client immediately sends 0-RTT data totaling 1025 bytes:
    • Early Data chunk 1: 800 bytes
    • Early Data chunk 2: 225 bytes
    • Total: 1025 bytes (one byte over limit)

4.5 Required Server Behavior

Enforcement Rule

early_data_accepted_bytes <= ticket.max_early_data_bytes

Server MUST use ticket's max_early_data_bytes = 1024, ignoring client value.

Rejection Condition

As soon as cumulative early data exceeds 1024 bytes:

  1. Reject early data
  2. Ensure no bytes beyond the limit are processed as application input

Concretely:

  • Bytes 0..1023 MAY be accepted if early data is permitted by policy
  • Byte 1024 (the 1025th byte) MUST NOT be processed
  • Server MUST signal rejection via ErrorEarlyDataRejected (0x0403)

Continuation Modes

Mode 1 (Recommended): Reject Early Data, Continue in 1-RTT

  1. Discard all early data (or at minimum, bytes beyond limit)
  2. Signal rejection via early_data_rejected indicator
  3. Complete resumption handshake
  4. Derive 1-RTT keys
  5. Accept only 1-RTT application data

Mode 2: Reject Resumption Entirely

  1. Reject resumption (ResumeReject)
  2. Discard all early data
  3. Require full handshake

4.6 Negative Case B: Early Data Outside Phase

Scenario: Client sends early data packet after resumption handshake completes.

State: Resumption handshake completed, early-data phase ended, session ESTABLISHED

Packet

EncryptedHeader:
  epoch_id:     FF FF FF FF         ; Reserved early data epoch
  seq:          00 00 00 00 00 00 00 01
  ...

Required Behavior

  • REJECT (early data epoch invalid outside early-data phase)
  • Do NOT decrypt or process the packet
  • Drop silently or emit ErrorInvalidEpoch (0x0301)

4.7 Negative Case C: Early Data with Sequence 0

Per Section 12.8, early data requires seq >= 1.

Packet

EncryptedHeader:
  epoch_id:     FF FF FF FF         ; Reserved early data epoch
  seq:          00 00 00 00 00 00 00 00  ; seq = 0 (INVALID for early data)
  ...

Required Behavior

  • REJECT (early data must have seq >= 1)
  • Drop silently

4.8 Security Invariants for Early Data

Per the Security Invariants appendix:

InvariantRequirement
Replayable data cannot steer stateEarly data MUST NOT cause durable state transitions
Non-authoritativeEarly data limits from ticket only, not client
Idempotent operations onlyEarly data suitable only for idempotent operations
Isolated keysEarly data keys cryptographically separated from 1-RTT keys

4.9 Validation Checklist

An implementation passes if:

  • Server enforces ticket-defined limit (1024) regardless of client offer
  • Server rejects over-limit early data
  • Server does not process the 1025th byte
  • Server rejects early data outside the early-data phase
  • Server rejects early data with seq = 0
  • Server signals rejection consistently
  • No "silent acceptance" occurs
  • Early data does not trigger epoch/migration/rekey state changes

5. Invalid Magic Byte

5.1 Purpose

This vector ensures implementations:

  • Reject packets with invalid magic bytes as the first line of defense
  • Do not process non-PALISADE packets
  • Fail fast before any further parsing

5.2 Normative Requirements

Per Section 8.1.1:

Magic = 0x5150 ("QP") - packets with magic ≠ 0x5150 MUST be rejected immediately.

5.3 Test Cases

Test Case A: All Zeros

PublicHeader[0:2] = 00 00  (invalid)
Expected: Reject immediately, drop silently

Test Case B: All Ones

PublicHeader[0:2] = FF FF  (invalid)
Expected: Reject immediately, drop silently

Test Case C: Reversed Magic

PublicHeader[0:2] = 50 51  (reversed "PQ")
Expected: Reject immediately, drop silently

Test Case D: One Byte Wrong

PublicHeader[0:2] = 51 51  (one byte wrong)
Expected: Reject immediately, drop silently

5.4 Required Behavior

Upon detecting invalid magic bytes:

  1. Reject immediately (first 2 bytes of packet)
  2. Do NOT parse any further fields
  3. Do NOT attempt AEAD decryption
  4. Drop silently (no error response to potential attacker)
  5. MAY increment an invalid-packet counter for monitoring

5.5 Validation Checklist

An implementation passes if:

  • All invalid magic values are rejected
  • Rejection occurs before parsing version/flags/length
  • No further processing occurs for invalid magic
  • Implementation remains stable under repeated invalid magic packets

6. Invalid Protocol Version

6.1 Purpose

This vector ensures implementations:

  • Reject unsupported protocol versions
  • Prevent version downgrade attacks
  • Handle version mismatch between handshake and data packets

6.2 Normative Requirements

Per Section 8.1.1 and Section 16:

Version = 0x12 for PALISADE v1.2 - unsupported versions MUST cause rejection.

6.3 Test Cases

Test Case A: Version Zero

PublicHeader:
  51 50                           ; valid magic
  00                              ; version = 0x00 (INVALID)
  ...

Expected: Reject, emit ErrorInvalidVersion (0x0100)

Test Case B: Future Version

PublicHeader:
  51 50                           ; valid magic
  FF                              ; version = 0xFF (unsupported future version)
  ...

Expected: Reject, emit ErrorInvalidVersion (0x0100)

Test Case C: Version 1.1 (If Not Supported)

PublicHeader:
  51 50                           ; valid magic
  11                              ; version = 0x11 (v1.1, may not be supported)
  ...

Expected: If v1.1 not supported, reject with ErrorInvalidVersion (0x0100)
         If v1.1 supported, process according to v1.1 rules

Test Case D: Version Mismatch Between Handshake and Data

State: Handshake completed with version 0x12
Packet: Data packet with version 0x11

Expected: Reject (version mismatch)

6.4 Required Behavior

Upon detecting unsupported version:

  1. Reject packet
  2. Emit ErrorInvalidVersion (0x0100) if in handshake phase
  3. Drop silently if in data phase (to avoid amplification)
  4. Do NOT attempt AEAD decryption
  5. Do NOT downgrade to earlier version without explicit negotiation

6.5 Validation Checklist

An implementation passes if:

  • Version 0x00 is rejected
  • Version 0xFF is rejected
  • Unsupported older versions are rejected
  • Version mismatch between handshake and data is detected
  • No version downgrade occurs without proper negotiation

7. key_phase Flag Validation

7.1 Purpose

This vector ensures implementations:

  • Reject key_phase=1 in STEADY state
  • Reject key_phase=1 on early data packets
  • Only allow key_phase=1 during TRANSITION state with armed next epoch

7.2 Normative Requirements

Per Section 9.3 and Section 11.11:

  • key_phase=0: Use current epoch keys
  • key_phase=1: Use next epoch keys (only valid during TRANSITION state)
  • key_phase=1 on early data (epoch 0xFFFFFFFF) is always invalid
  • Receivers MUST reject key_phase=1 if next epoch is not armed

Decrypt Attempt Limit

Receivers MUST attempt decryption with at most 2 key sets:

  • STEADY state: Only current epoch keys (1 attempt max)
  • TRANSITION state: Try hinted epoch first (key_phase selects), then fallback to other epoch (2 attempts max)

This bounds CPU usage and prevents attackers from causing unbounded decryption attempts.

7.3 Test Case A: key_phase=1 in STEADY State

State: STEADY (epoch 0, no next epoch armed)

Packet

(78 bytes total, canonical format):

PublicHeader (30 bytes):
  51 50                           ; magic
  12                              ; version
  04                              ; flags = 0x04 (key_phase=1, bit 2 set)
  00 4E                           ; length = 78
  [24 bytes RID]

[32 bytes ciphertext]             ; EncryptedHeader + payload
[16 bytes AEAD tag]

Expected Behavior

  • REJECT immediately
  • Do NOT attempt decryption with any keys
  • Drop silently
  • Reason: key_phase=1 is invalid when no next epoch is armed

7.4 Test Case B: key_phase=1 on Early Data

State: Early data phase active

Packet

PublicHeader:
  51 50                           ; magic
  12                              ; version
  04                              ; flags = 0x04 (key_phase=1)
  ...

EncryptedHeader (after decryption):
  epoch_id:     FF FF FF FF         ; Reserved early data epoch
  seq:          00 00 00 00 00 00 00 01
  ...

Expected Behavior

  • REJECT (Section 12.8: key_phase=1 invalid for early data)
  • Do NOT process the packet
  • Drop silently

7.5 Test Case C: key_phase=1 Without Armed Epoch

State: STEADY, no CTRL_REKEY received, epoch 0 only

Packet

PublicHeader:
  flags = 0x04 (key_phase=1)

EncryptedHeader:
  epoch_id:     00 00 00 01         ; epoch 1 (NOT armed)
  ...

Expected Behavior

  • REJECT (next epoch not armed)
  • Do NOT attempt decryption
  • Drop silently

7.6 Test Case D: key_phase=1 Valid in TRANSITION State

State: TRANSITION (epoch 0 current, epoch 1 armed via CTRL_REKEY)

Packet

PublicHeader:
  flags = 0x04 (key_phase=1)

EncryptedHeader:
  epoch_id:     00 00 00 01         ; epoch 1 (armed)
  ...

Expected Behavior

  • Attempt decryption with epoch 1 keys
  • If successful, ACCEPT
  • Update epoch state as appropriate

7.7 Validation Checklist

An implementation passes if:

  • key_phase=1 in STEADY state is rejected
  • key_phase=1 on early data is rejected
  • key_phase=1 without armed next epoch is rejected
  • key_phase=1 in TRANSITION state with armed epoch succeeds
  • No key confusion occurs between epochs

8. Invalid Epoch Transitions

8.1 Purpose

This vector ensures implementations:

  • Enforce epoch sovereignty (server controls epoch transitions)
  • Reject packets from unarmed epochs
  • Reject epoch jumps (skipping epochs)
  • Reject client unilateral epoch increments

8.2 Normative Requirements

Per Section 11.2 and Section 11.10:

  • Clients MUST NOT unilaterally increment epoch
  • Epoch transitions MUST occur only via authenticated CTRL_REKEY
  • Receivers MUST NOT advance epoch based solely on observing higher epoch_id
  • Epoch N+1 must be armed before accepting packets with epoch_id = N+1

Error Handling Policy

  • Handshake epoch errors → close session, emit ErrorInvalidEpoch (0x0301)
  • Data-plane epoch errors → silent drop, MAY increment ErrorInvalidEpoch counter

8.3 Test Case A: Unarmed Epoch Jump

State: STEADY, epoch 0 current, no CTRL_REKEY received

Packet

EncryptedHeader:
  epoch_id:     00 00 00 01         ; epoch 1 (NOT armed)
  seq:          00 00 00 00 00 00 00 00
  ...

Expected Behavior

  • REJECT (epoch 1 not armed)
  • Emit ErrorInvalidEpoch (0x0301)
  • Do NOT attempt decryption with unarmed epoch keys; only try keys that are currently installed/allowed (current and armed-next, max 2 attempts).
  • Do NOT arm epoch 1 based on this packet

8.4 Test Case B: Epoch Skip

State: STEADY, epoch 0 current

Packet

EncryptedHeader:
  epoch_id:     00 00 00 02         ; epoch 2 (skipped epoch 1)
  seq:          00 00 00 00 00 00 00 00
  ...

Expected Behavior

  • REJECT (epoch jump, epoch 2 cannot be armed without epoch 1)
  • Emit ErrorInvalidEpoch (0x0301)
  • Do NOT accept epoch jumps

8.5 Test Case C: Client Unilateral Epoch Increment

State: Client in STEADY, epoch 0

Client Action: Client sends packet with epoch_id = 0x00000001 without receiving CTRL_REKEY from server

Expected Server Behavior

  • REJECT (Section 11.2: clients MUST NOT unilaterally increment epoch)
  • Emit ErrorInvalidEpoch (0x0301)
  • This is a protocol violation

8.6 Test Case D: Old Epoch After Overlap Expires

State: TRANSITION complete, epoch 1 confirmed, overlap window expired

Packet

EncryptedHeader:
  epoch_id:     00 00 00 00         ; epoch 0 (old, overlap expired)
  ...

Expected Behavior

  • REJECT (epoch 0 no longer valid after overlap expires)
  • Drop silently
  • Only epoch 1+ packets accepted

8.7 Validation Checklist

An implementation passes if:

  • Packets with unarmed epoch_id are rejected
  • Epoch jumps (skipping epochs) are rejected
  • Client unilateral epoch increment is rejected
  • Old epoch packets after overlap expires are rejected
  • Epoch advancement requires authenticated CTRL_REKEY

9. Nonce Reuse Detection

9.1 Purpose

This vector ensures implementations:

  • Detect catastrophic nonce reuse as a receiver-safety diagnostic
  • Terminate session immediately upon nonce reuse detection
  • Log critical security violations
  • Do NOT automatically reconnect

9.2 Normative Requirements

Per Section 9.6:

If a receiver observes a second packet with the same (epoch_id, seq) pair where the (ciphertext || tag) differs from the first observed instance, this indicates catastrophic nonce reuse. The receiver MUST immediately terminate the session, MUST log at CRITICAL level, and MUST NOT automatically reconnect.

Important

This detection occurs prior to AEAD decryption by comparing the incoming (ciphertext || tag) against a cached value from the previously accepted packet. The receiver does NOT attempt to authenticate the second packet.

Processing Order

Replay-window checks occur before AEAD; nonce-reuse detection refines replay handling by distinguishing "same packet" (drop) from "different packet, same nonce" (fatal).

9.3 Detection Mechanism

To minimize CPU usage and detect nonce reuse before AEAD:

  1. When a packet is accepted (AEAD verified, replay check passed):
    • Cache: (epoch_id, seq)tag (or hash of ciphertext || tag)
    • Cache size: At least replay window size (e.g., 1024 entries)
  2. When a packet arrives with previously-seen (epoch_id, seq):
    • Before AEAD: Compare incoming tag against cached tag
    • If tags match: Normal replay, drop silently (no AEAD attempt)
    • If tags differ: NONCE REUSE DETECTED → terminate immediately

9.4 Test Case: Same (epoch, seq) with Different Ciphertext/Tag

State: ESTABLISHED, epoch 0

Packet 1

(78 bytes, canonical format):

PublicHeader (30 bytes):
  51 50 12 00 00 4E [24 bytes RID]

Ciphertext: C1 = [32 bytes]
Tag:        T1 = A1 B2 C3 D4 E5 F6 07 18 29 3A 4B 5C 6D 7E 8F 90

Processing: AEAD verifies → ACCEPT → Cache (epoch=0, seq=1)T1

Packet 2

(same epoch/seq, different ciphertext/tag):

PublicHeader (30 bytes):
  51 50 12 00 00 4E [24 bytes RID]

Ciphertext: C2 = [32 bytes, C2 ≠ C1]
Tag:        T2 = FF EE DD CC BB AA 99 88 77 66 55 44 33 22 11 00

Condition: T2 ≠ T1 (detected by tag comparison, no AEAD attempt)

9.5 Required Behavior

Upon detecting (epoch_id, seq) with different tag than cached:

  1. Do NOT attempt AEAD decryption (CPU protection)
  2. Immediately terminate session (fatal error)
  3. Log at CRITICAL level: "Nonce reuse detected: (epoch=X, seq=Y)"
  4. Zeroize all session keys immediately
  5. Do NOT automatically reconnect (requires user/admin intervention)
  6. MAY increment counter ErrorInternalError (0xFF00)

Rationale

Nonce reuse under AEAD completely breaks confidentiality (XOR of plaintexts revealed) and may enable forgery attacks. This is catastrophic. By detecting via tag comparison before AEAD, we avoid CPU exhaustion from attackers sending many forged packets with reused (epoch, seq).

9.6 Distinguishing Replay from Nonce Reuse

ConditionDetectionAction
Same (epoch, seq), same tagReplayDrop silently
Same (epoch, seq), different tagNonce reuseTerminate session, CRITICAL log

9.7 Validation Checklist

An implementation passes if:

  • Maintains cache of (epoch_id, seq)tag for accepted packets
  • Detects different tag for same (epoch, seq) before AEAD attempt
  • Session is terminated immediately upon nonce reuse detection
  • CRITICAL level log entry is generated
  • No automatic reconnection occurs
  • All session keys are zeroized
  • Normal replays (same tag) are dropped silently without AEAD

10. Invalid Control Frames

10.1 Purpose

This vector ensures implementations:

  • Reject unknown or reserved control frame types
  • Validate control frame structure and length
  • Reject malformed control payloads

10.2 Normative Requirements

Per Section 14:

  • Unknown control types MUST be rejected
  • CTRL_NONE (0x00) is reserved and MUST NOT be used
  • Control frame length must match expected payload size

10.3 Test Case A: Reserved Control Type (CTRL_NONE)

Control Frame

ctrl_type:    00                  ; CTRL_NONE (reserved, invalid)
length:       00 00 00            ; length = 0 (3 bytes, big-endian u24)
ctrl_data:    <empty>

Expected Behavior

  • REJECT (CTRL_NONE is reserved)
  • Emit ErrorInvalidControlFrame (0x0600)
  • Do NOT process as valid control

10.4 Test Case B: Unknown Control Type

Control Frame

ctrl_type:    FF                  ; Unknown type
length:       00 00 04            ; length = 4 (3 bytes, big-endian u24)
ctrl_data:    DE AD BE EF

Expected Behavior

  • REJECT (unknown control type)
  • Emit ErrorUnknownControlType (0x0601)
  • Do NOT process payload

10.5 Test Case C: CTRL_REKEY with Invalid Length

Per Section 11.3, CTRL_REKEY has empty ctrl_data (length = 0). Epoch keys are derived locally using HKDF-Expand(epoch_n_secret, label("epoch step"), 32), not transmitted in the control frame.

Control Frame

ctrl_type:    01                  ; CTRL_REKEY
length:       00 00 10            ; length = 16 (INVALID: should be 0)
ctrl_data:    [16 bytes of garbage]

Expected Behavior

  • REJECT (invalid length for CTRL_REKEY; expected 0)
  • Emit ErrorInvalidControlFrame (0x0600)
  • Do NOT process as rekey

10.6 Test Case D: CTRL_MIGRATE with Invalid Reason

Per Section 8.3, CTRL_MIGRATE has a 40-byte payload with the following structure:

  • migrate_nonce (32 bytes)
  • peer_epoch_observed (4 bytes, big-endian u32)
  • reason (1 byte, must be 0x01)
  • reserved (3 bytes, must be 0x000000)

Control Frame

ctrl_type:    02                  ; CTRL_MIGRATE
length:       00 00 28            ; length = 40 (3 bytes, big-endian u24)
ctrl_data:
  migrate_nonce:       [32 bytes]
  peer_epoch_observed: 00 00 00 00
  reason:              00         ; INVALID (must be 0x01)
  reserved:            00 00 00

Expected Behavior

  • REJECT (invalid reason code)
  • Emit ErrorInvalidControlFrame (0x0600)

10.7 Test Case E: CTRL_MIGRATE with Non-Zero Reserved

Control Frame

ctrl_type:    02                  ; CTRL_MIGRATE
length:       00 00 28            ; length = 40 (3 bytes, big-endian u24)
ctrl_data:
  migrate_nonce:       [32 bytes]
  peer_epoch_observed: 00 00 00 00
  reason:              01         ; valid
  reserved:            FF 00 00   ; INVALID (must be 0x000000)

Expected Behavior

  • REJECT (non-zero reserved field)
  • Emit ErrorInvalidControlFrame (0x0600)

10.8 Test Case F: ControlFrame Length Exceeds Available Plaintext

This tests bounds checking when ctrl_data length exceeds available plaintext.

Packet Construction

(78 bytes total):

After successful AEAD decryption, plaintext (32 bytes) contains:

EncryptedHeader (16 bytes):
  epoch_id:     00 00 00 00
  seq:          00 00 00 00 00 00 00 01
  padding_len:  00 00
  stream_id:    00
  hdr_flags:    00

ControlFrame (starting at byte 16):
  ctrl_type:    02                  ; CTRL_MIGRATE
  length:       00 00 28            ; length = 40 (claims 40 bytes of ctrl_data)
  ctrl_data:    [only 12 bytes available before end of plaintext]

Condition: Control frame claims 40 bytes of ctrl_data, but only 12 bytes remain in plaintext after the 4-byte control frame header (32 - 16 - 4 = 12).

Required Behavior

  • AEAD decryption succeeds
  • Parse EncryptedHeader (valid)
  • Read control frame header: ctrl_type = 0x02, length = 40
  • Detect: length (40) > available bytes (12) → BOUNDS ERROR
  • Reject packet post-decryption
  • Do NOT read beyond plaintext bounds
  • Do NOT process partial control frame
  • Drop silently
  • MAY increment counter ErrorInvalidControlFrame (0x0600)

10.9 Validation Checklist

An implementation passes if:

  • CTRL_NONE (0x00) is rejected
  • Unknown control types (0xFF, etc.) are rejected
  • Control frames with incorrect length are rejected
  • CTRL_MIGRATE with invalid reason is rejected
  • CTRL_MIGRATE with non-zero reserved is rejected
  • ControlFrame length exceeding plaintext causes rejection
  • Valid control frames are processed correctly

Appendix A: Error Code Reference

Error ConditionError CodeBehavior
Invalid versionErrorInvalidVersion (0x0100)Reject, close
Signature verification failureErrorSignatureVerifyFailed (0x0106)Abort, close
AEAD decryption failureErrorAEADDecryptFailed (0x0300)Drop silently
Invalid epochErrorInvalidEpoch (0x0301)Drop or close
Replay detectedErrorReplayDetected (0x0303)Drop silently
Packet too largeErrorPacketTooLarge (0x0305)Drop silently
Early data rejectedErrorEarlyDataRejected (0x0403)Reject early data
Invalid control frameErrorInvalidControlFrame (0x0600)Drop or close
Unknown control typeErrorUnknownControlType (0x0601)Drop
Nonce reuse detectedErrorInternalError (0xFF00)CRITICAL, terminate
Invalid magic(silent drop)Drop immediately
Reserved flags set(silent drop)Drop immediately
hdr_flags non-zero(silent drop)Drop

Appendix B: Test Matrix

Test IDCategoryInputExpected Result
N1.1HandshakeTampered CT_cSignature verification failure (0x0106)
N1.2HandshakeTampered server_nonceSignature verification failure (0x0106)
N2.1ReplayReplay same (epoch, seq)Second packet rejected
N2.2ReplayOut-of-order then replayReordered accepted, replay rejected
N2.3ReplaySequence 0 first packetAccepted (seq=0 valid for regular)
N3.1SizePacket = 61 bytesRejected before decryption
N3.2SizePacket = 1501 bytesRejected before decryption (0x0305)
N3.3Sizeflags = 0x02Rejected (reserved bit)
N3.4Sizeflags = 0xFARejected (reserved bits)
N3.5Sizehdr_flags = 0x01Rejected after decryption
N3.6AEADCorrupted tagAEAD fails, silent drop (0x0300)
N3.7Parserpadding_len overflowRejected post-decryption
N3.8stream_idstream_id = 0xFFNot delivered to app (STREAM_DUMMY)
N4.1Early Data1025 bytes (limit 1024)Excess rejected (0x0403)
N4.2Early DataClient claims 65535Ticket limit (1024) enforced
N4.3Early DataOutside early-data phaseRejected (0x0301)
N4.4Early Dataseq = 0Rejected (must be >= 1)
N5.1Magicmagic = 0x0000Rejected immediately
N5.2Magicmagic = 0x5051Rejected immediately
N6.1Versionversion = 0x00Rejected (0x0100)
N6.2Versionversion = 0xFFRejected (0x0100)
N7.1key_phasekey_phase=1 in STEADYRejected
N7.2key_phasekey_phase=1 on early dataRejected
N7.3key_phasekey_phase=1 without armed epochRejected
N8.1EpochUnarmed epoch_idRejected (0x0301)
N8.2EpochEpoch skip (0→2)Rejected (0x0301)
N8.3EpochClient unilateral incrementRejected (0x0301)
N9.1Nonce ReuseSame (epoch,seq), different ciphertextSession terminated, CRITICAL log
N10.1Controlctrl_type = 0x00Rejected (0x0600)
N10.2Controlctrl_type = 0xFFRejected (0x0601)
N10.3ControlCTRL_REKEY invalid lengthRejected (0x0600)
N10.4ControlCTRL_MIGRATE invalid reasonRejected (0x0600)
N10.5ControlCTRL_MIGRATE non-zero reservedRejected (0x0600)
N10.6ControlControlFrame length > plaintextRejected (0x0600)

Appendix C: Relationship to Security Invariants

These negative vectors verify the following security invariants:

Invariant #DescriptionTested By
1Receiver sovereignty over epochsN2, N4, N8
2Replayable data cannot steer stateN4
3Commit points are explicitN4
4Fail-fast on ambiguityN3 (reserved flags), N5 (magic), N6 (version)
6(epoch_id, seq) uniquenessN2, N9
7Replay window enforcementN2
9key_phase validationN7
10max_early_data_bytes from ticketN4
11Early data is replayableN4
12Epoch sovereigntyN8
18Global 128 KB size capN3
19Nonce uniquenessN9

Appendix D: Error Code Coverage

Error CodeNameTested?Test ID
0x0100ErrorInvalidVersionN6.1, N6.2
0x0106ErrorSignatureVerifyFailedN1.1, N1.2
0x0300ErrorAEADDecryptFailedN3.6 (explicit), N7, N8 (implicit)
0x0301ErrorInvalidEpochN4.3, N8.1, N8.2, N8.3
0x0303ErrorReplayDetectedN2.1, N2.2
0x0305ErrorPacketTooLargeN3.2
0x0403ErrorEarlyDataRejectedN4.1, N4.2
0x0600ErrorInvalidControlFrameN10.1, N10.3, N10.4, N10.5, N10.6
0x0601ErrorUnknownControlTypeN10.2
0xFF00ErrorInternalErrorN9.1

Coverage: 100% of critical error codes have explicit test cases.

PALISADE Protocol Specification Draft 00

INFORMATIONAL