// 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"
	"crypto/internal/fips140/hmac"
	"crypto/internal/fips140/sha256"
	"crypto/internal/fips140deps/byteorder"
	"crypto/internal/fips140deps/godebug"
	"io"
	"unsafe"
)

// Verified is set when verification succeeded. It can be expected to always be
// true when [fips140.Enabled] is true, or init would have panicked.
var Verified bool

// 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() {
	if !fips140.Enabled {
		return
	}

	if err := fips140.Supported(); err != nil {
		panic("fips140: " + err.Error())
	}

	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")
	}

	// "The temporary value(s) generated during the integrity test of the
	// module’s software or firmware shall [05.10] be zeroised from the module
	// upon completion of the integrity test"
	clear(sum)
	clear(nbuf[:])
	h.Reset()

	if godebug.Value("fips140") == "debug" {
		println("fips140: verified code+data")
	}

	Verified = true
}
