Skip to content

Commit

Permalink
Move decryption to pkg
Browse files Browse the repository at this point in the history
  • Loading branch information
everesio committed Oct 10, 2020
1 parent 6c51971 commit be14649
Show file tree
Hide file tree
Showing 18 changed files with 996 additions and 227 deletions.
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@ WORKDIR "/code"
ADD . "/code"
RUN make BINARY=spring-config-decryptor ${MAKE_TARGET}

FROM alpine:3.11
FROM alpine:3.12
COPY --from=builder /code/spring-config-decryptor /spring-config-decryptor
ENTRYPOINT ["/spring-config-decryptor"]
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

.PHONY: clean build fmt test

TAG ?= "v0.0.2"
TAG ?= "v0.0.3"

BUILD_FLAGS ?=
BINARY ?= spring-config-decryptor
Expand Down
5 changes: 4 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,7 @@ module github.com/grepplabs/spring-config-decryptor

go 1.14

require golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59
require (
github.com/pkg/errors v0.9.1
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0
)
8 changes: 4 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200109152110-61a87790db17 h1:nVJ3guKA9qdkEQ3TUdXI9QSINo2CUPM/cySEvw2w8I0=
golang.org/x/crypto v0.0.0-20200109152110-61a87790db17/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59 h1:3zb4D3T4G8jdExgVU/95+vQXfpEPiMdCaZgmGVxjNHM=
golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0 h1:hb9wdF1z5waM+dSIICn1l0DkLVDT3hqhhQsDNUmHPRE=
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
Expand Down
222 changes: 4 additions & 218 deletions main.go
Original file line number Diff line number Diff line change
@@ -1,227 +1,15 @@
package main

import (
"bufio"
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"crypto/rsa"
"crypto/sha1"
"crypto/x509"
"encoding/base64"
"encoding/binary"
"encoding/hex"
"encoding/pem"
"errors"
"flag"
"fmt"
"io"
"io/ioutil"
"os"
"regexp"
"strings"

"golang.org/x/crypto/pbkdf2"
"github.com/grepplabs/spring-config-decryptor/pkg/decryptor"
)

const (
cipherPrefix = "{cipher}"
defaultSalt = "deadbeef"
)

var (
cipherPattern = regexp.MustCompile(`{cipher}([A-Za-z0-9+/=]*)`)
)

type ValueDecryptorOption func(decryptor *ValueDecryptor) error

type ValueDecryptor struct {
privateKey *rsa.PrivateKey
salt []byte
}

func NewValueDecryptor(key []byte, options ...ValueDecryptorOption) (*ValueDecryptor, error) {
privateKey, err := ParsePrivateKey(key)
if err != nil {
return nil, err
}
result := &ValueDecryptor{privateKey: privateKey}
if err := WithSalt(defaultSalt)(result); err != nil {
return nil, err
}
for _, option := range options {
if err = option(result); err != nil {
return nil, err
}
}
return result, nil
}

func ParsePrivateKey(key []byte) (*rsa.PrivateKey, error) {
block, _ := pem.Decode(key)
if block != nil {
key = block.Bytes
}
parsedKey, err := x509.ParsePKCS8PrivateKey(key)
if err != nil {
parsedKey, err = x509.ParsePKCS1PrivateKey(key)
if err != nil {
return nil, fmt.Errorf("private key should be a PEM or plain PKCS1 or PKCS8; parse error: %v", err)
}
}
parsed, ok := parsedKey.(*rsa.PrivateKey)
if !ok {
return nil, errors.New("private key is invalid")
}
return parsed, nil
}

func WithSalt(salt string) ValueDecryptorOption {
return func(decryptor *ValueDecryptor) error {
if saltBytes, err := hex.DecodeString(salt); err != nil {
return fmt.Errorf("salt '%s' cannot be hex decoded: %v", salt, err)
} else {
decryptor.salt = saltBytes
}
return nil
}
}

func (d ValueDecryptor) DecryptValue(value string) (string, error) {
if !strings.HasPrefix(value, cipherPrefix) {
return value, nil
}
value = strings.TrimPrefix(value, cipherPrefix)
data, err := base64.StdEncoding.DecodeString(value)
if err != nil {
return "", fmt.Errorf("value '%s' cannot be base64 decoded: %v", value, err)
}
return d.decryptData(data)
}

