1
2
3
4
5 package openpgp
6
7 import (
8 "crypto"
9 "hash"
10 "io"
11 "strconv"
12 "time"
13
14 "github.com/ProtonMail/go-crypto/openpgp/armor"
15 "github.com/ProtonMail/go-crypto/openpgp/errors"
16 "github.com/ProtonMail/go-crypto/openpgp/internal/algorithm"
17 "github.com/ProtonMail/go-crypto/openpgp/packet"
18 )
19
20
21
22
23 func DetachSign(w io.Writer, signer *Entity, message io.Reader, config *packet.Config) error {
24 return detachSign(w, signer, message, packet.SigTypeBinary, config)
25 }
26
27
28
29
30 func ArmoredDetachSign(w io.Writer, signer *Entity, message io.Reader, config *packet.Config) (err error) {
31 return armoredDetachSign(w, signer, message, packet.SigTypeBinary, config)
32 }
33
34
35
36
37
38 func DetachSignText(w io.Writer, signer *Entity, message io.Reader, config *packet.Config) error {
39 return detachSign(w, signer, message, packet.SigTypeText, config)
40 }
41
42
43
44
45
46 func ArmoredDetachSignText(w io.Writer, signer *Entity, message io.Reader, config *packet.Config) error {
47 return armoredDetachSign(w, signer, message, packet.SigTypeText, config)
48 }
49
50 func armoredDetachSign(w io.Writer, signer *Entity, message io.Reader, sigType packet.SignatureType, config *packet.Config) (err error) {
51 out, err := armor.Encode(w, SignatureType, nil)
52 if err != nil {
53 return
54 }
55 err = detachSign(out, signer, message, sigType, config)
56 if err != nil {
57 return
58 }
59 return out.Close()
60 }
61
62 func detachSign(w io.Writer, signer *Entity, message io.Reader, sigType packet.SignatureType, config *packet.Config) (err error) {
63 signingKey, ok := signer.SigningKeyById(config.Now(), config.SigningKey())
64 if !ok {
65 return errors.InvalidArgumentError("no valid signing keys")
66 }
67 if signingKey.PrivateKey == nil {
68 return errors.InvalidArgumentError("signing key doesn't have a private key")
69 }
70 if signingKey.PrivateKey.Encrypted {
71 return errors.InvalidArgumentError("signing key is encrypted")
72 }
73 if _, ok := algorithm.HashToHashId(config.Hash()); !ok {
74 return errors.InvalidArgumentError("invalid hash function")
75 }
76
77 sig := createSignaturePacket(signingKey.PublicKey, sigType, config)
78
79 h, wrappedHash, err := hashForSignature(sig.Hash, sig.SigType)
80 if err != nil {
81 return
82 }
83 if _, err = io.Copy(wrappedHash, message); err != nil {
84 return err
85 }
86
87 err = sig.Sign(h, signingKey.PrivateKey, config)
88 if err != nil {
89 return
90 }
91
92 return sig.Serialize(w)
93 }
94
95
96
97 type FileHints struct {
98
99 IsBinary bool
100
101
102
103
104 FileName string
105
106 ModTime time.Time
107 }
108
109
110
111
112
113 func SymmetricallyEncrypt(ciphertext io.Writer, passphrase []byte, hints *FileHints, config *packet.Config) (plaintext io.WriteCloser, err error) {
114 if hints == nil {
115 hints = &FileHints{}
116 }
117
118 key, err := packet.SerializeSymmetricKeyEncrypted(ciphertext, passphrase, config)
119 if err != nil {
120 return
121 }
122
123 var w io.WriteCloser
124 cipherSuite := packet.CipherSuite{
125 Cipher: config.Cipher(),
126 Mode: config.AEAD().Mode(),
127 }
128 w, err = packet.SerializeSymmetricallyEncrypted(ciphertext, config.Cipher(), config.AEAD() != nil, cipherSuite, key, config)
129 if err != nil {
130 return
131 }
132
133 literalData := w
134 if algo := config.Compression(); algo != packet.CompressionNone {
135 var compConfig *packet.CompressionConfig
136 if config != nil {
137 compConfig = config.CompressionConfig
138 }
139 literalData, err = packet.SerializeCompressed(w, algo, compConfig)
140 if err != nil {
141 return
142 }
143 }
144
145 var epochSeconds uint32
146 if !hints.ModTime.IsZero() {
147 epochSeconds = uint32(hints.ModTime.Unix())
148 }
149 return packet.SerializeLiteral(literalData, hints.IsBinary, hints.FileName, epochSeconds)
150 }
151
152
153
154 func intersectPreferences(a []uint8, b []uint8) (intersection []uint8) {
155 var j int
156 for _, v := range a {
157 for _, v2 := range b {
158 if v == v2 {
159 a[j] = v
160 j++
161 break
162 }
163 }
164 }
165
166 return a[:j]
167 }
168
169
170
171 func intersectCipherSuites(a [][2]uint8, b [][2]uint8) (intersection [][2]uint8) {
172 var j int
173 for _, v := range a {
174 for _, v2 := range b {
175 if v[0] == v2[0] && v[1] == v2[1] {
176 a[j] = v
177 j++
178 break
179 }
180 }
181 }
182
183 return a[:j]
184 }
185
186 func hashToHashId(h crypto.Hash) uint8 {
187 v, ok := algorithm.HashToHashId(h)
188 if !ok {
189 panic("tried to convert unknown hash")
190 }
191 return v
192 }
193
194
195
196
197
198
199 func EncryptText(ciphertext io.Writer, to []*Entity, signed *Entity, hints *FileHints, config *packet.Config) (plaintext io.WriteCloser, err error) {
200 return encrypt(ciphertext, ciphertext, to, signed, hints, packet.SigTypeText, config)
201 }
202
203
204
205
206
207
208 func Encrypt(ciphertext io.Writer, to []*Entity, signed *Entity, hints *FileHints, config *packet.Config) (plaintext io.WriteCloser, err error) {
209 return encrypt(ciphertext, ciphertext, to, signed, hints, packet.SigTypeBinary, config)
210 }
211
212
213
214
215
216
217 func EncryptSplit(keyWriter io.Writer, dataWriter io.Writer, to []*Entity, signed *Entity, hints *FileHints, config *packet.Config) (plaintext io.WriteCloser, err error) {
218 return encrypt(keyWriter, dataWriter, to, signed, hints, packet.SigTypeBinary, config)
219 }
220
221
222
223
224
225
226 func EncryptTextSplit(keyWriter io.Writer, dataWriter io.Writer, to []*Entity, signed *Entity, hints *FileHints, config *packet.Config) (plaintext io.WriteCloser, err error) {
227 return encrypt(keyWriter, dataWriter, to, signed, hints, packet.SigTypeText, config)
228 }
229
230
231
232
233
234
235 func writeAndSign(payload io.WriteCloser, candidateHashes []uint8, signed *Entity, hints *FileHints, sigType packet.SignatureType, config *packet.Config) (plaintext io.WriteCloser, err error) {
236 var signer *packet.PrivateKey
237 if signed != nil {
238 signKey, ok := signed.SigningKeyById(config.Now(), config.SigningKey())
239 if !ok {
240 return nil, errors.InvalidArgumentError("no valid signing keys")
241 }
242 signer = signKey.PrivateKey
243 if signer == nil {
244 return nil, errors.InvalidArgumentError("no private key in signing key")
245 }
246 if signer.Encrypted {
247 return nil, errors.InvalidArgumentError("signing key must be decrypted")
248 }
249 }
250
251 var hash crypto.Hash
252 for _, hashId := range candidateHashes {
253 if h, ok := algorithm.HashIdToHash(hashId); ok && h.Available() {
254 hash = h
255 break
256 }
257 }
258
259
260 if configuredHash := config.Hash(); configuredHash.Available() {
261 for _, hashId := range candidateHashes {
262 if h, ok := algorithm.HashIdToHash(hashId); ok && h == configuredHash {
263 hash = h
264 break
265 }
266 }
267 }
268
269 if hash == 0 {
270 hashId := candidateHashes[0]
271 name, ok := algorithm.HashIdToString(hashId)
272 if !ok {
273 name = "#" + strconv.Itoa(int(hashId))
274 }
275 return nil, errors.InvalidArgumentError("cannot encrypt because no candidate hash functions are compiled in. (Wanted " + name + " in this case.)")
276 }
277
278 if signer != nil {
279 ops := &packet.OnePassSignature{
280 SigType: sigType,
281 Hash: hash,
282 PubKeyAlgo: signer.PubKeyAlgo,
283 KeyId: signer.KeyId,
284 IsLast: true,
285 }
286 if err := ops.Serialize(payload); err != nil {
287 return nil, err
288 }
289 }
290
291 if hints == nil {
292 hints = &FileHints{}
293 }
294
295 w := payload
296 if signer != nil {
297
298
299
300 w = noOpCloser{w}
301
302 }
303 var epochSeconds uint32
304 if !hints.ModTime.IsZero() {
305 epochSeconds = uint32(hints.ModTime.Unix())
306 }
307 literalData, err := packet.SerializeLiteral(w, hints.IsBinary, hints.FileName, epochSeconds)
308 if err != nil {
309 return nil, err
310 }
311
312 if signer != nil {
313 h, wrappedHash, err := hashForSignature(hash, sigType)
314 if err != nil {
315 return nil, err
316 }
317 metadata := &packet.LiteralData{
318 Format: 't',
319 FileName: hints.FileName,
320 Time: epochSeconds,
321 }
322 if hints.IsBinary {
323 metadata.Format = 'b'
324 }
325 return signatureWriter{payload, literalData, hash, wrappedHash, h, signer, sigType, config, metadata}, nil
326 }
327 return literalData, nil
328 }
329
330
331
332
333
334
335 func encrypt(keyWriter io.Writer, dataWriter io.Writer, to []*Entity, signed *Entity, hints *FileHints, sigType packet.SignatureType, config *packet.Config) (plaintext io.WriteCloser, err error) {
336 if len(to) == 0 {
337 return nil, errors.InvalidArgumentError("no encryption recipient provided")
338 }
339
340
341 candidateCiphers := []uint8{
342 uint8(packet.CipherAES256),
343 uint8(packet.CipherAES128),
344 }
345
346
347 candidateHashes := []uint8{
348 hashToHashId(crypto.SHA256),
349 hashToHashId(crypto.SHA384),
350 hashToHashId(crypto.SHA512),
351 hashToHashId(crypto.SHA3_256),
352 hashToHashId(crypto.SHA3_512),
353 }
354
355
356 candidateCipherSuites := [][2]uint8{
357 {uint8(packet.CipherAES256), uint8(packet.AEADModeGCM)},
358 {uint8(packet.CipherAES256), uint8(packet.AEADModeEAX)},
359 {uint8(packet.CipherAES256), uint8(packet.AEADModeOCB)},
360 {uint8(packet.CipherAES128), uint8(packet.AEADModeGCM)},
361 {uint8(packet.CipherAES128), uint8(packet.AEADModeEAX)},
362 {uint8(packet.CipherAES128), uint8(packet.AEADModeOCB)},
363 }
364
365 candidateCompression := []uint8{
366 uint8(packet.CompressionNone),
367 uint8(packet.CompressionZIP),
368 uint8(packet.CompressionZLIB),
369 }
370
371 encryptKeys := make([]Key, len(to))
372
373
374 aeadSupported := config.AEAD() != nil
375
376 for i := range to {
377 var ok bool
378 encryptKeys[i], ok = to[i].EncryptionKey(config.Now())
379 if !ok {
380 return nil, errors.InvalidArgumentError("cannot encrypt a message to key id " + strconv.FormatUint(to[i].PrimaryKey.KeyId, 16) + " because it has no valid encryption keys")
381 }
382
383 sig := to[i].PrimaryIdentity().SelfSignature
384 if !sig.SEIPDv2 {
385 aeadSupported = false
386 }
387
388 candidateCiphers = intersectPreferences(candidateCiphers, sig.PreferredSymmetric)
389 candidateHashes = intersectPreferences(candidateHashes, sig.PreferredHash)
390 candidateCipherSuites = intersectCipherSuites(candidateCipherSuites, sig.PreferredCipherSuites)
391 candidateCompression = intersectPreferences(candidateCompression, sig.PreferredCompression)
392 }
393
394
395
396 if len(candidateCiphers) == 0 {
397
398 candidateCiphers = []uint8{uint8(packet.CipherAES128)}
399 }
400 if len(candidateHashes) == 0 {
401
402 candidateHashes = []uint8{hashToHashId(crypto.SHA256)}
403 }
404 if len(candidateCipherSuites) == 0 {
405
406 candidateCipherSuites = [][2]uint8{{uint8(packet.CipherAES128), uint8(packet.AEADModeOCB)}}
407 }
408
409 cipher := packet.CipherFunction(candidateCiphers[0])
410 aeadCipherSuite := packet.CipherSuite{
411 Cipher: packet.CipherFunction(candidateCipherSuites[0][0]),
412 Mode: packet.AEADMode(candidateCipherSuites[0][1]),
413 }
414
415
416 configuredCipher := config.Cipher()
417 for _, c := range candidateCiphers {
418 cipherFunc := packet.CipherFunction(c)
419 if cipherFunc == configuredCipher {
420 cipher = cipherFunc
421 break
422 }
423 }
424
425 symKey := make([]byte, cipher.KeySize())
426 if _, err := io.ReadFull(config.Random(), symKey); err != nil {
427 return nil, err
428 }
429
430 for _, key := range encryptKeys {
431 if err := packet.SerializeEncryptedKey(keyWriter, key.PublicKey, cipher, symKey, config); err != nil {
432 return nil, err
433 }
434 }
435
436 var payload io.WriteCloser
437 payload, err = packet.SerializeSymmetricallyEncrypted(dataWriter, cipher, aeadSupported, aeadCipherSuite, symKey, config)
438 if err != nil {
439 return
440 }
441
442 payload, err = handleCompression(payload, candidateCompression, config)
443 if err != nil {
444 return nil, err
445 }
446
447 return writeAndSign(payload, candidateHashes, signed, hints, sigType, config)
448 }
449
450
451
452
453
454 func Sign(output io.Writer, signed *Entity, hints *FileHints, config *packet.Config) (input io.WriteCloser, err error) {
455 if signed == nil {
456 return nil, errors.InvalidArgumentError("no signer provided")
457 }
458
459
460 candidateHashes := []uint8{
461 hashToHashId(crypto.SHA256),
462 hashToHashId(crypto.SHA384),
463 hashToHashId(crypto.SHA512),
464 hashToHashId(crypto.SHA3_256),
465 hashToHashId(crypto.SHA3_512),
466 }
467 defaultHashes := candidateHashes[0:1]
468 preferredHashes := signed.PrimaryIdentity().SelfSignature.PreferredHash
469 if len(preferredHashes) == 0 {
470 preferredHashes = defaultHashes
471 }
472 candidateHashes = intersectPreferences(candidateHashes, preferredHashes)
473 if len(candidateHashes) == 0 {
474 return nil, errors.InvalidArgumentError("cannot sign because signing key shares no common algorithms with candidate hashes")
475 }
476
477 return writeAndSign(noOpCloser{output}, candidateHashes, signed, hints, packet.SigTypeBinary, config)
478 }
479
480
481
482
483 type signatureWriter struct {
484 encryptedData io.WriteCloser
485 literalData io.WriteCloser
486 hashType crypto.Hash
487 wrappedHash hash.Hash
488 h hash.Hash
489 signer *packet.PrivateKey
490 sigType packet.SignatureType
491 config *packet.Config
492 metadata *packet.LiteralData
493 }
494
495 func (s signatureWriter) Write(data []byte) (int, error) {
496 s.wrappedHash.Write(data)
497 switch s.sigType {
498 case packet.SigTypeBinary:
499 return s.literalData.Write(data)
500 case packet.SigTypeText:
501 flag := 0
502 return writeCanonical(s.literalData, data, &flag)
503 }
504 return 0, errors.UnsupportedError("unsupported signature type: " + strconv.Itoa(int(s.sigType)))
505 }
506
507 func (s signatureWriter) Close() error {
508 sig := createSignaturePacket(&s.signer.PublicKey, s.sigType, s.config)
509 sig.Hash = s.hashType
510 sig.Metadata = s.metadata
511
512 if err := sig.Sign(s.h, s.signer, s.config); err != nil {
513 return err
514 }
515 if err := s.literalData.Close(); err != nil {
516 return err
517 }
518 if err := sig.Serialize(s.encryptedData); err != nil {
519 return err
520 }
521 return s.encryptedData.Close()
522 }
523
524 func createSignaturePacket(signer *packet.PublicKey, sigType packet.SignatureType, config *packet.Config) *packet.Signature {
525 sigLifetimeSecs := config.SigLifetime()
526 return &packet.Signature{
527 Version: signer.Version,
528 SigType: sigType,
529 PubKeyAlgo: signer.PubKeyAlgo,
530 Hash: config.Hash(),
531 CreationTime: config.Now(),
532 IssuerKeyId: &signer.KeyId,
533 IssuerFingerprint: signer.Fingerprint,
534 Notations: config.Notations(),
535 SigLifetimeSecs: &sigLifetimeSecs,
536 }
537 }
538
539
540
541
542 type noOpCloser struct {
543 w io.Writer
544 }
545
546 func (c noOpCloser) Write(data []byte) (n int, err error) {
547 return c.w.Write(data)
548 }
549
550 func (c noOpCloser) Close() error {
551 return nil
552 }
553
554 func handleCompression(compressed io.WriteCloser, candidateCompression []uint8, config *packet.Config) (data io.WriteCloser, err error) {
555 data = compressed
556 confAlgo := config.Compression()
557 if confAlgo == packet.CompressionNone {
558 return
559 }
560
561
562
563 finalAlgo := packet.CompressionNone
564
565 for _, c := range candidateCompression {
566 if uint8(confAlgo) == c {
567 finalAlgo = confAlgo
568 break
569 }
570 }
571
572 if finalAlgo != packet.CompressionNone {
573 var compConfig *packet.CompressionConfig
574 if config != nil {
575 compConfig = config.CompressionConfig
576 }
577 data, err = packet.SerializeCompressed(compressed, finalAlgo, compConfig)
578 if err != nil {
579 return
580 }
581 }
582 return data, nil
583 }
584
View as plain text