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