diff --git a/hkdf.go b/hkdf.go index d4f8aa6a..cf14ac90 100644 --- a/hkdf.go +++ b/hkdf.go @@ -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) @@ -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() @@ -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) @@ -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() @@ -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. diff --git a/hkdf_test.go b/hkdf_test.go index cd5cb0c0..3e9e93f8 100644 --- a/hkdf_test.go +++ b/hkdf_test.go @@ -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) + } + } +} diff --git a/params.go b/params.go index fa24a8cd..fd5bd405 100644 --- a/params.go +++ b/params.go @@ -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")