main
Raw Download raw file
  1// Copyright 2023 Proton AG. 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 packet
  6
  7import (
  8	"crypto/cipher"
  9	"crypto/sha256"
 10	"fmt"
 11	"io"
 12	"strconv"
 13
 14	"github.com/ProtonMail/go-crypto/openpgp/errors"
 15	"golang.org/x/crypto/hkdf"
 16)
 17
 18// parseAead parses a V2 SEIPD packet (AEAD) as specified in
 19// https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-07.html#section-5.13.2
 20func (se *SymmetricallyEncrypted) parseAead(r io.Reader) error {
 21	headerData := make([]byte, 3)
 22	if n, err := io.ReadFull(r, headerData); n < 3 {
 23		return errors.StructuralError("could not read aead header: " + err.Error())
 24	}
 25
 26	// Cipher
 27	se.Cipher = CipherFunction(headerData[0])
 28	// cipherFunc must have block size 16 to use AEAD
 29	if se.Cipher.blockSize() != 16 {
 30		return errors.UnsupportedError("invalid aead cipher: " + strconv.Itoa(int(se.Cipher)))
 31	}
 32
 33	// Mode
 34	se.Mode = AEADMode(headerData[1])
 35	if se.Mode.TagLength() == 0 {
 36		return errors.UnsupportedError("unknown aead mode: " + strconv.Itoa(int(se.Mode)))
 37	}
 38
 39	// Chunk size
 40	se.ChunkSizeByte = headerData[2]
 41	if se.ChunkSizeByte > 16 {
 42		return errors.UnsupportedError("invalid aead chunk size byte: " + strconv.Itoa(int(se.ChunkSizeByte)))
 43	}
 44
 45	// Salt
 46	if n, err := io.ReadFull(r, se.Salt[:]); n < aeadSaltSize {
 47		return errors.StructuralError("could not read aead salt: " + err.Error())
 48	}
 49
 50	return nil
 51}
 52
 53// associatedData for chunks: tag, version, cipher, mode, chunk size byte
 54func (se *SymmetricallyEncrypted) associatedData() []byte {
 55	return []byte{
 56		0xD2,
 57		symmetricallyEncryptedVersionAead,
 58		byte(se.Cipher),
 59		byte(se.Mode),
 60		se.ChunkSizeByte,
 61	}
 62}
 63
 64// decryptAead decrypts a V2 SEIPD packet (AEAD) as specified in
 65// https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-07.html#section-5.13.2
 66func (se *SymmetricallyEncrypted) decryptAead(inputKey []byte) (io.ReadCloser, error) {
 67	if se.Cipher.KeySize() != len(inputKey) {
 68		return nil, errors.StructuralError(fmt.Sprintf("invalid session key length for cipher: got %d bytes, but expected %d bytes", len(inputKey), se.Cipher.KeySize()))
 69	}
 70
 71	aead, nonce := getSymmetricallyEncryptedAeadInstance(se.Cipher, se.Mode, inputKey, se.Salt[:], se.associatedData())
 72	// Carry the first tagLen bytes
 73	chunkSize := decodeAEADChunkSize(se.ChunkSizeByte)
 74	tagLen := se.Mode.TagLength()
 75	chunkBytes := make([]byte, chunkSize+tagLen*2)
 76	peekedBytes := chunkBytes[chunkSize+tagLen:]
 77	n, err := io.ReadFull(se.Contents, peekedBytes)
 78	if n < tagLen || (err != nil && err != io.EOF) {
 79		return nil, errors.StructuralError("not enough data to decrypt:" + err.Error())
 80	}
 81
 82	return &aeadDecrypter{
 83		aeadCrypter: aeadCrypter{
 84			aead:           aead,
 85			chunkSize:      decodeAEADChunkSize(se.ChunkSizeByte),
 86			nonce:          nonce,
 87			associatedData: se.associatedData(),
 88			chunkIndex:     nonce[len(nonce)-8:],
 89			packetTag:      packetTypeSymmetricallyEncryptedIntegrityProtected,
 90		},
 91		reader:      se.Contents,
 92		chunkBytes:  chunkBytes,
 93		peekedBytes: peekedBytes,
 94	}, nil
 95}
 96
 97// serializeSymmetricallyEncryptedAead encrypts to a writer a V2 SEIPD packet (AEAD) as specified in
 98// https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-07.html#section-5.13.2
 99func serializeSymmetricallyEncryptedAead(ciphertext io.WriteCloser, cipherSuite CipherSuite, chunkSizeByte byte, rand io.Reader, inputKey []byte) (Contents io.WriteCloser, err error) {
100	// cipherFunc must have block size 16 to use AEAD
101	if cipherSuite.Cipher.blockSize() != 16 {
102		return nil, errors.InvalidArgumentError("invalid aead cipher function")
103	}
104
105	if cipherSuite.Cipher.KeySize() != len(inputKey) {
106		return nil, errors.InvalidArgumentError("error in aead serialization: bad key length")
107	}
108
109	// Data for en/decryption: tag, version, cipher, aead mode, chunk size
110	prefix := []byte{
111		0xD2,
112		symmetricallyEncryptedVersionAead,
113		byte(cipherSuite.Cipher),
114		byte(cipherSuite.Mode),
115		chunkSizeByte,
116	}
117
118	// Write header (that correspond to prefix except first byte)
119	n, err := ciphertext.Write(prefix[1:])
120	if err != nil || n < 4 {
121		return nil, err
122	}
123
124	// Random salt
125	salt := make([]byte, aeadSaltSize)
126	if _, err := io.ReadFull(rand, salt); err != nil {
127		return nil, err
128	}
129
130	if _, err := ciphertext.Write(salt); err != nil {
131		return nil, err
132	}
133
134	aead, nonce := getSymmetricallyEncryptedAeadInstance(cipherSuite.Cipher, cipherSuite.Mode, inputKey, salt, prefix)
135
136	chunkSize := decodeAEADChunkSize(chunkSizeByte)
137	tagLen := aead.Overhead()
138	chunkBytes := make([]byte, chunkSize+tagLen)
139	return &aeadEncrypter{
140		aeadCrypter: aeadCrypter{
141			aead:           aead,
142			chunkSize:      chunkSize,
143			associatedData: prefix,
144			nonce:          nonce,
145			chunkIndex:     nonce[len(nonce)-8:],
146			packetTag:      packetTypeSymmetricallyEncryptedIntegrityProtected,
147		},
148		writer:     ciphertext,
149		chunkBytes: chunkBytes,
150	}, nil
151}
152
153func getSymmetricallyEncryptedAeadInstance(c CipherFunction, mode AEADMode, inputKey, salt, associatedData []byte) (aead cipher.AEAD, nonce []byte) {
154	hkdfReader := hkdf.New(sha256.New, inputKey, salt, associatedData)
155
156	encryptionKey := make([]byte, c.KeySize())
157	_, _ = readFull(hkdfReader, encryptionKey)
158
159	nonce = make([]byte, mode.IvLength())
160
161	// Last 64 bits of nonce are the counter
162	_, _ = readFull(hkdfReader, nonce[:len(nonce)-8])
163
164	blockCipher := c.new(encryptionKey)
165	aead = mode.new(blockCipher)
166
167	return
168}