diff options
author | JINMEI Tatuya <jinmei@isc.org> | 2011-04-26 19:59:14 +0200 |
---|---|---|
committer | JINMEI Tatuya <jinmei@isc.org> | 2011-04-26 19:59:14 +0200 |
commit | 00e36b253bd643b007f89a8ccb3addac1a723eba (patch) | |
tree | 233050af98170dec71bb1619a3e7dce7ed67a05f /src | |
parent | Merge branch 'work/resolveriterator' (diff) | |
parent | [trac781] add deleteHMAC() (diff) | |
download | kea-00e36b253bd643b007f89a8ccb3addac1a723eba.tar.xz kea-00e36b253bd643b007f89a8ccb3addac1a723eba.zip |
[trac812] Merge branch 'trac781' into trac812
with resolving conflicts for:
configure.ac
doc/Doxyfile
src/lib/Makefile.am
Diffstat (limited to 'src')
-rw-r--r-- | src/lib/Makefile.am | 4 | ||||
-rw-r--r-- | src/lib/cryptolink/Makefile.am | 12 | ||||
-rw-r--r-- | src/lib/cryptolink/crypto_hmac.cc | 237 | ||||
-rw-r--r-- | src/lib/cryptolink/crypto_hmac.h | 209 | ||||
-rw-r--r-- | src/lib/cryptolink/cryptolink.cc | 75 | ||||
-rw-r--r-- | src/lib/cryptolink/cryptolink.h | 203 | ||||
-rw-r--r-- | src/lib/cryptolink/tests/Makefile.am | 26 | ||||
-rw-r--r-- | src/lib/cryptolink/tests/crypto_unittests.cc | 505 | ||||
-rw-r--r-- | src/lib/cryptolink/tests/run_unittests.cc | 24 | ||||
-rw-r--r-- | src/lib/dns/python/tests/tsigkey_python_test.py | 22 | ||||
-rw-r--r-- | src/lib/dns/python/tsigkey_python.cc | 40 | ||||
-rw-r--r-- | src/lib/dns/tests/tsigkey_unittest.cc | 27 | ||||
-rw-r--r-- | src/lib/dns/tsigkey.cc | 71 | ||||
-rw-r--r-- | src/lib/dns/tsigkey.h | 31 |
14 files changed, 1468 insertions, 18 deletions
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/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..6543e4419e --- /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::dns::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::dns::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::dns::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..0c05c7e27a --- /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 <dns/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::dns::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::dns::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..eb5d7561ea --- /dev/null +++ b/src/lib/cryptolink/cryptolink.cc @@ -0,0 +1,75 @@ +// 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> + +#include <boost/scoped_ptr.hpp> + +using namespace std; +using namespace isc::dns; + + +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..0436edba7a --- /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 <dns/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..393607245e --- /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/dns/libdns++.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..cf4027074b --- /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 <dns/buffer.h> +#include <exceptions/exceptions.h> + +#include <boost/scoped_ptr.hpp> + +using namespace isc::dns; +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..d5e20c9d58 --- /dev/null +++ b/src/lib/cryptolink/tests/run_unittests.cc @@ -0,0 +1,24 @@ +// 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> + +#include <dns/tests/unittest_util.h> + +int +main(int argc, char* argv[]) { + ::testing::InitGoogleTest(&argc, argv); + + return (RUN_ALL_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/tsigkey_unittest.cc b/src/lib/dns/tests/tsigkey_unittest.cc index 6b2b8c5832..c10ffe0296 100644 --- a/src/lib/dns/tests/tsigkey_unittest.cc +++ b/src/lib/dns/tests/tsigkey_unittest.cc @@ -227,4 +227,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/tsigkey.cc b/src/lib/dns/tsigkey.cc index 057191da39..fa96843877 100644 --- a/src/lib/dns/tsigkey.cc +++ b/src/lib/dns/tsigkey.cc @@ -15,14 +15,26 @@ #include <map> #include <utility> #include <vector> +#include <sstream> #include <exceptions/exceptions.h> +#include <cryptolink/cryptolink.h> + #include <dns/name.h> +#include <dns/util/base64.h> #include <dns/tsigkey.h> using namespace std; +namespace { + bool isValidAlgorithmName(const isc::dns::Name& name) { + return (name == isc::dns::TSIGKey::HMACMD5_NAME() || + name == isc::dns::TSIGKey::HMACSHA1_NAME() || + name == isc::dns::TSIGKey::HMACSHA256_NAME()); + } +} + namespace isc { namespace dns { struct @@ -44,9 +56,7 @@ TSIGKey::TSIGKeyImpl { 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()) { + if (!isValidAlgorithmName(algorithm_name)) { isc_throw(InvalidParameter, "Unknown TSIG algorithm is specified: " << algorithm_name); } @@ -59,6 +69,50 @@ TSIGKey::TSIGKey(const Name& key_name, const Name& algorithm_name, impl_ = new TSIGKeyImpl(key_name, algorithm_name, 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); + if (!isValidAlgorithmName(algo_name)) { + isc_throw(InvalidParameter, "Unknown TSIG algorithm is specified: " << + algo_name); + } + + vector<uint8_t> secret; + decodeBase64(secret_str, secret); + + impl_ = new TSIGKeyImpl(Name(keyname_str), algo_name, &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_)) {} @@ -99,6 +153,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 = 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..ca9e9ec982 100644 --- a/src/lib/dns/tsigkey.h +++ b/src/lib/dns/tsigkey.h @@ -90,6 +90,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 @@ -139,6 +158,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. /// |