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