pool/sidecar/codec.go
Oliver Gugger 7c3bf310c0
sidecar: add string encoder and decoder
To allow a ticket to be transported easily over any communication
channel we also provide an easy ascii compatible exchange format which
includes a prefix and a checksum in addition to the tlv stream data.
The string encoding chosen is URL safe base64.
2021-02-22 10:26:00 +01:00

96 lines
2.9 KiB
Go

package sidecar
import (
"bytes"
"crypto/sha256"
"encoding/base64"
"fmt"
)
const (
// checksumLen is the number of bytes we add as a checksum. We'll take
// this many bytes out of the full SHA256 hash and add it to the string
// encoded version of the ticket as an additional integrity check.
checksumLen = 4
)
var (
// defaultStringEncoding is the default encoding algorithm we use to
// encode the gzipped binary data as a string.
defaultStringEncoding = base64.URLEncoding
// sidecarPrefix is a specially prepared prefix that will spell out
// "sidecar" when encoded as base64. It will cause all our sidecar
// tickets to be easily recognizable with a human readable part.
sidecarPrefix = []byte{0xb2, 0x27, 0x5e, 0x71, 0xaa, 0xc0}
)
// EncodeToString serializes and encodes the ticket as an URL safe string that
// contains a human readable prefix and a checksum.
func EncodeToString(t *Ticket) (string, error) {
var buf bytes.Buffer
// Add a prefix to make the ticket easily recognizable by human eyes.
if _, err := buf.Write(sidecarPrefix); err != nil {
return "", err
}
// Serialize the raw ticket itself.
if err := SerializeTicket(&buf, t); err != nil {
return "", err
}
// Create a checksum (SHA256) of all the bytes so far (prefix + raw
// ticket) and add 4 bytes of that to the string encoded version. This
// will prevent the ticket from appearing valid even if parts of it were
// not copy/pasted correctly.
hash := sha256.New()
_, _ = hash.Write(buf.Bytes())
checksum := hash.Sum(nil)[0:4]
if _, err := buf.Write(checksum); err != nil {
return "", err
}
return defaultStringEncoding.EncodeToString(buf.Bytes()), nil
}
// DecodeString decodes and then deserializes the given sidecar ticket string.
func DecodeString(s string) (*Ticket, error) {
rawBytes, err := defaultStringEncoding.DecodeString(s)
if err != nil {
return nil, err
}
if len(rawBytes) < len(sidecarPrefix)+checksumLen {
return nil, fmt.Errorf("not a sidecar ticket, invalid length")
}
totalLength := len(rawBytes)
prefixLength := len(sidecarPrefix)
payloadLength := totalLength - prefixLength - checksumLen
prefix := rawBytes[0:prefixLength]
payload := rawBytes[prefixLength : prefixLength+payloadLength]
checksum := rawBytes[totalLength-checksumLen : totalLength]
// Make sure the prefix matches our static prefix.
if !bytes.Equal(prefix, sidecarPrefix) {
return nil, fmt.Errorf("not a sicecar ticket, invalid prefix "+
"%x", prefix)
}
// Calculate the SHA256 sum of the prefix and payload and compare it to
// the checksum we also expect to be in the full string serialized
// ticket.
hash := sha256.New()
_, _ = hash.Write(prefix)
_, _ = hash.Write(payload)
calculatedChecksum := hash.Sum(nil)[0:4]
if !bytes.Equal(checksum, calculatedChecksum) {
return nil, fmt.Errorf("invalid sidecar ticket, checksum " +
"mismatch")
}
// Everything's fine, let's decode the actual payload.
return DeserializeTicket(bytes.NewReader(payload))
}