From fe080173033908f0f69b2dbf4a7ba1eaaea194bf Mon Sep 17 00:00:00 2001 From: Mateusz Poliwczak Date: Fri, 20 Feb 2026 10:56:04 +0100 Subject: [PATCH] crypto/rand: remove exported fields in the default Reader Uses a clever trick with embeddeding such that, the Reader is 0-byte and does not have any exported fields. This change is not really a must, but I think it is better not to have any exported fields that could be accessed with reflect or such. Change-Id: Ib2e0b71d1e56b74a608601c98a81c8646a6a6964 Reviewed-on: https://go-review.googlesource.com/c/go/+/747460 Reviewed-by: David Chase LUCI-TryBot-Result: Go LUCI Reviewed-by: Roland Shoemaker --- src/crypto/internal/fips140/drbg/rand.go | 16 +++++++++---- .../internal/fips140only/fips140only.go | 7 ------ .../fips140only/random_fips140v1.0.go | 17 +++++++++++++ .../fips140only/random_fips140v1.28.go | 16 +++++++++++++ src/crypto/internal/rand/rand.go | 16 ++++--------- .../internal/rand/random_fips140v1.0.go | 20 ++++++++++++++++ .../internal/rand/random_fips140v1.28.go | 19 +++++++++++++++ src/crypto/rand/rand_test.go | 24 +++++++++++++++++++ 8 files changed, 113 insertions(+), 22 deletions(-) create mode 100644 src/crypto/internal/fips140only/random_fips140v1.0.go create mode 100644 src/crypto/internal/fips140only/random_fips140v1.28.go create mode 100644 src/crypto/internal/rand/random_fips140v1.0.go create mode 100644 src/crypto/internal/rand/random_fips140v1.28.go diff --git a/src/crypto/internal/fips140/drbg/rand.go b/src/crypto/internal/fips140/drbg/rand.go index 949e74ac60..44284f93d8 100644 --- a/src/crypto/internal/fips140/drbg/rand.go +++ b/src/crypto/internal/fips140/drbg/rand.go @@ -124,14 +124,22 @@ func SetTestingReader(r io.Reader) { // [crypto/rand.Reader], used to recognize it when passed to // APIs that accept a rand io.Reader. // -// Any Reader that implements this interface is assumed to -// call [Read] as its Read method. -type DefaultReader interface{ defaultReader() } +// Any [io.Reader] that embeds this type is assumed to +// call [Read] as its [io.Reader.Read] method. +type DefaultReader struct{} + +func (d DefaultReader) defaultReader() {} + +// IsDefaultReader reports whether the r embeds the [DefaultReader] type. +func IsDefaultReader(r io.Reader) bool { + _, ok := r.(interface{ defaultReader() }) + return ok +} // ReadWithReader uses Reader to fill b with cryptographically secure random // bytes. It is intended for use in APIs that expose a rand io.Reader. func ReadWithReader(r io.Reader, b []byte) error { - if _, ok := r.(DefaultReader); ok { + if IsDefaultReader(r) { Read(b) return nil } diff --git a/src/crypto/internal/fips140only/fips140only.go b/src/crypto/internal/fips140only/fips140only.go index 1b0a4be6ba..a8d840b170 100644 --- a/src/crypto/internal/fips140only/fips140only.go +++ b/src/crypto/internal/fips140only/fips140only.go @@ -6,12 +6,10 @@ package fips140only import ( "crypto/fips140" - "crypto/internal/fips140/drbg" "crypto/internal/fips140/sha256" "crypto/internal/fips140/sha3" "crypto/internal/fips140/sha512" "hash" - "io" ) // Enforced reports whether FIPS 140-only mode is enabled and enforced, in which non-approved @@ -28,8 +26,3 @@ func ApprovedHash(h hash.Hash) bool { return false } } - -func ApprovedRandomReader(r io.Reader) bool { - _, ok := r.(drbg.DefaultReader) - return ok -} diff --git a/src/crypto/internal/fips140only/random_fips140v1.0.go b/src/crypto/internal/fips140only/random_fips140v1.0.go new file mode 100644 index 0000000000..f402f308bc --- /dev/null +++ b/src/crypto/internal/fips140only/random_fips140v1.0.go @@ -0,0 +1,17 @@ +// Copyright 2026 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. + +//go:build fips140v1.0 || fips140v1.26 + +package fips140only + +import ( + "crypto/internal/fips140/drbg" + "io" +) + +func ApprovedRandomReader(r io.Reader) bool { + _, ok := r.(drbg.DefaultReader) + return ok +} diff --git a/src/crypto/internal/fips140only/random_fips140v1.28.go b/src/crypto/internal/fips140only/random_fips140v1.28.go new file mode 100644 index 0000000000..b03b9ef537 --- /dev/null +++ b/src/crypto/internal/fips140only/random_fips140v1.28.go @@ -0,0 +1,16 @@ +// Copyright 2026 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. + +//go:build !(fips140v1.0 || fips140v1.26) + +package fips140only + +import ( + "crypto/internal/fips140/drbg" + "io" +) + +func ApprovedRandomReader(r io.Reader) bool { + return drbg.IsDefaultReader(r) +} diff --git a/src/crypto/internal/rand/rand.go b/src/crypto/internal/rand/rand.go index 29648b9f38..00e736a579 100644 --- a/src/crypto/internal/rand/rand.go +++ b/src/crypto/internal/rand/rand.go @@ -13,9 +13,11 @@ import ( _ "unsafe" ) -type reader struct { - drbg.DefaultReader -} +// defaultReader aliases [drbg.DefaultReader], so that [reader] +// does not have an exported DefaultReader field. +type defaultReader = drbg.DefaultReader + +type reader struct{ defaultReader } func (r reader) Read(b []byte) (n int, err error) { if boring.Enabled { @@ -63,11 +65,3 @@ func CustomReader(r io.Reader) io.Reader { } return Reader } - -// IsDefaultReader reports whether r is the default [crypto/rand.Reader]. -// -// If true, the Read method of r can be assumed to call [drbg.Read]. -func IsDefaultReader(r io.Reader) bool { - _, ok := r.(drbg.DefaultReader) - return ok -} diff --git a/src/crypto/internal/rand/random_fips140v1.0.go b/src/crypto/internal/rand/random_fips140v1.0.go new file mode 100644 index 0000000000..7314157443 --- /dev/null +++ b/src/crypto/internal/rand/random_fips140v1.0.go @@ -0,0 +1,20 @@ +// Copyright 2026 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. + +//go:build fips140v1.0 || fips140v1.26 + +package rand + +import ( + "crypto/internal/fips140/drbg" + "io" +) + +// IsDefaultReader reports whether r is the default [crypto/rand.Reader]. +// +// If true, the Read method of r can be assumed to call [drbg.Read]. +func IsDefaultReader(r io.Reader) bool { + _, ok := r.(drbg.DefaultReader) + return ok +} diff --git a/src/crypto/internal/rand/random_fips140v1.28.go b/src/crypto/internal/rand/random_fips140v1.28.go new file mode 100644 index 0000000000..b9dafecff6 --- /dev/null +++ b/src/crypto/internal/rand/random_fips140v1.28.go @@ -0,0 +1,19 @@ +// Copyright 2026 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. + +//go:build !(fips140v1.0 || fips140v1.26) + +package rand + +import ( + "crypto/internal/fips140/drbg" + "io" +) + +// IsDefaultReader reports whether r is the default [crypto/rand.Reader]. +// +// If true, the Read method of r can be assumed to call [drbg.Read]. +func IsDefaultReader(r io.Reader) bool { + return drbg.IsDefaultReader(r) +} diff --git a/src/crypto/rand/rand_test.go b/src/crypto/rand/rand_test.go index 3bb3d5f1ac..13e4a65db2 100644 --- a/src/crypto/rand/rand_test.go +++ b/src/crypto/rand/rand_test.go @@ -8,10 +8,12 @@ import ( "bytes" "compress/flate" "crypto/internal/cryptotest" + "crypto/internal/rand" "errors" "internal/testenv" "io" "os" + "reflect" "sync" "testing" ) @@ -213,3 +215,25 @@ func benchmarkRead(b *testing.B, size int) { } } } + +func TestDefaultReader(t *testing.T) { + if !rand.IsDefaultReader(Reader) { + t.Error("rand.IsDefaultReader(Reader) == False") + } + + typ := reflect.ValueOf(Reader).Type() + for method := range typ.Methods() { + if method.Name == "Read" { + continue + } + if method.IsExported() { + t.Fatal("unexpected exported method") + } + } + + for field := range typ.Fields() { + if field.IsExported() { + t.Fatal("unexpected exported field") + } + } +}