1// Copyright 2020 The BoringSSL Authors
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//     https://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
15package subprocess
16
17import (
18	"encoding/hex"
19	"encoding/json"
20	"fmt"
21)
22
23// aead implements an ACVP algorithm by making requests to the subprocess
24// to encrypt and decrypt with an AEAD.
25type aead struct {
26	algo                    string
27	tagMergedWithCiphertext bool
28}
29
30type aeadVectorSet struct {
31	Groups []aeadTestGroup `json:"testGroups"`
32}
33
34type aeadTestGroup struct {
35	ID          uint64 `json:"tgId"`
36	Type        string `json:"testType"`
37	Direction   string `json:"direction"`
38	KeyBits     int    `json:"keyLen"`
39	TagBits     int    `json:"tagLen"`
40	NonceSource string `json:"ivGen"`
41	Tests       []struct {
42		ID            uint64 `json:"tcId"`
43		PlaintextHex  string `json:"pt"`
44		CiphertextHex string `json:"ct"`
45		IVHex         string `json:"iv"`
46		KeyHex        string `json:"key"`
47		AADHex        string `json:"aad"`
48		TagHex        string `json:"tag"`
49	} `json:"tests"`
50}
51
52type aeadTestGroupResponse struct {
53	ID    uint64             `json:"tgId"`
54	Tests []aeadTestResponse `json:"tests"`
55}
56
57type aeadTestResponse struct {
58	ID            uint64  `json:"tcId"`
59	CiphertextHex *string `json:"ct,omitempty"`
60	TagHex        string  `json:"tag,omitempty"`
61	NonceHex      string  `json:"iv,omitempty"`
62	PlaintextHex  *string `json:"pt,omitempty"`
63	Passed        *bool   `json:"testPassed,omitempty"`
64}
65
66func (a *aead) Process(vectorSet []byte, m Transactable) (any, error) {
67	var parsed aeadVectorSet
68	if err := json.Unmarshal(vectorSet, &parsed); err != nil {
69		return nil, err
70	}
71
72	var ret []aeadTestGroupResponse
73	// See draft-celi-acvp-symmetric.html#table-6. (NIST no longer publish HTML
74	// versions of the ACVP documents. You can find fragments in
75	// https://github.com/usnistgov/ACVP.)
76	for _, group := range parsed.Groups {
77		group := group
78		response := aeadTestGroupResponse{
79			ID: group.ID,
80		}
81
82		var encrypt bool
83		switch group.Direction {
84		case "encrypt":
85			encrypt = true
86		case "decrypt":
87			encrypt = false
88		default:
89			return nil, fmt.Errorf("test group %d has unknown direction %q", group.ID, group.Direction)
90		}
91
92		var randnonce bool
93		switch group.NonceSource {
94		case "internal":
95			randnonce = true
96		case "external", "":
97			randnonce = false
98		default:
99			return nil, fmt.Errorf("test group %d has unknown nonce source %q", group.ID, group.NonceSource)
100		}
101
102		op := a.algo
103		if randnonce {
104			op += "-randnonce"
105		}
106		if encrypt {
107			op += "/seal"
108		} else {
109			op += "/open"
110		}
111
112		if group.KeyBits%8 != 0 || group.KeyBits < 0 {
113			return nil, fmt.Errorf("test group %d contains non-byte-multiple key length %d", group.ID, group.KeyBits)
114		}
115		keyBytes := group.KeyBits / 8
116
117		if group.TagBits%8 != 0 || group.TagBits < 0 {
118			return nil, fmt.Errorf("test group %d contains non-byte-multiple tag length %d", group.ID, group.TagBits)
119		}
120		tagBytes := group.TagBits / 8
121
122		for _, test := range group.Tests {
123			test := test
124
125			if len(test.KeyHex) != keyBytes*2 {
126				return nil, fmt.Errorf("test case %d/%d contains key %q of length %d, but expected %d-bit key", group.ID, test.ID, test.KeyHex, len(test.KeyHex), group.KeyBits)
127			}
128
129			key, err := hex.DecodeString(test.KeyHex)
130			if err != nil {
131				return nil, fmt.Errorf("failed to decode key in test case %d/%d: %s", group.ID, test.ID, err)
132			}
133
134			nonce, err := hex.DecodeString(test.IVHex)
135			if err != nil {
136				return nil, fmt.Errorf("failed to decode nonce in test case %d/%d: %s", group.ID, test.ID, err)
137			}
138
139			aad, err := hex.DecodeString(test.AADHex)
140			if err != nil {
141				return nil, fmt.Errorf("failed to decode aad in test case %d/%d: %s", group.ID, test.ID, err)
142			}
143
144			var inputHex, otherHex string
145			if encrypt {
146				inputHex, otherHex = test.PlaintextHex, test.CiphertextHex
147			} else {
148				inputHex, otherHex = test.CiphertextHex, test.PlaintextHex
149			}
150
151			if len(otherHex) != 0 {
152				return nil, fmt.Errorf("test case %d/%d has unexpected plain/ciphertext input", group.ID, test.ID)
153			}
154
155			input, err := hex.DecodeString(inputHex)
156			if err != nil {
157				return nil, fmt.Errorf("failed to decode hex in test case %d/%d: %s", group.ID, test.ID, err)
158			}
159
160			var tag []byte
161			if a.tagMergedWithCiphertext {
162				if len(test.TagHex) != 0 {
163					return nil, fmt.Errorf("test case %d/%d has unexpected tag input (should be merged into ciphertext)", group.ID, test.ID)
164				}
165				if !encrypt && len(input) < tagBytes {
166					return nil, fmt.Errorf("test case %d/%d has ciphertext shorter than the tag, but the tag should be included in it", group.ID, test.ID)
167				}
168			} else {
169				if !encrypt {
170					if tag, err = hex.DecodeString(test.TagHex); err != nil {
171						return nil, fmt.Errorf("failed to decode tag in test case %d/%d: %s", group.ID, test.ID, err)
172					}
173					if len(tag) != tagBytes {
174						return nil, fmt.Errorf("tag in test case %d/%d is %d bytes long, but should be %d", group.ID, test.ID, len(tag), tagBytes)
175					}
176				} else if len(test.TagHex) != 0 {
177					return nil, fmt.Errorf("test case %d/%d has unexpected tag input", group.ID, test.ID)
178				}
179			}
180
181			testResp := aeadTestResponse{ID: test.ID}
182
183			if encrypt {
184				m.TransactAsync(op, 1, [][]byte{uint32le(uint32(tagBytes)), key, input, nonce, aad}, func(result [][]byte) error {
185					if len(result[0]) < tagBytes {
186						return fmt.Errorf("ciphertext from subprocess for test case %d/%d is shorter than the tag (%d vs %d)", group.ID, test.ID, len(result[0]), tagBytes)
187					}
188
189					if a.tagMergedWithCiphertext {
190						ciphertextHex := hex.EncodeToString(result[0])
191						testResp.CiphertextHex = &ciphertextHex
192					} else {
193						ciphertext := result[0]
194						if randnonce {
195							var nonce []byte
196							ciphertext, nonce = splitOffRight(ciphertext, 12)
197							testResp.NonceHex = hex.EncodeToString(nonce)
198						}
199						ciphertext, tag := splitOffRight(ciphertext, tagBytes)
200						ciphertextHex := hex.EncodeToString(ciphertext)
201						testResp.CiphertextHex = &ciphertextHex
202						testResp.TagHex = hex.EncodeToString(tag)
203					}
204					response.Tests = append(response.Tests, testResp)
205					return nil
206				})
207			} else {
208				ciphertext := append(input, tag...)
209				if randnonce {
210					ciphertext = append(ciphertext, nonce...)
211					nonce = []byte{}
212				}
213				m.TransactAsync(op, 2, [][]byte{uint32le(uint32(tagBytes)), key, ciphertext, nonce, aad}, func(result [][]byte) error {
214					if len(result[0]) != 1 || (result[0][0]&0xfe) != 0 {
215						return fmt.Errorf("invalid AEAD status result from subprocess")
216					}
217					passed := result[0][0] == 1
218					testResp.Passed = &passed
219					if passed {
220						plaintextHex := hex.EncodeToString(result[1])
221						testResp.PlaintextHex = &plaintextHex
222					}
223					response.Tests = append(response.Tests, testResp)
224					return nil
225				})
226			}
227		}
228
229		m.Barrier(func() {
230			ret = append(ret, response)
231		})
232	}
233
234	if err := m.Flush(); err != nil {
235		return nil, err
236	}
237
238	return ret, nil
239}
240
241func splitOffRight(in []byte, suffixSize int) ([]byte, []byte) {
242	if len(in) < suffixSize {
243		panic("input too small to split")
244	}
245	split := len(in) - suffixSize
246	return in[:split], in[split:]
247}
248