1*e7b1675dSTing-Kang Chang// Copyright 2019 Google Inc. 2*e7b1675dSTing-Kang Chang// 3*e7b1675dSTing-Kang Chang// Licensed under the Apache License, Version 2.0 (the "License"); 4*e7b1675dSTing-Kang Chang// you may not use this file except in compliance with the License. 5*e7b1675dSTing-Kang Chang// You may obtain a copy of the License at 6*e7b1675dSTing-Kang Chang// 7*e7b1675dSTing-Kang Chang// http://www.apache.org/licenses/LICENSE-2.0 8*e7b1675dSTing-Kang Chang// 9*e7b1675dSTing-Kang Chang// Unless required by applicable law or agreed to in writing, software 10*e7b1675dSTing-Kang Chang// distributed under the License is distributed on an "AS IS" BASIS, 11*e7b1675dSTing-Kang Chang// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12*e7b1675dSTing-Kang Chang// See the License for the specific language governing permissions and 13*e7b1675dSTing-Kang Chang// limitations under the License. 14*e7b1675dSTing-Kang Chang// 15*e7b1675dSTing-Kang Chang//////////////////////////////////////////////////////////////////////////////// 16*e7b1675dSTing-Kang Chang 17*e7b1675dSTing-Kang Changpackage hcvault 18*e7b1675dSTing-Kang Chang 19*e7b1675dSTing-Kang Changimport ( 20*e7b1675dSTing-Kang Chang "encoding/base64" 21*e7b1675dSTing-Kang Chang "errors" 22*e7b1675dSTing-Kang Chang "net/url" 23*e7b1675dSTing-Kang Chang "strings" 24*e7b1675dSTing-Kang Chang 25*e7b1675dSTing-Kang Chang "github.com/google/tink/go/tink" 26*e7b1675dSTing-Kang Chang "github.com/hashicorp/vault/api" 27*e7b1675dSTing-Kang Chang) 28*e7b1675dSTing-Kang Chang 29*e7b1675dSTing-Kang Chang// vaultAEAD represents a HashiCorp Vault service to a particular URI. 30*e7b1675dSTing-Kang Changtype vaultAEAD struct { 31*e7b1675dSTing-Kang Chang encKeyPath string 32*e7b1675dSTing-Kang Chang decKeyPath string 33*e7b1675dSTing-Kang Chang client *api.Logical 34*e7b1675dSTing-Kang Chang} 35*e7b1675dSTing-Kang Chang 36*e7b1675dSTing-Kang Changvar _ tink.AEAD = (*vaultAEAD)(nil) 37*e7b1675dSTing-Kang Chang 38*e7b1675dSTing-Kang Changconst ( 39*e7b1675dSTing-Kang Chang encryptSegment = "encrypt" 40*e7b1675dSTing-Kang Chang decryptSegment = "decrypt" 41*e7b1675dSTing-Kang Chang) 42*e7b1675dSTing-Kang Chang 43*e7b1675dSTing-Kang Chang// newHCVaultAEAD returns a new HashiCorp Vault service. 44*e7b1675dSTing-Kang Changfunc newHCVaultAEAD(keyURI string, client *api.Logical) (tink.AEAD, error) { 45*e7b1675dSTing-Kang Chang encKeyPath, decKeyPath, err := getEndpointPaths(keyURI) 46*e7b1675dSTing-Kang Chang if err != nil { 47*e7b1675dSTing-Kang Chang return nil, err 48*e7b1675dSTing-Kang Chang } 49*e7b1675dSTing-Kang Chang return &vaultAEAD{ 50*e7b1675dSTing-Kang Chang encKeyPath: encKeyPath, 51*e7b1675dSTing-Kang Chang decKeyPath: decKeyPath, 52*e7b1675dSTing-Kang Chang client: client, 53*e7b1675dSTing-Kang Chang }, nil 54*e7b1675dSTing-Kang Chang} 55*e7b1675dSTing-Kang Chang 56*e7b1675dSTing-Kang Chang// Encrypt encrypts the plaintext data using a key stored in HashiCorp Vault. 57*e7b1675dSTing-Kang Chang// associatedData parameter is used as a context for key derivation, more 58*e7b1675dSTing-Kang Chang// information available https://www.vaultproject.io/docs/secrets/transit/index.html. 59*e7b1675dSTing-Kang Changfunc (a *vaultAEAD) Encrypt(plaintext, associatedData []byte) ([]byte, error) { 60*e7b1675dSTing-Kang Chang // Create an encryption request map according to Vault REST API: 61*e7b1675dSTing-Kang Chang // https://www.vaultproject.io/api/secret/transit/index.html#encrypt-data. 62*e7b1675dSTing-Kang Chang req := map[string]interface{}{ 63*e7b1675dSTing-Kang Chang "plaintext": base64.StdEncoding.EncodeToString(plaintext), 64*e7b1675dSTing-Kang Chang "context": base64.StdEncoding.EncodeToString(associatedData), 65*e7b1675dSTing-Kang Chang } 66*e7b1675dSTing-Kang Chang secret, err := a.client.Write(a.encKeyPath, req) 67*e7b1675dSTing-Kang Chang if err != nil { 68*e7b1675dSTing-Kang Chang return nil, err 69*e7b1675dSTing-Kang Chang } 70*e7b1675dSTing-Kang Chang ciphertext := secret.Data["ciphertext"].(string) 71*e7b1675dSTing-Kang Chang return []byte(ciphertext), nil 72*e7b1675dSTing-Kang Chang} 73*e7b1675dSTing-Kang Chang 74*e7b1675dSTing-Kang Chang// Decrypt decrypts the ciphertext using a key stored in HashiCorp Vault. 75*e7b1675dSTing-Kang Chang// associatedData parameter is used as a context for key derivation, more 76*e7b1675dSTing-Kang Chang// information available https://www.vaultproject.io/docs/secrets/transit/index.html. 77*e7b1675dSTing-Kang Changfunc (a *vaultAEAD) Decrypt(ciphertext, associatedData []byte) ([]byte, error) { 78*e7b1675dSTing-Kang Chang // Create a decryption request map according to Vault REST API: 79*e7b1675dSTing-Kang Chang // https://www.vaultproject.io/api/secret/transit/index.html#decrypt-data. 80*e7b1675dSTing-Kang Chang req := map[string]interface{}{ 81*e7b1675dSTing-Kang Chang "ciphertext": string(ciphertext), 82*e7b1675dSTing-Kang Chang "context": base64.StdEncoding.EncodeToString(associatedData), 83*e7b1675dSTing-Kang Chang } 84*e7b1675dSTing-Kang Chang secret, err := a.client.Write(a.decKeyPath, req) 85*e7b1675dSTing-Kang Chang if err != nil { 86*e7b1675dSTing-Kang Chang return nil, err 87*e7b1675dSTing-Kang Chang } 88*e7b1675dSTing-Kang Chang plaintext64 := secret.Data["plaintext"].(string) 89*e7b1675dSTing-Kang Chang plaintext, err := base64.StdEncoding.DecodeString(plaintext64) 90*e7b1675dSTing-Kang Chang if err != nil { 91*e7b1675dSTing-Kang Chang return nil, err 92*e7b1675dSTing-Kang Chang } 93*e7b1675dSTing-Kang Chang return plaintext, nil 94*e7b1675dSTing-Kang Chang} 95*e7b1675dSTing-Kang Chang 96*e7b1675dSTing-Kang Chang// getEndpointPaths transforms keyURL into the Vault transit encrypt and decrypt 97*e7b1675dSTing-Kang Chang// paths. The keyURL is expected to end in "/{mount}/keys/{keyName}". For 98*e7b1675dSTing-Kang Chang// example, the keyURL "hcvault:///transit/keys/key-foo" will be transformed to 99*e7b1675dSTing-Kang Chang// "transit/encrypt/key-foo" and "transit/decrypt/key-foo", and 100*e7b1675dSTing-Kang Chang// "hcvault://my-vault.example.com/teams/billing/service/cipher/keys/key-bar" 101*e7b1675dSTing-Kang Chang// will be transformed into 102*e7b1675dSTing-Kang Chang// "hcvault://my-vault.example.com/teams/billing/service/cipher/encrypt/key-bar" 103*e7b1675dSTing-Kang Chang// and 104*e7b1675dSTing-Kang Chang// "hcvault://my-vault.example.com/teams/billing/service/cipher/decrypt/key-bar". 105*e7b1675dSTing-Kang Changfunc getEndpointPaths(keyURL string) (encryptPath, decryptPath string, err error) { 106*e7b1675dSTing-Kang Chang u, err := url.Parse(keyURL) 107*e7b1675dSTing-Kang Chang if err != nil || u.Scheme != "hcvault" { 108*e7b1675dSTing-Kang Chang return "", "", errors.New("malformed keyURL") 109*e7b1675dSTing-Kang Chang } 110*e7b1675dSTing-Kang Chang 111*e7b1675dSTing-Kang Chang parts := strings.Split(u.EscapedPath(), "/") 112*e7b1675dSTing-Kang Chang length := len(parts) 113*e7b1675dSTing-Kang Chang if length < 4 || parts[length-2] != "keys" { 114*e7b1675dSTing-Kang Chang return "", "", errors.New("malformed keyURL") 115*e7b1675dSTing-Kang Chang } 116*e7b1675dSTing-Kang Chang 117*e7b1675dSTing-Kang Chang parts[length-2] = encryptSegment 118*e7b1675dSTing-Kang Chang encryptPath = strings.Join(parts[1:], "/") 119*e7b1675dSTing-Kang Chang parts[length-2] = decryptSegment 120*e7b1675dSTing-Kang Chang decryptPath = strings.Join(parts[1:], "/") 121*e7b1675dSTing-Kang Chang return encryptPath, decryptPath, nil 122*e7b1675dSTing-Kang Chang} 123