--- /n/sources/plan9/sys/include/libsec.h Mon Jun 24 19:54:05 2024 +++ /sys/include/libsec.h Tue Jun 23 00:00:00 2026 @@ -182,6 +182,7 @@ enum MD5dlen= 16, /* MD5 digest length */ AESdlen= 16, /* TODO: see rfc */ Poly1305dlen= 16, /* Poly1305 digest length */ + Maxdlen= 64, Hmacblksz = 64, /* in bytes; from rfc2104 */ }; @@ -447,3 +448,11 @@ void freecertchain(PEMChain *chain); /* password-based key derivation function 2 (rfc2898) */ void pbkdf2_x(uchar *p, ulong plen, uchar *s, ulong slen, ulong rounds, uchar *d, ulong dlen, DigestState* (*x)(uchar*, ulong, uchar*, ulong, uchar*, DigestState*), int xlen); + +/* + * HKDF + */ +void hkdfExtract(uchar*, DigestState*(*)(uchar*, ulong, uchar*, DigestState*), int, uchar*, int, uchar*, int); +int hkdfExpand(uchar*, DigestState*(*)(uchar*, ulong, uchar*, DigestState*), int, uchar*, int, uchar*, int, int); +int hkdfKey(uchar*, DigestState*(*x)(uchar*, ulong, uchar*, DigestState*), int xlen, uchar*, int, uchar*, int, uchar*, int, int); + --- /n/sources/plan9/sys/include/ape/libsec.h Thu Jan 31 10:50:06 2013 +++ /sys/include/ape/libsec.h Tue Jun 23 00:00:00 2026 @@ -150,6 +150,7 @@ enum MD4dlen= 16, /* MD4 digest length */ MD5dlen= 16, /* MD5 digest length */ AESdlen= 16, /* TODO: see rfc */ + Maxdlen= 64, Hmacblksz = 64, /* in bytes; from rfc2104 */ }; @@ -407,4 +408,11 @@ int okThumbprint(uchar *sha1, Thumbprint *ok); uchar *readcert(char *filename, int *pcertlen); PEMChain*readcertchain(char *filename); +/* + * HKDF + */ +void hkdfExtract(uchar*, DigestState*(*)(uchar*, ulong, uchar*, DigestState*), int, uchar*, int, uchar*, int); +int hkdfExpand(uchar*, DigestState*(*)(uchar*, ulong, uchar*, DigestState*), int, uchar*, int, uchar*, int, int); +int hkdfKey(uchar*, DigestState*(*x)(uchar*, ulong, uchar*, DigestState*), int xlen, uchar*, int, uchar*, int, uchar*, int, int); + #endif --- /dev/null Tue Jun 23 12:32:15 2026 +++ /sys/src/libsec/port/hkdf.c Tue Jun 23 00:00:00 2026 @@ -0,0 +1,105 @@ +// Derived from Go's src/crypto/hkdf/hkdf.go and +// src/crypto/internal/fips140/hkdf/hkdf.go, which carry this notice: +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package hkdf implements the HMAC-based Extract-and-Expand Key Derivation +// Function (HKDF) as defined in RFC 5869. +// +// HKDF is a cryptographic key derivation function (KDF) with the goal of +// expanding limited input keying material into one or more cryptographically +// strong secret keys. + +#include +#include +#include + +// hkdfExtract generates a pseudorandom key for use with hkdfExpand from an input +// secret and an optional independent salt. +// +// Only use this function if you need to reuse the extracted key with multiple +// Expand invocations and different context values. Most common scenarios, +// including the generation of multiple keys, should use hkdfKey instead. +void +hkdfExtract(uchar *out, DigestState*(*x)(uchar*, ulong, uchar*, DigestState*), int xlen, +uchar *secret, int nsecret, uchar *salt, int nsalt) +{ + uchar *s, zerosalt[Maxdlen] = { 0 }; + + assert(xlen <= Maxdlen); + + s = salt; + if(s == nil) { + s = zerosalt; + nsalt = xlen; + } + + hmac_x(secret, nsecret, s, nsalt, out, nil, x, xlen); +} + +#define MIN(a, b) ((a) < (b)? (a): (b)) + +// hkdfExpand derives a key from the given hash, key, and optional context info, +// returning a byte array in out of length keylen that can be used as cryptographic key. +// The extraction step is skipped. +// +// The key should have been generated by hkdfExtract, or be a uniformly +// random or pseudorandom cryptographically strong key. See RFC 5869, Section +// 3.3. Most common scenarios will want to use hkdfKey instead. +int +hkdfExpand(uchar *out, DigestState*(*x)(uchar*, ulong, uchar*, DigestState*), int xlen, +uchar *prk, int nprk, uchar *info, int ninfo, int keylen) +{ + u8int counter = 0; + uchar buf[Maxdlen] = { 0 }; + int olen = 0, blen = 0, remain; + DigestState *s; + + assert(xlen <= Maxdlen); + + if(keylen > xlen * 255) { + werrstr("hkdf: requested key length too large"); + return -1; + } + + if(info == nil) + ninfo = 0; + + while(olen < keylen) { + counter++; + assert(counter != 0); + s = hmac_x(buf, blen, prk, nprk, nil, nil, x, xlen); + hmac_x(info, ninfo, prk, nprk, nil, s, x, xlen); + hmac_x(&counter, 1, prk, nprk, buf, s, x, xlen); + blen = xlen; + remain = keylen - olen; + remain = MIN(remain, blen); + memmove(out + olen, buf, remain); + olen += remain; + } + + return 1; +} + +// hkdfKey derives a key from the given hash, secret, salt and context info, +// returning a byte array in out of length keylen that can be used as cryptographic key. +// Salt and info can be nil. +int +hkdfKey(uchar *out, DigestState*(*x)(uchar*, ulong, uchar*, DigestState*),int xlen, +uchar* secret, int nsecret, uchar* salt, int nsalt, uchar *info, int ninfo, int keylen) +{ + uchar prk[Maxdlen]; + + assert(xlen <= Maxdlen); + + if(keylen > xlen * 255) { + werrstr("hkdf: requested key length too large"); + return -1; + } + + hkdfExtract(prk, x, xlen, secret, nsecret, salt, nsalt); + hkdfExpand(out, x, xlen, prk, xlen, info, ninfo, keylen); + + return 1; +} --- /dev/null Tue Jun 23 12:32:15 2026 +++ /sys/src/libsec/port/hkdftest.c Tue Jun 23 00:00:00 2026 @@ -0,0 +1,410 @@ +// Derived from Go's src/cmd/go/src/crypto/hkdf/hkdf_test.go, which carries this notice: +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include +#include +#include "libsec.h" + +typedef struct hkdfTest hkdfTest; +struct hkdfTest { + DigestState*(*x)(uchar*, ulong, uchar*, DigestState*); + int xlen; + uchar master[128]; + int nmaster; + uchar salt[128]; + int nsalt; + uchar prk[128]; + int nprk; + uchar info[128]; + int ninfo; + uchar out[128]; + int nout; +}; + +hkdfTest hkdfTests[] = { + // Tests from RFC 5869 + { + sha2_256, + SHA2_256dlen, + { + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + }, + 22, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, + }, + 13, + { + 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, + }, + 32, + { + 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, + 0xf8, 0xf9, + }, + 10, + { + 0x3c, 0xb2, 0x5f, 0x25, 0xfa, 0xac, 0xd5, 0x7a, + 0x90, 0x43, 0x4f, 0x64, 0xd0, 0x36, 0x2f, 0x2a, + 0x2d, 0x2d, 0x0a, 0x90, 0xcf, 0x1a, 0x5a, 0x4c, + 0x5d, 0xb0, 0x2d, 0x56, 0xec, 0xc4, 0xc5, 0xbf, + 0x34, 0x00, 0x72, 0x08, 0xd5, 0xb8, 0x87, 0x18, + 0x58, 0x65, + }, + 42, + }, + { + sha2_256, + SHA2_256dlen, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + }, + 80, + { + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, + 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, + 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, + 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, + }, + 80, + { + 0x06, 0xa6, 0xb8, 0x8c, 0x58, 0x53, 0x36, 0x1a, + 0x06, 0x10, 0x4c, 0x9c, 0xeb, 0x35, 0xb4, 0x5c, + 0xef, 0x76, 0x00, 0x14, 0x90, 0x46, 0x71, 0x01, + 0x4a, 0x19, 0x3f, 0x40, 0xc1, 0x5f, 0xc2, 0x44, + }, + 32, + { + 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, + 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, + 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, + 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, + 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, + 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, + 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, + 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, + 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, + 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff, + }, + 80, + { + 0xb1, 0x1e, 0x39, 0x8d, 0xc8, 0x03, 0x27, 0xa1, + 0xc8, 0xe7, 0xf7, 0x8c, 0x59, 0x6a, 0x49, 0x34, + 0x4f, 0x01, 0x2e, 0xda, 0x2d, 0x4e, 0xfa, 0xd8, + 0xa0, 0x50, 0xcc, 0x4c, 0x19, 0xaf, 0xa9, 0x7c, + 0x59, 0x04, 0x5a, 0x99, 0xca, 0xc7, 0x82, 0x72, + 0x71, 0xcb, 0x41, 0xc6, 0x5e, 0x59, 0x0e, 0x09, + 0xda, 0x32, 0x75, 0x60, 0x0c, 0x2f, 0x09, 0xb8, + 0x36, 0x77, 0x93, 0xa9, 0xac, 0xa3, 0xdb, 0x71, + 0xcc, 0x30, 0xc5, 0x81, 0x79, 0xec, 0x3e, 0x87, + 0xc1, 0x4c, 0x01, 0xd5, 0xc1, 0xf3, 0x43, 0x4f, + 0x1d, 0x87, + }, + 82, + }, + { + sha2_256, + SHA2_256dlen, + { + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + }, + 22, + { 0 }, + 0, + { + 0x19, 0xef, 0x24, 0xa3, 0x2c, 0x71, 0x7b, 0x16, + 0x7f, 0x33, 0xa9, 0x1d, 0x6f, 0x64, 0x8b, 0xdf, + 0x96, 0x59, 0x67, 0x76, 0xaf, 0xdb, 0x63, 0x77, + 0xac, 0x43, 0x4c, 0x1c, 0x29, 0x3c, 0xcb, 0x04, + }, + 32, + { 0 }, + 0, + { + 0x8d, 0xa4, 0xe7, 0x75, 0xa5, 0x63, 0xc1, 0x8f, + 0x71, 0x5f, 0x80, 0x2a, 0x06, 0x3c, 0x5a, 0x31, + 0xb8, 0xa1, 0x1f, 0x5c, 0x5e, 0xe1, 0x87, 0x9e, + 0xc3, 0x45, 0x4e, 0x5f, 0x3c, 0x73, 0x8d, 0x2d, + 0x9d, 0x20, 0x13, 0x95, 0xfa, 0xa4, 0xb6, 0x1a, + 0x96, 0xc8, + }, + 42, + }, + { + sha2_256, + SHA2_256dlen, + { + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + }, + 22, + // nil + { 0 }, + -1, // replace values with a -1 length with nil + { + 0x19, 0xef, 0x24, 0xa3, 0x2c, 0x71, 0x7b, 0x16, + 0x7f, 0x33, 0xa9, 0x1d, 0x6f, 0x64, 0x8b, 0xdf, + 0x96, 0x59, 0x67, 0x76, 0xaf, 0xdb, 0x63, 0x77, + 0xac, 0x43, 0x4c, 0x1c, 0x29, 0x3c, 0xcb, 0x04, + }, + 32, + // nil + { 0 }, + -1, // replace values with a -1 length with nil + { + 0x8d, 0xa4, 0xe7, 0x75, 0xa5, 0x63, 0xc1, 0x8f, + 0x71, 0x5f, 0x80, 0x2a, 0x06, 0x3c, 0x5a, 0x31, + 0xb8, 0xa1, 0x1f, 0x5c, 0x5e, 0xe1, 0x87, 0x9e, + 0xc3, 0x45, 0x4e, 0x5f, 0x3c, 0x73, 0x8d, 0x2d, + 0x9d, 0x20, 0x13, 0x95, 0xfa, 0xa4, 0xb6, 0x1a, + 0x96, 0xc8, + }, + 42, + }, + { + sha1, + SHA1dlen, + { + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, + }, + 11, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, + }, + 13, + { + 0x9b, 0x6c, 0x18, 0xc4, 0x32, 0xa7, 0xbf, 0x8f, + 0x0e, 0x71, 0xc8, 0xeb, 0x88, 0xf4, 0xb3, 0x0b, + 0xaa, 0x2b, 0xa2, 0x43, + }, + 20, + { + 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, + 0xf8, 0xf9, + }, + 10, + { + 0x08, 0x5a, 0x01, 0xea, 0x1b, 0x10, 0xf3, 0x69, + 0x33, 0x06, 0x8b, 0x56, 0xef, 0xa5, 0xad, 0x81, + 0xa4, 0xf1, 0x4b, 0x82, 0x2f, 0x5b, 0x09, 0x15, + 0x68, 0xa9, 0xcd, 0xd4, 0xf1, 0x55, 0xfd, 0xa2, + 0xc2, 0x2e, 0x42, 0x24, 0x78, 0xd3, 0x05, 0xf3, + 0xf8, 0x96, + }, + 42, + }, + { + sha1, + SHA1dlen, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + }, + 80, + { + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, + 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, + 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, + 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, + }, + 80, + { + 0x8a, 0xda, 0xe0, 0x9a, 0x2a, 0x30, 0x70, 0x59, + 0x47, 0x8d, 0x30, 0x9b, 0x26, 0xc4, 0x11, 0x5a, + 0x22, 0x4c, 0xfa, 0xf6, + }, + 20, + { + 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, + 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, + 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, + 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, + 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, + 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, + 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, + 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, + 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, + 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff, + }, + 80, + { + 0x0b, 0xd7, 0x70, 0xa7, 0x4d, 0x11, 0x60, 0xf7, + 0xc9, 0xf1, 0x2c, 0xd5, 0x91, 0x2a, 0x06, 0xeb, + 0xff, 0x6a, 0xdc, 0xae, 0x89, 0x9d, 0x92, 0x19, + 0x1f, 0xe4, 0x30, 0x56, 0x73, 0xba, 0x2f, 0xfe, + 0x8f, 0xa3, 0xf1, 0xa4, 0xe5, 0xad, 0x79, 0xf3, + 0xf3, 0x34, 0xb3, 0xb2, 0x02, 0xb2, 0x17, 0x3c, + 0x48, 0x6e, 0xa3, 0x7c, 0xe3, 0xd3, 0x97, 0xed, + 0x03, 0x4c, 0x7f, 0x9d, 0xfe, 0xb1, 0x5c, 0x5e, + 0x92, 0x73, 0x36, 0xd0, 0x44, 0x1f, 0x4c, 0x43, + 0x00, 0xe2, 0xcf, 0xf0, 0xd0, 0x90, 0x0b, 0x52, + 0xd3, 0xb4, + }, + 82, + }, + { + sha1, + SHA1dlen, + { + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + }, + 22, + { 0 }, + 0, + { + 0xda, 0x8c, 0x8a, 0x73, 0xc7, 0xfa, 0x77, 0x28, + 0x8e, 0xc6, 0xf5, 0xe7, 0xc2, 0x97, 0x78, 0x6a, + 0xa0, 0xd3, 0x2d, 0x01, + }, + 20, + { 0 }, + 0, + { + 0x0a, 0xc1, 0xaf, 0x70, 0x02, 0xb3, 0xd7, 0x61, + 0xd1, 0xe5, 0x52, 0x98, 0xda, 0x9d, 0x05, 0x06, + 0xb9, 0xae, 0x52, 0x05, 0x72, 0x20, 0xa3, 0x06, + 0xe0, 0x7b, 0x6b, 0x87, 0xe8, 0xdf, 0x21, 0xd0, + 0xea, 0x00, 0x03, 0x3d, 0xe0, 0x39, 0x84, 0xd3, + 0x49, 0x18, + }, + 42, + }, + { + sha1, + SHA1dlen, + { + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + }, + 22, + // nil + { 0 }, + -1, // replace values with a -1 length with nil + { + 0x2a, 0xdc, 0xca, 0xda, 0x18, 0x77, 0x9e, 0x7c, + 0x20, 0x77, 0xad, 0x2e, 0xb1, 0x9d, 0x3f, 0x3e, + 0x73, 0x13, 0x85, 0xdd, + }, + 20, + // nil + { 0 }, + -1, // replace values with a -1 length with nil + { + 0x2c, 0x91, 0x11, 0x72, 0x04, 0xd7, 0x45, 0xf3, + 0x50, 0x0d, 0x63, 0x6a, 0x62, 0xf6, 0x4f, 0x0a, + 0xb3, 0xba, 0xe5, 0x48, 0xaa, 0x53, 0xd4, 0x23, + 0xb0, 0xd1, 0xf2, 0x7e, 0xbb, 0xa6, 0xf5, 0xe5, + 0x67, 0x3a, 0x08, 0x1d, 0x70, 0xcc, 0xe7, 0xac, + 0xfc, 0x48, + }, + 42, + }, +}; + +void +testHkdf(void) +{ + int i, j; + hkdfTest tt; + uchar *info, *salt, prk[128], key[128], expanded[128]; + + for(i = 0; i < nelem(hkdfTests); i++) { + tt = hkdfTests[i]; + + // by convention, we send nil for values with a -1 length + info = tt.info; + if(tt.ninfo == -1) + info = nil; + salt = tt.salt; + if(tt.nsalt == -1) + salt = nil; + + hkdfExtract(prk, tt.x, tt.xlen, tt.master, tt.nmaster, salt, tt.nsalt); + if(memcmp(prk, tt.prk, tt.nprk) != 0) + print("%d: hkdfExtract: FAIL!\n", i); + for(j = 0; j< tt.nprk; j++) + print("%2.2ux", prk[j]); + print("\n"); + + hkdfKey(key, tt.x, tt.xlen, tt.master, tt.nmaster, salt, tt.nsalt, info, tt.ninfo, tt.nout); + if(memcmp(key, tt.out, tt.nout) != 0) + print("%d: hkdfKey: FAIL!\n", i); + for(j = 0; j < tt.nout; j++) + print("%2.2ux", key[j]); + print("\n"); + + hkdfExpand(expanded, tt.x, tt.xlen, prk, tt.nprk, info, tt.ninfo, tt.nout); + if(memcmp(expanded, tt.out, tt.nout) != 0) + print("%d: hkdfExpand: FAIL!\n", i); + for(j = 0; j < tt.nout; j++) + print("%2.2ux", expanded[j]); + print("\n"); + } +} + +void +testHkdflimit(void) +{ + uchar master[] = { 0x00, 0x01, 0x02, 0x03}; + char *info = ""; + int limit = SHA1dlen * 255; + uchar key[SHA1dlen * 255 + 1]; + + if(hkdfKey(key, sha1, SHA1dlen, master, 4, nil, 0, (uchar*)info, 0, limit) != 1) + print("hkdfKey: limit: FAIL: key derivation\n"); + + if(hkdfKey(key, sha1, SHA1dlen, master, 4, nil, 0, (uchar*)info, 0, limit + 1) != -1) + print("hkdfKey: limit: FAIL: expected key derivation to fail, but it succeeded!\n"); +} + +void +main(void) +{ + testHkdf(); + testHkdflimit(); +} --- /n/sources/plan9/sys/src/libsec/port/mkfile Mon Jun 24 19:54:05 2024 +++ /sys/src/libsec/port/mkfile Tue Jun 23 00:00:00 2026 @@ -19,6 +19,7 @@ CFILES = des.c desmodes.c desECB.c desCBC.c des3ECB.c des3CBC.c\ dsagen.c dsaalloc.c dsaprivtopub.c dsasign.c dsaverify.c \ tlshand.c thumb.c readcert.c \ pbkdf2.c\ + hkdf.c\ ALLOFILES=${CFILES:%.c=%.$O} @@ -37,3 +38,6 @@ UPDATE=mkfile\ $O.rsatest: rsatest.$O $LD -o $target $prereq + +$O.hkdftest: hkdftest.$O + $LD -o $target $prereq --- /dev/null Tue Jun 23 12:32:15 2026 +++ /sys/man/2/hkdf Tue Jun 23 00:00:00 2026 @@ -0,0 +1,100 @@ +.TH HKDF 2 +.SH NAME +hkdfExpand, hkdfExtract, hkdfKey \- HMAC-based extract-and-expand key derivation function (HKDF) +.SH SYNOPSIS +.nr Wd \w'\fLvoid \fP'u +.nr In \w'\fLvoid \fP'u +.ta \n(Wdu \w'\fLSHA1state* \fP'u +\n(Wdu +\n(Wdu +\n(Wdu +\n(Wdu +. +.de Ti +.PP +.in +\\n(Inu +.ti -\\n(Inu +.B +.nh +.. +. +.ft L +.nf +#include +#include +#include +#define DS DigestState /* only to abbreviate SYNOPSIS */ +.fi +.Ti +int hkdfExpand(uchar *out, DS*(*x)(uchar*, ulong, uchar*, DS*), int xlen, uchar *prk, int nprk, uchar *info, int ninfo, int keylen) +.Ti +void hkdfExtract(uchar *out, DS*(*x)(uchar*, ulong, uchar*, DS*), int xlen, uchar *secret, int nsecret, uchar *salt, int nsalt) +.Ti +int hkdfKey(uchar *out, DS*(*x)(uchar*, ulong, uchar*, DS*), int xlen, uchar* secret, int nsecret, uchar* salt, int nsalt, uchar *info, int ninfo, int keylen) +.SH DESCRIPTION +.I hkdfExpand +derives a key from the given +.IR x +.IR sechash(2) +function, pseudo random key +.IR prk , +and optional context +.IR info , +returning a byte array in +.IR out +of length +.IR keylen +that can be used as cryptographic key. +The extraction step is skipped. +.PP +The key should have been generated by +.IR hkdfExtract , +or be a uniformly +random or pseudorandom cryptographically strong key. +See RFC 5869, +Section 3.3. +Most common scenarios will want to use +.IR hkdfKey +instead. +.PP +.I hkdfExtract +generates a pseudorandom key in +.IR out +for use with +.IR hkfExpand +from an input +.IR secret +and an optional independent +.IR salt . +.PP +Only use this function if you need to reuse the extracted key with multiple +Expand invocations and different context values. Most common scenarios, +including the generation of multiple keys, should use +.IR hkdfKey +instead. +.PP +.I hkdfKey +derives a key from the given +.IR x +.IR sechash(2) +function, +.IR secret , +.IR salt +and context +.IR info , +returning a byte array in +.IR out +of length +.IR keylen +that can be used as cryptographic key. +.PP +.IR Salt +and +.IR info +may be +.BR nil . +.SH SOURCE +.B /sys/src/libsec +.SH SEE ALSO +.IR sechash (2) +.br +RFC 5869 +.SH DIAGNOSTICS +These functions set +.IR errstr .