acvp: add support for finite-field Diffie–Hellman.

This involves adding a new function |DH_compute_key_hashed| that
combines the FFDH with the output hashing inside the FIPS module. This
new function uses the padded FFDH output, as newly specified in SP
800-56Ar3.

Change-Id: Iafcb7e276f16d39bf7d25d3b2f163b5cd6f67883
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/44504
Reviewed-by: David Benjamin <davidben@google.com>
diff --git a/crypto/fipsmodule/dh/dh.c b/crypto/fipsmodule/dh/dh.c
index 8194caa..6bc1e53 100644
--- a/crypto/fipsmodule/dh/dh.c
+++ b/crypto/fipsmodule/dh/dh.c
@@ -60,6 +60,7 @@
 
 #include <openssl/bn.h>
 #include <openssl/err.h>
+#include <openssl/digest.h>
 #include <openssl/mem.h>
 #include <openssl/thread.h>
 
@@ -384,56 +385,118 @@
   return ok;
 }
 
-int DH_compute_key(unsigned char *out, const BIGNUM *peers_key, DH *dh) {
-  BN_CTX *ctx = NULL;
-  BIGNUM *shared_key;
-  int ret = -1;
-  int check_result;
-
+static int dh_compute_key(DH *dh, BIGNUM *out_shared_key,
+                          const BIGNUM *peers_key, BN_CTX *ctx) {
   if (BN_num_bits(dh->p) > OPENSSL_DH_MAX_MODULUS_BITS) {
     OPENSSL_PUT_ERROR(DH, DH_R_MODULUS_TOO_LARGE);
-    goto err;
-  }
-
-  ctx = BN_CTX_new();
-  if (ctx == NULL) {
-    goto err;
-  }
-  BN_CTX_start(ctx);
-  shared_key = BN_CTX_get(ctx);
-  if (shared_key == NULL) {
-    goto err;
+    return 0;
   }
 
   if (dh->priv_key == NULL) {
     OPENSSL_PUT_ERROR(DH, DH_R_NO_PRIVATE_VALUE);
-    goto err;
+    return 0;
   }
 
-  if (!BN_MONT_CTX_set_locked(&dh->method_mont_p, &dh->method_mont_p_lock,
+  int check_result;
+  if (!DH_check_pub_key(dh, peers_key, &check_result) || check_result) {
+    OPENSSL_PUT_ERROR(DH, DH_R_INVALID_PUBKEY);
+    return 0;
+  }
+
+  int ret = 0;
+  BN_CTX_start(ctx);
+  BIGNUM *p_minus_1 = BN_CTX_get(ctx);
+
+  if (!p_minus_1 ||
+      !BN_MONT_CTX_set_locked(&dh->method_mont_p, &dh->method_mont_p_lock,
                               dh->p, ctx)) {
     goto err;
   }
 
-  if (!DH_check_pub_key(dh, peers_key, &check_result) || check_result) {
-    OPENSSL_PUT_ERROR(DH, DH_R_INVALID_PUBKEY);
-    goto err;
-  }
-
-  if (!BN_mod_exp_mont_consttime(shared_key, peers_key, dh->priv_key, dh->p,
-                                 ctx, dh->method_mont_p)) {
+  if (!BN_mod_exp_mont_consttime(out_shared_key, peers_key, dh->priv_key, dh->p,
+                                 ctx, dh->method_mont_p) ||
+      !BN_copy(p_minus_1, dh->p) ||
+      !BN_sub_word(p_minus_1, 1)) {
     OPENSSL_PUT_ERROR(DH, ERR_R_BN_LIB);
     goto err;
   }
 
-  ret = BN_bn2bin(shared_key, out);
-
-err:
-  if (ctx != NULL) {
-    BN_CTX_end(ctx);
-    BN_CTX_free(ctx);
+  // This performs the check required by SP 800-56Ar3 section 5.7.1.1 step two.
+  if (BN_cmp_word(out_shared_key, 1) <= 0 ||
+      BN_cmp(out_shared_key, p_minus_1) == 0) {
+    OPENSSL_PUT_ERROR(DH, DH_R_INVALID_PUBKEY);
+    goto err;
   }
 
+  ret = 1;
+
+ err:
+  BN_CTX_end(ctx);
+  return ret;
+}
+
+int DH_compute_key(unsigned char *out, const BIGNUM *peers_key, DH *dh) {
+  BN_CTX *ctx = BN_CTX_new();
+  if (ctx == NULL) {
+    return -1;
+  }
+  BN_CTX_start(ctx);
+
+  int ret = -1;
+  BIGNUM *shared_key = BN_CTX_get(ctx);
+  if (shared_key && dh_compute_key(dh, shared_key, peers_key, ctx)) {
+    ret = BN_bn2bin(shared_key, out);
+  }
+
+  BN_CTX_end(ctx);
+  BN_CTX_free(ctx);
+  return ret;
+}
+
+int DH_compute_key_hashed(DH *dh, uint8_t *out, size_t *out_len,
+                          size_t max_out_len, const BIGNUM *peers_key,
+                          const EVP_MD *digest) {
+  *out_len = (size_t)-1;
+
+  const size_t digest_len = EVP_MD_size(digest);
+  if (digest_len > max_out_len) {
+    return 0;
+  }
+
+  BN_CTX *ctx = BN_CTX_new();
+  if (ctx == NULL) {
+    return 0;
+  }
+  BN_CTX_start(ctx);
+
+  int ret = 0;
+  BIGNUM *shared_key = BN_CTX_get(ctx);
+  const size_t p_len = BN_num_bytes(dh->p);
+  uint8_t *shared_bytes = OPENSSL_malloc(p_len);
+  unsigned out_len_unsigned;
+  if (!shared_key ||
+      !shared_bytes ||
+      !dh_compute_key(dh, shared_key, peers_key, ctx) ||
+      // |DH_compute_key| doesn't pad the output. SP 800-56A is ambiguous about
+      // whether the output should be padded prior to revision three. But
+      // revision three, section C.1, awkwardly specifies padding to the length
+      // of p.
+      //
+      // Also, padded output avoids side-channels, so is always strongly
+      // advisable.
+      !BN_bn2bin_padded(shared_bytes, p_len, shared_key) ||
+      !EVP_Digest(shared_bytes, p_len, out, &out_len_unsigned, digest, NULL) ||
+      out_len_unsigned != digest_len) {
+    goto err;
+  }
+
+  *out_len = digest_len;
+  ret = 1;
+
+ err:
+  BN_CTX_end(ctx);
+  BN_CTX_free(ctx);
+  OPENSSL_free(shared_bytes);
   return ret;
 }
 
diff --git a/include/openssl/dh.h b/include/openssl/dh.h
index ef3c481..52d831d 100644
--- a/include/openssl/dh.h
+++ b/include/openssl/dh.h
@@ -178,6 +178,19 @@
 OPENSSL_EXPORT int DH_compute_key(uint8_t *out, const BIGNUM *peers_key,
                                   DH *dh);
 
+// DH_compute_key_hashed calculates the shared key between |dh| and |peers_key|
+// and hashes it with the given |digest|. If the hash output is less than
+// |max_out_len| bytes then it writes the hash output to |out| and sets
+// |*out_len| to the number of bytes written. Otherwise it signals an error. It
+// returns one on success or zero on error.
+//
+// NOTE: this follows the usual BoringSSL return-value convention, but that's
+// different from |DH_compute_key|, above.
+OPENSSL_EXPORT int DH_compute_key_hashed(DH *dh, uint8_t *out, size_t *out_len,
+                                         size_t max_out_len,
+                                         const BIGNUM *peers_key,
+                                         const EVP_MD *digest);
+
 
 // Utility functions.
 
diff --git a/util/fipstools/acvp/acvptool/subprocess/kasdh.go b/util/fipstools/acvp/acvptool/subprocess/kasdh.go
new file mode 100644
index 0000000..8251b7f
--- /dev/null
+++ b/util/fipstools/acvp/acvptool/subprocess/kasdh.go
@@ -0,0 +1,179 @@
+// Copyright (c) 2020, 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"
+)
+
+type kasDHVectorSet struct {
+	Groups []kasDHTestGroup `json:"testGroups"`
+}
+
+type kasDHTestGroup struct {
+	ID     uint64      `json:"tgId"`
+	Type   string      `json:"testType"`
+	Role   string      `json:"kasRole"`
+	Mode   string      `json:"kasMode"`
+	Hash   string      `json:"hashAlg"`
+	Scheme string      `json:"scheme"`
+	PHex   string      `json:"p"`
+	QHex   string      `json:"q"`
+	GHex   string      `json:"g"`
+	Tests  []kasDHTest `json:"tests"`
+}
+
+type kasDHTest struct {
+	ID            uint64 `json:"tcId"`
+	PeerPublicHex string `json:"ephemeralPublicServer"`
+	PrivateKeyHex string `json:"ephemeralPrivateIut"`
+	PublicKeyHex  string `json:"ephemeralPublicIut"`
+	ResultHex     string `json:"hashZIut"`
+}
+
+type kasDHTestGroupResponse struct {
+	ID    uint64              `json:"tgId"`
+	Tests []kasDHTestResponse `json:"tests"`
+}
+
+type kasDHTestResponse struct {
+	ID             uint64 `json:"tcId"`
+	LocalPublicHex string `json:"ephemeralPublicIut,omitempty"`
+	ResultHex      string `json:"hashZIut,omitempty"`
+	Passed         *bool  `json:"testPassed,omitempty"`
+}
+
+type kasDH struct{}
+
+func (k *kasDH) Process(vectorSet []byte, m Transactable) (interface{}, error) {
+	var parsed kasDHVectorSet
+	if err := json.Unmarshal(vectorSet, &parsed); err != nil {
+		return nil, err
+	}
+
+	// See https://usnistgov.github.io/ACVP/draft-fussell-acvp-kas-ffc.html
+	var ret []kasDHTestGroupResponse
+	for _, group := range parsed.Groups {
+		response := kasDHTestGroupResponse{
+			ID: group.ID,
+		}
+
+		var privateKeyGiven bool
+		switch group.Type {
+		case "AFT":
+			privateKeyGiven = false
+		case "VAL":
+			privateKeyGiven = true
+		default:
+			return nil, fmt.Errorf("unknown test type %q", group.Type)
+		}
+
+		switch group.Hash {
+		case "SHA2-224", "SHA2-256", "SHA2-384", "SHA2-512":
+			break
+		default:
+			return nil, fmt.Errorf("unknown hash function %q", group.Hash)
+		}
+
+		switch group.Role {
+		case "initiator", "responder":
+			break
+		default:
+			return nil, fmt.Errorf("unknown role %q", group.Role)
+		}
+
+		if group.Scheme != "dhEphem" {
+			return nil, fmt.Errorf("unknown scheme %q", group.Scheme)
+		}
+
+		p, err := hex.DecodeString(group.PHex)
+		if err != nil {
+			return nil, err
+		}
+
+		q, err := hex.DecodeString(group.QHex)
+		if err != nil {
+			return nil, err
+		}
+
+		g, err := hex.DecodeString(group.GHex)
+		if err != nil {
+			return nil, err
+		}
+
+		method := "FFDH/" + group.Hash
+
+		for _, test := range group.Tests {
+			if len(test.PeerPublicHex) == 0 {
+				return nil, fmt.Errorf("%d/%d is missing peer's key", group.ID, test.ID)
+			}
+
+			peerPublic, err := hex.DecodeString(test.PeerPublicHex)
+			if err != nil {
+				return nil, err
+			}
+
+			if (len(test.PrivateKeyHex) != 0) != privateKeyGiven {
+				return nil, fmt.Errorf("%d/%d incorrect private key presence", group.ID, test.ID)
+			}
+
+			if privateKeyGiven {
+				privateKey, err := hex.DecodeString(test.PrivateKeyHex)
+				if err != nil {
+					return nil, err
+				}
+
+				publicKey, err := hex.DecodeString(test.PublicKeyHex)
+				if err != nil {
+					return nil, err
+				}
+
+				expectedOutput, err := hex.DecodeString(test.ResultHex)
+				if err != nil {
+					return nil, err
+				}
+
+				result, err := m.Transact(method, 2, p, q, g, peerPublic, privateKey, publicKey)
+				if err != nil {
+					return nil, err
+				}
+
+				ok := bytes.Equal(result[1], expectedOutput)
+				response.Tests = append(response.Tests, kasDHTestResponse{
+					ID:     test.ID,
+					Passed: &ok,
+				})
+			} else {
+				result, err := m.Transact(method, 2, p, q, g, peerPublic, nil, nil)
+				if err != nil {
+					return nil, err
+				}
+
+				response.Tests = append(response.Tests, kasDHTestResponse{
+					ID:             test.ID,
+					LocalPublicHex: hex.EncodeToString(result[0]),
+					ResultHex:      hex.EncodeToString(result[1]),
+				})
+			}
+		}
+
+		ret = append(ret, response)
+	}
+
+	return ret, nil
+}
diff --git a/util/fipstools/acvp/acvptool/subprocess/subprocess.go b/util/fipstools/acvp/acvptool/subprocess/subprocess.go
index e3f11cc..c0d48b7 100644
--- a/util/fipstools/acvp/acvptool/subprocess/subprocess.go
+++ b/util/fipstools/acvp/acvptool/subprocess/subprocess.go
@@ -98,6 +98,7 @@
 		"RSA":            &rsa{},
 		"kdf-components": &tlsKDF{},
 		"KAS-ECC-SSC":    &kas{},
+		"KAS-FFC":        &kasDH{},
 	}
 	m.primitives["ECDSA"] = &ecdsa{"ECDSA", map[string]bool{"P-224": true, "P-256": true, "P-384": true, "P-521": true}, m.primitives}
 
diff --git a/util/fipstools/acvp/acvptool/subprocess/tlskdf.go b/util/fipstools/acvp/acvptool/subprocess/tlskdf.go
index 96a0a5c..4a8f7ee 100644
--- a/util/fipstools/acvp/acvptool/subprocess/tlskdf.go
+++ b/util/fipstools/acvp/acvptool/subprocess/tlskdf.go
@@ -35,8 +35,8 @@
 }
 
 type tlsKDFTest struct {
-	ID                   uint64 `json:"tcId"`
-	PMSHex               string `json:"preMasterSecret"`
+	ID     uint64 `json:"tcId"`
+	PMSHex string `json:"preMasterSecret"`
 	// ClientHelloRandomHex and ServerHelloRandomHex are used for deriving the
 	// master secret. ClientRandomHex and ServerRandomHex are used for deriving the
 	// key block. Having different values for these is not possible in a TLS
diff --git a/util/fipstools/acvp/modulewrapper/modulewrapper.cc b/util/fipstools/acvp/modulewrapper/modulewrapper.cc
index 37db281..2c9fd36 100644
--- a/util/fipstools/acvp/modulewrapper/modulewrapper.cc
+++ b/util/fipstools/acvp/modulewrapper/modulewrapper.cc
@@ -29,6 +29,7 @@
 #include <openssl/bn.h>
 #include <openssl/cipher.h>
 #include <openssl/cmac.h>
+#include <openssl/dh.h>
 #include <openssl/digest.h>
 #include <openssl/ec.h>
 #include <openssl/ec_key.h>
@@ -737,6 +738,35 @@
           "P-384",
           "P-521"
         ]
+      },
+      {
+        "algorithm": "KAS-FFC",
+        "revision": "1.0",
+        "mode": "Component",
+        "function": [
+          "keyPairGen"
+        ],
+        "scheme": {
+          "dhEphem": {
+            "kasRole": [
+              "initiator"
+            ],
+            "noKdfNoKc": {
+              "parameterSet": {
+                "fb": {
+                  "hashAlg": [
+                    "SHA2-256"
+                  ]
+                },
+                "fc": {
+                  "hashAlg": [
+                    "SHA2-256"
+                  ]
+                }
+              }
+            }
+          }
+        }
       }
     ])";
   return WriteReply(
@@ -1678,6 +1708,55 @@
                     output);
 }
 
