blob: f20707f175269190fc6ec7eeade37e85707d48ee [file] [log] [blame]
Adam Langleyd709b0d2019-11-05 11:37:22 -08001// 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
Adam Langley9a798eb2020-04-15 10:36:52 -070015// Package subprocess contains functionality to talk to a modulewrapper for
16// testing of various algorithm implementations.
Adam Langleyb7f0c1b2019-07-09 18:02:14 -070017package subprocess
18
19import (
20 "encoding/binary"
21 "encoding/json"
22 "errors"
23 "fmt"
24 "io"
25 "os"
26 "os/exec"
27)
28
Adam Langley9a798eb2020-04-15 10:36:52 -070029// 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)
Adam Langleye24491a2022-11-25 21:23:03 +000033 TransactAsync(cmd string, expectedResults int, args [][]byte, callback func([][]byte) error)
34 Barrier(callback func()) error
35 Flush() error
Adam Langley9a798eb2020-04-15 10:36:52 -070036}
37
Adam Langleyb7f0c1b2019-07-09 18:02:14 -070038// Subprocess is a "middle" layer that interacts with a FIPS module via running
39// a command and speaking a simple protocol over stdin/stdout.
40type Subprocess struct {
41 cmd *exec.Cmd
42 stdin io.WriteCloser
43 stdout io.ReadCloser
44 primitives map[string]primitive
Adam Langleye24491a2022-11-25 21:23:03 +000045 // supportsFlush is true if the modulewrapper indicated that it wants to receive flush commands.
46 supportsFlush bool
47 // pendingReads is a queue of expected responses. `readerRoutine` reads each response and calls the callback in the matching pendingRead.
48 pendingReads chan pendingRead
49 // readerFinished is a channel that is closed if `readerRoutine` has finished (e.g. because of a read error).
50 readerFinished chan struct{}
Adam Langleye24491a2022-11-25 21:23:03 +000051}
52
53// pendingRead represents an expected response from the modulewrapper.
54type pendingRead struct {
55 // barrierCallback is called as soon as this pendingRead is the next in the queue, before any read from the modulewrapper.
56 barrierCallback func()
57
58 // callback is called with the result from the modulewrapper. If this is nil then no read is performed.
59 callback func(result [][]byte) error
60 // cmd is the command that requested this read for logging purposes.
61 cmd string
62 expectedNumResults int
Adam Langleyb7f0c1b2019-07-09 18:02:14 -070063}
64
65// New returns a new Subprocess middle layer that runs the given binary.
66func New(path string) (*Subprocess, error) {
67 cmd := exec.Command(path)
68 cmd.Stderr = os.Stderr
69 stdin, err := cmd.StdinPipe()
70 if err != nil {
71 return nil, err
72 }
73 stdout, err := cmd.StdoutPipe()
74 if err != nil {
75 return nil, err
76 }
77
78 if err := cmd.Start(); err != nil {
79 return nil, err
80 }
81
Gurleen Grewal9747a532019-08-20 17:40:30 -070082 return NewWithIO(cmd, stdin, stdout), nil
83}
84
Adam Langleye24491a2022-11-25 21:23:03 +000085// maxPending is the maximum number of requests that can be in the pipeline.
86const maxPending = 4096
87
Gurleen Grewal9747a532019-08-20 17:40:30 -070088// NewWithIO returns a new Subprocess middle layer with the given ReadCloser and
89// WriteCloser. The returned Subprocess will call Wait on the Cmd when closed.
90func NewWithIO(cmd *exec.Cmd, in io.WriteCloser, out io.ReadCloser) *Subprocess {
Adam Langleyb7f0c1b2019-07-09 18:02:14 -070091 m := &Subprocess{
Adam Langleye24491a2022-11-25 21:23:03 +000092 cmd: cmd,
93 stdin: in,
94 stdout: out,
95 pendingReads: make(chan pendingRead, maxPending),
96 readerFinished: make(chan struct{}),
Adam Langleyb7f0c1b2019-07-09 18:02:14 -070097 }
98
99 m.primitives = map[string]primitive{
Adam Langley097ffe12022-05-04 15:27:12 -0700100 "SHA-1": &hashPrimitive{"SHA-1", 20},
101 "SHA2-224": &hashPrimitive{"SHA2-224", 28},
102 "SHA2-256": &hashPrimitive{"SHA2-256", 32},
103 "SHA2-384": &hashPrimitive{"SHA2-384", 48},
104 "SHA2-512": &hashPrimitive{"SHA2-512", 64},
Daniel McCarneyfc03aae2024-10-09 15:39:46 -0400105 "SHA2-512/224": &hashPrimitive{"SHA2-512/224", 28},
Adam Langley097ffe12022-05-04 15:27:12 -0700106 "SHA2-512/256": &hashPrimitive{"SHA2-512/256", 32},
Sureshbhai Dave7c232382022-06-07 13:29:07 +0530107 "SHA3-224": &hashPrimitive{"SHA3-224", 28},
108 "SHA3-256": &hashPrimitive{"SHA3-256", 32},
109 "SHA3-384": &hashPrimitive{"SHA3-384", 48},
110 "SHA3-512": &hashPrimitive{"SHA3-512", 64},
Adam Langley097ffe12022-05-04 15:27:12 -0700111 "ACVP-AES-ECB": &blockCipher{"AES", 16, 2, true, false, iterateAES},
112 "ACVP-AES-CBC": &blockCipher{"AES-CBC", 16, 2, true, true, iterateAESCBC},
113 "ACVP-AES-CBC-CS3": &blockCipher{"AES-CBC-CS3", 16, 1, false, true, iterateAESCBC},
114 "ACVP-AES-CTR": &blockCipher{"AES-CTR", 16, 1, false, true, nil},
Bob Beckb9512432022-07-19 14:47:58 -0600115 "ACVP-TDES-ECB": &blockCipher{"3DES-ECB", 8, 3, true, false, iterate3DES},
116 "ACVP-TDES-CBC": &blockCipher{"3DES-CBC", 8, 3, true, true, iterate3DESCBC},
Adam Langley097ffe12022-05-04 15:27:12 -0700117 "ACVP-AES-XTS": &xts{},
118 "ACVP-AES-GCM": &aead{"AES-GCM", false},
119 "ACVP-AES-GMAC": &aead{"AES-GCM", false},
120 "ACVP-AES-CCM": &aead{"AES-CCM", true},
121 "ACVP-AES-KW": &aead{"AES-KW", false},
122 "ACVP-AES-KWP": &aead{"AES-KWP", false},
123 "HMAC-SHA-1": &hmacPrimitive{"HMAC-SHA-1", 20},
124 "HMAC-SHA2-224": &hmacPrimitive{"HMAC-SHA2-224", 28},
125 "HMAC-SHA2-256": &hmacPrimitive{"HMAC-SHA2-256", 32},
126 "HMAC-SHA2-384": &hmacPrimitive{"HMAC-SHA2-384", 48},
127 "HMAC-SHA2-512": &hmacPrimitive{"HMAC-SHA2-512", 64},
Daniel McCarneyfc03aae2024-10-09 15:39:46 -0400128 "HMAC-SHA2-512/224": &hmacPrimitive{"HMAC-SHA2-512/224", 28},
Adam Langley097ffe12022-05-04 15:27:12 -0700129 "HMAC-SHA2-512/256": &hmacPrimitive{"HMAC-SHA2-512/256", 32},
Sureshbhai Dave7c232382022-06-07 13:29:07 +0530130 "HMAC-SHA3-224": &hmacPrimitive{"HMAC-SHA3-224", 28},
131 "HMAC-SHA3-256": &hmacPrimitive{"HMAC-SHA3-256", 32},
132 "HMAC-SHA3-384": &hmacPrimitive{"HMAC-SHA3-384", 48},
133 "HMAC-SHA3-512": &hmacPrimitive{"HMAC-SHA3-512", 64},
Adam Langley097ffe12022-05-04 15:27:12 -0700134 "ctrDRBG": &drbg{"ctrDRBG", map[string]bool{"AES-128": true, "AES-192": true, "AES-256": true}},
135 "hmacDRBG": &drbg{"hmacDRBG", map[string]bool{"SHA-1": true, "SHA2-224": true, "SHA2-256": true, "SHA2-384": true, "SHA2-512": true}},
136 "KDF": &kdfPrimitive{},
Adam Langley726585c2022-12-06 18:40:08 +0000137 "KDA": &hkdf{},
Adam Langley6e1e3672023-04-15 00:04:12 +0000138 "TLS-v1.2": &tlsKDF{},
Adam Langley480344d2023-04-13 02:09:12 +0000139 "TLS-v1.3": &tls13{},
Adam Langley097ffe12022-05-04 15:27:12 -0700140 "CMAC-AES": &keyedMACPrimitive{"CMAC-AES"},
141 "RSA": &rsa{},
Adam Langley097ffe12022-05-04 15:27:12 -0700142 "KAS-ECC-SSC": &kas{},
143 "KAS-FFC-SSC": &kasDH{},
Daniel McCarney2587c492024-10-10 15:04:48 -0400144 "PBKDF": &pbkdf{},
Adam Langleyb7f0c1b2019-07-09 18:02:14 -0700145 }
Adam Langley9a798eb2020-04-15 10:36:52 -0700146 m.primitives["ECDSA"] = &ecdsa{"ECDSA", map[string]bool{"P-224": true, "P-256": true, "P-384": true, "P-521": true}, m.primitives}
Daniel McCarneyed322d72024-10-18 18:45:42 -0400147 m.primitives["EDDSA"] = &eddsa{"EDDSA", map[string]bool{"ED-25519": true}}
Adam Langleyb7f0c1b2019-07-09 18:02:14 -0700148
Adam Langleye24491a2022-11-25 21:23:03 +0000149 go m.readerRoutine()
Gurleen Grewal9747a532019-08-20 17:40:30 -0700150 return m
Adam Langleyb7f0c1b2019-07-09 18:02:14 -0700151}
152
153// Close signals the child process to exit and waits for it to complete.
154func (m *Subprocess) Close() {
155 m.stdout.Close()
156 m.stdin.Close()
157 m.cmd.Wait()
Adam Langleycf3851c2023-05-23 00:07:03 +0000158 close(m.pendingReads)
Adam Langleye24491a2022-11-25 21:23:03 +0000159 <-m.readerFinished
Adam Langleyb7f0c1b2019-07-09 18:02:14 -0700160}
161
Adam Langleye24491a2022-11-25 21:23:03 +0000162func (m *Subprocess) flush() error {
163 if !m.supportsFlush {
164 return nil
165 }
166
167 const cmd = "flush"
168 buf := make([]byte, 8, 8+len(cmd))
169 binary.LittleEndian.PutUint32(buf, 1)
170 binary.LittleEndian.PutUint32(buf[4:], uint32(len(cmd)))
171 buf = append(buf, []byte(cmd)...)
172
173 if _, err := m.stdin.Write(buf); err != nil {
174 return err
175 }
176 return nil
177}
178
179func (m *Subprocess) enqueueRead(pending pendingRead) error {
180 select {
181 case <-m.readerFinished:
Adam Langleycf3851c2023-05-23 00:07:03 +0000182 panic("attempted to enqueue request after the reader failed")
Adam Langleye24491a2022-11-25 21:23:03 +0000183 default:
184 }
185
186 select {
187 case m.pendingReads <- pending:
188 break
189 default:
190 // `pendingReads` is full. Ensure that the modulewrapper will process
191 // some outstanding requests to free up space in the queue.
192 if err := m.flush(); err != nil {
193 return err
194 }
195 m.pendingReads <- pending
196 }
197
198 return nil
199}
200
201// TransactAsync performs a single request--response pair with the subprocess.
202// The callback will run at some future point, in a separate goroutine. All
203// callbacks will, however, be run in the order that TransactAsync was called.
204// Use Flush to wait for all outstanding callbacks.
205func (m *Subprocess) TransactAsync(cmd string, expectedNumResults int, args [][]byte, callback func(result [][]byte) error) {
206 if err := m.enqueueRead(pendingRead{nil, callback, cmd, expectedNumResults}); err != nil {
207 panic(err)
208 }
209
Adam Langleyb7f0c1b2019-07-09 18:02:14 -0700210 argLength := len(cmd)
211 for _, arg := range args {
212 argLength += len(arg)
213 }
214
215 buf := make([]byte, 4*(2+len(args)), 4*(2+len(args))+argLength)
216 binary.LittleEndian.PutUint32(buf, uint32(1+len(args)))
217 binary.LittleEndian.PutUint32(buf[4:], uint32(len(cmd)))
218 for i, arg := range args {
219 binary.LittleEndian.PutUint32(buf[4*(i+2):], uint32(len(arg)))
220 }
221 buf = append(buf, []byte(cmd)...)
222 for _, arg := range args {
223 buf = append(buf, arg...)
224 }
225
226 if _, err := m.stdin.Write(buf); err != nil {
Adam Langleye24491a2022-11-25 21:23:03 +0000227 panic(err)
228 }
229}
230
231// Flush tells the subprocess to complete all outstanding requests and waits
232// for all outstanding TransactAsync callbacks to complete.
233func (m *Subprocess) Flush() error {
234 if m.supportsFlush {
235 m.flush()
236 }
237
238 done := make(chan struct{})
239 if err := m.enqueueRead(pendingRead{barrierCallback: func() {
240 close(done)
241 }}); err != nil {
242 return err
243 }
244
245 <-done
246 return nil
247}
248
249// Barrier runs callback after all outstanding TransactAsync callbacks have
250// been run.
251func (m *Subprocess) Barrier(callback func()) error {
252 return m.enqueueRead(pendingRead{barrierCallback: callback})
253}
254
255func (m *Subprocess) Transact(cmd string, expectedNumResults int, args ...[]byte) ([][]byte, error) {
256 done := make(chan struct{})
257 var result [][]byte
258 m.TransactAsync(cmd, expectedNumResults, args, func(r [][]byte) error {
259 result = r
260 close(done)
261 return nil
262 })
263
264 if err := m.flush(); err != nil {
Adam Langleyb7f0c1b2019-07-09 18:02:14 -0700265 return nil, err
266 }
267
Adam Langleye24491a2022-11-25 21:23:03 +0000268 select {
269 case <-done:
270 return result, nil
271 case <-m.readerFinished:
Adam Langleycf3851c2023-05-23 00:07:03 +0000272 panic("was still waiting for a result when the reader finished")
Adam Langleye24491a2022-11-25 21:23:03 +0000273 }
274}
275
276func (m *Subprocess) readerRoutine() {
277 defer close(m.readerFinished)
278
279 for pendingRead := range m.pendingReads {
280 if pendingRead.barrierCallback != nil {
281 pendingRead.barrierCallback()
282 }
283
284 if pendingRead.callback == nil {
285 continue
286 }
287
288 result, err := m.readResult(pendingRead.cmd, pendingRead.expectedNumResults)
289 if err != nil {
Adam Langleycf3851c2023-05-23 00:07:03 +0000290 panic(fmt.Errorf("failed to read from subprocess: %w", err))
Adam Langleye24491a2022-11-25 21:23:03 +0000291 }
292
293 if err := pendingRead.callback(result); err != nil {
Adam Langleycf3851c2023-05-23 00:07:03 +0000294 panic(fmt.Errorf("result from subprocess was rejected: %w", err))
Adam Langleye24491a2022-11-25 21:23:03 +0000295 }
296 }
297}
298
299func (m *Subprocess) readResult(cmd string, expectedNumResults int) ([][]byte, error) {
300 buf := make([]byte, 4)
301
Adam Langleyb7f0c1b2019-07-09 18:02:14 -0700302 if _, err := io.ReadFull(m.stdout, buf); err != nil {
303 return nil, err
304 }
305
306 numResults := binary.LittleEndian.Uint32(buf)
Adam Langleye24491a2022-11-25 21:23:03 +0000307 if int(numResults) != expectedNumResults {
308 return nil, fmt.Errorf("expected %d results from %q but got %d", expectedNumResults, cmd, numResults)
Adam Langleyb7f0c1b2019-07-09 18:02:14 -0700309 }
310
311 buf = make([]byte, 4*numResults)
312 if _, err := io.ReadFull(m.stdout, buf); err != nil {
313 return nil, err
314 }
315
316 var resultsLength uint64
317 for i := uint32(0); i < numResults; i++ {
318 resultsLength += uint64(binary.LittleEndian.Uint32(buf[4*i:]))
319 }
320
321 if resultsLength > (1 << 30) {
322 return nil, fmt.Errorf("results too large (%d bytes)", resultsLength)
323 }
324
325 results := make([]byte, resultsLength)
326 if _, err := io.ReadFull(m.stdout, results); err != nil {
327 return nil, err
328 }
329
330 ret := make([][]byte, 0, numResults)
331 var offset int
332 for i := uint32(0); i < numResults; i++ {
333 length := binary.LittleEndian.Uint32(buf[4*i:])
334 ret = append(ret, results[offset:offset+int(length)])
335 offset += int(length)
336 }
337
338 return ret, nil
339}
340
341// Config returns a JSON blob that describes the supported primitives. The
342// format of the blob is defined by ACVP. See
343// http://usnistgov.github.io/ACVP/artifacts/draft-fussell-acvp-spec-00.html#rfc.section.11.15.2.1
344func (m *Subprocess) Config() ([]byte, error) {
Adam Langley9a798eb2020-04-15 10:36:52 -0700345 results, err := m.Transact("getConfig", 1)
Adam Langleyb7f0c1b2019-07-09 18:02:14 -0700346 if err != nil {
347 return nil, err
348 }
349 var config []struct {
Adam Langleye24491a2022-11-25 21:23:03 +0000350 Algorithm string `json:"algorithm"`
351 Features []string `json:"features"`
Adam Langleyb7f0c1b2019-07-09 18:02:14 -0700352 }
353 if err := json.Unmarshal(results[0], &config); err != nil {
354 return nil, errors.New("failed to parse config response from wrapper: " + err.Error())
355 }
356 for _, algo := range config {
Adam Langleye24491a2022-11-25 21:23:03 +0000357 if algo.Algorithm == "acvptool" {
358 for _, feature := range algo.Features {
359 switch feature {
360 case "batch":
361 m.supportsFlush = true
362 }
363 }
364 } else if _, ok := m.primitives[algo.Algorithm]; !ok {
Adam Langleyb7f0c1b2019-07-09 18:02:14 -0700365 return nil, fmt.Errorf("wrapper config advertises support for unknown algorithm %q", algo.Algorithm)
366 }
367 }
Adam Langleye24491a2022-11-25 21:23:03 +0000368
Adam Langleyb7f0c1b2019-07-09 18:02:14 -0700369 return results[0], nil
370}
371
372// Process runs a set of test vectors and returns the result.
David Benjamin77b6f252023-05-02 10:14:11 -0400373func (m *Subprocess) Process(algorithm string, vectorSet []byte) (any, error) {
Adam Langleyb7f0c1b2019-07-09 18:02:14 -0700374 prim, ok := m.primitives[algorithm]
375 if !ok {
376 return nil, fmt.Errorf("unknown algorithm %q", algorithm)
377 }
Adam Langley9a798eb2020-04-15 10:36:52 -0700378 ret, err := prim.Process(vectorSet, m)
Adam Langleyb7f0c1b2019-07-09 18:02:14 -0700379 if err != nil {
380 return nil, err
381 }
Adam Langleybef6a2f2020-08-21 16:16:09 -0700382 return ret, nil
Adam Langleyb7f0c1b2019-07-09 18:02:14 -0700383}
384
385type primitive interface {
David Benjamin77b6f252023-05-02 10:14:11 -0400386 Process(vectorSet []byte, t Transactable) (any, error)
Adam Langleyb7f0c1b2019-07-09 18:02:14 -0700387}
Adam Langleyc6550652020-06-30 17:37:13 -0700388
389func uint32le(n uint32) []byte {
390 var ret [4]byte
391 binary.LittleEndian.PutUint32(ret[:], n)
392 return ret[:]
393}