Internal numbered step comments (1. Read Tag, 2. Handle Payload Tag, etc.) restate what the code does rather than showing concrete usage examples. Per RFC-CORE-008 Principle 2: delete comments that restate what the code already expresses; keep only usage examples with realistic values. Co-Authored-By: Charon <charon@lethean.io>
113 lines
3 KiB
Go
113 lines
3 KiB
Go
package ueps
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"crypto/hmac"
|
|
"crypto/sha256"
|
|
"encoding/binary"
|
|
"io"
|
|
)
|
|
|
|
// packet, err := ReadAndVerify(bufio.NewReader(conn), secret)
|
|
// if err == errMissingHMAC { /* no HMAC tag found in frame */ }
|
|
var errMissingHMAC = tlvError("UEPS packet missing HMAC signature")
|
|
|
|
// packet, err := ReadAndVerify(bufio.NewReader(conn), wrongSecret)
|
|
// if 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 { dispatch(packet.Header.IntentID, packet.Header.ThreatScore, packet.Payload) }
|
|
func ReadAndVerify(reader *bufio.Reader, sharedSecret []byte) (*ParsedPacket, error) {
|
|
var signedData bytes.Buffer
|
|
header := UEPSHeader{}
|
|
var signature []byte
|
|
var payload []byte
|
|
|
|
for {
|
|
tagByte, err := reader.ReadByte()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if tagByte == TagPayload {
|
|
var err error
|
|
payload, err = io.ReadAll(reader)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
break
|
|
}
|
|
|
|
tagLengthByte, err := reader.ReadByte()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
tagLength := int(tagLengthByte)
|
|
|
|
tagValue := make([]byte, tagLength)
|
|
if _, err := io.ReadFull(reader, tagValue); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
switch tagByte {
|
|
case TagVersion:
|
|
header.Version = tagValue[0]
|
|
signedData.WriteByte(tagByte)
|
|
signedData.WriteByte(byte(tagLength))
|
|
signedData.Write(tagValue)
|
|
case TagCurrentLayer:
|
|
header.CurrentLayer = tagValue[0]
|
|
signedData.WriteByte(tagByte)
|
|
signedData.WriteByte(byte(tagLength))
|
|
signedData.Write(tagValue)
|
|
case TagTargetLayer:
|
|
header.TargetLayer = tagValue[0]
|
|
signedData.WriteByte(tagByte)
|
|
signedData.WriteByte(byte(tagLength))
|
|
signedData.Write(tagValue)
|
|
case TagIntent:
|
|
header.IntentID = tagValue[0]
|
|
signedData.WriteByte(tagByte)
|
|
signedData.WriteByte(byte(tagLength))
|
|
signedData.Write(tagValue)
|
|
case TagThreatScore:
|
|
header.ThreatScore = binary.BigEndian.Uint16(tagValue)
|
|
signedData.WriteByte(tagByte)
|
|
signedData.WriteByte(byte(tagLength))
|
|
signedData.Write(tagValue)
|
|
case TagHMAC:
|
|
signature = tagValue
|
|
default:
|
|
signedData.WriteByte(tagByte)
|
|
signedData.WriteByte(byte(tagLength))
|
|
signedData.Write(tagValue)
|
|
}
|
|
}
|
|
|
|
if len(signature) == 0 {
|
|
return nil, errMissingHMAC
|
|
}
|
|
|
|
messageAuthCode := hmac.New(sha256.New, sharedSecret)
|
|
messageAuthCode.Write(signedData.Bytes())
|
|
messageAuthCode.Write(payload)
|
|
expectedMessageAuthCode := messageAuthCode.Sum(nil)
|
|
|
|
if !hmac.Equal(signature, expectedMessageAuthCode) {
|
|
return nil, errIntegrityViolation
|
|
}
|
|
|
|
return &ParsedPacket{
|
|
Header: header,
|
|
Payload: payload,
|
|
}, nil
|
|
}
|