]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#4282] Checkpoint: add Status-Server
authorFrancis Dupont <fdupont@isc.org>
Thu, 25 Dec 2025 10:04:13 +0000 (11:04 +0100)
committerFrancis Dupont <fdupont@isc.org>
Tue, 20 Jan 2026 09:31:09 +0000 (10:31 +0100)
src/hooks/dhcp/radius/client_exchange.cc
src/hooks/dhcp/radius/client_message.cc
src/hooks/dhcp/radius/client_message.h
src/hooks/dhcp/radius/radius_parsers.cc
src/hooks/dhcp/radius/tests/message_unittests.cc

index 63ce0ec802222ed6b4da3afccc86da92cba691d2..90257c53f8a01ed52a6f7a701e081ef0df7513a4 100644 (file)
@@ -217,7 +217,8 @@ Exchange::buildRequest() {
     sent_->randomIdentifier();
 
     // Randomize or zero the authenticator.
-    if (sent_->getCode() == PW_ACCESS_REQUEST) {
+    if ((sent_->getCode() == PW_ACCESS_REQUEST) ||
+        (sent_->getCode() == PW_STATUS_SERVER))  {
         sent_->randomAuth();
     } else {
         sent_->zeroAuth();
@@ -256,6 +257,13 @@ Exchange::buildRequest() {
         }
     }
 
+    // Add Message-Authenticator to Status-Server message.
+    if ((sent_->getCode() == PW_STATUS_SERVER) &&
+        (attrs->count(PW_MESSAGE_AUTHENTICATOR) == 0)) {
+        const vector<uint8_t> zero(AUTH_VECTOR_LEN);
+        attrs->add(Attribute::fromBinary(PW_MESSAGE_AUTHENTICATOR, zero));
+    }
+
     // Encode the request.
     sent_->encode();
 }
@@ -613,6 +621,26 @@ Exchange::receivedHandler(ExchangePtr ex,
                           RADIUS_EXCHANGE_RECEIVED_ACCOUNTING_RESPONSE)
                     .arg(ex->identifier_);
             }
