Skip to content

Add support for the TLS13-KDF algorithm #272

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 10 commits into from
Apr 24, 2025
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
14 changes: 10 additions & 4 deletions const.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,11 @@ const ( //checkheader:ignore
_DigestNameSHA2_256 cString = "SHA2-256\x00"

// KDF names
_OSSL_KDF_NAME_HKDF cString = "HKDF\x00"
_OSSL_KDF_NAME_PBKDF2 cString = "PBKDF2\x00"
_OSSL_KDF_NAME_TLS1_PRF cString = "TLS1-PRF\x00"
_OSSL_MAC_NAME_HMAC cString = "HMAC\x00"
_OSSL_KDF_NAME_HKDF cString = "HKDF\x00"
_OSSL_KDF_NAME_PBKDF2 cString = "PBKDF2\x00"
_OSSL_KDF_NAME_TLS1_PRF cString = "TLS1-PRF\x00"
_OSSL_KDF_NAME_TLS13_KDF cString = "TLS13-KDF\x00"
_OSSL_MAC_NAME_HMAC cString = "HMAC\x00"

// KDF parameters
_OSSL_KDF_PARAM_DIGEST cString = "digest\x00"
Expand All @@ -62,6 +63,11 @@ const ( //checkheader:ignore
_OSSL_KDF_PARAM_SALT cString = "salt\x00"
_OSSL_KDF_PARAM_MODE cString = "mode\x00"

// TLS3-KDF parameters
_OSSL_KDF_PARAM_PREFIX cString = "prefix\x00"
_OSSL_KDF_PARAM_LABEL cString = "label\x00"
_OSSL_KDF_PARAM_DATA cString = "data\x00"

// PKEY parameters
_OSSL_PKEY_PARAM_PUB_KEY cString = "pub\x00"
_OSSL_PKEY_PARAM_PRIV_KEY cString = "priv\x00"
Expand Down
96 changes: 96 additions & 0 deletions hkdf.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,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 ossl.EVP_MD_PTR, mode int32, secret, salt, pseudorandomKey, info []byte) (ctx ossl.EVP_PKEY_CTX_PTR, err error) {
checkMajorVersion(1)

Expand Down Expand Up @@ -214,6 +228,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 ossl.EVP_KDF_CTX_free(ctx)
if _, err := ossl.EVP_KDF_derive(ctx, base(out), 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 @@ -261,6 +300,63 @@ 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() (ossl.EVP_KDF_PTR, error) {
checkMajorVersion(3)

kdf, err := ossl.EVP_KDF_fetch(nil, _OSSL_KDF_NAME_TLS13_KDF.ptr(), nil)
if err != nil {
return nil, err
}
return kdf, nil
})

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

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

ctx, err := ossl.EVP_KDF_CTX_new(kdf)
if err != nil {
return nil, err
}
defer func() {
if err != nil {
ossl.EVP_KDF_CTX_free(ctx)
}
}()

bld, err := newParamBuilder()
if err != nil {
return ctx, err
}
bld.addUTF8String(_OSSL_KDF_PARAM_DIGEST, ossl.EVP_MD_get0_name(md), 0)
bld.addInt32(_OSSL_KDF_PARAM_MODE, int32(ossl.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 ossl.OSSL_PARAM_free(params)

if _, err := ossl.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)
}
}
}
Loading