244 lines
8.0 KiB
Go
244 lines
8.0 KiB
Go
// Package gatewayv1contract provides public-contract helpers for the gateway
|
|
// v1 authenticated transport without importing service-internal packages.
|
|
package gatewayv1contract
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/ed25519"
|
|
"crypto/sha256"
|
|
"encoding/binary"
|
|
"errors"
|
|
)
|
|
|
|
const (
|
|
// ProtocolVersionV1 is the supported public protocol version literal.
|
|
ProtocolVersionV1 = "v1"
|
|
|
|
// SubscribeMessageType is the authenticated message type used to open the
|
|
// gateway push stream.
|
|
SubscribeMessageType = "gateway.subscribe"
|
|
|
|
// ServerTimeEventType is the bootstrap event type emitted by the gateway
|
|
// immediately after a push stream is opened.
|
|
ServerTimeEventType = "gateway.server_time"
|
|
|
|
requestDomainMarkerV1 = "galaxy-request-v1"
|
|
eventDomainMarkerV1 = "galaxy-event-v1"
|
|
)
|
|
|
|
var (
|
|
// ErrInvalidPayloadHash reports that payloadHash is not a raw SHA-256
|
|
// digest.
|
|
ErrInvalidPayloadHash = errors.New("payload_hash must be a 32-byte SHA-256 digest")
|
|
|
|
// ErrPayloadHashMismatch reports that payloadHash does not match
|
|
// payloadBytes.
|
|
ErrPayloadHashMismatch = errors.New("payload_hash does not match payload_bytes")
|
|
|
|
// ErrInvalidEventSignature reports that one gateway event signature is not
|
|
// a raw Ed25519 signature for the canonical event signing input.
|
|
ErrInvalidEventSignature = errors.New("invalid event signature")
|
|
|
|
// ErrInvalidResponseSignature reports that one gateway unary response
|
|
// signature is not a raw Ed25519 signature for the canonical response
|
|
// signing input.
|
|
ErrInvalidResponseSignature = errors.New("invalid response signature")
|
|
)
|
|
|
|
// RequestSigningFields stores the canonical public request fields bound into
|
|
// one client signature input.
|
|
type RequestSigningFields struct {
|
|
// ProtocolVersion identifies the gateway transport envelope version.
|
|
ProtocolVersion string
|
|
|
|
// DeviceSessionID identifies the authenticated device session bound to the
|
|
// request.
|
|
DeviceSessionID string
|
|
|
|
// MessageType is the stable authenticated gateway message type.
|
|
MessageType string
|
|
|
|
// TimestampMS carries the client request timestamp in milliseconds.
|
|
TimestampMS int64
|
|
|
|
// RequestID is the transport correlation and anti-replay identifier.
|
|
RequestID string
|
|
|
|
// PayloadHash stores the raw SHA-256 digest of PayloadBytes.
|
|
PayloadHash []byte
|
|
}
|
|
|
|
// EventSigningFields stores the canonical public stream-event fields bound
|
|
// into one gateway event signature input.
|
|
type EventSigningFields struct {
|
|
// EventType identifies the stable client-facing event category.
|
|
EventType string
|
|
|
|
// EventID is the stable event correlation identifier.
|
|
EventID string
|
|
|
|
// TimestampMS carries the gateway event timestamp in milliseconds.
|
|
TimestampMS int64
|
|
|
|
// RequestID optionally correlates the event to the opening client request.
|
|
RequestID string
|
|
|
|
// TraceID optionally carries the client-supplied trace correlation value.
|
|
TraceID string
|
|
|
|
// PayloadHash stores the raw SHA-256 digest of PayloadBytes.
|
|
PayloadHash []byte
|
|
}
|
|
|
|
// ResponseSigningFields stores the canonical public unary response fields
|
|
// bound into one gateway signature input.
|
|
type ResponseSigningFields struct {
|
|
// ProtocolVersion identifies the gateway transport envelope version.
|
|
ProtocolVersion string
|
|
|
|
// RequestID is the transport correlation identifier echoed by the gateway.
|
|
RequestID string
|
|
|
|
// TimestampMS carries the gateway response timestamp in milliseconds.
|
|
TimestampMS int64
|
|
|
|
// ResultCode stores the stable opaque gateway result code.
|
|
ResultCode string
|
|
|
|
// PayloadHash stores the raw SHA-256 digest of PayloadBytes.
|
|
PayloadHash []byte
|
|
}
|
|
|
|
// ComputePayloadHash returns the canonical raw SHA-256 digest for payloadBytes.
|
|
func ComputePayloadHash(payloadBytes []byte) []byte {
|
|
sum := sha256.Sum256(payloadBytes)
|
|
return bytes.Clone(sum[:])
|
|
}
|
|
|
|
// VerifyPayloadHash reports whether payloadHash matches payloadBytes under the
|
|
// public gateway payload-hash contract.
|
|
func VerifyPayloadHash(payloadBytes, payloadHash []byte) error {
|
|
if len(payloadHash) != sha256.Size {
|
|
return ErrInvalidPayloadHash
|
|
}
|
|
|
|
sum := sha256.Sum256(payloadBytes)
|
|
if !bytes.Equal(sum[:], payloadHash) {
|
|
return ErrPayloadHashMismatch
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// BuildRequestSigningInput returns the canonical byte sequence the v1 client
|
|
// request signature covers.
|
|
func BuildRequestSigningInput(fields RequestSigningFields) []byte {
|
|
size := len(requestDomainMarkerV1) +
|
|
len(fields.ProtocolVersion) +
|
|
len(fields.DeviceSessionID) +
|
|
len(fields.MessageType) +
|
|
len(fields.RequestID) +
|
|
len(fields.PayloadHash) +
|
|
(6 * binary.MaxVarintLen64) +
|
|
8
|
|
|
|
buf := make([]byte, 0, size)
|
|
buf = appendLengthPrefixedString(buf, requestDomainMarkerV1)
|
|
buf = appendLengthPrefixedString(buf, fields.ProtocolVersion)
|
|
buf = appendLengthPrefixedString(buf, fields.DeviceSessionID)
|
|
buf = appendLengthPrefixedString(buf, fields.MessageType)
|
|
buf = binary.BigEndian.AppendUint64(buf, uint64(fields.TimestampMS))
|
|
buf = appendLengthPrefixedString(buf, fields.RequestID)
|
|
buf = appendLengthPrefixedBytes(buf, fields.PayloadHash)
|
|
|
|
return buf
|
|
}
|
|
|
|
// BuildEventSigningInput returns the canonical byte sequence the v1 gateway
|
|
// event signature covers.
|
|
func BuildEventSigningInput(fields EventSigningFields) []byte {
|
|
size := len(eventDomainMarkerV1) +
|
|
len(fields.EventType) +
|
|
len(fields.EventID) +
|
|
len(fields.RequestID) +
|
|
len(fields.TraceID) +
|
|
len(fields.PayloadHash) +
|
|
(6 * binary.MaxVarintLen64) +
|
|
8
|
|
|
|
buf := make([]byte, 0, size)
|
|
buf = appendLengthPrefixedString(buf, eventDomainMarkerV1)
|
|
buf = appendLengthPrefixedString(buf, fields.EventType)
|
|
buf = appendLengthPrefixedString(buf, fields.EventID)
|
|
buf = binary.BigEndian.AppendUint64(buf, uint64(fields.TimestampMS))
|
|
buf = appendLengthPrefixedString(buf, fields.RequestID)
|
|
buf = appendLengthPrefixedString(buf, fields.TraceID)
|
|
buf = appendLengthPrefixedBytes(buf, fields.PayloadHash)
|
|
|
|
return buf
|
|
}
|
|
|
|
// BuildResponseSigningInput returns the canonical byte sequence the v1
|
|
// gateway unary response signature covers.
|
|
func BuildResponseSigningInput(fields ResponseSigningFields) []byte {
|
|
size := len("galaxy-response-v1") +
|
|
len(fields.ProtocolVersion) +
|
|
len(fields.RequestID) +
|
|
len(fields.ResultCode) +
|
|
len(fields.PayloadHash) +
|
|
(5 * binary.MaxVarintLen64) +
|
|
8
|
|
|
|
buf := make([]byte, 0, size)
|
|
buf = appendLengthPrefixedString(buf, "galaxy-response-v1")
|
|
buf = appendLengthPrefixedString(buf, fields.ProtocolVersion)
|
|
buf = appendLengthPrefixedString(buf, fields.RequestID)
|
|
buf = binary.BigEndian.AppendUint64(buf, uint64(fields.TimestampMS))
|
|
buf = appendLengthPrefixedString(buf, fields.ResultCode)
|
|
buf = appendLengthPrefixedBytes(buf, fields.PayloadHash)
|
|
|
|
return buf
|
|
}
|
|
|
|
// SignRequest returns one raw Ed25519 client signature for the canonical v1
|
|
// request signing input.
|
|
func SignRequest(privateKey ed25519.PrivateKey, fields RequestSigningFields) []byte {
|
|
return ed25519.Sign(privateKey, BuildRequestSigningInput(fields))
|
|
}
|
|
|
|
// VerifyEventSignature reports whether signature authenticates fields under
|
|
// publicKey using the canonical gateway event signing input.
|
|
func VerifyEventSignature(publicKey ed25519.PublicKey, signature []byte, fields EventSigningFields) error {
|
|
if len(publicKey) != ed25519.PublicKeySize || len(signature) != ed25519.SignatureSize {
|
|
return ErrInvalidEventSignature
|
|
}
|
|
if !ed25519.Verify(publicKey, BuildEventSigningInput(fields), signature) {
|
|
return ErrInvalidEventSignature
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// VerifyResponseSignature reports whether signature authenticates fields under
|
|
// publicKey using the canonical gateway unary-response signing input.
|
|
func VerifyResponseSignature(publicKey ed25519.PublicKey, signature []byte, fields ResponseSigningFields) error {
|
|
if len(publicKey) != ed25519.PublicKeySize || len(signature) != ed25519.SignatureSize {
|
|
return ErrInvalidResponseSignature
|
|
}
|
|
if !ed25519.Verify(publicKey, BuildResponseSigningInput(fields), signature) {
|
|
return ErrInvalidResponseSignature
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func appendLengthPrefixedString(dst []byte, value string) []byte {
|
|
return appendLengthPrefixedBytes(dst, []byte(value))
|
|
}
|
|
|
|
func appendLengthPrefixedBytes(dst []byte, value []byte) []byte {
|
|
dst = binary.AppendUvarint(dst, uint64(len(value)))
|
|
dst = append(dst, value...)
|
|
return dst
|
|
}
|