AX principle 2 requires comments to show usage with real values. The MarshalAndSign call in the NewPacketBuilder doc comment used a variable name (sharedSecret) instead of a concrete literal. Co-Authored-By: Charon <charon@lethean.io>
122 lines
4.3 KiB
Go
122 lines
4.3 KiB
Go
package ueps
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/hmac"
|
|
"crypto/sha256"
|
|
"encoding/binary"
|
|
"io"
|
|
)
|
|
|
|
// writeTLV(buffer, TagPayload, oversized) → errTLVValueTooLarge
|
|
// if err == errTLVValueTooLarge { /* value exceeded 255-byte TLV length limit */ }
|
|
var errTLVValueTooLarge = packetError("TLV value too large for 1-byte length header")
|
|
|
|
type packetError string
|
|
|
|
func (packetErrorValue packetError) Error() string { return string(packetErrorValue) }
|
|
|
|
// writeTLV(buffer, TagVersion, []byte{0x09}); writeTLV(buffer, TagHMAC, hmacSignature); buffer.WriteByte(TagPayload)
|
|
const (
|
|
TagVersion = 0x01
|
|
TagCurrentLayer = 0x02
|
|
TagTargetLayer = 0x03
|
|
TagIntent = 0x04
|
|
TagThreatScore = 0x05
|
|
TagHMAC = 0x06 // writeTLV(buffer, TagHMAC, hmacSignature) — covers all preceding header TLVs + payload
|
|
TagPayload = 0xFF // buffer.WriteByte(TagPayload); buffer.Write(rawPayload) — no length prefix
|
|
)
|
|
|
|
// header := ueps.UEPSHeader{Version: 0x09, CurrentLayer: 5, TargetLayer: 3, IntentID: 0x01, ThreatScore: 0}
|
|
type UEPSHeader struct {
|
|
Version uint8 // header.Version = 0x09 // IPv9
|
|
CurrentLayer uint8 // header.CurrentLayer = 5 // Application; 3 = Network, 4 = Transport
|
|
TargetLayer uint8 // header.TargetLayer = 3 // Network; 5 = Application (loopback)
|
|
IntentID uint8 // header.IntentID = 0x01 (ping), 0x02 (data), 0x03 (auth)
|
|
ThreatScore uint16 // header.ThreatScore = 100 // elevated on HMAC mismatch; 0 = clean, 65535 = maximum threat
|
|
}
|
|
|
|
// builder := ueps.NewPacketBuilder(0x01, []byte("ping")); frame, _ := builder.MarshalAndSign([]byte("shared-secret"))
|
|
type PacketBuilder struct {
|
|
Header UEPSHeader
|
|
Payload []byte
|
|
}
|
|
|
|
// builder := ueps.NewPacketBuilder(0x01, []byte("hello"))
|
|
// builder.Header.ThreatScore = 100
|
|
// frame, err := builder.MarshalAndSign([]byte("my-shared-secret"))
|
|
func NewPacketBuilder(intentID uint8, payload []byte) *PacketBuilder {
|
|
return &PacketBuilder{
|
|
Header: UEPSHeader{
|
|
Version: 0x09, // IPv9
|
|
CurrentLayer: 5, // Application
|
|
TargetLayer: 5, // Application
|
|
IntentID: intentID,
|
|
ThreatScore: 0, // builder.Header.ThreatScore = 100 // elevated threat; 0 = clean
|
|
},
|
|
Payload: payload,
|
|
}
|
|
}
|
|
|
|
// frame, err := builder.MarshalAndSign([]byte("my-shared-secret"))
|
|
func (builder *PacketBuilder) MarshalAndSign(sharedSecret []byte) ([]byte, error) {
|
|
frameBuffer := new(bytes.Buffer)
|
|
|
|
// writeTLV(frameBuffer, TagVersion, []byte{0x09}) → [0x01, 0x01, 0x09]
|
|
if err := writeTLV(frameBuffer, TagVersion, []byte{builder.Header.Version}); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := writeTLV(frameBuffer, TagCurrentLayer, []byte{builder.Header.CurrentLayer}); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := writeTLV(frameBuffer, TagTargetLayer, []byte{builder.Header.TargetLayer}); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := writeTLV(frameBuffer, TagIntent, []byte{builder.Header.IntentID}); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// binary.BigEndian.PutUint16(threatScoreBytes, 100) → [0x00, 0x64]
|
|
threatScoreBytes := make([]byte, 2)
|
|
binary.BigEndian.PutUint16(threatScoreBytes, builder.Header.ThreatScore)
|
|
if err := writeTLV(frameBuffer, TagThreatScore, threatScoreBytes); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// messageAuthCode.Write(frameBuffer.Bytes()) → covers all header TLVs before the HMAC tag
|
|
messageAuthCode := hmac.New(sha256.New, sharedSecret)
|
|
messageAuthCode.Write(frameBuffer.Bytes())
|
|
messageAuthCode.Write(builder.Payload)
|
|
hmacSignature := messageAuthCode.Sum(nil)
|
|
|
|
// writeTLV(frameBuffer, TagHMAC, hmacSignature) → [0x06, 0x20, <32 bytes>]
|
|
if err := writeTLV(frameBuffer, TagHMAC, hmacSignature); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// frameBuffer.Bytes() → [...headerTLVs..., 0x06, 0x20, <hmac32>, 0xFF, <payload...>]
|
|
frameBuffer.WriteByte(TagPayload)
|
|
frameBuffer.Write(builder.Payload)
|
|
|
|
return frameBuffer.Bytes(), nil
|
|
}
|
|
|
|
// writeTLV(buffer, TagVersion, []byte{0x09})
|
|
// writeTLV(buffer, TagIntent, []byte{intentID})
|
|
func writeTLV(writer io.Writer, tagType uint8, tagValue []byte) error {
|
|
// writeTLV(writer, TagVersion, bytes.Repeat([]byte("x"), 256)) → errTLVValueTooLarge
|
|
if len(tagValue) > 255 {
|
|
return errTLVValueTooLarge
|
|
}
|
|
|
|
if _, err := writer.Write([]byte{tagType}); err != nil {
|
|
return err
|
|
}
|
|
if _, err := writer.Write([]byte{uint8(len(tagValue))}); err != nil {
|
|
return err
|
|
}
|
|
if _, err := writer.Write(tagValue); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|