+        } else if (ex->request_->getCode() == PW_STATUS_SERVER) {
+            if (ex->received_->getCode() == PW_ACCESS_ACCEPT) {
+                LOG_DEBUG(radius_logger, RADIUS_DBG_TRACE,
+                          RADIUS_EXCHANGE_RECEIVED_ACCESS_ACCEPT)
+                    .arg(ex->identifier_);
+            } else if (ex->received_->getCode() == PW_ACCESS_REJECT) {
+                LOG_DEBUG(radius_logger, RADIUS_DBG_TRACE,
+                          RADIUS_EXCHANGE_RECEIVED_ACCESS_REJECT)
+                    .arg(ex->identifier_);
+            } else if (ex->received_->getCode() == PW_ACCOUNTING_RESPONSE) {
+                LOG_DEBUG(radius_logger, RADIUS_DBG_TRACE,
+                          RADIUS_EXCHANGE_RECEIVED_ACCOUNTING_RESPONSE)
+                    .arg(ex->identifier_);
+            } else {
+                LOG_ERROR(radius_logger, RADIUS_EXCHANGE_RECEIVED_UNEXPECTED)
+                    .arg(ex->identifier_)
+                    .arg(msgCodeToText(ex->request_->getCode()))
+                    .arg(msgCodeToText(ex->received_->getCode()));
+                ex->rc_ = BADRESP_RC;
+            }
         }
     } catch (const Exception& exc) {
         LOG_ERROR(radius_logger, RADIUS_EXCHANGE_RECEIVED_BAD_RESPONSE)
@@ -627,7 +655,7 @@ Exchange::receivedHandler(ExchangePtr ex,
         .arg(exchangeRCtoText(ex->rc_));
 
     // If bad then retry, if not including reject it is done.
-    if ((ex->rc_ != OK_RC) && ( ex->rc_ != REJECT_RC)) {
+    if ((ex->rc_ != OK_RC) && (ex->rc_ != REJECT_RC)) {
         ex->io_service_->post(std::bind(&Exchange::openNext, ex));
     } else {
         ex->logReplyMessages();
index 6750ae6a79a76eb6c554d60f730e556f50037d57..c63ed4395497ab4d0834b8bd6c62f6451b3a942c 100644 (file)
 #include <client_message.h>
 #include <radius_log.h>
 #include <cryptolink/crypto_hash.h>
+#include <cryptolink/crypto_hmac.h>
 #include <cryptolink/crypto_rng.h>
 #include <boost/scoped_ptr.hpp>
+#include <cstring>
 #include <sstream>
 
 using namespace isc;
@@ -152,6 +154,7 @@ Message::encode() {
     memmove(&buffer_[4], &auth_[0], auth_.size());
 
     // Fill attributes.
+    size_t msg_auth_ptr = 0;
     if (attributes_) {
         for (auto attr : *attributes_) {
             if (!attr) {
@@ -161,6 +164,16 @@ Message::encode() {
                 (attr->getType() == PW_USER_PASSWORD)) {
                 attr = encodeUserPassword(attr);
             }
+            if (attr->getType() == PW_MESSAGE_AUTHENTICATOR) {
+                if (msg_auth_ptr != 0) {
+                    isc_throw(BadValue, "2 Message-Authenticator attributes");
+                }
+                if ((attr->getValueType() != PW_TYPE_STRING) ||
+                    (attr->getValueLen() != AUTH_VECTOR_LEN)) {
+                    isc_throw(BadValue, "bad Message-Authenticator attribute");
+                }
+                msg_auth_ptr = buffer_.size();
+            }
             vector<uint8_t> binary = attr->toBytes();
             if (binary.empty()) {
                 continue;
@@ -177,14 +190,17 @@ Message::encode() {
     buffer_[2] = static_cast<uint8_t>((length_ & 0xff00) >> 8);
     buffer_[3] = static_cast<uint8_t>(length_ & 0xff);
 
-    if (code_ != PW_ACCESS_REQUEST) {
+    // Computed before the Response Authenticator.
+    if (msg_auth_ptr != 0) {
+        signMessageAuthenticator(msg_auth_ptr);
+    }
+    if ((code_ != PW_ACCESS_REQUEST) && (code_ != PW_STATUS_SERVER)) {
         boost::scoped_ptr<Hash> md(CryptoLink::getCryptoLink().createHash(MD5));
         md->update(&buffer_[0], buffer_.size());
         md->update(&secret_[0], secret_.size());
         md->final(&auth_[0], AUTH_VECTOR_LEN);
         memmove(&buffer_[4], &auth_[0], auth_.size());
     }
-
     LOG_DEBUG(radius_logger, RADIUS_DBG_TRACE, RADIUS_ENCODE_MESSAGE)
         .arg(msgCodeToText(code_))
         .arg(static_cast<unsigned>(code_))
@@ -209,7 +225,7 @@ Message::decode() {
     identifier_ = buffer_[1];
     length_ = static_cast<uint16_t>(buffer_[2]) << 8;
     length_ |= static_cast<uint16_t>(buffer_[3]);
-    if (code_ == PW_ACCESS_REQUEST) {
+    if ((code_ == PW_ACCESS_REQUEST) || (code_ == PW_STATUS_SERVER)) {
         auth_.resize(AUTH_VECTOR_LEN);
         memmove(&auth_[0], &buffer_[4], AUTH_VECTOR_LEN);
     } else if (auth_.size() != AUTH_VECTOR_LEN) {
@@ -220,8 +236,8 @@ Message::decode() {
                   << " length " << length_ << ", got " << buffer_.size());
     }
     if (length_ < AUTH_HDR_LEN) {
-      isc_throw(BadValue, "too short " << msgCodeToText(code_)
-                << " length " << length_ << " < " << AUTH_HDR_LEN);
+        isc_throw(BadValue, "too short " << msgCodeToText(code_)
+                  << " length " << length_ << " < " << AUTH_HDR_LEN);
     }
     if (length_ > PW_MAX_MSG_SIZE) {
         isc_throw(BadValue, "too large " << msgCodeToText(code_)
@@ -232,7 +248,7 @@ Message::decode() {
     }
 
     // Verify authentication.
-    if (code_ != PW_ACCESS_REQUEST) {
+    if ((code_ != PW_ACCESS_REQUEST) && (code_ != PW_STATUS_SERVER)) {
         vector<uint8_t> work = buffer_;
         memmove(&work[4], &auth_[0], auth_.size());
         boost::scoped_ptr<Hash> md(CryptoLink::getCryptoLink().createHash(MD5));
@@ -246,12 +262,15 @@ Message::decode() {
                       << " failed");
         }
     }
-    auth_.resize(AUTH_VECTOR_LEN);
-    memmove(&auth_[0], &buffer_[4], auth_.size());
+    if (code_ == PW_ACCOUNTING_REQUEST) {
+        auth_.resize(AUTH_VECTOR_LEN);
+        memmove(&auth_[0], &buffer_[4], auth_.size());
+    }
 
     // Get attributes.
     attributes_.reset(new Attributes());
     size_t ptr = AUTH_HDR_LEN;
+    size_t msg_auth_ptr = 0;
     for (;;) {
         if (ptr == length_) {
             break;
@@ -280,9 +299,18 @@ Message::decode() {
             (attr->getType() == PW_USER_PASSWORD)) {
             attr = decodeUserPassword(attr);
         }
+        if (attr->getType() == PW_MESSAGE_AUTHENTICATOR) {
+            if (msg_auth_ptr != 0) {
+                isc_throw(BadValue, "2 Message-Authenticator attributes");
+            }
+            msg_auth_ptr = ptr;
+        }
         attributes_->add(attr);
         ptr += len;
     }
+    if (msg_auth_ptr != 0) {
+        verifyMessageAuthenticator(msg_auth_ptr);
+    }
     if (attributes_->empty()) {
         attributes_.reset();
     }
@@ -395,5 +423,52 @@ Message::decodeUserPassword(const ConstAttributePtr& attr) {
     return (Attribute::fromBinary(PW_USER_PASSWORD, password));
 }
 
+void
+Message::signMessageAuthenticator(size_t ptr) {
+    if ((ptr < AUTH_HDR_LEN) || (ptr > buffer_.size() - 2 - AUTH_VECTOR_LEN) ||
+        (buffer_[ptr + 1] != 2 + AUTH_VECTOR_LEN) ||
+        (auth_.size() != AUTH_VECTOR_LEN)) {
+        isc_throw(Unexpected, "Can't sign Message-Authenticator");
+    }
+
+    boost::scoped_ptr<HMAC> hmac(
+        CryptoLink::getCryptoLink().createHMAC(&secret_[0], secret_.size(), MD5));
+
+    // Compute Message-Authenticator content.
+    std::vector<uint8_t> to_sign = buffer_;
+    memmove(&to_sign[4], &auth_[0], auth_.size());
+    memset(&to_sign[ptr + 2], 0, AUTH_VECTOR_LEN);
+    hmac->update(&to_sign[0], to_sign.size());
+    vector<uint8_t> sign = hmac->sign(AUTH_VECTOR_LEN);
+    memmove(&buffer_[2 + ptr], &sign[0], sign.size());
+}
+
+void
+Message::verifyMessageAuthenticator(size_t ptr) {
+    if ((ptr < AUTH_HDR_LEN) || (ptr > buffer_.size() - 2 - AUTH_VECTOR_LEN) ||
+        (buffer_[ptr + 1] != 2 + AUTH_VECTOR_LEN) ||
+        (auth_.size() != AUTH_VECTOR_LEN)) {
+        isc_throw(BadValue, "can't verify Message-Authenticator");
+    }
+
+    vector<uint8_t> sign;
+    sign.resize(AUTH_VECTOR_LEN);
+    memmove(&sign[0], &buffer_[ptr + 2], sign.size());
+
+    boost::scoped_ptr<HMAC> hmac(
+        CryptoLink::getCryptoLink().createHMAC(&secret_[0], secret_.size(), MD5));
+
+    // Build to_verify buffer.
+    size_t length = buffer_.size();
+    std::vector<uint8_t> to_verify = buffer_;
+    memmove(&to_verify[4], &auth_[0], auth_.size());
+    memset(&to_verify[ptr + 2], 0, AUTH_VECTOR_LEN);
+
+    hmac->update(&to_verify[0], to_verify.size());
+    if (!hmac->verify(&sign[0], sign.size())) {
+        isc_throw(BadValue, "bad Message-Authenticator signature");
+    }
+}
+
 } // end of namespace isc::radius
 } // end of namespace isc
index 09e109381c3162692875641b59f09bf2979b10a1..7e7f1e44a7c245693c844e68a07b9cfda479d2a1 100644 (file)
@@ -225,6 +225,16 @@ protected:
     /// @return decoded User-Password attribute.
     ConstAttributePtr decodeUserPassword(const ConstAttributePtr& attr);
 
+    /// @brief Encode Message-Authenticator in an Status-Server.
+    ///
+    /// @param ptr the pointer to the attribute in the buffer.
+    void signMessageAuthenticator(size_t ptr);
+
+    /// @brief Decode Message-Authenticator in an Status-Server.
+    ///
+    /// @param ptr the pointer to the attribute in the buffer.
+    void verifyMessageAuthenticator(size_t ptr);
+
     /// @brief Code (useful values in MsgCode): header[0].
     uint8_t code_;
 
index 4dc11f5fa5dd20679f5c93e0d41462e98f7d9caa..d7e9702ebb8b2a765fbd10f69a1eeb439b414100 100644 (file)
@@ -79,6 +79,7 @@ const AttrDefList RadiusConfigParser::USED_STANDARD_ATTR_DEFS = {
     { PW_ACCT_STATUS_TYPE,      "Acct-Status-Type",      PW_TYPE_INTEGER },
     { PW_ACCT_DELAY_TIME,       "Acct-Delay-Time",       PW_TYPE_INTEGER },
     { PW_ACCT_SESSION_ID,       "Acct-Session-Id",       PW_TYPE_STRING },
+    { PW_MESSAGE_AUTHENTICATOR, "Message-Authenticator", PW_TYPE_STRING },
     { PW_FRAMED_POOL,           "Framed-Pool",           PW_TYPE_STRING },
     { PW_NAS_IPV6_ADDRESS,      "NAS-IPv6-Address",      PW_TYPE_IPV6ADDR },
     { PW_DELEGATED_IPV6_PREFIX, "Delegated-IPv6-Prefix", PW_TYPE_IPV6PREFIX },
index 4ef50f0e52d2b5a3918ebb62d077907a56ae89e2..ba32658b0465b64572b444a894611e0b2caff720 100644 (file)
@@ -214,7 +214,7 @@ TEST_F(MessageTest, acceptRequest) {
     // Verify buffer.
     vector<uint8_t> expected = {
         0x01,           // Code (Access-Request).
-        0x67,           // Identifier (9x67)
+        0x67,           // Identifier (0x67)
         0x00, 0x26,     // Length (38).
         // Authenticator.
         0x35, 0x6a, 0x48, 0xfd, 0x66, 0x24, 0x6a, 0xe3,
@@ -314,13 +314,17 @@ TEST_F(MessageTest, accessAccept) {
     EXPECT_EQ(code, response->getCode());
     EXPECT_EQ(id, response->getIdentifier());
     EXPECT_EQ(length, response->getLength());
+    vector<uint8_t> got_auth = response->getAuth();
+    ASSERT_EQ(AUTH_VECTOR_LEN, got_auth.size());
+    EXPECT_TRUE(memcmp(&auth[0], &got_auth[0], AUTH_VECTOR_LEN) == 0)
+        << str::dumpAsHex(&auth[0], AUTH_VECTOR_LEN) << "\n"
+        << str::dumpAsHex(&got_auth[0], AUTH_VECTOR_LEN);
     vector<uint8_t> digest = {
         0x2e, 0x8b, 0xcc, 0xb3, 0x4c, 0x10, 0xea, 0xd7,
         0x57, 0xd5, 0x5a, 0x48, 0x00, 0xd0, 0x7e, 0x2a
     };
     ASSERT_EQ(AUTH_VECTOR_LEN, digest.size());
-    vector<uint8_t> got_auth = response->getAuth();
-    ASSERT_EQ(AUTH_VECTOR_LEN, got_auth.size());
+    memmove(&got_auth[0], &buffer[4], got_auth.size());
     EXPECT_TRUE(memcmp(&digest[0], &got_auth[0], AUTH_VECTOR_LEN) == 0)
         << str::dumpAsHex(&digest[0], AUTH_VECTOR_LEN) << "\n"
         << str::dumpAsHex(&got_auth[0], AUTH_VECTOR_LEN);
@@ -469,13 +473,17 @@ TEST_F(MessageTest, accountingResponse) {
     EXPECT_EQ(code, response->getCode());
     EXPECT_EQ(id, response->getIdentifier());
     EXPECT_EQ(length, response->getLength());
+    vector<uint8_t> got_auth = response->getAuth();
+    ASSERT_EQ(AUTH_VECTOR_LEN, got_auth.size());
+    EXPECT_TRUE(memcmp(&auth[0], &got_auth[0], AUTH_VECTOR_LEN) == 0)
+        << str::dumpAsHex(&auth[0], AUTH_VECTOR_LEN) << "\n"
+        << str::dumpAsHex(&got_auth[0], AUTH_VECTOR_LEN);
     vector<uint8_t> digest = {
         0x88, 0xc4, 0x21, 0xc3, 0x25, 0xf3, 0xdc, 0x57,
         0x14, 0x01, 0x4c, 0xef, 0x78, 0x03, 0x64, 0xbe
     };
     ASSERT_EQ(AUTH_VECTOR_LEN, digest.size());
-    vector<uint8_t> got_auth = response->getAuth();
-    ASSERT_EQ(AUTH_VECTOR_LEN, got_auth.size());
+    memmove(&got_auth[0], &buffer[4], got_auth.size());
     EXPECT_TRUE(memcmp(&digest[0], &got_auth[0], AUTH_VECTOR_LEN) == 0)
         << str::dumpAsHex(&digest[0], AUTH_VECTOR_LEN) << "\n"
         << str::dumpAsHex(&got_auth[0], AUTH_VECTOR_LEN);
@@ -696,6 +704,44 @@ TEST_F(MessageTest, badEncode) {
         EXPECT_THROW_MSG(message->encode(), Unexpected,
                          "Can't encode User-Password");
     }
+    {
+        SCOPED_TRACE("Two Message-Authenticators");
+        AttributesPtr attrs(new Attributes());
+        // Create a Message-Authenticator.
+        vector<uint8_t> zero(AUTH_VECTOR_LEN);
+        attrs->add(Attribute::fromBinary(PW_MESSAGE_AUTHENTICATOR, zero));
+        attrs->add(Attribute::fromBinary(PW_MESSAGE_AUTHENTICATOR, zero));
+        MessagePtr message(new Message(PW_STATUS_SERVER, 0,
+                                       Message::ZERO_AUTH(), "foobar",
+                                       attrs));
+        ASSERT_TRUE(message);
+        EXPECT_THROW_MSG(message->encode(), BadValue,
+                         "2 Message-Authenticator attributes");
+    }
+    {
+        SCOPED_TRACE("invalid Message-Authenticator");
+        AttributesPtr attrs(new Attributes());
+        // Create the Message-Authenticator attribute as an integer attribute.
+        attrs->add(Attribute::fromInt(PW_MESSAGE_AUTHENTICATOR, 666));
+        MessagePtr message(new Message(PW_STATUS_SERVER, 0,
+                                       Message::ZERO_AUTH(), "foobar",
+                                       attrs));
+        ASSERT_TRUE(message);
+        EXPECT_THROW_MSG(message->encode(), BadValue,
+                         "bad Message-Authenticator attribute");
+    }
+    {
+        SCOPED_TRACE("short Message-Authenticator");
+        AttributesPtr attrs(new Attributes());
+        vector<uint8_t> zero(AUTH_VECTOR_LEN - 1);
+        attrs->add(Attribute::fromBinary(PW_MESSAGE_AUTHENTICATOR, zero));
+        MessagePtr message(new Message(PW_STATUS_SERVER, 0,
+                                       Message::ZERO_AUTH(), "foobar",
+                                       attrs));
+        ASSERT_TRUE(message);
+        EXPECT_THROW_MSG(message->encode(), BadValue,
+                         "bad Message-Authenticator attribute");
+    }
 }
 
 // Verifies decode error cases.
@@ -734,8 +780,9 @@ TEST_F(MessageTest, badDecode) {
         vector<uint8_t> auth(AUTH_VECTOR_LEN - 1, 66);
         MessagePtr message(new Message(buffer, auth, "foobar"));
         ASSERT_TRUE(message);
-        // Must not be an Access-Request!
+        // Must not be an Access-Request or Status-Server!
         ASSERT_NE(PW_ACCESS_REQUEST, message->getCode());
+        ASSERT_NE(PW_STATUS_SERVER, message->getCode());
         EXPECT_THROW_MSG(message->decode(), InvalidOperation,
                          "bad authenticator");
     }
@@ -865,7 +912,409 @@ TEST_F(MessageTest, badDecode) {
                          "can't decode User-Password");
         // Note that other decodeUserPassword failure cases are not
         // available nor possible in the real world.
-   }
+    }
+    {
+        SCOPED_TRACE("Two Message-Authenticators");
+        // Easier with Status-Server.
+        vector<uint8_t> buffer = { 0x0c, 0x67, 0x00, 0x38 };
+        vector<uint8_t> auth;
+        for (size_t i = 0; i < AUTH_VECTOR_LEN; i++) {
+            buffer.push_back(10 + i);
+            auth.push_back(10 + i);
+        }
+        buffer.push_back(PW_MESSAGE_AUTHENTICATOR);
+        buffer.push_back(2 + AUTH_VECTOR_LEN);
+        for (size_t i = 0; i < AUTH_VECTOR_LEN; i++) {
+            buffer.push_back(20 + i);
+        }
+        buffer.push_back(PW_MESSAGE_AUTHENTICATOR);
+        buffer.push_back(2 + AUTH_VECTOR_LEN);
+        for (size_t i = 0; i < AUTH_VECTOR_LEN; i++) {
+            buffer.push_back(20 + i);
+        }
+        ASSERT_EQ(AUTH_HDR_LEN + 2 * (2 + AUTH_VECTOR_LEN), buffer.size());
+        MessagePtr message(new Message(buffer, auth, "foo"));
+        ASSERT_TRUE(message);
+        EXPECT_THROW_MSG(message->decode(), BadValue,
+                         "2 Message-Authenticator attributes");
+    }
+    {
+        SCOPED_TRACE("too short Message-Authenticator");
+        // Easier with Status-Server.
+        vector<uint8_t> buffer = { 0x0c, 0x67, 0x00, 0x25 };
+        vector<uint8_t> auth;
+        for (size_t i = 0; i < AUTH_VECTOR_LEN; i++) {
+            buffer.push_back(10 + i);
+            auth.push_back(10 + i);
+        }
+        buffer.push_back(PW_MESSAGE_AUTHENTICATOR);
+        buffer.push_back(1 + AUTH_VECTOR_LEN);
+        for (size_t i = 0; i < AUTH_VECTOR_LEN - 1; i++) {
+            buffer.push_back(20 + i);
+        }
+        ASSERT_EQ(AUTH_HDR_LEN + 1 + AUTH_VECTOR_LEN, buffer.size());
+        MessagePtr message(new Message(buffer, auth, "foo"));
+        ASSERT_TRUE(message);
+        EXPECT_THROW_MSG(message->decode(), BadValue,
+                         "can't verify Message-Authenticator");
+    }
+    {
+        SCOPED_TRACE("too long Message-Authenticator");
+        // Easier with Status-Server.
+        vector<uint8_t> buffer = { 0x0c, 0x67, 0x00, 0x27 };
+        vector<uint8_t> auth;
+        for (size_t i = 0; i < AUTH_VECTOR_LEN; i++) {
+            buffer.push_back(10 + i);
+            auth.push_back(10 + i);
+        }
+        buffer.push_back(PW_MESSAGE_AUTHENTICATOR);
+        buffer.push_back(3 + AUTH_VECTOR_LEN);
+        for (size_t i = 0; i < AUTH_VECTOR_LEN + 1; i++) {
+            buffer.push_back(20 + i);
+        }
+        ASSERT_EQ(AUTH_HDR_LEN + 3 + AUTH_VECTOR_LEN, buffer.size());
+        MessagePtr message(new Message(buffer, auth, "foo"));
+        ASSERT_TRUE(message);
+        EXPECT_THROW_MSG(message->decode(), BadValue,
+                         "can't verify Message-Authenticator");
+    }
+    {
+        SCOPED_TRACE("bad Message-Authenticator");
+        vector<uint8_t> buffer = {
+            0x0c,           // Code (Status-Server).
+            0xda,           // Identifier (0xda).
+            0x00, 0x26,     // Length (38).
+            // Authenticator.
+            0x8a, 0x54, 0xf4, 0x68, 0x6f, 0xb3, 0x94, 0xc5,
+            0x28, 0x66, 0xe3, 0x02, 0x18, 0x5d, 0x06, 0x23,
+            // Message-Authenticator.
+            0x50, 0x12,
+            0x5a, 0x66, 0x5e, 0x2e, 0x1e, 0x84, 0x11, 0xf3,
+            0xe2, 0x43, 0x82, 0x20, 0x97, 0xc8, 0x4f, 0xa3
+        };
+        ASSERT_EQ(AUTH_HDR_LEN + 2 + AUTH_VECTOR_LEN, buffer.size());
+        vector<uint8_t> auth = {
+            0x8a, 0x54, 0xf4, 0x68, 0x6f, 0xb3, 0x94, 0xc5,
+            0x28, 0x66, 0xe3, 0x02, 0x18, 0x5d, 0x06, 0x23
+        };
+        ASSERT_EQ(AUTH_VECTOR_LEN, auth.size());
+        // Correct password is "xyzzy5461".
+        MessagePtr message(new Message(buffer, auth, "foobar"));
+        ASSERT_TRUE(message);
+        EXPECT_THROW_MSG(message->decode(), BadValue,
+                         "bad Message-Authenticator signature");
+    }
+}
+
+// Verify basic (no Message-Authentivator) Status-Server processing.
+TEST_F(MessageTest, basicStatusServer) {
+    MsgCode code = PW_STATUS_SERVER;
+    uint8_t id = 0xda;
+    vector<uint8_t> auth = {
+        0x8a, 0x54, 0xf4, 0x68, 0x6f, 0xb3, 0x94, 0xc5,
+        0x28, 0x66, 0xe3, 0x02, 0x18, 0x5d, 0x06, 0x23
+    };
+    ASSERT_EQ(AUTH_VECTOR_LEN, auth.size());
+    string secret = "xyzzy5461";
+    AttributesPtr attrs(new Attributes());
+    ASSERT_TRUE(attrs);
+
+    // Create message.
+    MessagePtr request;
+    ASSERT_NO_THROW(request.reset(new Message(code, 0, auth, secret, attrs)));
+    ASSERT_TRUE(request);
+    // Identifier must be set explicitly.
+    request->setIdentifier(id);
+
+    // Encode request.
+    vector<uint8_t> buffer;
+    ASSERT_NO_THROW(buffer = request->encode());
+
+    // Check buffer.
+    uint16_t length = request->getLength();
+    ASSERT_EQ(20, length);
+    vector<uint8_t> got_buffer = request->getBuffer();
+    ASSERT_EQ(buffer.size(), got_buffer.size());
+    EXPECT_TRUE(memcmp(&buffer[0], &got_buffer[0], buffer.size()) == 0);
+
+    // Verify buffer.
+    vector<uint8_t> expected = {
+        0x0c,           // Code (Status-Server).
+        0xda,           // Identifier (0xda).
+        0x00, 0x14,     // Length (20).
+        // Authenticator.
+        0x8a, 0x54, 0xf4, 0x68, 0x6f, 0xb3, 0x94, 0xc5,
+        0x28, 0x66, 0xe3, 0x02, 0x18, 0x5d, 0x06, 0x23
+    };
+    ASSERT_EQ(20, expected.size());
+    EXPECT_TRUE(memcmp(&expected[0], &buffer[0], buffer.size()) == 0)
+        << str::dumpAsHex(&buffer[0], 20) << "\n"
+        << str::dumpAsHex(&expected[0], 20);
+
+    // Create message (response).
+    MessagePtr response;
+    ASSERT_NO_THROW(response.reset(new Message(buffer, auth, secret)));
+    ASSERT_TRUE(response);
+
+    // Decode response.
+    ASSERT_NO_THROW(response->decode());
+
+    // Verify response.
+    EXPECT_EQ(code, response->getCode());
+    EXPECT_EQ(id, response->getIdentifier());
+    EXPECT_EQ(length, response->getLength());
+    vector<uint8_t> got_auth = response->getAuth();
+    ASSERT_EQ(AUTH_VECTOR_LEN, got_auth.size());
+    EXPECT_TRUE(memcmp(&auth[0], &got_auth[0], AUTH_VECTOR_LEN) == 0)
+        << str::dumpAsHex(&auth[0], AUTH_VECTOR_LEN) << "\n"
+        << str::dumpAsHex(&got_auth[0], AUTH_VECTOR_LEN);
+    AttributesPtr got_attrs = response->getAttributes();
+    EXPECT_FALSE(got_attrs);
+}
+
+// Verify Status-Server with Message-Authenticator processing.
+TEST_F(MessageTest, statusServer) {
+    MsgCode code = PW_STATUS_SERVER;
+    uint8_t id = 0xda;
+    vector<uint8_t> auth = {
+        0x8a, 0x54, 0xf4, 0x68, 0x6f, 0xb3, 0x94, 0xc5,
+        0x28, 0x66, 0xe3, 0x02, 0x18, 0x5d, 0x06, 0x23
+    };
+    ASSERT_EQ(AUTH_VECTOR_LEN, auth.size());
+    string secret = "xyzzy5461";
+    AttributesPtr attrs(new Attributes());
+    vector<uint8_t> zero(AUTH_VECTOR_LEN);
+    attrs->add(Attribute::fromBinary(PW_MESSAGE_AUTHENTICATOR, zero));
+
+    // Create message.
+    MessagePtr request;
+    ASSERT_NO_THROW(request.reset(new Message(code, 0, auth, secret, attrs)));
+    ASSERT_TRUE(request);
+    // Identifier must be set explicitly.
+    request->setIdentifier(id);
+
+    // Encode request.
+    vector<uint8_t> buffer;
+    ASSERT_NO_THROW(buffer = request->encode());
+
+    // Check buffer.
+    uint16_t length = request->getLength();
+    ASSERT_EQ(38, length);
+    vector<uint8_t> got_buffer = request->getBuffer();
+    ASSERT_EQ(buffer.size(), got_buffer.size());
+    EXPECT_TRUE(memcmp(&buffer[0], &got_buffer[0], buffer.size()) == 0);
+
+    // Verify buffer.
+    vector<uint8_t> expected = {
+        0x0c,           // Code (Status-Server).
+        0xda,           // Identifier (0xda).
+        0x00, 0x26,     // Length (38).
+        // Authenticator.
+        0x8a, 0x54, 0xf4, 0x68, 0x6f, 0xb3, 0x94, 0xc5,
+        0x28, 0x66, 0xe3, 0x02, 0x18, 0x5d, 0x06, 0x23,
+        // Message-Authenticator.
+        0x50, 0x12,
+        0x5a, 0x66, 0x5e, 0x2e, 0x1e, 0x84, 0x11, 0xf3,
+        0xe2, 0x43, 0x82, 0x20, 0x97, 0xc8, 0x4f, 0xa3
+    };
+    ASSERT_EQ(38, expected.size());
+    EXPECT_TRUE(memcmp(&expected[0], &buffer[0], buffer.size()) == 0)
+        << str::dumpAsHex(&buffer[0], 38) << "\n"
+        << str::dumpAsHex(&expected[0], 38);
+
+    // Create message (response).
+    MessagePtr response;
+    ASSERT_NO_THROW(response.reset(new Message(buffer, auth, secret)));
+    ASSERT_TRUE(response);
+
+    // Decode response.
+    ASSERT_NO_THROW(response->decode());
+
+    // Verify response.
+    EXPECT_EQ(code, response->getCode());
+    EXPECT_EQ(id, response->getIdentifier());
+    EXPECT_EQ(length, response->getLength());
+    vector<uint8_t> got_auth = response->getAuth();
+    ASSERT_EQ(AUTH_VECTOR_LEN, got_auth.size());
+    EXPECT_TRUE(memcmp(&auth[0], &got_auth[0], AUTH_VECTOR_LEN) == 0)
+        << str::dumpAsHex(&auth[0], AUTH_VECTOR_LEN) << "\n"
+        << str::dumpAsHex(&got_auth[0], AUTH_VECTOR_LEN);
+    AttributesPtr got_attrs = response->getAttributes();
+    ASSERT_TRUE(got_attrs);
+    AttributesPtr exp_attrs(new Attributes());
+    ASSERT_TRUE(exp_attrs);
+    vector<uint8_t> exp_ma = {
+        0x5a, 0x66, 0x5e, 0x2e, 0x1e, 0x84, 0x11, 0xf3,
+        0xe2, 0x43, 0x82, 0x20, 0x97, 0xc8, 0x4f, 0xa3
+    };
+    exp_attrs->add(Attribute::fromBinary(PW_MESSAGE_AUTHENTICATOR, exp_ma));
+    EXPECT_TRUE(compare(*got_attrs, *exp_attrs))
+        << got_attrs->toText() << "\n" << exp_attrs->toText();
+}
+
+// Verify Access-Accept response to Status-Server.
+TEST_F(MessageTest, statusResponse) {
+    MsgCode code = PW_ACCESS_ACCEPT;
+    uint8_t id = 0xda;
+    vector<uint8_t> auth = {
+        0x8a, 0x54, 0xf4, 0x68, 0x6f, 0xb3, 0x94, 0xc5,
+        0x28, 0x66, 0xe3, 0x02, 0x18, 0x5d, 0x06, 0x23
+    };
+    ASSERT_EQ(AUTH_VECTOR_LEN, auth.size());
+    string secret = "xyzzy5461";
+    AttributesPtr attrs(new Attributes());
+    ASSERT_TRUE(attrs);
+
+    // Create message.
+    MessagePtr request;
+    ASSERT_NO_THROW(request.reset(new Message(code, 0, auth, secret, attrs)));
+    ASSERT_TRUE(request);
+    // Identifier must be set explicitly.
+    request->setIdentifier(id);
+
+    // Encode request.
+    vector<uint8_t> buffer;
+    ASSERT_NO_THROW(buffer = request->encode());
+
+    // Check buffer.
+    uint16_t length = request->getLength();
+    ASSERT_EQ(20, length);
+    vector<uint8_t> got_buffer = request->getBuffer();
+    ASSERT_EQ(buffer.size(), got_buffer.size());
+    EXPECT_TRUE(memcmp(&buffer[0], &got_buffer[0], buffer.size()) == 0);
+
+    // Verify buffer.
+    vector<uint8_t> expected = {
+        0x02,           // Code (Access-Accept).
+        0xda,           // Identifier (0xda).
+        0x00, 0x14,     // Length (20).
+        // Authenticator.
+        0xef, 0x0d, 0x55, 0x2a, 0x4b, 0xf2, 0xd6, 0x93,
+        0xec, 0x2b, 0x6f, 0xe8, 0xb5, 0x41, 0x1d, 0x66
+    };
+    ASSERT_EQ(20, expected.size());
+    EXPECT_TRUE(memcmp(&expected[0], &buffer[0], buffer.size()) == 0)
+        << str::dumpAsHex(&buffer[0], 20) << "\n"
+        << str::dumpAsHex(&expected[0], 20);
+
+    // Create message (response).
+    MessagePtr response;
+    ASSERT_NO_THROW(response.reset(new Message(buffer, auth, secret)));
+    ASSERT_TRUE(response);
+
+    // Decode response.
+    ASSERT_NO_THROW(response->decode());
+
+    // Verify response.
+    EXPECT_EQ(code, response->getCode());
+    EXPECT_EQ(id, response->getIdentifier());
+    EXPECT_EQ(length, response->getLength());
+    vector<uint8_t> got_auth = response->getAuth();
+    ASSERT_EQ(AUTH_VECTOR_LEN, got_auth.size());
+    EXPECT_TRUE(memcmp(&auth[0], &got_auth[0], AUTH_VECTOR_LEN) == 0)
+        << str::dumpAsHex(&auth[0], AUTH_VECTOR_LEN) << "\n"
+        << str::dumpAsHex(&got_auth[0], AUTH_VECTOR_LEN);
+    vector<uint8_t> digest = {
+        0xef, 0x0d, 0x55, 0x2a, 0x4b, 0xf2, 0xd6, 0x93,
+        0xec, 0x2b, 0x6f, 0xe8, 0xb5, 0x41, 0x1d, 0x66
+    };
+    ASSERT_EQ(AUTH_VECTOR_LEN, digest.size());
+    memmove(&got_auth[0], &buffer[4], got_auth.size());
+    EXPECT_TRUE(memcmp(&digest[0], &got_auth[0], AUTH_VECTOR_LEN) == 0)
+        << str::dumpAsHex(&digest[0], AUTH_VECTOR_LEN) << "\n"
+        << str::dumpAsHex(&got_auth[0], AUTH_VECTOR_LEN);
+    AttributesPtr got_attrs = response->getAttributes();
+    EXPECT_FALSE(got_attrs);
+}
+
+// Verify Access-Accept response to Status-Server with Message-Authenticator.
+TEST_F(MessageTest, statusResponseMessageAuthenticator) {
+    MsgCode code = PW_ACCESS_ACCEPT;
+    uint8_t id = 0xda;
+    vector<uint8_t> auth = {
+        0x8a, 0x54, 0xf4, 0x68, 0x6f, 0xb3, 0x94, 0xc5,
+        0x28, 0x66, 0xe3, 0x02, 0x18, 0x5d, 0x06, 0x23
+    };
+    ASSERT_EQ(AUTH_VECTOR_LEN, auth.size());
+    string secret = "xyzzy5461";
+    AttributesPtr attrs(new Attributes());
+    ASSERT_TRUE(attrs);
+    vector<uint8_t> zero(AUTH_VECTOR_LEN);
+    attrs->add(Attribute::fromBinary(PW_MESSAGE_AUTHENTICATOR, zero));
+
+    // Create message.
+    MessagePtr request;
+    ASSERT_NO_THROW(request.reset(new Message(code, 0, auth, secret, attrs)));
+    ASSERT_TRUE(request);
+    // Identifier must be set explicitly.
+    request->setIdentifier(id);
+
+    // Encode request.
+    vector<uint8_t> buffer;
+    ASSERT_NO_THROW(buffer = request->encode());
+
+    // Check buffer.
+    uint16_t length = request->getLength();
+    ASSERT_EQ(38, length);
+    vector<uint8_t> got_buffer = request->getBuffer();
+    ASSERT_EQ(buffer.size(), got_buffer.size());
+    EXPECT_TRUE(memcmp(&buffer[0], &got_buffer[0], buffer.size()) == 0);
+
+    // Verify buffer.
+    vector<uint8_t> expected = {
+        0x02,           // Code (Access-Accept).
+        0xda,           // Identifier (0xda).
+        0x00, 0x26,     // Length (38).
+        // Authenticator.
+        0x7e, 0x6d, 0x7a, 0x5f, 0x5d, 0xfa, 0x87, 0xb5,
+        0x19, 0xbe, 0xf2, 0x60, 0xa6, 0xf1, 0x50, 0x81,
+        // Message-Authenticator.
+        0x50, 0x12,
+        0x57, 0x56, 0x6a, 0x4a, 0x4a, 0x4c, 0x69, 0x0f,
+        0x8e, 0x18, 0xb7, 0x3a, 0xe7, 0xa7, 0xf6, 0x5f
+    };
+    ASSERT_EQ(38, expected.size());
+    EXPECT_TRUE(memcmp(&expected[0], &buffer[0], buffer.size()) == 0)
+        << str::dumpAsHex(&buffer[0], 38) << "\n"
+        << str::dumpAsHex(&expected[0], 38);
+
+    // Create message (response).
+    MessagePtr response;
+    ASSERT_NO_THROW(response.reset(new Message(buffer, auth, secret)));
+    ASSERT_TRUE(response);
+
+    // Decode response.
+    ASSERT_NO_THROW(response->decode());
+
+    // Verify response.
+    EXPECT_EQ(code, response->getCode());
+    EXPECT_EQ(id, response->getIdentifier());
+    EXPECT_EQ(length, response->getLength());
+    vector<uint8_t> got_auth = response->getAuth();
+    ASSERT_EQ(AUTH_VECTOR_LEN, got_auth.size());
+    EXPECT_TRUE(memcmp(&auth[0], &got_auth[0], AUTH_VECTOR_LEN) == 0)
+        << str::dumpAsHex(&auth[0], AUTH_VECTOR_LEN) << "\n"
+        << str::dumpAsHex(&got_auth[0], AUTH_VECTOR_LEN);
+    vector<uint8_t> digest = {
+        0x7e, 0x6d, 0x7a, 0x5f, 0x5d, 0xfa, 0x87, 0xb5,
+        0x19, 0xbe, 0xf2, 0x60, 0xa6, 0xf1, 0x50, 0x81
+    };
+    ASSERT_EQ(AUTH_VECTOR_LEN, digest.size());
+    memmove(&got_auth[0], &buffer[4], got_auth.size());
+    EXPECT_TRUE(memcmp(&digest[0], &got_auth[0], AUTH_VECTOR_LEN) == 0)
+        << str::dumpAsHex(&digest[0], AUTH_VECTOR_LEN) << "\n"
+        << str::dumpAsHex(&got_auth[0], AUTH_VECTOR_LEN);
+    AttributesPtr got_attrs = response->getAttributes();
+    ASSERT_TRUE(got_attrs);
+    AttributesPtr exp_attrs(new Attributes());
+    ASSERT_TRUE(exp_attrs);
+    vector<uint8_t> exp_ma = {
+        0x57, 0x56, 0x6a, 0x4a, 0x4a, 0x4c, 0x69, 0x0f,
+        0x8e, 0x18, 0xb7, 0x3a, 0xe7, 0xa7, 0xf6, 0x5f
+    };
+    exp_attrs->add(Attribute::fromBinary(PW_MESSAGE_AUTHENTICATOR, exp_ma));
+    EXPECT_TRUE(compare(*got_attrs, *exp_attrs))
+        << got_attrs->toText() << "\n" << exp_attrs->toText();
 }
 
 } // end of anonymous namespace