// Per FIPS 140-3 IG C.M, key lengths below 112 bits are only allowed for
// legacy use (i.e. verification only) and we don't support that.
if len(key) < 112/8 {
- return
+ fips.RecordNonApproved()
}
switch h.(type) {
case *sha256.Digest, *sha512.Digest, *sha3.Digest:
+ fips.RecordApproved()
default:
- return
+ fips.RecordNonApproved()
}
-
- // TODO(fips): set service indicator.
}
--- /dev/null
+// 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 fips
+
+import _ "unsafe" // for go:linkname
+
+// The service indicator lets users of the module query whether invoked services
+// are approved. Three states are stored in a per-goroutine value by the
+// runtime. The indicator starts at indicatorUnset after a reset. Invoking an
+// approved service transitions to indicatorTrue. Invoking a non-approved
+// service transitions to indicatorFalse, and it can't leave that state until a
+// reset. The idea is that functions can "delegate" checks to inner functions,
+// and if there's anything non-approved in the stack, the final result is
+// negative. Finally, we expose indicatorUnset as negative to the user, so that
+// we don't need to explicitly annotate fully non-approved services.
+
+//go:linkname getIndicator
+func getIndicator() uint8
+
+//go:linkname setIndicator
+func setIndicator(uint8)
+
+const (
+ indicatorUnset uint8 = iota
+ indicatorFalse
+ indicatorTrue
+)
+
+// ResetServiceIndicator clears the service indicator for the running goroutine.
+func ResetServiceIndicator() {
+ setIndicator(indicatorUnset)
+}
+
+// ServiceIndicator returns true if and only if all services invoked by this
+// goroutine since the last ResetServiceIndicator call are approved.
+//
+// If ResetServiceIndicator was not called before by this goroutine, its return
+// value is undefined.
+func ServiceIndicator() bool {
+ return getIndicator() == indicatorTrue
+}
+
+// RecordApproved is an internal function that records the use of an approved
+// service. It does not override RecordNonApproved calls in the same span.
+func RecordApproved() {
+ if getIndicator() == indicatorUnset {
+ setIndicator(indicatorTrue)
+ }
+}
+
+// RecordNonApproved is an internal function that records the use of a
+// non-approved service. It overrides any RecordApproved calls in the same span.
+func RecordNonApproved() {
+ setIndicator(indicatorFalse)
+}
--- /dev/null
+// 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 fips_test
+
+import (
+ "crypto/internal/fips"
+ "testing"
+)
+
+func TestIndicator(t *testing.T) {
+ fips.ResetServiceIndicator()
+ if fips.ServiceIndicator() {
+ t.Error("indicator should be false if no calls are made")
+ }
+
+ fips.ResetServiceIndicator()
+ fips.RecordApproved()
+ if !fips.ServiceIndicator() {
+ t.Error("indicator should be true if RecordApproved is called")
+ }
+
+ fips.ResetServiceIndicator()
+ fips.RecordApproved()
+ fips.RecordApproved()
+ if !fips.ServiceIndicator() {
+ t.Error("indicator should be true if RecordApproved is called multiple times")
+ }
+
+ fips.ResetServiceIndicator()
+ fips.RecordNonApproved()
+ if fips.ServiceIndicator() {
+ t.Error("indicator should be false if RecordNonApproved is called")
+ }
+
+ fips.ResetServiceIndicator()
+ fips.RecordApproved()
+ fips.RecordNonApproved()
+ if fips.ServiceIndicator() {
+ t.Error("indicator should be false if both RecordApproved and RecordNonApproved are called")
+ }
+
+ fips.ResetServiceIndicator()
+ fips.RecordNonApproved()
+ fips.RecordApproved()
+ if fips.ServiceIndicator() {
+ t.Error("indicator should be false if both RecordNonApproved and RecordApproved are called")
+ }
+
+ fips.ResetServiceIndicator()
+ fips.RecordNonApproved()
+ done := make(chan struct{})
+ go func() {
+ fips.ResetServiceIndicator()
+ fips.RecordApproved()
+ close(done)
+ }()
+ <-done
+ if fips.ServiceIndicator() {
+ t.Error("indicator should be false if RecordApproved is called in a different goroutine")
+ }
+
+ fips.ResetServiceIndicator()
+ fips.RecordApproved()
+ done = make(chan struct{})
+ go func() {
+ fips.ResetServiceIndicator()
+ fips.RecordNonApproved()
+ close(done)
+ }()
+ <-done
+ if !fips.ServiceIndicator() {
+ t.Error("indicator should be true if RecordNonApproved is called in a different goroutine")
+ }
+}
package sha256
import (
+ "crypto/internal/fips"
"errors"
"internal/byteorder"
)
}
func (d *Digest) Sum(in []byte) []byte {
+ fips.RecordApproved()
// Make a copy of d so that caller can keep writing and summing.
d0 := *d
hash := d0.checkSum()
package sha3
import (
+ "crypto/internal/fips"
"crypto/internal/fips/subtle"
"errors"
)
// Sum appends the current hash to b and returns the resulting slice.
// It does not change the underlying hash state.
-func (d *Digest) Sum(b []byte) []byte { return d.sum(b) }
+func (d *Digest) Sum(b []byte) []byte {
+ fips.RecordApproved()
+ return d.sum(b)
+}
+
func (d *Digest) sumGeneric(b []byte) []byte {
if d.state != spongeAbsorbing {
panic("sha3: Sum after Read")
import (
"bytes"
+ "crypto/internal/fips"
"errors"
"internal/byteorder"
"math/bits"
func (s *SHAKE) Write(p []byte) (n int, err error) { return s.d.Write(p) }
func (s *SHAKE) Read(out []byte) (n int, err error) {
+ fips.RecordApproved()
// Note that read is not exposed on Digest since SHA-3 does not offer
// variable output length. It is only used internally by Sum.
return s.d.read(out)
package sha512
import (
+ "crypto/internal/fips"
"errors"
"internal/byteorder"
)
}
func (d *Digest) Sum(in []byte) []byte {
+ fips.RecordApproved()
// Make a copy of d so that caller can keep writing and summing.
d0 := new(Digest)
*d0 = *d
reflectOffsUnlock()
return id
}
+
+//go:linkname fips_getIndicator crypto/internal/fips.getIndicator
+func fips_getIndicator() uint8 {
+ return getg().fipsIndicator
+}
+
+//go:linkname fips_setIndicator crypto/internal/fips.setIndicator
+func fips_setIndicator(indicator uint8) {
+ getg().fipsIndicator = indicator
+}
trackingStamp int64 // timestamp of when the G last started being tracked
runnableTime int64 // the amount of time spent runnable, cleared when running, only used when tracking
lockedm muintptr
+ fipsIndicator uint8
sig uint32
writebuf []byte
sigcode0 uintptr
_32bit uintptr // size on 32bit platforms
_64bit uintptr // size on 64bit platforms
}{
- {runtime.G{}, 272, 432}, // g, but exported for testing
+ {runtime.G{}, 276, 432}, // g, but exported for testing
{runtime.Sudog{}, 56, 88}, // sudog, but exported for testing
}