]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#4326] Checkpoint
authorFrancis Dupont <fdupont@isc.org>
Tue, 3 Mar 2026 14:26:36 +0000 (15:26 +0100)
committerFrancis Dupont <fdupont@isc.org>
Sat, 23 May 2026 17:53:48 +0000 (19:53 +0200)
doc/sphinx/arm/ext-gss-tsig.rst
src/hooks/d2/gss_tsig/tests/gss_tsig_api_utils.h
src/hooks/d2/gss_tsig/tests/gss_tsig_cfg_unittests.cc
src/hooks/d2/gss_tsig/tests/gss_tsig_context_unittests.cc

index 508f88467b1dc996b49b16654f18aed10b5430e9..2a6f2f1f092d71f0e5b574939a307a78ab197365 100644 (file)
@@ -599,76 +599,79 @@ defined, or if all servers have different values.
 
 .. table:: List of available parameters
 
-   +-------------------+----------+---------+---------------------+--------------------------------+
-   | Name              | Scope    | Type    | Default value       | Description                    |
-   |                   |          |         |                     |                                |
-   +===================+==========+=========+=====================+================================+
-   | client-keytab     | global / | string  | empty               | the Kerberos **client** key    |
-   |                   | server   |         |                     | table                          |
-   +-------------------+----------+---------+---------------------+--------------------------------+
-   | credentials-cache | global / | string  | empty               | the Kerberos credentials cache |
-   |                   | server   |         |                     |                                |
-   +-------------------+----------+---------+---------------------+--------------------------------+
-   | server-principal  | global / | string  | empty               | the Kerberos principal name of |
-   |                   | server   |         |                     | the DNS server that will       |
-   |                   |          |         |                     | receive updates                |
-   +-------------------+----------+---------+---------------------+--------------------------------+
-   | client-principal  | global / | string  | empty               | the Kerberos principal name of |
-   |                   | server   |         |                     | the Kea D2 service             |
-   +-------------------+----------+---------+---------------------+--------------------------------+
-   | gss-replay-flag   | global / | true /  | true                | require the GSS anti replay    |
-   |                   | server   | false   |                     | service (GSS_C_REPLAY_FLAG)    |
-   +-------------------+----------+---------+---------------------+--------------------------------+
-   | gss-sequence-flag | global / | true /  | false               | require the GSS sequence       |
-   |                   | server   | false   |                     | service (GSS_C_SEQUENCE_FLAG)  |
-   +-------------------+----------+---------+---------------------+--------------------------------+
-   | tkey-protocol     | global / | string  | "TCP"               | the protocol used to establish |
-   |                   | server   | "TCP" / |                     | the security context with the  |
-   |                   |          | "UDP"   |                     | DNS servers                    |
-   +-------------------+----------+---------+---------------------+--------------------------------+
-   | tkey-lifetime     | global / | uint32  | | 3600 seconds      | the lifetime of GSS-TSIG keys  |
-   |                   | server   |         | | ( 1 hour )        |                                |
-   +-------------------+----------+---------+---------------------+--------------------------------+
-   | rekey-interval    | global / | uint32  | | 2700 seconds      | the time interval the keys are |
-   |                   | server   |         | | ( 45 minutes )    | checked for rekeying           |
-   +-------------------+----------+---------+---------------------+--------------------------------+
-   | retry-interval    | global / | uint32  | | 120 seconds       | the time interval to retry to  |
-   |                   | server   |         | | ( 2 minutes )     | create a key if any error      |
-   |                   |          |         |                     | occurred previously            |
-   +-------------------+----------+---------+---------------------+--------------------------------+
-   | fallback          | global / | true /  | false               | the behavior to fallback to    |
-   |                   | server   | false   |                     | non-GSS-TSIG when GSS-TSIG     |
-   |                   |          |         |                     | should be used but no GSS-TSIG |
-   |                   |          |         |                     | key is available.              |
-   +-------------------+----------+---------+---------------------+--------------------------------+
-   | exchange-timeout  | global / | uint32  | | 3000 milliseconds | the time used to wait for the  |
-   |                   | server   |         | | ( 3 seconds )     | GSS-TSIG TKEY exchange to      |
-   |                   |          |         |                     | finish before it timeouts      |
-   +-------------------+----------+---------+---------------------+--------------------------------+
-   | user-context      | global / | string  | empty               | the user-provided data in JSON |
-   |                   | server   |         |                     | format (not used by            |
-   |                   |          |         |                     | the GSS-TSIG hook)             |
-   +-------------------+----------+---------+---------------------+--------------------------------+
-   | comment           | global / | string  | empty               | ignored                        |
-   |                   | server   |         |                     |                                |
-   +-------------------+----------+---------+---------------------+--------------------------------+
-   | id                | server   | string  | empty               | identifier to a DNS server     |
-   |                   |          |         |                     | (required)                     |
-   +-------------------+----------+---------+---------------------+--------------------------------+
-   | domain-names      | server   | list of | empty               | the many-to-one relationship   |
-   |                   |          | strings |                     | between D2 DNS servers and     |
-   |                   |          |         |                     | GSS-TSIG DNS servers           |
-   +-------------------+----------+---------+---------------------+--------------------------------+
-   | ip-address        | server   | IPv4 /  | empty               | the IP address at which the    |
-   |                   |          | IPv6    |                     | GSS-TSIG DNS server listens    |
-   |                   |          | address |                     | for DDNS and TKEY requests     |
-   |                   |          |         |                     | (required)                     |
-   +-------------------+----------+---------+---------------------+--------------------------------+
-   | port              | server   | uint16  | 53                  | the DNS transport port at      |
-   |                   |          |         |                     | which the GSS-TSIG DNS server  |
-   |                   |          |         |                     | listens for DDNS and TKEY      |
-   |                   |          |         |                     | requests                       |
-   +-------------------+----------+---------+---------------------+--------------------------------+
+   +----------------------+----------+---------+---------------------+--------------------------------+
+   | Name                 | Scope    | Type    | Default value       | Description                    |
+   |                      |          |         |                     |                                |
+   +======================+==========+=========+=====================+================================+
+   | client-keytab        | global / | string  | empty               | the Kerberos **client** key    |
+   |                      | server   |         |                     | table                          |
+   +----------------------+----------+---------+---------------------+--------------------------------+
+   | credentials-cache    | global / | string  | empty               | the Kerberos credentials cache |
+   |                      | server   |         |                     |                                |
+   +----------------------+----------+---------+---------------------+--------------------------------+
+   | server-principal     | global / | string  | empty               | the Kerberos principal name of |
+   |                      | server   |         |                     | the DNS server that will       |
+   |                      |          |         |                     | receive updates                |
+   +----------------------+----------+---------+---------------------+--------------------------------+
+   | client-principal     | global / | string  | empty               | the Kerberos principal name of |
+   |                      | server   |         |                     | the Kea D2 service             |
+   +----------------------+----------+---------+---------------------+--------------------------------+
+   | gss-replay-flag      | global / | true /  | true                | require the GSS anti replay    |
+   |                      | server   | false   |                     | service (GSS_C_REPLAY_FLAG)    |
+   +----------------------+----------+---------+---------------------+--------------------------------+
+   | gss-sequence-flag    | global / | true /  | false               | require the GSS sequence       |
+   |                      | server   | false   |                     | service (GSS_C_SEQUENCE_FLAG)  |
+   +----------------------+----------+---------+---------------------+--------------------------------+
+   | tkey-protocol        | global / | string  | "TCP"               | the protocol used to establish |
+   |                      | server   | "TCP" / |                     | the security context with the  |
+   |                      |          | "UDP"   |                     | DNS servers                    |
+   +----------------------+----------+---------+---------------------+--------------------------------+
+   | tkey-lifetime        | global / | uint32  | | 3600 seconds      | the lifetime of GSS-TSIG keys  |
+   |                      | server   |         | | ( 1 hour )        |                                |
+   +----------------------+----------+---------+---------------------+--------------------------------+
+   | rekey-interval       | global / | uint32  | | 2700 seconds      | the time interval the keys are |
+   |                      | server   |         | | ( 45 minutes )    | checked for rekeying           |
+   +----------------------+----------+---------+---------------------+--------------------------------+
+   | retry-interval       | global / | uint32  | | 120 seconds       | the time interval to retry to  |
+   |                      | server   |         | | ( 2 minutes )     | create a key if any error      |
+   |                      |          |         |                     | occurred previously            |
+   +----------------------+----------+---------+---------------------+--------------------------------+
+   | fallback             | global / | true /  | false               | the behavior to fallback to    |
+   |                      | server   | false   |                     | non-GSS-TSIG when GSS-TSIG     |
+   |                      |          |         |                     | should be used but no GSS-TSIG |
+   |                      |          |         |                     | key is available.              |
+   +----------------------+----------+---------+---------------------+--------------------------------+
+   | exchange-timeout     | global / | uint32  | | 3000 milliseconds | the time used to wait for the  |
+   |                      | server   |         | | ( 3 seconds )     | GSS-TSIG TKEY exchange to      |
+   |                      |          |         |                     | finish before it timeouts      |
+   +----------------------+----------+---------+---------------------+--------------------------------+
+   | ignore-bad-direction | global   | true /  | false               | ignore invalid MIC / bad       |      
+   |                      |          | false   |                     | direction verify failures.     |
+   +----------------------+----------+---------+---------------------+--------------------------------+
+   | user-context         | global / | string  | empty               | the user-provided data in JSON |
+   |                      | server   |         |                     | format (not used by            |
+   |                      |          |         |                     | the GSS-TSIG hook)             |
+   +----------------------+----------+---------+---------------------+--------------------------------+
+   | comment              | global / | string  | empty               | ignored                        |
+   |                      | server   |         |                     |                                |
+   +----------------------+----------+---------+---------------------+--------------------------------+
+   | id                   | server   | string  | empty               | identifier to a DNS server     |
+   |                      |          |         |                     | (required)                     |
+   +----------------------+----------+---------+---------------------+--------------------------------+
+   | domain-names         | server   | list of | empty               | the many-to-one relationship   |
+   |                      |          | strings |                     | between D2 DNS servers and     |
+   |                      |          |         |                     | GSS-TSIG DNS servers           |
+   +----------------------+----------+---------+---------------------+--------------------------------+
+   | ip-address           | server   | IPv4 /  | empty               | the IP address at which the    |
+   |                      |          | IPv6    |                     | GSS-TSIG DNS server listens    |
+   |                      |          | address |                     | for DDNS and TKEY requests     |
+   |                      |          |         |                     | (required)                     |
+   +----------------------+----------+---------+---------------------+--------------------------------+
+   | port                 | server   | uint16  | 53                  | the DNS transport port at      |
+   |                      |          |         |                     | which the GSS-TSIG DNS server  |
+   |                      |          |         |                     | listens for DDNS and TKEY      |
+   |                      |          |         |                     | requests                       |
+   +----------------------+----------+---------+---------------------+--------------------------------+
 
 The global parameters are described below:
 
