A malformed frame with length 0 for any single-byte tag (TagVersion, TagCurrentLayer, TagTargetLayer, TagIntent) or fewer than 2 bytes for TagThreatScore caused a runtime panic (index out of range) on untrusted input. Added len(tagValue) bounds checks in ReadAndVerify before each tagValue[0] and Uint16 access to eliminate the panic path. Co-Authored-By: Charon <charon@lethean.io>
112 lines
3 KiB
Go
112 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 = sentinelError("UEPS packet missing HMAC signature")
|
|
|
|
// packet, err := ReadAndVerify(bufio.NewReader(conn), wrongSecret)
|
|
// if err == errIntegrityViolation { header.ThreatScore += 100; /* reject packet */ }
|
|
var errIntegrityViolation = sentinelError("integrity violation: HMAC mismatch")
|
|
|
|
// dispatch(packet.Header.IntentID, packet.Header.ThreatScore, packet.Payload)
|
|
type ParsedPacket struct {
|
|
Header UEPSHeader // packet.Header.Version == 0x09; packet.Header.IntentID == 0x01; packet.Header.ThreatScore == 0
|
|
Payload []byte // packet.Payload == []byte("hello world")
|
|
}
|
|
|
|
// packet, err := ueps.ReadAndVerify(bufio.NewReader(conn), []byte("my-shared-secret"))
|
|
// if err == errMissingHMAC { return } // unauthenticated: no HMAC tag in stream
|
|
// if err == errIntegrityViolation { return } // tampered: HMAC mismatch; reject and raise threat score
|
|
// if err == nil { dispatch(packet.Header.IntentID, packet.Header.ThreatScore, packet.Payload) }
|
|
func ReadAndVerify(reader *bufio.Reader, sharedSecret []byte) (*ParsedPacket, error) {
|
|
var (
|
|
hmacInputBuffer bytes.Buffer
|
|
packetHeader UEPSHeader
|
|
hmacSignature []byte
|
|
payload []byte
|
|
)
|
|
|
|
for {
|
|
tagType, err := reader.ReadByte()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if tagType == TagPayload {
|
|
payload, err = io.ReadAll(reader)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
break
|
|
}
|
|
|
|
tagLength, err := reader.ReadByte()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
tagValue := make([]byte, int(tagLength))
|
|
if _, err := io.ReadFull(reader, tagValue); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
switch tagType {
|
|
case TagVersion:
|
|
if len(tagValue) >= 1 {
|
|
packetHeader.Version = tagValue[0]
|
|
}
|
|
case TagCurrentLayer:
|
|
if len(tagValue) >= 1 {
|
|
packetHeader.CurrentLayer = tagValue[0]
|
|
}
|
|
case TagTargetLayer:
|
|
if len(tagValue) >= 1 {
|
|
packetHeader.TargetLayer = tagValue[0]
|
|
}
|
|
case TagIntent:
|
|
if len(tagValue) >= 1 {
|
|
packetHeader.IntentID = tagValue[0]
|
|
}
|
|
case TagThreatScore:
|
|
if len(tagValue) >= 2 {
|
|
packetHeader.ThreatScore = binary.BigEndian.Uint16(tagValue)
|
|
}
|
|
case TagHMAC:
|
|
hmacSignature = tagValue
|
|
}
|
|
|
|
// hmacInputBuffer.Write([]byte{tagType, tagLength}); hmacInputBuffer.Write(tagValue) — covers all header TLVs except TagHMAC
|
|
if tagType != TagHMAC {
|
|
hmacInputBuffer.WriteByte(tagType)
|
|
hmacInputBuffer.WriteByte(tagLength)
|
|
hmacInputBuffer.Write(tagValue)
|
|
}
|
|
}
|
|
|
|
if len(hmacSignature) == 0 {
|
|
return nil, errMissingHMAC
|
|
}
|
|
|
|
messageAuthCode := hmac.New(sha256.New, sharedSecret)
|
|
messageAuthCode.Write(hmacInputBuffer.Bytes())
|
|
messageAuthCode.Write(payload)
|
|
expectedMessageAuthCode := messageAuthCode.Sum(nil)
|
|
|
|
if !hmac.Equal(hmacSignature, expectedMessageAuthCode) {
|
|
return nil, errIntegrityViolation
|
|
}
|
|
|
|
return &ParsedPacket{
|
|
Header: packetHeader,
|
|
Payload: payload,
|
|
}, nil
|
|
}
|