forked from toolshed/abra
		
	We were running behind and there were quite some deprecations to update. This was mostly in the upstream copy/pasta package but seems quite minimal.
		
			
				
	
	
		
			251 lines
		
	
	
		
			7.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			251 lines
		
	
	
		
			7.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Copyright (C) 2019 ProtonTech AG
 | |
| 
 | |
| package packet
 | |
| 
 | |
| import (
 | |
| 	"crypto/cipher"
 | |
| 	"encoding/binary"
 | |
| 	"io"
 | |
| 
 | |
| 	"github.com/ProtonMail/go-crypto/openpgp/errors"
 | |
| )
 | |
| 
 | |
| // aeadCrypter is an AEAD opener/sealer, its configuration, and data for en/decryption.
 | |
| type aeadCrypter struct {
 | |
| 	aead           cipher.AEAD
 | |
| 	chunkSize      int
 | |
| 	nonce          []byte
 | |
| 	associatedData []byte       // Chunk-independent associated data
 | |
| 	chunkIndex     []byte       // Chunk counter
 | |
| 	packetTag      packetType   // SEIP packet (v2) or AEAD Encrypted Data packet
 | |
| 	bytesProcessed int          // Amount of plaintext bytes encrypted/decrypted
 | |
| }
 | |
| 
 | |
| // computeNonce takes the incremental index and computes an eXclusive OR with
 | |
| // the least significant 8 bytes of the receivers' initial nonce (see sec.
 | |
| // 5.16.1 and 5.16.2). It returns the resulting nonce.
 | |
| func (wo *aeadCrypter) computeNextNonce() (nonce []byte) {
 | |
| 	if wo.packetTag == packetTypeSymmetricallyEncryptedIntegrityProtected {
 | |
| 		return wo.nonce
 | |
| 	}
 | |
| 
 | |
| 	nonce = make([]byte, len(wo.nonce))
 | |
| 	copy(nonce, wo.nonce)
 | |
| 	offset := len(wo.nonce) - 8
 | |
| 	for i := 0; i < 8; i++ {
 | |
| 		nonce[i+offset] ^= wo.chunkIndex[i]
 | |
| 	}
 | |
| 	return
 | |
| }
 | |
| 
 | |
| // incrementIndex performs an integer increment by 1 of the integer represented by the
 | |
| // slice, modifying it accordingly.
 | |
| func (wo *aeadCrypter) incrementIndex() error {
 | |
| 	index := wo.chunkIndex
 | |
| 	if len(index) == 0 {
 | |
| 		return errors.AEADError("Index has length 0")
 | |
| 	}
 | |
| 	for i := len(index) - 1; i >= 0; i-- {
 | |
| 		if index[i] < 255 {
 | |
| 			index[i]++
 | |
| 			return nil
 | |
| 		}
 | |
| 		index[i] = 0
 | |
| 	}
 | |
| 	return errors.AEADError("cannot further increment index")
 | |
| }
 | |
| 
 | |
| // aeadDecrypter reads and decrypts bytes. It buffers extra decrypted bytes when
 | |
| // necessary, similar to aeadEncrypter.
 | |
| type aeadDecrypter struct {
 | |
| 	aeadCrypter           // Embedded ciphertext opener
 | |
| 	reader      io.Reader // 'reader' is a partialLengthReader
 | |
| 	chunkBytes  []byte
 | |
| 	peekedBytes []byte    // Used to detect last chunk
 | |
| 	buffer      []byte    // Buffered decrypted bytes
 | |
| }
 | |
| 
 | |
| // Read decrypts bytes and reads them into dst. It decrypts when necessary and
 | |
| // buffers extra decrypted bytes. It returns the number of bytes copied into dst
 | |
| // and an error.
 | |
