From e2b4391f7cb33dd2a45d5a1553ff96a77f1d448d Mon Sep 17 00:00:00 2001 From: qmuntal Date: Fri, 23 May 2025 15:18:00 +0200 Subject: [PATCH 1/5] support serializing SymCrypt hash objects (cherry picked from commit 413fa493ebef8c115b7bc0e4411dfa006460568e) --- hash.go | 512 ++++++++++---------------------------------- hash_test.go | 116 +++++++++- params.go | 38 ++++ provideropenssl.go | 239 +++++++++++++++++++++ providersymcrypt.go | 380 ++++++++++++++++++++++++++++++++ shims.h | 10 +- 6 files changed, 893 insertions(+), 402 deletions(-) create mode 100644 params.go create mode 100644 provideropenssl.go create mode 100644 providersymcrypt.go diff --git a/hash.go b/hash.go index 120fc412..dc43990d 100644 --- a/hash.go +++ b/hash.go @@ -14,6 +14,25 @@ import ( "unsafe" ) +const ( + magicMD5 = "md5\x01" + magic1 = "sha\x01" + magic224 = "sha\x02" + magic256 = "sha\x03" + magic384 = "sha\x04" + magic512_224 = "sha\x05" + magic512_256 = "sha\x06" + magic512 = "sha\x07" + + marshaledSizeMD5 = len(magicMD5) + 4*4 + 64 + 8 + marshaledSize1 = len(magic1) + 5*4 + 64 + 8 + marshaledSize256 = len(magic256) + 8*4 + 64 + 8 + marshaledSize512 = len(magic512) + 8*8 + 128 + 8 +) + +// maxHashSize is the size of SHA52 and SHA3_512, the largest hashes we support. +const maxHashSize = 64 + // NOTE: Implementation ported from https://go-review.googlesource.com/c/go/+/404295. // The cgo calls in this file are arranged to avoid marking the parameters as escaping. // To do that, we call noescape (including via addr). @@ -111,35 +130,49 @@ func SHA3_512(p []byte) (sum [64]byte) { return } -var isMarshallableCache sync.Map +// provider is an identifier for a known provider. +type provider uint8 + +const ( + providerNone provider = iota + providerOSSLDefault + providerOSSLFIPS + providerSymCrypt +) + +var mdProviderCache sync.Map -// isHashMarshallable returns true if the memory layout of cb -// is known by this library and can therefore be marshalled. -func isHashMarshallable(ch crypto.Hash) bool { +// mdProvider returns the provider for the given hash. +func mdProvider(ch crypto.Hash) provider { if vMajor == 1 { - return true + return providerOSSLDefault } - if v, ok := isMarshallableCache.Load(ch); ok { - return v.(bool) + if v, ok := mdProviderCache.Load(ch); ok { + return v.(provider) } md := cryptoHashToMD(ch) if md == nil { - return false + return providerNone } prov := C.go_openssl_EVP_MD_get0_provider(md) if prov == nil { - return false + return providerNone } cname := C.go_openssl_OSSL_PROVIDER_get0_name(prov) if cname == nil { - return false + return providerNone } - name := C.GoString(cname) - // We only know the memory layout of the built-in providers. - // See evpHash.hashState for more details. - marshallable := name == "default" || name == "fips" - isMarshallableCache.Store(ch, marshallable) - return marshallable + var provider provider + switch C.GoString(cname) { + case "default": + provider = providerOSSLDefault + case "fips": + provider = providerOSSLFIPS + case "symcryptprovider": + provider = providerSymCrypt + } + mdProviderCache.Store(ch, provider) + return provider } // evpHash implements generic hash methods. @@ -152,7 +185,7 @@ type evpHash struct { size int blockSize int - marshallable bool + ch crypto.Hash } func newEvpHash(ch crypto.Hash, size, blockSize int) *evpHash { @@ -172,7 +205,7 @@ func newEvpHash(ch crypto.Hash, size, blockSize int) *evpHash { size: size, blockSize: blockSize, - marshallable: isHashMarshallable(ch), + ch: ch, } runtime.SetFinalizer(h, (*evpHash).finalize) return h @@ -249,11 +282,11 @@ func (h *evpHash) clone() (*evpHash, error) { return nil, newOpenSSLError("EVP_MD_CTX_new") } cloned := &evpHash{ - ctx: ctx, - ctx2: ctx2, - size: h.size, - blockSize: h.blockSize, - marshallable: h.marshallable, + ctx: ctx, + ctx2: ctx2, + size: h.size, + blockSize: h.blockSize, + ch: h.ch, } runtime.SetFinalizer(cloned, (*evpHash).finalize) return cloned, nil @@ -261,34 +294,67 @@ func (h *evpHash) clone() (*evpHash, error) { var testNotMarshalable bool // Used in tests. -// hashState returns a pointer to the internal hash structure. -// -// The EVP_MD_CTX memory layout has changed in OpenSSL 3 -// and the property holding the internal structure is no longer md_data but algctx. -func (h *evpHash) hashState() unsafe.Pointer { - if !h.marshallable || testNotMarshalable { - return nil - } - switch vMajor { - case 1: - // https://github.com/openssl/openssl/blob/0418e993c717a6863f206feaa40673a261de7395/crypto/evp/evp_local.h#L12. - type mdCtx struct { - _ [2]unsafe.Pointer - _ C.ulong - md_data unsafe.Pointer - } - return (*mdCtx)(unsafe.Pointer(h.ctx)).md_data - case 3: - // https://github.com/openssl/openssl/blob/5675a5aaf6a2e489022bcfc18330dae9263e598e/crypto/evp/evp_local.h#L16. - type mdCtx struct { - _ [3]unsafe.Pointer - _ C.ulong - _ [3]unsafe.Pointer - algctx unsafe.Pointer - } - return (*mdCtx)(unsafe.Pointer(h.ctx)).algctx +var errHashNotMarshallable = errors.New("openssl: hash state is not marshallable") + +func (d *evpHash) MarshalBinary() ([]byte, error) { + defer runtime.KeepAlive(d) + buf := make([]byte, 0, marshaledSize512) // stack allocate the buffer by setting the max size we support + magic, _ := cryptoHashEncodingInfo(d.ch) + if magic == "" || testNotMarshalable { + return nil, errHashNotMarshallable + } + switch mdProvider(d.ch) { + case providerOSSLDefault, providerOSSLFIPS: + return osslHashAppendBinary(d.ctx, d.ch, magic, buf) + case providerSymCrypt: + return symCryptHashAppendBinary(d.ctx, d.ch, magic, buf) default: - panic(errUnsupportedVersion()) + return nil, errHashNotMarshallable + } +} + +func (d *evpHash) UnmarshalBinary(b []byte) error { + defer runtime.KeepAlive(d) + magic, size := cryptoHashEncodingInfo(d.ch) + if magic == "" || testNotMarshalable { + return errHashNotMarshallable + } + if len(b) < len(magic) || string(b[:len(magic)]) != string(magic[:]) { + return errors.New("openssl: invalid hash state identifier") + } + if len(b) != size { + return errors.New("openssl: invalid hash state size") + } + switch mdProvider(d.ch) { + case providerOSSLDefault, providerOSSLFIPS: + return osslHashUnmarshalBinary(d.ctx, d.ch, magic, b) + case providerSymCrypt: + return symCryptHashUnmarshalBinary(d.ctx, d.ch, magic, b) + default: + return errHashNotMarshallable + } +} + +func cryptoHashEncodingInfo(ch crypto.Hash) (magic string, size int) { + switch ch { + case crypto.MD5: + return magicMD5, marshaledSizeMD5 + case crypto.SHA1: + return magic1, marshaledSize1 + case crypto.SHA224: + return magic224, marshaledSize256 + case crypto.SHA256: + return magic256, marshaledSize256 + case crypto.SHA384: + return magic384, marshaledSize512 + case crypto.SHA512_224: + return magic512_224, marshaledSize512 + case crypto.SHA512_256: + return magic512_256, marshaledSize512 + case crypto.SHA512: + return magic512, marshaledSize512 + default: + return "", 0 } } @@ -318,15 +384,6 @@ func NewMD5() hash.Hash { } } -// md5State layout is taken from -// https://github.com/openssl/openssl/blob/0418e993c717a6863f206feaa40673a261de7395/include/openssl/md5.h#L33. -type md5State struct { - h [4]uint32 - nl, nh uint32 - x [64]byte - nx uint32 -} - type md5Hash struct { *evpHash out [16]byte @@ -337,52 +394,6 @@ func (h *md5Hash) Sum(in []byte) []byte { return append(in, h.out[:]...) } -const ( - md5Magic = "md5\x01" - md5MarshaledSize = len(md5Magic) + 4*4 + 64 + 8 -) - -func (h *md5Hash) MarshalBinary() ([]byte, error) { - d := (*md5State)(h.hashState()) - if d == nil { - return nil, errors.New("crypto/md5: can't retrieve hash state") - } - b := make([]byte, 0, md5MarshaledSize) - b = append(b, md5Magic...) - b = appendUint32(b, d.h[0]) - b = appendUint32(b, d.h[1]) - b = appendUint32(b, d.h[2]) - b = appendUint32(b, d.h[3]) - b = append(b, d.x[:d.nx]...) - b = b[:len(b)+len(d.x)-int(d.nx)] // already zero - b = appendUint64(b, uint64(d.nl)>>3|uint64(d.nh)<<29) - return b, nil -} - -func (h *md5Hash) UnmarshalBinary(b []byte) error { - if len(b) < len(md5Magic) || string(b[:len(md5Magic)]) != md5Magic { - return errors.New("crypto/md5: invalid hash state identifier") - } - if len(b) != md5MarshaledSize { - return errors.New("crypto/md5: invalid hash state size") - } - d := (*md5State)(h.hashState()) - if d == nil { - return errors.New("crypto/md5: can't retrieve hash state") - } - b = b[len(md5Magic):] - b, d.h[0] = consumeUint32(b) - b, d.h[1] = consumeUint32(b) - b, d.h[2] = consumeUint32(b) - b, d.h[3] = consumeUint32(b) - b = b[copy(d.x[:], b):] - _, n := consumeUint64(b) - d.nl = uint32(n << 3) - d.nh = uint32(n >> 29) - d.nx = uint32(n) % 64 - return nil -} - // NewSHA1 returns a new SHA1 hash. func NewSHA1() hash.Hash { return &sha1Hash{ @@ -400,63 +411,6 @@ func (h *sha1Hash) Sum(in []byte) []byte { return append(in, h.out[:]...) } -// sha1State layout is taken from -// https://github.com/openssl/openssl/blob/0418e993c717a6863f206feaa40673a261de7395/include/openssl/sha.h#L34. -type sha1State struct { - h [5]uint32 - nl, nh uint32 - x [64]byte - nx uint32 -} - -const ( - sha1Magic = "sha\x01" - sha1MarshaledSize = len(sha1Magic) + 5*4 + 64 + 8 -) - -func (h *sha1Hash) MarshalBinary() ([]byte, error) { - d := (*sha1State)(h.hashState()) - if d == nil { - return nil, errors.New("crypto/sha1: can't retrieve hash state") - } - b := make([]byte, 0, sha1MarshaledSize) - b = append(b, sha1Magic...) - b = appendUint32(b, d.h[0]) - b = appendUint32(b, d.h[1]) - b = appendUint32(b, d.h[2]) - b = appendUint32(b, d.h[3]) - b = appendUint32(b, d.h[4]) - b = append(b, d.x[:d.nx]...) - b = b[:len(b)+len(d.x)-int(d.nx)] // already zero - b = appendUint64(b, uint64(d.nl)>>3|uint64(d.nh)<<29) - return b, nil -} - -func (h *sha1Hash) UnmarshalBinary(b []byte) error { - if len(b) < len(sha1Magic) || string(b[:len(sha1Magic)]) != sha1Magic { - return errors.New("crypto/sha1: invalid hash state identifier") - } - if len(b) != sha1MarshaledSize { - return errors.New("crypto/sha1: invalid hash state size") - } - d := (*sha1State)(h.hashState()) - if d == nil { - return errors.New("crypto/sha1: can't retrieve hash state") - } - b = b[len(sha1Magic):] - b, d.h[0] = consumeUint32(b) - b, d.h[1] = consumeUint32(b) - b, d.h[2] = consumeUint32(b) - b, d.h[3] = consumeUint32(b) - b, d.h[4] = consumeUint32(b) - b = b[copy(d.x[:], b):] - _, n := consumeUint64(b) - d.nl = uint32(n << 3) - d.nh = uint32(n >> 29) - d.nx = uint32(n) % 64 - return nil -} - // NewSHA224 returns a new SHA224 hash. func NewSHA224() hash.Hash { return &sha224Hash{ @@ -491,119 +445,6 @@ func (h *sha256Hash) Sum(in []byte) []byte { return append(in, h.out[:]...) } -const ( - magic224 = "sha\x02" - magic256 = "sha\x03" - marshaledSize256 = len(magic256) + 8*4 + 64 + 8 -) - -// sha256State layout is taken from -// https://github.com/openssl/openssl/blob/0418e993c717a6863f206feaa40673a261de7395/include/openssl/sha.h#L51. -type sha256State struct { - h [8]uint32 - nl, nh uint32 - x [64]byte - nx uint32 -} - -func (h *sha224Hash) MarshalBinary() ([]byte, error) { - d := (*sha256State)(h.hashState()) - if d == nil { - return nil, errors.New("crypto/sha256: can't retrieve hash state") - } - b := make([]byte, 0, marshaledSize256) - b = append(b, magic224...) - b = appendUint32(b, d.h[0]) - b = appendUint32(b, d.h[1]) - b = appendUint32(b, d.h[2]) - b = appendUint32(b, d.h[3]) - b = appendUint32(b, d.h[4]) - b = appendUint32(b, d.h[5]) - b = appendUint32(b, d.h[6]) - b = appendUint32(b, d.h[7]) - b = append(b, d.x[:d.nx]...) - b = b[:len(b)+len(d.x)-int(d.nx)] // already zero - b = appendUint64(b, uint64(d.nl)>>3|uint64(d.nh)<<29) - return b, nil -} - -func (h *sha256Hash) MarshalBinary() ([]byte, error) { - d := (*sha256State)(h.hashState()) - if d == nil { - return nil, errors.New("crypto/sha256: can't retrieve hash state") - } - b := make([]byte, 0, marshaledSize256) - b = append(b, magic256...) - b = appendUint32(b, d.h[0]) - b = appendUint32(b, d.h[1]) - b = appendUint32(b, d.h[2]) - b = appendUint32(b, d.h[3]) - b = appendUint32(b, d.h[4]) - b = appendUint32(b, d.h[5]) - b = appendUint32(b, d.h[6]) - b = appendUint32(b, d.h[7]) - b = append(b, d.x[:d.nx]...) - b = b[:len(b)+len(d.x)-int(d.nx)] // already zero - b = appendUint64(b, uint64(d.nl)>>3|uint64(d.nh)<<29) - return b, nil -} - -func (h *sha224Hash) UnmarshalBinary(b []byte) error { - if len(b) < len(magic224) || string(b[:len(magic224)]) != magic224 { - return errors.New("crypto/sha256: invalid hash state identifier") - } - if len(b) != marshaledSize256 { - return errors.New("crypto/sha256: invalid hash state size") - } - d := (*sha256State)(h.hashState()) - if d == nil { - return errors.New("crypto/sha256: can't retrieve hash state") - } - b = b[len(magic224):] - b, d.h[0] = consumeUint32(b) - b, d.h[1] = consumeUint32(b) - b, d.h[2] = consumeUint32(b) - b, d.h[3] = consumeUint32(b) - b, d.h[4] = consumeUint32(b) - b, d.h[5] = consumeUint32(b) - b, d.h[6] = consumeUint32(b) - b, d.h[7] = consumeUint32(b) - b = b[copy(d.x[:], b):] - _, n := consumeUint64(b) - d.nl = uint32(n << 3) - d.nh = uint32(n >> 29) - d.nx = uint32(n) % 64 - return nil -} - -func (h *sha256Hash) UnmarshalBinary(b []byte) error { - if len(b) < len(magic256) || string(b[:len(magic256)]) != magic256 { - return errors.New("crypto/sha256: invalid hash state identifier") - } - if len(b) != marshaledSize256 { - return errors.New("crypto/sha256: invalid hash state size") - } - d := (*sha256State)(h.hashState()) - if d == nil { - return errors.New("crypto/sha256: can't retrieve hash state") - } - b = b[len(magic256):] - b, d.h[0] = consumeUint32(b) - b, d.h[1] = consumeUint32(b) - b, d.h[2] = consumeUint32(b) - b, d.h[3] = consumeUint32(b) - b, d.h[4] = consumeUint32(b) - b, d.h[5] = consumeUint32(b) - b, d.h[6] = consumeUint32(b) - b, d.h[7] = consumeUint32(b) - b = b[copy(d.x[:], b):] - _, n := consumeUint64(b) - d.nl = uint32(n << 3) - d.nh = uint32(n >> 29) - d.nx = uint32(n) % 64 - return nil -} - // Clone returns a new [hash.Hash] object that is a deep clone of itself. // The duplicate object contains all state and data contained in the // original object at the point of duplication. @@ -660,127 +501,6 @@ func (h *sha512Hash) Sum(in []byte) []byte { return append(in, h.out[:]...) } -// sha512State layout is taken from -// https://github.com/openssl/openssl/blob/0418e993c717a6863f206feaa40673a261de7395/include/openssl/sha.h#L95. -type sha512State struct { - h [8]uint64 - nl, nh uint64 - x [128]byte - nx uint32 -} - -const ( - magic384 = "sha\x04" - magic512_224 = "sha\x05" - magic512_256 = "sha\x06" - magic512 = "sha\x07" - marshaledSize512 = len(magic512) + 8*8 + 128 + 8 -) - -func (h *sha384Hash) MarshalBinary() ([]byte, error) { - d := (*sha512State)(h.hashState()) - if d == nil { - return nil, errors.New("crypto/sha512: can't retrieve hash state") - } - b := make([]byte, 0, marshaledSize512) - b = append(b, magic384...) - b = appendUint64(b, d.h[0]) - b = appendUint64(b, d.h[1]) - b = appendUint64(b, d.h[2]) - b = appendUint64(b, d.h[3]) - b = appendUint64(b, d.h[4]) - b = appendUint64(b, d.h[5]) - b = appendUint64(b, d.h[6]) - b = appendUint64(b, d.h[7]) - b = append(b, d.x[:d.nx]...) - b = b[:len(b)+len(d.x)-int(d.nx)] // already zero - b = appendUint64(b, d.nl>>3|d.nh<<61) - return b, nil -} - -func (h *sha512Hash) MarshalBinary() ([]byte, error) { - d := (*sha512State)(h.hashState()) - if d == nil { - return nil, errors.New("crypto/sha512: can't retrieve hash state") - } - b := make([]byte, 0, marshaledSize512) - b = append(b, magic512...) - b = appendUint64(b, d.h[0]) - b = appendUint64(b, d.h[1]) - b = appendUint64(b, d.h[2]) - b = appendUint64(b, d.h[3]) - b = appendUint64(b, d.h[4]) - b = appendUint64(b, d.h[5]) - b = appendUint64(b, d.h[6]) - b = appendUint64(b, d.h[7]) - b = append(b, d.x[:d.nx]...) - b = b[:len(b)+len(d.x)-int(d.nx)] // already zero - b = appendUint64(b, d.nl>>3|d.nh<<61) - return b, nil -} - -func (h *sha384Hash) UnmarshalBinary(b []byte) error { - if len(b) < len(magic512) { - return errors.New("crypto/sha512: invalid hash state identifier") - } - if string(b[:len(magic384)]) != magic384 { - return errors.New("crypto/sha512: invalid hash state identifier") - } - if len(b) != marshaledSize512 { - return errors.New("crypto/sha512: invalid hash state size") - } - d := (*sha512State)(h.hashState()) - if d == nil { - return errors.New("crypto/sha512: can't retrieve hash state") - } - b = b[len(magic512):] - b, d.h[0] = consumeUint64(b) - b, d.h[1] = consumeUint64(b) - b, d.h[2] = consumeUint64(b) - b, d.h[3] = consumeUint64(b) - b, d.h[4] = consumeUint64(b) - b, d.h[5] = consumeUint64(b) - b, d.h[6] = consumeUint64(b) - b, d.h[7] = consumeUint64(b) - b = b[copy(d.x[:], b):] - _, n := consumeUint64(b) - d.nl = n << 3 - d.nh = n >> 61 - d.nx = uint32(n) % 128 - return nil -} - -func (h *sha512Hash) UnmarshalBinary(b []byte) error { - if len(b) < len(magic512) { - return errors.New("crypto/sha512: invalid hash state identifier") - } - if string(b[:len(magic512)]) != magic512 { - return errors.New("crypto/sha512: invalid hash state identifier") - } - if len(b) != marshaledSize512 { - return errors.New("crypto/sha512: invalid hash state size") - } - d := (*sha512State)(h.hashState()) - if d == nil { - return errors.New("crypto/sha512: can't retrieve hash state") - } - b = b[len(magic512):] - b, d.h[0] = consumeUint64(b) - b, d.h[1] = consumeUint64(b) - b, d.h[2] = consumeUint64(b) - b, d.h[3] = consumeUint64(b) - b, d.h[4] = consumeUint64(b) - b, d.h[5] = consumeUint64(b) - b, d.h[6] = consumeUint64(b) - b, d.h[7] = consumeUint64(b) - b = b[copy(d.x[:], b):] - _, n := consumeUint64(b) - d.nl = n << 3 - d.nh = n >> 61 - d.nx = uint32(n) % 128 - return nil -} - // Clone returns a new [hash.Hash] object that is a deep clone of itself. // The duplicate object contains all state and data contained in the // original object at the point of duplication. diff --git a/hash_test.go b/hash_test.go index c7103e28..9f9d2b7d 100644 --- a/hash_test.go +++ b/hash_test.go @@ -6,8 +6,15 @@ import ( "encoding" "hash" "io" + "strings" "testing" + // Blank imports to ensure that the hash functions are registered. + _ "crypto/md5" + _ "crypto/sha1" + _ "crypto/sha256" + _ "crypto/sha512" + "github.com/golang-fips/openssl/v2" ) @@ -153,20 +160,118 @@ func TestHash(t *testing.T) { if !bytes.Equal(sum, initSum) { t.Errorf("got:%x want:%x", sum, initSum) } + }) + } +} + +type hashEncoding interface { + hash.Hash + encoding.BinaryMarshaler + encoding.BinaryUnmarshaler +} + +type hashEncodingAppender interface { + hashEncoding + AppendBinary(b []byte) ([]byte, error) +} + +func TestHash_BinaryMarshaler(t *testing.T) { + msg := []byte("testing") + for _, ch := range []crypto.Hash{crypto.MD4, crypto.MD5, crypto.SHA1, crypto.SHA224, crypto.SHA256, crypto.SHA384, crypto.SHA512} { + t.Run(ch.String(), func(t *testing.T) { + t.Parallel() + if !openssl.SupportsHash(ch) { + t.Skip("hash not supported") + } - bw := h.(io.ByteWriter) + hashMarshaler, ok := cryptoToHash(ch)().(hashEncoding) + if !ok { + t.Fatal("BinaryMarshaler not supported") + } + + if _, err := hashMarshaler.Write(msg); err != nil { + t.Fatalf("Write failed: %v", err) + } + + state, err := hashMarshaler.MarshalBinary() + if err != nil { + if strings.Contains(err.Error(), "hash state is not marshallable") { + t.Skip("BinaryMarshaler not supported") + } + t.Fatalf("MarshalBinary failed: %v", err) + } + + hashUnmarshaler := cryptoToHash(ch)().(hashEncoding) + if err := hashUnmarshaler.UnmarshalBinary(state); err != nil { + t.Fatalf("UnmarshalBinary failed: %v", err) + } + + if actual, actual2 := hashMarshaler.Sum(nil), hashUnmarshaler.Sum(nil); !bytes.Equal(actual, actual2) { + t.Errorf("0x%x != appended 0x%x", actual, actual2) + } + + // Test that the hash state is compatible with native Go. + h, ok := ch.New().(hashEncoding) + if !ok { + // The standard library doesn't support encoding this hash. + // Nothing else to do. + return + } + h.Write(msg) + stateh, err := h.(encoding.BinaryMarshaler).MarshalBinary() + if err != nil { + t.Error(err) + } + if !bytes.Equal(state, stateh) { + t.Errorf("got 0x%x != want 0x%x", state, stateh) + } + h = ch.New().(hashEncoding) + if err := h.UnmarshalBinary(state); err != nil { + t.Error(err) + } + }) + } +} + +func TestHash_ByteWriter(t *testing.T) { + msg := []byte("testing") + for _, ch := range []crypto.Hash{crypto.MD5, crypto.SHA1, crypto.SHA224, crypto.SHA256, crypto.SHA384, crypto.SHA512} { + t.Run(ch.String(), func(t *testing.T) { + t.Parallel() + if !openssl.SupportsHash(ch) { + t.Skip("not supported") + } + bwh := cryptoToHash(ch)().(interface { + hash.Hash + io.ByteWriter + }) + initSum := bwh.Sum(nil) for i := 0; i < len(msg); i++ { - bw.WriteByte(msg[i]) + bwh.WriteByte(msg[i]) } - h.Reset() - sum = h.Sum(nil) + bwh.Reset() + sum := bwh.Sum(nil) if !bytes.Equal(sum, initSum) { t.Errorf("got:%x want:%x", sum, initSum) } + }) + } +} +func TestHash_StringWriter(t *testing.T) { + msg := []byte("testing") + for _, ch := range []crypto.Hash{crypto.MD5, crypto.SHA1, crypto.SHA224, crypto.SHA256, crypto.SHA384, crypto.SHA512} { + t.Run(ch.String(), func(t *testing.T) { + t.Parallel() + if !openssl.SupportsHash(ch) { + t.Skip("not supported") + } + h := cryptoToHash(ch)() + initSum := h.Sum(nil) + h.(io.StringWriter).WriteString("") h.(io.StringWriter).WriteString(string(msg)) h.Reset() - sum = h.Sum(nil) + sum := h.Sum(nil) if !bytes.Equal(sum, initSum) { t.Errorf("got:%x want:%x", sum, initSum) } @@ -222,6 +327,7 @@ func TestHash_OneShot(t *testing.T) { if !openssl.SupportsHash(tt.h) { t.Skip("skipping: not supported") } + _ = tt.oneShot(nil) // test that does not panic got := tt.oneShot(msg) h := cryptoToHash(tt.h)() h.Write(msg) diff --git a/params.go b/params.go new file mode 100644 index 00000000..8d99d770 --- /dev/null +++ b/params.go @@ -0,0 +1,38 @@ +//go:build !cmd_go_bootstrap + +package openssl + +// #include "goopenssl.h" +import "C" +import ( + "math" + "unsafe" +) + +// _OSSL_PARAM is a structure to pass or request object parameters. +// https://docs.openssl.org/3.0/man3/OSSL_PARAM/. +type _OSSL_PARAM struct { + Key *C.char + DataType uint32 + Data unsafe.Pointer + DataSize int + ReturnSize int +} + +func ossl_param_construct(key *C.char, dataType uint32, data unsafe.Pointer, dataSize int) _OSSL_PARAM { + return _OSSL_PARAM{ + Key: key, + DataType: dataType, + Data: data, + DataSize: dataSize, + ReturnSize: math.MaxInt - 1, + } +} + +func _OSSL_PARAM_construct_octet_string(key *C.char, data unsafe.Pointer, dataSize int) _OSSL_PARAM { + return ossl_param_construct(key, C.GO_OSSL_PARAM_OCTET_STRING, data, dataSize) +} + +func _OSSL_PARAM_construct_end() _OSSL_PARAM { + return _OSSL_PARAM{} +} diff --git a/provideropenssl.go b/provideropenssl.go new file mode 100644 index 00000000..912bcbbf --- /dev/null +++ b/provideropenssl.go @@ -0,0 +1,239 @@ +//go:build !cmd_go_bootstrap + +package openssl + +// #include "goopenssl.h" +import "C" +import ( + "crypto" + "errors" + "unsafe" +) + +// This file contains code specific to the built-in OpenSSL providers. + +// _OSSL_MD5_CTX layout is taken from +// https://github.com/openssl/openssl/blob/0418e993c717a6863f206feaa40673a261de7395/include/openssl/md5.h#L33. +type _OSSL_MD5_CTX struct { + h [4]uint32 + nl, nh uint32 + x [64]byte + nx uint32 +} + +func (d *_OSSL_MD5_CTX) UnmarshalBinary(b []byte) error { + b, d.h[0] = consumeUint32(b) + b, d.h[1] = consumeUint32(b) + b, d.h[2] = consumeUint32(b) + b, d.h[3] = consumeUint32(b) + b = b[copy(d.x[:], b):] + _, n := consumeUint64(b) + d.nl = uint32(n << 3) + d.nh = uint32(n >> 29) + d.nx = uint32(n) % 64 + return nil +} + +func (d *_OSSL_MD5_CTX) AppendBinary(buf []byte) ([]byte, error) { + buf = appendUint32(buf, d.h[0]) + buf = appendUint32(buf, d.h[1]) + buf = appendUint32(buf, d.h[2]) + buf = appendUint32(buf, d.h[3]) + buf = append(buf, d.x[:d.nx]...) + buf = append(buf, make([]byte, len(d.x)-int(d.nx))...) + buf = appendUint64(buf, uint64(d.nl)>>3|uint64(d.nh)<<29) + return buf, nil +} + +// _OSSL_SHA_CTX layout is taken from +// https://github.com/openssl/openssl/blob/0418e993c717a6863f206feaa40673a261de7395/include/openssl/sha.h#L34. +type _OSSL_SHA_CTX struct { + h [5]uint32 + nl, nh uint32 + x [64]byte + nx uint32 +} + +func (d *_OSSL_SHA_CTX) UnmarshalBinary(b []byte) error { + b, d.h[0] = consumeUint32(b) + b, d.h[1] = consumeUint32(b) + b, d.h[2] = consumeUint32(b) + b, d.h[3] = consumeUint32(b) + b, d.h[4] = consumeUint32(b) + b = b[copy(d.x[:], b):] + _, n := consumeUint64(b) + d.nl = uint32(n << 3) + d.nh = uint32(n >> 29) + d.nx = uint32(n) % 64 + return nil +} + +func (d *_OSSL_SHA_CTX) AppendBinary(buf []byte) ([]byte, error) { + buf = appendUint32(buf, d.h[0]) + buf = appendUint32(buf, d.h[1]) + buf = appendUint32(buf, d.h[2]) + buf = appendUint32(buf, d.h[3]) + buf = appendUint32(buf, d.h[4]) + buf = append(buf, d.x[:d.nx]...) + buf = append(buf, make([]byte, len(d.x)-int(d.nx))...) + buf = appendUint64(buf, uint64(d.nl)>>3|uint64(d.nh)<<29) + return buf, nil +} + +// _OSSL_SHA256_CTX layout is taken from +// https://github.com/openssl/openssl/blob/0418e993c717a6863f206feaa40673a261de7395/include/openssl/sha.h#L51. +type _OSSL_SHA256_CTX struct { + h [8]uint32 + nl, nh uint32 + x [64]byte + nx uint32 +} + +func (d *_OSSL_SHA256_CTX) UnmarshalBinary(b []byte) error { + b, d.h[0] = consumeUint32(b) + b, d.h[1] = consumeUint32(b) + b, d.h[2] = consumeUint32(b) + b, d.h[3] = consumeUint32(b) + b, d.h[4] = consumeUint32(b) + b, d.h[5] = consumeUint32(b) + b, d.h[6] = consumeUint32(b) + b, d.h[7] = consumeUint32(b) + b = b[copy(d.x[:], b):] + _, n := consumeUint64(b) + d.nl = uint32(n << 3) + d.nh = uint32(n >> 29) + d.nx = uint32(n) % 64 + return nil +} + +func (d *_OSSL_SHA256_CTX) AppendBinary(buf []byte) ([]byte, error) { + buf = appendUint32(buf, d.h[0]) + buf = appendUint32(buf, d.h[1]) + buf = appendUint32(buf, d.h[2]) + buf = appendUint32(buf, d.h[3]) + buf = appendUint32(buf, d.h[4]) + buf = appendUint32(buf, d.h[5]) + buf = appendUint32(buf, d.h[6]) + buf = appendUint32(buf, d.h[7]) + buf = append(buf, d.x[:d.nx]...) + buf = append(buf, make([]byte, len(d.x)-int(d.nx))...) + buf = appendUint64(buf, uint64(d.nl)>>3|uint64(d.nh)<<29) + return buf, nil +} + +// _OSSL_SHA512_CTX layout is taken from +// https://github.com/openssl/openssl/blob/0418e993c717a6863f206feaa40673a261de7395/include/openssl/sha.h#L95. +type _OSSL_SHA512_CTX struct { + h [8]uint64 + nl, nh uint64 + x [128]byte + nx uint32 +} + +func (d *_OSSL_SHA512_CTX) UnmarshalBinary(b []byte) error { + b, d.h[0] = consumeUint64(b) + b, d.h[1] = consumeUint64(b) + b, d.h[2] = consumeUint64(b) + b, d.h[3] = consumeUint64(b) + b, d.h[4] = consumeUint64(b) + b, d.h[5] = consumeUint64(b) + b, d.h[6] = consumeUint64(b) + b, d.h[7] = consumeUint64(b) + b = b[copy(d.x[:], b):] + _, n := consumeUint64(b) + d.nl = n << 3 + d.nh = n >> 61 + d.nx = uint32(n) % 128 + return nil +} + +func (d *_OSSL_SHA512_CTX) AppendBinary(buf []byte) ([]byte, error) { + buf = appendUint64(buf, d.h[0]) + buf = appendUint64(buf, d.h[1]) + buf = appendUint64(buf, d.h[2]) + buf = appendUint64(buf, d.h[3]) + buf = appendUint64(buf, d.h[4]) + buf = appendUint64(buf, d.h[5]) + buf = appendUint64(buf, d.h[6]) + buf = appendUint64(buf, d.h[7]) + buf = append(buf, d.x[:d.nx]...) + buf = append(buf, make([]byte, len(d.x)-int(d.nx))...) + buf = appendUint64(buf, d.nl>>3|d.nh<<61) + return buf, nil +} + +func getOSSLDigetsContext(ctx C.GO_EVP_MD_CTX_PTR) unsafe.Pointer { + switch vMajor { + case 1: + // https://github.com/openssl/openssl/blob/0418e993c717a6863f206feaa40673a261de7395/crypto/evp/evp_local.h#L12. + type mdCtx struct { + _ [2]unsafe.Pointer + _ uint32 + md_data unsafe.Pointer + } + return (*mdCtx)(unsafe.Pointer(ctx)).md_data + case 3: + // The EVP_MD_CTX memory layout has changed in OpenSSL 3 + // and the property holding the internal structure is no longer md_data but algctx. + // https://github.com/openssl/openssl/blob/5675a5aaf6a2e489022bcfc18330dae9263e598e/crypto/evp/evp_local.h#L16. + type mdCtx struct { + _ [3]unsafe.Pointer + _ uint32 + _ [3]unsafe.Pointer + algctx unsafe.Pointer + } + return (*mdCtx)(unsafe.Pointer(ctx)).algctx + default: + panic(errUnsupportedVersion()) + } +} + +var errHashStateInvalid = errors.New("openssl: can't retrieve hash state") + +func osslHashAppendBinary(ctx C.GO_EVP_MD_CTX_PTR, ch crypto.Hash, magic string, buf []byte) ([]byte, error) { + algctx := getOSSLDigetsContext(ctx) + if algctx == nil { + return nil, errHashStateInvalid + } + buf = append(buf, magic...) + switch ch { + case crypto.MD5: + d := (*_OSSL_MD5_CTX)(unsafe.Pointer(algctx)) + return d.AppendBinary(buf) + case crypto.SHA1: + d := (*_OSSL_SHA_CTX)(unsafe.Pointer(algctx)) + return d.AppendBinary(buf) + case crypto.SHA224, crypto.SHA256: + d := (*_OSSL_SHA256_CTX)(unsafe.Pointer(algctx)) + return d.AppendBinary(buf) + case crypto.SHA384, crypto.SHA512_224, crypto.SHA512_256, crypto.SHA512: + d := (*_OSSL_SHA512_CTX)(unsafe.Pointer(algctx)) + return d.AppendBinary(buf) + default: + panic("unsupported hash " + ch.String()) + } +} + +func osslHashUnmarshalBinary(ctx C.GO_EVP_MD_CTX_PTR, ch crypto.Hash, magic string, b []byte) error { + algctx := getOSSLDigetsContext(ctx) + if algctx == nil { + return errHashStateInvalid + } + b = b[len(magic):] + switch ch { + case crypto.MD5: + d := (*_OSSL_MD5_CTX)(unsafe.Pointer(algctx)) + return d.UnmarshalBinary(b) + case crypto.SHA1: + d := (*_OSSL_SHA_CTX)(unsafe.Pointer(algctx)) + return d.UnmarshalBinary(b) + case crypto.SHA224, crypto.SHA256: + d := (*_OSSL_SHA256_CTX)(unsafe.Pointer(algctx)) + return d.UnmarshalBinary(b) + case crypto.SHA384, crypto.SHA512_224, crypto.SHA512_256, crypto.SHA512: + d := (*_OSSL_SHA512_CTX)(unsafe.Pointer(algctx)) + return d.UnmarshalBinary(b) + default: + panic("unsupported hash " + ch.String()) + } +} diff --git a/providersymcrypt.go b/providersymcrypt.go new file mode 100644 index 00000000..26ffbce7 --- /dev/null +++ b/providersymcrypt.go @@ -0,0 +1,380 @@ +//go:build !cmd_go_bootstrap + +package openssl + +// #include "goopenssl.h" +import "C" +import ( + "crypto" + "encoding/binary" + "errors" + "runtime" + "sync" + "unsafe" +) + +// This file contains code specific to the SymCrypt provider. + +var ( + _SCOSSL_DIGEST_PARAM_STATE = C.CString("state") + _SCOSSL_DIGEST_PARAM_RECOMPUTE_CHECKSUM = C.CString("recompute_checksum") +) + +const ( + _SYMCRYPT_BLOB_MAGIC = 0x636D7973 // "cysm" in little-endian + + _SymCryptBlobTypeHashState = 0x100 + _SymCryptBlobTypeMd2State = _SymCryptBlobTypeHashState + 1 + _SymCryptBlobTypeMd4State = _SymCryptBlobTypeHashState + 2 + _SymCryptBlobTypeMd5State = _SymCryptBlobTypeHashState + 3 + _SymCryptBlobTypeSha1State = _SymCryptBlobTypeHashState + 4 + _SymCryptBlobTypeSha256State = _SymCryptBlobTypeHashState + 5 + _SymCryptBlobTypeSha384State = _SymCryptBlobTypeHashState + 6 + _SymCryptBlobTypeSha512State = _SymCryptBlobTypeHashState + 7 + _SymCryptBlobTypeSha3_256State = _SymCryptBlobTypeHashState + 8 + _SymCryptBlobTypeSha3_384State = _SymCryptBlobTypeHashState + 9 + _SymCryptBlobTypeSha3_512State = _SymCryptBlobTypeHashState + 10 + _SymCryptBlobTypeSha224State = _SymCryptBlobTypeHashState + 11 + _SymCryptBlobTypeSha512_224State = _SymCryptBlobTypeHashState + 12 + _SymCryptBlobTypeSha512_256State = _SymCryptBlobTypeHashState + 13 + _SymCryptBlobTypeSha3_224State = _SymCryptBlobTypeHashState + 14 + + _SYMCRYPT_MD5_STATE_EXPORT_SIZE = uint32(unsafe.Sizeof(_SYMCRYPT_MD5_STATE_EXPORT_BLOB{})) + _SYMCRYPT_SHA1_STATE_EXPORT_SIZE = uint32(unsafe.Sizeof(_SYMCRYPT_SHA1_STATE_EXPORT_BLOB{})) + _SYMCRYPT_SHA256_STATE_EXPORT_SIZE = uint32(unsafe.Sizeof(_SYMCRYPT_SHA256_STATE_EXPORT_BLOB{})) + _SYMCRYPT_SHA512_STATE_EXPORT_SIZE = uint32(unsafe.Sizeof(_SYMCRYPT_SHA512_STATE_EXPORT_BLOB{})) +) + +type _SYMCRYPT_BLOB_HEADER struct { + magic uint32 + size uint32 + _type uint32 +} + +type _SYMCRYPT_BLOB_TRAILER struct { + checksum [8]uint8 +} + +// _UINT64 is a 64-bit unsigned integer, stored in native endianess. +// It is used to represent a SymCrypt UINT64 type without making the +// parent struct 8-byte aligned, given that the Windows ABI makes +// the struct 4-byte aligned. +type _UINT64 [2]uint32 + +func newUINT64(v uint64) _UINT64 { + var u _UINT64 + if nativeEndian == binary.BigEndian { + u[0], u[1] = uint32(v>>32), uint32(v) + } else { + u[0], u[1] = uint32(v), uint32(v>>32) + } + return u +} + +func (u *_UINT64) uint64() uint64 { + if nativeEndian == binary.BigEndian { + return uint64(u[0])<<32 | (uint64(u[1])) + } + return uint64(u[0]) | (uint64(u[1]) << 32) +} + +// symCryptAppendBinary appends the binary representation of a SymCrypt state +// to the given destination slice. +func symCryptAppendBinary(dst, chain, buffer []byte, blength _UINT64) []byte { + length := blength.uint64() + var nx uint64 + if len(buffer) <= 64 { + nx = length & 0x3f + } else { + nx = length & 0x7f + } + dst = append(dst, chain...) + dst = append(dst, buffer[:nx]...) + dst = append(dst, make([]byte, len(buffer)-int(nx))...) + dst = appendUint64(dst, length) + return dst +} + +// symCryptUnmarshalBinary unmarshals the binary representation of a SymCrypt state +// from the given source slice. It returns the length of the data. +func symCryptUnmarshalBinary(d []byte, chain, buffer []byte) _UINT64 { + copy(chain[:], d) + d = d[len(chain):] + copy(buffer[:], d) + d = d[len(buffer):] + _, length := consumeUint64(d) + return newUINT64(length) +} + +// swapEndianessInt32 swaps the endianness of the given byte slice +// in place. It assumes the slice is a backup of a 32-bit integer array. +func swapEndianessInt32(d []uint8) { + for i := 0; i < len(d); i += 4 { + d[i], d[i+3] = d[i+3], d[i] + d[i+1], d[i+2] = d[i+2], d[i+1] + } + +} + +type _SYMCRYPT_MD5_STATE_EXPORT_BLOB struct { + header _SYMCRYPT_BLOB_HEADER + chain [16]uint8 // little endian + length _UINT64 // native endian + buffer [64]uint8 + _ [8]uint8 // reserved + _ _SYMCRYPT_BLOB_TRAILER +} + +func (b *_SYMCRYPT_MD5_STATE_EXPORT_BLOB) appendBinary(d []byte) ([]byte, error) { + // b.chain is little endian, but Go expects big endian, + // we need to swap the bytes. + swapEndianessInt32(b.chain[:]) + return symCryptAppendBinary(d, b.chain[:], b.buffer[:], b.length), nil +} + +func (b *_SYMCRYPT_MD5_STATE_EXPORT_BLOB) unmarshalBinary(d []byte) { + b.length = symCryptUnmarshalBinary(d, b.chain[:], b.buffer[:]) + swapEndianessInt32(b.chain[:]) +} + +type _SYMCRYPT_SHA1_STATE_EXPORT_BLOB struct { + header _SYMCRYPT_BLOB_HEADER + chain [20]uint8 // big endian + length _UINT64 // native endian + buffer [64]uint8 + _ [8]uint8 // reserved + _ _SYMCRYPT_BLOB_TRAILER +} + +func (b *_SYMCRYPT_SHA1_STATE_EXPORT_BLOB) appendBinary(d []byte) ([]byte, error) { + return symCryptAppendBinary(d, b.chain[:], b.buffer[:], b.length), nil +} + +func (b *_SYMCRYPT_SHA1_STATE_EXPORT_BLOB) unmarshalBinary(d []byte) { + b.length = symCryptUnmarshalBinary(d, b.chain[:], b.buffer[:]) +} + +type _SYMCRYPT_SHA256_STATE_EXPORT_BLOB struct { + header _SYMCRYPT_BLOB_HEADER + chain [32]uint8 // big endian + length _UINT64 // native endian + buffer [64]uint8 + _ [8]uint8 // reserved + _ _SYMCRYPT_BLOB_TRAILER +} + +func (b *_SYMCRYPT_SHA256_STATE_EXPORT_BLOB) appendBinary(d []byte) ([]byte, error) { + return symCryptAppendBinary(d, b.chain[:], b.buffer[:], b.length), nil +} + +func (b *_SYMCRYPT_SHA256_STATE_EXPORT_BLOB) unmarshalBinary(d []byte) { + b.length = symCryptUnmarshalBinary(d, b.chain[:], b.buffer[:]) +} + +type _SYMCRYPT_SHA512_STATE_EXPORT_BLOB struct { + header _SYMCRYPT_BLOB_HEADER + chain [64]uint8 // big endian + lengthL _UINT64 // native endian + lengthH _UINT64 // native endian + buffer [128]uint8 + _ [8]uint8 // reserved + _ _SYMCRYPT_BLOB_TRAILER +} + +func (b *_SYMCRYPT_SHA512_STATE_EXPORT_BLOB) appendBinary(d []byte) ([]byte, error) { + if b.lengthH.uint64() != 0 { + return nil, errors.New("exporting state with more than 2^63-1 bytes of data is not supported") + } + return symCryptAppendBinary(d, b.chain[:], b.buffer[:], b.lengthL), nil +} + +func (b *_SYMCRYPT_SHA512_STATE_EXPORT_BLOB) unmarshalBinary(d []byte) { + b.lengthL = symCryptUnmarshalBinary(d, b.chain[:], b.buffer[:]) +} + +func symCryptHashAppendBinary(ctx C.GO_EVP_MD_CTX_PTR, ch crypto.Hash, magic string, buf []byte) ([]byte, error) { + size, typ, serializable := symCryptHashStateInfo(ch) + if !serializable { + return nil, errHashNotMarshallable + } + state := make([]byte, size, _SYMCRYPT_SHA512_STATE_EXPORT_SIZE) // 512 is the largest size + var pinner runtime.Pinner + pinner.Pin(&state[0]) + defer pinner.Unpin() + params := [2]_OSSL_PARAM{ + _OSSL_PARAM_construct_octet_string(_SCOSSL_DIGEST_PARAM_STATE, unsafe.Pointer(&state[0]), len(state)), + _OSSL_PARAM_construct_end(), + } + if C.go_openssl_EVP_MD_CTX_get_params(ctx, (C.GO_OSSL_PARAM_PTR)(unsafe.Pointer(¶ms[0]))) != 1 { + return nil, newOpenSSLError("EVP_MD_CTX_get_params") + } + + header := (*_SYMCRYPT_BLOB_HEADER)(unsafe.Pointer(&state[0])) + if header.magic != _SYMCRYPT_BLOB_MAGIC { + return nil, errors.New("invalid blob magic") + } + if header.size != size { + return nil, errors.New("invalid blob size") + } + if header._type != typ { + return nil, errors.New("invalid blob type") + } + + buf = append(buf, magic...) + switch ch { + case crypto.MD5: + blob := (*_SYMCRYPT_MD5_STATE_EXPORT_BLOB)(unsafe.Pointer(&state[0])) + return blob.appendBinary(buf) + case crypto.SHA1: + blob := (*_SYMCRYPT_SHA1_STATE_EXPORT_BLOB)(unsafe.Pointer(&state[0])) + return blob.appendBinary(buf) + case crypto.SHA224, crypto.SHA256: + blob := (*_SYMCRYPT_SHA256_STATE_EXPORT_BLOB)(unsafe.Pointer(&state[0])) + return blob.appendBinary(buf) + case crypto.SHA384, crypto.SHA512_224, crypto.SHA512_256, crypto.SHA512: + blob := (*_SYMCRYPT_SHA512_STATE_EXPORT_BLOB)(unsafe.Pointer(&state[0])) + return blob.appendBinary(buf) + default: + panic("unsupported hash " + ch.String()) + } +} + +func symCryptHashUnmarshalBinary(ctx C.GO_EVP_MD_CTX_PTR, ch crypto.Hash, magic string, b []byte) error { + size, typ, serializable := symCryptHashStateInfo(ch) + if !serializable { + return errHashNotMarshallable + } + hdr := _SYMCRYPT_BLOB_HEADER{ + magic: _SYMCRYPT_BLOB_MAGIC, + size: size, + _type: typ, + } + var blobPtr unsafe.Pointer + b = b[len(magic):] + switch ch { + case crypto.MD5: + var blob _SYMCRYPT_MD5_STATE_EXPORT_BLOB + blobPtr = unsafe.Pointer(&blob) + blob.header = hdr + blob.unmarshalBinary(b) + case crypto.SHA1: + var blob _SYMCRYPT_SHA1_STATE_EXPORT_BLOB + blobPtr = unsafe.Pointer(&blob) + blob.header = hdr + blob.unmarshalBinary(b) + case crypto.SHA224, crypto.SHA256: + var blob _SYMCRYPT_SHA256_STATE_EXPORT_BLOB + blobPtr = unsafe.Pointer(&blob) + blob.header = hdr + blob.unmarshalBinary(b) + case crypto.SHA384, crypto.SHA512_224, crypto.SHA512_256, crypto.SHA512: + var blob _SYMCRYPT_SHA512_STATE_EXPORT_BLOB + blobPtr = unsafe.Pointer(&blob) + blob.header = hdr + blob.unmarshalBinary(b) + default: + panic("unsupported hash " + ch.String()) + } + bld := C.go_openssl_OSSL_PARAM_BLD_new() + if bld == nil { + return newOpenSSLError("OSSL_PARAM_BLD_new") + } + defer C.go_openssl_OSSL_PARAM_BLD_free(bld) + cbytes := C.CBytes(unsafe.Slice((*byte)(blobPtr), hdr.size)) + defer C.free(cbytes) + C.go_openssl_OSSL_PARAM_BLD_push_octet_string(bld, _SCOSSL_DIGEST_PARAM_STATE, cbytes, C.size_t(hdr.size)) + C.go_openssl_OSSL_PARAM_BLD_push_int32(bld, _SCOSSL_DIGEST_PARAM_RECOMPUTE_CHECKSUM, 1) + params := C.go_openssl_OSSL_PARAM_BLD_to_param(bld) + if params == nil { + return newOpenSSLError("OSSL_PARAM_BLD_to_param") + } + defer C.go_openssl_OSSL_PARAM_free(params) + if C.go_openssl_EVP_MD_CTX_set_params(ctx, params) == 0 { + return newOpenSSLError("EVP_MD_CTX_set_params") + } + return nil +} + +func symCryptHashStateInfo(ch crypto.Hash) (size, typ uint32, serializable bool) { + switch ch { + case crypto.MD5: + return _SYMCRYPT_MD5_STATE_EXPORT_SIZE, _SymCryptBlobTypeMd5State, symCryptHashStateSerializableMD5() + case crypto.SHA1: + return _SYMCRYPT_SHA1_STATE_EXPORT_SIZE, _SymCryptBlobTypeSha1State, symCryptHashStateSerializableSHA1() + case crypto.SHA224: + return _SYMCRYPT_SHA256_STATE_EXPORT_SIZE, _SymCryptBlobTypeSha224State, symCryptHashStateSerializableSHA224() + case crypto.SHA256: + return _SYMCRYPT_SHA256_STATE_EXPORT_SIZE, _SymCryptBlobTypeSha256State, symCryptHashStateSerializableSHA256() + case crypto.SHA384: + return _SYMCRYPT_SHA512_STATE_EXPORT_SIZE, _SymCryptBlobTypeSha384State, symCryptHashStateSerializableSHA384() + case crypto.SHA512_224: + return _SYMCRYPT_SHA512_STATE_EXPORT_SIZE, _SymCryptBlobTypeSha512_224State, symCryptHashStateSerializableSHA512_224() + case crypto.SHA512_256: + return _SYMCRYPT_SHA512_STATE_EXPORT_SIZE, _SymCryptBlobTypeSha512_256State, symCryptHashStateSerializableSHA512_256() + case crypto.SHA512: + return _SYMCRYPT_SHA512_STATE_EXPORT_SIZE, _SymCryptBlobTypeSha512State, symCryptHashStateSerializableSHA512() + default: + panic("unsupported hash " + ch.String()) + } +} + +var ( + symCryptHashStateSerializableMD5 = sync.OnceValue(func() bool { + return isSymCryptHashStateSerializable(crypto.MD5) + }) + symCryptHashStateSerializableSHA1 = sync.OnceValue(func() bool { + return isSymCryptHashStateSerializable(crypto.SHA1) + }) + symCryptHashStateSerializableSHA224 = sync.OnceValue(func() bool { + return isSymCryptHashStateSerializable(crypto.SHA224) + }) + symCryptHashStateSerializableSHA256 = sync.OnceValue(func() bool { + return isSymCryptHashStateSerializable(crypto.SHA256) + }) + symCryptHashStateSerializableSHA384 = sync.OnceValue(func() bool { + return isSymCryptHashStateSerializable(crypto.SHA384) + }) + symCryptHashStateSerializableSHA512_224 = sync.OnceValue(func() bool { + return isSymCryptHashStateSerializable(crypto.SHA512_224) + }) + symCryptHashStateSerializableSHA512_256 = sync.OnceValue(func() bool { + return isSymCryptHashStateSerializable(crypto.SHA512_256) + }) + symCryptHashStateSerializableSHA512 = sync.OnceValue(func() bool { + return isSymCryptHashStateSerializable(crypto.SHA512) + }) +) + +// isSymCryptHashStateSerializable checks if the SymCrypt hash state is serializable. +func isSymCryptHashStateSerializable(ch crypto.Hash) bool { + md := cryptoHashToMD(ch) + if md == nil { + return false + } + ctx := C.go_openssl_EVP_MD_CTX_new() + if ctx == nil { + return false + } + defer C.go_openssl_EVP_MD_CTX_free(ctx) + if C.go_openssl_EVP_DigestInit_ex(ctx, md, nil) != 1 { + return false + } + params := C.go_openssl_EVP_MD_CTX_gettable_params(ctx) + if params == nil { + return false + } + if C.go_openssl_OSSL_PARAM_locate_const(params, _SCOSSL_DIGEST_PARAM_STATE) == nil { + return false + } + params = C.go_openssl_EVP_MD_CTX_settable_params(ctx) + if params == nil { + return false + } + if C.go_openssl_OSSL_PARAM_locate_const(params, _SCOSSL_DIGEST_PARAM_STATE) == nil { + return false + } + if C.go_openssl_OSSL_PARAM_locate_const(params, _SCOSSL_DIGEST_PARAM_RECOMPUTE_CHECKSUM) == nil { + return false + } + return true +} diff --git a/shims.h b/shims.h index deddeb93..7650cf12 100644 --- a/shims.h +++ b/shims.h @@ -26,7 +26,9 @@ enum { GO_EVP_MAX_MD_SIZE = 64, GO_EVP_PKEY_PUBLIC_KEY = 0x86, - GO_EVP_PKEY_KEYPAIR = 0x87 + GO_EVP_PKEY_KEYPAIR = 0x87, + + GO_OSSL_PARAM_OCTET_STRING = 5 }; // #include @@ -201,6 +203,10 @@ 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_3_0(const GO_OSSL_PARAM_PTR, EVP_MD_CTX_gettable_params, (GO_EVP_MD_CTX_PTR ctx), (ctx)) \ +DEFINEFUNC_3_0(const GO_OSSL_PARAM_PTR, EVP_MD_CTX_settable_params, (GO_EVP_MD_CTX_PTR ctx), (ctx)) \ +DEFINEFUNC_3_0(int, EVP_MD_CTX_get_params, (GO_EVP_MD_CTX_PTR ctx, GO_OSSL_PARAM_PTR params), (ctx, params)) \ +DEFINEFUNC_3_0(int, EVP_MD_CTX_set_params, (GO_EVP_MD_CTX_PTR ctx, const GO_OSSL_PARAM_PTR params), (ctx, params)) \ 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)) \ @@ -350,11 +356,13 @@ DEFINEFUNC_3_0(int, EVP_MAC_init, (GO_EVP_MAC_CTX_PTR ctx, const unsigned char * DEFINEFUNC_3_0(int, EVP_MAC_update, (GO_EVP_MAC_CTX_PTR ctx, const unsigned char *data, size_t datalen), (ctx, data, datalen)) \ DEFINEFUNC_3_0(int, EVP_MAC_final, (GO_EVP_MAC_CTX_PTR ctx, unsigned char *out, size_t *outl, size_t outsize), (ctx, out, outl, outsize)) \ DEFINEFUNC_3_0(void, OSSL_PARAM_free, (GO_OSSL_PARAM_PTR p), (p)) \ +DEFINEFUNC_3_0(const GO_OSSL_PARAM_PTR, OSSL_PARAM_locate_const, (const GO_OSSL_PARAM_PTR p, const char *key), (p, key)) \ DEFINEFUNC_3_0(GO_OSSL_PARAM_BLD_PTR, OSSL_PARAM_BLD_new, (void), ()) \ DEFINEFUNC_3_0(void, OSSL_PARAM_BLD_free, (GO_OSSL_PARAM_BLD_PTR bld), (bld)) \ DEFINEFUNC_3_0(GO_OSSL_PARAM_PTR, OSSL_PARAM_BLD_to_param, (GO_OSSL_PARAM_BLD_PTR bld), (bld)) \ DEFINEFUNC_3_0(int, OSSL_PARAM_BLD_push_utf8_string, (GO_OSSL_PARAM_BLD_PTR bld, const char *key, const char *buf, size_t bsize), (bld, key, buf, bsize)) \ DEFINEFUNC_3_0(int, OSSL_PARAM_BLD_push_octet_string, (GO_OSSL_PARAM_BLD_PTR bld, const char *key, const void *buf, size_t bsize), (bld, key, buf, bsize)) \ +DEFINEFUNC_3_0(int, OSSL_PARAM_BLD_push_int32, (GO_OSSL_PARAM_BLD_PTR bld, const char *key, int32_t num), (bld, key, num)) \ DEFINEFUNC_3_0(int, OSSL_PARAM_BLD_push_BN, (GO_OSSL_PARAM_BLD_PTR bld, const char *key, const GO_BIGNUM_PTR bn), (bld, key, bn)) \ DEFINEFUNC_3_0(int, EVP_PKEY_CTX_set_hkdf_mode, (GO_EVP_PKEY_CTX_PTR arg0, int arg1), (arg0, arg1)) \ DEFINEFUNC_3_0(int, EVP_PKEY_CTX_set_hkdf_md, (GO_EVP_PKEY_CTX_PTR arg0, const GO_EVP_MD_PTR arg1), (arg0, arg1)) \ From c93b4dd496463492481ec45f3e854cfa529292b9 Mon Sep 17 00:00:00 2001 From: qmuntal Date: Fri, 23 May 2025 16:21:12 +0200 Subject: [PATCH 2/5] bump ubuntu image --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e3c0695e..55e2d549 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -7,7 +7,7 @@ jobs: matrix: go-version: [1.20.x] openssl-version: [1.0.2, 1.1.0, 1.1.1, 3.0.1, 3.0.13, 3.1.5, 3.2.1] - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - name: Install build tools run: sudo apt-get install -y build-essential From 84820f072c50a29cbccb9ef84136c22602cfe180 Mon Sep 17 00:00:00 2001 From: qmuntal Date: Fri, 23 May 2025 16:25:17 +0200 Subject: [PATCH 3/5] fix xi --- .github/workflows/test.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 55e2d549..44f56a12 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -5,7 +5,7 @@ jobs: strategy: fail-fast: false matrix: - go-version: [1.20.x] + go-version: [1.23.x] openssl-version: [1.0.2, 1.1.0, 1.1.1, 3.0.1, 3.0.13, 3.1.5, 3.2.1] runs-on: ubuntu-22.04 steps: @@ -22,6 +22,7 @@ jobs: - name: Check headers working-directory: ./cmd/checkheader run: go run . --ossl-include /usr/local/src/openssl-${{ matrix.openssl-version }}/include -shim ../../shims.h + if: ${{ matrix.openssl-version != '1.0.2' }} # Doesn't work on Ubuntu 22.04 - name: Set OpenSSL config and prove FIPS run: | sudo cp ./scripts/openssl-3.cnf /usr/local/ssl/openssl.cnf @@ -50,7 +51,7 @@ jobs: strategy: fail-fast: false matrix: - go-version: [1.20.x] + go-version: [1.23.x] openssl-version: [libcrypto-1_1-x64.dll, libcrypto-3-x64.dll] steps: - name: Install Go @@ -67,7 +68,7 @@ jobs: strategy: fail-fast: false matrix: - go-version: [1.20.x] + go-version: [1.23.x] openssl-version: [libcrypto.3.dylib] runs-on: macos-12 steps: From b095911ced65fe4902b10edcf256440a5006f96e Mon Sep 17 00:00:00 2001 From: qmuntal Date: Mon, 26 May 2025 11:11:15 +0200 Subject: [PATCH 4/5] use raw parameters --- params.go | 20 +++++++++++++++----- providersymcrypt.go | 35 +++++++++++++++++------------------ shims.h | 8 +++++++- 3 files changed, 39 insertions(+), 24 deletions(-) diff --git a/params.go b/params.go index 8d99d770..fd66fff1 100644 --- a/params.go +++ b/params.go @@ -5,18 +5,19 @@ package openssl // #include "goopenssl.h" import "C" import ( - "math" "unsafe" ) +const _OSSL_PARAM_UNMODIFIED uint = uint(^uintptr(0)) + // _OSSL_PARAM is a structure to pass or request object parameters. // https://docs.openssl.org/3.0/man3/OSSL_PARAM/. type _OSSL_PARAM struct { Key *C.char DataType uint32 Data unsafe.Pointer - DataSize int - ReturnSize int + DataSize uint + ReturnSize uint } func ossl_param_construct(key *C.char, dataType uint32, data unsafe.Pointer, dataSize int) _OSSL_PARAM { @@ -24,8 +25,8 @@ func ossl_param_construct(key *C.char, dataType uint32, data unsafe.Pointer, dat Key: key, DataType: dataType, Data: data, - DataSize: dataSize, - ReturnSize: math.MaxInt - 1, + DataSize: uint(dataSize), + ReturnSize: _OSSL_PARAM_UNMODIFIED, } } @@ -33,6 +34,15 @@ func _OSSL_PARAM_construct_octet_string(key *C.char, data unsafe.Pointer, dataSi return ossl_param_construct(key, C.GO_OSSL_PARAM_OCTET_STRING, data, dataSize) } +func _OSSL_PARAM_construct_int32(key *C.char, data *int32) _OSSL_PARAM { + return ossl_param_construct(key, C.GO_OSSL_PARAM_INTEGER, unsafe.Pointer(data), 4) +} + func _OSSL_PARAM_construct_end() _OSSL_PARAM { return _OSSL_PARAM{} } + +func _OSSL_PARAM_modified(param *_OSSL_PARAM) bool { + // If ReturnSize is not set, the parameter has not been modified. + return param != nil && param.ReturnSize != _OSSL_PARAM_UNMODIFIED +} diff --git a/providersymcrypt.go b/providersymcrypt.go index 26ffbce7..b2da8854 100644 --- a/providersymcrypt.go +++ b/providersymcrypt.go @@ -106,9 +106,9 @@ func symCryptUnmarshalBinary(d []byte, chain, buffer []byte) _UINT64 { return newUINT64(length) } -// swapEndianessInt32 swaps the endianness of the given byte slice +// swapEndianessUint32 swaps the endianness of the given byte slice // in place. It assumes the slice is a backup of a 32-bit integer array. -func swapEndianessInt32(d []uint8) { +func swapEndianessUint32(d []uint8) { for i := 0; i < len(d); i += 4 { d[i], d[i+3] = d[i+3], d[i] d[i+1], d[i+2] = d[i+2], d[i+1] @@ -128,13 +128,13 @@ type _SYMCRYPT_MD5_STATE_EXPORT_BLOB struct { func (b *_SYMCRYPT_MD5_STATE_EXPORT_BLOB) appendBinary(d []byte) ([]byte, error) { // b.chain is little endian, but Go expects big endian, // we need to swap the bytes. - swapEndianessInt32(b.chain[:]) + swapEndianessUint32(b.chain[:]) return symCryptAppendBinary(d, b.chain[:], b.buffer[:], b.length), nil } func (b *_SYMCRYPT_MD5_STATE_EXPORT_BLOB) unmarshalBinary(d []byte) { b.length = symCryptUnmarshalBinary(d, b.chain[:], b.buffer[:]) - swapEndianessInt32(b.chain[:]) + swapEndianessUint32(b.chain[:]) } type _SYMCRYPT_SHA1_STATE_EXPORT_BLOB struct { @@ -208,6 +208,9 @@ func symCryptHashAppendBinary(ctx C.GO_EVP_MD_CTX_PTR, ch crypto.Hash, magic str if C.go_openssl_EVP_MD_CTX_get_params(ctx, (C.GO_OSSL_PARAM_PTR)(unsafe.Pointer(¶ms[0]))) != 1 { return nil, newOpenSSLError("EVP_MD_CTX_get_params") } + if !_OSSL_PARAM_modified(¶ms[0]) { + return nil, errors.New("EVP_MD_CTX_get_params did not retrieve the state") + } header := (*_SYMCRYPT_BLOB_HEADER)(unsafe.Pointer(&state[0])) if header.magic != _SYMCRYPT_BLOB_MAGIC { @@ -275,21 +278,17 @@ func symCryptHashUnmarshalBinary(ctx C.GO_EVP_MD_CTX_PTR, ch crypto.Hash, magic default: panic("unsupported hash " + ch.String()) } - bld := C.go_openssl_OSSL_PARAM_BLD_new() - if bld == nil { - return newOpenSSLError("OSSL_PARAM_BLD_new") - } - defer C.go_openssl_OSSL_PARAM_BLD_free(bld) - cbytes := C.CBytes(unsafe.Slice((*byte)(blobPtr), hdr.size)) - defer C.free(cbytes) - C.go_openssl_OSSL_PARAM_BLD_push_octet_string(bld, _SCOSSL_DIGEST_PARAM_STATE, cbytes, C.size_t(hdr.size)) - C.go_openssl_OSSL_PARAM_BLD_push_int32(bld, _SCOSSL_DIGEST_PARAM_RECOMPUTE_CHECKSUM, 1) - params := C.go_openssl_OSSL_PARAM_BLD_to_param(bld) - if params == nil { - return newOpenSSLError("OSSL_PARAM_BLD_to_param") + var checksum int32 = 1 + cbytesBlob := C.CBytes(unsafe.Slice((*byte)(blobPtr), hdr.size)) + defer C.free(cbytesBlob) + cbytesCheksum := C.CBytes(unsafe.Slice((*byte)(unsafe.Pointer(&checksum)), 4)) + defer C.free(cbytesCheksum) + params := [3]_OSSL_PARAM{ + _OSSL_PARAM_construct_octet_string(_SCOSSL_DIGEST_PARAM_STATE, cbytesBlob, int(hdr.size)), + _OSSL_PARAM_construct_int32(_SCOSSL_DIGEST_PARAM_RECOMPUTE_CHECKSUM, (*int32)(cbytesCheksum)), + _OSSL_PARAM_construct_end(), } - defer C.go_openssl_OSSL_PARAM_free(params) - if C.go_openssl_EVP_MD_CTX_set_params(ctx, params) == 0 { + if C.go_openssl_EVP_MD_CTX_set_params(ctx, (C.GO_OSSL_PARAM_PTR)(unsafe.Pointer(¶ms[0]))) != 1 { return newOpenSSLError("EVP_MD_CTX_set_params") } return nil diff --git a/shims.h b/shims.h index 7650cf12..436380c3 100644 --- a/shims.h +++ b/shims.h @@ -26,8 +26,14 @@ enum { GO_EVP_MAX_MD_SIZE = 64, GO_EVP_PKEY_PUBLIC_KEY = 0x86, - GO_EVP_PKEY_KEYPAIR = 0x87, + GO_EVP_PKEY_KEYPAIR = 0x87 +}; +// #if OPENSSL_VERSION_NUMBER >= 0x30000000L +// #include +// #endif +enum { + GO_OSSL_PARAM_INTEGER = 1, GO_OSSL_PARAM_OCTET_STRING = 5 }; From a0b4ee1c04003518b18a86594413a387316ce3a7 Mon Sep 17 00:00:00 2001 From: qmuntal Date: Mon, 26 May 2025 11:13:47 +0200 Subject: [PATCH 5/5] fix mac ci --- .github/workflows/test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 44f56a12..8b981fb8 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -69,8 +69,8 @@ jobs: fail-fast: false matrix: go-version: [1.23.x] - openssl-version: [libcrypto.3.dylib] - runs-on: macos-12 + openssl-version: [/usr/local/opt/openssl@3/lib/libcrypto.3.dylib] + runs-on: macos-13 steps: - name: Install Go uses: actions/setup-go@v3