Move TLS 1.3 KDF functions into the FIPS module.
Change-Id: I32a40a73f96e029ac9096af826d15b22d9dcad28
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/58745
Auto-Submit: Adam Langley <agl@google.com>
Reviewed-by: David Benjamin <davidben@google.com>
Commit-Queue: David Benjamin <davidben@google.com>
diff --git a/util/fipstools/acvp/ACVP.md b/util/fipstools/acvp/ACVP.md
index d3578e2..61c6f88 100644
--- a/util/fipstools/acvp/ACVP.md
+++ b/util/fipstools/acvp/ACVP.md
@@ -77,6 +77,8 @@
| 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 |
+| HKDFExtract | secret, salt | Key |
+| HKDFExpandLabel | Output length, secret, label, transcript hash | 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/subprocess.go b/util/fipstools/acvp/acvptool/subprocess/subprocess.go
index 48d7083..b496982 100644
--- a/util/fipstools/acvp/acvptool/subprocess/subprocess.go
+++ b/util/fipstools/acvp/acvptool/subprocess/subprocess.go
@@ -107,6 +107,7 @@
"hmacDRBG": &drbg{"hmacDRBG", map[string]bool{"SHA-1": true, "SHA2-224": true, "SHA2-256": true, "SHA2-384": true, "SHA2-512": true}},
"KDF": &kdfPrimitive{},
"KDA": &hkdf{},
+ "TLS-v1.3": &tls13{},
"CMAC-AES": &keyedMACPrimitive{"CMAC-AES"},
"RSA": &rsa{},
"kdf-components": &tlsKDF{},
diff --git a/util/fipstools/acvp/acvptool/subprocess/tls13.go b/util/fipstools/acvp/acvptool/subprocess/tls13.go
new file mode 100644
index 0000000..b8b6e51
--- /dev/null
+++ b/util/fipstools/acvp/acvptool/subprocess/tls13.go
@@ -0,0 +1,240 @@
+// Copyright (c) 2023, 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 (
+ "crypto/sha256"
+ "crypto/sha512"
+ "encoding/hex"
+ "encoding/json"
+ "fmt"
+)
+
+// The following structures reflect the JSON of TLS 1.3 tests. See
+// https://pages.nist.gov/ACVP/draft-hammett-acvp-kdf-tls-v1.3.html
+
+type tls13TestVectorSet struct {
+ Groups []tls13TestGroup `json:"testGroups"`
+}
+
+type tls13TestGroup struct {
+ ID uint64 `json:"tgId"`
+ HashFunc string `json:"hmacAlg"`
+ Tests []tls13Test `json:"tests"`
+}
+
+type tls13Test struct {
+ ID uint64 `json:"tcId"`
+ // Although ACVP refers to these as client and server randoms, these
+ // fields are misnamed and really contain portions of the handshake
+ // transcript. Concatenated in order, they give the transcript up to
+ // the named message. In case of HelloRetryRequest, ClientHelloHex
+ // includes up to the second ClientHello.
+ ClientHelloHex string `json:"helloClientRandom"`
+ ServerHelloHex string `json:"helloServerRandom"`
+ ServerFinishedHex string `json:"finishedServerRandom"`
+ ClientFinishedHex string `json:"finishedClientRandom"`
+ DHEInputHex string `json:"dhe"`
+ PSKInputHex string `json:"psk"`
+}
+
+type tls13TestGroupResponse struct {
+ ID uint64 `json:"tgId"`
+ Tests []tls13TestResponse `json:"tests"`
+}
+
+type tls13TestResponse struct {
+ ID uint64 `json:"tcId"`
+ ClientEarlyTrafficSecretHex string `json:"clientEarlyTrafficSecret"`
+ EarlyExporterMasterSecretHex string `json:"earlyExporterMasterSecret"`
+ ClientHandshakeTrafficSecretHex string `json:"clientHandshakeTrafficSecret"`
+ ServerHandshakeTrafficSecretHex string `json:"serverHandshakeTrafficSecret"`
+ ClientApplicationTrafficSecretHex string `json:"clientApplicationTrafficSecret"`
+ ServerApplicationTrafficSecretHex string `json:"serverApplicationTrafficSecret"`
+ ExporterMasterSecretHex string `json:"exporterMasterSecret"`
+ ResumptionMasterSecretHex string `json:"resumptionMasterSecret"`
+}
+
+type tls13 struct{}
+
+func (k *tls13) Process(vectorSet []byte, m Transactable) (interface{}, error) {
+ var parsed tls13TestVectorSet
+ if err := json.Unmarshal(vectorSet, &parsed); err != nil {
+ return nil, err
+ }
+
+ var respGroups []tls13TestGroupResponse
+ for _, group := range parsed.Groups {
+ groupResp := tls13TestGroupResponse{ID: group.ID}
+
+ for _, test := range group.Tests {
+ testResp := tls13TestResponse{ID: test.ID}
+
+ clientHello, err := hex.DecodeString(test.ClientHelloHex)
+ if err != nil {
+ return nil, err
+ }
+ serverHello, err := hex.DecodeString(test.ServerHelloHex)
+ if err != nil {
+ return nil, err
+ }
+ serverFinished, err := hex.DecodeString(test.ServerFinishedHex)
+ if err != nil {
+ return nil, err
+ }
+ clientFinished, err := hex.DecodeString(test.ClientFinishedHex)
+ if err != nil {
+ return nil, err
+ }
+
+ // See https://www.rfc-editor.org/rfc/rfc8446#section-7.1
+ var hashLen int
+ var emptyHash []byte
+ switch group.HashFunc {
+ case "SHA2-256":
+ hashLen = 256 / 8
+ digest := sha256.Sum256(nil)
+ emptyHash = digest[:]
+ case "SHA2-384":
+ hashLen = 384 / 8
+ digest := sha512.Sum384(nil)
+ emptyHash = digest[:]
+ default:
+ return nil, fmt.Errorf("hash function %q is not supported for TLS v1.3", group.HashFunc)
+ }
+ hashLenBytes := uint32le(uint32(hashLen))
+
+ psk, err := hex.DecodeString(test.PSKInputHex)
+ if err != nil {
+ return nil, err
+ }
+ if len(psk) == 0 {
+ psk = make([]byte, hashLen)
+ }
+
+ dhe, err := hex.DecodeString(test.DHEInputHex)
+ if err != nil {
+ return nil, err
+ }
+ if len(dhe) == 0 {
+ dhe = make([]byte, hashLen)
+ }
+
+ zeros := make([]byte, hashLen)
+ earlySecret, err := m.Transact("HKDFExtract/"+group.HashFunc, 1, psk, zeros)
+ if err != nil {
+ return nil, fmt.Errorf("HKDFExtract operation failed: %s", err)
+ }
+
+ hashedToClientHello, err := m.Transact(group.HashFunc, 1, clientHello)
+ if err != nil {
+ return nil, fmt.Errorf("%q operation failed: %s", group.HashFunc, err)
+ }
+ hashedToServerHello, err := m.Transact(group.HashFunc, 1, concat(clientHello, serverHello))
+ if err != nil {
+ return nil, fmt.Errorf("%q operation failed: %s", group.HashFunc, err)
+ }
+ hashedToServerFinished, err := m.Transact(group.HashFunc, 1, concat(clientHello, serverHello, serverFinished))
+ if err != nil {
+ return nil, fmt.Errorf("%q operation failed: %s", group.HashFunc, err)
+ }
+ hashedMessages, err := m.Transact(group.HashFunc, 1, concat(clientHello, serverHello, serverFinished, clientFinished))
+ if err != nil {
+ return nil, fmt.Errorf("%q operation failed: %s", group.HashFunc, err)
+ }
+
+ clientEarlyTrafficSecret, err := m.Transact("HKDFExpandLabel/"+group.HashFunc, 1, hashLenBytes, earlySecret[0], []byte("c e traffic"), hashedToClientHello[0])
+ if err != nil {
+ return nil, fmt.Errorf("HKDFExpandLabel operation failed: %s", err)
+ }
+ testResp.ClientEarlyTrafficSecretHex = hex.EncodeToString(clientEarlyTrafficSecret[0])
+
+ earlyExporter, err := m.Transact("HKDFExpandLabel/"+group.HashFunc, 1, hashLenBytes, earlySecret[0], []byte("e exp master"), hashedToClientHello[0])
+ if err != nil {
+ return nil, fmt.Errorf("HKDFExpandLabel operation failed: %s", err)
+ }
+ testResp.EarlyExporterMasterSecretHex = hex.EncodeToString(earlyExporter[0])
+
+ derivedSecret, err := m.Transact("HKDFExpandLabel/"+group.HashFunc, 1, hashLenBytes, earlySecret[0], []byte("derived"), emptyHash[:])
+ if err != nil {
+ return nil, fmt.Errorf("HKDFExpandLabel operation failed: %s", err)
+ }
+
+ handshakeSecret, err := m.Transact("HKDFExtract/"+group.HashFunc, 1, dhe, derivedSecret[0])
+ if err != nil {
+ return nil, fmt.Errorf("HKDFExtract operation failed: %s", err)
+ }
+
+ clientHandshakeTrafficSecret, err := m.Transact("HKDFExpandLabel/"+group.HashFunc, 1, hashLenBytes, handshakeSecret[0], []byte("c hs traffic"), hashedToServerHello[0])
+ if err != nil {
+ return nil, fmt.Errorf("HKDFExpandLabel operation failed: %s", err)
+ }
+ testResp.ClientHandshakeTrafficSecretHex = hex.EncodeToString(clientHandshakeTrafficSecret[0])
+
+ serverHandshakeTrafficSecret, err := m.Transact("HKDFExpandLabel/"+group.HashFunc, 1, hashLenBytes, handshakeSecret[0], []byte("s hs traffic"), hashedToServerHello[0])
+ if err != nil {
+ return nil, fmt.Errorf("HKDFExpandLabel operation failed: %s", err)
+ }
+ testResp.ServerHandshakeTrafficSecretHex = hex.EncodeToString(serverHandshakeTrafficSecret[0])
+
+ derivedSecret, err = m.Transact("HKDFExpandLabel/"+group.HashFunc, 1, hashLenBytes, handshakeSecret[0], []byte("derived"), emptyHash[:])
+ if err != nil {
+ return nil, fmt.Errorf("HKDFExpandLabel operation failed: %s", err)
+ }
+
+ masterSecret, err := m.Transact("HKDFExtract/"+group.HashFunc, 1, zeros, derivedSecret[0])
+ if err != nil {
+ return nil, fmt.Errorf("HKDFExtract operation failed: %s", err)
+ }
+
+ clientAppTrafficSecret, err := m.Transact("HKDFExpandLabel/"+group.HashFunc, 1, hashLenBytes, masterSecret[0], []byte("c ap traffic"), hashedToServerFinished[0])
+ if err != nil {
+ return nil, fmt.Errorf("HKDFExpandLabel operation failed: %s", err)
+ }
+ testResp.ClientApplicationTrafficSecretHex = hex.EncodeToString(clientAppTrafficSecret[0])
+
+ serverAppTrafficSecret, err := m.Transact("HKDFExpandLabel/"+group.HashFunc, 1, hashLenBytes, masterSecret[0], []byte("s ap traffic"), hashedToServerFinished[0])
+ if err != nil {
+ return nil, fmt.Errorf("HKDFExpandLabel operation failed: %s", err)
+ }
+ testResp.ServerApplicationTrafficSecretHex = hex.EncodeToString(serverAppTrafficSecret[0])
+
+ exporterSecret, err := m.Transact("HKDFExpandLabel/"+group.HashFunc, 1, hashLenBytes, masterSecret[0], []byte("exp master"), hashedToServerFinished[0])
+ if err != nil {
+ return nil, fmt.Errorf("HKDFExpandLabel operation failed: %s", err)
+ }
+ testResp.ExporterMasterSecretHex = hex.EncodeToString(exporterSecret[0])
+
+ resumptionSecret, err := m.Transact("HKDFExpandLabel/"+group.HashFunc, 1, hashLenBytes, masterSecret[0], []byte("res master"), hashedMessages[0])
+ if err != nil {
+ return nil, fmt.Errorf("HKDFExpandLabel operation failed: %s", err)
+ }
+ testResp.ResumptionMasterSecretHex = hex.EncodeToString(resumptionSecret[0])
+
+ groupResp.Tests = append(groupResp.Tests, testResp)
+ }
+ respGroups = append(respGroups, groupResp)
+ }
+
+ return respGroups, nil
+}
+
+func concat(slices ...[]byte) []byte {
+ var ret []byte
+ for _, slice := range slices {
+ ret = append(ret, slice...)
+ }
+ return ret
+}
diff --git a/util/fipstools/acvp/acvptool/test/expected/TLS13.bz2 b/util/fipstools/acvp/acvptool/test/expected/TLS13.bz2
new file mode 100644
index 0000000..7693c5f
--- /dev/null
+++ b/util/fipstools/acvp/acvptool/test/expected/TLS13.bz2
Binary files differ
diff --git a/util/fipstools/acvp/acvptool/test/tests.json b/util/fipstools/acvp/acvptool/test/tests.json
index 36fdaad..3e7dbd0 100644
--- a/util/fipstools/acvp/acvptool/test/tests.json
+++ b/util/fipstools/acvp/acvptool/test/tests.json
@@ -30,5 +30,6 @@
{"Wrapper": "modulewrapper", "In": "vectors/SHA2-224.bz2", "Out": "expected/SHA2-224.bz2"},
{"Wrapper": "modulewrapper", "In": "vectors/SHA2-256.bz2", "Out": "expected/SHA2-256.bz2"},
{"Wrapper": "modulewrapper", "In": "vectors/SHA2-384.bz2", "Out": "expected/SHA2-384.bz2"},
-{"Wrapper": "modulewrapper", "In": "vectors/SHA2-512.bz2", "Out": "expected/SHA2-512.bz2"}
+{"Wrapper": "modulewrapper", "In": "vectors/SHA2-512.bz2", "Out": "expected/SHA2-512.bz2"},
+{"Wrapper": "modulewrapper", "In": "vectors/TLS13.bz2", "Out": "expected/TLS13.bz2"}
]
diff --git a/util/fipstools/acvp/acvptool/test/vectors/TLS13.bz2 b/util/fipstools/acvp/acvptool/test/vectors/TLS13.bz2
new file mode 100644
index 0000000..7e8ea08
--- /dev/null
+++ b/util/fipstools/acvp/acvptool/test/vectors/TLS13.bz2
Binary files differ
diff --git a/util/fipstools/acvp/modulewrapper/modulewrapper.cc b/util/fipstools/acvp/modulewrapper/modulewrapper.cc
index 5c4f9b0..85622c1 100644
--- a/util/fipstools/acvp/modulewrapper/modulewrapper.cc
+++ b/util/fipstools/acvp/modulewrapper/modulewrapper.cc
@@ -914,6 +914,20 @@
"increment": 8
}
]
+ },
+ {
+ "algorithm": "TLS-v1.3",
+ "mode": "KDF",
+ "revision": "RFC8446",
+ "hmacAlg": [
+ "SHA2-256",
+ "SHA2-384"
+ ],
+ "runningMode": [
+ "DHE",
+ "PSK",
+ "PSK-DHE"
+ ]
}
])";
return write_reply({Span<const uint8_t>(
@@ -1484,6 +1498,49 @@
return write_reply({out});
}
+template <const EVP_MD *HashFunc()>
+static bool HKDFExtract(const Span<const uint8_t> args[],
+ ReplyCallback write_reply) {
+ const EVP_MD *const md = HashFunc();
+ const auto secret = args[0];
+ const auto salt = args[1];
+
+ std::vector<uint8_t> out(EVP_MD_size(md));
+ size_t out_len;
+ if (!HKDF_extract(out.data(), &out_len, md, secret.data(), secret.size(),
+ salt.data(), salt.size())) {
+ return false;
+ }
+ assert(out_len == out.size());
+ return write_reply({out});
+}
+
+template <const EVP_MD *HashFunc()>
+static bool HKDFExpandLabel(const Span<const uint8_t> args[],
+ ReplyCallback write_reply) {
+ const EVP_MD *const md = HashFunc();
+ const auto out_len_bytes = args[0];
+ const auto secret = args[1];
+ const auto label = args[2];
+ const auto hash = args[3];
+
+ if (out_len_bytes.size() != sizeof(uint32_t)) {
+ return false;
+ }
+ const uint32_t out_len = CRYPTO_load_u32_le(out_len_bytes.data());
+ if (out_len > (1 << 24)) {
+ return false;
+ }
+
+ std::vector<uint8_t> out(out_len);
+ if (!CRYPTO_tls13_hkdf_expand_label(out.data(), out_len, md, secret.data(),
+ secret.size(), label.data(), label.size(),
+ hash.data(), hash.size())) {
+ return false;
+ }
+ return write_reply({out});
+}
+
template <bool WithReseed>
static bool DRBG(const Span<const uint8_t> args[], ReplyCallback write_reply) {
const auto out_len_bytes = args[0];
@@ -2029,6 +2086,10 @@
{"HKDF/SHA2-384", 4, HKDF<EVP_sha384>},
{"HKDF/SHA2-512", 4, HKDF<EVP_sha512>},
{"HKDF/SHA2-512/256", 4, HKDF<EVP_sha512_256>},
+ {"HKDFExpandLabel/SHA2-256", 4, HKDFExpandLabel<EVP_sha256>},
+ {"HKDFExpandLabel/SHA2-384", 4, HKDFExpandLabel<EVP_sha384>},
+ {"HKDFExtract/SHA2-256", 2, HKDFExtract<EVP_sha256>},
+ {"HKDFExtract/SHA2-384", 2, HKDFExtract<EVP_sha384>},
{"HMAC-SHA-1", 2, HMAC<EVP_sha1>},
{"HMAC-SHA2-224", 2, HMAC<EVP_sha224>},
{"HMAC-SHA2-256", 2, HMAC<EVP_sha256>},
diff --git a/util/fipstools/break-kat.go b/util/fipstools/break-kat.go
index ed29bb3..c412d0e 100644
--- a/util/fipstools/break-kat.go
+++ b/util/fipstools/break-kat.go
@@ -26,6 +26,7 @@
"SHA-256": "ff3b857da7236a2baa0f396b51522217",
"SHA-512": "212512f8d2ad8322781c6c4d69a9daa1",
"TLS-KDF": "abc3657b094c7628a0b282996fe75a75f4984fd94d4ecc2fcf53a2c469a3f731",
+ "TLS13-KDF": "024a0d80f357f2499a1244dac26dab66fc13ed85fca71dace146211119525874",
"RSA-sign": "d2b56e53306f720d7929d8708bf46f1c22300305582b115bedcac722d8aa5ab2",
"RSA-verify": "abe2cbc13d6bd39d48db5334ddbf8d070a93bdcb104e2cc5d0ee486ee295f6b31bda126c41890b98b73e70e6b65d82f95c663121755a90744c8d1c21148a1960be0eca446e9ff497f1345c537ef8119b9a4398e95c5c6de2b1c955905c5299d8ce7a3b6ab76380d9babdd15f610237e1f3f2aa1c1f1e770b62fbb596381b2ebdd77ecef9c90d4c92f7b6b05fed2936285fa94826e62055322a33b6f04c74ce69e5d8d737fb838b79d2d48e3daf71387531882531a95ac964d02ea413bf85952982bbc089527daff5b845c9a0f4d14ef1956d9c3acae882d12da66da0f35794f5ee32232333517db9315232a183b991654dbea41615345c885325926744a53915",
"ECDSA-sign": "1e35930be860d0942ca7bbd6f6ded87f157e4de24f81ed4b875c0e018e89a81f",