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 <drchase@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Roland Shoemaker <roland@golang.org>
This commit is contained in:
Mateusz Poliwczak
2026-02-20 10:56:04 +01:00
parent 4e693d1ec5
commit fe08017303
8 changed files with 113 additions and 22 deletions

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

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

View File

@@ -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
}

View File

@@ -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
}

View File

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

View File

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