• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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