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}
34
35// Subprocess is a "middle" layer that interacts with a FIPS module via running
36// a command and speaking a simple protocol over stdin/stdout.
37type Subprocess struct {
38	cmd        *exec.Cmd
39	stdin      io.WriteCloser
40	stdout     io.ReadCloser
41	primitives map[string]primitive
42}
43
44// New returns a new Subprocess middle layer that runs the given binary.
45func New(path string) (*Subprocess, error) {
46	cmd := exec.Command(path)
47	cmd.Stderr = os.Stderr
48	stdin, err := cmd.StdinPipe()
49	if err != nil {
50		return nil, err
51	}
52	stdout, err := cmd.StdoutPipe()
53	if err != nil {
54		return nil, err
55	}
56
57	if err := cmd.Start(); err != nil {
58		return nil, err
59	}
60
61	return NewWithIO(cmd, stdin, stdout), nil
62}
63
64// NewWithIO returns a new Subprocess middle layer with the given ReadCloser and
65// WriteCloser. The returned Subprocess will call Wait on the Cmd when closed.
66func NewWithIO(cmd *exec.Cmd, in io.WriteCloser, out io.ReadCloser) *Subprocess {
67	m := &Subprocess{
68		cmd:    cmd,
69		stdin:  in,
70		stdout: out,
71	}
72
73	m.primitives = map[string]primitive{
74		"SHA-1":          &hashPrimitive{"SHA-1", 20},
75		"SHA2-224":       &hashPrimitive{"SHA2-224", 28},
76		"SHA2-256":       &hashPrimitive{"SHA2-256", 32},
77		"SHA2-384":       &hashPrimitive{"SHA2-384", 48},
78		"SHA2-512":       &hashPrimitive{"SHA2-512", 64},
79		"SHA2-512/256":   &hashPrimitive{"SHA2-512/256", 32},
80		"ACVP-AES-ECB":   &blockCipher{"AES", 16, 2, true, false, iterateAES},
81		"ACVP-AES-CBC":   &blockCipher{"AES-CBC", 16, 2, true, true, iterateAESCBC},
82		"ACVP-AES-CTR":   &blockCipher{"AES-CTR", 16, 1, false, true, nil},
83		"ACVP-AES-XTS":   &xts{},
84		"ACVP-TDES-ECB":  &blockCipher{"3DES-ECB", 8, 3, true, false, iterate3DES},
85		"ACVP-TDES-CBC":  &blockCipher{"3DES-CBC", 8, 3, true, true, iterate3DESCBC},
86		"ACVP-AES-GCM":   &aead{"AES-GCM", false},
87		"ACVP-AES-CCM":   &aead{"AES-CCM", true},
88		"ACVP-AES-KW":    &aead{"AES-KW", false},
89		"ACVP-AES-KWP":   &aead{"AES-KWP", false},
90		"HMAC-SHA-1":     &hmacPrimitive{"HMAC-SHA-1", 20},
91		"HMAC-SHA2-224":  &hmacPrimitive{"HMAC-SHA2-224", 28},
92		"HMAC-SHA2-256":  &hmacPrimitive{"HMAC-SHA2-256", 32},
93		"HMAC-SHA2-384":  &hmacPrimitive{"HMAC-SHA2-384", 48},
94		"HMAC-SHA2-512":  &hmacPrimitive{"HMAC-SHA2-512", 64},
95		"ctrDRBG":        &drbg{"ctrDRBG", map[string]bool{"AES-128": true, "AES-192": true, "AES-256": true}},
96		"hmacDRBG":       &drbg{"hmacDRBG", map[string]bool{"SHA-1": true, "SHA2-224": true, "SHA2-256": true, "SHA2-384": true, "SHA2-512": true}},
97		"KDF":            &kdfPrimitive{},
98		"CMAC-AES":       &keyedMACPrimitive{"CMAC-AES"},
99		"RSA":            &rsa{},
100		"kdf-components": &tlsKDF{},
101		"KAS-ECC-SSC":    &kas{},
102		"KAS-FFC-SSC":    &kasDH{},
103	}
104	m.primitives["ECDSA"] = &ecdsa{"ECDSA", map[string]bool{"P-224": true, "P-256": true, "P-384": true, "P-521": true}, m.primitives}
105
106	return m
107}
108
109// Close signals the child process to exit and waits for it to complete.
110func (m *Subprocess) Close() {
111	m.stdout.Close()
112	m.stdin.Close()
113	m.cmd.Wait()
114}
115
116// Transact performs a single request--response pair with the subprocess.
117func (m *Subprocess) Transact(cmd string, expectedResults int, args ...[]byte) ([][]byte, error) {
118	argLength := len(cmd)
119	for _, arg := range args {
120		argLength += len(arg)
121	}
122
123	buf := make([]byte, 4*(2+len(args)), 4*(2+len(args))+argLength)
124	binary.LittleEndian.PutUint32(buf, uint32(1+len(args)))
125	binary.LittleEndian.PutUint32(buf[4:], uint32(len(cmd)))
126	for i, arg := range args {
127		binary.LittleEndian.PutUint32(buf[4*(i+2):], uint32(len(arg)))
128	}
129	buf = append(buf, []byte(cmd)...)
130	for _, arg := range args {
131		buf = append(buf, arg...)
132	}
133
134	if _, err := m.stdin.Write(buf); err != nil {
135		return nil, err
136	}
137
138	buf = buf[:4]
139	if _, err := io.ReadFull(m.stdout, buf); err != nil {
140		return nil, err
141	}
142
143	numResults := binary.LittleEndian.Uint32(buf)
144	if int(numResults) != expectedResults {
145		return nil, fmt.Errorf("expected %d results from %q but got %d", expectedResults, cmd, numResults)
146	}
147
148	buf = make([]byte, 4*numResults)
149	if _, err := io.ReadFull(m.stdout, buf); err != nil {
150		return nil, err
151	}
152
153	var resultsLength uint64
154	for i := uint32(0); i < numResults; i++ {
155		resultsLength += uint64(binary.LittleEndian.Uint32(buf[4*i:]))
156	}
157
158	if resultsLength > (1 << 30) {
159		return nil, fmt.Errorf("results too large (%d bytes)", resultsLength)
160	}
161
162	results := make([]byte, resultsLength)
163	if _, err := io.ReadFull(m.stdout, results); err != nil {
164		return nil, err
165	}
166
167	ret := make([][]byte, 0, numResults)
168	var offset int
169	for i := uint32(0); i < numResults; i++ {
170		length := binary.LittleEndian.Uint32(buf[4*i:])
171		ret = append(ret, results[offset:offset+int(length)])
172		offset += int(length)
173	}
174
175	return ret, nil
176}
177
178// Config returns a JSON blob that describes the supported primitives. The
179// format of the blob is defined by ACVP. See
180// http://usnistgov.github.io/ACVP/artifacts/draft-fussell-acvp-spec-00.html#rfc.section.11.15.2.1
181func (m *Subprocess) Config() ([]byte, error) {
182	results, err := m.Transact("getConfig", 1)
183	if err != nil {
184		return nil, err
185	}
186	var config []struct {
187		Algorithm string `json:"algorithm"`
188	}
189	if err := json.Unmarshal(results[0], &config); err != nil {
190		return nil, errors.New("failed to parse config response from wrapper: " + err.Error())
191	}
192	for _, algo := range config {
193		if _, ok := m.primitives[algo.Algorithm]; !ok {
194			return nil, fmt.Errorf("wrapper config advertises support for unknown algorithm %q", algo.Algorithm)
195		}
196	}
197	return results[0], nil
198}
199
200// Process runs a set of test vectors and returns the result.
201func (m *Subprocess) Process(algorithm string, vectorSet []byte) (interface{}, error) {
202	prim, ok := m.primitives[algorithm]
203	if !ok {
204		return nil, fmt.Errorf("unknown algorithm %q", algorithm)
205	}
206	ret, err := prim.Process(vectorSet, m)
207	if err != nil {
208		return nil, err
209	}
210	return ret, nil
211}
212
213type primitive interface {
214	Process(vectorSet []byte, t Transactable) (interface{}, error)
215}
216
217func uint32le(n uint32) []byte {
218	var ret [4]byte
219	binary.LittleEndian.PutUint32(ret[:], n)
220	return ret[:]
221}
222