main
Raw Download raw file
  1// Copyright 2011 The Go Authors. All rights reserved.
  2// Use of this source code is governed by a BSD-style
  3// license that can be found in the LICENSE file.
  4
  5package openpgp
  6
  7import (
  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// DetachSign signs message with the private key from signer (which must
 21// already have been decrypted) and writes the signature to w.
 22// If config is nil, sensible defaults will be used.
 23func 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// ArmoredDetachSign signs message with the private key from signer (which
 28// must already have been decrypted) and writes an armored signature to w.
 29// If config is nil, sensible defaults will be used.
 30func 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// DetachSignText signs message (after canonicalising the line endings) with
 35// the private key from signer (which must already have been decrypted) and
 36// writes the signature to w.
 37// If config is nil, sensible defaults will be used.
 38func 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// ArmoredDetachSignText signs message (after canonicalising the line endings)
 43// with the private key from signer (which must already have been decrypted)
 44// and writes an armored signature to w.
 45// If config is nil, sensible defaults will be used.
 46func ArmoredDetachSignText(w io.Writer, signer *Entity, message io.Reader, config *packet.Config) error {
 47	return armoredDetachSign(w, signer, message, packet.SigTypeText, config)
 48}
 49
 50func 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
 62func 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, err := sig.PrepareSign(config)
 80	if err != nil {
 81		return
 82	}
 83	wrappedHash, err := wrapHashForSignature(h, sig.SigType)
 84	if err != nil {
 85		return
 86	}
 87	if _, err = io.Copy(wrappedHash, message); err != nil {
 88		return err
 89	}
 90
 91	err = sig.Sign(h, signingKey.PrivateKey, config)
 92	if err != nil {
 93		return
 94	}
 95
 96	return sig.Serialize(w)
 97}
 98
 99// FileHints contains metadata about encrypted files. This metadata is, itself,