@@ -819,6 +822,10 @@ The server map parameters are described below:
   level. The default and supported values for the per-server level parameter are the same as
   for the global-level parameter.
 
+- ``ignore-bad-direction`` governs a workaround for Microsoft server bug.
+  When set explicitly to true DNS update responses sent when prerequisites
+  fail with the request signature are accepted as verified.
+
 - ``user-context`` is an optional parameter (see :ref:`user-context`
   for a general description of user contexts in Kea).
 
index f17cf416a378265537d9789e7d3f90b54968f726..be4bd7bd16888d14ac5d0598a3795aa081210049 100644 (file)
@@ -7,6 +7,7 @@
 #ifndef GSS_TSIG_API_UTILS_H
 #define GSS_TSIG_API_UTILS_H
 
+#include <gss_tsig_context.h>
 #include <gtest/gtest.h>
 #include <cstdlib>
 
@@ -30,6 +31,7 @@ public:
             has_ccname = true;
             ccname = std::string(ccname_env);
         }
+        GssApiSecCtx::ignore_bad_direction_ = false;
     }
 
     /// @brief Destructor.
@@ -44,6 +46,7 @@ public:
         } else {
             unsetenv("KRB5CCNAME");
         }
+        GssApiSecCtx::ignore_bad_direction_ = false;
     }
 
     /// @brief Set the keytab.
