initial commit
This commit is contained in:
commit
ff814d366c
|
@ -0,0 +1 @@
|
|||
* @jrapoport
|
|
@ -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.**
|
|
@ -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
|
|
@ -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 }}
|
|
@ -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
|
|
@ -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
|
|
@ -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).
|
|
@ -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.
|
|
@ -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
|
|
@ -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")
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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()))
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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())
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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()
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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())
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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
|
||||
})
|
||||
}
|
|
@ -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")
|
||||
}
|
|
@ -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}
|
|
@ -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}
|
|
@ -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}
|
|
@ -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}
|
|
@ -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}
|
|
@ -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}
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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")
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
package aes
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestCipherCFB(t *testing.T) {
|
||||
testCipher(t, EncryptCFB, DecryptCFB)
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
package aes
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestCipherCTR(t *testing.T) {
|
||||
testCipher(t, EncryptCTR, DecryptCTR)
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
package aes
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestCipherGCM(t *testing.T) {
|
||||
testCipher(t, EncryptGCM, DecryptGCM)
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
})
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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))
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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()))
|
||||
}
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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))
|
||||
}
|
|
@ -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) })
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
```
|
|
@ -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)
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
)
|
|
@ -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.
|
|
@ -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()
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
})
|
||||
}
|
|
@ -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
|
||||
)
|
|
@ -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")
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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{})
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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...)
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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))
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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))
|
||||
}
|
|
@ -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
Loading…
Reference in New Issue