From: Francis Dupont Date: Wed, 3 Jun 2015 23:27:30 +0000 (+0200) Subject: [sedhcpv6] Some improvements/bug fixes X-Git-Url: http://git.ipfire.org/gitweb/gitweb.cgi?a=commitdiff_plain;h=f5a1feb763a0c8671a1985a7564534e11d67e543;p=thirdparty%2Fkea.git [sedhcpv6] Some improvements/bug fixes --- diff --git a/doc/guide/dhcp6-srv.xml b/doc/guide/dhcp6-srv.xml index 34124d5415..196e6da573 100644 --- a/doc/guide/dhcp6-srv.xml +++ b/doc/guide/dhcp6-srv.xml @@ -2335,17 +2335,15 @@ should include options from the isc option space: A client is configured in a host reservation with the filename of its public key or certificate (the two keywords can be used indifferently: the key type always follows the secure DHCPv6 - option type). The two + option type). The two parameters are: public-key specifies - the name of a file containing the public key. + the name of a file containing the public key in PEM format. certificate specifies - the name of a file containing the certificate. + the name of a file containing the certificate in PEM format. - The format of the file content must follow the checking operation, - e.g., when it is a bit-to-bit compare it must be encoded in DER. diff --git a/src/bin/dhcp6/dhcp6_srv.cc b/src/bin/dhcp6/dhcp6_srv.cc index 91327fd722..c7440c7b7a 100644 --- a/src/bin/dhcp6/dhcp6_srv.cc +++ b/src/bin/dhcp6/dhcp6_srv.cc @@ -3262,6 +3262,7 @@ void Dhcpv6Srv::finalizeSignature(Pkt6Ptr& tbs) { try { key->update(tbs->rawBegin(), tbs->rawEnd() - tbs->rawBegin()); key->sign(start + sig_off, sig_len, BASIC); + key->clear(); } catch (const Exception& ex) { ostringstream sigmsg("signature sign failed: "); sigmsg << ex.what(); @@ -3271,6 +3272,10 @@ void Dhcpv6Srv::finalizeSignature(Pkt6Ptr& tbs) { LOG_ERROR(dhcp6_logger, SEDHCP6_SIGNATURE_FINALIZE_FAIL) .arg("signature sign failed?!"); } + vector dump(sig_len); + memcpy(&dump[0], start + sig_off, sig_len); + LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL_DATA, SEDHCP6_SIGNATURE_DUMP) + .arg(encode::encodeHex(dump)); } }; diff --git a/src/bin/dhcp6/sedhcp6_messages.mes b/src/bin/dhcp6/sedhcp6_messages.mes index 9c0ae14140..45abb355be 100644 --- a/src/bin/dhcp6/sedhcp6_messages.mes +++ b/src/bin/dhcp6/sedhcp6_messages.mes @@ -27,6 +27,9 @@ This error message indicates that the signature check has failed. This is a "should not happen" condition which reflects an internal error. +% SEDHCP6_SIGNATURE_DUMP final signature: %1 +This debug message dumps the final content of the signature. + % SEDHCP6_SIGNATURE_FINALIZE_FAIL signature finalize error: %1 This error message indicates that the signature finalize has failed. This is a "should not happen" condition which reflects an internal diff --git a/src/lib/cryptolink/botan_asym.cc b/src/lib/cryptolink/botan_asym.cc index c08e1b05e1..c515d66e0f 100644 --- a/src/lib/cryptolink/botan_asym.cc +++ b/src/lib/cryptolink/botan_asym.cc @@ -863,6 +863,31 @@ public: } } + /// \brief Clear the crypto state and go back to the initial state + /// (must be called before reusing an Asym object) + void clear() { + std::string hash = btn::getHashAlgorithmName(hash_); + if (hash.compare("Unknown") == 0) { + isc_throw(UnsupportedAlgorithm, + "Unknown hash algorithm: " << + static_cast(hash_)); + } + std::string emsa = "EMSA3(" + hash + ")"; + if (kind_ == PRIVATE) { + try { + signer_.reset(new Botan::PK_Signer(*priv_, emsa)); + } catch (const std::exception& exc) { + isc_throw(BadKey, "PK_Signer: " << exc.what()); + } + } else { + try { + verifier_.reset(new Botan::PK_Verifier(*pub_, emsa)); + } catch (const std::exception& exc) { + isc_throw(BadKey, "PK_Verifier: " << exc.what()); + } + } + } + /// @brief Export the key value (binary) /// /// See @ref isc::cryptolink::Asym::exportkey() for details @@ -1292,6 +1317,11 @@ Asym::verify(const void* sig, size_t len, const AsymFormat sig_format) { return (impl_->verify(sig, len, sig_format)); } +void +Asym::clear() { + impl_->clear(); +} + std::vector Asym::exportkey(const AsymKeyKind key_kind, const AsymFormat key_format) const { diff --git a/src/lib/cryptolink/botan_hash.cc b/src/lib/cryptolink/botan_hash.cc index 9451f0a1f5..7f24d8b522 100644 --- a/src/lib/cryptolink/botan_hash.cc +++ b/src/lib/cryptolink/botan_hash.cc @@ -180,7 +180,7 @@ Hash::~Hash() { } HashAlgorithm -Hash:getHashAlgorithm() const { +Hash::getHashAlgorithm() const { return (impl_->getHashAlgorithm()); } diff --git a/src/lib/cryptolink/botan_hmac.cc b/src/lib/cryptolink/botan_hmac.cc index 4b19202ae0..14539c97e7 100644 --- a/src/lib/cryptolink/botan_hmac.cc +++ b/src/lib/cryptolink/botan_hmac.cc @@ -219,7 +219,7 @@ HMAC::~HMAC() { } HashAlgorithm -HMAC:getHashAlgorithm() const { +HMAC::getHashAlgorithm() const { return (impl_->getHashAlgorithm()); } diff --git a/src/lib/cryptolink/crypto_asym.h b/src/lib/cryptolink/crypto_asym.h index 98fd6ad1e5..2b1ab050cc 100644 --- a/src/lib/cryptolink/crypto_asym.h +++ b/src/lib/cryptolink/crypto_asym.h @@ -188,6 +188,10 @@ public: /// called multiple times with different signatures. bool verify(const void* sig, size_t len, const AsymFormat sig_format); + /// \brief Clear the crypto state and go back to the initial state + /// (must be called before reusing an Asym object) + void clear(); + /// \brief Export the key value /// /// The result will be returned as a std::vector diff --git a/src/lib/cryptolink/openssl_asym.cc b/src/lib/cryptolink/openssl_asym.cc index 433d55029d..957aa79bb2 100644 --- a/src/lib/cryptolink/openssl_asym.cc +++ b/src/lib/cryptolink/openssl_asym.cc @@ -979,6 +979,29 @@ public: } } + /// @brief Clear the crypto state and go back to the initial state + /// (must be called before reusing an Asym object) + void clear() { + if (mdctx_) { + EVP_MD_CTX_cleanup(mdctx_.get()); + } else { + mdctx_.reset(new EVP_MD_CTX); + } + EVP_MD_CTX_init(mdctx_.get()); + const EVP_MD* md = ossl::getHashAlgorithm(hash_); + if (md == 0) { + isc_throw(UnsupportedAlgorithm, + "Unknown hash algorithm: " << + static_cast(hash_)); + } + if (!EVP_DigestInit_ex(mdctx_.get(), md, NULL)) { + EVP_MD_CTX_cleanup(mdctx_.get()); + EVP_PKEY_free(pkey_); + pkey_ =NULL; + isc_throw(LibraryError, "EVP_DigestInit_ex"); + } + } + /// @brief Export the key value (binary) /// /// See @ref isc::cryptolink::Asym::exportkey() for details @@ -1521,6 +1544,11 @@ Asym::verify(const void* sig, size_t len, const AsymFormat sig_format) { return (impl_->verify(sig, len, sig_format)); } +void +Asym::clear() { + impl_->clear(); +} + std::vector Asym::exportkey(const AsymKeyKind key_kind, const AsymFormat key_format) const { diff --git a/src/lib/cryptolink/tests/asym_unittests.cc b/src/lib/cryptolink/tests/asym_unittests.cc index 3589da154a..c2658e4d09 100644 --- a/src/lib/cryptolink/tests/asym_unittests.cc +++ b/src/lib/cryptolink/tests/asym_unittests.cc @@ -1,4 +1,4 @@ -// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2014, 2015 Internet Systems Consortium, Inc. ("ISC") // // Permission to use, copy, modify, and/or distribute this software for any // purpose with or without fee is hereby granted, provided that the above @@ -818,3 +818,83 @@ TEST(AsymTest, CERTIFICATE) { deleteAsym); EXPECT_FALSE(bad_bin->validate()); } + +// +// Multiple signatures +// +TEST(AsymTest, doubleSign) { + std::string data = "Kea provides DHCPv4 and DHCPv6 servers"; + OutputBuffer data_buf(data.size()); + data_buf.writeData(data.c_str(), data.size()); + CryptoLink& crypto = CryptoLink::getCryptoLink(); + + // Sign it + boost::shared_ptr rsa_sign(crypto.createAsym(privfile, "1234", + RSA_, SHA1, + PRIVATE, ASN1), + deleteAsym); + ASSERT_TRUE(rsa_sign); + + OutputBuffer sig1(1); + size_t sig1_len = rsa_sign->getSignatureLength(BASIC); + EXPECT_EQ(128, sig1_len); + rsa_sign->update(data_buf.getData(), data_buf.getLength()); + rsa_sign->sign(sig1, sig1_len, BASIC); + ASSERT_EQ(sig1_len, sig1.getLength()); + + // Clear state + rsa_sign->clear(); + + // Sign it again + OutputBuffer sig2(1); + size_t sig2_len = rsa_sign->getSignatureLength(BASIC); + EXPECT_EQ(128, sig2_len); + rsa_sign->update(data_buf.getData(), data_buf.getLength()); + rsa_sign->sign(sig2, sig2_len, BASIC); + EXPECT_EQ(sig2_len, sig2.getLength()); + + // Compare + ASSERT_EQ(sig1_len, sig2_len); + EXPECT_TRUE(std::memcmp(sig1.getData(), sig2.getData(), sig1_len) == 0); +} + +// +// Multiple verifies +// +TEST(AsymTest, doubleVerify) { + std::string data = "Kea provides DHCPv4 and DHCPv6 servers"; + OutputBuffer data_buf(data.size()); + data_buf.writeData(data.c_str(), data.size()); + CryptoLink& crypto = CryptoLink::getCryptoLink(); + + // Sign it + boost::shared_ptr rsa_sign(crypto.createAsym(privfile, "1234", + RSA_, SHA1, + PRIVATE, ASN1), + deleteAsym); + ASSERT_TRUE(rsa_sign); + + OutputBuffer sig(1); + size_t sig_len = rsa_sign->getSignatureLength(BASIC); + EXPECT_EQ(128, sig_len); + rsa_sign->update(data_buf.getData(), data_buf.getLength()); + rsa_sign->sign(sig, sig_len, BASIC); + EXPECT_EQ(sig_len, sig.getLength()); + + // Verify + boost::shared_ptr rsa_verify(crypto.createAsym(pubfile, "", + RSA_, SHA1, + PUBLIC, ASN1), + deleteAsym); + ASSERT_TRUE(rsa_verify); + + rsa_verify->update(data_buf.getData(), data_buf.getLength()); + EXPECT_TRUE(rsa_verify->verify(sig.getData(), sig.getLength(), BASIC)); + + // Clear state + rsa_verify->clear(); + + // Verify again + rsa_verify->update(data_buf.getData(), data_buf.getLength()); + EXPECT_TRUE(rsa_verify->verify(sig.getData(), sig.getLength(), BASIC)); +} diff --git a/src/lib/dhcp/option_custom.cc b/src/lib/dhcp/option_custom.cc index 971998fdfd..f11777782a 100644 --- a/src/lib/dhcp/option_custom.cc +++ b/src/lib/dhcp/option_custom.cc @@ -327,7 +327,7 @@ OptionCustom::dataFieldToText(const OptionDataType data_type, text << (readBoolean(index) ? "true" : "false"); break; case OPT_INT8_TYPE: - text << readInteger(index); + text << static_cast(readInteger(index)); break; case OPT_INT16_TYPE: text << readInteger(index); @@ -336,7 +336,7 @@ OptionCustom::dataFieldToText(const OptionDataType data_type, text << readInteger(index); break; case OPT_UINT8_TYPE: - text << readInteger(index); + text << static_cast(readInteger(index)); break; case OPT_UINT16_TYPE: text << readInteger(index); diff --git a/src/lib/util/ntp_utils.cc b/src/lib/util/ntp_utils.cc index 09d2d04625..0170784d81 100644 --- a/src/lib/util/ntp_utils.cc +++ b/src/lib/util/ntp_utils.cc @@ -34,6 +34,25 @@ const double FUZZ = 1.; // \brief Allowed clock drift (.01s) const double DRIFT = .01; + +// \brief Normalized unsigned number of seconds +uint64_t nuns(time_t t) { + // time_t can be a signed 32 bit integer + if (sizeof(time_t) == 4) { + // First/sign bit will be set after 20380118 + uint32_t repr = static_cast(t); + return (static_cast(repr)); + } + // or time_t is a signed 64 bit integer + return (static_cast(t)); +} + +// \brief Normalized fractional seconds +int64_t nfs(long f) { + // fractional seconds is a signed integer representing less than 1 second + return (static_cast(f)); +} + } bool Ntp::is_zero() const { @@ -51,8 +70,8 @@ Ntp::Ntp(uint64_t sec, uint16_t fraction) Ntp::Ntp(const struct timeval* tv) { - ntp_sec_ = static_cast(tv->tv_sec) + EPOCH_ADJUST; - uint64_t fcvt = (static_cast(tv->tv_usec) * 65536U) / 1000000UL; + ntp_sec_ = nuns(tv->tv_sec) + EPOCH_ADJUST; + int64_t fcvt = (nfs(static_cast(tv->tv_usec)) * 65536) / 1000000; ntp_fraction_ = static_cast(fcvt & 0xffff); } @@ -60,9 +79,9 @@ Ntp::Ntp(const ptime pt) { ptime epoch(date(1970, Jan, 1)); time_duration dur(pt - epoch); - ntp_sec_ = static_cast(dur.total_seconds()) + EPOCH_ADJUST; - uint64_t fcvt = static_cast(dur.fractional_seconds()) * 65536U; - fcvt /= time_duration::ticks_per_second(); + ntp_sec_ = nuns(static_cast(dur.total_seconds())) + EPOCH_ADJUST; + int64_t fcvt = (nfs(static_cast(dur.fractional_seconds())) * 65536U) + / time_duration::ticks_per_second(); ntp_fraction_ = static_cast(fcvt & 0xffff); } @@ -70,7 +89,7 @@ Ntp::Ntp(double secs, time_t base) { double intpart; double fracpart = std::modf(secs, &intpart); - ntp_sec_ = static_cast(intpart) + base + EPOCH_ADJUST; + ntp_sec_ = static_cast(intpart) + nuns(base) + EPOCH_ADJUST; ntp_fraction_ = static_cast(floor(fracpart * 65536.)); } @@ -113,8 +132,8 @@ std::vector Ntp::to_binary() const double Ntp::secs(time_t base) const { double ret = static_cast(ntp_sec_); - ret -= base + EPOCH_ADJUST; - ret += static_cast(ntp_fraction_) * (1./65536.); + ret -= nuns(base) + EPOCH_ADJUST; + ret += static_cast(ntp_fraction_) / 65536.; return (ret); } diff --git a/src/lib/util/ntp_utils.h b/src/lib/util/ntp_utils.h index f083e7132c..de472b1ec3 100644 --- a/src/lib/util/ntp_utils.h +++ b/src/lib/util/ntp_utils.h @@ -29,6 +29,8 @@ namespace util { /// /// \brief NTP (RFC 5905) time /// +/// The \c Ntp class implements NTP timestamps +/// /// External representation: uint64_t seconds, uint16_t fractional /// Network representation: 48+16 bit unsigned fixed point struct Ntp { @@ -54,6 +56,11 @@ struct Ntp { Ntp(const boost::posix_time::ptime pt); // \brief Conversion from based double + // + // In place of implementing full fixed point arithmetics + // \c Ntp objects are converted to small floating point values. + // The \param base (usually set to current time) helps to keep + // a good accuracy. Ntp(double secs, time_t base); // \brief Conversion from network diff --git a/src/lib/util/tests/Makefile.am b/src/lib/util/tests/Makefile.am index 9d727cb88a..b34d43b518 100644 --- a/src/lib/util/tests/Makefile.am +++ b/src/lib/util/tests/Makefile.am @@ -39,6 +39,7 @@ run_unittests_SOURCES += lru_list_unittest.cc run_unittests_SOURCES += memory_segment_local_unittest.cc run_unittests_SOURCES += memory_segment_common_unittest.h run_unittests_SOURCES += memory_segment_common_unittest.cc +run_unittests_SOURCES += ntp_utils_unittest.cc run_unittests_SOURCES += optional_value_unittest.cc run_unittests_SOURCES += pid_file_unittest.cc run_unittests_SOURCES += process_spawn_unittest.cc diff --git a/src/lib/util/tests/ntp_utils_unittest.cc b/src/lib/util/tests/ntp_utils_unittest.cc new file mode 100644 index 0000000000..b3cff3a5b8 --- /dev/null +++ b/src/lib/util/tests/ntp_utils_unittest.cc @@ -0,0 +1,178 @@ +// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC") +// +// 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 ISC DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL ISC 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. + +#include +#include + +#include + +using namespace isc; +using namespace isc::util; +using namespace std; +using namespace boost::posix_time; +using namespace boost::gregorian; + +namespace { + +const ptime epoch(date(1970, Jan, 1)); + +bool eq(Ntp ntpa, Ntp ntpb) { + return ((ntpa.ntp_sec_ == ntpb.ntp_sec_) && + (ntpa.ntp_fraction_ == ntpb.ntp_fraction_)); +} + +} + +TEST(NtpUtilsTest, zero) { + Ntp ntp; + EXPECT_TRUE(ntp.is_zero()); + ntp.ntp_fraction_ = 1; + EXPECT_TRUE(ntp.is_zero()); + ntp.ntp_sec_ = 1; + ntp.ntp_fraction_ = 0; + EXPECT_FALSE(ntp.is_zero()); +} + +TEST(NtpUtilsTest, timeval) { + struct timeval tv0; + tv0.tv_sec = 1234; + tv0.tv_usec = 5665; + Ntp ntp0(&tv0); + Ntp expected0(2208990034ULL, uint16_t(371U)); + EXPECT_TRUE(eq(ntp0, expected0)); + + ptime ptime1(date(2015, May, 1), hours(13) + minutes(13) + seconds(13)); + time_duration td1(ptime1 - epoch); + struct timeval tv1; + tv1.tv_sec = td1.total_seconds(); + tv1.tv_usec = 150; + Ntp ntp1(&tv1); + Ntp expected1(3639474793ULL, uint16_t(9U)); + EXPECT_TRUE(eq(ntp1, expected1)); + + ptime ptime2(date(2045, May, 1), hours(13) + minutes(13) + seconds(13)); + time_duration td2(ptime2 - epoch); + struct timeval tv2; + tv2.tv_sec = + static_cast(static_cast(td2.total_seconds())); + tv2.tv_usec = 150; + Ntp ntp2(&tv2); + Ntp expected2(4586245993ULL, uint16_t(9U)); + EXPECT_TRUE(eq(ntp2, expected2)); +} + +TEST(NtpUtilsTest, posixTime) { + Ntp ntp0(epoch); + Ntp expected0(2208988800ULL, uint16_t(0)); + EXPECT_TRUE(eq(ntp0, expected0)); + + ptime ptime1(date(2015, May, 1), + hours(13) + minutes(13) + seconds(13) + milliseconds(150)); + Ntp ntp1(ptime1); + Ntp expected1(3639474793ULL, uint16_t(9830U)); + EXPECT_TRUE(eq(ntp1, expected1)); + + ptime ptime2(date(2045, May, 1), + hours(13) + minutes(13) + seconds(13) + milliseconds(150)); + Ntp ntp2(ptime2); + Ntp expected2(4586245993ULL, uint16_t(9830U)); + EXPECT_TRUE(eq(ntp2, expected2)); +} + +TEST(NtpUtilsTest, fromBinary) { + const uint8_t data[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 }; + vector bin(8); + memcpy(&bin[0], data, 8); + vector too_short = bin; + too_short.resize(7); + Ntp ntp; + EXPECT_FALSE(ntp.from_binary(too_short)); + vector too_long = bin; + too_long.push_back(0x08); + EXPECT_FALSE(ntp.from_binary(too_long)); + EXPECT_TRUE(ntp.is_zero()); + EXPECT_TRUE(ntp.from_binary(bin)); + Ntp expected(0x10203040506ULL, uint16_t(0x708U)); + EXPECT_TRUE(eq(ntp, expected)); +} + +TEST(NtpUtilsTest, toBinary) { + const uint8_t expected[] = { 0x01, 0x02, 0x03, 0x04, + 0x05, 0x06, 0x07, 0x08 }; + Ntp ntp(0x10203040506ULL, uint16_t(0x708U)); + vector bin(ntp.to_binary()); + ASSERT_EQ(8, bin.size()); + EXPECT_TRUE(memcmp(&bin[0], expected, 8) == 0); +} + +TEST(NtpUtilsTest, double) { + struct timeval tv; + EXPECT_EQ(0, gettimeofday(&tv, 0)); + Ntp ntp(&tv); + double d(ntp.secs(tv.tv_sec)); + EXPECT_GE(d, 0.); + EXPECT_LT(d, 1.); + + double d1(123.456); + Ntp tmp(d1, tv.tv_sec); + double d2(tmp.secs(tv.tv_sec)); + EXPECT_LE(fabs(d2 - d1), 0.0001); +} + +TEST(NtpUtilsTest, verifyNew) { + struct timeval tv; + EXPECT_EQ(0, gettimeofday(&tv, 0)); + Ntp rd_new(&tv); + Ntp ts_new(&tv); + EXPECT_TRUE(Ntp::verify_new(rd_new, ts_new)); + ts_new.ntp_sec_ -= 200; + EXPECT_TRUE(Ntp::verify_new(rd_new,ts_new)); + ts_new.ntp_sec_ -= 200; + EXPECT_FALSE(Ntp::verify_new(rd_new,ts_new)); + ts_new = rd_new; + ts_new.ntp_sec_ += 200; + EXPECT_TRUE(Ntp::verify_new(rd_new,ts_new)); + ts_new.ntp_sec_ += 200; + EXPECT_FALSE(Ntp::verify_new(rd_new,ts_new)); +} + +TEST(NtpUtilsTest, verify) { + struct timeval tv; + EXPECT_EQ(0, gettimeofday(&tv, 0)); + Ntp rd_new(&tv); + Ntp ts_new(&tv); + Ntp rd_last(&tv); + rd_last.ntp_sec_ -= 1000; + Ntp ts_last(&tv); + ts_last.ntp_sec_ -= 1000; + bool to_update0 = false; + EXPECT_TRUE(Ntp::verify(rd_new, ts_new, rd_last, ts_last, &to_update0)); + EXPECT_TRUE(to_update0); + bool to_update1 = false; + Ntp next1(&tv); + next1.ntp_sec_ += 1; + EXPECT_TRUE(Ntp::verify(rd_new, ts_new, rd_new, next1, &to_update1)); + EXPECT_FALSE(to_update1); + bool to_update2 = false; + EXPECT_FALSE(Ntp::verify(rd_new, ts_new, next1, next1, &to_update2)); + EXPECT_FALSE(to_update2); + ts_new.ntp_sec_ -= 10; + bool to_update3 = false; + EXPECT_TRUE(Ntp::verify(rd_new, ts_new, rd_last, ts_last, &to_update3)); + EXPECT_TRUE(to_update3); + ts_new.ntp_sec_ -= 10; + bool to_update4 = false; + EXPECT_FALSE(Ntp::verify(rd_new, ts_new, rd_last, ts_last, &to_update4)); + EXPECT_FALSE(to_update4); +}