Implementation Considerations (Informative)
Status of This Appendix
This appendix is informative and non-normative. It provides guidance, rationale, and illustrative examples for implementing PALISADE securely, but it does not define protocol behavior or compliance requirements. Conformance to the PALISADE specification does not depend on following any particular implementation technique described in this appendix.
X.1 Secure Handling of Cryptographic Key Material
PALISADE's security properties, including forward secrecy and replay resistance, depend on the correct handling and timely disposal of cryptographic key material. While the core specification mandates that expired or superseded secrets be erased, the mechanisms used to achieve secure erasure are inherently implementation-dependent.
Implementations are encouraged to follow the general principles below:
- Minimize the lifetime of sensitive key material.
- Avoid unnecessary copying of secrets.
- Separate long-term keys from short-lived session keys.
- Prefer deriving keys on demand rather than caching derived material.
- Treat intermediate secrets (e.g., KEM shared secrets, handshake secrets) as highly sensitive.
The remainder of this appendix discusses common implementation strategies and tradeoffs.
X.2 Language Memory Models and Erasure Guarantees
Programming languages differ significantly in how memory is allocated, reused, and reclaimed. These differences affect the strength of guarantees that can be made about key erasure.
X.2.1 Explicit-Memory Languages
Examples include C, C++, Rust, Zig, and similar languages that provide explicit control over memory allocation and deallocation.
In these environments, implementations can provide strong guarantees regarding key erasure by:
- Overwriting sensitive buffers immediately after use.
- Preventing compiler optimizations from eliding zeroization operations.
- Avoiding reuse of memory containing key material without explicit clearing.
- Optionally locking memory pages to reduce exposure to swapping.
When implemented carefully, these approaches allow immediate and deterministic destruction of sensitive key material.
X.2.2 Garbage-Collected Languages
Examples include Go, Java, Swift, Python, JavaScript, and other languages with automatic memory management.
In garbage-collected environments:
- Explicit overwriting of buffers reduces exposure but does not guarantee immediate memory reuse.
- Memory containing key material may remain in process memory until garbage collection occurs.
- Swap or crash dumps may retain remnants of sensitive data.
Despite these limitations, garbage-collected implementations can still achieve acceptable security by:
- Explicitly overwriting key buffers before dereferencing them.
- Limiting the scope and lifetime of objects containing secrets.
- Avoiding long-lived global structures for key storage.
- Using runtime barriers or language-specific techniques to prevent premature optimization.
These environments provide best-effort erasure, which is sufficient for many deployment contexts when appropriately documented.
X.3 Illustrative Key Erasure Techniques
The following examples illustrate common approaches used to reduce the lifetime of sensitive key material. These examples are provided for educational purposes only and do not mandate any specific implementation strategy.
X.3.1 Example: C / C++
// Illustrative example only
#include <string.h>
void erase_key(uint8_t *key, size_t len) {
explicit_bzero(key, len);
}This approach relies on a zeroization primitive that is guaranteed not to be optimized away by the compiler.
X.3.2 Example: Rust
use zeroize::Zeroize;
// Illustrative example only
fn erase_key(key: &mut [u8]) {
key.zeroize();
}The zeroize crate provides compiler-resistant zeroization for sensitive data structures.
X.3.3 Example: Go
// Illustrative example only
import "runtime"
func eraseKey(key []byte) {
for i := range key {
key[i] = 0
}
runtime.KeepAlive(key)
}The runtime.KeepAlive call reduces the likelihood that the compiler will reorder or elide the zeroization.
X.4 Packet Format Implementation Guidance
Byte Order: All multi-byte integers MUST be encoded in big-endian (network byte order).
Length Fields: All length fields are in bytes and represent the length of the immediately following data.
Reserved Fields: All reserved fields MUST be set to zero by senders and SHOULD be ignored by receivers (forward compatibility).
Padding: No padding or alignment is used in any structure. Variable-length fields are length-prefixed.
Validation: Implementations MUST validate:
- Length fields do not cause buffer overruns
- Reserved bits/fields are zero
- Enum values are within defined ranges
- Total message length matches expected size
- Magic number matches expected value (0x5150)
- Version is supported (0x10 or 0x11)
Error Handling: Implementations SHOULD reject malformed packets immediately and log errors for diagnostic purposes. Do not attempt to parse beyond known-good boundaries.
X.5 Traffic Shaping Implementation (Non-Normative)
This section provides implementation guidance and reference strategies for realizing the traffic shaping modes defined in Sections 10.1–10.4. The material in this section is non-normative and does not impose specific architectural or scheduling requirements, provided that the externally observable behavior conforms to the protocol requirements.
Traffic shaping affects packet generation only. It does not alter cryptographic processing, nonce construction, replay protection, or packet validation rules.
X.5.1 General Implementation Principles
All traffic shaping modes MUST preserve the following invariants:
- All packets (real or dummy) use identical wire format and encryption.
- Nonce construction, replay protection, and epoch handling are unchanged.
- Dummy packets MUST be indistinguishable from real packets prior to successful AEAD decryption.
- Receivers MUST authenticate packets before inspecting any traffic-shaping metadata.
- Traffic shaping logic SHOULD be implemented as a local transmission policy layer, independent of cryptographic state machines.
X.5.2 Mode 1 (Light Padding) — Reference Strategy
A typical Mode 1 implementation applies randomized padding on a per-packet basis:
payload_len = len(application_payload) padding_len = uniform_random(pad_min, pad_max) padding = crypto_random_bytes(padding_len) encrypted_header.padding_len = padding_len plaintext = encrypted_header || application_payload || padding
Implementation Notes
- Padding bytes SHOULD be generated using a cryptographically secure RNG.
- Padding MUST NOT contain predictable patterns or reused buffers.
- Receivers MUST ignore padding bytes entirely after authenticated decryption.
- Implementations MAY tune pad_min / pad_max dynamically based on MTU or traffic class.
- Mode 1 is intentionally stateless and low-overhead.
X.5.3 Mode 2 (Constant-Rate) — Architectural Pattern
Mode 2 implementations typically separate concerns into:
- Traffic scheduler (controls send timing)
- Packet generator (real vs dummy)
- Bandwidth governor (prevents runaway transmission)
The protocol does not mandate a specific scheduler design. One common strategy is slot-based scheduling, described below.
X.5.4 Slot-Based Scheduling (Illustrative)
A slot-based scheduler divides time into fixed or jittered intervals ("slots") and ensures that at most one packet is emitted per slot.
slot_duration = slot_duration_ms ± random_jitter
packets_per_slot = 1
For each slot:
If real data is available, send a real packet.
Otherwise, send a dummy packet (subject to bandwidth constraints).This strategy ensures a stable packet emission rate without delaying real traffic.
X.5.5 Dummy Packet Generation
Dummy packets SHOULD be generated as follows:
- Payload length equals the selected bucket size.
- Payload contents are cryptographically random.
- Header fields (epoch, seq, nonce, etc.) are valid and monotonic.
- A dummy indicator flag MAY be set inside the encrypted header.
Dummy packets MUST:
- Use valid nonces and sequence numbers.
- Participate fully in replay protection.
- Be indistinguishable from real packets prior to decryption.
Receivers MUST authenticate dummy packets before discarding them.
X.5.6 Bucket Selection Strategies
Mode 2 supports multiple bucket strategies:
Single Fixed Size
- All packets padded to a constant size (e.g., 1200 bytes).
- Simplest and most predictable.
Fixed-Size Buckets
- A small set of allowed sizes (e.g., 512, 1024, 1500).
- Real packets are padded up to the nearest bucket.
- Dummy packets randomly select a bucket.
Low-Variation Buckets
- Each bucket permits ±V bytes of random variation.
- Improves resistance to exact size fingerprinting.
- Variation MUST be cryptographically random.
Implementations MUST reject plaintexts that exceed the selected bucket size.
X.5.7 Bandwidth and DoS Protection
To prevent amplification and resource exhaustion, implementations SHOULD enforce:
- A maximum total bandwidth cap (bytes/sec).
- A cap on dummy packet generation rate.
- Priority for real traffic over dummy traffic.
One possible policy:
if real_traffic_rate >= target_rate:
send no dummy packets
else:
send min(required_dummy_packets, bandwidth_cap)Dummy packet generation SHOULD be suppressed if bandwidth caps are reached.
X.5.8 Reference Pseudocode (Illustrative)
class ConstantRateShaper:
def __init__(self, config):
self.slot_rate = config.slot_rate_packets_per_sec
self.slot_duration = config.slot_duration_ms / 1000
self.max_bandwidth = config.max_bandwidth_bytes_per_sec
self.bytes_sent = 0
self.window_start = now()
def send(self, packet, is_dummy=False):
if now() - self.window_start > 1.0:
self.bytes_sent = 0
self.window_start = now()
if self.bytes_sent + len(packet) > self.max_bandwidth:
if is_dummy:
return False
transmit(packet)
self.bytes_sent += len(packet)
return TrueThis example illustrates rate bounding, not required logic.
X.5.9 Operational Considerations
- Mode switching SHOULD be rate-limited to avoid traffic anomalies.
- Configuration changes SHOULD take effect at epoch boundaries.
- Logging of dummy packet behavior SHOULD be rate-limited to avoid leaks.
- High-security modes significantly increase bandwidth usage and power consumption.
X.5.10 Security Summary
Correct implementation of traffic shaping:
- Reduces size and timing metadata leakage
- Does not weaken cryptographic guarantees
- Preserves replay protection and nonce uniqueness
- Remains robust under packet loss and reordering
Traffic shaping is defense-in-depth, not a substitute for encryption or authentication.
X.6 Rekeying Implementation (Non-Normative)
This section provides implementation guidance for rekeying and epoch transitions in PALISADE. It is non-normative and does not alter or supersede the protocol requirements defined in Section 11.
Its purpose is to help implementers build correct, safe, and efficient rekeying logic that conforms to the protocol's security properties.
X.6.1 Rekeying Overview
Rekeying transitions a session from epoch n to epoch n+1 while preserving tunnel continuity.
Implementations typically consist of:
- Rekey trigger detection
- Epoch key derivation
- Controlled epoch transition
- Temporary overlap handling
- Secure key erasure
X.6.2 Rekey Triggers (Implementation Guidance)
Implementations commonly initiate rekeying when one or more of the following conditions are met:
- Sequence counter approaches exhaustion (see Section 9)
- Configured time interval elapses (e.g., every 60 seconds)
- Configured data volume threshold is exceeded
- A CTRL_REKEY control frame is received
Typical implementations centralize rekey trigger checks in the packet send path to ensure timely transitions.
X.6.3 Epoch Transition State Tracking
A practical implementation models rekeying as a short-lived transition state:
EPOCH_NORMAL (n)
|
| rekey triggered
v
EPOCH_TRANSITION (n → n+1)
|
| overlap window expires
v
EPOCH_NORMAL (n+1)During transition:
- New packets are sent using epoch n+1 only
- Old epoch n keys remain available for decryption only
- Overlap duration and out-of-order limits are enforced
X.6.4 Deriving and Activating a New Epoch
A typical rekey initiation flow:
def initiate_rekey():
# Derive next epoch secret
epoch_n1_secret = hkdf_expand(epoch_n_secret, b"epoch step", 32)
# Derive traffic keys for new epoch
epoch_n1_keys = derive_traffic_keys(epoch_n1_secret)
# Atomically switch transmit state
epoch_id += 1
seq = 0
current_epoch = epoch_n1
# Start overlap timer
overlap_expiry = now() + overlap_duration_ms
# Send CTRL_REKEY frame
send_ctrl_rekey(epoch_id)Key points:
- Epoch increment and sequence reset occur atomically
- No packet is sent with mixed epoch/sequence state
- Old keys are retained only for decryption
X.6.5 Decryption During Epoch Overlap
During the overlap window, receivers may accept packets from two epochs.
A common decryption strategy:
def decrypt_packet(packet):
# Fast path: current epoch
pt = try_decrypt(packet, current_epoch.keys)
if pt:
return pt
# Overlap path: previous epoch
if overlap_active():
pt = try_decrypt(packet, old_epoch.keys)
if pt and within_out_of_order_window(packet):
return pt
return None # rejectNotes:
- Always try the current epoch first
- Old epoch packets must still pass replay checks
- Overlap does not relax replay protection rules
X.6.6 Out-of-Order Packet Tracking
Implementations typically maintain a bounded structure to track old-epoch packets during overlap.
Example approach:
class OutOfOrderTracker:
def __init__(self, max_ooo):
self.max_ooo = max_ooo
self.seen = set()
def accept(self, seq, min_allowed_seq):
if seq < min_allowed_seq:
return False
if seq in self.seen:
return False
self.seen.add(seq)
return TrueGuidance:
- Track only old-epoch packets
- Enforce max_out_of_order_packets
- Garbage-collect tracking state periodically
- Do not reuse this tracker outside overlap
X.6.7 Overlap Timer Handling
When the overlap window expires:
def expire_overlap():
zeroize(old_epoch.secret)
zeroize(old_epoch.keys)
old_epoch = NoneImplementations should:
- Use a monotonic clock for overlap timers
- Ensure key erasure occurs exactly once
- Prevent race conditions between decryption and erasure
X.6.8 Secure Key Erasure
Recommended practices:
- Zero epoch secrets immediately after they become obsolete
- Zero traffic keys and IVs together
- Use compiler-safe zeroing primitives
- Avoid keeping old keys in long-lived objects
For garbage-collected languages, explicitly overwrite buffers before dereferencing.
X.6.9 Error Handling During Rekeying
If rekeying fails at any point:
- Abort the session
- Erase all partially derived keys
- Do not attempt to recover mid-transition
- Require a full handshake for recovery
Partial or inconsistent epoch state is unsafe.
X.6.10 Implementation Pitfalls to Avoid
Common mistakes:
- Sending packets with old epoch after rekey
- Accepting old epoch packets after overlap expiry
- Forgetting to reset sequence counter on epoch increment
- Treating overlap as optional replay relaxation
- Failing to erase old keys promptly
X.6.11 Summary
Correct rekeying implementations:
- Are atomic
- Are bounded in time and packet count
- Preserve replay guarantees
- Enforce strict key lifetimes
- Never reuse (epoch_id, seq) pairs
When implemented correctly, rekeying provides strong forward secrecy without disrupting tunnel operation.
X.7 Session Resumption Implementation (Non-Normative)
This appendix provides implementation guidance for PALISADE session resumption and 0-RTT early data. It supplements the normative requirements in Section 12 and is non-authoritative.
Nothing in this appendix relaxes or overrides protocol requirements.
X.7.1 Resumption Ticket Data Structures
X.7.1.1 TicketInner (Logical Structure)
Before encryption, the inner ticket contains the following fields:
struct TicketInner {
u128 ticket_id; // Unique identifier (single-use)
u32 issued_at; // UNIX timestamp (seconds)
u32 expires_at; // UNIX timestamp (seconds)
u8 max_early_data; // Maximum 0-RTT data in KB (0 = disabled)
opaque psk[32]; // Resumption pre-shared key
opaque client_id[16]; // Optional stable client identifier
opaque session_id[16]; // Original session identifier
}Total size: 89 bytes (fixed)
Implementation notes:
ticket_idMUST be unpredictable and collision-resistantpskMUST be generated from cryptographically secure randomness- All fields MUST be serialized deterministically before encryption
X.7.2 Ticket Encryption and Authentication
X.7.2.1 Ticket Encryption
Servers encrypt TicketInner using AEAD:
encrypted_inner = AEAD_Encrypt(
key = ticket_secret,
nonce = random_nonce[12],
aad = label("ticket"),
pt = TicketInner
)Implementation guidance:
ticket_secretshould be rotated periodically (see Section 6)nonceMUST be generated using a CSPRNGaadMUST be constant and protocol-specific
X.7.2.2 Ticket Authentication
Each ticket is authenticated with a server signature:
ticket_hash = SHA-3-256(nonce || encrypted_inner) signature = SIG_server_privkey(ticket_hash)
Implementation notes:
- The signature protects against ticket forgery
- The hash MUST be computed before signature generation
- Signature verification MUST precede ticket decryption on the server
X.7.3 Resumption Handshake Processing
X.7.3.1 Server Processing Flow
A typical resumption server flow:
- Verify
server_signatureon ResumeTicket - Decrypt
encrypted_innerusingticket_secret - Validate TicketInner fields:
expires_at> current_timeticket_idnot previously used
- Record
ticket_idas used (replay cache) - Perform fresh KEM exchange using
K'_c - Derive resumption master secret
- Issue ServerHelloResume
Implementation guidance:
- Ticket replay checks MUST occur before key derivation
- Ticket validation failures SHOULD be indistinguishable on the wire
- All intermediate secrets SHOULD be erased immediately on failure
X.7.4 Resumption Key Derivation (Reference)
The resumption key schedule combines a PSK with a fresh KEM shared secret using a sequential HKDF-Extract combiner, aligned with TLS 1.3.
def derive_resumption_master_secret(
psk: bytes,
ss_prime_c: bytes,
resume_nonce: bytes,
server_nonce_prime: bytes,
transcript_hash: bytes
) -> bytes:
# Combine PSK and fresh KEM secret
ikm1 = HKDF_Extract(zeros(32), psk)
ikm2 = HKDF_Extract(ikm1, ss_prime_c)
# Mix in nonces
ikm = ikm2 || resume_nonce || server_nonce_prime
# Extract early secret
early_secret = HKDF_Extract(zeros(32), ikm)
# Bind transcript
handshake_secret = HKDF_Expand(
early_secret,
label("handshake secret") || transcript_hash,
32
)
# Derive resumption master secret
return HKDF_Expand(
handshake_secret,
label("master secret"),
32
)Notes:
- This construction ensures forward secrecy even if the PSK leaks
- Transcript binding prevents cross-session key confusion
- Implementations SHOULD reuse the same HKDF utilities as the full handshake
X.7.5 0-RTT Early Data Implementation
X.7.5.1 Early Data Key Derivation
If max_early_data > 0:
early_data_key = HKDF-Expand(resumption_secret, label("early data key"), 32)
early_data_iv = HKDF-Expand(resumption_secret, label("early data iv"), 12)Implementation notes:
- Early data keys MUST be distinct from application traffic keys
- Early data MUST use an independent nonce sequence
- Early data MUST NOT reuse epoch/sequence state
X.7.5.2 Early Data Safety Enforcement
Applications MUST enforce replay-safe behavior for early data.
Examples of safe early data usage:
- Read-only queries
- Cache fetches
- Idempotent requests with deduplication tokens
Examples of unsafe early data usage:
- State mutation
- Financial transactions
- Authentication or authorization changes
Servers SHOULD:
- Reject early data exceeding
max_early_data - Log early data rejection events (rate-limited)
- Disable early data for sensitive endpoints
X.7.6 Ticket Replay Cache Implementation
X.7.6.1 Replay Cache Semantics
Servers track used ticket_id values to enforce single-use tickets.
Requirements:
- Each
ticket_idMUST be accepted at most once - Cache entries MUST expire automatically
- Cache MUST be resilient to DoS amplification
X.7.6.2 Lifetime Management
Recommended defaults:
| Parameter | Value |
|---|---|
| Minimum retention | 5 minutes |
| Default retention | 1 hour |
| Maximum retention | 24 hours |
Implementation strategies:
- Time-indexed hash maps
- Sharded replay caches
- Periodic cleanup (≥ once per minute)
Example structure:
class TicketReplayCache:
def __init__(self):
self.used = {} # ticket_id -> expiry_time
def mark_used(self, ticket_id, expiry):
self.used[ticket_id] = expiry
def is_used(self, ticket_id):
return ticket_id in self.used
def cleanup(self, now):
self.used = {
tid: exp for tid, exp in self.used.items()
if exp > now
}X.7.7 Operational Considerations
Logging
- Replay attempts SHOULD be logged
- Ticket validation failures SHOULD be rate-limited
Key Hygiene
ticket_secretSHOULD be rotated (see Section 6)- Old ticket secrets MUST be erased after rotation
- Resumption secrets MUST be erased after session establishment
Deployment Guidance
- Disable early data for admin or control-plane APIs
- Monitor replay cache growth
- Treat replay cache exhaustion as a security event
X.7.8 Security Summary
When implemented correctly, PALISADE resumption provides:
- Strong replay resistance (single-use tickets)
- Forward secrecy (fresh PQ KEM per resumption)
- Context separation (domain-separated KDF labels)
- TLS-grade 0-RTT semantics with explicit constraints
X.8 Migration Implementation (Non-Normative)
This appendix provides implementation guidance for PALISADE migration and roaming. It is non-normative and does not define wire formats or protocol requirements. All normative behavior is specified in Section 13.
Migration is a best-effort optimization, not a transport guarantee. Implementations should favor simplicity, correctness, and security over aggressive recovery behavior.
X.8.1 Migration State Model
Implementations SHOULD maintain an explicit per-session migration state machine:
IDLE → IN_PROGRESS → COMPLETED → IDLE
Recommended states:
IDLE
No migration activity.
IN_PROGRESS
A migration has been accepted and address rebinding is underway.
COMPLETED
Migration has completed successfully; state will reset to IDLE.
Migration state MUST be scoped per session and per direction.
X.8.2 Soft Migration Processing Flow
Soft migration is initiated by receiving a valid CTRL_MIGRATE control frame within an authenticated encrypted packet.
Recommended Processing Steps
- Verify packet authenticity and decrypt
- Validate
epoch_id == current_epoch - Apply epoch/sequence replay checks
- Verify migration reason normalization
- Check migration state (must not already be IN_PROGRESS)
- Update peer address binding (5-tuple)
- Mark migration as COMPLETED
Address Rebinding
When a migration is accepted:
- The implementation SHOULD bind the session to the observed source address of the authenticated packet.
- No cryptographic material is changed.
- Existing replay windows and sequence counters remain intact.
X.8.3 Concurrency Handling
Migration is inherently stateful and must be serialized.
Recommended behavior:
- Only one migration per session may be processed at a time.
- If a migration is already IN_PROGRESS:
- Additional migration requests SHOULD be rejected or ignored.
- Queuing migration requests is discouraged due to complexity.
- Atomicity is more important than throughput.
X.8.4 Rate Limiting (DoS Protection)
Implementations SHOULD apply rate limiting to migration attempts to prevent abuse.
Suggested strategy:
- Maintain a sliding time window per session.
- Track the number of accepted migration attempts.
- Reject or ignore migration requests exceeding policy thresholds.
Example (illustrative only):
- Max migrations per session: 10
- Time window: 60 seconds
Exact thresholds are implementation-defined and SHOULD be configurable.
X.8.5 Replay and Duplication Defense
Migration control frames are already protected by:
- AEAD authentication
- Epoch/sequence anti-replay window
No additional cryptographic replay mechanism is required.
However, implementations MAY add lightweight safeguards:
- Track the most recent migration attempt
- Reject immediate duplicate migration frames
- Avoid reprocessing identical migration events
These measures are optional and should not weaken correctness.
X.8.6 Migration Timeout Handling
Implementations SHOULD ensure migration processing completes promptly.
Recommended behavior:
- Set a migration processing timeout (e.g., 3–5 seconds).
- If migration does not complete within the timeout:
- Reset migration state to IDLE
- Retain the previous address binding
Timeouts prevent sessions from becoming stuck in partial migration states.
X.8.7 Bidirectional Migration
PALISADE permits simultaneous bidirectional migration:
- Client and server may independently migrate their sending paths.
- Each direction SHOULD track migration state independently.
- Address rebinding applies only to the peer that initiated migration.
Implementations MUST avoid assuming migration symmetry.
X.8.8 Interaction with Rekeying
Migration and rekeying are orthogonal:
- Soft migration MUST NOT derive new keys.
- Rekeying MUST occur only via CTRL_REKEY or configured triggers.
- Epoch overlap logic remains unchanged by migration.
If a migration coincides with an epoch transition:
- Rekeying semantics take precedence.
- Migration address rebinding remains valid across epochs.
X.8.9 Failure Handling and Fallback
If soft migration fails:
- The session SHOULD continue using the previous address binding if possible.
- If packets can no longer be exchanged:
- Hard migration (session resumption) SHOULD be attempted.
- Migration failures MUST NOT corrupt cryptographic state.
X.8.10 Logging and Telemetry (Optional)
Implementations MAY log migration events for diagnostics and monitoring:
- Migration attempts
- Success or failure
- Rate-limit rejections
- Address changes
Logs SHOULD NOT include sensitive material or unencrypted packet contents.
X.8.11 Security Summary
Implementation choices SHOULD preserve the following properties:
- No unauthenticated migration
- No replay-driven state changes
- No migration-triggered key reuse
- No distinguishable migration causes on the wire
- No unbounded resource consumption
Migration should always degrade safely.
X.9 Error Handling Implementation (Non-Normative)
This appendix provides implementation guidance for handling PALISADE protocol errors. It supplements Section 15 by offering recommended patterns for error classification, logging, disclosure control, and operational robustness. Nothing in this appendix alters the normative behavior defined in Section 15.
X.9.1 Design Goals
An error handling implementation SHOULD aim to:
- Fail closed on cryptographic or protocol integrity violations
- Avoid creating side-channels or distinguishable error oracles
- Bound resource usage under error conditions
- Provide operators with sufficient diagnostics without exposing sensitive data
- Maintain forward compatibility with future error codes
X.9.2 Centralized Error Handling
Implementations SHOULD route all protocol errors through a centralized error handler rather than scattering error logic throughout the codebase.
A centralized handler simplifies:
- Consistent fatal vs. non-fatal handling
- Uniform logging and metrics
- Disclosure policy enforcement
- Rate limiting of error responses
X.9.3 Error Classification (Implementation View)
Although Section 15 defines fatal vs. non-fatal semantics, implementations often benefit from an additional disclosure classification:
| Level | Description | Wire Behavior |
|---|---|---|
| Public | Safe to disclose | Send error code and generic message |
| Limited | Potentially sensitive | Send error code with generic text only |
| Internal | Dangerous to disclose | Do not send; log locally only |
Note: Disclosure level does not override fatality. A fatal error may still be internal-only.
X.9.4 Error Response Generation
When to Send an Error Response
An implementation MAY send an error response if:
- The session or handshake state is still valid
- Sending the response does not leak sensitive information
- The response cannot be abused for amplification or probing
An implementation SHOULD suppress error responses when:
- AEAD decryption fails
- Replay is detected
- The peer appears unauthenticated or malicious
- Rate limits are exceeded
X.9.5 Logging Guidelines
Implementations SHOULD log all protocol errors locally with sufficient detail for diagnosis.
Recommended Log Fields
- Timestamp
- Error code
- Error category
- Session identifier (opaque)
- Direction (ingress/egress)
- Epoch and sequence (if applicable)
- Local action taken (drop, terminate, continue)
Sensitive material (keys, nonces, raw ciphertext) MUST NOT be logged.
X.9.6 Metrics and Observability
Implementations MAY expose metrics such as:
- Total errors by category
- Fatal vs. non-fatal error counts
- Error rates over time
- Replay detections
- Decryption failures
Metrics SHOULD be aggregated and MUST NOT expose per-packet identifiers.
X.9.7 Rate Limiting
Error responses SHOULD be rate-limited to prevent:
- Reflection attacks
- Amplification attacks
- Error-based probing
Recommended strategies:
- Token bucket per peer
- Global error response cap per second
- Silent drop once limits are exceeded
X.9.8 Error Handling Flow (Illustrative)
Example high-level flow:
receive_packet()
↓
parse_headers()
↓
decrypt_or_verify()
↓
if error:
classify_error()
log_error()
if fatal:
erase_keys()
terminate_session()
if disclosure_allowed:
send_error_response()
returnX.9.9 Duplicate and Replay Errors
For replayed or duplicate packets:
- Implementations SHOULD drop silently
- Implementations MAY increment replay counters
- Implementations SHOULD NOT send error responses
This prevents replay detection from becoming a timing or oracle signal.
X.9.10 Interaction with Handshake Errors
During the handshake phase:
- Some errors (e.g., version mismatch) may be safely reported
- Others (e.g., signature failure) SHOULD use generic messages
- Handshake errors SHOULD NOT reveal which specific check failed
X.9.11 Testing Recommendations
Implementations SHOULD include tests for:
- Fatal error termination paths
- Suppression of error responses on cryptographic failure
- Rate-limited error generation
- Replay detection without disclosure
- Compatibility with unknown error codes
Unknown error codes SHOULD be treated as fatal unless explicitly specified otherwise.
X.9.12 Forward Compatibility
Implementations SHOULD:
- Ignore unknown error codes safely
- Log unknown codes for analysis
- Avoid assuming contiguous or complete error code ranges
This ensures interoperability with future PALISADE versions.
X.9.13 Summary
This appendix provides guidance for:
- Safe, consistent error handling
- Preventing side-channel leakage
- Maintaining operational observability
- Supporting robust, production-grade deployments
The authoritative protocol behavior remains defined in Section 15.
X.10 Control Frame Implementation (Non-Normative)
This appendix provides implementation guidance for processing PALISADE control frames. It does not define wire formats or mandatory protocol behavior. All normative requirements are specified in Section 14.
X.10.1 Control Frame Handling Overview
Control frames are carried inside encrypted packets and are indistinguishable from data packets on the wire until decryption.
Recommended processing pipeline:
- Decrypt packet using normal AEAD path
- Inspect
PublicHeader.flags - If
control_framebit set:- parse payload as ControlFrame
- dispatch by
ctrl_type
- Apply control-specific logic
Control frames benefit from the same authentication, confidentiality, and replay protection as normal packets.
X.10.2 Control Frame Dispatch Model
Implementations SHOULD dispatch control frames through a centralized handler:
def handle_control_frame(frame):
if frame.type == CTRL_REKEY:
handle_rekey(frame)
elif frame.type == CTRL_MIGRATE:
handle_migration(frame)
elif frame.type == CTRL_CLOSE:
handle_close(frame)
elif frame.type == CTRL_ACK:
handle_ack(frame)
else:
ignore_or_log(frame)Unknown control types SHOULD be ignored safely unless explicitly enabled.
X.10.3 Optional Control Acknowledgments (ACKs)
Control-frame acknowledgments are optional and intended for deployments that require:
- Reliable control delivery
- Auditable state transitions
- High-loss or unstable networks
ACK support SHOULD be configurable per deployment.
X.10.4 Control Frame Sequencing (ACK-Enabled Mode)
When ACKs are enabled, implementations SHOULD:
- Maintain a separate control-frame sequence counter
- Use a 64-bit monotonically increasing counter
- Increment once per transmitted control frame
- Never wrap the counter
Sequence counters are per-session and per-direction.
X.10.5 Pending-Frame Tracking
ACK-enabled implementations SHOULD maintain a pending-frame table:
PendingFrame {
ctrl_type
sequence_number
send_timestamp
retry_count
}This table enables retransmission and duplicate suppression.
X.10.6 ACK Sender State Machine (Recommended)
Send Control Frame
↓
Insert into pending table
↓
Start retransmission timer
↓
Wait for ACK / NAK / DEFERHandling outcomes:
- ACK → Remove from pending (success)
- NAK → Remove from pending (failure)
- DEFER → Extend timer, keep pending
- Timeout → Retransmit (up to retry limit)
After exceeding retry limits, implementations SHOULD log failure and abandon the frame.
X.10.7 Duplicate Detection (Receiver Side)
To handle retransmissions safely, receivers SHOULD:
- Track recently processed control-frame sequence numbers
- Detect duplicates
- Re-emit the original ACK if a duplicate is received
This ensures idempotent processing even under packet loss.
X.10.8 Retransmission Parameters (Recommended Defaults)
Suggested tuning values:
| Parameter | Value |
|---|---|
| Initial timeout | 2 seconds |
| Retries | 3 |
| Backoff | Exponential |
| Max wait | ~14 seconds |
These are guidelines, not protocol limits.
X.10.9 ACK Handling Logic (Illustrative)
def on_control_frame(frame):
if is_duplicate(frame.seq):
send_ack(frame, STATUS_ACK)
return
try:
process(frame)
mark_processed(frame.seq)
send_ack(frame, STATUS_ACK)
except ValidationError:
send_ack(frame, STATUS_NAK)
except BusyError:
send_ack(frame, STATUS_DEFER)X.10.10 Compatibility Considerations
Implementations SHOULD ensure:
- ACK-enabled nodes accept non-sequenced control frames
- Non-ACK nodes silently ignore
CTRL_ACKframes - Mixed deployments interoperate without negotiation
ACK support MUST NOT be required for baseline protocol correctness.
X.10.11 Logging and Telemetry (Optional)
Implementations MAY log:
- Control-frame sends
- ACK / NAK / DEFER events
- Retransmissions
- Control-frame failures
Logs SHOULD be rate-limited and MUST NOT leak sensitive material.
X.10.12 Security Considerations
Implementation choices MUST preserve:
- Replay safety (no double-processing)
- Bounded resource use (no unbounded pending frames)
- Idempotent state transitions
- Resistance to ACK-based amplification attacks
ACKs improve reliability but increase complexity; deploy only where justified.
X.11 Ticket and Cookie Key Rotation Practices
Many deployments rotate ticket and cookie encryption keys periodically to reduce the impact of compromise and limit the validity window of replayable artifacts.
Common operational patterns include:
- Maintaining a small number of active keys (e.g., current and previous).
- Issuing new tickets or cookies using only the current key.
- Accepting artifacts encrypted with either active key.
- Securely erasing keys after a defined retention window.
Rotation intervals vary by deployment, but shorter intervals reduce exposure at the cost of increased operational complexity.
X.12 Deployment Profiles and Tradeoffs
Different deployment environments impose different security and operational constraints.
High-Assurance Deployments
- Prefer explicit-memory languages for key-handling code.
- Minimize reliance on garbage collection.
- Isolate cryptographic operations where possible.
- Favor deterministic key lifetimes and erasure.
Managed Runtime Deployments
- Accept best-effort erasure guarantees.
- Minimize key lifetime and object retention.
- Avoid unnecessary serialization or logging of sensitive data.
- Clearly document residual risks associated with memory management.
Hybrid Approaches
Some deployments combine approaches, using explicit-memory components for cryptographic operations and higher-level languages for protocol orchestration. This approach can balance strong security guarantees with development velocity.
X.13 Summary
This appendix provides practical guidance for implementing PALISADE securely across a range of programming environments. While the core specification defines required security properties, correct implementation requires careful attention to memory handling, key lifetime, and operational practices.
Implementers are encouraged to assess their deployment context and choose techniques that appropriately balance security, performance, and complexity.
PALISADE Protocol Specification Draft 00
INFORMATIONAL