• 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
15// Package subprocess contains functionality to talk to a modulewrapper for
16// testing of various algorithm implementations.
17package subprocess
18
19import (
20	"encoding/binary"
21	"encoding/json"
22	"errors"
23	"fmt"
24	"io"
25	"os"
26	"os/exec"
27)
28
29// Transactable provides an interface to allow test injection of transactions
30// that don't call a server.
31type Transactable interface {
32	Transact(cmd string, expectedResults int, args ...[]byte) ([][]byte, error)
33	TransactAsync(cmd string, expectedResults int, args [][]byte, callback func([][]byte) error)
34	Barrier(callback func()) error
35	Flush() error
36}
37
38// Subprocess is a "middle" layer that interacts with a FIPS module via running
39// a command and speaking a simple protocol over stdin/stdout.
40type Subprocess struct {
41	cmd        *exec.Cmd
42	stdin      io.WriteCloser
43	stdout     io.ReadCloser
44	primitives map[string]primitive
45	// supportsFlush is true if the modulewrapper indicated that it wants to receive flush commands.
46	supportsFlush bool
47	// pendingReads is a queue of expected responses. `readerRoutine` reads each response and calls the callback in the matching pendingRead.
48	pendingReads chan pendingRead
49	// readerFinished is a channel that is closed if `readerRoutine` has finished (e.g. because of a read error).
50	readerFinished chan struct{}
51}
52
53// pendingRead represents an expected response from the modulewrapper.
54type pendingRead struct {
55	// barrierCallback is called as soon as this pendingRead is the next in the queue, before any read from the modulewrapper.
56	barrierCallback func()
57
58	// callback is called with the result from the modulewrapper. If this is nil then no read is performed.
59	callback func(result [][]byte) error
60	// cmd is the command that requested this read for logging purposes.
61	cmd                string
62	expectedNumResults int
63}
64
65// New returns a new Subprocess middle layer that runs the given binary.
66func New(path string) (*Subprocess, error) {
67	cmd := exec.Command(path)
68	cmd.Stderr = os.Stderr
69	stdin, err := cmd.StdinPipe()
70	if err != nil {
71		return nil, err
72	}
73	stdout, err := cmd.StdoutPipe()
74	if err != nil {
75		return nil, err
76	}
77
78	if err := cmd.Start(); err != nil {
79		return nil, err
80	}
81
82	return NewWithIO(cmd, stdin, stdout), nil
83}
84
85// maxPending is the maximum number of requests that can be in the pipeline.
86const maxPending = 4096
87
88// NewWithIO returns a new Subprocess middle layer with the given ReadCloser and
89// WriteCloser. The returned Subprocess will call Wait on the Cmd when closed.
90func NewWithIO(cmd *exec.Cmd, in io.WriteCloser, out io.ReadCloser) *Subprocess {
91	m := &Subprocess{
92		cmd:            cmd,
93		stdin:          in,
94		stdout:         out,
95		pendingReads:   make(chan pendingRead, maxPending),
96		readerFinished: make(chan struct{}),
97	}
98
99	m.primitives = map[string]primitive{
100		"SHA-1":             &hashPrimitive{"SHA-1", 20},
101		"SHA2-224":          &hashPrimitive{"SHA2-224", 28},
102		"SHA2-256":          &hashPrimitive{"SHA2-256", 32},
103		"SHA2-384":          &hashPrimitive{"SHA2-384", 48},
104		"SHA2-512":          &hashPrimitive{"SHA2-512", 64},
105		"SHA2-512/256":      &hashPrimitive{"SHA2-512/256", 32},
106		"SHA3-224":          &hashPrimitive{"SHA3-224", 28},
107		"SHA3-256":          &hashPrimitive{"SHA3-256", 32},
108		"SHA3-384":          &hashPrimitive{"SHA3-384", 48},
109		"SHA3-512":          &hashPrimitive{"SHA3-512", 64},
110		"ACVP-AES-ECB":      &blockCipher{"AES", 16, 2, true, false, iterateAES},
111		"ACVP-AES-CBC":      &blockCipher{"AES-CBC", 16, 2, true, true, iterateAESCBC},
112		"ACVP-AES-CBC-CS3":  &blockCipher{"AES-CBC-CS3", 16, 1, false, true, iterateAESCBC},
113		"ACVP-AES-CTR":      &blockCipher{"AES-CTR", 16, 1, false, true, nil},
114		"ACVP-TDES-ECB":     &blockCipher{"3DES-ECB", 8, 3, true, false, iterate3DES},
115		"ACVP-TDES-CBC":     &blockCipher{"3DES-CBC", 8, 3, true, true, iterate3DESCBC},
116		"ACVP-AES-XTS":      &xts{},
117		"ACVP-AES-GCM":      &aead{"AES-GCM", false},
118		"ACVP-AES-GMAC":     &aead{"AES-GCM", false},
119		"ACVP-AES-CCM":      &aead{"AES-CCM", true},
120		"ACVP-AES-KW":       &aead{"AES-KW", false},
121		"ACVP-AES-KWP":      &aead{"AES-KWP", false},
122		"HMAC-SHA-1":        &hmacPrimitive{"HMAC-SHA-1", 20},
123		"HMAC-SHA2-224":     &hmacPrimitive{"HMAC-SHA2-224", 28},
124		"HMAC-SHA2-256":     &hmacPrimitive{"HMAC-SHA2-256", 32},
125		"HMAC-SHA2-384":     &hmacPrimitive{"HMAC-SHA2-384", 48},
126		"HMAC-SHA2-512":     &hmacPrimitive{"HMAC-SHA2-512", 64},
127		"HMAC-SHA2-512/256": &hmacPrimitive{"HMAC-SHA2-512/256", 32},
128		"HMAC-SHA3-224":     &hmacPrimitive{"HMAC-SHA3-224", 28},
129		"HMAC-SHA3-256":     &hmacPrimitive{"HMAC-SHA3-256", 32},
130		"HMAC-SHA3-384":     &hmacPrimitive{"HMAC-SHA3-384", 48},
131		"HMAC-SHA3-512":     &hmacPrimitive{"HMAC-SHA3-512", 64},
132		"ctrDRBG":           &drbg{"ctrDRBG", map[string]bool{"AES-128": true, "AES-192": true, "AES-256": true}},
133		"hmacDRBG":          &drbg{"hmacDRBG", map[string]bool{"SHA-1": true, "SHA2-224": true, "SHA2-256": true, "SHA2-384": true, "SHA2-512": true}},
134		"KDF":               &kdfPrimitive{},
135		"KDA":               &hkdf{},
136		"TLS-v1.2":          &tlsKDF{},
137		"TLS-v1.3":          &tls13{},
138		"CMAC-AES":          &keyedMACPrimitive{"CMAC-AES"},
139		"RSA":               &rsa{},
140		"KAS-ECC-SSC":       &kas{},
141		"KAS-FFC-SSC":       &kasDH{},
142	}
143	m.primitives["ECDSA"] = &ecdsa{"ECDSA", map[string]bool{"P-224": true, "P-256": true, "P-384": true, "P-521": true}, m.primitives}
144
145	go m.readerRoutine()
146	return m
147}
148
149// Close signals the child process to exit and waits for it to complete.
150func (m *Subprocess) Close() {
151	m.stdout.Close()
152	m.stdin.Close()
153	m.cmd.Wait()
154	close(m.pendingReads)
155	<-m.readerFinished
156}
157
158func (m *Subprocess) flush() error {
159	if !m.supportsFlush {
160		return nil
161	}
162
163	const cmd = "flush"
164	buf := make([]byte, 8, 8+len(cmd))
165	binary.LittleEndian.PutUint32(buf, 1)
166	binary.LittleEndian.PutUint32(buf[4:], uint32(len(cmd)))
167	buf = append(buf, []byte(cmd)...)
168
169	if _, err := m.stdin.Write(buf); err != nil {
170		return err
171	}
172	return nil
173}
174
175func (m *Subprocess) enqueueRead(pending pendingRead) error {
176	select {
177	case <-m.readerFinished:
178		panic("attempted to enqueue request after the reader failed")
179	default:
180	}
181
182	select {
183	case m.pendingReads <- pending:
184		break
185	default:
186		// `pendingReads` is full. Ensure that the modulewrapper will process
187		// some outstanding requests to free up space in the queue.
188		if err := m.flush(); err != nil {
189			return err
190		}
191		m.pendingReads <- pending
192	}
193
194	return nil
195}
196
197// TransactAsync performs a single request--response pair with the subprocess.
198// The callback will run at some future point, in a separate goroutine. All
199// callbacks will, however, be run in the order that TransactAsync was called.
200// Use Flush to wait for all outstanding callbacks.
201func (m *Subprocess) TransactAsync(cmd string, expectedNumResults int, args [][]byte, callback func(result [][]byte) error) {
202	if err := m.enqueueRead(pendingRead{nil, callback, cmd, expectedNumResults}); err != nil {
203		panic(err)
204	}
205
206	argLength := len(cmd)
207	for _, arg := range args {
208		argLength += len(arg)
209	}
210
211	buf := make([]byte, 4*(2+len(args)), 4*(2+len(args))+argLength)
212	binary.LittleEndian.PutUint32(buf, uint32(1+len(args)))
213	binary.LittleEndian.PutUint32(buf[4:], uint32(len(cmd)))
214	for i, arg := range args {
215		binary.LittleEndian.PutUint32(buf[4*(i+2):], uint32(len(arg)))
216	}
217	buf = append(buf, []byte(cmd)...)
218	for _, arg := range args {
219		buf = append(buf, arg...)
220	}
221
222	if _, err := m.stdin.Write(buf); err != nil {
223		panic(err)
224	}
225}
226
227// Flush tells the subprocess to complete all outstanding requests and waits
228// for all outstanding TransactAsync callbacks to complete.
229func (m *Subprocess) Flush() error {
230	if m.supportsFlush {
231		m.flush()
232	}
233
234	done := make(chan struct{})
235	if err := m.enqueueRead(pendingRead{barrierCallback: func() {
236		close(done)
237	}}); err != nil {
238		return err
239	}
240
241	<-done
242	return nil
243}
244
245// Barrier runs callback after all outstanding TransactAsync callbacks have
246// been run.
247func (m *Subprocess) Barrier(callback func()) error {
248	return m.enqueueRead(pendingRead{barrierCallback: callback})
249}
250
251func (m *Subprocess) Transact(cmd string, expectedNumResults int, args ...[]byte) ([][]byte, error) {
252	done := make(chan struct{})
253	var result [][]byte
254	m.TransactAsync(cmd, expectedNumResults, args, func(r [][]byte) error {
255		result = r
256		close(done)
257		return nil
258	})
259
260	if err := m.flush(); err != nil {
261		return nil, err
262	}
263
264	select {
265	case <-done:
266		return result, nil
267	case <-m.readerFinished:
268		panic("was still waiting for a result when the reader finished")
269	}
270}
271
272func (m *Subprocess) readerRoutine() {
273	defer close(m.readerFinished)
274
275	for pendingRead := range m.pendingReads {
276		if pendingRead.barrierCallback != nil {
277			pendingRead.barrierCallback()
278		}
279
280		if pendingRead.callback == nil {
281			continue
282		}
283
284		result, err := m.readResult(pendingRead.cmd, pendingRead.expectedNumResults)
285		if err != nil {
286			panic(fmt.Errorf("failed to read from subprocess: %w", err))
287		}
288
289		if err := pendingRead.callback(result); err != nil {
290			panic(fmt.Errorf("result from subprocess was rejected: %w", err))
291		}
292	}
293}
294
295func (m *Subprocess) readResult(cmd string, expectedNumResults int) ([][]byte, error) {
296	buf := make([]byte, 4)
297
298	if _, err := io.ReadFull(m.stdout, buf); err != nil {
299		return nil, err
300	}
301
302	numResults := binary.LittleEndian.Uint32(buf)
303	if int(numResults) != expectedNumResults {
304		return nil, fmt.Errorf("expected %d results from %q but got %d", expectedNumResults, cmd, numResults)
305	}
306
307	buf = make([]byte, 4*numResults)
308	if _, err := io.ReadFull(m.stdout, buf); err != nil {
309		return nil, err
310	}
311
312	var resultsLength uint64
313	for i := uint32(0); i < numResults; i++ {
314		resultsLength += uint64(binary.LittleEndian.Uint32(buf[4*i:]))
315	}
316
317	if resultsLength > (1 << 30) {
318		return nil, fmt.Errorf("results too large (%d bytes)", resultsLength)
319	}
320
321	results := make([]byte, resultsLength)
322	if _, err := io.ReadFull(m.stdout, results); err != nil {
323		return nil, err
324	}
325
326	ret := make([][]byte, 0, numResults)
327	var offset int
328	for i := uint32(0); i < numResults; i++ {
329		length := binary.LittleEndian.Uint32(buf[4*i:])
330		ret = append(ret, results[offset:offset+int(length)])
331		offset += int(length)
332	}
333
334	return ret, nil
335}
336
337// Config returns a JSON blob that describes the supported primitives. The
338// format of the blob is defined by ACVP. See
339// http://usnistgov.github.io/ACVP/artifacts/draft-fussell-acvp-spec-00.html#rfc.section.11.15.2.1
340func (m *Subprocess) Config() ([]byte, error) {
341	results, err := m.Transact("getConfig", 1)
342	if err != nil {
343		return nil, err
344	}
345	var config []struct {
346		Algorithm string   `json:"algorithm"`
347		Features  []string `json:"features"`
348	}
349	if err := json.Unmarshal(results[0], &config); err != nil {
350		return nil, errors.New("failed to parse config response from wrapper: " + err.Error())
351	}
352	for _, algo := range config {
353		if algo.Algorithm == "acvptool" {
354			for _, feature := range algo.Features {
355				switch feature {
356				case "batch":
357					m.supportsFlush = true
358				}
359			}
360		} else if _, ok := m.primitives[algo.Algorithm]; !ok {
361			return nil, fmt.Errorf("wrapper config advertises support for unknown algorithm %q", algo.Algorithm)
362		}
363	}
364
365	return results[0], nil
366}
367
368// Process runs a set of test vectors and returns the result.
369func (m *Subprocess) Process(algorithm string, vectorSet []byte) (any, error) {
370	prim, ok := m.primitives[algorithm]
371	if !ok {
372		return nil, fmt.Errorf("unknown algorithm %q", algorithm)
373	}
374	ret, err := prim.Process(vectorSet, m)
375	if err != nil {
376		return nil, err
377	}
378	return ret, nil
379}
380
381type primitive interface {
382	Process(vectorSet []byte, t Transactable) (any, error)
383}
384
385func uint32le(n uint32) []byte {
386	var ret [4]byte
387	binary.LittleEndian.PutUint32(ret[:], n)
388	return ret[:]
389}
390