package ueps import ( "bufio" "bytes" "crypto/hmac" "crypto/sha256" "encoding/binary" "io" ) // packet, err := ReadAndVerify(r, secret) // if errors.Is(err, errMissingHMAC) { /* no HMAC tag found in frame */ } var errMissingHMAC = tlvError("UEPS packet missing HMAC signature") // packet, err := ReadAndVerify(r, wrongSecret) // if errors.Is(err, errIntegrityViolation) { /* HMAC mismatch — threat score incremented */ } var errIntegrityViolation = tlvError("integrity violation: HMAC mismatch (ThreatScore +100)") // packet, err := ueps.ReadAndVerify(r, 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) { // Buffer to reconstruct the data for HMAC verification // We have to "record" what we read to verify the signature later. 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 value := make([]byte, length) if _, err := io.ReadFull(reader, value); err != nil { return nil, err } // Store for processing switch tag { case TagVersion: header.Version = value[0] // Reconstruct signed data: Tag + Len + Val signedData.WriteByte(tag) signedData.WriteByte(byte(length)) signedData.Write(value) case TagCurrentLayer: header.CurrentLayer = value[0] signedData.WriteByte(tag) signedData.WriteByte(byte(length)) signedData.Write(value) case TagTargetLayer: header.TargetLayer = value[0] signedData.WriteByte(tag) signedData.WriteByte(byte(length)) signedData.Write(value) case TagIntent: header.IntentID = value[0] signedData.WriteByte(tag) signedData.WriteByte(byte(length)) signedData.Write(value) case TagThreatScore: header.ThreatScore = binary.BigEndian.Uint16(value) signedData.WriteByte(tag) signedData.WriteByte(byte(length)) signedData.Write(value) case TagHMAC: signature = value // 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(value) } } 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) { // Log this. This is a Threat Event. // "Axiom Violation: Integrity Check Failed" return nil, errIntegrityViolation } return &ParsedPacket{ Header: header, Payload: payload, }, nil }