go-p2p/ueps/reader.go
Snider 6fd3fe1cd2
All checks were successful
Security Scan / security (pull_request) Successful in 8s
Test / test (pull_request) Successful in 2m2s
refactor(node): migrate module path to dappco.re/go/core/p2p
Update go.mod module line from forge.lthn.ai/core/go-p2p to
dappco.re/go/core/p2p. Migrate core dependency paths: go-log to
dappco.re/go/core/log v0.1.0, go-io to dappco.re/go/core/io v0.2.0.
Update all .go import paths across 18 source files. Borg, Poindexter,
and Enchantrix dependencies remain on forge.lthn.ai as they have not
been migrated upstream.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-22 02:06:51 +00:00

115 lines
2.8 KiB
Go

package ueps
import (
"bufio"
"bytes"
"crypto/hmac"
"crypto/sha256"
"encoding/binary"
"io"
coreerr "dappco.re/go/core/log"
)
// ParsedPacket holds the verified data
type ParsedPacket struct {
Header UEPSHeader
Payload []byte
}
// ReadAndVerify reads a UEPS frame from the stream and validates the HMAC.
// It consumes the stream up to the end of the packet.
func ReadAndVerify(r *bufio.Reader, sharedSecret []byte) (*ParsedPacket, error) {
// Buffer to reconstruct the data for HMAC verification
var signedData bytes.Buffer
header := UEPSHeader{}
var signature []byte
var payload []byte
// Loop through TLVs
for {
// 1. Read Tag
tag, err := r.ReadByte()
if err != nil {
return nil, err
}
// 2. Read Length (2-byte big-endian uint16)
lenBuf := make([]byte, 2)
if _, err := io.ReadFull(r, lenBuf); err != nil {
return nil, err
}
length := int(binary.BigEndian.Uint16(lenBuf))
// 3. Read Value
value := make([]byte, length)
if _, err := io.ReadFull(r, value); err != nil {
return nil, err
}
// 4. Handle Tag
switch tag {
case TagVersion:
header.Version = value[0]
signedData.WriteByte(tag)
signedData.Write(lenBuf)
signedData.Write(value)
case TagCurrentLay:
header.CurrentLayer = value[0]
signedData.WriteByte(tag)
signedData.Write(lenBuf)
signedData.Write(value)
case TagTargetLay:
header.TargetLayer = value[0]
signedData.WriteByte(tag)
signedData.Write(lenBuf)
signedData.Write(value)
case TagIntent:
header.IntentID = value[0]
signedData.WriteByte(tag)
signedData.Write(lenBuf)
signedData.Write(value)
case TagThreatScore:
header.ThreatScore = binary.BigEndian.Uint16(value)
signedData.WriteByte(tag)
signedData.Write(lenBuf)
signedData.Write(value)
case TagHMAC:
signature = value
// HMAC tag itself is not part of the signed data
case TagPayload:
payload = value
// Exit loop after payload (last tag in UEPS frame)
// Note: The HMAC covers the Payload but NOT the TagPayload/Length bytes
// to match the PacketBuilder.MarshalAndSign logic.
goto verify
default:
// Unknown tag (future proofing), verify it but ignore semantics
signedData.WriteByte(tag)
signedData.Write(lenBuf)
signedData.Write(value)
}
}
verify:
if len(signature) == 0 {
return nil, coreerr.E("ueps.ReadAndVerify", "UEPS packet missing HMAC signature", nil)
}
// 5. Verify HMAC
// Reconstruct: Headers (signedData) + Payload
mac := hmac.New(sha256.New, sharedSecret)
mac.Write(signedData.Bytes())
mac.Write(payload)
expectedMAC := mac.Sum(nil)
if !hmac.Equal(signature, expectedMAC) {
return nil, coreerr.E("ueps.ReadAndVerify", "integrity violation: HMAC mismatch (ThreatScore +100)", nil)
}
return &ParsedPacket{
Header: header,
Payload: payload,
}, nil
}