Skip to content

Commit

Permalink
Merge pull request #2 from ApplauseOSS/PD-4823-binary-envelope
Browse files Browse the repository at this point in the history
PD-4823 Support aws_encryption_sdk message format for decryption
  • Loading branch information
agaffney authored Jul 31, 2019
2 parents cbedec5 + 41f429d commit 6934b08
Show file tree
Hide file tree
Showing 9 changed files with 495 additions and 30 deletions.
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.0.1
0.1.0
29 changes: 7 additions & 22 deletions decrypt-and-start.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
package main

import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/ec2metadata"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/kms"

"encoding/base64"
"flag"
"fmt"
"github.com/applauseoss/decrypt-and-start/lib"
enc_sdk "github.com/applauseoss/decrypt-and-start/lib/aws_encryption_sdk"
"log"
"os"
"os/exec"
Expand All @@ -32,34 +29,22 @@ func Exec() {
}

func main() {
// Initialize a "fake" session to get our region
metaSession, _ := session.NewSession()
metaClient := ec2metadata.New(metaSession)
region, _ := metaClient.Region()
conf := aws.NewConfig().WithRegion(region)
// Initialize KMS session
sess := session.Must(session.NewSession(conf))
// KMS service client
svc := kms.New(sess)
for _, e := range os.Environ() {
// e = each k=v pair/line, pair = split k = [0], v = [1] array
pair := strings.SplitN(e, "=", 2)
// See if value starts with 'decrypt:'
if strings.HasPrefix(pair[1], "decrypt:") {
fmt.Println("Decrypting " + pair[0] + " ...")
cyphertext, err := base64.URLEncoding.DecodeString(strings.TrimPrefix(pair[1], "decrypt:"))
fmt.Println("Decrypting the value of " + pair[0] + "...")
ciphertext, err := base64.StdEncoding.DecodeString(strings.TrimPrefix(pair[1], "decrypt:"))
if err != nil {
log.Fatal(err)
}
// blob := []byte(string(cyphertext))
blob := cyphertext
// decrypt data
result, err := svc.Decrypt(&kms.DecryptInput{CiphertextBlob: blob})
kms_helper := enc_sdk.NewKmsHelper(lib.GetRegion())
decrypted_value, err := kms_helper.Decrypt(ciphertext)
if err != nil {
log.Fatal(err)
}
decrypted_value := string(result.Plaintext)
os.Setenv(pair[0], decrypted_value)
os.Setenv(pair[0], string(decrypted_value))
}
}
Exec()
Expand Down
8 changes: 8 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
module github.com/applauseoss/decrypt-and-start

go 1.12

require (
github.com/aws/aws-sdk-go v1.21.8
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4
)
11 changes: 11 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
github.com/aws/aws-sdk-go v1.21.8 h1:Lv6hW2twBhC6mGZAuWtqplEpIIqtVctJg02sE7Qn0Zw=
github.com/aws/aws-sdk-go v1.21.8/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
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=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
117 changes: 117 additions & 0 deletions lib/aws_encryption_sdk/algorithms.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package aws_encryption_sdk

import (
"crypto/sha256"
"crypto/sha512"
"hash"
)

const (
ALGORITHM_TYPE_AES = 1
ALGORITHM_MODE_GCM = 1
)

type Algorithm struct {
Id uint16
Type uint8
DataKeyLength uint16
Mode uint8
IVLength uint8
AuthTagLength uint8
HashFunc func() hash.Hash
}

// List of encryption algorithms for AWS Encryption SDK
// https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/algorithms-reference.html
var Algorithms = []Algorithm{
{
Id: 0x0378,
Type: ALGORITHM_TYPE_AES,
DataKeyLength: 32,
Mode: ALGORITHM_MODE_GCM,
IVLength: 12,
AuthTagLength: 16,
HashFunc: sha512.New384,
},
{
Id: 0x0346,
Type: ALGORITHM_TYPE_AES,
DataKeyLength: 24,
Mode: ALGORITHM_MODE_GCM,
IVLength: 12,
AuthTagLength: 16,
HashFunc: sha512.New384,
},
{
Id: 0x0214,
Type: ALGORITHM_TYPE_AES,
DataKeyLength: 16,
Mode: ALGORITHM_MODE_GCM,
IVLength: 12,
AuthTagLength: 16,
HashFunc: sha256.New,
},
{
Id: 0x0178,
Type: ALGORITHM_TYPE_AES,
DataKeyLength: 32,
Mode: ALGORITHM_MODE_GCM,
IVLength: 12,
AuthTagLength: 16,
HashFunc: sha256.New,
},
{
Id: 0x0146,
Type: ALGORITHM_TYPE_AES,
DataKeyLength: 24,
Mode: ALGORITHM_MODE_GCM,
IVLength: 12,
AuthTagLength: 16,
HashFunc: sha256.New,
},
{
Id: 0x0114,
Type: ALGORITHM_TYPE_AES,
DataKeyLength: 16,
Mode: ALGORITHM_MODE_GCM,
IVLength: 12,
AuthTagLength: 16,
HashFunc: sha256.New,
},
{
Id: 0x0078,
Type: ALGORITHM_TYPE_AES,
DataKeyLength: 32,
Mode: ALGORITHM_MODE_GCM,
IVLength: 12,
AuthTagLength: 16,
HashFunc: nil,
},
{
Id: 0x0046,
Type: ALGORITHM_TYPE_AES,
DataKeyLength: 24,
Mode: ALGORITHM_MODE_GCM,
IVLength: 12,
AuthTagLength: 16,
HashFunc: nil,
},
{
Id: 0x0014,
Type: ALGORITHM_TYPE_AES,
DataKeyLength: 16,
Mode: ALGORITHM_MODE_GCM,
IVLength: 12,
AuthTagLength: 16,
HashFunc: nil,
},
}

func lookupAlgorithm(id uint16) *Algorithm {
for _, algo := range Algorithms {
if id == algo.Id {
return &algo
}
}
return nil
}
152 changes: 152 additions & 0 deletions lib/aws_encryption_sdk/kms_helper.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
package aws_encryption_sdk

import (
"bytes"
"crypto/aes"
"crypto/cipher"
"encoding/binary"
"errors"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/kms"
"golang.org/x/crypto/hkdf"
"strings"
)

type KmsHelper struct {
client *kms.KMS
}

func NewKmsHelper(region string) *KmsHelper {
k := &KmsHelper{}
// Set up AWS KMS session
conf := aws.NewConfig().WithRegion(region)
sess := session.Must(session.NewSession(conf))
k.client = kms.New(sess)
return k
}

// Decrypt encrypted data keys
func (k *KmsHelper) decryptDataKeys(m *Message) ([][]byte, error) {
ret := make([][]byte, 0)
var i uint16
for i = 0; i < m.EncDataKeyCount; i++ {
data, err := k.kmsDecrypt(m.EncDataKeys[i].EncKeyData, m)
if err != nil {
return nil, err
}
ret = append(ret, data)
}
return ret, nil
}

// Generate derived encryption key
func (k *KmsHelper) getDerivedKey(key []byte, m *Message) []byte {
if m.Algorithm.HashFunc != nil {
info := bytes.NewBuffer(nil)
binary.Write(info, binary.BigEndian, m.Algorithm.Id)
info.Write(m.MessageId[:])
tmp_hkdf := hkdf.New(m.Algorithm.HashFunc, key, nil, info.Bytes())
ret := make([]byte, m.Algorithm.DataKeyLength)
tmp_hkdf.Read(ret)
return ret
} else {
return key
}
}

// Build additional data string for use in decryption
func (k *KmsHelper) buildContentAAD(m *Message, f *Frame) []byte {
ret := bytes.NewBuffer(nil)
ret.Write(m.MessageId[:])
ret.Write(f.AADContentString)
binary.Write(ret, binary.BigEndian, f.SeqNumber)
binary.Write(ret, binary.BigEndian, uint64(f.EncContentLength))
return ret.Bytes()
}

// Decrypt using KMS
func (k *KmsHelper) kmsDecrypt(data []byte, m *Message) ([]byte, error) {
input := &kms.DecryptInput{
CiphertextBlob: data,
}
if m != nil {
context := make(map[string]*string)
for key, value := range m.EncContext {
context[key] = &value
}
input.EncryptionContext = context
}
result, err := k.client.Decrypt(input)
if err != nil {
return nil, err
}
return result.Plaintext, nil
}

// Decryption entrypoint
func (k *KmsHelper) Decrypt(data []byte) ([]byte, error) {
var err error
var plaintext []byte
var data_keys [][]byte

// Try simple KMS decryption first
if plaintext, err = k.kmsDecrypt(data, nil); err == nil {
return plaintext, nil
} else if strings.HasPrefix(err.Error(), kms.ErrCodeInvalidCiphertextException) {
// Do nothing for an InvalidCiphertextException error
} else {
// Unknown error
return nil, err
}

r := bytes.NewReader(data)
message := NewMessage()
message.Decode(r)
data_keys, err = k.decryptDataKeys(message)
if err != nil {
return nil, err
}
plaintext = make([]byte, 0)
for _, frame := range message.Frames {
// TODO: support multiple data keys
tmp_key := k.getDerivedKey(data_keys[0], message)

var c cipher.Block
switch message.Algorithm.Type {
case ALGORITHM_TYPE_AES:
c, err = aes.NewCipher(tmp_key)
if err != nil {
return nil, err
}
default:
return nil, errors.New("Unknown encryption algorithm type")
}

var mode cipher.AEAD
switch message.Algorithm.Mode {
case ALGORITHM_MODE_GCM:
mode, err = cipher.NewGCM(c)
if err != nil {
return nil, err
}
default:
return nil, errors.New("Unknown encryption algorithm mode")
}

ciphertext := frame.EncContent
// The encryption functions expect the auth tag to be appended to the ciphertext
ciphertext = append(ciphertext, frame.AuthTag...)
nonce := frame.IV

frame_plaintext, err := mode.Open(nil, nonce, ciphertext, k.buildContentAAD(message, &frame))
if err != nil {
return nil, err
}

// Append frame plaintext to overall plaintext
plaintext = append(plaintext, frame_plaintext...)
}

return plaintext, nil
}
Loading

0 comments on commit 6934b08

Please sign in to comment.