1// Copyright (c) 2019, Google Inc. 2// 3// Permission to use, copy, modify, and/or distribute this software for any 4// purpose with or without fee is hereby granted, provided that the above 5// copyright notice and this permission notice appear in all copies. 6// 7// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 10// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION 12// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN 13// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 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://usnistgov.github.io/ACVP/artifacts/acvp_sub_drbg.html#rfc.section.4 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 []struct { 48 AdditionalDataHex string `json:"additionalInput"` 49 EntropyHex string `json:"entropyInput"` 50 Use string `json:"intendedUse"` 51 } `json:"otherInput"` 52 } `json:"tests"` 53} 54 55type drbgTestGroupResponse struct { 56 ID uint64 `json:"tgId"` 57 Tests []drbgTestResponse `json:"tests"` 58} 59 60type drbgTestResponse struct { 61 ID uint64 `json:"tcId"` 62 OutHex string `json:"returnedBits,omitempty"` 63} 64 65// drbg implements an ACVP algorithm by making requests to the 66// subprocess to generate random bits with the given entropy and other paramaters. 67type drbg struct { 68 // algo is the ACVP name for this algorithm and also the command name 69 // given to the subprocess to generate random bytes. 70 algo string 71 modes map[string]bool // the supported underlying primitives for the DRBG 72} 73 74func (d *drbg) Process(vectorSet []byte, m Transactable) (interface{}, error) { 75 var parsed drbgTestVectorSet 76 if err := json.Unmarshal(vectorSet, &parsed); err != nil { 77 return nil, err 78 } 79 80 var ret []drbgTestGroupResponse 81 // See 82 // https://usnistgov.github.io/ACVP/artifacts/acvp_sub_drbg.html#rfc.section.4 83 // for details about the tests. 84 for _, group := range parsed.Groups { 85 response := drbgTestGroupResponse{ 86 ID: group.ID, 87 } 88 89 if _, ok := d.modes[group.Mode]; !ok { 90 return nil, fmt.Errorf("test group %d specifies mode %q, which is not supported for the %s algorithm", group.ID, group.Mode, d.algo) 91 } 92 93 if group.PredictionResistance { 94 return nil, fmt.Errorf("Test group %d specifies prediction-resistance mode, which is not supported", group.ID) 95 } 96 97 if group.Reseed { 98 return nil, fmt.Errorf("Test group %d requests re-seeding, which is not supported", group.ID) 99 } 100 101 if group.RetBits%8 != 0 { 102 return nil, fmt.Errorf("Test group %d requests %d-bit outputs, but fractional-bytes are not supported", group.ID, group.RetBits) 103 } 104 105 for _, test := range group.Tests { 106 ent, err := extractField(test.EntropyHex, group.EntropyBits) 107 if err != nil { 108 return nil, fmt.Errorf("failed to extract entropy hex from test case %d/%d: %s", group.ID, test.ID, err) 109 } 110 111 nonce, err := extractField(test.NonceHex, group.NonceBits) 112 if err != nil { 113 return nil, fmt.Errorf("failed to extract nonce hex from test case %d/%d: %s", group.ID, test.ID, err) 114 } 115 116 perso, err := extractField(test.PersonalizationHex, group.PersonalizationBits) 117 if err != nil { 118 return nil, fmt.Errorf("failed to extract personalization hex from test case %d/%d: %s", group.ID, test.ID, err) 119 } 120 121 const numAdditionalInputs = 2 122 if len(test.Other) != numAdditionalInputs { 123 return nil, fmt.Errorf("test case %d/%d provides %d additional inputs, but subprocess only expects %d", group.ID, test.ID, len(test.Other), numAdditionalInputs) 124 } 125 126 var additionalInputs [numAdditionalInputs][]byte 127 for i, other := range test.Other { 128 if other.Use != "generate" { 129 return nil, fmt.Errorf("other %d from test case %d/%d has use %q, but expected 'generate'", i, group.ID, test.ID, other.Use) 130 } 131 additionalInputs[i], err = extractField(other.AdditionalDataHex, group.AdditionalDataBits) 132 if err != nil { 133 return nil, fmt.Errorf("failed to extract additional input %d from test case %d/%d: %s", i, group.ID, test.ID, err) 134 } 135 } 136 137 outLen := group.RetBits / 8 138 var outLenBytes [4]byte 139 binary.LittleEndian.PutUint32(outLenBytes[:], uint32(outLen)) 140 result, err := m.Transact(d.algo+"/"+group.Mode, 1, outLenBytes[:], ent, perso, additionalInputs[0], additionalInputs[1], nonce) 141 if err != nil { 142 return nil, fmt.Errorf("DRBG operation failed: %s", err) 143 } 144 145 if l := uint64(len(result[0])); l != outLen { 146 return nil, fmt.Errorf("wrong length DRBG result: %d bytes but wanted %d", l, outLen) 147 } 148 149 // https://usnistgov.github.io/ACVP/artifacts/acvp_sub_drbg.html#rfc.section.4 150 response.Tests = append(response.Tests, drbgTestResponse{ 151 ID: test.ID, 152 OutHex: hex.EncodeToString(result[0]), 153 }) 154 } 155 156 ret = append(ret, response) 157 } 158 159 return ret, nil 160} 161 162// validate the length and hex of a JSON field in test vectors 163func extractField(fieldHex string, bits uint64) ([]byte, error) { 164 if uint64(len(fieldHex))*4 != bits { 165 return nil, fmt.Errorf("expected %d bits but have %d-byte hex string", bits, len(fieldHex)) 166 } 167 return hex.DecodeString(fieldHex) 168} 169