AX-2 violation: usage examples belong at the declaration site, not scattered inside function bodies. The errIntegrityViolation usage hint was already present at the variable declaration (line 17) and was duplicated inside ReadAndVerify, creating noise in the implementation. Co-Authored-By: Charon <charon@lethean.io>
126 lines
3.4 KiB
Go
126 lines
3.4 KiB
Go
package ueps
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"crypto/hmac"
|
|
"crypto/sha256"
|
|
"encoding/binary"
|
|
"io"
|
|
)
|
|
|
|
// packet, err := ReadAndVerify(bufio.NewReader(conn), secret)
|
|
// if errors.Is(err, errMissingHMAC) { /* no HMAC tag found in frame */ }
|
|
var errMissingHMAC = tlvError("UEPS packet missing HMAC signature")
|
|
|
|
// packet, err := ReadAndVerify(bufio.NewReader(conn), wrongSecret)
|
|
// if errors.Is(err, errIntegrityViolation) { /* HMAC mismatch — threat score incremented */ }
|
|
var errIntegrityViolation = tlvError("integrity violation: HMAC mismatch (ThreatScore +100)")
|
|
|
|
// packet, err := ueps.ReadAndVerify(bufio.NewReader(conn), sharedSecret)
|
|
// if err == nil { _ = packet.Header.IntentID; _ = packet.Header.ThreatScore; _ = packet.Payload }
|
|
type ParsedPacket struct {
|
|
Header UEPSHeader
|
|
Payload []byte
|
|
}
|
|
|
|
// packet, err := ueps.ReadAndVerify(bufio.NewReader(conn), []byte("my-shared-secret"))
|
|
// if err != nil { /* integrity violation or truncated frame */ }
|
|
func ReadAndVerify(reader *bufio.Reader, sharedSecret []byte) (*ParsedPacket, error) {
|
|
var signedData bytes.Buffer
|
|
header := UEPSHeader{}
|
|
var signature []byte
|
|
var payload []byte
|
|
|
|
// Loop through TLVs until we hit Payload (0xFF) or EOF
|
|
for {
|
|
// 1. Read Tag
|
|
tag, err := reader.ReadByte()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// 2. Handle Payload Tag (0xFF) - The Exit Condition
|
|
if tag == TagPayload {
|
|
// Payload is length-prefixless; caller frames the stream.
|
|
// HMAC covers signedData (header TLVs) + raw payload bytes, not the 0xFF tag.
|
|
remaining, err := io.ReadAll(reader)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
payload = remaining
|
|
break
|
|
}
|
|
|
|
// 3. Read Length (Standard TLV)
|
|
lengthByte, err := reader.ReadByte()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
length := int(lengthByte)
|
|
|
|
// 4. Read Value
|
|
tagValue := make([]byte, length)
|
|
if _, err := io.ReadFull(reader, tagValue); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Store for processing
|
|
switch tag {
|
|
case TagVersion:
|
|
header.Version = tagValue[0]
|
|
// Reconstruct signed data: Tag + Len + Val
|
|
signedData.WriteByte(tag)
|
|
signedData.WriteByte(byte(length))
|
|
signedData.Write(tagValue)
|
|
case TagCurrentLayer:
|
|
header.CurrentLayer = tagValue[0]
|
|
signedData.WriteByte(tag)
|
|
signedData.WriteByte(byte(length))
|
|
signedData.Write(tagValue)
|
|
case TagTargetLayer:
|
|
header.TargetLayer = tagValue[0]
|
|
signedData.WriteByte(tag)
|
|
signedData.WriteByte(byte(length))
|
|
signedData.Write(tagValue)
|
|
case TagIntent:
|
|
header.IntentID = tagValue[0]
|
|
signedData.WriteByte(tag)
|
|
signedData.WriteByte(byte(length))
|
|
signedData.Write(tagValue)
|
|
case TagThreatScore:
|
|
header.ThreatScore = binary.BigEndian.Uint16(tagValue)
|
|
signedData.WriteByte(tag)
|
|
signedData.WriteByte(byte(length))
|
|
signedData.Write(tagValue)
|
|
case TagHMAC:
|
|
signature = tagValue
|
|
// We do NOT add the HMAC itself to signedData
|
|
default:
|
|
// Unknown tag (future proofing), verify it but ignore semantics
|
|
signedData.WriteByte(tag)
|
|
signedData.WriteByte(byte(length))
|
|
signedData.Write(tagValue)
|
|
}
|
|
}
|
|
|
|
if len(signature) == 0 {
|
|
return nil, errMissingHMAC
|
|
}
|
|
|
|
// 5. Verify HMAC
|
|
// Reconstruct: Headers (signedData) + Payload
|
|
messageAuthCode := hmac.New(sha256.New, sharedSecret)
|
|
messageAuthCode.Write(signedData.Bytes())
|
|
messageAuthCode.Write(payload)
|
|
expectedMAC := messageAuthCode.Sum(nil)
|
|
|
|
if !hmac.Equal(signature, expectedMAC) {
|
|
return nil, errIntegrityViolation
|
|
}
|
|
|
|
return &ParsedPacket{
|
|
Header: header,
|
|
Payload: payload,
|
|
}, nil
|
|
}
|