1// Copyright 2019 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/binary"
19	"encoding/hex"
20	"encoding/json"
21	"fmt"
22)
23
24// The following structures reflect the JSON of ACVP DRBG tests. See
25// https://pages.nist.gov/ACVP/draft-vassilev-acvp-drbg.html#name-test-vectors
26
27type drbgTestVectorSet struct {
28	Groups []drbgTestGroup `json:"testGroups"`
29}
30
31type drbgTestGroup struct {
32	ID                    uint64 `json:"tgId"`
33	Mode                  string `json:"mode"`
34	UseDerivationFunction bool   `json:"derFunc,omitempty"`
35	PredictionResistance  bool   `json:"predResistance"`
36	Reseed                bool   `json:"reSeed"`
37	EntropyBits           uint64 `json:"entropyInputLen"`
38	NonceBits             uint64 `json:"nonceLen"`
39	PersonalizationBits   uint64 `json:"persoStringLen"`
40	AdditionalDataBits    uint64 `json:"additionalInputLen"`
41	RetBits               uint64 `json:"returnedBitsLen"`
42	Tests                 []struct {
43		ID                 uint64           `json:"tcId"`
44		EntropyHex         string           `json:"entropyInput"`
45		NonceHex           string           `json:"nonce"`
46		PersonalizationHex string           `json:"persoString"`
47		Other              []drbgOtherInput `json:"otherInput"`
48	} `json:"tests"`
49}
50
51type drbgOtherInput struct {
52	Use               string `json:"intendedUse"`
53	AdditionalDataHex string `json:"additionalInput"`
54	EntropyHex        string `json:"entropyInput"`
55}
56
57type drbgTestGroupResponse struct {
58	ID    uint64             `json:"tgId"`
59	Tests []drbgTestResponse `json:"tests"`
60}
61
62type drbgTestResponse struct {
63	ID     uint64 `json:"tcId"`
64	OutHex string `json:"returnedBits,omitempty"`
65}
66
67// drbg implements an ACVP algorithm by making requests to the
68// subprocess to generate random bits with the given entropy and other paramaters.
69type drbg struct {
70	// algo is the ACVP name for this algorithm and also the command name
71	// given to the subprocess to generate random bytes.
72	algo  string
73	modes map[string]bool // the supported underlying primitives for the DRBG
74}
75
76func (d *drbg) Process(vectorSet []byte, m Transactable) (any, error) {
77	var parsed drbgTestVectorSet
78	if err := json.Unmarshal(vectorSet, &parsed); err != nil {
79		return nil, err
80	}
81
82	var ret []drbgTestGroupResponse
83	// See
84	// https://pages.nist.gov/ACVP/draft-vassilev-acvp-drbg.html#name-test-vectors
85	// for details about the tests.
86	for _, group := range parsed.Groups {
87		group := group
88		response := drbgTestGroupResponse{
89			ID: group.ID,
90		}
91
92		if _, ok := d.modes[group.Mode]; !ok {
93			return nil, fmt.Errorf("test group %d specifies mode %q, which is not supported for the %s algorithm", group.ID, group.Mode, d.algo)
94		}
95
96		if group.RetBits%8 != 0 {
97			return nil, fmt.Errorf("Test group %d requests %d-bit outputs, but fractional-bytes are not supported", group.ID, group.RetBits)
98		}
99
100		for _, test := range group.Tests {
101			test := test
102
103			ent, err := extractField(test.EntropyHex, group.EntropyBits)
104			if err != nil {
105				return nil, fmt.Errorf("failed to extract entropy hex from test case %d/%d: %s", group.ID, test.ID, err)
106			}
107
108			nonce, err := extractField(test.NonceHex, group.NonceBits)
109			if err != nil {
110				return nil, fmt.Errorf("failed to extract nonce hex from test case %d/%d: %s", group.ID, test.ID, err)
111			}
112
113			perso, err := extractField(test.PersonalizationHex, group.PersonalizationBits)
114			if err != nil {
115				return nil, fmt.Errorf("failed to extract personalization hex from test case %d/%d: %s", group.ID, test.ID, err)
116			}
117
118			outLen := group.RetBits / 8
119			var outLenBytes [4]byte
120			binary.LittleEndian.PutUint32(outLenBytes[:], uint32(outLen))
121
122			var cmd string
123			var args [][]byte
124			if group.PredictionResistance {
125				var a1, a2, a3, a4 []byte
126				if err := extractOtherInputs(test.Other, []drbgOtherInputExpectations{
127					{"generate", group.AdditionalDataBits, &a1, group.EntropyBits, &a2},
128					{"generate", group.AdditionalDataBits, &a3, group.EntropyBits, &a4}}); err != nil {
129					return nil, fmt.Errorf("failed to parse other inputs from test case %d/%d: %s", group.ID, test.ID, err)
130				}
131				cmd = d.algo + "-pr/" + group.Mode
132				args = [][]byte{outLenBytes[:], ent, perso, a1, a2, a3, a4, nonce}
133			} else if group.Reseed {
134				var a1, a2, a3, a4 []byte
135				if err := extractOtherInputs(test.Other, []drbgOtherInputExpectations{
136					{"reSeed", group.AdditionalDataBits, &a1, group.EntropyBits, &a2},
137					{"generate", group.AdditionalDataBits, &a3, 0, nil},
138					{"generate", group.AdditionalDataBits, &a4, 0, nil}}); err != nil {
139					return nil, fmt.Errorf("failed to parse other inputs from test case %d/%d: %s", group.ID, test.ID, err)
140				}
141				cmd = d.algo + "-reseed/" + group.Mode
142				args = [][]byte{outLenBytes[:], ent, perso, a1, a2, a3, a4, nonce}
143			} else {
144				var a1, a2 []byte
145				if err := extractOtherInputs(test.Other, []drbgOtherInputExpectations{
146					{"generate", group.AdditionalDataBits, &a1, 0, nil},
147					{"generate", group.AdditionalDataBits, &a2, 0, nil}}); err != nil {
148					return nil, fmt.Errorf("failed to parse other inputs from test case %d/%d: %s", group.ID, test.ID, err)
149				}
150				cmd = d.algo + "/" + group.Mode
151				args = [][]byte{outLenBytes[:], ent, perso, a1, a2, nonce}
152			}
153
154			m.TransactAsync(cmd, 1, args, func(result [][]byte) error {
155				if l := uint64(len(result[0])); l != outLen {
156					return fmt.Errorf("wrong length DRBG result: %d bytes but wanted %d", l, outLen)
157				}
158
159				// https://pages.nist.gov/ACVP/draft-vassilev-acvp-drbg.html#name-responses
160				response.Tests = append(response.Tests, drbgTestResponse{
161					ID:     test.ID,
162					OutHex: hex.EncodeToString(result[0]),
163				})
164				return nil
165			})
166		}
167
168		m.Barrier(func() {
169			ret = append(ret, response)
170		})
171	}
172
173	if err := m.Flush(); err != nil {
174		return nil, err
175	}
176
177	return ret, nil
178}
179
180type drbgOtherInputExpectations struct {
181	use                   string
182	additionalInputBitLen uint64
183	additionalInputOut    *[]byte
184	entropyBitLen         uint64
185	entropyOut            *[]byte
186}
187
188func extractOtherInputs(inputs []drbgOtherInput, expected []drbgOtherInputExpectations) (err error) {
189	if len(inputs) != len(expected) {
190		return fmt.Errorf("found %d other inputs but %d were expected", len(inputs), len(expected))
191	}
192
193	for i := range inputs {
194		input, expect := &inputs[i], &expected[i]
195
196		if input.Use != expect.use {
197			return fmt.Errorf("other input #%d has type %q but expected %q", i, input.Use, expect.use)
198		}
199
200		if expect.additionalInputBitLen == 0 {
201			if len(input.AdditionalDataHex) != 0 {
202				return fmt.Errorf("other input #%d has unexpected additional input", i)
203			}
204		} else {
205			*expect.additionalInputOut, err = extractField(input.AdditionalDataHex, expect.additionalInputBitLen)
206			if err != nil {
207				return err
208			}
209		}
210
211		if expect.entropyBitLen == 0 {
212			if len(input.EntropyHex) != 0 {
213				return fmt.Errorf("other input #%d has unexpected entropy value", i)
214			}
215		} else {
216			*expect.entropyOut, err = extractField(input.EntropyHex, expect.entropyBitLen)
217			if err != nil {
218				return err
219			}
220		}
221	}
222
223	return nil
224}
225
226// validate the length and hex of a JSON field in test vectors
227func extractField(fieldHex string, bits uint64) ([]byte, error) {
228	if uint64(len(fieldHex))*4 != bits {
229		return nil, fmt.Errorf("expected %d bits but have %d-byte hex string", bits, len(fieldHex))
230	}
231	return hex.DecodeString(fieldHex)
232}
233