package subprocess import ( "encoding/binary" "encoding/json" "errors" "fmt" "io" "os" "os/exec" ) // Subprocess is a "middle" layer that interacts with a FIPS module via running // a command and speaking a simple protocol over stdin/stdout. type Subprocess struct { cmd *exec.Cmd stdin io.WriteCloser stdout io.ReadCloser primitives map[string]primitive } // New returns a new Subprocess middle layer that runs the given binary. func New(path string) (*Subprocess, error) { cmd := exec.Command(path) cmd.Stderr = os.Stderr stdin, err := cmd.StdinPipe() if err != nil { return nil, err } stdout, err := cmd.StdoutPipe() if err != nil { return nil, err } if err := cmd.Start(); err != nil { return nil, err } return NewWithIO(cmd, stdin, stdout), nil } // NewWithIO returns a new Subprocess middle layer with the given ReadCloser and // WriteCloser. The returned Subprocess will call Wait on the Cmd when closed. func NewWithIO(cmd *exec.Cmd, in io.WriteCloser, out io.ReadCloser) *Subprocess { m := &Subprocess{ cmd: cmd, stdin: in, stdout: out, } m.primitives = map[string]primitive{ "SHA-1": &hashPrimitive{"SHA-1", 20, m}, "SHA2-224": &hashPrimitive{"SHA2-224", 28, m}, "SHA2-256": &hashPrimitive{"SHA2-256", 32, m}, "SHA2-384": &hashPrimitive{"SHA2-384", 48, m}, "SHA2-512": &hashPrimitive{"SHA2-512", 64, m}, "ACVP-AES-ECB": &blockCipher{"AES", 16, false, m}, "ACVP-AES-CBC": &blockCipher{"AES-CBC", 16, true, m}, } return m } // Close signals the child process to exit and waits for it to complete. func (m *Subprocess) Close() { m.stdout.Close() m.stdin.Close() m.cmd.Wait() } // transact performs a single request--response pair with the subprocess. func (m *Subprocess) transact(cmd string, expectedResults int, args ...[]byte) ([][]byte, error) { argLength := len(cmd) for _, arg := range args { argLength += len(arg) } buf := make([]byte, 4*(2+len(args)), 4*(2+len(args))+argLength) binary.LittleEndian.PutUint32(buf, uint32(1+len(args))) binary.LittleEndian.PutUint32(buf[4:], uint32(len(cmd))) for i, arg := range args { binary.LittleEndian.PutUint32(buf[4*(i+2):], uint32(len(arg))) } buf = append(buf, []byte(cmd)...) for _, arg := range args { buf = append(buf, arg...) } if _, err := m.stdin.Write(buf); err != nil { return nil, err } buf = buf[:4] if _, err := io.ReadFull(m.stdout, buf); err != nil { return nil, err } numResults := binary.LittleEndian.Uint32(buf) if int(numResults) != expectedResults { return nil, fmt.Errorf("expected %d results from %q but got %d", expectedResults, cmd, numResults) } buf = make([]byte, 4*numResults) if _, err := io.ReadFull(m.stdout, buf); err != nil { return nil, err } var resultsLength uint64 for i := uint32(0); i < numResults; i++ { resultsLength += uint64(binary.LittleEndian.Uint32(buf[4*i:])) } if resultsLength > (1 << 30) { return nil, fmt.Errorf("results too large (%d bytes)", resultsLength) } results := make([]byte, resultsLength) if _, err := io.ReadFull(m.stdout, results); err != nil { return nil, err } ret := make([][]byte, 0, numResults) var offset int for i := uint32(0); i < numResults; i++ { length := binary.LittleEndian.Uint32(buf[4*i:]) ret = append(ret, results[offset:offset+int(length)]) offset += int(length) } return ret, nil } // Config returns a JSON blob that describes the supported primitives. The // format of the blob is defined by ACVP. See // http://usnistgov.github.io/ACVP/artifacts/draft-fussell-acvp-spec-00.html#rfc.section.11.15.2.1 func (m *Subprocess) Config() ([]byte, error) { results, err := m.transact("getConfig", 1) if err != nil { return nil, err } var config []struct { Algorithm string `json:"algorithm"` } if err := json.Unmarshal(results[0], &config); err != nil { return nil, errors.New("failed to parse config response from wrapper: " + err.Error()) } for _, algo := range config { if _, ok := m.primitives[algo.Algorithm]; !ok { return nil, fmt.Errorf("wrapper config advertises support for unknown algorithm %q", algo.Algorithm) } } return results[0], nil } // Process runs a set of test vectors and returns the result. func (m *Subprocess) Process(algorithm string, vectorSet []byte) ([]byte, error) { prim, ok := m.primitives[algorithm] if !ok { return nil, fmt.Errorf("unknown algorithm %q", algorithm) } ret, err := prim.Process(vectorSet) if err != nil { return nil, err } return json.Marshal(ret) } type primitive interface { Process(vectorSet []byte) (interface{}, error) }