summaryrefslogtreecommitdiff
path: root/vendor/github.com/Shopify/ejson/json
diff options
context:
space:
mode:
authorDave Henderson <dhenderson@gmail.com>2018-11-09 23:45:23 -0500
committerDave Henderson <dhenderson@gmail.com>2018-11-09 23:46:55 -0500
commit27078d2f3b249202b8f799d2cf9e0b3cd80d69ec (patch)
treedbd2afd1539294b818b303615e36d850845daf78 /vendor/github.com/Shopify/ejson/json
parent06955432208ae0c4caf8f2a436af8ba94c97d0ce (diff)
Vendoring github.com/Shopify/ejson
Signed-off-by: Dave Henderson <dhenderson@gmail.com>
Diffstat (limited to 'vendor/github.com/Shopify/ejson/json')
-rw-r--r--vendor/github.com/Shopify/ejson/json/key.go62
-rw-r--r--vendor/github.com/Shopify/ejson/json/pipeline.go73
-rw-r--r--vendor/github.com/Shopify/ejson/json/walker.go133
3 files changed, 268 insertions, 0 deletions
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
+}