Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for CSHAKE #80

Merged
merged 12 commits into from
Jan 7, 2025
186 changes: 87 additions & 99 deletions cng/hash.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ import (
"github.com/microsoft/go-crypto-winnative/internal/bcrypt"
)

// maxHashSize is the size of SHA512 and SHA3_512, the largest hashes we support.
const maxHashSize = 64

// SupportsHash returns true if a hash.Hash implementation is supported for h.
func SupportsHash(h crypto.Hash) bool {
switch h {
Expand Down Expand Up @@ -84,27 +87,6 @@ func SHA512(p []byte) (sum [64]byte) {
return
}

func SHA3_256(p []byte) (sum [32]byte) {
if err := hashOneShot(bcrypt.SHA3_256_ALGORITHM, p, sum[:]); err != nil {
panic("bcrypt: SHA3_256 failed")
}
return
}

func SHA3_384(p []byte) (sum [48]byte) {
if err := hashOneShot(bcrypt.SHA3_384_ALGORITHM, p, sum[:]); err != nil {
panic("bcrypt: SHA3_384 failed")
}
return
}

func SHA3_512(p []byte) (sum [64]byte) {
if err := hashOneShot(bcrypt.SHA3_512_ALGORITHM, p, sum[:]); err != nil {
panic("bcrypt: SHA3_512 failed")
}
return
}

// NewMD4 returns a new MD4 hash.
func NewMD4() hash.Hash {
return newHashX(bcrypt.MD4_ALGORITHM, bcrypt.ALG_NONE_FLAG, nil)
Expand Down Expand Up @@ -135,21 +117,6 @@ func NewSHA512() hash.Hash {
return newHashX(bcrypt.SHA512_ALGORITHM, bcrypt.ALG_NONE_FLAG, nil)
}

// NewSHA3_256 returns a new SHA256 hash.
func NewSHA3_256() hash.Hash {
return newHashX(bcrypt.SHA3_256_ALGORITHM, bcrypt.ALG_NONE_FLAG, nil)
}

// NewSHA3_384 returns a new SHA384 hash.
func NewSHA3_384() hash.Hash {
return newHashX(bcrypt.SHA3_384_ALGORITHM, bcrypt.ALG_NONE_FLAG, nil)
}

// NewSHA3_512 returns a new SHA512 hash.
func NewSHA3_512() hash.Hash {
return newHashX(bcrypt.SHA3_512_ALGORITHM, bcrypt.ALG_NONE_FLAG, nil)
}

type hashAlgorithm struct {
handle bcrypt.ALG_HANDLE
id string
Expand Down Expand Up @@ -181,11 +148,11 @@ func hashToID(h hash.Hash) string {
return hx.alg.id
}

// hashX implements [hash.Hash].
type hashX struct {
alg *hashAlgorithm
_ctx bcrypt.HASH_HANDLE // access it using withCtx
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This pattern seems like it makes it easier to spot bad access vs. sprinkling runtime.KeepAlive, why the change? (Also IMO would be good for golang-fips/openssl#238 (review).)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is easy to forget to use withCtx (it happened in the OpenSSL module), so it is not a silver bullet. I prefer the runtime.KeepAlive pattern because it doesn't add a nested block on each function that access the context (thus the code is simpler), and also to keep consistency with other algorithms, which tend to use the runtime.KeepAlive rather than custom withX functions.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, if withCtx didn't help in practice, I agree that simplifying to runtime.KeepAlive makes sense. (If it did help, I would have said that it would make sense to expand that style to other algorithms as the way to get to consistency. 😄)

alg *hashAlgorithm
ctx bcrypt.HASH_HANDLE

buf []byte
key []byte
}

Expand All @@ -196,88 +163,75 @@ func newHashX(id string, flag bcrypt.AlgorithmProviderFlags, key []byte) *hashX
panic(err)
}
h := &hashX{alg: alg, key: bytes.Clone(key)}
// Don't allocate hx.buf nor call bcrypt.CreateHash yet,
// which would be wasteful if the caller only wants to know
// the hash type. This is a common pattern in this package,
// as some functions accept a `func() hash.Hash` parameter
// and call it just to know the hash type.
runtime.SetFinalizer(h, (*hashX).finalize)
// Don't call bcrypt.CreateHash yet, it would be wasteful
// if the caller only wants to know the hash type. This
// is a common pattern in this package, as some functions
// accept a `func() hash.Hash` parameter and call it just
// to know the hash type.
return h
}

func (h *hashX) finalize() {
if h._ctx != 0 {
bcrypt.DestroyHash(h._ctx)
}
bcrypt.DestroyHash(h.ctx)
}

func (h *hashX) withCtx(fn func(ctx bcrypt.HASH_HANDLE) error) error {
func (h *hashX) init() {
defer runtime.KeepAlive(h)
if h._ctx == 0 {
err := bcrypt.CreateHash(h.alg.handle, &h._ctx, nil, h.key, 0)
if err != nil {
panic(err)
}
if h.ctx != 0 {
return
}
err := bcrypt.CreateHash(h.alg.handle, &h.ctx, nil, h.key, bcrypt.HASH_REUSABLE_FLAG)
if err != nil {
panic(err)
}
return fn(h._ctx)
runtime.SetFinalizer(h, (*hashX).finalize)
}

func (h *hashX) Clone() (hash.Hash, error) {
defer runtime.KeepAlive(h)
h2 := &hashX{alg: h.alg, key: bytes.Clone(h.key)}
err := h.withCtx(func(ctx bcrypt.HASH_HANDLE) error {
return bcrypt.DuplicateHash(ctx, &h2._ctx, nil, 0)
})
if err != nil {
return nil, err
if h.ctx != 0 {
err := bcrypt.DuplicateHash(h.ctx, &h2.ctx, nil, 0)
if err != nil {
return nil, err
}
runtime.SetFinalizer(h2, (*hashX).finalize)
}
runtime.SetFinalizer(h2, (*hashX).finalize)
return h2, nil
}

func (h *hashX) Reset() {
if h._ctx != 0 {
bcrypt.DestroyHash(h._ctx)
h._ctx = 0
defer runtime.KeepAlive(h)
if h.ctx != 0 {
hashReset(h.ctx, h.Size())
}
}

func (h *hashX) Write(p []byte) (n int, err error) {
err = h.withCtx(func(ctx bcrypt.HASH_HANDLE) error {
for n < len(p) && err == nil {
nn := len32(p[n:])
err = bcrypt.HashData(h._ctx, p[n:n+nn], 0)
n += nn
}
return err
})
if err != nil {
// hash.Hash interface mandates Write should never return an error.
panic(err)
}
defer runtime.KeepAlive(h)
h.init()
hashData(h.ctx, p)
return len(p), nil
}

func (h *hashX) WriteString(s string) (int, error) {
// TODO: use unsafe.StringData once we drop support
// for go1.19 and earlier.
hdr := (*struct {
Data *byte
Len int
})(unsafe.Pointer(&s))
return h.Write(unsafe.Slice(hdr.Data, len(s)))
defer runtime.KeepAlive(h)
return h.Write(unsafe.Slice(unsafe.StringData(s), len(s)))
}

func (h *hashX) WriteByte(c byte) error {
err := h.withCtx(func(ctx bcrypt.HASH_HANDLE) error {
return bcrypt.HashDataRaw(h._ctx, &c, 1, 0)
})
if err != nil {
// hash.Hash interface mandates Write should never return an error.
panic(err)
}
defer runtime.KeepAlive(h)
h.init()
hashByte(h.ctx, c)
return nil
}

func (h *hashX) Sum(in []byte) []byte {
defer runtime.KeepAlive(h)
h.init()
return hashSum(h.ctx, h.Size(), in)
}

func (h *hashX) Size() int {
return int(h.alg.size)
}
Expand All @@ -286,21 +240,55 @@ func (h *hashX) BlockSize() int {
return int(h.alg.blockSize)
}

func (h *hashX) Sum(in []byte) []byte {
// hashData writes p to ctx. It panics on error.
func hashData(ctx bcrypt.HASH_HANDLE, p []byte) {
var n int
var err error
for n < len(p) && err == nil {
nn := len32(p[n:])
err = bcrypt.HashData(ctx, p[n:n+nn], 0)
n += nn
}
if err != nil {
panic(err)
}
}

// hashByte writes c to ctx. It panics on error.
func hashByte(ctx bcrypt.HASH_HANDLE, c byte) {
err := bcrypt.HashDataRaw(ctx, &c, 1, 0)
if err != nil {
panic(err)
}
}

// hashSum writes the hash of ctx to in and returns the result.
// size is the size of the hash output.
// It panics on error.
func hashSum(ctx bcrypt.HASH_HANDLE, size int, in []byte) []byte {
var ctx2 bcrypt.HASH_HANDLE
err := h.withCtx(func(ctx bcrypt.HASH_HANDLE) error {
return bcrypt.DuplicateHash(ctx, &ctx2, nil, 0)
})
err := bcrypt.DuplicateHash(ctx, &ctx2, nil, 0)
if err != nil {
panic(err)
}
defer bcrypt.DestroyHash(ctx2)
if h.buf == nil {
h.buf = make([]byte, h.alg.size)
}
err = bcrypt.FinishHash(ctx2, h.buf, 0)
buf := make([]byte, size, maxHashSize) // explicit cap to allow stack allocation
err = bcrypt.FinishHash(ctx2, buf, 0)
if err != nil {
panic(err)
}
return append(in, h.buf...)
return append(in, buf...)
}

// hashReset resets the hash state of ctx.
// size is the size of the hash output.
// It panics on error.
func hashReset(ctx bcrypt.HASH_HANDLE, size int) {
// bcrypt.FinishHash expects the output buffer to match the hash size.
// We don't care about the output, so we just pass a stack-allocated buffer
// that is large enough to hold the largest hash size we support.
var discard [maxHashSize]byte
if err := bcrypt.FinishHash(ctx, discard[:size], 0); err != nil {
panic(err)
}
}
12 changes: 6 additions & 6 deletions cng/hash_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,11 @@ func cryptoToHash(h crypto.Hash) func() hash.Hash {
case crypto.SHA512:
return cng.NewSHA512
case crypto.SHA3_256:
return cng.NewSHA3_256
return func() hash.Hash { return cng.NewSHA3_256() }
case crypto.SHA3_384:
return cng.NewSHA3_384
return func() hash.Hash { return cng.NewSHA3_384() }
case crypto.SHA3_512:
return cng.NewSHA3_512
return func() hash.Hash { return cng.NewSHA3_512() }
}
return nil
}
Expand Down Expand Up @@ -156,15 +156,15 @@ func TestHash_OneShot(t *testing.T) {
return b[:]
}},
{crypto.SHA3_256, func(p []byte) []byte {
b := cng.SHA3_256(p)
b := cng.SumSHA3_256(p)
return b[:]
}},
{crypto.SHA3_384, func(p []byte) []byte {
b := cng.SHA3_384(p)
b := cng.SumSHA3_384(p)
return b[:]
}},
{crypto.SHA3_512, func(p []byte) []byte {
b := cng.SHA3_512(p)
b := cng.SumSHA3_512(p)
return b[:]
}},
}
Expand Down
Loading