Commit ae3a9cd1 authored by Francis Dupont's avatar Francis Dupont

[trac3593] added truncated HMAC support to TSIG

parent a431e11b
848. [func] fdupont
Added truncated HMAC support to TSIG, as per RFC 4635.
(Trac #3593, git ...)
847. [build] fdupont
Removed no longer used configuration option --with-shared-memory
and associated files and variables.
......
......@@ -106,6 +106,12 @@
"algorithm": "HMAC-SHA1",
"secret": "hRrp29wzUv3uzSNRLlY68w=="
}
{
"name": "d2.sha512.key",
"algorithm": "HMAC-SHA512",
"digest_bits": 256,
"secret": "/4wklkm04jeH4anx2MKGJLcya+ZLHldL5d6mK+4q6UXQP7KJ9mS2QG29hh0SJR4LA0ikxNJTUMvir42gLx6fGQ=="
}
]
}
......
......@@ -92,6 +92,9 @@
# Valid values for algorithm are: HMAC-MD5, HMAC-SHA1,
# HMAC-SHA224, HMAC-SHA256,
# HMAC-SHA384, HMAC-SHA512
# "digest_bits" : 256,
# Minimum truncated length in bits.
# Default 0 (means truncation is forbidden).
"secret" : "<shared secret value>"
}
# ,
......
......@@ -283,6 +283,17 @@ corresponding values in the DHCP servers' "dhcp-ddns" configuration section.
This value is not case sensitive.
</simpara>
</listitem>
<listitem>
<simpara>
<command>digest_bits</command> -
is used to specify the minimum truncated length in bits.
The default value 0 means truncation is forbidden, not 0
values must be an integral number of octets, be greater
than 80 and the half of the full length. Note in BIND9
this parameter is appended after a dash to the algorithm
name.
</simpara>
</listitem>
<listitem>
<simpara>
<command>secret</command> -
......
......@@ -20,6 +20,7 @@
#include <boost/foreach.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/scoped_ptr.hpp>
#include <boost/algorithm/string/predicate.hpp>
#include <sstream>
......@@ -143,8 +144,9 @@ const char* TSIGKeyInfo::HMAC_SHA384_STR = "HMAC-SHA384";
const char* TSIGKeyInfo::HMAC_SHA512_STR = "HMAC-SHA512";
TSIGKeyInfo::TSIGKeyInfo(const std::string& name, const std::string& algorithm,
const std::string& secret)
:name_(name), algorithm_(algorithm), secret_(secret), tsig_key_() {
const std::string& secret, uint32_t digestbits)
:name_(name), algorithm_(algorithm), secret_(secret),
digestbits_(digestbits), tsig_key_() {
remakeKey();
}
......@@ -180,6 +182,9 @@ TSIGKeyInfo::remakeKey() {
stream << dns::Name(name_).toText() << ":"
<< secret_ << ":"
<< stringToAlgorithmName(algorithm_);
if (digestbits_ > 0) {
stream << ":" << digestbits_;
}
tsig_key_.reset(new dns::TSIGKey(stream.str()));
} catch (const std::exception& ex) {
......@@ -366,14 +371,17 @@ TSIGKeyInfoParser::build(isc::data::ConstElementPtr key_config) {
std::string name;
std::string algorithm;
uint32_t digestbits = 0;
std::string secret;
std::map<std::string, isc::data::Element::Position> pos;
// Fetch the key's parsed scalar values from parser's local storage.
// All are required, if any are missing we'll throw.
// Only digestbits is optional and doesn't throw when missing
try {
pos["name"] = local_scalars_.getParam("name", name);
pos["algorithm"] = local_scalars_.getParam("algorithm", algorithm);
pos["digest_bits"] = local_scalars_.getParam("digest_bits", digestbits,
DCfgContextBase::OPTIONAL);
pos["secret"] = local_scalars_.getParam("secret", secret);
} catch (const std::exception& ex) {
isc_throw(D2CfgError, "TSIG Key incomplete : " << ex.what()
......@@ -400,6 +408,44 @@ TSIGKeyInfoParser::build(isc::data::ConstElementPtr key_config) {
isc_throw(D2CfgError, "TSIG key : " << ex.what() << " (" << pos["algorithm"] << ")");
}
// Not zero Digestbits must be an integral number of octets, greater
// than 80 and the half of the full length
if (digestbits > 0) {
if ((digestbits % 8) != 0) {
isc_throw(D2CfgError, "Invalid TSIG key digest_bits specified : " <<
digestbits << " (" << pos["digest_bits"] << ")");
}
if (digestbits < 80) {
isc_throw(D2CfgError, "TSIG key digest_bits too small : " <<
digestbits << " (" << pos["digest_bits"] << ")");
}
if (boost::iequals(algorithm, TSIGKeyInfo::HMAC_SHA224_STR)) {
if (digestbits < 112) {
isc_throw(D2CfgError, "TSIG key digest_bits too small : " <<
digestbits << " (" << pos["digest_bits"]
<< ")");
}
} else if (boost::iequals(algorithm, TSIGKeyInfo::HMAC_SHA256_STR)) {
if (digestbits < 128) {
isc_throw(D2CfgError, "TSIG key digest_bits too small : " <<
digestbits << " (" << pos["digest_bits"]
<< ")");
}
} else if (boost::iequals(algorithm, TSIGKeyInfo::HMAC_SHA384_STR)) {
if (digestbits < 192) {
isc_throw(D2CfgError, "TSIG key digest_bits too small : " <<
digestbits << " (" << pos["digest_bits"]
<< ")");
}
} else if (boost::iequals(algorithm, TSIGKeyInfo::HMAC_SHA512_STR)) {
if (digestbits < 256) {
isc_throw(D2CfgError, "TSIG key digest_bits too small : " <<
digestbits << " (" << pos["digest_bits"]
<< ")");
}
}
}
// Secret cannot be blank.
// Cryptolink lib doesn't offer any way to validate these. As long as it
// isn't blank we'll accept it. If the content is bad, the call to in
......@@ -414,7 +460,7 @@ TSIGKeyInfoParser::build(isc::data::ConstElementPtr key_config) {
// with an invalid secret content.
TSIGKeyInfoPtr key_info;
try {
key_info.reset(new TSIGKeyInfo(name, algorithm, secret));
key_info.reset(new TSIGKeyInfo(name, algorithm, secret, digestbits));
} catch (const std::exception& ex) {
isc_throw(D2CfgError, ex.what() << " (" << key_config->getPosition() << ")");
......@@ -435,6 +481,9 @@ TSIGKeyInfoParser::createConfigParser(const std::string& config_id,
(config_id == "secret")) {
parser = new isc::dhcp::StringParser(config_id,
local_scalars_.getStringStorage());
} else if (config_id == "digest_bits") {
parser = new isc::dhcp::Uint32Parser(config_id,
local_scalars_.getUint32Storage());
} else {
isc_throw(NotImplemented,
"parser error: TSIGKeyInfo parameter not supported: "
......
......@@ -41,7 +41,8 @@ namespace d2 {
/// forward domains and one list for reverse domains.
///
/// The key list consists of one or more TSIG keys, each entry described by
/// a name, the algorithm method name, and its secret key component.
/// a name, the algorithm method name, optionally the minimum truncated
/// length, and its secret key component.
///
/// @todo NOTE that TSIG configuration parsing is functional, the use of
/// TSIG Keys during the actual DNS update transactions is not. This will be
......@@ -317,33 +318,41 @@ public:
/// Activate: 20140515143700
/// @endcode
/// where the value the "Key:" entry is the secret component of the key.)
/// @param digestbits the minimum truncated length in bits
///
/// @throw D2CfgError if values supplied are invalid:
/// name cannot be blank, algorithm must be a supported value,
/// secret must be a non-blank, base64 encoded string.
TSIGKeyInfo(const std::string& name, const std::string& algorithm,
const std::string& secret);
const std::string& secret, uint32_t digestbits = 0);
/// @brief Destructor
virtual ~TSIGKeyInfo();
/// @brief Getter which returns the key's name.
///
/// @return returns the name as as std::string.
/// @return returns the name as a std::string.
const std::string getName() const {
return (name_);
}
/// @brief Getter which returns the key's algorithm string ID
///
/// @return returns the algorithm as as std::string.
/// @return returns the algorithm as a std::string.
const std::string getAlgorithm() const {
return (algorithm_);
}
/// @brief Getter which returns the key's minimum truncated length
///
/// @return returns the minimum truncated length or 0 as an uint32_t
uint32_t getDigestbits() const {
return (digestbits_);
}
/// @brief Getter which returns the key's secret.
///
/// @return returns the secret as as std::string.
/// @return returns the secret as a std::string.
const std::string getSecret() const {
return (secret_);
}
......@@ -376,7 +385,7 @@ private:
/// @brief Creates the actual TSIG key instance member
///
/// Replaces this tsig_key member with a key newly created using the key
/// name, algorithm id, and secret.
/// name, algorithm id, digest bits, and secret.
/// This method is currently only called by the constructor, however it
/// could be called post-construction should keys ever support expiration.
///
......@@ -395,6 +404,10 @@ private:
/// @brief The base64 encoded string secret value component of this key.
std::string secret_;
/// @brief The minimum truncated length in bits
/// (0 means no truncation is allowed and is the default)
uint32_t digestbits_;
/// @brief The actual TSIG key.
dns::TSIGKeyPtr tsig_key_;
};
......@@ -759,7 +772,8 @@ public:
/// The key elements currently supported are(see dhcp-ddns.spec):
/// 1. name
/// 2. algorithm
/// 3. secret
/// 3. digestbits
/// 4. secret
///
/// @param config_id is the "item_name" for a specific member element of
/// the "tsig_key" specification.
......
......@@ -58,6 +58,12 @@
"item_optional": false,
"item_default": ""
},
{
"item_name": "digest_bits",
"item_type": "integer",
"item_optional": true,
"item_default": 0
},
{
"item_name": "secret",
"item_type": "string",
......
......@@ -249,11 +249,13 @@ bool checkServer(DnsServerInfoPtr server, const char* hostname,
/// @return returns true if there is a match across the board, otherwise it
/// returns false.
bool checkKey(TSIGKeyInfoPtr key, const std::string& name,
const std::string& algorithm, const std::string& secret) {
const std::string& algorithm, const std::string& secret,
uint32_t digestbits = 0) {
// Return value, assume its a match.
return (((key) &&
(key->getName() == name) &&
(key->getAlgorithm() == algorithm) &&
(key->getDigestbits() == digestbits) &&
(key->getSecret() == secret) &&
(key->getTSIGKey())));
}
......@@ -618,6 +620,7 @@ TEST_F(TSIGKeyInfoTest, validEntry) {
std::string config = "{"
" \"name\": \"d2_key_one\" , "
" \"algorithm\": \"HMAC-MD5\" , "
" \"digest_bits\": 120 , "
" \"secret\": \"dGhpcyBrZXkgd2lsbCBtYXRjaA==\" "
"}";
ASSERT_TRUE(fromJSON(config));
......@@ -638,7 +641,7 @@ TEST_F(TSIGKeyInfoTest, validEntry) {
// Verify the key contents.
EXPECT_TRUE(checkKey(key, "d2_key_one", "HMAC-MD5",
"dGhpcyBrZXkgd2lsbCBtYXRjaA=="));
"dGhpcyBrZXkgd2lsbCBtYXRjaA==", 120));
}
/// @brief Verifies that attempting to parse an invalid list of TSIGKeyInfo
......@@ -649,11 +652,13 @@ TEST_F(TSIGKeyInfoTest, invalidTSIGKeyList) {
" { \"name\": \"key1\" , "
" \"algorithm\": \"HMAC-MD5\" ,"
" \"digest_bits\": 120 , "
" \"secret\": \"GWG/Xfbju4O2iXGqkSu4PQ==\" "
" },"
// this entry has an invalid algorithm
" { \"name\": \"key2\" , "
" \"algorithm\": \"\" ,"
" \"digest_bits\": 120 , "
" \"secret\": \"GWG/Xfbju4O2iXGqkSu4PQ==\" "
" },"
" { \"name\": \"key3\" , "
......@@ -680,10 +685,12 @@ TEST_F(TSIGKeyInfoTest, duplicateTSIGKey) {
" { \"name\": \"key1\" , "
" \"algorithm\": \"HMAC-MD5\" ,"
" \"digest_bits\": 120 , "
" \"secret\": \"GWG/Xfbju4O2iXGqkSu4PQ==\" "
" },"
" { \"name\": \"key2\" , "
" \"algorithm\": \"HMAC-MD5\" ,"
" \"digest_bits\": 120 , "
" \"secret\": \"GWG/Xfbju4O2iXGqkSu4PQ==\" "
" },"
" { \"name\": \"key1\" , "
......@@ -710,26 +717,32 @@ TEST_F(TSIGKeyInfoTest, validTSIGKeyList) {
" { \"name\": \"key1\" , "
" \"algorithm\": \"HMAC-MD5\" ,"
" \"digest_bits\": 80 , "
" \"secret\": \"dGhpcyBrZXkgd2lsbCBtYXRjaA==\" "
" },"
" { \"name\": \"key2\" , "
" \"algorithm\": \"HMAC-SHA1\" ,"
" \"digest_bits\": 80 , "
" \"secret\": \"dGhpcyBrZXkgd2lsbCBtYXRjaA==\" "
" },"
" { \"name\": \"key3\" , "
" \"algorithm\": \"HMAC-SHA256\" ,"
" \"digest_bits\": 128 , "
" \"secret\": \"dGhpcyBrZXkgd2lsbCBtYXRjaA==\" "
" },"
" { \"name\": \"key4\" , "
" \"algorithm\": \"HMAC-SHA224\" ,"
" \"digest_bits\": 112 , "
" \"secret\": \"dGhpcyBrZXkgd2lsbCBtYXRjaA==\" "
" },"
" { \"name\": \"key5\" , "
" \"algorithm\": \"HMAC-SHA384\" ,"
" \"digest_bits\": 192 , "
" \"secret\": \"dGhpcyBrZXkgd2lsbCBtYXRjaA==\" "
" },"
" { \"name\": \"key6\" , "
" \"algorithm\": \"HMAC-SHA512\" ,"
" \"digest_bits\": 256 , "
" \"secret\": \"dGhpcyBrZXkgd2lsbCBtYXRjaA==\" "
" }"
" ]";
......@@ -754,7 +767,8 @@ TEST_F(TSIGKeyInfoTest, validTSIGKeyList) {
TSIGKeyInfoPtr& key = gotit->second;
// Verify the key contents.
EXPECT_TRUE(checkKey(key, "key1", TSIGKeyInfo::HMAC_MD5_STR, ref_secret));
EXPECT_TRUE(checkKey(key, "key1", TSIGKeyInfo::HMAC_MD5_STR,
ref_secret, 80));
// Find the 2nd key and retrieve it.
gotit = keys_->find("key2");
......@@ -762,7 +776,8 @@ TEST_F(TSIGKeyInfoTest, validTSIGKeyList) {
key = gotit->second;
// Verify the key contents.
EXPECT_TRUE(checkKey(key, "key2", TSIGKeyInfo::HMAC_SHA1_STR, ref_secret));
EXPECT_TRUE(checkKey(key, "key2", TSIGKeyInfo::HMAC_SHA1_STR,
ref_secret, 80));
// Find the 3rd key and retrieve it.
gotit = keys_->find("key3");
......@@ -771,7 +786,7 @@ TEST_F(TSIGKeyInfoTest, validTSIGKeyList) {
// Verify the key contents.
EXPECT_TRUE(checkKey(key, "key3", TSIGKeyInfo::HMAC_SHA256_STR,
ref_secret));
ref_secret, 128));
// Find the 4th key and retrieve it.
gotit = keys_->find("key4");
......@@ -780,7 +795,7 @@ TEST_F(TSIGKeyInfoTest, validTSIGKeyList) {
// Verify the key contents.
EXPECT_TRUE(checkKey(key, "key4", TSIGKeyInfo::HMAC_SHA224_STR,
ref_secret));
ref_secret, 112));
// Find the 5th key and retrieve it.
gotit = keys_->find("key5");
......@@ -789,7 +804,7 @@ TEST_F(TSIGKeyInfoTest, validTSIGKeyList) {
// Verify the key contents.
EXPECT_TRUE(checkKey(key, "key5", TSIGKeyInfo::HMAC_SHA384_STR,
ref_secret));
ref_secret, 192));
// Find the 6th key and retrieve it.
gotit = keys_->find("key6");
......@@ -798,7 +813,7 @@ TEST_F(TSIGKeyInfoTest, validTSIGKeyList) {
// Verify the key contents.
EXPECT_TRUE(checkKey(key, "key6", TSIGKeyInfo::HMAC_SHA512_STR,
ref_secret));
ref_secret, 256));
}
/// @brief Tests the enforcement of data validation when parsing DnsServerInfos.
......@@ -1261,6 +1276,7 @@ TEST_F(D2CfgMgrTest, fullConfig) {
"{"
" \"name\": \"d2_key.billcat.net\" , "
" \"algorithm\": \"hmac-md5\" , "
" \"digest_bits\": 120 , "
" \"secret\": \"LSWXnfkKZjdPJI5QxlpnfQ==\" "
"}"
"],"
......
......@@ -479,6 +479,195 @@
}
}
#----- D2.tsig_keys, digest_bits tests
,{
"description" : "D2.tsig_keys, all valid algorthms",
"data" :
{
"forward_ddns" : {},
"reverse_ddns" : {},
"tsig_keys" :
[
{
"name" : "d2.md5.key",
"algorithm" : "HMAC-MD5",
"digest_bits" : 80,
"secret" : "LSWXnfkKZjdPJI5QxlpnfQ=="
},
{
"name" : "d2.sha1.key",
"algorithm" : "HMAC-SHA1",
"digest_bits" : 80,
"secret" : "hRrp29wzUv3uzSNRLlY68w=="
},
{
"name" : "d2.sha224.key",
"algorithm" : "HMAC-SHA224",
"digest_bits" : 112,
"secret" : "bZEG7Ow8OgAUPfLWV3aAUQ=="
},
{
"name" : "d2.sha256.key",
"algorithm" : "hmac-sha256",
"digest_bits" : 128,
"secret" : "bjF4hYhTfQ5MX0siagelsw=="
},
{
"name" : "d2.sha384.key",
"algorithm" : "hmac-sha384",
"digest_bits" : 192,
"secret" : "Gwk53fvy3CmbupoI9TgigA=="
},
{
"name" : "d2.sha512.key",
"algorithm" : "hmac-sha512",
"digest_bits" : 256,
"secret" : "wP+5FqMnKXCxBWljU/BZAA=="
}
]
}
}
#-----
,{
"description" : "D2.tsig_keys, invalid digest_bits",
"should_fail" : true,
"data" :
{
"forward_ddns" : {},
"reverse_ddns" : {},
"tsig_keys" :
[
{
"name" : "d2.md5.key",
"algorithm" : "HMAC-MD5",
"digest_bits" : 84,
"secret" : "LSWXnfkKZjdPJI5QxlpnfQ=="
},
]
}
}
#-----
,{
"description" : "D2.tsig_keys, too small truncated HMAC-MD5",
"should_fail" : true,
"data" :
{
"forward_ddns" : {},
"reverse_ddns" : {},
"tsig_keys" :
[
{
"name" : "d2.md5.key",
"algorithm" : "HMAC-MD5",
"digest_bits" : 72,
"secret" : "LSWXnfkKZjdPJI5QxlpnfQ=="
},
]
}
}
#-----
,{
"description" : "D2.tsig_keys, too small truncated HMAC-SHA1",
"should_fail" : true,
"data" :
{
"forward_ddns" : {},
"reverse_ddns" : {},
"tsig_keys" :
[
{
"name" : "d2.sha1.key",
"algorithm" : "HMAC-SHA1",
"digest_bits" : 72,
"secret" : "hRrp29wzUv3uzSNRLlY68w=="
},
]
}
}
#-----
,{
"description" : "D2.tsig_keys, too small truncated HMAC-SHA224",
"should_fail" : true,
"data" :
{
"forward_ddns" : {},
"reverse_ddns" : {},
"tsig_keys" :
[
{
"name" : "d2.sha224.key",
"algorithm" : "HMAC-SHA224",
"digest_bits" : 104,
"secret" : "bZEG7Ow8OgAUPfLWV3aAUQ=="
},
]
}
}
#-----
,{
"description" : "D2.tsig_keys, too small truncated HMAC-SHA256",
"should_fail" : true,
"data" :
{
"forward_ddns" : {},
"reverse_ddns" : {},
"tsig_keys" :
[
{
"name" : "d2.sha256.key",
"algorithm" : "hmac-sha256",
"digest_bits" : 120,
"secret" : "bjF4hYhTfQ5MX0siagelsw=="
},
]
}
}
#-----
,{
"description" : "D2.tsig_keys, too small truncated HMAC-SHA384",
"should_fail" : true,
"data" :
{
"forward_ddns" : {},
"reverse_ddns" : {},
"tsig_keys" :
[
{
"name" : "d2.sha384.key",
"algorithm" : "hmac-sha384",
"digest_bits" : 184,
"secret" : "Gwk53fvy3CmbupoI9TgigA=="
},
]
}
}
#-----
,{
"description" : "D2.tsig_keys, too small truncated HMAC-SHA512",
"should_fail" : true,
"data" :
{
"forward_ddns" : {},
"reverse_ddns" : {},
"tsig_keys" :
[
{
"name" : "d2.sha512.key",
"algorithm" : "hmac-sha512",
"digest_bits" : 248,
"secret" : "wP+5FqMnKXCxBWljU/BZAA=="
}
]
}
}
#----- D2.tsig_keys, secret tests
,{
"description" : "D2.tsig_keys, missing secret",
......
// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
// Copyright (C) 2011, 2014 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
......@@ -178,19 +178,14 @@ public:
/// @todo 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
/// SEE BELOW FOR TEMPORARY CHANGE
try {
Botan::SecureVector<Botan::byte> our_mac = hmac_->final();
if (len < getOutputLength()) {
// Currently we don't support truncated signature in TSIG (see
// #920). To avoid validating too short signature accidently,
// we enforce the standard signature size for the moment.
// Once we support truncation correctly, this if-clause should
// (and the capitalized comment above) be removed.
size_t size = getOutputLength();
if (len != 0 && (len < 10 || len < size / 2)) {
return (false);
}
if (len == 0 || len > getOutputLength()) {
len = getOutputLength();
if (len == 0 || len > size) {
len = size;
}
return (Botan::same_mem(&our_mac[0],
static_cast<const unsigned char*>(sig),
......
// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
// Copyright (C) 2011, 2014 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 (C) 2011 Internet Systems Consortium, Inc. ("ISC")
// Copyright (C) 2011, 2014 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 (C) 2011 Internet Systems Consortium, Inc. ("ISC")
// Copyright (C) 2011, 2014 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 (C) 2011 Internet Systems Consortium, Inc. ("ISC")
// Copyright (C) 2011, 2014 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
......
......@@ -201,7 +201,7 @@ public:
/// See @ref isc::cryptolink::HMAC::verify() for details.
bool verify(const void* sig, size_t len) {
size_t size = getOutputLength();
if (len != 0 && len < size / 2) {
if (len != 0 && (len < 10 || len < size / 2)) {
return (false);
}