index 77c74b1e34b7e37dc43ce3fa40db1632f18d21d3..cf65cd10aa6a227a1c344132683994622dc6702b 100644 (file)
@@ -668,6 +668,7 @@ TEST(GssTsigCfgTest, configure) {
         "\"tkey-lifetime\": 7200,\n"
         "\"tkey-protocol\": \"UDP\",\n"
         "\"exchange-timeout\": 2000,\n"
+        "\"ignore-bad-direction\": true,\n"
         "\"servers\": [\n"
         " {\n"
         "  \"domain-names\": [ ],\n"
@@ -697,6 +698,10 @@ TEST(GssTsigCfgTest, configure) {
     EXPECT_EQ("FILE:/etc/krb5.keytab", cfg.getClientKeyTab());
     EXPECT_EQ("FILE:/etc/ccache", cfg.getCredsCache());
     EXPECT_EQ(86400, cfg.getMaxKeyLifetime());
+    EXPECT_TRUE(cfg.getIgnoreBadDirection());
+    EXPECT_TRUE(GssApiSecCtx::ignore_bad_direction_);
+    // Put this in the fixture if one is created...
+    GssApiSecCtx::ignore_bad_direction_ = false;
     const DnsServerList& servers = cfg.getServerList();
     ASSERT_EQ(2, servers.size());
 
@@ -844,6 +849,13 @@ TEST(GssTsigCfgTest, configureUnexpectedType) {
     expected += location;
     EXPECT_THROW_MSG(cfg.configure(json), BadValue, expected);
 
+    config = "{ \"ignore-bad-direction\": 1 }";
+    ASSERT_NO_THROW(json = Element::fromJSON(config));
+    ASSERT_TRUE(json);
+    expected = "gss_tsig 'ignore-bad-direction' parameter is not a boolean";
+    expected += location;
+    EXPECT_THROW_MSG(cfg.configure(json), BadValue, expected);
+
     config = "{ \"user-context\": [ ] }";
     ASSERT_NO_THROW(json = Element::fromJSON(config));
     ASSERT_TRUE(json);
@@ -1234,6 +1246,18 @@ TEST(GssTsigCfgTest, configureDuplicate) {
     EXPECT_THROW_MSG(cfg.configure(json), BadValue, expected);
 }
 
+/// @brief Checks ignore bad direction default value.
+TEST(GssTsigCfgTest, IgnoreBadDirectionDefault) {
+    // Constructor default is false.
+     GssTsigCfg cfg;
+     ASSERT_FALSE(cfg.getIgnoreBadDirection());
+     ASSERT_FALSE(GssApiSecCtx::ignore_bad_direction_);
+     ConstElementPtr json = Element::createMap();
+     ASSERT_NO_THROW(cfg.configure(json));
+     EXPECT_FALSE(cfg.getIgnoreBadDirection());
+     EXPECT_FALSE(GssApiSecCtx::ignore_bad_direction_);
+}
+
 /// @brief Check TKEY protocol default value.
 TEST(GssTsigCfgTest, tkeyProtoDefault) {
     // Constructor default is TCP.
index 669b5350cd7be4ba193ab651e79c77271ee3fd49..f06cbb8f3151289f20749c88ddf913ac0955014d 100644 (file)
@@ -648,4 +648,176 @@ TEST_F(GssTsigContextTest, signBadVerify) {
     EXPECT_EQ(GSS_S_BAD_SIG, srv_key->getSecCtx().getLastError());
 }
 
+/// @brief Check that verify fail on bad direction.
+TEST_F(GssTsigContextTest, badDirection) {
+    GssTsigKeyPtr key;
+    string name = "1234.sig-foo.example.com.";
+    ASSERT_NO_THROW(key.reset(new GssTsigKey(name)));
+    ASSERT_TRUE(key);
+
+    setKeytab();
+    setAdministratorCCache();
+
+    // Server.
+    GssTsigKeyPtr srv_key;
+    ASSERT_NO_THROW(srv_key.reset(new GssTsigKey(name)));
+    ASSERT_TRUE(srv_key);
+    EXPECT_FALSE(srv_key->getSecCtx().get());
+    GssApiName srv_name("DNS/blu.example.nil@EXAMPLE.NIL");
+    OM_uint32 lifetime = 0;
+    GssApiCredPtr srv_cred(new GssApiCred(srv_name, GSS_C_ACCEPT, lifetime));
+
+    // Client.
+    GssApiName clnt_name;
+    GssApiCredPtr cred;
+    EXPECT_FALSE(key->getSecCtx().get());
+    OM_uint32 flags = GSS_C_REPLAY_FLAG | GSS_C_MUTUAL_FLAG | GSS_C_INTEG_FLAG;
+    bool ret = false;
+
+    // Exchanges.
+    GssApiBuffer intoken0;
+    GssApiBuffer outtoken0;
+    ASSERT_NO_THROW(ret = key->getSecCtx().init(cred, srv_name, flags,
+                                                intoken0, outtoken0,
+                                                lifetime));
+    ASSERT_FALSE(outtoken0.empty());
+    GssApiBuffer outtoken1;
+    ASSERT_NO_THROW(ret = srv_key->getSecCtx().accept(*srv_cred, outtoken0,
+                                                      clnt_name, outtoken1));
+    EXPECT_TRUE(ret);
+    GssApiBuffer outtoken2;
+    ASSERT_NO_THROW(ret = key->getSecCtx().init(cred, srv_name, flags,
+                                                outtoken1, outtoken2,
+                                                lifetime));
+    ASSERT_TRUE(ret);
+    EXPECT_TRUE(outtoken2.empty());
+
+    // Build the message to sign.
+    message_.clear(Message::RENDER);
+    message_.setQid(0x1234);
+    message_.setOpcode(Opcode::QUERY());
+    message_.setRcode(Rcode::NOERROR());
+    message_.setHeaderFlag(Message::HEADERFLAG_QR);
+    Name qname("foo.example.nil.");
+    message_.addQuestion(Question(qname, RRClass::IN(), RRType::A()));
+    MessageRenderer renderer;
+    OutputBuffer obuf(1024);
+    renderer.setBuffer(&obuf);
+
+    // Sign.
+    GssTsigContextPtr ctx;
+    ASSERT_NO_THROW(ctx.reset(new GssTsigContext(*key)));
+    ASSERT_TRUE(ctx);
+    EXPECT_NO_THROW(message_.toWire(renderer, ctx.get()));
+    // len is 33 + 62 + 28 = 123.
+    ASSERT_TRUE(obuf.getData());
+    EXPECT_EQ(123, obuf.getLength());
+
+    // Check the TSIG RR.
+    message_.clear(Message::PARSE);
+    InputBuffer ibuf(obuf.getData(), obuf.getLength());
+    EXPECT_NO_THROW(message_.fromWire(ibuf));
+    const TSIGRecord* tsig = message_.getTSIGRecord();
+    ASSERT_TRUE(tsig);
+
+    // Verify using the wrong key.
+    ASSERT_NO_THROW(ctx.reset(new GssTsigContext(*key)));
+    ASSERT_TRUE(ctx);
+    TSIGError error = TSIGError::NOERROR();
+    EXPECT_NO_THROW(error = ctx->verify(tsig, obuf.getData(),
+                                        obuf.getLength()));
+    // Error message is 'A token had an invalid Message Integrity Check (MIC)',
+    // and 'Packet was replayed in wrong direction'.
+    EXPECT_EQ(TSIGError::BAD_SIG(), error);
+    EXPECT_EQ(TSIGError::BAD_SIG(), ctx->getError());
+    EXPECT_EQ(TSIGContext::RECEIVED_REQUEST, ctx->getState());
+    EXPECT_EQ(GSS_S_BAD_SIG, key->getSecCtx().getLastError());
+}
+
+/// @brief Check that verify fail on bad direction instead ignored.
+TEST_F(GssTsigContextTest, ignoreBadDirection) {
+    GssTsigKeyPtr key;
+    string name = "1234.sig-foo.example.com.";
+    ASSERT_NO_THROW(key.reset(new GssTsigKey(name)));
+    ASSERT_TRUE(key);
+
+    setKeytab();
+    setAdministratorCCache();
+
+    // Server.
+    GssTsigKeyPtr srv_key;
+    ASSERT_NO_THROW(srv_key.reset(new GssTsigKey(name)));
+    ASSERT_TRUE(srv_key);
+    EXPECT_FALSE(srv_key->getSecCtx().get());
+    GssApiName srv_name("DNS/blu.example.nil@EXAMPLE.NIL");
+    OM_uint32 lifetime = 0;
+    GssApiCredPtr srv_cred(new GssApiCred(srv_name, GSS_C_ACCEPT, lifetime));
+
+    // Client.
+    GssApiName clnt_name;
+    GssApiCredPtr cred;
+    EXPECT_FALSE(key->getSecCtx().get());
+    OM_uint32 flags = GSS_C_REPLAY_FLAG | GSS_C_MUTUAL_FLAG | GSS_C_INTEG_FLAG;
+    bool ret = false;
+
+    // Exchanges.
+    GssApiBuffer intoken0;
+    GssApiBuffer outtoken0;
+    ASSERT_NO_THROW(ret = key->getSecCtx().init(cred, srv_name, flags,
+                                                intoken0, outtoken0,
+                                                lifetime));
+    ASSERT_FALSE(outtoken0.empty());
+    GssApiBuffer outtoken1;
+    ASSERT_NO_THROW(ret = srv_key->getSecCtx().accept(*srv_cred, outtoken0,
+                                                      clnt_name, outtoken1));
+    EXPECT_TRUE(ret);
+    GssApiBuffer outtoken2;
+    ASSERT_NO_THROW(ret = key->getSecCtx().init(cred, srv_name, flags,
+                                                outtoken1, outtoken2,
+                                                lifetime));
+    ASSERT_TRUE(ret);
+    EXPECT_TRUE(outtoken2.empty());
+
+    // Build the message to sign.
+    message_.clear(Message::RENDER);
+    message_.setQid(0x1234);
+    message_.setOpcode(Opcode::QUERY());
+    message_.setRcode(Rcode::NOERROR());
+    message_.setHeaderFlag(Message::HEADERFLAG_QR);
+    Name qname("foo.example.nil.");
+    message_.addQuestion(Question(qname, RRClass::IN(), RRType::A()));
+    MessageRenderer renderer;
+    OutputBuffer obuf(1024);
+    renderer.setBuffer(&obuf);
+
+    // Sign.
+    GssTsigContextPtr ctx;
+    ASSERT_NO_THROW(ctx.reset(new GssTsigContext(*key)));
+    ASSERT_TRUE(ctx);
+    EXPECT_NO_THROW(message_.toWire(renderer, ctx.get()));
+    // len is 33 + 62 + 28 = 123.
+    ASSERT_TRUE(obuf.getData());
+    EXPECT_EQ(123, obuf.getLength());
+
+    // Check the TSIG RR.
+    message_.clear(Message::PARSE);
+    InputBuffer ibuf(obuf.getData(), obuf.getLength());
+    EXPECT_NO_THROW(message_.fromWire(ibuf));
+    const TSIGRecord* tsig = message_.getTSIGRecord();
+    ASSERT_TRUE(tsig);
+
+    // Verify using the wrong key.
+    ASSERT_NO_THROW(ctx.reset(new GssTsigContext(*key)));
+    ASSERT_TRUE(ctx);
+    TSIGError error = TSIGError::NOERROR();
+    GssApiSecCtx::ignore_bad_direction_ = true;
+    EXPECT_NO_THROW(error = ctx->verify(tsig, obuf.getData(),
+                                        obuf.getLength()));
+    // No longer fail even the signature was in the wrong direction.
+    EXPECT_EQ(TSIGError::NOERROR(), error);
+    EXPECT_TRUE(ctx->lastHadSignature());
+    EXPECT_EQ(TSIGError::NOERROR(), ctx->getError());
+    EXPECT_EQ(TSIGContext::RECEIVED_REQUEST, ctx->getState());
+}
+
 }