75 lines
2.5 KiB
Go
75 lines
2.5 KiB
Go
package canon
|
|
|
|
import (
|
|
"crypto/ed25519"
|
|
"encoding/binary"
|
|
"errors"
|
|
)
|
|
|
|
const (
|
|
// ResponseDomainMarkerV1 binds the v1 server response signature to the
|
|
// Galaxy gateway transport contract.
|
|
ResponseDomainMarkerV1 = "galaxy-response-v1"
|
|
)
|
|
|
|
// ErrInvalidResponseSignature reports that a server response signature is
|
|
// not a raw Ed25519 signature for the canonical response signing input.
|
|
var ErrInvalidResponseSignature = errors.New("invalid response signature")
|
|
|
|
// ResponseSigningFields contains the canonical v1 response fields that are
|
|
// bound into the server signing input.
|
|
type ResponseSigningFields struct {
|
|
// ProtocolVersion identifies the transport envelope version.
|
|
ProtocolVersion string
|
|
|
|
// RequestID is the transport correlation identifier copied from the
|
|
// authenticated request.
|
|
RequestID string
|
|
|
|
// TimestampMS carries the server response timestamp in milliseconds.
|
|
TimestampMS int64
|
|
|
|
// ResultCode is the opaque downstream result code returned to the client.
|
|
ResultCode string
|
|
|
|
// PayloadHash is the raw SHA-256 digest of response payload bytes.
|
|
PayloadHash []byte
|
|
}
|
|
|
|
// BuildResponseSigningInput returns the canonical byte sequence the v1 server
|
|
// response signature covers. String and byte fields are length-prefixed with
|
|
// uvarint(len(field)) followed by raw bytes, while TimestampMS is appended as
|
|
// an 8-byte big-endian uint64.
|
|
func BuildResponseSigningInput(fields ResponseSigningFields) []byte {
|
|
size := len(ResponseDomainMarkerV1) +
|
|
len(fields.ProtocolVersion) +
|
|
len(fields.RequestID) +
|
|
len(fields.ResultCode) +
|
|
len(fields.PayloadHash) +
|
|
(5 * binary.MaxVarintLen64) +
|
|
8
|
|
|
|
buf := make([]byte, 0, size)
|
|
buf = appendLengthPrefixedString(buf, ResponseDomainMarkerV1)
|
|
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
|
|
}
|
|
|
|
// VerifyResponseSignature verifies that signature authenticates fields under
|
|
// publicKey using the canonical v1 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
|
|
}
|