-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcodec.go
More file actions
125 lines (105 loc) · 3.17 KB
/
Copy pathcodec.go
File metadata and controls
125 lines (105 loc) · 3.17 KB
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
package temporal
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"errors"
"fmt"
"io"
common "go.temporal.io/api/common/v1"
"go.temporal.io/sdk/converter"
)
// CryptCodec implements converter.PayloadCodec interface to encrypt/decrypt payloads.
type CryptCodec struct {
key []byte
}
// NewCryptCodec creates a new CryptCodec instance with 16, 24, or 32-byte key for AES encryption.
func NewCryptCodec(key []byte) (*CryptCodec, error) {
if len(key) != 16 && len(key) != 24 && len(key) != 32 {
return nil, errors.New("key length must be 16, 24, or 32 bytes for AES-128, AES-192, or AES-256")
}
return &CryptCodec{key: key}, nil
}
// Encode encrypts payloads before sending them to the Temporal server.
func (c *CryptCodec) Encode(payloads []*common.Payload) ([]*common.Payload, error) {
result := make([]*common.Payload, len(payloads))
for i, p := range payloads {
if p == nil {
continue
}
// Marshal the original Payload completely (including its metadata)
payloadBytes, err := p.Marshal()
if err != nil {
return nil, fmt.Errorf("failed to marshal payload: %w", err)
}
// Initialize AES-GCM cipher
block, err := aes.NewCipher(c.key)
if err != nil {
return nil, err
}
aesGCM, err := cipher.NewGCM(block)
if err != nil {
return nil, err
}
nonce := make([]byte, aesGCM.NonceSize())
if _, err = io.ReadFull(rand.Reader, nonce); err != nil {
return nil, err
}
encryptedData := aesGCM.Seal(nonce, nonce, payloadBytes, nil)
// Create new Payload with encrypted data
result[i] = &common.Payload{
Metadata: map[string][]byte{
"encoding": []byte("binary/encrypted"),
},
Data: encryptedData,
}
}
return result, nil
}
// Decode decrypts payloads arriving from the Temporal server.
func (c *CryptCodec) Decode(payloads []*common.Payload) ([]*common.Payload, error) {
result := make([]*common.Payload, len(payloads))
for i, p := range payloads {
if p == nil {
continue
}
// Decode only encrypted payloads
if string(p.Metadata["encoding"]) != "binary/encrypted" {
result[i] = p
continue
}
encryptedData := p.GetData()
block, err := aes.NewCipher(c.key)
if err != nil {
return nil, err
}
aesGCM, err := cipher.NewGCM(block)
if err != nil {
return nil, err
}
nonceSize := aesGCM.NonceSize()
if len(encryptedData) < nonceSize {
return nil, errors.New("ciphertext too short")
}
nonce, ciphertext := encryptedData[:nonceSize], encryptedData[nonceSize:]
decryptedBytes, err := aesGCM.Open(nil, nonce, ciphertext, nil)
if err != nil {
return nil, fmt.Errorf("failed to decrypt payload: %w", err)
}
// Restore the original Payload
originalPayload := &common.Payload{}
if err := originalPayload.Unmarshal(decryptedBytes); err != nil {
return nil, fmt.Errorf("failed to unmarshal decrypted payload: %w", err)
}
result[i] = originalPayload
}
return result, nil
}
// GetEncryptingDataConverter wraps the default DataConverter with the encrypting codec.
func GetEncryptingDataConverter(key []byte) (converter.DataConverter, error) {
codec, err := NewCryptCodec(key)
if err != nil {
return nil, err
}
return converter.NewCodecDataConverter(converter.GetDefaultDataConverter(), codec), nil
}