// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package fips140

import (
	"crypto/internal/fips140deps/godebug"
	"errors"
	"strings"
	_ "unsafe" // for go:linkname
)

// fatal is [runtime.fatal], pushed via linkname.
//
//go:linkname fatal crypto/internal/fips140.fatal
func fatal(string)

// failfipscast is a GODEBUG key allowing simulation of a CAST or PCT failure,
// as required during FIPS 140-3 functional testing. The value is the whole name
// of the target CAST or PCT.
var failfipscast = godebug.Value("#failfipscast")

// CAST runs the named Cryptographic Algorithm Self-Test (if operated in FIPS
// mode) and aborts the program (stopping the module input/output and entering
// the "error state") if the self-test fails.
//
// CASTs are mandatory self-checks that must be performed by FIPS 140-3 modules
// before the algorithm is used. See Implementation Guidance 10.3.A.
//
// The name must not contain commas, colons, hashes, or equal signs.
//
// If a package p calls CAST from its init function, an import of p should also
// be added to crypto/internal/fips140test. If a package p calls CAST on the first
// use of the algorithm, an invocation of that algorithm should be added to
// fipstest.TestConditionals.
func CAST(name string, f func() error) {
	if strings.ContainsAny(name, ",#=:") {
		panic("fips: invalid self-test name: " + name)
	}
	if !Enabled {
		return
	}

	err := f()
	if name == failfipscast {
		err = errors.New("simulated CAST failure")
	}
	if err != nil {
		fatal("FIPS 140-3 self-test failed: " + name + ": " + err.Error())
		panic("unreachable")
	}
	if debug {
		println("FIPS 140-3 self-test passed:", name)
	}
}

// PCT runs the named Pairwise Consistency Test (if operated in FIPS mode) and
// aborts the program (stopping the module input/output and entering the "error
// state") if the test fails.
//
// PCTs are mandatory for every generated (but not imported) key pair, including
// ephemeral keys (which effectively doubles the cost of key establishment). See
// Implementation Guidance 10.3.A Additional Comment 1.
//
// The name must not contain commas, colons, hashes, or equal signs.
//
// If a package p calls PCT during key generation, an invocation of that
// function should be added to fipstest.TestConditionals.
func PCT(name string, f func() error) {
	if strings.ContainsAny(name, ",#=:") {
		panic("fips: invalid self-test name: " + name)
	}
	if !Enabled {
		return
	}

	err := f()
	if name == failfipscast {
		err = errors.New("simulated PCT failure")
	}
	if err != nil {
		fatal("FIPS 140-3 self-test failed: " + name + ": " + err.Error())
		panic("unreachable")
	}
	if debug {
		println("FIPS 140-3 PCT passed:", name)
	}
}
