• 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}
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-CBC-CS3":  &blockCipher{"AES-CBC-CS3", 16, 1, false, true, iterateAESCBC},
83		"ACVP-AES-CTR":      &blockCipher{"AES-CTR", 16, 1, false, true, nil},
84		"ACVP-AES-XTS":      &xts{},
85		"ACVP-AES-GCM":      &aead{"AES-GCM", false},
86		"ACVP-AES-GMAC":     &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		"HMAC-SHA2-512/256": &hmacPrimitive{"HMAC-SHA2-512/256", 32},
96		"ctrDRBG":           &drbg{"ctrDRBG", map[string]bool{"AES-128": true, "AES-192": true, "AES-256": true}},
97		"hmacDRBG":          &drbg{"hmacDRBG", map[string]bool{"SHA-1": true, "SHA2-224": true, "SHA2-256": true, "SHA2-384": true, "SHA2-512": true}},
98		"KDF":               &kdfPrimitive{},
99		"KAS-KDF":           &hkdf{},
100		"CMAC-AES":          &keyedMACPrimitive{"CMAC-AES"},
101		"RSA":               &rsa{},
102		"kdf-components":    &tlsKDF{},
103		"KAS-ECC-SSC":       &kas{},
104		"KAS-FFC-SSC":       &kasDH{},
105	}
106	m.primitives["ECDSA"] = &ecdsa{"ECDSA", map[string]bool{"P-224": true, "P-256": true, "P-384": true, "P-521": true}, m.primitives}
107
108	return m
109}
110
111// Close signals the child process to exit and waits for it to complete.
112func (m *Subprocess) Close() {
113	m.stdout.Close()
114	m.stdin.Close()
115	m.cmd.Wait()
116}
117
118// Transact performs a single request--response pair with the subprocess.
119func (m *Subprocess) Transact(cmd string, expectedResults int, args ...[]byte) ([][]byte, error) {
120	argLength := len(cmd)
121	for _, arg := range args {
122		argLength += len(arg)
123	}
124
125	buf := make([]byte, 4*(2+len(args)), 4*(2+len(args))+argLength)
126	binary.LittleEndian.PutUint32(buf, uint32(1+len(args)))
127	binary.LittleEndian.PutUint32(buf[4:], uint32(len(cmd)))
128	for i, arg := range args {
129		binary.LittleEndian.PutUint32(buf[4*(i+2):], uint32(len(arg)))
130	}
131	buf = append(buf, []byte(cmd)...)
132	for _, arg := range args {
133		buf = append(buf, arg...)
134	}
135
136	if _, err := m.stdin.Write(buf); err != nil {
137		return nil, err
138	}
139
140	buf = buf[:4]
141	if _, err := io.ReadFull(m.stdout, buf); err != nil {
142		return nil, err
143	}
144
145	numResults := binary.LittleEndian.Uint32(buf)
146	if int(numResults) != expectedResults {
147		return nil, fmt.Errorf("expected %d results from %q but got %d", expectedResults, cmd, numResults)
148	}
149
150	buf = make([]byte, 4*numResults)
151	if _, err := io.ReadFull(m.stdout, buf); err != nil {
152		return nil, err
153	}
154
155	var resultsLength uint64
156	for i := uint32(0); i < numResults; i++ {
157		resultsLength += uint64(binary.LittleEndian.Uint32(buf[4*i:]))
158	}
159
160	if resultsLength > (1 << 30) {
161		return nil, fmt.Errorf("results too large (%d bytes)", resultsLength)
162	}
163
164	results := make([]byte, resultsLength)
165	if _, err := io.ReadFull(m.stdout, results); err != nil {
166		return nil, err
167	}
168
169	ret := make([][]byte, 0, numResults)
170	var offset int
171	for i := uint32(0); i < numResults; i++ {
172		length := binary.LittleEndian.Uint32(buf[4*i:])
173		ret = append(ret, results[offset:offset+int(length)])
174		offset += int(length)
175	}
176
177	return ret, nil
178}
179
180// Config returns a JSON blob that describes the supported primitives. The
181// format of the blob is defined by ACVP. See
182// http://usnistgov.github.io/ACVP/artifacts/draft-fussell-acvp-spec-00.html#rfc.section.11.15.2.1
183func (m *Subprocess) Config() ([]byte, error) {
184	results, err := m.Transact("getConfig", 1)
185	if err != nil {
186		return nil, err
187	}
188	var config []struct {
189		Algorithm string `json:"algorithm"`
190	}
191	if err := json.Unmarshal(results[0], &config); err != nil {
192		return nil, errors.New("failed to parse config response from wrapper: " + err.Error())
193	}
194	for _, algo := range config {
195		if _, ok := m.primitives[algo.Algorithm]; !ok {
196			return nil, fmt.Errorf("wrapper config advertises support for unknown algorithm %q", algo.Algorithm)
197		}
198	}
199	return results[0], nil
200}
201
202// Process runs a set of test vectors and returns the result.
203func (m *Subprocess) Process(algorithm string, vectorSet []byte) (interface{}, error) {
204	prim, ok := m.primitives[algorithm]
205	if !ok {
206		return nil, fmt.Errorf("unknown algorithm %q", algorithm)
207	}
208	ret, err := prim.Process(vectorSet, m)
209	if err != nil {
210		return nil, err
211	}
212	return ret, nil
213}
214
215type primitive interface {
216	Process(vectorSet []byte, t Transactable) (interface{}, error)
217}
218
219func uint32le(n uint32) []byte {
220	var ret [4]byte
221	binary.LittleEndian.PutUint32(ret[:], n)
222	return ret[:]
223}
224