From 393b366a906f602e367a176a89cf78a860ebade8 Mon Sep 17 00:00:00 2001 From: qmuntal Date: Tue, 7 Jan 2025 15:48:24 +0100 Subject: [PATCH 01/13] implement CSHAKE support --- cshake.go | 243 +++++++++++++++++++++++++++++++++++++++++++++++ cshake_test.go | 251 +++++++++++++++++++++++++++++++++++++++++++++++++ goopenssl.c | 8 ++ goopenssl.h | 3 + shims.h | 11 ++- 5 files changed, 515 insertions(+), 1 deletion(-) create mode 100644 cshake.go create mode 100644 cshake_test.go diff --git a/cshake.go b/cshake.go new file mode 100644 index 00000000..a301acc1 --- /dev/null +++ b/cshake.go @@ -0,0 +1,243 @@ +//go:build !cmd_go_bootstrap + +package openssl + +// #include "goopenssl.h" +import "C" +import ( + "runtime" + "strconv" + "sync" + "unsafe" +) + +func shakeOneShot(size int, p []byte, sum []byte) bool { + return C.go_openssl_EVP_Digest(unsafe.Pointer(&*addr(p)), C.size_t(len(p)), (*C.uchar)(unsafe.Pointer(&*addr(sum))), nil, loadShake(size).md, nil) != 0 +} + +// SumSHAKE128 applies the SHAKE128 extendable output function to data and +// returns an output of the given length in bytes. +func SumSHAKE128(data []byte, length int) []byte { + out := make([]byte, length) + if !shakeOneShot(128, data, out[:]) { + panic("openssl: SumSHAKE128 failed") + } + return out +} + +// SumSHAKE256 applies the SHAKE256 extendable output function to data and +// returns an output of the given length in bytes. +func SumSHAKE256(data []byte, length int) []byte { + out := make([]byte, length) + if !shakeOneShot(256, data, out[:]) { + panic("openssl: SumSHAKE256 failed") + } + return out +} + +// SupportsSHAKE128 returns true if the SHAKE128 extendable output function is +// supported. +func SupportsSHAKE128() bool { + return supportsSHAKE(128) +} + +// SupportsSHAKE256 returns true if the SHAKE256 extendable output function is +// supported. +func SupportsSHAKE256() bool { + return supportsSHAKE(256) +} + +// SupportsCSHAKE128 returns true if the CSHAKE128 extendable output function is +// supported. +func SupportsCSHAKE128() bool { + return false +} + +// SupportsCSHAKE256 returns true if the CSHAKE256 extendable output function is +// supported. +func SupportsCSHAKE256() bool { + return false +} + +// cacheSHAKESupported is a cache of SHAKE size support. +var cacheSHAKESupported sync.Map + +// SupportsSHAKE returns true if the SHAKE extendable output function is +// supported. +func supportsSHAKE(size int) bool { + if vMajor == 1 || (vMajor == 3 && vMinor < 3) { + // SHAKE MD's are supported since OpenSSL 1.1.1, + // but EVP_DigestSqueeze is only supported since 3.3, + // and we need it to implement [sha3.SHAKE]. + return false + } + if v, ok := cacheSHAKESupported.Load(size); ok { + return v.(bool) + } + alg := loadShake(size) + if alg == nil { + cacheSHAKESupported.Store(size, false) + return false + } + // EVP_MD objects can be non-nil even when they can't be used + // in a EVP_MD_CTX, e.g. MD5 in FIPS mode. We need to prove + // if they can be used by passing them to a EVP_MD_CTX. + var supported bool + if ctx := C.go_openssl_EVP_MD_CTX_new(); ctx != nil { + supported = C.go_openssl_EVP_DigestInit_ex(ctx, alg.md, nil) == 1 + C.go_openssl_EVP_MD_CTX_free(ctx) + } + cacheSHAKESupported.Store(size, supported) + return supported +} + +// SHAKE is an instance of a SHAKE extendable output function. +type SHAKE struct { + alg *shakeAlgorithm + ctx C.GO_EVP_MD_CTX_PTR +} + +// NewSHAKE128 creates a new SHAKE128 XOF. +func NewSHAKE128() *SHAKE { + return newSHAKE(128) +} + +// NewSHAKE256 creates a new SHAKE256 XOF. +func NewSHAKE256() *SHAKE { + return newSHAKE(256) +} + +// NewCSHAKE128 creates a new cSHAKE128 XOF. +// +// N is used to define functions based on cSHAKE, it can be empty when plain +// cSHAKE is desired. S is a customization byte string used for domain +// separation. When N and S are both empty, this is equivalent to NewSHAKE128. +func NewCSHAKE128(N, S []byte) *SHAKE { + return nil +} + +// NewCSHAKE256 creates a new cSHAKE256 XOF. +// +// N is used to define functions based on cSHAKE, it can be empty when plain +// cSHAKE is desired. S is a customization byte string used for domain +// separation. When N and S are both empty, this is equivalent to NewSHAKE256. +func NewCSHAKE256(N, S []byte) *SHAKE { + return nil +} + +func newSHAKE(size int) *SHAKE { + if vMajor == 1 || (vMajor == 3 && vMinor < 3) { + panic("openssl: SHAKE is not supported by this version of OpenSSL") + + } + + alg := loadShake(size) + if alg == nil { + panic("openssl: unsupported SHAKE" + strconv.Itoa(size) + " function") + } + ctx := C.go_openssl_EVP_MD_CTX_new() + if ctx == nil { + panic(newOpenSSLError("EVP_MD_CTX_new")) + } + if C.go_openssl_EVP_DigestInit_ex(ctx, alg.md, nil) != 1 { + C.go_openssl_EVP_MD_CTX_free(ctx) + panic(newOpenSSLError("EVP_DigestInit_ex")) + } + if C.go_openssl_EVP_MD_CTX_ctrl(ctx, C.EVP_MD_CTRL_XOF_LEN, C.int(alg.xofLength), nil) != 1 { + C.go_openssl_EVP_MD_CTX_free(ctx) + panic(newOpenSSLError("EVP_MD_CTX_ctrl")) + } + s := &SHAKE{ctx: ctx} + runtime.SetFinalizer(s, (*SHAKE).finalize) + return s +} + +func (s *SHAKE) finalize() { + C.go_openssl_EVP_MD_CTX_free(s.ctx) +} + +// Write absorbs more data into the XOF's state. +// +// It panics if any output has already been read. +func (s *SHAKE) Write(p []byte) (n int, err error) { + defer runtime.KeepAlive(s) + if len(p) == 0 { + return 0, nil + } + if C.go_openssl_EVP_DigestUpdate(s.ctx, unsafe.Pointer(&*addr(p)), C.size_t(len(p))) != 1 { + panic(newOpenSSLError("EVP_DigestUpdate")) + } + return len(p), nil +} + +// Read squeezes more output from the XOF. +// +// Any call to Write after a call to Read will panic. +func (s *SHAKE) Read(p []byte) (n int, err error) { + defer runtime.KeepAlive(s) + if len(p) == 0 { + return 0, nil + } + if C.go_openssl_EVP_DigestSqueeze(s.ctx, (*C.uchar)(unsafe.Pointer(&*addr(p))), C.size_t(len(p))) != 1 { + panic(newOpenSSLError("EVP_DigestSqueeze")) + } + return len(p), nil +} + +// Reset resets the XOF to its initial state. +func (s *SHAKE) Reset() { + defer runtime.KeepAlive(s) + if C.go_openssl_EVP_DigestInit_ex(s.ctx, nil, nil) != 1 { + panic(newOpenSSLError("EVP_DigestInit_ex")) + } +} + +// BlockSize returns the rate of the XOF. +func (s *SHAKE) BlockSize() int { + return s.alg.blockSize +} + +// cacheSHAKE is a cache of SHAKE XOF length to GO_EVP_MD_PTR. +var cacheSHAKE sync.Map + +type shakeAlgorithm struct { + md C.GO_EVP_MD_PTR + blockSize int + xofLength int +} + +// loadShake converts a crypto.Hash to a EVP_MD. +func loadShake(xofLength int) *shakeAlgorithm { + if v, ok := cacheMD.Load(xofLength); ok { + return v.(*shakeAlgorithm) + } + + var shake shakeAlgorithm + switch xofLength { + case 32: + if versionAtOrAbove(1, 1, 0) { + shake.md = C.go_openssl_EVP_shake128() + shake.xofLength = 32 + } + case 64: + if versionAtOrAbove(1, 1, 0) { + shake.md = C.go_openssl_EVP_shake256() + shake.xofLength = 64 + } + } + if shake.md == nil { + cacheMD.Store(xofLength, (*hashAlgorithm)(nil)) + return nil + } + shake.blockSize = int(C.go_openssl_EVP_MD_get_block_size(shake.md)) + if vMajor == 3 { + md := C.go_openssl_EVP_MD_fetch(nil, C.go_openssl_EVP_MD_get0_name(shake.md), nil) + // Don't overwrite md in case it can't be fetched, as the md may still be used + // outside of EVP_MD_CTX. + if md != nil { + shake.md = md + } + } + cacheMD.Store(xofLength, &shake) + return &shake +} diff --git a/cshake_test.go b/cshake_test.go new file mode 100644 index 00000000..962fc6ca --- /dev/null +++ b/cshake_test.go @@ -0,0 +1,251 @@ +package openssl_test + +import ( + "bytes" + "encoding/hex" + "hash" + "io" + "math/rand" + "testing" + + "github.com/golang-fips/openssl/v2" +) + +// testShakes contains functions that return *sha3.SHAKE instances for +// with output-length equal to the KAT length. +var testShakes = map[string]struct { + constructor func(N []byte, S []byte) *openssl.SHAKE + defAlgoName string + defCustomStr string +}{ + // NewCSHAKE without customization produces same result as SHAKE + "SHAKE128": {openssl.NewCSHAKE128, "", ""}, + "SHAKE256": {openssl.NewCSHAKE256, "", ""}, + "CSHAKE128": {openssl.NewCSHAKE128, "CSHAKE128", "CustomString"}, + "CSHAKE256": {openssl.NewCSHAKE256, "CSHAKE256", "CustomString"}, +} + +func skipCSHAKEIfNotSupported(t *testing.T, algo string) { + t.Helper() + var supported bool + switch algo { + case "SHAKE128": + supported = openssl.SupportsSHAKE128() + case "SHAKE256": + supported = openssl.SupportsSHAKE256() + case "CSHAKE128": + supported = openssl.SupportsCSHAKE128() + case "CSHAKE256": + supported = openssl.SupportsCSHAKE256() + } + if !supported { + t.Skip("skipping: not supported") + } +} + +// TestCSHAKESqueezing checks that squeezing the full output a single time produces +// the same output as repeatedly squeezing the instance. +func TestCSHAKESqueezing(t *testing.T) { + const testString = "brekeccakkeccak koax koax" + for algo, v := range testShakes { + skipCSHAKEIfNotSupported(t, algo) + + d0 := v.constructor([]byte(v.defAlgoName), []byte(v.defCustomStr)) + d0.Write([]byte(testString)) + ref := make([]byte, 32) + d0.Read(ref) + + d1 := v.constructor([]byte(v.defAlgoName), []byte(v.defCustomStr)) + d1.Write([]byte(testString)) + var multiple []byte + for range ref { + d1.Read(make([]byte, 0)) + one := make([]byte, 1) + d1.Read(one) + multiple = append(multiple, one...) + } + if !bytes.Equal(ref, multiple) { + t.Errorf("%s: squeezing %d bytes one at a time failed", algo, len(ref)) + } + } +} + +// sequentialBytes produces a buffer of size consecutive bytes 0x00, 0x01, ..., used for testing. +// +// The alignment of each slice is intentionally randomized to detect alignment +// issues in the implementation. See https://golang.org/issue/37644. +func sequentialBytes(size int) []byte { + alignmentOffset := rand.Intn(8) + result := make([]byte, size+alignmentOffset)[alignmentOffset:] + for i := range result { + result[i] = byte(i) + } + return result +} + +func TestCSHAKEReset(t *testing.T) { + out1 := make([]byte, 32) + out2 := make([]byte, 32) + + for algo, v := range testShakes { + skipCSHAKEIfNotSupported(t, algo) + + // Calculate hash for the first time + c := v.constructor(nil, []byte{0x99, 0x98}) + c.Write(sequentialBytes(0x100)) + c.Read(out1) + + // Calculate hash again + c.Reset() + c.Write(sequentialBytes(0x100)) + c.Read(out2) + + if !bytes.Equal(out1, out2) { + t.Error("\nExpected:\n", out1, "\ngot:\n", out2) + } + } +} + +func TestCSHAKEAccumulated(t *testing.T) { + t.Run("CSHAKE128", func(t *testing.T) { + if !openssl.SupportsSHAKE128() { + t.Skip("skipping: not supported") + } + testCSHAKEAccumulated(t, openssl.NewCSHAKE128, (1600-256)/8, + "bb14f8657c6ec5403d0b0e2ef3d3393497e9d3b1a9a9e8e6c81dbaa5fd809252") + }) + t.Run("CSHAKE256", func(t *testing.T) { + if !openssl.SupportsSHAKE256() { + t.Skip("skipping: not supported") + } + testCSHAKEAccumulated(t, openssl.NewCSHAKE256, (1600-512)/8, + "0baaf9250c6e25f0c14ea5c7f9bfde54c8a922c8276437db28f3895bdf6eeeef") + }) +} + +func testCSHAKEAccumulated(t *testing.T, newCSHAKE func(N, S []byte) *openssl.SHAKE, rate int64, exp string) { + rnd := newCSHAKE(nil, nil) + acc := newCSHAKE(nil, nil) + for n := 0; n < 200; n++ { + N := make([]byte, n) + rnd.Read(N) + for s := 0; s < 200; s++ { + S := make([]byte, s) + rnd.Read(S) + + c := newCSHAKE(N, S) + io.CopyN(c, rnd, 100 /* < rate */) + io.CopyN(acc, c, 200) + + c.Reset() + io.CopyN(c, rnd, rate) + io.CopyN(acc, c, 200) + + c.Reset() + io.CopyN(c, rnd, 200 /* > rate */) + io.CopyN(acc, c, 200) + } + } + out := make([]byte, 32) + acc.Read(out) + if got := hex.EncodeToString(out); got != exp { + t.Errorf("got %s, want %s", got, exp) + } +} + +func TestCSHAKELargeS(t *testing.T) { + if !openssl.SupportsSHAKE128() { + t.Skip("skipping: not supported") + } + const s = (1<<32)/8 + 1000 // s * 8 > 2^32 + S := make([]byte, s) + rnd := openssl.NewSHAKE128() + rnd.Read(S) + c := openssl.NewCSHAKE128(nil, S) + io.CopyN(c, rnd, 1000) + out := make([]byte, 32) + c.Read(out) + + exp := "2cb9f237767e98f2614b8779cf096a52da9b3a849280bbddec820771ae529cf0" + if got := hex.EncodeToString(out); got != exp { + t.Errorf("got %s, want %s", got, exp) + } +} + +func TestCSHAKESum(t *testing.T) { + const testString = "hello world" + t.Run("CSHAKE128", func(t *testing.T) { + if !openssl.SupportsSHAKE128() { + t.Skip("skipping: not supported") + } + h := openssl.NewCSHAKE128(nil, nil) + h.Write([]byte(testString[:5])) + h.Write([]byte(testString[5:])) + want := make([]byte, 32) + h.Read(want) + got := openssl.SumSHAKE128([]byte(testString), 32) + if !bytes.Equal(got, want) { + t.Errorf("got:%x want:%x", got, want) + } + }) + t.Run("CSHAKE256", func(t *testing.T) { + if !openssl.SupportsSHAKE256() { + t.Skip("skipping: not supported") + } + h := openssl.NewCSHAKE256(nil, nil) + h.Write([]byte(testString[:5])) + h.Write([]byte(testString[5:])) + want := make([]byte, 32) + h.Read(want) + got := openssl.SumSHAKE256([]byte(testString), 32) + if !bytes.Equal(got, want) { + t.Errorf("got:%x want:%x", got, want) + } + }) +} + +// benchmarkHash tests the speed to hash num buffers of buflen each. +func benchmarkHash(b *testing.B, h hash.Hash, size, num int) { + b.StopTimer() + h.Reset() + data := sequentialBytes(size) + b.SetBytes(int64(size * num)) + b.StartTimer() + + var state []byte + for i := 0; i < b.N; i++ { + for j := 0; j < num; j++ { + h.Write(data) + } + state = h.Sum(state[:0]) + } + b.StopTimer() + h.Reset() +} + +// benchmarkCSHAKE is specialized to the Shake instances, which don't +// require a copy on reading output. +func benchmarkCSHAKE(b *testing.B, h *openssl.SHAKE, size, num int) { + b.StopTimer() + h.Reset() + data := sequentialBytes(size) + d := make([]byte, 32) + + b.SetBytes(int64(size * num)) + b.StartTimer() + + for i := 0; i < b.N; i++ { + h.Reset() + for j := 0; j < num; j++ { + h.Write(data) + } + h.Read(d) + } +} + +func BenchmarkCSHAKE128_MTU(b *testing.B) { benchmarkCSHAKE(b, openssl.NewSHAKE128(), 1350, 1) } +func BenchmarkCSHAKE256_MTU(b *testing.B) { benchmarkCSHAKE(b, openssl.NewSHAKE256(), 1350, 1) } +func BenchmarkCSHAKE256_16x(b *testing.B) { benchmarkCSHAKE(b, openssl.NewSHAKE256(), 16, 1024) } +func BenchmarkCSHAKE256_1MiB(b *testing.B) { benchmarkCSHAKE(b, openssl.NewSHAKE256(), 1024, 1024) } + +func BenchmarkCSHA3_512_1MiB(b *testing.B) { benchmarkHash(b, openssl.NewSHA3_512(), 1024, 1024) } diff --git a/goopenssl.c b/goopenssl.c index 626f184b..0c70e4e2 100644 --- a/goopenssl.c +++ b/goopenssl.c @@ -20,6 +20,7 @@ #define DEFINEFUNC_1_1(ret, func, args, argscall) DEFINEFUNC(ret, func, args, argscall) #define DEFINEFUNC_1_1_1(ret, func, args, argscall) DEFINEFUNC(ret, func, args, argscall) #define DEFINEFUNC_3_0(ret, func, args, argscall) DEFINEFUNC(ret, func, args, argscall) +#define DEFINEFUNC_3_3(ret, func, args, argscall) DEFINEFUNC(ret, func, args, argscall) #define DEFINEFUNC_RENAMED_1_1(ret, func, oldfunc, args, argscall) DEFINEFUNC(ret, func, args, argscall) #define DEFINEFUNC_RENAMED_3_0(ret, func, oldfunc, args, argscall) DEFINEFUNC(ret, func, args, argscall) #define DEFINEFUNC_VARIADIC_3_0(ret, func, newname, args, argscall) DEFINEFUNC(ret, newname, args, argscall) @@ -33,6 +34,7 @@ FOR_ALL_OPENSSL_FUNCTIONS #undef DEFINEFUNC_1_1 #undef DEFINEFUNC_1_1_1 #undef DEFINEFUNC_3_0 +#undef DEFINEFUNC_3_3 #undef DEFINEFUNC_RENAMED_1_1 #undef DEFINEFUNC_RENAMED_3_0 #undef DEFINEFUNC_VARIADIC_3_0 @@ -124,6 +126,11 @@ go_openssl_load_functions(void* handle, unsigned int major, unsigned int minor, { \ DEFINEFUNC_INTERNAL(func, #func) \ } +#define DEFINEFUNC_3_3(ret, func, args, argscall) \ + if (major == 3 && minor >= 3) \ + { \ + DEFINEFUNC_INTERNAL(func, #func) \ + } #define DEFINEFUNC_RENAMED_1_1(ret, func, oldfunc, args, argscall) \ if (major == 1 && minor == 0) \ { \ @@ -157,6 +164,7 @@ FOR_ALL_OPENSSL_FUNCTIONS #undef DEFINEFUNC_1_1 #undef DEFINEFUNC_1_1_1 #undef DEFINEFUNC_3_0 +#undef DEFINEFUNC_3_3 #undef DEFINEFUNC_RENAMED_1_1 #undef DEFINEFUNC_RENAMED_3_0 #undef DEFINEFUNC_VARIADIC_3_0 diff --git a/goopenssl.h b/goopenssl.h index f5cdced6..692ab6f9 100644 --- a/goopenssl.h +++ b/goopenssl.h @@ -54,6 +54,8 @@ int go_openssl_DSA_set0_key_backport(GO_DSA_PTR d, GO_BIGNUM_PTR pub_key, GO_BIG DEFINEFUNC(ret, func, args, argscall) #define DEFINEFUNC_3_0(ret, func, args, argscall) \ DEFINEFUNC(ret, func, args, argscall) +#define DEFINEFUNC_3_3(ret, func, args, argscall) \ + DEFINEFUNC(ret, func, args, argscall) #define DEFINEFUNC_RENAMED_1_1(ret, func, oldfunc, args, argscall) \ DEFINEFUNC(ret, func, args, argscall) #define DEFINEFUNC_RENAMED_3_0(ret, func, oldfunc, args, argscall) \ @@ -70,6 +72,7 @@ FOR_ALL_OPENSSL_FUNCTIONS #undef DEFINEFUNC_1_1 #undef DEFINEFUNC_1_1_1 #undef DEFINEFUNC_3_0 +#undef DEFINEFUNC_3_3 #undef DEFINEFUNC_RENAMED_1_1 #undef DEFINEFUNC_RENAMED_3_0 #undef DEFINEFUNC_VARIADIC_3_0 diff --git a/shims.h b/shims.h index 437312ad..c95c4f94 100644 --- a/shims.h +++ b/shims.h @@ -27,7 +27,9 @@ enum { GO_EVP_MAX_MD_SIZE = 64, GO_EVP_PKEY_PUBLIC_KEY = 0x86, - GO_EVP_PKEY_KEYPAIR = 0x87 + GO_EVP_PKEY_KEYPAIR = 0x87, + + EVP_MD_CTRL_XOF_LEN = 0x3 }; // #include @@ -151,6 +153,9 @@ typedef void* GO_SHA_CTX_PTR; // DEFINEFUNC_3_0 acts like DEFINEFUNC but only aborts the process if function can't be loaded // when using 3.0.0 or higher. // +// DEFINEFUNC_3_3 acts like DEFINEFUNC but only aborts the process if function can't be loaded +// when using 3.3.0 or higher. +// // DEFINEFUNC_RENAMED_1_1 acts like DEFINEFUNC but tries to load the function using the new name when using >= 1.1.x // and the old name when using 1.0.2. In both cases the function will have the new name. // @@ -219,11 +224,13 @@ DEFINEFUNC_RENAMED_1_1(GO_EVP_MD_CTX_PTR, EVP_MD_CTX_new, EVP_MD_CTX_create, (vo DEFINEFUNC_RENAMED_1_1(void, EVP_MD_CTX_free, EVP_MD_CTX_destroy, (GO_EVP_MD_CTX_PTR ctx), (ctx)) \ DEFINEFUNC(int, EVP_MD_CTX_copy, (GO_EVP_MD_CTX_PTR out, const GO_EVP_MD_CTX_PTR in), (out, in)) \ DEFINEFUNC(int, EVP_MD_CTX_copy_ex, (GO_EVP_MD_CTX_PTR out, const GO_EVP_MD_CTX_PTR in), (out, in)) \ +DEFINEFUNC_1_1_1(int, EVP_MD_CTX_ctrl, (GO_EVP_MD_CTX_PTR ctx, int cmd, int p1, void *p2), (ctx, cmd, p1, p2)) \ DEFINEFUNC(int, EVP_Digest, (const void *data, size_t count, unsigned char *md, unsigned int *size, const GO_EVP_MD_PTR type, GO_ENGINE_PTR impl), (data, count, md, size, type, impl)) \ DEFINEFUNC(int, EVP_DigestInit_ex, (GO_EVP_MD_CTX_PTR ctx, const GO_EVP_MD_PTR type, GO_ENGINE_PTR impl), (ctx, type, impl)) \ DEFINEFUNC(int, EVP_DigestInit, (GO_EVP_MD_CTX_PTR ctx, const GO_EVP_MD_PTR type), (ctx, type)) \ DEFINEFUNC(int, EVP_DigestUpdate, (GO_EVP_MD_CTX_PTR ctx, const void *d, size_t cnt), (ctx, d, cnt)) \ DEFINEFUNC(int, EVP_DigestFinal, (GO_EVP_MD_CTX_PTR ctx, unsigned char *md, unsigned int *s), (ctx, md, s)) \ +DEFINEFUNC_3_3(int, EVP_DigestSqueeze, (GO_EVP_MD_CTX_PTR ctx, unsigned char *out, size_t outlen), (ctx, out, outlen)) \ DEFINEFUNC_1_1_1(int, EVP_DigestSign, (GO_EVP_MD_CTX_PTR ctx, unsigned char *sigret, size_t *siglen, const unsigned char *tbs, size_t tbslen), (ctx, sigret, siglen, tbs, tbslen)) \ DEFINEFUNC(int, EVP_DigestSignInit, (GO_EVP_MD_CTX_PTR ctx, GO_EVP_PKEY_CTX_PTR *pctx, const GO_EVP_MD_PTR type, GO_ENGINE_PTR e, GO_EVP_PKEY_PTR pkey), (ctx, pctx, type, e, pkey)) \ DEFINEFUNC(int, EVP_DigestSignFinal, (GO_EVP_MD_CTX_PTR ctx, unsigned char *sig, size_t *siglen), (ctx, sig, siglen)) \ @@ -245,6 +252,8 @@ DEFINEFUNC_1_1_1(const GO_EVP_MD_PTR, EVP_sha3_224, (void), ()) \ DEFINEFUNC_1_1_1(const GO_EVP_MD_PTR, EVP_sha3_256, (void), ()) \ DEFINEFUNC_1_1_1(const GO_EVP_MD_PTR, EVP_sha3_384, (void), ()) \ DEFINEFUNC_1_1_1(const GO_EVP_MD_PTR, EVP_sha3_512, (void), ()) \ +DEFINEFUNC_1_1_1(const GO_EVP_MD_PTR, EVP_shake128, (void), ()) \ +DEFINEFUNC_1_1_1(const GO_EVP_MD_PTR, EVP_shake256, (void), ()) \ DEFINEFUNC_LEGACY_1_0(void, HMAC_CTX_init, (GO_HMAC_CTX_PTR arg0), (arg0)) \ DEFINEFUNC_LEGACY_1_0(void, HMAC_CTX_cleanup, (GO_HMAC_CTX_PTR arg0), (arg0)) \ DEFINEFUNC_LEGACY_1(int, HMAC_Init_ex, (GO_HMAC_CTX_PTR arg0, const void *arg1, int arg2, const GO_EVP_MD_PTR arg3, GO_ENGINE_PTR arg4), (arg0, arg1, arg2, arg3, arg4)) \ From 4667b2ecfd7c39ddede8c4a7e39828647a57042f Mon Sep 17 00:00:00 2001 From: qmuntal Date: Tue, 7 Jan 2025 15:50:42 +0100 Subject: [PATCH 02/13] fix shake sizes --- cshake.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cshake.go b/cshake.go index a301acc1..59e5fe03 100644 --- a/cshake.go +++ b/cshake.go @@ -214,12 +214,12 @@ func loadShake(xofLength int) *shakeAlgorithm { var shake shakeAlgorithm switch xofLength { - case 32: + case 128: if versionAtOrAbove(1, 1, 0) { shake.md = C.go_openssl_EVP_shake128() shake.xofLength = 32 } - case 64: + case 256: if versionAtOrAbove(1, 1, 0) { shake.md = C.go_openssl_EVP_shake256() shake.xofLength = 64 From a44d0771e2e2c0b97103f0c2abe3b6705b9a011a Mon Sep 17 00:00:00 2001 From: qmuntal Date: Tue, 7 Jan 2025 16:02:43 +0100 Subject: [PATCH 03/13] formatting --- cshake.go | 2 +- goopenssl.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cshake.go b/cshake.go index 59e5fe03..198c06c3 100644 --- a/cshake.go +++ b/cshake.go @@ -147,7 +147,7 @@ func newSHAKE(size int) *SHAKE { C.go_openssl_EVP_MD_CTX_free(ctx) panic(newOpenSSLError("EVP_MD_CTX_ctrl")) } - s := &SHAKE{ctx: ctx} + s := &SHAKE{alg: alg, ctx: ctx} runtime.SetFinalizer(s, (*SHAKE).finalize) return s } diff --git a/goopenssl.c b/goopenssl.c index 0c70e4e2..ff860ed4 100644 --- a/goopenssl.c +++ b/goopenssl.c @@ -127,7 +127,7 @@ go_openssl_load_functions(void* handle, unsigned int major, unsigned int minor, DEFINEFUNC_INTERNAL(func, #func) \ } #define DEFINEFUNC_3_3(ret, func, args, argscall) \ - if (major == 3 && minor >= 3) \ + if (major == 3 && minor >= 3) \ { \ DEFINEFUNC_INTERNAL(func, #func) \ } From 1592b8edb0834a8b3827779c38f51263e335ef3f Mon Sep 17 00:00:00 2001 From: qmuntal Date: Tue, 7 Jan 2025 17:38:07 +0100 Subject: [PATCH 04/13] fix constructors --- cshake.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cshake.go b/cshake.go index 198c06c3..d08e2195 100644 --- a/cshake.go +++ b/cshake.go @@ -113,6 +113,9 @@ func NewSHAKE256() *SHAKE { // cSHAKE is desired. S is a customization byte string used for domain // separation. When N and S are both empty, this is equivalent to NewSHAKE128. func NewCSHAKE128(N, S []byte) *SHAKE { + if len(N) == 0 && len(S) == 0 { + return NewSHAKE128() + } return nil } @@ -122,6 +125,9 @@ func NewCSHAKE128(N, S []byte) *SHAKE { // cSHAKE is desired. S is a customization byte string used for domain // separation. When N and S are both empty, this is equivalent to NewSHAKE256. func NewCSHAKE256(N, S []byte) *SHAKE { + if len(N) == 0 && len(S) == 0 { + return NewSHAKE256() + } return nil } From 278fbd4892fafb5acf26f0ec3876a6ea23b9a9f1 Mon Sep 17 00:00:00 2001 From: qmuntal Date: Tue, 7 Jan 2025 17:48:00 +0100 Subject: [PATCH 05/13] simplify supportsSHAKE --- cshake.go | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/cshake.go b/cshake.go index d08e2195..578882ab 100644 --- a/cshake.go +++ b/cshake.go @@ -74,19 +74,7 @@ func supportsSHAKE(size int) bool { if v, ok := cacheSHAKESupported.Load(size); ok { return v.(bool) } - alg := loadShake(size) - if alg == nil { - cacheSHAKESupported.Store(size, false) - return false - } - // EVP_MD objects can be non-nil even when they can't be used - // in a EVP_MD_CTX, e.g. MD5 in FIPS mode. We need to prove - // if they can be used by passing them to a EVP_MD_CTX. - var supported bool - if ctx := C.go_openssl_EVP_MD_CTX_new(); ctx != nil { - supported = C.go_openssl_EVP_DigestInit_ex(ctx, alg.md, nil) == 1 - C.go_openssl_EVP_MD_CTX_free(ctx) - } + supported := loadShake(size) != nil cacheSHAKESupported.Store(size, supported) return supported } From dc537e20b00300f8ea7a44fc0d62079f8b76b560 Mon Sep 17 00:00:00 2001 From: qmuntal Date: Tue, 7 Jan 2025 18:04:43 +0100 Subject: [PATCH 06/13] don't set EVP_MD_CTRL_XOF_LEN --- cshake.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cshake.go b/cshake.go index 578882ab..8400e762 100644 --- a/cshake.go +++ b/cshake.go @@ -137,10 +137,10 @@ func newSHAKE(size int) *SHAKE { C.go_openssl_EVP_MD_CTX_free(ctx) panic(newOpenSSLError("EVP_DigestInit_ex")) } - if C.go_openssl_EVP_MD_CTX_ctrl(ctx, C.EVP_MD_CTRL_XOF_LEN, C.int(alg.xofLength), nil) != 1 { - C.go_openssl_EVP_MD_CTX_free(ctx) - panic(newOpenSSLError("EVP_MD_CTX_ctrl")) - } + //if C.go_openssl_EVP_MD_CTX_ctrl(ctx, C.EVP_MD_CTRL_XOF_LEN, C.int(alg.xofLength), nil) != 1 { + // C.go_openssl_EVP_MD_CTX_free(ctx) + // panic(newOpenSSLError("EVP_MD_CTX_ctrl")) + //} s := &SHAKE{alg: alg, ctx: ctx} runtime.SetFinalizer(s, (*SHAKE).finalize) return s From 1526cf8f2f8efd364c23d6be98b246a16c4d37d0 Mon Sep 17 00:00:00 2001 From: qmuntal Date: Tue, 7 Jan 2025 18:10:33 +0100 Subject: [PATCH 07/13] set xor_len --- cshake.go | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/cshake.go b/cshake.go index 8400e762..6558a524 100644 --- a/cshake.go +++ b/cshake.go @@ -137,10 +137,6 @@ func newSHAKE(size int) *SHAKE { C.go_openssl_EVP_MD_CTX_free(ctx) panic(newOpenSSLError("EVP_DigestInit_ex")) } - //if C.go_openssl_EVP_MD_CTX_ctrl(ctx, C.EVP_MD_CTRL_XOF_LEN, C.int(alg.xofLength), nil) != 1 { - // C.go_openssl_EVP_MD_CTX_free(ctx) - // panic(newOpenSSLError("EVP_MD_CTX_ctrl")) - //} s := &SHAKE{alg: alg, ctx: ctx} runtime.SetFinalizer(s, (*SHAKE).finalize) return s @@ -172,6 +168,9 @@ func (s *SHAKE) Read(p []byte) (n int, err error) { if len(p) == 0 { return 0, nil } + if C.go_openssl_EVP_MD_CTX_ctrl(s.ctx, C.EVP_MD_CTRL_XOF_LEN, C.int(len(p)), nil) != 1 { + panic(newOpenSSLError("EVP_MD_CTX_ctrl")) + } if C.go_openssl_EVP_DigestSqueeze(s.ctx, (*C.uchar)(unsafe.Pointer(&*addr(p))), C.size_t(len(p))) != 1 { panic(newOpenSSLError("EVP_DigestSqueeze")) } @@ -197,7 +196,6 @@ var cacheSHAKE sync.Map type shakeAlgorithm struct { md C.GO_EVP_MD_PTR blockSize int - xofLength int } // loadShake converts a crypto.Hash to a EVP_MD. @@ -211,12 +209,10 @@ func loadShake(xofLength int) *shakeAlgorithm { case 128: if versionAtOrAbove(1, 1, 0) { shake.md = C.go_openssl_EVP_shake128() - shake.xofLength = 32 } case 256: if versionAtOrAbove(1, 1, 0) { shake.md = C.go_openssl_EVP_shake256() - shake.xofLength = 64 } } if shake.md == nil { From cff0bbb50441841102f81729988a7688e48c9c90 Mon Sep 17 00:00:00 2001 From: qmuntal Date: Tue, 7 Jan 2025 18:12:16 +0100 Subject: [PATCH 08/13] cache xof_len --- cshake.go | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/cshake.go b/cshake.go index 6558a524..dea2f1e8 100644 --- a/cshake.go +++ b/cshake.go @@ -81,8 +81,9 @@ func supportsSHAKE(size int) bool { // SHAKE is an instance of a SHAKE extendable output function. type SHAKE struct { - alg *shakeAlgorithm - ctx C.GO_EVP_MD_CTX_PTR + alg *shakeAlgorithm + ctx C.GO_EVP_MD_CTX_PTR + lastXofLen int } // NewSHAKE128 creates a new SHAKE128 XOF. @@ -168,8 +169,11 @@ func (s *SHAKE) Read(p []byte) (n int, err error) { if len(p) == 0 { return 0, nil } - if C.go_openssl_EVP_MD_CTX_ctrl(s.ctx, C.EVP_MD_CTRL_XOF_LEN, C.int(len(p)), nil) != 1 { - panic(newOpenSSLError("EVP_MD_CTX_ctrl")) + if len(p) != s.lastXofLen { + if C.go_openssl_EVP_MD_CTX_ctrl(s.ctx, C.EVP_MD_CTRL_XOF_LEN, C.int(len(p)), nil) != 1 { + panic(newOpenSSLError("EVP_MD_CTX_ctrl")) + } + s.lastXofLen = len(p) } if C.go_openssl_EVP_DigestSqueeze(s.ctx, (*C.uchar)(unsafe.Pointer(&*addr(p))), C.size_t(len(p))) != 1 { panic(newOpenSSLError("EVP_DigestSqueeze")) From 6b09b11e7783fb0b1a008123c429328d6fe5b661 Mon Sep 17 00:00:00 2001 From: qmuntal Date: Tue, 7 Jan 2025 18:22:21 +0100 Subject: [PATCH 09/13] improve test hierarchy --- cshake_test.go | 66 ++++++++++++++++++++++++++------------------------ 1 file changed, 35 insertions(+), 31 deletions(-) diff --git a/cshake_test.go b/cshake_test.go index 962fc6ca..89c762d5 100644 --- a/cshake_test.go +++ b/cshake_test.go @@ -48,25 +48,27 @@ func skipCSHAKEIfNotSupported(t *testing.T, algo string) { func TestCSHAKESqueezing(t *testing.T) { const testString = "brekeccakkeccak koax koax" for algo, v := range testShakes { - skipCSHAKEIfNotSupported(t, algo) - - d0 := v.constructor([]byte(v.defAlgoName), []byte(v.defCustomStr)) - d0.Write([]byte(testString)) - ref := make([]byte, 32) - d0.Read(ref) - - d1 := v.constructor([]byte(v.defAlgoName), []byte(v.defCustomStr)) - d1.Write([]byte(testString)) - var multiple []byte - for range ref { - d1.Read(make([]byte, 0)) - one := make([]byte, 1) - d1.Read(one) - multiple = append(multiple, one...) - } - if !bytes.Equal(ref, multiple) { - t.Errorf("%s: squeezing %d bytes one at a time failed", algo, len(ref)) - } + t.Run(algo, func(t *testing.T) { + skipCSHAKEIfNotSupported(t, algo) + + d0 := v.constructor([]byte(v.defAlgoName), []byte(v.defCustomStr)) + d0.Write([]byte(testString)) + ref := make([]byte, 32) + d0.Read(ref) + + d1 := v.constructor([]byte(v.defAlgoName), []byte(v.defCustomStr)) + d1.Write([]byte(testString)) + var multiple []byte + for range ref { + d1.Read(make([]byte, 0)) + one := make([]byte, 1) + d1.Read(one) + multiple = append(multiple, one...) + } + if !bytes.Equal(ref, multiple) { + t.Errorf("%s: squeezing %d bytes one at a time failed", algo, len(ref)) + } + }) } } @@ -88,21 +90,23 @@ func TestCSHAKEReset(t *testing.T) { out2 := make([]byte, 32) for algo, v := range testShakes { - skipCSHAKEIfNotSupported(t, algo) + t.Run(algo, func(t *testing.T) { + skipCSHAKEIfNotSupported(t, algo) - // Calculate hash for the first time - c := v.constructor(nil, []byte{0x99, 0x98}) - c.Write(sequentialBytes(0x100)) - c.Read(out1) + // Calculate hash for the first time + c := v.constructor(nil, []byte{0x99, 0x98}) + c.Write(sequentialBytes(0x100)) + c.Read(out1) - // Calculate hash again - c.Reset() - c.Write(sequentialBytes(0x100)) - c.Read(out2) + // Calculate hash again + c.Reset() + c.Write(sequentialBytes(0x100)) + c.Read(out2) - if !bytes.Equal(out1, out2) { - t.Error("\nExpected:\n", out1, "\ngot:\n", out2) - } + if !bytes.Equal(out1, out2) { + t.Error("\nExpected:\n", out1, "\ngot:\n", out2) + } + }) } } From dc77acd7673dba235cdbe10f3c667c9390a98af0 Mon Sep 17 00:00:00 2001 From: qmuntal Date: Wed, 8 Jan 2025 09:42:05 +0100 Subject: [PATCH 10/13] fix tests --- cshake.go | 89 +++++++++++++++++--------------------------------- cshake_test.go | 28 ++++++++-------- shims.h | 2 -- 3 files changed, 44 insertions(+), 75 deletions(-) diff --git a/cshake.go b/cshake.go index dea2f1e8..25205cb5 100644 --- a/cshake.go +++ b/cshake.go @@ -35,48 +35,22 @@ func SumSHAKE256(data []byte, length int) []byte { return out } -// SupportsSHAKE128 returns true if the SHAKE128 extendable output function is -// supported. -func SupportsSHAKE128() bool { - return supportsSHAKE(128) -} - -// SupportsSHAKE256 returns true if the SHAKE256 extendable output function is -// supported. -func SupportsSHAKE256() bool { - return supportsSHAKE(256) -} - -// SupportsCSHAKE128 returns true if the CSHAKE128 extendable output function is -// supported. -func SupportsCSHAKE128() bool { - return false -} - -// SupportsCSHAKE256 returns true if the CSHAKE256 extendable output function is -// supported. -func SupportsCSHAKE256() bool { - return false -} - -// cacheSHAKESupported is a cache of SHAKE size support. -var cacheSHAKESupported sync.Map - -// SupportsSHAKE returns true if the SHAKE extendable output function is -// supported. -func supportsSHAKE(size int) bool { +// SupportsSHAKE returns true if the SHAKE extendable output functions +// with the given securityBits are supported. +func SupportsSHAKE(securityBits int) bool { if vMajor == 1 || (vMajor == 3 && vMinor < 3) { // SHAKE MD's are supported since OpenSSL 1.1.1, // but EVP_DigestSqueeze is only supported since 3.3, // and we need it to implement [sha3.SHAKE]. return false } - if v, ok := cacheSHAKESupported.Load(size); ok { - return v.(bool) - } - supported := loadShake(size) != nil - cacheSHAKESupported.Store(size, supported) - return supported + return loadShake(securityBits) != nil +} + +// SupportsCSHAKE returns true if the CSHAKE extendable output functions +// with the given securityBits are supported. +func SupportsCSHAKE(securityBits int) bool { + return false } // SHAKE is an instance of a SHAKE extendable output function. @@ -203,35 +177,32 @@ type shakeAlgorithm struct { } // loadShake converts a crypto.Hash to a EVP_MD. -func loadShake(xofLength int) *shakeAlgorithm { - if v, ok := cacheMD.Load(xofLength); ok { +func loadShake(securityBits int) (alg *shakeAlgorithm) { + if v, ok := cacheMD.Load(securityBits); ok { return v.(*shakeAlgorithm) } + defer func() { + cacheMD.Store(securityBits, alg) + }() - var shake shakeAlgorithm - switch xofLength { + var name *C.char + switch securityBits { case 128: - if versionAtOrAbove(1, 1, 0) { - shake.md = C.go_openssl_EVP_shake128() - } + name = C.CString("SHAKE-128") case 256: - if versionAtOrAbove(1, 1, 0) { - shake.md = C.go_openssl_EVP_shake256() - } - } - if shake.md == nil { - cacheMD.Store(xofLength, (*hashAlgorithm)(nil)) + name = C.CString("SHAKE-256") + default: return nil } - shake.blockSize = int(C.go_openssl_EVP_MD_get_block_size(shake.md)) - if vMajor == 3 { - md := C.go_openssl_EVP_MD_fetch(nil, C.go_openssl_EVP_MD_get0_name(shake.md), nil) - // Don't overwrite md in case it can't be fetched, as the md may still be used - // outside of EVP_MD_CTX. - if md != nil { - shake.md = md - } + defer C.free(unsafe.Pointer(name)) + + md := C.go_openssl_EVP_MD_fetch(nil, name, nil) + if md == nil { + return nil } - cacheMD.Store(xofLength, &shake) - return &shake + + alg = new(shakeAlgorithm) + alg.md = md + alg.blockSize = int(C.go_openssl_EVP_MD_get_block_size(md)) + return alg } diff --git a/cshake_test.go b/cshake_test.go index 89c762d5..59c4d71f 100644 --- a/cshake_test.go +++ b/cshake_test.go @@ -30,13 +30,13 @@ func skipCSHAKEIfNotSupported(t *testing.T, algo string) { var supported bool switch algo { case "SHAKE128": - supported = openssl.SupportsSHAKE128() + supported = openssl.SupportsSHAKE(128) case "SHAKE256": - supported = openssl.SupportsSHAKE256() + supported = openssl.SupportsSHAKE(256) case "CSHAKE128": - supported = openssl.SupportsCSHAKE128() + supported = openssl.SupportsCSHAKE(128) case "CSHAKE256": - supported = openssl.SupportsCSHAKE256() + supported = openssl.SupportsCSHAKE(256) } if !supported { t.Skip("skipping: not supported") @@ -94,7 +94,7 @@ func TestCSHAKEReset(t *testing.T) { skipCSHAKEIfNotSupported(t, algo) // Calculate hash for the first time - c := v.constructor(nil, []byte{0x99, 0x98}) + c := v.constructor(nil, []byte(v.defCustomStr)) c.Write(sequentialBytes(0x100)) c.Read(out1) @@ -112,14 +112,14 @@ func TestCSHAKEReset(t *testing.T) { func TestCSHAKEAccumulated(t *testing.T) { t.Run("CSHAKE128", func(t *testing.T) { - if !openssl.SupportsSHAKE128() { + if !openssl.SupportsCSHAKE(128) { t.Skip("skipping: not supported") } testCSHAKEAccumulated(t, openssl.NewCSHAKE128, (1600-256)/8, "bb14f8657c6ec5403d0b0e2ef3d3393497e9d3b1a9a9e8e6c81dbaa5fd809252") }) t.Run("CSHAKE256", func(t *testing.T) { - if !openssl.SupportsSHAKE256() { + if !openssl.SupportsCSHAKE(256) { t.Skip("skipping: not supported") } testCSHAKEAccumulated(t, openssl.NewCSHAKE256, (1600-512)/8, @@ -158,7 +158,7 @@ func testCSHAKEAccumulated(t *testing.T, newCSHAKE func(N, S []byte) *openssl.SH } func TestCSHAKELargeS(t *testing.T) { - if !openssl.SupportsSHAKE128() { + if !openssl.SupportsCSHAKE(128) { t.Skip("skipping: not supported") } const s = (1<<32)/8 + 1000 // s * 8 > 2^32 @@ -178,11 +178,11 @@ func TestCSHAKELargeS(t *testing.T) { func TestCSHAKESum(t *testing.T) { const testString = "hello world" - t.Run("CSHAKE128", func(t *testing.T) { - if !openssl.SupportsSHAKE128() { + t.Run("SHAKE128", func(t *testing.T) { + if !openssl.SupportsSHAKE(128) { t.Skip("skipping: not supported") } - h := openssl.NewCSHAKE128(nil, nil) + h := openssl.NewSHAKE128() h.Write([]byte(testString[:5])) h.Write([]byte(testString[5:])) want := make([]byte, 32) @@ -192,11 +192,11 @@ func TestCSHAKESum(t *testing.T) { t.Errorf("got:%x want:%x", got, want) } }) - t.Run("CSHAKE256", func(t *testing.T) { - if !openssl.SupportsSHAKE256() { + t.Run("SHAKE256", func(t *testing.T) { + if !openssl.SupportsSHAKE(256) { t.Skip("skipping: not supported") } - h := openssl.NewCSHAKE256(nil, nil) + h := openssl.NewSHAKE256() h.Write([]byte(testString[:5])) h.Write([]byte(testString[5:])) want := make([]byte, 32) diff --git a/shims.h b/shims.h index c95c4f94..df51d373 100644 --- a/shims.h +++ b/shims.h @@ -252,8 +252,6 @@ DEFINEFUNC_1_1_1(const GO_EVP_MD_PTR, EVP_sha3_224, (void), ()) \ DEFINEFUNC_1_1_1(const GO_EVP_MD_PTR, EVP_sha3_256, (void), ()) \ DEFINEFUNC_1_1_1(const GO_EVP_MD_PTR, EVP_sha3_384, (void), ()) \ DEFINEFUNC_1_1_1(const GO_EVP_MD_PTR, EVP_sha3_512, (void), ()) \ -DEFINEFUNC_1_1_1(const GO_EVP_MD_PTR, EVP_shake128, (void), ()) \ -DEFINEFUNC_1_1_1(const GO_EVP_MD_PTR, EVP_shake256, (void), ()) \ DEFINEFUNC_LEGACY_1_0(void, HMAC_CTX_init, (GO_HMAC_CTX_PTR arg0), (arg0)) \ DEFINEFUNC_LEGACY_1_0(void, HMAC_CTX_cleanup, (GO_HMAC_CTX_PTR arg0), (arg0)) \ DEFINEFUNC_LEGACY_1(int, HMAC_Init_ex, (GO_HMAC_CTX_PTR arg0, const void *arg1, int arg2, const GO_EVP_MD_PTR arg3, GO_ENGINE_PTR arg4), (arg0, arg1, arg2, arg3, arg4)) \ From bea8a34ebe314bafe610ddf4f935cb20b4fe36cd Mon Sep 17 00:00:00 2001 From: qmuntal Date: Wed, 8 Jan 2025 10:55:31 +0100 Subject: [PATCH 11/13] fix reset --- cshake.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cshake.go b/cshake.go index 25205cb5..4f37fdf7 100644 --- a/cshake.go +++ b/cshake.go @@ -11,8 +11,8 @@ import ( "unsafe" ) -func shakeOneShot(size int, p []byte, sum []byte) bool { - return C.go_openssl_EVP_Digest(unsafe.Pointer(&*addr(p)), C.size_t(len(p)), (*C.uchar)(unsafe.Pointer(&*addr(sum))), nil, loadShake(size).md, nil) != 0 +func shakeOneShot(secuirtyBits int, data []byte, out []byte) bool { + return C.go_openssl_EVP_Digest(unsafe.Pointer(&*addr(data)), C.size_t(len(data)), (*C.uchar)(unsafe.Pointer(&*addr(out))), nil, loadShake(secuirtyBits).md, nil) != 0 } // SumSHAKE128 applies the SHAKE128 extendable output function to data and @@ -161,6 +161,7 @@ func (s *SHAKE) Reset() { if C.go_openssl_EVP_DigestInit_ex(s.ctx, nil, nil) != 1 { panic(newOpenSSLError("EVP_DigestInit_ex")) } + s.lastXofLen = 0 } // BlockSize returns the rate of the XOF. From 5e3d1cc5d420757c76d647433ad2ec119f0d4456 Mon Sep 17 00:00:00 2001 From: qmuntal Date: Wed, 8 Jan 2025 11:08:13 +0100 Subject: [PATCH 12/13] fix shakeOneShot --- cshake.go | 44 ++++++++++++++++++++++++++++---------------- shims.h | 1 + 2 files changed, 29 insertions(+), 16 deletions(-) diff --git a/cshake.go b/cshake.go index 4f37fdf7..f32e98c5 100644 --- a/cshake.go +++ b/cshake.go @@ -11,17 +11,36 @@ import ( "unsafe" ) -func shakeOneShot(secuirtyBits int, data []byte, out []byte) bool { - return C.go_openssl_EVP_Digest(unsafe.Pointer(&*addr(data)), C.size_t(len(data)), (*C.uchar)(unsafe.Pointer(&*addr(out))), nil, loadShake(secuirtyBits).md, nil) != 0 +// shakeOneShot applies the SHAKE extendable output function to data and +// writes the output to out. +func shakeOneShot(secuirtyBits int, data []byte, out []byte) { + // Can't use EVP_Digest because it doesn't support output lengths + // larger than the block size, while crypto/sha3 supports any length. + alg := loadShake(secuirtyBits) + if alg == nil { + panic("openssl: unsupported SHAKE" + strconv.Itoa(secuirtyBits) + " function") + } + ctx := C.go_openssl_EVP_MD_CTX_new() + if ctx == nil { + panic(newOpenSSLError("EVP_MD_CTX_new")) + } + defer C.go_openssl_EVP_MD_CTX_free(ctx) + if C.go_openssl_EVP_DigestInit_ex(ctx, alg.md, nil) != 1 { + panic(newOpenSSLError("EVP_DigestInit_ex")) + } + if C.go_openssl_EVP_DigestUpdate(ctx, unsafe.Pointer(&*addr(data)), C.size_t(len(data))) != 1 { + panic(newOpenSSLError("EVP_DigestUpdate")) + } + if C.go_openssl_EVP_DigestFinalXOF(ctx, (*C.uchar)(unsafe.Pointer(&*addr(out))), C.size_t(len(out))) != 0 { + panic(newOpenSSLError("EVP_DigestFinalXOF")) + } } // SumSHAKE128 applies the SHAKE128 extendable output function to data and // returns an output of the given length in bytes. func SumSHAKE128(data []byte, length int) []byte { out := make([]byte, length) - if !shakeOneShot(128, data, out[:]) { - panic("openssl: SumSHAKE128 failed") - } + shakeOneShot(128, data, out[:]) return out } @@ -29,9 +48,7 @@ func SumSHAKE128(data []byte, length int) []byte { // returns an output of the given length in bytes. func SumSHAKE256(data []byte, length int) []byte { out := make([]byte, length) - if !shakeOneShot(256, data, out[:]) { - panic("openssl: SumSHAKE256 failed") - } + shakeOneShot(256, data, out[:]) return out } @@ -94,15 +111,10 @@ func NewCSHAKE256(N, S []byte) *SHAKE { return nil } -func newSHAKE(size int) *SHAKE { - if vMajor == 1 || (vMajor == 3 && vMinor < 3) { - panic("openssl: SHAKE is not supported by this version of OpenSSL") - - } - - alg := loadShake(size) +func newSHAKE(securityBits int) *SHAKE { + alg := loadShake(securityBits) if alg == nil { - panic("openssl: unsupported SHAKE" + strconv.Itoa(size) + " function") + panic("openssl: unsupported SHAKE" + strconv.Itoa(securityBits) + " function") } ctx := C.go_openssl_EVP_MD_CTX_new() if ctx == nil { diff --git a/shims.h b/shims.h index df51d373..a79c20a6 100644 --- a/shims.h +++ b/shims.h @@ -230,6 +230,7 @@ DEFINEFUNC(int, EVP_DigestInit_ex, (GO_EVP_MD_CTX_PTR ctx, const GO_EVP_MD_PTR t DEFINEFUNC(int, EVP_DigestInit, (GO_EVP_MD_CTX_PTR ctx, const GO_EVP_MD_PTR type), (ctx, type)) \ DEFINEFUNC(int, EVP_DigestUpdate, (GO_EVP_MD_CTX_PTR ctx, const void *d, size_t cnt), (ctx, d, cnt)) \ DEFINEFUNC(int, EVP_DigestFinal, (GO_EVP_MD_CTX_PTR ctx, unsigned char *md, unsigned int *s), (ctx, md, s)) \ +DEFINEFUNC_3_3(int, EVP_DigestFinalXOF, (GO_EVP_MD_CTX_PTR ctx, unsigned char *out, size_t outlen), (ctx, out, outlen)) \ DEFINEFUNC_3_3(int, EVP_DigestSqueeze, (GO_EVP_MD_CTX_PTR ctx, unsigned char *out, size_t outlen), (ctx, out, outlen)) \ DEFINEFUNC_1_1_1(int, EVP_DigestSign, (GO_EVP_MD_CTX_PTR ctx, unsigned char *sigret, size_t *siglen, const unsigned char *tbs, size_t tbslen), (ctx, sigret, siglen, tbs, tbslen)) \ DEFINEFUNC(int, EVP_DigestSignInit, (GO_EVP_MD_CTX_PTR ctx, GO_EVP_PKEY_CTX_PTR *pctx, const GO_EVP_MD_PTR type, GO_ENGINE_PTR e, GO_EVP_PKEY_PTR pkey), (ctx, pctx, type, e, pkey)) \ From 369bd5df93c9490b900a1fee2aee5990c477aa5a Mon Sep 17 00:00:00 2001 From: qmuntal Date: Wed, 8 Jan 2025 11:11:18 +0100 Subject: [PATCH 13/13] fix shakeOneShot --- cshake.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cshake.go b/cshake.go index f32e98c5..796e5350 100644 --- a/cshake.go +++ b/cshake.go @@ -31,7 +31,7 @@ func shakeOneShot(secuirtyBits int, data []byte, out []byte) { if C.go_openssl_EVP_DigestUpdate(ctx, unsafe.Pointer(&*addr(data)), C.size_t(len(data))) != 1 { panic(newOpenSSLError("EVP_DigestUpdate")) } - if C.go_openssl_EVP_DigestFinalXOF(ctx, (*C.uchar)(unsafe.Pointer(&*addr(out))), C.size_t(len(out))) != 0 { + if C.go_openssl_EVP_DigestFinalXOF(ctx, (*C.uchar)(unsafe.Pointer(&*addr(out))), C.size_t(len(out))) != 1 { panic(newOpenSSLError("EVP_DigestFinalXOF")) } }