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/keybase/go-crypto/openpgp/armor"
15	"github.com/keybase/go-crypto/openpgp/errors"
16	"github.com/keybase/go-crypto/openpgp/packet"
17	"github.com/keybase/go-crypto/openpgp/s2k"
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
62// SignWithSigner signs the message of type sigType with s and writes the
63// signature to w.
64// If config is nil, sensible defaults will be used.
65func SignWithSigner(s packet.Signer, w io.Writer, message io.Reader, sigType packet.SignatureType, config *packet.Config) (err error) {
66	keyId := s.KeyId()
67	sig := new(packet.Signature)
68	sig.SigType = sigType
69	sig.PubKeyAlgo = s.PublicKeyAlgo()
70	sig.Hash = config.Hash()
71	sig.CreationTime = config.Now()
72	sig.IssuerKeyId = &keyId
73
74	s.Reset()
75
76	wrapped := s.(hash.Hash)
77
78	if sigType == packet.SigTypeText {
79		wrapped = NewCanonicalTextHash(s)
80	}
81
82	io.Copy(wrapped, message)
83
84	err = sig.Sign(s, nil, config)
85	if err != nil {
86		return
87	}
88
89	err = sig.Serialize(w)
90
91	return
92}
93
94func detachSign(w io.Writer, signer *Entity, message io.Reader, sigType packet.SignatureType, config *packet.Config) (err error) {
95	signerSubkey, ok := signer.signingKey(config.Now())
96	if !ok {
97		err = errors.InvalidArgumentError("no valid signing keys")
98		return
99	}
100	if signerSubkey.PrivateKey == nil {
101		return errors.InvalidArgumentError("signing key doesn't have a private key")
102	}
103	if signerSubkey.PrivateKey.Encrypted {
104		return errors.InvalidArgumentError("signing key is encrypted")
105	}
106
107	sig := new(packet.Signature)
108	sig.SigType = sigType
109	sig.PubKeyAlgo = signerSubkey.PrivateKey.PubKeyAlgo
110	sig.Hash = config.Hash()
111	sig.CreationTime = config.Now()
112	sig.IssuerKeyId = &signerSubkey.PrivateKey.KeyId
113
114	h, wrappedHash, err := hashForSignature(sig.Hash, sig.SigType)
115	if err != nil {
116		return
117	}
118	io.Copy(wrappedHash, message)
119
120	err = sig.Sign(h, signerSubkey.PrivateKey, config)
121	if err != nil {
122		return
123	}
124
125	return sig.Serialize(w)
126}
127
128// FileHints contains metadata about encrypted files. This metadata is, itself,
129// encrypted.
130type FileHints struct {
131	// IsBinary can be set to hint that the contents are binary data.
132	IsBinary bool
133	// FileName hints at the name of the file that should be written. It's
134	// truncated to 255 bytes if longer. It may be empty to suggest that the
135	// file should not be written to disk. It may be equal to "_CONSOLE" to
136	// suggest the data should not be written to disk.
137	FileName string
138	// ModTime contains the modification time of the file, or the zero time if not applicable.
139	ModTime time.Time
140}
141
142// SymmetricallyEncrypt acts like gpg -c: it encrypts a file with a passphrase.
143// The resulting WriteCloser must be closed after the contents of the file have
144// been written.
145// If config is nil, sensible defaults will be used.
146func SymmetricallyEncrypt(ciphertext io.Writer, passphrase []byte, hints *FileHints, config *packet.Config) (plaintext io.WriteCloser, err error) {
147	if hints == nil {
148		hints = &FileHints{}
149	}
150
151	key, err := packet.SerializeSymmetricKeyEncrypted(ciphertext, passphrase, config)
152	if err != nil {
153		return
154	}
155	w, err := packet.SerializeSymmetricallyEncrypted(ciphertext, config.Cipher(), key, config)
156	if err != nil {
157		return
158	}
159
160	literaldata := w
161	if algo := config.Compression(); algo != packet.CompressionNone {
162		var compConfig *packet.CompressionConfig
163		if config != nil {
164			compConfig = config.CompressionConfig
165		}
166		literaldata, err = packet.SerializeCompressed(w, algo, compConfig)
167		if err != nil {
168			return
169		}
170	}
171
172	var epochSeconds uint32
173	if !hints.ModTime.IsZero() {
174		epochSeconds = uint32(hints.ModTime.Unix())
175	}
176	return packet.SerializeLiteral(literaldata, hints.IsBinary, hints.FileName, epochSeconds)
177}
178
179// intersectPreferences mutates and returns a prefix of a that contains only
180// the values in the intersection of a and b. The order of a is preserved.
181func intersectPreferences(a []uint8, b []uint8) (intersection []uint8) {
182	var j int
183	for _, v := range a {
184		for _, v2 := range b {
185			if v == v2 {
186				a[j] = v
187				j++
188				break
189			}
190		}
191	}
192
193	return a[:j]
194}
195
196func hashToHashId(h crypto.Hash) uint8 {
197	v, ok := s2k.HashToHashId(h)
198	if !ok {
199		panic("tried to convert unknown hash")
200	}
201	return v
202}
203
204// Encrypt encrypts a message to a number of recipients and, optionally, signs
205// it. hints contains optional information, that is also encrypted, that aids
206// the recipients in processing the message. The resulting WriteCloser must
207// be closed after the contents of the file have been written.
208// If config is nil, sensible defaults will be used.
209func Encrypt(ciphertext io.Writer, to []*Entity, signed *Entity, hints *FileHints, config *packet.Config) (plaintext io.WriteCloser, err error) {
210	var signer *packet.PrivateKey
211	if signed != nil {
212		signKey, ok := signed.signingKey(config.Now())
213		if !ok {
214			return nil, errors.InvalidArgumentError("no valid signing keys")
215		}
216		signer = signKey.PrivateKey
217		if signer == nil {
218			return nil, errors.InvalidArgumentError("no private key in signing key")
219		}
220		if signer.Encrypted {
221			return nil, errors.InvalidArgumentError("signing key must be decrypted")
222		}
223	}
224
225	// These are the possible ciphers that we'll use for the message.
226	candidateCiphers := []uint8{
227		uint8(packet.CipherAES128),
228		uint8(packet.CipherAES256),
229		uint8(packet.CipherCAST5),
230	}
231	// These are the possible hash functions that we'll use for the signature.
232	candidateHashes := []uint8{
233		hashToHashId(crypto.SHA256),
234		hashToHashId(crypto.SHA512),
235		hashToHashId(crypto.SHA1),
236		hashToHashId(crypto.RIPEMD160),
237	}
238
239	// If no preferences were specified, assume something safe and reasonable.
240	defaultCiphers := []uint8{
241		uint8(packet.CipherAES128),
242		uint8(packet.CipherAES192),
243		uint8(packet.CipherAES256),
244		uint8(packet.CipherCAST5),
245	}
246
247	defaultHashes := []uint8{
248		hashToHashId(crypto.SHA256),
249		hashToHashId(crypto.SHA512),
250		hashToHashId(crypto.RIPEMD160),
251	}
252
253	encryptKeys := make([]Key, len(to))
254	for i := range to {
255		var ok bool
256		encryptKeys[i], ok = to[i].encryptionKey(config.Now())
257		if !ok {
258			return nil, errors.InvalidArgumentError("cannot encrypt a message to key id " + strconv.FormatUint(to[i].PrimaryKey.KeyId, 16) + " because it has no encryption keys")
259		}
260
261		sig := to[i].primaryIdentity().SelfSignature
262
263		preferredSymmetric := sig.PreferredSymmetric
264		if len(preferredSymmetric) == 0 {
265			preferredSymmetric = defaultCiphers
266		}
267		preferredHashes := sig.PreferredHash
268		if len(preferredHashes) == 0 {
269			preferredHashes = defaultHashes
270		}
271		candidateCiphers = intersectPreferences(candidateCiphers, preferredSymmetric)
272		candidateHashes = intersectPreferences(candidateHashes, preferredHashes)
273	}
274
275	if len(candidateCiphers) == 0 {
276		return nil, errors.InvalidArgumentError("cannot encrypt because recipient set shares no common ciphers")
277	}
278	if len(candidateHashes) == 0 {
279		return nil, errors.InvalidArgumentError("cannot encrypt because recipient set shares no common hashes")
280	}
281
282	cipher := packet.CipherFunction(candidateCiphers[0])
283	// If the cipher specifed by config is a candidate, we'll use that.
284	configuredCipher := config.Cipher()
285	for _, c := range candidateCiphers {
286		cipherFunc := packet.CipherFunction(c)
287		if cipherFunc == configuredCipher {
288			cipher = cipherFunc
289			break
290		}
291	}
292
293	var hash crypto.Hash
294	for _, hashId := range candidateHashes {
295		if h, ok := s2k.HashIdToHash(hashId); ok && h.Available() {
296			hash = h
297			break
298		}
299	}
300
301	// If the hash specified by config is a candidate, we'll use that.
302	if configuredHash := config.Hash(); configuredHash.Available() {
303		for _, hashId := range candidateHashes {
304			if h, ok := s2k.HashIdToHash(hashId); ok && h == configuredHash {
305				hash = h
306				break
307			}
308		}
309	}
310
311	if hash == 0 {
312		hashId := candidateHashes[0]
313		name, ok := s2k.HashIdToString(hashId)
314		if !ok {
315			name = "#" + strconv.Itoa(int(hashId))
316		}
317		return nil, errors.InvalidArgumentError("cannot encrypt because no candidate hash functions are compiled in. (Wanted " + name + " in this case.)")
318	}
319
320	symKey := make([]byte, cipher.KeySize())
321	if _, err := io.ReadFull(config.Random(), symKey); err != nil {
322		return nil, err
323	}
324
325	for _, key := range encryptKeys {
326		if err := packet.SerializeEncryptedKey(ciphertext, key.PublicKey, cipher, symKey, config); err != nil {
327			return nil, err
328		}
329	}
330
331	encryptedData, err := packet.SerializeSymmetricallyEncrypted(ciphertext, cipher, symKey, config)
332	if err != nil {
333		return
334	}
335
336	if signer != nil {
337		ops := &packet.OnePassSignature{
338			SigType:    packet.SigTypeBinary,
339			Hash:       hash,
340			PubKeyAlgo: signer.PubKeyAlgo,
341			KeyId:      signer.KeyId,
342			IsLast:     true,
343		}
344		if err := ops.Serialize(encryptedData); err != nil {
345			return nil, err
346		}
347	}
348
349	if hints == nil {
350		hints = &FileHints{}
351	}
352
353	w := encryptedData
354	if signer != nil {
355		// If we need to write a signature packet after the literal
356		// data then we need to stop literalData from closing
357		// encryptedData.
358		w = noOpCloser{encryptedData}
359
360	}
361	var epochSeconds uint32
362	if !hints.ModTime.IsZero() {
363		epochSeconds = uint32(hints.ModTime.Unix())
364	}
365	literalData, err := packet.SerializeLiteral(w, hints.IsBinary, hints.FileName, epochSeconds)
366	if err != nil {
367		return nil, err
368	}
369
370	if signer != nil {
371		return signatureWriter{encryptedData, literalData, hash, hash.New(), signer, config}, nil
372	}
373	return literalData, nil
374}
375
376// signatureWriter hashes the contents of a message while passing it along to
377// literalData. When closed, it closes literalData, writes a signature packet
378// to encryptedData and then also closes encryptedData.
379type signatureWriter struct {
380	encryptedData io.WriteCloser
381	literalData   io.WriteCloser
382	hashType      crypto.Hash
383	h             hash.Hash
384	signer        *packet.PrivateKey
385	config        *packet.Config
386}
387
388func (s signatureWriter) Write(data []byte) (int, error) {
389	s.h.Write(data)
390	return s.literalData.Write(data)
391}
392
393func (s signatureWriter) Close() error {
394	sig := &packet.Signature{
395		SigType:      packet.SigTypeBinary,
396		PubKeyAlgo:   s.signer.PubKeyAlgo,
397		Hash:         s.hashType,
398		CreationTime: s.config.Now(),
399		IssuerKeyId:  &s.signer.KeyId,
400	}
401
402	if err := sig.Sign(s.h, s.signer, s.config); err != nil {
403		return err
404	}
405	if err := s.literalData.Close(); err != nil {
406		return err
407	}
408	if err := sig.Serialize(s.encryptedData); err != nil {
409		return err
410	}
411	return s.encryptedData.Close()
412}
413
414// noOpCloser is like an ioutil.NopCloser, but for an io.Writer.
415// TODO: we have two of these in OpenPGP packages alone. This probably needs
416// to be promoted somewhere more common.
417type noOpCloser struct {
418	w io.Writer
419}
420
421func (c noOpCloser) Write(data []byte) (n int, err error) {
422	return c.w.Write(data)
423}
424
425func (c noOpCloser) Close() error {
426	return nil
427}
428
429// AttachedSign is like openpgp.Encrypt (as in p.crypto/openpgp/write.go), but
430// don't encrypt at all, just sign the literal unencrypted data.
431// Unfortunately we need to duplicate some code here that's already
432// in write.go
433func AttachedSign(out io.WriteCloser, signed Entity, hints *FileHints,
434	config *packet.Config) (in io.WriteCloser, err error) {
435
436	if hints == nil {
437		hints = &FileHints{}
438	}
439
440	if config == nil {
441		config = &packet.Config{}
442	}
443
444	var signer *packet.PrivateKey
445
446	signKey, ok := signed.signingKey(config.Now())
447	if !ok {
448		err = errors.InvalidArgumentError("no valid signing keys")
449		return
450	}
451	signer = signKey.PrivateKey
452	if signer == nil {
453		err = errors.InvalidArgumentError("no valid signing keys")
454		return
455	}
456	if signer.Encrypted {
457		err = errors.InvalidArgumentError("signing key must be decrypted")
458		return
459	}
460
461	if algo := config.Compression(); algo != packet.CompressionNone {
462		var compConfig *packet.CompressionConfig
463		if config != nil {
464			compConfig = config.CompressionConfig
465		}
466		out, err = packet.SerializeCompressed(out, algo, compConfig)
467		if err != nil {
468			return
469		}
470	}
471
472	hasher := config.Hash() // defaults to SHA-256
473
474	ops := &packet.OnePassSignature{
475		SigType:    packet.SigTypeBinary,
476		Hash:       hasher,
477		PubKeyAlgo: signer.PubKeyAlgo,
478		KeyId:      signer.KeyId,
479		IsLast:     true,
480	}
481
482	if err = ops.Serialize(out); err != nil {
483		return
484	}
485
486	var epochSeconds uint32
487	if !hints.ModTime.IsZero() {
488		epochSeconds = uint32(hints.ModTime.Unix())
489	}
490
491	// We don't want the literal serializer to closer the output stream
492	// since we're going to need to write to it when we finish up the
493	// signature stuff.
494	in, err = packet.SerializeLiteral(noOpCloser{out}, hints.IsBinary, hints.FileName, epochSeconds)
495
496	if err != nil {
497		return
498	}
499
500	// If we need to write a signature packet after the literal
501	// data then we need to stop literalData from closing
502	// encryptedData.
503	in = signatureWriter{out, in, hasher, hasher.New(), signer, config}
504
505	return
506}
507