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