1// Copyright 2019 The BoringSSL Authors 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 "bytes" 19 "encoding/hex" 20 "encoding/json" 21 "fmt" 22) 23 24// The following structures reflect the JSON of ACVP ECDSA tests. See 25// https://pages.nist.gov/ACVP/draft-fussell-acvp-ecdsa.html#name-test-vectors 26 27type ecdsaTestVectorSet struct { 28 Groups []ecdsaTestGroup `json:"testGroups"` 29 Algorithm string `json:"algorithm"` 30 Mode string `json:"mode"` 31} 32 33type ecdsaTestGroup struct { 34 ID uint64 `json:"tgId"` 35 Curve string `json:"curve"` 36 SecretGenerationMode string `json:"secretGenerationMode,omitempty"` 37 HashAlgo string `json:"hashAlg,omitEmpty"` 38 ComponentTest bool `json:"componentTest"` 39 Tests []struct { 40 ID uint64 `json:"tcId"` 41 QxHex string `json:"qx,omitempty"` 42 QyHex string `json:"qy,omitempty"` 43 RHex string `json:"r,omitempty"` 44 SHex string `json:"s,omitempty"` 45 MsgHex string `json:"message,omitempty"` 46 } `json:"tests"` 47} 48 49type ecdsaTestGroupResponse struct { 50 ID uint64 `json:"tgId"` 51 Tests []ecdsaTestResponse `json:"tests"` 52 QxHex string `json:"qx,omitempty"` 53 QyHex string `json:"qy,omitempty"` 54} 55 56type ecdsaTestResponse struct { 57 ID uint64 `json:"tcId"` 58 DHex string `json:"d,omitempty"` 59 QxHex string `json:"qx,omitempty"` 60 QyHex string `json:"qy,omitempty"` 61 RHex string `json:"r,omitempty"` 62 SHex string `json:"s,omitempty"` 63 Passed *bool `json:"testPassed,omitempty"` // using pointer so value is not omitted when it is false 64} 65 66// ecdsa implements an ACVP algorithm by making requests to the 67// subprocess to generate and verify ECDSA keys and signatures. 68type ecdsa struct { 69 // algo is the ACVP name for this algorithm and also the command name 70 // given to the subprocess to hash with this hash function. 71 algo string 72 curves map[string]bool // supported curve names 73 primitives map[string]primitive 74} 75 76func (e *ecdsa) Process(vectorSet []byte, m Transactable) (any, error) { 77 var parsed ecdsaTestVectorSet 78 if err := json.Unmarshal(vectorSet, &parsed); err != nil { 79 return nil, err 80 } 81 82 if parsed.Algorithm == "DetECDSA" && parsed.Mode != "sigGen" { 83 return nil, fmt.Errorf("DetECDSA only specifies sigGen mode") 84 } 85 86 var ret []ecdsaTestGroupResponse 87 // See 88 // https://pages.nist.gov/ACVP/draft-fussell-acvp-ecdsa.html#name-test-vectors 89 // for details about the tests. 90 for _, group := range parsed.Groups { 91 group := group 92 93 if _, ok := e.curves[group.Curve]; !ok { 94 return nil, fmt.Errorf("curve %q in test group %d not supported", group.Curve, group.ID) 95 } 96 97 response := ecdsaTestGroupResponse{ 98 ID: group.ID, 99 } 100 var sigGenPrivateKey []byte 101 102 for _, test := range group.Tests { 103 test := test 104 105 var testResp ecdsaTestResponse 106 testResp.ID = test.ID 107 108 switch parsed.Mode { 109 case "keyGen": 110 if group.SecretGenerationMode != "testing candidates" { 111 return nil, fmt.Errorf("invalid secret generation mode in test group %d: %q", group.ID, group.SecretGenerationMode) 112 } 113 m.TransactAsync(e.algo+"/"+"keyGen", 3, [][]byte{[]byte(group.Curve)}, func(result [][]byte) error { 114 testResp.DHex = hex.EncodeToString(result[0]) 115 testResp.QxHex = hex.EncodeToString(result[1]) 116 testResp.QyHex = hex.EncodeToString(result[2]) 117 response.Tests = append(response.Tests, testResp) 118 return nil 119 }) 120 121 case "keyVer": 122 qx, err := hex.DecodeString(test.QxHex) 123 if err != nil { 124 return nil, fmt.Errorf("failed to decode qx in test case %d/%d: %s", group.ID, test.ID, err) 125 } 126 qy, err := hex.DecodeString(test.QyHex) 127 if err != nil { 128 return nil, fmt.Errorf("failed to decode qy in test case %d/%d: %s", group.ID, test.ID, err) 129 } 130 m.TransactAsync(e.algo+"/"+"keyVer", 1, [][]byte{[]byte(group.Curve), qx, qy}, func(result [][]byte) error { 131 // result[0] should be a single byte: zero if false, one if true 132 switch { 133 case bytes.Equal(result[0], []byte{00}): 134 f := false 135 testResp.Passed = &f 136 case bytes.Equal(result[0], []byte{01}): 137 t := true 138 testResp.Passed = &t 139 default: 140 return fmt.Errorf("key verification returned unexpected result: %q", result[0]) 141 } 142 response.Tests = append(response.Tests, testResp) 143 return nil 144 }) 145 146 case "sigGen": 147 if group.ComponentTest && parsed.Algorithm == "DetECDSA" { 148 return nil, fmt.Errorf("DetECDSA does not support component tests") 149 } 150 151 p := e.primitives[group.HashAlgo] 152 h, ok := p.(*hashPrimitive) 153 if !ok { 154 return nil, fmt.Errorf("unsupported hash algorithm %q in test group %d", group.HashAlgo, group.ID) 155 } 156 157 if len(sigGenPrivateKey) == 0 { 158 // Ask the subprocess to generate a key for this test group. 159 cmd := e.algo + "/keyGen" 160 if e.algo == "DetECDSA" { 161 // Use "ECDSA/keyGen" for DetECDSA to avoid the module wrapper needing to support a second 162 // keyGen command for DetECDSA. 163 cmd = "ECDSA/keyGen" 164 } 165 result, err := m.Transact(cmd, 3, []byte(group.Curve)) 166 if err != nil { 167 return nil, fmt.Errorf("key generation failed for test case %d/%d: %s", group.ID, test.ID, err) 168 } 169 170 sigGenPrivateKey = result[0] 171 response.QxHex = hex.EncodeToString(result[1]) 172 response.QyHex = hex.EncodeToString(result[2]) 173 } 174 175 msg, err := hex.DecodeString(test.MsgHex) 176 if err != nil { 177 return nil, fmt.Errorf("failed to decode message hex in test case %d/%d: %s", group.ID, test.ID, err) 178 } 179 op := e.algo + "/" + "sigGen" 180 if group.ComponentTest { 181 if len(msg) != h.size { 182 return nil, fmt.Errorf("test case %d/%d contains message %q of length %d, but expected length %d", group.ID, test.ID, test.MsgHex, len(msg), h.size) 183 } 184 op += "/componentTest" 185 } 186 m.TransactAsync(op, 2, [][]byte{[]byte(group.Curve), sigGenPrivateKey, []byte(group.HashAlgo), msg}, func(result [][]byte) error { 187 testResp.RHex = hex.EncodeToString(result[0]) 188 testResp.SHex = hex.EncodeToString(result[1]) 189 response.Tests = append(response.Tests, testResp) 190 return nil 191 }) 192 193 case "sigVer": 194 p := e.primitives[group.HashAlgo] 195 _, ok := p.(*hashPrimitive) 196 if !ok { 197 return nil, fmt.Errorf("unsupported hash algorithm %q in test group %d", group.HashAlgo, group.ID) 198 } 199 200 msg, err := hex.DecodeString(test.MsgHex) 201 if err != nil { 202 return nil, fmt.Errorf("failed to decode message hex in test case %d/%d: %s", group.ID, test.ID, err) 203 } 204 qx, err := hex.DecodeString(test.QxHex) 205 if err != nil { 206 return nil, fmt.Errorf("failed to decode qx in test case %d/%d: %s", group.ID, test.ID, err) 207 } 208 qy, err := hex.DecodeString(test.QyHex) 209 if err != nil { 210 return nil, fmt.Errorf("failed to decode qy in test case %d/%d: %s", group.ID, test.ID, err) 211 } 212 r, err := hex.DecodeString(test.RHex) 213 if err != nil { 214 return nil, fmt.Errorf("failed to decode R in test case %d/%d: %s", group.ID, test.ID, err) 215 } 216 s, err := hex.DecodeString(test.SHex) 217 if err != nil { 218 return nil, fmt.Errorf("failed to decode S in test case %d/%d: %s", group.ID, test.ID, err) 219 } 220 m.TransactAsync(e.algo+"/"+"sigVer", 1, [][]byte{[]byte(group.Curve), []byte(group.HashAlgo), msg, qx, qy, r, s}, func(result [][]byte) error { 221 // result[0] should be a single byte: zero if false, one if true 222 switch { 223 case bytes.Equal(result[0], []byte{00}): 224 f := false 225 testResp.Passed = &f 226 case bytes.Equal(result[0], []byte{01}): 227 t := true 228 testResp.Passed = &t 229 default: 230 return fmt.Errorf("signature verification returned unexpected result: %q", result[0]) 231 } 232 response.Tests = append(response.Tests, testResp) 233 return nil 234 }) 235 236 default: 237 return nil, fmt.Errorf("invalid mode %q in ECDSA vector set", parsed.Mode) 238 } 239 } 240 241 m.Barrier(func() { 242 ret = append(ret, response) 243 }) 244 } 245 246 if err := m.Flush(); err != nil { 247 return nil, err 248 } 249 250 return ret, nil 251} 252