wal-g

Форк
0
210 строк · 5.1 Кб
1
package openpgp
2

3
import (
4
	"bufio"
5
	"bytes"
6
	"io"
7
	"strings"
8
	"sync"
9

10
	"github.com/ProtonMail/go-crypto/openpgp"
11
	"github.com/pkg/errors"
12
	"github.com/wal-g/wal-g/internal/crypto"
13
	"github.com/wal-g/wal-g/internal/ioextensions"
14
)
15

16
// Crypter incapsulates specific of cypher method
17
// Includes keys, infrastructure information etc
18
// If many encryption methods will be used it worth
19
// to extract interface
20
type Crypter struct {
21
	KeyRingID      string
22
	IsUseKeyRingID bool
23

24
	ArmoredKey      string
25
	IsUseArmoredKey bool
26

27
	ArmoredKeyPath      string
28
	IsUseArmoredKeyPath bool
29

30
	PubKey    openpgp.EntityList
31
	SecretKey openpgp.EntityList
32

33
	loadPassphrase func() (string, bool)
34

35
	mutex sync.RWMutex
36
}
37

38
func (crypter *Crypter) Name() string {
39
	return "Opengpg/Crypter"
40
}
41

42
// CrypterFromKey creates Crypter from armored key.
43
func CrypterFromKey(armoredKey string, loadPassphrase func() (string, bool)) crypto.Crypter {
44
	return &Crypter{ArmoredKey: armoredKey, IsUseArmoredKey: true, loadPassphrase: loadPassphrase}
45
}
46

47
// CrypterFromKeyPath creates Crypter from armored key path.
48
func CrypterFromKeyPath(armoredKeyPath string, loadPassphrase func() (string, bool)) crypto.Crypter {
49
	return &Crypter{ArmoredKeyPath: armoredKeyPath, IsUseArmoredKeyPath: true, loadPassphrase: loadPassphrase}
50
}
51

52
// CrypterFromKeyRingID create Crypter from key ring ID.
53
func CrypterFromKeyRingID(keyRingID string, loadPassphrase func() (string, bool)) crypto.Crypter {
54
	return &Crypter{KeyRingID: keyRingID, IsUseKeyRingID: true, loadPassphrase: loadPassphrase}
55
}
56

57
func (crypter *Crypter) setupPubKey() error {
58
	crypter.mutex.RLock()
59
	if crypter.PubKey != nil {
60
		crypter.mutex.RUnlock()
61
		return nil
62
	}
63
	crypter.mutex.RUnlock()
64

65
	crypter.mutex.Lock()
66
	defer crypter.mutex.Unlock()
67
	if crypter.PubKey != nil { // already set up
68
		return nil
69
	}
70

71
	switch {
72
	case crypter.IsUseArmoredKey:
73
		evaluatedKey := strings.Replace(crypter.ArmoredKey, `\n`, "\n", -1)
74
		entityList, err := openpgp.ReadArmoredKeyRing(strings.NewReader(evaluatedKey))
75

76
		if err != nil {
77
			return err
78
		}
79

80
		crypter.PubKey = entityList
81

82
	case crypter.IsUseArmoredKeyPath:
83
		entityList, err := readPGPKey(crypter.ArmoredKeyPath)
84

85
		if err != nil {
86
			return err
87
		}
88

89
		crypter.PubKey = entityList
90

91
	default:
92
		// TODO: legacy gpg external use, need to remove in next major version
93
		armor, err := crypto.GetPubRingArmor(crypter.KeyRingID)
94

95
		if err != nil {
96
			return err
97
		}
98

99
		entityList, err := openpgp.ReadArmoredKeyRing(bytes.NewReader(armor))
100

101
		if err != nil {
102
			return err
103
		}
104

105
		crypter.PubKey = entityList
106
	}
107
	return nil
108
}
109

110
// Encrypt creates encryption writer from ordinary writer
111
func (crypter *Crypter) Encrypt(writer io.Writer) (io.WriteCloser, error) {
112
	err := crypter.setupPubKey()
113
	if err != nil {
114
		return nil, err
115
	}
116

117
	// We use buffered writer because encryption starts writing header immediately,
118
	// which can be inappropriate for further usage with blocking writers.
119
	// E. g. if underlying writer is a pipe, then this thread will be blocked before
120
	// creation of new thread, reading from this pipe.Writer.
121
	bufferedWriter := bufio.NewWriter(writer)
122
	encryptedWriter, err := openpgp.Encrypt(bufferedWriter, crypter.PubKey, nil, nil, nil)
123

124
	if err != nil {
125
		return nil, errors.Wrapf(err, "opengpg encryption error")
126
	}
127

128
	return ioextensions.NewOnCloseFlusher(encryptedWriter, bufferedWriter), nil
129
}
130

131
// Decrypt creates decrypted reader from ordinary reader
132
func (crypter *Crypter) Decrypt(reader io.Reader) (io.Reader, error) {
133
	err := crypter.loadSecret()
134

135
	if err != nil {
136
		return nil, err
137
	}
138

139
	md, err := openpgp.ReadMessage(reader, crypter.SecretKey, nil, nil)
140

141
	if err != nil {
142
		return nil, errors.WithStack(err)
143
	}
144

145
	return md.UnverifiedBody, nil
146
}
147

148
// load the secret key based on the settings
149
func (crypter *Crypter) loadSecret() error {
150
	// check if we actually need to load it
151
	crypter.mutex.RLock()
152
	if crypter.SecretKey != nil {
153
		crypter.mutex.RUnlock()
154
		return nil
155
	}
156
	// unlock needs to be there twice due to different code paths
157
	crypter.mutex.RUnlock()
158

159
	// we need to load, so lock for writing
160
	crypter.mutex.Lock()
161
	defer crypter.mutex.Unlock()
162

163
	// double check as the key might have been loaded between the RUnlock and Lock
164
	if crypter.SecretKey != nil {
165
		return nil
166
	}
167

168
	if crypter.IsUseArmoredKey {
169
		evaluatedKey := strings.Replace(crypter.ArmoredKey, `\n`, "\n", -1)
170
		entityList, err := openpgp.ReadArmoredKeyRing(strings.NewReader(evaluatedKey))
171

172
		if err != nil {
173
			return errors.WithStack(err)
174
		}
175

176
		crypter.SecretKey = entityList
177
	} else if crypter.IsUseArmoredKeyPath {
178
		entityList, err := readPGPKey(crypter.ArmoredKeyPath)
179

180
		if err != nil {
181
			return errors.WithStack(err)
182
		}
183

184
		crypter.SecretKey = entityList
185
	} else {
186
		// TODO: legacy gpg external use, need to remove in next major version
187
		armor, err := crypto.GetSecretRingArmor(crypter.KeyRingID)
188

189
		if err != nil {
190
			return errors.WithStack(err)
191
		}
192

193
		entityList, err := openpgp.ReadArmoredKeyRing(bytes.NewReader(armor))
194

195
		if err != nil {
196
			return errors.WithStack(err)
197
		}
198

199
		crypter.SecretKey = entityList
200
	}
201

202
	if passphrase, ok := crypter.loadPassphrase(); ok {
203
		err := decryptSecretKey(crypter.SecretKey, passphrase)
204

205
		if err != nil {
206
			return errors.WithStack(err)
207
		}
208
	}
209
	return nil
210
}
211

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.