From 27078d2f3b249202b8f799d2cf9e0b3cd80d69ec Mon Sep 17 00:00:00 2001 From: Dave Henderson Date: Fri, 9 Nov 2018 23:45:23 -0500 Subject: Vendoring github.com/Shopify/ejson Signed-off-by: Dave Henderson --- vendor/github.com/Shopify/ejson/json/key.go | 62 +++++++++++ vendor/github.com/Shopify/ejson/json/pipeline.go | 73 +++++++++++++ vendor/github.com/Shopify/ejson/json/walker.go | 133 +++++++++++++++++++++++ 3 files changed, 268 insertions(+) create mode 100644 vendor/github.com/Shopify/ejson/json/key.go create mode 100644 vendor/github.com/Shopify/ejson/json/pipeline.go create mode 100644 vendor/github.com/Shopify/ejson/json/walker.go (limited to 'vendor/github.com/Shopify/ejson/json') diff --git a/vendor/github.com/Shopify/ejson/json/key.go b/vendor/github.com/Shopify/ejson/json/key.go new file mode 100644 index 00000000..66177c88 --- /dev/null +++ b/vendor/github.com/Shopify/ejson/json/key.go @@ -0,0 +1,62 @@ +package json + +import ( + "encoding/hex" + "encoding/json" + "errors" +) + +const ( + // PublicKeyField is the key name at which the public key should be + // stored in an EJSON document. + PublicKeyField = "_public_key" +) + +// ErrPublicKeyMissing indicates that the PublicKeyField key was not found +// at the top level of the JSON document provided. +var ErrPublicKeyMissing = errors.New("public key not present in EJSON file") + +// ErrPublicKeyInvalid means that the PublicKeyField key was found, but the +// value could not be parsed into a valid key. +var ErrPublicKeyInvalid = errors.New("public key has invalid format") + +// ExtractPublicKey finds the _public_key value in an EJSON document and +// parses it into a key usable with the crypto library. +func ExtractPublicKey(data []byte) (key [32]byte, err error) { + var ( + obj map[string]interface{} + ks string + ok bool + bs []byte + ) + err = json.Unmarshal(data, &obj) + if err != nil { + return + } + k, ok := obj[PublicKeyField] + if !ok { + goto missing + } + ks, ok = k.(string) + if !ok { + goto invalid + } + if len(ks) != 64 { + goto invalid + } + bs, err = hex.DecodeString(ks) + if err != nil { + goto invalid + } + if len(bs) != 32 { + goto invalid + } + copy(key[:], bs) + return +missing: + err = ErrPublicKeyMissing + return +invalid: + err = ErrPublicKeyInvalid + return +} diff --git a/vendor/github.com/Shopify/ejson/json/pipeline.go b/vendor/github.com/Shopify/ejson/json/pipeline.go new file mode 100644 index 00000000..4527a18d --- /dev/null +++ b/vendor/github.com/Shopify/ejson/json/pipeline.go @@ -0,0 +1,73 @@ +package json + +type pipeline struct { + final []byte + err error + pendingBytes []byte + queue chan queueItem + done chan struct{} +} + +type queueItem struct { + pr <-chan promiseResult + bs []byte + term bool +} + +type promiseResult struct { + bytes []byte + err error +} + +func newPipeline() *pipeline { + pl := &pipeline{ + queue: make(chan queueItem, 512), + done: make(chan struct{}), + } + go pl.run() + return pl +} + +func (p *pipeline) run() { + for qi := range p.queue { + if qi.term { + close(p.done) + } else if qi.pr != nil { + res := <-qi.pr + if res.err != nil { + p.err = res.err + } + p.final = append(p.final, res.bytes...) + } else { + p.final = append(p.final, qi.bs...) + } + } +} + +func (p *pipeline) appendBytes(bs []byte) { + p.pendingBytes = append(p.pendingBytes, bs...) +} + +func (p *pipeline) appendByte(b byte) { + p.pendingBytes = append(p.pendingBytes, b) +} + +func (p *pipeline) appendPromise(ch <-chan promiseResult) { + p.flushPendingBytes() + p.queue <- queueItem{pr: ch} +} + +func (p *pipeline) flush() ([]byte, error) { + p.flushPendingBytes() + p.queue <- queueItem{term: true} + <-p.done + close(p.queue) + return p.final, p.err +} + +func (p *pipeline) flushPendingBytes() { + if len(p.pendingBytes) > 0 { + p.queue <- queueItem{bs: p.pendingBytes} + p.pendingBytes = nil + } +} diff --git a/vendor/github.com/Shopify/ejson/json/walker.go b/vendor/github.com/Shopify/ejson/json/walker.go new file mode 100644 index 00000000..b377f67e --- /dev/null +++ b/vendor/github.com/Shopify/ejson/json/walker.go @@ -0,0 +1,133 @@ +// Package json implements functions to load the Public key data from an EJSON +// file, and to walk that data file, encrypting or decrypting any keys which, +// according to the specification, are marked as encryptable (see README.md for +// details). +// +// It may be non-obvious why this is implemented using a scanner and not by +// loading the structure, manipulating it, then dumping it. Since Go's maps are +// explicitly randomized, that would cause the entire structure to be randomized +// each time the file was written, rendering diffs over time essentially +// useless. +package json + +import ( + "bytes" + "fmt" + + "github.com/dustin/gojson" +) + +// Walker takes an Action, which will run on fields selected by EJSON for +// encryption, and provides a Walk method, which iterates on all the fields in +// a JSON text, running the Action on all selected fields. Fields are selected +// if they are a Value (not a Key) of type string, and their referencing Key did +// *not* begin with an Underscore. Note that this +// underscore-to-disable-encryption syntax does not propagate down the hierarchy +// to children. +// That is: +// * In {"_a": "b"}, Action will not be run at all. +// * In {"a": "b"}, Action will be run with "b", and the return value will +// replace "b". +// * In {"k": {"a": ["b"]}, Action will run on "b". +// * In {"_k": {"a": ["b"]}, Action run on "b". +// * In {"k": {"_a": ["b"]}, Action will not run. +type Walker struct { + Action func([]byte) ([]byte, error) +} + +// Walk walks an entire JSON structure, running the ejsonWalker.Action on each +// actionable node. A node is actionable if it's a string *value*, and its +// referencing key doesn't begin with an underscore. For each actionable node, +// the contents are replaced with the result of Action. Everything else is +// unchanged. +func (ew *Walker) Walk(data []byte) ([]byte, error) { + var ( + inLiteral bool + literalStart int + isComment bool + scanner json.Scanner + ) + scanner.Reset() + pline := newPipeline() + for i, c := range data { + switch v := scanner.Step(&scanner, int(c)); v { + case json.ScanContinue, json.ScanSkipSpace: + // Uninteresting byte. Just advance to next. + case json.ScanBeginLiteral: + inLiteral = true + literalStart = i + case json.ScanObjectKey: + // The literal we just finished reading was a Key. Decide whether it was a + // encryptable by checking whether the first byte after the '"' was an + // underscore, then append it verbatim to the output buffer. + inLiteral = false + isComment = data[literalStart+1] == '_' + pline.appendBytes(data[literalStart:i]) + case json.ScanError: + // Some error happened; just bail. + pline.flush() + return nil, fmt.Errorf("invalid json") + case json.ScanEnd: + // We successfully hit the end of input. + pline.appendByte(c) + return pline.flush() + default: + if inLiteral { + inLiteral = false + // We finished reading some literal, and it wasn't a Key, meaning it's + // potentially encryptable. If it was a string, and the most recent Key + // encountered didn't begin with a '_', we are to encrypt it. In any + // other case, we append it verbatim to the output buffer. + if isComment || data[literalStart] != '"' { + pline.appendBytes(data[literalStart:i]) + } else { + res := make(chan promiseResult) + go func(subData []byte) { + actioned, err := ew.runAction(subData) + res <- promiseResult{actioned, err} + close(res) + }(data[literalStart:i]) + pline.appendPromise(res) + } + } + } + if !inLiteral { + // If we're in a literal, we save up bytes because we may have to encrypt + // them. Outside of a literal, we simply append each byte as we read it. + pline.appendByte(c) + } + } + if scanner.EOF() == json.ScanError { + // Unexpected EOF => malformed JSON + pline.flush() + return nil, fmt.Errorf("invalid json") + } + return pline.flush() +} + +func (ew *Walker) runAction(data []byte) ([]byte, error) { + trimmed := bytes.TrimSpace(data) + unquoted, ok := json.UnquoteBytes(trimmed) + if !ok { + return nil, fmt.Errorf("invalid json") + } + done, err := ew.Action(unquoted) + if err != nil { + return nil, err + } + quoted, err := quoteBytes(done) + if err != nil { + return nil, err + } + return append(quoted, data[len(trimmed):]...), nil +} + +// probably a better way to do this, but... +func quoteBytes(in []byte) ([]byte, error) { + data := []string{string(in)} + out, err := json.Marshal(data) + if err != nil { + return nil, err + } + return out[1 : len(out)-1], nil +} -- cgit v1.2.3