• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1package subprocess
2
3import (
4	"encoding/binary"
5	"encoding/json"
6	"errors"
7	"fmt"
8	"io"
9	"os"
10	"os/exec"
11)
12
13// Subprocess is a "middle" layer that interacts with a FIPS module via running
14// a command and speaking a simple protocol over stdin/stdout.
15type Subprocess struct {
16	cmd        *exec.Cmd
17	stdin      io.WriteCloser
18	stdout     io.ReadCloser
19	primitives map[string]primitive
20}
21
22// New returns a new Subprocess middle layer that runs the given binary.
23func New(path string) (*Subprocess, error) {
24	cmd := exec.Command(path)
25	cmd.Stderr = os.Stderr
26	stdin, err := cmd.StdinPipe()
27	if err != nil {
28		return nil, err
29	}
30	stdout, err := cmd.StdoutPipe()
31	if err != nil {
32		return nil, err
33	}
34
35	if err := cmd.Start(); err != nil {
36		return nil, err
37	}
38
39	return NewWithIO(cmd, stdin, stdout), nil
40}
41
42// NewWithIO returns a new Subprocess middle layer with the given ReadCloser and
43// WriteCloser. The returned Subprocess will call Wait on the Cmd when closed.
44func NewWithIO(cmd *exec.Cmd, in io.WriteCloser, out io.ReadCloser) *Subprocess {
45	m := &Subprocess{
46		cmd:	cmd,
47		stdin:  in,
48		stdout: out,
49	}
50
51	m.primitives = map[string]primitive{
52		"SHA-1":        &hashPrimitive{"SHA-1", 20, m},
53		"SHA2-224":     &hashPrimitive{"SHA2-224", 28, m},
54		"SHA2-256":     &hashPrimitive{"SHA2-256", 32, m},
55		"SHA2-384":     &hashPrimitive{"SHA2-384", 48, m},
56		"SHA2-512":     &hashPrimitive{"SHA2-512", 64, m},
57		"ACVP-AES-ECB": &blockCipher{"AES", 16, false, m},
58		"ACVP-AES-CBC": &blockCipher{"AES-CBC", 16, true, m},
59	}
60
61	return m
62}
63
64// Close signals the child process to exit and waits for it to complete.
65func (m *Subprocess) Close() {
66	m.stdout.Close()
67	m.stdin.Close()
68	m.cmd.Wait()
69}
70
71// transact performs a single request--response pair with the subprocess.
72func (m *Subprocess) transact(cmd string, expectedResults int, args ...[]byte) ([][]byte, error) {
73	argLength := len(cmd)
74	for _, arg := range args {
75		argLength += len(arg)
76	}
77
78	buf := make([]byte, 4*(2+len(args)), 4*(2+len(args))+argLength)
79	binary.LittleEndian.PutUint32(buf, uint32(1+len(args)))
80	binary.LittleEndian.PutUint32(buf[4:], uint32(len(cmd)))
81	for i, arg := range args {
82		binary.LittleEndian.PutUint32(buf[4*(i+2):], uint32(len(arg)))
83	}
84	buf = append(buf, []byte(cmd)...)
85	for _, arg := range args {
86		buf = append(buf, arg...)
87	}
88
89	if _, err := m.stdin.Write(buf); err != nil {
90		return nil, err
91	}
92
93	buf = buf[:4]
94	if _, err := io.ReadFull(m.stdout, buf); err != nil {
95		return nil, err
96	}
97
98	numResults := binary.LittleEndian.Uint32(buf)
99	if int(numResults) != expectedResults {
100		return nil, fmt.Errorf("expected %d results from %q but got %d", expectedResults, cmd, numResults)
101	}
102
103	buf = make([]byte, 4*numResults)
104	if _, err := io.ReadFull(m.stdout, buf); err != nil {
105		return nil, err
106	}
107
108	var resultsLength uint64
109	for i := uint32(0); i < numResults; i++ {
110		resultsLength += uint64(binary.LittleEndian.Uint32(buf[4*i:]))
111	}
112
113	if resultsLength > (1 << 30) {
114		return nil, fmt.Errorf("results too large (%d bytes)", resultsLength)
115	}
116
117	results := make([]byte, resultsLength)
118	if _, err := io.ReadFull(m.stdout, results); err != nil {
119		return nil, err
120	}
121
122	ret := make([][]byte, 0, numResults)
123	var offset int
124	for i := uint32(0); i < numResults; i++ {
125		length := binary.LittleEndian.Uint32(buf[4*i:])
126		ret = append(ret, results[offset:offset+int(length)])
127		offset += int(length)
128	}
129
130	return ret, nil
131}
132
133// Config returns a JSON blob that describes the supported primitives. The
134// format of the blob is defined by ACVP. See
135// http://usnistgov.github.io/ACVP/artifacts/draft-fussell-acvp-spec-00.html#rfc.section.11.15.2.1
136func (m *Subprocess) Config() ([]byte, error) {
137	results, err := m.transact("getConfig", 1)
138	if err != nil {
139		return nil, err
140	}
141	var config []struct {
142		Algorithm string `json:"algorithm"`
143	}
144	if err := json.Unmarshal(results[0], &config); err != nil {
145		return nil, errors.New("failed to parse config response from wrapper: " + err.Error())
146	}
147	for _, algo := range config {
148		if _, ok := m.primitives[algo.Algorithm]; !ok {
149			return nil, fmt.Errorf("wrapper config advertises support for unknown algorithm %q", algo.Algorithm)
150		}
151	}
152	return results[0], nil
153}
154
155// Process runs a set of test vectors and returns the result.
156func (m *Subprocess) Process(algorithm string, vectorSet []byte) ([]byte, error) {
157	prim, ok := m.primitives[algorithm]
158	if !ok {
159		return nil, fmt.Errorf("unknown algorithm %q", algorithm)
160	}
161	ret, err := prim.Process(vectorSet)
162	if err != nil {
163		return nil, err
164	}
165	return json.Marshal(ret)
166}
167
168type primitive interface {
169	Process(vectorSet []byte) (interface{}, error)
170}
171