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