main
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}