Source file src/crypto/internal/fips140test/cast_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  import (
     8  	"crypto"
     9  	"crypto/internal/fips140"
    10  	"crypto/rand"
    11  	"fmt"
    12  	"internal/testenv"
    13  	"io/fs"
    14  	"os"
    15  	"regexp"
    16  	"slices"
    17  	"strings"
    18  	"testing"
    19  
    20  	// Import packages that define CASTs to test them.
    21  	"crypto/internal/cryptotest"
    22  	_ "crypto/internal/fips140/aes"
    23  	_ "crypto/internal/fips140/aes/gcm"
    24  	_ "crypto/internal/fips140/drbg"
    25  	"crypto/internal/fips140/ecdh"
    26  	"crypto/internal/fips140/ecdsa"
    27  	"crypto/internal/fips140/ed25519"
    28  	_ "crypto/internal/fips140/hkdf"
    29  	_ "crypto/internal/fips140/hmac"
    30  	"crypto/internal/fips140/mlkem"
    31  	"crypto/internal/fips140/rsa"
    32  	"crypto/internal/fips140/sha256"
    33  	_ "crypto/internal/fips140/sha3"
    34  	_ "crypto/internal/fips140/sha512"
    35  	_ "crypto/internal/fips140/tls12"
    36  	_ "crypto/internal/fips140/tls13"
    37  )
    38  
    39  var allCASTs = []string{
    40  	"AES-CBC",
    41  	"CTR_DRBG",
    42  	"CounterKDF",
    43  	"DetECDSA P-256 SHA2-512 sign",
    44  	"ECDH PCT",
    45  	"ECDSA P-256 SHA2-512 sign and verify",
    46  	"ECDSA PCT",
    47  	"Ed25519 sign and verify",
    48  	"Ed25519 sign and verify PCT",
    49  	"HKDF-SHA2-256",
    50  	"HMAC-SHA2-256",
    51  	"KAS-ECC-SSC P-256",
    52  	"ML-DSA sign and verify PCT",
    53  	"ML-DSA-44",
    54  	"ML-KEM PCT", // -768
    55  	"ML-KEM PCT", // -1024
    56  	"ML-KEM-768",
    57  	"PBKDF2",
    58  	"RSA sign and verify PCT",
    59  	"RSASSA-PKCS-v1.5 2048-bit sign and verify",
    60  	"SHA2-256",
    61  	"SHA2-512",
    62  	"TLSv1.2-SHA2-256",
    63  	"TLSv1.3-SHA2-256",
    64  	"cSHAKE128",
    65  }
    66  
    67  func init() {
    68  	if fips140.Version() == "v1.0.0" {
    69  		allCASTs = slices.DeleteFunc(allCASTs, func(s string) bool {
    70  			return strings.HasPrefix(s, "ML-DSA")
    71  		})
    72  	}
    73  }
    74  
    75  func TestAllCASTs(t *testing.T) {
    76  	testenv.MustHaveSource(t)
    77  
    78  	// Ask "go list" for the location of the crypto/internal/fips140 tree, as it
    79  	// might be the unpacked frozen tree selected with GOFIPS140.
    80  	cmd := testenv.Command(t, testenv.GoToolPath(t), "list", "-f", `{{.Dir}}`, "crypto/internal/fips140")
    81  	out, err := testenv.CleanCmdEnv(cmd).CombinedOutput()
    82  	if err != nil {
    83  		t.Fatalf("go list: %v\n%s", err, out)
    84  	}
    85  	fipsDir := strings.TrimSpace(string(out))
    86  	t.Logf("FIPS module directory: %s", fipsDir)
    87  
    88  	// Find all invocations of fips140.CAST or fips140.PCT.
    89  	var foundCASTs []string
    90  	castRe := regexp.MustCompile(`fips140\.(CAST|PCT)\("([^"]+)"`)
    91  	if err := fs.WalkDir(os.DirFS(fipsDir), ".", func(path string, d fs.DirEntry, err error) error {
    92  		if err != nil {
    93  			return err
    94  		}
    95  		if d.IsDir() || !strings.HasSuffix(path, ".go") {
    96  			return nil
    97  		}
    98  		data, err := os.ReadFile(fipsDir + "/" + path)
    99  		if err != nil {
   100  			return err
   101  		}
   102  		for _, m := range castRe.FindAllSubmatch(data, -1) {
   103  			foundCASTs = append(foundCASTs, string(m[2]))
   104  		}
   105  		return nil
   106  	}); err != nil {
   107  		t.Fatalf("WalkDir: %v", err)
   108  	}
   109  
   110  	slices.Sort(foundCASTs)
   111  	if !slices.Equal(foundCASTs, allCASTs) {
   112  		t.Errorf("AllCASTs is out of date. Found CASTs: %#v", foundCASTs)
   113  	}
   114  }
   115  
   116  // TestConditionals causes the conditional CASTs and PCTs to be invoked.
   117  func TestConditionals(t *testing.T) {
   118  	fips140v126Conditionals()
   119  	// ML-KEM PCT
   120  	kMLKEM, err := mlkem.GenerateKey768()
   121  	if err != nil {
   122  		t.Error(err)
   123  	} else {
   124  		// ML-KEM-768
   125  		kMLKEM.EncapsulationKey().Encapsulate()
   126  	}
   127  	// ECDH PCT
   128  	kDH, err := ecdh.GenerateKey(ecdh.P256(), rand.Reader)
   129  	if err != nil {
   130  		t.Error(err)
   131  	} else {
   132  		// KAS-ECC-SSC P-256
   133  		ecdh.ECDH(ecdh.P256(), kDH, kDH.PublicKey())
   134  	}
   135  	// ECDSA PCT
   136  	kDSA, err := ecdsa.GenerateKey(ecdsa.P256(), rand.Reader)
   137  	if err != nil {
   138  		t.Error(err)
   139  	} else {
   140  		// ECDSA P-256 SHA2-512 sign and verify
   141  		ecdsa.SignDeterministic(ecdsa.P256(), sha256.New, kDSA, make([]byte, 32))
   142  	}
   143  	// Ed25519 sign and verify PCT
   144  	k25519, err := ed25519.GenerateKey()
   145  	if err != nil {
   146  		t.Error(err)
   147  	} else {
   148  		// Ed25519 sign and verify
   149  		ed25519.Sign(k25519, make([]byte, 32))
   150  	}
   151  	// RSA sign and verify PCT
   152  	kRSA, err := rsa.GenerateKey(rand.Reader, 2048)
   153  	if err != nil {
   154  		t.Error(err)
   155  	} else {
   156  		// RSASSA-PKCS-v1.5 2048-bit sign and verify
   157  		rsa.SignPKCS1v15(kRSA, crypto.SHA256.String(), make([]byte, 32))
   158  	}
   159  	t.Log("completed successfully")
   160  }
   161  
   162  func TestCASTPasses(t *testing.T) {
   163  	moduleStatus(t)
   164  	cryptotest.MustSupportFIPS140(t)
   165  
   166  	cmd := testenv.Command(t, testenv.Executable(t), "-test.run=^TestConditionals$", "-test.v")
   167  	cmd.Env = append(cmd.Environ(), "GODEBUG=fips140=debug")
   168  	out, err := cmd.CombinedOutput()
   169  	t.Logf("running with GODEBUG=fips140=debug:\n%s", out)
   170  	if err != nil || !strings.Contains(string(out), "completed successfully") {
   171  		t.Errorf("TestConditionals did not complete successfully")
   172  	}
   173  
   174  	for _, name := range allCASTs {
   175  		t.Run(name, func(t *testing.T) {
   176  			if !strings.Contains(string(out), fmt.Sprintf("passed: %s\n", name)) {
   177  				t.Errorf("CAST/PCT %s success was not logged", name)
   178  			} else {
   179  				t.Logf("CAST/PCT succeeded: %s", name)
   180  			}
   181  		})
   182  	}
   183  }
   184  
   185  func TestCASTFailures(t *testing.T) {
   186  	moduleStatus(t)
   187  	cryptotest.MustSupportFIPS140(t)
   188  
   189  	for _, name := range allCASTs {
   190  		t.Run(name, func(t *testing.T) {
   191  			// Don't parallelize if running in verbose mode, to produce a less
   192  			// confusing recoding for the validation lab.
   193  			if !testing.Verbose() {
   194  				t.Parallel()
   195  			}
   196  			t.Logf("Testing CAST/PCT failure...")
   197  			cmd := testenv.Command(t, testenv.Executable(t), "-test.run=^TestConditionals$", "-test.v")
   198  			GODEBUG := fmt.Sprintf("GODEBUG=failfipscast=%s,fips140=on", name)
   199  			cmd.Env = append(cmd.Environ(), GODEBUG)
   200  			out, err := cmd.CombinedOutput()
   201  			t.Logf("running with %s:\n%s", GODEBUG, out)
   202  			if err == nil {
   203  				t.Fatal("test did not fail as expected")
   204  			}
   205  			if strings.Contains(string(out), "completed successfully") {
   206  				t.Errorf("CAST/PCT %s failure did not stop the program", name)
   207  			} else if !strings.Contains(string(out), "self-test failed: "+name) {
   208  				t.Errorf("CAST/PCT %s failure did not log the expected message", name)
   209  			} else {
   210  				t.Logf("CAST/PCT %s failed as expected and caused the program to exit", name)
   211  			}
   212  		})
   213  	}
   214  }
   215  

View as plain text