-
Notifications
You must be signed in to change notification settings - Fork 21
/
decrypter.go
137 lines (117 loc) · 4.04 KB
/
decrypter.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
package cloudkms
import (
"crypto"
"crypto/rsa"
"errors"
"fmt"
"hash/crc32"
"io"
"cloud.google.com/go/kms/apiv1/kmspb"
"google.golang.org/protobuf/types/known/wrapperspb"
"go.step.sm/crypto/kms/apiv1"
"go.step.sm/crypto/pemutil"
)
// CreateDecrypter implements the apiv1.Decrypter interface and returns
// a crypto.Decrypter backed by a decryption key in Google Cloud KMS.
func (k *CloudKMS) CreateDecrypter(req *apiv1.CreateDecrypterRequest) (crypto.Decrypter, error) {
if req.DecryptionKey == "" {
return nil, errors.New("decryption key cannot be empty")
}
return NewDecrypter(k.client, req.DecryptionKey)
}
// Decrypter implements a crypto.Decrypter using Google Cloud KMS.
type Decrypter struct {
client KeyManagementClient
decryptionKey string
publicKey crypto.PublicKey
}
// NewDecrypter creates a new crypto.Decrypter backed by the given
// Google Cloud KMS decryption key.
func NewDecrypter(client KeyManagementClient, decryptionKey string) (*Decrypter, error) {
// Make sure that the key exists.
decrypter := &Decrypter{
client: client,
decryptionKey: resourceName(decryptionKey),
}
if err := decrypter.preloadKey(); err != nil { // TODO(hs): (option for) lazy load instead?
return nil, err
}
return decrypter, nil
}
func (d *Decrypter) preloadKey() error {
ctx, cancel := defaultContext()
defer cancel()
response, err := d.client.GetPublicKey(ctx, &kmspb.GetPublicKeyRequest{
Name: d.decryptionKey,
})
if err != nil {
return fmt.Errorf("cloudKMS GetPublicKey failed: %w", err)
}
d.publicKey, err = pemutil.ParseKey([]byte(response.Pem))
return err
}
// Public returns the public key of this decrypter
func (d *Decrypter) Public() crypto.PublicKey {
return d.publicKey
}
// validateOAEPOptions validates the RSA OAEP options provided.
func validateOAEPOptions(o *rsa.OAEPOptions) error {
if o == nil { // var o *rsa.OAEPOptions; nothing to verify
return nil
}
if len(o.Label) > 0 {
return errors.New("cloudKMS does not support RSA-OAEP label")
}
switch o.Hash {
case crypto.Hash(0), crypto.SHA1, crypto.SHA256, crypto.SHA512:
return nil
default:
return fmt.Errorf("cloudKMS does not support hash algorithm %q with RSA-OAEP", o.Hash)
}
}
// Decrypt decrypts ciphertext using the decryption key backed by Google Cloud KMS and returns
// the plaintext bytes. An error is returned when decryption fails. Google Cloud KMS only supports
// RSA keys with 2048, 3072 or 4096 bits and will always use OAEP. It supports SHA1, SHA256 and
// SHA512. Labels are not supported. Before calling out to GCP, some validation is performed
// so that known bad parameters are detected client-side and a more meaningful error is returned
// for those cases.
//
// Also see https://cloud.google.com/kms/docs/algorithms#asymmetric_encryption_algorithms.
func (d *Decrypter) Decrypt(_ io.Reader, ciphertext []byte, opts crypto.DecrypterOpts) ([]byte, error) {
if opts == nil {
opts = &rsa.OAEPOptions{}
}
switch o := opts.(type) {
case *rsa.OAEPOptions:
if err := validateOAEPOptions(o); err != nil {
return nil, err
}
case *rsa.PKCS1v15DecryptOptions:
return nil, errors.New("cloudKMS does not support PKCS #1 v1.5 decryption")
default:
return nil, errors.New("invalid options for Decrypt")
}
req := &kmspb.AsymmetricDecryptRequest{
Name: d.decryptionKey,
Ciphertext: ciphertext,
CiphertextCrc32C: wrapperspb.Int64(crc32c(ciphertext)),
}
ctx, cancel := defaultContext()
defer cancel()
response, err := d.client.AsymmetricDecrypt(ctx, req)
if err != nil {
return nil, fmt.Errorf("cloudKMS AsymmetricDecrypt failed: %w", err)
}
if !response.VerifiedCiphertextCrc32C {
return nil, errors.New("cloudKMS AsymmetricDecrypt: request corrupted in-transit")
}
if crc32c(response.Plaintext) != response.PlaintextCrc32C.Value {
return nil, errors.New("cloudKMS AsymmetricDecrypt: response corrupted in-transit")
}
return response.Plaintext, nil
}
func crc32c(data []byte) int64 {
t := crc32.MakeTable(crc32.Castagnoli)
return int64(crc32.Checksum(data, t))
}
var _ apiv1.Decrypter = (*CloudKMS)(nil)