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