Source file src/crypto/internal/fips140test/acvp_test.go

     1  // Copyright 2024 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package fipstest
     6  
     7  // A module wrapper adapting the Go FIPS module to the protocol used by the
     8  // BoringSSL project's `acvptool`.
     9  //
    10  // The `acvptool` "lowers" the NIST ACVP server JSON test vectors into a simpler
    11  // stdin/stdout protocol that can be implemented by a module shim. The tool
    12  // will fork this binary, request the supported configuration, and then provide
    13  // test cases over stdin, expecting results to be returned on stdout.
    14  //
    15  // See "Testing other FIPS modules"[0] from the BoringSSL ACVP.md documentation
    16  // for a more detailed description of the protocol used between the acvptool
    17  // and module wrappers.
    18  //
    19  // [0]: https://boringssl.googlesource.com/boringssl/+/refs/heads/master/util/fipstools/acvp/ACVP.md#testing-other-fips-modules
    20  
    21  import (
    22  	"bufio"
    23  	"bytes"
    24  	"crypto/internal/cryptotest"
    25  	"crypto/internal/fips140"
    26  	"crypto/internal/fips140/ecdsa"
    27  	"crypto/internal/fips140/hmac"
    28  	"crypto/internal/fips140/mlkem"
    29  	"crypto/internal/fips140/pbkdf2"
    30  	"crypto/internal/fips140/sha256"
    31  	"crypto/internal/fips140/sha3"
    32  	"crypto/internal/fips140/sha512"
    33  	_ "embed"
    34  	"encoding/binary"
    35  	"errors"
    36  	"fmt"
    37  	"internal/testenv"
    38  	"io"
    39  	"os"
    40  	"path/filepath"
    41  	"strings"
    42  	"testing"
    43  )
    44  
    45  func TestMain(m *testing.M) {
    46  	if os.Getenv("ACVP_WRAPPER") == "1" {
    47  		wrapperMain()
    48  	} else {
    49  		os.Exit(m.Run())
    50  	}
    51  }
    52  
    53  func wrapperMain() {
    54  	if err := processingLoop(bufio.NewReader(os.Stdin), os.Stdout); err != nil {
    55  		fmt.Fprintf(os.Stderr, "processing error: %v\n", err)
    56  		os.Exit(1)
    57  	}
    58  }
    59  
    60  type request struct {
    61  	name string
    62  	args [][]byte
    63  }
    64  
    65  type commandHandler func([][]byte) ([][]byte, error)
    66  
    67  type command struct {
    68  	// requiredArgs enforces that an exact number of arguments are provided to the handler.
    69  	requiredArgs int
    70  	handler      commandHandler
    71  }
    72  
    73  var (
    74  	// SHA2 algorithm capabilities:
    75  	//   https://pages.nist.gov/ACVP/draft-celi-acvp-sha.html#section-7.2
    76  	// HMAC algorithm capabilities:
    77  	//   https://pages.nist.gov/ACVP/draft-fussell-acvp-mac.html#section-7
    78  	// PBKDF2 algorithm capabilities:
    79  	//   https://pages.nist.gov/ACVP/draft-celi-acvp-pbkdf.html#section-7.3
    80  	// ML-KEM algorithm capabilities:
    81  	//   https://pages.nist.gov/ACVP/draft-celi-acvp-ml-kem.html#section-7.3
    82  	// HMAC DRBG algorithm capabilities:
    83  	//   https://pages.nist.gov/ACVP/draft-vassilev-acvp-drbg.html#section-7.2
    84  	//go:embed acvp_capabilities.json
    85  	capabilitiesJson []byte
    86  
    87  	// commands should reflect what config says we support. E.g. adding a command here will be a NOP
    88  	// unless the configuration/acvp_capabilities.json indicates the command's associated algorithm
    89  	// is supported.
    90  	commands = map[string]command{
    91  		"getConfig": cmdGetConfig(),
    92  
    93  		"SHA2-224":         cmdHashAft(sha256.New224()),
    94  		"SHA2-224/MCT":     cmdHashMct(sha256.New224()),
    95  		"SHA2-256":         cmdHashAft(sha256.New()),
    96  		"SHA2-256/MCT":     cmdHashMct(sha256.New()),
    97  		"SHA2-384":         cmdHashAft(sha512.New384()),
    98  		"SHA2-384/MCT":     cmdHashMct(sha512.New384()),
    99  		"SHA2-512":         cmdHashAft(sha512.New()),
   100  		"SHA2-512/MCT":     cmdHashMct(sha512.New()),
   101  		"SHA2-512/224":     cmdHashAft(sha512.New512_224()),
   102  		"SHA2-512/224/MCT": cmdHashMct(sha512.New512_224()),
   103  		"SHA2-512/256":     cmdHashAft(sha512.New512_256()),
   104  		"SHA2-512/256/MCT": cmdHashMct(sha512.New512_256()),
   105  
   106  		"SHA3-256":     cmdHashAft(sha3.New256()),
   107  		"SHA3-256/MCT": cmdSha3Mct(sha3.New256()),
   108  		"SHA3-224":     cmdHashAft(sha3.New224()),
   109  		"SHA3-224/MCT": cmdSha3Mct(sha3.New224()),
   110  		"SHA3-384":     cmdHashAft(sha3.New384()),
   111  		"SHA3-384/MCT": cmdSha3Mct(sha3.New384()),
   112  		"SHA3-512":     cmdHashAft(sha3.New512()),
   113  		"SHA3-512/MCT": cmdSha3Mct(sha3.New512()),
   114  
   115  		"HMAC-SHA2-224":     cmdHmacAft(func() fips140.Hash { return sha256.New224() }),
   116  		"HMAC-SHA2-256":     cmdHmacAft(func() fips140.Hash { return sha256.New() }),
   117  		"HMAC-SHA2-384":     cmdHmacAft(func() fips140.Hash { return sha512.New384() }),
   118  		"HMAC-SHA2-512":     cmdHmacAft(func() fips140.Hash { return sha512.New() }),
   119  		"HMAC-SHA2-512/224": cmdHmacAft(func() fips140.Hash { return sha512.New512_224() }),
   120  		"HMAC-SHA2-512/256": cmdHmacAft(func() fips140.Hash { return sha512.New512_256() }),
   121  		"HMAC-SHA3-224":     cmdHmacAft(func() fips140.Hash { return sha3.New224() }),
   122  		"HMAC-SHA3-256":     cmdHmacAft(func() fips140.Hash { return sha3.New256() }),
   123  		"HMAC-SHA3-384":     cmdHmacAft(func() fips140.Hash { return sha3.New384() }),
   124  		"HMAC-SHA3-512":     cmdHmacAft(func() fips140.Hash { return sha3.New512() }),
   125  
   126  		"PBKDF": cmdPbkdf(),
   127  
   128  		"ML-KEM-768/keyGen":  cmdMlKem768KeyGenAft(),
   129  		"ML-KEM-768/encap":   cmdMlKem768EncapAft(),
   130  		"ML-KEM-768/decap":   cmdMlKem768DecapAft(),
   131  		"ML-KEM-1024/keyGen": cmdMlKem1024KeyGenAft(),
   132  		"ML-KEM-1024/encap":  cmdMlKem1024EncapAft(),
   133  		"ML-KEM-1024/decap":  cmdMlKem1024DecapAft(),
   134  
   135  		"hmacDRBG/SHA2-224":     cmdHmacDrbgAft(func() fips140.Hash { return sha256.New224() }),
   136  		"hmacDRBG/SHA2-256":     cmdHmacDrbgAft(func() fips140.Hash { return sha256.New() }),
   137  		"hmacDRBG/SHA2-384":     cmdHmacDrbgAft(func() fips140.Hash { return sha512.New384() }),
   138  		"hmacDRBG/SHA2-512":     cmdHmacDrbgAft(func() fips140.Hash { return sha512.New() }),
   139  		"hmacDRBG/SHA2-512/224": cmdHmacDrbgAft(func() fips140.Hash { return sha512.New512_224() }),
   140  		"hmacDRBG/SHA2-512/256": cmdHmacDrbgAft(func() fips140.Hash { return sha512.New512_256() }),
   141  		"hmacDRBG/SHA3-224":     cmdHmacDrbgAft(func() fips140.Hash { return sha3.New224() }),
   142  		"hmacDRBG/SHA3-256":     cmdHmacDrbgAft(func() fips140.Hash { return sha3.New256() }),
   143  		"hmacDRBG/SHA3-384":     cmdHmacDrbgAft(func() fips140.Hash { return sha3.New384() }),
   144  		"hmacDRBG/SHA3-512":     cmdHmacDrbgAft(func() fips140.Hash { return sha3.New512() }),
   145  	}
   146  )
   147  
   148  func processingLoop(reader io.Reader, writer io.Writer) error {
   149  	// Per ACVP.md:
   150  	//   The protocol is request–response: the subprocess only speaks in response to a request
   151  	//   and there is exactly one response for every request.
   152  	for {
   153  		req, err := readRequest(reader)
   154  		if errors.Is(err, io.EOF) {
   155  			break
   156  		} else if err != nil {
   157  			return fmt.Errorf("reading request: %w", err)
   158  		}
   159  
   160  		cmd, exists := commands[req.name]
   161  		if !exists {
   162  			return fmt.Errorf("unknown command: %q", req.name)
   163  		}
   164  
   165  		if gotArgs := len(req.args); gotArgs != cmd.requiredArgs {
   166  			return fmt.Errorf("command %q expected %d args, got %d", req.name, cmd.requiredArgs, gotArgs)
   167  		}
   168  
   169  		response, err := cmd.handler(req.args)
   170  		if err != nil {
   171  			return fmt.Errorf("command %q failed: %w", req.name, err)
   172  		}
   173  
   174  		if err = writeResponse(writer, response); err != nil {
   175  			return fmt.Errorf("command %q response failed: %w", req.name, err)
   176  		}
   177  	}
   178  
   179  	return nil
   180  }
   181  
   182  func readRequest(reader io.Reader) (*request, error) {
   183  	// Per ACVP.md:
   184  	//   Requests consist of one or more byte strings and responses consist
   185  	//   of zero or more byte strings. A request contains: the number of byte
   186  	//   strings, the length of each byte string, and the contents of each byte
   187  	//   string. All numbers are 32-bit little-endian and values are
   188  	//   concatenated in the order specified.
   189  	var numArgs uint32
   190  	if err := binary.Read(reader, binary.LittleEndian, &numArgs); err != nil {
   191  		return nil, err
   192  	}
   193  	if numArgs == 0 {
   194  		return nil, errors.New("invalid request: zero args")
   195  	}
   196  
   197  	args, err := readArgs(reader, numArgs)
   198  	if err != nil {
   199  		return nil, err
   200  	}
   201  
   202  	return &request{
   203  		name: string(args[0]),
   204  		args: args[1:],
   205  	}, nil
   206  }
   207  
   208  func readArgs(reader io.Reader, requiredArgs uint32) ([][]byte, error) {
   209  	argLengths := make([]uint32, requiredArgs)
   210  	args := make([][]byte, requiredArgs)
   211  
   212  	for i := range argLengths {
   213  		if err := binary.Read(reader, binary.LittleEndian, &argLengths[i]); err != nil {
   214  			return nil, fmt.Errorf("invalid request: failed to read %d-th arg len: %w", i, err)
   215  		}
   216  	}
   217  
   218  	for i, length := range argLengths {
   219  		buf := make([]byte, length)
   220  		if _, err := io.ReadFull(reader, buf); err != nil {
   221  			return nil, fmt.Errorf("invalid request: failed to read %d-th arg data: %w", i, err)
   222  		}
   223  		args[i] = buf
   224  	}
   225  
   226  	return args, nil
   227  }
   228  
   229  func writeResponse(writer io.Writer, args [][]byte) error {
   230  	// See `readRequest` for details on the base format. Per ACVP.md:
   231  	//   A response has the same format except that there may be zero byte strings
   232  	//   and the first byte string has no special meaning.
   233  	numArgs := uint32(len(args))
   234  	if err := binary.Write(writer, binary.LittleEndian, numArgs); err != nil {
   235  		return fmt.Errorf("writing arg count: %w", err)
   236  	}
   237  
   238  	for i, arg := range args {
   239  		if err := binary.Write(writer, binary.LittleEndian, uint32(len(arg))); err != nil {
   240  			return fmt.Errorf("writing %d-th arg length: %w", i, err)
   241  		}
   242  	}
   243  
   244  	for i, b := range args {
   245  		if _, err := writer.Write(b); err != nil {
   246  			return fmt.Errorf("writing %d-th arg data: %w", i, err)
   247  		}
   248  	}
   249  
   250  	return nil
   251  }
   252  
   253  // "All implementations must support the getConfig command
   254  // which takes no arguments and returns a single byte string
   255  // which is a JSON blob of ACVP algorithm configuration."
   256  func cmdGetConfig() command {
   257  	return command{
   258  		handler: func(args [][]byte) ([][]byte, error) {
   259  			return [][]byte{capabilitiesJson}, nil
   260  		},
   261  	}
   262  }
   263  
   264  // cmdHashAft returns a command handler for the specified hash
   265  // algorithm for algorithm functional test (AFT) test cases.
   266  //
   267  // This shape of command expects a message as the sole argument,
   268  // and writes the resulting digest as a response.
   269  //
   270  // See https://pages.nist.gov/ACVP/draft-celi-acvp-sha.html
   271  func cmdHashAft(h fips140.Hash) command {
   272  	return command{
   273  		requiredArgs: 1, // Message to hash.
   274  		handler: func(args [][]byte) ([][]byte, error) {
   275  			h.Reset()
   276  			h.Write(args[0])
   277  			digest := make([]byte, 0, h.Size())
   278  			digest = h.Sum(digest)
   279  
   280  			return [][]byte{digest}, nil
   281  		},
   282  	}
   283  }
   284  
   285  // cmdHashMct returns a command handler for the specified hash
   286  // algorithm for monte carlo test (MCT) test cases.
   287  //
   288  // This shape of command expects a seed as the sole argument,
   289  // and writes the resulting digest as a response. It implements
   290  // the "standard" flavour of the MCT, not the "alternative".
   291  //
   292  // This algorithm was ported from `HashMCT` in BSSL's `modulewrapper.cc`
   293  // Note that it differs slightly from the upstream NIST MCT[0] algorithm
   294  // in that it does not perform the outer 100 iterations itself. See
   295  // footnote #1 in the ACVP.md docs[1], the acvptool handles this.
   296  //
   297  // [0]: https://pages.nist.gov/ACVP/draft-celi-acvp-sha.html#section-6.2
   298  // [1]: https://boringssl.googlesource.com/boringssl/+/refs/heads/master/util/fipstools/acvp/ACVP.md#testing-other-fips-modules
   299  func cmdHashMct(h fips140.Hash) command {
   300  	return command{
   301  		requiredArgs: 1, // Seed message.
   302  		handler: func(args [][]byte) ([][]byte, error) {
   303  			hSize := h.Size()
   304  			seed := args[0]
   305  
   306  			if seedLen := len(seed); seedLen != hSize {
   307  				return nil, fmt.Errorf("invalid seed size: expected %d got %d", hSize, seedLen)
   308  			}
   309  
   310  			digest := make([]byte, 0, hSize)
   311  			buf := make([]byte, 0, 3*hSize)
   312  			buf = append(buf, seed...)
   313  			buf = append(buf, seed...)
   314  			buf = append(buf, seed...)
   315  
   316  			for i := 0; i < 1000; i++ {
   317  				h.Reset()
   318  				h.Write(buf)
   319  				digest = h.Sum(digest[:0])
   320  
   321  				copy(buf, buf[hSize:])
   322  				copy(buf[2*hSize:], digest)
   323  			}
   324  
   325  			return [][]byte{buf[hSize*2:]}, nil
   326  		},
   327  	}
   328  }
   329  
   330  // cmdSha3Mct returns a command handler for the specified hash
   331  // algorithm for SHA-3 monte carlo test (MCT) test cases.
   332  //
   333  // This shape of command expects a seed as the sole argument,
   334  // and writes the resulting digest as a response. It implements
   335  // the "standard" flavour of the MCT, not the "alternative".
   336  //
   337  // This algorithm was ported from the "standard" MCT algorithm
   338  // specified in  draft-celi-acvp-sha3[0]. Note this differs from
   339  // the SHA2-* family of MCT tests handled by cmdHashMct. However,
   340  // like that handler it does not perform the outer 100 iterations.
   341  //
   342  // [0]: https://pages.nist.gov/ACVP/draft-celi-acvp-sha3.html#section-6.2.1
   343  func cmdSha3Mct(h fips140.Hash) command {
   344  	return command{
   345  		requiredArgs: 1, // Seed message.
   346  		handler: func(args [][]byte) ([][]byte, error) {
   347  			seed := args[0]
   348  			md := make([][]byte, 1001)
   349  			md[0] = seed
   350  
   351  			for i := 1; i <= 1000; i++ {
   352  				h.Reset()
   353  				h.Write(md[i-1])
   354  				md[i] = h.Sum(nil)
   355  			}
   356  
   357  			return [][]byte{md[1000]}, nil
   358  		},
   359  	}
   360  }
   361  
   362  func cmdHmacAft(h func() fips140.Hash) command {
   363  	return command{
   364  		requiredArgs: 2, // Message and key
   365  		handler: func(args [][]byte) ([][]byte, error) {
   366  			msg := args[0]
   367  			key := args[1]
   368  			mac := hmac.New(h, key)
   369  			mac.Write(msg)
   370  			return [][]byte{mac.Sum(nil)}, nil
   371  		},
   372  	}
   373  }
   374  
   375  func cmdPbkdf() command {
   376  	return command{
   377  		// Hash name, key length, salt, password, iteration count
   378  		requiredArgs: 5,
   379  		handler: func(args [][]byte) ([][]byte, error) {
   380  			h, err := lookupHash(string(args[0]))
   381  			if err != nil {
   382  				return nil, fmt.Errorf("PBKDF2 failed: %w", err)
   383  			}
   384  
   385  			keyLen := binary.LittleEndian.Uint32(args[1]) / 8
   386  			salt := args[2]
   387  			password := args[3]
   388  			iterationCount := binary.LittleEndian.Uint32(args[4])
   389  
   390  			derivedKey, err := pbkdf2.Key(h, string(password), salt, int(iterationCount), int(keyLen))
   391  			if err != nil {
   392  				return nil, fmt.Errorf("PBKDF2 failed: %w", err)
   393  			}
   394  
   395  			return [][]byte{derivedKey}, nil
   396  		},
   397  	}
   398  }
   399  
   400  func lookupHash(name string) (func() fips140.Hash, error) {
   401  	var h func() fips140.Hash
   402  
   403  	switch name {
   404  	case "SHA2-224":
   405  		h = func() fips140.Hash { return sha256.New224() }
   406  	case "SHA2-256":
   407  		h = func() fips140.Hash { return sha256.New() }
   408  	case "SHA2-384":
   409  		h = func() fips140.Hash { return sha512.New384() }
   410  	case "SHA2-512":
   411  		h = func() fips140.Hash { return sha512.New() }
   412  	case "SHA2-512/224":
   413  		h = func() fips140.Hash { return sha512.New512_224() }
   414  	case "SHA2-512/256":
   415  		h = func() fips140.Hash { return sha512.New512_256() }
   416  	case "SHA3-224":
   417  		h = func() fips140.Hash { return sha3.New224() }
   418  	case "SHA3-256":
   419  		h = func() fips140.Hash { return sha3.New256() }
   420  	case "SHA3-384":
   421  		h = func() fips140.Hash { return sha3.New384() }
   422  	case "SHA3-512":
   423  		h = func() fips140.Hash { return sha3.New512() }
   424  	default:
   425  		return nil, fmt.Errorf("unknown hash name: %q", name)
   426  	}
   427  
   428  	return h, nil
   429  }
   430  
   431  func cmdMlKem768KeyGenAft() command {
   432  	return command{
   433  		requiredArgs: 1, // Seed
   434  		handler: func(args [][]byte) ([][]byte, error) {
   435  			seed := args[0]
   436  
   437  			dk, err := mlkem.NewDecapsulationKey768(seed)
   438  			if err != nil {
   439  				return nil, fmt.Errorf("generating ML-KEM 768 decapsulation key: %w", err)
   440  			}
   441  
   442  			// Important: we must return the full encoding of dk, not the seed.
   443  			return [][]byte{dk.EncapsulationKey().Bytes(), mlkem.TestingOnlyExpandedBytes768(dk)}, nil
   444  		},
   445  	}
   446  }
   447  
   448  func cmdMlKem768EncapAft() command {
   449  	return command{
   450  		requiredArgs: 2, // Public key, entropy
   451  		handler: func(args [][]byte) ([][]byte, error) {
   452  			pk := args[0]
   453  			entropy := args[1]
   454  
   455  			ek, err := mlkem.NewEncapsulationKey768(pk)
   456  			if err != nil {
   457  				return nil, fmt.Errorf("generating ML-KEM 768 encapsulation key: %w", err)
   458  			}
   459  
   460  			if len(entropy) != 32 {
   461  				return nil, fmt.Errorf("wrong entropy length: got %d, want 32", len(entropy))
   462  			}
   463  
   464  			sharedKey, ct := ek.EncapsulateInternal((*[32]byte)(entropy[:32]))
   465  
   466  			return [][]byte{ct, sharedKey}, nil
   467  		},
   468  	}
   469  }
   470  
   471  func cmdMlKem768DecapAft() command {
   472  	return command{
   473  		requiredArgs: 2, // Private key, ciphertext
   474  		handler: func(args [][]byte) ([][]byte, error) {
   475  			pk := args[0]
   476  			ct := args[1]
   477  
   478  			dk, err := mlkem.TestingOnlyNewDecapsulationKey768(pk)
   479  			if err != nil {
   480  				return nil, fmt.Errorf("generating ML-KEM 768 decapsulation key: %w", err)
   481  			}
   482  
   483  			sharedKey, err := dk.Decapsulate(ct)
   484  			if err != nil {
   485  				return nil, fmt.Errorf("decapsulating ML-KEM 768 ciphertext: %w", err)
   486  			}
   487  
   488  			return [][]byte{sharedKey}, nil
   489  		},
   490  	}
   491  }
   492  
   493  func cmdMlKem1024KeyGenAft() command {
   494  	return command{
   495  		requiredArgs: 1, // Seed
   496  		handler: func(args [][]byte) ([][]byte, error) {
   497  			seed := args[0]
   498  
   499  			dk, err := mlkem.NewDecapsulationKey1024(seed)
   500  			if err != nil {
   501  				return nil, fmt.Errorf("generating ML-KEM 1024 decapsulation key: %w", err)
   502  			}
   503  
   504  			// Important: we must return the full encoding of dk, not the seed.
   505  			return [][]byte{dk.EncapsulationKey().Bytes(), mlkem.TestingOnlyExpandedBytes1024(dk)}, nil
   506  		},
   507  	}
   508  }
   509  
   510  func cmdMlKem1024EncapAft() command {
   511  	return command{
   512  		requiredArgs: 2, // Public key, entropy
   513  		handler: func(args [][]byte) ([][]byte, error) {
   514  			pk := args[0]
   515  			entropy := args[1]
   516  
   517  			ek, err := mlkem.NewEncapsulationKey1024(pk)
   518  			if err != nil {
   519  				return nil, fmt.Errorf("generating ML-KEM 1024 encapsulation key: %w", err)
   520  			}
   521  
   522  			if len(entropy) != 32 {
   523  				return nil, fmt.Errorf("wrong entropy length: got %d, want 32", len(entropy))
   524  			}
   525  
   526  			sharedKey, ct := ek.EncapsulateInternal((*[32]byte)(entropy[:32]))
   527  
   528  			return [][]byte{ct, sharedKey}, nil
   529  		},
   530  	}
   531  }
   532  
   533  func cmdMlKem1024DecapAft() command {
   534  	return command{
   535  		requiredArgs: 2, // Private key, ciphertext
   536  		handler: func(args [][]byte) ([][]byte, error) {
   537  			pk := args[0]
   538  			ct := args[1]
   539  
   540  			dk, err := mlkem.TestingOnlyNewDecapsulationKey1024(pk)
   541  			if err != nil {
   542  				return nil, fmt.Errorf("generating ML-KEM 1024 decapsulation key: %w", err)
   543  			}
   544  
   545  			sharedKey, err := dk.Decapsulate(ct)
   546  			if err != nil {
   547  				return nil, fmt.Errorf("decapsulating ML-KEM 1024 ciphertext: %w", err)
   548  			}
   549  
   550  			return [][]byte{sharedKey}, nil
   551  		},
   552  	}
   553  }
   554  
   555  func cmdHmacDrbgAft(h func() fips140.Hash) command {
   556  	return command{
   557  		requiredArgs: 6, // Output length, entropy, personalization, ad1, ad2, nonce
   558  		handler: func(args [][]byte) ([][]byte, error) {
   559  			outLen := binary.LittleEndian.Uint32(args[0])
   560  			entropy := args[1]
   561  			personalization := args[2]
   562  			ad1 := args[3]
   563  			ad2 := args[4]
   564  			nonce := args[5]
   565  
   566  			// Our capabilities describe no additional data support.
   567  			if len(ad1) != 0 || len(ad2) != 0 {
   568  				return nil, errors.New("additional data not supported")
   569  			}
   570  
   571  			// Our capabilities describe no prediction resistance (requires reseed) and no reseed.
   572  			// So the test procedure is:
   573  			//   * Instantiate DRBG
   574  			//   * Generate but don't output
   575  			//   * Generate output
   576  			//   * Uninstantiate
   577  			// See Table 7 in draft-vassilev-acvp-drbg
   578  			out := make([]byte, outLen)
   579  			drbg := ecdsa.TestingOnlyNewDRBG(h, entropy, nonce, personalization)
   580  			drbg.Generate(out)
   581  			drbg.Generate(out)
   582  
   583  			return [][]byte{out}, nil
   584  		},
   585  	}
   586  }
   587  
   588  func TestACVP(t *testing.T) {
   589  	testenv.SkipIfShortAndSlow(t)
   590  
   591  	const (
   592  		bsslModule    = "boringssl.googlesource.com/boringssl.git"
   593  		bsslVersion   = "v0.0.0-20250108043213-d3f61eeacbf7"
   594  		goAcvpModule  = "github.com/cpu/go-acvp"
   595  		goAcvpVersion = "v0.0.0-20250102201911-6839fc40f9f8"
   596  	)
   597  
   598  	// In crypto/tls/bogo_shim_test.go the test is skipped if run on a builder with runtime.GOOS == "windows"
   599  	// due to flaky networking. It may be necessary to do the same here.
   600  
   601  	// Stat the acvp test config file so the test will be re-run if it changes, invalidating cached results
   602  	// from the old config.
   603  	if _, err := os.Stat("acvp_test.config.json"); err != nil {
   604  		t.Fatalf("failed to stat config file: %s", err)
   605  	}
   606  
   607  	// Fetch the BSSL module and use the JSON output to find the absolute path to the dir.
   608  	bsslDir := cryptotest.FetchModule(t, bsslModule, bsslVersion)
   609  
   610  	t.Log("building acvptool")
   611  
   612  	// Build the acvptool binary.
   613  	toolPath := filepath.Join(t.TempDir(), "acvptool.exe")
   614  	goTool := testenv.GoToolPath(t)
   615  	cmd := testenv.Command(t, goTool,
   616  		"build",
   617  		"-o", toolPath,
   618  		"./util/fipstools/acvp/acvptool")
   619  	cmd.Dir = bsslDir
   620  	out := &strings.Builder{}
   621  	cmd.Stderr = out
   622  	if err := cmd.Run(); err != nil {
   623  		t.Fatalf("failed to build acvptool: %s\n%s", err, out.String())
   624  	}
   625  
   626  	// Similarly, fetch the ACVP data module that has vectors/expected answers.
   627  	dataDir := cryptotest.FetchModule(t, goAcvpModule, goAcvpVersion)
   628  
   629  	cwd, err := os.Getwd()
   630  	if err != nil {
   631  		t.Fatalf("failed to fetch cwd: %s", err)
   632  	}
   633  	configPath := filepath.Join(cwd, "acvp_test.config.json")
   634  	t.Logf("running check_expected.go\ncwd: %q\ndata_dir: %q\nconfig: %q\ntool: %q\nmodule-wrapper: %q\n",
   635  		cwd, dataDir, configPath, toolPath, os.Args[0])
   636  
   637  	// Run the check_expected test driver using the acvptool we built, and this test binary as the
   638  	// module wrapper. The file paths in the config file are specified relative to the dataDir root
   639  	// so we run the command from that dir.
   640  	args := []string{
   641  		"run",
   642  		filepath.Join(bsslDir, "util/fipstools/acvp/acvptool/test/check_expected.go"),
   643  		"-tool",
   644  		toolPath,
   645  		// Note: module prefix must match Wrapper value in acvp_test.config.json.
   646  		"-module-wrappers", "go:" + os.Args[0],
   647  		"-tests", configPath,
   648  	}
   649  	cmd = testenv.Command(t, goTool, args...)
   650  	cmd.Dir = dataDir
   651  	cmd.Env = append(os.Environ(), "ACVP_WRAPPER=1")
   652  	output, err := cmd.CombinedOutput()
   653  	if err != nil {
   654  		t.Fatalf("failed to run acvp tests: %s\n%s", err, string(output))
   655  	}
   656  	t.Log(string(output))
   657  }
   658  
   659  func TestTooFewArgs(t *testing.T) {
   660  	commands["test"] = command{
   661  		requiredArgs: 1,
   662  		handler: func(args [][]byte) ([][]byte, error) {
   663  			if gotArgs := len(args); gotArgs != 1 {
   664  				return nil, fmt.Errorf("expected 1 args, got %d", gotArgs)
   665  			}
   666  			return nil, nil
   667  		},
   668  	}
   669  
   670  	var output bytes.Buffer
   671  	err := processingLoop(mockRequest(t, "test", nil), &output)
   672  	if err == nil {
   673  		t.Fatalf("expected error, got nil")
   674  	}
   675  	expectedErr := "expected 1 args, got 0"
   676  	if !strings.Contains(err.Error(), expectedErr) {
   677  		t.Errorf("expected error to contain %q, got %v", expectedErr, err)
   678  	}
   679  }
   680  
   681  func TestTooManyArgs(t *testing.T) {
   682  	commands["test"] = command{
   683  		requiredArgs: 1,
   684  		handler: func(args [][]byte) ([][]byte, error) {
   685  			if gotArgs := len(args); gotArgs != 1 {
   686  				return nil, fmt.Errorf("expected 1 args, got %d", gotArgs)
   687  			}
   688  			return nil, nil
   689  		},
   690  	}
   691  
   692  	var output bytes.Buffer
   693  	err := processingLoop(mockRequest(
   694  		t, "test", [][]byte{[]byte("one"), []byte("two")}), &output)
   695  	if err == nil {
   696  		t.Fatalf("expected error, got nil")
   697  	}
   698  	expectedErr := "expected 1 args, got 2"
   699  	if !strings.Contains(err.Error(), expectedErr) {
   700  		t.Errorf("expected error to contain %q, got %v", expectedErr, err)
   701  	}
   702  }
   703  
   704  func TestGetConfig(t *testing.T) {
   705  	var output bytes.Buffer
   706  	err := processingLoop(mockRequest(t, "getConfig", nil), &output)
   707  	if err != nil {
   708  		t.Errorf("unexpected error: %v", err)
   709  	}
   710  
   711  	respArgs := readResponse(t, &output)
   712  	if len(respArgs) != 1 {
   713  		t.Fatalf("expected 1 response arg, got %d", len(respArgs))
   714  	}
   715  
   716  	if !bytes.Equal(respArgs[0], capabilitiesJson) {
   717  		t.Errorf("expected config %q, got %q", string(capabilitiesJson), string(respArgs[0]))
   718  	}
   719  }
   720  
   721  func TestSha2256(t *testing.T) {
   722  	testMessage := []byte("gophers eat grass")
   723  	expectedDigest := []byte{
   724  		188, 142, 10, 214, 48, 236, 72, 143, 70, 216, 223, 205, 219, 69, 53, 29,
   725  		205, 207, 162, 6, 14, 70, 113, 60, 251, 170, 201, 236, 119, 39, 141, 172,
   726  	}
   727  
   728  	var output bytes.Buffer
   729  	err := processingLoop(mockRequest(t, "SHA2-256", [][]byte{testMessage}), &output)
   730  	if err != nil {
   731  		t.Errorf("unexpected error: %v", err)
   732  	}
   733  
   734  	respArgs := readResponse(t, &output)
   735  	if len(respArgs) != 1 {
   736  		t.Fatalf("expected 1 response arg, got %d", len(respArgs))
   737  	}
   738  
   739  	if !bytes.Equal(respArgs[0], expectedDigest) {
   740  		t.Errorf("expected digest %v, got %v", expectedDigest, respArgs[0])
   741  	}
   742  }
   743  
   744  func mockRequest(t *testing.T, cmd string, args [][]byte) io.Reader {
   745  	t.Helper()
   746  
   747  	msgData := append([][]byte{[]byte(cmd)}, args...)
   748  
   749  	var buf bytes.Buffer
   750  	if err := writeResponse(&buf, msgData); err != nil {
   751  		t.Fatalf("writeResponse error: %v", err)
   752  	}
   753  
   754  	return &buf
   755  }
   756  
   757  func readResponse(t *testing.T, reader io.Reader) [][]byte {
   758  	var numArgs uint32
   759  	if err := binary.Read(reader, binary.LittleEndian, &numArgs); err != nil {
   760  		t.Fatalf("failed to read response args count: %v", err)
   761  	}
   762  
   763  	args, err := readArgs(reader, numArgs)
   764  	if err != nil {
   765  		t.Fatalf("failed to read %d response args: %v", numArgs, err)
   766  	}
   767  
   768  	return args
   769  }
   770  

View as plain text