util/fipstools: add SSH KDF ACVP support
This commit updates the acvptool subprocess handling to support the
SSH KDF ACVP tests specified in:
https://pages.nist.gov/ACVP/draft-celi-acvp-kdf-ssh.html
These vectors use the algorithm "kdf-components" and the mode "ssh".
Module wrappers that advertise capabilities that include this algo/mode
need to implement two new commands (described in ACVP.md):
1. "SSHKDF/$HASH/client" for deriving client direction keys.
2. "SSHKDF/$HASH/server" for deriving server direction keys.
Both commands take K, H, SessionID and a cipher name as input and return
the IV key, the encryption key, and the integrity key for their
respective direction.
Change-Id: Ib32612222f0bba299c1365b0bd9188a604fd1ead
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/74347
Reviewed-by: Adam Langley <agl@google.com>
Commit-Queue: Adam Langley <agl@google.com>
Reviewed-by: Bob Beck <bbe@google.com>
diff --git a/util/fipstools/acvp/ACVP.md b/util/fipstools/acvp/ACVP.md
index 9b8f9bc..3a3b84e 100644
--- a/util/fipstools/acvp/ACVP.md
+++ b/util/fipstools/acvp/ACVP.md
@@ -139,6 +139,8 @@
| SLH-DSA-XX/keyGen | Seed | Private key, public key |
| SLH-DSA-XX/sigGen | Private key, message, entropy or empty | Signature |
| SLH-DSA-XX/sigVer | Public key, message, signature | Single-byte validity flag |
+| SSHKDF/<HASH>/client | K, H, SessionID, cipher algorithm | client IV key, client encryption key, client integrity key |
+| SSHKDF/<HASH>/server | K, H, SessionID, cipher algorithm | server IV key, server encryption key, server integrity key |
¹ The iterated tests would result in excessive numbers of round trips if the module wrapper handled only basic operations. Thus some ACVP logic is pushed down for these tests so that the inner loop can be handled locally. Either read the NIST documentation ([block-ciphers](https://pages.nist.gov/ACVP/draft-celi-acvp-symmetric.html#name-monte-carlo-tests-for-block) [hashes](https://pages.nist.gov/ACVP/draft-celi-acvp-sha.html#name-monte-carlo-tests-for-sha-1)) to understand the iteration count and return values or, probably more fruitfully, see how these functions are handled in the `modulewrapper` directory.
diff --git a/util/fipstools/acvp/acvptool/subprocess/ssh.go b/util/fipstools/acvp/acvptool/subprocess/ssh.go
new file mode 100644
index 0000000..1a9df28
--- /dev/null
+++ b/util/fipstools/acvp/acvptool/subprocess/ssh.go
@@ -0,0 +1,127 @@
+package subprocess
+
+import (
+ "encoding/hex"
+ "encoding/json"
+ "fmt"
+)
+
+// The following structures reflect the JSON of KDF SSH tests. See
+// https://pages.nist.gov/ACVP/draft-celi-acvp-kdf-ssh.html#name-test-vectors
+
+type sshTestVectorSet struct {
+ Algorithm string `json:"algorithm"`
+ Mode string `json:"mode"`
+ Groups []sshTestGroup `json:"testGroups"`
+}
+
+type sshTestGroup struct {
+ ID uint64 `json:"tgId"`
+ TestType string `json:"testType"`
+ HashAlg string `json:"hashAlg"`
+ Cipher string `json:"cipher"`
+ Tests []struct {
+ ID uint64 `json:"tcId"`
+ KHex string `json:"k"`
+ HHex string `json:"h"`
+ SessionIDHex string `json:"sessionID"`
+ } `json:"tests"`
+}
+
+type sshTestGroupResponse struct {
+ ID uint64 `json:"tgId"`
+ Tests []sshTestResponse `json:"tests"`
+}
+
+type sshTestResponse struct {
+ ID uint64 `json:"tcId"`
+ InitialIvClientHex string `json:"initialIvClient"`
+ InitialIvServerHex string `json:"initialIvServer"`
+ EncryptionKeyClientHex string `json:"encryptionKeyClient"`
+ EncryptionKeyServerHex string `json:"encryptionKeyServer"`
+ IntegrityKeyClientHex string `json:"integrityKeyClient"`
+ IntegrityKeyServerHex string `json:"integrityKeyServer"`
+}
+
+type ssh struct {
+}
+
+func (s *ssh) Process(vectorSet []byte, m Transactable) (any, error) {
+ var parsed sshTestVectorSet
+ if err := json.Unmarshal(vectorSet, &parsed); err != nil {
+ return nil, err
+ }
+
+ if parsed.Algorithm != "kdf-components" {
+ return nil, fmt.Errorf("unexpected algorithm: %q", parsed.Algorithm)
+ }
+ if parsed.Mode != "ssh" {
+ return nil, fmt.Errorf("unexpected mode: %q", parsed.Mode)
+ }
+
+ var ret []sshTestGroupResponse
+ for _, group := range parsed.Groups {
+ group := group
+
+ // Only the AFT test type is specified for SSH:
+ // https://pages.nist.gov/ACVP/draft-celi-acvp-kdf-ssh.html#name-test-types
+ if group.TestType != "AFT" {
+ return nil, fmt.Errorf("test group %d had unexpected test type: %q", group.ID, group.TestType)
+ }
+
+ response := sshTestGroupResponse{
+ ID: group.ID,
+ }
+
+ for _, test := range group.Tests {
+ test := test
+
+ resp := sshTestResponse{
+ ID: test.ID,
+ }
+
+ k, err := hex.DecodeString(test.KHex)
+ if err != nil {
+ return nil, fmt.Errorf("failed to decode K hex in test case %d/%d: %s", group.ID, test.ID, err)
+ }
+ h, err := hex.DecodeString(test.HHex)
+ if err != nil {
+ return nil, fmt.Errorf("failed to decode H hex in test case %d/%d: %s", group.ID, test.ID, err)
+ }
+ sessionID, err := hex.DecodeString(test.SessionIDHex)
+ if err != nil {
+ return nil, fmt.Errorf("failed to decode session ID hex in test case %d/%d: %s", group.ID, test.ID, err)
+ }
+
+ cmd := fmt.Sprintf("SSHKDF/%s/client", group.HashAlg)
+ m.TransactAsync(cmd, 3, [][]byte{k, h, sessionID, []byte(group.Cipher)}, func(result [][]byte) error {
+ resp.InitialIvClientHex = hex.EncodeToString(result[0])
+ resp.EncryptionKeyClientHex = hex.EncodeToString(result[1])
+ resp.IntegrityKeyClientHex = hex.EncodeToString(result[2])
+ return nil
+ })
+
+ cmd = fmt.Sprintf("SSHKDF/%s/server", group.HashAlg)
+ m.TransactAsync(cmd, 3, [][]byte{k, h, sessionID, []byte(group.Cipher)}, func(result [][]byte) error {
+ resp.InitialIvServerHex = hex.EncodeToString(result[0])
+ resp.EncryptionKeyServerHex = hex.EncodeToString(result[1])
+ resp.IntegrityKeyServerHex = hex.EncodeToString(result[2])
+ return nil
+ })
+
+ m.Barrier(func() {
+ response.Tests = append(response.Tests, resp)
+ })
+ }
+
+ m.Barrier(func() {
+ ret = append(ret, response)
+ })
+ }
+
+ if err := m.Flush(); err != nil {
+ return nil, err
+ }
+
+ return ret, nil
+}
diff --git a/util/fipstools/acvp/acvptool/subprocess/subprocess.go b/util/fipstools/acvp/acvptool/subprocess/subprocess.go
index 0b8b527..13f9e17 100644
--- a/util/fipstools/acvp/acvptool/subprocess/subprocess.go
+++ b/util/fipstools/acvp/acvptool/subprocess/subprocess.go
@@ -147,6 +147,7 @@
"ML-DSA": &mldsa{},
"ML-KEM": &mlkem{},
"SLH-DSA": &slhdsa{},
+ "kdf-components": &ssh{},
}
m.primitives["ECDSA"] = &ecdsa{"ECDSA", map[string]bool{"P-224": true, "P-256": true, "P-384": true, "P-521": true}, m.primitives}
m.primitives["DetECDSA"] = &ecdsa{"DetECDSA", map[string]bool{"P-224": true, "P-256": true, "P-384": true, "P-521": true}, m.primitives}