initial commit

This commit is contained in:
jrapoport 2021-01-11 12:24:13 -08:00
commit ff814d366c
104 changed files with 12689 additions and 0 deletions

1
.github/CODEOWNERS vendored Normal file
View File

@ -0,0 +1 @@
* @jrapoport

29
.github/ISSUE_TEMPLATE.md vendored Normal file
View File

@ -0,0 +1,29 @@
<!--
If you are reporting a new issue, please check to see if it's already been reported
by searching the issue list for this repository first, to avoid duplicates. If there
is a duplicate, please close your issue and add a comment to the existing issue instead.
If you suspect your issue is a bug, please edit your issue description to
include the BUG REPORT INFORMATION shown below. If you don't provide this and we
cannot debug your issue, we will close it. We will, however, reopen it if you
later provide the information.
If you have an issue that can be shown visually, please provide a screenshot or
gif of the problem as well.
---------------------------------------------------
BUG REPORT INFORMATION
---------------------------------------------------
Use the commands below to provide key information from your environment:
You do NOT have to include this information if this is a FEATURE REQUEST
-->
**- Do you want to request a *feature* or report a *bug*?**
**- What is happening?**
**- If you think it's a bug, please provide complete repro steps.**
**- What did you expect to happen?**
**- Go version? OS version? or anything else you think might be helpful.**

44
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View File

@ -0,0 +1,44 @@
<!--
Thanks for submitting a pull request!
Please make sure you've read and understood our contributing guidelines;
https://github.com/jrapoport/chestnut/blob/master/CONTRIBUTING.md
If this is a bug fix, make sure your description includes "fixes #xxxx", or
"closes #xxxx", where #xxxx is the issue number.
Please provide enough information so that we can review your pull request.
The first three fields are mandatory:
-->
**- Problem**
<!--
Explain **why** you are making this change.
What existing problem does the pull request solve?
-->
**- Solution**
<!--
Explain **how** this change fixes the problem.
What does this change do to solve the the problem?
-->
**- Changes**
<!--
Explain **what** the actual changes are.
What did you change to implement your solution?
-->
**- Changelog**
<!--
A short (one line) summary that describes the change for the changelog:
-->
**- Tip me in BTC or ETH (not mandatory but encouraged)**
btc: bc1qqswuhm3nk60uv9g4j6e2230q46n9yklm4060wv
eth: 0x1866daF9F494F0C565CD4B93FA8B0FC3303a9acF

26
.github/workflows/fossa.yml vendored Normal file
View File

@ -0,0 +1,26 @@
name: Dependency License Scanning
on:
push:
branches:
- master
defaults:
run:
shell: bash
jobs:
fossa:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Fossa init
run: |-
curl -H 'Cache-Control: no-cache' https://raw.githubusercontent.com/fossas/fossa-cli/master/install.sh | bash
fossa init --project github.com/${GITHUB_REPOSITORY}
cat .fossa.yml
- name: Upload dependencies
run: fossa analyze --verbose go:.
env:
FOSSA_API_KEY: ${{ secrets.FOSSA_API_KEY }}

27
.github/workflows/test.yml vendored Normal file
View File

@ -0,0 +1,27 @@
on:
push:
branches: [master]
tags: ['*']
pull_request:
types: [opened, synchronize, reopened]
name: test
jobs:
test:
strategy:
matrix:
go-version: [1.15.x]
runs-on: ubuntu-latest
steps:
- name: Install Go
uses: actions/setup-go@v2
with:
go-version: ${{ matrix.go-version }}
- name: Checkout code
uses: actions/checkout@v2
- name: Install dependencies
run: make deps
- name: Lint and test
run: make all
# TEST_FLAGS="-covermode=atomic -coverpkg=./... -coverprofile=coverage.txt"
# - name: Upload coverage to Codecov
# uses: codecov/codecov-action@v1

155
.gitignore vendored Normal file
View File

@ -0,0 +1,155 @@
# Created by https://www.toptal.com/developers/gitignore/api/go,visualstudiocode,jetbrains+all,macos
# Edit at https://www.toptal.com/developers/gitignore?templates=go,visualstudiocode,jetbrains+all,macos
### Go ###
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
# Test binary, built with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Dependency directories (remove the comment below to include it)
# vendor/
### Go Patch ###
/vendor/
/Godeps/
### JetBrains+all ###
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
# Generated files
.idea/**/contentModel.xml
# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
# Gradle
.idea/**/gradle.xml
.idea/**/libraries
# Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn. Uncomment if using
# auto-import.
# .idea/artifacts
# .idea/compiler.xml
# .idea/jarRepositories.xml
# .idea/modules.xml
# .idea/*.iml
# .idea/modules
# *.iml
# *.ipr
# CMake
cmake-build-*/
# Mongo Explorer plugin
.idea/**/mongoSettings.xml
# File-based project format
*.iws
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
# Editor-based Rest Client
.idea/httpRequests
# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser
### JetBrains+all Patch ###
# Ignores the whole .idea folder and all .iml files
# See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360
.idea/
# Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023
*.iml
modules.xml
.idea/misc.xml
*.ipr
# Sonarlint plugin
.idea/sonarlint
### macOS ###
# General
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
### VisualStudioCode ###
.vscode/*
!.vscode/tasks.json
!.vscode/launch.json
*.code-workspace
### VisualStudioCode Patch ###
# Ignore all local history of files
.history
.ionide
# End of https://www.toptal.com/developers/gitignore/api/go,visualstudiocode,jetbrains+all,macos

50
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,50 @@
# CONTRIBUTING
Contributions are always welcome, no matter how large or small. Before contributing,
please check to see if the issue is already being tracked and if there is already a PR.
## Setup
> Install Go 1.15.x
Chestnut uses the Go Modules support built into Go 1.11 to build.
The easiest is to clone Chestnut in a directory outside of GOPATH,
as in the following example:
```sh
$ git clone https://github.com/jrapoport/chestnut
$ cd chestnut
$ make deps
```
## Running examples
```sh
$ make examples
```
## Testing
```sh
$ make test
```
## Pull Requests
Pull requests are welcome!.
1. Fork the repo and create your branch from `master`.
2. If you've added code that should be tested, add tests.
3. If you've changed APIs, update the documentation.
4. Ensure the test suite passes.
5. Make sure your code lints.
```sh
# will run fmt, lint, & vet
$ make pr
```
## License
By contributing to Chestnut, you agree that your contributions will be licensed
under its [MIT license](LICENSE).

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 jrapoport
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

47
Makefile Normal file
View File

@ -0,0 +1,47 @@
GO := go
GO_PATH := $(shell $(GO) env GOPATH)
GO_BIN := $(GO_PATH)/bin
GO_MOD := $(GO) mod
GO_GET := $(GO) get -u -v
GO_FMT := $(GO) fmt
GO_RUN := $(GO) run
GO_TEST:= $(GO) test -p 1 -v
GO_LINT := $(GO_BIN)/golint
# BUG: go vet: structtag field repeats json warning with valid override #40102
# https://github.com/golang/go/issues/40102
GO_VET:= $(GO) vet -v -structtag=false
$(GO_LINT):
$(GO_GET) golang.org/x/lint/golint
deps:
$(GO_MOD) tidy
$(GO_MOD) download
fmt:
$(GO_FMT) ./...
lint: $(GO_LINT)
$(GO_LINT) ./...
vet:
$(GO_VET) ./...
pr: lint vet
test:
$(GO_TEST) $(TEST_FLAGS) ./...
# build any example with make <name>
EXAMPLE_NAME := $(word 1, $(MAKECMDGOALS))
$(EXAMPLE_NAME):
ifneq ($(filter examples/$(EXAMPLE_NAME),$(wildcard examples/*)),)
$(GO_RUN) examples/$(EXAMPLE_NAME)/main.go
endif
all: pr test
.DEFAULT_GOAL := all
.PHONY: deps fmt lint vet keystore

1027
README.md Normal file

File diff suppressed because it is too large Load Diff

445
chestnut.go Normal file
View File

@ -0,0 +1,445 @@
package chestnut
import (
"errors"
"fmt"
"reflect"
"github.com/jrapoport/chestnut/encoding/compress"
"github.com/jrapoport/chestnut/encoding/compress/zstd"
"github.com/jrapoport/chestnut/encoding/json"
"github.com/jrapoport/chestnut/encoding/json/encoders/secure"
"github.com/jrapoport/chestnut/log"
"github.com/jrapoport/chestnut/storage"
"github.com/jrapoport/chestnut/value"
)
// Chestnut is used to manage an encrypted store. It provides additional features such
// as chained encryption, independently secured secrets, sparse encryption, and hashing.
// For more detail, SEE: https://github.com/jrapoport/chestnut/blob/master/README.md
type Chestnut struct {
opts ChestOptions
store storage.Storage
log log.Logger
}
// NewChestnut is used to create a new chestnut encrypted store.
func NewChestnut(store storage.Storage, opt ...ChestOption) *Chestnut {
const logName = "chestnut"
//logger := storage.LoggerFromStore(store, logName)
opts := applyOptions(DefaultChestOptions, opt...)
logger := log.Named(opts.log, logName)
cn := &Chestnut{opts, store, logger}
if err := cn.validConfig(); err != nil {
logger.Fatal(err)
return nil
}
return cn
}
func (cn *Chestnut) validConfig() error {
if cn.store == nil {
return errors.New("store required")
}
if cn.opts.encryptor == nil {
return errors.New("encryptor is required")
}
if cn.opts.compressor != nil || cn.opts.decompressor != nil {
cn.opts.compression = compress.Custom
}
if cn.opts.compression == compress.Custom && cn.opts.compressor == nil {
return errors.New("compressor is required")
}
if cn.opts.compression == compress.Custom && cn.opts.decompressor == nil {
return errors.New("decompressor is required")
}
return nil
}
// Open the storage chest
func (cn *Chestnut) Open() error {
if err := cn.validConfig(); err != nil {
return err
}
cn.log.Debug("opening storage chest")
if err := cn.store.Open(); err != nil {
return cn.logError("open", err)
}
cn.log.Info("storage chest open")
if cn.opts.encryptor != nil {
cn.log.Infof("using encryption: %s",
cn.opts.encryptor.Name())
}
if cn.opts.compression != compress.None {
cn.log.Infof("%s compression active",
cn.opts.compression)
}
if !cn.opts.overwrites {
cn.log.Info("overwrites are disabled")
}
return nil
}
// Put encrypts the plaintext and stores it at key.
func (cn *Chestnut) Put(name string, key []byte, plaintext []byte) error {
cn.log.Debugf("put: %d plaintext bytes to key: %s", len(plaintext), key)
// the store will make these same checks, but encryption
// is expensive, so we are going to do them upfront here.
if err := storage.ValidKey(name, key); err != nil {
return cn.logError("put", err)
} else if len(plaintext) <= 0 {
err = errors.New("plaintext cannot be empty")
return cn.logError("put", err)
} else if err = cn.CanPut(name, key); err != nil {
return cn.logError("put", err)
}
if cn.opts.compression != compress.None {
var err error
if plaintext, err = cn.compress(plaintext); err != nil {
return cn.logError("put", err)
}
}
cn.log.Debugf("put: encrypt %d bytes", len(plaintext))
cipherText, err := cn.encrypt(plaintext)
if err != nil {
return cn.logError("put", err)
}
cn.log.Debugf("put: encrypted %d bytes", len(cipherText))
return cn.logError("", cn.store.Put(name, key, cipherText))
}
// Get decrypts the ciphertext at key and returns the plaintext.
func (cn *Chestnut) Get(name string, key []byte) ([]byte, error) {
cn.log.Debugf("get: ciphertext at key: %s", key)
ciphertext, err := cn.store.Get(name, key)
if err != nil {
return nil, cn.logError("", err)
}
cn.log.Debugf("get: decrypt %d bytes", len(ciphertext))
plaintext, err := cn.decrypt(ciphertext)
if err != nil {
return nil, cn.logError("get", err)
}
cn.log.Debugf("put: decrypted %d bytes", len(plaintext))
// decompress will check to see if the data is compressed.
// if sit is not compressed, it returns the buffer.
if plaintext, err = cn.decompress(plaintext); err != nil {
return nil, cn.logError("get", err)
}
return plaintext, nil
}
// Save encrypts the struct in v and stores the encoded result at key.
func (cn *Chestnut) Save(name string, key []byte, v interface{}) error {
cn.log.Debugf("save: %v value to key: %s", reflect.TypeOf(v), key)
// the store will make these same checks, but encryption
// is expensive, so we are going to do them upfront here.
if err := storage.ValidKey(name, key); err != nil {
return cn.logError("save", err)
} else if v == nil {
err = errors.New("value cannot be nil")
return cn.logError("save", err)
} else if err = cn.CanPut(name, key); err != nil {
return cn.logError("save", err)
}
cn.log.Debugf("save: encrypt %v value", reflect.TypeOf(v))
ciphertext, err := cn.marshal(v)
if err != nil {
return cn.logError("save", err)
}
cn.log.Debugf("save: put %d encrypted bytes", len(ciphertext))
if err = cn.store.Put(name, key, ciphertext); err != nil {
return cn.logError("save", err)
}
cn.log.Debugf("save: encrypted %v value", reflect.TypeOf(v))
return nil
}
// Load decrypts the struct at key and returns the decoded result in v.
func (cn *Chestnut) Load(name string, key []byte, v interface{}) error {
cn.log.Debugf("load: %v value at key: %s", reflect.TypeOf(v), key)
if err := cn.load(name, key, v, false); err != nil {
return cn.logError("load", err)
}
cn.log.Debugf("load: decrypted %v value", reflect.TypeOf(v))
return nil
}
// Sparse loads the struct at key and returns the sparsely decoded result in v.
// Unlike Load, it does not decrypt the encoded struct and secure fields are
// replaced with empty values. If the struct was not saved as a sparsely encoded
// struct this has no effect and is equivalent to calling Load. Structs must
// have been saved with secure fields to be loaded as sparse structs by Sparse.
func (cn *Chestnut) Sparse(name string, key []byte, v interface{}) error {
cn.log.Debugf("sparse: %v value at key: %s", reflect.TypeOf(v), key)
if err := cn.load(name, key, v, true); err != nil {
return cn.logError("sparse", err)
}
cn.log.Debugf("sparse: decrypted sparse %v value", reflect.TypeOf(v))
return nil
}
// SaveKeyed encrypts the keyed value and stores the result.
func (cn *Chestnut) SaveKeyed(v value.Keyed) error {
if v == nil || reflect.ValueOf(v).IsNil() {
err := errors.New("value cannot be nil")
return cn.logError("save value", err)
} else if err := v.ValidKey(); err != nil {
return cn.logError("save value", err)
}
err := cn.Save(v.Namespace(), v.Key(), v)
return cn.logError("save value", err)
}
// LoadKeyed decrypts the keyed value and returns the result.
func (cn *Chestnut) LoadKeyed(v value.Keyed) error {
if v == nil || reflect.ValueOf(v).IsNil() {
err := errors.New("value cannot be nil")
return cn.logError("load value", err)
} else if err := v.ValidKey(); err != nil {
return cn.logError("load value", err)
}
err := cn.Load(v.Namespace(), v.Key(), v)
return cn.logError("load value", err)
}
// SparseKeyed sparsely loads the keyed value and returns the result.
// Unlike LoadKeyed, it does not decrypt the keyed value. SEE: Sparse.
func (cn *Chestnut) SparseKeyed(v value.Keyed) error {
if v == nil || reflect.ValueOf(v).IsNil() {
err := errors.New("value cannot be nil")
return cn.logError("sparse value", err)
} else if err := v.ValidKey(); err != nil {
return cn.logError("sparse value", err)
}
err := cn.Sparse(v.Namespace(), v.Key(), v)
return cn.logError("sparse value", err)
}
// Has checks for a key in the storage chest. Has returns true
// if the key is found, otherwise false.
func (cn *Chestnut) Has(name string, key []byte) (bool, error) {
cn.log.Debugf("has: key: %s", key)
has, err := cn.store.Has(name, key)
cn.log.Debugf("has: key %s: %t", key, has)
return has, cn.logError("", err)
}
// CanPut returns nil if writing to key is ok. If overwrites
// are disabled and the key exists, ErrForbidden is returned.
func (cn *Chestnut) CanPut(name string, key []byte) error {
cn.log.Debugf("can put: key: %s", key)
if err := storage.ValidKey(name, key); err != nil {
return cn.logError("can put", err)
}
if cn.opts.overwrites {
cn.log.Debug("can put: overwrites enabled")
return nil
}
// if overwrites are disabled check to see if we have the key. if
// the key does not exist, has will return an error. technically,
// this could get confused because an error would also be returned
// if the key was invalid, but since we already check that above
// we can safely assume that's not the error. since we expect an error
// when the key is not found has Has will log it, we can ignore it.
if has, _ := cn.Has(name, key); has {
return cn.logError("can put", ErrForbidden)
}
// we didn't find the key and there is no error, this is not an overwrite.
cn.log.Debugf("can put: can write to key: %s", key)
return nil
}
// Delete removes a key from the storage chest.
func (cn *Chestnut) Delete(name string, key []byte) error {
cn.log.Debugf("delete: key: %s", key)
return cn.logError("", cn.store.Delete(name, key))
}
// List returns a list of keys in the namespace.
func (cn *Chestnut) List(namespace string) ([][]byte, error) {
cn.log.Infof("list: all keys")
keys, err := cn.store.List(namespace)
cn.log.Debugf("list: found %d keys: %s", len(keys), keys)
return keys, cn.logError("", err)
}
// Export saves a copy of the storage chest to directory at path.
func (cn *Chestnut) Export(path string) error {
cn.log.Debugf("export: to path: %s", path)
return cn.logError("", cn.store.Export(path))
}
// Close the storage chest
func (cn *Chestnut) Close() error {
cn.log.Info("closing storage chest")
if err := cn.store.Close(); err != nil {
return cn.logError("close", err)
}
cn.log.Info("storage chest closed")
return nil
}
// Logger gets a copy of the logger from the storage chest's options
func (cn *Chestnut) Logger() log.Logger {
return cn.opts.log
}
// SetLogger sets the logger for the storage chest
func (cn *Chestnut) SetLogger(l log.Logger) {
if l == nil {
l = log.Log
}
cn.log = l
}
// load decrypts the secure or sparse value at key and stores the result in v.
func (cn *Chestnut) load(name string, key []byte, v interface{}, sparse bool) error {
if v == nil {
return errors.New("value cannot be nil")
}
ciphertext, err := cn.store.Get(name, key)
if err != nil {
return err
}
return cn.unmarshal(ciphertext, v, sparse)
}
// encrypt returns the plaintext data as ciphertext.
func (cn *Chestnut) encrypt(plaintext []byte) (ciphertext []byte, err error) {
cn.log.Debugf("encrypt: encrypting %d bytes", len(plaintext))
ciphertext, err = cn.opts.encryptor.Encrypt(plaintext)
if err != nil {
err = cn.logError("encrypt", err)
return
}
cn.log.Debugf("encrypt: encrypted %d bytes", len(ciphertext))
return
}
// decrypt returns the ciphertext data as plaintext.
func (cn *Chestnut) decrypt(ciphertext []byte) (plaintext []byte, err error) {
cn.log.Debugf("decrypt: decrypting %d bytes", len(ciphertext))
plaintext, err = cn.opts.encryptor.Decrypt(ciphertext)
if err != nil {
err = cn.logError("decrypt", err)
return
}
cn.log.Debugf("decrypt: decrypted %d bytes", len(plaintext))
return
}
// marshal returns the JSON encoding of v as ciphertext.
func (cn *Chestnut) marshal(v interface{}) (ciphertext []byte, err error) {
if v == nil {
err = errors.New("value cannot be nil")
return nil, cn.logError("marshal", err)
}
cn.log.Debugf("marshal: %v value", reflect.TypeOf(v))
ciphertext, err = json.SecureMarshal(v, cn.encrypt, secure.WithLogger(cn.log))
if err != nil {
err = cn.logError("marshal", err)
return
}
cn.log.Debugf("marshal: encrypted %d bytes", len(ciphertext))
return
}
// unmarshal returns the plaintext decoded JSON value at v.
func (cn *Chestnut) unmarshal(ciphertext []byte, v interface{}, sparse bool) error {
if v == nil {
err := errors.New("value cannot be nil")
return cn.logError("unmarshal", err)
}
cn.log.Debugf("unmarshal: decrypt %d bytes to %v value",
len(ciphertext), reflect.TypeOf(v))
opts := []secure.Option{secure.WithLogger(cn.log)}
if sparse {
cn.log.Debug("use sparse decoding")
opts = append(opts, secure.SparseDecode())
}
err := json.SecureUnmarshal(ciphertext, v, cn.decrypt, opts...)
if err != nil {
return cn.logError("unmarshal", err)
}
cn.log.Debugf("unmarshal: decrypted %v value", reflect.TypeOf(v))
return nil
}
func (cn *Chestnut) compress(data []byte) ([]byte, error) {
format := cn.opts.compression
if format == compress.None {
return data, nil
}
var compressor compress.CompressorFunc
switch format {
case compress.Zstd:
compressor = zstd.Compress
case compress.Custom:
compressor = cn.opts.compressor
default:
break
}
if compressor == nil {
err := fmt.Errorf("%s unsupported", format)
return nil, cn.logError("compress", err)
}
size := len(data)
cn.log.Debugf("compressing %d bytes with %s", size, format)
compressed, err := compressor(data)
if err != nil {
return nil, cn.logError("compress", err)
}
compressed = compress.EncodeFormat(compressed, format)
cn.log.Debugf("%s compressed encrypted bytes to %d (%0.2f%% reduction)",
format, len(compressed), calcReduction(size, len(compressed)))
return compressed, nil
}
func calcReduction(oldSize, newSize int) float64 {
return ((float64(oldSize) - float64(newSize)) / float64(oldSize)) * 100
}
func (cn *Chestnut) decompress(data []byte) ([]byte, error) {
// check for compression
compressed, format := compress.DecodeFormat(data)
// this does not appear to be data we compressed
if format == compress.None {
return compressed, nil
}
var decompressor compress.DecompressorFunc
switch format {
case compress.Zstd:
decompressor = zstd.Decompress
case compress.Custom:
decompressor = cn.opts.decompressor
default:
break
}
if decompressor == nil {
err := fmt.Errorf("%s unsupported", format)
return nil, cn.logError("decompress", err)
}
cn.log.Debugf("decompressing %d bytes with %s", len(compressed), format)
decompressed, err := decompressor(compressed)
if err != nil {
return nil, cn.logError("decompress", err)
}
cn.log.Debugf("decompressed %d bytes with %s",
len(decompressed), format)
return decompressed, nil
}
func (cn *Chestnut) logError(name string, err error) error {
if err == nil {
return nil
}
if name != "" {
err = fmt.Errorf("%s: %w", name, err)
}
cn.log.Error(err)
return err
}
// ErrForbidden the storage operation is forbidden
var ErrForbidden = errors.New("forbidden")

607
chestnut_test.go Normal file
View File

@ -0,0 +1,607 @@
package chestnut
import (
"path/filepath"
"reflect"
"sort"
"testing"
"github.com/google/uuid"
"github.com/jrapoport/chestnut/encoding/compress"
"github.com/jrapoport/chestnut/encoding/compress/zstd"
"github.com/jrapoport/chestnut/encryptor"
"github.com/jrapoport/chestnut/encryptor/aes"
"github.com/jrapoport/chestnut/encryptor/crypto"
"github.com/jrapoport/chestnut/log"
"github.com/jrapoport/chestnut/storage"
"github.com/jrapoport/chestnut/storage/nuts"
"github.com/jrapoport/chestnut/value"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
)
type TObject struct {
ValueA string `json:"value_a"`
ValueB int `json:"value_b"`
}
type THash struct {
TObject
HashValueA string `json:"hash_value_a,hash"`
HashValueB int `json:"hash_value_b,hash"`
}
type TSecure struct {
TObject
SecureValueA string `json:"sparse_value_a,secure"`
SecureValueB int `json:"sparse_value_b,secure"`
}
type TAll struct {
TObject
Hash THash
Secure TSecure
AllValueA string `json:"all_value_a,secure,hash"`
AllValueB int `json:"all_value_b,secure,hash"`
}
type testCase struct {
key string
value string
err assert.ErrorAssertionFunc
assertHas assert.BoolAssertionFunc
}
var (
testName = "test-namespace"
testValue = "i-am-plaintext"
textSecret = crypto.TextSecret("i-am-a-good-secret")
encryptorOpt = WithAES(crypto.Key256, aes.CFB, textSecret)
)
var lorumIpsum = `Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed
do eiusmod tempor incididunt ut labore et dolore magna aliqua. Eu consequat ac
felis donec et odio pellentesqu diam. Hac habitasse platea dictumst quisque
sagittis purus. Risus at ultrices mi tempus imperdiet nulla malesuada
pellentesque. Vitae justo eget magna fermentum iaculis eu non diam phasellus.
Cursus risus at ultrices mi tempus imperdiet. Ante metus dictum at tempor
commodo. Accumsan lacus vel facilisis volutpat est velit egestas. Dolor sed
viverra ipsum nunc aliquet bibendum enim facilisis. Tristique risus nec feugiat
in. Feugiat nisl pretium fusce id velit ut tortor pretium. Eget magna fermentum
iaculis eu. Velit laoreet id donec ultrices tincidunt. Tristique senectus et
netus et malesuada fames ac turpis egestas. Diam phasellus vestibulum lorem sed
risus ultricies tristique. Cursus mattis molestie a iaculis. Sem nulla pharetra
diam sit amet nisl suscipit adipiscing bibendum. Viverra justo nec ultrices dui
sapien eget. Ornare arcu dui vivamus arcu felis. Egestas integer eget aliquet
nibh praesent. Risus feugiat in ante metus dictum at tempor commodo. Id ornare
arcu odio ut sem. Tincidunt dui ut ornare lectus. Sagittis orci a scelerisque
purus. Suscipit adipiscing bibendum est ultricies integer quis. Libero nunc
consequat interdum varius sit amet mattis vulputate. Euismod lacinia at quis
risus sed vulputate. Molestie at elementum eu facilisis sed odio morbi quis.
Nunc sed augue lacus viverra vitae congue eu consequat ac. Interdum velit
euismod in pellentesque. Mi sit amet mauris commodo quis imperdiet massa
tincidunt. Eget magna fermentum iaculis eu. Metus aliquam eleifend mi in nulla
posuere sollicitudin. Nisi est sit amet facilisis magna. Tellus in hac habitasse
platea dictumst. Venenatis tellus in metus vulputate eu scelerisque. Feugiat sed
lectus vestibulum mattis. Sit amet nisl suscipit adipiscing bibendum est
ultricies. Ipsum nunc aliquet bibendum enim facilisis gravida neque. Duis
tristique sollicitudin nibh sit amet commodo. Purus in massa tempor nec. Eget
aliquet nibh praesent tristique magna sit amet. Mauris in aliquam semf ringilla`
var valOut = testValue
var objectSrc = TObject{
ValueA: testValue,
ValueB: 42,
}
var objOut = objectSrc
var hashSrc = THash{
TObject: objectSrc,
HashValueA: testValue,
HashValueB: 1600,
}
var hashOut = THash{
TObject: objOut,
HashValueA: "sha256:0fdabf2262ab284503a700b876994fc95ee4690133db96acfb5f9ea526d71e94",
HashValueB: 1600,
}
var secureSrc = TSecure{
TObject: objectSrc,
SecureValueA: testValue,
SecureValueB: 1337,
}
var secureOut = TSecure{
TObject: objOut,
SecureValueA: "i-am-plaintext",
SecureValueB: 1337,
}
var secureSparse = TSecure{
TObject: objOut,
SecureValueA: "",
SecureValueB: 0,
}
var allSrc = TAll{
TObject: objectSrc,
Hash: hashSrc,
Secure: secureSrc,
AllValueA: "i-am-a-random-string",
AllValueB: 0xbeef,
}
var allOut = TAll{
TObject: objOut,
Hash: hashOut,
Secure: secureOut,
AllValueA: "sha256:50d5a31ee8353543fe8d6c0de2c9d5e5e2cdb7b973c4f9c25f99fcdf41bd5eec",
AllValueB: 0xbeef,
}
var allSparse = TAll{
TObject: objOut,
Hash: hashOut,
Secure: secureSparse,
AllValueA: "",
AllValueB: 0,
}
type objTest struct {
key string
src interface{}
dst interface{}
out interface{}
spr interface{}
err assert.ErrorAssertionFunc
}
var putTests = []testCase{
{"", "", assert.Error, assert.False},
{"a", "", assert.Error, assert.False},
{"b", testValue, assert.NoError, assert.True},
{"c/c", testValue, assert.NoError, assert.True},
{".d", testValue, assert.NoError, assert.True},
{newKey(), testValue, assert.NoError, assert.True},
}
var tests = append(putTests,
testCase{"not-found", "", assert.Error, assert.False},
)
var objTests = []objTest{
{"", nil, nil, nil, nil, assert.Error},
{"a", nil, nil, nil, nil, assert.Error},
{"b", testValue, new(string), &valOut, &valOut, assert.NoError},
{newKey(), testValue, new(string), &valOut, &valOut, assert.NoError},
{newKey(), objectSrc, &TObject{}, &objOut, &objOut, assert.NoError},
{newKey(), hashSrc, &THash{}, &hashOut, &hashOut, assert.NoError},
{newKey(), secureSrc, &TSecure{}, &secureOut, &secureSparse, assert.NoError},
{newKey(), allSrc, &TAll{}, &allOut, &allSparse, assert.NoError},
}
func newKey() string {
return uuid.New().String()
}
func nutsDBStore(t *testing.T) storage.Storage {
path := t.TempDir()
store := nuts.NewStore(path)
assert.NotNil(t, store)
return store
}
type ChestnutTestSuite struct {
suite.Suite
cn *Chestnut
}
func TestChestnut(t *testing.T) {
suite.Run(t, new(ChestnutTestSuite))
}
func (ts *ChestnutTestSuite) SetupTest() {
store := nutsDBStore(ts.T())
assert.NotNil(ts.T(), store)
ts.cn = NewChestnut(store, encryptorOpt)
assert.NotNil(ts.T(), ts.cn)
err := ts.cn.Open()
assert.NoError(ts.T(), err)
}
func (ts *ChestnutTestSuite) TearDownTest() {
err := ts.cn.Close()
assert.NoError(ts.T(), err)
}
func (ts *ChestnutTestSuite) BeforeTest(_, testName string) {
switch testName {
case "TestChestnut_Put",
"TestChestnut_List",
"TestChestnut_Save":
break
case "TestChestnut_Load",
"TestChestnut_Sparse":
ts.TestChestnut_Save()
break
case "TestChestnut_LoadKeyed",
"TestChestnut_SparseKeyed":
ts.TestChestnut_SaveKeyed()
break
default:
ts.TestChestnut_Put()
}
}
func (ts *ChestnutTestSuite) TestChestnut_Put() {
for i, test := range putTests {
err := ts.cn.Put(testName, []byte(test.key), []byte(test.value))
test.err(ts.T(), err, "%d test key: %s", i, test.key)
}
}
func (ts *ChestnutTestSuite) TestChestnut_Get() {
for i, test := range tests {
value, err := ts.cn.Get(testName, []byte(test.key))
test.err(ts.T(), err, "%d test key: %s", i, test.key)
assert.Equal(ts.T(), test.value, string(value),
"%d test key: %s", i, test.key)
}
}
func (ts *ChestnutTestSuite) TestChestnut_Save() {
for i, test := range objTests {
err := ts.cn.Save(testName, []byte(test.key), test.src)
test.err(ts.T(), err, "%d test key: %s", i, test.key)
}
}
func (ts *ChestnutTestSuite) TestChestnut_Load() {
for i, test := range objTests {
if test.dst == nil {
continue
}
typ := reflect.ValueOf(test.dst).Elem().Type()
ptr := reflect.New(typ).Interface()
err := ts.cn.Load(testName, []byte(test.key), ptr)
test.err(ts.T(), err, "%d test key: %s", i, test.key)
assert.Equal(ts.T(), test.out, ptr)
}
}
func (ts *ChestnutTestSuite) TestChestnut_Sparse() {
for i, test := range objTests {
if test.dst == nil {
continue
}
typ := reflect.ValueOf(test.dst).Elem().Type()
ptr := reflect.New(typ).Interface()
err := ts.cn.Sparse(testName, []byte(test.key), ptr)
test.err(ts.T(), err, "%d test key: %s", i, test.key)
assert.Equal(ts.T(), test.spr, ptr)
}
}
var keyedObj = value.NewSecureValue(newKey(), []byte(lorumIpsum))
func (ts *ChestnutTestSuite) TestChestnut_SaveKeyed() {
objs := []struct {
in *value.Secure
err assert.ErrorAssertionFunc
}{
{nil, assert.Error},
{&value.Secure{}, assert.Error},
{keyedObj, assert.NoError},
}
for i, test := range objs {
err := ts.cn.SaveKeyed(test.in)
test.err(ts.T(), err, "%d test", i)
}
}
func (ts *ChestnutTestSuite) TestChestnut_LoadKeyed() {
objs := []struct {
in *value.Secure
out *value.Secure
err assert.ErrorAssertionFunc
}{
{nil, nil, assert.Error},
{&value.Secure{}, nil, assert.Error},
{&value.Secure{ID: value.ID{ID: "not-found"}}, nil, assert.Error},
{&value.Secure{ID: keyedObj.ID}, keyedObj, assert.NoError},
}
for i, test := range objs {
err := ts.cn.LoadKeyed(test.in)
test.err(ts.T(), err, "%d test", i)
if err == nil {
assert.Equal(ts.T(), test.out, test.in)
}
}
}
func (ts *ChestnutTestSuite) TestChestnut_SparseKeyed() {
sparse := &value.Secure{
ID: keyedObj.ID,
Metadata: map[string]interface{}{},
}
objs := []struct {
in *value.Secure
out *value.Secure
err assert.ErrorAssertionFunc
}{
{nil, nil, assert.Error},
{&value.Secure{}, nil, assert.Error},
{in: &value.Secure{ID: value.ID{ID: "not-found"}}, err: assert.Error},
{&value.Secure{ID: keyedObj.ID}, sparse, assert.NoError},
}
for i, test := range objs {
err := ts.cn.SparseKeyed(test.in)
test.err(ts.T(), err, "%d test", i)
if err == nil {
assert.Equal(ts.T(), test.out, test.in)
}
}
}
func (ts *ChestnutTestSuite) TestChestnut_Has() {
for i, test := range tests {
has, _ := ts.cn.Has(testName, []byte(test.key))
test.assertHas(ts.T(), has, "%d test key: %s", i, test.key)
}
}
func (ts *ChestnutTestSuite) TestChestnut_List() {
const listLen = 100
list := make([]string, listLen)
for i := 0; i < listLen; i++ {
list[i] = uuid.New().String()
err := ts.cn.Put(testName, []byte(list[i]), []byte(testValue))
assert.NoError(ts.T(), err)
}
keys, err := ts.cn.List(testName)
assert.NoError(ts.T(), err)
assert.Len(ts.T(), keys, listLen)
// put both lists in the same order so we can compare them
strKeys := make([]string, len(keys))
for i, k := range keys {
strKeys[i] = string(k)
}
sort.Strings(list)
sort.Strings(strKeys)
assert.Equal(ts.T(), list, strKeys)
}
func (ts *ChestnutTestSuite) TestChestnut_Delete() {
var deleteTests = []struct {
key string
err assert.ErrorAssertionFunc
}{
{"", assert.Error},
{"a", assert.NoError},
{"b", assert.NoError},
{"c/c", assert.NoError},
{".d", assert.NoError},
{"eee", assert.NoError},
{"not-found", assert.NoError},
}
for i, test := range deleteTests {
err := ts.cn.Delete(testName, []byte(test.key))
test.err(ts.T(), err, "%d test key: %s", i, test.key)
}
}
func (ts *ChestnutTestSuite) TestStore_Export() {
err := ts.cn.Export(ts.T().TempDir())
assert.NoError(ts.T(), err)
}
func (ts *ChestnutTestSuite) TestStore_SecureEntry() {
const (
testKey = "hello"
testValue = "world"
testData = "foobar"
)
entries := make([]*value.Secure, 20)
for i := range entries {
e := value.NewSecureValue(uuid.New().String(), []byte(testData))
e.SetMetadata(testKey, testValue)
entries[i] = e
}
for _, e := range entries {
err := ts.cn.Save(testName, e.Key(), e)
assert.NoError(ts.T(), err)
}
for _, e := range entries {
spr := &value.Secure{}
err := ts.cn.Sparse(testName, e.Key(), &spr)
assert.NoError(ts.T(), err)
assert.Empty(ts.T(), spr.Data)
assert.Equal(ts.T(), testValue, spr.GetMetadata(testKey))
}
for _, e := range entries {
spr := &value.Secure{}
err := ts.cn.Load(testName, e.Key(), &spr)
assert.NoError(ts.T(), err)
assert.Equal(ts.T(), testData, string(spr.Data))
assert.Equal(ts.T(), testValue, spr.GetMetadata(testKey))
}
}
func TestChestnut_OverwritesDisabled(t *testing.T) {
testOptionDisableOverwrites(t, false)
}
func TestChestnut_OverwritesEnabled(t *testing.T) {
testOptionDisableOverwrites(t, true)
}
func testOptionDisableOverwrites(t *testing.T, enabled bool) {
key := newKey()
path := filepath.Join(t.TempDir())
store := nuts.NewStore(path)
assert.NotNil(t, store)
opts := []ChestOption{
encryptorOpt,
}
assertErr := assert.NoError
if !enabled {
assertErr = assert.Error
opts = append(opts, OverwritesForbidden())
}
cn := NewChestnut(store, opts...)
assert.NotNil(t, cn)
assert.Equal(t, enabled, cn.opts.overwrites)
defer func() {
err := cn.Close()
assert.NoError(t, err)
}()
err := cn.Open()
assert.NoError(t, err)
err = cn.Put(testName, []byte(key), []byte(testValue))
assert.NoError(t, err)
// this should fail with an error if overwrites are disabled
err = cn.Put(testName, []byte(key), []byte(testValue))
assertErr(t, err)
}
func TestChestnut_ChainedEncryptor(t *testing.T) {
var operation = "encrypting"
// initialize a keystore with a chained encryptor
openSecret := func(s crypto.Secret) []byte {
t.Logf("%s with secret %s", operation, s.ID())
return []byte(s.ID())
}
managedSecret := crypto.NewManagedSecret(uuid.New().String(), "i-am-a-managed-secret")
secureSecret1 := crypto.NewSecureSecret(uuid.New().String(), openSecret)
secureSecret2 := crypto.NewSecureSecret(uuid.New().String(), openSecret)
encryptorChainOpt := WithEncryptorChain(
encryptor.NewAESEncryptor(crypto.Key128, aes.CFB, secureSecret1),
encryptor.NewAESEncryptor(crypto.Key192, aes.CTR, managedSecret),
encryptor.NewAESEncryptor(crypto.Key256, aes.GCM, secureSecret2),
)
path := t.TempDir()
store := nuts.NewStore(path)
assert.NotNil(t, store)
cn := NewChestnut(store, encryptorChainOpt)
assert.NotNil(t, cn)
defer func() {
err := cn.Close()
assert.NoError(t, err)
}()
err := cn.Open()
assert.NoError(t, err)
key := newKey()
err = cn.Put(testName, []byte(key), []byte(testValue))
assert.NoError(t, err)
operation = "decrypting"
v, err := cn.Get(testName, []byte(key))
assert.NotEmpty(t, v)
assert.NoError(t, err)
assert.Equal(t, []byte(testValue), v)
err = cn.Delete(testName, []byte(key))
assert.NoError(t, err)
e := value.NewSecureValue(uuid.New().String(), []byte(testValue))
err = cn.Save(testName, []byte(key), e)
assert.NoError(t, err)
se1 := &value.Secure{}
err = cn.Sparse(testName, []byte(key), se1)
assert.NoError(t, err)
se2 := &value.Secure{}
err = cn.Load(testName, []byte(key), se2)
assert.NoError(t, err)
}
func TestChestnut_Compression(t *testing.T) {
compOpt := WithCompression(compress.Zstd)
key := newKey()
path := filepath.Join(t.TempDir())
store := nuts.NewStore(path)
assert.NotNil(t, store)
cn := NewChestnut(store, encryptorOpt, compOpt)
assert.NotNil(t, cn)
defer func() {
err := cn.Close()
assert.NoError(t, err)
}()
err := cn.Open()
assert.NoError(t, err)
err = cn.Put(testName, []byte(key), []byte(lorumIpsum))
assert.NoError(t, err)
val, err := cn.Get(testName, []byte(key))
assert.NoError(t, err)
assert.Equal(t, lorumIpsum, string(val))
}
func TestChestnut_Compressors(t *testing.T) {
compOpt := WithCompressors(zstd.Compress, zstd.Decompress)
key := newKey()
path := filepath.Join(t.TempDir())
store := nuts.NewStore(path)
assert.NotNil(t, store)
cn := NewChestnut(store, encryptorOpt, compOpt)
assert.NotNil(t, cn)
defer func() {
err := cn.Close()
assert.NoError(t, err)
}()
err := cn.Open()
assert.NoError(t, err)
err = cn.Put(testName, []byte(key), []byte(lorumIpsum))
assert.NoError(t, err)
val, err := cn.Get(testName, []byte(key))
assert.NoError(t, err)
assert.Equal(t, lorumIpsum, string(val))
}
func TestChestnut_OpenErr(t *testing.T) {
cn := &Chestnut{}
err := cn.Open()
assert.Error(t, err)
}
func TestChestnut_SetLogger(t *testing.T) {
path := t.TempDir()
store := nuts.NewStore(path)
assert.NotNil(t, store)
cn := NewChestnut(store, encryptorOpt)
cn.SetLogger(log.NewZapLoggerWithLevel(log.DebugLevel))
defer func() {
err := cn.Close()
assert.NoError(t, err)
}()
err := cn.Open()
assert.NoError(t, err)
}
func TestChestnut_WithLogger(t *testing.T) {
levels := []log.Level{
log.DebugLevel,
log.InfoLevel,
log.WarnLevel,
log.ErrorLevel,
log.PanicLevel,
}
type LoggerOpt func(log.Level) ChestOption
logOpts := []LoggerOpt{
WithLogrusLogger,
WithStdLogger,
WithZapLogger,
}
path := t.TempDir()
store := nuts.NewStore(path)
assert.NotNil(t, store)
for _, level := range levels {
for _, logOpt := range logOpts {
opt := logOpt(level)
cn := NewChestnut(store, encryptorOpt, opt)
err := cn.Open()
assert.NoError(t, err)
err = cn.Close()
assert.NoError(t, err)
}
}
}

View File

@ -0,0 +1,88 @@
package compress
import (
"bytes"
"encoding/hex"
)
// Format is the supporter compression algorithm type.
type Format string
// TODO: support additional compression algorithms besides
// Zstandard from https://github.com/klauspost/compress
const (
// None no compression.
None Format = ""
// Custom a custom compression format is being used.
Custom Format = "custom"
// Zstd Zstandard compression https://facebook.github.io/zstd/.
Zstd Format = "zstd"
)
// CompressorFunc is the function the prototype for compression.
type CompressorFunc func(data []byte) (compressed []byte, err error)
// PassthroughCompressor is a dummy function for development and testing *ONLY*.
/*
* WARNING: DO NOT USE IN PRODUCTION.
* PassthroughCompressor is *NOT* compression and *DOES NOT* compress data.
*/
var PassthroughCompressor CompressorFunc = func(data []byte) ([]byte, error) {
return []byte(hex.EncodeToString(data)), nil
}
// DecompressorFunc is the function the prototype for decompression.
type DecompressorFunc func(compressed []byte) (data []byte, err error)
// PassthroughDecompressor is a dummy function for development and testing *ONLY*.
/*
* WARNING: DO NOT USE IN PRODUCTION.
* PassthroughDecompressor is *NOT* decompression and *DOES NOT* decompress data.
*/
var PassthroughDecompressor DecompressorFunc = func(compressed []byte) ([]byte, error) {
return hex.DecodeString(string(compressed))
}
var (
formatTag = []byte{0xB, 0xA, 0xD, 0xA, 0x5, 0x5, 0x5, 0xB}
formatSep = []byte{0x1e} // US-ASCII Record Separator
)
// EncodeFormat adds the compression format to the compressed data.
func EncodeFormat(data []byte, f Format) []byte {
if f == None || len(data) <= 0 {
return data
}
return bytes.Join([][]byte{formatTag, []byte(f), data}, formatSep)
}
// DecodeFormat removes and returns the compression format from the compressed data.
// If no compression format is found DecodeFormat returns the original the data.
func DecodeFormat(data []byte) ([]byte, Format) {
if len(data) <= 0 {
return data, None
}
if !bytes.HasPrefix(data, formatTag) {
return data, None
}
parts := bytes.SplitN(data, formatSep, 3)
if len(parts) < 3 {
return data, None
}
// double check
if !bytes.Equal(parts[0], formatTag) {
return data, None
}
format := Format(parts[1])
switch format {
case Zstd:
break
case Custom:
break
default:
return data, None
}
return parts[2], format
}

View File

@ -0,0 +1,71 @@
package compress
import (
"testing"
"github.com/stretchr/testify/assert"
)
var (
empty = []byte("")
value = []byte("i-am-a-test-in")
valueFmt = []byte{0xb, 0xa, 0xd, 0xa, 0x5, 0x5, 0x5, 0xb, 0x1e, 0x7a, 0x73, 0x74, 0x64, 0x1e,
0x69, 0x2d, 0x61, 0x6d, 0x2d, 0x61, 0x2d, 0x74, 0x65, 0x73, 0x74, 0x2d, 0x69, 0x6e}
comp = []byte{
0x28, 0xb5, 0x2f, 0xfd, 0x4, 0x0, 0x71, 0x0, 0x0, 0x69, 0x2d, 0x61, 0x6d, 0x2d, 0x61, 0x2d,
0x74, 0x65, 0x73, 0x74, 0x2d, 0x69, 0x6e, 0x31, 0x49, 0x18, 0x48}
compFmt = []byte{
0xb, 0xa, 0xd, 0xa, 0x5, 0x5, 0x5, 0xb, 0x1e, 0x7a, 0x73, 0x74, 0x64, 0x1e, 0x28, 0xb5,
0x2f, 0xfd, 0x4, 0x0, 0x71, 0x0, 0x0, 0x69, 0x2d, 0x61, 0x6d, 0x2d, 0x61, 0x2d, 0x74, 0x65,
0x73, 0x74, 0x2d, 0x69, 0x6e, 0x31, 0x49, 0x18, 0x48}
extra = []byte{
0x69, 0x2d, 0x61, 0x6d, 0x2d, 0x1e, 0x2d, 0x74, 0x65, 0x73, 0x1e, 0x2d, 0x69, 0x6e}
extraFmt = []byte{
0xb, 0xa, 0xd, 0xa, 0x5, 0x5, 0x5, 0xb, 0x1e, 0x7a, 0x73, 0x74, 0x64, 0x1e, 0x69, 0x2d,
0x61, 0x6d, 0x2d, 0x1e, 0x2d, 0x74, 0x65, 0x73, 0x1e, 0x2d, 0x69, 0x6e}
badFmt = []byte{0xb, 0xa, 0xd, 0xa, 0x5, 0x5, 0x5, 0xb, 0x1e, 0xa, 0x73, 0x74, 0x64, 0x1e, 0x69,
0x2d, 0x61, 0x6d, 0x2d, 0x61, 0x2d, 0x74, 0x65, 0x73, 0x74, 0x2d, 0x69, 0x6e}
)
func TestEncodeFormat(t *testing.T) {
type testCase struct {
in []byte
format Format
out []byte
}
var tests = []testCase{
{nil, None, nil},
{nil, Zstd, nil},
{empty, None, empty},
{empty, Zstd, empty},
{value, None, value},
{value, Zstd, valueFmt},
{comp, Zstd, compFmt},
}
for _, test := range tests {
out := EncodeFormat(test.in, test.format)
assert.Equal(t, test.out, out)
}
}
func TestDecodeFormat(t *testing.T) {
type testCase struct {
in []byte
out []byte
format Format
}
var tests = []testCase{
{nil, nil, None},
{empty, empty, None},
{value, value, None},
{valueFmt, value, Zstd},
{compFmt, comp, Zstd},
{extraFmt, extra, Zstd},
{badFmt, badFmt, None},
}
for _, test := range tests {
out, format := DecodeFormat(test.in)
assert.Equal(t, test.format, format)
assert.Equal(t, test.out, out)
}
}

View File

@ -0,0 +1,34 @@
package zstd
import (
"github.com/jrapoport/chestnut/encoding/compress"
"github.com/klauspost/compress/zstd"
)
// Zstandard compression
// https://facebook.github.io/zstd/
var (
_ compress.CompressorFunc = Compress // Compress conforms to CompressorFunc
_ compress.DecompressorFunc = Decompress // Decompress conforms to DecompressorFunc
)
// Create a writer that caches compressors.
// For this operation type we supply a nil Reader.
var encoderZStd, _ = zstd.NewWriter(nil)
// Compress a buffer. If you have a destination buffer,
// the allocation src the call can also be eliminated.
func Compress(src []byte) ([]byte, error) {
return encoderZStd.EncodeAll(src, make([]byte, 0, len(src))), nil
}
// Create a reader that caches decompressors.
// For this operation type we supply a nil Reader.
var decoderZStd, _ = zstd.NewReader(nil)
// Decompress a buffer. We don't supply a destination
// buffer, so it will be allocated by the decoder.
func Decompress(src []byte) ([]byte, error) {
return decoderZStd.DecodeAll(src, nil)
}

View File

@ -0,0 +1,66 @@
package zstd
import (
"testing"
"github.com/stretchr/testify/assert"
)
var (
testNil = []byte(nil)
testEmpty = []byte("")
testSpace = []byte(" ")
testValue = []byte("i-am-an-uncompressed-string")
compSpace = []byte{0x28, 0xb5, 0x2f, 0xfd, 0x4, 0x0, 0x9, 0x0, 0x0, 0x20, 0x8d, 0x63, 0x68, 0xb6}
compValue = []byte{0x28, 0xb5, 0x2f, 0xfd, 0x4, 0x0, 0xd9, 0x0, 0x0, 0x69, 0x2d, 0x61, 0x6d, 0x2d,
0x61, 0x6e, 0x2d, 0x75, 0x6e, 0x63, 0x6f, 0x6d, 0x70, 0x72, 0x65, 0x73, 0x73, 0x65, 0x64, 0x2d,
0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0xab, 0x52, 0xd3, 0x9d}
)
func TestCompressZStd(t *testing.T) {
tests := []struct {
src []byte
out []byte
err assert.ErrorAssertionFunc
}{
{testNil, testEmpty, assert.NoError},
{testEmpty, testEmpty, assert.NoError},
{testSpace, compSpace, assert.NoError},
{testValue, compValue, assert.NoError},
}
for _, test := range tests {
bytes, err := Compress(test.src)
test.err(t, err)
assert.Equal(t, test.out, bytes)
}
}
func TestDecompressZStd(t *testing.T) {
tests := []struct {
src []byte
out []byte
err assert.ErrorAssertionFunc
}{
{testNil, testNil, assert.NoError},
{testEmpty, testNil, assert.NoError},
{testValue, testNil, assert.Error},
{compSpace, testSpace, assert.NoError},
{compValue, testValue, assert.NoError},
}
for _, test := range tests {
bytes, err := Decompress(test.src)
test.err(t, err)
assert.Equal(t, test.out, bytes)
}
}
func TestZStd(t *testing.T) {
// compress the src
buf, err := Compress(testValue)
assert.NoError(t, err)
assert.NotNil(t, buf)
// decompress the result
src, err := Decompress(buf)
assert.NoError(t, err)
assert.Equal(t, testValue, src)
}

34
encoding/json/decode.go Normal file
View File

@ -0,0 +1,34 @@
package json
import (
"errors"
"github.com/jrapoport/chestnut/encoding/json/encoders"
"github.com/jrapoport/chestnut/encoding/json/encoders/secure"
)
// SecureUnmarshal decrypts & parses the JSON-encoded data returned by SecureUnmarshal and stores
// the result in the value pointed to by v. If v is nil or not a pointer, Unmarshal returns an
// error. SecureUnmarshal adds support for sparse decryption and via JSON struct tag options. If
// SecureMarshal is called at least one 'secure' option set on a struct field JSON tag, only those
// fields will be encrypted. The remaining encoded data stored as sparse plaintext. If SecureUnmarshal
// is called on a sparse encoding with the sparse option set, SecureUnmarshal will skip the decryption
// step and return only the plaintext decoding of v with encrypted fields replaced by empty values.
// For more detail, SEE: https://github.com/jrapoport/chestnut/blob/master/README.md
func SecureUnmarshal(data []byte, v interface{}, decryptFunc secure.DecryptionFunction, opt ...secure.Option) error {
if v == nil {
return errors.New("nil value")
}
enc := encoders.NewEncoder()
ext := secure.NewSecureDecoderExtension(encoders.DefaultID, decryptFunc, opt...)
enc.RegisterExtension(ext)
defer ext.Close()
unsealed, err := ext.Unseal(data)
if err != nil {
return err
}
if err = ext.Open(); err != nil {
return err
}
return enc.Unmarshal(unsealed, v)
}

View File

@ -0,0 +1,39 @@
package json
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestSecureUnmarshal(t *testing.T) {
// uncompressed secure
secureObj := &Family{}
err := SecureUnmarshal(familyEnc, secureObj, decrypt)
assertDecoding(t, familyDec, secureObj, err)
// compressed secure
secureObj = &Family{}
err = SecureUnmarshal(familyComp, secureObj, decrypt, compOpt)
assertDecoding(t, familyDec, secureObj, err)
// uncompressed sparse
sparseObj := &Family{}
err = SecureUnmarshal(familyEnc, sparseObj, decrypt, sparseOpt)
assertDecoding(t, familySpr, sparseObj, err)
// compressed sparse
sparseObj = &Family{}
err = SecureUnmarshal(familyComp, sparseObj, decrypt, compOpt, sparseOpt)
assertDecoding(t, familySpr, sparseObj, err)
}
func TestSecureUnmarshal_Error(t *testing.T) {
secureObj := &Family{}
assert.Panics(t, func() {
_ = SecureUnmarshal(familyEnc, secureObj, nil)
})
err := SecureUnmarshal(familyEnc, nil, decrypt)
assert.Error(t, err)
err = SecureUnmarshal(nil, secureObj, decrypt)
assert.Error(t, err)
err = SecureUnmarshal([]byte("bad encoding"), secureObj, decrypt)
assert.Error(t, err)
}

31
encoding/json/encode.go Normal file
View File

@ -0,0 +1,31 @@
package json
import (
"errors"
"github.com/jrapoport/chestnut/encoding/json/encoders"
"github.com/jrapoport/chestnut/encoding/json/encoders/secure"
)
// SecureMarshal returns an encrypted JSON encoding of v. It adds support for sparse encryption and
// hashing via JSON struct tag options. If SecureMarshal is called at least one 'secure' option set
// on a struct field JSON tag, only those fields will be encrypted. The remaining encoded data stored
// as sparse plaintext. If no secure tag option is found, all the encoded data will be encrypted.
// For more detail, SEE: https://github.com/jrapoport/chestnut/blob/master/README.md
func SecureMarshal(v interface{}, encryptFunc secure.EncryptionFunction, opt ...secure.Option) ([]byte, error) {
if v == nil {
return nil, errors.New("nil value")
}
enc := encoders.NewEncoder()
ext := secure.NewSecureEncoderExtension(encoders.DefaultID, encryptFunc, opt...)
enc.RegisterExtension(ext)
if err := ext.Open(); err != nil {
return nil, err
}
buf, err := enc.Marshal(v)
if err != nil {
return nil, err
}
ext.Close()
return ext.Seal(buf)
}

View File

@ -0,0 +1,31 @@
package json
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestSecureMarshal(t *testing.T) {
// uncompressed
bytes, err := SecureMarshal(family, encrypt)
assert.NoError(t, err)
assert.Equal(t, familyEnc, bytes)
// compressed
bytes, err = SecureMarshal(family, encrypt, compOpt)
assert.NoError(t, err)
assert.Equal(t, familyComp, bytes)
}
func TestSecureMarshal_Error(t *testing.T) {
assert.Panics(t, func() {
_, _ = SecureMarshal(family, nil)
})
bytes, err := SecureMarshal(nil, nil)
assert.Error(t, err)
assert.Nil(t, bytes)
bytes, err = SecureMarshal(nil, encrypt)
assert.Error(t, err)
assert.Nil(t, bytes)
}

View File

@ -0,0 +1,39 @@
package encoders
import (
"encoding/hex"
"log"
"github.com/jrapoport/chestnut/encryptor/crypto"
"github.com/json-iterator/go"
)
// NewEncoder returns a new encoder with a _clean_ configuration and _no_ registered
// extensions. Extensions registered to this encoder will not impact the global encoder.
// Config options match jsoniter ConfigCompatibleWithStandardLibrary.
func NewEncoder() jsoniter.API {
return jsoniter.Config{
EscapeHTML: true,
SortMapKeys: true,
ValidateJsonRawMessage: true,
}.Froze()
}
// DefaultID can be used with a SecureEncoderExtension instead of a set id. When used,
// it will be replaced with a randomly generated 8 character hex id for the encoder.
// #954535 is hex color code for Chestnut. https://en.wikipedia.org/wiki/Chestnut_(color)
var DefaultID = "0x954535"
// InvalidID is an invalid encoder id.
const InvalidID = ""
// NewEncoderID returns a new random encoder id as a hex string. This id
// is not guaranteed to be unique and does not have to be. It is only used
// internally in the encoder so there is no risk of collision.
func NewEncoderID() string {
id, err := crypto.MakeRand(4)
if err != nil {
log.Fatal(err)
}
return hex.EncodeToString(id)
}

View File

@ -0,0 +1,64 @@
package hash
import (
"strings"
"unsafe"
"github.com/jrapoport/chestnut/log"
jsoniter "github.com/json-iterator/go"
)
// Encoder is a ValEncoder strings that hashes string data
// with HashingFunction before encoding it to stream.
type Encoder struct {
hashName string
hashFunc HashingFunction
encoder jsoniter.ValEncoder
log log.Logger
}
// NewHashEncoder returns a string encoder that with encode string value using the supplied hashFn.
// The hash encoder will run before the other encoders, ensuring that struct fields are hashed first.
func NewHashEncoder(name string, hashFn HashingFunction, encoder jsoniter.ValEncoder) jsoniter.ValEncoder {
if name == "" {
name = "hash"
}
return &Encoder{hashName: name, hashFunc: hashFn, encoder: encoder, log: log.Log}
}
// SetLogger changes the logger for the encoder.
func (e *Encoder) SetLogger(l log.Logger) {
e.log = l
}
// Encode writes the value of ptr to stream.
func (e *Encoder) Encode(ptr unsafe.Pointer, stream *jsoniter.Stream) {
e.log.Debug("encoding hash")
if e.IsEmpty(ptr) || e.hashFunc == nil {
e.log.Warn("cannot encode empty ptr or nil hash function")
e.encoder.Encode(ptr, stream)
return
}
prefix := e.hashName + ":"
if strings.HasPrefix(*((*string)(ptr)), prefix) {
e.log.Warn("do not re-hash field")
e.encoder.Encode(ptr, stream)
return
}
data := *((*[]byte)(ptr))
e.log.Debugf("hash string: %s", string(data))
hash, err := e.hashFunc(data)
if err == nil {
hash = string(prefix) + hash
e.log.Debugf("encoding hash: %s", hash)
ptr = unsafe.Pointer(&hash)
} else {
e.log.Error(err)
}
e.encoder.Encode(ptr, stream)
}
// IsEmpty returns true is ptr is empty, otherwise false.
func (e *Encoder) IsEmpty(ptr unsafe.Pointer) bool {
return e.encoder.IsEmpty(ptr)
}

View File

@ -0,0 +1,66 @@
package hash
import (
"bytes"
"reflect"
"testing"
"unsafe"
"github.com/jrapoport/chestnut/encoding/tags"
jsoniter "github.com/json-iterator/go"
"github.com/modern-go/reflect2"
"github.com/stretchr/testify/assert"
)
func TestHashEncoder(t *testing.T) {
var tests = []struct {
in []byte
out string
assertEmpty assert.BoolAssertionFunc
}{
{
nil,
`""`,
assert.True,
},
{
[]byte(""),
`""`,
assert.True,
},
{
[]byte("abcdefghijklmnopqrstuvwxyz"),
`"sha256:71c480df93d6ae2f1efad1447c66c9525e316218cf51fc8d9ed832f2daf18b73"`,
assert.False,
},
{
[]byte("abcdefghijklmnopqrstuvwxyz1234567890"),
`"sha256:77d721c817f9d216c1fb783bcad9cdc20aaa2427402683f1f75dd6dfbe657470"`,
assert.False,
},
}
for _, test := range tests {
var buf bytes.Buffer
conf := jsoniter.ConfigDefault
valEncoder := conf.EncoderOf(reflect2.DefaultTypeOfKind(reflect.String))
stream := jsoniter.NewStream(conf, &buf, 100)
stream.Reset(&buf)
he := NewHashEncoder(tags.HashSHA256, EncodeToSHA256, valEncoder)
he.Encode(unsafe.Pointer(&test.in), stream)
assert.Equal(t, test.out, string(stream.Buffer()))
test.assertEmpty(t, he.IsEmpty(unsafe.Pointer(&test.in)))
}
}
func TestHashEncoder_NoRehash(t *testing.T) {
var testIn = "sha256:71c480df93d6ae2f1efad1447c66c9525e316218cf51fc8d9ed832f2daf18b73"
const testOut = `"sha256:71c480df93d6ae2f1efad1447c66c9525e316218cf51fc8d9ed832f2daf18b73"`
var buf bytes.Buffer
conf := jsoniter.ConfigDefault
valEncoder := conf.EncoderOf(reflect2.DefaultTypeOfKind(reflect.String))
stream := jsoniter.NewStream(conf, &buf, 100)
stream.Reset(&buf)
he := NewHashEncoder(tags.HashSHA256, EncodeToSHA256, valEncoder)
he.Encode(unsafe.Pointer(&testIn), stream)
assert.Equal(t, testOut, string(stream.Buffer()))
}

View File

@ -0,0 +1,30 @@
package hash
import (
"encoding/hex"
"github.com/jrapoport/chestnut/encoding/tags"
"github.com/jrapoport/chestnut/encryptor/crypto"
)
// HashingFunction defines the prototype for the hash callback. Defaults to EncodeToSHA256.
type HashingFunction func(buf []byte) (hash string, err error)
// FunctionForName returns the hash function for a given otherwise nil (passthrough).
func FunctionForName(name tags.Hash) HashingFunction {
switch name {
case tags.HashSHA256:
return EncodeToSHA256
default:
return nil
}
}
// EncodeToSHA256 returns a sha256 hash of data as string.
var EncodeToSHA256 = func(buf []byte) (string, error) {
hash, err := crypto.HashSHA256(buf)
if err != nil {
return "", err
}
return hex.EncodeToString(hash), nil
}

View File

@ -0,0 +1,48 @@
package hash
import (
"testing"
"github.com/jrapoport/chestnut/encoding/tags"
"github.com/stretchr/testify/assert"
)
func TestHashFunctionForName(t *testing.T) {
fn := FunctionForName(tags.HashNone)
assert.Nil(t, fn)
fn = FunctionForName(tags.HashSHA256)
assert.NotNil(t, fn)
h1, err := EncodeToSHA256([]byte("test"))
assert.NoError(t, err)
h2, err := fn([]byte("test"))
assert.NoError(t, err)
assert.Equal(t, h1, h2)
}
func TestEncodeToSHA256(t *testing.T) {
var tests = []struct {
in []byte
out string
}{
{
nil,
"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
},
{
[]byte(""),
"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
},
{
[]byte("abcdefghijklmnopqrstuvwxyz"),
"71c480df93d6ae2f1efad1447c66c9525e316218cf51fc8d9ed832f2daf18b73",
},
{[]byte("abcdefghijklmnopqrstuvwxyz1234567890"),
"77d721c817f9d216c1fb783bcad9cdc20aaa2427402683f1f75dd6dfbe657470",
},
}
for _, test := range tests {
h, err := EncodeToSHA256(test.in)
assert.NoError(t, err)
assert.Equal(t, test.out, h, test.in)
}
}

View File

@ -0,0 +1,168 @@
package lookup
import (
"errors"
"fmt"
"reflect"
"unsafe"
"github.com/jrapoport/chestnut/log"
jsoniter "github.com/json-iterator/go"
"github.com/modern-go/reflect2"
)
// Decoder is a ValDecoder that reads lookup table key strings from the iterator. When
// a key is found in the lookup table it decodes the lookup table data in place of the key.
type Decoder struct {
token string
stream *jsoniter.Stream
valType reflect2.Type
decoder jsoniter.ValDecoder
log log.Logger
}
// NewLookupDecoder returns an decoder that reads a lookup table. It will check the
// iterated string values to see if they match our lookup token. If there is a match,
// it will replace it with a decoded value from the lookup table or an empty value.
func NewLookupDecoder(ctx *Context, typ reflect2.Type, decoder jsoniter.ValDecoder) jsoniter.ValDecoder {
logger := log.Log
if decoder == nil {
logger.Fatal(errors.New("value encoder required"))
return nil
}
if ctx == nil {
logger.Fatal(errors.New("lookup context required"))
return nil
}
if ctx.Stream == nil {
logger.Fatal(errors.New("lookup stream required"))
return nil
}
return &Decoder{
token: ctx.Token,
stream: ctx.Stream,
valType: typ,
decoder: decoder,
log: logger,
}
}
// SetLogger changes the logger for the decoder.
func (d *Decoder) SetLogger(l log.Logger) {
d.log = l
}
// Decode sets ptr to the next value of iterator.
func (d *Decoder) Decode(ptr unsafe.Pointer, iter *jsoniter.Iterator) {
d.log.Debugf("decoding type %s", d.valType)
// if we are dealing with an empty interface, skip it.
if d.isEmptyInterface(ptr) {
d.log.Warn("cannot encode to empty interface")
iter.Skip()
return
}
// we really shouldn't be here with an invalid token, if for
// some reason we are, call the default decoder and bail.
if d.token == InvalidToken {
d.log.Warn("invalid token")
d.decoder.Decode(ptr, iter)
return
}
// get the from type
fromType := iter.WhatIsNext()
// secure tokens will be type string. if this is not
// a string, call the default decoder and bail.
if fromType != jsoniter.StringValue {
d.log.Debug("skipping non-string value")
d.decoder.Decode(ptr, iter)
return
}
// read the string & for mat a key
key := Key(iter.ReadString())
// check to see if it is one of ours
if !key.IsTokenKey(d.token) {
// we use an Iterator avoid setting the ptr directly since it might be a string
// or an interface or who knows what. this was the codecs handle it for us.
subIter := iter.Pool().BorrowIterator([]byte(fmt.Sprintf(`"%s"`, key)))
defer iter.Pool().ReturnIterator(subIter)
d.log.Debugf("decode string: %s", key)
// decode the string
d.decoder.Decode(ptr, subIter)
return
}
// we have a valid lookup key. look it up in our table
val, err := d.lookupKey(key)
// did we find something in the lookup table?
if err != nil || val == nil {
d.log.Debugf("lookup entry not found: %s", key)
// this is expected when sparse decoding a struct.
if d.valType.Kind() == reflect.Interface {
d.log.Debugf("decode empty %s for interface", key.Kind())
// if we have a map then set an explicitly typed empty value
*(*interface{})(ptr) = emptyValueOfKind(key.Kind())
}
return
}
// clear the buffer
d.stream.Reset(nil)
val.WriteTo(d.stream)
subIter := iter.Pool().BorrowIterator(d.stream.Buffer())
defer iter.Pool().ReturnIterator(subIter)
// decode the string
d.decoder.Decode(ptr, subIter)
d.log.Debugf("decoded lookup entry for %s: %s", key, string(d.stream.Buffer()))
}
func (d *Decoder) lookupKey(key Key) (jsoniter.Any, error) {
d.log.Debugf("lookup key: %s", key)
logErr := func(err error) error {
d.log.Error(err)
return err
}
if d.stream == nil {
return nil, logErr(errors.New("lookup stream not found"))
}
table, ok := d.stream.Attachment.(jsoniter.Any)
if !ok || table == nil {
return nil, logErr(errors.New("lookup table not found"))
}
val := table.Get(key.String())
if val.ValueType() == jsoniter.InvalidValue {
err := fmt.Errorf("lookup key not found: %s", key)
d.log.Debug(err) // this is an expected error
return nil, err
}
d.log.Debugf("lookup found %s for key %s: %s", val.ValueType(), key, val.ToString())
return val, nil
}
func (d *Decoder) isEmptyInterface(ptr unsafe.Pointer) bool {
if d.valType.Kind() != reflect.Interface {
return false
}
i, ok := d.valType.(*reflect2.UnsafeIFaceType)
if !ok {
return false
}
return reflect2.IsNil(i.UnsafeIndirect(ptr))
}
func emptyValueOfKind(kind reflect.Kind) interface{} {
var v interface{}
switch kind {
case reflect.String:
v = ""
case reflect.Bool:
v = false
case reflect.Uint8, reflect.Int8,
reflect.Uint16, reflect.Int16,
reflect.Uint32, reflect.Int32,
reflect.Uint64, reflect.Int64,
reflect.Uint, reflect.Int,
reflect.Float32, reflect.Float64,
reflect.Uintptr:
v = 0.0
default:
}
return v
}

View File

@ -0,0 +1,79 @@
package lookup
import (
"fmt"
"reflect"
"testing"
"unsafe"
"github.com/jrapoport/chestnut/encoding/json/encoders"
jsoniter "github.com/json-iterator/go"
"github.com/modern-go/reflect2"
"github.com/stretchr/testify/assert"
)
func TestLookupDecoder_Decode(t *testing.T) {
type testObject struct {
Value string
}
tests := []struct {
value interface{}
key string
encoding string
}{
{
"a-string-value",
`"tst0xtesting%d_24"`,
`"a-string-value"`,
},
{
[]string{"a-string-slice"},
`"tst0xtesting%d_23"`,
`["a-string-slice"]`,
},
{
99.9,
`"tst0xtesting%d_14"`,
`99.9`,
},
{
testObject{"a-struct-value"},
`"tst0xtesting%d_25"`,
`{"Value":"a-struct-value"}`,
},
{
&testObject{"a-struct-ptr-value"},
`"tst0xtesting%d_22"`,
`{"Value":"a-struct-ptr-value"}`,
},
}
lookUpTable := "{"
for i, test := range tests {
key := fmt.Sprintf(test.key, i)
if i > 0 {
lookUpTable += ","
}
entry := fmt.Sprintf("%s:%s", key, test.encoding)
lookUpTable += entry
}
lookUpTable += "}"
ctx := &Context{
NewLookupToken(testPrefix, testID),
newTestStream(t),
}
enc := encoders.NewEncoder()
ctx.Stream.Attachment = enc.Get([]byte(lookUpTable))
for i, test := range tests {
typ := reflect2.TypeOf(&test.value)
decoder := enc.DecoderOf(typ)
le := NewLookupDecoder(ctx, typ, decoder)
key := fmt.Sprintf(test.key, i)
iter := enc.BorrowIterator([]byte(key))
ptr := reflect.New(reflect.TypeOf(test.value)).Interface()
le.Decode(unsafe.Pointer(&ptr), iter)
enc.ReturnIterator(iter)
assert.Equal(t, test.encoding, string(ctx.Stream.Buffer()))
any := jsoniter.Get(ctx.Stream.Buffer())
assert.NotEqual(t, jsoniter.InvalidValue, any.ValueType())
}
}

View File

@ -0,0 +1,113 @@
package lookup
import (
"errors"
"fmt"
"unsafe"
"github.com/jrapoport/chestnut/encoding/json/encoders"
"github.com/jrapoport/chestnut/log"
jsoniter "github.com/json-iterator/go"
"github.com/modern-go/reflect2"
)
var cleanEncoder = encoders.NewEncoder()
// Encoder is a ValEncoder that encodes the data to lookup table and encodes a
// entry key for the data into the stream that can be read later by the decoder.
type Encoder struct {
token string
stream *jsoniter.Stream
valType reflect2.Type
encoder jsoniter.ValEncoder
log log.Logger
}
// NewLookupEncoder returns an encoder that builds a lookup table. It will strip out tagged
// struct fields and collect the encoded values in the provided stream as a map. As it strips
// out values, it replaces them with a token key for the lookup table. Later we can use this
// key as a lookup to reconstruct the encoded struct as it is decoded. The hash encoder must
// be run before this encoder, so the struct fields are hashed before they are stripped.
func NewLookupEncoder(ctx *Context, typ reflect2.Type, encoder jsoniter.ValEncoder) jsoniter.ValEncoder {
logger := log.Log
if encoder == nil {
logger.Fatal(errors.New("value encoder required"))
return nil
}
if ctx == nil {
logger.Fatal(errors.New("lookup context required"))
return nil
}
if ctx.Token == InvalidToken {
logger.Fatal(errors.New("lookup token required"))
return nil
}
if ctx.Stream == nil {
logger.Fatal(errors.New("lookup stream required"))
return nil
}
return &Encoder{
token: ctx.Token,
stream: ctx.Stream,
valType: typ,
encoder: encoder,
log: logger,
}
}
// SetLogger changes the logger for the encoder.
func (e *Encoder) SetLogger(l log.Logger) {
e.log = l
}
// Encode writes the value of ptr to stream.
func (e *Encoder) Encode(ptr unsafe.Pointer, stream *jsoniter.Stream) {
e.log.Debugf("encoding type %s", e.valType)
// FIXME: I've looked around for a way to avoid this, or unwrap the encoder, but it's
// not clear what the best way to do that is or if it's possible with jsoniter as-is.
// NOTE: This is *SUPER important*. This is so when UpdateStructDescriptor is called
// recursively for nested structs the ValEncoder we use is a ORIGINAL ValEncoder, and
// NOT a copy of our modified Encode (that strips out values). If we don't do this,
// tagged fields will also be stripped out of our steam and not just the encoded stream.
// We know this is happening because when it does: encoding stream == lookup stream.
if stream == e.stream {
// we are being called recursively so try and get a clean encoder.
if subEncoder := cleanEncoder.EncoderOf(e.valType); subEncoder != nil {
e.log.Debugf("use sub-encoder type %s", e.valType)
// use the clean encoder to encode to our own stream.
subEncoder.Encode(ptr, stream)
} else {
e.log.Error(fmt.Errorf("sub-encoder for type %s not found", e.valType))
}
return
}
// encode the ptr to the lookup table
key := e.encodeLookup(ptr, e.nextIndex())
// encode our lookup key to the main stream
e.log.Debugf("encoded lookup key: %s", key)
stream.WriteString(key.String())
}
// IsEmpty returns true is ptr is empty, otherwise false.
func (e *Encoder) IsEmpty(ptr unsafe.Pointer) bool {
return e.encoder.IsEmpty(ptr)
}
func (e *Encoder) encodeLookup(ptr unsafe.Pointer, tableIndex int) Key {
key := NewLookupKey(e.token, tableIndex, e.valType)
// encode the actual data into our lookup table
if tableIndex > 0 {
e.stream.WriteMore()
}
e.stream.WriteObjectField(key.String())
e.encoder.Encode(ptr, e.stream)
e.log.Debugf("encoded lookup for key %s: %s", string(e.stream.Buffer()), key)
return key
}
// we shouldn't need locking here since it should not to be called concurrently.
func (e *Encoder) nextIndex() int {
idx, _ := e.stream.Attachment.(int)
e.stream.Attachment = idx + 1
return idx
}

View File

@ -0,0 +1,89 @@
package lookup
import (
"fmt"
"testing"
"github.com/jrapoport/chestnut/encoding/json/encoders"
"github.com/modern-go/reflect2"
"github.com/stretchr/testify/assert"
)
func TestLookupEncoder_Encode(t *testing.T) {
type testObject struct {
Value string
}
tests := []struct {
value interface{}
key string
encoding string
}{
{
"a-string-value",
`"tst0xtesting%d_24"`,
`"a-string-value"`,
},
{
[]string{"a-string-slice"},
`"tst0xtesting%d_23"`,
`["a-string-slice"]`,
},
{
99.9,
`"tst0xtesting%d_14"`,
`99.9`,
},
{
testObject{"a-struct-value"},
`"tst0xtesting%d_25"`,
`{"Value":"a-struct-value"}`,
},
{
&testObject{"a-struct-ptr-value"},
`"tst0xtesting%d_22"`,
`{"Value":"a-struct-ptr-value"}`,
},
}
encoded := ""
lookup := ""
stream := newTestStream(t)
ctx := &Context{
NewLookupToken(testPrefix, testID),
newTestStream(t),
}
enc := encoders.NewEncoder()
for i, test := range tests {
typ := reflect2.TypeOf(test.value)
encoder := enc.EncoderOf(typ)
le := NewLookupEncoder(ctx, typ, encoder)
le.Encode(reflect2.PtrOf(test.value), stream)
key := fmt.Sprintf(test.key, i)
encoded += key
assert.Equal(t, encoded, string(stream.Buffer()))
if i > 0 {
lookup += ","
}
entry := fmt.Sprintf("%s:%s", key, test.encoding)
lookup += entry
assert.Equal(t, lookup, string(ctx.Stream.Buffer()))
}
}
func TestLookupEncoder_IsEmpty(t *testing.T) {
tests := []struct {
value interface{}
assertEmpty assert.BoolAssertionFunc
}{
{"", assert.True},
{"not-empty", assert.False},
{[]string{}, assert.True},
{[]string{"not-empty"}, assert.False},
}
encoder := encoders.NewEncoder()
for _, test := range tests {
enc := encoder.EncoderOf(reflect2.TypeOf(test.value))
le := &Encoder{encoder: enc}
empty := le.IsEmpty(reflect2.PtrOf(test.value))
test.assertEmpty(t, empty, "value: %v", test.value)
}
}

View File

@ -0,0 +1,60 @@
package lookup
import (
"fmt"
"reflect"
"strconv"
"strings"
jsoniter "github.com/json-iterator/go"
"github.com/modern-go/reflect2"
)
// InvalidToken is an invalid lookup token.
const InvalidToken = ""
const tokenSeparator = "_"
// NewLookupToken returns the field name for sparse encoded data
// as the encoder id with the format "[prefix]-[encoder id]".
func NewLookupToken(prefix, encoderID string) string {
return fmt.Sprintf("%s%s", prefix, encoderID)
}
// Key is an encoded lookup data key.
type Key string
// NewLookupKey creates a new lookup table key with the encoding field index and type. The field
// index is *not* the index relative to a StructField, but relative to the JSON encoding itself.
func NewLookupKey(token string, index int, typ reflect2.Type) Key {
return Key(fmt.Sprintf("%s%d%s%d", token, index, tokenSeparator, typ.Kind()))
}
// IsTokenKey returns true if the key was derived from the lookup token.
func (k Key) IsTokenKey(token string) bool {
return strings.HasPrefix(k.String(), token)
}
// Kind returns the encoded reflect.Kind for the key.
func (k Key) Kind() reflect.Kind {
parts := strings.Split(k.String(), tokenSeparator)
if len(parts) < 2 {
return reflect.Invalid
}
// the last part should be the type
kind, err := strconv.Atoi(parts[len(parts)-1])
if err != nil {
return reflect.Invalid
}
return reflect.Kind(kind)
}
func (k Key) String() string {
return string(k)
}
// Context holds the context for the lookup coders.
type Context struct {
Token string
Stream *jsoniter.Stream
}

View File

@ -0,0 +1,21 @@
package lookup
import (
"bytes"
"testing"
jsoniter "github.com/json-iterator/go"
"github.com/stretchr/testify/assert"
)
const testID = "0xtesting"
const testPrefix = "tst"
func newTestStream(t *testing.T) *jsoniter.Stream {
var buf bytes.Buffer
conf := jsoniter.ConfigDefault
stream := jsoniter.NewStream(conf, &buf, 4096)
stream.Reset(&buf)
assert.NotNil(t, stream)
return stream
}

View File

@ -0,0 +1,318 @@
package secure
import (
"encoding/hex"
"errors"
"fmt"
"sync"
"github.com/jrapoport/chestnut/encoding/json/encoders"
"github.com/jrapoport/chestnut/encoding/json/encoders/lookup"
"github.com/jrapoport/chestnut/encoding/json/packager"
"github.com/jrapoport/chestnut/log"
jsoniter "github.com/json-iterator/go"
"github.com/modern-go/reflect2"
)
// DecryptionFunction defines the prototype for the decryption callback.
// See WARNING regarding use of PassthroughDecryption.
type DecryptionFunction func(ciphertext []byte) (plaintext []byte, err error)
// PassthroughDecryption is a dummy function for development and testing *ONLY*.
/*
* WARNING: DO NOT USE IN PRODUCTION.
* PassthroughDecryption is *NOT* decryption and *DOES NOT* decrypt data.
*/
var PassthroughDecryption DecryptionFunction = func(ciphertext []byte) ([]byte, error) {
return hex.DecodeString(string(ciphertext))
}
// DecoderExtension is a JSON encoder extension for the encryption and decryption of JSON
// encoded data. It supports full encryption / decryption of the encoded block in in
// addition to sparse encryption and hashing of structs on a per field basis via supplementary
// JSON struct field tag options. For addition information sparse encryption & hashing, please
// SEE: https://github.com/jrapoport/chestnut/blob/master/README.md
//
// For additional information on json-iterator extensions, please
// SEE: https://github.com/json-iterator/go/wiki/Extension
type DecoderExtension struct {
jsoniter.DecoderExtension
opts Options
encoderID string
encoder jsoniter.API
lookupCtx *lookup.Context
lookupBuffer []byte
open bool
decryptFunc DecryptionFunction
log log.Logger
mu sync.RWMutex
}
// NewSecureDecoderExtension returns a new DecoderExtension using the supplied DecryptionFunction. If
// an encoder id is supplied, this decoder will restrict itself to packages with a matching id.
func NewSecureDecoderExtension(encoderID string, dfn DecryptionFunction, opt ...Option) *DecoderExtension {
const decoderName = "decoder"
opts := DefaultOptions
opts = applyOptions(opts, opt...)
encoder := encoders.NewEncoder()
logName := decoderName
if encoderID != encoders.InvalidID {
logName += fmt.Sprintf(" [%s]", encoderID)
}
ext := new(DecoderExtension)
ext.opts = opts
ext.log = log.Named(opts.log, logName)
ext.encoderID = encoderID
ext.decryptFunc = dfn
ext.encoder = encoder
ext.lookupCtx = &lookup.Context{}
if ext.encoder == nil {
ext.log.Fatal(errors.New("encoder not found"))
}
if ext.decryptFunc == nil {
ext.log.Panic(errors.New("decryption function required"))
}
return ext
}
// Unseal decrypts and returns the encoded value as an unsealed package. If sparse
// is true AND the data format is sparse, the data will not be decrypted the struct
// will be decoded with empty values in place of secure fields.
// TODO: We could hash the encoded data and add that to our plaintext block before we
// encrypt it as a tamper check. Not sure that is necessary or useful right now though.
func (ext *DecoderExtension) Unseal(encoded []byte) ([]byte, error) {
ext.mu.Lock()
defer ext.mu.Unlock()
ext.log.Debugf("unsealing encoded %d bytes", len(encoded))
/// must do this first
if ext.open {
ext.log.Debug("decoder is open, closing it")
ext.close()
}
// unwrap the package
pkg, err := packager.DecodePackage(encoded)
if err != nil {
return nil, ext.logError(err)
}
if err = pkg.Valid(); err != nil {
err = fmt.Errorf("invalid encoding %w", err)
return nil, ext.logError(err)
}
compressed := pkg.Compressed
ext.log.Debugf("package data is compressed: %t", compressed)
// IF we have an encoder ID, check that it matches the package
ext.log.Debugf("checking encoding id %s", pkg.EncoderID)
if ext.encoderID != encoders.DefaultID &&
ext.encoderID != pkg.EncoderID {
err = fmt.Errorf(" encoder %s package %s id mismatch", ext.encoderID, pkg.EncoderID)
return nil, ext.logError(err)
}
ext.log.Debugf("sparse option set: %t", ext.opts.sparse)
isSparse := pkg.Format == packager.Sparse && ext.opts.sparse
ext.log.Debugf("sparse decoding: %t", isSparse)
if !isSparse {
// decrypt the data unless we are sparse decoding
ext.log.Debugf("decrypting %d ciphertext bytes", len(pkg.Cipher))
if pkg.Cipher, err = ext.decrypt(pkg.Cipher); err != nil {
return nil, ext.logError(err)
}
ext.log.Debugf("decrypted %d bytes", len(pkg.Cipher))
if compressed {
ext.log.Debug("ciphertext is compressed")
if !ext.hasDecompressor() {
err = errors.New("compressed package requires decompressor")
return nil, ext.logError(err)
}
ext.log.Debugf("decompress %d ciphertext bytes", len(pkg.Cipher))
pkg.Cipher, err = ext.decompress(pkg.Cipher)
if err != nil {
return nil, ext.logError(err)
}
ext.log.Debugf("decompressed %d ciphertext bytes", len(pkg.Cipher))
}
}
switch pkg.Format {
// the format is secure, we are done
case packager.Secure:
ext.log.Debugf("unsealed %d secure data bytes: %s", len(pkg.Cipher), string(pkg.Cipher))
return pkg.Cipher, nil
case packager.Sparse:
// set the lookup context
ext.log.Debugf("unsealed sparse token: %s", pkg.Token)
ext.lookupCtx.Token = pkg.Token
if !isSparse {
ext.log.Debugf("unsealed %d lookup data bytes: %s", len(pkg.Cipher), string(pkg.Cipher))
ext.lookupBuffer = pkg.Cipher
}
break
default:
return nil, ext.logError(errors.New("unknown package format"))
}
if compressed {
ext.log.Debug("encoded data is compressed")
if !ext.hasDecompressor() {
err = errors.New("compressed package requires decompressor")
return nil, ext.logError(err)
}
ext.log.Debugf("decompress %d encoded bytes", len(pkg.Encoded))
pkg.Encoded, err = ext.decompress(pkg.Encoded)
if err != nil {
return nil, ext.logError(err)
}
ext.log.Debugf("decompressed %d encoded bytes", len(pkg.Encoded))
}
if len(pkg.Encoded) > 0 {
ext.log.Debugf("unsealed %d sparse data bytes: %s", len(pkg.Encoded), string(pkg.Encoded))
}
return pkg.Encoded, nil
}
func (ext *DecoderExtension) hasDecompressor() bool {
return ext.opts.decompressor != nil
}
func (ext *DecoderExtension) decompress(data []byte) ([]byte, error) {
if len(data) <= 0 {
return nil, nil
}
if !ext.hasDecompressor() {
return data, nil
}
return ext.opts.decompressor(data)
}
// decrypt calls the DecryptionFunction if set, otherwise panic.
// See WARNING regarding the use of PassthroughDecryption.
func (ext *DecoderExtension) decrypt(ciphertext []byte) ([]byte, error) {
if ext.decryptFunc == nil {
ext.log.Panic(errors.New("decryption function required"))
}
return ext.decryptFunc(ciphertext)
}
// DecorateDecoder customizes the decoding by specifying alternate lookup table decoder that
// recognizes previously encoded lookup table keys and replaces them with decoded values.
func (ext *DecoderExtension) DecorateDecoder(typ reflect2.Type, decoder jsoniter.ValDecoder) jsoniter.ValDecoder {
if !ext.isOpen() {
ext.log.Debug("decoder is not open, cannot decorate decoder")
return decoder
}
if ext.lookupCtx == nil || ext.lookupCtx.Token == lookup.InvalidToken {
ext.log.Debug("decoding is not sparse, do not add lookup decoder")
return decoder
}
ext.log.Debugf("added lookup decoder for type: %s", typ)
decoder = lookup.NewLookupDecoder(ext.lookupCtx, typ, decoder)
if dec, ok := decoder.(*lookup.Decoder); ok {
dec.SetLogger(log.Named(ext.log, typ.String()))
}
return decoder
}
// Open should be called before Unmarshal to prepare the decoder.
func (ext *DecoderExtension) Open() error {
ext.mu.Lock()
defer ext.mu.Unlock()
ext.log.Debug("opening decoder")
if ext.open {
return ext.logError(errors.New("decoder already open"))
}
if err := ext.openLookupStream(); err != nil {
err = fmt.Errorf("failed to open decoder %w", err)
return ext.logError(err)
}
ext.open = true
ext.log.Debug("decoder open")
return nil
}
func (ext *DecoderExtension) isOpen() bool {
ext.mu.RLock()
defer ext.mu.RUnlock()
return ext.open
}
// Close should be called after Unmarshal.
func (ext *DecoderExtension) Close() {
ext.mu.Lock()
defer ext.mu.Unlock()
ext.close()
}
// close is the non-locking internal close call.
func (ext *DecoderExtension) close() {
ext.log.Debug("closing decoder")
ext.closeLookupStream()
ext.open = false
ext.log.Debug("decoder closed")
}
func (ext *DecoderExtension) openLookupStream() error {
ext.log.Debug("opening lookup stream")
stream := ext.encoder.BorrowStream(nil)
if stream == nil {
return ext.logError(errors.New("lookup stream is nil"))
}
if err := stream.Flush(); err != nil {
err = fmt.Errorf("cannot flush lookup stream %w", err)
return ext.logError(err)
}
// setup the lookup context
ext.setupLookupContext(stream)
if !ext.validLookupContext() {
return ext.logError(errors.New("invalid lookup context"))
}
ext.log.Debug("lookup stream open")
return nil
}
func (ext *DecoderExtension) setupLookupContext(stream *jsoniter.Stream) {
ext.log.Debugf("setup lookup context: %s", ext.lookupCtx.Token)
stream.Attachment = ext.encoder.Get(ext.lookupBuffer)
ext.lookupCtx.Stream = stream
ext.lookupBuffer = nil
}
func (ext *DecoderExtension) validLookupContext() bool {
if ext.lookupCtx == nil {
ext.log.Error(errors.New("lookup context is nil"))
return false
}
if ext.lookupCtx.Stream == nil {
ext.log.Error(errors.New("lookup stream is nil"))
return false
}
if ext.lookupCtx.Token != lookup.InvalidToken &&
ext.lookupCtx.Stream.Attachment == nil {
ext.log.Error(errors.New("lookup table is nil"))
return false
}
return true
}
func (ext *DecoderExtension) closeLookupStream() {
ext.log.Debug("closing lookup stream")
ext.lookupBuffer = nil
if ext.lookupCtx == nil {
ext.log.Warn("lookup context is nil")
return
}
stream := ext.lookupCtx.Stream
if stream == nil {
ext.log.Warn("lookup stream is nil")
return
}
stream.Attachment = nil
ext.encoder.ReturnStream(stream)
ext.lookupCtx.Token = lookup.InvalidToken
ext.lookupCtx.Stream = nil
ext.log.Debug("lookup stream closed")
}
func (ext *DecoderExtension) logError(e error) error {
if e == nil {
return e
}
ext.log.Error(e)
return e
}

View File

@ -0,0 +1,86 @@
package secure
import (
"reflect"
"testing"
"github.com/jrapoport/chestnut/encoding/compress/zstd"
"github.com/jrapoport/chestnut/encoding/json/encoders"
"github.com/stretchr/testify/assert"
)
type decoderTest struct {
src []byte
unsealed []byte
dst interface{}
res interface{}
mp map[string]interface{}
sparse Option
}
var decoderTests = []decoderTest{
{noneSealed, noneUnsealed, &None{}, noneDecoded, noneMap, noOpt},
{noneSealed, noneUnsealed, &None{}, noneDecoded, noneMap, sparseOpt},
{noneComp, noneUnsealed, &None{}, noneDecoded, noneMap, noOpt},
{noneComp, noneUnsealed, &None{}, noneDecoded, noneMap, sparseOpt},
{jsonSealed, jsonUnsealed, &JSON{}, jsonDecoded, jsonMap, noOpt},
{jsonSealed, jsonUnsealed, &JSON{}, jsonDecoded, jsonMap, sparseOpt},
{jsonComp, jsonUnsealed, &JSON{}, jsonDecoded, jsonMap, noOpt},
{jsonComp, jsonUnsealed, &JSON{}, jsonDecoded, jsonMap, sparseOpt},
{hashSealed, hashUnsealed, &Hash{}, hashDecoded, hashMap, noOpt},
{hashSealed, hashUnsealed, &Hash{}, hashDecoded, hashMap, sparseOpt},
{hashComp, hashUnsealed, &Hash{}, hashDecoded, hashMap, noOpt},
{hashComp, hashUnsealed, &Hash{}, hashDecoded, hashMap, sparseOpt},
{secSealed, secUnsealed, &Secure{}, secDecoded, secMap, noOpt},
{secSealed, secUnsealed, &Secure{}, secSparse, secMapSparse, sparseOpt},
{secComp, secUnsealed, &Secure{}, secDecoded, secMap, noOpt},
{secComp, secUnsealed, &Secure{}, secSparse, secMapSparse, sparseOpt},
{bothSealed, bothUnsealed, &Both{}, bothDecoded, bothMap, noOpt},
{bothSealed, bothUnsealed, &Both{}, bothSparse, bothMapSparse, sparseOpt},
{bothComp, bothUnsealed, &Both{}, bothDecoded, bothMap, noOpt},
{bothComp, bothUnsealed, &Both{}, bothSparse, bothMapSparse, sparseOpt},
{allSealed, allUnsealed, &All{SI: ifc{}}, allDecoded, allMap, noOpt},
{allSealed, allUnsealed, &All{SI: ifc{}}, allSparse, allMapSparse, sparseOpt},
{allComp, allUnsealed, &All{SI: ifc{}}, allDecoded, allMap, noOpt},
{allComp, allUnsealed, &All{SI: ifc{}}, allSparse, allMapSparse, sparseOpt},
}
func TestSecureDecoderExtension(t *testing.T) {
for _, test := range decoderTests {
testName := reflect.TypeOf(test.dst).Elem().Name()
if test.sparse != nil {
testName += " sparse"
}
t.Run(testName, func(t *testing.T) {
encoder := encoders.NewEncoder()
// register decoding extension
decoderExt := NewSecureDecoderExtension(testEncoderID,
PassthroughDecryption,
WithDecompressor(zstd.Decompress),
test.sparse)
encoder.RegisterExtension(decoderExt)
// unseal the encoding
unsealed, err := decoderExt.Unseal(test.src)
assert.NoError(t, err)
assert.Equal(t, test.unsealed, unsealed)
// open the decoder
err = decoderExt.Open()
assert.NoError(t, err)
// securely decode the value
err = encoder.Unmarshal(unsealed, test.dst)
assert.NoError(t, err)
assertDecoding(t, test.res, test.dst, err)
// securely decode the reflected interface
typ := reflect.ValueOf(test.dst).Elem().Type()
ptr := reflect.New(typ).Interface()
err = encoder.Unmarshal(unsealed, ptr)
assertDecoding(t, test.res, ptr, err)
// securely decode the mapped struct
var mapped interface{}
err = encoder.Unmarshal(unsealed, &mapped)
assertDecoding(t, test.mp, mapped, err)
// close the decoder
decoderExt.Close()
})
}
}

View File

@ -0,0 +1,332 @@
package secure
import (
"encoding/hex"
"errors"
"fmt"
"reflect"
"sync"
"github.com/jrapoport/chestnut/encoding/json/encoders"
"github.com/jrapoport/chestnut/encoding/json/encoders/hash"
"github.com/jrapoport/chestnut/encoding/json/encoders/lookup"
"github.com/jrapoport/chestnut/encoding/json/packager"
"github.com/jrapoport/chestnut/encoding/tags"
"github.com/jrapoport/chestnut/log"
"github.com/json-iterator/go"
)
// SecureLookupPrefix will format the secure lookup token to "[prefix]-[encoder id]-[index]".
const SecureLookupPrefix = "cn"
// EncryptionFunction defines the prototype for the encryption callback.
// See WARNING regarding use of PassthroughEncryption.
type EncryptionFunction func(plaintext []byte) (ciphertext []byte, err error)
// PassthroughEncryption is a dummy function for development and testing *ONLY*.
/*
* WARNING: DO NOT USE IN PRODUCTION.
* PassthroughEncryption is *NOT* encryption and *DOES NOT* encrypt data.
*/
var PassthroughEncryption EncryptionFunction = func(plaintext []byte) ([]byte, error) {
return []byte(hex.EncodeToString(plaintext)), nil
}
// EncoderExtension is a JSON encoder extension for the encryption and decryption of JSON
// encoded data. It supports full encryption / decryption of the encoded block in in
// addition to sparse encryption and hashing of structs on a per field basis via supplementary
// JSON struct field tag options. For additional information on sparse encryption & hashing, please
// SEE: https://github.com/jrapoport/chestnut/blob/master/README.md
//
// For additional information on json-iterator extensions, please
// SEE: https://github.com/json-iterator/go/wiki/Extension
type EncoderExtension struct {
jsoniter.EncoderExtension
opts Options
encoderID string
encoder jsoniter.API
lookupCtx *lookup.Context
lookupBuffer []byte
open bool
encryptFunc EncryptionFunction
log log.Logger
mu sync.RWMutex
}
// NewSecureEncoderExtension returns a new EncoderExtension using the supplied
// EncryptionFunction. If no encoder id is supplied, a new random encoder id will be used.
func NewSecureEncoderExtension(encoderID string, efn EncryptionFunction, opt ...Option) *EncoderExtension {
const encoderName = "encoder"
if encoderID == encoders.InvalidID {
encoderID = encoders.NewEncoderID()
}
opts := DefaultOptions
opts = applyOptions(opts, opt...)
encoder := encoders.NewEncoder()
logName := fmt.Sprintf("%s [%s]", encoderName, encoderID)
token := lookup.NewLookupToken(SecureLookupPrefix, encoderID)
ext := new(EncoderExtension)
ext.opts = opts
ext.log = log.Named(opts.log, logName)
ext.encoderID = encoderID
ext.encryptFunc = efn
ext.encoder = encoder
ext.lookupCtx = &lookup.Context{Token: token}
if encoder == nil {
ext.log.Fatal(errors.New("encoder not found"))
}
if efn == nil {
ext.log.Panic(errors.New("encryption required"))
}
return ext
}
// Seal encrypts and returns the encoded value as a sealed package.
func (ext *EncoderExtension) Seal(encoded []byte) ([]byte, error) {
ext.mu.Lock()
defer ext.mu.Unlock()
ext.log.Debugf("sealing %d encoded bytes: %s", len(encoded), string(encoded))
/// must do this first
if ext.open {
ext.log.Debug("encoder is open, closing it")
ext.close()
}
token := ext.lookupCtx.Token
ext.log.Debugf("package token: %s", token)
plaintext := ext.lookupBuffer
if ext.isSparse() {
ext.log.Debug("sparse encoding data")
} else {
ext.log.Debug("secure encoding data")
plaintext = encoded
token = ""
encoded = nil
}
if ext.hasCompressor() {
var err error
ext.log.Debugf("compress %d plaintext bytes", len(plaintext))
if plaintext, err = ext.compress(plaintext); err != nil {
return nil, ext.logError(err)
}
ext.log.Debugf("compressed %d plaintext bytes", len(plaintext))
ext.log.Debugf("compress %d encoded bytes", len(encoded))
if encoded, err = ext.compress(encoded); err != nil {
return nil, ext.logError(err)
}
ext.log.Debugf("compressed %d encoded bytes", len(encoded))
}
ext.log.Debugf("encrypting %d plaintext bytes: %s",
len(plaintext), string(plaintext))
// encrypt the blocks
ciphertext, err := ext.encrypt(plaintext)
if err != nil {
return nil, ext.logError(err)
}
ext.log.Debugf("encrypted %d bytes", len(ciphertext))
comp := ext.hasCompressor()
ext.log.Debug("sealing package")
pkg, err := packager.EncodePackage(ext.encoderID, token, ciphertext, encoded, comp)
if err != nil {
return nil, ext.logError(err)
}
ext.log.Debugf("sealed %d encoded bytes", len(pkg))
return pkg, nil
}
func (ext *EncoderExtension) hasCompressor() bool {
return ext.opts.compressor != nil
}
func (ext *EncoderExtension) compress(data []byte) ([]byte, error) {
if len(data) <= 0 {
return nil, nil
}
if !ext.hasCompressor() {
return data, nil
}
return ext.opts.compressor(data)
}
// encrypt calls the EncryptionFunction if set, otherwise panic.
// See WARNING regarding the use of PassthroughEncryption.
func (ext *EncoderExtension) encrypt(plaintext []byte) ([]byte, error) {
if ext.encryptFunc == nil {
ext.log.Panic(errors.New("encryption function required"))
}
return ext.encryptFunc(plaintext)
}
// UpdateStructDescriptor customizes the encoding by specifying alternate
// lookup encoder for secure struct field tags and hash struct field strings.
func (ext *EncoderExtension) UpdateStructDescriptor(structDescriptor *jsoniter.StructDescriptor) {
if !ext.isOpen() {
ext.log.Debug("encoder is not open, cannot update struct descriptor")
return
}
ext.log.Debugf("updating struct: %s", structDescriptor.Type)
for _, binding := range structDescriptor.Fields {
field := binding.Field
typ := field.Type()
ext.log.Debugf("updating struct field %s.%s", structDescriptor.Type, field.Name())
tag, has := binding.Field.Tag().Lookup(tags.JSONTag)
if !has {
ext.log.Debug("json tag not found, ignore")
continue
}
name, opts := tags.ParseJSONTag(tag)
ext.log.Debugf("json tag name: %s options: %s", name, opts)
if tags.IgnoreField(name) {
ext.log.Debugf("json tag name %s, ignore", name)
binding.ToNames = []string{}
continue
}
hashName := tags.HashName(opts)
secure := tags.IsSecure(opts)
if !secure && hashName == tags.HashNone {
ext.log.Debug("tag options not found, ignore")
continue
}
encoder := binding.Encoder
if secure {
ext.log.Debugf("added lookup encoder to secure field %s", field.Name())
encoder = lookup.NewLookupEncoder(ext.lookupCtx, typ, encoder)
if enc, ok := encoder.(*lookup.Encoder); ok {
enc.SetLogger(log.Named(ext.log, typ.String()))
}
}
if hashName != tags.HashNone && typ.Kind() == reflect.String {
// if the hash name is unsupported hashFn will be nil
if hashFn := hash.FunctionForName(hashName); hashFn != nil {
ext.log.Debugf("added %s hash encoder for field %s", field.Name(), hashName)
encoder = hash.NewHashEncoder(hashName.String(), hashFn, encoder)
if enc, ok := encoder.(*hash.Encoder); ok {
enc.SetLogger(log.Named(ext.log, hashName.String()))
}
} else {
ext.log.Warnf("%s hash encoder not found", hashName)
}
}
binding.Encoder = encoder
}
}
// Open should be called before Marshal to prepare the encoder.
func (ext *EncoderExtension) Open() error {
ext.mu.Lock()
defer ext.mu.Unlock()
ext.log.Debug("opening encoder")
if ext.open {
return ext.logError(errors.New("encoder already open"))
}
if err := ext.openLookupStream(); err != nil {
err = fmt.Errorf("failed to open encoder %w", err)
return ext.logError(err)
}
ext.open = true
ext.log.Debug("encoder open")
return nil
}
func (ext *EncoderExtension) isOpen() bool {
ext.mu.RLock()
defer ext.mu.RUnlock()
return ext.open
}
// Close should be called after Marshal, but before Seal. Calling
// Seal before Close will call Close automatically if necessary.
func (ext *EncoderExtension) Close() {
ext.mu.Lock()
defer ext.mu.Unlock()
ext.close()
}
// close is the non-locking internal close call.
func (ext *EncoderExtension) close() {
ext.log.Debug("closing encoder")
ext.open = false
ext.closeLookupStream()
ext.log.Debug("encoder closed")
}
func (ext *EncoderExtension) openLookupStream() error {
ext.log.Debug("opening lookup stream")
stream := ext.encoder.BorrowStream(nil)
if stream == nil {
return ext.logError(errors.New("lookup stream is nil"))
}
if err := stream.Flush(); err != nil {
err = fmt.Errorf("cannot flush lookup stream %w", err)
return ext.logError(err)
}
ext.setupLookupContext(stream)
if !ext.validLookupContext() {
return ext.logError(errors.New("invalid lookup context"))
}
ext.log.Debug("lookup stream open")
return nil
}
func (ext *EncoderExtension) setupLookupContext(stream *jsoniter.Stream) {
ext.log.Debugf("setup lookup context: %s", ext.lookupCtx.Token)
// reset the lookup index to 0
stream.Attachment = 0
stream.WriteObjectStart()
ext.lookupCtx.Stream = stream
}
func (ext *EncoderExtension) validLookupContext() bool {
if ext.lookupCtx == nil {
ext.log.Error(errors.New("lookup context is nil"))
return false
}
if ext.lookupCtx.Stream == nil {
ext.log.Error(errors.New("lookup stream is nil"))
return false
}
if ext.lookupCtx.Token == lookup.InvalidToken {
ext.log.Error(errors.New("lookup token is invalid"))
return false
}
sa := ext.lookupCtx.Stream.Attachment
if sa == nil || sa.(int) != 0 {
ext.log.Error(errors.New("lookup index is invalid"))
return false
}
return true
}
func (ext *EncoderExtension) closeLookupStream() {
ext.log.Debug("closing lookup stream")
if ext.lookupCtx == nil {
ext.log.Warn("lookup context is nil")
return
}
stream := ext.lookupCtx.Stream
if stream == nil {
ext.log.Warn("lookup stream is nil")
return
}
stream.WriteObjectEnd()
ext.lookupBuffer = stream.Buffer()
stream.Attachment = nil
ext.encoder.ReturnStream(stream)
ext.lookupCtx.Stream = nil
ext.log.Debug("lookup stream closed")
}
// isSparse checks to see if the value used sparse encryption. If the encoded struct
// used struct tags to secure specific fields, we should have a lookup table.
func (ext *EncoderExtension) isSparse() bool {
const emptyBuffer = "{}"
return len(ext.lookupBuffer) > len(emptyBuffer)
}
func (ext *EncoderExtension) logError(e error) error {
if e == nil {
return e
}
ext.log.Error(e)
return e
}

View File

@ -0,0 +1,66 @@
package secure
import (
"reflect"
"testing"
"github.com/jrapoport/chestnut/encoding/json/encoders"
"github.com/jrapoport/chestnut/encoding/json/packager"
"github.com/stretchr/testify/assert"
)
type encoderTest struct {
src interface{}
dst []byte
sealed []byte
compressed Option
}
var encoderTests = []encoderTest{
{noneObj, noneEncoded, noneSealed, noOpt},
{noneObj, noneEncoded, noneComp, compOpt},
{jsonObj, jsonEncoded, jsonSealed, noOpt},
{jsonObj, jsonEncoded, jsonComp, compOpt},
{hashObj, hashEncoded, hashSealed, noOpt},
{hashObj, hashEncoded, hashComp, compOpt},
{secObj, secEncoded, secSealed, noOpt},
{secObj, secEncoded, secComp, compOpt},
{bothObj, bothEncoded, bothSealed, noOpt},
{bothObj, bothEncoded, bothComp, compOpt},
{allObj, allEncoded, allSealed, noOpt},
{allObj, allEncoded, allComp, compOpt},
}
func TestSecureEncoderExtension(t *testing.T) {
for _, test := range encoderTests {
testName := reflect.TypeOf(test.src).Elem().Name()
if test.compressed != nil {
testName += " compressed"
}
t.Run(testName, func(t *testing.T) {
encoder := encoders.NewEncoder()
// register encoding extension
encoderExt := NewSecureEncoderExtension(testEncoderID,
PassthroughEncryption,
test.compressed)
encoder.RegisterExtension(encoderExt)
// open the encoder
err := encoderExt.Open()
assert.NoError(t, err)
// securely encode the value
encoded, err := encoder.Marshal(test.src)
assertJSON(t, test.dst, encoded, err)
// close the encoder
encoderExt.Close()
// seal the encoding
sealed, err := encoderExt.Seal(encoded)
assert.NoError(t, err)
assert.Equal(t, test.sealed, sealed)
// unwrap the sealed package & ake sure it is valid
pkg, err := packager.DecodePackage(sealed)
assert.NoError(t, err)
assert.NotNil(t, pkg)
assert.NoError(t, pkg.Valid())
})
}
}

View File

@ -0,0 +1,107 @@
package secure
import (
"github.com/jrapoport/chestnut/encoding/compress"
"github.com/jrapoport/chestnut/encoding/compress/zstd"
"github.com/jrapoport/chestnut/log"
)
// Options provides a default implementation for common options for a secure encoding.
type Options struct {
// compressor is only valid for encoders
compressor compress.CompressorFunc
// decompressor is only valid for decoders
decompressor compress.DecompressorFunc
// sparse is only valid for decoding sparse packages
sparse bool
// log is the logger to use
log log.Logger
}
// DefaultOptions represents the recommended default Options for secure encoding.
var DefaultOptions = Options{
log: log.Log,
}
// A Option sets options such as compression or sparse decoding.
type Option interface {
apply(*Options)
}
// EmptyOption does not alter the encoder configuration. It can be embedded
// in another structure to build custom encoder options.
type EmptyOption struct{}
func (EmptyOption) apply(*Options) {}
// funcOption wraps a function that modifies Options
// into an implementation of the Option interface.
type funcOption struct {
f func(*Options)
}
// apply applies an Option to Options.
func (fdo *funcOption) apply(do *Options) {
fdo.f(do)
}
func newFuncOption(f func(*Options)) *funcOption {
return &funcOption{
f: f,
}
}
// applyOptions accepts a Options struct and applies the Option(s) to it.
func applyOptions(opts Options, opt ...Option) Options {
if opt != nil {
for _, o := range opt {
o.apply(&opts)
}
}
return opts
}
// SparseDecode returns a Option that set the decoder to return sparsely
// decoded data. If the JSON data was not sparely encoded, this does nothing.
func SparseDecode() Option {
return newFuncOption(func(o *Options) {
o.sparse = true
})
}
// WithCompressor returns a Option that compresses data.
func WithCompressor(compressor compress.CompressorFunc) Option {
return newFuncOption(func(o *Options) {
o.compressor = compressor
})
}
// WithDecompressor returns a Option that decompresses data.
func WithDecompressor(decompressor compress.DecompressorFunc) Option {
return newFuncOption(func(o *Options) {
o.decompressor = decompressor
})
}
// WithCompression returns a Option that compresses & decompresses data with Zstd.
func WithCompression(format compress.Format) Option {
return newFuncOption(func(o *Options) {
switch format {
case compress.Zstd:
o.compressor = zstd.Compress
o.decompressor = zstd.Decompress
default:
break
}
})
}
// WithLogger returns a Option which sets the logger for the extension.
func WithLogger(l log.Logger) Option {
return newFuncOption(func(o *Options) {
o.log = l
})
}

View File

@ -0,0 +1,121 @@
package secure
import (
"reflect"
"testing"
"github.com/jrapoport/chestnut/encoding/compress"
"github.com/jrapoport/chestnut/encoding/json/encoders"
jsoniter "github.com/json-iterator/go"
"github.com/stretchr/testify/assert"
)
const testEncoderID = "86fb3fa0"
type testCase struct {
src interface{}
dst interface{}
res interface{}
mp map[string]interface{}
sparse Option
}
var (
noOpt = EmptyOption{}
// ignored on non-sparse packages
sparseOpt = SparseDecode()
compOpt = WithCompression(compress.Zstd)
)
var tests = []testCase{
{noneObj, &None{}, noneDecoded, noneMap, noOpt},
{noneObj, &None{}, noneDecoded, noneMap, sparseOpt},
{jsonObj, &JSON{}, jsonDecoded, jsonMap, noOpt},
{jsonObj, &JSON{}, jsonDecoded, jsonMap, sparseOpt},
{hashObj, &Hash{}, hashDecoded, hashMap, noOpt},
{hashObj, &Hash{}, hashDecoded, hashMap, sparseOpt},
{secObj, &Secure{}, secDecoded, secMap, noOpt},
{secObj, &Secure{}, secSparse, secMapSparse, sparseOpt},
{bothObj, &Both{}, bothDecoded, bothMap, noOpt},
{bothObj, &Both{}, bothSparse, bothMapSparse, sparseOpt},
{allObj, &All{SI: ifc{}}, allDecoded, allMap, noOpt},
{allObj, &All{SI: ifc{}}, allSparse, allMapSparse, sparseOpt},
}
func TestSecureExtension(t *testing.T) {
comps := []Option{noOpt, compOpt}
for _, compressed := range comps {
for _, test := range tests {
testName := reflect.TypeOf(test.dst).Elem().Name()
if test.sparse != nil {
testName += " sparse"
}
if compressed != nil {
testName += " compressed"
}
t.Run(testName, func(t *testing.T) {
encoder := encoders.NewEncoder()
// register encoding extension
encoderExt := NewSecureEncoderExtension(testEncoderID,
PassthroughEncryption, compressed)
encoder.RegisterExtension(encoderExt)
// register decoding extension
decoderExt := NewSecureDecoderExtension(testEncoderID,
PassthroughDecryption, compressed, test.sparse)
encoder.RegisterExtension(decoderExt)
// open the encoder
err := encoderExt.Open()
assert.NoError(t, err)
// securely encode the value
encoded, err := encoder.Marshal(test.src)
assert.NoError(t, err)
// close the encoder
encoderExt.Close()
// seal the encoding
sealed, err := encoderExt.Seal(encoded)
assert.NoError(t, err)
// unseal the encoding
unsealed, err := decoderExt.Unseal(sealed)
assert.NoError(t, err)
// open the decoder
err = decoderExt.Open()
assert.NoError(t, err)
// securely decode the value
err = encoder.Unmarshal(unsealed, test.dst)
assert.NoError(t, err)
assertDecoding(t, test.res, test.dst, err)
// securely decode the reflected interface
typ := reflect.ValueOf(test.dst).Elem().Type()
ptr := reflect.New(typ).Interface()
err = encoder.Unmarshal(unsealed, ptr)
assertDecoding(t, test.res, ptr, err)
// securely decode the mapped struct
var mapped interface{}
err = encoder.Unmarshal(unsealed, &mapped)
assertDecoding(t, test.mp, mapped, err)
// close the decoder
decoderExt.Close()
})
}
}
}
func assertJSON(t *testing.T, expected, actual []byte, err error) {
e := assert.NoError(t, err)
if !e {
t.Fatal(err)
}
valid := jsoniter.Valid(actual)
assert.True(t, valid, "invalid JSON")
assert.Equal(t, string(expected), string(actual))
}
func assertDecoding(t *testing.T, expected, actual interface{}, err error) {
e := assert.NoError(t, err)
if !e {
t.Fatal(err)
}
assert.Equal(t, expected, actual)
deep := reflect.DeepEqual(expected, actual)
assert.True(t, deep, "values are not deep equal")
}

View File

@ -0,0 +1,891 @@
package secure
type All struct {
Default string
Blank string `json:""`
Ignore string `json:"-"`
Named string `json:"named"`
Empty string `json:"empty"`
Omit string `json:"omit,omitempty"`
Number int `json:"number"`
Float float64 `json:"float"`
Array []string
Bool bool `json:""`
Int int32 `json:"int32"`
Map map[string]string
EmbedObject JSON
ObjectMap map[string]*JSON
Ptr *JSON
Nested JSON
JSON
SecureDefault string `json:"SecureDefault,secure"`
SecureBlank string `json:",secure"`
SecureIgnore string `json:"-"`
SecureNamed string `json:"named_secure,secure"`
SecureEmpty string `json:"empty_secure,secure"`
SecureOmit string `json:"omit_secure,omitempty,secure"`
SecureNumber int `json:"number_secure,secure"`
SecureFloat float64 `json:"secure_float,secure"`
SecureArray []string `json:",secure"`
SecureBool bool `json:"bool_secure,secure"`
SecureInt int32 `json:"int32_secure,secure"`
SecureMap map[string]string `json:",secure"`
SecureObject JSON `json:",secure"`
SecureObjectMap map[string]*JSON `json:",secure"`
SecurePtr *JSON `json:",secure"`
SecureNested JSON `json:",secure"`
None `json:",secure"`
SparseHash Both
IFaceTest
SI IFaceTest `json:",secure"`
}
type IFaceTest interface {
}
type ifc struct {
Value string
}
var allObj = &All{
Default: "default-value",
Blank: "blank-value",
Ignore: "ignore-value",
Named: "named-value",
Empty: "",
Omit: "",
Number: 42,
Float: 99.9,
Array: []string{"array-string"},
Bool: true,
Int: 42,
Map: map[string]string{"string-map-key": "map-string"},
EmbedObject: *jsonObj,
ObjectMap: map[string]*JSON{"object-map-key": jsonObj},
Ptr: jsonObj,
Nested: *jsonObj,
JSON: *jsonObj,
SecureDefault: "default-secure",
SecureBlank: "blank-secure",
SecureIgnore: "ignore-secure",
SecureNamed: "named-secure",
SecureEmpty: "",
SecureOmit: "",
SecureNumber: 42,
SecureArray: []string{"array-string"},
SecureBool: true,
SecureFloat: 99.0,
SecureInt: 42,
SecureMap: map[string]string{"string-map-key": "map-string"},
SecureObject: *jsonObj,
SecureObjectMap: map[string]*JSON{"object-map-key": jsonObj},
SecurePtr: jsonObj,
SecureNested: *jsonObj,
None: *noneObj,
SparseHash: *bothObj,
IFaceTest: ifc{"test-interface-value"},
SI: ifc{"test-secure-interface-value"},
}
var allDecoded = &All{
Default: "default-value",
Blank: "blank-value",
Named: "named-value",
Number: 42,
Float: 99.9,
Array: []string{"array-string"},
Bool: true,
Int: 42,
Map: map[string]string{"string-map-key": "map-string"},
EmbedObject: *jsonDecoded,
ObjectMap: map[string]*JSON{"object-map-key": jsonDecoded},
Ptr: jsonDecoded,
Nested: *jsonDecoded,
JSON: *jsonDecoded,
SecureDefault: "default-secure",
SecureBlank: "blank-secure",
SecureNamed: "named-secure",
SecureNumber: 42,
SecureFloat: 99,
SecureArray: []string{"array-string"},
SecureBool: true,
SecureInt: 42,
SecureMap: map[string]string{"string-map-key": "map-string"},
SecureObject: *jsonDecoded,
SecureObjectMap: map[string]*JSON{"object-map-key": jsonDecoded},
SecurePtr: jsonDecoded,
SecureNested: *jsonDecoded,
None: *noneDecoded,
SparseHash: *bothDecoded,
IFaceTest: map[string]interface{}{"Value": "test-interface-value"},
SI: map[string]interface{}{"Value": "test-secure-interface-value"},
}
var allSparse = &All{
Default: "default-value",
Blank: "blank-value",
Named: "named-value",
Number: 42,
Float: 99.9,
Array: []string{"array-string"},
Bool: true,
Int: 42,
Map: map[string]string{"string-map-key": "map-string"},
EmbedObject: *jsonDecoded,
ObjectMap: map[string]*JSON{"object-map-key": jsonDecoded},
Ptr: jsonDecoded,
Nested: *jsonDecoded,
JSON: *jsonDecoded,
None: *noneDecoded,
SparseHash: *bothSparse,
IFaceTest: map[string]interface{}{"Value": "test-interface-value"},
}
var allMap = map[string]interface{}{
"Array": []interface{}{"array-string"},
"Blank": "blank-value",
"Bool": true,
"Default": "default-value",
"EmbedObject": jsonMap,
"Empty": "",
"Float": 99.9,
"IFaceTest": map[string]interface{}{
"Value": "test-interface-value",
},
"Map": map[string]interface{}{
"string-map-key": "map-string",
},
"Nested": jsonMap,
"Number": 42.0,
"ObjectMap": map[string]interface{}{
"object-map-key": jsonMap,
},
"Ptr": jsonMap,
"SecureArray": []interface{}{"array-string"},
"SecureBlank": "blank-secure",
"SecureDefault": "default-secure",
"SI": map[string]interface{}{
"Value": "test-secure-interface-value",
},
"SecureMap": map[string]interface{}{
"string-map-key": "map-string",
},
"SecureNested": jsonMap,
"SecureObject": jsonMap,
"SecureObjectMap": map[string]interface{}{
"object-map-key": jsonMap,
},
"SecurePtr": jsonMap,
"SparseHash": bothMap,
"TagBlank": "blank-value",
"TagDefault": "default-value",
"Value": "object-value",
"bool_secure": true,
"empty": "",
"empty_secure": "",
"float": 99.9,
"int32": 42.0,
"int32_secure": 42.0,
"named": "named-value",
"named_secure": "named-secure",
"number": 42.0,
"number_secure": 42.0,
"secure_float": 99.0,
"tag_empty": "",
"tag_float": 99.9,
"tag_named": "named-value",
"tag_number": 42.0,
}
var allMapSparse = map[string]interface{}{
"Array": []interface{}{"array-string"},
"Blank": "blank-value",
"Bool": true,
"Default": "default-value",
"EmbedObject": jsonMap,
"Empty": "",
"Float": 99.9,
"IFaceTest": map[string]interface{}{
"Value": "test-interface-value",
},
"Map": map[string]interface{}{
"string-map-key": "map-string",
},
"Nested": jsonMap,
"Number": 42.0,
"ObjectMap": map[string]interface{}{
"object-map-key": jsonMap,
},
"Ptr": jsonMap,
"SecureArray": interface{}(nil),
"SecureBlank": "",
"SecureDefault": "",
"SI": nil,
"SecureMap": nil,
"SecureNested": nil,
"SecureObject": nil,
"SecureObjectMap": nil,
"SecurePtr": nil,
"SparseHash": bothMapSparse,
"TagBlank": "blank-value",
"TagDefault": "default-value",
"Value": "object-value",
"bool_secure": false,
"empty": "",
"empty_secure": "",
"float": 99.9,
"int32": 42.0,
"int32_secure": 0.0,
"named": "named-value",
"named_secure": "",
"number": 42.0,
"number_secure": 0.0,
"secure_float": 0.0,
"tag_empty": "",
"tag_float": 99.9,
"tag_named": "named-value",
"tag_number": 42.0,
}
var allEncoded = []byte(`{"Default":"default-value","Blank":"blank-value","na` +
`med":"named-value","empty":"","number":42,"float":99.9,"Array":["array-s` +
`tring"],"Bool":true,"int32":42,"Map":{"string-map-key":"map-string"},"Em` +
`bedObject":{"TagDefault":"default-value","TagBlank":"blank-value","tag_n` +
`amed":"named-value","tag_empty":"","tag_number":42,"tag_float":99.9},"Ob` +
`jectMap":{"object-map-key":{"TagDefault":"default-value","TagBlank":"bla` +
`nk-value","tag_named":"named-value","tag_empty":"","tag_number":42,"tag_` +
`float":99.9}},"Ptr":{"TagDefault":"default-value","TagBlank":"blank-valu` +
`e","tag_named":"named-value","tag_empty":"","tag_number":42,"tag_float":` +
`99.9},"Nested":{"TagDefault":"default-value","TagBlank":"blank-value","t` +
`ag_named":"named-value","tag_empty":"","tag_number":42,"tag_float":99.9}` +
`,"TagDefault":"default-value","TagBlank":"blank-value","tag_named":"name` +
`d-value","tag_empty":"","tag_number":42,"tag_float":99.9,"SecureDefault"` +
`:"cn86fb3fa00_24","SecureBlank":"cn86fb3fa01_24","named_secure":"cn86fb3` +
`fa02_24","empty_secure":"cn86fb3fa03_24","number_secure":"cn86fb3fa04_2"` +
`,"secure_float":"cn86fb3fa05_14","SecureArray":"cn86fb3fa06_23","bool_se` +
`cure":"cn86fb3fa07_1","int32_secure":"cn86fb3fa08_5","SecureMap":"cn86fb` +
`3fa09_21","SecureObject":"cn86fb3fa010_25","SecureObjectMap":"cn86fb3fa0` +
`11_21","SecurePtr":"cn86fb3fa012_22","SecureNested":"cn86fb3fa013_25","V` +
`alue":"object-value","Empty":"","Number":42,"Float":99.9,"SparseHash":{"` +
`Default":"default-value","Blank":"blank-value","named":"named-value","em` +
`pty":"","number":42,"float":99.9,"SecureDefault":"cn86fb3fa014_24","Secu` +
`reBlank":"cn86fb3fa015_24","named_secure":"cn86fb3fa016_24","empty_secur` +
`e":"cn86fb3fa017_24","number_secure":"cn86fb3fa018_2","float_secure":"cn` +
`86fb3fa019_14","HashBlank":"sha256:e05f57c166bef02522e61113b7cd05ccb7bec` +
`3319d974809cb83f015e5f07fd5","named_hash":"sha256:dea5cf8c7d3fa5c266b2bf` +
`3abfbf693f7546dd2642569a2120f3536d45a6b5ab","empty_hash":"","number_hash` +
`":42,"float_hash":99.9,"SecureHashBlank":"cn86fb3fa020_24","named_secure` +
`_hash":"cn86fb3fa021_24","empty_secure_hash":"cn86fb3fa022_24","number_s` +
`ecure_hash":"cn86fb3fa023_2","float_secure_hash":"cn86fb3fa024_14"},"IFa` +
`ceTest":{"Value":"test-interface-value"},"SI":"cn86fb3fa025_20"}`)
var allUnsealed = []byte(`{"Default":"default-value","Blank":"blank-value","n` +
`amed":"named-value","empty":"","number":42,"float":99.9,"Array":["array-` +
`string"],"Bool":true,"int32":42,"Map":{"string-map-key":"map-string"},"E` +
`mbedObject":{"TagDefault":"default-value","TagBlank":"blank-value","tag_` +
`named":"named-value","tag_empty":"","tag_number":42,"tag_float":99.9},"O` +
`bjectMap":{"object-map-key":{"TagDefault":"default-value","TagBlank":"bl` +
`ank-value","tag_named":"named-value","tag_empty":"","tag_number":42,"tag` +
`_float":99.9}},"Ptr":{"TagDefault":"default-value","TagBlank":"blank-val` +
`ue","tag_named":"named-value","tag_empty":"","tag_number":42,"tag_float"` +
`:99.9},"Nested":{"TagDefault":"default-value","TagBlank":"blank-value","` +
`tag_named":"named-value","tag_empty":"","tag_number":42,"tag_float":99.9` +
`},"TagDefault":"default-value","TagBlank":"blank-value","tag_named":"nam` +
`ed-value","tag_empty":"","tag_number":42,"tag_float":99.9,"SecureDefault` +
`":"cn86fb3fa00_24","SecureBlank":"cn86fb3fa01_24","named_secure":"cn86fb` +
`3fa02_24","empty_secure":"cn86fb3fa03_24","number_secure":"cn86fb3fa04_2` +
`","secure_float":"cn86fb3fa05_14","SecureArray":"cn86fb3fa06_23","bool_s` +
`ecure":"cn86fb3fa07_1","int32_secure":"cn86fb3fa08_5","SecureMap":"cn86f` +
`b3fa09_21","SecureObject":"cn86fb3fa010_25","SecureObjectMap":"cn86fb3fa` +
`011_21","SecurePtr":"cn86fb3fa012_22","SecureNested":"cn86fb3fa013_25","` +
`Value":"object-value","Empty":"","Number":42,"Float":99.9,"SparseHash":{` +
`"Default":"default-value","Blank":"blank-value","named":"named-value","e` +
`mpty":"","number":42,"float":99.9,"SecureDefault":"cn86fb3fa014_24","Sec` +
`ureBlank":"cn86fb3fa015_24","named_secure":"cn86fb3fa016_24","empty_secu` +
`re":"cn86fb3fa017_24","number_secure":"cn86fb3fa018_2","float_secure":"c` +
`n86fb3fa019_14","HashBlank":"sha256:e05f57c166bef02522e61113b7cd05ccb7be` +
`c3319d974809cb83f015e5f07fd5","named_hash":"sha256:dea5cf8c7d3fa5c266b2b` +
`f3abfbf693f7546dd2642569a2120f3536d45a6b5ab","empty_hash":"","number_has` +
`h":42,"float_hash":99.9,"SecureHashBlank":"cn86fb3fa020_24","named_secur` +
`e_hash":"cn86fb3fa021_24","empty_secure_hash":"cn86fb3fa022_24","number_` +
`secure_hash":"cn86fb3fa023_2","float_secure_hash":"cn86fb3fa024_14"},"IF` +
`aceTest":{"Value":"test-interface-value"},"SI":"cn86fb3fa025_20"}`)
var allSealed = []byte{0x6a, 0xff, 0x81, 0x3, 0x1, 0x1, 0x7, 0x50, 0x61,
0x63, 0x6b, 0x61, 0x67, 0x65, 0x1, 0xff, 0x82, 0x0, 0x1, 0x7, 0x1, 0x7,
0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x1, 0xc, 0x0, 0x1, 0x6, 0x46,
0x6f, 0x72, 0x6d, 0x61, 0x74, 0x1, 0xc, 0x0, 0x1, 0xa, 0x43, 0x6f, 0x6d,
0x70, 0x72, 0x65, 0x73, 0x73, 0x65, 0x64, 0x1, 0x2, 0x0, 0x1, 0x9, 0x45,
0x6e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x49, 0x44, 0x1, 0xc, 0x0, 0x1, 0x5,
0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x1, 0xc, 0x0, 0x1, 0x6, 0x43, 0x69, 0x70,
0x68, 0x65, 0x72, 0x1, 0xa, 0x0, 0x1, 0x7, 0x45, 0x6e, 0x63, 0x6f, 0x64,
0x65, 0x64, 0x1, 0xa, 0x0, 0x0, 0x0, 0xfe, 0x14, 0x0, 0xff, 0x82, 0x1, 0x5,
0x30, 0x2e, 0x30, 0x2e, 0x31, 0x1, 0x6, 0x73, 0x70, 0x61, 0x72, 0x73, 0x65,
0x2, 0x8, 0x38, 0x36, 0x66, 0x62, 0x33, 0x66, 0x61, 0x30, 0x1, 0xa, 0x63,
0x6e, 0x38, 0x36, 0x66, 0x62, 0x33, 0x66, 0x61, 0x30, 0x1, 0xfe, 0xb, 0x34,
0x37, 0x62, 0x32, 0x32, 0x36, 0x33, 0x36, 0x65, 0x33, 0x38, 0x33, 0x36,
0x36, 0x36, 0x36, 0x32, 0x33, 0x33, 0x36, 0x36, 0x36, 0x31, 0x33, 0x30,
0x33, 0x30, 0x35, 0x66, 0x33, 0x32, 0x33, 0x34, 0x32, 0x32, 0x33, 0x61,
0x32, 0x32, 0x36, 0x34, 0x36, 0x35, 0x36, 0x36, 0x36, 0x31, 0x37, 0x35,
0x36, 0x63, 0x37, 0x34, 0x32, 0x64, 0x37, 0x33, 0x36, 0x35, 0x36, 0x33,
0x37, 0x35, 0x37, 0x32, 0x36, 0x35, 0x32, 0x32, 0x32, 0x63, 0x32, 0x32,
0x36, 0x33, 0x36, 0x65, 0x33, 0x38, 0x33, 0x36, 0x36, 0x36, 0x36, 0x32,
0x33, 0x33, 0x36, 0x36, 0x36, 0x31, 0x33, 0x30, 0x33, 0x31, 0x35, 0x66,
0x33, 0x32, 0x33, 0x34, 0x32, 0x32, 0x33, 0x61, 0x32, 0x32, 0x36, 0x32,
0x36, 0x63, 0x36, 0x31, 0x36, 0x65, 0x36, 0x62, 0x32, 0x64, 0x37, 0x33,
0x36, 0x35, 0x36, 0x33, 0x37, 0x35, 0x37, 0x32, 0x36, 0x35, 0x32, 0x32,
0x32, 0x63, 0x32, 0x32, 0x36, 0x33, 0x36, 0x65, 0x33, 0x38, 0x33, 0x36,
0x36, 0x36, 0x36, 0x32, 0x33, 0x33, 0x36, 0x36, 0x36, 0x31, 0x33, 0x30,
0x33, 0x32, 0x35, 0x66, 0x33, 0x32, 0x33, 0x34, 0x32, 0x32, 0x33, 0x61,
0x32, 0x32, 0x36, 0x65, 0x36, 0x31, 0x36, 0x64, 0x36, 0x35, 0x36, 0x34,
0x32, 0x64, 0x37, 0x33, 0x36, 0x35, 0x36, 0x33, 0x37, 0x35, 0x37, 0x32,
0x36, 0x35, 0x32, 0x32, 0x32, 0x63, 0x32, 0x32, 0x36, 0x33, 0x36, 0x65,
0x33, 0x38, 0x33, 0x36, 0x36, 0x36, 0x36, 0x32, 0x33, 0x33, 0x36, 0x36,
0x36, 0x31, 0x33, 0x30, 0x33, 0x33, 0x35, 0x66, 0x33, 0x32, 0x33, 0x34,
0x32, 0x32, 0x33, 0x61, 0x32, 0x32, 0x32, 0x32, 0x32, 0x63, 0x32, 0x32,
0x36, 0x33, 0x36, 0x65, 0x33, 0x38, 0x33, 0x36, 0x36, 0x36, 0x36, 0x32,
0x33, 0x33, 0x36, 0x36, 0x36, 0x31, 0x33, 0x30, 0x33, 0x34, 0x35, 0x66,
0x33, 0x32, 0x32, 0x32, 0x33, 0x61, 0x33, 0x34, 0x33, 0x32, 0x32, 0x63,
0x32, 0x32, 0x36, 0x33, 0x36, 0x65, 0x33, 0x38, 0x33, 0x36, 0x36, 0x36,
0x36, 0x32, 0x33, 0x33, 0x36, 0x36, 0x36, 0x31, 0x33, 0x30, 0x33, 0x35,
0x35, 0x66, 0x33, 0x31, 0x33, 0x34, 0x32, 0x32, 0x33, 0x61, 0x33, 0x39,
0x33, 0x39, 0x32, 0x63, 0x32, 0x32, 0x36, 0x33, 0x36, 0x65, 0x33, 0x38,
0x33, 0x36, 0x36, 0x36, 0x36, 0x32, 0x33, 0x33, 0x36, 0x36, 0x36, 0x31,
0x33, 0x30, 0x33, 0x36, 0x35, 0x66, 0x33, 0x32, 0x33, 0x33, 0x32, 0x32,
0x33, 0x61, 0x35, 0x62, 0x32, 0x32, 0x36, 0x31, 0x37, 0x32, 0x37, 0x32,
0x36, 0x31, 0x37, 0x39, 0x32, 0x64, 0x37, 0x33, 0x37, 0x34, 0x37, 0x32,
0x36, 0x39, 0x36, 0x65, 0x36, 0x37, 0x32, 0x32, 0x35, 0x64, 0x32, 0x63,
0x32, 0x32, 0x36, 0x33, 0x36, 0x65, 0x33, 0x38, 0x33, 0x36, 0x36, 0x36,
0x36, 0x32, 0x33, 0x33, 0x36, 0x36, 0x36, 0x31, 0x33, 0x30, 0x33, 0x37,
0x35, 0x66, 0x33, 0x31, 0x32, 0x32, 0x33, 0x61, 0x37, 0x34, 0x37, 0x32,
0x37, 0x35, 0x36, 0x35, 0x32, 0x63, 0x32, 0x32, 0x36, 0x33, 0x36, 0x65,
0x33, 0x38, 0x33, 0x36, 0x36, 0x36, 0x36, 0x32, 0x33, 0x33, 0x36, 0x36,
0x36, 0x31, 0x33, 0x30, 0x33, 0x38, 0x35, 0x66, 0x33, 0x35, 0x32, 0x32,
0x33, 0x61, 0x33, 0x34, 0x33, 0x32, 0x32, 0x63, 0x32, 0x32, 0x36, 0x33,
0x36, 0x65, 0x33, 0x38, 0x33, 0x36, 0x36, 0x36, 0x36, 0x32, 0x33, 0x33,
0x36, 0x36, 0x36, 0x31, 0x33, 0x30, 0x33, 0x39, 0x35, 0x66, 0x33, 0x32,
0x33, 0x31, 0x32, 0x32, 0x33, 0x61, 0x37, 0x62, 0x32, 0x32, 0x37, 0x33,
0x37, 0x34, 0x37, 0x32, 0x36, 0x39, 0x36, 0x65, 0x36, 0x37, 0x32, 0x64,
0x36, 0x64, 0x36, 0x31, 0x37, 0x30, 0x32, 0x64, 0x36, 0x62, 0x36, 0x35,
0x37, 0x39, 0x32, 0x32, 0x33, 0x61, 0x32, 0x32, 0x36, 0x64, 0x36, 0x31,
0x37, 0x30, 0x32, 0x64, 0x37, 0x33, 0x37, 0x34, 0x37, 0x32, 0x36, 0x39,
0x36, 0x65, 0x36, 0x37, 0x32, 0x32, 0x37, 0x64, 0x32, 0x63, 0x32, 0x32,
0x36, 0x33, 0x36, 0x65, 0x33, 0x38, 0x33, 0x36, 0x36, 0x36, 0x36, 0x32,
0x33, 0x33, 0x36, 0x36, 0x36, 0x31, 0x33, 0x30, 0x33, 0x31, 0x33, 0x30,
0x35, 0x66, 0x33, 0x32, 0x33, 0x35, 0x32, 0x32, 0x33, 0x61, 0x37, 0x62,
0x32, 0x32, 0x35, 0x34, 0x36, 0x31, 0x36, 0x37, 0x34, 0x34, 0x36, 0x35,
0x36, 0x36, 0x36, 0x31, 0x37, 0x35, 0x36, 0x63, 0x37, 0x34, 0x32, 0x32,
0x33, 0x61, 0x32, 0x32, 0x36, 0x34, 0x36, 0x35, 0x36, 0x36, 0x36, 0x31,
0x37, 0x35, 0x36, 0x63, 0x37, 0x34, 0x32, 0x64, 0x37, 0x36, 0x36, 0x31,
0x36, 0x63, 0x37, 0x35, 0x36, 0x35, 0x32, 0x32, 0x32, 0x63, 0x32, 0x32,
0x35, 0x34, 0x36, 0x31, 0x36, 0x37, 0x34, 0x32, 0x36, 0x63, 0x36, 0x31,
0x36, 0x65, 0x36, 0x62, 0x32, 0x32, 0x33, 0x61, 0x32, 0x32, 0x36, 0x32,
0x36, 0x63, 0x36, 0x31, 0x36, 0x65, 0x36, 0x62, 0x32, 0x64, 0x37, 0x36,
0x36, 0x31, 0x36, 0x63, 0x37, 0x35, 0x36, 0x35, 0x32, 0x32, 0x32, 0x63,
0x32, 0x32, 0x37, 0x34, 0x36, 0x31, 0x36, 0x37, 0x35, 0x66, 0x36, 0x65,
0x36, 0x31, 0x36, 0x64, 0x36, 0x35, 0x36, 0x34, 0x32, 0x32, 0x33, 0x61,
0x32, 0x32, 0x36, 0x65, 0x36, 0x31, 0x36, 0x64, 0x36, 0x35, 0x36, 0x34,
0x32, 0x64, 0x37, 0x36, 0x36, 0x31, 0x36, 0x63, 0x37, 0x35, 0x36, 0x35,
0x32, 0x32, 0x32, 0x63, 0x32, 0x32, 0x37, 0x34, 0x36, 0x31, 0x36, 0x37,
0x35, 0x66, 0x36, 0x35, 0x36, 0x64, 0x37, 0x30, 0x37, 0x34, 0x37, 0x39,
0x32, 0x32, 0x33, 0x61, 0x32, 0x32, 0x32, 0x32, 0x32, 0x63, 0x32, 0x32,
0x37, 0x34, 0x36, 0x31, 0x36, 0x37, 0x35, 0x66, 0x36, 0x65, 0x37, 0x35,
0x36, 0x64, 0x36, 0x32, 0x36, 0x35, 0x37, 0x32, 0x32, 0x32, 0x33, 0x61,
0x33, 0x34, 0x33, 0x32, 0x32, 0x63, 0x32, 0x32, 0x37, 0x34, 0x36, 0x31,
0x36, 0x37, 0x35, 0x66, 0x36, 0x36, 0x36, 0x63, 0x36, 0x66, 0x36, 0x31,
0x37, 0x34, 0x32, 0x32, 0x33, 0x61, 0x33, 0x39, 0x33, 0x39, 0x32, 0x65,
0x33, 0x39, 0x37, 0x64, 0x32, 0x63, 0x32, 0x32, 0x36, 0x33, 0x36, 0x65,
0x33, 0x38, 0x33, 0x36, 0x36, 0x36, 0x36, 0x32, 0x33, 0x33, 0x36, 0x36,
0x36, 0x31, 0x33, 0x30, 0x33, 0x31, 0x33, 0x31, 0x35, 0x66, 0x33, 0x32,
0x33, 0x31, 0x32, 0x32, 0x33, 0x61, 0x37, 0x62, 0x32, 0x32, 0x36, 0x66,
0x36, 0x32, 0x36, 0x61, 0x36, 0x35, 0x36, 0x33, 0x37, 0x34, 0x32, 0x64,
0x36, 0x64, 0x36, 0x31, 0x37, 0x30, 0x32, 0x64, 0x36, 0x62, 0x36, 0x35,
0x37, 0x39, 0x32, 0x32, 0x33, 0x61, 0x37, 0x62, 0x32, 0x32, 0x35, 0x34,
0x36, 0x31, 0x36, 0x37, 0x34, 0x34, 0x36, 0x35, 0x36, 0x36, 0x36, 0x31,
0x37, 0x35, 0x36, 0x63, 0x37, 0x34, 0x32, 0x32, 0x33, 0x61, 0x32, 0x32,
0x36, 0x34, 0x36, 0x35, 0x36, 0x36, 0x36, 0x31, 0x37, 0x35, 0x36, 0x63,
0x37, 0x34, 0x32, 0x64, 0x37, 0x36, 0x36, 0x31, 0x36, 0x63, 0x37, 0x35,
0x36, 0x35, 0x32, 0x32, 0x32, 0x63, 0x32, 0x32, 0x35, 0x34, 0x36, 0x31,
0x36, 0x37, 0x34, 0x32, 0x36, 0x63, 0x36, 0x31, 0x36, 0x65, 0x36, 0x62,
0x32, 0x32, 0x33, 0x61, 0x32, 0x32, 0x36, 0x32, 0x36, 0x63, 0x36, 0x31,
0x36, 0x65, 0x36, 0x62, 0x32, 0x64, 0x37, 0x36, 0x36, 0x31, 0x36, 0x63,
0x37, 0x35, 0x36, 0x35, 0x32, 0x32, 0x32, 0x63, 0x32, 0x32, 0x37, 0x34,
0x36, 0x31, 0x36, 0x37, 0x35, 0x66, 0x36, 0x65, 0x36, 0x31, 0x36, 0x64,
0x36, 0x35, 0x36, 0x34, 0x32, 0x32, 0x33, 0x61, 0x32, 0x32, 0x36, 0x65,
0x36, 0x31, 0x36, 0x64, 0x36, 0x35, 0x36, 0x34, 0x32, 0x64, 0x37, 0x36,
0x36, 0x31, 0x36, 0x63, 0x37, 0x35, 0x36, 0x35, 0x32, 0x32, 0x32, 0x63,
0x32, 0x32, 0x37, 0x34, 0x36, 0x31, 0x36, 0x37, 0x35, 0x66, 0x36, 0x35,
0x36, 0x64, 0x37, 0x30, 0x37, 0x34, 0x37, 0x39, 0x32, 0x32, 0x33, 0x61,
0x32, 0x32, 0x32, 0x32, 0x32, 0x63, 0x32, 0x32, 0x37, 0x34, 0x36, 0x31,
0x36, 0x37, 0x35, 0x66, 0x36, 0x65, 0x37, 0x35, 0x36, 0x64, 0x36, 0x32,
0x36, 0x35, 0x37, 0x32, 0x32, 0x32, 0x33, 0x61, 0x33, 0x34, 0x33, 0x32,
0x32, 0x63, 0x32, 0x32, 0x37, 0x34, 0x36, 0x31, 0x36, 0x37, 0x35, 0x66,
0x36, 0x36, 0x36, 0x63, 0x36, 0x66, 0x36, 0x31, 0x37, 0x34, 0x32, 0x32,
0x33, 0x61, 0x33, 0x39, 0x33, 0x39, 0x32, 0x65, 0x33, 0x39, 0x37, 0x64,
0x37, 0x64, 0x32, 0x63, 0x32, 0x32, 0x36, 0x33, 0x36, 0x65, 0x33, 0x38,
0x33, 0x36, 0x36, 0x36, 0x36, 0x32, 0x33, 0x33, 0x36, 0x36, 0x36, 0x31,
0x33, 0x30, 0x33, 0x31, 0x33, 0x32, 0x35, 0x66, 0x33, 0x32, 0x33, 0x32,
0x32, 0x32, 0x33, 0x61, 0x37, 0x62, 0x32, 0x32, 0x35, 0x34, 0x36, 0x31,
0x36, 0x37, 0x34, 0x34, 0x36, 0x35, 0x36, 0x36, 0x36, 0x31, 0x37, 0x35,
0x36, 0x63, 0x37, 0x34, 0x32, 0x32, 0x33, 0x61, 0x32, 0x32, 0x36, 0x34,
0x36, 0x35, 0x36, 0x36, 0x36, 0x31, 0x37, 0x35, 0x36, 0x63, 0x37, 0x34,
0x32, 0x64, 0x37, 0x36, 0x36, 0x31, 0x36, 0x63, 0x37, 0x35, 0x36, 0x35,
0x32, 0x32, 0x32, 0x63, 0x32, 0x32, 0x35, 0x34, 0x36, 0x31, 0x36, 0x37,
0x34, 0x32, 0x36, 0x63, 0x36, 0x31, 0x36, 0x65, 0x36, 0x62, 0x32, 0x32,
0x33, 0x61, 0x32, 0x32, 0x36, 0x32, 0x36, 0x63, 0x36, 0x31, 0x36, 0x65,
0x36, 0x62, 0x32, 0x64, 0x37, 0x36, 0x36, 0x31, 0x36, 0x63, 0x37, 0x35,
0x36, 0x35, 0x32, 0x32, 0x32, 0x63, 0x32, 0x32, 0x37, 0x34, 0x36, 0x31,
0x36, 0x37, 0x35, 0x66, 0x36, 0x65, 0x36, 0x31, 0x36, 0x64, 0x36, 0x35,
0x36, 0x34, 0x32, 0x32, 0x33, 0x61, 0x32, 0x32, 0x36, 0x65, 0x36, 0x31,
0x36, 0x64, 0x36, 0x35, 0x36, 0x34, 0x32, 0x64, 0x37, 0x36, 0x36, 0x31,
0x36, 0x63, 0x37, 0x35, 0x36, 0x35, 0x32, 0x32, 0x32, 0x63, 0x32, 0x32,
0x37, 0x34, 0x36, 0x31, 0x36, 0x37, 0x35, 0x66, 0x36, 0x35, 0x36, 0x64,
0x37, 0x30, 0x37, 0x34, 0x37, 0x39, 0x32, 0x32, 0x33, 0x61, 0x32, 0x32,
0x32, 0x32, 0x32, 0x63, 0x32, 0x32, 0x37, 0x34, 0x36, 0x31, 0x36, 0x37,
0x35, 0x66, 0x36, 0x65, 0x37, 0x35, 0x36, 0x64, 0x36, 0x32, 0x36, 0x35,
0x37, 0x32, 0x32, 0x32, 0x33, 0x61, 0x33, 0x34, 0x33, 0x32, 0x32, 0x63,
0x32, 0x32, 0x37, 0x34, 0x36, 0x31, 0x36, 0x37, 0x35, 0x66, 0x36, 0x36,
0x36, 0x63, 0x36, 0x66, 0x36, 0x31, 0x37, 0x34, 0x32, 0x32, 0x33, 0x61,
0x33, 0x39, 0x33, 0x39, 0x32, 0x65, 0x33, 0x39, 0x37, 0x64, 0x32, 0x63,
0x32, 0x32, 0x36, 0x33, 0x36, 0x65, 0x33, 0x38, 0x33, 0x36, 0x36, 0x36,
0x36, 0x32, 0x33, 0x33, 0x36, 0x36, 0x36, 0x31, 0x33, 0x30, 0x33, 0x31,
0x33, 0x33, 0x35, 0x66, 0x33, 0x32, 0x33, 0x35, 0x32, 0x32, 0x33, 0x61,
0x37, 0x62, 0x32, 0x32, 0x35, 0x34, 0x36, 0x31, 0x36, 0x37, 0x34, 0x34,
0x36, 0x35, 0x36, 0x36, 0x36, 0x31, 0x37, 0x35, 0x36, 0x63, 0x37, 0x34,
0x32, 0x32, 0x33, 0x61, 0x32, 0x32, 0x36, 0x34, 0x36, 0x35, 0x36, 0x36,
0x36, 0x31, 0x37, 0x35, 0x36, 0x63, 0x37, 0x34, 0x32, 0x64, 0x37, 0x36,
0x36, 0x31, 0x36, 0x63, 0x37, 0x35, 0x36, 0x35, 0x32, 0x32, 0x32, 0x63,
0x32, 0x32, 0x35, 0x34, 0x36, 0x31, 0x36, 0x37, 0x34, 0x32, 0x36, 0x63,
0x36, 0x31, 0x36, 0x65, 0x36, 0x62, 0x32, 0x32, 0x33, 0x61, 0x32, 0x32,
0x36, 0x32, 0x36, 0x63, 0x36, 0x31, 0x36, 0x65, 0x36, 0x62, 0x32, 0x64,
0x37, 0x36, 0x36, 0x31, 0x36, 0x63, 0x37, 0x35, 0x36, 0x35, 0x32, 0x32,
0x32, 0x63, 0x32, 0x32, 0x37, 0x34, 0x36, 0x31, 0x36, 0x37, 0x35, 0x66,
0x36, 0x65, 0x36, 0x31, 0x36, 0x64, 0x36, 0x35, 0x36, 0x34, 0x32, 0x32,
0x33, 0x61, 0x32, 0x32, 0x36, 0x65, 0x36, 0x31, 0x36, 0x64, 0x36, 0x35,
0x36, 0x34, 0x32, 0x64, 0x37, 0x36, 0x36, 0x31, 0x36, 0x63, 0x37, 0x35,
0x36, 0x35, 0x32, 0x32, 0x32, 0x63, 0x32, 0x32, 0x37, 0x34, 0x36, 0x31,
0x36, 0x37, 0x35, 0x66, 0x36, 0x35, 0x36, 0x64, 0x37, 0x30, 0x37, 0x34,
0x37, 0x39, 0x32, 0x32, 0x33, 0x61, 0x32, 0x32, 0x32, 0x32, 0x32, 0x63,
0x32, 0x32, 0x37, 0x34, 0x36, 0x31, 0x36, 0x37, 0x35, 0x66, 0x36, 0x65,
0x37, 0x35, 0x36, 0x64, 0x36, 0x32, 0x36, 0x35, 0x37, 0x32, 0x32, 0x32,
0x33, 0x61, 0x33, 0x34, 0x33, 0x32, 0x32, 0x63, 0x32, 0x32, 0x37, 0x34,
0x36, 0x31, 0x36, 0x37, 0x35, 0x66, 0x36, 0x36, 0x36, 0x63, 0x36, 0x66,
0x36, 0x31, 0x37, 0x34, 0x32, 0x32, 0x33, 0x61, 0x33, 0x39, 0x33, 0x39,
0x32, 0x65, 0x33, 0x39, 0x37, 0x64, 0x32, 0x63, 0x32, 0x32, 0x36, 0x33,
0x36, 0x65, 0x33, 0x38, 0x33, 0x36, 0x36, 0x36, 0x36, 0x32, 0x33, 0x33,
0x36, 0x36, 0x36, 0x31, 0x33, 0x30, 0x33, 0x31, 0x33, 0x34, 0x35, 0x66,
0x33, 0x32, 0x33, 0x34, 0x32, 0x32, 0x33, 0x61, 0x32, 0x32, 0x36, 0x34,
0x36, 0x35, 0x36, 0x36, 0x36, 0x31, 0x37, 0x35, 0x36, 0x63, 0x37, 0x34,
0x32, 0x64, 0x37, 0x33, 0x36, 0x35, 0x36, 0x33, 0x37, 0x35, 0x37, 0x32,
0x36, 0x35, 0x32, 0x32, 0x32, 0x63, 0x32, 0x32, 0x36, 0x33, 0x36, 0x65,
0x33, 0x38, 0x33, 0x36, 0x36, 0x36, 0x36, 0x32, 0x33, 0x33, 0x36, 0x36,
0x36, 0x31, 0x33, 0x30, 0x33, 0x31, 0x33, 0x35, 0x35, 0x66, 0x33, 0x32,
0x33, 0x34, 0x32, 0x32, 0x33, 0x61, 0x32, 0x32, 0x36, 0x32, 0x36, 0x63,
0x36, 0x31, 0x36, 0x65, 0x36, 0x62, 0x32, 0x64, 0x37, 0x33, 0x36, 0x35,
0x36, 0x33, 0x37, 0x35, 0x37, 0x32, 0x36, 0x35, 0x32, 0x32, 0x32, 0x63,
0x32, 0x32, 0x36, 0x33, 0x36, 0x65, 0x33, 0x38, 0x33, 0x36, 0x36, 0x36,
0x36, 0x32, 0x33, 0x33, 0x36, 0x36, 0x36, 0x31, 0x33, 0x30, 0x33, 0x31,
0x33, 0x36, 0x35, 0x66, 0x33, 0x32, 0x33, 0x34, 0x32, 0x32, 0x33, 0x61,
0x32, 0x32, 0x36, 0x65, 0x36, 0x31, 0x36, 0x64, 0x36, 0x35, 0x36, 0x34,
0x32, 0x64, 0x37, 0x33, 0x36, 0x35, 0x36, 0x33, 0x37, 0x35, 0x37, 0x32,
0x36, 0x35, 0x32, 0x32, 0x32, 0x63, 0x32, 0x32, 0x36, 0x33, 0x36, 0x65,
0x33, 0x38, 0x33, 0x36, 0x36, 0x36, 0x36, 0x32, 0x33, 0x33, 0x36, 0x36,
0x36, 0x31, 0x33, 0x30, 0x33, 0x31, 0x33, 0x37, 0x35, 0x66, 0x33, 0x32,
0x33, 0x34, 0x32, 0x32, 0x33, 0x61, 0x32, 0x32, 0x32, 0x32, 0x32, 0x63,
0x32, 0x32, 0x36, 0x33, 0x36, 0x65, 0x33, 0x38, 0x33, 0x36, 0x36, 0x36,
0x36, 0x32, 0x33, 0x33, 0x36, 0x36, 0x36, 0x31, 0x33, 0x30, 0x33, 0x31,
0x33, 0x38, 0x35, 0x66, 0x33, 0x32, 0x32, 0x32, 0x33, 0x61, 0x33, 0x34,
0x33, 0x32, 0x32, 0x63, 0x32, 0x32, 0x36, 0x33, 0x36, 0x65, 0x33, 0x38,
0x33, 0x36, 0x36, 0x36, 0x36, 0x32, 0x33, 0x33, 0x36, 0x36, 0x36, 0x31,
0x33, 0x30, 0x33, 0x31, 0x33, 0x39, 0x35, 0x66, 0x33, 0x31, 0x33, 0x34,
0x32, 0x32, 0x33, 0x61, 0x33, 0x39, 0x33, 0x39, 0x32, 0x65, 0x33, 0x39,
0x32, 0x63, 0x32, 0x32, 0x36, 0x33, 0x36, 0x65, 0x33, 0x38, 0x33, 0x36,
0x36, 0x36, 0x36, 0x32, 0x33, 0x33, 0x36, 0x36, 0x36, 0x31, 0x33, 0x30,
0x33, 0x32, 0x33, 0x30, 0x35, 0x66, 0x33, 0x32, 0x33, 0x34, 0x32, 0x32,
0x33, 0x61, 0x32, 0x32, 0x37, 0x33, 0x36, 0x38, 0x36, 0x31, 0x33, 0x32,
0x33, 0x35, 0x33, 0x36, 0x33, 0x61, 0x33, 0x31, 0x33, 0x37, 0x36, 0x31,
0x33, 0x37, 0x36, 0x34, 0x36, 0x32, 0x33, 0x35, 0x36, 0x32, 0x33, 0x30,
0x36, 0x33, 0x36, 0x32, 0x36, 0x34, 0x33, 0x32, 0x36, 0x36, 0x36, 0x36,
0x33, 0x37, 0x33, 0x39, 0x33, 0x32, 0x36, 0x35, 0x33, 0x35, 0x33, 0x33,
0x33, 0x39, 0x33, 0x30, 0x33, 0x35, 0x33, 0x36, 0x36, 0x33, 0x33, 0x33,
0x33, 0x31, 0x36, 0x34, 0x33, 0x35, 0x33, 0x38, 0x33, 0x37, 0x33, 0x33,
0x36, 0x34, 0x33, 0x37, 0x36, 0x33, 0x36, 0x36, 0x33, 0x30, 0x36, 0x35,
0x36, 0x32, 0x33, 0x32, 0x33, 0x30, 0x33, 0x39, 0x36, 0x35, 0x33, 0x33,
0x33, 0x37, 0x33, 0x37, 0x33, 0x35, 0x33, 0x39, 0x36, 0x33, 0x33, 0x31,
0x33, 0x39, 0x36, 0x35, 0x33, 0x34, 0x33, 0x39, 0x33, 0x36, 0x33, 0x36,
0x36, 0x33, 0x33, 0x37, 0x36, 0x31, 0x33, 0x38, 0x33, 0x32, 0x36, 0x32,
0x36, 0x32, 0x32, 0x32, 0x32, 0x63, 0x32, 0x32, 0x36, 0x33, 0x36, 0x65,
0x33, 0x38, 0x33, 0x36, 0x36, 0x36, 0x36, 0x32, 0x33, 0x33, 0x36, 0x36,
0x36, 0x31, 0x33, 0x30, 0x33, 0x32, 0x33, 0x31, 0x35, 0x66, 0x33, 0x32,
0x33, 0x34, 0x32, 0x32, 0x33, 0x61, 0x32, 0x32, 0x37, 0x33, 0x36, 0x38,
0x36, 0x31, 0x33, 0x32, 0x33, 0x35, 0x33, 0x36, 0x33, 0x61, 0x33, 0x34,
0x33, 0x37, 0x33, 0x34, 0x33, 0x31, 0x36, 0x31, 0x33, 0x37, 0x33, 0x34,
0x33, 0x30, 0x33, 0x33, 0x33, 0x32, 0x33, 0x36, 0x36, 0x31, 0x33, 0x37,
0x33, 0x33, 0x33, 0x34, 0x33, 0x37, 0x33, 0x35, 0x33, 0x34, 0x33, 0x39,
0x36, 0x35, 0x36, 0x32, 0x36, 0x32, 0x36, 0x36, 0x36, 0x33, 0x33, 0x30,
0x33, 0x35, 0x33, 0x36, 0x33, 0x39, 0x36, 0x32, 0x36, 0x34, 0x33, 0x39,
0x36, 0x35, 0x33, 0x32, 0x33, 0x30, 0x36, 0x34, 0x36, 0x36, 0x33, 0x31,
0x33, 0x39, 0x36, 0x31, 0x33, 0x36, 0x36, 0x36, 0x36, 0x32, 0x33, 0x39,
0x36, 0x35, 0x33, 0x39, 0x33, 0x35, 0x33, 0x33, 0x36, 0x33, 0x36, 0x33,
0x36, 0x36, 0x33, 0x38, 0x33, 0x32, 0x33, 0x39, 0x33, 0x34, 0x36, 0x31,
0x36, 0x36, 0x36, 0x32, 0x33, 0x37, 0x36, 0x33, 0x33, 0x37, 0x36, 0x31,
0x33, 0x30, 0x33, 0x32, 0x33, 0x32, 0x32, 0x32, 0x32, 0x63, 0x32, 0x32,
0x36, 0x33, 0x36, 0x65, 0x33, 0x38, 0x33, 0x36, 0x36, 0x36, 0x36, 0x32,
0x33, 0x33, 0x36, 0x36, 0x36, 0x31, 0x33, 0x30, 0x33, 0x32, 0x33, 0x32,
0x35, 0x66, 0x33, 0x32, 0x33, 0x34, 0x32, 0x32, 0x33, 0x61, 0x32, 0x32,
0x37, 0x33, 0x36, 0x38, 0x36, 0x31, 0x33, 0x32, 0x33, 0x35, 0x33, 0x36,
0x33, 0x61, 0x33, 0x36, 0x33, 0x39, 0x36, 0x31, 0x33, 0x31, 0x33, 0x39,
0x33, 0x32, 0x36, 0x34, 0x33, 0x37, 0x33, 0x36, 0x33, 0x35, 0x33, 0x34,
0x33, 0x38, 0x33, 0x35, 0x33, 0x31, 0x33, 0x32, 0x36, 0x31, 0x33, 0x32,
0x33, 0x36, 0x33, 0x32, 0x33, 0x34, 0x36, 0x33, 0x36, 0x34, 0x33, 0x32,
0x36, 0x33, 0x33, 0x36, 0x33, 0x32, 0x36, 0x35, 0x33, 0x37, 0x33, 0x35,
0x33, 0x39, 0x33, 0x32, 0x36, 0x33, 0x36, 0x34, 0x36, 0x36, 0x33, 0x36,
0x36, 0x31, 0x36, 0x34, 0x36, 0x35, 0x33, 0x34, 0x36, 0x32, 0x33, 0x39,
0x36, 0x31, 0x33, 0x33, 0x33, 0x38, 0x33, 0x34, 0x36, 0x35, 0x33, 0x37,
0x33, 0x31, 0x33, 0x30, 0x33, 0x31, 0x33, 0x38, 0x36, 0x34, 0x33, 0x38,
0x33, 0x36, 0x33, 0x35, 0x33, 0x30, 0x33, 0x31, 0x33, 0x32, 0x33, 0x36,
0x33, 0x39, 0x33, 0x32, 0x36, 0x34, 0x36, 0x31, 0x36, 0x34, 0x32, 0x32,
0x32, 0x63, 0x32, 0x32, 0x36, 0x33, 0x36, 0x65, 0x33, 0x38, 0x33, 0x36,
0x36, 0x36, 0x36, 0x32, 0x33, 0x33, 0x36, 0x36, 0x36, 0x31, 0x33, 0x30,
0x33, 0x32, 0x33, 0x33, 0x35, 0x66, 0x33, 0x32, 0x32, 0x32, 0x33, 0x61,
0x33, 0x34, 0x33, 0x32, 0x32, 0x63, 0x32, 0x32, 0x36, 0x33, 0x36, 0x65,
0x33, 0x38, 0x33, 0x36, 0x36, 0x36, 0x36, 0x32, 0x33, 0x33, 0x36, 0x36,
0x36, 0x31, 0x33, 0x30, 0x33, 0x32, 0x33, 0x34, 0x35, 0x66, 0x33, 0x31,
0x33, 0x34, 0x32, 0x32, 0x33, 0x61, 0x33, 0x39, 0x33, 0x39, 0x32, 0x65,
0x33, 0x39, 0x32, 0x63, 0x32, 0x32, 0x36, 0x33, 0x36, 0x65, 0x33, 0x38,
0x33, 0x36, 0x36, 0x36, 0x36, 0x32, 0x33, 0x33, 0x36, 0x36, 0x36, 0x31,
0x33, 0x30, 0x33, 0x32, 0x33, 0x35, 0x35, 0x66, 0x33, 0x32, 0x33, 0x30,
0x32, 0x32, 0x33, 0x61, 0x37, 0x62, 0x32, 0x32, 0x35, 0x36, 0x36, 0x31,
0x36, 0x63, 0x37, 0x35, 0x36, 0x35, 0x32, 0x32, 0x33, 0x61, 0x32, 0x32,
0x37, 0x34, 0x36, 0x35, 0x37, 0x33, 0x37, 0x34, 0x32, 0x64, 0x37, 0x33,
0x36, 0x35, 0x36, 0x33, 0x37, 0x35, 0x37, 0x32, 0x36, 0x35, 0x32, 0x64,
0x36, 0x39, 0x36, 0x65, 0x37, 0x34, 0x36, 0x35, 0x37, 0x32, 0x36, 0x36,
0x36, 0x31, 0x36, 0x33, 0x36, 0x35, 0x32, 0x64, 0x37, 0x36, 0x36, 0x31,
0x36, 0x63, 0x37, 0x35, 0x36, 0x35, 0x32, 0x32, 0x37, 0x64, 0x37, 0x64,
0x1, 0xfe, 0x8, 0x9c, 0x7b, 0x22, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74,
0x22, 0x3a, 0x22, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x2d, 0x76,
0x61, 0x6c, 0x75, 0x65, 0x22, 0x2c, 0x22, 0x42, 0x6c, 0x61, 0x6e, 0x6b,
0x22, 0x3a, 0x22, 0x62, 0x6c, 0x61, 0x6e, 0x6b, 0x2d, 0x76, 0x61, 0x6c,
0x75, 0x65, 0x22, 0x2c, 0x22, 0x6e, 0x61, 0x6d, 0x65, 0x64, 0x22, 0x3a,
0x22, 0x6e, 0x61, 0x6d, 0x65, 0x64, 0x2d, 0x76, 0x61, 0x6c, 0x75, 0x65,
0x22, 0x2c, 0x22, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x3a, 0x22, 0x22,
0x2c, 0x22, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x22, 0x3a, 0x34, 0x32,
0x2c, 0x22, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x22, 0x3a, 0x39, 0x39, 0x2e,
0x39, 0x2c, 0x22, 0x41, 0x72, 0x72, 0x61, 0x79, 0x22, 0x3a, 0x5b, 0x22,
0x61, 0x72, 0x72, 0x61, 0x79, 0x2d, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67,
0x22, 0x5d, 0x2c, 0x22, 0x42, 0x6f, 0x6f, 0x6c, 0x22, 0x3a, 0x74, 0x72,
0x75, 0x65, 0x2c, 0x22, 0x69, 0x6e, 0x74, 0x33, 0x32, 0x22, 0x3a, 0x34,
0x32, 0x2c, 0x22, 0x4d, 0x61, 0x70, 0x22, 0x3a, 0x7b, 0x22, 0x73, 0x74,
0x72, 0x69, 0x6e, 0x67, 0x2d, 0x6d, 0x61, 0x70, 0x2d, 0x6b, 0x65, 0x79,
0x22, 0x3a, 0x22, 0x6d, 0x61, 0x70, 0x2d, 0x73, 0x74, 0x72, 0x69, 0x6e,
0x67, 0x22, 0x7d, 0x2c, 0x22, 0x45, 0x6d, 0x62, 0x65, 0x64, 0x4f, 0x62,
0x6a, 0x65, 0x63, 0x74, 0x22, 0x3a, 0x7b, 0x22, 0x54, 0x61, 0x67, 0x44,
0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x22, 0x3a, 0x22, 0x64, 0x65, 0x66,
0x61, 0x75, 0x6c, 0x74, 0x2d, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x2c,
0x22, 0x54, 0x61, 0x67, 0x42, 0x6c, 0x61, 0x6e, 0x6b, 0x22, 0x3a, 0x22,
0x62, 0x6c, 0x61, 0x6e, 0x6b, 0x2d, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22,
0x2c, 0x22, 0x74, 0x61, 0x67, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x64, 0x22,
0x3a, 0x22, 0x6e, 0x61, 0x6d, 0x65, 0x64, 0x2d, 0x76, 0x61, 0x6c, 0x75,
0x65, 0x22, 0x2c, 0x22, 0x74, 0x61, 0x67, 0x5f, 0x65, 0x6d, 0x70, 0x74,
0x79, 0x22, 0x3a, 0x22, 0x22, 0x2c, 0x22, 0x74, 0x61, 0x67, 0x5f, 0x6e,
0x75, 0x6d, 0x62, 0x65, 0x72, 0x22, 0x3a, 0x34, 0x32, 0x2c, 0x22, 0x74,
0x61, 0x67, 0x5f, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x22, 0x3a, 0x39, 0x39,
0x2e, 0x39, 0x7d, 0x2c, 0x22, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x4d,
0x61, 0x70, 0x22, 0x3a, 0x7b, 0x22, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74,
0x2d, 0x6d, 0x61, 0x70, 0x2d, 0x6b, 0x65, 0x79, 0x22, 0x3a, 0x7b, 0x22,
0x54, 0x61, 0x67, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x22, 0x3a,
0x22, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x2d, 0x76, 0x61, 0x6c,
0x75, 0x65, 0x22, 0x2c, 0x22, 0x54, 0x61, 0x67, 0x42, 0x6c, 0x61, 0x6e,
0x6b, 0x22, 0x3a, 0x22, 0x62, 0x6c, 0x61, 0x6e, 0x6b, 0x2d, 0x76, 0x61,
0x6c, 0x75, 0x65, 0x22, 0x2c, 0x22, 0x74, 0x61, 0x67, 0x5f, 0x6e, 0x61,
0x6d, 0x65, 0x64, 0x22, 0x3a, 0x22, 0x6e, 0x61, 0x6d, 0x65, 0x64, 0x2d,
0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x2c, 0x22, 0x74, 0x61, 0x67, 0x5f,
0x65, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x3a, 0x22, 0x22, 0x2c, 0x22, 0x74,
0x61, 0x67, 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x22, 0x3a, 0x34,
0x32, 0x2c, 0x22, 0x74, 0x61, 0x67, 0x5f, 0x66, 0x6c, 0x6f, 0x61, 0x74,
0x22, 0x3a, 0x39, 0x39, 0x2e, 0x39, 0x7d, 0x7d, 0x2c, 0x22, 0x50, 0x74,
0x72, 0x22, 0x3a, 0x7b, 0x22, 0x54, 0x61, 0x67, 0x44, 0x65, 0x66, 0x61,
0x75, 0x6c, 0x74, 0x22, 0x3a, 0x22, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c,
0x74, 0x2d, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x2c, 0x22, 0x54, 0x61,
0x67, 0x42, 0x6c, 0x61, 0x6e, 0x6b, 0x22, 0x3a, 0x22, 0x62, 0x6c, 0x61,
0x6e, 0x6b, 0x2d, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x2c, 0x22, 0x74,
0x61, 0x67, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x64, 0x22, 0x3a, 0x22, 0x6e,
0x61, 0x6d, 0x65, 0x64, 0x2d, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x2c,
0x22, 0x74, 0x61, 0x67, 0x5f, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x3a,
0x22, 0x22, 0x2c, 0x22, 0x74, 0x61, 0x67, 0x5f, 0x6e, 0x75, 0x6d, 0x62,
0x65, 0x72, 0x22, 0x3a, 0x34, 0x32, 0x2c, 0x22, 0x74, 0x61, 0x67, 0x5f,
0x66, 0x6c, 0x6f, 0x61, 0x74, 0x22, 0x3a, 0x39, 0x39, 0x2e, 0x39, 0x7d,
0x2c, 0x22, 0x4e, 0x65, 0x73, 0x74, 0x65, 0x64, 0x22, 0x3a, 0x7b, 0x22,
0x54, 0x61, 0x67, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x22, 0x3a,
0x22, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x2d, 0x76, 0x61, 0x6c,
0x75, 0x65, 0x22, 0x2c, 0x22, 0x54, 0x61, 0x67, 0x42, 0x6c, 0x61, 0x6e,
0x6b, 0x22, 0x3a, 0x22, 0x62, 0x6c, 0x61, 0x6e, 0x6b, 0x2d, 0x76, 0x61,
0x6c, 0x75, 0x65, 0x22, 0x2c, 0x22, 0x74, 0x61, 0x67, 0x5f, 0x6e, 0x61,
0x6d, 0x65, 0x64, 0x22, 0x3a, 0x22, 0x6e, 0x61, 0x6d, 0x65, 0x64, 0x2d,
0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x2c, 0x22, 0x74, 0x61, 0x67, 0x5f,
0x65, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x3a, 0x22, 0x22, 0x2c, 0x22, 0x74,
0x61, 0x67, 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x22, 0x3a, 0x34,
0x32, 0x2c, 0x22, 0x74, 0x61, 0x67, 0x5f, 0x66, 0x6c, 0x6f, 0x61, 0x74,
0x22, 0x3a, 0x39, 0x39, 0x2e, 0x39, 0x7d, 0x2c, 0x22, 0x54, 0x61, 0x67,
0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x22, 0x3a, 0x22, 0x64, 0x65,
0x66, 0x61, 0x75, 0x6c, 0x74, 0x2d, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22,
0x2c, 0x22, 0x54, 0x61, 0x67, 0x42, 0x6c, 0x61, 0x6e, 0x6b, 0x22, 0x3a,
0x22, 0x62, 0x6c, 0x61, 0x6e, 0x6b, 0x2d, 0x76, 0x61, 0x6c, 0x75, 0x65,
0x22, 0x2c, 0x22, 0x74, 0x61, 0x67, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x64,
0x22, 0x3a, 0x22, 0x6e, 0x61, 0x6d, 0x65, 0x64, 0x2d, 0x76, 0x61, 0x6c,
0x75, 0x65, 0x22, 0x2c, 0x22, 0x74, 0x61, 0x67, 0x5f, 0x65, 0x6d, 0x70,
0x74, 0x79, 0x22, 0x3a, 0x22, 0x22, 0x2c, 0x22, 0x74, 0x61, 0x67, 0x5f,
0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x22, 0x3a, 0x34, 0x32, 0x2c, 0x22,
0x74, 0x61, 0x67, 0x5f, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x22, 0x3a, 0x39,
0x39, 0x2e, 0x39, 0x2c, 0x22, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x44,
0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x22, 0x3a, 0x22, 0x63, 0x6e, 0x38,
0x36, 0x66, 0x62, 0x33, 0x66, 0x61, 0x30, 0x30, 0x5f, 0x32, 0x34, 0x22,
0x2c, 0x22, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x42, 0x6c, 0x61, 0x6e,
0x6b, 0x22, 0x3a, 0x22, 0x63, 0x6e, 0x38, 0x36, 0x66, 0x62, 0x33, 0x66,
0x61, 0x30, 0x31, 0x5f, 0x32, 0x34, 0x22, 0x2c, 0x22, 0x6e, 0x61, 0x6d,
0x65, 0x64, 0x5f, 0x73, 0x65, 0x63, 0x75, 0x72, 0x65, 0x22, 0x3a, 0x22,
0x63, 0x6e, 0x38, 0x36, 0x66, 0x62, 0x33, 0x66, 0x61, 0x30, 0x32, 0x5f,
0x32, 0x34, 0x22, 0x2c, 0x22, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x5f, 0x73,
0x65, 0x63, 0x75, 0x72, 0x65, 0x22, 0x3a, 0x22, 0x63, 0x6e, 0x38, 0x36,
0x66, 0x62, 0x33, 0x66, 0x61, 0x30, 0x33, 0x5f, 0x32, 0x34, 0x22, 0x2c,
0x22, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x5f, 0x73, 0x65, 0x63, 0x75,
0x72, 0x65, 0x22, 0x3a, 0x22, 0x63, 0x6e, 0x38, 0x36, 0x66, 0x62, 0x33,
0x66, 0x61, 0x30, 0x34, 0x5f, 0x32, 0x22, 0x2c, 0x22, 0x73, 0x65, 0x63,
0x75, 0x72, 0x65, 0x5f, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x22, 0x3a, 0x22,
0x63, 0x6e, 0x38, 0x36, 0x66, 0x62, 0x33, 0x66, 0x61, 0x30, 0x35, 0x5f,
0x31, 0x34, 0x22, 0x2c, 0x22, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x41,
0x72, 0x72, 0x61, 0x79, 0x22, 0x3a, 0x22, 0x63, 0x6e, 0x38, 0x36, 0x66,
0x62, 0x33, 0x66, 0x61, 0x30, 0x36, 0x5f, 0x32, 0x33, 0x22, 0x2c, 0x22,
0x62, 0x6f, 0x6f, 0x6c, 0x5f, 0x73, 0x65, 0x63, 0x75, 0x72, 0x65, 0x22,
0x3a, 0x22, 0x63, 0x6e, 0x38, 0x36, 0x66, 0x62, 0x33, 0x66, 0x61, 0x30,
0x37, 0x5f, 0x31, 0x22, 0x2c, 0x22, 0x69, 0x6e, 0x74, 0x33, 0x32, 0x5f,
0x73, 0x65, 0x63, 0x75, 0x72, 0x65, 0x22, 0x3a, 0x22, 0x63, 0x6e, 0x38,
0x36, 0x66, 0x62, 0x33, 0x66, 0x61, 0x30, 0x38, 0x5f, 0x35, 0x22, 0x2c,
0x22, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x4d, 0x61, 0x70, 0x22, 0x3a,
0x22, 0x63, 0x6e, 0x38, 0x36, 0x66, 0x62, 0x33, 0x66, 0x61, 0x30, 0x39,
0x5f, 0x32, 0x31, 0x22, 0x2c, 0x22, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65,
0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x22, 0x3a, 0x22, 0x63, 0x6e, 0x38,
0x36, 0x66, 0x62, 0x33, 0x66, 0x61, 0x30, 0x31, 0x30, 0x5f, 0x32, 0x35,
0x22, 0x2c, 0x22, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x4f, 0x62, 0x6a,
0x65, 0x63, 0x74, 0x4d, 0x61, 0x70, 0x22, 0x3a, 0x22, 0x63, 0x6e, 0x38,
0x36, 0x66, 0x62, 0x33, 0x66, 0x61, 0x30, 0x31, 0x31, 0x5f, 0x32, 0x31,
0x22, 0x2c, 0x22, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x50, 0x74, 0x72,
0x22, 0x3a, 0x22, 0x63, 0x6e, 0x38, 0x36, 0x66, 0x62, 0x33, 0x66, 0x61,
0x30, 0x31, 0x32, 0x5f, 0x32, 0x32, 0x22, 0x2c, 0x22, 0x53, 0x65, 0x63,
0x75, 0x72, 0x65, 0x4e, 0x65, 0x73, 0x74, 0x65, 0x64, 0x22, 0x3a, 0x22,
0x63, 0x6e, 0x38, 0x36, 0x66, 0x62, 0x33, 0x66, 0x61, 0x30, 0x31, 0x33,
0x5f, 0x32, 0x35, 0x22, 0x2c, 0x22, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x22,
0x3a, 0x22, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2d, 0x76, 0x61, 0x6c,
0x75, 0x65, 0x22, 0x2c, 0x22, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x3a,
0x22, 0x22, 0x2c, 0x22, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x22, 0x3a,
0x34, 0x32, 0x2c, 0x22, 0x46, 0x6c, 0x6f, 0x61, 0x74, 0x22, 0x3a, 0x39,
0x39, 0x2e, 0x39, 0x2c, 0x22, 0x53, 0x70, 0x61, 0x72, 0x73, 0x65, 0x48,
0x61, 0x73, 0x68, 0x22, 0x3a, 0x7b, 0x22, 0x44, 0x65, 0x66, 0x61, 0x75,
0x6c, 0x74, 0x22, 0x3a, 0x22, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74,
0x2d, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x2c, 0x22, 0x42, 0x6c, 0x61,
0x6e, 0x6b, 0x22, 0x3a, 0x22, 0x62, 0x6c, 0x61, 0x6e, 0x6b, 0x2d, 0x76,
0x61, 0x6c, 0x75, 0x65, 0x22, 0x2c, 0x22, 0x6e, 0x61, 0x6d, 0x65, 0x64,
0x22, 0x3a, 0x22, 0x6e, 0x61, 0x6d, 0x65, 0x64, 0x2d, 0x76, 0x61, 0x6c,
0x75, 0x65, 0x22, 0x2c, 0x22, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x3a,
0x22, 0x22, 0x2c, 0x22, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x22, 0x3a,
0x34, 0x32, 0x2c, 0x22, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x22, 0x3a, 0x39,
0x39, 0x2e, 0x39, 0x2c, 0x22, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x44,
0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x22, 0x3a, 0x22, 0x63, 0x6e, 0x38,
0x36, 0x66, 0x62, 0x33, 0x66, 0x61, 0x30, 0x31, 0x34, 0x5f, 0x32, 0x34,
0x22, 0x2c, 0x22, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x42, 0x6c, 0x61,
0x6e, 0x6b, 0x22, 0x3a, 0x22, 0x63, 0x6e, 0x38, 0x36, 0x66, 0x62, 0x33,
0x66, 0x61, 0x30, 0x31, 0x35, 0x5f, 0x32, 0x34, 0x22, 0x2c, 0x22, 0x6e,
0x61, 0x6d, 0x65, 0x64, 0x5f, 0x73, 0x65, 0x63, 0x75, 0x72, 0x65, 0x22,
0x3a, 0x22, 0x63, 0x6e, 0x38, 0x36, 0x66, 0x62, 0x33, 0x66, 0x61, 0x30,
0x31, 0x36, 0x5f, 0x32, 0x34, 0x22, 0x2c, 0x22, 0x65, 0x6d, 0x70, 0x74,
0x79, 0x5f, 0x73, 0x65, 0x63, 0x75, 0x72, 0x65, 0x22, 0x3a, 0x22, 0x63,
0x6e, 0x38, 0x36, 0x66, 0x62, 0x33, 0x66, 0x61, 0x30, 0x31, 0x37, 0x5f,
0x32, 0x34, 0x22, 0x2c, 0x22, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x5f,
0x73, 0x65, 0x63, 0x75, 0x72, 0x65, 0x22, 0x3a, 0x22, 0x63, 0x6e, 0x38,
0x36, 0x66, 0x62, 0x33, 0x66, 0x61, 0x30, 0x31, 0x38, 0x5f, 0x32, 0x22,
0x2c, 0x22, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x5f, 0x73, 0x65, 0x63, 0x75,
0x72, 0x65, 0x22, 0x3a, 0x22, 0x63, 0x6e, 0x38, 0x36, 0x66, 0x62, 0x33,
0x66, 0x61, 0x30, 0x31, 0x39, 0x5f, 0x31, 0x34, 0x22, 0x2c, 0x22, 0x48,
0x61, 0x73, 0x68, 0x42, 0x6c, 0x61, 0x6e, 0x6b, 0x22, 0x3a, 0x22, 0x73,
0x68, 0x61, 0x32, 0x35, 0x36, 0x3a, 0x65, 0x30, 0x35, 0x66, 0x35, 0x37,
0x63, 0x31, 0x36, 0x36, 0x62, 0x65, 0x66, 0x30, 0x32, 0x35, 0x32, 0x32,
0x65, 0x36, 0x31, 0x31, 0x31, 0x33, 0x62, 0x37, 0x63, 0x64, 0x30, 0x35,
0x63, 0x63, 0x62, 0x37, 0x62, 0x65, 0x63, 0x33, 0x33, 0x31, 0x39, 0x64,
0x39, 0x37, 0x34, 0x38, 0x30, 0x39, 0x63, 0x62, 0x38, 0x33, 0x66, 0x30,
0x31, 0x35, 0x65, 0x35, 0x66, 0x30, 0x37, 0x66, 0x64, 0x35, 0x22, 0x2c,
0x22, 0x6e, 0x61, 0x6d, 0x65, 0x64, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x22,
0x3a, 0x22, 0x73, 0x68, 0x61, 0x32, 0x35, 0x36, 0x3a, 0x64, 0x65, 0x61,
0x35, 0x63, 0x66, 0x38, 0x63, 0x37, 0x64, 0x33, 0x66, 0x61, 0x35, 0x63,
0x32, 0x36, 0x36, 0x62, 0x32, 0x62, 0x66, 0x33, 0x61, 0x62, 0x66, 0x62,
0x66, 0x36, 0x39, 0x33, 0x66, 0x37, 0x35, 0x34, 0x36, 0x64, 0x64, 0x32,
0x36, 0x34, 0x32, 0x35, 0x36, 0x39, 0x61, 0x32, 0x31, 0x32, 0x30, 0x66,
0x33, 0x35, 0x33, 0x36, 0x64, 0x34, 0x35, 0x61, 0x36, 0x62, 0x35, 0x61,
0x62, 0x22, 0x2c, 0x22, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x5f, 0x68, 0x61,
0x73, 0x68, 0x22, 0x3a, 0x22, 0x22, 0x2c, 0x22, 0x6e, 0x75, 0x6d, 0x62,
0x65, 0x72, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x22, 0x3a, 0x34, 0x32, 0x2c,
0x22, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x22,
0x3a, 0x39, 0x39, 0x2e, 0x39, 0x2c, 0x22, 0x53, 0x65, 0x63, 0x75, 0x72,
0x65, 0x48, 0x61, 0x73, 0x68, 0x42, 0x6c, 0x61, 0x6e, 0x6b, 0x22, 0x3a,
0x22, 0x63, 0x6e, 0x38, 0x36, 0x66, 0x62, 0x33, 0x66, 0x61, 0x30, 0x32,
0x30, 0x5f, 0x32, 0x34, 0x22, 0x2c, 0x22, 0x6e, 0x61, 0x6d, 0x65, 0x64,
0x5f, 0x73, 0x65, 0x63, 0x75, 0x72, 0x65, 0x5f, 0x68, 0x61, 0x73, 0x68,
0x22, 0x3a, 0x22, 0x63, 0x6e, 0x38, 0x36, 0x66, 0x62, 0x33, 0x66, 0x61,
0x30, 0x32, 0x31, 0x5f, 0x32, 0x34, 0x22, 0x2c, 0x22, 0x65, 0x6d, 0x70,
0x74, 0x79, 0x5f, 0x73, 0x65, 0x63, 0x75, 0x72, 0x65, 0x5f, 0x68, 0x61,
0x73, 0x68, 0x22, 0x3a, 0x22, 0x63, 0x6e, 0x38, 0x36, 0x66, 0x62, 0x33,
0x66, 0x61, 0x30, 0x32, 0x32, 0x5f, 0x32, 0x34, 0x22, 0x2c, 0x22, 0x6e,
0x75, 0x6d, 0x62, 0x65, 0x72, 0x5f, 0x73, 0x65, 0x63, 0x75, 0x72, 0x65,
0x5f, 0x68, 0x61, 0x73, 0x68, 0x22, 0x3a, 0x22, 0x63, 0x6e, 0x38, 0x36,
0x66, 0x62, 0x33, 0x66, 0x61, 0x30, 0x32, 0x33, 0x5f, 0x32, 0x22, 0x2c,
0x22, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x5f, 0x73, 0x65, 0x63, 0x75, 0x72,
0x65, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x22, 0x3a, 0x22, 0x63, 0x6e, 0x38,
0x36, 0x66, 0x62, 0x33, 0x66, 0x61, 0x30, 0x32, 0x34, 0x5f, 0x31, 0x34,
0x22, 0x7d, 0x2c, 0x22, 0x49, 0x46, 0x61, 0x63, 0x65, 0x54, 0x65, 0x73,
0x74, 0x22, 0x3a, 0x7b, 0x22, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x3a,
0x22, 0x74, 0x65, 0x73, 0x74, 0x2d, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x66,
0x61, 0x63, 0x65, 0x2d, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x7d, 0x2c,
0x22, 0x53, 0x49, 0x22, 0x3a, 0x22, 0x63, 0x6e, 0x38, 0x36, 0x66, 0x62,
0x33, 0x66, 0x61, 0x30, 0x32, 0x35, 0x5f, 0x32, 0x30, 0x22, 0x7d, 0x0}
var allComp = []byte{0x6a, 0xff, 0x81, 0x3, 0x1, 0x1, 0x7, 0x50, 0x61,
0x63, 0x6b, 0x61, 0x67, 0x65, 0x1, 0xff, 0x82, 0x0, 0x1, 0x7, 0x1, 0x7,
0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x1, 0xc, 0x0, 0x1, 0x6, 0x46,
0x6f, 0x72, 0x6d, 0x61, 0x74, 0x1, 0xc, 0x0, 0x1, 0xa, 0x43, 0x6f, 0x6d,
0x70, 0x72, 0x65, 0x73, 0x73, 0x65, 0x64, 0x1, 0x2, 0x0, 0x1, 0x9, 0x45,
0x6e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x49, 0x44, 0x1, 0xc, 0x0, 0x1, 0x5,
0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x1, 0xc, 0x0, 0x1, 0x6, 0x43, 0x69, 0x70,
0x68, 0x65, 0x72, 0x1, 0xa, 0x0, 0x1, 0x7, 0x45, 0x6e, 0x63, 0x6f, 0x64,
0x65, 0x64, 0x1, 0xa, 0x0, 0x0, 0x0, 0xfe, 0x6, 0x56, 0xff, 0x82, 0x1, 0x5,
0x30, 0x2e, 0x30, 0x2e, 0x31, 0x1, 0x6, 0x73, 0x70, 0x61, 0x72, 0x73, 0x65,
0x1, 0x1, 0x1, 0x8, 0x38, 0x36, 0x66, 0x62, 0x33, 0x66, 0x61, 0x30, 0x1,
0xa, 0x63, 0x6e, 0x38, 0x36, 0x66, 0x62, 0x33, 0x66, 0x61, 0x30, 0x1, 0xfe,
0x3, 0xa0, 0x32, 0x38, 0x62, 0x35, 0x32, 0x66, 0x66, 0x64, 0x36, 0x34,
0x39, 0x61, 0x30, 0x34, 0x31, 0x35, 0x30, 0x65, 0x30, 0x30, 0x65, 0x32,
0x39, 0x62, 0x34, 0x64, 0x31, 0x66, 0x35, 0x30, 0x36, 0x39, 0x65, 0x61,
0x38, 0x38, 0x38, 0x30, 0x37, 0x65, 0x37, 0x66, 0x34, 0x37, 0x66, 0x65,
0x66, 0x38, 0x39, 0x38, 0x33, 0x32, 0x61, 0x61, 0x35, 0x61, 0x64, 0x39,
0x35, 0x35, 0x64, 0x37, 0x34, 0x65, 0x39, 0x32, 0x33, 0x31, 0x39, 0x36,
0x37, 0x35, 0x64, 0x64, 0x35, 0x36, 0x30, 0x33, 0x36, 0x62, 0x34, 0x37,
0x33, 0x31, 0x33, 0x33, 0x33, 0x33, 0x36, 0x62, 0x30, 0x64, 0x30, 0x64,
0x66, 0x61, 0x30, 0x62, 0x65, 0x32, 0x30, 0x63, 0x63, 0x37, 0x34, 0x33,
0x39, 0x37, 0x63, 0x61, 0x63, 0x66, 0x32, 0x34, 0x65, 0x31, 0x35, 0x38,
0x66, 0x63, 0x64, 0x34, 0x35, 0x36, 0x61, 0x30, 0x38, 0x64, 0x61, 0x36,
0x38, 0x36, 0x35, 0x64, 0x34, 0x32, 0x37, 0x63, 0x61, 0x32, 0x34, 0x37,
0x36, 0x65, 0x63, 0x35, 0x36, 0x62, 0x39, 0x32, 0x36, 0x32, 0x39, 0x31,
0x61, 0x64, 0x64, 0x39, 0x66, 0x35, 0x38, 0x38, 0x31, 0x64, 0x65, 0x66,
0x38, 0x37, 0x33, 0x65, 0x62, 0x61, 0x39, 0x32, 0x31, 0x31, 0x37, 0x32,
0x61, 0x37, 0x66, 0x35, 0x61, 0x63, 0x32, 0x30, 0x34, 0x37, 0x38, 0x38,
0x35, 0x38, 0x62, 0x31, 0x66, 0x33, 0x32, 0x36, 0x38, 0x36, 0x39, 0x32,
0x35, 0x61, 0x39, 0x31, 0x37, 0x62, 0x62, 0x64, 0x31, 0x35, 0x33, 0x33,
0x31, 0x62, 0x35, 0x66, 0x37, 0x30, 0x33, 0x64, 0x65, 0x39, 0x61, 0x38,
0x39, 0x39, 0x34, 0x39, 0x30, 0x38, 0x34, 0x66, 0x65, 0x61, 0x65, 0x66,
0x32, 0x37, 0x39, 0x35, 0x66, 0x37, 0x63, 0x61, 0x36, 0x38, 0x66, 0x38,
0x33, 0x62, 0x62, 0x65, 0x35, 0x36, 0x63, 0x39, 0x34, 0x30, 0x66, 0x39,
0x64, 0x65, 0x33, 0x37, 0x34, 0x61, 0x30, 0x62, 0x39, 0x64, 0x65, 0x35,
0x38, 0x61, 0x38, 0x66, 0x34, 0x31, 0x63, 0x62, 0x31, 0x37, 0x32, 0x64,
0x62, 0x34, 0x66, 0x63, 0x31, 0x65, 0x33, 0x33, 0x35, 0x66, 0x35, 0x30,
0x61, 0x39, 0x34, 0x36, 0x64, 0x66, 0x30, 0x62, 0x33, 0x62, 0x61, 0x64,
0x37, 0x35, 0x62, 0x65, 0x30, 0x64, 0x62, 0x66, 0x33, 0x37, 0x31, 0x32,
0x61, 0x65, 0x37, 0x38, 0x31, 0x36, 0x34, 0x62, 0x37, 0x31, 0x30, 0x66,
0x35, 0x34, 0x63, 0x39, 0x66, 0x34, 0x34, 0x39, 0x63, 0x66, 0x62, 0x64,
0x61, 0x34, 0x65, 0x34, 0x66, 0x38, 0x61, 0x30, 0x37, 0x39, 0x65, 0x39,
0x63, 0x35, 0x65, 0x35, 0x36, 0x62, 0x39, 0x62, 0x34, 0x61, 0x65, 0x63,
0x30, 0x38, 0x30, 0x62, 0x30, 0x65, 0x65, 0x38, 0x36, 0x35, 0x62, 0x62,
0x35, 0x36, 0x36, 0x64, 0x34, 0x32, 0x38, 0x37, 0x64, 0x39, 0x66, 0x31,
0x38, 0x34, 0x61, 0x66, 0x66, 0x30, 0x31, 0x36, 0x62, 0x36, 0x30, 0x33,
0x62, 0x61, 0x39, 0x62, 0x34, 0x65, 0x35, 0x66, 0x34, 0x30, 0x61, 0x34,
0x33, 0x62, 0x66, 0x38, 0x35, 0x34, 0x66, 0x65, 0x32, 0x35, 0x31, 0x36,
0x36, 0x64, 0x63, 0x61, 0x39, 0x38, 0x38, 0x61, 0x63, 0x34, 0x36, 0x62,
0x35, 0x33, 0x37, 0x37, 0x66, 0x30, 0x38, 0x39, 0x34, 0x38, 0x31, 0x63,
0x31, 0x31, 0x33, 0x38, 0x33, 0x38, 0x38, 0x31, 0x30, 0x32, 0x65, 0x38,
0x32, 0x66, 0x38, 0x38, 0x33, 0x33, 0x30, 0x63, 0x31, 0x63, 0x39, 0x63,
0x34, 0x30, 0x31, 0x62, 0x34, 0x64, 0x31, 0x33, 0x33, 0x62, 0x62, 0x33,
0x30, 0x64, 0x30, 0x61, 0x34, 0x66, 0x36, 0x38, 0x35, 0x33, 0x63, 0x36,
0x32, 0x33, 0x30, 0x65, 0x38, 0x35, 0x32, 0x37, 0x30, 0x65, 0x36, 0x64,
0x33, 0x34, 0x35, 0x64, 0x37, 0x36, 0x39, 0x64, 0x31, 0x63, 0x66, 0x62,
0x34, 0x35, 0x39, 0x39, 0x34, 0x64, 0x64, 0x37, 0x61, 0x64, 0x30, 0x33,
0x33, 0x64, 0x32, 0x34, 0x30, 0x39, 0x63, 0x61, 0x35, 0x63, 0x31, 0x63,
0x30, 0x63, 0x35, 0x37, 0x31, 0x34, 0x35, 0x37, 0x38, 0x33, 0x61, 0x36,
0x38, 0x37, 0x39, 0x64, 0x65, 0x61, 0x36, 0x65, 0x38, 0x61, 0x64, 0x36,
0x32, 0x35, 0x34, 0x36, 0x34, 0x64, 0x64, 0x39, 0x61, 0x31, 0x31, 0x33,
0x33, 0x66, 0x65, 0x31, 0x30, 0x39, 0x32, 0x33, 0x31, 0x32, 0x34, 0x37,
0x64, 0x65, 0x30, 0x32, 0x65, 0x38, 0x32, 0x62, 0x30, 0x62, 0x66, 0x63,
0x63, 0x35, 0x33, 0x31, 0x39, 0x31, 0x63, 0x35, 0x65, 0x35, 0x34, 0x37,
0x36, 0x64, 0x38, 0x61, 0x64, 0x38, 0x31, 0x39, 0x65, 0x33, 0x66, 0x32,
0x33, 0x63, 0x61, 0x39, 0x35, 0x38, 0x30, 0x32, 0x62, 0x35, 0x31, 0x31,
0x33, 0x33, 0x30, 0x30, 0x36, 0x39, 0x36, 0x35, 0x64, 0x32, 0x30, 0x32,
0x65, 0x34, 0x65, 0x62, 0x38, 0x30, 0x38, 0x32, 0x34, 0x32, 0x63, 0x62,
0x36, 0x33, 0x30, 0x30, 0x39, 0x30, 0x30, 0x33, 0x31, 0x63, 0x30, 0x63,
0x30, 0x30, 0x33, 0x39, 0x37, 0x65, 0x37, 0x31, 0x65, 0x30, 0x61, 0x31,
0x33, 0x30, 0x65, 0x37, 0x39, 0x30, 0x39, 0x30, 0x33, 0x33, 0x33, 0x31,
0x32, 0x63, 0x63, 0x65, 0x32, 0x35, 0x32, 0x63, 0x33, 0x32, 0x33, 0x63,
0x35, 0x38, 0x37, 0x38, 0x32, 0x38, 0x35, 0x38, 0x39, 0x63, 0x34, 0x30,
0x64, 0x34, 0x38, 0x30, 0x38, 0x31, 0x63, 0x35, 0x30, 0x61, 0x34, 0x34,
0x30, 0x64, 0x30, 0x38, 0x35, 0x38, 0x33, 0x34, 0x65, 0x63, 0x61, 0x66,
0x33, 0x38, 0x30, 0x32, 0x39, 0x31, 0x35, 0x65, 0x65, 0x61, 0x31, 0x32,
0x33, 0x65, 0x38, 0x62, 0x30, 0x32, 0x35, 0x38, 0x61, 0x34, 0x33, 0x32,
0x31, 0x39, 0x37, 0x63, 0x63, 0x61, 0x32, 0x34, 0x30, 0x30, 0x62, 0x30,
0x39, 0x32, 0x65, 0x36, 0x35, 0x65, 0x31, 0x37, 0x39, 0x33, 0x64, 0x35,
0x36, 0x36, 0x66, 0x30, 0x36, 0x33, 0x38, 0x61, 0x38, 0x38, 0x39, 0x65,
0x62, 0x37, 0x35, 0x36, 0x65, 0x37, 0x32, 0x61, 0x35, 0x36, 0x34, 0x62,
0x39, 0x65, 0x31, 0x37, 0x38, 0x63, 0x39, 0x30, 0x35, 0x65, 0x31, 0x35,
0x33, 0x32, 0x30, 0x30, 0x30, 0x38, 0x31, 0x64, 0x31, 0x65, 0x33, 0x38,
0x63, 0x62, 0x30, 0x35, 0x35, 0x31, 0x34, 0x37, 0x30, 0x63, 0x33, 0x63,
0x30, 0x63, 0x63, 0x38, 0x63, 0x30, 0x63, 0x39, 0x31, 0x64, 0x34, 0x35,
0x37, 0x31, 0x65, 0x64, 0x32, 0x36, 0x63, 0x37, 0x36, 0x64, 0x33, 0x38,
0x62, 0x62, 0x66, 0x30, 0x30, 0x31, 0x33, 0x30, 0x35, 0x63, 0x34, 0x30,
0x31, 0x35, 0x36, 0x30, 0x36, 0x63, 0x32, 0x38, 0x66, 0x61, 0x38, 0x61,
0x63, 0x32, 0x36, 0x32, 0x63, 0x35, 0x35, 0x30, 0x34, 0x66, 0x39, 0x61,
0x31, 0x39, 0x39, 0x63, 0x64, 0x64, 0x1, 0xfe, 0x2, 0x84, 0x28, 0xb5, 0x2f,
0xfd, 0x64, 0x9c, 0x7, 0xb5, 0x13, 0x0, 0xd2, 0x1d, 0x57, 0x23, 0x20, 0x6d,
0xf4, 0x23, 0x9c, 0x51, 0xdd, 0x30, 0xe0, 0x83, 0x35, 0x48, 0x51, 0x93,
0xbd, 0xa0, 0xd6, 0xfb, 0xd, 0x5e, 0x27, 0x6, 0xbc, 0xc0, 0xbb, 0xbf, 0xec,
0x8d, 0x97, 0xaa, 0x0, 0x3, 0x0, 0x6b, 0x5, 0x13, 0xaf, 0x8e, 0xee, 0x40,
0xc1, 0x6a, 0xfa, 0xe9, 0xd5, 0xfe, 0x85, 0x1a, 0x89, 0x2b, 0xfd, 0x95,
0x7e, 0xf1, 0xd3, 0xb, 0xe, 0x58, 0x4d, 0x9c, 0xb0, 0x98, 0xb4, 0x66, 0xbd,
0x5a, 0xba, 0x93, 0xcf, 0x5d, 0x42, 0x5f, 0x73, 0xad, 0xae, 0x66, 0xed,
0xcc, 0x73, 0x9d, 0xbc, 0xa6, 0xf4, 0x25, 0x4e, 0xa9, 0x8d, 0x79, 0x55,
0x56, 0xe9, 0xad, 0xca, 0x96, 0xef, 0xf5, 0x74, 0x5e, 0x1b, 0x55, 0xe6,
0x52, 0xa7, 0xf3, 0xa7, 0xc, 0x47, 0xab, 0xb2, 0xaa, 0x7b, 0xc7, 0x56,
0x6d, 0x8b, 0x9c, 0xcc, 0x5a, 0x12, 0x85, 0xa1, 0xc, 0x6b, 0x6d, 0xbe,
0x54, 0xe4, 0x9c, 0x6e, 0xd1, 0xa9, 0xc8, 0xc6, 0x18, 0x7b, 0xdf, 0xed,
0x7a, 0xd5, 0x97, 0xef, 0xb1, 0xa9, 0xb8, 0xea, 0xd6, 0xc3, 0xe7, 0xda,
0x19, 0x12, 0x43, 0x72, 0x3, 0x86, 0x2d, 0x4c, 0x61, 0x8f, 0xb9, 0x84,
0x31, 0x24, 0x37, 0x78, 0xc9, 0xfa, 0x43, 0xc1, 0xaa, 0x20, 0x81, 0x0,
0x32, 0xb4, 0xee, 0xd8, 0xac, 0xe3, 0x62, 0xac, 0x23, 0xe3, 0xe2, 0xc2,
0x2c, 0x2a, 0x4e, 0x14, 0x49, 0x56, 0xdc, 0x3a, 0x3e, 0x16, 0x1d, 0x2b,
0xee, 0x98, 0xb4, 0x96, 0x22, 0xe3, 0xa4, 0xe3, 0x5a, 0x5e, 0x9b, 0xac,
0x6f, 0x81, 0xe6, 0x5b, 0xc8, 0x7c, 0x5, 0x2b, 0xfa, 0x57, 0xfa, 0x4, 0x7b,
0x17, 0x58, 0x4d, 0x11, 0xab, 0x29, 0x36, 0x7e, 0x63, 0xe3, 0x37, 0x36,
0x7e, 0x1b, 0x5f, 0x34, 0xbe, 0xe0, 0x8, 0xc8, 0x77, 0xfe, 0x83, 0x54,
0xa0, 0x2f, 0x1f, 0x2, 0x56, 0xd3, 0xc3, 0x1f, 0x86, 0x1c, 0x79, 0x47,
0xe2, 0xe1, 0x4f, 0x82, 0x23, 0x20, 0x3f, 0x1c, 0xa1, 0xdb, 0x85, 0x1a,
0xac, 0x1e, 0x62, 0x2f, 0x64, 0x27, 0x8a, 0x4, 0x58, 0x39, 0x70, 0x3, 0x35,
0xec, 0x95, 0x12, 0x11, 0xb7, 0xd6, 0xb9, 0x1, 0x72, 0xc4, 0xad, 0xd, 0x60,
0xc5, 0x0, 0x30, 0xc, 0xe4, 0xeb, 0x91, 0x53, 0x59, 0x75, 0x2, 0xd9, 0xbe,
0x7c, 0x20, 0x10, 0x2b, 0x66, 0xc8, 0x91, 0xfb, 0x78, 0x1e, 0x32, 0xfd,
0xc7, 0x21, 0x7, 0xe4, 0x8e, 0x64, 0xc8, 0xe, 0xc8, 0x1d, 0x1, 0x56, 0xfc,
0x10, 0xc7, 0x21, 0x12, 0x94, 0x21, 0x5f, 0x7, 0xe2, 0xf5, 0x7, 0x38, 0x22,
0x6f, 0x20, 0x40, 0x42, 0x0, 0x86, 0x6c, 0xf8, 0x3a, 0x52, 0x73, 0xa9,
0x53, 0x93, 0xf1, 0x0, 0xcc, 0x23, 0x2c, 0x8a, 0x4, 0x34, 0x9f, 0x4f, 0x71,
0x12, 0x95, 0x70, 0x80, 0xe1, 0xd2, 0x24, 0x1, 0x12, 0x8f, 0xa6, 0x68, 0x2,
0xc0, 0xc, 0x2c, 0xe2, 0x11, 0x10, 0xb7, 0x48, 0xb, 0x87, 0xd4, 0xf, 0x98,
0x59, 0xe, 0x18, 0xcf, 0xe0, 0x72, 0x2a, 0x40, 0x9e, 0x21, 0xe5, 0x14,
0xb8, 0xc, 0xc0, 0x4, 0x14, 0xd3, 0x8c, 0xec, 0xf2, 0xcb, 0x8e, 0x6e, 0x25,
0x5a, 0x8c, 0x90, 0x3c, 0xd, 0xb8, 0x1, 0x34, 0xb4, 0x5a, 0x0, 0x9e, 0x2e,
0xbc, 0x43, 0x1b, 0x48, 0xc, 0xed, 0x78, 0x5, 0xd2, 0x80, 0x75, 0x5e, 0x31,
0x1e, 0x2e, 0x1d, 0x1a, 0x9c, 0x3, 0x3b, 0xba, 0xb1, 0x27, 0xc, 0x88, 0x5,
0xb0, 0x9, 0x83, 0xec, 0xa3, 0x21, 0x86, 0xe1, 0xb0, 0x5b, 0x56, 0x8a,
0xc0, 0x7b, 0xd2, 0x71, 0x24, 0x97, 0x99, 0xe, 0x42, 0xe0, 0x0, 0xab, 0x8,
0xa0, 0xa0, 0x30, 0x83, 0x46, 0xb6, 0x80, 0x80, 0xbb, 0x23, 0x8f, 0xcb,
0x34, 0x20, 0xf8, 0xa6, 0x11, 0x3, 0x1, 0x88, 0x0, 0xd0, 0x42, 0xdd, 0x6,
0x53, 0x71, 0x81, 0xdf, 0x62, 0xc4, 0x14, 0xd4, 0xa7, 0x4b, 0x40, 0xa4,
0x2e, 0xc6, 0x78, 0x3b, 0x10, 0xd, 0xc1, 0xed, 0xc, 0x36, 0xc0, 0x8e, 0x4,
0x74, 0x1f, 0x8a, 0xa4, 0x50, 0xcd, 0x30, 0x60, 0x38, 0x69, 0x10, 0x83,
0x80, 0xf, 0x37, 0x7a, 0x21, 0xf9, 0xd0, 0x17, 0x5a, 0x1c, 0x9f, 0x68,
0xc7, 0xaf, 0x9, 0x40, 0x26, 0x75, 0x17, 0x5b, 0x6c, 0x1c, 0x48, 0x97,
0xc0, 0x21, 0x14, 0x14, 0xb0, 0x4c, 0x81, 0x1f, 0x75, 0x5a, 0xa0, 0x6d,
0x22, 0xf8, 0xc7, 0x6c, 0x1d, 0x1b, 0x3, 0xad, 0x21, 0x72, 0xc, 0x98, 0x2e,
0x0, 0x6b, 0x10, 0x6e, 0x14, 0x82, 0xa5, 0x32, 0x56, 0x67, 0x19, 0x0, 0x4f,
0x2d, 0x61, 0x73, 0x0, 0x1c, 0x6, 0x8a, 0xdd, 0x1f, 0x46, 0xfa, 0xd2, 0x74,
0xf8, 0x5f, 0x79, 0x5e, 0x2e, 0xee, 0x0}

View File

@ -0,0 +1,405 @@
package secure
type Both struct {
Default string
Blank string `json:""`
Ignore string `json:"-"`
Named string `json:"named"`
Empty string `json:"empty"`
Omit string `json:"omit,omitempty"`
Number int `json:"number"`
Float float64 `json:"float"`
SecureDefault string `json:",secure"`
SecureBlank string `json:",secure"`
SecureIgnore string `json:"-"`
SecureNamed string `json:"named_secure,secure"`
SecureEmpty string `json:"empty_secure,secure"`
SecureOmit string `json:"omit_secure,omitempty,secure"`
SecureNumber int `json:"number_secure,secure"`
SecureFloat float64 `json:"float_secure,secure"`
HashBlank string `json:",hash"`
HashIgnore string `json:"-"`
HashNamed string `json:"named_hash,hash"`
HashEmpty string `json:"empty_hash,hash"`
HashOmit string `json:"omit_hash,omitempty,hash"`
HashNumber int `json:"number_hash,hash"`
HashFloat float64 `json:"float_hash,hash"`
SecureHashBlank string `json:",hash,secure"`
SecureHashIgnore string `json:"-"`
SecureHashNamed string `json:"named_secure_hash,hash,secure"`
SecureHashEmpty string `json:"empty_secure_hash,hash,secure"`
SecureHashOmit string `json:"omit_secure_hash,omitempty,hash,secure"`
SecureHashNumber int `json:"number_secure_hash,hash,secure"`
SecureHashFloat float64 `json:"float_secure_hash,hash,secure"`
}
var bothObj = &Both{
Default: "default-value",
Blank: "blank-value",
Ignore: "ignore-value",
Named: "named-value",
Empty: "",
Number: 42,
Float: 99.9,
SecureDefault: "default-secure",
SecureBlank: "blank-secure",
SecureIgnore: "ignore-secure",
SecureNamed: "named-secure",
SecureEmpty: "",
SecureNumber: 42,
SecureFloat: 99.9,
HashBlank: "blank-hash",
HashIgnore: "ignore-hash",
HashNamed: "named-hash",
HashEmpty: "",
HashNumber: 42,
HashFloat: 99.9,
SecureHashBlank: "default-secure",
SecureHashIgnore: "blank-secure",
SecureHashNamed: "ignore-secure",
SecureHashEmpty: "named-secure",
SecureHashOmit: "",
SecureHashNumber: 42,
SecureHashFloat: 99.9,
}
var bothDecoded = &Both{
Default: "default-value",
Blank: "blank-value",
Named: "named-value",
Number: 42,
Float: 99.9,
SecureDefault: "default-secure",
SecureBlank: "blank-secure",
SecureNamed: "named-secure",
SecureNumber: 42,
SecureFloat: 99.9,
HashBlank: "sha256:e05f57c166bef02522e61113b7cd05ccb7bec3319d974809cb83f015e5f07fd5",
HashNamed: "sha256:dea5cf8c7d3fa5c266b2bf3abfbf693f7546dd2642569a2120f3536d45a6b5ab",
HashNumber: 42,
HashFloat: 99.9,
SecureHashBlank: "sha256:17a7db5b0cbd2ff792e539056c31d5873d7cf0eb209e37759c19e4966c7a82bb",
SecureHashNamed: "sha256:4741a740326a7347549ebbfc0569bd9e20df19a6fb9e953ccf8294afb7c7a022",
SecureHashEmpty: "sha256:69a192d76548512a2624cd2c62e7592cdf6ade4b9a384e71018d865012692dad",
SecureHashNumber: 42,
SecureHashFloat: 99.9,
}
var bothSparse = &Both{
Default: "default-value",
Blank: "blank-value",
Named: "named-value",
Number: 42,
Float: 99.9,
HashBlank: "sha256:e05f57c166bef02522e61113b7cd05ccb7bec3319d974809cb83f015e5f07fd5",
HashNamed: "sha256:dea5cf8c7d3fa5c266b2bf3abfbf693f7546dd2642569a2120f3536d45a6b5ab",
HashNumber: 42,
HashFloat: 99.9,
}
var bothMap = map[string]interface{}{
"Blank": "blank-value",
"Default": "default-value",
"HashBlank": "sha256:e05f57c166bef02522e61113b7cd05ccb7bec3319d974809cb83f015e5f07fd5",
"SecureBlank": "blank-secure",
"SecureDefault": "default-secure",
"SecureHashBlank": "sha256:17a7db5b0cbd2ff792e539056c31d5873d7cf0eb209e37759c19e4966c7a82bb",
"empty": "",
"empty_hash": "",
"empty_secure": "",
"empty_secure_hash": "sha256:69a192d76548512a2624cd2c62e7592cdf6ade4b9a384e71018d865012692dad",
"float": 99.9,
"float_hash": 99.9,
"float_secure": 99.9,
"float_secure_hash": 99.9,
"named": "named-value",
"named_hash": "sha256:dea5cf8c7d3fa5c266b2bf3abfbf693f7546dd2642569a2120f3536d45a6b5ab",
"named_secure": "named-secure",
"named_secure_hash": "sha256:4741a740326a7347549ebbfc0569bd9e20df19a6fb9e953ccf8294afb7c7a022",
"number": 42.0,
"number_hash": 42.0,
"number_secure": 42.0,
"number_secure_hash": 42.0,
}
var bothMapSparse = map[string]interface{}{
"Blank": "blank-value",
"Default": "default-value",
"HashBlank": "sha256:e05f57c166bef02522e61113b7cd05ccb7bec3319d974809cb83f015e5f07fd5",
"SecureBlank": "",
"SecureDefault": "",
"SecureHashBlank": "",
"empty": "",
"empty_hash": "",
"empty_secure": "",
"empty_secure_hash": "",
"float": 99.9,
"float_hash": 99.9,
"float_secure": 0.0,
"float_secure_hash": 0.0,
"named": "named-value",
"named_hash": "sha256:dea5cf8c7d3fa5c266b2bf3abfbf693f7546dd2642569a2120f3536d45a6b5ab",
"named_secure": "",
"named_secure_hash": "",
"number": 42.0,
"number_hash": 42.0,
"number_secure": 0.0,
"number_secure_hash": 0.0,
}
var bothEncoded = []byte(`{"Default":"default-value","Blank":"blank-value","n` +
`amed":"named-value","empty":"","number":42,"float":99.9,"SecureDefault":` +
`"cn86fb3fa00_24","SecureBlank":"cn86fb3fa01_24","named_secure":"cn86fb3f` +
`a02_24","empty_secure":"cn86fb3fa03_24","number_secure":"cn86fb3fa04_2",` +
`"float_secure":"cn86fb3fa05_14","HashBlank":"sha256:e05f57c166bef02522e6` +
`1113b7cd05ccb7bec3319d974809cb83f015e5f07fd5","named_hash":"sha256:dea5c` +
`f8c7d3fa5c266b2bf3abfbf693f7546dd2642569a2120f3536d45a6b5ab","empty_hash` +
`":"","number_hash":42,"float_hash":99.9,"SecureHashBlank":"cn86fb3fa06_2` +
`4","named_secure_hash":"cn86fb3fa07_24","empty_secure_hash":"cn86fb3fa08` +
`_24","number_secure_hash":"cn86fb3fa09_2","float_secure_hash":"cn86fb3fa` +
`010_14"}`)
var bothUnsealed = []byte(`{"Default":"default-value","Blank":"blank-value","` +
`named":"named-value","empty":"","number":42,"float":99.9,"SecureDefault"` +
`:"cn86fb3fa00_24","SecureBlank":"cn86fb3fa01_24","named_secure":"cn86fb3` +
`fa02_24","empty_secure":"cn86fb3fa03_24","number_secure":"cn86fb3fa04_2"` +
`,"float_secure":"cn86fb3fa05_14","HashBlank":"sha256:e05f57c166bef02522e` +
`61113b7cd05ccb7bec3319d974809cb83f015e5f07fd5","named_hash":"sha256:dea5` +
`cf8c7d3fa5c266b2bf3abfbf693f7546dd2642569a2120f3536d45a6b5ab","empty_has` +
`h":"","number_hash":42,"float_hash":99.9,"SecureHashBlank":"cn86fb3fa06_` +
`24","named_secure_hash":"cn86fb3fa07_24","empty_secure_hash":"cn86fb3fa0` +
`8_24","number_secure_hash":"cn86fb3fa09_2","float_secure_hash":"cn86fb3f` +
`a010_14"}`)
var bothSealed = []byte{0x6a, 0xff, 0x81, 0x3, 0x1, 0x1, 0x7, 0x50, 0x61,
0x63, 0x6b, 0x61, 0x67, 0x65, 0x1, 0xff, 0x82, 0x0, 0x1, 0x7, 0x1, 0x7,
0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x1, 0xc, 0x0, 0x1, 0x6, 0x46,
0x6f, 0x72, 0x6d, 0x61, 0x74, 0x1, 0xc, 0x0, 0x1, 0xa, 0x43, 0x6f, 0x6d,
0x70, 0x72, 0x65, 0x73, 0x73, 0x65, 0x64, 0x1, 0x2, 0x0, 0x1, 0x9, 0x45,
0x6e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x49, 0x44, 0x1, 0xc, 0x0, 0x1, 0x5,
0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x1, 0xc, 0x0, 0x1, 0x6, 0x43, 0x69, 0x70,
0x68, 0x65, 0x72, 0x1, 0xa, 0x0, 0x1, 0x7, 0x45, 0x6e, 0x63, 0x6f, 0x64,
0x65, 0x64, 0x1, 0xa, 0x0, 0x0, 0x0, 0xfe, 0x6, 0xa9, 0xff, 0x82, 0x1, 0x5,
0x30, 0x2e, 0x30, 0x2e, 0x31, 0x1, 0x6, 0x73, 0x70, 0x61, 0x72, 0x73, 0x65,
0x2, 0x8, 0x38, 0x36, 0x66, 0x62, 0x33, 0x66, 0x61, 0x30, 0x1, 0xa, 0x63,
0x6e, 0x38, 0x36, 0x66, 0x62, 0x33, 0x66, 0x61, 0x30, 0x1, 0xfe, 0x3, 0xb6,
0x37, 0x62, 0x32, 0x32, 0x36, 0x33, 0x36, 0x65, 0x33, 0x38, 0x33, 0x36,
0x36, 0x36, 0x36, 0x32, 0x33, 0x33, 0x36, 0x36, 0x36, 0x31, 0x33, 0x30,
0x33, 0x30, 0x35, 0x66, 0x33, 0x32, 0x33, 0x34, 0x32, 0x32, 0x33, 0x61,
0x32, 0x32, 0x36, 0x34, 0x36, 0x35, 0x36, 0x36, 0x36, 0x31, 0x37, 0x35,
0x36, 0x63, 0x37, 0x34, 0x32, 0x64, 0x37, 0x33, 0x36, 0x35, 0x36, 0x33,
0x37, 0x35, 0x37, 0x32, 0x36, 0x35, 0x32, 0x32, 0x32, 0x63, 0x32, 0x32,
0x36, 0x33, 0x36, 0x65, 0x33, 0x38, 0x33, 0x36, 0x36, 0x36, 0x36, 0x32,
0x33, 0x33, 0x36, 0x36, 0x36, 0x31, 0x33, 0x30, 0x33, 0x31, 0x35, 0x66,
0x33, 0x32, 0x33, 0x34, 0x32, 0x32, 0x33, 0x61, 0x32, 0x32, 0x36, 0x32,
0x36, 0x63, 0x36, 0x31, 0x36, 0x65, 0x36, 0x62, 0x32, 0x64, 0x37, 0x33,
0x36, 0x35, 0x36, 0x33, 0x37, 0x35, 0x37, 0x32, 0x36, 0x35, 0x32, 0x32,
0x32, 0x63, 0x32, 0x32, 0x36, 0x33, 0x36, 0x65, 0x33, 0x38, 0x33, 0x36,
0x36, 0x36, 0x36, 0x32, 0x33, 0x33, 0x36, 0x36, 0x36, 0x31, 0x33, 0x30,
0x33, 0x32, 0x35, 0x66, 0x33, 0x32, 0x33, 0x34, 0x32, 0x32, 0x33, 0x61,
0x32, 0x32, 0x36, 0x65, 0x36, 0x31, 0x36, 0x64, 0x36, 0x35, 0x36, 0x34,
0x32, 0x64, 0x37, 0x33, 0x36, 0x35, 0x36, 0x33, 0x37, 0x35, 0x37, 0x32,
0x36, 0x35, 0x32, 0x32, 0x32, 0x63, 0x32, 0x32, 0x36, 0x33, 0x36, 0x65,
0x33, 0x38, 0x33, 0x36, 0x36, 0x36, 0x36, 0x32, 0x33, 0x33, 0x36, 0x36,
0x36, 0x31, 0x33, 0x30, 0x33, 0x33, 0x35, 0x66, 0x33, 0x32, 0x33, 0x34,
0x32, 0x32, 0x33, 0x61, 0x32, 0x32, 0x32, 0x32, 0x32, 0x63, 0x32, 0x32,
0x36, 0x33, 0x36, 0x65, 0x33, 0x38, 0x33, 0x36, 0x36, 0x36, 0x36, 0x32,
0x33, 0x33, 0x36, 0x36, 0x36, 0x31, 0x33, 0x30, 0x33, 0x34, 0x35, 0x66,
0x33, 0x32, 0x32, 0x32, 0x33, 0x61, 0x33, 0x34, 0x33, 0x32, 0x32, 0x63,
0x32, 0x32, 0x36, 0x33, 0x36, 0x65, 0x33, 0x38, 0x33, 0x36, 0x36, 0x36,
0x36, 0x32, 0x33, 0x33, 0x36, 0x36, 0x36, 0x31, 0x33, 0x30, 0x33, 0x35,
0x35, 0x66, 0x33, 0x31, 0x33, 0x34, 0x32, 0x32, 0x33, 0x61, 0x33, 0x39,
0x33, 0x39, 0x32, 0x65, 0x33, 0x39, 0x32, 0x63, 0x32, 0x32, 0x36, 0x33,
0x36, 0x65, 0x33, 0x38, 0x33, 0x36, 0x36, 0x36, 0x36, 0x32, 0x33, 0x33,
0x36, 0x36, 0x36, 0x31, 0x33, 0x30, 0x33, 0x36, 0x35, 0x66, 0x33, 0x32,
0x33, 0x34, 0x32, 0x32, 0x33, 0x61, 0x32, 0x32, 0x37, 0x33, 0x36, 0x38,
0x36, 0x31, 0x33, 0x32, 0x33, 0x35, 0x33, 0x36, 0x33, 0x61, 0x33, 0x31,
0x33, 0x37, 0x36, 0x31, 0x33, 0x37, 0x36, 0x34, 0x36, 0x32, 0x33, 0x35,
0x36, 0x32, 0x33, 0x30, 0x36, 0x33, 0x36, 0x32, 0x36, 0x34, 0x33, 0x32,
0x36, 0x36, 0x36, 0x36, 0x33, 0x37, 0x33, 0x39, 0x33, 0x32, 0x36, 0x35,
0x33, 0x35, 0x33, 0x33, 0x33, 0x39, 0x33, 0x30, 0x33, 0x35, 0x33, 0x36,
0x36, 0x33, 0x33, 0x33, 0x33, 0x31, 0x36, 0x34, 0x33, 0x35, 0x33, 0x38,
0x33, 0x37, 0x33, 0x33, 0x36, 0x34, 0x33, 0x37, 0x36, 0x33, 0x36, 0x36,
0x33, 0x30, 0x36, 0x35, 0x36, 0x32, 0x33, 0x32, 0x33, 0x30, 0x33, 0x39,
0x36, 0x35, 0x33, 0x33, 0x33, 0x37, 0x33, 0x37, 0x33, 0x35, 0x33, 0x39,
0x36, 0x33, 0x33, 0x31, 0x33, 0x39, 0x36, 0x35, 0x33, 0x34, 0x33, 0x39,
0x33, 0x36, 0x33, 0x36, 0x36, 0x33, 0x33, 0x37, 0x36, 0x31, 0x33, 0x38,
0x33, 0x32, 0x36, 0x32, 0x36, 0x32, 0x32, 0x32, 0x32, 0x63, 0x32, 0x32,
0x36, 0x33, 0x36, 0x65, 0x33, 0x38, 0x33, 0x36, 0x36, 0x36, 0x36, 0x32,
0x33, 0x33, 0x36, 0x36, 0x36, 0x31, 0x33, 0x30, 0x33, 0x37, 0x35, 0x66,
0x33, 0x32, 0x33, 0x34, 0x32, 0x32, 0x33, 0x61, 0x32, 0x32, 0x37, 0x33,
0x36, 0x38, 0x36, 0x31, 0x33, 0x32, 0x33, 0x35, 0x33, 0x36, 0x33, 0x61,
0x33, 0x34, 0x33, 0x37, 0x33, 0x34, 0x33, 0x31, 0x36, 0x31, 0x33, 0x37,
0x33, 0x34, 0x33, 0x30, 0x33, 0x33, 0x33, 0x32, 0x33, 0x36, 0x36, 0x31,
0x33, 0x37, 0x33, 0x33, 0x33, 0x34, 0x33, 0x37, 0x33, 0x35, 0x33, 0x34,
0x33, 0x39, 0x36, 0x35, 0x36, 0x32, 0x36, 0x32, 0x36, 0x36, 0x36, 0x33,
0x33, 0x30, 0x33, 0x35, 0x33, 0x36, 0x33, 0x39, 0x36, 0x32, 0x36, 0x34,
0x33, 0x39, 0x36, 0x35, 0x33, 0x32, 0x33, 0x30, 0x36, 0x34, 0x36, 0x36,
0x33, 0x31, 0x33, 0x39, 0x36, 0x31, 0x33, 0x36, 0x36, 0x36, 0x36, 0x32,
0x33, 0x39, 0x36, 0x35, 0x33, 0x39, 0x33, 0x35, 0x33, 0x33, 0x36, 0x33,
0x36, 0x33, 0x36, 0x36, 0x33, 0x38, 0x33, 0x32, 0x33, 0x39, 0x33, 0x34,
0x36, 0x31, 0x36, 0x36, 0x36, 0x32, 0x33, 0x37, 0x36, 0x33, 0x33, 0x37,
0x36, 0x31, 0x33, 0x30, 0x33, 0x32, 0x33, 0x32, 0x32, 0x32, 0x32, 0x63,
0x32, 0x32, 0x36, 0x33, 0x36, 0x65, 0x33, 0x38, 0x33, 0x36, 0x36, 0x36,
0x36, 0x32, 0x33, 0x33, 0x36, 0x36, 0x36, 0x31, 0x33, 0x30, 0x33, 0x38,
0x35, 0x66, 0x33, 0x32, 0x33, 0x34, 0x32, 0x32, 0x33, 0x61, 0x32, 0x32,
0x37, 0x33, 0x36, 0x38, 0x36, 0x31, 0x33, 0x32, 0x33, 0x35, 0x33, 0x36,
0x33, 0x61, 0x33, 0x36, 0x33, 0x39, 0x36, 0x31, 0x33, 0x31, 0x33, 0x39,
0x33, 0x32, 0x36, 0x34, 0x33, 0x37, 0x33, 0x36, 0x33, 0x35, 0x33, 0x34,
0x33, 0x38, 0x33, 0x35, 0x33, 0x31, 0x33, 0x32, 0x36, 0x31, 0x33, 0x32,
0x33, 0x36, 0x33, 0x32, 0x33, 0x34, 0x36, 0x33, 0x36, 0x34, 0x33, 0x32,
0x36, 0x33, 0x33, 0x36, 0x33, 0x32, 0x36, 0x35, 0x33, 0x37, 0x33, 0x35,
0x33, 0x39, 0x33, 0x32, 0x36, 0x33, 0x36, 0x34, 0x36, 0x36, 0x33, 0x36,
0x36, 0x31, 0x36, 0x34, 0x36, 0x35, 0x33, 0x34, 0x36, 0x32, 0x33, 0x39,
0x36, 0x31, 0x33, 0x33, 0x33, 0x38, 0x33, 0x34, 0x36, 0x35, 0x33, 0x37,
0x33, 0x31, 0x33, 0x30, 0x33, 0x31, 0x33, 0x38, 0x36, 0x34, 0x33, 0x38,
0x33, 0x36, 0x33, 0x35, 0x33, 0x30, 0x33, 0x31, 0x33, 0x32, 0x33, 0x36,
0x33, 0x39, 0x33, 0x32, 0x36, 0x34, 0x36, 0x31, 0x36, 0x34, 0x32, 0x32,
0x32, 0x63, 0x32, 0x32, 0x36, 0x33, 0x36, 0x65, 0x33, 0x38, 0x33, 0x36,
0x36, 0x36, 0x36, 0x32, 0x33, 0x33, 0x36, 0x36, 0x36, 0x31, 0x33, 0x30,
0x33, 0x39, 0x35, 0x66, 0x33, 0x32, 0x32, 0x32, 0x33, 0x61, 0x33, 0x34,
0x33, 0x32, 0x32, 0x63, 0x32, 0x32, 0x36, 0x33, 0x36, 0x65, 0x33, 0x38,
0x33, 0x36, 0x36, 0x36, 0x36, 0x32, 0x33, 0x33, 0x36, 0x36, 0x36, 0x31,
0x33, 0x30, 0x33, 0x31, 0x33, 0x30, 0x35, 0x66, 0x33, 0x31, 0x33, 0x34,
0x32, 0x32, 0x33, 0x61, 0x33, 0x39, 0x33, 0x39, 0x32, 0x65, 0x33, 0x39,
0x37, 0x64, 0x1, 0xfe, 0x2, 0xc3, 0x7b, 0x22, 0x44, 0x65, 0x66, 0x61, 0x75,
0x6c, 0x74, 0x22, 0x3a, 0x22, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74,
0x2d, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x2c, 0x22, 0x42, 0x6c, 0x61,
0x6e, 0x6b, 0x22, 0x3a, 0x22, 0x62, 0x6c, 0x61, 0x6e, 0x6b, 0x2d, 0x76,
0x61, 0x6c, 0x75, 0x65, 0x22, 0x2c, 0x22, 0x6e, 0x61, 0x6d, 0x65, 0x64,
0x22, 0x3a, 0x22, 0x6e, 0x61, 0x6d, 0x65, 0x64, 0x2d, 0x76, 0x61, 0x6c,
0x75, 0x65, 0x22, 0x2c, 0x22, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x3a,
0x22, 0x22, 0x2c, 0x22, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x22, 0x3a,
0x34, 0x32, 0x2c, 0x22, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x22, 0x3a, 0x39,
0x39, 0x2e, 0x39, 0x2c, 0x22, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x44,
0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x22, 0x3a, 0x22, 0x63, 0x6e, 0x38,
0x36, 0x66, 0x62, 0x33, 0x66, 0x61, 0x30, 0x30, 0x5f, 0x32, 0x34, 0x22,
0x2c, 0x22, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x42, 0x6c, 0x61, 0x6e,
0x6b, 0x22, 0x3a, 0x22, 0x63, 0x6e, 0x38, 0x36, 0x66, 0x62, 0x33, 0x66,
0x61, 0x30, 0x31, 0x5f, 0x32, 0x34, 0x22, 0x2c, 0x22, 0x6e, 0x61, 0x6d,
0x65, 0x64, 0x5f, 0x73, 0x65, 0x63, 0x75, 0x72, 0x65, 0x22, 0x3a, 0x22,
0x63, 0x6e, 0x38, 0x36, 0x66, 0x62, 0x33, 0x66, 0x61, 0x30, 0x32, 0x5f,
0x32, 0x34, 0x22, 0x2c, 0x22, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x5f, 0x73,
0x65, 0x63, 0x75, 0x72, 0x65, 0x22, 0x3a, 0x22, 0x63, 0x6e, 0x38, 0x36,
0x66, 0x62, 0x33, 0x66, 0x61, 0x30, 0x33, 0x5f, 0x32, 0x34, 0x22, 0x2c,
0x22, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x5f, 0x73, 0x65, 0x63, 0x75,
0x72, 0x65, 0x22, 0x3a, 0x22, 0x63, 0x6e, 0x38, 0x36, 0x66, 0x62, 0x33,
0x66, 0x61, 0x30, 0x34, 0x5f, 0x32, 0x22, 0x2c, 0x22, 0x66, 0x6c, 0x6f,
0x61, 0x74, 0x5f, 0x73, 0x65, 0x63, 0x75, 0x72, 0x65, 0x22, 0x3a, 0x22,
0x63, 0x6e, 0x38, 0x36, 0x66, 0x62, 0x33, 0x66, 0x61, 0x30, 0x35, 0x5f,
0x31, 0x34, 0x22, 0x2c, 0x22, 0x48, 0x61, 0x73, 0x68, 0x42, 0x6c, 0x61,
0x6e, 0x6b, 0x22, 0x3a, 0x22, 0x73, 0x68, 0x61, 0x32, 0x35, 0x36, 0x3a,
0x65, 0x30, 0x35, 0x66, 0x35, 0x37, 0x63, 0x31, 0x36, 0x36, 0x62, 0x65,
0x66, 0x30, 0x32, 0x35, 0x32, 0x32, 0x65, 0x36, 0x31, 0x31, 0x31, 0x33,
0x62, 0x37, 0x63, 0x64, 0x30, 0x35, 0x63, 0x63, 0x62, 0x37, 0x62, 0x65,
0x63, 0x33, 0x33, 0x31, 0x39, 0x64, 0x39, 0x37, 0x34, 0x38, 0x30, 0x39,
0x63, 0x62, 0x38, 0x33, 0x66, 0x30, 0x31, 0x35, 0x65, 0x35, 0x66, 0x30,
0x37, 0x66, 0x64, 0x35, 0x22, 0x2c, 0x22, 0x6e, 0x61, 0x6d, 0x65, 0x64,
0x5f, 0x68, 0x61, 0x73, 0x68, 0x22, 0x3a, 0x22, 0x73, 0x68, 0x61, 0x32,
0x35, 0x36, 0x3a, 0x64, 0x65, 0x61, 0x35, 0x63, 0x66, 0x38, 0x63, 0x37,
0x64, 0x33, 0x66, 0x61, 0x35, 0x63, 0x32, 0x36, 0x36, 0x62, 0x32, 0x62,
0x66, 0x33, 0x61, 0x62, 0x66, 0x62, 0x66, 0x36, 0x39, 0x33, 0x66, 0x37,
0x35, 0x34, 0x36, 0x64, 0x64, 0x32, 0x36, 0x34, 0x32, 0x35, 0x36, 0x39,
0x61, 0x32, 0x31, 0x32, 0x30, 0x66, 0x33, 0x35, 0x33, 0x36, 0x64, 0x34,
0x35, 0x61, 0x36, 0x62, 0x35, 0x61, 0x62, 0x22, 0x2c, 0x22, 0x65, 0x6d,
0x70, 0x74, 0x79, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x22, 0x3a, 0x22, 0x22,
0x2c, 0x22, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x5f, 0x68, 0x61, 0x73,
0x68, 0x22, 0x3a, 0x34, 0x32, 0x2c, 0x22, 0x66, 0x6c, 0x6f, 0x61, 0x74,
0x5f, 0x68, 0x61, 0x73, 0x68, 0x22, 0x3a, 0x39, 0x39, 0x2e, 0x39, 0x2c,
0x22, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x48, 0x61, 0x73, 0x68, 0x42,
0x6c, 0x61, 0x6e, 0x6b, 0x22, 0x3a, 0x22, 0x63, 0x6e, 0x38, 0x36, 0x66,
0x62, 0x33, 0x66, 0x61, 0x30, 0x36, 0x5f, 0x32, 0x34, 0x22, 0x2c, 0x22,
0x6e, 0x61, 0x6d, 0x65, 0x64, 0x5f, 0x73, 0x65, 0x63, 0x75, 0x72, 0x65,
0x5f, 0x68, 0x61, 0x73, 0x68, 0x22, 0x3a, 0x22, 0x63, 0x6e, 0x38, 0x36,
0x66, 0x62, 0x33, 0x66, 0x61, 0x30, 0x37, 0x5f, 0x32, 0x34, 0x22, 0x2c,
0x22, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x5f, 0x73, 0x65, 0x63, 0x75, 0x72,
0x65, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x22, 0x3a, 0x22, 0x63, 0x6e, 0x38,
0x36, 0x66, 0x62, 0x33, 0x66, 0x61, 0x30, 0x38, 0x5f, 0x32, 0x34, 0x22,
0x2c, 0x22, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x5f, 0x73, 0x65, 0x63,
0x75, 0x72, 0x65, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x22, 0x3a, 0x22, 0x63,
0x6e, 0x38, 0x36, 0x66, 0x62, 0x33, 0x66, 0x61, 0x30, 0x39, 0x5f, 0x32,
0x22, 0x2c, 0x22, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x5f, 0x73, 0x65, 0x63,
0x75, 0x72, 0x65, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x22, 0x3a, 0x22, 0x63,
0x6e, 0x38, 0x36, 0x66, 0x62, 0x33, 0x66, 0x61, 0x30, 0x31, 0x30, 0x5f,
0x31, 0x34, 0x22, 0x7d, 0x0}
var bothComp = []byte{0x6a, 0xff, 0x81, 0x3, 0x1, 0x1, 0x7, 0x50, 0x61,
0x63, 0x6b, 0x61, 0x67, 0x65, 0x1, 0xff, 0x82, 0x0, 0x1, 0x7, 0x1, 0x7,
0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x1, 0xc, 0x0, 0x1, 0x6, 0x46,
0x6f, 0x72, 0x6d, 0x61, 0x74, 0x1, 0xc, 0x0, 0x1, 0xa, 0x43, 0x6f, 0x6d,
0x70, 0x72, 0x65, 0x73, 0x73, 0x65, 0x64, 0x1, 0x2, 0x0, 0x1, 0x9, 0x45,
0x6e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x49, 0x44, 0x1, 0xc, 0x0, 0x1, 0x5,
0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x1, 0xc, 0x0, 0x1, 0x6, 0x43, 0x69, 0x70,
0x68, 0x65, 0x72, 0x1, 0xa, 0x0, 0x1, 0x7, 0x45, 0x6e, 0x63, 0x6f, 0x64,
0x65, 0x64, 0x1, 0xa, 0x0, 0x0, 0x0, 0xfe, 0x3, 0x5e, 0xff, 0x82, 0x1, 0x5,
0x30, 0x2e, 0x30, 0x2e, 0x31, 0x1, 0x6, 0x73, 0x70, 0x61, 0x72, 0x73, 0x65,
0x1, 0x1, 0x1, 0x8, 0x38, 0x36, 0x66, 0x62, 0x33, 0x66, 0x61, 0x30, 0x1,
0xa, 0x63, 0x6e, 0x38, 0x36, 0x66, 0x62, 0x33, 0x66, 0x61, 0x30, 0x1, 0xfe,
0x1, 0xec, 0x32, 0x38, 0x62, 0x35, 0x32, 0x66, 0x66, 0x64, 0x34, 0x34,
0x30, 0x30, 0x64, 0x62, 0x30, 0x30, 0x33, 0x64, 0x30, 0x37, 0x30, 0x30,
0x38, 0x32, 0x35, 0x31, 0x32, 0x65, 0x31, 0x39, 0x39, 0x30, 0x32, 0x37,
0x37, 0x34, 0x34, 0x62, 0x32, 0x31, 0x35, 0x34, 0x61, 0x66, 0x32, 0x31,
0x62, 0x37, 0x61, 0x32, 0x34, 0x38, 0x37, 0x36, 0x64, 0x38, 0x31, 0x64,
0x64, 0x30, 0x30, 0x66, 0x35, 0x37, 0x35, 0x32, 0x62, 0x38, 0x39, 0x31,
0x65, 0x38, 0x37, 0x37, 0x37, 0x36, 0x35, 0x37, 0x38, 0x30, 0x30, 0x39,
0x35, 0x61, 0x35, 0x30, 0x32, 0x64, 0x38, 0x36, 0x39, 0x31, 0x38, 0x31,
0x39, 0x30, 0x66, 0x61, 0x37, 0x61, 0x32, 0x39, 0x31, 0x36, 0x39, 0x31,
0x34, 0x33, 0x65, 0x33, 0x38, 0x36, 0x30, 0x38, 0x65, 0x35, 0x39, 0x62,
0x32, 0x39, 0x61, 0x62, 0x36, 0x63, 0x65, 0x66, 0x38, 0x62, 0x63, 0x37,
0x62, 0x62, 0x66, 0x34, 0x66, 0x32, 0x30, 0x62, 0x62, 0x62, 0x62, 0x63,
0x61, 0x64, 0x62, 0x30, 0x62, 0x36, 0x65, 0x38, 0x38, 0x63, 0x31, 0x64,
0x61, 0x36, 0x39, 0x37, 0x64, 0x32, 0x32, 0x61, 0x39, 0x61, 0x32, 0x61,
0x62, 0x32, 0x64, 0x39, 0x63, 0x39, 0x36, 0x37, 0x34, 0x64, 0x63, 0x62,
0x39, 0x63, 0x65, 0x65, 0x37, 0x32, 0x66, 0x61, 0x63, 0x61, 0x30, 0x37,
0x35, 0x37, 0x65, 0x39, 0x33, 0x38, 0x61, 0x39, 0x35, 0x37, 0x36, 0x37,
0x63, 0x35, 0x32, 0x33, 0x37, 0x64, 0x39, 0x38, 0x35, 0x66, 0x65, 0x64,
0x64, 0x32, 0x34, 0x61, 0x32, 0x65, 0x35, 0x36, 0x32, 0x31, 0x39, 0x36,
0x34, 0x62, 0x39, 0x36, 0x39, 0x36, 0x63, 0x63, 0x36, 0x35, 0x33, 0x36,
0x31, 0x62, 0x35, 0x31, 0x65, 0x64, 0x39, 0x35, 0x35, 0x61, 0x32, 0x66,
0x62, 0x33, 0x62, 0x63, 0x39, 0x32, 0x65, 0x32, 0x32, 0x37, 0x61, 0x37,
0x64, 0x33, 0x34, 0x62, 0x39, 0x61, 0x37, 0x33, 0x32, 0x61, 0x38, 0x64,
0x34, 0x37, 0x62, 0x34, 0x64, 0x63, 0x39, 0x37, 0x65, 0x36, 0x33, 0x39,
0x65, 0x35, 0x64, 0x63, 0x38, 0x34, 0x38, 0x66, 0x33, 0x64, 0x33, 0x37,
0x61, 0x39, 0x63, 0x30, 0x61, 0x62, 0x31, 0x35, 0x34, 0x38, 0x61, 0x30,
0x31, 0x36, 0x35, 0x34, 0x38, 0x62, 0x36, 0x31, 0x36, 0x34, 0x62, 0x38,
0x62, 0x32, 0x36, 0x32, 0x39, 0x34, 0x63, 0x31, 0x38, 0x61, 0x33, 0x66,
0x63, 0x31, 0x32, 0x32, 0x36, 0x61, 0x30, 0x30, 0x62, 0x31, 0x30, 0x37,
0x32, 0x36, 0x38, 0x30, 0x66, 0x31, 0x30, 0x35, 0x30, 0x37, 0x66, 0x64,
0x38, 0x39, 0x30, 0x30, 0x38, 0x33, 0x63, 0x33, 0x63, 0x31, 0x39, 0x65,
0x66, 0x37, 0x35, 0x31, 0x30, 0x63, 0x32, 0x62, 0x30, 0x33, 0x32, 0x31,
0x37, 0x62, 0x30, 0x61, 0x31, 0x66, 0x33, 0x34, 0x38, 0x38, 0x31, 0x65,
0x30, 0x38, 0x30, 0x31, 0x31, 0x30, 0x30, 0x30, 0x33, 0x64, 0x63, 0x62,
0x31, 0x33, 0x30, 0x30, 0x31, 0x63, 0x30, 0x36, 0x31, 0x63, 0x30, 0x32,
0x63, 0x30, 0x36, 0x31, 0x62, 0x63, 0x33, 0x38, 0x65, 0x34, 0x33, 0x38,
0x64, 0x38, 0x33, 0x31, 0x30, 0x30, 0x33, 0x33, 0x38, 0x38, 0x39, 0x63,
0x31, 0x63, 0x62, 0x32, 0x65, 0x31, 0x65, 0x63, 0x63, 0x32, 0x30, 0x37,
0x63, 0x30, 0x37, 0x30, 0x30, 0x31, 0x35, 0x35, 0x38, 0x30, 0x62, 0x31,
0x61, 0x31, 0x65, 0x38, 0x32, 0x62, 0x30, 0x61, 0x38, 0x62, 0x31, 0x35,
0x34, 0x33, 0x33, 0x64, 0x30, 0x31, 0x63, 0x34, 0x62, 0x34, 0x37, 0x35,
0x64, 0x37, 0x1, 0xfe, 0x1, 0x40, 0x28, 0xb5, 0x2f, 0xfd, 0x44, 0x0, 0xc3,
0x1, 0x8d, 0x9, 0x0, 0x2, 0x11, 0x31, 0x1d, 0x60, 0x49, 0xeb, 0xb3, 0x15,
0xd, 0xc5, 0x55, 0x3f, 0xfa, 0xcd, 0x25, 0xb4, 0xe2, 0x60, 0xc1, 0x50,
0x89, 0x4f, 0xb5, 0x15, 0x21, 0xe6, 0x66, 0x3b, 0x71, 0x18, 0x6, 0xe3, 0xb,
0xa8, 0x90, 0x71, 0x10, 0x2b, 0xd6, 0x36, 0x6b, 0x2f, 0xab, 0x7a, 0x9b,
0x34, 0xfd, 0x49, 0x94, 0xcc, 0x56, 0x53, 0x6d, 0xbe, 0xb7, 0x4a, 0xd7,
0x27, 0xb6, 0xdf, 0xdf, 0x9c, 0xbe, 0xb3, 0x77, 0xd3, 0x29, 0x7f, 0x7a,
0xcb, 0x15, 0xef, 0x94, 0xfd, 0x1d, 0xa, 0x1f, 0x86, 0x41, 0x7d, 0xbf,
0xce, 0xab, 0x2b, 0x3a, 0x9f, 0x8a, 0x76, 0xec, 0x14, 0x6a, 0xb1, 0xc7,
0x50, 0x4a, 0xce, 0x7b, 0xb5, 0x73, 0x7a, 0x9e, 0x5b, 0x9d, 0x10, 0x42,
0xeb, 0x99, 0x9a, 0xe7, 0xbd, 0x77, 0x91, 0x5b, 0xfa, 0x7a, 0xbc, 0xad,
0x26, 0x1f, 0xca, 0xa1, 0x30, 0x1, 0x1a, 0x54, 0xc8, 0x50, 0x4c, 0x43,
0xa5, 0x54, 0xc, 0x84, 0x2a, 0x8d, 0x73, 0xf8, 0x53, 0xff, 0x16, 0x89,
0x73, 0xc4, 0xe2, 0xbc, 0x0, 0xd, 0xb, 0x60, 0xac, 0x61, 0x82, 0xd, 0x90,
0x47, 0x93, 0xaa, 0x21, 0xc2, 0xfb, 0xb0, 0x24, 0x68, 0x10, 0x1b, 0x26,
0x24, 0xe, 0xe, 0xde, 0xf0, 0xf9, 0x81, 0x13, 0x43, 0xc2, 0x48, 0x63, 0x43,
0x43, 0xc2, 0x88, 0x0, 0x1a, 0xf4, 0x5, 0x61, 0x4, 0x80, 0x87, 0xd, 0x13,
0xc8, 0xc2, 0xef, 0x3, 0xa8, 0x10, 0x2b, 0x0, 0x47, 0x3d, 0xa8, 0x5c, 0xf3,
0x25, 0xf5, 0x2, 0x72, 0x1d, 0x0, 0xc3, 0x81, 0x17, 0x8b, 0x8b, 0x3, 0x18,
0x87, 0x74, 0xc7, 0xcd, 0x10, 0x0, 0xfe, 0xc, 0x77, 0x8b, 0xb4, 0x71, 0x30,
0xba, 0x38, 0xcc, 0x64, 0xf0, 0xa4, 0x5, 0x68, 0x32, 0x64, 0xd2, 0x2, 0x90,
0x1, 0x80, 0xd8, 0x62, 0xb4, 0xd8, 0x2f, 0x59, 0xda, 0x11, 0x41, 0x86,
0x70, 0xa1, 0x34, 0xc0, 0x8c, 0x4b, 0x44, 0x29, 0x70, 0xc, 0x6, 0x30, 0x1c,
0xd0, 0x38, 0x18, 0x1, 0x60, 0xb8, 0xa1, 0x17, 0x92, 0x38, 0xec, 0x43, 0xb,
0x80, 0x4f, 0xe8, 0xe, 0x3d, 0x10, 0x80, 0x28, 0xa9, 0x9a, 0xc3, 0x56,
0x50, 0xd6, 0xca, 0xd8, 0x72, 0x96, 0xa6, 0x43, 0xf9, 0x2, 0x4d, 0x8, 0x16,
0x78, 0x0}

View File

@ -0,0 +1,205 @@
package secure
type Hash struct {
Default string
Blank string `json:""`
Ignore string `json:"-"`
Named string `json:"named"`
Empty string `json:"empty"`
Omit string `json:"omit,omitempty"`
Number int `json:"number"`
Float float64 `json:"float"`
HashDefault string
HashBlank string `json:",hash"`
HashIgnore string `json:"-"`
HashNamed string `json:"named_hash,hash"`
HashEmpty string `json:"empty_hash,hash"`
HashOmit string `json:"omit_hash,omitempty,hash"`
HashNumber int `json:"number_hash,hash"`
HashFloat float64 `json:"float_hash,hash"`
}
var hashObj = &Hash{
Default: "default-value",
Blank: "blank-value",
Ignore: "ignore-value",
Named: "named-value",
Empty: "",
Number: 42,
Float: 99.9,
HashDefault: "default-hash",
HashBlank: "blank-hash",
HashIgnore: "ignore-hash",
HashNamed: "named-hash",
HashEmpty: "",
HashNumber: 42,
HashFloat: 99.9,
}
var hashDecoded = &Hash{
Default: "default-value",
Blank: "blank-value",
Named: "named-value",
Number: 42,
Float: 99.9,
HashDefault: "default-hash",
HashBlank: "sha256:e05f57c166bef02522e61113b7cd05ccb7bec3319d974809cb83f015e5f07fd5",
HashIgnore: "",
HashNamed: "sha256:dea5cf8c7d3fa5c266b2bf3abfbf693f7546dd2642569a2120f3536d45a6b5ab",
HashEmpty: "",
HashNumber: 42,
HashFloat: 99.9,
}
var hashMap = map[string]interface{}{
"Blank": "blank-value",
"Default": "default-value",
"HashBlank": "sha256:e05f57c166bef02522e61113b7cd05ccb7bec3319d974809cb83f015e5f07fd5",
"HashDefault": "default-hash",
"empty": "",
"empty_hash": "",
"float": 99.9,
"float_hash": 99.9,
"named": "named-value",
"named_hash": "sha256:dea5cf8c7d3fa5c266b2bf3abfbf693f7546dd2642569a2120f3536d45a6b5ab",
"number": 42.0,
"number_hash": 42.0,
}
var hashEncoded = []byte(`{"Default":"default-value","Blank":"blank-value","n` +
`amed":"named-value","empty":"","number":42,"float":99.9,"HashDefault":"d` +
`efault-hash","HashBlank":"sha256:e05f57c166bef02522e61113b7cd05ccb7bec33` +
`19d974809cb83f015e5f07fd5","named_hash":"sha256:dea5cf8c7d3fa5c266b2bf3a` +
`bfbf693f7546dd2642569a2120f3536d45a6b5ab","empty_hash":"","number_hash":` +
`42,"float_hash":99.9}`)
var hashUnsealed = []byte(`{"Default":"default-value","Blank":"blank-value","` +
`named":"named-value","empty":"","number":42,"float":99.9,"HashDefault":"` +
`default-hash","HashBlank":"sha256:e05f57c166bef02522e61113b7cd05ccb7bec3` +
`319d974809cb83f015e5f07fd5","named_hash":"sha256:dea5cf8c7d3fa5c266b2bf3` +
`abfbf693f7546dd2642569a2120f3536d45a6b5ab","empty_hash":"","number_hash"` +
`:42,"float_hash":99.9}`)
var hashSealed = []byte{0x6a, 0xff, 0x81, 0x3, 0x1, 0x1, 0x7, 0x50, 0x61,
0x63, 0x6b, 0x61, 0x67, 0x65, 0x1, 0xff, 0x82, 0x0, 0x1, 0x7, 0x1, 0x7,
0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x1, 0xc, 0x0, 0x1, 0x6, 0x46,
0x6f, 0x72, 0x6d, 0x61, 0x74, 0x1, 0xc, 0x0, 0x1, 0xa, 0x43, 0x6f, 0x6d,
0x70, 0x72, 0x65, 0x73, 0x73, 0x65, 0x64, 0x1, 0x2, 0x0, 0x1, 0x9, 0x45,
0x6e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x49, 0x44, 0x1, 0xc, 0x0, 0x1, 0x5,
0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x1, 0xc, 0x0, 0x1, 0x6, 0x43, 0x69, 0x70,
0x68, 0x65, 0x72, 0x1, 0xa, 0x0, 0x1, 0x7, 0x45, 0x6e, 0x63, 0x6f, 0x64,
0x65, 0x64, 0x1, 0xa, 0x0, 0x0, 0x0, 0xfe, 0x2, 0xf0, 0xff, 0x82, 0x1, 0x5,
0x30, 0x2e, 0x30, 0x2e, 0x31, 0x1, 0x6, 0x73, 0x65, 0x63, 0x75, 0x72, 0x65,
0x2, 0x8, 0x38, 0x36, 0x66, 0x62, 0x33, 0x66, 0x61, 0x30, 0x2, 0xfe, 0x2,
0xd0, 0x37, 0x62, 0x32, 0x32, 0x34, 0x34, 0x36, 0x35, 0x36, 0x36, 0x36,
0x31, 0x37, 0x35, 0x36, 0x63, 0x37, 0x34, 0x32, 0x32, 0x33, 0x61, 0x32,
0x32, 0x36, 0x34, 0x36, 0x35, 0x36, 0x36, 0x36, 0x31, 0x37, 0x35, 0x36,
0x63, 0x37, 0x34, 0x32, 0x64, 0x37, 0x36, 0x36, 0x31, 0x36, 0x63, 0x37,
0x35, 0x36, 0x35, 0x32, 0x32, 0x32, 0x63, 0x32, 0x32, 0x34, 0x32, 0x36,
0x63, 0x36, 0x31, 0x36, 0x65, 0x36, 0x62, 0x32, 0x32, 0x33, 0x61, 0x32,
0x32, 0x36, 0x32, 0x36, 0x63, 0x36, 0x31, 0x36, 0x65, 0x36, 0x62, 0x32,
0x64, 0x37, 0x36, 0x36, 0x31, 0x36, 0x63, 0x37, 0x35, 0x36, 0x35, 0x32,
0x32, 0x32, 0x63, 0x32, 0x32, 0x36, 0x65, 0x36, 0x31, 0x36, 0x64, 0x36,
0x35, 0x36, 0x34, 0x32, 0x32, 0x33, 0x61, 0x32, 0x32, 0x36, 0x65, 0x36,
0x31, 0x36, 0x64, 0x36, 0x35, 0x36, 0x34, 0x32, 0x64, 0x37, 0x36, 0x36,
0x31, 0x36, 0x63, 0x37, 0x35, 0x36, 0x35, 0x32, 0x32, 0x32, 0x63, 0x32,
0x32, 0x36, 0x35, 0x36, 0x64, 0x37, 0x30, 0x37, 0x34, 0x37, 0x39, 0x32,
0x32, 0x33, 0x61, 0x32, 0x32, 0x32, 0x32, 0x32, 0x63, 0x32, 0x32, 0x36,
0x65, 0x37, 0x35, 0x36, 0x64, 0x36, 0x32, 0x36, 0x35, 0x37, 0x32, 0x32,
0x32, 0x33, 0x61, 0x33, 0x34, 0x33, 0x32, 0x32, 0x63, 0x32, 0x32, 0x36,
0x36, 0x36, 0x63, 0x36, 0x66, 0x36, 0x31, 0x37, 0x34, 0x32, 0x32, 0x33,
0x61, 0x33, 0x39, 0x33, 0x39, 0x32, 0x65, 0x33, 0x39, 0x32, 0x63, 0x32,
0x32, 0x34, 0x38, 0x36, 0x31, 0x37, 0x33, 0x36, 0x38, 0x34, 0x34, 0x36,
0x35, 0x36, 0x36, 0x36, 0x31, 0x37, 0x35, 0x36, 0x63, 0x37, 0x34, 0x32,
0x32, 0x33, 0x61, 0x32, 0x32, 0x36, 0x34, 0x36, 0x35, 0x36, 0x36, 0x36,
0x31, 0x37, 0x35, 0x36, 0x63, 0x37, 0x34, 0x32, 0x64, 0x36, 0x38, 0x36,
0x31, 0x37, 0x33, 0x36, 0x38, 0x32, 0x32, 0x32, 0x63, 0x32, 0x32, 0x34,
0x38, 0x36, 0x31, 0x37, 0x33, 0x36, 0x38, 0x34, 0x32, 0x36, 0x63, 0x36,
0x31, 0x36, 0x65, 0x36, 0x62, 0x32, 0x32, 0x33, 0x61, 0x32, 0x32, 0x37,
0x33, 0x36, 0x38, 0x36, 0x31, 0x33, 0x32, 0x33, 0x35, 0x33, 0x36, 0x33,
0x61, 0x36, 0x35, 0x33, 0x30, 0x33, 0x35, 0x36, 0x36, 0x33, 0x35, 0x33,
0x37, 0x36, 0x33, 0x33, 0x31, 0x33, 0x36, 0x33, 0x36, 0x36, 0x32, 0x36,
0x35, 0x36, 0x36, 0x33, 0x30, 0x33, 0x32, 0x33, 0x35, 0x33, 0x32, 0x33,
0x32, 0x36, 0x35, 0x33, 0x36, 0x33, 0x31, 0x33, 0x31, 0x33, 0x31, 0x33,
0x33, 0x36, 0x32, 0x33, 0x37, 0x36, 0x33, 0x36, 0x34, 0x33, 0x30, 0x33,
0x35, 0x36, 0x33, 0x36, 0x33, 0x36, 0x32, 0x33, 0x37, 0x36, 0x32, 0x36,
0x35, 0x36, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x31, 0x33, 0x39, 0x36,
0x34, 0x33, 0x39, 0x33, 0x37, 0x33, 0x34, 0x33, 0x38, 0x33, 0x30, 0x33,
0x39, 0x36, 0x33, 0x36, 0x32, 0x33, 0x38, 0x33, 0x33, 0x36, 0x36, 0x33,
0x30, 0x33, 0x31, 0x33, 0x35, 0x36, 0x35, 0x33, 0x35, 0x36, 0x36, 0x33,
0x30, 0x33, 0x37, 0x36, 0x36, 0x36, 0x34, 0x33, 0x35, 0x32, 0x32, 0x32,
0x63, 0x32, 0x32, 0x36, 0x65, 0x36, 0x31, 0x36, 0x64, 0x36, 0x35, 0x36,
0x34, 0x35, 0x66, 0x36, 0x38, 0x36, 0x31, 0x37, 0x33, 0x36, 0x38, 0x32,
0x32, 0x33, 0x61, 0x32, 0x32, 0x37, 0x33, 0x36, 0x38, 0x36, 0x31, 0x33,
0x32, 0x33, 0x35, 0x33, 0x36, 0x33, 0x61, 0x36, 0x34, 0x36, 0x35, 0x36,
0x31, 0x33, 0x35, 0x36, 0x33, 0x36, 0x36, 0x33, 0x38, 0x36, 0x33, 0x33,
0x37, 0x36, 0x34, 0x33, 0x33, 0x36, 0x36, 0x36, 0x31, 0x33, 0x35, 0x36,
0x33, 0x33, 0x32, 0x33, 0x36, 0x33, 0x36, 0x36, 0x32, 0x33, 0x32, 0x36,
0x32, 0x36, 0x36, 0x33, 0x33, 0x36, 0x31, 0x36, 0x32, 0x36, 0x36, 0x36,
0x32, 0x36, 0x36, 0x33, 0x36, 0x33, 0x39, 0x33, 0x33, 0x36, 0x36, 0x33,
0x37, 0x33, 0x35, 0x33, 0x34, 0x33, 0x36, 0x36, 0x34, 0x36, 0x34, 0x33,
0x32, 0x33, 0x36, 0x33, 0x34, 0x33, 0x32, 0x33, 0x35, 0x33, 0x36, 0x33,
0x39, 0x36, 0x31, 0x33, 0x32, 0x33, 0x31, 0x33, 0x32, 0x33, 0x30, 0x36,
0x36, 0x33, 0x33, 0x33, 0x35, 0x33, 0x33, 0x33, 0x36, 0x36, 0x34, 0x33,
0x34, 0x33, 0x35, 0x36, 0x31, 0x33, 0x36, 0x36, 0x32, 0x33, 0x35, 0x36,
0x31, 0x36, 0x32, 0x32, 0x32, 0x32, 0x63, 0x32, 0x32, 0x36, 0x35, 0x36,
0x64, 0x37, 0x30, 0x37, 0x34, 0x37, 0x39, 0x35, 0x66, 0x36, 0x38, 0x36,
0x31, 0x37, 0x33, 0x36, 0x38, 0x32, 0x32, 0x33, 0x61, 0x32, 0x32, 0x32,
0x32, 0x32, 0x63, 0x32, 0x32, 0x36, 0x65, 0x37, 0x35, 0x36, 0x64, 0x36,
0x32, 0x36, 0x35, 0x37, 0x32, 0x35, 0x66, 0x36, 0x38, 0x36, 0x31, 0x37,
0x33, 0x36, 0x38, 0x32, 0x32, 0x33, 0x61, 0x33, 0x34, 0x33, 0x32, 0x32,
0x63, 0x32, 0x32, 0x36, 0x36, 0x36, 0x63, 0x36, 0x66, 0x36, 0x31, 0x37,
0x34, 0x35, 0x66, 0x36, 0x38, 0x36, 0x31, 0x37, 0x33, 0x36, 0x38, 0x32,
0x32, 0x33, 0x61, 0x33, 0x39, 0x33, 0x39, 0x32, 0x65, 0x33, 0x39, 0x37,
0x64, 0x0}
var hashComp = []byte{0x6a, 0xff, 0x81, 0x3, 0x1, 0x1, 0x7, 0x50, 0x61,
0x63, 0x6b, 0x61, 0x67, 0x65, 0x1, 0xff, 0x82, 0x0, 0x1, 0x7, 0x1, 0x7,
0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x1, 0xc, 0x0, 0x1, 0x6, 0x46,
0x6f, 0x72, 0x6d, 0x61, 0x74, 0x1, 0xc, 0x0, 0x1, 0xa, 0x43, 0x6f, 0x6d,
0x70, 0x72, 0x65, 0x73, 0x73, 0x65, 0x64, 0x1, 0x2, 0x0, 0x1, 0x9, 0x45,
0x6e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x49, 0x44, 0x1, 0xc, 0x0, 0x1, 0x5,
0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x1, 0xc, 0x0, 0x1, 0x6, 0x43, 0x69, 0x70,
0x68, 0x65, 0x72, 0x1, 0xa, 0x0, 0x1, 0x7, 0x45, 0x6e, 0x63, 0x6f, 0x64,
0x65, 0x64, 0x1, 0xa, 0x0, 0x0, 0x0, 0xfe, 0x1, 0xee, 0xff, 0x82, 0x1, 0x5,
0x30, 0x2e, 0x30, 0x2e, 0x31, 0x1, 0x6, 0x73, 0x65, 0x63, 0x75, 0x72, 0x65,
0x1, 0x1, 0x1, 0x8, 0x38, 0x36, 0x66, 0x62, 0x33, 0x66, 0x61, 0x30, 0x2,
0xfe, 0x1, 0xcc, 0x32, 0x38, 0x62, 0x35, 0x32, 0x66, 0x66, 0x64, 0x34,
0x34, 0x30, 0x30, 0x36, 0x38, 0x30, 0x30, 0x62, 0x64, 0x30, 0x36, 0x30,
0x30, 0x36, 0x32, 0x38, 0x65, 0x32, 0x61, 0x31, 0x64, 0x36, 0x30, 0x30,
0x39, 0x64, 0x63, 0x62, 0x33, 0x38, 0x64, 0x38, 0x32, 0x36, 0x61, 0x35,
0x61, 0x61, 0x34, 0x39, 0x61, 0x34, 0x38, 0x32, 0x65, 0x38, 0x39, 0x64,
0x36, 0x63, 0x30, 0x36, 0x35, 0x62, 0x35, 0x34, 0x30, 0x65, 0x39, 0x38,
0x64, 0x64, 0x38, 0x63, 0x61, 0x63, 0x64, 0x61, 0x37, 0x64, 0x34, 0x39,
0x32, 0x32, 0x34, 0x30, 0x31, 0x30, 0x65, 0x30, 0x62, 0x36, 0x62, 0x64,
0x38, 0x36, 0x64, 0x63, 0x65, 0x35, 0x36, 0x63, 0x65, 0x61, 0x32, 0x36,
0x39, 0x63, 0x61, 0x66, 0x34, 0x30, 0x37, 0x31, 0x31, 0x66, 0x32, 0x36,
0x61, 0x36, 0x32, 0x61, 0x31, 0x64, 0x38, 0x39, 0x61, 0x31, 0x36, 0x61,
0x39, 0x33, 0x65, 0x61, 0x64, 0x37, 0x65, 0x37, 0x66, 0x37, 0x33, 0x66,
0x61, 0x63, 0x36, 0x35, 0x36, 0x34, 0x35, 0x39, 0x36, 0x66, 0x63, 0x61,
0x39, 0x32, 0x39, 0x36, 0x36, 0x33, 0x63, 0x34, 0x62, 0x66, 0x36, 0x34,
0x36, 0x34, 0x30, 0x35, 0x63, 0x62, 0x36, 0x35, 0x37, 0x65, 0x37, 0x64,
0x33, 0x31, 0x33, 0x39, 0x64, 0x34, 0x66, 0x34, 0x36, 0x62, 0x33, 0x33,
0x64, 0x34, 0x36, 0x61, 0x31, 0x62, 0x36, 0x32, 0x64, 0x34, 0x61, 0x38,
0x39, 0x37, 0x39, 0x61, 0x62, 0x36, 0x36, 0x32, 0x63, 0x34, 0x66, 0x36,
0x33, 0x61, 0x61, 0x31, 0x33, 0x34, 0x32, 0x34, 0x38, 0x31, 0x64, 0x33,
0x31, 0x66, 0x31, 0x62, 0x63, 0x62, 0x37, 0x32, 0x61, 0x36, 0x32, 0x32,
0x61, 0x33, 0x66, 0x38, 0x66, 0x32, 0x66, 0x34, 0x64, 0x65, 0x34, 0x36,
0x33, 0x62, 0x39, 0x36, 0x33, 0x65, 0x31, 0x65, 0x33, 0x35, 0x38, 0x37,
0x38, 0x65, 0x30, 0x38, 0x38, 0x33, 0x35, 0x65, 0x63, 0x61, 0x65, 0x36,
0x33, 0x62, 0x38, 0x30, 0x30, 0x66, 0x39, 0x32, 0x33, 0x62, 0x30, 0x35,
0x38, 0x37, 0x37, 0x65, 0x30, 0x39, 0x64, 0x33, 0x34, 0x63, 0x61, 0x31,
0x31, 0x37, 0x37, 0x65, 0x62, 0x31, 0x32, 0x31, 0x38, 0x38, 0x33, 0x63,
0x34, 0x64, 0x37, 0x33, 0x35, 0x34, 0x66, 0x35, 0x38, 0x33, 0x39, 0x32,
0x38, 0x38, 0x63, 0x66, 0x34, 0x61, 0x64, 0x33, 0x62, 0x32, 0x38, 0x38,
0x63, 0x30, 0x34, 0x31, 0x63, 0x37, 0x31, 0x64, 0x65, 0x32, 0x30, 0x38,
0x63, 0x30, 0x30, 0x64, 0x30, 0x36, 0x65, 0x31, 0x64, 0x38, 0x33, 0x33,
0x34, 0x66, 0x63, 0x32, 0x65, 0x37, 0x30, 0x30, 0x61, 0x30, 0x31, 0x31,
0x30, 0x30, 0x30, 0x33, 0x33, 0x31, 0x39, 0x66, 0x36, 0x61, 0x66, 0x30,
0x30, 0x34, 0x64, 0x30, 0x36, 0x66, 0x62, 0x32, 0x62, 0x30, 0x30, 0x30,
0x34, 0x38, 0x30, 0x62, 0x38, 0x31, 0x31, 0x64, 0x33, 0x61, 0x32, 0x63,
0x63, 0x38, 0x63, 0x63, 0x62, 0x31, 0x32, 0x39, 0x34, 0x38, 0x64, 0x61,
0x30, 0x30, 0x33, 0x30, 0x39, 0x34, 0x30, 0x30, 0x37, 0x38, 0x64, 0x33,
0x38, 0x66, 0x38, 0x30, 0x32, 0x63, 0x35, 0x61, 0x65, 0x30, 0x63, 0x32,
0x33, 0x36, 0x37, 0x36, 0x39, 0x33, 0x61, 0x39, 0x34, 0x32, 0x66, 0x31,
0x65, 0x33, 0x64, 0x61, 0x30, 0x66, 0x30, 0x0}

View File

@ -0,0 +1,111 @@
package secure
type JSON struct {
TagDefault string
TagBlank string `json:""`
TagIgnore string `json:"-"`
TagNamed string `json:"tag_named"`
TagEmpty string `json:"tag_empty"`
TagOmit string `json:"tag_omit,omitempty"`
TagNumber int `json:"tag_number"`
TagFloat float64 `json:"tag_float"`
}
var jsonObj = &JSON{
TagDefault: "default-value",
TagBlank: "blank-value",
TagIgnore: "ignore-value",
TagNamed: "named-value",
TagEmpty: "",
TagNumber: 42,
TagFloat: 99.9,
}
var jsonDecoded = &JSON{
TagDefault: "default-value",
TagBlank: "blank-value",
TagNamed: "named-value",
TagNumber: 42,
TagFloat: 99.9,
}
var jsonMap = map[string]interface{}{
"TagBlank": "blank-value",
"TagDefault": "default-value",
"tag_empty": "",
"tag_float": 99.9,
"tag_named": "named-value",
"tag_number": 42.0,
}
var jsonEncoded = []byte(`{"TagDefault":"default-value","TagBlank":"blank-val` +
`ue","tag_named":"named-value","tag_empty":"","tag_number":42,"tag_float"` +
`:99.9}`)
var jsonUnsealed = []byte(`{"TagDefault":"default-value","TagBlank":"blank-va` +
`lue","tag_named":"named-value","tag_empty":"","tag_number":42,"tag_float` +
`":99.9}`)
var jsonSealed = []byte{0x6a, 0xff, 0x81, 0x3, 0x1, 0x1, 0x7, 0x50, 0x61,
0x63, 0x6b, 0x61, 0x67, 0x65, 0x1, 0xff, 0x82, 0x0, 0x1, 0x7, 0x1, 0x7,
0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x1, 0xc, 0x0, 0x1, 0x6, 0x46,
0x6f, 0x72, 0x6d, 0x61, 0x74, 0x1, 0xc, 0x0, 0x1, 0xa, 0x43, 0x6f, 0x6d,
0x70, 0x72, 0x65, 0x73, 0x73, 0x65, 0x64, 0x1, 0x2, 0x0, 0x1, 0x9, 0x45,
0x6e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x49, 0x44, 0x1, 0xc, 0x0, 0x1, 0x5,
0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x1, 0xc, 0x0, 0x1, 0x6, 0x43, 0x69, 0x70,
0x68, 0x65, 0x72, 0x1, 0xa, 0x0, 0x1, 0x7, 0x45, 0x6e, 0x63, 0x6f, 0x64,
0x65, 0x64, 0x1, 0xa, 0x0, 0x0, 0x0, 0xfe, 0x1, 0x22, 0xff, 0x82, 0x1, 0x5,
0x30, 0x2e, 0x30, 0x2e, 0x31, 0x1, 0x6, 0x73, 0x65, 0x63, 0x75, 0x72, 0x65,
0x2, 0x8, 0x38, 0x36, 0x66, 0x62, 0x33, 0x66, 0x61, 0x30, 0x2, 0xfe, 0x1,
0x2, 0x37, 0x62, 0x32, 0x32, 0x35, 0x34, 0x36, 0x31, 0x36, 0x37, 0x34,
0x34, 0x36, 0x35, 0x36, 0x36, 0x36, 0x31, 0x37, 0x35, 0x36, 0x63, 0x37,
0x34, 0x32, 0x32, 0x33, 0x61, 0x32, 0x32, 0x36, 0x34, 0x36, 0x35, 0x36,
0x36, 0x36, 0x31, 0x37, 0x35, 0x36, 0x63, 0x37, 0x34, 0x32, 0x64, 0x37,
0x36, 0x36, 0x31, 0x36, 0x63, 0x37, 0x35, 0x36, 0x35, 0x32, 0x32, 0x32,
0x63, 0x32, 0x32, 0x35, 0x34, 0x36, 0x31, 0x36, 0x37, 0x34, 0x32, 0x36,
0x63, 0x36, 0x31, 0x36, 0x65, 0x36, 0x62, 0x32, 0x32, 0x33, 0x61, 0x32,
0x32, 0x36, 0x32, 0x36, 0x63, 0x36, 0x31, 0x36, 0x65, 0x36, 0x62, 0x32,
0x64, 0x37, 0x36, 0x36, 0x31, 0x36, 0x63, 0x37, 0x35, 0x36, 0x35, 0x32,
0x32, 0x32, 0x63, 0x32, 0x32, 0x37, 0x34, 0x36, 0x31, 0x36, 0x37, 0x35,
0x66, 0x36, 0x65, 0x36, 0x31, 0x36, 0x64, 0x36, 0x35, 0x36, 0x34, 0x32,
0x32, 0x33, 0x61, 0x32, 0x32, 0x36, 0x65, 0x36, 0x31, 0x36, 0x64, 0x36,
0x35, 0x36, 0x34, 0x32, 0x64, 0x37, 0x36, 0x36, 0x31, 0x36, 0x63, 0x37,
0x35, 0x36, 0x35, 0x32, 0x32, 0x32, 0x63, 0x32, 0x32, 0x37, 0x34, 0x36,
0x31, 0x36, 0x37, 0x35, 0x66, 0x36, 0x35, 0x36, 0x64, 0x37, 0x30, 0x37,
0x34, 0x37, 0x39, 0x32, 0x32, 0x33, 0x61, 0x32, 0x32, 0x32, 0x32, 0x32,
0x63, 0x32, 0x32, 0x37, 0x34, 0x36, 0x31, 0x36, 0x37, 0x35, 0x66, 0x36,
0x65, 0x37, 0x35, 0x36, 0x64, 0x36, 0x32, 0x36, 0x35, 0x37, 0x32, 0x32,
0x32, 0x33, 0x61, 0x33, 0x34, 0x33, 0x32, 0x32, 0x63, 0x32, 0x32, 0x37,
0x34, 0x36, 0x31, 0x36, 0x37, 0x35, 0x66, 0x36, 0x36, 0x36, 0x63, 0x36,
0x66, 0x36, 0x31, 0x37, 0x34, 0x32, 0x32, 0x33, 0x61, 0x33, 0x39, 0x33,
0x39, 0x32, 0x65, 0x33, 0x39, 0x37, 0x64, 0x0}
var jsonComp = []byte{0x6a, 0xff, 0x81, 0x3, 0x1, 0x1, 0x7, 0x50, 0x61,
0x63, 0x6b, 0x61, 0x67, 0x65, 0x1, 0xff, 0x82, 0x0, 0x1, 0x7, 0x1, 0x7,
0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x1, 0xc, 0x0, 0x1, 0x6, 0x46,
0x6f, 0x72, 0x6d, 0x61, 0x74, 0x1, 0xc, 0x0, 0x1, 0xa, 0x43, 0x6f, 0x6d,
0x70, 0x72, 0x65, 0x73, 0x73, 0x65, 0x64, 0x1, 0x2, 0x0, 0x1, 0x9, 0x45,
0x6e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x49, 0x44, 0x1, 0xc, 0x0, 0x1, 0x5,
0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x1, 0xc, 0x0, 0x1, 0x6, 0x43, 0x69, 0x70,
0x68, 0x65, 0x72, 0x1, 0xa, 0x0, 0x1, 0x7, 0x45, 0x6e, 0x63, 0x6f, 0x64,
0x65, 0x64, 0x1, 0xa, 0x0, 0x0, 0x0, 0xff, 0xf3, 0xff, 0x82, 0x1, 0x5,
0x30, 0x2e, 0x30, 0x2e, 0x31, 0x1, 0x6, 0x73, 0x65, 0x63, 0x75, 0x72, 0x65,
0x1, 0x1, 0x1, 0x8, 0x38, 0x36, 0x66, 0x62, 0x33, 0x66, 0x61, 0x30, 0x2,
0xff, 0xd2, 0x32, 0x38, 0x62, 0x35, 0x32, 0x66, 0x66, 0x64, 0x30, 0x34,
0x30, 0x30, 0x65, 0x35, 0x30, 0x32, 0x30, 0x30, 0x32, 0x32, 0x30, 0x35,
0x31, 0x32, 0x31, 0x39, 0x39, 0x30, 0x62, 0x35, 0x30, 0x61, 0x32, 0x38,
0x31, 0x37, 0x34, 0x61, 0x31, 0x30, 0x61, 0x30, 0x37, 0x62, 0x63, 0x33,
0x38, 0x38, 0x66, 0x30, 0x37, 0x61, 0x30, 0x63, 0x39, 0x64, 0x62, 0x62,
0x31, 0x34, 0x34, 0x65, 0x65, 0x33, 0x33, 0x31, 0x35, 0x30, 0x66, 0x36,
0x37, 0x66, 0x63, 0x63, 0x30, 0x31, 0x30, 0x35, 0x35, 0x33, 0x31, 0x38,
0x38, 0x33, 0x64, 0x65, 0x32, 0x66, 0x61, 0x61, 0x38, 0x63, 0x30, 0x33,
0x31, 0x64, 0x34, 0x38, 0x64, 0x34, 0x39, 0x65, 0x64, 0x31, 0x38, 0x33,
0x30, 0x35, 0x62, 0x34, 0x33, 0x34, 0x33, 0x61, 0x36, 0x35, 0x62, 0x62,
0x35, 0x33, 0x64, 0x36, 0x39, 0x37, 0x62, 0x39, 0x32, 0x62, 0x33, 0x32,
0x39, 0x61, 0x62, 0x39, 0x32, 0x62, 0x62, 0x32, 0x37, 0x65, 0x33, 0x63,
0x33, 0x38, 0x39, 0x66, 0x35, 0x65, 0x34, 0x30, 0x32, 0x34, 0x61, 0x33,
0x35, 0x37, 0x64, 0x66, 0x35, 0x35, 0x32, 0x36, 0x65, 0x62, 0x63, 0x37,
0x38, 0x34, 0x30, 0x36, 0x30, 0x30, 0x33, 0x33, 0x63, 0x37, 0x36, 0x31,
0x38, 0x61, 0x65, 0x30, 0x39, 0x65, 0x31, 0x37, 0x35, 0x36, 0x36, 0x65,
0x38, 0x61, 0x36, 0x37, 0x31, 0x39, 0x33, 0x61, 0x31, 0x34, 0x33, 0x30,
0x35, 0x38, 0x37, 0x65, 0x61, 0x64, 0x33, 0x64, 0x0}

View File

@ -0,0 +1,79 @@
package secure
type None struct {
Value string
Empty string
Number int
Float float64
}
var noneObj = &None{
Value: "object-value",
Empty: "",
Number: 42,
Float: 99.9,
}
var noneDecoded = &None{
Value: "object-value",
Number: 42,
Float: 99.9,
}
var noneMap = map[string]interface{}{
"Empty": "",
"Float": 99.9,
"Number": 42.0,
"Value": "object-value",
}
var noneEncoded = []byte(`{"Value":"object-value","Empty":"","Number":42,"Float":99.9}`)
var noneUnsealed = []byte(`{"Value":"object-value","Empty":"","Number":42,"Float":99.9}`)
var noneSealed = []byte{0x6a, 0xff, 0x81, 0x3, 0x1, 0x1, 0x7, 0x50, 0x61,
0x63, 0x6b, 0x61, 0x67, 0x65, 0x1, 0xff, 0x82, 0x0, 0x1, 0x7, 0x1, 0x7,
0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x1, 0xc, 0x0, 0x1, 0x6, 0x46,
0x6f, 0x72, 0x6d, 0x61, 0x74, 0x1, 0xc, 0x0, 0x1, 0xa, 0x43, 0x6f, 0x6d,
0x70, 0x72, 0x65, 0x73, 0x73, 0x65, 0x64, 0x1, 0x2, 0x0, 0x1, 0x9, 0x45,
0x6e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x49, 0x44, 0x1, 0xc, 0x0, 0x1, 0x5,
0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x1, 0xc, 0x0, 0x1, 0x6, 0x43, 0x69, 0x70,
0x68, 0x65, 0x72, 0x1, 0xa, 0x0, 0x1, 0x7, 0x45, 0x6e, 0x63, 0x6f, 0x64,
0x65, 0x64, 0x1, 0xa, 0x0, 0x0, 0x0, 0xff, 0x96, 0xff, 0x82, 0x1, 0x5,
0x30, 0x2e, 0x30, 0x2e, 0x31, 0x1, 0x6, 0x73, 0x65, 0x63, 0x75, 0x72, 0x65,
0x2, 0x8, 0x38, 0x36, 0x66, 0x62, 0x33, 0x66, 0x61, 0x30, 0x2, 0x78, 0x37,
0x62, 0x32, 0x32, 0x35, 0x36, 0x36, 0x31, 0x36, 0x63, 0x37, 0x35, 0x36,
0x35, 0x32, 0x32, 0x33, 0x61, 0x32, 0x32, 0x36, 0x66, 0x36, 0x32, 0x36,
0x61, 0x36, 0x35, 0x36, 0x33, 0x37, 0x34, 0x32, 0x64, 0x37, 0x36, 0x36,
0x31, 0x36, 0x63, 0x37, 0x35, 0x36, 0x35, 0x32, 0x32, 0x32, 0x63, 0x32,
0x32, 0x34, 0x35, 0x36, 0x64, 0x37, 0x30, 0x37, 0x34, 0x37, 0x39, 0x32,
0x32, 0x33, 0x61, 0x32, 0x32, 0x32, 0x32, 0x32, 0x63, 0x32, 0x32, 0x34,
0x65, 0x37, 0x35, 0x36, 0x64, 0x36, 0x32, 0x36, 0x35, 0x37, 0x32, 0x32,
0x32, 0x33, 0x61, 0x33, 0x34, 0x33, 0x32, 0x32, 0x63, 0x32, 0x32, 0x34,
0x36, 0x36, 0x63, 0x36, 0x66, 0x36, 0x31, 0x37, 0x34, 0x32, 0x32, 0x33,
0x61, 0x33, 0x39, 0x33, 0x39, 0x32, 0x65, 0x33, 0x39, 0x37, 0x64, 0x0}
var noneComp = []byte{0x6a, 0xff, 0x81, 0x3, 0x1, 0x1, 0x7, 0x50, 0x61,
0x63, 0x6b, 0x61, 0x67, 0x65, 0x1, 0xff, 0x82, 0x0, 0x1, 0x7, 0x1, 0x7,
0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x1, 0xc, 0x0, 0x1, 0x6, 0x46,
0x6f, 0x72, 0x6d, 0x61, 0x74, 0x1, 0xc, 0x0, 0x1, 0xa, 0x43, 0x6f, 0x6d,
0x70, 0x72, 0x65, 0x73, 0x73, 0x65, 0x64, 0x1, 0x2, 0x0, 0x1, 0x9, 0x45,
0x6e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x49, 0x44, 0x1, 0xc, 0x0, 0x1, 0x5,
0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x1, 0xc, 0x0, 0x1, 0x6, 0x43, 0x69, 0x70,
0x68, 0x65, 0x72, 0x1, 0xa, 0x0, 0x1, 0x7, 0x45, 0x6e, 0x63, 0x6f, 0x64,
0x65, 0x64, 0x1, 0xa, 0x0, 0x0, 0x0, 0xff, 0xb1, 0xff, 0x82, 0x1, 0x5,
0x30, 0x2e, 0x30, 0x2e, 0x31, 0x1, 0x6, 0x73, 0x65, 0x63, 0x75, 0x72, 0x65,
0x1, 0x1, 0x1, 0x8, 0x38, 0x36, 0x66, 0x62, 0x33, 0x66, 0x61, 0x30, 0x2,
0xff, 0x90, 0x32, 0x38, 0x62, 0x35, 0x32, 0x66, 0x66, 0x64, 0x30, 0x34,
0x30, 0x30, 0x64, 0x64, 0x30, 0x31, 0x30, 0x30, 0x63, 0x32, 0x63, 0x33,
0x30, 0x64, 0x31, 0x35, 0x39, 0x30, 0x33, 0x62, 0x30, 0x33, 0x30, 0x63,
0x64, 0x38, 0x37, 0x39, 0x39, 0x36, 0x63, 0x66, 0x31, 0x30, 0x34, 0x37,
0x32, 0x37, 0x35, 0x61, 0x30, 0x37, 0x62, 0x30, 0x37, 0x62, 0x30, 0x34,
0x62, 0x35, 0x66, 0x62, 0x65, 0x63, 0x34, 0x65, 0x65, 0x34, 0x30, 0x66,
0x30, 0x36, 0x38, 0x38, 0x33, 0x39, 0x64, 0x33, 0x39, 0x35, 0x38, 0x61,
0x34, 0x66, 0x31, 0x38, 0x66, 0x32, 0x36, 0x35, 0x39, 0x31, 0x36, 0x61,
0x63, 0x36, 0x66, 0x37, 0x37, 0x39, 0x63, 0x33, 0x31, 0x34, 0x34, 0x35,
0x37, 0x63, 0x36, 0x66, 0x34, 0x62, 0x63, 0x61, 0x30, 0x30, 0x30, 0x63,
0x64, 0x39, 0x30, 0x34, 0x61, 0x64, 0x66, 0x33, 0x36, 0x64, 0x34, 0x39,
0x38, 0x66, 0x39, 0x64, 0x30, 0x30, 0x61, 0x32, 0x39, 0x62, 0x61, 0x30,
0x35, 0x34, 0x0}

View File

@ -0,0 +1,209 @@
package secure
type Secure struct {
Default string
Blank string `json:""`
Ignore string `json:"-"`
Named string `json:"named"`
Empty string `json:"empty"`
Omit string `json:"omit,omitempty"`
Number int `json:"number"`
Float float64 `json:"float"`
SecureDefault string `json:",secure"`
SecureBlank string `json:",secure"`
SecureIgnore string `json:"-"`
SecureNamed string `json:"named_secure,secure"`
SecureEmpty string `json:"empty_secure,secure"`
SecureOmit string `json:"omit_secure,omitempty,secure"`
SecureNumber int `json:"number_secure,secure"`
SecureFloat float64 `json:"float_secure,secure"`
}
var secObj = &Secure{
Default: "default-value",
Blank: "blank-value",
Ignore: "ignore-value",
Named: "named-value",
Empty: "",
Number: 42,
Float: 99.9,
SecureDefault: "default-secure",
SecureBlank: "blank-secure",
SecureIgnore: "ignore-secure",
SecureNamed: "named-secure",
SecureEmpty: "",
SecureNumber: 42,
SecureFloat: 99.9,
}
var secDecoded = &Secure{
Default: "default-value",
Blank: "blank-value",
Named: "named-value",
Number: 42,
Float: 99.9,
SecureDefault: "default-secure",
SecureBlank: "blank-secure",
SecureNamed: "named-secure",
SecureNumber: 42,
SecureFloat: 99.9,
}
var secSparse = &Secure{
Default: "default-value",
Blank: "blank-value",
Named: "named-value",
Number: 42,
Float: 99.9,
}
var secMap = map[string]interface{}{
"Blank": "blank-value",
"Default": "default-value",
"SecureBlank": "blank-secure",
"SecureDefault": "default-secure",
"empty": "",
"empty_secure": "",
"float": 99.9,
"float_secure": 99.9,
"named": "named-value",
"named_secure": "named-secure",
"number": 42.0,
"number_secure": 42.0,
}
var secMapSparse = map[string]interface{}{
"Blank": "blank-value",
"Default": "default-value",
"SecureBlank": "",
"SecureDefault": "",
"empty": "",
"empty_secure": "",
"float": 99.9,
"float_secure": 0.0,
"named": "named-value",
"named_secure": "",
"number": 42.0,
"number_secure": 0.0,
}
var secEncoded = []byte(`{"Default":"default-value","Blank":"blank-value","na` +
`med":"named-value","empty":"","number":42,"float":99.9,"SecureDefault":"` +
`cn86fb3fa00_24","SecureBlank":"cn86fb3fa01_24","named_secure":"cn86fb3fa` +
`02_24","empty_secure":"cn86fb3fa03_24","number_secure":"cn86fb3fa04_2","` +
`float_secure":"cn86fb3fa05_14"}`)
var secUnsealed = []byte(`{"Default":"default-value","Blank":"blank-value","n` +
`amed":"named-value","empty":"","number":42,"float":99.9,"SecureDefault":` +
`"cn86fb3fa00_24","SecureBlank":"cn86fb3fa01_24","named_secure":"cn86fb3f` +
`a02_24","empty_secure":"cn86fb3fa03_24","number_secure":"cn86fb3fa04_2",` +
`"float_secure":"cn86fb3fa05_14"}`)
var secSealed = []byte{0x6a, 0xff, 0x81, 0x3, 0x1, 0x1, 0x7, 0x50, 0x61,
0x63, 0x6b, 0x61, 0x67, 0x65, 0x1, 0xff, 0x82, 0x0, 0x1, 0x7, 0x1, 0x7,
0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x1, 0xc, 0x0, 0x1, 0x6, 0x46,
0x6f, 0x72, 0x6d, 0x61, 0x74, 0x1, 0xc, 0x0, 0x1, 0xa, 0x43, 0x6f, 0x6d,
0x70, 0x72, 0x65, 0x73, 0x73, 0x65, 0x64, 0x1, 0x2, 0x0, 0x1, 0x9, 0x45,
0x6e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x49, 0x44, 0x1, 0xc, 0x0, 0x1, 0x5,
0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x1, 0xc, 0x0, 0x1, 0x6, 0x43, 0x69, 0x70,
0x68, 0x65, 0x72, 0x1, 0xa, 0x0, 0x1, 0x7, 0x45, 0x6e, 0x63, 0x6f, 0x64,
0x65, 0x64, 0x1, 0xa, 0x0, 0x0, 0x0, 0xfe, 0x2, 0x9b, 0xff, 0x82, 0x1, 0x5,
0x30, 0x2e, 0x30, 0x2e, 0x31, 0x1, 0x6, 0x73, 0x70, 0x61, 0x72, 0x73, 0x65,
0x2, 0x8, 0x38, 0x36, 0x66, 0x62, 0x33, 0x66, 0x61, 0x30, 0x1, 0xa, 0x63,
0x6e, 0x38, 0x36, 0x66, 0x62, 0x33, 0x66, 0x61, 0x30, 0x1, 0xfe, 0x1, 0x40,
0x37, 0x62, 0x32, 0x32, 0x36, 0x33, 0x36, 0x65, 0x33, 0x38, 0x33, 0x36,
0x36, 0x36, 0x36, 0x32, 0x33, 0x33, 0x36, 0x36, 0x36, 0x31, 0x33, 0x30,
0x33, 0x30, 0x35, 0x66, 0x33, 0x32, 0x33, 0x34, 0x32, 0x32, 0x33, 0x61,
0x32, 0x32, 0x36, 0x34, 0x36, 0x35, 0x36, 0x36, 0x36, 0x31, 0x37, 0x35,
0x36, 0x63, 0x37, 0x34, 0x32, 0x64, 0x37, 0x33, 0x36, 0x35, 0x36, 0x33,
0x37, 0x35, 0x37, 0x32, 0x36, 0x35, 0x32, 0x32, 0x32, 0x63, 0x32, 0x32,
0x36, 0x33, 0x36, 0x65, 0x33, 0x38, 0x33, 0x36, 0x36, 0x36, 0x36, 0x32,
0x33, 0x33, 0x36, 0x36, 0x36, 0x31, 0x33, 0x30, 0x33, 0x31, 0x35, 0x66,
0x33, 0x32, 0x33, 0x34, 0x32, 0x32, 0x33, 0x61, 0x32, 0x32, 0x36, 0x32,
0x36, 0x63, 0x36, 0x31, 0x36, 0x65, 0x36, 0x62, 0x32, 0x64, 0x37, 0x33,
0x36, 0x35, 0x36, 0x33, 0x37, 0x35, 0x37, 0x32, 0x36, 0x35, 0x32, 0x32,
0x32, 0x63, 0x32, 0x32, 0x36, 0x33, 0x36, 0x65, 0x33, 0x38, 0x33, 0x36,
0x36, 0x36, 0x36, 0x32, 0x33, 0x33, 0x36, 0x36, 0x36, 0x31, 0x33, 0x30,
0x33, 0x32, 0x35, 0x66, 0x33, 0x32, 0x33, 0x34, 0x32, 0x32, 0x33, 0x61,
0x32, 0x32, 0x36, 0x65, 0x36, 0x31, 0x36, 0x64, 0x36, 0x35, 0x36, 0x34,
0x32, 0x64, 0x37, 0x33, 0x36, 0x35, 0x36, 0x33, 0x37, 0x35, 0x37, 0x32,
0x36, 0x35, 0x32, 0x32, 0x32, 0x63, 0x32, 0x32, 0x36, 0x33, 0x36, 0x65,
0x33, 0x38, 0x33, 0x36, 0x36, 0x36, 0x36, 0x32, 0x33, 0x33, 0x36, 0x36,
0x36, 0x31, 0x33, 0x30, 0x33, 0x33, 0x35, 0x66, 0x33, 0x32, 0x33, 0x34,
0x32, 0x32, 0x33, 0x61, 0x32, 0x32, 0x32, 0x32, 0x32, 0x63, 0x32, 0x32,
0x36, 0x33, 0x36, 0x65, 0x33, 0x38, 0x33, 0x36, 0x36, 0x36, 0x36, 0x32,
0x33, 0x33, 0x36, 0x36, 0x36, 0x31, 0x33, 0x30, 0x33, 0x34, 0x35, 0x66,
0x33, 0x32, 0x32, 0x32, 0x33, 0x61, 0x33, 0x34, 0x33, 0x32, 0x32, 0x63,
0x32, 0x32, 0x36, 0x33, 0x36, 0x65, 0x33, 0x38, 0x33, 0x36, 0x36, 0x36,
0x36, 0x32, 0x33, 0x33, 0x36, 0x36, 0x36, 0x31, 0x33, 0x30, 0x33, 0x35,
0x35, 0x66, 0x33, 0x31, 0x33, 0x34, 0x32, 0x32, 0x33, 0x61, 0x33, 0x39,
0x33, 0x39, 0x32, 0x65, 0x33, 0x39, 0x37, 0x64, 0x1, 0xfe, 0x1, 0x2b, 0x7b,
0x22, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x22, 0x3a, 0x22, 0x64,
0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x2d, 0x76, 0x61, 0x6c, 0x75, 0x65,
0x22, 0x2c, 0x22, 0x42, 0x6c, 0x61, 0x6e, 0x6b, 0x22, 0x3a, 0x22, 0x62,
0x6c, 0x61, 0x6e, 0x6b, 0x2d, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x2c,
0x22, 0x6e, 0x61, 0x6d, 0x65, 0x64, 0x22, 0x3a, 0x22, 0x6e, 0x61, 0x6d,
0x65, 0x64, 0x2d, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x2c, 0x22, 0x65,
0x6d, 0x70, 0x74, 0x79, 0x22, 0x3a, 0x22, 0x22, 0x2c, 0x22, 0x6e, 0x75,
0x6d, 0x62, 0x65, 0x72, 0x22, 0x3a, 0x34, 0x32, 0x2c, 0x22, 0x66, 0x6c,
0x6f, 0x61, 0x74, 0x22, 0x3a, 0x39, 0x39, 0x2e, 0x39, 0x2c, 0x22, 0x53,
0x65, 0x63, 0x75, 0x72, 0x65, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74,
0x22, 0x3a, 0x22, 0x63, 0x6e, 0x38, 0x36, 0x66, 0x62, 0x33, 0x66, 0x61,
0x30, 0x30, 0x5f, 0x32, 0x34, 0x22, 0x2c, 0x22, 0x53, 0x65, 0x63, 0x75,
0x72, 0x65, 0x42, 0x6c, 0x61, 0x6e, 0x6b, 0x22, 0x3a, 0x22, 0x63, 0x6e,
0x38, 0x36, 0x66, 0x62, 0x33, 0x66, 0x61, 0x30, 0x31, 0x5f, 0x32, 0x34,
0x22, 0x2c, 0x22, 0x6e, 0x61, 0x6d, 0x65, 0x64, 0x5f, 0x73, 0x65, 0x63,
0x75, 0x72, 0x65, 0x22, 0x3a, 0x22, 0x63, 0x6e, 0x38, 0x36, 0x66, 0x62,
0x33, 0x66, 0x61, 0x30, 0x32, 0x5f, 0x32, 0x34, 0x22, 0x2c, 0x22, 0x65,
0x6d, 0x70, 0x74, 0x79, 0x5f, 0x73, 0x65, 0x63, 0x75, 0x72, 0x65, 0x22,
0x3a, 0x22, 0x63, 0x6e, 0x38, 0x36, 0x66, 0x62, 0x33, 0x66, 0x61, 0x30,
0x33, 0x5f, 0x32, 0x34, 0x22, 0x2c, 0x22, 0x6e, 0x75, 0x6d, 0x62, 0x65,
0x72, 0x5f, 0x73, 0x65, 0x63, 0x75, 0x72, 0x65, 0x22, 0x3a, 0x22, 0x63,
0x6e, 0x38, 0x36, 0x66, 0x62, 0x33, 0x66, 0x61, 0x30, 0x34, 0x5f, 0x32,
0x22, 0x2c, 0x22, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x5f, 0x73, 0x65, 0x63,
0x75, 0x72, 0x65, 0x22, 0x3a, 0x22, 0x63, 0x6e, 0x38, 0x36, 0x66, 0x62,
0x33, 0x66, 0x61, 0x30, 0x35, 0x5f, 0x31, 0x34, 0x22, 0x7d, 0x0}
var secComp = []byte{0x6a, 0xff, 0x81, 0x3, 0x1, 0x1, 0x7, 0x50, 0x61,
0x63, 0x6b, 0x61, 0x67, 0x65, 0x1, 0xff, 0x82, 0x0, 0x1, 0x7, 0x1, 0x7,
0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x1, 0xc, 0x0, 0x1, 0x6, 0x46,
0x6f, 0x72, 0x6d, 0x61, 0x74, 0x1, 0xc, 0x0, 0x1, 0xa, 0x43, 0x6f, 0x6d,
0x70, 0x72, 0x65, 0x73, 0x73, 0x65, 0x64, 0x1, 0x2, 0x0, 0x1, 0x9, 0x45,
0x6e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x49, 0x44, 0x1, 0xc, 0x0, 0x1, 0x5,
0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x1, 0xc, 0x0, 0x1, 0x6, 0x43, 0x69, 0x70,
0x68, 0x65, 0x72, 0x1, 0xa, 0x0, 0x1, 0x7, 0x45, 0x6e, 0x63, 0x6f, 0x64,
0x65, 0x64, 0x1, 0xa, 0x0, 0x0, 0x0, 0xfe, 0x1, 0x9f, 0xff, 0x82, 0x1, 0x5,
0x30, 0x2e, 0x30, 0x2e, 0x31, 0x1, 0x6, 0x73, 0x70, 0x61, 0x72, 0x73, 0x65,
0x1, 0x1, 0x1, 0x8, 0x38, 0x36, 0x66, 0x62, 0x33, 0x66, 0x61, 0x30, 0x1,
0xa, 0x63, 0x6e, 0x38, 0x36, 0x66, 0x62, 0x33, 0x66, 0x61, 0x30, 0x1, 0xff,
0xcc, 0x32, 0x38, 0x62, 0x35, 0x32, 0x66, 0x66, 0x64, 0x30, 0x34, 0x30,
0x30, 0x63, 0x64, 0x30, 0x32, 0x30, 0x30, 0x32, 0x34, 0x30, 0x34, 0x37,
0x62, 0x32, 0x32, 0x36, 0x33, 0x36, 0x65, 0x33, 0x38, 0x33, 0x36, 0x36,
0x36, 0x36, 0x32, 0x33, 0x33, 0x36, 0x36, 0x36, 0x31, 0x33, 0x30, 0x33,
0x30, 0x35, 0x66, 0x33, 0x32, 0x33, 0x34, 0x32, 0x32, 0x33, 0x61, 0x32,
0x32, 0x36, 0x34, 0x36, 0x35, 0x36, 0x36, 0x36, 0x31, 0x37, 0x35, 0x36,
0x63, 0x37, 0x34, 0x32, 0x64, 0x37, 0x33, 0x36, 0x35, 0x36, 0x33, 0x37,
0x35, 0x37, 0x32, 0x36, 0x35, 0x32, 0x32, 0x32, 0x63, 0x33, 0x31, 0x36,
0x32, 0x36, 0x63, 0x36, 0x31, 0x36, 0x65, 0x36, 0x62, 0x33, 0x32, 0x36,
0x65, 0x36, 0x31, 0x36, 0x64, 0x36, 0x35, 0x36, 0x34, 0x33, 0x33, 0x33,
0x34, 0x35, 0x66, 0x33, 0x32, 0x32, 0x32, 0x33, 0x61, 0x33, 0x34, 0x33,
0x32, 0x33, 0x35, 0x35, 0x66, 0x33, 0x31, 0x33, 0x34, 0x32, 0x32, 0x33,
0x61, 0x33, 0x39, 0x33, 0x39, 0x32, 0x65, 0x33, 0x39, 0x37, 0x64, 0x30,
0x38, 0x30, 0x30, 0x33, 0x36, 0x39, 0x63, 0x35, 0x64, 0x66, 0x38, 0x30,
0x30, 0x31, 0x38, 0x32, 0x65, 0x61, 0x30, 0x30, 0x61, 0x33, 0x30, 0x33,
0x36, 0x31, 0x34, 0x37, 0x64, 0x34, 0x35, 0x36, 0x31, 0x62, 0x31, 0x36,
0x32, 0x61, 0x38, 0x32, 0x37, 0x39, 0x65, 0x37, 0x33, 0x33, 0x33, 0x32,
0x31, 0x1, 0xff, 0xa3, 0x28, 0xb5, 0x2f, 0xfd, 0x44, 0x0, 0x2b, 0x0, 0xa5,
0x4, 0x0, 0x22, 0x7, 0x18, 0x1b, 0x70, 0x69, 0x15, 0x20, 0x8a, 0xa5, 0xa6,
0x46, 0x5a, 0x76, 0x37, 0xad, 0xc5, 0x63, 0x67, 0xc5, 0x71, 0x42, 0x87,
0x58, 0x23, 0x31, 0x6c, 0xbf, 0xfe, 0xff, 0xbd, 0x8d, 0x2b, 0x84, 0x5,
0x9e, 0x98, 0x4a, 0x52, 0x82, 0x21, 0x57, 0x89, 0x21, 0xa4, 0x2e, 0x69,
0x4e, 0x30, 0x68, 0x65, 0x9b, 0xa8, 0xac, 0xe1, 0x67, 0x1, 0xd6, 0xca,
0x43, 0x3d, 0xb0, 0xfb, 0x49, 0x2a, 0xdf, 0x6c, 0x7b, 0xb1, 0x7f, 0xfe,
0xfc, 0x2, 0x22, 0xde, 0xe6, 0x97, 0x7d, 0xda, 0x4c, 0x2b, 0xb7, 0xe7,
0x99, 0x56, 0x26, 0xfc, 0x7c, 0x23, 0xab, 0xe2, 0x58, 0x9e, 0x87, 0x1c,
0xd5, 0x6d, 0xe1, 0x8c, 0x14, 0x0, 0x42, 0x29, 0x80, 0x19, 0x97, 0x88,
0x52, 0xe0, 0x18, 0xc, 0x60, 0x38, 0xa0, 0x71, 0x30, 0x2, 0xc0, 0x70, 0x43,
0x2f, 0x24, 0x71, 0xd8, 0x87, 0x16, 0x0, 0x9f, 0xd0, 0x1d, 0x7a, 0x20, 0x0,
0x51, 0x52, 0x35, 0x87, 0xad, 0xa0, 0xac, 0x95, 0xb1, 0xe5, 0x2c, 0x4d,
0x87, 0xf2, 0x5, 0x61, 0xe4, 0xde, 0x51, 0x0}

View File

@ -0,0 +1,68 @@
package packager
import (
"bytes"
"encoding/gob"
)
// EncodePackage returns a valid binary enc package for storage.
func EncodePackage(encoderID, token string, cipher, encoded []byte, compressed bool) ([]byte, error) {
if encoderID == "" {
}
format := Secure
// are we sparse?
sparse := len(encoded) >= minSparse
if sparse {
format = Sparse
}
// start the package
pkg := &Package{
Version: currentVer.String(),
Format: format,
Compressed: compressed,
EncoderID: encoderID,
Token: token,
Cipher: cipher,
Encoded: encoded,
}
if err := pkg.Valid(); err != nil {
return nil, err
}
return encode(pkg)
}
func encode(pkg *Package) ([]byte, error) {
if err := pkg.Valid(); err != nil {
return nil, err
}
b := bytes.Buffer{}
e := gob.NewEncoder(&b)
if err := e.Encode(pkg); err != nil {
return nil, err
}
return b.Bytes(), nil
}
// DecodePackage takes packaged data and returns the ciphertext and encoding block.
func DecodePackage(bytes []byte) (*Package, error) {
pkg, err := decode(bytes)
if err != nil {
return nil, err
}
// check the package ver
if err = pkg.checkVersion(); err != nil {
return nil, err
}
if err = pkg.Valid(); err != nil {
return nil, err
}
return pkg, err
}
func decode(data []byte) (*Package, error) {
pkg := &Package{}
buf := bytes.Buffer{}
buf.Write(data)
d := gob.NewDecoder(&buf)
return pkg, d.Decode(pkg)
}

View File

@ -0,0 +1,143 @@
package packager
import (
"errors"
"fmt"
"github.com/hashicorp/go-version"
"github.com/jrapoport/chestnut/encoding/json/encoders"
)
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
}

View File

@ -0,0 +1,216 @@
package packager
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
)
const (
noComp = false
comp = true
ver = Version
)
var (
empty = ""
id = "c1ff7755"
token = "lookup-token"
sec = []byte("AAAAB3NzaC1yc2EAAAABJQAAAQB/nAmOjTmezNUDKYvEeIRf2Ynw")
enc = []byte("{\"test_object\":{\"" + token + "\":0}}")
zstd = []byte("KLUv/QQAAQEAeyJ0ZXN0X29iamVjdCI6eyJjbmMxZmY3NzU1IjowfX1hE1Nm")
emptyZstd = []byte("KLUv/QQACQAAII1jaLY=")
badVer = "999.999.999"
badFormat = Format("invalid")
badData = []byte("==")
badZstd = []byte("bm9wZQ")
comps = []bool{noComp, comp}
secIns = [][]byte{[]byte(nil), []byte(empty), badData, badZstd, sec, emptyZstd, zstd}
encIns = [][]byte{badData, enc, badZstd, emptyZstd, zstd}
)
type TestCase struct {
ver string
fmt Format
id string
token string
comp bool
sec []byte
enc []byte
wrapErr assert.ErrorAssertionFunc
unwrapErr assert.ErrorAssertionFunc
}
var tests = []TestCase{
// malformed packages
{empty, "", empty, empty, noComp, nil, nil,
assert.Error, assert.Error},
{"0", "", empty, empty, noComp, nil, nil,
assert.Error, assert.Error},
{badVer, "", empty, empty, noComp, nil, nil,
assert.Error, assert.Error},
{ver, "", empty, empty, noComp, nil, nil,
assert.Error, assert.Error},
{ver, badFormat, empty, empty, noComp, nil, nil,
assert.Error, assert.Error},
{ver, Secure, id, empty, noComp, nil, nil,
assert.Error, assert.Error},
{ver, Sparse, empty, empty, noComp, nil, nil,
assert.Error, assert.Error},
// valid packages
{ver, Secure, id, empty, noComp, sec, nil,
assert.NoError, assert.NoError},
{ver, Sparse, id, token, noComp, sec, enc,
assert.NoError, assert.NoError},
// valid compressed packages
{ver, Secure, id, empty, comp, zstd, nil,
assert.NoError, assert.NoError},
{ver, Sparse, id, token, comp, zstd, zstd,
assert.NoError, assert.NoError},
}
func genSecureTestCases() {
for _, c := range comps {
for secIdx, secIn := range secIns {
wrapErr := assert.Error
unwrapErr := assert.Error
if c {
if secIdx >= 4 {
wrapErr = assert.NoError
unwrapErr = assert.NoError
}
} else {
if secIdx >= 3 {
wrapErr = assert.NoError
unwrapErr = assert.NoError
}
}
tc := TestCase{
ver: ver,
fmt: Secure,
id: id,
comp: c,
sec: secIn,
wrapErr: wrapErr,
unwrapErr: unwrapErr,
}
tests = append(tests, tc)
}
}
}
func genSparseTestCases() {
for _, c := range comps {
for secIdx, secIn := range secIns {
for encIdx, encIn := range encIns {
wrapErr := assert.Error
unwrapErr := assert.Error
if c {
if encIdx == 1 {
continue
} else if secIdx >= 4 && encIdx > 2 {
wrapErr = assert.NoError
unwrapErr = assert.NoError
}
} else {
if secIdx >= 3 && encIdx == 1 {
wrapErr = assert.NoError
unwrapErr = assert.NoError
}
}
tc := TestCase{
ver: ver,
fmt: Sparse,
id: id,
token: token,
comp: c,
sec: secIn,
enc: encIn,
wrapErr: wrapErr,
unwrapErr: unwrapErr,
}
tests = append(tests, tc)
}
}
}
}
type PackageTestSuite struct {
suite.Suite
}
func TestStore(t *testing.T) {
suite.Run(t, new(PackageTestSuite))
}
func (ts *PackageTestSuite) SetupSuite() {
genSecureTestCases()
genSparseTestCases()
}
func (ts *PackageTestSuite) TestPackage_Encode() {
for _, test := range tests {
bytes, err := EncodePackage(test.id, test.token, test.sec, test.enc, test.comp)
test.wrapErr(ts.T(), err)
if err == nil {
assert.NotEmpty(ts.T(), bytes)
} else {
assert.Empty(ts.T(), bytes)
}
}
}
func (ts *PackageTestSuite) TestPackage_Decode() {
for _, test := range tests {
testPkg := &Package{
Version: test.ver,
Format: test.fmt,
Compressed: test.comp,
EncoderID: test.id,
Token: test.token,
Cipher: test.sec,
Encoded: test.enc,
}
bytes, err := encode(testPkg)
pkg, err := DecodePackage(bytes)
test.unwrapErr(ts.T(), err)
if err != nil {
assert.Nil(ts.T(), pkg)
} else {
assertPackage(ts.T(), test, pkg)
}
}
}
func (ts *PackageTestSuite) TestPackage() {
for _, test := range tests {
bytes, err := EncodePackage(test.id, test.token, test.sec, test.enc, test.comp)
test.wrapErr(ts.T(), err, string(bytes))
if err != nil {
assert.Empty(ts.T(), string(bytes))
continue
} else {
assert.NotEmpty(ts.T(), string(bytes))
}
pkg, err := DecodePackage(bytes)
test.unwrapErr(ts.T(), err)
if err != nil {
assert.Nil(ts.T(), pkg)
} else {
assertPackage(ts.T(), test, pkg)
}
}
}
func assertPackage(t *testing.T, test TestCase, pkg *Package) {
assert.NotNil(t, pkg)
assert.NoError(t, pkg.Valid())
assert.Equal(t, test.ver, pkg.Version)
assert.Equal(t, test.fmt, pkg.Format)
assert.Equal(t, test.comp, pkg.Compressed)
assert.Equal(t, test.id, pkg.EncoderID)
assert.Equal(t, test.token, pkg.Token)
assert.Equal(t, test.sec, pkg.Cipher)
assert.Equal(t, test.enc, pkg.Encoded)
}

View File

@ -0,0 +1,63 @@
package json
import (
"reflect"
"testing"
"github.com/jrapoport/chestnut/encoding/compress"
"github.com/jrapoport/chestnut/encoding/json/encoders/secure"
"github.com/stretchr/testify/assert"
)
var (
encrypt = secure.PassthroughEncryption
decrypt = secure.PassthroughDecryption
compOpt = secure.WithCompression(compress.Zstd)
sparseOpt = secure.SparseDecode()
)
func TestSecureEncoding(t *testing.T) {
secureObj := &Family{}
bytes, err := SecureMarshal(family, encrypt)
assert.NoError(t, err)
assert.Equal(t, familyEnc, bytes)
err = SecureUnmarshal(bytes, secureObj, decrypt)
assertDecoding(t, familyDec, secureObj, err)
}
func TestCompressedEncoding(t *testing.T) {
secureObj := &Family{}
bytes, err := SecureMarshal(family, encrypt, compOpt)
assert.NoError(t, err)
assert.Equal(t, familyComp, bytes)
err = SecureUnmarshal(bytes, secureObj, decrypt, compOpt)
assertDecoding(t, familyDec, secureObj, err)
}
func TestSparseDecoding(t *testing.T) {
sparseObj := &Family{}
bytes, err := SecureMarshal(family, encrypt)
assert.NoError(t, err)
assert.Equal(t, familyEnc, bytes)
err = SecureUnmarshal(bytes, sparseObj, decrypt, sparseOpt)
assertDecoding(t, familySpr, sparseObj, err)
}
func TestCompressedSparseDecoding(t *testing.T) {
sparseObj := &Family{}
bytes, err := SecureMarshal(family, encrypt, compOpt)
assert.NoError(t, err)
assert.Equal(t, familyComp, bytes)
err = SecureUnmarshal(bytes, sparseObj, decrypt, compOpt, sparseOpt)
assertDecoding(t, familySpr, sparseObj, err)
}
func assertDecoding(t *testing.T, expected, actual interface{}, err error) {
e := assert.NoError(t, err)
if !e {
t.Fatal(err)
}
assert.Equal(t, expected, actual)
deep := reflect.DeepEqual(expected, actual)
assert.True(t, deep, "values are not deep equal")
}

1027
encoding/json/tags_test.go Normal file

File diff suppressed because it is too large Load Diff

83
encoding/tags/tags.go Normal file
View File

@ -0,0 +1,83 @@
package tags
import "strings"
const (
// TODO: Add support for chestnut & gorm struct field tags
// ChestnutTag = "cn"
// GORMTag = "gorm"
// JSONTag is the default JSON struct tag to use.
JSONTag = "json"
// SecureOption is the tag option to enable sparse encryption of a struct field.
SecureOption = "secure"
// HashOption is the tag option to hash a struct field of type string. Defaults to SHA256.
HashOption = "hash"
jsonSeparator = ","
jsonNameIgnore = "-"
)
// Hash provides a type for supported hash function names
type Hash string
const (
// HashNone is used to indicate no has function was found in the tag options.
HashNone Hash = ""
// HashSHA256 sets the HashOption to use sha256. This is the default.
// TODO: support parsing this from the struct field tag hash option e.g. `...,hash=md5"`
HashSHA256 = "sha256"
)
func (h Hash) String() string {
return string(h)
}
// ParseJSONTag returns the name and options for a JSON struct field tag.
func ParseJSONTag(tag string) (name string, opts []string) {
parts := strings.Split(tag, jsonSeparator)
switch len(parts) {
case 0:
return "", []string{}
case 1:
return parts[0], []string{}
default:
if IgnoreField(parts[0]) {
return parts[0], []string{}
}
return parts[0], parts[1:]
}
}
// IgnoreField checks the name to see if field should be ignored.
func IgnoreField(name string) bool {
return name == jsonNameIgnore
}
// HasOption checks to see if the tag options contain a specific option.
func HasOption(opts []string, opt string) bool {
for _, s := range opts {
if s == opt {
return true
}
}
return false
}
// HashName checks to see if the hash option is set. The struct field *MUST BE*
// type string and capable of holding the decoded hash as a string. If no hash option
// is found it will return HashNone. Defaults to HashSHA256 (sha256).
func HashName(opts []string) Hash {
if HasOption(opts, HashOption) {
return HashSHA256
}
return HashNone // do not hash
}
// IsSecure checks to see if the secure option is set.
func IsSecure(opts []string) bool {
return HasOption(opts, SecureOption)
}

105
encoding/tags/tags_test.go Normal file
View File

@ -0,0 +1,105 @@
package tags
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestParseJSONTag(t *testing.T) {
tests := []struct {
tag string
name string
opts []string
}{
{"", "", []string{}},
{"-", "-", []string{}},
{"test", "test", []string{}},
{",secure", "", []string{SecureOption}},
{"-,secure", "-", []string{}},
{"test,secure", "test", []string{SecureOption}},
{",secure,hash", "", []string{SecureOption, HashOption}},
{"-,secure,hash", "-", []string{}},
{"test,secure,hash", "test", []string{SecureOption, HashOption}},
{",secure,hash,omitempty", "", []string{SecureOption, HashOption, "omitempty"}},
{"-,secure,hash,omitempty", "-", []string{}},
{"test,secure,hash,omitempty", "test", []string{SecureOption, HashOption, "omitempty"}},
}
for _, test := range tests {
name, opts := ParseJSONTag(test.tag)
assert.Equal(t, test.name, name)
assert.ElementsMatch(t, test.opts, opts)
}
}
func TestIgnoreField(t *testing.T) {
tests := []struct {
tag string
assertBool assert.BoolAssertionFunc
}{
{"", assert.False},
{"-", assert.True},
{"test", assert.False},
{",secure", assert.False},
{"-,secure", assert.True},
{"test,secure", assert.False},
{",secure,hash", assert.False},
{"-,secure,hash", assert.True},
{"test,secure,hash", assert.False},
{",secure,hash,omitempty", assert.False},
{"-,secure,hash,omitempty", assert.True},
{"test,secure,hash,omitempty", assert.False},
}
for _, test := range tests {
name, _ := ParseJSONTag(test.tag)
test.assertBool(t, IgnoreField(name), "unexpected")
}
}
func TestHasOption(t *testing.T) {
tests := []struct {
opts []string
opt string
has bool
}{
{[]string{}, "", false},
{[]string{}, SecureOption, false},
{[]string{HashOption}, SecureOption, false},
{[]string{SecureOption}, SecureOption, true},
{nil, SecureOption, false},
}
for _, test := range tests {
has := HasOption(test.opts, test.opt)
assert.Equal(t, test.has, has)
}
}
func TestHashFunction(t *testing.T) {
tests := []struct {
opts []string
name Hash
}{
{nil, HashNone},
{[]string{}, HashNone},
{[]string{HashOption}, HashSHA256},
}
for _, test := range tests {
name := HashName(test.opts)
assert.Equal(t, test.name, name)
}
}
func TestIsSecure(t *testing.T) {
tests := []struct {
opts []string
is bool
}{
{nil, false},
{[]string{}, false},
{[]string{SecureOption}, true},
}
for _, test := range tests {
is := IsSecure(test.opts)
assert.Equal(t, test.is, is)
}
}

75
encryptor/aes.go Normal file
View File

@ -0,0 +1,75 @@
package encryptor
import (
"fmt"
"github.com/jrapoport/chestnut/encryptor/aes"
"github.com/jrapoport/chestnut/encryptor/crypto"
)
// AESEncryptor is an encryptor that supports the
// following AES keyLen lengths & cipher modes:
// - AES128-CFB, AES192-CFB, AES256-CFB
// - AES128-CTR, AES192-CTR, AES256-CTR
// - AES128-GCM, AES192-GCM, AES256-GCM
type AESEncryptor struct {
secret crypto.Secret
keyLen crypto.KeyLen
mode crypto.Mode
}
var _ crypto.Encryptor = (*AESEncryptor)(nil)
// NewAESEncryptor returns a new AESEncryptor configured
// with an AES keyLen length and mode for a secret.
func NewAESEncryptor(keyLen crypto.KeyLen, mode crypto.Mode, secret crypto.Secret) *AESEncryptor {
ae := new(AESEncryptor)
ae.secret = secret
ae.keyLen = keyLen
ae.mode = mode
return ae
}
// ID returns the id of the encryptor (secret) that
// was used to encrypt the data (for tracking).
func (e *AESEncryptor) ID() string {
return e.secret.ID()
}
// Name returns the name of the configured AES encryption cipher
// in following format "[cipher][keyLen length]-[mode]" e.g. "aes192-ctr".
func (e *AESEncryptor) Name() string {
return crypto.CipherName("aes", e.keyLen, e.mode)
}
// Encrypt returns the plain data encrypted with the configured cipher mode and secret.
func (e *AESEncryptor) Encrypt(plaintext []byte) ([]byte, error) {
var encryptCall aes.CipherCall
switch e.mode {
case aes.CFB:
encryptCall = aes.EncryptCFB
case aes.CTR:
encryptCall = aes.EncryptCTR
case aes.GCM:
encryptCall = aes.EncryptGCM
default:
return nil, fmt.Errorf("unsupported encryption cipher mode: %s", e.mode)
}
return encryptCall(e.keyLen, e.secret.Open(), plaintext)
}
// Decrypt returns the cipher data decrypted with the configured cipher mode and secret.
func (e *AESEncryptor) Decrypt(ciphertext []byte) ([]byte, error) {
var decryptCall aes.CipherCall
switch e.mode {
case aes.CFB:
decryptCall = aes.DecryptCFB
case aes.CTR:
decryptCall = aes.DecryptCTR
case aes.GCM:
decryptCall = aes.DecryptGCM
default:
return nil, fmt.Errorf("unsupported decryption cipher mode: %s", e.mode)
}
return decryptCall(e.keyLen, e.secret.Open(), ciphertext)
}

89
encryptor/aes/aes.go Normal file
View File

@ -0,0 +1,89 @@
package aes
import (
"crypto/aes"
"crypto/cipher"
"errors"
"github.com/jrapoport/chestnut/encryptor/crypto"
)
// currently supported modes
const (
CFB crypto.Mode = "cfb"
CTR = "ctr"
GCM = "gcm"
)
// CipherCall is function the prototype for the encryption and decryption.
type CipherCall func(length crypto.KeyLen, secret, data []byte) ([]byte, error)
// cipherTransform preforms the encryption or decryption and returns the result.
type cipherTransform func(header crypto.Header, block cipher.Block, data []byte) ([]byte, error)
// encrypt is a generalized AES decryption function that takes plaintext and return a serialized Entry.
func encrypt(keyLen crypto.KeyLen, secret, plaintext []byte, header crypto.Header, encryptT cipherTransform) ([]byte, error) {
if plaintext == nil || len(plaintext) <= 0 {
return nil, errors.New("invalid plain data")
}
// create the cipher key
key, err := crypto.NewCipherKey(keyLen, secret, header.Salt)
if err != nil {
return nil, err
}
// create the cipher block
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
ciphertext, err := encryptT(header, block, plaintext)
if err != nil {
return nil, err
}
// check the result
data := crypto.NewData(header, ciphertext)
if err = isDataValid(data); err != nil {
return nil, err
}
// encode the encrypted data and return the result
return crypto.EncodeData(data)
}
// decrypt is a generalized AES decryption function that takes a serialized Entry and returns plaintext.
func decrypt(keyLen crypto.KeyLen, secret, ciphertext []byte, decryptT cipherTransform) ([]byte, error) {
if ciphertext == nil || len(ciphertext) <= 0 {
return nil, errors.New("invalid cipher data")
}
// decode the encrypted data
data, err := crypto.DecodeData(ciphertext)
if err != nil {
return nil, err
}
// check the encoding
if err = isDataValid(data); err != nil {
return nil, err
}
// get the cipher key
key, err := crypto.NewCipherKey(keyLen, secret, data.Salt)
if err != nil {
return nil, err
}
// get the cipher block
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
// decrypt the data
return decryptT(data.Header, block, data.Bytes)
}
func isDataValid(data crypto.Data) error {
if err := data.Valid(); err != nil {
return err
}
// check the iv
if data.IV != nil && len(data.IV) < aes.BlockSize {
return errors.New("invalid iv")
}
return nil
}

70
encryptor/aes/aes_test.go Normal file
View File

@ -0,0 +1,70 @@
package aes
import (
"testing"
"github.com/jrapoport/chestnut/encryptor/crypto"
"github.com/stretchr/testify/assert"
)
func TestAllCiphers(t *testing.T) {
ciphers := []struct {
name crypto.Mode
encryptCall CipherCall
decryptCall CipherCall
}{
{CFB, EncryptCFB, DecryptCFB},
{CTR, EncryptCTR, DecryptCTR},
{GCM, EncryptGCM, DecryptGCM},
}
for _, cipher := range ciphers {
t.Run(cipher.name.String(), func(t *testing.T) {
testCipher(t, cipher.encryptCall, cipher.decryptCall)
})
}
}
func testCipher(t *testing.T, encryptCall, decryptCall CipherCall) {
const (
secret = "i-am-a-good-secret"
plaintext = "Lorem ipsum dolor sit amet"
)
lens := []crypto.KeyLen{
crypto.Key128,
crypto.Key192,
crypto.Key256,
}
for _, l := range lens {
t.Run(l.String(), func(t *testing.T) {
encrypted, err := encryptCall(l, []byte(secret), []byte(plaintext))
assert.NoError(t, err)
assert.NotEmpty(t, encrypted)
data, err := crypto.DecodeData(encrypted)
assert.NoError(t, err)
assert.NotNil(t, data)
assert.NoError(t, isDataValid(data))
assert.Equal(t, l, data.KeyLen)
decrypted, err := decryptCall(l, []byte(secret), encrypted)
assert.NoError(t, err)
assert.NotEmpty(t, decrypted)
assert.Equal(t, plaintext, string(decrypted))
})
}
// bad plain data
_, err := encryptCall(crypto.Key256, []byte(secret), nil)
assert.Error(t, err)
// mismatch
e, _ := encryptCall(crypto.Key256, []byte(secret), []byte(plaintext))
d, _ := decryptCall(crypto.Key128, []byte(secret), e)
assert.NotEqual(t, plaintext, string(d))
// bad cipher data
badData := [][]byte{
nil,
[]byte(""),
[]byte("bad"),
}
for _, bd := range badData {
_, err = decryptCall(crypto.Key256, []byte(secret), bd)
assert.Error(t, err)
}
}

24
encryptor/aes/cfb.go Normal file
View File

@ -0,0 +1,24 @@
package aes
import (
"crypto/cipher"
"github.com/jrapoport/chestnut/encryptor/crypto"
)
var (
_ CipherCall = EncryptCFB // EncryptCFB conforms to CipherCall
_ CipherCall = DecryptCFB // DecryptCFB conforms to CipherCall
)
// EncryptCFB supports AES128-CFB, AES192-CFB, and AES256-CFB encryption.
func EncryptCFB(length crypto.KeyLen, secret, plaintext []byte) ([]byte, error) {
// encrypt the data
return xorStreamEncrypt(length, CFB, secret, plaintext, cipher.NewCFBEncrypter)
}
// DecryptCFB supports AES128-CFB, AES192-CFB, and AES256-CFB decryption.
func DecryptCFB(length crypto.KeyLen, secret, ciphertext []byte) ([]byte, error) {
// decrypt the data
return xorStreamDecrypt(length, CFB, secret, ciphertext, cipher.NewCFBDecrypter)
}

View File

@ -0,0 +1,7 @@
package aes
import "testing"
func TestCipherCFB(t *testing.T) {
testCipher(t, EncryptCFB, DecryptCFB)
}

24
encryptor/aes/ctr.go Normal file
View File

@ -0,0 +1,24 @@
package aes
import (
"crypto/cipher"
"github.com/jrapoport/chestnut/encryptor/crypto"
)
var (
_ CipherCall = EncryptCTR // EncryptCTR conforms to CipherCall
_ CipherCall = DecryptCTR // DecryptCTR conforms to CipherCall
)
// EncryptCTR supports AES128-CTR, AES192-CTR, and AES256-CTR encryption.
func EncryptCTR(length crypto.KeyLen, secret, plaintext []byte) ([]byte, error) {
// encrypt the data
return xorStreamEncrypt(length, CTR, secret, plaintext, cipher.NewCTR)
}
// DecryptCTR supports AES128-CTR, AES192-CTR, and AES256-CTR decryption.
func DecryptCTR(length crypto.KeyLen, secret, ciphertext []byte) ([]byte, error) {
// decrypt the data
return xorStreamDecrypt(length, CTR, secret, ciphertext, cipher.NewCTR)
}

View File

@ -0,0 +1,7 @@
package aes
import "testing"
func TestCipherCTR(t *testing.T) {
testCipher(t, EncryptCTR, DecryptCTR)
}

60
encryptor/aes/gcm.go Normal file
View File

@ -0,0 +1,60 @@
package aes
import (
"crypto/cipher"
"github.com/jrapoport/chestnut/encryptor/crypto"
)
var (
_ CipherCall = EncryptGCM // EncryptGCM conforms to CipherCall
_ CipherCall = DecryptGCM // DecryptGCM conforms to CipherCall
)
// newGMCHeader returns a header containing a nonce suitable for a gcm cipher.
func newGMCHeader(keyLen crypto.KeyLen) (crypto.Header, error) {
salt, err := crypto.MakeSalt()
if err != nil {
return crypto.Header{}, err
}
nonce, err := crypto.MakeNonce()
if err != nil {
return crypto.Header{}, err
}
return crypto.NewHeader("aes", keyLen, GCM, salt, nil, nonce)
}
// EncryptGCM supports AES128-GCM, AES192-GCM, and AES256-GCM encryption.
func EncryptGCM(keyLen crypto.KeyLen, secret, plaintext []byte) ([]byte, error) {
// create the header
header, err := newGMCHeader(keyLen)
if err != nil {
return nil, err
}
// seal the data with gcms
sealData := func(_ crypto.Header, block cipher.Block, _ []byte) ([]byte, error) {
// create the AHEAD
gcm, gcmErr := cipher.NewGCM(block)
if gcmErr != nil {
return nil, gcmErr
}
// encrypt the data
return gcm.Seal(nil, header.Nonce, plaintext, nil), nil
}
return encrypt(keyLen, secret, plaintext, header, sealData)
}
// DecryptGCM supports AES128-GCM, AES192-GCM, and AES256-GCM decryption.
func DecryptGCM(keyLen crypto.KeyLen, secret, ciphertext []byte) ([]byte, error) {
// open the data with gcm
openData := func(header crypto.Header, block cipher.Block, data []byte) ([]byte, error) {
// create the AHEAD
gcm, err := cipher.NewGCM(block)
if err != nil {
return nil, err
}
// decrypt the data
return gcm.Open(nil, header.Nonce, data, nil)
}
return decrypt(keyLen, secret, ciphertext, openData)
}

View File

@ -0,0 +1,7 @@
package aes
import "testing"
func TestCipherGCM(t *testing.T) {
testCipher(t, EncryptGCM, DecryptGCM)
}

55
encryptor/aes/stream.go Normal file
View File

@ -0,0 +1,55 @@
package aes
import (
"crypto/aes"
"crypto/cipher"
"github.com/jrapoport/chestnut/encryptor/crypto"
)
type streamCipher func(block cipher.Block, iv []byte) cipher.Stream
// newStreamHeader returns a generic header suitable for aes stream ciphers that require an iv.
func newStreamHeader(keyLen crypto.KeyLen, mode crypto.Mode) (crypto.Header, error) {
salt, err := crypto.MakeRand(crypto.SaltLength)
if err != nil {
return crypto.Header{}, err
}
iv, err := crypto.MakeRand(aes.BlockSize)
if err != nil {
return crypto.Header{}, err
}
return crypto.NewHeader("aes", keyLen, mode, salt, iv, nil)
}
// xorStreamEncrypt is a generic function for AES XOR stream encryption ciphers.
func xorStreamEncrypt(keyLen crypto.KeyLen, mode crypto.Mode, secret,
plaintext []byte, newEncryptor streamCipher) ([]byte, error) {
// create the header
header, err := newStreamHeader(keyLen, mode)
if err != nil {
return nil, err
}
// encrypt the data
encryptStream := func(_ crypto.Header, block cipher.Block, _ []byte) ([]byte, error) {
ciphertext := make([]byte, len(plaintext))
stream := newEncryptor(block, header.IV)
stream.XORKeyStream(ciphertext, plaintext)
return ciphertext, nil
}
return encrypt(keyLen, secret, plaintext, header, encryptStream)
}
// xorStreamDecrypt is a generic function for AES XOR stream decryption ciphers.
func xorStreamDecrypt(keyLen crypto.KeyLen, _ crypto.Mode, secret,
ciphertext []byte, newDecrypter streamCipher) ([]byte, error) {
// decrypt the data
var decryptStream = func(header crypto.Header, block cipher.Block, data []byte) ([]byte, error) {
plaintext := make([]byte, len(data))
stream := newDecrypter(block, header.IV)
stream.XORKeyStream(plaintext, data)
// return the plain data
return plaintext, nil
}
return decrypt(keyLen, secret, ciphertext, decryptStream)
}

87
encryptor/aes_test.go Normal file
View File

@ -0,0 +1,87 @@
package encryptor
import (
"fmt"
"testing"
"github.com/google/uuid"
"github.com/jrapoport/chestnut/encryptor/aes"
"github.com/jrapoport/chestnut/encryptor/crypto"
"github.com/stretchr/testify/assert"
)
const testPlainText = "Lorem ipsum dolor sit amet"
var (
textSecret = crypto.TextSecret("i-am-a-good-secret")
managedSecret = crypto.NewManagedSecret(uuid.New().String(), "i-am-a-managed-secret")
secureSecret = crypto.NewSecureSecret(uuid.New().String(), func(s crypto.Secret) []byte {
return []byte(s.ID())
})
)
func testAESEncryptor(t *testing.T, secret crypto.Secret, keyLen crypto.KeyLen, mode crypto.Mode) {
ae := &AESEncryptor{secret, keyLen, mode}
assert.Equal(t, secret.ID(), ae.ID())
assert.Equal(t, crypto.CipherName("aes", keyLen, mode), ae.Name())
e, err := ae.Encrypt([]byte(testPlainText))
assert.NoError(t, err)
assert.NotEmpty(t, e)
d, err := ae.Decrypt(e)
assert.NoError(t, err)
assert.NotEmpty(t, d)
assert.Equal(t, testPlainText, string(d))
}
func TestAESEncryptor(t *testing.T) {
secrets := []struct {
name string
crypto.Secret
}{
{"TextSecret", textSecret},
{"ManagedSecret", managedSecret},
{"SecureSecret", secureSecret},
}
modes := []crypto.Mode{
aes.CFB,
aes.CTR,
aes.GCM,
}
keyLens := []crypto.KeyLen{
crypto.Key128,
crypto.Key192,
crypto.Key256,
}
testSecrets := func(t *testing.T, keyLen crypto.KeyLen, mode crypto.Mode) {
for _, secret := range secrets {
t.Run(secret.name, func(t *testing.T) {
testAESEncryptor(t, secret, keyLen, mode)
})
}
}
testKeyLens := func(t *testing.T, mode crypto.Mode) {
for _, keyLen := range keyLens {
t.Run(keyLen.String(), func(t *testing.T) {
testSecrets(t, keyLen, mode)
})
}
}
for _, mode := range modes {
t.Run(mode.String(), func(t *testing.T) {
testKeyLens(t, mode)
})
}
// load a bad cipher
const invalidMode = "Invalid_Cipher_Mode"
ae := &AESEncryptor{textSecret, crypto.Key128, invalidMode}
t.Run(fmt.Sprintf("%s_%s", invalidMode, "Encrypt"), func(t *testing.T) {
// try to encrypt with a bad cipher
_, err := ae.Encrypt([]byte(testPlainText))
assert.Error(t, err)
})
t.Run(fmt.Sprintf("%s_%s", invalidMode, "Decrypt"), func(t *testing.T) {
// try to decrypt with a bad cipher
_, err := ae.Decrypt([]byte(testPlainText))
assert.Error(t, err)
})
}

85
encryptor/chain.go Normal file
View File

@ -0,0 +1,85 @@
package encryptor
import (
"strings"
"github.com/jrapoport/chestnut/encryptor/crypto"
)
// ChainEncryptor is an encryptor that supports an chain of other Encryptors.
// Bytes will be encrypted by chaining the Encryptors in a FIFO order.
type ChainEncryptor struct {
id string
name string
ids []string
names []string
encryption []crypto.Encryptor
decryption []crypto.Encryptor
}
var _ crypto.Encryptor = (*ChainEncryptor)(nil)
const chainSep = " "
// NewChainEncryptor creates a new ChainEncryptor consisting of a chain
// of the supplied Encryptors.
func NewChainEncryptor(encryptors ...crypto.Encryptor) *ChainEncryptor {
if len(encryptors) == 0 {
return nil
}
// reverse the encryptors from FIFO to LIFO
decryptors := make([]crypto.Encryptor, len(encryptors))
for i := range encryptors {
decryptors[len(encryptors)-1-i] = encryptors[i]
}
chain := new(ChainEncryptor)
chain.encryption = encryptors
chain.decryption = decryptors
chain.ids = make([]string, len(encryptors))
chain.names = make([]string, len(encryptors))
for i, e := range chain.encryption {
chain.ids[i] = e.ID()
chain.names[i] = e.Name()
}
chain.id = strings.Join(chain.ids, chainSep)
chain.name = strings.Join(chain.names, chainSep)
return chain
}
// ID returns a concatenated list of the ids of chained encryptor(s) / secrets
// that were used to encrypt the data (for tracking) separated by spaces.
func (e *ChainEncryptor) ID() string {
return e.id
}
// Name returns a concatenated list of the cipher names of the chained encryptor(s)
// that were used to encrypt the data separated by spaces.
func (e *ChainEncryptor) Name() string {
return e.name
}
// Encrypt returns data encrypted with the chain of Encryptors.
func (e *ChainEncryptor) Encrypt(plaintext []byte) ([]byte, error) {
var err error
ciphertext := plaintext
for _, en := range e.encryption {
ciphertext, err = en.Encrypt(ciphertext)
if err != nil {
break
}
}
return ciphertext, err
}
// Decrypt returns data decrypted with the chain of Encryptors.
func (e *ChainEncryptor) Decrypt(ciphertext []byte) ([]byte, error) {
var err error
plaintext := ciphertext
for _, de := range e.decryption {
plaintext, err = de.Decrypt(plaintext)
if err != nil {
break
}
}
return plaintext, err
}

64
encryptor/chian_test.go Normal file
View File

@ -0,0 +1,64 @@
package encryptor
import (
"strings"
"testing"
"github.com/jrapoport/chestnut/encryptor/aes"
"github.com/jrapoport/chestnut/encryptor/crypto"
"github.com/stretchr/testify/assert"
)
func TestChainEncryptor_Nil(t *testing.T) {
assert.Nil(t, NewChainEncryptor())
}
func TestChainEncryptor_Single(t *testing.T) {
ae := NewAESEncryptor(crypto.Key128, aes.CFB, textSecret)
assert.NotNil(t, ae)
chain := NewChainEncryptor(ae)
assert.Equal(t, ae.Name(), chain.Name())
assert.Equal(t, ae.ID(), chain.ID())
testChainEncryptor(t, chain)
}
func TestChainEncryptor_Chained(t *testing.T) {
encryptors := []crypto.Encryptor{
&AESEncryptor{textSecret, crypto.Key128, aes.CFB},
&AESEncryptor{managedSecret, crypto.Key192, aes.CTR},
&AESEncryptor{secureSecret, crypto.Key256, aes.GCM},
}
chain := NewChainEncryptor(encryptors...)
testChainName(t, chain, encryptors)
testChainID(t, chain, encryptors)
testChainEncryptor(t, chain)
}
func testChainName(t *testing.T, chain *ChainEncryptor, encryptors []crypto.Encryptor) {
var names []string
for _, e := range encryptors {
names = append(names, e.Name())
}
name := strings.Join(names, chainSep)
assert.Equal(t, name, chain.Name())
}
func testChainID(t *testing.T, chain *ChainEncryptor, encryptors []crypto.Encryptor) {
var ids []string
for _, e := range encryptors {
ids = append(ids, e.ID())
}
id := strings.Join(ids, chainSep)
assert.Equal(t, id, chain.ID())
}
func testChainEncryptor(t *testing.T, chain *ChainEncryptor) {
assert.NotNil(t, chain)
e, err := chain.Encrypt([]byte(testPlainText))
assert.NoError(t, err)
assert.NotEmpty(t, e)
d, err := chain.Decrypt(e)
assert.NoError(t, err)
assert.NotEmpty(t, d)
assert.Equal(t, testPlainText, string(d))
}

67
encryptor/crypto/data.go Normal file
View File

@ -0,0 +1,67 @@
package crypto
import (
"bytes"
"encoding/gob"
"errors"
"fmt"
)
// Data is a serializable wrapper for encrypted
// bytes with additional metadata in the Header.
type Data struct {
Header
Bytes []byte
}
// NewData returns an Data initialized
// with a Header and encrypted data.
func NewData(h Header, data []byte) Data {
return Data{h, data}
}
// Valid returns an error if the Data is not valid.
func (e Data) Valid() error {
if err := e.Header.Valid(); err != nil {
return fmt.Errorf("invalid header %w", err)
}
// check the data
if len(e.Bytes) <= 0 {
return errors.New("invalid data")
}
return nil
}
// EncodeData encodes Data to a byte representation. This provides a small abstraction
// in case we want to swap out the gob encoder for something else.
func EncodeData(data Data) ([]byte, error) {
if err := data.Valid(); err != nil {
return nil, err
}
return GobEncodeData(data)
}
// DecodeData decodes a byte representation to Data. This provides a small abstraction
// in case we want to swap out the gob decoder for something else.
func DecodeData(b []byte) (Data, error) {
return GobDecodeData(b)
}
// GobEncodeData serializes Data to a gob binary representation.
func GobEncodeData(data Data) ([]byte, error) {
b := bytes.Buffer{}
e := gob.NewEncoder(&b)
if err := e.Encode(data); err != nil {
return nil, err
}
return b.Bytes(), nil
}
// GobDecodeData deserializes a gob binary representation to Data.
func GobDecodeData(b []byte) (Data, error) {
data := Data{}
buf := bytes.Buffer{}
buf.Write(b)
d := gob.NewDecoder(&buf)
return data, d.Decode(&data)
}

View File

@ -0,0 +1,81 @@
package crypto
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestData(t *testing.T) {
const name = "aes256-gcm"
s, err := MakeRand(SaltLength)
assert.NoError(t, err)
iv, err := MakeRand(8)
assert.NoError(t, err)
nonce, err := MakeRand(NonceLength)
assert.NoError(t, err)
bytes, err := MakeRand(512)
assert.NoError(t, err)
type testCase struct {
cipher string
key KeyLen
mode Mode
salt []byte
iv []byte
nonce []byte
bytes []byte
err assert.ErrorAssertionFunc
}
tests := []testCase{
{"aes", Key256, "gcm", nil, nil, nil, nil, assert.Error},
{"aes", Key256, "gcm", s, nil, nonce, nil, assert.Error},
{"aes", Key256, "gcm", s, iv, nil, nil, assert.Error},
{"aes", Key256, "gcm", s, iv, nonce, nil, assert.Error},
{"aes", Key256, "gcm", s, iv, nil, bytes, assert.NoError},
{"aes", Key256, "gcm", s, nil, nonce, bytes, assert.NoError},
{"aes", Key256, "gcm", s, iv, nonce, bytes, assert.NoError},
}
for _, test := range tests {
data := NewData(Header{test.cipher, test.key, test.mode,
test.salt, test.iv, test.nonce}, test.bytes)
test.err(t, data.Valid())
}
}
func makeHeader(t *testing.T) Header {
s, err := MakeRand(SaltLength)
assert.NoError(t, err)
iv, err := MakeRand(NonceLength)
assert.NoError(t, err)
nonce, err := MakeRand(NonceLength)
assert.NoError(t, err)
h, err := NewHeader("aes", Key256, "gcm", s, iv, nonce)
assert.NoError(t, err)
return h
}
func TestEncodeData(t *testing.T) {
bytes, err := MakeRand(512)
assert.NoError(t, err)
data := NewData(makeHeader(t), bytes)
assert.NoError(t, data.Valid())
enc, err := EncodeData(data)
assert.NoError(t, err)
assert.NotEmpty(t, enc)
dec, err := DecodeData(enc)
assert.NoError(t, err)
assert.Equal(t, data, dec)
}
func TestGobEncodeData(t *testing.T) {
bytes, err := MakeRand(512)
assert.NoError(t, err)
data := NewData(makeHeader(t), bytes)
assert.NoError(t, data.Valid())
enc, err := GobEncodeData(data)
assert.NoError(t, err)
assert.NotEmpty(t, enc)
dec, err := GobDecodeData(enc)
assert.NoError(t, err)
assert.Equal(t, data, dec)
}

View File

@ -0,0 +1,17 @@
package crypto
// Encryptor is the interface use to supply cipher implementations to the datastore.
type Encryptor interface {
// ID returns the id of the secret used to encrypt the data.
ID() string
// Name returns the name of encryption cipher, keyLen length
// and mode used to encrypt the data ("aes192-ctr").
Name() string
// Encrypt returns data encrypted with the secret.
Encrypt(plaintext []byte) (ciphertext []byte, err error)
// Decrypt returns data decrypted with the secret.
Decrypt(ciphertext []byte) (plaintext []byte, err error)
}

12
encryptor/crypto/hash.go Normal file
View File

@ -0,0 +1,12 @@
package crypto
import "crypto/sha256"
// HashSHA256 returns a sha256 hash of data.
func HashSHA256(data []byte) ([]byte, error) {
h := sha256.New()
if _, err := h.Write(data); err != nil {
return nil, err
}
return h.Sum(nil), nil
}

View File

@ -0,0 +1,38 @@
package crypto
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestHashSHA256(t *testing.T) {
var tests = []struct {
in string
out []byte
}{
{
"",
[]byte{0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14, 0x9a, 0xfb, 0xf4, 0xc8,
0x99, 0x6f, 0xb9, 0x24, 0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b, 0x93, 0x4c, 0xa4,
0x95, 0x99, 0x1b, 0x78, 0x52, 0xb8, 0x55},
},
{
"abcdefghijklmnopqrstuvwxyz",
[]byte{0x71, 0xc4, 0x80, 0xdf, 0x93, 0xd6, 0xae, 0x2f, 0x1e, 0xfa, 0xd1, 0x44,
0x7c, 0x66, 0xc9, 0x52, 0x5e, 0x31, 0x62, 0x18, 0xcf, 0x51, 0xfc, 0x8d, 0x9e, 0xd8,
0x32, 0xf2, 0xda, 0xf1, 0x8b, 0x73},
},
{
"abcdefghijklmnopqrstuvwxyz1234567890",
[]byte{0x77, 0xd7, 0x21, 0xc8, 0x17, 0xf9, 0xd2, 0x16, 0xc1, 0xfb, 0x78, 0x3b, 0xca,
0xd9, 0xcd, 0xc2, 0xa, 0xaa, 0x24, 0x27, 0x40, 0x26, 0x83, 0xf1, 0xf7, 0x5d, 0xd6,
0xdf, 0xbe, 0x65, 0x74, 0x70},
},
}
for _, test := range tests {
h, err := HashSHA256([]byte(test.in))
assert.NoError(t, err)
assert.Equal(t, test.out, h, test.in)
}
}

View File

@ -0,0 +1,67 @@
package crypto
import (
"errors"
"fmt"
"strings"
)
// MinSaltLength is the minimum length of the salt buffer.
const MinSaltLength = 8
// A Header describes an encryption block. It contains the cipher name,
// key length, mode used as well as the cipher key salt, iv or nonce.
type Header struct {
Cipher string // e.g. "aes"
KeyLen KeyLen // e.g. 128
Mode Mode // e.g. "gcm"
Salt []byte
IV []byte
Nonce []byte
}
// NewHeader create a new Header checking the length of the
// salt buffer against MinSaltLength. If the length of the
// salt buffer is less than MinSaltLength it returns an error.
func NewHeader(cipher string, keyLen KeyLen, mode Mode, salt []byte, iv []byte, nonce []byte) (Header, error) {
cipher = strings.ToLower(cipher)
mode = Mode(strings.ToLower(mode.String()))
h := Header{cipher, keyLen, mode, salt, iv, nonce}
if err := h.Valid(); err != nil {
return Header{}, err
}
return h, nil
}
// Valid returns an error if the Header is not valid.
func (h Header) Valid() error {
if h.Cipher == "" {
return errors.New("cipher required")
}
if h.KeyLen <= 0 {
return errors.New("key length required")
}
if h.Mode == "" {
return errors.New("mode required")
}
if len(h.Salt) < MinSaltLength {
return fmt.Errorf("salt length %d < %d minimum", len(h.Salt), MinSaltLength)
}
if h.Nonce != nil && len(h.Nonce) < NonceLength {
return fmt.Errorf("nonce length %d < %d minimum", len(h.Nonce), NonceLength)
}
return nil
}
// Name returns the name of the cipher in following
// format "[cipher][key length]-[mode]" e.g. "aes192-ctr".
func (h *Header) Name() string {
return CipherName(h.Cipher, h.KeyLen, h.Mode)
}
// CipherName is a convenience function that returns the name,
// key length, and mode of a cipher in the following format
// "[cipher][key length]-[mode]" e.g. "aes192-ctr".
func CipherName(cipher string, keyLen KeyLen, mode Mode) string {
return fmt.Sprintf("%s%s-%s", strings.ToLower(cipher), keyLen, strings.ToLower(mode.String()))
}

View File

@ -0,0 +1,48 @@
package crypto
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestHeader(t *testing.T) {
const name = "aes256-gcm"
s, err := MakeRand(SaltLength)
assert.NoError(t, err)
iv, err := MakeRand(8)
assert.NoError(t, err)
nonce, err := MakeRand(NonceLength)
assert.NoError(t, err)
type testCase struct {
cipher string
key KeyLen
mode Mode
salt []byte
iv []byte
nonce []byte
name string
err assert.ErrorAssertionFunc
}
tests := []testCase{
{"", 0, "", nil, nil, nil, "", assert.Error},
{"aes", 0, "", nil, nil, nil, "", assert.Error},
{"aes", Key256, "", nil, nil, nil, "", assert.Error},
{"aes", Key256, "gcm", nil, nil, nil, "", assert.Error},
{"aes", Key256, "gcm", []byte(""), nil, nil, "", assert.Error},
{"aes", Key256, "gcm", s, nil, []byte(""), "", assert.Error},
{"aes", Key256, "gcm", s, nil, nil, name, assert.NoError},
{"aes", Key256, "gcm", s, iv, nil, name, assert.NoError},
{"aes", Key256, "gcm", s, nil, nonce, name, assert.NoError},
{"aes", Key256, "gcm", s, iv, nonce, name, assert.NoError},
{"AES", Key256, "GCM", s, iv, nonce, name, assert.NoError},
}
for _, test := range tests {
h, err := NewHeader(test.cipher, test.key, test.mode, test.salt, test.iv, test.nonce)
test.err(t, err)
if err == nil {
assert.NotNil(t, h)
assert.Equal(t, test.name, h.Name())
}
}
}

52
encryptor/crypto/key.go Normal file
View File

@ -0,0 +1,52 @@
package crypto
import (
"crypto/sha512"
"golang.org/x/crypto/pbkdf2"
"golang.org/x/crypto/scrypt"
)
// KeyLen is used to select 128, 192, or 256 bit keys.
type KeyLen int
// key lengths
const (
Key128 KeyLen = 16 // 128 bit
Key192 = 24 // 128 bit
Key256 = 32 // 128 bit
)
func (k KeyLen) String() string {
switch k {
case Key128:
return "128"
case Key192:
return "192"
case Key256:
return "256"
default:
return ""
}
}
// NewCipherKey generate a new cipher key of the appropriate key length.
// Note: Currently this is hard-coded to 4096 key iterations. The thinking here is that
// the strength of secret was determined externally and therefore it less important to
// iterate (again) a large number of times. 1<<15 (or 32768) key iterations, seems to
// be the current consensus for passwords in general (2020).
func NewCipherKey(l KeyLen, secret, salt []byte) ([]byte, error) {
const keyIterations = 4096
return NewScryptCipherKey(l, keyIterations, secret, salt)
}
// NewPBKDF2CipherKey generate a new cipher key using pbkdf2.
func NewPBKDF2CipherKey(l KeyLen, iterations int, secret, salt []byte) ([]byte, error) {
// sha512, in addition to being more secure, should be faster on 64-bit systems
return pbkdf2.Key(secret, salt, iterations, int(l), sha512.New), nil
}
// NewScryptCipherKey generate a new cipher key using scrypt.
func NewScryptCipherKey(l KeyLen, iterations int, secret, salt []byte) ([]byte, error) {
return scrypt.Key(secret, salt, iterations, 8, 1, int(l))
}

View File

@ -0,0 +1,34 @@
package crypto
import (
"testing"
"github.com/stretchr/testify/assert"
)
const (
secret = "i-am-a-secret"
saltLen = 8
keyLen = 32
iterations = 1024
)
func TestNewCipherKeys(t *testing.T) {
salt, err := MakeRand(saltLen)
assert.NoError(t, err)
assert.Len(t, salt, saltLen)
sec := []byte(secret)
cipher := func() ([]byte, error) { return NewCipherKey(keyLen, sec, salt) }
pbkdf2 := func() ([]byte, error) { return NewPBKDF2CipherKey(keyLen, iterations, sec, salt) }
scrypt := func() ([]byte, error) { return NewScryptCipherKey(keyLen, iterations, sec, salt) }
test := func(newKey func() ([]byte, error)) {
key1, err1 := newKey()
key2, err2 := newKey()
assert.NoError(t, err1)
assert.NoError(t, err2)
assert.Equal(t, key1, key2)
}
t.Run("NewCipherKey", func(t *testing.T) { test(cipher) })
t.Run("NewPBKDF2CipherKey", func(t *testing.T) { test(pbkdf2) })
t.Run("NewScryptCipherKey", func(t *testing.T) { test(scrypt) })
}

8
encryptor/crypto/mode.go Normal file
View File

@ -0,0 +1,8 @@
package crypto
// Mode are the supported modes for a cipher.
type Mode string
func (m Mode) String() string {
return string(m)
}

38
encryptor/crypto/rand.go Normal file
View File

@ -0,0 +1,38 @@
package crypto
import (
"crypto/rand"
"fmt"
"io"
)
const (
// SaltLength is the default salt length.
SaltLength = 32
// NonceLength is the default nonce length.
NonceLength = 12
)
// MakeRand returns a buffer of size length filled with random bytes.
func MakeRand(length uint) ([]byte, error) {
// generate random bytes
r := make([]byte, length)
n, err := io.ReadFull(rand.Reader, r)
if err != nil {
return nil, err
} else if uint(n) != length {
return nil, fmt.Errorf("invalid buffer length %d != %d", n, length)
}
return r, nil
}
// MakeSalt returns random salt of size length.
func MakeSalt() ([]byte, error) {
return MakeRand(SaltLength)
}
// MakeNonce returns random nonce of size length.
func MakeNonce() ([]byte, error) {
return MakeRand(NonceLength)
}

View File

@ -0,0 +1,17 @@
package crypto
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestMakeRand(t *testing.T) {
const testLength = 20
buf, err := MakeRand(0)
assert.NoError(t, err)
assert.Len(t, buf, 0)
buf, err = MakeRand(testLength)
assert.NoError(t, err)
assert.Len(t, buf, testLength)
}

View File

@ -0,0 +1,70 @@
package crypto
// Secret is the interface that wraps a cipher keyLen and its id.
type Secret interface {
// ID return the id of the secret for tracking, or rollover etc.
ID() string
// Open returns a byte representation of the secret for encryption and decryption.
Open() []byte
}
// A TextSecret provides a simple plaintext secret.
type TextSecret string
var _ Secret = (*TextSecret)(nil)
// ID return the id of the secret for tracking, or rollover etc.
func (s TextSecret) ID() string {
return "text"
}
// Open returns a byte representation of the secret for encryption and decryption.
func (s TextSecret) Open() []byte {
return []byte(s)
}
// A ManagedSecret provides a simple plaintext secret alongside a unique id.
type ManagedSecret struct {
id string
TextSecret
}
var _ Secret = (*ManagedSecret)(nil)
// NewManagedSecret creates a new ManagedSecret with a secret with its corresponding id.
func NewManagedSecret(id, secret string) *ManagedSecret {
return &ManagedSecret{id, TextSecret(secret)}
}
// ID return the id of the secret for tracking, or rollover etc.
func (s ManagedSecret) ID() string {
return s.id
}
// SecureSecret provides a unique id for a secret alongside an openSecret callback which
// returns a byte representation of the secret for encryption and decryption on Open.
// When SecureSecret calls openSecret it will pass a copy of itself as a Secret. This allows
// for remote loading of the secret based on its id, or using a secure in-memory storage
// solution for the secret like memguarded (https://github.com/n0rad/memguarded).
type SecureSecret struct {
id string
open func(Secret) []byte
}
var _ Secret = (*SecureSecret)(nil)
// NewSecureSecret creates a new SecureSecret with an id and an callback function which
// returns a byte representation of the secret for encryption and decryption.
func NewSecureSecret(id string, openSecret func(Secret) []byte) *SecureSecret {
return &SecureSecret{id, openSecret}
}
// ID return the id of the secret for tracking, or rollover etc.
func (s SecureSecret) ID() string {
return s.id
}
// Open returns a byte representation of the secret for encryption and decryption.
func (s SecureSecret) Open() []byte {
return s.open(s)
}

18
examples/README.md Normal file
View File

@ -0,0 +1,18 @@
# Examples
This is a repository for Chestnut examples. Feel free to contribute.
## The Goods
- [sparse](sparse) - Provides an sparse encryption example
- [hash](hash) - Provides a hash example
- [keystore](keystore) - Provides an encrypted keystore example
## Running the examples
```shell
# run any example with make <name>
$ make sparse
```

64
examples/hash/main.go Normal file
View File

@ -0,0 +1,64 @@
package main
import (
"fmt"
"log"
"os"
"path/filepath"
"github.com/jrapoport/chestnut"
"github.com/jrapoport/chestnut/encryptor/aes"
"github.com/jrapoport/chestnut/encryptor/crypto"
"github.com/jrapoport/chestnut/storage/nuts"
)
func main() {
path := filepath.Join(os.TempDir(), "hash")
defer os.RemoveAll(path)
// use nutsdb
store := nuts.NewStore(path)
// use a simple text secret
textSecret := crypto.TextSecret("i-am-a-good-secret")
// use AES256-CFB encryption
opt := chestnut.WithAES(crypto.Key256, aes.CFB, textSecret)
// open the storage chest with nutsdb and the aes encryptor
cn := chestnut.NewChestnut(store, opt)
if err := cn.Open(); err != nil {
log.Panic(err)
}
// define an struct with a hash field
type HashValue struct {
// ClearString will not be hashed
ClearString string
// HashString with the 'hash' tag option
HashString string `json:",hash"`
}
src := &HashValue{
ClearString: "I am a string",
HashString: "I will be hashed",
}
// a key for the value
namespace := "sparse-values"
key := []byte("sparse-value-id")
// save the struct with sparse encryption
if err := cn.Save(namespace, key, src); err != nil {
log.Panic(err)
}
// load the value
err := cn.Load(namespace, key, src)
if err != nil {
log.Panic(err)
}
fmt.Println("clear field:", src.ClearString)
fmt.Println("hashed field:", src.HashString)
}

67
examples/keystore/main.go Normal file
View File

@ -0,0 +1,67 @@
package main
import (
"bytes"
"fmt"
"log"
"os"
"path/filepath"
"github.com/btcsuite/btcd/btcec"
"github.com/jrapoport/chestnut"
"github.com/jrapoport/chestnut/encryptor/aes"
"github.com/jrapoport/chestnut/encryptor/crypto"
"github.com/jrapoport/chestnut/keystore"
"github.com/jrapoport/chestnut/storage/nuts"
)
func main() {
path := filepath.Join(os.TempDir(), "keystore")
defer os.RemoveAll(path)
// use nutsdb
store := nuts.NewStore(path)
// use a simple text secret
textSecret := crypto.TextSecret("i-am-a-good-secret")
opts := []chestnut.ChestOption{
// use AES256-CFB encryption
chestnut.WithAES(crypto.Key256, aes.CFB, textSecret),
}
// open the keystore with nutsdb and the aes encryptor
ks := keystore.NewKeystore(store, opts...)
if err := ks.Open(); err != nil {
log.Panic(err)
}
// generate a new *btcec.PrivateKey
pk1, err := btcec.NewPrivateKey(btcec.S256())
if err != nil {
log.Panic(err)
}
// convert pk from *btcec.PrivateKey to ci.PrivKey.
privKey1 := keystore.BTCECPrivateKeyToPrivKey(pk1)
// encrypt the private key and put in the keystore
if err = ks.Put("my private key", privKey1); err != nil {
log.Panic(err)
}
// get the private key from the store and decrypt it
privKey2, err := ks.Get("my private key")
if err != nil {
log.Panic(err)
}
// convert the saved private key to *btcec.PrivateKey
pk2 := keystore.PrivKeyToBTCECPrivateKey(privKey2)
// compare the keys
if bytes.Equal(pk1.Serialize(), pk2.Serialize()) {
fmt.Println("private keys are equal")
}
}

66
examples/sparse/main.go Normal file
View File

@ -0,0 +1,66 @@
package main
import (
"fmt"
"log"
"os"
"path/filepath"
"github.com/jrapoport/chestnut"
"github.com/jrapoport/chestnut/encryptor/aes"
"github.com/jrapoport/chestnut/encryptor/crypto"
"github.com/jrapoport/chestnut/storage/nuts"
)
func main() {
path := filepath.Join(os.TempDir(), "sparse")
defer os.RemoveAll(path)
// use nutsdb
store := nuts.NewStore(path)
// use a simple text secret
textSecret := crypto.TextSecret("i-am-a-good-secret")
// use AES256-CFB encryption
opt := chestnut.WithAES(crypto.Key256, aes.CFB, textSecret)
// open the storage chest with nutsdb and the aes encryptor
cn := chestnut.NewChestnut(store, opt)
if err := cn.Open(); err != nil {
log.Panic(err)
}
// define a sparse struct with a secure field
type SparseKeyed struct {
// SecretString with the 'secure' tag option
SecretString string `json:",secure"`
// PublicString will not be encrypted
PublicString string
}
src := &SparseKeyed{
SecretString: "I am secret",
PublicString: "I am visible",
}
// a key for the value
namespace := "sparse-values"
key := []byte("sparse-value-id")
// save the value with sparse encryption
if err := cn.Save(namespace, key, src); err != nil {
log.Panic(err)
}
sparse := &SparseKeyed{}
// load a sparse copy
err := cn.Sparse(namespace, key, sparse)
if err != nil {
log.Panic(err)
}
fmt.Println("secure field:", sparse.SecretString)
fmt.Println("public field:", sparse.PublicString)
}

19
go.mod Normal file
View File

@ -0,0 +1,19 @@
module github.com/jrapoport/chestnut
go 1.15
require (
github.com/btcsuite/btcd v0.21.0-beta
github.com/google/uuid v1.1.4
github.com/hashicorp/go-version v1.2.1
github.com/ipfs/go-ipfs v0.7.0
github.com/json-iterator/go v1.1.10
github.com/klauspost/compress v1.11.6
github.com/libp2p/go-libp2p-core v0.8.0
github.com/modern-go/reflect2 v1.0.1
github.com/sirupsen/logrus v1.7.0
github.com/stretchr/testify v1.6.1
github.com/xujiajun/nutsdb v0.5.0
go.uber.org/zap v1.16.0
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad
)

1170
go.sum Normal file

File diff suppressed because it is too large Load Diff

27
keystore/README.md Normal file
View File

@ -0,0 +1,27 @@
# Keystore
Keystore is an IPFS compliant keystore built on Chestnut. It implements an IPFS keystore interface, allowing it to be used natively with many existing IPFS implementations, and tools.
We recommend using AES256-CTR for encryption based in part on this
[helpful analysis](https://www.highgo.ca/2019/08/08/the-difference-in-five-modes-in-the-aes-encryption-algorithm/)
of database encryption approaches and trade-offs from Shawn Wang, PostgreSQL Database Core.
For a detailed example on importing and using the Keystore, please check out the [Keystore](../examples/keystore)
example under the `examples` folder.
### IMPORTANT!
```go
package main
import (
"github.com/ipfs/go-ipfs/keystore"
"github.com/libp2p/go-libp2p-core/crypto"
)
```
Please **make sure** you import
[go-ipfs](github.com/ipfs/go-ipfs) and [go-libp2p-core](https://github.com/libp2p/go-libp2p-core/),
and are **NOT** importing [go-ipfs-keystore](github.com/ipfs/go-ipfs-keystore) and
[go-libp2p-crypto](github.com/libp2p/go-libp2p-crypto). Those repos are **DEPRECATED**,
out of date, archived, etc. This will save you time and sanity.

117
keystore/keystore.go Normal file
View File

@ -0,0 +1,117 @@
package keystore
import (
"errors"
"github.com/ipfs/go-ipfs/keystore"
"github.com/jrapoport/chestnut"
"github.com/jrapoport/chestnut/log"
"github.com/jrapoport/chestnut/storage"
ci "github.com/libp2p/go-libp2p-core/crypto"
)
const (
namespace = "keys"
logName = "keystore"
)
// Keystore is used to manage an encrypted IPFS-compliant keystore.
type Keystore struct {
cn *chestnut.Chestnut
store storage.Storage
log log.Logger
}
var _ keystore.Keystore = (*Keystore)(nil)
// NewKeystore is used to create a new chestnut ipfs-compliant keystore.
// Suggest using using this with AES256-CTR encryption based in part
// on this helpful analysis from Shawn Wang, PostgreSQL Database Core:
// https://www.highgo.ca/2019/08/08/the-difference-in-five-modes-in-the-aes-encryption-algorithm/
func NewKeystore(store storage.Storage, opt ...chestnut.ChestOption) *Keystore {
// keystore requires that overwrites are forbidden
opt = append(opt, chestnut.OverwritesForbidden())
cn := chestnut.NewChestnut(store, opt...)
logger := log.Named(cn.Logger(), logName)
ks := &Keystore{cn, store, logger}
if err := ks.validConfig(); err != nil {
logger.Fatal(err)
return nil
}
return ks
}
func (ks *Keystore) validConfig() error {
if ks.store == nil {
return errors.New("store required")
}
return nil
}
// Open the Keystore
func (ks *Keystore) Open() error {
if err := ks.validConfig(); err != nil {
return err
}
return ks.cn.Open()
}
// Has returns whether or not a key exists in the Keystore
func (ks *Keystore) Has(s string) (bool, error) {
return ks.cn.Has(namespace, []byte(s))
}
// Put stores a key in the Keystore, if a key with
// the same name already exists, returns ErrKeyExists
func (ks *Keystore) Put(s string, key ci.PrivKey) error {
if key == nil {
return errors.New("invalid key")
}
data, err := ci.MarshalPrivateKey(key)
if err != nil {
return err
}
err = ks.cn.Put(namespace, []byte(s), data)
if errors.Is(err, chestnut.ErrForbidden) {
return keystore.ErrKeyExists
}
return err
}
// Get retrieves a key from the Keystore if it
// exists, and returns ErrNoSuchKey otherwise.
func (ks *Keystore) Get(s string) (ci.PrivKey, error) {
data, err := ks.cn.Get(namespace, []byte(s))
if err != nil {
return nil, keystore.ErrNoSuchKey
}
return ci.UnmarshalPrivateKey(data)
}
// Delete removes a key from the Keystore
func (ks *Keystore) Delete(s string) error {
return ks.cn.Delete(namespace, []byte(s))
}
// List returns a list of key identifier
func (ks *Keystore) List() ([]string, error) {
list, err := ks.cn.List(namespace)
if err != nil {
return nil, err
}
keys := make([]string, len(list))
for i, key := range list {
keys[i] = string(key)
}
return keys, nil
}
// Export the Keystore
func (ks *Keystore) Export(path string) error {
return ks.cn.Export(path)
}
// Close the Keystore
func (ks *Keystore) Close() error {
return ks.cn.Open()
}

162
keystore/keystore_test.go Normal file
View File

@ -0,0 +1,162 @@
package keystore
import (
"log"
"sort"
"testing"
"github.com/google/uuid"
"github.com/jrapoport/chestnut"
"github.com/jrapoport/chestnut/encryptor/aes"
"github.com/jrapoport/chestnut/encryptor/crypto"
"github.com/jrapoport/chestnut/storage"
"github.com/jrapoport/chestnut/storage/nuts"
ci "github.com/libp2p/go-libp2p-core/crypto"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
)
var (
testName = uuid.New().String()
textSecret = crypto.TextSecret("i-am-a-good-secret")
encryptorOpt = chestnut.WithAES(crypto.Key256, aes.CFB, textSecret)
privateKey = func() ci.PrivKey {
pk, _, err := ci.GenerateKeyPair(ci.ECDSA, 512)
if err != nil {
log.Fatal(err)
}
return pk
}()
)
type testCase struct {
name string
key ci.PrivKey
err assert.ErrorAssertionFunc
exists bool
}
var tests = []testCase{
{"", nil, assert.Error, false},
{"", nil, assert.Error, false},
{"f", nil, assert.Error, false},
{"g", privateKey, assert.NoError, true},
{"h", privateKey, assert.NoError, true},
{"i/i", privateKey, assert.NoError, true},
{".j", privateKey, assert.NoError, true},
{testName, privateKey, assert.NoError, true},
}
var testCaseNotFound = testCase{"not-found", nil, assert.Error, false}
type KeystoreTestSuite struct {
suite.Suite
keystore *Keystore
}
func newNutsDBStore(t *testing.T) storage.Storage {
path := t.TempDir()
store := nuts.NewStore(path)
assert.NotNil(t, store)
return store
}
func TestKeystore(t *testing.T) {
suite.Run(t, new(KeystoreTestSuite))
}
func (ts *KeystoreTestSuite) SetupTest() {
store := newNutsDBStore(ts.T())
ts.keystore = NewKeystore(store, encryptorOpt)
assert.NotNil(ts.T(), ts.keystore)
err := ts.keystore.Open()
assert.NoError(ts.T(), err)
}
func (ts *KeystoreTestSuite) TearDownTest() {
err := ts.keystore.Close()
assert.NoError(ts.T(), err)
}
func (ts *KeystoreTestSuite) BeforeTest(_, testName string) {
switch testName {
case "TestKeystore_Encryptor",
"TestKeystore_Put",
"TestKeystore_List":
break
default:
ts.TestKeystore_Put()
}
}
func (ts *KeystoreTestSuite) TestKeystore_Encryptor() {
err := ts.keystore.Put(testName, privateKey)
assert.NoError(ts.T(), err)
pk, err := ts.keystore.Get(testName)
assert.NotNil(ts.T(), pk)
assert.NoError(ts.T(), err)
assert.Equal(ts.T(), privateKey.Type().String(), pk.Type().String())
}
func (ts *KeystoreTestSuite) TestKeystore_Put() {
for i, test := range tests {
err := ts.keystore.Put(test.name, test.key)
test.err(ts.T(), err, "%d test name: %s", i, test.name)
}
err := ts.keystore.Put(testName, privateKey)
assert.Error(ts.T(), err)
}
func (ts *KeystoreTestSuite) TestKeystore_Get() {
getTests := append(tests, testCaseNotFound)
for i, test := range getTests {
key, err := ts.keystore.Get(test.name)
test.err(ts.T(), err, "%d test name: %s", i, test.name)
assert.Equal(ts.T(), test.key, key, "%d test name: %s", i, test.name)
}
}
func (ts *KeystoreTestSuite) TestKeystore_Has() {
for _, test := range tests {
has, _ := ts.keystore.Has(test.name)
assert.Equal(ts.T(), test.exists, has)
}
}
func (ts *KeystoreTestSuite) TestKeystore_List() {
const listLen = 100
list := make([]string, listLen)
for i := 0; i < listLen; i++ {
list[i] = uuid.New().String()
err := ts.keystore.Put(list[i], privateKey)
assert.NoError(ts.T(), err)
}
keys, err := ts.keystore.List()
assert.NoError(ts.T(), err)
assert.Len(ts.T(), keys, listLen)
// put both lists in the same order so we can compare them
sort.Strings(list)
sort.Strings(keys)
assert.Equal(ts.T(), list, keys)
}
func (ts *KeystoreTestSuite) TestKeystore_Delete() {
for i, test := range tests {
if test.exists == false {
continue
}
err := ts.keystore.Delete(test.name)
test.err(ts.T(), err, "%d test key: %s", i, test.key)
}
}
func (ts *KeystoreTestSuite) TestKeystore_Export() {
err := ts.keystore.Export(ts.T().TempDir())
assert.NoError(ts.T(), err)
}
func TestKeystore_OpenErr(t *testing.T) {
ks := &Keystore{}
err := ks.Open()
assert.Error(t, err)
}

113
keystore/keyutils.go Normal file
View File

@ -0,0 +1,113 @@
package keystore
import (
"crypto/ecdsa"
"crypto/rsa"
"log"
"github.com/btcsuite/btcd/btcec"
"github.com/libp2p/go-libp2p-core/crypto"
"golang.org/x/crypto/ed25519"
)
// PrivKeyToRSAPrivateKey converts libp2p/go-libp2p-core/crypto
// private keys to standard library rsa private keys.
func PrivKeyToRSAPrivateKey(privKey crypto.PrivKey) *rsa.PrivateKey {
key, err := crypto.PrivKeyToStdKey(privKey)
if err != nil {
log.Panic(err)
return nil
}
if pk, ok := key.(*rsa.PrivateKey); ok {
return pk
}
return nil
}
// RSAPrivateKeyToPrivKey converts standard library rsa
// private keys to libp2p/go-libp2p-core/crypto private keys.
func RSAPrivateKeyToPrivKey(privateKey *rsa.PrivateKey) crypto.PrivKey {
pk, _, err := crypto.KeyPairFromStdKey(privateKey)
if err != nil {
log.Panic(err)
return nil
}
return pk
}
// PrivKeyToECDSAPrivateKey converts libp2p/go-libp2p-core/crypto
// private keys to new standard library ecdsa private keys.
func PrivKeyToECDSAPrivateKey(privKey crypto.PrivKey) *ecdsa.PrivateKey {
key, err := crypto.PrivKeyToStdKey(privKey)
if err != nil {
log.Panic(err)
return nil
}
if pk, ok := key.(*ecdsa.PrivateKey); ok {
return pk
}
return nil
}
// ECDSAPrivateKeyToPrivKey converts standard library ecdsa
// private keys to libp2p/go-libp2p-core/crypto private keys.
func ECDSAPrivateKeyToPrivKey(privateKey *ecdsa.PrivateKey) crypto.PrivKey {
pk, _, err := crypto.KeyPairFromStdKey(privateKey)
if err != nil {
log.Panic(err)
return nil
}
return pk
}
// PrivKeyToEd25519PrivateKey converts libp2p/go-libp2p-core/crypto
// private keys to ed25519 private keys.
func PrivKeyToEd25519PrivateKey(privKey crypto.PrivKey) *ed25519.PrivateKey {
key, err := crypto.PrivKeyToStdKey(privKey)
if err != nil {
log.Panic(err)
return nil
}
if pk, ok := key.(*ed25519.PrivateKey); ok {
return pk
}
return nil
}
// Ed25519PrivateKeyToPrivKey converts ed25519 private keys
// to libp2p/go-libp2p-core/crypto private keys.
func Ed25519PrivateKeyToPrivKey(privateKey *ed25519.PrivateKey) crypto.PrivKey {
pk, _, err := crypto.KeyPairFromStdKey(privateKey)
if err != nil {
log.Panic(err)
return nil
}
return pk
}
// PrivKeyToBTCECPrivateKey converts libp2p/go-libp2p-core/crypto
// private keys to standard library btcec (and secp256k1) private keys.
// Internally equivalent to (*btcec.PrivateKey)(privKey.(*crypto.Secp256k1PrivateKey)).
func PrivKeyToBTCECPrivateKey(privKey crypto.PrivKey) *btcec.PrivateKey {
key, err := crypto.PrivKeyToStdKey(privKey)
if err != nil {
log.Panic(err)
return nil
}
if pk, ok := key.(*crypto.Secp256k1PrivateKey); ok {
return (*btcec.PrivateKey)(pk)
}
return nil
}
// BTCECPrivateKeyToPrivKey converts standard library btcec (and secp256k1)
// private keys to libp2p/go-libp2p-core/crypto private keys. Internally
// equivalent to (*crypto.Secp256k1PrivateKey)(privateKey).
func BTCECPrivateKeyToPrivKey(privateKey *btcec.PrivateKey) crypto.PrivKey {
pk, _, err := crypto.KeyPairFromStdKey(privateKey)
if err != nil {
log.Panic(err)
return nil
}
return pk
}

121
keystore/keyutils_test.go Normal file
View File

@ -0,0 +1,121 @@
package keystore
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
"testing"
"github.com/btcsuite/btcd/btcec"
"github.com/libp2p/go-libp2p-core/crypto"
"github.com/stretchr/testify/assert"
"golang.org/x/crypto/ed25519"
)
func testPrivKeyToPrivateKey(t *testing.T, pk1 interface{}, conv func() interface{}) {
assert.NotNil(t, pk1)
stdKey := conv()
assert.NotNil(t, stdKey)
pk2, _, err := crypto.KeyPairFromStdKey(stdKey)
assert.NoError(t, err)
assert.Equal(t, pk1, pk2)
}
func testPrivateKeyToPrivKey(t *testing.T, pk1 interface{}, conv func() crypto.PrivKey) {
assert.NotNil(t, pk1)
privKey := conv()
assert.NotNil(t, privKey)
pk2, err := crypto.PrivKeyToStdKey(privKey)
assert.NoError(t, err)
assert.Equal(t, pk1, pk2)
}
func TestPrivKeyToRSAPrivateKey(t *testing.T) {
privKey, _, err := crypto.GenerateRSAKeyPair(2048, rand.Reader)
assert.NoError(t, err)
testPrivKeyToPrivateKey(t, privKey, func() interface{} {
return PrivKeyToRSAPrivateKey(privKey)
})
assert.Panics(t, func() {
_ = PrivKeyToRSAPrivateKey(nil)
})
}
func TestPrivKeyToECDSAPrivateKey(t *testing.T) {
privKey, _, err := crypto.GenerateECDSAKeyPair(rand.Reader)
assert.NoError(t, err)
testPrivKeyToPrivateKey(t, privKey, func() interface{} {
return PrivKeyToECDSAPrivateKey(privKey)
})
assert.Panics(t, func() {
_ = PrivKeyToECDSAPrivateKey(nil)
})
}
func TestPrivKeyToEd25519PrivateKey(t *testing.T) {
privKey, _, err := crypto.GenerateEd25519Key(rand.Reader)
assert.NoError(t, err)
testPrivKeyToPrivateKey(t, privKey, func() interface{} {
return PrivKeyToEd25519PrivateKey(privKey)
})
assert.Panics(t, func() {
_ = PrivKeyToEd25519PrivateKey(nil)
})
}
func TestPrivKeyToBTCECPrivateKey(t *testing.T) {
privKey, _, err := crypto.GenerateSecp256k1Key(rand.Reader)
assert.NoError(t, err)
testPrivKeyToPrivateKey(t, privKey, func() interface{} {
return PrivKeyToBTCECPrivateKey(privKey)
})
assert.Panics(t, func() {
_ = PrivKeyToBTCECPrivateKey(nil)
})
}
func TestRSAPrivateKeyToPrivKey(t *testing.T) {
rsaKey, err := rsa.GenerateKey(rand.Reader, 2048)
assert.NoError(t, err)
testPrivateKeyToPrivKey(t, rsaKey, func() crypto.PrivKey {
return RSAPrivateKeyToPrivKey(rsaKey)
})
assert.Panics(t, func() {
_ = RSAPrivateKeyToPrivKey(nil)
})
}
func TestECDSAPrivateKeyToPrivKey(t *testing.T) {
ecdsaKey, err := ecdsa.GenerateKey(elliptic.P521(), rand.Reader)
assert.NoError(t, err)
testPrivateKeyToPrivKey(t, ecdsaKey, func() crypto.PrivKey {
return ECDSAPrivateKeyToPrivKey(ecdsaKey)
})
assert.Panics(t, func() {
_ = ECDSAPrivateKeyToPrivKey(nil)
})
}
func TestEd25519PrivateKeyToPrivKey(t *testing.T) {
_, edKey, err := ed25519.GenerateKey(rand.Reader)
assert.NoError(t, err)
testPrivateKeyToPrivKey(t, &edKey, func() crypto.PrivKey {
return Ed25519PrivateKeyToPrivKey(&edKey)
})
assert.Panics(t, func() {
_ = Ed25519PrivateKeyToPrivKey(nil)
})
}
func TestBTCECPrivateKeyToPrivKey(t *testing.T) {
btcecKey, err := btcec.NewPrivateKey(btcec.S256())
key := (*crypto.Secp256k1PrivateKey)(btcecKey)
assert.NoError(t, err)
testPrivateKeyToPrivKey(t, key, func() crypto.PrivKey {
return BTCECPrivateKeyToPrivKey(btcecKey)
})
assert.Panics(t, func() {
_ = BTCECPrivateKeyToPrivKey(nil)
})
}

28
log/level.go Normal file
View File

@ -0,0 +1,28 @@
package log
// A Level is a logging priority. Higher levels are more important.
// This is here as a convenience when using the various log options.
type Level int
const (
// DebugLevel logs are typically voluminous,
// and are usually disabled in production.
DebugLevel Level = iota - 1
// InfoLevel is the default logging priority.
InfoLevel
// WarnLevel logs are more important than Info,
// but don't need individual human review.
WarnLevel
// ErrorLevel logs are high-priority. If an application runs
// smoothly, it shouldn't generate any error-level logs.
ErrorLevel
// PanicLevel logs a message, then panics.
PanicLevel
// FatalLevel logs a message, then calls os.Exit(1).
FatalLevel
)

51
log/level_test.go Normal file
View File

@ -0,0 +1,51 @@
package log
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestLevel(t *testing.T) {
levels := []Level{
DebugLevel,
InfoLevel,
WarnLevel,
ErrorLevel,
PanicLevel,
FatalLevel,
}
type NewLoggerFunc func(Level) Logger
tests := []struct {
name string
logFn NewLoggerFunc
}{
{"logrus", NewLogrusLoggerWithLevel},
{"std", NewStdLoggerWithLevel},
{"zap", NewZapLoggerWithLevel},
}
for _, level := range levels {
for _, test := range tests {
logger := test.logFn(level)
// debug
logger.Debug(test.name, " ", "debug")
logger.Debugf("%s %s", test.name, "debug")
// info
logger.Info(test.name, " ", "info")
logger.Infof("%s %s", test.name, "info")
// warn
logger.Warn(test.name, " ", "warn")
logger.Warnf("%s %s", test.name, "warn")
// error
logger.Error(test.name, " ", "error")
logger.Errorf("%s %s", test.name, "error")
// panic
assert.Panics(t, func() {
logger.Panic(test.name, " ", "panic")
})
assert.Panics(t, func() {
logger.Panicf("%s %s", test.name, "panic")
})
}
}
}

43
log/logger.go Normal file
View File

@ -0,0 +1,43 @@
package log
// Log is the same as the default standard logger from "log".
var Log = NewStdLoggerWithLevel(PanicLevel)
// Logger is a generic logger interface.
type Logger interface {
// Debug logs args when the logger level is debug.
Debug(v ...interface{})
// Debugf formats args and logs the result when the logger level is debug.
Debugf(format string, v ...interface{})
// Info logs args when the logger level is info.
Info(args ...interface{})
// Infof formats args and logs the result when the logger level is info.
Infof(format string, v ...interface{})
// Warn logs args when the logger level is warn.
Warn(v ...interface{})
// Warnf formats args and logs the result when the logger level is warn.
Warnf(format string, v ...interface{})
// Error logs args when the logger level is error.
Error(v ...interface{})
// Errorf formats args and logs the result when the logger level is debug.
Errorf(format string, v ...interface{})
// Panic logs args on panic.
Panic(v ...interface{})
// Panicf formats args and logs the result on panic.
Panicf(format string, v ...interface{})
// Fatal logs args when the error is fatal.
Fatal(v ...interface{})
// Fatalf formats args and logs the result when the error is fatal.
Fatalf(format string, v ...interface{})
}

33
log/logrus.go Normal file
View File

@ -0,0 +1,33 @@
package log
import "github.com/sirupsen/logrus"
var _ Logger = (*logrus.Logger)(nil)
var _ Logger = (*logrus.Entry)(nil)
// NewLogrusLoggerWithLevel returns a new production logrus logger with the log level.
func NewLogrusLoggerWithLevel(lvl Level) Logger {
l := logrus.New()
l.SetLevel(levelToLogrusLevel(lvl))
return l.WithContext(nil)
}
// NOTE: for logrus panic is a higher level than fatal.
func levelToLogrusLevel(lvl Level) logrus.Level {
switch lvl {
case DebugLevel:
return logrus.DebugLevel
case InfoLevel:
return logrus.InfoLevel
case WarnLevel:
return logrus.WarnLevel
case ErrorLevel:
return logrus.ErrorLevel
case PanicLevel:
return logrus.PanicLevel
case FatalLevel:
return logrus.FatalLevel
default:
return logrus.InfoLevel
}
}

33
log/named.go Normal file
View File

@ -0,0 +1,33 @@
package log
import (
"log"
"github.com/sirupsen/logrus"
"go.uber.org/zap"
)
// logrusField matches zap
const logrusField = "logger"
// Named adds a name string to the logger. How the name is added is
// logger specific i.e. a logrus field or std logger prefix, etc.
func Named(logger interface{}, name string) Logger {
switch l := logger.(type) {
case *logrus.Logger:
return l.WithField(logrusField, name)
case *logrus.Entry:
return l.WithField(logrusField, name)
case *log.Logger:
l.SetPrefix(name + " ")
return &stdLogger{l, InfoLevel}
case *stdLogger:
l.SetPrefix(name + " ")
return l
case *zap.SugaredLogger:
return l.Named(name)
case *zap.Logger:
return l.Sugar().Named(name)
}
return nil
}

53
log/named_test.go Normal file
View File

@ -0,0 +1,53 @@
package log
import (
"log"
"os"
"testing"
"github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
"go.uber.org/zap"
)
func TestWrapper(t *testing.T) {
const (
testName = "test"
emptyName = ""
)
tests := []struct {
logger interface{}
name string
assertNil assert.ValueAssertionFunc
}{
{nil, emptyName, assert.Nil},
{logrus.New(), emptyName, assert.NotNil},
{logrus.New(), testName, assert.NotNil},
{logrus.New().WithContext(nil), emptyName, assert.NotNil},
{logrus.New().WithContext(nil), testName, assert.NotNil},
{NewLogrusLoggerWithLevel(ErrorLevel), emptyName, assert.NotNil},
{NewLogrusLoggerWithLevel(ErrorLevel), testName, assert.NotNil},
{log.New(os.Stderr, "", 0), emptyName, assert.NotNil},
{log.New(os.Stderr, "", 0), testName, assert.NotNil},
{NewStdLoggerWithLevel(ErrorLevel), emptyName, assert.NotNil},
{NewStdLoggerWithLevel(ErrorLevel), testName, assert.NotNil},
{zap.NewExample(), emptyName, assert.NotNil},
{zap.NewExample(), testName, assert.NotNil},
{zap.NewExample().Sugar(), emptyName, assert.NotNil},
{zap.NewExample().Sugar(), testName, assert.NotNil},
{NewZapLoggerWithLevel(ErrorLevel), emptyName, assert.NotNil},
{NewZapLoggerWithLevel(ErrorLevel), testName, assert.NotNil},
}
for _, test := range tests {
logger := Named(test.logger, "name")
test.assertNil(t, logger)
if logger != nil {
_, ok := logger.(Logger)
assert.True(t, ok)
// error
logger.Error(testName)
logger.Errorf("%s", testName)
}
}
}

109
log/std.go Normal file
View File

@ -0,0 +1,109 @@
package log
import (
"io"
"log"
"os"
)
// stdLogger is a wrapper of standard log.
type stdLogger struct {
*log.Logger
level Level
}
var _ Logger = (*stdLogger)(nil)
// NewStdLoggerWithLevel is a stderr logger with the log level.
func NewStdLoggerWithLevel(lvl Level) Logger {
return NewStdLogger(lvl, os.Stderr, "", log.LstdFlags)
}
// NewStdLogger returns a new standard logger with the log level.
func NewStdLogger(lvl Level, out io.Writer, prefix string, flag int) Logger {
return &stdLogger{log.New(out, prefix, flag), lvl}
}
// Debug logs args when the logger level is debug.
func (l *stdLogger) Debug(v ...interface{}) {
if l.level > DebugLevel {
return
}
l.Print(v...)
}
// Debugf formats args and logs the result when the logger level is debug.
func (l *stdLogger) Debugf(format string, v ...interface{}) {
if l.level > DebugLevel {
return
}
l.Printf(format, v...)
}
// Info logs args when the logger level is info.
func (l *stdLogger) Info(v ...interface{}) {
if l.level > InfoLevel {
return
}
l.Print(v...)
}
// Infof formats args and logs the result when the logger level is info.
func (l *stdLogger) Infof(format string, v ...interface{}) {
if l.level > InfoLevel {
return
}
l.Printf(format, v...)
}
// Warn logs args when the logger level is warn.
func (l *stdLogger) Warn(v ...interface{}) {
if l.level > WarnLevel {
return
}
l.Print(v...)
}
// Warnf formats args and logs the result when the logger level is warn.
func (l *stdLogger) Warnf(format string, v ...interface{}) {
if l.level > WarnLevel {
return
}
l.Printf(format, v...)
}
// Error logs args when the logger level is error.
func (l *stdLogger) Error(v ...interface{}) {
if l.level > ErrorLevel {
return
}
l.Print(v...)
}
// Errorf formats args and logs the result when the logger level is debug.
func (l *stdLogger) Errorf(format string, v ...interface{}) {
if l.level > ErrorLevel {
return
}
l.Printf(format, v...)
}
// Panic logs args on panic.
func (l *stdLogger) Panic(v ...interface{}) {
l.Logger.Panic(v...)
}
// Panicf formats args and logs the result on panic.
func (l *stdLogger) Panicf(format string, v ...interface{}) {
l.Logger.Panicf(format, v...)
}
// Fatal logs args when the error is fatal.
func (l *stdLogger) Fatal(v ...interface{}) {
l.Logger.Fatal(v...)
}
// Fatalf formats args and logs the result when the error is fatal.
func (l *stdLogger) Fatalf(format string, v ...interface{}) {
l.Logger.Fatalf(format, v...)
}

41
log/zap.go Normal file
View File

@ -0,0 +1,41 @@
package log
import (
"log"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
var _ Logger = (*zap.SugaredLogger)(nil)
// NewZapLoggerWithLevel returns a new production zap logger with the log level.
func NewZapLoggerWithLevel(lvl Level) Logger {
zlvl := levelToZapLevel(lvl)
opt := zap.IncreaseLevel(zlvl)
l, err := zap.NewProduction(opt)
if err != nil {
log.Fatal(err.Error())
return nil
}
return l.Sugar()
}
func levelToZapLevel(lvl Level) zapcore.Level {
switch lvl {
case DebugLevel:
return zapcore.DebugLevel
case InfoLevel:
return zapcore.InfoLevel
case WarnLevel:
return zapcore.WarnLevel
case ErrorLevel:
return zapcore.ErrorLevel
case PanicLevel:
return zapcore.PanicLevel
case FatalLevel:
return zapcore.FatalLevel
default:
return zapcore.InfoLevel
}
}

163
options.go Normal file
View File

@ -0,0 +1,163 @@
package chestnut
import (
"github.com/jrapoport/chestnut/encoding/compress"
"github.com/jrapoport/chestnut/encryptor"
"github.com/jrapoport/chestnut/encryptor/crypto"
"github.com/jrapoport/chestnut/log"
)
// ChestOptions provides a default implementation for common options for a secure store.
type ChestOptions struct {
encryptor crypto.Encryptor
chainEncryptors []crypto.Encryptor
compression compress.Format
compressor compress.CompressorFunc
decompressor compress.DecompressorFunc
// overwrites allows a storage chest to save data over existing data with the same storage key.
// if Overwrite is true, overwrite are enabled and successive calls to save data
// with the same key will succeed. The existing data will be overwritten by the new data.
// if Overwrite is false, overwrite are disabled and successive calls to save data
// with the same key will fail with an error. The existing data will not be overwritten.
overwrites bool
log log.Logger
}
// DefaultChestOptions represents the recommended default ChestOptions for a store.
var DefaultChestOptions = ChestOptions{
overwrites: true,
log: log.Log,
}
// A ChestOption sets options such as encryptors, key rolling, and other parameters, etc.
type ChestOption interface {
apply(*ChestOptions)
}
// EmptyChestOption does not alter the encrypted store's configuration.
// It can be embedded in another structure to build custom options.
type EmptyChestOption struct{}
func (EmptyChestOption) apply(*ChestOptions) {}
// funcOption wraps a function that modifies ChestOptions
// into an implementation of the ChestOption interface.
type funcOption struct {
f func(*ChestOptions)
}
// apply applies an Option to ChestOptions.
func (fdo *funcOption) apply(do *ChestOptions) {
fdo.f(do)
}
func newFuncOption(f func(*ChestOptions)) *funcOption {
return &funcOption{
f: f,
}
}
// applyOptions accepts a ChestOptions struct and applies the ChestOption(s) to it.
func applyOptions(opts ChestOptions, opt ...ChestOption) ChestOptions {
for _, o := range opt {
o.apply(&opts)
}
chainEncryptors(&opts)
return opts
}
// chainEncryptors chains all encryptors into one.
func chainEncryptors(opts *ChestOptions) {
// Prepend opts.encryptor to the chaining encryptors if it exists, so that single
// encryptor will be executed before any other chained encryptor.
encryptors := opts.chainEncryptors
if opts.encryptor != nil {
encryptors = append([]crypto.Encryptor{opts.encryptor}, opts.chainEncryptors...)
}
var chained crypto.Encryptor
if len(encryptors) == 0 {
chained = nil
} else if len(encryptors) == 1 {
chained = encryptors[0]
} else {
chained = encryptor.NewChainEncryptor(encryptors...)
}
opts.encryptor = chained
}
// WithEncryptor returns a ChestOption that specifies the encryptor to use.
func WithEncryptor(e crypto.Encryptor) ChestOption {
return newFuncOption(func(o *ChestOptions) {
if o.encryptor != nil {
panic("The encryptor was already set and may not be reset.")
}
o.encryptor = e
})
}
// WithEncryptorChain returns a ChestOption that specifies an encryptor chain.
// for encrypted stores. The first encryptor will be the outer most,
// while the last encryptor will be the inner most wrapper around the real call.
// All encryptors added by this method will be chained. If a single encryptor
// has also been set, it will be *prepended* to the encryptor chain,
// making it the outer most encryptor in the encryptor chain.
func WithEncryptorChain(encryptors ...crypto.Encryptor) ChestOption {
return newFuncOption(func(o *ChestOptions) {
o.chainEncryptors = append(o.chainEncryptors, encryptors...)
})
}
// WithAES is a convenience that returns a ChestOption which sets the encryptor
// to be an AESEncryptor initialized with a key length, cipher mode, and Secret.
func WithAES(keyLen crypto.KeyLen, mode crypto.Mode, secret crypto.Secret) ChestOption {
return WithEncryptor(encryptor.NewAESEncryptor(keyLen, mode, secret))
}
// WithCompressors instructs the storage chest to compress/decompress data with these compressor
// functions before committing it. If this option is set, WithCompression is ignored.
func WithCompressors(c compress.CompressorFunc, d compress.DecompressorFunc) ChestOption {
return newFuncOption(func(o *ChestOptions) {
o.compression = compress.Custom
o.compressor = c
o.decompressor = d
})
}
// WithCompression instructs the storage chest to compress data using the this compression format
// before committing it. Compression this way is self-contained, meaning changes only effect data
// going forward. Previously saved data, compressed or uncompressed, will be transparently retrieved
// regardless of a change to this setting.
func WithCompression(format compress.Format) ChestOption {
return newFuncOption(func(o *ChestOptions) {
o.compression = format
})
}
// OverwritesForbidden prevents the store from overwriting existing data.
func OverwritesForbidden() ChestOption {
return newFuncOption(func(o *ChestOptions) {
o.overwrites = false
})
}
// WithLogger returns a StoreOption which sets the logger to use for the encrypted store.
func WithLogger(l log.Logger) ChestOption {
return newFuncOption(func(o *ChestOptions) {
o.log = l
})
}
// WithStdLogger is a convenience that returns a StoreOption for a standard err logger.
func WithStdLogger(lvl log.Level) ChestOption {
return WithLogger(log.NewStdLoggerWithLevel(lvl))
}
// WithLogrusLogger is a convenience that returns a StoreOption for a default logrus logger.
func WithLogrusLogger(lvl log.Level) ChestOption {
return WithLogger(log.NewLogrusLoggerWithLevel(lvl))
}
// WithZapLogger is a convenience that returns a StoreOption for a production zap logger.
func WithZapLogger(lvl log.Level) ChestOption {
return WithLogger(log.NewZapLoggerWithLevel(lvl))
}

250
storage/nuts/store.go Normal file
View File

@ -0,0 +1,250 @@
package nuts
import (
"bytes"
"errors"
"fmt"
"github.com/jrapoport/chestnut/log"
"github.com/jrapoport/chestnut/storage"
jsoniter "github.com/json-iterator/go"
"github.com/xujiajun/nutsdb"
)
const logName = "nutsdb"
// Store is an implementation the Storage interface for nutsdb
// https://github.com/xujiajun/nutsdb.
type Store struct {
opts storage.StoreOptions
path string
db *nutsdb.DB
log log.Logger
}
var _ storage.Storage = (*Store)(nil)
// NewStore is used to instantiate a datastore backed by nutsdb.
func NewStore(path string, opt ...storage.StoreOption) *Store {
opts := storage.ApplyOptions(storage.DefaultStoreOptions, opt...)
logger := log.Named(opts.Logger(), logName)
if path == "" {
logger.Fatal("store path required")
}
return &Store{path: path, opts: opts, log: logger}
}
// Options returns the configuration options for the store.
func (s *Store) Options() storage.StoreOptions {
return s.opts
}
// Open opens the store.
func (s *Store) Open() (err error) {
s.log.Debugf("opening store at path: %s", s.path)
opt := nutsdb.DefaultOptions
opt.Dir = s.path
if s.db, err = nutsdb.Open(opt); err != nil {
err = s.logError("open", err)
return
}
s.log.Infof("opened store at path: %s", s.path)
return
}
// Put an entry in the store.
func (s *Store) Put(name string, key []byte, value []byte) error {
s.log.Debugf("put: %d value bytes to key: %s", len(value), key)
if err := storage.ValidKey(name, key); err != nil {
return s.logError("put", err)
} else if len(value) <= 0 {
err = errors.New("value cannot be empty")
return s.logError("put", err)
}
putValue := func(tx *nutsdb.Tx) error {
s.log.Debugf("put: tx key: %s.%s value (%d bytes)",
name, string(key), len(value))
return tx.Put(name, key, value, 0)
}
return s.logError("put", s.db.Update(putValue))
}
// Get a value from the store.
func (s *Store) Get(name string, key []byte) ([]byte, error) {
s.log.Debugf("get: value at key: %s", key)
if err := storage.ValidKey(name, key); err != nil {
return nil, s.logError("get", err)
}
var value []byte
getValue := func(tx *nutsdb.Tx) error {
s.log.Debugf("get: tx key: %s.%s",
name, key)
e, err := tx.Get(name, key)
if err != nil {
return err
}
value = e.Value
s.log.Debugf("get: tx key: %s.%s value (%d bytes)",
name, string(key), len(value))
return nil
}
if err := s.db.View(getValue); err != nil {
return nil, s.logError("get", err)
}
return value, nil
}
// Save the value in v and store the result at key.
func (s *Store) Save(name string, key []byte, v interface{}) error {
bytes, err := jsoniter.Marshal(v)
if err != nil {
return s.logError("save", err)
}
return s.Put(name, key, bytes)
}
// Load the value at key and stores the result in v.
func (s *Store) Load(name string, key []byte, v interface{}) error {
bytes, err := s.Get(name, key)
if err != nil {
return s.logError("load", err)
}
return s.logError("load", jsoniter.Unmarshal(bytes, v))
}
// Has checks for a key in the store.
func (s *Store) Has(name string, key []byte) (bool, error) {
s.log.Debugf("has: key: %s", key)
if err := storage.ValidKey(name, key); err != nil {
return false, s.logError("has", err)
}
var has bool
hasKey := func(tx *nutsdb.Tx) error {
s.log.Debugf("has: tx get namespace: %s", name)
entries, err := tx.GetAll(name)
if err != nil {
return err
}
s.log.Debugf("has: tx found %d keys in: %s", len(entries), name)
for _, entry := range entries {
has = bytes.Equal(key, entry.Key)
if has {
s.log.Debugf("has: tx key found: %s.%s", name, string(key))
break
}
}
return nil
}
if err := s.db.View(hasKey); err != nil {
return false, s.logError("has", err)
}
s.log.Debugf("has: found key %s: %t", key, has)
return has, nil
}
// Delete removes a key from the store.
func (s *Store) Delete(name string, key []byte) error {
s.log.Debugf("delete: key: %s", key)
if err := storage.ValidKey(name, key); err != nil {
return s.logError("delete", err)
}
del := func(tx *nutsdb.Tx) error {
s.log.Debugf("delete: tx key: %s.%s", name, string(key))
return tx.Delete(name, key)
}
return s.logError("delete", s.db.Update(del))
}
// List returns a list of all keys in the namespace.
func (s *Store) List(name string) (keys [][]byte, err error) {
s.log.Debugf("list: keys in namespace: %s", name)
listKeys := func(tx *nutsdb.Tx) error {
var txErr error
keys, txErr = s.list(tx, name)
return txErr
}
if err = s.db.View(listKeys); err != nil {
return nil, s.logError("list", err)
}
s.log.Debugf("list: found %d keys: %s", len(keys), keys)
return
}
func (s *Store) list(tx *nutsdb.Tx, name string) ([][]byte, error) {
var keys [][]byte
s.log.Debugf("list: tx scan namespace: %s", name)
entries, err := tx.GetAll(name)
if err != nil {
return nil, err
}
keys = make([][]byte, len(entries))
s.log.Debugf("list: tx found %d keys in: %s", len(entries), name)
for i, entry := range entries {
s.log.Debugf("list: tx found key: %s.%s", name, string(entry.Key))
keys[i] = entry.Key
}
return keys, nil
}
// ListAll returns a list of all keys in the store.
func (s *Store) ListAll() (map[string][][]byte, error) {
s.log.Debugf("list: all keys")
var total int
allKeys := map[string][][]byte{}
listKeys := func(tx *nutsdb.Tx) error {
for name := range s.db.BPTreeIdx {
keys, txErr := s.list(tx, name)
if txErr != nil {
return txErr
}
allKeys[name] = keys
total += len(keys)
}
return nil
}
if err := s.db.View(listKeys); err != nil {
return nil, s.logError("list", err)
}
s.log.Debugf("list: found %d keys: %s", total, allKeys)
return allKeys, nil
}
// Export copies the datastore to directory at path.
func (s *Store) Export(path string) error {
s.log.Debugf("export: to path: %s", path)
if path == "" {
err := fmt.Errorf("invalid path: %s", path)
return s.logError("export", err)
} else if s.path == path {
err := fmt.Errorf("path cannot be store path: %s", path)
return s.logError("export", err)
}
if err := s.db.Backup(path); err != nil {
return s.logError("export", err)
}
s.log.Debugf("export: to path complete: %s", path)
return nil
}
// Close closes the datastore and releases all db resources.
func (s *Store) Close() error {
s.log.Debugf("closing store at path: %s", s.path)
defer func() {
// this is fine because the only possible error
// is ErrDBClosed if the db is *already* closed
s.db = nil
s.log.Info("store closed")
}()
return s.logError("close", s.db.Close())
}
func (s *Store) logError(name string, err error) error {
if err == nil {
return nil
}
if name != "" {
err = fmt.Errorf("%s: %w", name, err)
}
s.log.Error(err)
return err
}

221
storage/nuts/store_test.go Normal file
View File

@ -0,0 +1,221 @@
package nuts
import (
"fmt"
"sort"
"testing"
"github.com/google/uuid"
"github.com/jrapoport/chestnut/log"
"github.com/jrapoport/chestnut/storage"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
)
type testCase struct {
name string
key string
value string
err assert.ErrorAssertionFunc
has assert.BoolAssertionFunc
}
type TestObject struct {
Value string
}
var (
testName = "test-name"
testKey = "test-key"
testValue = "test-value"
testObj = &TestObject{"hello"}
)
var putTests = []testCase{
{"", "", "", assert.Error, assert.False},
{"a", testKey, "", assert.Error, assert.False},
{"b", testKey, testValue, assert.NoError, assert.True},
{"c/c", testKey, testValue, assert.NoError, assert.True},
{".d", testKey, testValue, assert.NoError, assert.True},
{testName, "", "", assert.Error, assert.False},
{testName, "a", "", assert.Error, assert.False},
{testName, "b", testValue, assert.NoError, assert.True},
{testName, "c/c", testValue, assert.NoError, assert.True},
{testName, ".d", testValue, assert.NoError, assert.True},
{testName, testKey, testValue, assert.NoError, assert.True},
}
var tests = append(putTests,
testCase{testName, "not-found", "", assert.Error, assert.False},
)
type StoreTestSuite struct {
suite.Suite
store *Store
}
func TestStore(t *testing.T) {
suite.Run(t, new(StoreTestSuite))
}
func (ts *StoreTestSuite) SetupTest() {
ts.store = NewStore(ts.T().TempDir())
err := ts.store.Open()
assert.NoError(ts.T(), err)
}
func (ts *StoreTestSuite) TearDownTest() {
err := ts.store.Close()
assert.NoError(ts.T(), err)
}
func (ts *StoreTestSuite) BeforeTest(_, testName string) {
switch testName {
case "TestStore_Put",
"TestStore_Save",
"TestStore_Load",
"TestStore_List",
"TestStore_ListAll":
break
default:
ts.TestStore_Put()
}
}
func (ts *StoreTestSuite) TestStore_Put() {
for i, test := range putTests {
err := ts.store.Put(test.name, []byte(test.key), []byte(test.value))
test.err(ts.T(), err, "%d test name: %s key: %s", i, test.name, test.key)
}
}
func (ts *StoreTestSuite) TestStore_Save() {
err := ts.store.Save(testName, []byte(testKey), testObj)
assert.NoError(ts.T(), err)
}
func (ts *StoreTestSuite) TestStore_Load() {
ts.T().Run("Setup", func(t *testing.T) {
ts.TestStore_Save()
})
to := &TestObject{}
err := ts.store.Load(testName, []byte(testKey), to)
assert.NoError(ts.T(), err)
assert.Equal(ts.T(), testObj, to)
}
func (ts *StoreTestSuite) TestStore_Get() {
for i, test := range tests {
value, err := ts.store.Get(test.name, []byte(test.key))
test.err(ts.T(), err, "%d test name: %s key: %s", i, test.name, test.key)
assert.Equal(ts.T(), test.value, string(value),
"%d test key: %s", i, test.key)
}
}
func (ts *StoreTestSuite) TestStore_Has() {
for i, test := range tests {
has, _ := ts.store.Has(test.name, []byte(test.key))
test.has(ts.T(), has, "%d test key: %s", i, test.key)
}
}
func (ts *StoreTestSuite) TestStore_List() {
const listLen = 100
list := make([]string, listLen)
for i := 0; i < listLen; i++ {
list[i] = uuid.New().String()
err := ts.store.Put(testName, []byte(list[i]), []byte(testValue))
assert.NoError(ts.T(), err)
}
keys, err := ts.store.List(testName)
assert.NoError(ts.T(), err)
assert.Len(ts.T(), keys, listLen)
// put both lists in the same order so we can compare them
strKeys := make([]string, len(keys))
for i, k := range keys {
strKeys[i] = string(k)
}
sort.Strings(list)
sort.Strings(strKeys)
assert.Equal(ts.T(), list, strKeys)
}
func (ts *StoreTestSuite) TestStore_ListAll() {
const listLen = 100
list := make([]string, listLen)
for i := 0; i < listLen; i++ {
list[i] = uuid.New().String()
ns := fmt.Sprintf("%s%d", testName, i)
err := ts.store.Put(ns, []byte(list[i]), []byte(testValue))
assert.NoError(ts.T(), err)
}
keyMap, err := ts.store.ListAll()
assert.NoError(ts.T(), err)
var keys []string
for _, ks := range keyMap {
for _, k := range ks {
keys = append(keys, string(k))
}
}
assert.Len(ts.T(), keys, listLen)
sort.Strings(list)
sort.Strings(keys)
assert.Equal(ts.T(), list, keys)
}
func (ts *StoreTestSuite) TestStore_Delete() {
var deleteTests = []struct {
key string
err assert.ErrorAssertionFunc
}{
{"", assert.Error},
{"a", assert.NoError},
{"b", assert.NoError},
{"c/c", assert.NoError},
{".d", assert.NoError},
{"eee", assert.NoError},
{"not-found", assert.NoError},
}
for i, test := range deleteTests {
err := ts.store.Delete(testName, []byte(test.key))
test.err(ts.T(), err, "%d test key: %s", i, test.key)
}
}
func (ts *StoreTestSuite) TestStore_Export() {
err := ts.store.Export("")
assert.Error(ts.T(), err)
err = ts.store.Export(ts.store.path)
assert.Error(ts.T(), err)
err = ts.store.Export(ts.T().TempDir())
assert.NoError(ts.T(), err)
}
func TestStore_WithLogger(t *testing.T) {
levels := []log.Level{
log.DebugLevel,
log.InfoLevel,
log.WarnLevel,
log.ErrorLevel,
log.PanicLevel,
}
type LoggerOpt func(log.Level) storage.StoreOption
logOpts := []LoggerOpt{
storage.WithLogrusLogger,
storage.WithStdLogger,
storage.WithZapLogger,
}
path := t.TempDir()
for _, level := range levels {
for _, logOpt := range logOpts {
opt := logOpt(level)
store := NewStore(path, opt)
assert.NotNil(t, store)
err := store.Open()
assert.NoError(t, err)
err = store.Close()
assert.NoError(t, err)
}
}
}

76
storage/options.go Normal file
View File

@ -0,0 +1,76 @@
package storage
import "github.com/jrapoport/chestnut/log"
// StoreOptions provides a default implementation for common storage Options stores should support.
type StoreOptions struct {
log log.Logger
}
// Logger returns the configured logger for the store.
func (o StoreOptions) Logger() log.Logger {
return o.log
}
// DefaultStoreOptions represents the recommended default StoreOptions for a store.
var DefaultStoreOptions = StoreOptions{
log: log.Log,
}
// A StoreOption sets options such disabling overwrite, and other parameters, etc.
type StoreOption interface {
apply(*StoreOptions)
}
// EmptyStoreOption does not alter the store configuration.
// It can be embedded in another structure to build custom options.
type EmptyStoreOption struct{}
func (EmptyStoreOption) apply(*StoreOptions) {}
// funcOption wraps a function that modifies StoreOptions
// into an implementation of the StoreOption interface.
type funcOption struct {
f func(*StoreOptions)
}
// Apply applies an StoreOption to StoreOptions.
func (fdo *funcOption) apply(do *StoreOptions) {
fdo.f(do)
}
func newFuncOption(f func(*StoreOptions)) *funcOption {
return &funcOption{
f: f,
}
}
// ApplyOptions accepts an StoreOptions struct and applies the StoreOption(s) to it.
func ApplyOptions(opts StoreOptions, opt ...StoreOption) StoreOptions {
for _, o := range opt {
o.apply(&opts)
}
return opts
}
// WithLogger returns a StoreOption which sets the logger to use for the encrypted store.
func WithLogger(l log.Logger) StoreOption {
return newFuncOption(func(o *StoreOptions) {
o.log = l
})
}
// WithStdLogger is a convenience that returns a StoreOption for a standard err logger.
func WithStdLogger(lvl log.Level) StoreOption {
return WithLogger(log.NewStdLoggerWithLevel(lvl))
}
// WithLogrusLogger is a convenience that returns a StoreOption for a default logrus logger.
func WithLogrusLogger(lvl log.Level) StoreOption {
return WithLogger(log.NewLogrusLoggerWithLevel(lvl))
}
// WithZapLogger is a convenience that returns a StoreOption for a production zap logger.
func WithZapLogger(lvl log.Level) StoreOption {
return WithLogger(log.NewZapLoggerWithLevel(lvl))
}

53
storage/storage.go Normal file
View File

@ -0,0 +1,53 @@
package storage
import (
"errors"
"fmt"
)
// Storage provides a management interface for a datastore.
type Storage interface {
// Open opens the store.
Open() error
// Put a value in the store.
Put(namespace string, key []byte, value []byte) error
// Get a value from the store.
Get(namespace string, key []byte) (value []byte, err error)
// Has checks for a key in the store.
Has(namespace string, key []byte) (bool, error)
// Save the value in v and stores the result at key.
Save(namespace string, key []byte, v interface{}) error
// Load the value at key and stores the result in v.
Load(namespace string, key []byte, v interface{}) error
// List returns a list of all keys in the namespace.
List(namespace string) ([][]byte, error)
// Delete removes a key from the store.
Delete(name string, key []byte) error
// Close closes the store.
Close() error
// Export saves the store to path.
Export(path string) error
}
// ErrInvalidKey the storage key is invalid.
var ErrInvalidKey = errors.New("invalid storage key")
// ValidKey returns nil if the key is valid, otherwise ErrInvalidKey.
func ValidKey(name string, key []byte) error {
if name == "" {
return fmt.Errorf("%w namespace: %s", ErrInvalidKey, name)
}
if len(key) <= 0 {
return fmt.Errorf("%w: %s", ErrInvalidKey, key)
}
return nil
}

Some files were not shown because too many files have changed in this diff Show More