diff --git a/src/cmd/compile/internal/ssagen/intrinsics.go b/src/cmd/compile/internal/ssagen/intrinsics.go index bf9e71c1701..190c4840ce9 100644 --- a/src/cmd/compile/internal/ssagen/intrinsics.go +++ b/src/cmd/compile/internal/ssagen/intrinsics.go @@ -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]) }, diff --git a/src/cmd/compile/internal/ssagen/intrinsics_test.go b/src/cmd/compile/internal/ssagen/intrinsics_test.go index 9311f843454..782426215c9 100644 --- a/src/cmd/compile/internal/ssagen/intrinsics_test.go +++ b/src/cmd/compile/internal/ssagen/intrinsics_test.go @@ -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) { diff --git a/src/crypto/internal/constanttime/constant_time.go b/src/crypto/internal/constanttime/constant_time.go new file mode 100644 index 00000000000..55253071956 --- /dev/null +++ b/src/crypto/internal/constanttime/constant_time.go @@ -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") +} diff --git a/src/crypto/internal/fips140/edwards25519/tables.go b/src/crypto/internal/fips140/edwards25519/tables.go index 801b76771d1..7da3f7b15bc 100644 --- a/src/crypto/internal/fips140/edwards25519/tables.go +++ b/src/crypto/internal/fips140/edwards25519/tables.go @@ -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 diff --git a/src/crypto/internal/fips140/nistec/generate.go b/src/crypto/internal/fips140/nistec/generate.go index 7786dc556f5..75b1ac60f0b 100644 --- a/src/crypto/internal/fips140/nistec/generate.go +++ b/src/crypto/internal/fips140/nistec/generate.go @@ -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) } } diff --git a/src/crypto/internal/fips140/nistec/p224.go b/src/crypto/internal/fips140/nistec/p224.go index 82bced251fe..7965b186891 100644 --- a/src/crypto/internal/fips140/nistec/p224.go +++ b/src/crypto/internal/fips140/nistec/p224.go @@ -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) } } diff --git a/src/crypto/internal/fips140/nistec/p256.go b/src/crypto/internal/fips140/nistec/p256.go index c957c542473..650bde4e73e 100644 --- a/src/crypto/internal/fips140/nistec/p256.go +++ b/src/crypto/internal/fips140/nistec/p256.go @@ -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) } diff --git a/src/crypto/internal/fips140/nistec/p384.go b/src/crypto/internal/fips140/nistec/p384.go index 318c08a9797..352f1a806e8 100644 --- a/src/crypto/internal/fips140/nistec/p384.go +++ b/src/crypto/internal/fips140/nistec/p384.go @@ -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) } } diff --git a/src/crypto/internal/fips140/nistec/p521.go b/src/crypto/internal/fips140/nistec/p521.go index 8ade8a33040..429f6379934 100644 --- a/src/crypto/internal/fips140/nistec/p521.go +++ b/src/crypto/internal/fips140/nistec/p521.go @@ -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) } } diff --git a/src/crypto/internal/fips140/rsa/pkcs1v22.go b/src/crypto/internal/fips140/rsa/pkcs1v22.go index 94e7345996a..29c47069a3e 100644 --- a/src/crypto/internal/fips140/rsa/pkcs1v22.go +++ b/src/crypto/internal/fips140/rsa/pkcs1v22.go @@ -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 { diff --git a/src/crypto/internal/fips140/subtle/constant_time.go b/src/crypto/internal/fips140/subtle/constant_time.go index fa7a002d3fa..fc1e3079855 100644 --- a/src/crypto/internal/fips140/subtle/constant_time.go +++ b/src/crypto/internal/fips140/subtle/constant_time.go @@ -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) -} diff --git a/src/crypto/internal/fips140deps/fipsdeps_test.go b/src/crypto/internal/fips140deps/fipsdeps_test.go index 3eaae1830d0..29a56047c3c 100644 --- a/src/crypto/internal/fips140deps/fipsdeps_test.go +++ b/src/crypto/internal/fips140deps/fipsdeps_test.go @@ -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) { diff --git a/src/crypto/subtle/constant_time.go b/src/crypto/subtle/constant_time.go index 8eeff3b629b..14c911101b0 100644 --- a/src/crypto/subtle/constant_time.go +++ b/src/crypto/subtle/constant_time.go @@ -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) } diff --git a/src/go/build/deps_test.go b/src/go/build/deps_test.go index bc7eae69def..48a9f3e75bb 100644 --- a/src/go/build/deps_test.go +++ b/src/go/build/deps_test.go @@ -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,