Documentation
¶
Overview ¶
Package tesla implements a TESLA-inspired hash-chain key schedule for delayed disclosure of packet authentication keys.
Key Schedule Overview ¶
Keys form a backward hash chain: the executor generates a random secret seed k_L (the tail) and hashes it L times to obtain the public anchor k_0:
k_0 = H^L(k_L) (public anchor, published at setup)
k_i = H(k_{i+1}) (each key is the hash of the next one)
The executor uses key k_i during epoch i (1 ≤ i < L) and discloses it after a configurable disclosure delay d has elapsed. Because k_0 is public, epoch 0 has no signing key: nothing is tagged before epoch 1 starts. Because k_L is never disclosed, nothing is tagged from epoch L on either. A verifier who has buffered packets from epoch i can verify them once k_i is published by checking:
H^i(k_i) == k_0
Given a disclosed key k_τ, the key for an earlier epoch t (t ≤ τ) is reconstructed by hashing forward τ-t times:
k_t = H^(τ-t)(k_τ)
Per-Measurement Derivation ¶
Because a single executor may service multiple measurements concurrently, an additional per-measurement key ak is derived from the current chain key:
ak = HKDF-SHA256(secret=k_i, info=measurement_id, length=32)
Authentication Tag ¶
The authentication tag written into the IPv4 IPID field is:
tag = SipHash-2-4(k0 || k1 = ak[0:16], packet[0:64])[0:2] (16-bit truncation)
with the packet's IPID and checksum fields zeroed. The eBPF tagger (tagger.c) and the pure-Go fallback compute it identically; see ComputeTag.
Index ¶
- Constants
- func ChainSeed(seed []byte, generation int64) ([]byte, error)
- func ComputeTag(ak, payload []byte) (uint16, error)
- func DeriveAK(k []byte, measurementID []byte) ([]byte, error)
- func DeriveFromDisclosed(disclosedKey []byte, disclosedEpoch, targetEpoch int64) ([]byte, error)
- func VerifyChain(anchor, key []byte, t int64) bool
- func VerifyTag(disclosedKey []byte, epoch int64, measurementID, packet []byte, tag uint16) (bool, error)
- type Config
- type KeySchedule
- func (ks *KeySchedule) Anchor() []byte
- func (ks *KeySchedule) ChainLength() int64
- func (ks *KeySchedule) ComputeTagForPacket(t time.Time, measurementID, payload []byte) (uint16, error)
- func (ks *KeySchedule) Config() Config
- func (ks *KeySchedule) CurrentKey(t time.Time) []byte
- func (ks *KeySchedule) DisclosedKey(t time.Time) (index int64, key []byte, ok bool)
- func (ks *KeySchedule) Exhausted(t time.Time) bool
- func (ks *KeySchedule) Expiry() time.Time
- func (ks *KeySchedule) KeyAtEpoch(epoch int64) ([]byte, error)
Constants ¶
const DefaultChainHorizon = 7 * 24 * time.Hour
DefaultChainHorizon is the uptime a default-sized chain covers. The default ChainLength is DefaultChainHorizon/Delay, so a longer epoch gives a shorter chain rather than a shorter usable lifetime.
Variables ¶
This section is empty.
Functions ¶
func ChainSeed ¶
ChainSeed derives the tail k_L of one chain from a configured seed:
k_L = HKDF-SHA256(secret=seed, info="debuglet tesla chain <generation>", length=32)
Each generation of the same seed yields a different chain, so a restart with an unchanged seed does not reuse keys an earlier chain disclosed.
func ComputeTag ¶
ComputeTag computes the 16-bit attribution tag both taggers write into the IPv4 Identification field: SipHash-2-4 keyed with ak over at most the first 64 bytes of the packet, as tagger.c computes it in the kernel. It does not canonicalize the packet; the caller zeroes the mutable header fields.
func DeriveAK ¶
DeriveAK computes the per-measurement authentication key:
ak = HKDF-SHA256(secret=k, info=measurementID, length=32)
measurementID should be the raw bytes of the UUID (or any stable byte representation of the measurement identifier).
func DeriveFromDisclosed ¶
DeriveFromDisclosed reconstructs the key at targetEpoch given a disclosed key at disclosedEpoch. Because the chain runs backward (k_i = H(k_{i+1})), a verifier can only derive keys at epochs ≤ disclosedEpoch by hashing the disclosed key forward (disclosedEpoch → targetEpoch, t ≤ disclosedEpoch):
k_t = H^(disclosedEpoch - t)(k_disclosedEpoch)
To reconstruct a later (newer) key you would need an even newer disclosed key, since the chain cannot be inverted.
func VerifyChain ¶
VerifyChain checks that H^t(k_t) == k_0 (the public anchor). A verifier calls this after reconstructing k_t to confirm its authenticity.
func VerifyTag ¶
func VerifyTag(disclosedKey []byte, epoch int64, measurementID, packet []byte, tag uint16) (bool, error)
VerifyTag checks whether tag matches the expected tag for the packet at a given epoch, given the disclosed key for that epoch and the measurement ID. A packet that begins with an IPv4 header has its IPID and checksum fields zeroed before hashing, as both taggers do.
Types ¶
type Config ¶
type Config struct {
// Seed is the secret tail k_L of the hash chain. If nil, a random
// 32-byte seed is generated on the first call to NewKeySchedule.
// The seed is NEVER disclosed; it is only used internally to derive
// keys by hashing backwards toward k_0.
Seed []byte
// ChainLength L is the total number of epochs supported by this
// schedule. The keys k_0 … k_L are generated; k_0 is the public
// anchor and k_L is derived from the seed.
//
// If zero, it is derived from Delay so that the chain covers
// DefaultChainHorizon of uptime. Sizing this from a wall-clock horizon
// matters: k_L is never disclosed, so the last signing epoch is L-1 and
// from the start of epoch L (Expiry) the schedule has no signing key and
// nothing is tagged.
ChainLength int64
// Delay is the interval duration I (one epoch). A key disclosed after
// the disclosure delay d (expressed as a number of epochs) has elapsed.
// Defaults to 10 seconds.
Delay time.Duration
// Epoch is the reference wall-clock time that anchors epoch 0.
// Defaults to the time NewKeySchedule is called.
Epoch time.Time
}
Config carries the tuneable parameters of the TESLA key schedule.
type KeySchedule ¶
type KeySchedule struct {
// contains filtered or unexported fields
}
KeySchedule is a thread-safe TESLA hash-chain key schedule.
Keys are indexed by epoch number t where t ∈ [0, L]:
epoch t covers the time interval [Epoch + t*Delay, Epoch + (t+1)*Delay).
The chain direction is backward: k_0 is the public anchor and k_L is the private seed tail. Key k_t is used during epoch t and disclosed after the disclosure delay d has elapsed (i.e., once epoch t+d has started).
k_0 is public from setup, so it never signs: epoch 0, and any instant before Epoch, has no usable signing key. The first usable key is k_1 at Epoch+Delay and the last is k_{L-1}; from Expiry, the start of epoch L, no key signs.
func NewKeySchedule ¶
func NewKeySchedule(cfg Config) (*KeySchedule, error)
NewKeySchedule creates a KeySchedule from cfg.
The chain direction is backward (k_i = H(k_{i+1})). The private seed k_L is stored internally and the public anchor k_0 = H^L(seed) is computed once.
If cfg.Seed is empty a cryptographically random 32-byte seed is generated. A seed that is not exactly 32 bytes long is folded through SHA-256 so that k_L is always one key wide. If cfg.Delay is zero it defaults to 10 seconds. If cfg.ChainLength is zero it is derived from Delay (see DefaultChainHorizon).
func (*KeySchedule) Anchor ¶
func (ks *KeySchedule) Anchor() []byte
Anchor returns k_0, the public anchor that should be published at setup. Verifiers use it to check consistency: H^t(k_t) == k_0.
func (*KeySchedule) ChainLength ¶
func (ks *KeySchedule) ChainLength() int64
ChainLength returns L, the highest epoch this schedule can serve.
func (*KeySchedule) ComputeTagForPacket ¶
func (ks *KeySchedule) ComputeTagForPacket(t time.Time, measurementID, payload []byte) (uint16, error)
ComputeTagForPacket derives ak from the chain key at time t and then computes the tag over payload. It fails while no chain key is usable.
func (*KeySchedule) Config ¶
func (ks *KeySchedule) Config() Config
Config returns a copy of the schedule's configuration.
func (*KeySchedule) CurrentKey ¶
func (ks *KeySchedule) CurrentKey(t time.Time) []byte
CurrentKey returns the chain key k_t for the epoch that contains time t, or nil while no key is usable: epochOf maps epoch 0 and any instant before Epoch to 0, whose key is the public anchor (see KeySchedule), and every instant from the start of epoch L to L, whose key k_L is never disclosed. Every signing path reads the key here, so this is the one place the rule is decided.
func (*KeySchedule) DisclosedKey ¶
DisclosedKey returns the epoch index and key that should be disclosed at time t. A key for epoch τ is disclosed once d disclosure-delay epochs have elapsed after τ. With d=1, the key for epoch (current−1) is disclosed when epoch current starts.
If t is still within epoch 0 (no key is disclosable yet), ok is false. From epoch L on the disclosed key stays k_{L-1}, the last signing key; k_L is never disclosed.
func (*KeySchedule) Exhausted ¶
func (ks *KeySchedule) Exhausted(t time.Time) bool
Exhausted reports whether time t falls at or past the end of the chain, the start of epoch L. k_L is never disclosed, so an exhausted schedule has no signing key and CurrentKey returns nil from then on.
func (*KeySchedule) Expiry ¶
func (ks *KeySchedule) Expiry() time.Time
Expiry returns the wall-clock time at which the chain runs out.
func (*KeySchedule) KeyAtEpoch ¶
func (ks *KeySchedule) KeyAtEpoch(epoch int64) ([]byte, error)
KeyAtEpoch returns the chain key for the given epoch index.