-
Notifications
You must be signed in to change notification settings - Fork 10
/
path_decrypt.go
176 lines (153 loc) · 5.29 KB
/
path_decrypt.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
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package gcpkms
import (
"context"
"encoding/base64"
"fmt"
"github.com/hashicorp/errwrap"
"github.com/hashicorp/vault/sdk/framework"
"github.com/hashicorp/vault/sdk/logical"
"github.com/patrickmn/go-cache"
kmspb "google.golang.org/genproto/googleapis/cloud/kms/v1"
)
func (b *backend) pathDecrypt() *framework.Path {
return &framework.Path{
Pattern: "decrypt/" + framework.GenericNameRegex("key"),
DisplayAttrs: &framework.DisplayAttributes{
OperationPrefix: operationPrefixGoogleCloudKMS,
OperationVerb: "decrypt",
},
HelpSynopsis: "Decrypt a ciphertext value using a named key",
HelpDescription: `
Use the named encryption key to decrypt a ciphertext string previously
encrypted with this same key. The provided ciphertext come from a previous
invocation of the /encrypt endpoint. It is not guaranteed to work with values
encrypted with the same Google Cloud KMS key outside of Vault.
`,
Fields: map[string]*framework.FieldSchema{
"key": &framework.FieldSchema{
Type: framework.TypeString,
Description: `
Name of the key in Vault to use for decryption. This key must already exist in
Vault and must map back to a Google Cloud KMS key.
`,
},
"additional_authenticated_data": &framework.FieldSchema{
Type: framework.TypeString,
Description: `
Optional data that was specified during encryption of this payload.
`,
},
"ciphertext": &framework.FieldSchema{
Type: framework.TypeString,
Description: `
Ciphertext to decrypt as previously returned from an encrypt operation. This
must be base64-encoded ciphertext as previously returned from an encrypt
operation.
`,
},
"key_version": &framework.FieldSchema{
Type: framework.TypeInt,
Description: `
Integer version of the crypto key version to use for decryption. This is
required for asymmetric keys. For symmetric keys, Cloud KMS will choose the
correct version automatically.
`,
},
},
Callbacks: map[logical.Operation]framework.OperationFunc{
logical.UpdateOperation: withFieldValidator(b.pathDecryptWrite),
},
}
}
// pathDecryptWrite corresponds to PUT/POST gcpkms/decrypt/:key and is
// used to decrypt the ciphertext string using the named key.
func (b *backend) pathDecryptWrite(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
key := d.Get("key").(string)
aad := d.Get("additional_authenticated_data").(string)
keyVersion := d.Get("key_version").(int)
k, err := b.Key(ctx, req.Storage, key)
if err != nil {
if err == ErrKeyNotFound {
return logical.ErrorResponse(err.Error()), logical.ErrInvalidRequest
}
return nil, err
}
// We gave the user back base64-encoded ciphertext in the /encrypt payload
ciphertext, err := base64.StdEncoding.DecodeString(d.Get("ciphertext").(string))
if err != nil {
return nil, errwrap.Wrapf("failed to base64 decode ciphtertext: {{err}}", err)
}
cryptoKey := k.CryptoKeyID
if keyVersion > 0 {
if k.MinVersion > 0 && keyVersion < k.MinVersion {
resp := fmt.Sprintf("requested version %d is less than minimum allowed version of %d",
keyVersion, k.MinVersion)
return logical.ErrorResponse(resp), logical.ErrPermissionDenied
}
if k.MaxVersion > 0 && keyVersion > k.MaxVersion {
resp := fmt.Sprintf("requested version %d is greater than maximum allowed version of %d",
keyVersion, k.MaxVersion)
return logical.ErrorResponse(resp), logical.ErrPermissionDenied
}
cryptoKey = fmt.Sprintf("%s/cryptoKeyVersions/%d", cryptoKey, keyVersion)
}
kmsClient, closer, err := b.KMSClient(req.Storage)
if err != nil {
return nil, err
}
defer closer()
// Lookup the key so we can determine the type of decryption (symmetric or
// asymmetric).
var ck *kmspb.CryptoKey
cachedCk, found := b.keysCache.Get(k.CryptoKeyID)
if !found {
fetchedCk, err := kmsClient.GetCryptoKey(ctx, &kmspb.GetCryptoKeyRequest{
Name: k.CryptoKeyID,
})
if err != nil {
return nil, errwrap.Wrapf("failed to get underlying crypto key: {{err}}", err)
}
_ = b.keysCache.Add(k.CryptoKeyID, fetchedCk, cache.DefaultExpiration)
ck = fetchedCk
} else {
castedCk, ok := cachedCk.(*kmspb.CryptoKey)
if !ok {
return nil, fmt.Errorf("failed to get underlying crypto key: %q", err)
}
ck = castedCk
}
var plaintext string
switch ck.Purpose {
case kmspb.CryptoKey_ASYMMETRIC_DECRYPT:
if keyVersion == 0 {
return nil, errMissingFields("key_version")
}
resp, err := kmsClient.AsymmetricDecrypt(ctx, &kmspb.AsymmetricDecryptRequest{
Name: cryptoKey,
Ciphertext: ciphertext,
})
if err != nil {
return nil, errwrap.Wrapf("failed to decrypt ciphertext (asymmetric): {{err}}", err)
}
plaintext = string(resp.Plaintext)
case kmspb.CryptoKey_ENCRYPT_DECRYPT, kmspb.CryptoKey_CRYPTO_KEY_PURPOSE_UNSPECIFIED:
resp, err := kmsClient.Decrypt(ctx, &kmspb.DecryptRequest{
Name: cryptoKey,
Ciphertext: ciphertext,
AdditionalAuthenticatedData: []byte(aad),
})
if err != nil {
return nil, errwrap.Wrapf("failed to decrypt ciphertext (symmetric): {{err}}", err)
}
plaintext = string(resp.Plaintext)
case kmspb.CryptoKey_ASYMMETRIC_SIGN:
return nil, logical.ErrUnsupportedOperation
}
return &logical.Response{
Data: map[string]interface{}{
"plaintext": plaintext,
},
}, nil
}