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