| func (ar *aeadDecrypter) Read(dst []byte) (n int, err error) {
 | |
| 	// Return buffered plaintext bytes from previous calls
 | |
| 	if len(ar.buffer) > 0 {
 | |
| 		n = copy(dst, ar.buffer)
 | |
| 		ar.buffer = ar.buffer[n:]
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	// Read a chunk
 | |
| 	tagLen := ar.aead.Overhead()
 | |
| 	copy(ar.chunkBytes, ar.peekedBytes) // Copy bytes peeked in previous chunk or in initialization
 | |
| 	bytesRead, errRead := io.ReadFull(ar.reader, ar.chunkBytes[tagLen:])
 | |
| 	if errRead != nil && errRead != io.EOF && errRead != io.ErrUnexpectedEOF {
 | |
| 		return 0, errRead
 | |
| 	}
 | |
| 
 | |
| 	if bytesRead > 0 {
 | |
| 		ar.peekedBytes = ar.chunkBytes[bytesRead:bytesRead+tagLen]
 | |
| 
 | |
| 		decrypted, errChunk := ar.openChunk(ar.chunkBytes[:bytesRead])
 | |
| 		if errChunk != nil {
 | |
| 			return 0, errChunk
 | |
| 		}
 | |
| 
 | |
| 		// Return decrypted bytes, buffering if necessary
 | |
| 		n = copy(dst, decrypted)
 | |
| 		ar.buffer = decrypted[n:]
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	return 0, io.EOF
 | |
| }
 | |
| 
 | |
| // Close checks the final authentication tag of the stream.
 | |
| // In the future, this function could also be used to wipe the reader
 | |
| // and peeked & decrypted bytes, if necessary.
 | |
| func (ar *aeadDecrypter) Close() (err error) {
 | |
| 	errChunk := ar.validateFinalTag(ar.peekedBytes)
 | |
| 	if errChunk != nil {
 | |
| 		return errChunk
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // openChunk decrypts and checks integrity of an encrypted chunk, returning
 | |
| // the underlying plaintext and an error. It accesses peeked bytes from next
 | |
| // chunk, to identify the last chunk and decrypt/validate accordingly.
 | |
| func (ar *aeadDecrypter) openChunk(data []byte) ([]byte, error) {
 | |
| 	adata := ar.associatedData
 | |
| 	if ar.aeadCrypter.packetTag == packetTypeAEADEncrypted {
 | |
| 		adata = append(ar.associatedData, ar.chunkIndex...)
 | |
| 	}
 | |
| 
 | |
| 	nonce := ar.computeNextNonce()
 | |
| 	plainChunk, err := ar.aead.Open(data[:0:len(data)], nonce, data, adata)
 | |
| 	if err != nil {
 | |
| 		return nil, errors.ErrAEADTagVerification
 | |
| 	}
 | |
| 	ar.bytesProcessed += len(plainChunk)
 | |
| 	if err = ar.aeadCrypter.incrementIndex(); err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	return plainChunk, nil
 | |
| }
 | |
| 
 | |
| // Checks the summary tag. It takes into account the total decrypted bytes into
 | |
| // the associated data. It returns an error, or nil if the tag is valid.
 | |
| func (ar *aeadDecrypter) validateFinalTag(tag []byte) error {
 | |
| 	// Associated: tag, version, cipher, aead, chunk size, ...
 | |
| 	amountBytes := make([]byte, 8)
 | |
| 	binary.BigEndian.PutUint64(amountBytes, uint64(ar.bytesProcessed))
 | |
| 
 | |
| 	adata := ar.associatedData
 | |
| 	if ar.aeadCrypter.packetTag == packetTypeAEADEncrypted {
 | |
| 		// ... index ...
 | |
| 		adata = append(ar.associatedData, ar.chunkIndex...)
 | |
| 	}
 | |
| 
 | |
| 	// ... and total number of encrypted octets
 | |
| 	adata = append(adata, amountBytes...)
 | |
| 	nonce := ar.computeNextNonce()
 | |
| 	if _, err := ar.aead.Open(nil, nonce, tag, adata); err != nil {
 | |
| 		return errors.ErrAEADTagVerification
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // aeadEncrypter encrypts and writes bytes. It encrypts when necessary according
 | |
| // to the AEAD block size, and buffers the extra encrypted bytes for next write.
 | |
| type aeadEncrypter struct {
 | |
| 	aeadCrypter                // Embedded plaintext sealer
 | |
| 	writer      io.WriteCloser // 'writer' is a partialLengthWriter
 | |
| 	chunkBytes  []byte
 | |
| 	offset      int
 | |
| }
 | |
| 
 | |
| // Write encrypts and writes bytes. It encrypts when necessary and buffers extra
 | |
| // plaintext bytes for next call. When the stream is finished, Close() MUST be
 | |
| // called to append the final tag.
 | |
| func (aw *aeadEncrypter) Write(plaintextBytes []byte) (n int, err error) {
 | |
| 	for n != len(plaintextBytes) {
 | |
| 		copied := copy(aw.chunkBytes[aw.offset:aw.chunkSize], plaintextBytes[n:])
 | |
| 		n += copied
 | |
| 		aw.offset += copied
 | |
| 
 | |
| 		if aw.offset == aw.chunkSize {
 | |
| 			encryptedChunk, err := aw.sealChunk(aw.chunkBytes[:aw.offset])
 | |
| 			if err != nil {
 | |
| 				return n, err
 | |
| 			}
 | |
| 			_, err = aw.writer.Write(encryptedChunk)
 | |
| 			if err != nil {
 | |
| 				return n, err
 | |
| 			}
 | |
| 			aw.offset = 0
 | |
| 		}
 | |
| 	}
 | |
| 	return
 | |
| }
 | |
| 
 | |
| // Close encrypts and writes the remaining buffered plaintext if any, appends
 | |
| // the final authentication tag, and closes the embedded writer. This function
 | |
| // MUST be called at the end of a stream.
 | |
| func (aw *aeadEncrypter) Close() (err error) {
 | |
| 	// Encrypt and write a chunk if there's buffered data left, or if we haven't
 | |
| 	// written any chunks yet.
 | |
| 	if aw.offset > 0 || aw.bytesProcessed == 0 {
 | |
| 		lastEncryptedChunk, err := aw.sealChunk(aw.chunkBytes[:aw.offset])
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 		_, err = aw.writer.Write(lastEncryptedChunk)
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 	}
 | |
| 	// Compute final tag (associated data: packet tag, version, cipher, aead,
 | |
| 	// chunk size...
 | |
| 	adata := aw.associatedData
 | |
| 
 | |
| 	if aw.aeadCrypter.packetTag == packetTypeAEADEncrypted {
 | |
| 		// ... index ...
 | |
| 		adata = append(aw.associatedData, aw.chunkIndex...)
 | |
| 	}
 | |
| 
 | |
| 	// ... and total number of encrypted octets
 | |
| 	amountBytes := make([]byte, 8)
 | |
| 	binary.BigEndian.PutUint64(amountBytes, uint64(aw.bytesProcessed))
 | |
| 	adata = append(adata, amountBytes...)
 | |
| 
 | |
| 	nonce := aw.computeNextNonce()
 | |
| 	finalTag := aw.aead.Seal(nil, nonce, nil, adata)
 | |
| 	_, err = aw.writer.Write(finalTag)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	return aw.writer.Close()
 | |
| }
 | |
| 
 | |
| // sealChunk Encrypts and authenticates the given chunk.
 | |
| func (aw *aeadEncrypter) sealChunk(data []byte) ([]byte, error) {
 | |
| 	if len(data) > aw.chunkSize {
 | |
| 		return nil, errors.AEADError("chunk exceeds maximum length")
 | |
| 	}
 | |
| 	if aw.associatedData == nil {
 | |
| 		return nil, errors.AEADError("can't seal without headers")
 | |
| 	}
 | |
| 	adata := aw.associatedData
 | |
| 	if aw.aeadCrypter.packetTag == packetTypeAEADEncrypted {
 | |
| 		adata = append(aw.associatedData, aw.chunkIndex...)
 | |
| 	}
 | |
| 
 | |
| 	nonce := aw.computeNextNonce()
 | |
| 	encrypted := aw.aead.Seal(data[:0], nonce, data, adata)
 | |
| 	aw.bytesProcessed += len(data)
 | |
| 	if err := aw.aeadCrypter.incrementIndex(); err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	return encrypted, nil
 | |
| }
 |