forked from hashicorp/vault
-
Notifications
You must be signed in to change notification settings - Fork 1
/
path_encrypt.go
141 lines (117 loc) · 3.38 KB
/
path_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
package transit
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"encoding/base64"
"fmt"
"github.com/hashicorp/vault/logical"
"github.com/hashicorp/vault/logical/framework"
)
func pathEncrypt() *framework.Path {
return &framework.Path{
Pattern: `encrypt/(?P<name>\w+)`,
Fields: map[string]*framework.FieldSchema{
"name": &framework.FieldSchema{
Type: framework.TypeString,
Description: "Name of the policy",
},
"plaintext": &framework.FieldSchema{
Type: framework.TypeString,
Description: "Plaintext value to encrypt",
},
"context": &framework.FieldSchema{
Type: framework.TypeString,
Description: "Context for key derivation. Required for derived keys.",
},
},
Callbacks: map[logical.Operation]framework.OperationFunc{
logical.WriteOperation: pathEncryptWrite,
},
HelpSynopsis: pathEncryptHelpSyn,
HelpDescription: pathEncryptHelpDesc,
}
}
func pathEncryptWrite(
req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
name := d.Get("name").(string)
value := d.Get("plaintext").(string)
if len(value) == 0 {
return logical.ErrorResponse("missing plaintext to encrypt"), logical.ErrInvalidRequest
}
// Decode the plaintext value
plaintext, err := base64.StdEncoding.DecodeString(value)
if err != nil {
return logical.ErrorResponse("failed to decode plaintext as base64"), logical.ErrInvalidRequest
}
// Decode the context if any
contextRaw := d.Get("context").(string)
var context []byte
if len(contextRaw) != 0 {
var err error
context, err = base64.StdEncoding.DecodeString(contextRaw)
if err != nil {
return logical.ErrorResponse("failed to decode context as base64"), logical.ErrInvalidRequest
}
}
// Get the policy
p, err := getPolicy(req, name)
if err != nil {
return nil, err
}
// Error if invalid policy
if p == nil {
isDerived := len(context) != 0
p, err = generatePolicy(req.Storage, name, isDerived)
if err != nil {
return logical.ErrorResponse(fmt.Sprintf("failed to upsert policy: %v", err)), logical.ErrInvalidRequest
}
}
// Derive the key that should be used
key, err := p.DeriveKey(context)
if err != nil {
return logical.ErrorResponse(err.Error()), logical.ErrInvalidRequest
}
// Guard against a potentially invalid cipher-mode
switch p.CipherMode {
case "aes-gcm":
default:
return logical.ErrorResponse("unsupported cipher mode"), logical.ErrInvalidRequest
}
// Setup the cipher
aesCipher, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
// Setup the GCM AEAD
gcm, err := cipher.NewGCM(aesCipher)
if err != nil {
return nil, err
}
// Compute random nonce
nonce := make([]byte, gcm.NonceSize())
_, err = rand.Read(nonce)
if err != nil {
return nil, err
}
// Encrypt and tag with GCM
out := gcm.Seal(nil, nonce, plaintext, nil)
// Place the encrypted data after the nonce
full := append(nonce, out...)
// Convert to base64
encoded := base64.StdEncoding.EncodeToString(full)
// Prepend some information
encoded = "vault:v0:" + encoded
// Generate the response
resp := &logical.Response{
Data: map[string]interface{}{
"ciphertext": encoded,
},
}
return resp, nil
}
const pathEncryptHelpSyn = `Encrypt a plaintext value using a named key`
const pathEncryptHelpDesc = `
This path uses the named key from the request path to encrypt a user
provided plaintext. The plaintext must be base64 encoded.
`