crypto/internal/constanttime: expose intrinsics to the FIPS 140-3 packages

Intrinsifying things inside the module (crypto/internal/fips140/subtle)
is asking for trouble, as the import paths are rewritten by the
GOFIPS140 mechanism, and we might have to support multiple modules
in the future.

Importing crypto/subtle from inside a FIPS 140-3 module is not allowed,
and is basically asking for circular dependencies.

Instead, break off the intrinsics into their own package
(crypto/internal/constanttime), and keep the byte slice operations
in crypto/internal/fips140/subtle. crypto/subtle then becomes a thin
dispatch layer.

Change-Id: I6a6a6964cd5cb5ad06e9d1679201447f5a811da4
Reviewed-on: https://go-review.googlesource.com/c/go/+/716120
Reviewed-by: Keith Randall <khr@google.com>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
Reviewed-by: Keith Randall <khr@golang.org>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Auto-Submit: Filippo Valsorda <filippo@golang.org>
Reviewed-by: Jorropo <jorropo.pgm@gmail.com>
This commit is contained in:
Filippo Valsorda 2025-10-29 13:05:19 +01:00 committed by Gopher Robot
parent 388c41c412
commit 00ee1860ce
14 changed files with 110 additions and 100 deletions

View file

@ -1603,10 +1603,10 @@ func initIntrinsics(cfg *intrinsicBuildConfig) {
},
sys.AMD64)
/******** crypto/subtle ********/
// We implement a superset of the ConstantTimeSelect promise:
// ConstantTimeSelect returns x if v != 0 and y if v == 0.
add("crypto/subtle", "ConstantTimeSelect",
/******** crypto/internal/constanttime ********/
// We implement a superset of the Select promise:
// Select returns x if v != 0 and y if v == 0.
add("crypto/internal/constanttime", "Select",
func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value {
v, x, y := args[0], args[1], args[2]
@ -1627,7 +1627,7 @@ func initIntrinsics(cfg *intrinsicBuildConfig) {
return s.newValue3(ssa.OpCondSelect, types.Types[types.TINT], x, y, check)
},
sys.ArchAMD64, sys.ArchARM64, sys.ArchLoong64, sys.ArchPPC64, sys.ArchPPC64LE, sys.ArchWasm) // all with CMOV support.
add("crypto/subtle", "constantTimeBoolToUint8",
add("crypto/internal/constanttime", "boolToUint8",
func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value {
return s.newValue1(ssa.OpCvtBoolToUint8, types.Types[types.TUINT8], args[0])
},

View file

@ -42,7 +42,7 @@ var wantIntrinsics = map[testIntrinsicKey]struct{}{
{"386", "math/bits", "TrailingZeros8"}: struct{}{},
{"386", "runtime", "KeepAlive"}: struct{}{},
{"386", "runtime", "slicebytetostringtmp"}: struct{}{},
{"386", "crypto/subtle", "constantTimeBoolToUint8"}: struct{}{},
{"386", "crypto/internal/constanttime", "boolToUint8"}: struct{}{},
{"amd64", "internal/runtime/atomic", "And"}: struct{}{},
{"amd64", "internal/runtime/atomic", "And32"}: struct{}{},
{"amd64", "internal/runtime/atomic", "And64"}: struct{}{},
@ -189,8 +189,8 @@ var wantIntrinsics = map[testIntrinsicKey]struct{}{
{"amd64", "sync/atomic", "SwapUint32"}: struct{}{},
{"amd64", "sync/atomic", "SwapUint64"}: struct{}{},
{"amd64", "sync/atomic", "SwapUintptr"}: struct{}{},
{"amd64", "crypto/subtle", "ConstantTimeSelect"}: struct{}{},
{"amd64", "crypto/subtle", "constantTimeBoolToUint8"}: struct{}{},
{"amd64", "crypto/internal/constanttime", "Select"}: struct{}{},
{"amd64", "crypto/internal/constanttime", "boolToUint8"}: struct{}{},
{"arm", "internal/runtime/sys", "Bswap32"}: struct{}{},
{"arm", "internal/runtime/sys", "Bswap64"}: struct{}{},
{"arm", "internal/runtime/sys", "GetCallerPC"}: struct{}{},
@ -219,7 +219,7 @@ var wantIntrinsics = map[testIntrinsicKey]struct{}{
{"arm", "math/bits", "TrailingZeros8"}: struct{}{},
{"arm", "runtime", "KeepAlive"}: struct{}{},
{"arm", "runtime", "slicebytetostringtmp"}: struct{}{},
{"arm", "crypto/subtle", "constantTimeBoolToUint8"}: struct{}{},
{"arm", "crypto/internal/constanttime", "boolToUint8"}: struct{}{},
{"arm64", "internal/runtime/atomic", "And"}: struct{}{},
{"arm64", "internal/runtime/atomic", "And32"}: struct{}{},
{"arm64", "internal/runtime/atomic", "And64"}: struct{}{},
@ -364,8 +364,8 @@ var wantIntrinsics = map[testIntrinsicKey]struct{}{
{"arm64", "sync/atomic", "SwapUint32"}: struct{}{},
{"arm64", "sync/atomic", "SwapUint64"}: struct{}{},
{"arm64", "sync/atomic", "SwapUintptr"}: struct{}{},
{"arm64", "crypto/subtle", "ConstantTimeSelect"}: struct{}{},
{"arm64", "crypto/subtle", "constantTimeBoolToUint8"}: struct{}{},
{"arm64", "crypto/internal/constanttime", "Select"}: struct{}{},
{"arm64", "crypto/internal/constanttime", "boolToUint8"}: struct{}{},
{"loong64", "internal/runtime/atomic", "And"}: struct{}{},
{"loong64", "internal/runtime/atomic", "And32"}: struct{}{},
{"loong64", "internal/runtime/atomic", "And64"}: struct{}{},
@ -512,8 +512,8 @@ var wantIntrinsics = map[testIntrinsicKey]struct{}{
{"loong64", "sync/atomic", "SwapUint32"}: struct{}{},
{"loong64", "sync/atomic", "SwapUint64"}: struct{}{},
{"loong64", "sync/atomic", "SwapUintptr"}: struct{}{},
{"loong64", "crypto/subtle", "ConstantTimeSelect"}: struct{}{},
{"loong64", "crypto/subtle", "constantTimeBoolToUint8"}: struct{}{},
{"loong64", "crypto/internal/constanttime", "Select"}: struct{}{},
{"loong64", "crypto/internal/constanttime", "boolToUint8"}: struct{}{},
{"mips", "internal/runtime/atomic", "And"}: struct{}{},
{"mips", "internal/runtime/atomic", "And8"}: struct{}{},
{"mips", "internal/runtime/atomic", "Cas"}: struct{}{},
@ -585,7 +585,7 @@ var wantIntrinsics = map[testIntrinsicKey]struct{}{
{"mips", "sync/atomic", "SwapInt32"}: struct{}{},
{"mips", "sync/atomic", "SwapUint32"}: struct{}{},
{"mips", "sync/atomic", "SwapUintptr"}: struct{}{},
{"mips", "crypto/subtle", "constantTimeBoolToUint8"}: struct{}{},
{"mips", "crypto/internal/constanttime", "boolToUint8"}: struct{}{},
{"mips64", "internal/runtime/atomic", "And"}: struct{}{},
{"mips64", "internal/runtime/atomic", "And8"}: struct{}{},
{"mips64", "internal/runtime/atomic", "Cas"}: struct{}{},
@ -674,7 +674,7 @@ var wantIntrinsics = map[testIntrinsicKey]struct{}{
{"mips64", "sync/atomic", "SwapUint32"}: struct{}{},
{"mips64", "sync/atomic", "SwapUint64"}: struct{}{},
{"mips64", "sync/atomic", "SwapUintptr"}: struct{}{},
{"mips64", "crypto/subtle", "constantTimeBoolToUint8"}: struct{}{},
{"mips64", "crypto/internal/constanttime", "boolToUint8"}: struct{}{},
{"mips64le", "internal/runtime/atomic", "And"}: struct{}{},
{"mips64le", "internal/runtime/atomic", "And8"}: struct{}{},
{"mips64le", "internal/runtime/atomic", "Cas"}: struct{}{},
@ -763,7 +763,7 @@ var wantIntrinsics = map[testIntrinsicKey]struct{}{
{"mips64le", "sync/atomic", "SwapUint32"}: struct{}{},
{"mips64le", "sync/atomic", "SwapUint64"}: struct{}{},
{"mips64le", "sync/atomic", "SwapUintptr"}: struct{}{},
{"mips64le", "crypto/subtle", "constantTimeBoolToUint8"}: struct{}{},
{"mips64le", "crypto/internal/constanttime", "boolToUint8"}: struct{}{},
{"mipsle", "internal/runtime/atomic", "And"}: struct{}{},
{"mipsle", "internal/runtime/atomic", "And8"}: struct{}{},
{"mipsle", "internal/runtime/atomic", "Cas"}: struct{}{},
@ -835,7 +835,7 @@ var wantIntrinsics = map[testIntrinsicKey]struct{}{
{"mipsle", "sync/atomic", "SwapInt32"}: struct{}{},
{"mipsle", "sync/atomic", "SwapUint32"}: struct{}{},
{"mipsle", "sync/atomic", "SwapUintptr"}: struct{}{},
{"mipsle", "crypto/subtle", "constantTimeBoolToUint8"}: struct{}{},
{"mipsle", "crypto/internal/constanttime", "boolToUint8"}: struct{}{},
{"ppc64", "internal/runtime/atomic", "And"}: struct{}{},
{"ppc64", "internal/runtime/atomic", "And8"}: struct{}{},
{"ppc64", "internal/runtime/atomic", "Cas"}: struct{}{},
@ -960,8 +960,8 @@ var wantIntrinsics = map[testIntrinsicKey]struct{}{
{"ppc64", "sync/atomic", "SwapUint32"}: struct{}{},
{"ppc64", "sync/atomic", "SwapUint64"}: struct{}{},
{"ppc64", "sync/atomic", "SwapUintptr"}: struct{}{},
{"ppc64", "crypto/subtle", "ConstantTimeSelect"}: struct{}{},
{"ppc64", "crypto/subtle", "constantTimeBoolToUint8"}: struct{}{},
{"ppc64", "crypto/internal/constanttime", "Select"}: struct{}{},
{"ppc64", "crypto/internal/constanttime", "boolToUint8"}: struct{}{},
{"ppc64le", "internal/runtime/atomic", "And"}: struct{}{},
{"ppc64le", "internal/runtime/atomic", "And8"}: struct{}{},
{"ppc64le", "internal/runtime/atomic", "Cas"}: struct{}{},
@ -1086,8 +1086,8 @@ var wantIntrinsics = map[testIntrinsicKey]struct{}{
{"ppc64le", "sync/atomic", "SwapUint32"}: struct{}{},
{"ppc64le", "sync/atomic", "SwapUint64"}: struct{}{},
{"ppc64le", "sync/atomic", "SwapUintptr"}: struct{}{},
{"ppc64le", "crypto/subtle", "ConstantTimeSelect"}: struct{}{},
{"ppc64le", "crypto/subtle", "constantTimeBoolToUint8"}: struct{}{},
{"ppc64le", "crypto/internal/constanttime", "Select"}: struct{}{},
{"ppc64le", "crypto/internal/constanttime", "boolToUint8"}: struct{}{},
{"riscv64", "internal/runtime/atomic", "And"}: struct{}{},
{"riscv64", "internal/runtime/atomic", "And8"}: struct{}{},
{"riscv64", "internal/runtime/atomic", "Cas"}: struct{}{},
@ -1208,7 +1208,7 @@ var wantIntrinsics = map[testIntrinsicKey]struct{}{
{"riscv64", "sync/atomic", "SwapUint32"}: struct{}{},
{"riscv64", "sync/atomic", "SwapUint64"}: struct{}{},
{"riscv64", "sync/atomic", "SwapUintptr"}: struct{}{},
{"riscv64", "crypto/subtle", "constantTimeBoolToUint8"}: struct{}{},
{"riscv64", "crypto/internal/constanttime", "boolToUint8"}: struct{}{},
{"s390x", "internal/runtime/atomic", "And"}: struct{}{},
{"s390x", "internal/runtime/atomic", "And8"}: struct{}{},
{"s390x", "internal/runtime/atomic", "Cas"}: struct{}{},
@ -1327,7 +1327,7 @@ var wantIntrinsics = map[testIntrinsicKey]struct{}{
{"s390x", "sync/atomic", "SwapUint32"}: struct{}{},
{"s390x", "sync/atomic", "SwapUint64"}: struct{}{},
{"s390x", "sync/atomic", "SwapUintptr"}: struct{}{},
{"s390x", "crypto/subtle", "constantTimeBoolToUint8"}: struct{}{},
{"s390x", "crypto/internal/constanttime", "boolToUint8"}: struct{}{},
{"wasm", "internal/runtime/sys", "GetCallerPC"}: struct{}{},
{"wasm", "internal/runtime/sys", "GetCallerSP"}: struct{}{},
{"wasm", "internal/runtime/sys", "GetClosurePtr"}: struct{}{},
@ -1363,8 +1363,8 @@ var wantIntrinsics = map[testIntrinsicKey]struct{}{
{"wasm", "math/bits", "TrailingZeros8"}: struct{}{},
{"wasm", "runtime", "KeepAlive"}: struct{}{},
{"wasm", "runtime", "slicebytetostringtmp"}: struct{}{},
{"wasm", "crypto/subtle", "ConstantTimeSelect"}: struct{}{},
{"wasm", "crypto/subtle", "constantTimeBoolToUint8"}: struct{}{},
{"wasm", "crypto/internal/constanttime", "Select"}: struct{}{},
{"wasm", "crypto/internal/constanttime", "boolToUint8"}: struct{}{},
}
func TestIntrinsics(t *testing.T) {

View file

@ -0,0 +1,42 @@
// Copyright 2025 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 constanttime
// The functions in this package are compiler intrinsics for constant-time
// operations. They are exposed by crypto/subtle and used directly by the
// FIPS 140-3 module.
// Select returns x if v == 1 and y if v == 0.
// Its behavior is undefined if v takes any other value.
func Select(v, x, y int) int {
// This is intrinsicified on arches with CMOV.
// It implements the following superset behavior:
// ConstantTimeSelect returns x if v != 0 and y if v == 0.
// Do the same here to avoid non portable UB.
v = int(boolToUint8(v != 0))
return ^(v-1)&x | (v-1)&y
}
// ByteEq returns 1 if x == y and 0 otherwise.
func ByteEq(x, y uint8) int {
return int(boolToUint8(x == y))
}
// Eq returns 1 if x == y and 0 otherwise.
func Eq(x, y int32) int {
return int(boolToUint8(x == y))
}
// LessOrEq returns 1 if x <= y and 0 otherwise.
// Its behavior is undefined if x or y are negative or > 2**31 - 1.
func LessOrEq(x, y int) int {
return int(boolToUint8(x <= y))
}
// boolToUint8 is a compiler intrinsic.
// It returns 1 for true and 0 for false.
func boolToUint8(b bool) uint8 {
panic("unreachable; must be intrinsicified")
}

View file

@ -4,9 +4,7 @@
package edwards25519
import (
"crypto/internal/fips140/subtle"
)
import "crypto/internal/constanttime"
// A dynamic lookup table for variable-base, constant-time scalar muls.
type projLookupTable struct {
@ -95,7 +93,7 @@ func (v *projLookupTable) SelectInto(dest *projCached, x int8) {
dest.Zero()
for j := 1; j <= 8; j++ {
// Set dest = j*Q if |x| = j
cond := subtle.ConstantTimeByteEq(xabs, uint8(j))
cond := constanttime.ByteEq(xabs, uint8(j))
dest.Select(&v.points[j-1], dest, cond)
}
// Now dest = |x|*Q, conditionally negate to get x*Q
@ -111,7 +109,7 @@ func (v *affineLookupTable) SelectInto(dest *affineCached, x int8) {
dest.Zero()
for j := 1; j <= 8; j++ {
// Set dest = j*Q if |x| = j
cond := subtle.ConstantTimeByteEq(xabs, uint8(j))
cond := constanttime.ByteEq(xabs, uint8(j))
dest.Select(&v.points[j-1], dest, cond)
}
// Now dest = |x|*Q, conditionally negate to get x*Q

View file

@ -140,8 +140,8 @@ const tmplNISTEC = `// Copyright 2022 The Go Authors. All rights reserved.
package nistec
import (
"crypto/internal/constanttime"
"crypto/internal/fips140/nistec/fiat"
"crypto/internal/fips140/subtle"
"errors"
"sync"
)
@ -467,7 +467,7 @@ func (table *{{.p}}Table) Select(p *{{.P}}Point, n uint8) {
}
p.Set(New{{.P}}Point())
for i := uint8(1); i < 16; i++ {
cond := subtle.ConstantTimeByteEq(i, n)
cond := constanttime.ByteEq(i, n)
p.Select(table[i-1], p, cond)
}
}

View file

@ -7,8 +7,8 @@
package nistec
import (
"crypto/internal/constanttime"
"crypto/internal/fips140/nistec/fiat"
"crypto/internal/fips140/subtle"
"errors"
"sync"
)
@ -333,7 +333,7 @@ func (table *p224Table) Select(p *P224Point, n uint8) {
}
p.Set(NewP224Point())
for i := uint8(1); i < 16; i++ {
cond := subtle.ConstantTimeByteEq(i, n)
cond := constanttime.ByteEq(i, n)
p.Select(table[i-1], p, cond)
}
}

View file

@ -7,8 +7,8 @@
package nistec
import (
"crypto/internal/constanttime"
"crypto/internal/fips140/nistec/fiat"
"crypto/internal/fips140/subtle"
"crypto/internal/fips140deps/byteorder"
"crypto/internal/fips140deps/cpu"
"errors"
@ -458,7 +458,7 @@ func (table *p256Table) Select(p *P256Point, n uint8) {
}
p.Set(NewP256Point())
for i := uint8(1); i <= 16; i++ {
cond := subtle.ConstantTimeByteEq(i, n)
cond := constanttime.ByteEq(i, n)
p.Select(&table[i-1], p, cond)
}
}
@ -553,7 +553,7 @@ func (table *p256AffineTable) Select(p *p256AffinePoint, n uint8) {
panic("nistec: internal error: p256AffineTable.Select called with out-of-bounds value")
}
for i := uint8(1); i <= 32; i++ {
cond := subtle.ConstantTimeByteEq(i, n)
cond := constanttime.ByteEq(i, n)
p.x.Select(&table[i-1].x, &p.x, cond)
p.y.Select(&table[i-1].y, &p.y, cond)
}
@ -618,7 +618,7 @@ func (p *P256Point) ScalarBaseMult(scalar []byte) (*P256Point, error) {
// the point at infinity (because infinity can't be represented in affine
// coordinates). Here we conditionally set p to the infinity if sel is zero.
// In the loop, that's handled by AddAffine.
selIsZero := subtle.ConstantTimeByteEq(sel, 0)
selIsZero := constanttime.ByteEq(sel, 0)
p.Select(NewP256Point(), t.Projective(), selIsZero)
for index >= 5 {
@ -636,7 +636,7 @@ func (p *P256Point) ScalarBaseMult(scalar []byte) (*P256Point, error) {
table := &p256GeneratorTables[(index+1)/6]
table.Select(t, sel)
t.Negate(sign)
selIsZero := subtle.ConstantTimeByteEq(sel, 0)
selIsZero := constanttime.ByteEq(sel, 0)
p.AddAffine(p, t, selIsZero)
}

View file

@ -7,8 +7,8 @@
package nistec
import (
"crypto/internal/constanttime"
"crypto/internal/fips140/nistec/fiat"
"crypto/internal/fips140/subtle"
"errors"
"sync"
)
@ -333,7 +333,7 @@ func (table *p384Table) Select(p *P384Point, n uint8) {
}
p.Set(NewP384Point())
for i := uint8(1); i < 16; i++ {
cond := subtle.ConstantTimeByteEq(i, n)
cond := constanttime.ByteEq(i, n)
p.Select(table[i-1], p, cond)
}
}

View file

@ -7,8 +7,8 @@
package nistec
import (
"crypto/internal/constanttime"
"crypto/internal/fips140/nistec/fiat"
"crypto/internal/fips140/subtle"
"errors"
"sync"
)
@ -333,7 +333,7 @@ func (table *p521Table) Select(p *P521Point, n uint8) {
}
p.Set(NewP521Point())
for i := uint8(1); i < 16; i++ {
cond := subtle.ConstantTimeByteEq(i, n)
cond := constanttime.ByteEq(i, n)
p.Select(table[i-1], p, cond)
}
}

View file

@ -9,6 +9,7 @@ package rsa
import (
"bytes"
"crypto/internal/constanttime"
"crypto/internal/fips140"
"crypto/internal/fips140/drbg"
"crypto/internal/fips140/sha256"
@ -432,7 +433,7 @@ func DecryptOAEP(hash, mgfHash hash.Hash, priv *PrivateKey, ciphertext []byte, l
hash.Write(label)
lHash := hash.Sum(nil)
firstByteIsZero := subtle.ConstantTimeByteEq(em[0], 0)
firstByteIsZero := constanttime.ByteEq(em[0], 0)
seed := em[1 : hash.Size()+1]
db := em[hash.Size()+1:]
@ -458,11 +459,11 @@ func DecryptOAEP(hash, mgfHash hash.Hash, priv *PrivateKey, ciphertext []byte, l
rest := db[hash.Size():]
for i := 0; i < len(rest); i++ {
equals0 := subtle.ConstantTimeByteEq(rest[i], 0)
equals1 := subtle.ConstantTimeByteEq(rest[i], 1)
index = subtle.ConstantTimeSelect(lookingForIndex&equals1, i, index)
lookingForIndex = subtle.ConstantTimeSelect(equals1, 0, lookingForIndex)
invalid = subtle.ConstantTimeSelect(lookingForIndex&^equals0, 1, invalid)
equals0 := constanttime.ByteEq(rest[i], 0)
equals1 := constanttime.ByteEq(rest[i], 1)
index = constanttime.Select(lookingForIndex&equals1, i, index)
lookingForIndex = constanttime.Select(equals1, 0, lookingForIndex)
invalid = constanttime.Select(lookingForIndex&^equals0, 1, invalid)
}
if firstByteIsZero&lHash2Good&^invalid&^lookingForIndex != 1 {

View file

@ -5,6 +5,7 @@
package subtle
import (
"crypto/internal/constanttime"
"crypto/internal/fips140deps/byteorder"
"math/bits"
)
@ -24,7 +25,7 @@ func ConstantTimeCompare(x, y []byte) int {
v |= x[i] ^ y[i]
}
return ConstantTimeByteEq(v, 0)
return constanttime.ByteEq(v, 0)
}
// ConstantTimeLessOrEqBytes returns 1 if x <= y and 0 otherwise. The comparison
@ -58,20 +59,6 @@ func ConstantTimeLessOrEqBytes(x, y []byte) int {
return int(b ^ 1)
}
// ConstantTimeSelect returns x if v == 1 and y if v == 0.
// Its behavior is undefined if v takes any other value.
func ConstantTimeSelect(v, x, y int) int { return ^(v-1)&x | (v-1)&y }
// ConstantTimeByteEq returns 1 if x == y and 0 otherwise.
func ConstantTimeByteEq(x, y uint8) int {
return int((uint32(x^y) - 1) >> 31)
}
// ConstantTimeEq returns 1 if x == y and 0 otherwise.
func ConstantTimeEq(x, y int32) int {
return int((uint64(uint32(x^y)) - 1) >> 63)
}
// ConstantTimeCopy copies the contents of y into x (a slice of equal length)
// if v == 1. If v == 0, x is left unchanged. Its behavior is undefined if v
// takes any other value.
@ -86,11 +73,3 @@ func ConstantTimeCopy(v int, x, y []byte) {
x[i] = x[i]&xmask | y[i]&ymask
}
}
// ConstantTimeLessOrEq returns 1 if x <= y and 0 otherwise.
// Its behavior is undefined if x or y are negative or > 2**31 - 1.
func ConstantTimeLessOrEq(x, y int) int {
x32 := int32(x)
y32 := int32(y)
return int(((x32 - y32 - 1) >> 31) & 1)
}

View file

@ -28,6 +28,9 @@ var AllowedInternalPackages = map[string]bool{
// randutil.MaybeReadByte is used in non-FIPS mode by GenerateKey functions.
"crypto/internal/randutil": true,
// constanttime are the constant-time intrinsics.
"crypto/internal/constanttime": true,
}
func TestImports(t *testing.T) {

View file

@ -6,63 +6,47 @@
// code but require careful thought to use correctly.
package subtle
import "crypto/internal/fips140/subtle"
import (
"crypto/internal/constanttime"
"crypto/internal/fips140/subtle"
)
// These functions are forwarded to crypto/internal/constanttime for intrinsified
// operations, and to crypto/internal/fips140/subtle for byte slice operations.
// ConstantTimeCompare returns 1 if the two slices, x and y, have equal contents
// and 0 otherwise. The time taken is a function of the length of the slices and
// is independent of the contents. If the lengths of x and y do not match it
// returns 0 immediately.
func ConstantTimeCompare(x, y []byte) int {
if len(x) != len(y) {
return 0
}
var v byte
for i := 0; i < len(x); i++ {
v |= x[i] ^ y[i]
}
return ConstantTimeByteEq(v, 0)
return subtle.ConstantTimeCompare(x, y)
}
// ConstantTimeSelect returns x if v == 1 and y if v == 0.
// Its behavior is undefined if v takes any other value.
func ConstantTimeSelect(v, x, y int) int {
// This is intrinsicified on arches with CMOV.
// It implements the following superset behavior:
// ConstantTimeSelect returns x if v != 0 and y if v == 0.
// Do the same here to avoid non portable UB.
v = int(constantTimeBoolToUint8(v != 0))
return ^(v-1)&x | (v-1)&y
return constanttime.Select(v, x, y)
}
// ConstantTimeByteEq returns 1 if x == y and 0 otherwise.
func ConstantTimeByteEq(x, y uint8) int {
return int(constantTimeBoolToUint8(x == y))
return constanttime.ByteEq(x, y)
}
// ConstantTimeEq returns 1 if x == y and 0 otherwise.
func ConstantTimeEq(x, y int32) int {
return int(constantTimeBoolToUint8(x == y))
return constanttime.Eq(x, y)
}
// ConstantTimeCopy copies the contents of y into x (a slice of equal length)
// if v == 1. If v == 0, x is left unchanged. Its behavior is undefined if v
// takes any other value.
func ConstantTimeCopy(v int, x, y []byte) {
// Forward this one since it gains nothing from compiler intrinsics.
subtle.ConstantTimeCopy(v, x, y)
}
// ConstantTimeLessOrEq returns 1 if x <= y and 0 otherwise.
// Its behavior is undefined if x or y are negative or > 2**31 - 1.
func ConstantTimeLessOrEq(x, y int) int {
return int(constantTimeBoolToUint8(x <= y))
}
// constantTimeBoolToUint8 is a compiler intrinsic.
// It returns 1 for true and 0 for false.
func constantTimeBoolToUint8(b bool) uint8 {
panic("unreachable; must be intrinsicified")
return constanttime.LessOrEq(x, y)
}

View file

@ -479,6 +479,8 @@ var depsRules = `
io, math/rand/v2 < crypto/internal/randutil;
NONE < crypto/internal/constanttime;
STR < crypto/internal/impl;
OS < crypto/internal/sysrand
@ -496,6 +498,7 @@ var depsRules = `
crypto/internal/impl,
crypto/internal/entropy,
crypto/internal/randutil,
crypto/internal/constanttime,
crypto/internal/entropy/v1.0.0,
crypto/internal/fips140deps/byteorder,
crypto/internal/fips140deps/cpu,