blob: 404569f50411f767d617bf56bf1f8aee2cb19f7c [file] [log] [blame]
Pete Bentley0c61efe2019-08-13 09:32:23 +01001package 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 m := &Subprocess{
40 cmd: cmd,
41 stdin: stdin,
42 stdout: stdout,
43 }
44
45 m.primitives = map[string]primitive{
46 "SHA-1": &hashPrimitive{"SHA-1", 20, m},
47 "SHA2-224": &hashPrimitive{"SHA2-224", 28, m},
48 "SHA2-256": &hashPrimitive{"SHA2-256", 32, m},
49 "SHA2-384": &hashPrimitive{"SHA2-384", 48, m},
50 "SHA2-512": &hashPrimitive{"SHA2-512", 64, m},
51 "ACVP-AES-ECB": &blockCipher{"AES", 16, false, m},
52 "ACVP-AES-CBC": &blockCipher{"AES-CBC", 16, true, m},
53 }
54
55 return m, nil
56}
57
58// Close signals the child process to exit and waits for it to complete.
59func (m *Subprocess) Close() {
60 m.stdout.Close()
61 m.stdin.Close()
62 m.cmd.Wait()
63}
64
65// transact performs a single request--response pair with the subprocess.
66func (m *Subprocess) transact(cmd string, expectedResults int, args ...[]byte) ([][]byte, error) {
67 argLength := len(cmd)
68 for _, arg := range args {
69 argLength += len(arg)
70 }
71
72 buf := make([]byte, 4*(2+len(args)), 4*(2+len(args))+argLength)
73 binary.LittleEndian.PutUint32(buf, uint32(1+len(args)))
74 binary.LittleEndian.PutUint32(buf[4:], uint32(len(cmd)))
75 for i, arg := range args {
76 binary.LittleEndian.PutUint32(buf[4*(i+2):], uint32(len(arg)))
77 }
78 buf = append(buf, []byte(cmd)...)
79 for _, arg := range args {
80 buf = append(buf, arg...)
81 }
82
83 if _, err := m.stdin.Write(buf); err != nil {
84 return nil, err
85 }
86
87 buf = buf[:4]
88 if _, err := io.ReadFull(m.stdout, buf); err != nil {
89 return nil, err
90 }
91
92 numResults := binary.LittleEndian.Uint32(buf)
93 if int(numResults) != expectedResults {
94 return nil, fmt.Errorf("expected %d results from %q but got %d", expectedResults, cmd, numResults)
95 }
96
97 buf = make([]byte, 4*numResults)
98 if _, err := io.ReadFull(m.stdout, buf); err != nil {
99 return nil, err
100 }
101
102 var resultsLength uint64
103 for i := uint32(0); i < numResults; i++ {
104 resultsLength += uint64(binary.LittleEndian.Uint32(buf[4*i:]))
105 }
106
107 if resultsLength > (1 << 30) {
108 return nil, fmt.Errorf("results too large (%d bytes)", resultsLength)
109 }
110
111 results := make([]byte, resultsLength)
112 if _, err := io.ReadFull(m.stdout, results); err != nil {
113 return nil, err
114 }
115
116 ret := make([][]byte, 0, numResults)
117 var offset int
118 for i := uint32(0); i < numResults; i++ {
119 length := binary.LittleEndian.Uint32(buf[4*i:])
120 ret = append(ret, results[offset:offset+int(length)])
121 offset += int(length)
122 }
123
124 return ret, nil
125}
126
127// Config returns a JSON blob that describes the supported primitives. The
128// format of the blob is defined by ACVP. See
129// http://usnistgov.github.io/ACVP/artifacts/draft-fussell-acvp-spec-00.html#rfc.section.11.15.2.1
130func (m *Subprocess) Config() ([]byte, error) {
131 results, err := m.transact("getConfig", 1)
132 if err != nil {
133 return nil, err
134 }
135 var config []struct {
136 Algorithm string `json:"algorithm"`
137 }
138 if err := json.Unmarshal(results[0], &config); err != nil {
139 return nil, errors.New("failed to parse config response from wrapper: " + err.Error())
140 }
141 for _, algo := range config {
142 if _, ok := m.primitives[algo.Algorithm]; !ok {
143 return nil, fmt.Errorf("wrapper config advertises support for unknown algorithm %q", algo.Algorithm)
144 }
145 }
146 return results[0], nil
147}
148
149// Process runs a set of test vectors and returns the result.
150func (m *Subprocess) Process(algorithm string, vectorSet []byte) ([]byte, error) {
151 prim, ok := m.primitives[algorithm]
152 if !ok {
153 return nil, fmt.Errorf("unknown algorithm %q", algorithm)
154 }
155 ret, err := prim.Process(vectorSet)
156 if err != nil {
157 return nil, err
158 }
159 return json.Marshal(ret)
160}
161
162type primitive interface {
163 Process(vectorSet []byte) (interface{}, error)
164}