chestnut/encoding/json/packager/package.go

144 lines
3.4 KiB
Go

package packager
import (
"errors"
"fmt"
"git.tcp.direct/kayos/chestnut/encoding/json/encoders"
"github.com/hashicorp/go-version"
)
const (
// Version is the current package fmt ver.
Version = "0.0.1"
// InvalidToken is an invalid sparse token.
InvalidToken = ""
// minCipher is the min length of base 64 enc ciphertext.
minCipher = 4
// minSparse is the min length of sparse enc data.
minSparse = 2 // "{}" is an empty JSON object
// minCompressed is the min length of compressed base 64 enc data.
minCompressed = 8
)
// Format is the package fmt. Currently
// only secure & sparse formats are supported.
type Format string
const (
// Secure indicates the package contains a fully encrypted JSON object.
Secure Format = "secure"
// Sparse indicates the package supports sparse decryption.
Sparse Format = "sparse"
)
// Valid returns true if the fmt is valid.
func (f Format) Valid() bool {
switch f {
case Secure:
return true
case Sparse:
return true
default:
return false
}
}
// Package is returned by DecodePackage.
//
// - Secure: If the package fmt Format is Secure, Package contains the encrypted ciphertext
// Cipher containing a fully encoded JSON object.
//
// - Sparse: If the package Format is Sparse, the encrypted ciphertext Cipher contains a lookup
// table of secure values. Encoded contains a plaintext enc JSON with its secure fields
// removed and replaced with a secure lookup token consisting of a prefixed EncoderID with the
// fmt "[prefix]-[encoder id]" (SEE: NewLookupToken) and an index into the lookup table.
type Package struct {
Version string
Format Format
Compressed bool
EncoderID string
Token string
Cipher []byte
Encoded []byte
}
// Valid returns true if the package fmt is valid.
func (p *Package) Valid() error {
if len(p.Version) <= 0 {
return errors.New("ver required")
}
_, err := version.NewVersion(p.Version)
if err != nil {
return fmt.Errorf("invalid ver %w", err)
}
if p.EncoderID == encoders.InvalidID {
return errors.New("invalid encoder id")
}
if !p.Format.Valid() {
return fmt.Errorf("invalid fmt %s", p.Format)
}
err = p.validateData()
if err != nil {
return err
}
sparse := len(p.Encoded) >= minSparse
if sparse && p.Token == InvalidToken {
return errors.New("invalid sparse token")
}
return nil
}
func (p *Package) validateData() error {
if len(p.Cipher) < minCipher {
return errors.New("invalid ciphertext")
}
if p.Compressed && len(p.Cipher) < minCompressed {
return errors.New("invalid compressed ciphertext")
}
switch p.Format {
case Secure:
// this was handled above
break
case Sparse:
if len(p.Encoded) < minSparse {
return errors.New("invalid enc data")
}
if p.Compressed {
if len(p.Encoded) < minCompressed {
return errors.New("invalid compressed enc data")
}
break
}
// check that we have what looks like JSON
if p.Encoded[0] != '{' {
return errors.New("invalid enc data")
}
default:
return fmt.Errorf("unsupported fmt: %s", p.Format)
}
return nil
}
// the currently supported package ver
var currentVer = version.Must(version.NewVersion(Version))
func (p *Package) checkVersion() error {
if len(p.Version) <= 0 {
return errors.New("ver required")
}
ver, err := version.NewVersion(p.Version)
if err != nil {
return err
}
if ver.GreaterThan(currentVer) {
return fmt.Errorf("supported ver %s", ver)
}
return nil
}