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