diff options
author | JINMEI Tatuya <jinmei@isc.org> | 2011-04-27 08:59:32 +0200 |
---|---|---|
committer | JINMEI Tatuya <jinmei@isc.org> | 2011-04-27 08:59:32 +0200 |
commit | cc788af2c45043fbb98365d5fb85c1d16307dda2 (patch) | |
tree | 8aa772f84adeda12d722b4cdc2f908e6f5b36930 /src | |
parent | [master] Merge branch 'master' of ssh://bind10.isc.org/var/bind10/git/bind10 (diff) | |
parent | [trac812] minor editorial cleanups (diff) | |
download | kea-cc788af2c45043fbb98365d5fb85c1d16307dda2.tar.xz kea-cc788af2c45043fbb98365d5fb85c1d16307dda2.zip |
[trac812next] Merge branch 'trac812' into trac812next
Diffstat (limited to 'src')
33 files changed, 3163 insertions, 37 deletions
diff --git a/src/bin/xfrin/tests/Makefile.am b/src/bin/xfrin/tests/Makefile.am index dfa771474f..d4efbc77e0 100644 --- a/src/bin/xfrin/tests/Makefile.am +++ b/src/bin/xfrin/tests/Makefile.am @@ -6,7 +6,7 @@ EXTRA_DIST = $(PYTESTS) # required by loadable python modules. LIBRARY_PATH_PLACEHOLDER = if SET_ENV_LIBRARY_PATH -LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/xfr/.libs:$$$(ENV_LIBRARY_PATH) +LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/xfr/.libs:$$$(ENV_LIBRARY_PATH) endif # test using command-line arguments, so use check-local target instead of TESTS diff --git a/src/bin/xfrout/tests/Makefile.am b/src/bin/xfrout/tests/Makefile.am index 1ec3c064d9..11916afbfe 100644 --- a/src/bin/xfrout/tests/Makefile.am +++ b/src/bin/xfrout/tests/Makefile.am @@ -6,7 +6,7 @@ EXTRA_DIST = $(PYTESTS) # required by loadable python modules. LIBRARY_PATH_PLACEHOLDER = if SET_ENV_LIBRARY_PATH -LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/util/io/.libs:$$$(ENV_LIBRARY_PATH) +LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/util/io/.libs:$$$(ENV_LIBRARY_PATH) endif # test using command-line arguments, so use check-local target instead of TESTS diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am index 81455c454e..9afc9b3b60 100644 --- a/src/lib/Makefile.am +++ b/src/lib/Makefile.am @@ -1,2 +1,2 @@ -SUBDIRS = exceptions util dns cc config python xfr bench log asiolink \ - asiodns nsas cache resolve testutils datasrc server_common +SUBDIRS = exceptions util cryptolink dns cc config python xfr bench log \ + asiolink asiodns nsas cache resolve testutils datasrc server_common diff --git a/src/lib/asiolink/Makefile.am b/src/lib/asiolink/Makefile.am index 66d5eda3e7..22b3a8e183 100644 --- a/src/lib/asiolink/Makefile.am +++ b/src/lib/asiolink/Makefile.am @@ -2,7 +2,6 @@ SUBDIRS = . tests AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib AM_CPPFLAGS += $(BOOST_INCLUDES) -AM_CPPFLAGS += -I$(top_srcdir)/src/lib/dns -I$(top_builddir)/src/lib/dns AM_CXXFLAGS = $(B10_CXXFLAGS) diff --git a/src/lib/asiolink/tests/Makefile.am b/src/lib/asiolink/tests/Makefile.am index 37d9ef39e1..bfdf7c1cb8 100644 --- a/src/lib/asiolink/tests/Makefile.am +++ b/src/lib/asiolink/tests/Makefile.am @@ -1,6 +1,5 @@ AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib AM_CPPFLAGS += $(BOOST_INCLUDES) -AM_CPPFLAGS += -I$(top_builddir)/src/lib/dns -I$(top_srcdir)/src/bin AM_CPPFLAGS += -I$(top_builddir)/src/lib/util -I$(top_srcdir)/src/util AM_CPPFLAGS += -I$(top_builddir)/src/lib/cc AM_CPPFLAGS += -DTEST_DATA_DIR=\"$(srcdir)/testdata\" @@ -17,8 +16,6 @@ TESTS = if HAVE_GTEST TESTS += run_unittests run_unittests_SOURCES = run_unittests.cc -run_unittests_SOURCES += $(top_srcdir)/src/lib/dns/tests/unittest_util.h -run_unittests_SOURCES += $(top_srcdir)/src/lib/dns/tests/unittest_util.cc run_unittests_SOURCES += io_address_unittest.cc run_unittests_SOURCES += io_endpoint_unittest.cc run_unittests_SOURCES += io_socket_unittest.cc @@ -32,7 +29,6 @@ run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES) run_unittests_LDADD = $(GTEST_LDADD) run_unittests_LDADD += $(SQLITE_LIBS) -run_unittests_LDADD += $(top_builddir)/src/lib/dns/libdns++.la run_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libasiolink.la run_unittests_LDADD += $(top_builddir)/src/lib/util/libutil.la run_unittests_LDADD += $(top_builddir)/src/lib/log/liblog.la diff --git a/src/lib/asiolink/tests/run_unittests.cc b/src/lib/asiolink/tests/run_unittests.cc index c285f9e8c8..97bcb65782 100644 --- a/src/lib/asiolink/tests/run_unittests.cc +++ b/src/lib/asiolink/tests/run_unittests.cc @@ -22,7 +22,6 @@ main(int argc, char* argv[]) { ::testing::InitGoogleTest(&argc, argv); // Initialize Google test isc::log::setRootLoggerName("unittest"); // Set a root logger name - isc::UnitTestUtil::addDataPath(TEST_DATA_DIR); // Add location of test data return (RUN_ALL_TESTS()); } diff --git a/src/lib/cc/tests/Makefile.am b/src/lib/cc/tests/Makefile.am index 535c46422a..71e6988fa4 100644 --- a/src/lib/cc/tests/Makefile.am +++ b/src/lib/cc/tests/Makefile.am @@ -26,7 +26,6 @@ run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS) run_unittests_LDADD = $(GTEST_LDADD) run_unittests_LDADD += $(top_builddir)/src/lib/cc/libcc.la -run_unittests_LDADD += $(top_builddir)/src/lib/dns/libdns++.la run_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la endif diff --git a/src/lib/cryptolink/Makefile.am b/src/lib/cryptolink/Makefile.am new file mode 100644 index 0000000000..2f5d9c38d0 --- /dev/null +++ b/src/lib/cryptolink/Makefile.am @@ -0,0 +1,12 @@ +SUBDIRS = . tests + +AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib +AM_CPPFLAGS += $(BOOST_INCLUDES) +AM_CXXFLAGS = $(B10_CXXFLAGS) + +CLEANFILES = *.gcno *.gcda + +lib_LTLIBRARIES = libcryptolink.la + +libcryptolink_la_SOURCES = cryptolink.h cryptolink.cc +libcryptolink_la_SOURCES += crypto_hmac.h crypto_hmac.cc diff --git a/src/lib/cryptolink/crypto_hmac.cc b/src/lib/cryptolink/crypto_hmac.cc new file mode 100644 index 0000000000..ea70c3298b --- /dev/null +++ b/src/lib/cryptolink/crypto_hmac.cc @@ -0,0 +1,237 @@ +// Copyright (C) 2011 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 <cryptolink.h> +#include <cryptolink/crypto_hmac.h> + +#include <boost/scoped_ptr.hpp> + +#include <botan/botan.h> +#include <botan/hmac.h> +#include <botan/hash.h> +#include <botan/types.h> + +namespace { +const char* +getBotanHashAlgorithmName(isc::cryptolink::HashAlgorithm algorithm) { + switch (algorithm) { + case isc::cryptolink::MD5: + return ("MD5"); + break; + case isc::cryptolink::SHA1: + return ("SHA-1"); + break; + case isc::cryptolink::SHA256: + return ("SHA-256"); + break; + case isc::cryptolink::UNKNOWN_HASH: + return ("Unknown"); + break; + } + // compiler should have prevented us to reach this, since we have + // no default. But we need a return value anyway + return ("Unknown"); +} + +} // local namespace + + +namespace isc { +namespace cryptolink { + +class HMACImpl { +public: + explicit HMACImpl(const void* secret, size_t secret_len, + const HashAlgorithm hash_algorithm) { + Botan::HashFunction* hash; + try { + hash = Botan::get_hash( + getBotanHashAlgorithmName(hash_algorithm)); + } catch (const Botan::Algorithm_Not_Found&) { + isc_throw(isc::cryptolink::UnsupportedAlgorithm, + "Unknown hash algorithm: " + hash_algorithm); + } catch (const Botan::Exception& exc) { + isc_throw(isc::cryptolink::LibraryError, exc.what()); + } + + hmac_.reset(new Botan::HMAC::HMAC(hash)); + + // If the key length is larger than the block size, we hash the + // key itself first. + try { + if (secret_len > hash->HASH_BLOCK_SIZE) { + Botan::SecureVector<Botan::byte> hashed_key = + hash->process(static_cast<const Botan::byte*>(secret), + secret_len); + hmac_->set_key(hashed_key.begin(), hashed_key.size()); + } else { + hmac_->set_key(static_cast<const Botan::byte*>(secret), + secret_len); + } + } catch (const Botan::Invalid_Key_Length& ikl) { + isc_throw(BadKey, ikl.what()); + } catch (const Botan::Exception& exc) { + isc_throw(isc::cryptolink::LibraryError, exc.what()); + } + } + + ~HMACImpl() { } + + size_t getOutputLength() const { + return (hmac_->OUTPUT_LENGTH); + } + + void update(const void* data, const size_t len) { + try { + hmac_->update(static_cast<const Botan::byte*>(data), len); + } catch (const Botan::Exception& exc) { + isc_throw(isc::cryptolink::LibraryError, exc.what()); + } + } + + void sign(isc::util::OutputBuffer& result, size_t len) { + try { + Botan::SecureVector<Botan::byte> b_result(hmac_->final()); + + if (len == 0 || len > b_result.size()) { + len = b_result.size(); + } + result.writeData(b_result.begin(), len); + } catch (const Botan::Exception& exc) { + isc_throw(isc::cryptolink::LibraryError, exc.what()); + } + } + + void sign(void* result, size_t len) { + try { + Botan::SecureVector<Botan::byte> b_result(hmac_->final()); + size_t output_size = getOutputLength(); + if (output_size > len) { + output_size = len; + } + memcpy(result, b_result.begin(), output_size); + } catch (const Botan::Exception& exc) { + isc_throw(isc::cryptolink::LibraryError, exc.what()); + } + } + + std::vector<uint8_t> sign(size_t len) { + try { + Botan::SecureVector<Botan::byte> b_result(hmac_->final()); + if (len == 0 || len > b_result.size()) { + return (std::vector<uint8_t>(b_result.begin(), b_result.end())); + } else { + return (std::vector<uint8_t>(b_result.begin(), &b_result[len])); + } + } catch (const Botan::Exception& exc) { + isc_throw(isc::cryptolink::LibraryError, exc.what()); + } + } + + + bool verify(const void* sig, size_t len) { + // Botan's verify_mac checks if len matches the output_length, + // which causes it to fail for truncated signatures, so we do + // the check ourselves + try { + Botan::SecureVector<Botan::byte> our_mac = hmac_->final(); + if (len == 0 || len > getOutputLength()) { + len = getOutputLength(); + } + return (Botan::same_mem(&our_mac[0], + static_cast<const unsigned char*>(sig), + len)); + } catch (const Botan::Exception& exc) { + isc_throw(isc::cryptolink::LibraryError, exc.what()); + } + } + +private: + boost::scoped_ptr<Botan::HMAC> hmac_; +}; + +HMAC::HMAC(const void* secret, size_t secret_length, + const HashAlgorithm hash_algorithm) +{ + impl_ = new HMACImpl(secret, secret_length, hash_algorithm); +} + +HMAC::~HMAC() { + delete impl_; +} + +size_t +HMAC::getOutputLength() const { + return (impl_->getOutputLength()); +} + +void +HMAC::update(const void* data, const size_t len) { + impl_->update(data, len); +} + +void +HMAC::sign(isc::util::OutputBuffer& result, size_t len) { + impl_->sign(result, len); +} + +void +HMAC::sign(void* result, size_t len) { + impl_->sign(result, len); +} + +std::vector<uint8_t> +HMAC::sign(size_t len) { + return impl_->sign(len); +} + +bool +HMAC::verify(const void* sig, const size_t len) { + return (impl_->verify(sig, len)); +} + +void +signHMAC(const void* data, size_t data_len, const void* secret, + size_t secret_len, const HashAlgorithm hash_algorithm, + isc::util::OutputBuffer& result, size_t len) +{ + boost::scoped_ptr<HMAC> hmac( + CryptoLink::getCryptoLink().createHMAC(secret, + secret_len, + hash_algorithm)); + hmac->update(data, data_len); + hmac->sign(result, len); +} + + +bool +verifyHMAC(const void* data, const size_t data_len, const void* secret, + size_t secret_len, const HashAlgorithm hash_algorithm, + const void* sig, const size_t sig_len) +{ + boost::scoped_ptr<HMAC> hmac( + CryptoLink::getCryptoLink().createHMAC(secret, + secret_len, + hash_algorithm)); + hmac->update(data, data_len); + return (hmac->verify(sig, sig_len)); +} + +void +deleteHMAC(HMAC* hmac) { + delete hmac; +} + +} // namespace cryptolink +} // namespace isc diff --git a/src/lib/cryptolink/crypto_hmac.h b/src/lib/cryptolink/crypto_hmac.h new file mode 100644 index 0000000000..2eb0d0ec8f --- /dev/null +++ b/src/lib/cryptolink/crypto_hmac.h @@ -0,0 +1,209 @@ +// Copyright (C) 2011 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 <util/buffer.h> + +#include <boost/noncopyable.hpp> + +#include <cryptolink/cryptolink.h> + +#ifndef _ISC_CRYPTO_HMAC_H +#define _ISC_CRYPTO_HMAC_H + +namespace isc { +namespace cryptolink { + +/// Forward declaration, pimpl style +class HMACImpl; + +/// \brief HMAC support +/// +/// This class is used to create and verify HMAC signatures. Instances +/// can be created with CryptoLink::createHMAC() +/// +class HMAC : private boost::noncopyable { +private: + /// \brief Constructor from a secret and a hash algorithm + /// + /// \exception UnsupportedAlgorithmException if the given algorithm + /// is unknown or not supported by the underlying library + /// \exception InvalidKeyLength if the given key secret_len is bad + /// \exception LibraryError if there was any unexpected exception + /// in the underlying library + /// + /// Notes: if the secret is longer than the block size of its + /// algorithm, the constructor will run it through the hash + /// algorithm, and use the digest as the secret for this HMAC + /// operation + /// + /// \param secret The secret to sign with + /// \param len The length of the secret + /// \param hash_algorithm The hash algorithm + HMAC(const void* secret, size_t secret_len, + const HashAlgorithm hash_algorithm); + + friend HMAC* CryptoLink::createHMAC(const void*, size_t, + const HashAlgorithm); + +public: + /// \brief Destructor + ~HMAC(); + + /// \brief Returns the output size of the digest + /// + /// \return output size of the digest + size_t getOutputLength() const; + + /// \brief Add data to digest + /// + /// \exception LibraryError if there was any unexpected exception + /// in the underlying library + /// + /// \param data The data to add + /// \param len The size of the data + void update(const void* data, const size_t len); + + /// \brief Calculate the final signature + /// + /// The result will be appended to the given outputbuffer + /// + /// \exception LibraryError if there was any unexpected exception + /// in the underlying library + /// + /// \param result The OutputBuffer to append the result to + /// \param len The number of bytes from the result to copy. If this + /// value is smaller than the algorithms output size, the + /// result will be truncated. If this value is larger, or 0 + /// (the default), it will be ignored + void sign(isc::util::OutputBuffer& result, size_t len = 0); + + /// \brief Calculate the final signature + /// + /// len bytes of data from the result will be copied to *result + /// If len is larger than the output size, only output_size bytes + /// will be copied. If it is smaller, the output will be truncated + /// + /// \exception LibraryError if there was any unexpected exception + /// in the underlying library + /// + /// At least len bytes of data must be available for writing at + /// result + void sign(void* result, size_t len); + + /// \brief Calculate the final signatre + /// + /// The result will be returned as a std::vector<uint8_t> + /// + /// \exception LibraryError if there was any unexpected exception + /// in the underlying library + /// + /// \param len The number of bytes from the result to copy. If this + /// value is smaller than the algorithms output size, the + /// result will be truncated. If this value is larger, or 0 + /// (the default), it will be ignored + /// \return a vector containing the signature + std::vector<uint8_t> sign(size_t len = 0); + + /// \brief Verify an existing signature + /// + /// \exception LibraryError if there was any unexpected exception + /// in the underlying library + /// + /// \param sig The signature to verify + /// \param len The length of the signature. If this is non-zero, + /// and smaller than the output length of the algorithm, + /// only len bytes will be checked + /// \return true if the signature is correct, false otherwise + bool verify(const void* sig, size_t len); + +private: + HMACImpl* impl_; +}; + +/// \brief Create an HMAC signature for the given data +/// +/// This is a convenience function that calculates the hmac signature, +/// given a fixed amount of data. Internally it does the same as +/// creating an HMAC object, feeding it the data, and calculating the +/// resulting signature. +/// +/// \exception UnsupportedAlgorithm if the given algorithm is unknown +/// or not supported by the underlying library +/// \exception BadKey if the given key secret_len is bad +/// \exception LibraryError if there was any unexpected exception +/// in the underlying library +/// +/// Notes: if the secret is longer than the block size of its +/// algorithm, the constructor will run it through the hash +/// algorithm, and use the digest as the secret for this HMAC +/// operation +/// +/// \param data The data to sign +/// \param data_len The length of the data +/// \param secret The secret to sign with +/// \param secret_len The length of the secret +/// \param hash_algorithm The hash algorithm +/// \param result The signature will be appended to this buffer +/// \param len If this is non-zero and less than the output size, +/// the result will be truncated to len bytes +void signHMAC(const void* data, + const size_t data_len, + const void* secret, + size_t secret_len, + const HashAlgorithm hash_algorithm, + isc::util::OutputBuffer& result, + size_t len = 0); + +/// \brief Verify an HMAC signature for the given data +/// +/// This is a convenience function that verifies an hmac signature, +/// given a fixed amount of data. Internally it does the same as +/// creating an HMAC object, feeding it the data, and checking the +/// resulting signature. +/// +/// \exception UnsupportedAlgorithm if the given algorithm is unknown +/// or not supported by the underlying library +/// \exception BadKey if the given key secret_len is bad +/// \exception LibraryError if there was any unexpected exception +/// in the underlying library +/// +/// Notes: if the secret is longer than the block size of its +/// algorithm, the constructor will run it through the hash +/// algorithm, and use the digest as the secret for this HMAC +/// operation +/// +/// \param data The data to verify +/// \param data_len The length of the data +/// \param secret The secret to sign with +/// \param secret_len The length of the secret +/// \param hash_algorithm The hash algorithm +/// \param sig The signature to verify +/// \param sig_len The length of the signature +/// \return True if the signature verifies, false if not +bool verifyHMAC(const void* data, + const size_t data_len, + const void* secret, + size_t secret_len, + const HashAlgorithm hash_algorithm, + const void* sig, + const size_t sig_len); + +/// \brief Delete an HMAC object +void deleteHMAC(HMAC* hmac); + +} // namespace cryptolink +} // namespace isc + +#endif // __ISC_CRYPTO_HMAC + diff --git a/src/lib/cryptolink/cryptolink.cc b/src/lib/cryptolink/cryptolink.cc new file mode 100644 index 0000000000..d1c375d4a6 --- /dev/null +++ b/src/lib/cryptolink/cryptolink.cc @@ -0,0 +1,69 @@ +// Copyright (C) 2011 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 <cryptolink/cryptolink.h> +#include <cryptolink/crypto_hmac.h> + +#include <botan/botan.h> + +namespace isc { +namespace cryptolink { + +// For Botan, we use the CryptoLink class object in RAII style +class CryptoLinkImpl { +private: + Botan::LibraryInitializer botan_init_; +}; + +CryptoLink::~CryptoLink() { + delete impl_; +} + +CryptoLink& +CryptoLink::getCryptoLink() { + CryptoLink& c = getCryptoLinkInternal(); + if (c.impl_ == NULL) { + c.initialize(); + } + return (c); +} + +CryptoLink& +CryptoLink::getCryptoLinkInternal() { + static CryptoLink instance; + return (instance); +} + +void +CryptoLink::initialize() { + CryptoLink& c = getCryptoLinkInternal(); + if (c.impl_ == NULL) { + try { + c.impl_ = new CryptoLinkImpl(); + } catch (const Botan::Exception& ex) { + isc_throw(InitializationError, ex.what()); + } + } +} + +HMAC* +CryptoLink::createHMAC(const void* secret, size_t secret_len, + const HashAlgorithm hash_algorithm) +{ + return (new HMAC(secret, secret_len, hash_algorithm)); +} + +} // namespace cryptolink +} // namespace isc + diff --git a/src/lib/cryptolink/cryptolink.h b/src/lib/cryptolink/cryptolink.h new file mode 100644 index 0000000000..34f7cd360e --- /dev/null +++ b/src/lib/cryptolink/cryptolink.h @@ -0,0 +1,203 @@ +// Copyright (C) 2011 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. + +#ifndef _ISC_CRYPTO_H +#define _ISC_CRYPTO_H + +#include <string> +#include <util/buffer.h> +#include <exceptions/exceptions.h> + +#include <boost/noncopyable.hpp> +#include <boost/scoped_ptr.hpp> + +#include <memory> + +namespace isc { +namespace cryptolink { + +enum HashAlgorithm { + MD5 = 0, ///< MD5 + SHA1 = 1, ///< SHA-1 + SHA256 = 2, ///< SHA-256 + UNKNOWN_HASH = 3 ///< This value can be used in conversion + /// functions, to be returned when the + /// input is unknown (but a value MUST be + /// returned), for instance when the input + /// is a Name or a string, and the return + /// value is a HashAlgorithm. +}; + +// Forward declaration for createHMAC() +class HMAC; + +/// General exception class that is the base for all crypto-related +/// exceptions +class CryptoLinkError : public Exception { +public: + CryptoLinkError(const char* file, size_t line, const char* what) : + isc::Exception(file, line, what) {} +}; + +/// This exception is thrown if there was a problem initializing the +/// crypto library +class InitializationError : public CryptoLinkError { +public: + InitializationError(const char* file, size_t line, const char* what) : + CryptoLinkError(file, line, what) {} +}; + +/// This exception is thrown when a cryptographic action is requested +/// for an algorithm that is not supported by the underlying library. +class UnsupportedAlgorithm : public CryptoLinkError { +public: + UnsupportedAlgorithm(const char* file, size_t line, const char* what) : + CryptoLinkError(file, line, what) {} +}; + +/// This exception is thrown when the underlying library could not +/// handle the key data. +class BadKey : public CryptoLinkError { +public: + BadKey(const char* file, size_t line, const char* what) : + CryptoLinkError(file, line, what) {} +}; + +/// This exception is raised when a general error that was not +/// specifically caught is thrown by the underlying library. It +/// is replaced by this one so as not have 'external' exceptions +/// bubbling up +class LibraryError : public CryptoLinkError { +public: + LibraryError(const char* file, size_t line, const char* what) : + CryptoLinkError(file, line, what) {} +}; + +/// Forward declaration for pimpl +class CryptoLinkImpl; + +/// \brief Singleton entry point and factory class +/// +/// This is a singleton class that serves as the entry point to +/// the underlying cryptography library, and as a factory for objects +/// within the cryptolink library. +/// +/// There is only one way to access it, through getCryptoLink(), which +/// returns a reference to the initialized library. On the first call, +/// it will be initialized automatically. You can however initialize it +/// manually through a call to the initalize(), before your first call +/// to getCryptoLink. Any subsequent call to initialize() will be a +/// noop. +/// +/// In order for the CryptoLink library to be sure that the underlying +/// library has been initialized, and because we do not want to add +/// such a check to every class and function within it, we have made +/// the constructors of all classes within cryptolink private. This way +/// a caller cannot instantiate an object before the library is +/// initialized, but must use CryptoLink's create method (e.g. +/// createHMAC()), which enforces (automatic) initialization. +/// +/// In order for the CryptoLink class to be able to create objects that +/// have private constructors, it is declared a friend class of these +/// classes. +/// +/// Since these factory functions return bare pointers, we also provide +/// deleter functions for them (e.g. deleteHMAC()), so that a caller +/// can use that to make sure it uses the correct delete operator (the +/// one defined at compilation time of this library). A way to make +/// sure you do not forget this, is to place the result of the create +/// functions in a shared_ptr with the corresponding deleter function. +/// +/// \note All other classes within cryptolink should have private +/// constructors as well, and should have a factory function from +/// CryptoLink, and a deleter function. +/// +// Internal note: we can use this class later to initialize and manage +// dynamic (PKCS#11) libs +class CryptoLink : private boost::noncopyable { +public: + /// \brief Returns a reference to the singleton instance + /// + /// If the library has not been initialized yet, it will be + /// initialized with some default values. + /// + /// Since this class is noncopyable, you must use the return + /// value directly, or store it in a reference variable. + /// + /// \exception InitializationError if initialization fails + /// + /// \return Reference to the singleton instance + static CryptoLink& getCryptoLink(); + + /// \brief Initialize the library manually + /// + /// If the library has already been initialized (either by a call + /// to initialize() or automatically in getCryptoLink()), this + /// function does nothing. + /// + /// \note A call to initialize() is not strictly necessary with + /// the current implementation. + /// + /// \exception InitializationError if initialization fails + /// + static void initialize(); + + /// \brief Factory function for HMAC objects + /// + /// CryptoLink objects cannot be constructed directly. This + /// function creates a new HMAC object usable for signing or + /// verification. + /// + /// The caller is responsible for deleting the object, and it is + /// therefore highly recommended to place the return value of this + /// function in a scoped_ptr or shared_ptr. + /// + /// Notes: if the secret is longer than the block size of its + /// algorithm, the constructor will run it through the hash + /// algorithm, and use the digest as the secret for this HMAC + /// operation + /// + /// If you want to safely delete objects created with this method, + /// you can use the function deleteHMAC() as defined in + /// crypto_hmac.h + /// + /// \exception UnsupportedAlgorithmException if the given algorithm + /// is unknown or not supported by the underlying library + /// \exception InvalidKeyLength if the given key secret_len is bad + /// \exception LibraryError if there was any unexpected exception + /// in the underlying library + /// + /// \param secret The secret to sign with + /// \param secret_len The length of the secret + /// \param hash_algorithm The hash algorithm + HMAC* createHMAC(const void* secret, size_t secret_len, + const HashAlgorithm hash_algorithm); + +private: + // To enable us to use an optional explicit initialization call, + // the 'real' instance getter is private + static CryptoLink& getCryptoLinkInternal(); + + // To prevent people constructing their own, we make the constructor + // private too. + CryptoLink() : impl_(NULL) {} + ~CryptoLink(); + + CryptoLinkImpl* impl_; +}; + +} // namespace cryptolink +} // namespace isc + +#endif // _ISC_CRYPTO_H diff --git a/src/lib/cryptolink/tests/Makefile.am b/src/lib/cryptolink/tests/Makefile.am new file mode 100644 index 0000000000..c8b5e266f4 --- /dev/null +++ b/src/lib/cryptolink/tests/Makefile.am @@ -0,0 +1,26 @@ +SUBDIRS = . + +AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib +AM_CPPFLAGS += $(BOOST_INCLUDES) +AM_CXXFLAGS = $(B10_CXXFLAGS) + +if USE_STATIC_LINK +AM_LDFLAGS = -static +endif + +CLEANFILES = *.gcno *.gcda + +TESTS = +if HAVE_GTEST +TESTS += run_unittests +run_unittests_SOURCES = run_unittests.cc +run_unittests_SOURCES += crypto_unittests.cc +run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES) +run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS) +run_unittests_LDADD = $(GTEST_LDADD) +run_unittests_LDADD += $(top_builddir)/src/lib/cryptolink/libcryptolink.la +run_unittests_LDADD += $(top_builddir)/src/lib/util/libutil.la +run_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la +endif + +noinst_PROGRAMS = $(TESTS) diff --git a/src/lib/cryptolink/tests/crypto_unittests.cc b/src/lib/cryptolink/tests/crypto_unittests.cc new file mode 100644 index 0000000000..cdf7857c1f --- /dev/null +++ b/src/lib/cryptolink/tests/crypto_unittests.cc @@ -0,0 +1,505 @@ +// Copyright (C) 2011 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 <config.h> +#include <gtest/gtest.h> + +#include <cryptolink/cryptolink.h> +#include <cryptolink/crypto_hmac.h> + +#include <util/buffer.h> +#include <exceptions/exceptions.h> + +#include <boost/scoped_ptr.hpp> + +using namespace isc::util; +using namespace isc::cryptolink; + +namespace { + void checkData(const uint8_t* data, const uint8_t* expected, + size_t len) { + for (size_t i = 0; i < len; ++i) { + ASSERT_EQ(expected[i], data[i]); + } + } + + void checkBuffer(const OutputBuffer& buf, const uint8_t* expected, + size_t len) + { + ASSERT_EQ(len, buf.getLength()); + checkData(static_cast<const uint8_t*>(buf.getData()), expected, + len); + } + + // Sign and verify with the convenience functions + void doHMACTestConv(const std::string& data, + const void* secret, + size_t secret_len, + const HashAlgorithm hash_algorithm, + const uint8_t* expected_hmac, + size_t hmac_len) { + OutputBuffer data_buf(data.size()); + data_buf.writeData(data.c_str(), data.size()); + OutputBuffer hmac_sig(0); + + // Sign it + signHMAC(data_buf.getData(), data_buf.getLength(), + secret, secret_len, hash_algorithm, hmac_sig, hmac_len); + + // Check if the signature is what we expect + checkBuffer(hmac_sig, expected_hmac, hmac_len); + + // Check whether we can verify it ourselves + EXPECT_TRUE(verifyHMAC(data_buf.getData(), data_buf.getLength(), + secret, secret_len, hash_algorithm, + hmac_sig.getData(), + hmac_sig.getLength())); + + // Change the sig by flipping the first octet, and check + // whether verification fails then + hmac_sig.writeUint8At(~hmac_sig[0], 0); + EXPECT_FALSE(verifyHMAC(data_buf.getData(), data_buf.getLength(), + secret, secret_len, hash_algorithm, + hmac_sig.getData(), + hmac_sig.getLength())); + } + + // Sign and verify with an instantiation of an HMAC object + void doHMACTestDirect(const std::string& data, + const void* secret, + size_t secret_len, + const HashAlgorithm hash_algorithm, + const uint8_t* expected_hmac, + size_t hmac_len) { + OutputBuffer data_buf(data.size()); + data_buf.writeData(data.c_str(), data.size()); + OutputBuffer hmac_sig(1); + CryptoLink& crypto = CryptoLink::getCryptoLink(); + + // Sign it + boost::scoped_ptr<HMAC> hmac_sign(crypto.createHMAC(secret, + secret_len, + hash_algorithm)); + hmac_sign->update(data_buf.getData(), data_buf.getLength()); + hmac_sign->sign(hmac_sig, hmac_len); + + // Check if the signature is what we expect + checkBuffer(hmac_sig, expected_hmac, hmac_len); + + // Check whether we can verify it ourselves + boost::scoped_ptr<HMAC> hmac_verify(crypto.createHMAC(secret, + secret_len, + hash_algorithm)); + hmac_verify->update(data_buf.getData(), data_buf.getLength()); + EXPECT_TRUE(hmac_verify->verify(hmac_sig.getData(), + hmac_sig.getLength())); + + // Change the sig by flipping the first octet, and check + // whether verification fails then + hmac_sig.writeUint8At(~hmac_sig[0], 0); + EXPECT_FALSE(hmac_verify->verify(hmac_sig.getData(), + hmac_sig.getLength())); + } + + void doHMACTestVector(const std::string& data, + const void* secret, + size_t secret_len, + const HashAlgorithm hash_algorithm, + const uint8_t* expected_hmac, + size_t hmac_len) { + CryptoLink& crypto = CryptoLink::getCryptoLink(); + boost::scoped_ptr<HMAC> hmac_sign(crypto.createHMAC(secret, + secret_len, + hash_algorithm)); + hmac_sign->update(data.c_str(), data.size()); + std::vector<uint8_t> sig = hmac_sign->sign(hmac_len); + ASSERT_EQ(hmac_len, sig.size()); + checkData(&sig[0], expected_hmac, hmac_len); + + boost::scoped_ptr<HMAC> hmac_verify(crypto.createHMAC(secret, + secret_len, + hash_algorithm)); + hmac_verify->update(data.c_str(), data.size()); + EXPECT_TRUE(hmac_verify->verify(&sig[0], sig.size())); + + sig[0] = ~sig[0]; + EXPECT_FALSE(hmac_verify->verify(&sig[0], sig.size())); + } + + void doHMACTestArray(const std::string& data, + const void* secret, + size_t secret_len, + const HashAlgorithm hash_algorithm, + const uint8_t* expected_hmac, + size_t hmac_len) { + CryptoLink& crypto = CryptoLink::getCryptoLink(); + boost::scoped_ptr<HMAC> hmac_sign(crypto.createHMAC(secret, + secret_len, + hash_algorithm)); + hmac_sign->update(data.c_str(), data.size()); + + // note: this is not exception-safe, and can leak, but + // if there is an unexpected exception in the code below we + // have more important things to fix. + uint8_t* sig = new uint8_t[hmac_len]; + + hmac_sign->sign(sig, hmac_len); + checkData(sig, expected_hmac, hmac_len); + + boost::scoped_ptr<HMAC> hmac_verify(crypto.createHMAC(secret, + secret_len, + hash_algorithm)); + hmac_verify->update(data.c_str(), data.size()); + EXPECT_TRUE(hmac_verify->verify(sig, hmac_len)); + + sig[0] = ~sig[0]; + EXPECT_FALSE(hmac_verify->verify(sig, hmac_len)); + + delete[] sig; + } + + void doHMACTest(const std::string& data, + const void* secret, + size_t secret_len, + const HashAlgorithm hash_algorithm, + const uint8_t* expected_hmac, + size_t hmac_len) { + doHMACTestConv(data, secret, secret_len, hash_algorithm, + expected_hmac, hmac_len); + doHMACTestDirect(data, secret, secret_len, hash_algorithm, + expected_hmac, hmac_len); + doHMACTestVector(data, secret, secret_len, hash_algorithm, + expected_hmac, hmac_len); + doHMACTestArray(data, secret, secret_len, hash_algorithm, + expected_hmac, hmac_len); + } +} + +// +// Test values taken from RFC 2202 +// +TEST(CryptoLinkTest, HMAC_MD5_RFC2202_SIGN) { + const uint8_t secret[] = { 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b }; + const uint8_t hmac_expected[] = { 0x92, 0x94, 0x72, 0x7a, 0x36, + 0x38, 0xbb, 0x1c, 0x13, 0xf4, + 0x8e, 0xf8, 0x15, 0x8b, 0xfc, + 0x9d }; + doHMACTest("Hi There", secret, 16, MD5, hmac_expected, 16); + + const uint8_t hmac_expected2[] = { 0x75, 0x0c, 0x78, 0x3e, 0x6a, + 0xb0, 0xb5, 0x03, 0xea, 0xa8, + 0x6e, 0x31, 0x0a, 0x5d, 0xb7, + 0x38 }; + doHMACTest("what do ya want for nothing?", "Jefe", 4, MD5, + hmac_expected2, 16); + + const uint8_t secret3[] = { 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa }; + const uint8_t hmac_expected3[] = { 0x56, 0xbe, 0x34, 0x52, 0x1d, + 0x14, 0x4c, 0x88, 0xdb, 0xb8, + 0xc7, 0x33, 0xf0, 0xe8, 0xb3, + 0xf6}; + doHMACTest(std::string(50, 0xdd), secret3, 16, MD5, hmac_expected3, 16); + + const std::string data4(50, 0xcd); + const uint8_t secret4[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, + 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, + 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, + 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, + 0x19 }; + const uint8_t hmac_expected4[] = { 0x69, 0x7e, 0xaf, 0x0a, 0xca, + 0x3a, 0x3a, 0xea, 0x3a, 0x75, + 0x16, 0x47, 0x46, 0xff, 0xaa, + 0x79 }; + doHMACTest(data4, secret4, 25, MD5, hmac_expected4, 16); + + const uint8_t secret5[] = { 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c }; + const uint8_t hmac_expected5[] = { 0x56, 0x46, 0x1e, 0xf2, 0x34, + 0x2e, 0xdc, 0x00, 0xf9, 0xba, + 0xb9, 0x95, 0x69, 0x0e, 0xfd, + 0x4c }; + doHMACTest("Test With Truncation", secret5, 16, MD5, + hmac_expected5, 16); + doHMACTest("Test With Truncation", secret5, 16, MD5, + hmac_expected5, 12); + + const uint8_t hmac_expected6[] = { 0x6b, 0x1a, 0xb7, 0xfe, 0x4b, + 0xd7, 0xbf, 0x8f, 0x0b, 0x62, + 0xe6, 0xce, 0x61, 0xb9, 0xd0, + 0xcd }; + doHMACTest("Test Using Larger Than Block-Size Key - Hash Key First", + std::string(80, 0xaa).c_str(), 80, MD5, hmac_expected6, 16); + + const uint8_t hmac_expected7[] = { 0x6f, 0x63, 0x0f, 0xad, 0x67, + 0xcd, 0xa0, 0xee, 0x1f, 0xb1, + 0xf5, 0x62, 0xdb, 0x3a, 0xa5, + 0x3e }; + doHMACTest("Test Using Larger Than Block-Size Key and Larger Than " + "One Block-Size Data", + std::string(80, 0xaa).c_str(), 80, MD5, hmac_expected7, 16); +} + +// +// Test values taken from RFC 2202 +// +TEST(CryptoLinkTest, HMAC_SHA1_RFC2202_SIGN) { + const uint8_t secret[] = { 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b }; + const uint8_t hmac_expected[] = { 0xb6, 0x17, 0x31, 0x86, 0x55, + 0x05, 0x72, 0x64, 0xe2, 0x8b, + 0xc0, 0xb6, 0xfb, 0x37, 0x8c, + 0x8e, 0xf1, 0x46, 0xbe, 0x00 }; + doHMACTest("Hi There", secret, 20, SHA1, hmac_expected, 20); + + const uint8_t hmac_expected2[] = { 0xef, 0xfc, 0xdf, 0x6a, 0xe5, + 0xeb, 0x2f, 0xa2, 0xd2, 0x74, + 0x16, 0xd5, 0xf1, 0x84, 0xdf, + 0x9c, 0x25, 0x9a, 0x7c, 0x79 }; + doHMACTest("what do ya want for nothing?", "Jefe", 4, SHA1, + hmac_expected2, 20); + + const uint8_t secret3[] = { 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa }; + const uint8_t hmac_expected3[] = { 0x12, 0x5d, 0x73, 0x42, 0xb9, + 0xac, 0x11, 0xcd, 0x91, 0xa3, + 0x9a, 0xf4, 0x8a, 0xa1, 0x7b, + 0x4f, 0x63, 0xf1, 0x75, 0xd3 }; + doHMACTest(std::string(50, 0xdd), secret3, 20, SHA1, hmac_expected3, 20); + + const uint8_t secret4[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, + 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, + 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, + 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, + 0x19 }; + const uint8_t hmac_expected4[] = { 0x4c, 0x90, 0x07, 0xf4, 0x02, + 0x62, 0x50, 0xc6, 0xbc, 0x84, + 0x14, 0xf9, 0xbf, 0x50, 0xc8, + 0x6c, 0x2d, 0x72, 0x35, 0xda }; + doHMACTest(std::string(50, 0xcd), secret4, 25, SHA1, hmac_expected4, 20); + + const uint8_t secret5[] = { 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c }; + const uint8_t hmac_expected5[] = { 0x4c, 0x1a, 0x03, 0x42, 0x4b, + 0x55, 0xe0, 0x7f, 0xe7, 0xf2, + 0x7b, 0xe1, 0xd5, 0x8b, 0xb9, + 0x32, 0x4a, 0x9a, 0x5a, 0x04 }; + doHMACTest("Test With Truncation", secret5, 20, SHA1, + hmac_expected5, 20); + doHMACTest("Test With Truncation", secret5, 20, SHA1, + hmac_expected5, 12); + + const uint8_t hmac_expected6[] = { 0xaa, 0x4a, 0xe5, 0xe1, 0x52, + 0x72, 0xd0, 0x0e, 0x95, 0x70, + 0x56, 0x37, 0xce, 0x8a, 0x3b, + 0x55, 0xed, 0x40, 0x21, 0x12 }; + doHMACTest("Test Using Larger Than Block-Size Key - Hash Key First", + std::string(80, 0xaa).c_str(), 80, SHA1, hmac_expected6, 20); + + const uint8_t hmac_expected7[] = { 0xe8, 0xe9, 0x9d, 0x0f, 0x45, + 0x23, 0x7d, 0x78, 0x6d, 0x6b, + 0xba, 0xa7, 0x96, 0x5c, 0x78, + 0x08, 0xbb, 0xff, 0x1a, 0x91 }; + doHMACTest("Test Using Larger Than Block-Size Key and Larger Than " + "One Block-Size Data", + std::string(80, 0xaa).c_str(), 80, SHA1, hmac_expected7, 20); +} + +// +// Test values taken from RFC 4231 +// +TEST(CryptoLinkTest, HMAC_SHA256_RFC2202_SIGN) { + const uint8_t secret[] = { 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b }; + const uint8_t hmac_expected[] = { 0xb0, 0x34, 0x4c, 0x61, 0xd8, + 0xdb, 0x38, 0x53, 0x5c, 0xa8, + 0xaf, 0xce, 0xaf, 0x0b, 0xf1, + 0x2b, 0x88, 0x1d, 0xc2, 0x00, + 0xc9, 0x83, 0x3d, 0xa7, 0x26, + 0xe9, 0x37, 0x6c, 0x2e, 0x32, + 0xcf, 0xf7 }; + doHMACTest("Hi There", secret, 20, SHA256, hmac_expected, 32); + + const uint8_t hmac_expected2[] = { 0x5b, 0xdc, 0xc1, 0x46, 0xbf, + 0x60, 0x75, 0x4e, 0x6a, 0x04, + 0x24, 0x26, 0x08, 0x95, 0x75, + 0xc7, 0x5a, 0x00, 0x3f, 0x08, + 0x9d, 0x27, 0x39, 0x83, 0x9d, + 0xec, 0x58, 0xb9, 0x64, 0xec, + 0x38, 0x43 }; + doHMACTest("what do ya want for nothing?", "Jefe", 4, SHA256, + hmac_expected2, 32); + + const uint8_t secret3[] = { 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa }; + const uint8_t hmac_expected3[] = { 0x77, 0x3e, 0xa9, 0x1e, 0x36, + 0x80, 0x0e, 0x46, 0x85, 0x4d, + 0xb8, 0xeb, 0xd0, 0x91, 0x81, + 0xa7, 0x29, 0x59, 0x09, 0x8b, + 0x3e, 0xf8, 0xc1, 0x22, 0xd9, + 0x63, 0x55, 0x14, 0xce, 0xd5, + 0x65, 0xfe }; + doHMACTest(std::string(50, 0xdd), secret3, 20, SHA256, hmac_expected3, 32); + + const uint8_t secret4[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, + 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, + 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, + 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, + 0x19 }; + const uint8_t hmac_expected4[] = { 0x82, 0x55, 0x8a, 0x38, 0x9a, + 0x44, 0x3c, 0x0e, 0xa4, 0xcc, + 0x81, 0x98, 0x99, 0xf2, 0x08, + 0x3a, 0x85, 0xf0, 0xfa, 0xa3, + 0xe5, 0x78, 0xf8, 0x07, 0x7a, + 0x2e, 0x3f, 0xf4, 0x67, 0x29, + 0x66, 0x5b }; + doHMACTest(std::string(50, 0xcd), secret4, 25, SHA256, hmac_expected4, 32); + + const uint8_t secret5[] = { 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c }; + const uint8_t hmac_expected5[] = { 0xa3, 0xb6, 0x16, 0x74, 0x73, + 0x10, 0x0e, 0xe0, 0x6e, 0x0c, + 0x79, 0x6c, 0x29, 0x55, 0x55, + 0x2b }; + doHMACTest("Test With Truncation", secret5, 20, SHA256, + hmac_expected5, 16); + + const uint8_t hmac_expected6[] = { 0x60, 0xe4, 0x31, 0x59, 0x1e, + 0xe0, 0xb6, 0x7f, 0x0d, 0x8a, + 0x26, 0xaa, 0xcb, 0xf5, 0xb7, + 0x7f, 0x8e, 0x0b, 0xc6, 0x21, + 0x37, 0x28, 0xc5, 0x14, 0x05, + 0x46, 0x04, 0x0f, 0x0e, 0xe3, + 0x7f, 0x54 }; + doHMACTest("Test Using Larger Than Block-Size Key - Hash Key First", + std::string(131, 0xaa).c_str(), 131, SHA256, hmac_expected6, 32); + + const uint8_t hmac_expected7[] = { 0x9b, 0x09, 0xff, 0xa7, 0x1b, + 0x94, 0x2f, 0xcb, 0x27, 0x63, + 0x5f, 0xbc, 0xd5, 0xb0, 0xe9, + 0x44, 0xbf, 0xdc, 0x63, 0x64, + 0x4f, 0x07, 0x13, 0x93, 0x8a, + 0x7f, 0x51, 0x53, 0x5c, 0x3a, + 0x35, 0xe2 }; + doHMACTest("This is a test using a larger than block-size key and a" + " larger than block-size data. The key needs to be hashe" + "d before being used by the HMAC algorithm.", + std::string(131, 0xaa).c_str(), 131, SHA256, hmac_expected7, 32); +} + +namespace { + size_t + sigVectorLength(HashAlgorithm alg, size_t len) { + std::auto_ptr<HMAC> hmac_sign( + CryptoLink::getCryptoLink().createHMAC("asdf", 4, alg)); + //boost::scoped_ptr<HMAC> hmac_sign( + // CryptoLink::getCryptoLink().createHMAC("asdf", 4, alg)); + hmac_sign->update("asdf", 4); + const std::vector<uint8_t> sig = hmac_sign->sign(len); + return (sig.size()); + } + + size_t + sigBufferLength(HashAlgorithm alg, size_t len) { + boost::scoped_ptr<HMAC> hmac_sign( + CryptoLink::getCryptoLink().createHMAC("asdf", 4, alg)); + hmac_sign->update("asdf", 4); + OutputBuffer sig(0); + hmac_sign->sign(sig, len); + return (sig.getLength()); + } +} + +TEST(CryptoLinkTest, HMACSigLengthArgument) { + std::vector<uint8_t> sig; + + EXPECT_EQ(16, sigVectorLength(MD5, 0)); + EXPECT_EQ(8, sigVectorLength(MD5, 8)); + EXPECT_EQ(16, sigVectorLength(MD5, 16)); + EXPECT_EQ(16, sigVectorLength(MD5, 40)); + EXPECT_EQ(16, sigVectorLength(MD5, 2000)); + + EXPECT_EQ(20, sigBufferLength(SHA1, 0)); + EXPECT_EQ(8, sigBufferLength(SHA1, 8)); + EXPECT_EQ(20, sigBufferLength(SHA1, 20)); + EXPECT_EQ(20, sigBufferLength(SHA1, 40)); + EXPECT_EQ(20, sigBufferLength(SHA1, 2000)); + + EXPECT_EQ(32, sigBufferLength(SHA256, 0)); + EXPECT_EQ(8, sigBufferLength(SHA256, 8)); + EXPECT_EQ(32, sigBufferLength(SHA256, 32)); + EXPECT_EQ(32, sigBufferLength(SHA256, 40)); + EXPECT_EQ(32, sigBufferLength(SHA256, 3200)); + + EXPECT_EQ(16, sigBufferLength(MD5, 0)); + EXPECT_EQ(8, sigBufferLength(MD5, 8)); + EXPECT_EQ(16, sigBufferLength(MD5, 16)); + EXPECT_EQ(16, sigBufferLength(MD5, 40)); + EXPECT_EQ(16, sigBufferLength(MD5, 2000)); + + EXPECT_EQ(20, sigBufferLength(SHA1, 0)); + EXPECT_EQ(8, sigBufferLength(SHA1, 8)); + EXPECT_EQ(20, sigBufferLength(SHA1, 20)); + EXPECT_EQ(20, sigBufferLength(SHA1, 40)); + EXPECT_EQ(20, sigBufferLength(SHA1, 2000)); + + EXPECT_EQ(32, sigBufferLength(SHA256, 0)); + EXPECT_EQ(8, sigBufferLength(SHA256, 8)); + EXPECT_EQ(32, sigBufferLength(SHA256, 32)); + EXPECT_EQ(32, sigBufferLength(SHA256, 40)); + EXPECT_EQ(32, sigBufferLength(SHA256, 3200)); +} + +TEST(CryptoLinkTest, BadKey) { + OutputBuffer data_buf(0); + OutputBuffer hmac_sig(0); + CryptoLink& crypto = CryptoLink::getCryptoLink(); + + EXPECT_THROW(crypto.createHMAC(NULL, 0, MD5), BadKey); + EXPECT_THROW(crypto.createHMAC(NULL, 0, UNKNOWN_HASH), UnsupportedAlgorithm); + + EXPECT_THROW(signHMAC(data_buf.getData(), data_buf.getLength(), + NULL, 0, MD5, hmac_sig), BadKey); + EXPECT_THROW(signHMAC(data_buf.getData(), data_buf.getLength(), + NULL, 0, UNKNOWN_HASH, hmac_sig), + UnsupportedAlgorithm); + + EXPECT_THROW(verifyHMAC(data_buf.getData(), data_buf.getLength(), + NULL, 0, MD5, hmac_sig.getData(), + hmac_sig.getLength()), BadKey); + EXPECT_THROW(verifyHMAC(data_buf.getData(), data_buf.getLength(), + NULL, 0, UNKNOWN_HASH, hmac_sig.getData(), + hmac_sig.getLength()), + UnsupportedAlgorithm); +} + +TEST(CryptoLinkTest, Singleton) { + const CryptoLink& c1 = CryptoLink::getCryptoLink(); + const CryptoLink& c2 = CryptoLink::getCryptoLink(); + ASSERT_EQ(&c1, &c2); +} diff --git a/src/lib/cryptolink/tests/run_unittests.cc b/src/lib/cryptolink/tests/run_unittests.cc new file mode 100644 index 0000000000..d16327e0d3 --- /dev/null +++ b/src/lib/cryptolink/tests/run_unittests.cc @@ -0,0 +1,22 @@ +// Copyright (C) 2011 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 <gtest/gtest.h> + +int +main(int argc, char* argv[]) { + ::testing::InitGoogleTest(&argc, argv); + + return (RUN_ALL_TESTS()); +} diff --git a/src/lib/dns/Makefile.am b/src/lib/dns/Makefile.am index 5a7151ed0e..3cfc871c90 100644 --- a/src/lib/dns/Makefile.am +++ b/src/lib/dns/Makefile.am @@ -80,12 +80,18 @@ libdns___la_SOURCES += rrsetlist.h rrsetlist.cc libdns___la_SOURCES += rrttl.h rrttl.cc libdns___la_SOURCES += rrtype.cc libdns___la_SOURCES += question.h question.cc +libdns___la_SOURCES += tsig.h tsig.cc +libdns___la_SOURCES += tsigerror.h tsigerror.cc libdns___la_SOURCES += tsigkey.h tsigkey.cc libdns___la_SOURCES += rdata/generic/detail/nsec_bitmap.h libdns___la_SOURCES += rdata/generic/detail/nsec_bitmap.cc libdns___la_CPPFLAGS = $(AM_CPPFLAGS) -libdns___la_LIBADD = $(top_builddir)/src/lib/util/libutil.la +# Most applications of libdns++ will only implicitly rely on libcryptolink, +# so we add the dependency here so that the applications don't have to link +# libcryptolink explicitly. +libdns___la_LIBADD = $(top_builddir)/src/lib/cryptolink/libcryptolink.la +libdns___la_LIBADD += $(top_builddir)/src/lib/util/libutil.la nodist_libdns___la_SOURCES = rdataclass.cc rrclass.h rrtype.h nodist_libdns___la_SOURCES += rrparamregistry.cc diff --git a/src/lib/dns/python/tests/Makefile.am b/src/lib/dns/python/tests/Makefile.am index 272f1c1442..184f06d7b5 100644 --- a/src/lib/dns/python/tests/Makefile.am +++ b/src/lib/dns/python/tests/Makefile.am @@ -20,7 +20,7 @@ EXTRA_DIST += testutil.py # required by loadable python modules. LIBRARY_PATH_PLACEHOLDER = if SET_ENV_LIBRARY_PATH -LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$$$(ENV_LIBRARY_PATH) +LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$$$(ENV_LIBRARY_PATH) endif # test using command-line arguments, so use check-local target instead of TESTS diff --git a/src/lib/dns/python/tests/tsigkey_python_test.py b/src/lib/dns/python/tests/tsigkey_python_test.py index 06e68687ee..97be501e04 100644 --- a/src/lib/dns/python/tests/tsigkey_python_test.py +++ b/src/lib/dns/python/tests/tsigkey_python_test.py @@ -44,6 +44,28 @@ class TSIGKeyTest(unittest.TestCase): TSIGKey.HMACMD5_NAME, 'should be binary') # signature mismatch + def test_str(self): + k1 = TSIGKey('test.example:CwsLCwsLCwsLCwsLCwsLCw==:hmac-md5.sig-alg.reg.int') + self.assertEqual(Name('test.example.'), k1.get_key_name()) + self.assertEqual(Name('hmac-md5.sig-alg.reg.int.'), k1.get_algorithm_name()) + self.assertEqual(b'\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b', + k1.get_secret()) + self.assertEqual('test.example.:CwsLCwsLCwsLCwsLCwsLCw==:hmac-md5.sig-alg.reg.int.', + k1.to_text()) + + self.assertRaises(InvalidParameter, TSIGKey, + 'test.example:CwsLCwsLCwsLCwsLCwsLCw==:unsupported') + self.assertRaises(InvalidParameter, TSIGKey, + '::') + self.assertRaises(InvalidParameter, TSIGKey, + 'test.example:') + self.assertRaises(InvalidParameter, TSIGKey, + 'test.example:%bad_base_64%') + self.assertRaises(InvalidParameter, TSIGKey, + 'test.example:CwsLCwsLCwsLCwsLCwsLCw==:') + self.assertRaises(InvalidParameter, TSIGKey, + 'test.:example:CwsLCwsLCwsLCwsLCwsLCw==') + class TSIGKeyRingTest(unittest.TestCase): key_name = Name('example.com') secret = b'someRandomData' diff --git a/src/lib/dns/python/tsigkey_python.cc b/src/lib/dns/python/tsigkey_python.cc index aa87909214..906dfc0177 100644 --- a/src/lib/dns/python/tsigkey_python.cc +++ b/src/lib/dns/python/tsigkey_python.cc @@ -55,6 +55,7 @@ void TSIGKey_destroy(s_TSIGKey* self); PyObject* TSIGKey_getKeyName(const s_TSIGKey* self); PyObject* TSIGKey_getAlgorithmName(const s_TSIGKey* self); PyObject* TSIGKey_getSecret(const s_TSIGKey* self); +PyObject* TSIGKey_toText(const s_TSIGKey* self); // This list contains the actual set of functions we have in // python. Each entry has @@ -72,6 +73,8 @@ PyMethodDef TSIGKey_methods[] = { { "get_secret", reinterpret_cast<PyCFunction>(TSIGKey_getSecret), METH_NOARGS, "Return the value of the TSIG secret." }, + { "to_text", reinterpret_cast<PyCFunction>(TSIGKey_toText), METH_NOARGS, + "Returns the string representation (name:secret:algorithm)" }, { NULL, NULL, 0, NULL } }; @@ -148,27 +151,33 @@ createNameObject(const Name& source) { int TSIGKey_init(s_TSIGKey* self, PyObject* args) { + const char* str; + const s_Name* key_name; const s_Name* algorithm_name; PyObject* bytes_obj; const char* secret; Py_ssize_t secret_len; - if (PyArg_ParseTuple(args, "O!O!O", &name_type, &key_name, + + try { + if (PyArg_ParseTuple(args, "s", &str)) { + self->tsigkey = new TSIGKey(str); + return (0); + } else if (PyArg_ParseTuple(args, "O!O!O", &name_type, &key_name, &name_type, &algorithm_name, &bytes_obj) && - PyObject_AsCharBuffer(bytes_obj, &secret, &secret_len) != -1) { - try { - self->tsigkey = new TSIGKey(*key_name->name, - *algorithm_name->name, - secret, secret_len); - } catch (const isc::InvalidParameter& ex) { - PyErr_SetString(po_InvalidParameter, ex.what()); - return (-1); - } catch (...) { - PyErr_SetString(po_IscException, "Unexpected exception"); - return (-1); + PyObject_AsCharBuffer(bytes_obj, &secret, &secret_len) != -1) { + self->tsigkey = new TSIGKey(*key_name->name, + *algorithm_name->name, + secret, secret_len); + return (0); } - return (0); + } catch (const isc::InvalidParameter& ex) { + PyErr_SetString(po_InvalidParameter, ex.what()); + return (-1); + } catch (...) { + PyErr_SetString(po_IscException, "Unexpected exception"); + return (-1); } PyErr_Clear(); @@ -201,6 +210,11 @@ TSIGKey_getSecret(const s_TSIGKey* const self) { self->tsigkey->getSecretLength())); } +PyObject* +TSIGKey_toText(const s_TSIGKey* self) { + return (Py_BuildValue("s", self->tsigkey->toText().c_str())); +} + // Module Initialization, all statics are initialized here bool initModulePart_TSIGKey(PyObject* mod) { diff --git a/src/lib/dns/tests/Makefile.am b/src/lib/dns/tests/Makefile.am index f5dd5123cb..d49feb32ad 100644 --- a/src/lib/dns/tests/Makefile.am +++ b/src/lib/dns/tests/Makefile.am @@ -47,6 +47,8 @@ run_unittests_SOURCES += question_unittest.cc run_unittests_SOURCES += rrparamregistry_unittest.cc run_unittests_SOURCES += masterload_unittest.cc run_unittests_SOURCES += message_unittest.cc +run_unittests_SOURCES += tsig_unittest.cc +run_unittests_SOURCES += tsigerror_unittest.cc run_unittests_SOURCES += tsigkey_unittest.cc run_unittests_SOURCES += run_unittests.cc run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES) @@ -54,6 +56,7 @@ run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS) run_unittests_LDADD = $(GTEST_LDADD) run_unittests_LDADD += $(top_builddir)/src/lib/dns/libdns++.la run_unittests_LDADD += $(top_builddir)/src/lib/util/libutil.la +run_unittests_LDADD += $(top_builddir)/src/lib/util/unittests/libutil_unittests.la run_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la endif diff --git a/src/lib/dns/tests/tsig_unittest.cc b/src/lib/dns/tests/tsig_unittest.cc new file mode 100644 index 0000000000..28189cc648 --- /dev/null +++ b/src/lib/dns/tests/tsig_unittest.cc @@ -0,0 +1,505 @@ +// Copyright (C) 2011 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 <time.h> +#include <string> +#include <stdexcept> +#include <vector> + +#include <boost/scoped_ptr.hpp> + +#include <gtest/gtest.h> + +#include <exceptions/exceptions.h> + +#include <util/buffer.h> +#include <util/encode/base64.h> +#include <util/unittests/newhook.h> + +#include <dns/message.h> +#include <dns/messagerenderer.h> +#include <dns/question.h> +#include <dns/opcode.h> +#include <dns/rcode.h> +#include <dns/rrclass.h> +#include <dns/rrtype.h> +#include <dns/tsig.h> +#include <dns/tsigkey.h> + +#include <dns/tests/unittest_util.h> + +using namespace std; +using namespace isc; +using namespace isc::dns; +using namespace isc::util; +using namespace isc::util::encode; +using namespace isc::dns::rdata; +using isc::UnitTestUtil; + +// See dnssectime.cc +namespace isc { +namespace dns { +namespace tsig { +namespace detail { +extern int64_t (*gettimeFunction)(); +} +} +} +} + +namespace { +// See dnssectime_unittest.cc +template <int64_t NOW> +int64_t +testGetTime() { + return (NOW); +} + +class TSIGTest : public ::testing::Test { +protected: + TSIGTest() : + tsig_ctx(NULL), qid(0x2d65), test_name("www.example.com"), + test_class(RRClass::IN()), test_ttl(86400), message(Message::RENDER), + buffer(0), renderer(buffer) + { + // Make sure we use the system time by default so that we won't be + // confused due to other tests that tweak the time. + tsig::detail::gettimeFunction = NULL; + + decodeBase64("SFuWd/q99SzF8Yzd1QbB9g==", secret); + tsig_ctx.reset(new TSIGContext(TSIGKey(test_name, + TSIGKey::HMACMD5_NAME(), + &secret[0], secret.size()))); + tsig_verify_ctx.reset(new TSIGContext(TSIGKey(test_name, + TSIGKey::HMACMD5_NAME(), + &secret[0], + secret.size()))); + } + ~TSIGTest() { + tsig::detail::gettimeFunction = NULL; + } + + // Many of the tests below create some DNS message and sign it under + // some specific TSIG context. This helper method unifies the common + // logic with slightly different parameters. + ConstTSIGRecordPtr createMessageAndSign(uint16_t qid, const Name& qname, + TSIGContext* ctx, + unsigned int message_flags = + RD_FLAG, + RRType qtype = RRType::A(), + const char* answer_data = NULL, + const RRType* answer_type = NULL, + bool add_question = true, + Rcode rcode = Rcode::NOERROR()); + + // bit-wise constant flags to configure DNS header flags for test + // messages. + static const unsigned int QR_FLAG = 0x1; + static const unsigned int AA_FLAG = 0x2; + static const unsigned int RD_FLAG = 0x4; + + boost::scoped_ptr<TSIGContext> tsig_ctx; + boost::scoped_ptr<TSIGContext> tsig_verify_ctx; + const uint16_t qid; + const Name test_name; + const RRClass test_class; + const RRTTL test_ttl; + Message message; + OutputBuffer buffer; + MessageRenderer renderer; + vector<uint8_t> secret; +}; + +ConstTSIGRecordPtr +TSIGTest::createMessageAndSign(uint16_t id, const Name& qname, + TSIGContext* ctx, unsigned int message_flags, + RRType qtype, const char* answer_data, + const RRType* answer_type, bool add_question, + Rcode rcode) +{ + message.clear(Message::RENDER); + message.setQid(id); + message.setOpcode(Opcode::QUERY()); + message.setRcode(rcode); + if ((message_flags & QR_FLAG) != 0) { + message.setHeaderFlag(Message::HEADERFLAG_QR); + } + if ((message_flags & AA_FLAG) != 0) { + message.setHeaderFlag(Message::HEADERFLAG_AA); + } + if ((message_flags & RD_FLAG) != 0) { + message.setHeaderFlag(Message::HEADERFLAG_RD); + } + if (add_question) { + message.addQuestion(Question(qname, test_class, qtype)); + } + if (answer_data != NULL) { + if (answer_type == NULL) { + answer_type = &qtype; + } + RRsetPtr answer_rrset(new RRset(qname, test_class, *answer_type, + test_ttl)); + answer_rrset->addRdata(createRdata(*answer_type, test_class, + answer_data)); + message.addRRset(Message::SECTION_ANSWER, answer_rrset); + } + renderer.clear(); + message.toWire(renderer); + + ConstTSIGRecordPtr tsig = ctx->sign(id, renderer.getData(), + renderer.getLength()); + EXPECT_EQ(TSIGContext::SIGNED, ctx->getState()); + + return (tsig); +} + +void +commonTSIGChecks(ConstTSIGRecordPtr tsig, uint16_t expected_qid, + uint64_t expected_timesigned, + const uint8_t* expected_mac, size_t expected_maclen, + uint16_t expected_error = 0, + uint16_t expected_otherlen = 0, + const uint8_t* expected_otherdata = NULL, + const Name& expected_algorithm = TSIGKey::HMACMD5_NAME()) +{ + ASSERT_TRUE(tsig != NULL); + const any::TSIG& tsig_rdata = tsig->getRdata(); + + EXPECT_EQ(expected_algorithm, tsig_rdata.getAlgorithm()); + EXPECT_EQ(expected_timesigned, tsig_rdata.getTimeSigned()); + EXPECT_EQ(300, tsig_rdata.getFudge()); + EXPECT_EQ(expected_maclen, tsig_rdata.getMACSize()); + EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, + tsig_rdata.getMAC(), tsig_rdata.getMACSize(), + expected_mac, expected_maclen); + EXPECT_EQ(expected_qid, tsig_rdata.getOriginalID()); + EXPECT_EQ(expected_error, tsig_rdata.getError()); + EXPECT_EQ(expected_otherlen, tsig_rdata.getOtherLen()); + EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, + tsig_rdata.getOtherData(), tsig_rdata.getOtherLen(), + expected_otherdata, expected_otherlen); +} + +TEST_F(TSIGTest, initialState) { + // Until signing or verifying, the state should be INIT + EXPECT_EQ(TSIGContext::INIT, tsig_ctx->getState()); + + // And there should be no error code. + EXPECT_EQ(TSIGError(Rcode::NOERROR()), tsig_ctx->getError()); +} + +// Example output generated by +// "dig -y www.example.com:SFuWd/q99SzF8Yzd1QbB9g== www.example.com +// QID: 0x2d65 +// Time Signed: 0x00004da8877a +// MAC: 227026ad297beee721ce6c6fff1e9ef3 +const uint8_t common_expected_mac[] = { + 0x22, 0x70, 0x26, 0xad, 0x29, 0x7b, 0xee, 0xe7, + 0x21, 0xce, 0x6c, 0x6f, 0xff, 0x1e, 0x9e, 0xf3 +}; +TEST_F(TSIGTest, sign) { + tsig::detail::gettimeFunction = testGetTime<0x4da8877a>; + + { + SCOPED_TRACE("Sign test for query"); + commonTSIGChecks(createMessageAndSign(qid, test_name, tsig_ctx.get()), + qid, 0x4da8877a, common_expected_mac, + sizeof(common_expected_mac)); + } +} + +// Same test as sign, but specifying the key name with upper-case (i.e. +// non canonical) characters. The digest must be the same. It should actually +// be ensured at the level of TSIGKey, but we confirm that at this level, too. +TEST_F(TSIGTest, signUsingUpperCasedKeyName) { + tsig::detail::gettimeFunction = testGetTime<0x4da8877a>; + + TSIGContext cap_ctx(TSIGKey(Name("WWW.EXAMPLE.COM"), + TSIGKey::HMACMD5_NAME(), + &secret[0], secret.size())); + + { + SCOPED_TRACE("Sign test for query using non canonical key name"); + commonTSIGChecks(createMessageAndSign(qid, test_name, &cap_ctx), qid, + 0x4da8877a, common_expected_mac, + sizeof(common_expected_mac)); + } +} + +// Same as the previous test, but for the algorithm name. +TEST_F(TSIGTest, signUsingUpperCasedAlgorithmName) { + tsig::detail::gettimeFunction = testGetTime<0x4da8877a>; + + TSIGContext cap_ctx(TSIGKey(test_name, + Name("HMAC-md5.SIG-alg.REG.int"), + &secret[0], secret.size())); + + { + SCOPED_TRACE("Sign test for query using non canonical algorithm name"); + commonTSIGChecks(createMessageAndSign(qid, test_name, &cap_ctx), qid, + 0x4da8877a, common_expected_mac, + sizeof(common_expected_mac)); + } +} + +TEST_F(TSIGTest, signAtActualTime) { + // Sign the message using the actual time, and check the accuracy of it. + // We cannot reasonably predict the expected MAC, so don't bother to + // check it. + const uint64_t now = static_cast<uint64_t>(time(NULL)); + + { + SCOPED_TRACE("Sign test for query at actual time"); + ConstTSIGRecordPtr tsig = createMessageAndSign(qid, test_name, + tsig_ctx.get()); + const any::TSIG& tsig_rdata = tsig->getRdata(); + + // Check the resulted time signed is in the range of [now, now + 5] + // (5 is an arbitrary choice). Note that due to the order of the call + // to time() and sign(), time signed must not be smaller than the + // current time. + EXPECT_LE(now, tsig_rdata.getTimeSigned()); + EXPECT_GE(now + 5, tsig_rdata.getTimeSigned()); + } +} + +TEST_F(TSIGTest, signBadData) { + // some specific bad data should be rejected proactively. + const unsigned char dummy_data = 0; + EXPECT_THROW(tsig_ctx->sign(0, NULL, 10), InvalidParameter); + EXPECT_THROW(tsig_ctx->sign(0, &dummy_data, 0), InvalidParameter); +} + +#ifdef ENABLE_CUSTOM_OPERATOR_NEW +// We enable this test only when we enable custom new/delete at build time +// We could enable/disable the test runtime using the gtest filter, but +// we'd basically like to minimize the number of disabled tests (they +// should generally be considered tests that temporarily fail and should +// be fixed). +TEST_F(TSIGTest, signExceptionSafety) { + // Check sign() provides the strong exception guarantee for the simpler + // case (with a key error and empty MAC). The general case is more + // complicated and involves more memory allocation, so the test result + // won't be reliable. + + tsig_verify_ctx->verifyTentative(createMessageAndSign(qid, test_name, + tsig_ctx.get()), + TSIGError::BAD_KEY()); + // At this point the state should be changed to "CHECKED" + ASSERT_EQ(TSIGContext::CHECKED, tsig_verify_ctx->getState()); + try { + int dummydata; + isc::util::unittests::force_throw_on_new = true; + isc::util::unittests::throw_size_on_new = sizeof(TSIGRecord); + tsig_verify_ctx->sign(0, &dummydata, sizeof(dummydata)); + isc::util::unittests::force_throw_on_new = false; + ASSERT_FALSE(true) << "Expected throw on new, but it didn't happen"; + } catch (const std::bad_alloc&) { + isc::util::unittests::force_throw_on_new = false; + + // sign() threw, so the state should still be "CHECKED". + EXPECT_EQ(TSIGContext::CHECKED, tsig_verify_ctx->getState()); + } + isc::util::unittests::force_throw_on_new = false; +} +#endif // ENABLE_CUSTOM_OPERATOR_NEW + +// Same test as "sign" but use a different algorithm just to confirm we don't +// naively hardcode constants specific to a particular algorithm. +// Test data generated by +// "dig -y hmac-sha1:www.example.com:MA+QDhXbyqUak+qnMFyTyEirzng= www.example.com" +// QID: 0x0967, RDflag +// Current Time: 00004da8be86 +// Time Signed: 00004dae7d5f +// HMAC Size: 20 +// HMAC: 415340c7daf824ed684ee586f7b5a67a2febc0d3 +TEST_F(TSIGTest, signUsingHMACSHA1) { + tsig::detail::gettimeFunction = testGetTime<0x4dae7d5f>; + + secret.clear(); + decodeBase64("MA+QDhXbyqUak+qnMFyTyEirzng=", secret); + TSIGContext sha1_ctx(TSIGKey(test_name, TSIGKey::HMACSHA1_NAME(), + &secret[0], secret.size())); + + const uint16_t sha1_qid = 0x0967; + const uint8_t expected_mac[] = { + 0x41, 0x53, 0x40, 0xc7, 0xda, 0xf8, 0x24, 0xed, 0x68, 0x4e, + 0xe5, 0x86, 0xf7, 0xb5, 0xa6, 0x7a, 0x2f, 0xeb, 0xc0, 0xd3 + }; + { + SCOPED_TRACE("Sign test using HMAC-SHA1"); + commonTSIGChecks(createMessageAndSign(sha1_qid, test_name, &sha1_ctx), + sha1_qid, 0x4dae7d5f, expected_mac, + sizeof(expected_mac), 0, 0, NULL, + TSIGKey::HMACSHA1_NAME()); + } +} + +// An example response to the signed query used for the "sign" test. +// Answer: www.example.com. 86400 IN A 192.0.2.1 +// MAC: 8fcda66a7cd1a3b9948eb1869d384a9f +TEST_F(TSIGTest, signResponse) { + tsig::detail::gettimeFunction = testGetTime<0x4da8877a>; + + ConstTSIGRecordPtr tsig = createMessageAndSign(qid, test_name, + tsig_ctx.get()); + tsig_verify_ctx->verifyTentative(tsig); + EXPECT_EQ(TSIGContext::CHECKED, tsig_verify_ctx->getState()); + + // Transform the original message to a response, then sign the response + // with the context of "verified state". + tsig = createMessageAndSign(qid, test_name, tsig_verify_ctx.get(), + QR_FLAG|AA_FLAG|RD_FLAG, + RRType::A(), "192.0.2.1"); + const uint8_t expected_mac[] = { + 0x8f, 0xcd, 0xa6, 0x6a, 0x7c, 0xd1, 0xa3, 0xb9, + 0x94, 0x8e, 0xb1, 0x86, 0x9d, 0x38, 0x4a, 0x9f + }; + { + SCOPED_TRACE("Sign test for response"); + commonTSIGChecks(tsig, qid, 0x4da8877a, + expected_mac, sizeof(expected_mac)); + } +} + +// Example of signing multiple messages in a single TCP stream, +// taken from data using BIND 9's "one-answer" transfer-format. +// First message: +// QID: 0x3410, flags QR, AA +// Question: example.com/IN/AXFR +// Answer: example.com. 86400 IN SOA ns.example.com. root.example.com. ( +// 2011041503 7200 3600 2592000 1200) +// Time Signed: 0x4da8e951 +// Second message: +// Answer: example.com. 86400 IN NS ns.example.com. +// MAC: 102458f7f62ddd7d638d746034130968 +TEST_F(TSIGTest, signContinuation) { + tsig::detail::gettimeFunction = testGetTime<0x4da8e951>; + + const uint16_t axfr_qid = 0x3410; + const Name zone_name("example.com"); + + // Create and sign the AXFR request, then verify it. + tsig_verify_ctx->verifyTentative(createMessageAndSign(axfr_qid, zone_name, + tsig_ctx.get(), 0, + RRType::AXFR())); + EXPECT_EQ(TSIGContext::CHECKED, tsig_verify_ctx->getState()); + + // Create and sign the first response message (we don't need the result + // for the purpose of this test) + createMessageAndSign(axfr_qid, zone_name, tsig_verify_ctx.get(), + AA_FLAG|QR_FLAG, RRType::AXFR(), + "ns.example.com. root.example.com. " + "2011041503 7200 3600 2592000 1200", + &RRType::SOA()); + + // Create and sign the second response message + const uint8_t expected_mac[] = { + 0x10, 0x24, 0x58, 0xf7, 0xf6, 0x2d, 0xdd, 0x7d, + 0x63, 0x8d, 0x74, 0x60, 0x34, 0x13, 0x09, 0x68 + }; + { + SCOPED_TRACE("Sign test for continued response in TCP stream"); + commonTSIGChecks(createMessageAndSign(axfr_qid, zone_name, + tsig_verify_ctx.get(), + AA_FLAG|QR_FLAG, RRType::AXFR(), + "ns.example.com.", &RRType::NS(), + false), + axfr_qid, 0x4da8e951, + expected_mac, sizeof(expected_mac)); + } +} + +// BADTIME example, taken from data using specially hacked BIND 9's nsupdate +// Query: +// QID: 0x1830, RD flag +// Current Time: 00004da8be86 +// Time Signed: 00004da8b9d6 +// Question: www.example.com/IN/SOA +//(mac) 8406 7d50 b8e7 d054 3d50 5bd9 de2a bb68 +// Response: +// QRbit, RCODE=9(NOTAUTH) +// Time Signed: 00004da8b9d6 (the one in the query) +// MAC: d4b043f6f44495ec8a01260e39159d76 +// Error: 0x12 (BADTIME), Other Len: 6 +// Other data: 00004da8be86 +TEST_F(TSIGTest, badtimeResponse) { + tsig::detail::gettimeFunction = testGetTime<0x4da8b9d6>; + + const uint16_t test_qid = 0x7fc4; + ConstTSIGRecordPtr tsig = createMessageAndSign(test_qid, test_name, + tsig_ctx.get(), 0, + RRType::SOA()); + + // "advance the clock" and try validating, which should fail due to BADTIME + // (verifyTentative actually doesn't check the time, though) + tsig::detail::gettimeFunction = testGetTime<0x4da8be86>; + tsig_verify_ctx->verifyTentative(tsig, TSIGError::BAD_TIME()); + EXPECT_EQ(TSIGError::BAD_TIME(), tsig_verify_ctx->getError()); + + // make and sign a response in the context of TSIG error. + tsig = createMessageAndSign(test_qid, test_name, tsig_verify_ctx.get(), + QR_FLAG, RRType::SOA(), NULL, NULL, + true, Rcode::NOTAUTH()); + const uint8_t expected_otherdata[] = { 0, 0, 0x4d, 0xa8, 0xbe, 0x86 }; + const uint8_t expected_mac[] = { + 0xd4, 0xb0, 0x43, 0xf6, 0xf4, 0x44, 0x95, 0xec, + 0x8a, 0x01, 0x26, 0x0e, 0x39, 0x15, 0x9d, 0x76 + }; + { + SCOPED_TRACE("Sign test for response with BADTIME"); + commonTSIGChecks(tsig, message.getQid(), 0x4da8b9d6, + expected_mac, sizeof(expected_mac), + 18, // error: BADTIME + sizeof(expected_otherdata), + expected_otherdata); + } +} + +TEST_F(TSIGTest, badsigResponse) { + tsig::detail::gettimeFunction = testGetTime<0x4da8877a>; + + // Sign a simple message, and force the verification to fail with + // BADSIG. + tsig_verify_ctx->verifyTentative(createMessageAndSign(qid, test_name, + tsig_ctx.get()), + TSIGError::BAD_SIG()); + + // Sign the same message (which doesn't matter for this test) with the + // context of "checked state". + { + SCOPED_TRACE("Sign test for response with BADSIG error"); + commonTSIGChecks(createMessageAndSign(qid, test_name, + tsig_verify_ctx.get()), + message.getQid(), 0x4da8877a, NULL, 0, + 16); // 16: BADSIG + } +} + +TEST_F(TSIGTest, badkeyResponse) { + // A similar test as badsigResponse but for BADKEY + tsig::detail::gettimeFunction = testGetTime<0x4da8877a>; + tsig_verify_ctx->verifyTentative(createMessageAndSign(qid, test_name, + tsig_ctx.get()), + TSIGError::BAD_KEY()); + { + SCOPED_TRACE("Sign test for response with BADKEY error"); + commonTSIGChecks(createMessageAndSign(qid, test_name, + tsig_verify_ctx.get()), + message.getQid(), 0x4da8877a, NULL, 0, + 17); // 17: BADKEYSIG + } +} + +} // end namespace diff --git a/src/lib/dns/tests/tsigerror_unittest.cc b/src/lib/dns/tests/tsigerror_unittest.cc new file mode 100644 index 0000000000..58665878f4 --- /dev/null +++ b/src/lib/dns/tests/tsigerror_unittest.cc @@ -0,0 +1,102 @@ +// Copyright (C) 2011 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 <string> +#include <ostream> + +#include <gtest/gtest.h> + +#include <exceptions/exceptions.h> + +#include <dns/rcode.h> +#include <dns/tsigerror.h> + +using namespace std; +using namespace isc; +using namespace isc::dns; + +namespace { +TEST(TSIGErrorTest, constructFromErrorCode) { + // These are pretty trivial, and also test getCode(); + EXPECT_EQ(0, TSIGError(0).getCode()); + EXPECT_EQ(18, TSIGError(18).getCode()); + EXPECT_EQ(65535, TSIGError(65535).getCode()); +} + +TEST(TSIGErrorTest, constructFromRcode) { + // We use RCODE for code values from 0-15. + EXPECT_EQ(0, TSIGError(Rcode::NOERROR()).getCode()); + EXPECT_EQ(15, TSIGError(Rcode(15)).getCode()); + + // From error code 16 TSIG errors define a separate space, so passing + // corresponding RCODE for such code values should be prohibited. + EXPECT_THROW(TSIGError(Rcode(16)).getCode(), OutOfRange); +} + +TEST(TSIGErrorTest, constants) { + // We'll only test arbitrarily chosen subsets of the codes. + // This class is quite simple, so it should be suffice. + + EXPECT_EQ(TSIGError::BAD_SIG_CODE, TSIGError(16).getCode()); + EXPECT_EQ(TSIGError::BAD_KEY_CODE, TSIGError(17).getCode()); + EXPECT_EQ(TSIGError::BAD_TIME_CODE, TSIGError(18).getCode()); + + EXPECT_EQ(0, TSIGError::NOERROR().getCode()); + EXPECT_EQ(9, TSIGError::NOTAUTH().getCode()); + EXPECT_EQ(14, TSIGError::RESERVED14().getCode()); + EXPECT_EQ(TSIGError::BAD_SIG_CODE, TSIGError::BAD_SIG().getCode()); + EXPECT_EQ(TSIGError::BAD_KEY_CODE, TSIGError::BAD_KEY().getCode()); + EXPECT_EQ(TSIGError::BAD_TIME_CODE, TSIGError::BAD_TIME().getCode()); +} + +TEST(TSIGErrorTest, equal) { + EXPECT_TRUE(TSIGError::NOERROR() == TSIGError(Rcode::NOERROR())); + EXPECT_TRUE(TSIGError(Rcode::NOERROR()) == TSIGError::NOERROR()); + EXPECT_TRUE(TSIGError::NOERROR().equals(TSIGError(Rcode::NOERROR()))); + EXPECT_TRUE(TSIGError::NOERROR().equals(TSIGError(Rcode::NOERROR()))); + + EXPECT_TRUE(TSIGError::BAD_SIG() == TSIGError(16)); + EXPECT_TRUE(TSIGError(16) == TSIGError::BAD_SIG()); + EXPECT_TRUE(TSIGError::BAD_SIG().equals(TSIGError(16))); + EXPECT_TRUE(TSIGError(16).equals(TSIGError::BAD_SIG())); +} + +TEST(TSIGErrorTest, nequal) { + EXPECT_TRUE(TSIGError::BAD_KEY() != TSIGError(Rcode::NOERROR())); + EXPECT_TRUE(TSIGError(Rcode::NOERROR()) != TSIGError::BAD_KEY()); + EXPECT_TRUE(TSIGError::BAD_KEY().nequals(TSIGError(Rcode::NOERROR()))); + EXPECT_TRUE(TSIGError(Rcode::NOERROR()).nequals(TSIGError::BAD_KEY())); +} + +TEST(TSIGErrorTest, toText) { + // TSIGError derived from the standard Rcode + EXPECT_EQ("NOERROR", TSIGError(Rcode::NOERROR()).toText()); + + // Well known TSIG errors + EXPECT_EQ("BADSIG", TSIGError::BAD_SIG().toText()); + EXPECT_EQ("BADKEY", TSIGError::BAD_KEY().toText()); + EXPECT_EQ("BADTIME", TSIGError::BAD_TIME().toText()); + + // Unknown (or not yet supported) codes. Simply converted as numeric. + EXPECT_EQ("19", TSIGError(19).toText()); + EXPECT_EQ("65535", TSIGError(65535).toText()); +} + +// test operator<<. We simply confirm it appends the result of toText(). +TEST(TSIGErrorTest, LeftShiftOperator) { + ostringstream oss; + oss << TSIGError::BAD_KEY(); + EXPECT_EQ(TSIGError::BAD_KEY().toText(), oss.str()); +} +} // end namespace diff --git a/src/lib/dns/tests/tsigkey_unittest.cc b/src/lib/dns/tests/tsigkey_unittest.cc index 6b2b8c5832..0354cb1b8e 100644 --- a/src/lib/dns/tests/tsigkey_unittest.cc +++ b/src/lib/dns/tests/tsigkey_unittest.cc @@ -18,6 +18,8 @@ #include <exceptions/exceptions.h> +#include <cryptolink/cryptolink.h> + #include <dns/tsigkey.h> #include <dns/tests/unittest_util.h> @@ -38,6 +40,15 @@ TEST_F(TSIGKeyTest, algorithmNames) { EXPECT_EQ(Name("hmac-md5.sig-alg.reg.int"), TSIGKey::HMACMD5_NAME()); EXPECT_EQ(Name("hmac-sha1"), TSIGKey::HMACSHA1_NAME()); EXPECT_EQ(Name("hmac-sha256"), TSIGKey::HMACSHA256_NAME()); + + // Also check conversion to cryptolink definitions + EXPECT_EQ(isc::cryptolink::MD5, TSIGKey(key_name, TSIGKey::HMACMD5_NAME(), + NULL, 0).getCryptoAlgorithm()); + EXPECT_EQ(isc::cryptolink::SHA1, TSIGKey(key_name, TSIGKey::HMACSHA1_NAME(), + NULL, 0).getCryptoAlgorithm()); + EXPECT_EQ(isc::cryptolink::SHA256, TSIGKey(key_name, + TSIGKey::HMACSHA256_NAME(), + NULL, 0).getCryptoAlgorithm()); } TEST_F(TSIGKeyTest, construct) { @@ -58,6 +69,11 @@ TEST_F(TSIGKeyTest, construct) { secret.c_str(), secret.size()).getAlgorithmName().toText()); + EXPECT_EQ("example.com.", + TSIGKey(Name("EXAMPLE.CoM."), TSIGKey::HMACSHA256_NAME(), + secret.c_str(), + secret.size()).getKeyName().toText()); + // Invalid combinations of secret and secret_len: EXPECT_THROW(TSIGKey(key_name, TSIGKey::HMACSHA1_NAME(), secret.c_str(), 0), isc::InvalidParameter); @@ -227,4 +243,31 @@ TEST_F(TSIGKeyRingTest, findFromSome) { keyring.find(Name("noexist.example")).key); } +TEST(TSIGStringTest, TSIGKeyFromToString) { + TSIGKey k1 = TSIGKey("test.example:MSG6Ng==:hmac-md5.sig-alg.reg.int"); + TSIGKey k2 = TSIGKey("test.example.:MSG6Ng==:hmac-md5.sig-alg.reg.int."); + TSIGKey k3 = TSIGKey("test.example:MSG6Ng=="); + TSIGKey k4 = TSIGKey(Name("test.example."), Name("hmac-sha1."), NULL, 0); + + EXPECT_EQ("test.example.:MSG6Ng==:hmac-md5.sig-alg.reg.int.", + k1.toText()); + EXPECT_EQ("test.example.:MSG6Ng==:hmac-md5.sig-alg.reg.int.", + k2.toText()); + EXPECT_EQ("test.example.:MSG6Ng==:hmac-md5.sig-alg.reg.int.", + k3.toText()); + EXPECT_EQ("test.example.::hmac-sha1.", k4.toText()); + + EXPECT_THROW(TSIGKey(""), isc::InvalidParameter); + EXPECT_THROW(TSIGKey(":"), isc::InvalidParameter); + EXPECT_THROW(TSIGKey("::"), isc::InvalidParameter); + EXPECT_THROW(TSIGKey("..:aa:"), isc::InvalidParameter); + EXPECT_THROW(TSIGKey("test.example:xxxx:"), isc::InvalidParameter); + EXPECT_THROW(TSIGKey("test.example.::"), isc::InvalidParameter); + EXPECT_THROW(TSIGKey("test.example.:"), isc::InvalidParameter); + EXPECT_THROW(TSIGKey("test.example.:MSG6Ng==:"), isc::InvalidParameter); + EXPECT_THROW(TSIGKey("test.example.:MSG6Ng==:unknown"), isc::InvalidParameter); + +} + + } // end namespace diff --git a/src/lib/dns/tsig.cc b/src/lib/dns/tsig.cc new file mode 100644 index 0000000000..6e24eb67d6 --- /dev/null +++ b/src/lib/dns/tsig.cc @@ -0,0 +1,226 @@ +// Copyright (C) 2011 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 <sys/time.h> + +#include <stdint.h> + +#include <cassert> // for the tentative verifyTentative() +#include <vector> + +#include <boost/shared_ptr.hpp> + +#include <exceptions/exceptions.h> + +#include <util/buffer.h> + +#include <dns/rdataclass.h> +#include <dns/rrclass.h> +#include <dns/tsig.h> +#include <dns/tsigerror.h> +#include <dns/tsigkey.h> + +#include <cryptolink/cryptolink.h> +#include <cryptolink/crypto_hmac.h> + +using namespace std; +using namespace isc::util; +using namespace isc::cryptolink; +using namespace isc::dns::rdata; + +namespace isc { +namespace dns { + +// Borrowed from dnssectime.cc. This trick should be unified somewhere. +namespace tsig { +namespace detail { +int64_t (*gettimeFunction)() = NULL; +} +} + +namespace { +int64_t +gettimeofdayWrapper() { + using namespace tsig::detail; + if (gettimeFunction != NULL) { + return (gettimeFunction()); + } + + struct timeval now; + gettimeofday(&now, NULL); + + return (static_cast<int64_t>(now.tv_sec)); +} +} + +namespace { +typedef boost::shared_ptr<HMAC> HMACPtr; +} + +const RRClass& +TSIGRecord::getClass() { + return (RRClass::ANY()); +} + +struct TSIGContext::TSIGContextImpl { + TSIGContextImpl(const TSIGKey& key) : + state_(INIT), key_(key), error_(Rcode::NOERROR()), + previous_timesigned_(0) + {} + State state_; + TSIGKey key_; + vector<uint8_t> previous_digest_; + TSIGError error_; + uint64_t previous_timesigned_; // only meaningful for response with BADTIME +}; + +TSIGContext::TSIGContext(const TSIGKey& key) : impl_(new TSIGContextImpl(key)) +{ +} + +TSIGContext::~TSIGContext() { + delete impl_; +} + +TSIGContext::State +TSIGContext::getState() const { + return (impl_->state_); +} + +TSIGError +TSIGContext::getError() const { + return (impl_->error_); +} + +ConstTSIGRecordPtr +TSIGContext::sign(const uint16_t qid, const void* const data, + const size_t data_len) +{ + if (data == NULL || data_len == 0) { + isc_throw(InvalidParameter, "TSIG sign error: empty data is given"); + } + + TSIGError error(TSIGError::NOERROR()); + const uint64_t now = (gettimeofdayWrapper() & 0x0000ffffffffffffULL); + + // For responses adjust the error code. + if (impl_->state_ == CHECKED) { + error = impl_->error_; + } + + // For errors related to key or MAC, return an unsigned response as + // specified in Section 4.3 of RFC2845. + if (error == TSIGError::BAD_SIG() || error == TSIGError::BAD_KEY()) { + ConstTSIGRecordPtr tsig(new TSIGRecord( + any::TSIG(impl_->key_.getAlgorithmName(), + now, DEFAULT_FUDGE, NULL, 0, + qid, error.getCode(), 0, NULL))); + impl_->previous_digest_.clear(); + impl_->state_ = SIGNED; + return (tsig); + } + + OutputBuffer variables(0); + HMACPtr hmac(CryptoLink::getCryptoLink().createHMAC( + impl_->key_.getSecret(), + impl_->key_.getSecretLength(), + impl_->key_.getCryptoAlgorithm()), + deleteHMAC); + + // If the context has previous MAC (either the Request MAC or its own + // previous MAC), digest it. + if (impl_->state_ != INIT) { + const uint16_t previous_digest_len(impl_->previous_digest_.size()); + variables.writeUint16(previous_digest_len); + if (previous_digest_len != 0) { + variables.writeData(&impl_->previous_digest_[0], + previous_digest_len); + } + hmac->update(variables.getData(), variables.getLength()); + } + + // Digest the message (without TSIG) + hmac->update(data, data_len); + + // + // Digest TSIG variables. If state_ is SIGNED we skip digesting them + // except for time related variables (RFC2845 4.4). + // + variables.clear(); + if (impl_->state_ != SIGNED) { + impl_->key_.getKeyName().toWire(variables); + TSIGRecord::getClass().toWire(variables); + variables.writeUint32(TSIGRecord::TSIG_TTL); + impl_->key_.getAlgorithmName().toWire(variables); + } + const uint64_t time_signed = (error == TSIGError::BAD_TIME()) ? + impl_->previous_timesigned_ : now; + variables.writeUint16(time_signed >> 32); + variables.writeUint32(time_signed & 0xffffffff); + variables.writeUint16(DEFAULT_FUDGE); + hmac->update(variables.getData(), variables.getLength()); + variables.clear(); + + if (impl_->state_ != SIGNED) { + variables.writeUint16(error.getCode()); + + // For BADTIME error, digest 6 bytes of other data. + // (6 bytes = size of time signed value) + variables.writeUint16((error == TSIGError::BAD_TIME()) ? 6 : 0); + hmac->update(variables.getData(), variables.getLength()); + + variables.clear(); + if (error == TSIGError::BAD_TIME()) { + variables.writeUint16(now >> 32); + variables.writeUint32(now & 0xffffffff); + hmac->update(variables.getData(), variables.getLength()); + } + } + const uint16_t otherlen = variables.getLength(); + + // Get the final digest, update internal state, then finish. + vector<uint8_t> digest = hmac->sign(); + ConstTSIGRecordPtr tsig(new TSIGRecord( + any::TSIG(impl_->key_.getAlgorithmName(), + time_signed, DEFAULT_FUDGE, + digest.size(), &digest[0], + qid, error.getCode(), otherlen, + otherlen == 0 ? + NULL : variables.getData()))); + // Exception free from now on. + impl_->previous_digest_.swap(digest); + impl_->state_ = SIGNED; + return (tsig); +} + +void +TSIGContext::verifyTentative(ConstTSIGRecordPtr tsig, TSIGError error) { + const any::TSIG tsig_rdata = tsig->getRdata(); + + impl_->error_ = error; + if (error == TSIGError::BAD_TIME()) { + impl_->previous_timesigned_ = tsig_rdata.getTimeSigned(); + } + + // For simplicity we assume non empty digests. + assert(tsig_rdata.getMACSize() != 0); + impl_->previous_digest_.assign( + static_cast<const uint8_t*>(tsig_rdata.getMAC()), + static_cast<const uint8_t*>(tsig_rdata.getMAC()) + + tsig_rdata.getMACSize()); + + impl_->state_ = CHECKED; +} +} // namespace dns +} // namespace isc diff --git a/src/lib/dns/tsig.h b/src/lib/dns/tsig.h new file mode 100644 index 0000000000..55ab41e1b4 --- /dev/null +++ b/src/lib/dns/tsig.h @@ -0,0 +1,302 @@ +// Copyright (C) 2011 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. + +#ifndef __TSIG_H +#define __TSIG_H 1 + +#include <boost/shared_ptr.hpp> +#include <boost/noncopyable.hpp> + +#include <dns/rdataclass.h> +#include <dns/tsigerror.h> +#include <dns/tsigkey.h> + +namespace isc { +namespace dns { +/// TSIG resource record. +/// +/// A \c TSIGRecord class object represents a TSIG resource record and is +/// responsible for conversion to and from wire format TSIG record based on +/// the protocol specification (RFC2845). +/// This class is provided so that other classes and applications can handle +/// TSIG without knowing protocol details of TSIG, such as that it uses a +/// fixed constant of TTL. +/// +/// \note So the plan is to eventually provide a \c toWire() method and +/// the "from wire" constructor. They are not yet provided in this initial +/// step. +/// +/// \note +/// This class could be a derived class of \c AbstractRRset. That way +/// it would be able to be used in a polymorphic way; for example, +/// an application can construct a TSIG RR by itself and insert it to a +/// \c Message object as a generic RRset. On the other hand, it would mean +/// this class would have to implement an \c RdataIterator (even though it +/// can be done via straightforward forwarding) while the iterator is mostly +/// redundant since there should be one and only one RDATA for a valid TSIG +/// RR. Likewise, some methods such as \c setTTL() method wouldn't be well +/// defined due to such special rules for TSIG as using a fixed TTL. +/// Overall, TSIG is a very special RR type that simply uses the compatible +/// resource record format, and it will be unlikely that a user wants to +/// handle it through a generic interface in a polymorphic way. +/// We therefore chose to define it as a separate class. This is also +/// similar to why \c EDNS is a separate class. +class TSIGRecord { +public: + /// Constructor from TSIG RDATA + /// + /// \exception std::bad_alloc Resource allocation for copying the RDATA + /// fails + explicit TSIGRecord(const rdata::any::TSIG& tsig_rdata) : + rdata_(tsig_rdata) + {} + + /// Return the RDATA of the TSIG RR + /// + /// \exception None + const rdata::any::TSIG& getRdata() const { return (rdata_); } + + /// \name Protocol constants and defaults + /// + //@{ + /// Return the RR class of TSIG + /// + /// TSIG always uses the ANY RR class. This static method returns it, + /// when, though unlikely, an application wants to know which class TSIG + /// is supposed to use. + /// + /// \exception None + static const RRClass& getClass(); + + /// The TTL value to be used in TSIG RRs. + static const uint32_t TSIG_TTL = 0; + //@} + +private: + const rdata::any::TSIG rdata_; +}; + +/// A pointer-like type pointing to a \c TSIGRecord object. +typedef boost::shared_ptr<TSIGRecord> TSIGRecordPtr; + +/// A pointer-like type pointing to an immutable \c TSIGRecord object. +typedef boost::shared_ptr<const TSIGRecord> ConstTSIGRecordPtr; + +/// TSIG session context. +/// +/// The \c TSIGContext class maintains a context of a signed session of +/// DNS transactions by TSIG. In many cases a TSIG signed session consists +/// of a single set of request (e.g. normal query) and reply (e.g. normal +/// response), where the request is initially signed by the client, and the +/// reply is signed by the server using the initial signature. As mentioned +/// in RFC2845, a session can consist of multiple exchanges in a TCP +/// connection. As also mentioned in the RFC, an AXFR response often contains +/// multiple DNS messages, which can belong to the same TSIG session. +/// This class supports all these cases. +/// +/// A \c TSIGContext object is generally constructed with a TSIG key to be +/// used for the session, and keeps track of various kinds of session specific +/// information, such as the original digest while waiting for a response or +/// verification error information that is to be used for a subsequent +/// response. +/// +/// This class has two main methods, \c sign() and \c verify(). +/// The \c sign() method signs given data (which is supposed to be a complete +/// DNS message without the TSIG itself) using the TSIG key and other +/// related information associated with the \c TSIGContext object. +/// The \c verify() method verifies a given DNS message that contains a TSIG +/// RR using the key and other internal information. +/// +/// In general, a DNS client that wants to send a signed query will construct +/// a \c TSIGContext object with the TSIG key that the client is intending to +/// use, and sign the query with the context. The client will keeps the +/// context, and verify the response with it. +/// +/// On the other hand, a DNS server will construct a \c TSIGContext object +/// with the information of the TSIG RR included in a query with a set of +/// possible keys (in the form of a \c TSIGKeyRing object). The constructor +/// in this mode will identify the appropriate TSIG key (or internally record +/// an error if it doesn't find a key). The server will then verify the +/// query with the context, and generate a signed response using the same +/// same context. (Note: this mode is not yet implemented and may change, +/// see below). +/// +/// When multiple messages belong to the same TSIG session, either side +/// (signer or verifier) will keep using the same context. It records +/// the latest session state (such as the previous digest) so that repeated +/// calls to \c sign() or \c verify() work correctly in terms of the TSIG +/// protocol. +/// +/// \note The \c verify() method is not yet implemented. The implementation +/// and documentation should be updated in the corresponding task. +/// +/// <b>TCP Consideration</b> +/// +/// RFC2845 describes the case where a single TSIG session is used for +/// multiple DNS messages (Section 4.4). This class supports signing and +/// verifying the messages in this scenario, but does not care if the messages +/// were delivered over a TCP connection or not. If, for example, the +/// same \c TSIGContext object is used to sign two independent DNS queries +/// sent over UDP, they will be considered to belong to the same TSIG +/// session, and, as a result, verification will be likely to fail. +/// +/// \b Copyability +/// +/// This class is currently non copyable based on the observation of the +/// typical usage as described above. But there is no strong technical +/// reason why this class cannot be copyable. If we see the need for it +/// in future we may change the implementation on this point. +/// +/// <b>Note to developers:</b> +/// One basic design choice is to make the \c TSIGContext class is as +/// independent from the \c Message class. This is because the latter is +/// much more complicated, depending on many other classes, while TSIG is +/// a very specific part of the entire DNS protocol set. If the \c TSIGContext +/// class depends on \c \c Message, it will be more vulnerable to changes +/// to other classes, and will be more difficult to test due to the +/// direct or indirect dependencies. The interface of \c sign() that takes +/// opaque data (instead of, e.g., a \c Message or \c MessageRenderer object) +/// is therefore a deliberate design decision. +class TSIGContext : boost::noncopyable { +public: + /// Internal state of context + /// + /// The constants of this enum type define a specific state of + /// \c TSIGContext to adjust the behavior. The definition is public + /// and the state can be seen via the \c getState() method, but this is + /// mostly private information. It's publicly visible mainly for testing + /// purposes; there is no API for the application to change the state + /// directly. + enum State { + INIT, ///< Initial state + SIGNED, ///< Sign completed + CHECKED ///< Verification completed (may or may not successfully) + }; + + /// \name Constructors and destructor + /// + //@{ + /// Constructor from a TSIG key. + /// + /// \exception std::bad_alloc Resource allocation for internal data fails + /// + /// \param key The TSIG key to be used for TSIG sessions with this context. + explicit TSIGContext(const TSIGKey& key); + + /// The destructor. + ~TSIGContext(); + //@} + + /// Sign a DNS message. + /// + /// This method computes the TSIG MAC for the given data, which is + /// generally expected to be a complete, wire-format DNS message + /// that doesn't contain a TSIG RR, based on the TSIG key and + /// other context information of \c TSIGContext, and returns a + /// result in the form of a (pointer object pointing to) + /// \c TSIGRecord object. + /// + /// The caller of this method will use the returned value to render a + /// complete TSIG RR into the message that has been signed so that it + /// will become a complete TSIG-signed message. + /// + /// \note Normal applications are not expected to call this method + /// directly; they will usually use the \c Message::toWire() method + /// with a \c TSIGContext object being a parameter and have the + /// \c Message class create a complete signed message. + /// + /// This method treats the given data as opaque, even though it's generally + /// expected to represent a wire-format DNS message (see also the class + /// description), and doesn't inspect it in any way. For example, it + /// doesn't check whether the data length is sane for a valid DNS message. + /// This is also the reason why this method takes the \c qid parameter, + /// which will be used as the original ID of the resulting + /// \c TSIGRecordx object, even though this value should be stored in the + /// first two octets (in wire format) of the given data. + /// + /// \note This method still checks and rejects empty data (\c NULL pointer + /// data or the specified data length is 0) in order to avoid catastrophic + /// effect such as program crash. Empty data is not necessarily invalid + /// for HMAC computation, but obviously it doesn't make sense for a DNS + /// message. + /// + /// This method provides the strong exception guarantee; unless the method + /// returns (without an exception being thrown), the internal state of + /// the \c TSIGContext won't be modified. + /// + /// \exception InvalidParameter \c data is NULL or \c data_len is 0 + /// \exception cryptolink::LibraryError Some unexpected error in the + /// underlying crypto operation + /// \exception std::bad_alloc Temporary resource allocation failure + /// + /// \param qid The QID to be as the value of the original ID field of + /// the resulting TSIG record + /// \param data Points to the wire-format data to be signed + /// \param data_len The length of \c data in bytes + /// + /// \return A TSIG record for the given data along with the context. + ConstTSIGRecordPtr sign(const uint16_t qid, const void* const data, + const size_t data_len); + + /// Return the current state of the context + /// + /// \note + /// The states are visible in public mainly for testing purposes. + /// Normal applications won't have to deal with them. + /// + /// \exception None + State getState() const; + + /// Return the TSIG error as a result of the latest verification + /// + /// This method can be called even before verifying anything, but the + /// returned value is meaningless in that case. + /// + /// \exception None + TSIGError getError() const; + + // This method is tentatively added for testing until a complete + // verify() method is implemented. Once it's done this should be + // removed, and corresponding tests should be updated. + // + // This tentative "verify" method changes the internal state of + // the TSIGContext to the CHECKED as if it were verified (though possibly + // unsuccessfully) with given tsig_rdata. If the error parameter is + // given and not NOERROR, it's recorded inside the context so that the + // subsequent sign() will behave accordingly. + void verifyTentative(ConstTSIGRecordPtr tsig, + TSIGError error = TSIGError::NOERROR()); + + /// \name Protocol constants and defaults + /// + //@{ + /// The recommended fudge value (in seconds) by RFC2845. + /// + /// Right now fudge is not tunable, and all TSIGs generated by this API + /// will have this value of fudge. + static const uint16_t DEFAULT_FUDGE = 300; + //@} + +private: + struct TSIGContextImpl; + TSIGContextImpl* impl_; +}; +} +} + +#endif // __TSIG_H + +// Local Variables: +// mode: c++ +// End: diff --git a/src/lib/dns/tsigerror.cc b/src/lib/dns/tsigerror.cc new file mode 100644 index 0000000000..e63c9ab2dd --- /dev/null +++ b/src/lib/dns/tsigerror.cc @@ -0,0 +1,57 @@ +// Copyright (C) 2011 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 <ostream> +#include <string> + +#include <boost/lexical_cast.hpp> + +#include <exceptions/exceptions.h> + +#include <dns/rcode.h> +#include <dns/tsigerror.h> + +namespace isc { +namespace dns { +namespace { +const char* const tsigerror_text[] = { + "BADSIG", + "BADKEY", + "BADTIME" +}; +} + +TSIGError::TSIGError(Rcode rcode) : code_(rcode.getCode()) { + if (code_ > MAX_RCODE_FOR_TSIGERROR) { + isc_throw(OutOfRange, "Invalid RCODE for TSIG Error: " << rcode); + } +} + +std::string +TSIGError::toText() const { + if (code_ <= MAX_RCODE_FOR_TSIGERROR) { + return (Rcode(code_).toText()); + } else if (code_ <= BAD_TIME_CODE) { + return (tsigerror_text[code_ - (MAX_RCODE_FOR_TSIGERROR + 1)]); + } else { + return (boost::lexical_cast<std::string>(code_)); + } +} + +std::ostream& +operator<<(std::ostream& os, const TSIGError& error) { + return (os << error.toText()); +} +} // namespace dns +} // namespace isc diff --git a/src/lib/dns/tsigerror.h b/src/lib/dns/tsigerror.h new file mode 100644 index 0000000000..4463daf5b9 --- /dev/null +++ b/src/lib/dns/tsigerror.h @@ -0,0 +1,328 @@ +// Copyright (C) 2011 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. + +#ifndef __TSIGERROR_H +#define __TSIGERROR_H 1 + +#include <ostream> +#include <string> + +#include <dns/rcode.h> + +namespace isc { +namespace dns { + +class RRClass; + +/// TSIG errors +/// +/// The \c TSIGError class objects represent standard errors related to +/// TSIG protocol operations as defined in related specifications, mainly +/// in RFC2845. +/// +/// (RCODEs) of the header section of DNS messages, and extended response +/// codes as defined in the EDNS specification. +class TSIGError { +public: + /// Constants for pre-defined TSIG error values. + /// + /// Code values from 0 through 15 (inclusive) are derived from those of + /// RCODE and are not defined here. See the \c Rcode class. + /// + /// \note Unfortunately some systems define "BADSIG" as a macro in a public + /// header file. To avoid conflict with it we add an underscore to our + /// definitions. + enum CodeValue { + BAD_SIG_CODE = 16, ///< 16: TSIG verification failure + BAD_KEY_CODE = 17, ///< 17: TSIG key is not recognized + BAD_TIME_CODE = 18 ///< 18: Current time and time signed are too different + }; + + /// \name Constructors + /// + /// We use the default versions of destructor, copy constructor, + /// and assignment operator. + //@{ + /// Constructor from the code value. + /// + /// \exception None + /// + /// \param code The underlying 16-bit error code value of the \c TSIGError. + explicit TSIGError(uint16_t error_code) : code_(error_code) {} + + /// Constructor from \c Rcode. + /// + /// As defined in RFC2845, error code values from 0 to 15 (inclusive) are + /// derived from the DNS RCODEs, which are represented via the \c Rcode + /// class in this library. This constructor works as a converter from + /// these RCODEs to corresponding TSIGError objects. + /// + /// \exception isc::OutOfRange Given rcode is not convertible to + /// TSIGErrors. + /// + /// \param rcode the \c Rcode from which the TSIGError should be derived. + explicit TSIGError(Rcode rcode); + //@} + + /// \brief Returns the \c TSIGCode error code value. + /// + /// \exception None + /// + /// \return The underlying code value corresponding to the \c TSIGError. + uint16_t getCode() const { return (code_); } + + /// \brief Return true iff two \c TSIGError objects are equal. + /// + /// Two TSIGError objects are equal iff their error codes are equal. + /// + /// \exception None + /// + /// \param other the \c TSIGError object to compare against. + /// \return true if the two TSIGError are equal; otherwise false. + bool equals(const TSIGError& other) const + { return (code_ == other.code_); } + + /// \brief Same as \c equals(). + bool operator==(const TSIGError& other) const { return (equals(other)); } + + /// \brief Return true iff two \c TSIGError objects are not equal. + /// + /// \exception None + /// + /// \param other the \c TSIGError object to compare against. + /// \return true if the two TSIGError objects are not equal; + /// otherwise false. + bool nequals(const TSIGError& other) const + { return (code_ != other.code_); } + + /// \brief Same as \c nequals(). + bool operator!=(const TSIGError& other) const { return (nequals(other)); } + + /// \brief Convert the \c TSIGError to a string. + /// + /// For codes derived from RCODEs up to 15, this method returns the + /// same string as \c Rcode::toText() for the corresponding code. + /// For other pre-defined code values (see TSIGError::CodeValue), + /// this method returns a string representation of the "mnemonic' used + /// for the enum and constant objects as defined in RFC2845. + /// For example, the string for code value 16 is "BADSIG", etc. + /// For other code values it returns a string representation of the decimal + /// number of the value, e.g. "32", "100", etc. + /// + /// \exception std::bad_alloc Resource allocation for the string fails + /// + /// \return A string representation of the \c TSIGError. + std::string toText() const; + + /// A constant TSIG error object derived from \c Rcode::NOERROR() + static const TSIGError& NOERROR(); + + /// A constant TSIG error object derived from \c Rcode::FORMERR() + static const TSIGError& FORMERR(); + + /// A constant TSIG error object derived from \c Rcode::SERVFAIL() + static const TSIGError& SERVFAIL(); + + /// A constant TSIG error object derived from \c Rcode::NXDOMAIN() + static const TSIGError& NXDOMAIN(); + + /// A constant TSIG error object derived from \c Rcode::NOTIMP() + static const TSIGError& NOTIMP(); + + /// A constant TSIG error object derived from \c Rcode::REFUSED() + static const TSIGError& REFUSED(); + + /// A constant TSIG error object derived from \c Rcode::YXDOMAIN() + static const TSIGError& YXDOMAIN(); + + /// A constant TSIG error object derived from \c Rcode::YXRRSET() + static const TSIGError& YXRRSET(); + + /// A constant TSIG error object derived from \c Rcode::NXRRSET() + static const TSIGError& NXRRSET(); + + /// A constant TSIG error object derived from \c Rcode::NOTAUTH() + static const TSIGError& NOTAUTH(); + + /// A constant TSIG error object derived from \c Rcode::NOTZONE() + static const TSIGError& NOTZONE(); + + /// A constant TSIG error object derived from \c Rcode::RESERVED11() + static const TSIGError& RESERVED11(); + + /// A constant TSIG error object derived from \c Rcode::RESERVED12() + static const TSIGError& RESERVED12(); + + /// A constant TSIG error object derived from \c Rcode::RESERVED13() + static const TSIGError& RESERVED13(); + + /// A constant TSIG error object derived from \c Rcode::RESERVED14() + static const TSIGError& RESERVED14(); + + /// A constant TSIG error object derived from \c Rcode::RESERVED15() + static const TSIGError& RESERVED15(); + + /// A constant TSIG error object for the BADSIG code + /// (see \c TSIGError::BAD_SIG_CODE). + static const TSIGError& BAD_SIG(); + + /// A constant TSIG error object for the BADKEY code + /// (see \c TSIGError::BAD_KEY_CODE). + static const TSIGError& BAD_KEY(); + + /// A constant TSIG error object for the BADTIME code + /// (see \c TSIGError::BAD_TIME_CODE). + static const TSIGError& BAD_TIME(); + +private: + // This is internally used to specify the maximum possible RCODE value + // that can be convertible to TSIGErrors. + static const int MAX_RCODE_FOR_TSIGERROR = 15; + + uint16_t code_; +}; + +inline const TSIGError& +TSIGError::NOERROR() { + static TSIGError e(Rcode::NOERROR()); + return (e); +} + +inline const TSIGError& +TSIGError::FORMERR() { + static TSIGError e(Rcode::FORMERR()); + return (e); +} + +inline const TSIGError& +TSIGError::SERVFAIL() { + static TSIGError e(Rcode::SERVFAIL()); + return (e); +} + +inline const TSIGError& +TSIGError::NXDOMAIN() { + static TSIGError e(Rcode::NXDOMAIN()); + return (e); +} + +inline const TSIGError& +TSIGError::NOTIMP() { + static TSIGError e(Rcode::NOTIMP()); + return (e); +} + +inline const TSIGError& +TSIGError::REFUSED() { + static TSIGError e(Rcode::REFUSED()); + return (e); +} + +inline const TSIGError& +TSIGError::YXDOMAIN() { + static TSIGError e(Rcode::YXDOMAIN()); + return (e); +} + +inline const TSIGError& +TSIGError::YXRRSET() { + static TSIGError e(Rcode::YXRRSET()); + return (e); +} + +inline const TSIGError& +TSIGError::NXRRSET() { + static TSIGError e(Rcode::NXRRSET()); + return (e); +} + +inline const TSIGError& +TSIGError::NOTAUTH() { + static TSIGError e(Rcode::NOTAUTH()); + return (e); +} + +inline const TSIGError& +TSIGError::NOTZONE() { + static TSIGError e(Rcode::NOTZONE()); + return (e); +} + +inline const TSIGError& +TSIGError::RESERVED11() { + static TSIGError e(Rcode::RESERVED11()); + return (e); +} + +inline const TSIGError& +TSIGError::RESERVED12() { + static TSIGError e(Rcode::RESERVED12()); + return (e); +} + +inline const TSIGError& +TSIGError::RESERVED13() { + static TSIGError e(Rcode::RESERVED13()); + return (e); +} + +inline const TSIGError& +TSIGError::RESERVED14() { + static TSIGError e(Rcode::RESERVED14()); + return (e); +} + +inline const TSIGError& +TSIGError::RESERVED15() { + static TSIGError e(Rcode::RESERVED15()); + return (e); +} + +inline const TSIGError& +TSIGError::BAD_SIG() { + static TSIGError e(BAD_SIG_CODE); + return (e); +} + +inline const TSIGError& +TSIGError::BAD_KEY() { + static TSIGError e(BAD_KEY_CODE); + return (e); +} + +inline const TSIGError& +TSIGError::BAD_TIME() { + static TSIGError e(BAD_TIME_CODE); + return (e); +} + +/// Insert the \c TSIGError as a string into stream. +/// +/// This method convert \c tsig_error into a string and inserts it into the +/// output stream \c os. +/// +/// \param os A \c std::ostream object on which the insertion operation is +/// performed. +/// \param tsig_error An \c TSIGError object output by the operation. +/// \return A reference to the same \c std::ostream object referenced by +/// parameter \c os after the insertion operation. +std::ostream& operator<<(std::ostream& os, const TSIGError& tsig_error); +} +} + +#endif // __TSIGERROR_H + +// Local Variables: +// mode: c++ +// End: diff --git a/src/lib/dns/tsigkey.cc b/src/lib/dns/tsigkey.cc index 057191da39..615b5b6155 100644 --- a/src/lib/dns/tsigkey.cc +++ b/src/lib/dns/tsigkey.cc @@ -15,50 +15,112 @@ #include <map> #include <utility> #include <vector> +#include <sstream> #include <exceptions/exceptions.h> +#include <cryptolink/cryptolink.h> + #include <dns/name.h> +#include <util/encode/base64.h> #include <dns/tsigkey.h> using namespace std; +using namespace isc::cryptolink; namespace isc { namespace dns { +namespace { + HashAlgorithm + convertAlgorithmName(const isc::dns::Name& name) { + if (name == TSIGKey::HMACMD5_NAME()) { + return (isc::cryptolink::MD5); + } + if (name == TSIGKey::HMACSHA1_NAME()) { + return (isc::cryptolink::SHA1); + } + if (name == TSIGKey::HMACSHA256_NAME()) { + return (isc::cryptolink::SHA256); + } + isc_throw(InvalidParameter, + "Unknown TSIG algorithm is specified: " << name); + } +} + struct TSIGKey::TSIGKeyImpl { TSIGKeyImpl(const Name& key_name, const Name& algorithm_name, + isc::cryptolink::HashAlgorithm algorithm, const void* secret, size_t secret_len) : key_name_(key_name), algorithm_name_(algorithm_name), + algorithm_(algorithm), secret_(static_cast<const uint8_t*>(secret), static_cast<const uint8_t*>(secret) + secret_len) { - // Convert the name to the canonical form. + // Convert the key and algorithm names to the canonical form. + key_name_.downcase(); algorithm_name_.downcase(); } - const Name key_name_; + Name key_name_; Name algorithm_name_; + const isc::cryptolink::HashAlgorithm algorithm_; const vector<uint8_t> secret_; }; TSIGKey::TSIGKey(const Name& key_name, const Name& algorithm_name, const void* secret, size_t secret_len) : impl_(NULL) { - if (algorithm_name != HMACMD5_NAME() && - algorithm_name != HMACSHA1_NAME() && - algorithm_name != HMACSHA256_NAME()) { - isc_throw(InvalidParameter, "Unknown TSIG algorithm is specified: " << - algorithm_name); - } + const HashAlgorithm algorithm = convertAlgorithmName(algorithm_name); if ((secret != NULL && secret_len == 0) || (secret == NULL && secret_len != 0)) { isc_throw(InvalidParameter, "TSIGKey secret and its length are inconsistent"); } - - impl_ = new TSIGKeyImpl(key_name, algorithm_name, secret, secret_len); + impl_ = new TSIGKeyImpl(key_name, algorithm_name, algorithm, secret, + secret_len); +} + +TSIGKey::TSIGKey(const std::string& str) : impl_(NULL) { + try { + istringstream iss(str); + + string keyname_str; + getline(iss, keyname_str, ':'); + if (iss.fail() || iss.bad() || iss.eof()) { + isc_throw(InvalidParameter, "Invalid TSIG key string: " << str); + } + + string secret_str; + getline(iss, secret_str, ':'); + if (iss.fail() || iss.bad()) { + isc_throw(InvalidParameter, "Invalid TSIG key string: " << str); + } + + string algo_str; + if (!iss.eof()) { + getline(iss, algo_str); + } + if (iss.fail() || iss.bad()) { + isc_throw(InvalidParameter, "Invalid TSIG key string: " << str); + } + + const Name algo_name(algo_str.empty() ? "hmac-md5.sig-alg.reg.int" : + algo_str); + const HashAlgorithm algorithm = convertAlgorithmName(algo_name); + + vector<uint8_t> secret; + util::encode::decodeBase64(secret_str, secret); + + impl_ = new TSIGKeyImpl(Name(keyname_str), algo_name, algorithm, + &secret[0], secret.size()); + } catch (const Exception& e) { + // 'reduce' the several types of exceptions name parsing and + // Base64 decoding can throw to just the InvalidParameter + isc_throw(InvalidParameter, e.what()); + } } + TSIGKey::TSIGKey(const TSIGKey& source) : impl_(new TSIGKeyImpl(*source.impl_)) {} @@ -89,6 +151,11 @@ TSIGKey::getAlgorithmName() const { return (impl_->algorithm_name_); } +isc::cryptolink::HashAlgorithm +TSIGKey::getCryptoAlgorithm() const { + return (impl_->algorithm_); +} + const void* TSIGKey::getSecret() const { return ((impl_->secret_.size() > 0) ? &impl_->secret_[0] : NULL); @@ -99,6 +166,17 @@ TSIGKey::getSecretLength() const { return (impl_->secret_.size()); } +std::string +TSIGKey::toText() const { + const vector<uint8_t> secret_v(static_cast<const uint8_t*>(getSecret()), + static_cast<const uint8_t*>(getSecret()) + + getSecretLength()); + std::string secret_str = util::encode::encodeBase64(secret_v); + + return (getKeyName().toText() + ":" + secret_str + ":" + + getAlgorithmName().toText()); +} + const Name& TSIGKey::HMACMD5_NAME() { static Name alg_name("hmac-md5.sig-alg.reg.int"); diff --git a/src/lib/dns/tsigkey.h b/src/lib/dns/tsigkey.h index e56fa88eb4..4136278d66 100644 --- a/src/lib/dns/tsigkey.h +++ b/src/lib/dns/tsigkey.h @@ -15,6 +15,8 @@ #ifndef __TSIGKEY_H #define __TSIGKEY_H 1 +#include <cryptolink/cryptolink.h> + namespace isc { namespace dns { @@ -90,6 +92,25 @@ public: TSIGKey(const Name& key_name, const Name& algorithm_name, const void* secret, size_t secret_len); + /// \brief Constructor from an input string + /// + /// The string must be of the form: + /// <name>:<secret>[:<algorithm>] + /// Where <name> is a domain name for the key, <secret> is a + /// base64 representation of the key secret, and the optional + /// algorithm is an algorithm identifier as specified in RFC4635 + /// The default algorithm is hmac-md5.sig-alg.reg.int. + /// + /// Since ':' is used as a separator here, it is not possible to + /// use this constructor to create keys with a ':' character in + /// their name. + /// + /// \exception InvalidParameter exception if the input string is + /// invalid. + /// + /// \param str The string to make a TSIGKey from + explicit TSIGKey(const std::string& str); + /// \brief The copy constructor. /// /// It internally allocates a resource, and if it fails a corresponding @@ -123,6 +144,9 @@ public: /// Return the algorithm name. const Name& getAlgorithmName() const; + /// Return the hash algorithm name in the form of cryptolink::HashAlgorithm + isc::cryptolink::HashAlgorithm getCryptoAlgorithm() const; + /// Return the length of the TSIG secret in bytes. size_t getSecretLength() const; @@ -139,6 +163,18 @@ public: const void* getSecret() const; //@} + /// \brief Converts the TSIGKey to a string value + /// + /// The resulting string will be of the form + /// name:secret:algorithm + /// Where <name> is a domain name for the key, <secret> is a + /// base64 representation of the key secret, and algorithm is + /// an algorithm identifier as specified in RFC4635 + /// + /// \param key the TSIG key to convert + /// \return The string representation of the given TSIGKey. + std::string toText() const; + /// /// \name Well known algorithm names as defined in RFC2845 and RFC4635. /// diff --git a/src/lib/python/isc/notify/tests/Makefile.am b/src/lib/python/isc/notify/tests/Makefile.am index 07129ec76b..a83ff86a00 100644 --- a/src/lib/python/isc/notify/tests/Makefile.am +++ b/src/lib/python/isc/notify/tests/Makefile.am @@ -6,7 +6,7 @@ EXTRA_DIST = $(PYTESTS) # required by loadable python modules. LIBRARY_PATH_PLACEHOLDER = if SET_ENV_LIBRARY_PATH -LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$$$(ENV_LIBRARY_PATH) +LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$$$(ENV_LIBRARY_PATH) endif # test using command-line arguments, so use check-local target instead of TESTS diff --git a/src/lib/util/unittests/Makefile.am b/src/lib/util/unittests/Makefile.am index b7026478ff..e7cb447f54 100644 --- a/src/lib/util/unittests/Makefile.am +++ b/src/lib/util/unittests/Makefile.am @@ -3,5 +3,6 @@ AM_CXXFLAGS = $(B10_CXXFLAGS) lib_LTLIBRARIES = libutil_unittests.la libutil_unittests_la_SOURCES = fork.h fork.cc +libutil_unittests_la_SOURCES += newhook.h newhook.cc CLEANFILES = *.gcno *.gcda diff --git a/src/lib/util/unittests/newhook.cc b/src/lib/util/unittests/newhook.cc new file mode 100644 index 0000000000..b9d9fb633b --- /dev/null +++ b/src/lib/util/unittests/newhook.cc @@ -0,0 +1,51 @@ +// Copyright (C) 2011 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 <stdlib.h> + +#include <new> +#include <stdexcept> + +#include "newhook.h" + +#ifdef ENABLE_CUSTOM_OPERATOR_NEW +void* +operator new(size_t size) throw(std::bad_alloc) { + if (isc::util::unittests::force_throw_on_new && + size == isc::util::unittests::throw_size_on_new) { + throw std::bad_alloc(); + } + void* p = malloc(size); + if (p == NULL) { + throw std::bad_alloc(); + } + return (p); +} + +void +operator delete(void* p) throw() { + if (p != NULL) { + free (p); + } +} +#endif + +namespace isc { +namespace util { +namespace unittests { +bool force_throw_on_new = false; +size_t throw_size_on_new = 0; +} +} +} diff --git a/src/lib/util/unittests/newhook.h b/src/lib/util/unittests/newhook.h new file mode 100644 index 0000000000..b0dbb64838 --- /dev/null +++ b/src/lib/util/unittests/newhook.h @@ -0,0 +1,76 @@ +// Copyright (C) 2011 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. + +#ifndef __UTIL_UNITTESTS_NEWHOOK_H +#define __UTIL_UNITTESTS_NEWHOOK_H 1 + +/** + * @file newhook.h + * @short Enable the use of special operator new that throws for testing. + * + * This small utility allows a test case to force the global operator new + * to throw for a given size to test a case where memory allocation fails + * (which normally doesn't happen). To enable the feature, everything must + * be built with defining ENABLE_CUSTOM_OPERATOR_NEW beforehand, and set + * \c force_throw_on_new to \c true and \c throw_size_on_new to the size + * of data that should trigger the exception, immediately before starting + * the specific test that needs the exception. + * + * Example: + * \code #include <util/unittests/newhook.h> + * ... + * TEST(SomeTest, newException) { + * isc::util::unittests::force_throw_on_new = true; + * isc::util::unittests::throw_size_on_new = sizeof(Foo); + * try { + * // this will do 'new Foo()' internally and should throw + * createFoo(); + * isc::util::unittests::force_throw_on_new = false; + * ASSERT_FALSE(true) << "Expected throw on new"; + * } catch (const std::bad_alloc&) { + * isc::util::unittests::force_throw_on_new = false; + * // do some integrity check, etc, if necessary + * } + * } \endcode + * + * Replacing the global operator new (and delete) is a dangerous technique, + * and triggering an exception solely based on the allocation size is not + * reliable, so this feature is disabled by default two-fold: The + * ENABLE_CUSTOM_OPERATOR_NEW build time variable, and run-time + * \c force_throw_on_new. + */ + +namespace isc { +namespace util { +namespace unittests { +/// Switch to enable the use of special operator new +/// +/// This is set to \c false by default. +extern bool force_throw_on_new; + +/// The allocation size that triggers an exception in the special operator new +/// +/// The default value is 0. The value of this variable has no meaning +/// unless the use of the special operator is enabled at build time and +/// via \c force_throw_on_new. +extern size_t throw_size_on_new; +} +} +} + +#endif // __UTIL_UNITTESTS_NEWHOOK_H + +// Local Variables: +// mode: c++ +// End: |