func (d ValueDecryptor) decryptData(data []byte) (string, error) {
if len(data) < 2 {
return "", errors.New("data too short to read session key length")
}
length := binary.BigEndian.Uint16(data[0:2])

if len(data) < int(length+2) {
return "", errors.New("data too short to read session key cipher text")
}
ciphertext := data[2 : length+2]

iv, err := rsa.DecryptPKCS1v15(rand.Reader, d.privateKey, ciphertext)
if err != nil {
return "", err
}
key := pbkdf2.Key([]byte(hex.EncodeToString(iv)), d.salt, 1024, 32, sha1.New)

plaintext, err := d.decryptCBC(key, data[2+length:])
if err != nil {
return "", err
}
return string(d.unpad(plaintext)), nil
}

func (d ValueDecryptor) decryptCBC(key, ciphertext []byte) (plaintext []byte, err error) {
var block cipher.Block

if block, err = aes.NewCipher(key); err != nil {
return
}
if len(ciphertext) < aes.BlockSize {
return nil, errors.New("cipher text length shorter than AES block size")
}

iv := ciphertext[:aes.BlockSize]
ciphertext = ciphertext[aes.BlockSize:]

cbc := cipher.NewCBCDecrypter(block, iv)
cbc.CryptBlocks(ciphertext, ciphertext)

plaintext = ciphertext

return
}

func (d ValueDecryptor) unpad(src []byte) []byte {
length := len(src)
unpadding := int(src[length-1])
return src[:(length - unpadding)]
}

type ConfigDecryptor struct {
valueDecryptor *ValueDecryptor
}

func NewConfigDecryptor(valueDecryptor *ValueDecryptor) *ConfigDecryptor {
return &ConfigDecryptor{
valueDecryptor: valueDecryptor,
}
}

func (c ConfigDecryptor) Decrypt(output io.Writer, input io.Reader) error {
var (
line string
err error
)
rd := bufio.NewReader(input)
wr := bufio.NewWriter(output)
defer func() {
if err := wr.Flush(); err != nil {
exitOnError("writing flush error: %v", err)
}
}()
for {
if line, err = rd.ReadString('\n'); err != nil {
if err == io.EOF {
if err = c.decryptLine(wr, line); err != nil {
return err
}
break
}
return fmt.Errorf("read file line error: %v", err)
}
if err = c.decryptLine(wr, line); err != nil {
return err
}
}
return nil
}

func (c ConfigDecryptor) decryptLine(wr *bufio.Writer, line string) (err error) {
line, err = c.processLine(line)
if err != nil {
return fmt.Errorf("line processing error: %v", err)
}
_, err = wr.WriteString(line)
if err != nil {
return fmt.Errorf("line writing error: %v", err)
}
return nil
}

func (c ConfigDecryptor) processLine(line string) (string, error) {
var sb strings.Builder

for {
ns := cipherPattern.FindStringIndex(line)
if ns == nil {
sb.WriteString(line)
break
}
sb.WriteString(line[:ns[0]])
value := line[ns[0]:ns[1]]
plainText, err := c.valueDecryptor.DecryptValue(value)
if err != nil {
return "", err
}
sb.WriteString(plainText)
line = line[ns[1]:]
}
return sb.String(), nil
}

const (
defaultEnvEncryptKey = "ENCRYPT_KEY"
)
Expand Down Expand Up @@ -282,14 +70,12 @@ func main() {
output = f
}

valueDecryptor, err := NewValueDecryptor(key)
dcr, err := decryptor.NewDecryptor(key)
if err != nil {
exitOnError("create value decryptor error: %v", err)
exitOnError("create decryptor error: %v", err)
return
}

configDecryptor := NewConfigDecryptor(valueDecryptor)
err = configDecryptor.Decrypt(output, input)
err = dcr.Decrypt(output, input)
if err != nil {
exitOnError("decrypt error: %v", err)
}
Expand Down
Loading

0 comments on commit be14649

Please sign in to comment.