Skip to content

Backport TLS13-KDF/Extract fixes to 1.24 support branch #276

New issue

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

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

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 120 additions & 0 deletions hkdf.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,20 @@ func SupportsHKDF() bool {
}
}

// SupprtsTLS13KDF reports whether the current OpenSSL version supports TLS13-KDF.
func SupportsTLS13KDF() bool {
switch vMajor {
case 1:
return false
case 3:
// TLS13-KDF is available in OpenSSL 3.0.0 and later.
_, err := fetchTLS13_KDF()
return err == nil
default:
panic(errUnsupportedVersion())
}
}

func newHKDFCtx1(md C.GO_EVP_MD_PTR, mode C.int, secret, salt, pseudorandomKey, info []byte) (ctx C.GO_EVP_PKEY_CTX_PTR, err error) {
checkMajorVersion(1)

Expand Down Expand Up @@ -109,6 +123,14 @@ func (c *hkdf1) Read(p []byte) (int, error) {
return n, nil
}

// hkdfAllZerosSalt is a preallocated buffer of zeros used in ExtractHKDF().
// The size should be kept as large as the output length of any hash algorithm
// used with HKDF.
var hkdfAllZerosSalt [64]byte

// ExtractHDKF implements the HDKF extract step.
// If salt is nil, then this function replaces it internally with a buffer of
// zeros whose length equals the output length of the specified hash algorithm.
func ExtractHKDF(h func() hash.Hash, secret, salt []byte) ([]byte, error) {
if !SupportsHKDF() {
return nil, errUnsupportedVersion()
Expand All @@ -119,6 +141,20 @@ func ExtractHKDF(h func() hash.Hash, secret, salt []byte) ([]byte, error) {
return nil, err
}

// If calling code specifies nil salt, replace it with a buffer of hashLen
// zeros, as specified in RFC 5896 and as OpenSSL EVP_KDF-HKDF documentation
// instructs. Take a slice of a preallocated buffer to avoid allocating new
// buffer per call, but fall back to allocating a buffer if preallocated
// buffer is not large enough.
if salt == nil {
hlen := h().Size()
if hlen > len(hkdfAllZerosSalt) {
salt = make([]byte, hlen)
} else {
salt = hkdfAllZerosSalt[:hlen]
}
}

switch vMajor {
case 1:
ctx, err := newHKDFCtx1(md, C.GO_EVP_KDF_HKDF_MODE_EXTRACT_ONLY, secret, salt, nil, nil)
Expand Down Expand Up @@ -188,6 +224,31 @@ func ExpandHKDFOneShot(h func() hash.Hash, pseudorandomKey, info []byte, keyLeng
return out, nil
}

// ExpandTLS13KDF derives a key from the given hash, key, label and context. It will use
// "TLS13-KDF" algorithm to do so.
func ExpandTLS13KDF(h func() hash.Hash, pseudorandomKey, label, context []byte, keyLength int) ([]byte, error) {
if !SupportsTLS13KDF() {
return nil, errUnsupportedVersion()
}

md, err := hashFuncToMD(h)
if err != nil {
return nil, err
}

out := make([]byte, keyLength)

ctx, err := newTLS13KDFExpandCtx3(md, label, context, pseudorandomKey)
if err != nil {
return nil, err
}
defer C.go_openssl_EVP_KDF_CTX_free(ctx)
if _, err := C.go_openssl_EVP_KDF_derive(ctx, base(out), C.size_t(keyLength), nil); err != nil {
return nil, err
}
return out, nil
}

func ExpandHKDF(h func() hash.Hash, pseudorandomKey, info []byte) (io.Reader, error) {
if !SupportsHKDF() {
return nil, errUnsupportedVersion()
Expand Down Expand Up @@ -233,6 +294,65 @@ func (c *hkdf3) finalize() {
}
}

// fetchTLS13_KDF fetches the TLS13-KDF algorithm.
// It is safe to call this function concurrently.
// The returned EVP_KDF_PTR shouldn't be freed.
var fetchTLS13_KDF = sync.OnceValues(func() (C.GO_EVP_KDF_PTR, error) {
checkMajorVersion(3)

name := C.CString("TLS13-KDF")
kdf := C.go_openssl_EVP_KDF_fetch(nil, name, nil)
C.free(unsafe.Pointer(name))
if kdf == nil {
return nil, newOpenSSLError("EVP_KDF_fetch")
}
return kdf, nil
})

// newTLS13KDFExpandCtx3 fetches the "TLS13-KDF" for TLS 1.3 handshakes.
func newTLS13KDFExpandCtx3(md C.GO_EVP_MD_PTR, label, context, pseudorandomKey []byte) (_ C.GO_EVP_KDF_CTX_PTR, err error) {
checkMajorVersion(3)

kdf, err := fetchTLS13_KDF()
if err != nil {
return nil, err
}

ctx, err := C.go_openssl_EVP_KDF_CTX_new(kdf)
if err != nil {
return nil, err
}
defer func() {
if err != nil {
C.go_openssl_EVP_KDF_CTX_free(ctx)
}
}()

bld, err := newParamBuilder()
if err != nil {
return ctx, err
}
bld.addUTF8String(_OSSL_KDF_PARAM_DIGEST, C.go_openssl_EVP_MD_get0_name(md), 0)
bld.addInt32(_OSSL_KDF_PARAM_MODE, int32(C.GO_EVP_KDF_HKDF_MODE_EXPAND_ONLY))
bld.addOctetString(_OSSL_KDF_PARAM_PREFIX, []byte("tls13 "))
bld.addOctetString(_OSSL_KDF_PARAM_LABEL, label)
bld.addOctetString(_OSSL_KDF_PARAM_DATA, context)
if len(pseudorandomKey) > 0 {
bld.addOctetString(_OSSL_KDF_PARAM_KEY, pseudorandomKey)
}

params, err := bld.build()
if err != nil {
return ctx, err
}
defer C.go_openssl_OSSL_PARAM_free(params)

if _, err := C.go_openssl_EVP_KDF_CTX_set_params(ctx, params); err != nil {
return ctx, err
}
return ctx, nil
}

// fetchHKDF3 fetches the HKDF algorithm.
// It is safe to call this function concurrently.
// The returned EVP_KDF_PTR shouldn't be freed.
Expand Down
198 changes: 198 additions & 0 deletions hkdf_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -448,3 +448,201 @@ func TestExpandHKDFOneShotLimit(t *testing.T) {
t.Errorf("expected error for key expansion overflow")
}
}

type tls13kdfTest struct {
hash func() hash.Hash
prk []byte
label []byte
ctx []byte
out []byte
}

var tls13kdfTests = []tls13kdfTest{
{
openssl.NewSHA256,
[]byte{
0x07, 0x77, 0x09, 0x36, 0x2c, 0x2e, 0x32, 0xdf,
0x0d, 0xdc, 0x3f, 0x0d, 0xc4, 0x7b, 0xba, 0x63,
0x90, 0xb6, 0xc7, 0x3b, 0xb5, 0x0f, 0x9c, 0x31,
0x22, 0xec, 0x84, 0x4a, 0xd7, 0xc2, 0xb3, 0xe5,
},
[]byte("res binder") ,
[]byte{},
[]byte{
0x10, 0x6d, 0x4e, 0xea, 0x65, 0x19, 0x16, 0xc7,
0xff, 0x7d, 0xd1, 0x2f, 0x24, 0x04, 0x6a, 0x46,
0x60, 0x11, 0x40, 0x8b, 0xed, 0x37, 0x06, 0x49,
0x73, 0x84, 0x05, 0x79, 0x94, 0x15, 0x00, 0x3b,
0xce, 0x9d, 0xa1, 0x04, 0x78, 0xae, 0xd3, 0x4f,
0xe2, 0x0c,
},
},
{
openssl.NewSHA256,
[]byte{
0x07, 0x77, 0x09, 0x36, 0x2c, 0x2e, 0x32, 0xdf,
0x0d, 0xdc, 0x3f, 0x0d, 0xc4, 0x7b, 0xba, 0x63,
0x90, 0xb6, 0xc7, 0x3b, 0xb5, 0x0f, 0x9c, 0x31,
0x22, 0xec, 0x84, 0x4a, 0xd7, 0xc2, 0xb3, 0xe5,
},
[]byte("c e traffic") ,
[]byte{},
[]byte{
0x7e, 0xb6, 0x59, 0x96, 0x14, 0xf4, 0x1a, 0x27,
0x09, 0x8d, 0x7a, 0x26, 0xdf, 0x32, 0x6a, 0x0d,
0xf8, 0xd2, 0xad, 0xd5, 0x2a, 0x46, 0xa5, 0x37,
0xa7, 0x25, 0x16, 0x01, 0xb8, 0x8e, 0x30, 0x61,
0x45, 0x40, 0x83, 0x76, 0xbf, 0xcc, 0xb8, 0xae,
0xba, 0x0f,
},
},
{
openssl.NewSHA256,
[]byte{
0x07, 0x77, 0x09, 0x36, 0x2c, 0x2e, 0x32, 0xdf,
0x0d, 0xdc, 0x3f, 0x0d, 0xc4, 0x7b, 0xba, 0x63,
0x90, 0xb6, 0xc7, 0x3b, 0xb5, 0x0f, 0x9c, 0x31,
0x22, 0xec, 0x84, 0x4a, 0xd7, 0xc2, 0xb3, 0xe5,
},
[]byte("c hs traffic") ,
[]byte{},
[]byte{
0x1f, 0xf4, 0xeb, 0xec, 0xca, 0x5b, 0x6f, 0x1c,
0x98, 0x7f, 0xd0, 0xc1, 0x74, 0x4e, 0x4f, 0x1f,
0x46, 0xf5, 0x27, 0x06, 0xa8, 0x30, 0xb3, 0x72,
0x06, 0xe2, 0x7f, 0x23, 0xdb, 0x8e, 0xc0, 0xc2,
0xea, 0x26, 0xdf, 0xf4, 0x8d, 0x73, 0xc7, 0x01,
0x20, 0x20,
},
},
{
openssl.NewSHA256,
[]byte{
0x07, 0x77, 0x09, 0x36, 0x2c, 0x2e, 0x32, 0xdf,
0x0d, 0xdc, 0x3f, 0x0d, 0xc4, 0x7b, 0xba, 0x63,
0x90, 0xb6, 0xc7, 0x3b, 0xb5, 0x0f, 0x9c, 0x31,
0x22, 0xec, 0x84, 0x4a, 0xd7, 0xc2, 0xb3, 0xe5,
},
[]byte("s hs traffic") ,
[]byte{},
[]byte{
0xe9, 0xe4, 0x51, 0x4b, 0xe9, 0x0d, 0xb0, 0x44,
0x07, 0x42, 0x6c, 0x52, 0x77, 0xdf, 0x8c, 0x7f,
0x19, 0x38, 0xcb, 0x72, 0x76, 0x97, 0x0a, 0x66,
0x2b, 0x58, 0x7e, 0xee, 0x8a, 0xdd, 0x0d, 0xe2,
0x15, 0xe8, 0x60, 0x37, 0x3d, 0x16, 0x9d, 0xdc,
0x74, 0xb7,
},
},
{
openssl.NewSHA256,
[]byte{
0x07, 0x77, 0x09, 0x36, 0x2c, 0x2e, 0x32, 0xdf,
0x0d, 0xdc, 0x3f, 0x0d, 0xc4, 0x7b, 0xba, 0x63,
0x90, 0xb6, 0xc7, 0x3b, 0xb5, 0x0f, 0x9c, 0x31,
0x22, 0xec, 0x84, 0x4a, 0xd7, 0xc2, 0xb3, 0xe5,
},
[]byte("c ap traffic") ,
[]byte{},
[]byte{
0x51, 0xee, 0xb6, 0x24, 0x50, 0x5f, 0x88, 0xdb,
0x61, 0x9c, 0x10, 0x25, 0x6f, 0xa5, 0xa0, 0xbc,
0x0e, 0x5f, 0x81, 0xde, 0xf6, 0x59, 0x2d, 0x99,
0xc9, 0x73, 0x1a, 0x3e, 0x4e, 0x11, 0x93, 0x0c,
0xae, 0x51, 0xa1, 0xf8, 0x42, 0x42, 0x45, 0xbe,
0x52, 0x50,
},
},
{
openssl.NewSHA256,
[]byte{
0x07, 0x77, 0x09, 0x36, 0x2c, 0x2e, 0x32, 0xdf,
0x0d, 0xdc, 0x3f, 0x0d, 0xc4, 0x7b, 0xba, 0x63,
0x90, 0xb6, 0xc7, 0x3b, 0xb5, 0x0f, 0x9c, 0x31,
0x22, 0xec, 0x84, 0x4a, 0xd7, 0xc2, 0xb3, 0xe5,
},
[]byte("s ap traffic") ,
[]byte{},
[]byte{
0x08, 0x8c, 0xff, 0x31, 0xa0, 0xa1, 0x64, 0xca,
0x88, 0x1a, 0xc1, 0xde, 0xef, 0xa2, 0x38, 0xed,
0x43, 0x02, 0x68, 0x7f, 0xe9, 0x59, 0xc9, 0x81,
0xc2, 0xc1, 0x42, 0xfc, 0xa5, 0xad, 0xee, 0xc9,
0xbb, 0xfa, 0x6e, 0xb9, 0x9a, 0x4d, 0xd3, 0x3d,
0x11, 0x52,
},
},
{
openssl.NewSHA256,
[]byte{
0x07, 0x77, 0x09, 0x36, 0x2c, 0x2e, 0x32, 0xdf,
0x0d, 0xdc, 0x3f, 0x0d, 0xc4, 0x7b, 0xba, 0x63,
0x90, 0xb6, 0xc7, 0x3b, 0xb5, 0x0f, 0x9c, 0x31,
0x22, 0xec, 0x84, 0x4a, 0xd7, 0xc2, 0xb3, 0xe5,
},
[]byte("e exp master") ,
[]byte{},
[]byte{
0x32, 0x59, 0x33, 0x2c, 0xf3, 0xb0, 0xc2, 0x0d,
0x96, 0xe0, 0x38, 0x01, 0x8c, 0xb1, 0xd1, 0xeb,
0x9d, 0xbd, 0x64, 0xba, 0xb2, 0x54, 0x3e, 0xe5,
0xca, 0x33, 0xe8, 0x17, 0xc3, 0x62, 0x7e, 0x62,
0x45, 0x9f, 0x96, 0xdd, 0x81, 0x51, 0x55, 0xef,
0x1b, 0xb6,
},
},
{
openssl.NewSHA256,
[]byte{
0x07, 0x77, 0x09, 0x36, 0x2c, 0x2e, 0x32, 0xdf,
0x0d, 0xdc, 0x3f, 0x0d, 0xc4, 0x7b, 0xba, 0x63,
0x90, 0xb6, 0xc7, 0x3b, 0xb5, 0x0f, 0x9c, 0x31,
0x22, 0xec, 0x84, 0x4a, 0xd7, 0xc2, 0xb3, 0xe5,
},
[]byte("exp master") ,
[]byte{},
[]byte{
0x50, 0x6a, 0x2e, 0xe3, 0x95, 0x77, 0x6c, 0xfb,
0x77, 0x8c, 0x5a, 0xe3, 0x22, 0x32, 0x35, 0xd2,
0x73, 0x81, 0x50, 0x85, 0x0e, 0x51, 0x59, 0x01,
0xa8, 0x99, 0x4f, 0xea, 0xfa, 0x6d, 0x22, 0x83,
0xf2, 0x5d, 0x9f, 0xba, 0xc5, 0xc9, 0xb6, 0x4b,
0xdc, 0x8d,
},
},
{
openssl.NewSHA256,
[]byte{
0x07, 0x77, 0x09, 0x36, 0x2c, 0x2e, 0x32, 0xdf,
0x0d, 0xdc, 0x3f, 0x0d, 0xc4, 0x7b, 0xba, 0x63,
0x90, 0xb6, 0xc7, 0x3b, 0xb5, 0x0f, 0x9c, 0x31,
0x22, 0xec, 0x84, 0x4a, 0xd7, 0xc2, 0xb3, 0xe5,
},
[]byte("res master") ,
[]byte{},
[]byte{
0xc1, 0x37, 0xab, 0x0f, 0x7b, 0x58, 0xb7, 0xe9,
0xdd, 0xf9, 0xff, 0xb4, 0x0d, 0x1e, 0xaa, 0xa8,
0x67, 0x6c, 0x15, 0xdf, 0xdb, 0xff, 0x7c, 0x0b,
0xc3, 0xcb, 0xd9, 0x21, 0x3e, 0x95, 0xcd, 0xbb,
0xe1, 0x70, 0xdd, 0x37, 0x8a, 0xae, 0x45, 0x89,
0xb0, 0x68,
},
},
}

func TestExpandTLS13KDF(t *testing.T) {
if !openssl.SupportsTLS13KDF() {
t.Skip("TLS13-KDF is not supported")
}
for i, tt := range tls13kdfTests {
out, err := openssl.ExpandTLS13KDF(tt.hash, tt.prk, tt.label, tt.ctx, len(tt.out))
if err != nil {
t.Errorf("test %d (label: %s): error expanding TLS13-KDF: %v.", i, tt.label, err)
continue
}
if !bytes.Equal(out, tt.out) {
t.Errorf("test %d (label: %s): incorrect output from ExpandTLS13KDF: have %v, need %v.", i, tt.label, out, tt.out)
}
}
}
3 changes: 3 additions & 0 deletions params.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ var (
_OSSL_KDF_PARAM_INFO = C.CString("info")
_OSSL_KDF_PARAM_SALT = C.CString("salt")
_OSSL_KDF_PARAM_MODE = C.CString("mode")
_OSSL_KDF_PARAM_PREFIX = C.CString("prefix")
_OSSL_KDF_PARAM_LABEL = C.CString("label")
_OSSL_KDF_PARAM_DATA = C.CString("data")

// PKEY parameters
_OSSL_PKEY_PARAM_PUB_KEY = C.CString("pub")
Expand Down
Loading