From c1571feb5faf5cce844354c63d0f3e842464bea3 Mon Sep 17 00:00:00 2001 From: Adam Langley Date: Wed, 21 Jul 2021 17:04:28 -0700 Subject: [PATCH] acvp: add HKDF support. Change-Id: I26251ce85f2cb1b441ae415b1506161a90bd3efa Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/48585 Reviewed-by: David Benjamin --- util/fipstools/acvp/ACVP.md | 1 + .../acvp/acvptool/subprocess/hkdf.go | 203 ++++++++++++++++++ .../acvp/acvptool/subprocess/subprocess.go | 1 + .../acvp/acvptool/test/expected/KAS-KDF.bz2 | Bin 0 -> 808 bytes util/fipstools/acvp/acvptool/test/tests.json | 1 + .../acvp/acvptool/test/vectors/KAS-KDF.bz2 | Bin 0 -> 4401 bytes .../testmodulewrapper/testmodulewrapper.go | 58 +++++ 7 files changed, 264 insertions(+) create mode 100644 util/fipstools/acvp/acvptool/subprocess/hkdf.go create mode 100644 util/fipstools/acvp/acvptool/test/expected/KAS-KDF.bz2 create mode 100644 util/fipstools/acvp/acvptool/test/vectors/KAS-KDF.bz2 diff --git a/util/fipstools/acvp/ACVP.md b/util/fipstools/acvp/ACVP.md index 97ec42332..c9570c6fa 100644 --- a/util/fipstools/acvp/ACVP.md +++ b/util/fipstools/acvp/ACVP.md @@ -72,6 +72,7 @@ The other commands are as follows. (Note that you only need to implement the com | ECDSA/sigGen | Curve name, private key, hash name, message | R, S | | ECDSA/sigVer | Curve name, hash name, message, X, Y, R, S | Single-byte validity flag | | FFDH | p, q, g, peer public key, local private key (or empty), local public key (or empty) | Local public key, shared key | +| HKDF/<HASH> | key, salt, info, num output bytes | Key | | HMAC-SHA-1 | Value to hash, key | Digest | | HMAC-SHA2-224 | Value to hash, key | Digest | | HMAC-SHA2-256 | Value to hash, key | Digest | diff --git a/util/fipstools/acvp/acvptool/subprocess/hkdf.go b/util/fipstools/acvp/acvptool/subprocess/hkdf.go new file mode 100644 index 000000000..21ebca6fb --- /dev/null +++ b/util/fipstools/acvp/acvptool/subprocess/hkdf.go @@ -0,0 +1,203 @@ +// Copyright (c) 2021, Google Inc. +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY +// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION +// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN +// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +package subprocess + +import ( + "bytes" + "encoding/hex" + "encoding/json" + "fmt" + "strings" +) + +// The following structures reflect the JSON of ACVP KAS KDF tests. See +// https://pages.nist.gov/ACVP/draft-hammett-acvp-kas-kdf-twostep.html + +type hkdfTestVectorSet struct { + Groups []hkdfTestGroup `json:"testGroups"` +} + +type hkdfTestGroup struct { + ID uint64 `json:"tgId"` + Type string `json:"testType"` // AFT or VAL + Config hkdfConfiguration `json:"kdfConfiguration"` + Tests []hkdfTest `json:"tests"` +} + +type hkdfTest struct { + ID uint64 `json:"tcId"` + Params hkdfParameters `json:"kdfParameter"` + PartyU hkdfPartyInfo `json:"fixedInfoPartyU"` + PartyV hkdfPartyInfo `json:"fixedInfoPartyV"` + ExpectedHex string `json:"dkm"` +} + +type hkdfConfiguration struct { + Type string `json:"kdfType"` + AdditionalNonce bool `json:"requiresAdditionalNoncePair"` + OutputBits uint32 `json:"l"` + FixedInfoPattern string `json:"fixedInfoPattern"` + FixedInputEncoding string `json:"fixedInfoEncoding"` + KDFMode string `json:"kdfMode"` + MACMode string `json:"macMode"` + CounterLocation string `json:"counterLocation"` + CounterBits uint `json:"counterLen"` +} + +func (c *hkdfConfiguration) extract() (outBytes uint32, hashName string, err error) { + if c.Type != "twoStep" || + c.AdditionalNonce || + c.FixedInfoPattern != "uPartyInfo||vPartyInfo" || + c.FixedInputEncoding != "concatenation" || + c.KDFMode != "feedback" || + c.CounterLocation != "after fixed data" || + c.CounterBits != 8 || + c.OutputBits%8 != 0 { + return 0, "", fmt.Errorf("KAS-KDF not configured for HKDF: %#v", c) + } + + if !strings.HasPrefix(c.MACMode, "HMAC-") { + return 0, "", fmt.Errorf("MAC mode %q does't start with 'HMAC-'", c.MACMode) + } + + return c.OutputBits / 8, c.MACMode[5:], nil +} + +type hkdfParameters struct { + SaltHex string `json:"salt"` + KeyHex string `json:"z"` +} + +func (p *hkdfParameters) extract() (key, salt []byte, err error) { + salt, err = hex.DecodeString(p.SaltHex) + if err != nil { + return nil, nil, err + } + + key, err = hex.DecodeString(p.KeyHex) + if err != nil { + return nil, nil, err + } + + return key, salt, nil +} + +type hkdfPartyInfo struct { + IDHex string `json:"partyId"` + ExtraHex string `json:"ephemeralData"` +} + +func (p *hkdfPartyInfo) data() ([]byte, error) { + ret, err := hex.DecodeString(p.IDHex) + if err != nil { + return nil, err + } + + if len(p.ExtraHex) > 0 { + extra, err := hex.DecodeString(p.ExtraHex) + if err != nil { + return nil, err + } + ret = append(ret, extra...) + } + + return ret, nil +} + +type hkdfTestGroupResponse struct { + ID uint64 `json:"tgId"` + Tests []hkdfTestResponse `json:"tests"` +} + +type hkdfTestResponse struct { + ID uint64 `json:"tcId"` + KeyOut string `json:"dkm,omitempty"` + Passed *bool `json:"testPassed,omitempty"` +} + +type hkdf struct{} + +func (k *hkdf) Process(vectorSet []byte, m Transactable) (interface{}, error) { + var parsed hkdfTestVectorSet + if err := json.Unmarshal(vectorSet, &parsed); err != nil { + return nil, err + } + + var respGroups []hkdfTestGroupResponse + for _, group := range parsed.Groups { + groupResp := hkdfTestGroupResponse{ID: group.ID} + + var isValidationTest bool + switch group.Type { + case "VAL": + isValidationTest = true + case "AFT": + isValidationTest = false + default: + return nil, fmt.Errorf("unknown test type %q", group.Type) + } + + outBytes, hashName, err := group.Config.extract() + if err != nil { + return nil, err + } + + for _, test := range group.Tests { + testResp := hkdfTestResponse{ID: test.ID} + + key, salt, err := test.Params.extract() + if err != nil { + return nil, err + } + uData, err := test.PartyU.data() + if err != nil { + return nil, err + } + vData, err := test.PartyV.data() + if err != nil { + return nil, err + } + + var expected []byte + if isValidationTest { + expected, err = hex.DecodeString(test.ExpectedHex) + if err != nil { + return nil, err + } + } + + info := make([]byte, 0, len(uData)+len(vData)) + info = append(info, uData...) + info = append(info, vData...) + + resp, err := m.Transact("HKDF/"+hashName, 1, key, salt, info, uint32le(outBytes)) + if err != nil { + return nil, fmt.Errorf("HKDF operation failed: %s", err) + } + + if isValidationTest { + passed := bytes.Equal(expected, resp[0]) + testResp.Passed = &passed + } else { + testResp.KeyOut = hex.EncodeToString(resp[0]) + } + + groupResp.Tests = append(groupResp.Tests, testResp) + } + respGroups = append(respGroups, groupResp) + } + + return respGroups, nil +} diff --git a/util/fipstools/acvp/acvptool/subprocess/subprocess.go b/util/fipstools/acvp/acvptool/subprocess/subprocess.go index 5256b1ee4..fe74993db 100644 --- a/util/fipstools/acvp/acvptool/subprocess/subprocess.go +++ b/util/fipstools/acvp/acvptool/subprocess/subprocess.go @@ -96,6 +96,7 @@ func NewWithIO(cmd *exec.Cmd, in io.WriteCloser, out io.ReadCloser) *Subprocess "ctrDRBG": &drbg{"ctrDRBG", map[string]bool{"AES-128": true, "AES-192": true, "AES-256": true}}, "hmacDRBG": &drbg{"hmacDRBG", map[string]bool{"SHA-1": true, "SHA2-224": true, "SHA2-256": true, "SHA2-384": true, "SHA2-512": true}}, "KDF": &kdfPrimitive{}, + "KAS-KDF": &hkdf{}, "CMAC-AES": &keyedMACPrimitive{"CMAC-AES"}, "RSA": &rsa{}, "kdf-components": &tlsKDF{}, diff --git a/util/fipstools/acvp/acvptool/test/expected/KAS-KDF.bz2 b/util/fipstools/acvp/acvptool/test/expected/KAS-KDF.bz2 new file mode 100644 index 0000000000000000000000000000000000000000..df3edf565fcb6d62c643eef7493c7e5d5748c83c GIT binary patch literal 808 zcmV+@1K0dQT4*^jL0KkKS(sjd8UO@2|A0hPKmbq&|L_B#&I&)T-wIFzJ|6d>9QSKj zA`>*Hl=Sop8XkxM001JX{t98HLYjaY00008srfQuXd0CDAjr@F8UVs6`lgS)i8eoo=BWMVyHX631 z8}IDL_p7(Nqh@Cj5fKqt4TKs1009Oto$Cuo;eI9PS{Z6>4AJylY*B{KmAX{n-{^Rv zFL<+i`))&El546!;Yninwj!wAv`O#-=vdZZ>T{{Mz2&njQ48X(l@C=t8poI}@Y0%S z`krA+O|)y_rzRogyfbyqc9rGlodMxnI){rbgj0j5p`Dym;hjB%?zg?F0cidoi; ztjO0w$HeH!a{wyzvWZZ`FOn&?DZ4^9K-_Q=vQdq*D&n{`C5f@E;u#s! zVZ)?doIvP87-O|zLP;$zWf>AVs7UiOn^}hzwZiUfX)wv)5v@`vNvIXC2^p`ZZ8%wV zt*a}HF0OZ9Y7mVTKMpy35HvO>Ha0e!-s)pxXBxp|Usjob4n^=U5FH2-zbW z=VYYFp46K0d@)j5X07GDiT#pKK$l7X#bZ)1` z8 literal 0 HcmV?d00001 diff --git a/util/fipstools/acvp/acvptool/test/tests.json b/util/fipstools/acvp/acvptool/test/tests.json index 6cf549d25..dfbaac54c 100644 --- a/util/fipstools/acvp/acvptool/test/tests.json +++ b/util/fipstools/acvp/acvptool/test/tests.json @@ -18,6 +18,7 @@ {"Wrapper": "modulewrapper", "In": "vectors/HMAC-SHA2-256.bz2", "Out": "expected/HMAC-SHA2-256.bz2"}, {"Wrapper": "modulewrapper", "In": "vectors/HMAC-SHA2-384.bz2", "Out": "expected/HMAC-SHA2-384.bz2"}, {"Wrapper": "modulewrapper", "In": "vectors/HMAC-SHA2-512.bz2", "Out": "expected/HMAC-SHA2-512.bz2"}, +{"Wrapper": "testmodulewrapper", "In": "vectors/KAS-KDF.bz2", "Out": "expected/KAS-KDF.bz2"}, {"Wrapper": "modulewrapper", "In": "vectors/KAS-ECC-SSC.bz2"}, {"Wrapper": "modulewrapper", "In": "vectors/KAS-FFC-SSC.bz2"}, {"Wrapper": "testmodulewrapper", "In": "vectors/KDF.bz2"}, diff --git a/util/fipstools/acvp/acvptool/test/vectors/KAS-KDF.bz2 b/util/fipstools/acvp/acvptool/test/vectors/KAS-KDF.bz2 new file mode 100644 index 0000000000000000000000000000000000000000..eadbc7e265beaf090f1ce1ac75f500960681c93c GIT binary patch literal 4401 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 literal 0 HcmV?d00001 diff --git a/util/fipstools/acvp/acvptool/testmodulewrapper/testmodulewrapper.go b/util/fipstools/acvp/acvptool/testmodulewrapper/testmodulewrapper.go index 00c32ab9b..e98946177 100644 --- a/util/fipstools/acvp/acvptool/testmodulewrapper/testmodulewrapper.go +++ b/util/fipstools/acvp/acvptool/testmodulewrapper/testmodulewrapper.go @@ -16,6 +16,7 @@ import ( "io" "os" + "golang.org/x/crypto/hkdf" "golang.org/x/crypto/xts" ) @@ -24,6 +25,7 @@ var handlers = map[string]func([][]byte) error{ "KDF-counter": kdfCounter, "AES-XTS/encrypt": xtsEncrypt, "AES-XTS/decrypt": xtsDecrypt, + "HKDF/SHA2-256": hkdfMAC, } func getConfig(args [][]byte) error { @@ -69,6 +71,39 @@ func getConfig(args [][]byte) error { "tweakMode": [ "number" ] + }, { + "algorithm": "KAS-KDF", + "mode": "TwoStep", + "revision": "Sp800-56Cr2", + "capabilities": [{ + "macSaltMethods": [ + "random", + "default" + ], + "fixedInfoPattern": "uPartyInfo||vPartyInfo", + "encoding": [ + "concatenation" + ], + "kdfMode": "feedback", + "macMode": [ + "HMAC-SHA2-256" + ], + "supportedLengths": [{ + "min": 128, + "max": 512, + "increment": 64 + }], + "fixedDataOrder": [ + "after fixed data" + ], + "counterLength": [ + 8 + ], + "requiresEmptyIv": true, + "supportsEmptyIv": true + }], + "l": 256, + "z": [256, 384] } ]`)) } @@ -188,6 +223,29 @@ func doXTS(args [][]byte, decrypt bool) error { return reply(msg) } +func hkdfMAC(args [][]byte) error { + if len(args) != 4 { + return fmt.Errorf("HKDF received %d args, wanted 4", len(args)) + } + + key := args[0] + salt := args[1] + info := args[2] + lengthBytes := args[3] + + if len(lengthBytes) != 4 { + return fmt.Errorf("uint32 length was %d bytes long", len(lengthBytes)) + } + + length := binary.LittleEndian.Uint32(lengthBytes) + + mac := hkdf.New(sha256.New, key, salt, info) + ret := make([]byte, length) + mac.Read(ret) + + return reply(ret) +} + const ( maxArgs = 8 maxArgLength = 1 << 20