1// Copyright 2024 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 shake tests. See 25// https://pages.nist.gov/ACVP/draft-celi-acvp-sha3.html#name-test-vectors 26 27type shakeTestVectorSet struct { 28 Groups []shakeTestGroup `json:"testGroups"` 29} 30 31type shakeTestGroup struct { 32 ID uint64 `json:"tgId"` 33 Type string `json:"testType"` 34 MaxOutLenBits uint32 `json:"maxOutLen"` 35 MinOutLenBits uint32 `json:"minOutLen"` 36 Tests []struct { 37 ID uint64 `json:"tcId"` 38 BitLength uint64 `json:"len"` 39 BitOutLength uint32 `json:"outLen"` 40 MsgHex string `json:"msg"` 41 } `json:"tests"` 42} 43 44type shakeTestGroupResponse struct { 45 ID uint64 `json:"tgId"` 46 Tests []shakeTestResponse `json:"tests"` 47} 48 49type shakeTestResponse struct { 50 ID uint64 `json:"tcId"` 51 DigestHex string `json:"md,omitempty"` 52 MCTResults []shakeMCTResult `json:"resultsArray,omitempty"` 53} 54 55type shakeMCTResult struct { 56 DigestHex string `json:"md"` 57 OutputLen uint32 `json:"outLen,omitempty"` 58} 59 60// shake implements an ACVP algorithm by making requests to the 61// subprocess to hash strings. 62type shake struct { 63 // algo is the ACVP name for this algorithm and also the command name 64 // given to the subprocess to hash with this hash function. 65 algo string 66 // size is the number of bytes of digest that the hash produces for AFT tests. 67 size int 68} 69 70func (h *shake) Process(vectorSet []byte, m Transactable) (any, error) { 71 var parsed shakeTestVectorSet 72 if err := json.Unmarshal(vectorSet, &parsed); err != nil { 73 return nil, err 74 } 75 76 var ret []shakeTestGroupResponse 77 // See 78 // https://pages.nist.gov/ACVP/draft-celi-acvp-sha3.html#name-test-types 79 // for details about the tests. 80 for _, group := range parsed.Groups { 81 group := group 82 response := shakeTestGroupResponse{ 83 ID: group.ID, 84 } 85 86 for _, test := range group.Tests { 87 test := test 88 89 if uint64(len(test.MsgHex))*4 != test.BitLength { 90 return nil, fmt.Errorf("test case %d/%d contains hex message of length %d but specifies a bit length of %d", group.ID, test.ID, len(test.MsgHex), test.BitLength) 91 } 92 msg, err := hex.DecodeString(test.MsgHex) 93 if err != nil { 94 return nil, fmt.Errorf("failed to decode hex in test case %d/%d: %s", group.ID, test.ID, err) 95 } 96 97 if test.BitOutLength%8 != 0 { 98 return nil, fmt.Errorf("test case %d/%d has bit length %d - fractional bytes not supported", group.ID, test.ID, test.BitOutLength) 99 } 100 101 switch group.Type { 102 case "AFT": 103 // "AFTs all produce a single digest size, matching the security strength of the extendable output function." 104 if test.BitOutLength != uint32(h.size*8) { 105 return nil, fmt.Errorf("AFT test case %d/%d has bit length %d but expected %d", group.ID, test.ID, test.BitOutLength, h.size*8) 106 } 107 108 m.TransactAsync(h.algo, 1, [][]byte{msg, uint32le(test.BitOutLength / 8)}, func(result [][]byte) error { 109 response.Tests = append(response.Tests, shakeTestResponse{ 110 ID: test.ID, 111 DigestHex: hex.EncodeToString(result[0]), 112 }) 113 return nil 114 }) 115 case "VOT": 116 // "The VOTs SHALL produce varying digest sizes based on the capabilities of the IUT" 117 m.TransactAsync(h.algo+"/VOT", 1, [][]byte{msg, uint32le(test.BitOutLength / 8)}, func(result [][]byte) error { 118 response.Tests = append(response.Tests, shakeTestResponse{ 119 ID: test.ID, 120 DigestHex: hex.EncodeToString(result[0]), 121 }) 122 return nil 123 }) 124 case "MCT": 125 // https://pages.nist.gov/ACVP/draft-celi-acvp-sha3.html#name-shake-monte-carlo-test 126 testResponse := shakeTestResponse{ID: test.ID} 127 128 if group.MinOutLenBits%8 != 0 { 129 return nil, fmt.Errorf("MCT test group %d has min output length %d - fractional bytes not supported", group.ID, group.MinOutLenBits) 130 } 131 if group.MaxOutLenBits%8 != 0 { 132 return nil, fmt.Errorf("MCT test group %d has max output length %d - fractional bytes not supported", group.ID, group.MaxOutLenBits) 133 } 134 135 digest := msg 136 minOutLenBytes := uint32le(group.MinOutLenBits / 8) 137 maxOutLenBytes := uint32le(group.MaxOutLenBits / 8) 138 outputLenBytes := uint32le(group.MaxOutLenBits / 8) 139 140 for i := 0; i < 100; i++ { 141 args := [][]byte{digest, minOutLenBytes, maxOutLenBytes, outputLenBytes} 142 result, err := m.Transact(h.algo+"/MCT", 2, args...) 143 if err != nil { 144 panic(h.algo + " mct operation failed: " + err.Error()) 145 } 146 147 digest = result[0] 148 outputLenBytes = uint32le(binary.LittleEndian.Uint32(result[1])) 149 mctResult := shakeMCTResult{DigestHex: hex.EncodeToString(digest), OutputLen: uint32(len(digest) * 8)} 150 testResponse.MCTResults = append(testResponse.MCTResults, mctResult) 151 } 152 153 response.Tests = append(response.Tests, testResponse) 154 default: 155 return nil, fmt.Errorf("test group %d has unknown type %q", group.ID, group.Type) 156 } 157 } 158 159 m.Barrier(func() { 160 ret = append(ret, response) 161 }) 162 } 163 164 if err := m.Flush(); err != nil { 165 return nil, err 166 } 167 168 return ret, nil 169} 170