]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[sedhcpv6] Some improvements/bug fixes
authorFrancis Dupont <fdupont@isc.org>
Wed, 3 Jun 2015 23:27:30 +0000 (01:27 +0200)
committerFrancis Dupont <fdupont@isc.org>
Wed, 3 Jun 2015 23:27:30 +0000 (01:27 +0200)
14 files changed:
doc/guide/dhcp6-srv.xml
src/bin/dhcp6/dhcp6_srv.cc
src/bin/dhcp6/sedhcp6_messages.mes
src/lib/cryptolink/botan_asym.cc
src/lib/cryptolink/botan_hash.cc
src/lib/cryptolink/botan_hmac.cc
src/lib/cryptolink/crypto_asym.h
src/lib/cryptolink/openssl_asym.cc
src/lib/cryptolink/tests/asym_unittests.cc
src/lib/dhcp/option_custom.cc
src/lib/util/ntp_utils.cc
src/lib/util/ntp_utils.h
src/lib/util/tests/Makefile.am
src/lib/util/tests/ntp_utils_unittest.cc [new file with mode: 0644]

index 34124d54154d8feb4502526a380f92b85e645b5f..196e6da5735331c6b67b20abcfeb4e01accb2812 100644 (file)
@@ -2335,17 +2335,15 @@ should include options from the isc option space:
       <para>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:
       <itemizedlist>
       <listitem><simpara> <command>public-key</command> specifies
-      the name of a file containing the public key.
+      the name of a file containing the public key in PEM format.
       </simpara></listitem>
       <listitem><simpara> <command>certificate</command> specifies
-      the name of a file containing the certificate.
+      the name of a file containing the certificate in PEM format.
       </simpara></listitem>
       </itemizedlist>
-      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.
       </para>
    </section>
 
index 91327fd7227daf13e1edaeec9f168f4a45613fe1..c7440c7b7a6f7ba23c34b012997f590d712d7c64 100644 (file)
@@ -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<uint8_t> 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));
 }
 
 };
