Skip to content
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

Implement ExpandHKDFOneShot #224

Merged
merged 2 commits into from
Nov 25, 2024
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
37 changes: 37 additions & 0 deletions hkdf.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,43 @@ func ExtractHKDF(h func() hash.Hash, secret, salt []byte) ([]byte, error) {
}
}

// ExpandHKDFOneShot derives a key from the given hash, key, and optional context info.
func ExpandHKDFOneShot(h func() hash.Hash, pseudorandomKey, info []byte, keyLength int) ([]byte, error) {
if !SupportsHKDF() {
return nil, errUnsupportedVersion()
}

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

out := make([]byte, keyLength)
switch vMajor {
case 1:
ctx, err := newHKDFCtx1(md, C.GO_EVP_KDF_HKDF_MODE_EXPAND_ONLY, nil, nil, pseudorandomKey, info)
if err != nil {
return nil, err
}
defer C.go_openssl_EVP_PKEY_CTX_free(ctx)
if C.go_openssl_EVP_PKEY_derive_wrapper(ctx, base(out), C.size_t(keyLength)).result != 1 {
return nil, newOpenSSLError("EVP_PKEY_derive")
}
case 3:
ctx, err := newHKDFCtx3(md, C.GO_EVP_KDF_HKDF_MODE_EXPAND_ONLY, nil, nil, pseudorandomKey, info)
if err != nil {
return nil, err
}
defer C.go_openssl_EVP_KDF_CTX_free(ctx)
if C.go_openssl_EVP_KDF_derive(ctx, base(out), C.size_t(keyLength), nil) != 1 {
return nil, newOpenSSLError("EVP_KDF_derive")
}
default:
panic(errUnsupportedVersion())
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why panic vs. returning the error?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is common practice in this repo. The supported version switches should be exhaustive so we know what to update when we start supporting a new version.

Copy link
Collaborator

@dagood dagood Nov 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What difference does return nil, errUnsupportedVersion() vs. panic(errUnsupportedVersion()) make to the switch's exhaustiveness?

FWIW, most uses of errUnsupportedVersion do use panic. Only tls1prf.go and hkdf.go return it, so maybe my confusion is in what situations it makes sense to return it rather than panic. (Maybe a doc comment on errUnsupportedVersion would make sense if it's worth explaining it for the future.)

(examples)

openssl/tls1prf.go

Lines 50 to 57 in 4f0359e

switch vMajor {
case 1:
return tls1PRF1(result, secret, label, seed, md)
case 3:
return tls1PRF3(result, secret, label, seed, md)
default:
return errUnsupportedVersion()
}

openssl/tls1prf.go

Lines 15 to 25 in 4f0359e

func SupportsTLS1PRF() bool {
switch vMajor {
case 1:
return vMinor >= 1
case 3:
_, err := fetchTLS1PRF3()
return err == nil
default:
panic(errUnsupportedVersion())
}
}

openssl/hkdf.go

Lines 17 to 27 in a47a022

func SupportsHKDF() bool {
switch vMajor {
case 1:
return versionAtOrAbove(1, 1, 1)
case 3:
_, err := fetchHKDF3()
return err == nil
default:
panic(errUnsupportedVersion())
}
}

openssl/hkdf.go

Lines 113 to 115 in a47a022

if !SupportsHKDF() {
return nil, errUnsupportedVersion()
}

That said, since this isn't new, going ahead with approval. 😄

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What difference does return nil, errUnsupportedVersion() vs. panic(errUnsupportedVersion()) make to the switch's exhaustiveness?

Go doesn't have exhaustive, so it is idiomatic to panic in the default case to at least get a runtime panic.

Good catch about the inconsistency, will fix in a follow up PR.

}
return out, nil
}

func ExpandHKDF(h func() hash.Hash, pseudorandomKey, info []byte) (io.Reader, error) {
if !SupportsHKDF() {
return nil, errUnsupportedVersion()
Expand Down
43 changes: 43 additions & 0 deletions hkdf_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -405,3 +405,46 @@ func TestHKDFUnsupportedHash(t *testing.T) {
t.Error("expected error for unsupported hash")
}
}
func TestExpandHKDFOneShot(t *testing.T) {
if !openssl.SupportsHKDF() {
t.Skip("HKDF is not supported")
}
for i, tt := range hkdfTests {
out, err := openssl.ExpandHKDFOneShot(tt.hash, tt.prk, tt.info, len(tt.out))
if err != nil {
t.Errorf("test %d: error expanding HKDF one-shot: %v.", i, err)
continue
}
if !bytes.Equal(out, tt.out) {
t.Errorf("test %d: incorrect output from ExpandHKDFOneShot: have %v, need %v.", i, out, tt.out)
}
}
}

func TestExpandHKDFOneShotLimit(t *testing.T) {
if !openssl.SupportsHKDF() {
t.Skip("HKDF is not supported")
}
hash := openssl.NewSHA1
master := []byte{0x00, 0x01, 0x02, 0x03}
info := []byte{}

prk, err := openssl.ExtractHKDF(hash, master, nil)
if err != nil {
t.Fatalf("error extracting HKDF: %v.", err)
}
limit := hash().Size() * 255
out, err := openssl.ExpandHKDFOneShot(hash, prk, info, limit)
if err != nil {
t.Errorf("error expanding HKDF one-shot: %v.", err)
}
if len(out) != limit {
t.Errorf("incorrect output length: have %d, need %d.", len(out), limit)
}

// Expanding one more byte should fail
_, err = openssl.ExpandHKDFOneShot(hash, prk, info, limit+1)
if err == nil {
t.Errorf("expected error for key expansion overflow")
}
}
Loading