main
Raw Download raw file
  1// Copyright (C) 2019 ProtonTech AG
  2
  3package packet
  4
  5import (
  6	"crypto/cipher"
  7	"encoding/binary"
  8	"io"
  9
 10	"github.com/ProtonMail/go-crypto/openpgp/errors"
 11)
 12
 13// aeadCrypter is an AEAD opener/sealer, its configuration, and data for en/decryption.
 14type aeadCrypter struct {
 15	aead           cipher.AEAD
 16	chunkSize      int
 17	nonce          []byte
 18	associatedData []byte       // Chunk-independent associated data
 19	chunkIndex     []byte       // Chunk counter
 20	packetTag      packetType   // SEIP packet (v2) or AEAD Encrypted Data packet
 21	bytesProcessed int          // Amount of plaintext bytes encrypted/decrypted
 22}
 23
 24// computeNonce takes the incremental index and computes an eXclusive OR with
 25// the least significant 8 bytes of the receivers' initial nonce (see sec.
 26// 5.16.1 and 5.16.2). It returns the resulting nonce.
 27func (wo *aeadCrypter) computeNextNonce() (nonce []byte) {
 28	if wo.packetTag == packetTypeSymmetricallyEncryptedIntegrityProtected {
 29		return wo.nonce
 30	}
 31
 32	nonce = make([]byte, len(wo.nonce))
 33	copy(nonce, wo.nonce)
 34	offset := len(wo.nonce) - 8
 35	for i := 0; i < 8; i++ {
 36		nonce[i+offset] ^= wo.chunkIndex[i]
 37	}
 38	return
 39}
 40
 41// incrementIndex performs an integer increment by 1 of the integer represented by the
 42// slice, modifying it accordingly.
 43func (wo *aeadCrypter) incrementIndex() error {
 44	index := wo.chunkIndex
 45	if len(index) == 0 {
 46		return errors.AEADError("Index has length 0")
 47	}
 48	for i := len(index) - 1; i >= 0; i-- {
 49		if index[i] < 255 {
 50			index[i]++
 51			return nil
 52		}
 53		index[i] = 0
 54	}
 55	return errors.AEADError("cannot further increment index")
 56}
 57
 58// aeadDecrypter reads and decrypts bytes. It buffers extra decrypted bytes when
 59// necessary, similar to aeadEncrypter.
 60type aeadDecrypter struct {
 61	aeadCrypter           // Embedded ciphertext opener
 62	reader      io.Reader // 'reader' is a partialLengthReader
 63	chunkBytes  []byte
 64	peekedBytes []byte    // Used to detect last chunk
 65	buffer      []byte    // Buffered decrypted bytes
 66}
 67
 68// Read decrypts bytes and reads them into dst. It decrypts when necessary and
 69// buffers extra decrypted bytes. It returns the number of bytes copied into dst
 70// and an error.
 71func (ar *aeadDecrypter) Read(dst []byte) (n int, err error) {
 72	// Return buffered plaintext bytes from previous calls
 73	if len(ar.buffer) > 0 {
 74		n = copy(dst, ar.buffer)
 75		ar.buffer = ar.buffer[n:]
 76		return
 77	}
 78
 79	// Read a chunk
 80	tagLen := ar.aead.Overhead()
 81	copy(ar.chunkBytes, ar.peekedBytes) // Copy bytes peeked in previous chunk or in initialization
 82	bytesRead, errRead := io.ReadFull(ar.reader, ar.chunkBytes[tagLen:])
 83	if errRead != nil && errRead != io.EOF && errRead != io.ErrUnexpectedEOF {
 84		return 0, errRead
 85	}
 86
 87	if bytesRead > 0 {
 88		ar.peekedBytes = ar.chunkBytes[bytesRead:bytesRead+tagLen]
 89
 90		decrypted, errChunk := ar.openChunk(ar.chunkBytes[:bytesRead])
 91		if errChunk != nil {
 92			return 0, errChunk
 93		}
 94
 95		// Return decrypted bytes, buffering if necessary
 96		n = copy(dst, decrypted)
 97		ar.buffer = decrypted[n:]
 98		return
 99	}
100
101	return 0, io.EOF
102}
103
104// Close checks the final authentication tag of the stream.
105// In the future, this function could also be used to wipe the reader
106// and peeked & decrypted bytes, if necessary.
107func (ar *aeadDecrypter) Close() (err error) {
108	errChunk := ar.validateFinalTag(ar.peekedBytes)
109	if errChunk != nil {
110		return errChunk
111	}
112	return nil
113}
114
115// openChunk decrypts and checks integrity of an encrypted chunk, returning
116// the underlying plaintext and an error. It accesses peeked bytes from next
117// chunk, to identify the last chunk and decrypt/validate accordingly.
118func (ar *aeadDecrypter) openChunk(data []byte) ([]byte, error) {
119	adata := ar.associatedData
120	if ar.aeadCrypter.packetTag == packetTypeAEADEncrypted {
121		adata = append(ar.associatedData, ar.chunkIndex...)
122	}
123
124	nonce := ar.computeNextNonce()
125	plainChunk, err := ar.aead.Open(data[:0:len(data)], nonce, data, adata)
126	if err != nil {
127		return nil, errors.ErrAEADTagVerification
128	}
129	ar.bytesProcessed += len(plainChunk)
130	if err = ar.aeadCrypter.incrementIndex(); err != nil {
131		return nil, err
132	}
133	return plainChunk, nil
134}
135
136// Checks the summary tag. It takes into account the total decrypted bytes into
137// the associated data. It returns an error, or nil if the tag is valid.
138func (ar *aeadDecrypter) validateFinalTag(tag []byte) error {
139	// Associated: tag, version, cipher, aead, chunk size, ...
140	amountBytes := make([]byte, 8)
141	binary.BigEndian.PutUint64(amountBytes, uint64(ar.bytesProcessed))
142
143	adata := ar.associatedData
144	if ar.aeadCrypter.packetTag == packetTypeAEADEncrypted {
145		// ... index ...
146		adata = append(ar.associatedData, ar.chunkIndex...)
147	}
148
149	// ... and total number of encrypted octets
150	adata = append(adata, amountBytes...)
151	nonce := ar.computeNextNonce()
152	if _, err := ar.aead.Open(nil, nonce, tag, adata); err != nil {
153		return errors.ErrAEADTagVerification
154	}
155	return nil
156}
157
158// aeadEncrypter encrypts and writes bytes. It encrypts when necessary according
159// to the AEAD block size, and buffers the extra encrypted bytes for next write.
160type aeadEncrypter struct {
161	aeadCrypter                // Embedded plaintext sealer
162	writer      io.WriteCloser // 'writer' is a partialLengthWriter
163	chunkBytes  []byte
164	offset      int
165}
166
167// Write encrypts and writes bytes. It encrypts when necessary and buffers extra
168// plaintext bytes for next call. When the stream is finished, Close() MUST be
169// called to append the final tag.
170func (aw *aeadEncrypter) Write(plaintextBytes []byte) (n int, err error) {
171	for n != len(plaintextBytes) {
172		copied := copy(aw.chunkBytes[aw.offset:aw.chunkSize], plaintextBytes[n:])
173		n += copied
174		aw.offset += copied
175
176		if aw.offset == aw.chunkSize {
177			encryptedChunk, err := aw.sealChunk(aw.chunkBytes[:aw.offset])
178			if err != nil {
179				return n, err
180			}
181			_, err = aw.writer.Write(encryptedChunk)
182			if err != nil {
183				return n, err
184			}
185			aw.offset = 0
186		}
187	}
188	return
189}
190
191// Close encrypts and writes the remaining buffered plaintext if any, appends
192// the final authentication tag, and closes the embedded writer. This function
193// MUST be called at the end of a stream.
194func (aw *aeadEncrypter) Close() (err error) {
195	// Encrypt and write a chunk if there's buffered data left, or if we haven't
196	// written any chunks yet.
197	if aw.offset > 0 || aw.bytesProcessed == 0 {
198		lastEncryptedChunk, err := aw.sealChunk(aw.chunkBytes[:aw.offset])
199		if err != nil {
200			return err
201		}
202		_, err = aw.writer.Write(lastEncryptedChunk)
203		if err != nil {
204			return err
205		}
206	}
207	// Compute final tag (associated data: packet tag, version, cipher, aead,
208	// chunk size...
209	adata := aw.associatedData
210
211	if aw.aeadCrypter.packetTag == packetTypeAEADEncrypted {
212		// ... index ...
213		adata = append(aw.associatedData, aw.chunkIndex...)
214	}
215
216	// ... and total number of encrypted octets
217	amountBytes := make([]byte, 8)
218	binary.BigEndian.PutUint64(amountBytes, uint64(aw.bytesProcessed))
219	adata = append(adata, amountBytes...)
220
221	nonce := aw.computeNextNonce()
222	finalTag := aw.aead.Seal(nil, nonce, nil, adata)
223	_, err = aw.writer.Write(finalTag)
224	if err != nil {
225		return err
226	}
227	return aw.writer.Close()
228}
229
230// sealChunk Encrypts and authenticates the given chunk.
231func (aw *aeadEncrypter) sealChunk(data []byte) ([]byte, error) {
232	if len(data) > aw.chunkSize {
233		return nil, errors.AEADError("chunk exceeds maximum length")
234	}
235	if aw.associatedData == nil {
236		return nil, errors.AEADError("can't seal without headers")
237	}
238	adata := aw.associatedData
239	if aw.aeadCrypter.packetTag == packetTypeAEADEncrypted {
240		adata = append(aw.associatedData, aw.chunkIndex...)
241	}
242
243	nonce := aw.computeNextNonce()
244	encrypted := aw.aead.Seal(data[:0], nonce, data, adata)
245	aw.bytesProcessed += len(data)
246	if err := aw.aeadCrypter.incrementIndex(); err != nil {
247		return nil, err
248	}
249	return encrypted, nil
250}