xref: /aosp_15_r20/external/tink/go/streamingaead/subtle/noncebased/noncebased.go (revision e7b1675dde1b92d52ec075b0a92829627f2c52a5)
1// Copyright 2020 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//      http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14//
15////////////////////////////////////////////////////////////////////////////////
16
17// Package noncebased provides a reusable streaming AEAD framework.
18//
19// It tackles the segment handling portions of the nonce based online
20// encryption scheme proposed in "Online Authenticated-Encryption and its
21// Nonce-Reuse Misuse-Resistance" by Hoang, Reyhanitabar, Rogaway and Vizár
22// (https://eprint.iacr.org/2015/189.pdf).
23//
24// In this scheme, the format of a ciphertext is:
25//
26//   header || segment_0 || segment_1 || ... || segment_k.
27//
28// The format of header is:
29//
30//   headerLength || salt || nonce_prefix
31//
32// headerLength is 1 byte which documents the size of the header and can be
33// obtained via HeaderLength(). In principle, headerLength is redundant
34// information, since the length of the header can be determined from the key
35// size.
36//
37// salt is a salt used in the key derivation.
38//
39// nonce_prefix is a prefix for all per-segment nonces.
40//
41// segment_i is the i-th segment of the ciphertext. The size of segment_1 ..
42// segment_{k-1} is ciphertextSegmentSize. segment_0 is shorter, so that
43// segment_0 plus additional data of size firstCiphertextSegmentOffset (e.g.
44// the header) aligns with ciphertextSegmentSize.
45//
46// The first segment size will be:
47//
48//		ciphertextSegmentSize - HeaderLength() - firstCiphertextSegmentOffset.
49package noncebased
50
51import (
52	"encoding/binary"
53	"errors"
54	"io"
55	"math"
56)
57
58var (
59	// ErrNonceSizeTooShort indicates that the specified nonce size isn't large
60	// enough to hold the nonce prefix, counter and last segment flag.
61	ErrNonceSizeTooShort = errors.New("nonce size too short")
62
63	// ErrCiphertextSegmentTooShort indicates the the ciphertext segment being
64	// processed is too short.
65	ErrCiphertextSegmentTooShort = errors.New("ciphertext segment too short")
66
67	// ErrTooManySegments indicates that the ciphertext has too many segments.
68	ErrTooManySegments = errors.New("too many segments")
69)
70
71// SegmentEncrypter facilitates implementing various streaming AEAD encryption
72// modes.
73type SegmentEncrypter interface {
74	EncryptSegment(segment, nonce []byte) ([]byte, error)
75}
76
77// Writer provides a framework for ingesting plaintext data and
78// writing encrypted data to the wrapped io.Writer. The scheme used for
79// encrypting segments is specified by providing a SegmentEncrypter
80// implementation.
81type Writer struct {
82	w                            io.Writer
83	segmentEncrypter             SegmentEncrypter
84	encryptedSegmentCnt          uint64
85	firstCiphertextSegmentOffset int
86	nonceSize                    int
87	noncePrefix                  []byte
88	plaintext                    []byte
89	plaintextPos                 int
90	ciphertext                   []byte
91	closed                       bool
92}
93
94// WriterParams contains the options for instantiating a Writer via NewWriter().
95type WriterParams struct {
96	// W is the underlying writer being wrapped.
97	W io.Writer
98
99	// SegmentEncrypter provides a method for encrypting segments.
100	SegmentEncrypter SegmentEncrypter
101
102	// NonceSize is the length of generated nonces. It must be at least 5 +
103	// len(NoncePrefix). It can be longer, but longer nonces introduce more
104	// overhead in the resultant ciphertext.
105	NonceSize int
106
107	// NoncePrefix is a constant that all nonces throughout the ciphertext will
108	// start with. It's length must be at least 5 bytes shorter than NonceSize.
109	NoncePrefix []byte
110
111	// The size of the segments which the plaintext will be split into.
112	PlaintextSegmentSize int
113
114	// FirstCiphertexSegmentOffset indicates where the ciphertext should begin in
115	// W. This allows for the existence of overhead in the stream unrelated to
116	// this encryption scheme.
117	FirstCiphertextSegmentOffset int
118}
119
120// NewWriter creates a new Writer instance.
121func NewWriter(params WriterParams) (*Writer, error) {
122	if params.NonceSize-len(params.NoncePrefix) < 5 {
123		return nil, ErrNonceSizeTooShort
124	}
125	return &Writer{
126		w:                            params.W,
127		segmentEncrypter:             params.SegmentEncrypter,
128		nonceSize:                    params.NonceSize,
129		noncePrefix:                  params.NoncePrefix,
130		firstCiphertextSegmentOffset: params.FirstCiphertextSegmentOffset,
131		plaintext:                    make([]byte, params.PlaintextSegmentSize),
132	}, nil
133}
134
135// Write encrypts passed data and passes the encrypted data to the underlying writer.
136func (w *Writer) Write(p []byte) (int, error) {
137	if w.closed {
138		return 0, errors.New("write on closed writer")
139	}
140
141	pos := 0
142	for {
143		ptLim := len(w.plaintext)
144		if w.encryptedSegmentCnt == 0 {
145			ptLim -= w.firstCiphertextSegmentOffset
146		}
147		n := copy(w.plaintext[w.plaintextPos:ptLim], p[pos:])
148		w.plaintextPos += n
149		pos += n
150		if pos == len(p) {
151			break
152		}
153
154		nonce, err := generateSegmentNonce(w.nonceSize, w.noncePrefix, w.encryptedSegmentCnt, false)
155		if err != nil {
156			return pos, err
157		}
158
159		w.ciphertext, err = w.segmentEncrypter.EncryptSegment(w.plaintext[:ptLim], nonce)
160		if err != nil {
161			return pos, err
162		}
163
164		if _, err := w.w.Write(w.ciphertext); err != nil {
165			return pos, err
166		}
167
168		w.plaintextPos = 0
169		w.encryptedSegmentCnt++
170	}
171	return pos, nil
172}
173
174// Close encrypts the remaining data, flushes it to the underlying writer and
175// closes this writer.
176func (w *Writer) Close() error {
177	if w.closed {
178		return nil
179	}
180
181	nonce, err := generateSegmentNonce(w.nonceSize, w.noncePrefix, w.encryptedSegmentCnt, true)
182	if err != nil {
183		return err
184	}
185
186	w.ciphertext, err = w.segmentEncrypter.EncryptSegment(w.plaintext[:w.plaintextPos], nonce)
187	if err != nil {
188		return err
189	}
190
191	if _, err := w.w.Write(w.ciphertext); err != nil {
192		return err
193	}
194
195	w.plaintextPos = 0
196	w.encryptedSegmentCnt++
197	w.closed = true
198	return nil
199}
200
201// SegmentDecrypter facilitates implementing various streaming AEAD encryption modes.
202type SegmentDecrypter interface {
203	DecryptSegment(segment, nonce []byte) ([]byte, error)
204}
205
206// Reader facilitates the decryption of ciphertexts created using a Writer.
207//
208// The scheme used for decrypting segments is specified by providing a
209// SegmentDecrypter implementation. The implementation must align
210// with the SegmentEncrypter used in the Writer.
211type Reader struct {
212	r                            io.Reader
213	segmentDecrypter             SegmentDecrypter
214	decryptedSegmentCnt          uint64
215	firstCiphertextSegmentOffset int
216	nonceSize                    int
217	noncePrefix                  []byte
218	plaintext                    []byte
219	plaintextPos                 int
220	ciphertext                   []byte
221	ciphertextPos                int
222}
223
224// ReaderParams contains the options for instantiating a Reader via NewReader().
225type ReaderParams struct {
226	// R is the underlying reader being wrapped.
227	R io.Reader
228
229	// SegmentDecrypter provides a method for decrypting segments.
230	SegmentDecrypter SegmentDecrypter
231
232	// NonceSize is the length of generated nonces. It must match the NonceSize
233	// of the Writer used to create the ciphertext.
234	NonceSize int
235
236	// NoncePrefix is a constant that all nocnes throughout the ciphertext start
237	// with. It's extracted from the header of the ciphertext.
238	NoncePrefix []byte
239
240	// The size of the ciphertext segments.
241	CiphertextSegmentSize int
242
243	// FirstCiphertexSegmentOffset indicates where the ciphertext actually begins
244	// in R. This allows for the existence of overhead in the stream unrelated to
245	// this encryption scheme.
246	FirstCiphertextSegmentOffset int
247}
248
249// NewReader creates a new Reader instance.
250func NewReader(params ReaderParams) (*Reader, error) {
251	if params.NonceSize-len(params.NoncePrefix) < 5 {
252		return nil, ErrNonceSizeTooShort
253	}
254	return &Reader{
255		r:                            params.R,
256		segmentDecrypter:             params.SegmentDecrypter,
257		nonceSize:                    params.NonceSize,
258		noncePrefix:                  params.NoncePrefix,
259		firstCiphertextSegmentOffset: params.FirstCiphertextSegmentOffset,
260
261		// Allocate an extra byte to detect the last segment.
262		ciphertext: make([]byte, params.CiphertextSegmentSize+1),
263	}, nil
264}
265
266// Read decrypts data from underlying reader and passes it to p.
267func (r *Reader) Read(p []byte) (int, error) {
268	if r.plaintextPos < len(r.plaintext) {
269		n := copy(p, r.plaintext[r.plaintextPos:])
270		r.plaintextPos += n
271		return n, nil
272	}
273
274	r.plaintextPos = 0
275
276	ctLim := len(r.ciphertext)
277	if r.decryptedSegmentCnt == 0 {
278		ctLim -= r.firstCiphertextSegmentOffset
279	}
280	n, err := io.ReadFull(r.r, r.ciphertext[r.ciphertextPos:ctLim])
281	if err != nil && err != io.ErrUnexpectedEOF {
282		return 0, err
283	}
284
285	var (
286		lastSegment bool
287		segment     int
288	)
289	if err != nil {
290		lastSegment = true
291		segment = r.ciphertextPos + n
292	} else {
293		segment = r.ciphertextPos + n - 1
294	}
295
296	if segment < 0 {
297		return 0, ErrCiphertextSegmentTooShort
298	}
299
300	nonce, err := generateSegmentNonce(r.nonceSize, r.noncePrefix, r.decryptedSegmentCnt, lastSegment)
301	if err != nil {
302		return 0, err
303	}
304
305	r.plaintext, err = r.segmentDecrypter.DecryptSegment(r.ciphertext[:segment], nonce)
306	if err != nil {
307		return 0, err
308	}
309
310	// Copy 1 byte remainder to the beginning of ciphertext.
311	if !lastSegment {
312		remainderOffset := segment
313		r.ciphertext[0] = r.ciphertext[remainderOffset]
314		r.ciphertextPos = 1
315	}
316
317	r.decryptedSegmentCnt++
318
319	n = copy(p, r.plaintext)
320	r.plaintextPos = n
321	return n, nil
322}
323
324// generateSegmentNonce returns a nonce for a segment.
325//
326// The format of the nonce is:
327//
328//   nonce_prefix || ctr || last_block.
329//
330// nonce_prefix is a constant prefix used throughout the whole ciphertext.
331//
332// The ctr is a 32 bit counter.
333//
334// last_block is 1 byte which is set to 1 for the last segment and 0
335// otherwise.
336func generateSegmentNonce(size int, prefix []byte, segmentNum uint64, last bool) ([]byte, error) {
337	if segmentNum >= math.MaxUint32 {
338		return nil, ErrTooManySegments
339	}
340
341	nonce := make([]byte, size)
342	copy(nonce, prefix)
343	offset := len(prefix)
344	binary.BigEndian.PutUint32(nonce[offset:], uint32(segmentNum))
345	offset += 4
346	if last {
347		nonce[offset] = 1
348	}
349	return nonce, nil
350}
351