This commit is contained in:
Sudo-Ivan
2025-01-01 03:12:26 -06:00
parent 3ffd5b72a1
commit 6cdc02346f
6 changed files with 522 additions and 186 deletions

View File

@@ -43,7 +43,7 @@ const (
DEBUG_VERBOSE = 4 // Detailed information DEBUG_VERBOSE = 4 // Detailed information
DEBUG_TRACE = 5 // Very detailed tracing DEBUG_TRACE = 5 // Very detailed tracing
DEBUG_PACKETS = 6 // Packet-level details DEBUG_PACKETS = 6 // Packet-level details
DEBUG_ALL = 7 // Everything DEBUG_ALL = 7 // Everything including identity operations
) )
type Reticulum struct { type Reticulum struct {
@@ -336,11 +336,10 @@ func (tw *transportWrapper) GetStatus() int {
func (tw *transportWrapper) Send(data []byte) interface{} { func (tw *transportWrapper) Send(data []byte) interface{} {
p := &packet.Packet{ p := &packet.Packet{
Header: [2]byte{ PacketType: packet.PacketTypeData,
packet.PacketTypeData, // First byte Hops: 0,
0, // Second byte (hops) Data: data,
}, HeaderType: packet.HeaderType1,
Data: data,
} }
err := tw.Transport.SendPacket(p) err := tw.Transport.SendPacket(p)
@@ -490,14 +489,27 @@ func (r *Reticulum) Stop() error {
} }
func (r *Reticulum) handleAnnounce(data []byte, iface common.NetworkInterface) { func (r *Reticulum) handleAnnounce(data []byte, iface common.NetworkInterface) {
debugLog(2, "Received announce packet on interface %s (%d bytes)", iface.GetName(), len(data)) debugLog(DEBUG_INFO, "Received announce packet on interface %s (%d bytes)", iface.GetName(), len(data))
a := &announce.Announce{} a := &announce.Announce{}
if err := a.HandleAnnounce(data); err != nil { if err := a.HandleAnnounce(data); err != nil {
debugLog(1, "Error handling announce: %v", err) debugLog(DEBUG_ERROR, "Error handling announce: %v", err)
return return
} }
// Log announce details
debugLog(DEBUG_ALL, "Announce details:")
debugLog(DEBUG_ALL, " Hash: %x", a.Hash())
// Get fields using packet data
packet := a.GetPacket()
if len(packet) > 2 {
destHash := packet[2:18]
hops := packet[50]
debugLog(DEBUG_ALL, " Destination Hash: %x", destHash)
debugLog(DEBUG_ALL, " Hops: %d", hops)
}
// Check announce history // Check announce history
announceKey := fmt.Sprintf("%x", a.Hash()) announceKey := fmt.Sprintf("%x", a.Hash())
r.announceHistoryMu.Lock() r.announceHistoryMu.Lock()
@@ -581,15 +593,29 @@ func (h *AnnounceHandler) AspectFilter() []string {
return h.aspectFilter return h.aspectFilter
} }
func (h *AnnounceHandler) ReceivedAnnounce(destHash []byte, identity interface{}, appData []byte) error { func (h *AnnounceHandler) ReceivedAnnounce(destHash []byte, id interface{}, appData []byte) error {
debugLog(3, "Received announce from %x", destHash) debugLog(DEBUG_INFO, "Received announce from %x", destHash)
if len(appData) > 0 { if len(appData) > 0 {
debugLog(3, "Announce contained app data: %s", string(appData)) debugLog(DEBUG_VERBOSE, "Announce app data: %s", string(appData))
} }
if id, ok := identity.([]byte); ok { // Type assert using the package path
debugLog(4, "Identity: %x", id) if identity, ok := id.(*identity.Identity); ok {
debugLog(DEBUG_ALL, "Identity details:")
debugLog(DEBUG_ALL, " Hash: %s", identity.GetHexHash())
debugLog(DEBUG_ALL, " Public Key: %x", identity.GetPublicKey())
ratchets := identity.GetRatchets()
debugLog(DEBUG_ALL, " Active Ratchets: %d", len(ratchets))
if len(ratchets) > 0 {
ratchetKey := identity.GetCurrentRatchetKey()
if ratchetKey != nil {
ratchetID := identity.GetRatchetID(ratchetKey)
debugLog(DEBUG_ALL, " Current Ratchet ID: %x", ratchetID)
}
}
} }
return nil return nil

View File

@@ -50,7 +50,7 @@ type Destination struct {
destType byte destType byte
appName string appName string
aspects []string aspects []string
hash []byte hashValue []byte
acceptsLinks bool acceptsLinks bool
proofStrategy byte proofStrategy byte
@@ -95,12 +95,12 @@ func New(id *identity.Identity, direction byte, destType byte, appName string, a
} }
// Generate destination hash // Generate destination hash
d.hash = d.Hash() d.hashValue = d.calculateHash()
return d, nil return d, nil
} }
func (d *Destination) Hash() []byte { func (d *Destination) calculateHash() []byte {
nameHash := sha256.Sum256([]byte(d.ExpandName())) nameHash := sha256.Sum256([]byte(d.ExpandName()))
identityHash := sha256.Sum256(d.identity.GetPublicKey()) identityHash := sha256.Sum256(d.identity.GetPublicKey())
@@ -134,8 +134,8 @@ func (d *Destination) Announce(appData []byte) error {
packet := make([]byte, 0) packet := make([]byte, 0)
// Add destination hash // Add destination hash
packet = append(packet, d.hash...) packet = append(packet, d.hashValue...)
log.Printf("[DEBUG-4] Added destination hash %x to announce", d.hash[:8]) log.Printf("[DEBUG-4] Added destination hash %x to announce", d.hashValue[:8])
// Add identity public key // Add identity public key
pubKey := d.identity.GetPublicKey() pubKey := d.identity.GetPublicKey()
@@ -386,3 +386,21 @@ func (d *Destination) GetPublicKey() []byte {
func (d *Destination) GetIdentity() *identity.Identity { func (d *Destination) GetIdentity() *identity.Identity {
return d.identity return d.identity
} }
func (d *Destination) GetType() byte {
return d.destType
}
func (d *Destination) GetHash() []byte {
d.mutex.RLock()
defer d.mutex.RUnlock()
if d.hashValue == nil {
d.mutex.RUnlock()
d.mutex.Lock()
defer d.mutex.Unlock()
if d.hashValue == nil {
d.hashValue = d.calculateHash()
}
}
return d.hashValue
}

View File

@@ -17,6 +17,8 @@ import (
"encoding/hex" "encoding/hex"
"log"
"github.com/Sudo-Ivan/reticulum-go/pkg/common" "github.com/Sudo-Ivan/reticulum-go/pkg/common"
"golang.org/x/crypto/curve25519" "golang.org/x/crypto/curve25519"
"golang.org/x/crypto/hkdf" "golang.org/x/crypto/hkdf"
@@ -35,6 +37,9 @@ const (
AES128_BLOCKSIZE = 16 AES128_BLOCKSIZE = 16
HASHLENGTH = 256 HASHLENGTH = 256
SIGLENGTH = KEYSIZE SIGLENGTH = KEYSIZE
RATCHET_ROTATION_INTERVAL = 1800 // Default 30 minutes in seconds
MAX_RETAINED_RATCHETS = 512 // Maximum number of retained ratchet keys
) )
type Identity struct { type Identity struct {
@@ -116,28 +121,33 @@ func New() (*Identity, error) {
i := &Identity{ i := &Identity{
ratchets: make(map[string][]byte), ratchets: make(map[string][]byte),
ratchetExpiry: make(map[string]int64), ratchetExpiry: make(map[string]int64),
mutex: &sync.RWMutex{},
} }
// Generate X25519 key pair // Generate X25519 key pair
i.privateKey = make([]byte, curve25519.ScalarSize) i.privateKey = make([]byte, curve25519.ScalarSize)
if _, err := io.ReadFull(rand.Reader, i.privateKey); err != nil { if _, err := io.ReadFull(rand.Reader, i.privateKey); err != nil {
log.Printf("[DEBUG-1] Failed to generate X25519 private key: %v", err)
return nil, err return nil, err
} }
var err error var err error
i.publicKey, err = curve25519.X25519(i.privateKey, curve25519.Basepoint) i.publicKey, err = curve25519.X25519(i.privateKey, curve25519.Basepoint)
if err != nil { if err != nil {
log.Printf("[DEBUG-1] Failed to generate X25519 public key: %v", err)
return nil, err return nil, err
} }
// Generate Ed25519 signing keypair // Generate Ed25519 signing keypair
pubKey, privKey, err := ed25519.GenerateKey(rand.Reader) pubKey, privKey, err := ed25519.GenerateKey(rand.Reader)
if err != nil { if err != nil {
log.Printf("[DEBUG-1] Failed to generate Ed25519 keypair: %v", err)
return nil, err return nil, err
} }
i.signingKey = privKey i.signingKey = privKey
i.verificationKey = pubKey i.verificationKey = pubKey
log.Printf("[DEBUG-7] Created new identity with hash: %x", i.Hash())
return i, nil return i, nil
} }
@@ -162,9 +172,15 @@ func (i *Identity) Verify(data []byte, signature []byte) bool {
func (i *Identity) Encrypt(plaintext []byte, ratchet []byte) ([]byte, error) { func (i *Identity) Encrypt(plaintext []byte, ratchet []byte) ([]byte, error) {
if i.publicKey == nil { if i.publicKey == nil {
log.Printf("[DEBUG-1] Encryption failed: identity has no public key")
return nil, errors.New("encryption failed: identity does not hold a public key") return nil, errors.New("encryption failed: identity does not hold a public key")
} }
log.Printf("[DEBUG-7] Starting encryption for identity %s", i.GetHexHash())
if ratchet != nil {
log.Printf("[DEBUG-7] Using ratchet for encryption")
}
// Generate ephemeral keypair // Generate ephemeral keypair
ephemeralPrivKey := make([]byte, curve25519.ScalarSize) ephemeralPrivKey := make([]byte, curve25519.ScalarSize)
if _, err := io.ReadFull(rand.Reader, ephemeralPrivKey); err != nil { if _, err := io.ReadFull(rand.Reader, ephemeralPrivKey); err != nil {
@@ -232,6 +248,7 @@ func (i *Identity) Encrypt(plaintext []byte, ratchet []byte) ([]byte, error) {
token = append(token, ciphertext...) token = append(token, ciphertext...)
token = append(token, mac...) token = append(token, mac...)
log.Printf("[DEBUG-7] Encryption completed successfully")
return token, nil return token, nil
} }
@@ -270,12 +287,11 @@ func Remember(packet []byte, destHash []byte, publicKey []byte, appData []byte)
func ValidateAnnounce(packet []byte, destHash []byte, publicKey []byte, signature []byte, appData []byte) bool { func ValidateAnnounce(packet []byte, destHash []byte, publicKey []byte, signature []byte, appData []byte) bool {
if len(publicKey) != KEYSIZE/8 { if len(publicKey) != KEYSIZE/8 {
log.Printf("[DEBUG-7] Invalid public key length: %d", len(publicKey))
return false return false
} }
if len(destHash) > TRUNCATED_HASHLENGTH/8 { log.Printf("[DEBUG-7] Validating announce for destination hash: %x", destHash)
destHash = destHash[:TRUNCATED_HASHLENGTH/8]
}
announced := &Identity{} announced := &Identity{}
announced.publicKey = publicKey[:KEYSIZE/16] announced.publicKey = publicKey[:KEYSIZE/16]
@@ -285,10 +301,12 @@ func ValidateAnnounce(packet []byte, destHash []byte, publicKey []byte, signatur
signedData = append(signedData, appData...) signedData = append(signedData, appData...)
if !announced.Verify(signedData, signature) { if !announced.Verify(signedData, signature) {
log.Printf("[DEBUG-7] Signature verification failed")
return false return false
} }
Remember(packet, destHash, publicKey, appData) Remember(packet, destHash, publicKey, appData)
log.Printf("[DEBUG-7] Announce validated and remembered successfully")
return true return true
} }
@@ -369,9 +387,15 @@ func (i *Identity) GetCurrentRatchetKey() []byte {
func (i *Identity) Decrypt(ciphertextToken []byte, ratchets [][]byte, enforceRatchets bool, ratchetIDReceiver *common.RatchetIDReceiver) ([]byte, error) { func (i *Identity) Decrypt(ciphertextToken []byte, ratchets [][]byte, enforceRatchets bool, ratchetIDReceiver *common.RatchetIDReceiver) ([]byte, error) {
if i.privateKey == nil { if i.privateKey == nil {
log.Printf("[DEBUG-1] Decryption failed: identity has no private key")
return nil, errors.New("decryption failed because identity does not hold a private key") return nil, errors.New("decryption failed because identity does not hold a private key")
} }
log.Printf("[DEBUG-7] Starting decryption for identity %s", i.GetHexHash())
if len(ratchets) > 0 {
log.Printf("[DEBUG-7] Attempting decryption with %d ratchets", len(ratchets))
}
if len(ciphertextToken) <= KEYSIZE/8/2 { if len(ciphertextToken) <= KEYSIZE/8/2 {
return nil, errors.New("decryption failed because the token size was invalid") return nil, errors.New("decryption failed because the token size was invalid")
} }
@@ -450,6 +474,7 @@ func (i *Identity) Decrypt(ciphertextToken []byte, ratchets [][]byte, enforceRat
ratchetIDReceiver.LatestRatchetID = nil ratchetIDReceiver.LatestRatchetID = nil
} }
log.Printf("[DEBUG-7] Decryption completed successfully")
return plaintext[:len(plaintext)-padding], nil return plaintext[:len(plaintext)-padding], nil
} }
@@ -461,13 +486,17 @@ func (i *Identity) tryRatchetDecryption(peerPubBytes, ciphertext, ratchet []byte
// Get ratchet ID // Get ratchet ID
ratchetPubBytes, err := curve25519.X25519(ratchetPriv, curve25519.Basepoint) ratchetPubBytes, err := curve25519.X25519(ratchetPriv, curve25519.Basepoint)
if err != nil { if err != nil {
log.Printf("[DEBUG-7] Failed to generate ratchet public key: %v", err)
return nil, nil, err return nil, nil, err
} }
ratchetID := i.GetRatchetID(ratchetPubBytes) ratchetID := i.GetRatchetID(ratchetPubBytes)
log.Printf("[DEBUG-7] Decrypting with ratchet ID: %x", ratchetID)
// Generate shared key // Generate shared key
sharedKey, err := curve25519.X25519(ratchetPriv, peerPubBytes) sharedKey, err := curve25519.X25519(ratchetPriv, peerPubBytes)
if err != nil { if err != nil {
log.Printf("[DEBUG-7] Failed to generate shared key: %v", err)
return nil, nil, err return nil, nil, err
} }
@@ -475,17 +504,20 @@ func (i *Identity) tryRatchetDecryption(peerPubBytes, ciphertext, ratchet []byte
hkdfReader := hkdf.New(sha256.New, sharedKey, i.GetSalt(), i.GetContext()) hkdfReader := hkdf.New(sha256.New, sharedKey, i.GetSalt(), i.GetContext())
derivedKey := make([]byte, 32) derivedKey := make([]byte, 32)
if _, err := io.ReadFull(hkdfReader, derivedKey); err != nil { if _, err := io.ReadFull(hkdfReader, derivedKey); err != nil {
log.Printf("[DEBUG-7] Failed to derive key: %v", err)
return nil, nil, err return nil, nil, err
} }
// Create AES cipher // Create AES cipher
block, err := aes.NewCipher(derivedKey) block, err := aes.NewCipher(derivedKey)
if err != nil { if err != nil {
log.Printf("[DEBUG-7] Failed to create cipher: %v", err)
return nil, nil, err return nil, nil, err
} }
// Extract IV and decrypt // Extract IV and decrypt
if len(ciphertext) < aes.BlockSize { if len(ciphertext) < aes.BlockSize {
log.Printf("[DEBUG-7] Ciphertext too short")
return nil, nil, errors.New("ciphertext too short") return nil, nil, errors.New("ciphertext too short")
} }
@@ -493,6 +525,7 @@ func (i *Identity) tryRatchetDecryption(peerPubBytes, ciphertext, ratchet []byte
actualCiphertext := ciphertext[aes.BlockSize:] actualCiphertext := ciphertext[aes.BlockSize:]
if len(actualCiphertext)%aes.BlockSize != 0 { if len(actualCiphertext)%aes.BlockSize != 0 {
log.Printf("[DEBUG-7] Ciphertext is not a multiple of block size")
return nil, nil, errors.New("ciphertext is not a multiple of block size") return nil, nil, errors.New("ciphertext is not a multiple of block size")
} }
@@ -504,15 +537,18 @@ func (i *Identity) tryRatchetDecryption(peerPubBytes, ciphertext, ratchet []byte
// Remove padding // Remove padding
padding := int(plaintext[len(plaintext)-1]) padding := int(plaintext[len(plaintext)-1])
if padding > aes.BlockSize || padding == 0 { if padding > aes.BlockSize || padding == 0 {
log.Printf("[DEBUG-7] Invalid padding")
return nil, nil, errors.New("invalid padding") return nil, nil, errors.New("invalid padding")
} }
for i := len(plaintext) - padding; i < len(plaintext); i++ { for i := len(plaintext) - padding; i < len(plaintext); i++ {
if plaintext[i] != byte(padding) { if plaintext[i] != byte(padding) {
log.Printf("[DEBUG-7] Invalid padding")
return nil, nil, errors.New("invalid padding") return nil, nil, errors.New("invalid padding")
} }
} }
log.Printf("[DEBUG-7] Decrypted successfully")
return plaintext[:len(plaintext)-padding], ratchetID, nil return plaintext[:len(plaintext)-padding], ratchetID, nil
} }
@@ -555,6 +591,8 @@ func (i *Identity) DecryptWithHMAC(data []byte, key []byte) ([]byte, error) {
} }
func (i *Identity) ToFile(path string) error { func (i *Identity) ToFile(path string) error {
log.Printf("[DEBUG-7] Saving identity %s to file: %s", i.GetHexHash(), path)
data := map[string]interface{}{ data := map[string]interface{}{
"private_key": i.privateKey, "private_key": i.privateKey,
"public_key": i.publicKey, "public_key": i.publicKey,
@@ -565,26 +603,36 @@ func (i *Identity) ToFile(path string) error {
file, err := os.Create(path) file, err := os.Create(path)
if err != nil { if err != nil {
log.Printf("[DEBUG-1] Failed to create identity file: %v", err)
return err return err
} }
defer file.Close() defer file.Close()
return json.NewEncoder(file).Encode(data) if err := json.NewEncoder(file).Encode(data); err != nil {
log.Printf("[DEBUG-1] Failed to encode identity data: %v", err)
return err
}
log.Printf("[DEBUG-7] Identity saved successfully")
return nil
} }
func RecallIdentity(path string) (*Identity, error) { func RecallIdentity(path string) (*Identity, error) {
log.Printf("[DEBUG-7] Attempting to recall identity from: %s", path)
file, err := os.Open(path) file, err := os.Open(path)
if err != nil { if err != nil {
log.Printf("[DEBUG-1] Failed to open identity file: %v", err)
return nil, err return nil, err
} }
defer file.Close() defer file.Close()
var data map[string]interface{} var data map[string]interface{}
if err := json.NewDecoder(file).Decode(&data); err != nil { if err := json.NewDecoder(file).Decode(&data); err != nil {
log.Printf("[DEBUG-1] Failed to decode identity data: %v", err)
return nil, err return nil, err
} }
// Reconstruct identity from saved data
id := &Identity{ id := &Identity{
privateKey: data["private_key"].([]byte), privateKey: data["private_key"].([]byte),
publicKey: data["public_key"].([]byte), publicKey: data["public_key"].([]byte),
@@ -593,8 +641,10 @@ func RecallIdentity(path string) (*Identity, error) {
appData: data["app_data"].([]byte), appData: data["app_data"].([]byte),
ratchets: make(map[string][]byte), ratchets: make(map[string][]byte),
ratchetExpiry: make(map[string]int64), ratchetExpiry: make(map[string]int64),
mutex: &sync.RWMutex{},
} }
log.Printf("[DEBUG-7] Successfully recalled identity with hash: %s", id.GetHexHash())
return id, nil return id, nil
} }
@@ -686,3 +736,98 @@ func NewIdentity() (*Identity, error) {
return i, nil return i, nil
} }
func (i *Identity) RotateRatchet() ([]byte, error) {
i.mutex.Lock()
defer i.mutex.Unlock()
log.Printf("[DEBUG-7] Rotating ratchet for identity %s", i.GetHexHash())
// Generate new ratchet key
newRatchet := make([]byte, RATCHETSIZE/8)
if _, err := io.ReadFull(rand.Reader, newRatchet); err != nil {
log.Printf("[DEBUG-1] Failed to generate new ratchet: %v", err)
return nil, err
}
// Get public key for ratchet ID
ratchetPub, err := curve25519.X25519(newRatchet, curve25519.Basepoint)
if err != nil {
log.Printf("[DEBUG-1] Failed to generate ratchet public key: %v", err)
return nil, err
}
ratchetID := i.GetRatchetID(ratchetPub)
expiry := time.Now().Unix() + RATCHET_EXPIRY
// Store new ratchet
i.ratchets[string(ratchetID)] = newRatchet
i.ratchetExpiry[string(ratchetID)] = expiry
log.Printf("[DEBUG-7] New ratchet generated with ID: %x, expiry: %d", ratchetID, expiry)
// Cleanup old ratchets if we exceed max retained
if len(i.ratchets) > MAX_RETAINED_RATCHETS {
var oldestID string
oldestTime := time.Now().Unix()
for id, exp := range i.ratchetExpiry {
if exp < oldestTime {
oldestTime = exp
oldestID = id
}
}
delete(i.ratchets, oldestID)
delete(i.ratchetExpiry, oldestID)
log.Printf("[DEBUG-7] Cleaned up oldest ratchet with ID: %x", []byte(oldestID))
}
log.Printf("[DEBUG-7] Current number of active ratchets: %d", len(i.ratchets))
return newRatchet, nil
}
func (i *Identity) GetRatchets() [][]byte {
i.mutex.RLock()
defer i.mutex.RUnlock()
log.Printf("[DEBUG-7] Getting ratchets for identity %s", i.GetHexHash())
ratchets := make([][]byte, 0, len(i.ratchets))
now := time.Now().Unix()
expired := 0
// Return only non-expired ratchets
for id, expiry := range i.ratchetExpiry {
if expiry > now {
ratchets = append(ratchets, i.ratchets[id])
} else {
// Clean up expired ratchets
delete(i.ratchets, id)
delete(i.ratchetExpiry, id)
expired++
}
}
log.Printf("[DEBUG-7] Retrieved %d active ratchets, cleaned up %d expired", len(ratchets), expired)
return ratchets
}
func (i *Identity) CleanupExpiredRatchets() {
i.mutex.Lock()
defer i.mutex.Unlock()
log.Printf("[DEBUG-7] Starting ratchet cleanup for identity %s", i.GetHexHash())
now := time.Now().Unix()
cleaned := 0
for id, expiry := range i.ratchetExpiry {
if expiry <= now {
delete(i.ratchets, id)
delete(i.ratchetExpiry, id)
cleaned++
}
}
log.Printf("[DEBUG-7] Cleaned up %d expired ratchets, %d remaining", cleaned, len(i.ratchets))
}

View File

@@ -5,7 +5,7 @@ import (
"crypto/cipher" "crypto/cipher"
"crypto/ed25519" "crypto/ed25519"
"crypto/rand" "crypto/rand"
"crypto/sha256" "encoding/hex"
"errors" "errors"
"fmt" "fmt"
"io" "io"
@@ -16,6 +16,8 @@ import (
"github.com/Sudo-Ivan/reticulum-go/pkg/destination" "github.com/Sudo-Ivan/reticulum-go/pkg/destination"
"github.com/Sudo-Ivan/reticulum-go/pkg/identity" "github.com/Sudo-Ivan/reticulum-go/pkg/identity"
"github.com/Sudo-Ivan/reticulum-go/pkg/packet" "github.com/Sudo-Ivan/reticulum-go/pkg/packet"
"github.com/Sudo-Ivan/reticulum-go/pkg/pathfinder"
"github.com/Sudo-Ivan/reticulum-go/pkg/resolver"
"github.com/Sudo-Ivan/reticulum-go/pkg/resource" "github.com/Sudo-Ivan/reticulum-go/pkg/resource"
"github.com/Sudo-Ivan/reticulum-go/pkg/transport" "github.com/Sudo-Ivan/reticulum-go/pkg/transport"
) )
@@ -38,10 +40,6 @@ const (
STATUS_CLOSED = 0x02 STATUS_CLOSED = 0x02
STATUS_FAILED = 0x03 STATUS_FAILED = 0x03
PACKET_TYPE_DATA = 0x00
PACKET_TYPE_LINK = 0x01
PACKET_TYPE_IDENTIFY = 0x02
PROVE_NONE = 0x00 PROVE_NONE = 0x00
PROVE_ALL = 0x01 PROVE_ALL = 0x01
PROVE_APP = 0x02 PROVE_APP = 0x02
@@ -56,6 +54,7 @@ type Link struct {
lastOutbound time.Time lastOutbound time.Time
lastDataReceived time.Time lastDataReceived time.Time
lastDataSent time.Time lastDataSent time.Time
pathFinder *pathfinder.PathFinder
remoteIdentity *identity.Identity remoteIdentity *identity.Identity
sessionKey []byte sessionKey []byte
@@ -97,6 +96,7 @@ func NewLink(dest *destination.Destination, transport *transport.Transport, esta
lastOutbound: time.Time{}, lastOutbound: time.Time{},
lastDataReceived: time.Time{}, lastDataReceived: time.Time{},
lastDataSent: time.Time{}, lastDataSent: time.Time{},
pathFinder: pathfinder.NewPathFinder(),
} }
} }
@@ -117,16 +117,21 @@ func (l *Link) Establish() error {
log.Printf("[DEBUG-4] Creating link request packet for destination %x", destPublicKey[:8]) log.Printf("[DEBUG-4] Creating link request packet for destination %x", destPublicKey[:8])
// Create link request packet p := &packet.Packet{
p, err := packet.NewPacket( HeaderType: packet.HeaderType1,
packet.PACKET_TYPE_LINK, PacketType: packet.PacketTypeLinkReq,
0x00, TransportType: 0,
0x00, Context: packet.ContextLinkIdentify,
destPublicKey, ContextFlag: packet.FlagUnset,
l.linkID, Hops: 0,
) DestinationType: l.destination.GetType(),
if err != nil { DestinationHash: l.destination.GetHash(),
log.Printf("[DEBUG-3] Failed to create link request packet: %v", err) Data: l.linkID,
CreateReceipt: true,
}
if err := p.Pack(); err != nil {
log.Printf("[DEBUG-3] Failed to pack link request packet: %v", err)
return err return err
} }
@@ -139,15 +144,20 @@ func (l *Link) Identify(id *identity.Identity) error {
return errors.New("link not active") return errors.New("link not active")
} }
// Create identify packet p := &packet.Packet{
p, err := packet.NewPacket( HeaderType: packet.HeaderType1,
packet.PACKET_TYPE_IDENTIFY, PacketType: packet.PacketTypeData,
0x00, TransportType: 0,
0x00, Context: packet.ContextLinkIdentify,
l.destination.GetPublicKey(), ContextFlag: packet.FlagUnset,
id.GetPublicKey(), Hops: 0,
) DestinationType: l.destination.GetType(),
if err != nil { DestinationHash: l.destination.GetHash(),
Data: id.GetPublicKey(),
CreateReceipt: true,
}
if err := p.Pack(); err != nil {
return err return err
} }
@@ -466,11 +476,28 @@ func (l *Link) SendPacket(data []byte) error {
return err return err
} }
p := &packet.Packet{
HeaderType: packet.HeaderType1,
PacketType: packet.PacketTypeData,
TransportType: 0,
Context: packet.ContextNone,
ContextFlag: packet.FlagUnset,
Hops: 0,
DestinationType: l.destination.GetType(),
DestinationHash: l.destination.GetHash(),
Data: encrypted,
CreateReceipt: false,
}
if err := p.Pack(); err != nil {
return err
}
log.Printf("[DEBUG-4] Sending encrypted packet of %d bytes", len(encrypted)) log.Printf("[DEBUG-4] Sending encrypted packet of %d bytes", len(encrypted))
l.lastOutbound = time.Now() l.lastOutbound = time.Now()
l.lastDataSent = time.Now() l.lastDataSent = time.Now()
return nil return l.transport.SendPacket(p)
} }
func (l *Link) HandleInbound(data []byte) error { func (l *Link) HandleInbound(data []byte) error {
@@ -482,55 +509,24 @@ func (l *Link) HandleInbound(data []byte) error {
return errors.New("link not active") return errors.New("link not active")
} }
log.Printf("[DEBUG-7] Received encrypted packet of %d bytes", len(data)) // Decode and log packet details
l.decodePacket(data)
// Decrypt data using session key // Decrypt if we have a session key
decryptedData, err := l.decrypt(data) if l.sessionKey != nil {
if err != nil { decrypted, err := l.decrypt(data)
log.Printf("[DEBUG-3] Failed to decrypt packet: %v", err) if err != nil {
return err log.Printf("[DEBUG-3] Failed to decrypt packet: %v", err)
} return err
// Split message and HMAC
if len(decryptedData) < sha256.Size {
log.Printf("[DEBUG-3] Received data too short: %d bytes", len(decryptedData))
return errors.New("received data too short")
}
message := decryptedData[:len(decryptedData)-sha256.Size]
messageHMAC := decryptedData[len(decryptedData)-sha256.Size:]
// Log packet details
log.Printf("[DEBUG-7] Decrypted packet details:")
log.Printf("[DEBUG-7] - Size: %d bytes", len(message))
log.Printf("[DEBUG-7] - First 16 bytes: %x", message[:min(16, len(message))])
if len(message) > 0 {
log.Printf("[DEBUG-7] - Type: 0x%02x", message[0])
switch message[0] {
case packet.PacketData:
log.Printf("[DEBUG-7] - Type: Data Packet")
case packet.PacketAnnounce:
log.Printf("[DEBUG-7] - Type: Announce Packet")
case packet.PacketLinkRequest:
log.Printf("[DEBUG-7] - Type: Link Request")
case packet.PacketProof:
log.Printf("[DEBUG-7] - Type: Proof Request")
default:
log.Printf("[DEBUG-7] - Type: Unknown (0x%02x)", message[0])
} }
} data = decrypted
// Verify HMAC
if !l.destination.GetIdentity().ValidateHMAC(l.hmacKey, message, messageHMAC) {
log.Printf("[DEBUG-3] Invalid HMAC for packet")
return errors.New("invalid message authentication code")
} }
l.lastInbound = time.Now() l.lastInbound = time.Now()
l.lastDataReceived = time.Now() l.lastDataReceived = time.Now()
if l.packetCallback != nil { if l.packetCallback != nil {
l.packetCallback(message, nil) l.packetCallback(data, nil)
} }
return nil return nil
@@ -737,6 +733,69 @@ func (l *Link) HandleProofRequest(packet *packet.Packet) bool {
} }
} }
func (l *Link) decodePacket(data []byte) {
if len(data) < 1 {
log.Printf("[DEBUG-7] Invalid packet: zero length")
return
}
packetType := data[0]
log.Printf("[DEBUG-7] Packet Analysis:")
log.Printf("[DEBUG-7] - Size: %d bytes", len(data))
log.Printf("[DEBUG-7] - Type: 0x%02x", packetType)
switch packetType {
case packet.PacketTypeData:
log.Printf("[DEBUG-7] - Type Description: Data Packet")
if len(data) > 1 {
log.Printf("[DEBUG-7] - Payload Size: %d bytes", len(data)-1)
}
case packet.PacketTypeLinkReq:
log.Printf("[DEBUG-7] - Type Description: Link Management")
if len(data) > 32 {
log.Printf("[DEBUG-7] - Link ID: %x", data[1:33])
}
case packet.PacketTypeAnnounce:
log.Printf("[DEBUG-7] - Type Description: RNS Announce")
if len(data) > 33 {
destHash := data[1:17]
pubKey := data[17:49]
log.Printf("[DEBUG-7] - Destination Hash: %x", destHash)
log.Printf("[DEBUG-7] - Public Key: %x", pubKey)
if len(data) > 81 {
signature := data[49:81]
appData := data[81:]
if identity.ValidateAnnounce(data, destHash, pubKey, signature, appData) {
log.Printf("[DEBUG-7] - Announce signature valid")
if path, ok := l.pathFinder.GetPath(hex.EncodeToString(destHash)); ok {
log.Printf("[DEBUG-7] - Updated path: Interface=%s, Hops=%d",
path.Interface, path.HopCount)
}
}
}
}
case packet.PacketTypeProof:
log.Printf("[DEBUG-7] - Type Description: RNS Discovery")
if len(data) > 17 {
searchHash := data[1:17]
log.Printf("[DEBUG-7] - Searching for Hash: %x", searchHash)
if id, err := resolver.ResolveIdentity(hex.EncodeToString(searchHash)); err == nil {
log.Printf("[DEBUG-7] - Found matching identity: %s", id.GetHexHash())
}
}
default:
log.Printf("[DEBUG-7] - Type Description: Unknown (0x%02x)", packetType)
log.Printf("[DEBUG-7] - Raw Hex: %x", data)
}
}
// Helper function for min of two ints // Helper function for min of two ints
func min(a, b int) int { func min(a, b int) int {
if a < b { if a < b {

View File

@@ -5,10 +5,6 @@ const (
EncryptedMDU = 383 // Maximum size of payload data in encrypted packet EncryptedMDU = 383 // Maximum size of payload data in encrypted packet
PlainMDU = 464 // Maximum size of payload data in unencrypted packet PlainMDU = 464 // Maximum size of payload data in unencrypted packet
// Header Types
HeaderType1 = 0 // Two byte header, one 16 byte address field
HeaderType2 = 1 // Two byte header, two 16 byte address fields
// Propagation Types // Propagation Types
PropagationBroadcast = 0 PropagationBroadcast = 0
PropagationTransport = 1 PropagationTransport = 1

View File

@@ -1,116 +1,208 @@
package packet package packet
import ( import (
"crypto/sha256"
"errors" "errors"
"fmt"
"time" "time"
) )
const ( const (
// Packet Types // Packet Types
PacketTypeData = 0x00 PacketTypeData = 0x00
PacketTypeAnnounce = 0x01 PacketTypeAnnounce = 0x01
PacketTypeLink = 0x02 PacketTypeLinkReq = 0x02
PacketTypeProof = 0x03 PacketTypeProof = 0x03
PACKET_TYPE_DATA = 0x00
PACKET_TYPE_LINK = 0x01
PACKET_TYPE_IDENTIFY = 0x02
// Sizes // Header Types
HeaderSize = 2 HeaderType1 = 0x00
AddressSize = 16 HeaderType2 = 0x01
ContextSize = 1
MaxDataSize = 465 // Context Types
RandomBlobSize = 16 ContextNone = 0x00
ContextResource = 0x01
ContextResourceAdv = 0x02
ContextResourceReq = 0x03
ContextResourceHMU = 0x04
ContextResourcePRF = 0x05
ContextResourceICL = 0x06
ContextResourceRCL = 0x07
ContextCacheReq = 0x08
ContextRequest = 0x09
ContextResponse = 0x0A
ContextPathResponse = 0x0B
ContextCommand = 0x0C
ContextCmdStatus = 0x0D
ContextChannel = 0x0E
ContextKeepalive = 0xFA
ContextLinkIdentify = 0xFB
ContextLinkClose = 0xFC
ContextLinkProof = 0xFD
ContextLRRTT = 0xFE
ContextLRProof = 0xFF
// Flag Values
FlagSet = 0x01
FlagUnset = 0x00
// Header sizes
HeaderMaxSize = 64
MTU = 500
AddressSize = 32 // Size of address/hash fields in bytes
) )
// Header flags and types
const (
// First byte flags
IFACFlag = 0x80
HeaderTypeFlag = 0x40
ContextFlag = 0x20
PropagationFlags = 0x18
DestinationFlags = 0x06
PacketTypeFlags = 0x01
// Second byte
HopsField = 0xFF
)
// Packet represents a network packet in the Reticulum protocol
type Packet struct { type Packet struct {
Header [2]byte HeaderType byte
Addresses []byte PacketType byte
Context byte TransportType byte
Data []byte Context byte
AccessCode []byte ContextFlag byte
RandomBlob []byte Hops byte
Timestamp time.Time
DestinationType byte
DestinationHash []byte
TransportID []byte
Data []byte
Raw []byte
Packed bool
Sent bool
CreateReceipt bool
FromPacked bool
SentAt time.Time
PacketHash []byte
RatchetID []byte
RSSI *float64
SNR *float64
Q *float64
Addresses []byte // Add this field for address storage
} }
// NewPacket creates a new packet with the specified parameters func NewPacket(destType byte, data []byte, packetType byte, context byte,
func NewPacket(packetType byte, flags byte, hops byte, destKey []byte, data []byte) (*Packet, error) { transportType byte, headerType byte, transportID []byte, createReceipt bool,
if len(destKey) != AddressSize { contextFlag byte) *Packet {
return nil, errors.New("invalid destination key length")
return &Packet{
HeaderType: headerType,
PacketType: packetType,
TransportType: transportType,
Context: context,
ContextFlag: contextFlag,
Hops: 0,
DestinationType: destType,
Data: data,
TransportID: transportID,
CreateReceipt: createReceipt,
Packed: false,
Sent: false,
FromPacked: false,
} }
p := &Packet{
Header: [2]byte{flags, hops},
Addresses: make([]byte, AddressSize),
Data: data,
Timestamp: time.Now(),
}
// Set packet type in flags
p.Header[0] |= packetType & PacketTypeFlags
// Copy destination address
copy(p.Addresses, destKey)
return p, nil
} }
// Serialize converts the packet into a byte slice func (p *Packet) Pack() error {
if p.Packed {
return nil
}
flags := (p.HeaderType << 6) | (p.ContextFlag << 5) |
(p.TransportType << 4) | (p.DestinationType << 2) | p.PacketType
header := make([]byte, 0)
header = append(header, flags)
header = append(header, p.Hops)
if p.HeaderType == HeaderType2 && p.TransportID != nil {
header = append(header, p.TransportID...)
header = append(header, p.DestinationHash...)
} else if p.HeaderType == HeaderType1 {
header = append(header, p.DestinationHash...)
} else {
return errors.New("invalid header configuration")
}
header = append(header, p.Context)
p.Raw = append(header, p.Data...)
if len(p.Raw) > MTU {
return errors.New("packet size exceeds MTU")
}
p.Packed = true
p.updateHash()
return nil
}
func (p *Packet) Unpack() error {
if len(p.Raw) < 3 {
return errors.New("packet too short")
}
flags := p.Raw[0]
p.Hops = p.Raw[1]
p.HeaderType = (flags & 0b01000000) >> 6
p.ContextFlag = (flags & 0b00100000) >> 5
p.TransportType = (flags & 0b00010000) >> 4
p.DestinationType = (flags & 0b00001100) >> 2
p.PacketType = flags & 0b00000011
dstLen := 16 // Truncated hash length
if p.HeaderType == HeaderType2 {
if len(p.Raw) < 2*dstLen+3 {
return errors.New("packet too short for header type 2")
}
p.TransportID = p.Raw[2 : dstLen+2]
p.DestinationHash = p.Raw[dstLen+2 : 2*dstLen+2]
p.Context = p.Raw[2*dstLen+2]
p.Data = p.Raw[2*dstLen+3:]
} else {
if len(p.Raw) < dstLen+3 {
return errors.New("packet too short for header type 1")
}
p.TransportID = nil
p.DestinationHash = p.Raw[2 : dstLen+2]
p.Context = p.Raw[dstLen+2]
p.Data = p.Raw[dstLen+3:]
}
p.Packed = false
p.updateHash()
return nil
}
func (p *Packet) GetHash() []byte {
hashable := p.getHashablePart()
hash := sha256.Sum256(hashable)
return hash[:]
}
func (p *Packet) getHashablePart() []byte {
hashable := []byte{p.Raw[0] & 0b00001111}
if p.HeaderType == HeaderType2 {
hashable = append(hashable, p.Raw[18:]...)
} else {
hashable = append(hashable, p.Raw[2:]...)
}
return hashable
}
func (p *Packet) updateHash() {
p.PacketHash = p.GetHash()
}
func (p *Packet) Serialize() ([]byte, error) { func (p *Packet) Serialize() ([]byte, error) {
totalSize := HeaderSize + len(p.Addresses) + ContextSize + len(p.Data) if !p.Packed {
if p.AccessCode != nil { if err := p.Pack(); err != nil {
totalSize += len(p.AccessCode) return nil, fmt.Errorf("failed to pack packet: %w", err)
}
} }
buffer := make([]byte, totalSize) p.Addresses = p.DestinationHash
offset := 0
// Write header return p.Raw, nil
copy(buffer[offset:], p.Header[:])
offset += HeaderSize
// Write access code if present
if p.AccessCode != nil {
copy(buffer[offset:], p.AccessCode)
offset += len(p.AccessCode)
}
// Write addresses
copy(buffer[offset:], p.Addresses)
offset += len(p.Addresses)
// Write context
buffer[offset] = p.Context
offset += ContextSize
// Write data
copy(buffer[offset:], p.Data)
return buffer, nil
}
type AnnouncePacket struct {
Header [2]byte
DestHash []byte
PublicKey []byte
AppData []byte
RandomBlob []byte
Signature []byte
HopCount byte
Timestamp time.Time
} }