+template<const EVP_MD* (*HashFunc)()>
+static bool FFDH(const Span<const uint8_t> args[]) {
+  bssl::UniquePtr<BIGNUM> p(BytesToBIGNUM(args[0]));
+  bssl::UniquePtr<BIGNUM> q(BytesToBIGNUM(args[1]));
+  bssl::UniquePtr<BIGNUM> g(BytesToBIGNUM(args[2]));
+  bssl::UniquePtr<BIGNUM> their_pub(BytesToBIGNUM(args[3]));
+  const Span<const uint8_t> private_key_span = args[4];
+  const Span<const uint8_t> public_key_span = args[5];
+
+  bssl::UniquePtr<DH> dh(DH_new());
+  if (!DH_set0_pqg(dh.get(), p.get(), q.get(), g.get())) {
+    fprintf(stderr, "DH_set0_pqg failed.\n");
+    return 0;
+  }
+
+  // DH_set0_pqg took ownership of these values.
+  p.release();
+  q.release();
+  g.release();
+
+  if (!private_key_span.empty()) {
+    bssl::UniquePtr<BIGNUM> private_key(BytesToBIGNUM(private_key_span));
+    bssl::UniquePtr<BIGNUM> public_key(BytesToBIGNUM(public_key_span));
+
+    if (!DH_set0_key(dh.get(), public_key.get(), private_key.get())) {
+      fprintf(stderr, "DH_set0_key failed.\n");
+      return 0;
+    }
+
+    // DH_set0_key took ownership of these values.
+    public_key.release();
+    private_key.release();
+  } else if (!DH_generate_key(dh.get())) {
+    fprintf(stderr, "DH_generate_key failed.\n");
+    return false;
+  }
+
+  uint8_t digest[EVP_MAX_MD_SIZE];
+  size_t digest_len;
+  if (!DH_compute_key_hashed(dh.get(), digest, &digest_len, sizeof(digest),
+                             their_pub.get(), HashFunc())) {
+    fprintf(stderr, "DH_compute_key_hashed failed.\n");
+    return false;
+  }
+
+  return WriteReply(STDOUT_FILENO, BIGNUMBytes(DH_get0_pub_key(dh.get())),
+                    Span<const uint8_t>(digest, digest_len));
+}
+
 static constexpr struct {
   const char name[kMaxNameLength + 1];
   uint8_t expected_args;
@@ -1748,6 +1827,7 @@
     {"ECDH/P-256", 3, ECDH<NID_X9_62_prime256v1>},
     {"ECDH/P-384", 3, ECDH<NID_secp384r1>},
     {"ECDH/P-521", 3, ECDH<NID_secp521r1>},
+    {"FFDH/SHA2-256", 6, FFDH<EVP_sha256>},
 };
 
 int main() {