index 9c0ae141405981530443597d5dc90dc47693be22..45abb355be5364bee9bc774c0912f1857939f34f 100644 (file)
@@ -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
index c08e1b05e110daf10325c2ca47f90eb2b2cc248c..c515d66e0f78e70ef3768b40ef104508d0d1ed7e 100644 (file)
@@ -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<int>(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<uint8_t>
 Asym::exportkey(const AsymKeyKind key_kind,
                 const AsymFormat key_format) const {
index 9451f0a1f56da060b2efa8167c23b14c55f36e06..7f24d8b522427116f9a85d7edad3e9471d1714a2 100644 (file)
@@ -180,7 +180,7 @@ Hash::~Hash() {
 }
 
 HashAlgorithm
-Hash:getHashAlgorithm() const {
+Hash::getHashAlgorithm() const {
     return (impl_->getHashAlgorithm());
 }
 
index 4b19202ae0660fccaa37616566f74383db281fe8..14539c97e736a16a2f38b8f09764a50c1e7e5a45 100644 (file)
@@ -219,7 +219,7 @@ HMAC::~HMAC() {
 }
 
 HashAlgorithm
-HMAC:getHashAlgorithm() const {
+HMAC::getHashAlgorithm() const {
     return (impl_->getHashAlgorithm());
 }
 
index 98fd6ad1e5926ba2f718d183de55e361e85e0b15..2b1ab050cc9559e6c953ca038d20f0d5dfd36d47 100644 (file)
@@ -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<uint8_t>
index 433d55029d8d3431a0f9860ce18d3d89ab77fc15..957aa79bb2c2efd3c89b6fbe30b79a4d90d4aa1b 100644 (file)
@@ -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<int>(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<uint8_t>
 Asym::exportkey(const AsymKeyKind key_kind,
                 const AsymFormat key_format) const {
index 3589da154a89b407afdfaebdb4c4e3f93cca87fb..c2658e4d09711e2ca262e88ef1f930c8cacf16ed 100644 (file)
@@ -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<Asym> 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<Asym> 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<Asym> 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));
+}
index 971998fdfdf3fc3266197cfb265af628f4df58b5..f11777782a83cbc53c25c81a86c2ce940ecb4222 100644 (file)
@@ -327,7 +327,7 @@ OptionCustom::dataFieldToText(const OptionDataType data_type,
         text << (readBoolean(index) ? "true" : "false");
         break;
     case OPT_INT8_TYPE:
-        text << readInteger<int8_t>(index);
+        text << static_cast<int>(readInteger<int8_t>(index));
         break;
     case OPT_INT16_TYPE:
         text << readInteger<int16_t>(index);
@@ -336,7 +336,7 @@ OptionCustom::dataFieldToText(const OptionDataType data_type,
         text << readInteger<int32_t>(index);
         break;
     case OPT_UINT8_TYPE:
-        text << readInteger<uint8_t>(index);
+        text << static_cast<unsigned>(readInteger<uint8_t>(index));
         break;
     case OPT_UINT16_TYPE:
         text << readInteger<uint16_t>(index);
index 09d2d046254685247abd0070945c87d324c5205c..0170784d81c17b0571a67efb98d9d9e89224bcd6 100644 (file)
@@ -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<uint32_t>(t);
+        return (static_cast<uint64_t>(repr));
+    }
+    // or time_t is a signed 64 bit integer
+    return (static_cast<uint64_t>(t));
+}
+
+// \brief Normalized fractional seconds
+int64_t nfs(long f) {
+    // fractional seconds is a signed integer representing less than 1 second
+    return (static_cast<int64_t>(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<uint32_t>(tv->tv_sec) + EPOCH_ADJUST;
-    uint64_t fcvt = (static_cast<uint64_t>(tv->tv_usec) * 65536U) / 1000000UL;
+    ntp_sec_ = nuns(tv->tv_sec) + EPOCH_ADJUST;
+    int64_t fcvt = (nfs(static_cast<long>(tv->tv_usec)) * 65536) / 1000000;
     ntp_fraction_ = static_cast<uint16_t>(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<uint32_t>(dur.total_seconds()) + EPOCH_ADJUST;
-    uint64_t fcvt = static_cast<uint64_t>(dur.fractional_seconds()) * 65536U;
-    fcvt /= time_duration::ticks_per_second();
+    ntp_sec_ = nuns(static_cast<unsigned>(dur.total_seconds())) + EPOCH_ADJUST;
+    int64_t fcvt = (nfs(static_cast<long>(dur.fractional_seconds())) * 65536U)
+        / time_duration::ticks_per_second();
     ntp_fraction_ = static_cast<uint16_t>(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<uint64_t>(intpart) + base + EPOCH_ADJUST;
+    ntp_sec_ = static_cast<uint64_t>(intpart) + nuns(base) + EPOCH_ADJUST;
     ntp_fraction_ = static_cast<uint16_t>(floor(fracpart * 65536.));
 }
 
@@ -113,8 +132,8 @@ std::vector<uint8_t> Ntp::to_binary() const
 double Ntp::secs(time_t base) const
 {
     double ret = static_cast<double>(ntp_sec_);
-    ret -= base + EPOCH_ADJUST;
-    ret += static_cast<double>(ntp_fraction_) * (1./65536.);
+    ret -= nuns(base) + EPOCH_ADJUST;
+    ret += static_cast<double>(ntp_fraction_) / 65536.;
     return (ret);
 }
 
index f083e7132c5a81adf43dee11f30a906c1aefdc43..de472b1ec3f744badd9cd689dc3c3549d9a08b12 100644 (file)
@@ -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
index 9d727cb88a44f3fc793db41da19e3f319c4f100e..b34d43b5189098813554562c368cc6b4d6252ead 100644 (file)
@@ -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 (file)
index 0000000..b3cff3a
--- /dev/null
@@ -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 <cmath>
+#include <util/ntp_utils.h>
+
+#include <gtest/gtest.h>
+
+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<time_t>(static_cast<unsigned>(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<uint8_t> bin(8);
+    memcpy(&bin[0], data, 8);
+    vector<uint8_t> too_short = bin;
+    too_short.resize(7);
+    Ntp ntp;
+    EXPECT_FALSE(ntp.from_binary(too_short));
+    vector<uint8_t> 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<uint8_t> 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);
+}