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

View as plain text