100// encrypted.
101type FileHints struct {
102	// IsBinary can be set to hint that the contents are binary data.
103	IsBinary bool
104	// FileName hints at the name of the file that should be written. It's
105	// truncated to 255 bytes if longer. It may be empty to suggest that the
106	// file should not be written to disk. It may be equal to "_CONSOLE" to
107	// suggest the data should not be written to disk.
108	FileName string
109	// ModTime contains the modification time of the file, or the zero time if not applicable.
110	ModTime time.Time
111}
112
113// SymmetricallyEncrypt acts like gpg -c: it encrypts a file with a passphrase.
114// The resulting WriteCloser must be closed after the contents of the file have
115// been written.
116// If config is nil, sensible defaults will be used.
117func SymmetricallyEncrypt(ciphertext io.Writer, passphrase []byte, hints *FileHints, config *packet.Config) (plaintext io.WriteCloser, err error) {
118	if hints == nil {
119		hints = &FileHints{}
120	}
121
122	key, err := packet.SerializeSymmetricKeyEncrypted(ciphertext, passphrase, config)
123	if err != nil {
124		return
125	}
126
127	var w io.WriteCloser
128	cipherSuite := packet.CipherSuite{
129		Cipher: config.Cipher(),
130		Mode:   config.AEAD().Mode(),
131	}
132	w, err = packet.SerializeSymmetricallyEncrypted(ciphertext, config.Cipher(), config.AEAD() != nil, cipherSuite, key, config)
133	if err != nil {
134		return
135	}
136
137	literalData := w
138	if algo := config.Compression(); algo != packet.CompressionNone {
139		var compConfig *packet.CompressionConfig
140		if config != nil {
141			compConfig = config.CompressionConfig
142		}
143		literalData, err = packet.SerializeCompressed(w, algo, compConfig)
144		if err != nil {
145			return
146		}
147	}
148
149	var epochSeconds uint32
150	if !hints.ModTime.IsZero() {
151		epochSeconds = uint32(hints.ModTime.Unix())
152	}
153	return packet.SerializeLiteral(literalData, hints.IsBinary, hints.FileName, epochSeconds)
154}
155
156// intersectPreferences mutates and returns a prefix of a that contains only
157// the values in the intersection of a and b. The order of a is preserved.
158func intersectPreferences(a []uint8, b []uint8) (intersection []uint8) {
159	var j int
160	for _, v := range a {
161		for _, v2 := range b {
162			if v == v2 {
163				a[j] = v
164				j++
165				break
166			}
167		}
168	}
169
170	return a[:j]
171}
172
173// intersectPreferences mutates and returns a prefix of a that contains only
174// the values in the intersection of a and b. The order of a is preserved.
175func intersectCipherSuites(a [][2]uint8, b [][2]uint8) (intersection [][2]uint8) {
176	var j int
177	for _, v := range a {
178		for _, v2 := range b {
179			if v[0] == v2[0] && v[1] == v2[1] {
180				a[j] = v
181				j++
182				break
183			}
184		}
185	}
186
187	return a[:j]
188}
189
190func hashToHashId(h crypto.Hash) uint8 {
191	v, ok := algorithm.HashToHashId(h)
192	if !ok {
193		panic("tried to convert unknown hash")
194	}
195	return v
196}
197
198// EncryptText encrypts a message to a number of recipients and, optionally,
199// signs it. Optional information is contained in 'hints', also encrypted, that
200// aids the recipients in processing the message. The resulting WriteCloser
201// must be closed after the contents of the file have been written. If config
202// is nil, sensible defaults will be used. The signing is done in text mode.
203func EncryptText(ciphertext io.Writer, to []*Entity, signed *Entity, hints *FileHints, config *packet.Config) (plaintext io.WriteCloser, err error) {
204	return encrypt(ciphertext, ciphertext, to, signed, hints, packet.SigTypeText, config)
205}
206
207// Encrypt encrypts a message to a number of recipients and, optionally, signs
208// it. hints contains optional information, that is also encrypted, that aids
209// the recipients in processing the message. The resulting WriteCloser must
210// be closed after the contents of the file have been written.
211// If config is nil, sensible defaults will be used.
212func Encrypt(ciphertext io.Writer, to []*Entity, signed *Entity, hints *FileHints, config *packet.Config) (plaintext io.WriteCloser, err error) {
213	return encrypt(ciphertext, ciphertext, to, signed, hints, packet.SigTypeBinary, config)
214}
215
216// EncryptSplit encrypts a message to a number of recipients and, optionally, signs
217// it. hints contains optional information, that is also encrypted, that aids
218// the recipients in processing the message. The resulting WriteCloser must
219// be closed after the contents of the file have been written.
220// If config is nil, sensible defaults will be used.
221func EncryptSplit(keyWriter io.Writer, dataWriter io.Writer, to []*Entity, signed *Entity, hints *FileHints, config *packet.Config) (plaintext io.WriteCloser, err error) {
222	return encrypt(keyWriter, dataWriter, to, signed, hints, packet.SigTypeBinary, config)
223}
224
225// EncryptTextSplit encrypts a message to a number of recipients and, optionally, signs
226// it. hints contains optional information, that is also encrypted, that aids
227// the recipients in processing the message. The resulting WriteCloser must
228// be closed after the contents of the file have been written.
229// If config is nil, sensible defaults will be used.
230func EncryptTextSplit(keyWriter io.Writer, dataWriter io.Writer, to []*Entity, signed *Entity, hints *FileHints, config *packet.Config) (plaintext io.WriteCloser, err error) {
231	return encrypt(keyWriter, dataWriter, to, signed, hints, packet.SigTypeText, config)
232}
233
234// writeAndSign writes the data as a payload package and, optionally, signs
235// it. hints contains optional information, that is also encrypted,
236// that aids the recipients in processing the message. The resulting
237// WriteCloser must be closed after the contents of the file have been
238// written. If config is nil, sensible defaults will be used.
239func writeAndSign(payload io.WriteCloser, candidateHashes []uint8, signed *Entity, hints *FileHints, sigType packet.SignatureType, config *packet.Config) (plaintext io.WriteCloser, err error) {
240	var signer *packet.PrivateKey
241	if signed != nil {
242		signKey, ok := signed.SigningKeyById(config.Now(), config.SigningKey())
243		if !ok {
244			return nil, errors.InvalidArgumentError("no valid signing keys")
245		}
246		signer = signKey.PrivateKey
247		if signer == nil {
248			return nil, errors.InvalidArgumentError("no private key in signing key")
249		}
250		if signer.Encrypted {
251			return nil, errors.InvalidArgumentError("signing key must be decrypted")
252		}
253	}
254
255	var hash crypto.Hash
256	for _, hashId := range candidateHashes {
257		if h, ok := algorithm.HashIdToHash(hashId); ok && h.Available() {
258			hash = h
259			break
260		}
261	}
262
263	// If the hash specified by config is a candidate, we'll use that.
264	if configuredHash := config.Hash(); configuredHash.Available() {
265		for _, hashId := range candidateHashes {
266			if h, ok := algorithm.HashIdToHash(hashId); ok && h == configuredHash {
267				hash = h
268				break
269			}
270		}
271	}
272
273	if hash == 0 {
274		hashId := candidateHashes[0]
275		name, ok := algorithm.HashIdToString(hashId)
276		if !ok {
277			name = "#" + strconv.Itoa(int(hashId))
278		}
279		return nil, errors.InvalidArgumentError("cannot encrypt because no candidate hash functions are compiled in. (Wanted " + name + " in this case.)")
280	}
281
282	var salt []byte
283	if signer != nil {
284		var opsVersion = 3
285		if signer.Version == 6 {
286			opsVersion = signer.Version
287		}
288		ops := &packet.OnePassSignature{
289			Version:    opsVersion,
290			SigType:    sigType,
291			Hash:       hash,
292			PubKeyAlgo: signer.PubKeyAlgo,
293			KeyId:      signer.KeyId,
294			IsLast:     true,
295		}
296		if opsVersion == 6 {
297			ops.KeyFingerprint = signer.Fingerprint
298			salt, err = packet.SignatureSaltForHash(hash, config.Random())
299			if err != nil {
300				return nil, err
301			}
302			ops.Salt = salt
303		}
304		if err := ops.Serialize(payload); err != nil {
305			return nil, err
306		}
307	}
308
309	if hints == nil {
310		hints = &FileHints{}
311	}
312
313	w := payload
314	if signer != nil {
315		// If we need to write a signature packet after the literal
316		// data then we need to stop literalData from closing
317		// encryptedData.
318		w = noOpCloser{w}
319
320	}
321	var epochSeconds uint32
322	if !hints.ModTime.IsZero() {
323		epochSeconds = uint32(hints.ModTime.Unix())
324	}
325	literalData, err := packet.SerializeLiteral(w, hints.IsBinary, hints.FileName, epochSeconds)
326	if err != nil {
327		return nil, err
328	}
329
330	if signer != nil {
331		h, wrappedHash, err := hashForSignature(hash, sigType, salt)
332		if err != nil {
333			return nil, err
334		}
335		metadata := &packet.LiteralData{
336			Format:   'u',
337			FileName: hints.FileName,
338			Time:     epochSeconds,
339		}
340		if hints.IsBinary {
341			metadata.Format = 'b'
342		}
343		return signatureWriter{payload, literalData, hash, wrappedHash, h, salt, signer, sigType, config, metadata}, nil
344	}
345	return literalData, nil
346}
347
348// encrypt encrypts a message to a number of recipients and, optionally, signs
349// it. hints contains optional information, that is also encrypted, that aids
350// the recipients in processing the message. The resulting WriteCloser must
351// be closed after the contents of the file have been written.
352// If config is nil, sensible defaults will be used.
353func encrypt(keyWriter io.Writer, dataWriter io.Writer, to []*Entity, signed *Entity, hints *FileHints, sigType packet.SignatureType, config *packet.Config) (plaintext io.WriteCloser, err error) {
354	if len(to) == 0 {
355		return nil, errors.InvalidArgumentError("no encryption recipient provided")
356	}
357
358	// These are the possible ciphers that we'll use for the message.
359	candidateCiphers := []uint8{
360		uint8(packet.CipherAES256),
361		uint8(packet.CipherAES128),
362	}
363
364	// These are the possible hash functions that we'll use for the signature.
365	candidateHashes := []uint8{
366		hashToHashId(crypto.SHA256),
367		hashToHashId(crypto.SHA384),
368		hashToHashId(crypto.SHA512),
369		hashToHashId(crypto.SHA3_256),
370		hashToHashId(crypto.SHA3_512),
371	}
372
373	// Prefer GCM if everyone supports it
374	candidateCipherSuites := [][2]uint8{
375		{uint8(packet.CipherAES256), uint8(packet.AEADModeGCM)},
376		{uint8(packet.CipherAES256), uint8(packet.AEADModeEAX)},
377		{uint8(packet.CipherAES256), uint8(packet.AEADModeOCB)},
378		{uint8(packet.CipherAES128), uint8(packet.AEADModeGCM)},
379		{uint8(packet.CipherAES128), uint8(packet.AEADModeEAX)},
380		{uint8(packet.CipherAES128), uint8(packet.AEADModeOCB)},
381	}
382
383	candidateCompression := []uint8{
384		uint8(packet.CompressionNone),
385		uint8(packet.CompressionZIP),
386		uint8(packet.CompressionZLIB),
387	}
388
389	encryptKeys := make([]Key, len(to))
390
391	// AEAD is used only if config enables it and every key supports it
392	aeadSupported := config.AEAD() != nil
393
394	for i := range to {
395		var ok bool
396		encryptKeys[i], ok = to[i].EncryptionKey(config.Now())
397		if !ok {
398			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")
399		}
400
401		primarySelfSignature, _ := to[i].PrimarySelfSignature()
402		if primarySelfSignature == nil {
403			return nil, errors.InvalidArgumentError("entity without a self-signature")
404		}
405
406		if !primarySelfSignature.SEIPDv2 {
407			aeadSupported = false
408		}
409
410		candidateCiphers = intersectPreferences(candidateCiphers, primarySelfSignature.PreferredSymmetric)
411		candidateHashes = intersectPreferences(candidateHashes, primarySelfSignature.PreferredHash)
412		candidateCipherSuites = intersectCipherSuites(candidateCipherSuites, primarySelfSignature.PreferredCipherSuites)
413		candidateCompression = intersectPreferences(candidateCompression, primarySelfSignature.PreferredCompression)
414	}
415
416	// In the event that the intersection of supported algorithms is empty we use the ones
417	// labelled as MUST that every implementation supports.
418	if len(candidateCiphers) == 0 {
419		// https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-07.html#section-9.3
420		candidateCiphers = []uint8{uint8(packet.CipherAES128)}
421	}
422	if len(candidateHashes) == 0 {
423		// https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-07.html#hash-algos
424		candidateHashes = []uint8{hashToHashId(crypto.SHA256)}
425	}
426	if len(candidateCipherSuites) == 0 {
427		// https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-07.html#section-9.6
428		candidateCipherSuites = [][2]uint8{{uint8(packet.CipherAES128), uint8(packet.AEADModeOCB)}}
429	}
430
431	cipher := packet.CipherFunction(candidateCiphers[0])
432	aeadCipherSuite := packet.CipherSuite{
433		Cipher: packet.CipherFunction(candidateCipherSuites[0][0]),
434		Mode:   packet.AEADMode(candidateCipherSuites[0][1]),
435	}
436
437	// If the cipher specified by config is a candidate, we'll use that.
438	configuredCipher := config.Cipher()
439	for _, c := range candidateCiphers {
440		cipherFunc := packet.CipherFunction(c)
441		if cipherFunc == configuredCipher {
442			cipher = cipherFunc
443			break
444		}
445	}
446
447	var symKey []byte
448	if aeadSupported {
449		symKey = make([]byte, aeadCipherSuite.Cipher.KeySize())
450	} else {
451		symKey = make([]byte, cipher.KeySize())
452	}
453
454	if _, err := io.ReadFull(config.Random(), symKey); err != nil {
455		return nil, err
456	}
457
458	for _, key := range encryptKeys {
459		if err := packet.SerializeEncryptedKeyAEAD(keyWriter, key.PublicKey, cipher, aeadSupported, symKey, config); err != nil {
460			return nil, err
461		}
462	}
463
464	var payload io.WriteCloser
465	payload, err = packet.SerializeSymmetricallyEncrypted(dataWriter, cipher, aeadSupported, aeadCipherSuite, symKey, config)
466	if err != nil {
467		return
468	}
469
470	payload, err = handleCompression(payload, candidateCompression, config)
471	if err != nil {
472		return nil, err
473	}
474
475	return writeAndSign(payload, candidateHashes, signed, hints, sigType, config)
476}
477
478// Sign signs a message. The resulting WriteCloser must be closed after the
479// contents of the file have been written.  hints contains optional information
480// that aids the recipients in processing the message.
481// If config is nil, sensible defaults will be used.
482func Sign(output io.Writer, signed *Entity, hints *FileHints, config *packet.Config) (input io.WriteCloser, err error) {
483	if signed == nil {
484		return nil, errors.InvalidArgumentError("no signer provided")
485	}
486
487	// These are the possible hash functions that we'll use for the signature.
488	candidateHashes := []uint8{
489		hashToHashId(crypto.SHA256),
490		hashToHashId(crypto.SHA384),
491		hashToHashId(crypto.SHA512),
492		hashToHashId(crypto.SHA3_256),
493		hashToHashId(crypto.SHA3_512),
494	}
495	defaultHashes := candidateHashes[0:1]
496	primarySelfSignature, _ := signed.PrimarySelfSignature()
497	if primarySelfSignature == nil {
498		return nil, errors.StructuralError("signed entity has no self-signature")
499	}
500	preferredHashes := primarySelfSignature.PreferredHash
501	if len(preferredHashes) == 0 {
502		preferredHashes = defaultHashes
503	}
504	candidateHashes = intersectPreferences(candidateHashes, preferredHashes)
505	if len(candidateHashes) == 0 {
506		return nil, errors.StructuralError("cannot sign because signing key shares no common algorithms with candidate hashes")
507	}
508
509	return writeAndSign(noOpCloser{output}, candidateHashes, signed, hints, packet.SigTypeBinary, config)
510}
511
512// signatureWriter hashes the contents of a message while passing it along to
513// literalData. When closed, it closes literalData, writes a signature packet
514// to encryptedData and then also closes encryptedData.
515type signatureWriter struct {
516	encryptedData io.WriteCloser
517	literalData   io.WriteCloser
518	hashType      crypto.Hash
519	wrappedHash   hash.Hash
520	h             hash.Hash
521	salt          []byte // v6 only
522	signer        *packet.PrivateKey
523	sigType       packet.SignatureType
524	config        *packet.Config
525	metadata      *packet.LiteralData // V5 signatures protect document metadata
526}
527
528func (s signatureWriter) Write(data []byte) (int, error) {
529	s.wrappedHash.Write(data)
530	switch s.sigType {
531	case packet.SigTypeBinary:
532		return s.literalData.Write(data)
533	case packet.SigTypeText:
534		flag := 0
535		return writeCanonical(s.literalData, data, &flag)
536	}
537	return 0, errors.UnsupportedError("unsupported signature type: " + strconv.Itoa(int(s.sigType)))
538}
539
540func (s signatureWriter) Close() error {
541	sig := createSignaturePacket(&s.signer.PublicKey, s.sigType, s.config)
542	sig.Hash = s.hashType
543	sig.Metadata = s.metadata
544
545	if err := sig.SetSalt(s.salt); err != nil {
546		return err
547	}
548
549	if err := sig.Sign(s.h, s.signer, s.config); err != nil {
550		return err
551	}
552	if err := s.literalData.Close(); err != nil {
553		return err
554	}
555	if err := sig.Serialize(s.encryptedData); err != nil {
556		return err
557	}
558	return s.encryptedData.Close()
559}
560
561func createSignaturePacket(signer *packet.PublicKey, sigType packet.SignatureType, config *packet.Config) *packet.Signature {
562	sigLifetimeSecs := config.SigLifetime()
563	return &packet.Signature{
564		Version:           signer.Version,
565		SigType:           sigType,
566		PubKeyAlgo:        signer.PubKeyAlgo,
567		Hash:              config.Hash(),
568		CreationTime:      config.Now(),
569		IssuerKeyId:       &signer.KeyId,
570		IssuerFingerprint: signer.Fingerprint,
571		Notations:         config.Notations(),
572		SigLifetimeSecs:   &sigLifetimeSecs,
573	}
574}
575
576// noOpCloser is like an ioutil.NopCloser, but for an io.Writer.
577// TODO: we have two of these in OpenPGP packages alone. This probably needs
578// to be promoted somewhere more common.
579type noOpCloser struct {
580	w io.Writer
581}
582
583func (c noOpCloser) Write(data []byte) (n int, err error) {
584	return c.w.Write(data)
585}
586
587func (c noOpCloser) Close() error {
588	return nil
589}
590
591func handleCompression(compressed io.WriteCloser, candidateCompression []uint8, config *packet.Config) (data io.WriteCloser, err error) {
592	data = compressed
593	confAlgo := config.Compression()
594	if confAlgo == packet.CompressionNone {
595		return
596	}
597
598	// Set algorithm labelled as MUST as fallback
599	// https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-07.html#section-9.4
600	finalAlgo := packet.CompressionNone
601	// if compression specified by config available we will use it
602	for _, c := range candidateCompression {
603		if uint8(confAlgo) == c {
604			finalAlgo = confAlgo
605			break
606		}
607	}
608
609	if finalAlgo != packet.CompressionNone {
610		var compConfig *packet.CompressionConfig
611		if config != nil {
612			compConfig = config.CompressionConfig
613		}
614		data, err = packet.SerializeCompressed(compressed, finalAlgo, compConfig)
615		if err != nil {
616			return
617		}
618	}
619	return data, nil
620}