// 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 check implements the FIPS-140 load-time code+data verification.
// Every FIPS package providing cryptographic functionality except hmac and sha256
// must import crypto/internal/fips140/check, so that the verification happens
// before initialization of package global variables.
// The hmac and sha256 packages are used by this package, so they cannot import it.
// Instead, those packages must be careful not to change global variables during init.
// (If necessary, we could have check call a PostCheck function in those packages
// after the check has completed.)
package check

import (
	"crypto/internal/fips140/hmac"
	"crypto/internal/fips140/sha256"
	"crypto/internal/fips140deps/byteorder"
	"crypto/internal/fips140deps/godebug"
	"io"
	"runtime"
	"unsafe"
)

// Enabled reports whether verification was enabled.
// If Enabled returns true, then verification succeeded,
// because if it failed the binary would have panicked at init time.
func Enabled() bool {
	return enabled
}

var enabled bool  // set when verification is enabled
var Verified bool // set when verification succeeds, for testing

// Supported reports whether the current GOOS/GOARCH is Supported at all.
func Supported() bool {
	// See cmd/internal/obj/fips.go's EnableFIPS for commentary.
	switch {
	case runtime.GOARCH == "wasm",
		runtime.GOOS == "windows" && runtime.GOARCH == "386",
		runtime.GOOS == "windows" && runtime.GOARCH == "arm",
		runtime.GOOS == "aix":
		return false
	}
	return true
}

// Linkinfo holds the go:fipsinfo symbol prepared by the linker.
// See cmd/link/internal/ld/fips.go for details.
//
//go:linkname Linkinfo go:fipsinfo
var Linkinfo struct {
	Magic [16]byte
	Sum   [32]byte
	Self  uintptr
	Sects [4]struct {
		// Note: These must be unsafe.Pointer, not uintptr,
		// or else checkptr panics about turning uintptrs
		// into pointers into the data segment during
		// go test -race.
		Start unsafe.Pointer
		End   unsafe.Pointer
	}
}

// "\xff"+fipsMagic is the expected linkinfo.Magic.
// We avoid writing that explicitly so that the string does not appear
// elsewhere in normal binaries, just as a precaution.
const fipsMagic = " Go fipsinfo \xff\x00"

var zeroSum [32]byte

func init() {
	v := godebug.Value("#fips140")
	enabled = v != "" && v != "off"
	if !enabled {
		return
	}

	if asanEnabled {
		// ASAN disapproves of reading swaths of global memory below.
		// One option would be to expose runtime.asanunpoison through
		// crypto/internal/fips140deps and then call it to unpoison the range
		// before reading it, but it is unclear whether that would then cause
		// false negatives. For now, FIPS+ASAN doesn't need to work.
		// If this is made to work, also re-enable the test in check_test.go
		// and in cmd/dist/test.go.
		panic("fips140: cannot verify in asan mode")
	}

	switch v {
	case "on", "only", "debug":
		// ok
	default:
		panic("fips140: unknown GODEBUG setting fips140=" + v)
	}

	if !Supported() {
		panic("fips140: unavailable on " + runtime.GOOS + "-" + runtime.GOARCH)
	}

	if Linkinfo.Magic[0] != 0xff || string(Linkinfo.Magic[1:]) != fipsMagic || Linkinfo.Sum == zeroSum {
		panic("fips140: no verification checksum found")
	}

	h := hmac.New(sha256.New, make([]byte, 32))
	w := io.Writer(h)

	/*
		// Uncomment for debugging.
		// Commented (as opposed to a const bool flag)
		// to avoid import "os" in default builds.
		f, err := os.Create("fipscheck.o")
		if err != nil {
			panic(err)
		}
		w = io.MultiWriter(h, f)
	*/

	w.Write([]byte("go fips object v1\n"))

	var nbuf [8]byte
	for _, sect := range Linkinfo.Sects {
		n := uintptr(sect.End) - uintptr(sect.Start)
		byteorder.BEPutUint64(nbuf[:], uint64(n))
		w.Write(nbuf[:])
		w.Write(unsafe.Slice((*byte)(sect.Start), n))
	}
	sum := h.Sum(nil)

	if [32]byte(sum) != Linkinfo.Sum {
		panic("fips140: verification mismatch")
	}

	if v == "debug" {
		println("fips140: verified code+data")
	}

	Verified = true
}
