-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathtransit_encrypt.go
153 lines (141 loc) · 4.03 KB
/
transit_encrypt.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
package forest
import (
"bytes"
"context"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"net/http"
"strings"
)
// Plaintext is the required field
type encryptRequest struct {
Plaintext string `json:"plaintext"`
Context string `json:"context,omitempty"`
KeyVersion int `json:"key_version,omitempty"`
Nonce string `json:"nonce,omitempty"`
BatchInput []interface{} `json:"batch_input,omitempty"`
Type string `json:"type,omitempty"`
ConvergentEncryption string `json:"convergent_encryption,omitempty"`
}
// The one we want is Data.Ciphertext
type encryptResponse struct {
RequestID string `json:"request_id"`
LeaseID string `json:"lease_id"`
Renewable bool `json:"renewable"`
LeaseDuration int64 `json:"lease_duration"`
Data struct {
Ciphertext string `json:"ciphertext"`
KeyVersion int `json:"key_version"`
} `json:"data"`
WrapInfo interface{} `json:"wrap_info"`
Warnings []string `json:"warnings"`
Auth interface{} `json:"auth"`
}
type encryptResponseStream struct {
startData bool
initiate strings.Builder
temp *bytes.Buffer
}
func (e *encryptResponseStream) Read(p []byte) (n int, err error) {
return e.temp.Read(p)
}
func (e *encryptResponseStream) Write(p []byte) (n int, err error) {
if e.temp == nil {
e.temp = bytes.NewBuffer(nil)
}
for _, b := range p {
if e.startData {
// Stop early since we hit the closing quote
if b == '"' {
return 0, nil
}
err = e.temp.WriteByte(b)
if err != nil {
return 0, err
}
} else {
err = e.initiate.WriteByte(b)
if err != nil {
return 0, err
}
dd := e.initiate.String()
if strings.Contains(dd, `"ciphertext":"`) {
e.startData = true
}
}
n++
}
return
}
// TransitEncrypt will encrypt payload for sending somewhere else. 'key' is the encryptor name.
func (v *Vault) TransitEncrypt(ctx context.Context, key string, payload []byte) (cipherText string, err error) {
encoded := base64.StdEncoding.EncodeToString(payload)
path := fmt.Sprintf("/%s/encrypt/%s", v.Config.TransitEngine, key)
body := encryptRequest{
Plaintext: encoded,
}
reqBody, err := json.Marshal(body)
if err != nil {
return "", err
}
req, err := v.requestGen(ctx, http.MethodPost, path, bytes.NewBuffer(reqBody))
if err != nil {
return
}
res, err := v.Config.HTTPClient.Do(req)
if err != nil {
return
}
defer res.Body.Close()
if err := checkErrorResponse(res); err != nil {
return "", err
}
response := encryptResponse{}
err = json.NewDecoder(res.Body).Decode(&response)
if err != nil {
return "", err
}
cipherText = response.Data.Ciphertext
return
}
// TransitEncryptStream will encrypt payload in stream manner to prevent memory overload on huge number of operation.
// Use this function for big files. The returned io Reader is a stream of pure encoded vault data without bells and whistles of JSON.
func (v *Vault) TransitEncryptStream(ctx context.Context, key string, payload io.Reader) (io.Reader, error) {
// Initiate buf json holder
buf := bytes.NewBufferString(`{"plaintext":"`)
// Creates the encoder. Any data written to encoder will be written to buf in encoded fashion
encoded := base64.NewEncoder(base64.StdEncoding, buf)
// Starts the payload encryption
_, err := io.Copy(encoded, payload)
if err != nil {
return nil, err
}
// Close has to be called now to trigger adding remaining data to buf
err = encoded.Close()
if err != nil {
return nil, err
}
// Enclose the json stream
_, err = buf.WriteString(`"}`)
if err != nil {
return nil, err
}
path := fmt.Sprintf("/%s/encrypt/%s", v.Config.TransitEngine, key)
req, err := v.requestGen(ctx, http.MethodPost, path, buf)
if err != nil {
return nil, err
}
res, err := v.Config.HTTPClient.Do(req)
if err != nil {
return nil, err
}
defer res.Body.Close()
cipher := newStreamInBetween(`"ciphertext":"`, '"')
_, err = io.Copy(cipher, res.Body)
if err != nil && err != io.ErrShortWrite {
return nil, err
}
return cipher, nil
}