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>
chromium-5359
Adam Langley 4 years ago committed by Adam Langley
parent ce7f08827d
commit 28cab640d1
  1. 119
      crypto/fipsmodule/dh/dh.c
  2. 13
      include/openssl/dh.h
  3. 179
      util/fipstools/acvp/acvptool/subprocess/kasdh.go
  4. 1
      util/fipstools/acvp/acvptool/subprocess/subprocess.go
  5. 4
      util/fipstools/acvp/acvptool/subprocess/tlskdf.go
  6. 80
      util/fipstools/acvp/modulewrapper/modulewrapper.cc

@ -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 @@ err:
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;
return 0;
}
ctx = BN_CTX_new();
if (ctx == NULL) {
goto err;
if (dh->priv_key == NULL) {
OPENSSL_PUT_ERROR(DH, DH_R_NO_PRIVATE_VALUE);
return 0;
}
BN_CTX_start(ctx);
shared_key = BN_CTX_get(ctx);
if (shared_key == NULL) {
goto err;
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;
}
if (dh->priv_key == NULL) {
OPENSSL_PUT_ERROR(DH, DH_R_NO_PRIVATE_VALUE);
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 (!BN_MONT_CTX_set_locked(&dh->method_mont_p, &dh->method_mont_p_lock,
dh->p, ctx)) {
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;
}
if (!DH_check_pub_key(dh, peers_key, &check_result) || check_result) {
// 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;
}
if (!BN_mod_exp_mont_consttime(shared_key, peers_key, dh->priv_key, dh->p,
ctx, dh->method_mont_p)) {
OPENSSL_PUT_ERROR(DH, ERR_R_BN_LIB);
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);
ret = BN_bn2bin(shared_key, out);
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);
}
err:
if (ctx != NULL) {
BN_CTX_end(ctx);
BN_CTX_free(ctx);
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;
}

@ -178,6 +178,19 @@ OPENSSL_EXPORT int DH_generate_key(DH *dh);
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.

@ -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
}

@ -98,6 +98,7 @@ func NewWithIO(cmd *exec.Cmd, in io.WriteCloser, out io.ReadCloser) *Subprocess
"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}

@ -35,8 +35,8 @@ type tlsKDFTestGroup struct {
}
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

@ -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 @@ static bool GetConfig(const Span<const uint8_t> args[]) {
"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 @@ static bool ECDH(const Span<const uint8_t> args[]) {
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 @@ static constexpr struct {
{"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() {

Loading…
Cancel
Save