summaryrefslogtreecommitdiff
path: root/vendor/github.com/Shopify/ejson/crypto
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/crypto
parent06955432208ae0c4caf8f2a436af8ba94c97d0ce (diff)
Vendoring github.com/Shopify/ejson
Signed-off-by: Dave Henderson <dhenderson@gmail.com>
Diffstat (limited to 'vendor/github.com/Shopify/ejson/crypto')
-rw-r--r--vendor/github.com/Shopify/ejson/crypto/boxed_message.go104
-rw-r--r--vendor/github.com/Shopify/ejson/crypto/crypto.go160
2 files changed, 264 insertions, 0 deletions
diff --git a/vendor/github.com/Shopify/ejson/crypto/boxed_message.go b/vendor/github.com/Shopify/ejson/crypto/boxed_message.go
new file mode 100644
index 00000000..5dc86980
--- /dev/null
+++ b/vendor/github.com/Shopify/ejson/crypto/boxed_message.go
@@ -0,0 +1,104 @@
+package crypto
+
+import (
+ "encoding/base64"
+ "fmt"
+ "regexp"
+ "strconv"
+)
+
+var messageParser = regexp.MustCompile("\\AEJ\\[(\\d):([A-Za-z0-9+=/]{44}):([A-Za-z0-9+=/]{32}):(.+)\\]\\z")
+
+// boxedMessage dumps and loads the wire format for encrypted messages. The
+// schema is fairly simple:
+//
+// "EJ["
+// SchemaVersion ( "1" )
+// ":"
+// EncrypterPublic :: base64-encoded 32-byte key
+// ":"
+// Nonce :: base64-encoded 24-byte nonce
+// ":"
+// Box :: base64-encoded encrypted message
+// "]"
+type boxedMessage struct {
+ SchemaVersion int
+ EncrypterPublic [32]byte
+ Nonce [24]byte
+ Box []byte
+}
+
+// IsBoxedMessage tests whether a value is formatted using the boxedMessage
+// format. This can be used to determine whether a string value requires
+// encryption or is already encrypted.
+func IsBoxedMessage(data []byte) bool {
+ return messageParser.Find(data) != nil
+}
+
+// Dump dumps to the wire format
+func (b *boxedMessage) Dump() []byte {
+ pub := base64.StdEncoding.EncodeToString(b.EncrypterPublic[:])
+ nonce := base64.StdEncoding.EncodeToString(b.Nonce[:])
+ box := base64.StdEncoding.EncodeToString(b.Box)
+
+ str := fmt.Sprintf("EJ[%d:%s:%s:%s]",
+ b.SchemaVersion, pub, nonce, box)
+ return []byte(str)
+}
+
+// Load restores from the wire format.
+func (b *boxedMessage) Load(from []byte) error {
+ var ssver, spub, snonce, sbox string
+ var err error
+
+ allMatches := messageParser.FindAllStringSubmatch(string(from), -1) // -> [][][]byte
+ if len(allMatches) != 1 {
+ return fmt.Errorf("invalid message format")
+ }
+ matches := allMatches[0]
+ if len(matches) != 5 {
+ return fmt.Errorf("invalid message format")
+ }
+
+ ssver = matches[1]
+ spub = matches[2]
+ snonce = matches[3]
+ sbox = matches[4]
+
+ b.SchemaVersion, err = strconv.Atoi(ssver)
+ if err != nil {
+ return err
+ }
+
+ pub, err := base64.StdEncoding.DecodeString(spub)
+ if err != nil {
+ return err
+ }
+ pubBytes := []byte(pub)
+ if len(pubBytes) != 32 {
+ return fmt.Errorf("public key invalid")
+ }
+ var public [32]byte
+ copy(public[:], pubBytes[0:32])
+ b.EncrypterPublic = public
+
+ nnc, err := base64.StdEncoding.DecodeString(snonce)
+ if err != nil {
+ return err
+ }
+ nonceBytes := []byte(nnc)
+ if len(nonceBytes) != 24 {
+ return fmt.Errorf("nonce invalid")
+ }
+ var nonce [24]byte
+ copy(nonce[:], nonceBytes[0:24])
+ b.Nonce = nonce
+
+ box, err := base64.StdEncoding.DecodeString(sbox)
+ if err != nil {
+ return err
+ }
+ b.Box = []byte(box)
+
+ return nil
+}
diff --git a/vendor/github.com/Shopify/ejson/crypto/crypto.go b/vendor/github.com/Shopify/ejson/crypto/crypto.go
new file mode 100644
index 00000000..63de1c6a
--- /dev/null
+++ b/vendor/github.com/Shopify/ejson/crypto/crypto.go
@@ -0,0 +1,160 @@
+// Package crypto implements a simple convenience wrapper around
+// golang.org/x/crypto/nacl/box. It ultimately models a situation where you
+// don't care about authenticating the encryptor, so the nonce and encryption
+// public key are prepended to the encrypted message.
+//
+// Shared key precomputation is used when encrypting but not when decrypting.
+// This is not an inherent limitation, but it would complicate the
+// implementation a little bit to do precomputation during decryption also.
+// If performance becomes an issue (highly unlikely), it's completely feasible
+// to add.
+package crypto
+
+import (
+ "crypto/rand"
+ "errors"
+ "fmt"
+
+ "golang.org/x/crypto/nacl/box"
+)
+
+// Keypair models a Curve25519 keypair. To generate a new Keypair, declare an
+// empty one and call Generate() on it.
+type Keypair struct {
+ Public [32]byte
+ Private [32]byte
+}
+
+// Encrypter is generated from a keypair (typically a newly-generated ephemeral
+// keypair, used only for this session) with the public key of an authorized
+// decrypter. It is then capable of encrypting messages to that decrypter's
+// private key. An instance should normally be obtained only by calling
+// Encrypter() on a Keypair instance.
+type Encrypter struct {
+ Keypair *Keypair
+ PeerPublic [32]byte
+ SharedKey [32]byte
+}
+
+// Decrypter is generated from a keypair (a fixed keypair, generally, whose
+// private key is stored in configuration management or otherwise), and used to
+// decrypt messages. It should normally be obtained by calling Decrypter() on a
+// Keypair instance.
+type Decrypter struct {
+ Keypair *Keypair
+}
+
+// ErrDecryptionFailed means the decryption didn't work. This normally
+// indicates that the message was corrupted or the wrong keypair was used.
+var ErrDecryptionFailed = errors.New("couldn't decrypt message")
+
+// Generate generates a new Curve25519 keypair into a (presumably) empty Keypair
+// structure.
+func (k *Keypair) Generate() (err error) {
+ var pub, priv *[32]byte
+ pub, priv, err = box.GenerateKey(rand.Reader)
+ if err != nil {
+ return
+ }
+ k.Public = *pub
+ k.Private = *priv
+ return
+}
+
+// PublicString returns the public key in the canonical hex-encoded printable form.
+func (k *Keypair) PublicString() string {
+ return fmt.Sprintf("%x", k.Public)
+}
+
+// PrivateString returns the private key in the canonical hex-encoded printable form.
+func (k *Keypair) PrivateString() string {
+ return fmt.Sprintf("%x", k.Private)
+}
+
+// Encrypter returns an Encrypter instance, given a public key, to encrypt
+// messages to the paired, unknown, private key.
+func (k *Keypair) Encrypter(peerPublic [32]byte) *Encrypter {
+ return NewEncrypter(k, peerPublic)
+}
+
+// Decrypter returns a Decrypter instance, used to decrypt properly formatted
+// messages from arbitrary encrypters.
+func (k *Keypair) Decrypter() *Decrypter {
+ return &Decrypter{Keypair: k}
+}
+
+// NewEncrypter instantiates an Encrypter after pre-computing the shared key for
+// the owned keypair and the given decrypter public key.
+func NewEncrypter(kp *Keypair, peerPublic [32]byte) *Encrypter {
+ var shared [32]byte
+ box.Precompute(&shared, &peerPublic, &kp.Private)
+ return &Encrypter{
+ Keypair: kp,
+ PeerPublic: peerPublic,
+ SharedKey: shared,
+ }
+}
+
+func (e *Encrypter) encrypt(message []byte) (*boxedMessage, error) {
+ nonce, err := genNonce()
+ if err != nil {
+ return nil, err
+ }
+
+ out := box.SealAfterPrecomputation(nil, []byte(message), &nonce, &e.SharedKey)
+
+ return &boxedMessage{
+ SchemaVersion: 1,
+ EncrypterPublic: e.Keypair.Public,
+ Nonce: nonce,
+ Box: out,
+ }, nil
+}
+
+// Encrypt takes a plaintext message and returns an encrypted message. Unlike
+// raw nacl/box encryption, this message is decryptable without passing the
+// nonce or public key out-of-band, as it includes both. This is not less
+// secure, it just doesn't allow for authorizing the encryptor. That's fine,
+// since authorization isn't a desired property of this particular cryptosystem.
+func (e *Encrypter) Encrypt(message []byte) ([]byte, error) {
+ if IsBoxedMessage(message) {
+ return message, nil
+ }
+ boxedMessage, err := e.encrypt(message)
+ if err != nil {
+ return nil, err
+ }
+ return boxedMessage.Dump(), nil
+}
+
+// Decrypt is passed an encrypted message or a particular format (the format
+// generated by (*Encrypter)Encrypt(), which includes the nonce and public key
+// used to create the ciphertext. It returns the decrypted string. Note that,
+// unlike with encryption, Shared-key-precomputation is not used for decryption.
+func (d *Decrypter) Decrypt(message []byte) ([]byte, error) {
+ var bm boxedMessage
+ if err := bm.Load(message); err != nil {
+ return nil, err
+ }
+ return d.decrypt(&bm)
+}
+
+func (d *Decrypter) decrypt(bm *boxedMessage) ([]byte, error) {
+ plaintext, ok := box.Open(nil, bm.Box, &bm.Nonce, &bm.EncrypterPublic, &d.Keypair.Private)
+ if !ok {
+ return nil, ErrDecryptionFailed
+ }
+ return plaintext, nil
+}
+
+func genNonce() (nonce [24]byte, err error) {
+ var n int
+ n, err = rand.Read(nonce[0:24])
+ if err != nil {
+ return
+ }
+ if n != 24 {
+ err = fmt.Errorf("not enough bytes returned from rand.Reader")
+ }
+ return
+}