Kea 3.3.2
client_message.cc
Go to the documentation of this file.
1// Copyright (C) 2023-2026 Internet Systems Consortium, Inc. ("ISC")
2//
3// This Source Code Form is subject to the terms of the Mozilla Public
4// License, v. 2.0. If a copy of the MPL was not distributed with this
5// file, You can obtain one at http://mozilla.org/MPL/2.0/.
6
7#include <config.h>
8
9#include <client_attribute.h>
10#include <client_message.h>
11#include <radius_log.h>
15#include <boost/scoped_ptr.hpp>
16#include <cstring>
17#include <sstream>
18
19using namespace isc;
20using namespace isc::asiolink;
21using namespace isc::cryptolink;
22using namespace isc::data;
23using namespace isc::util;
24using namespace std;
25
26namespace isc {
27namespace radius {
28
29string
30msgCodeToText(const uint8_t code) {
31 ostringstream result;
32 switch (code) {
34 return ("Access-Request");
36 return ("Access-Accept");
38 return ("Access-Reject");
40 return ("Accounting-Request");
42 return ("Accounting-Response");
44 return ("Accounting-Status");
46 return ("Password-Request");
47 case PW_PASSWORD_ACK:
48 return ("Password-Ack");
50 return ("Password-Reject");
52 return ("Accounting-Message");
54 return ("Access-Challenge");
56 return ("Status-Server");
58 return ("Status-Client");
59 default:
60 result << "Message-Code-" << static_cast<unsigned>(code);
61 return (result.str());
62 }
63}
64
65Message::Message(const uint8_t code, uint16_t length,
66 const vector<uint8_t>& auth, const string& secret,
67 const AttributesPtr& attributes)
68 : code_(code), identifier_(0), length_(length), auth_(auth),
69 secret_(secret), attributes_(attributes), buffer_() {
70}
71
73 : code_(other.code_),
75 length_(other.length_),
76 auth_(other.auth_),
77 secret_(other.secret_),
79 buffer_(other.buffer_) {
80 if (!other.attributes_) {
81 attributes_.reset();
82 } else {
83 for (auto const& attr : *other.attributes_) {
84 attributes_->add(attr);
85 }
86 }
87}
88
89Message::Message(const vector<uint8_t>& buffer,
90 const vector<uint8_t>& auth,
91 const string& secret)
92 : code_(0), identifier_(0), length_(0), auth_(auth), secret_(secret),
93 attributes_(), buffer_(buffer) {
94}
95
97 if (secret_.size() > 0) {
98 memset(&secret_[0], 0, secret_.size());
99 }
100 secret_.clear();
101}
102
103void
105 vector<uint8_t> r = cryptolink::random(1);
106 if (r.size() == 0) {
107 isc_throw(Unexpected, "random failed");
108 }
109 identifier_ = r[0];
110}
111
112void
113Message::setAuth(const vector<uint8_t>& auth) {
114 if (auth.size() != AUTH_VECTOR_LEN) {
115 isc_throw(BadValue, "authenticator must be 16 bytes long");
116 }
117 auth_ = auth;
118}
119
120void
122 auth_.clear();
123 auth_.resize(AUTH_VECTOR_LEN, 0);
124}
125
126void
128 auth_ = cryptolink::random(AUTH_VECTOR_LEN);
129 if (auth_.size() != AUTH_VECTOR_LEN) {
130 isc_throw(Unexpected, "random failed");
131 }
132}
133
134void
135Message::setSecret(const string& secret) {
136 if (secret.empty()) {
137 isc_throw(BadValue, "empty secret");
138 }
139 secret_ = secret;
140}
141
142vector<uint8_t>
144 if (secret_.empty()) {
145 isc_throw(InvalidOperation, "empty secret");
146 }
147 if (auth_.size() != AUTH_VECTOR_LEN) {
148 isc_throw(BadValue, "Bad auth");
149 }
150
151 // Header.
152 buffer_.resize(AUTH_HDR_LEN);
153 buffer_[0] = code_;
154 buffer_[1] = identifier_;
155 buffer_[2] = static_cast<uint8_t>((length_ & 0xff00) >> 8);
156 buffer_[3] = static_cast<uint8_t>(length_ & 0xff);
157 memmove(&buffer_[4], &auth_[0], auth_.size());
158
159 // Fill attributes.
160 size_t msg_auth_ptr = 0;
161 if (attributes_) {
162 for (auto attr : *attributes_) {
163 if (!attr) {
164 continue;
165 }
166 if ((code_ == PW_ACCESS_REQUEST) &&
167 (attr->getType() == PW_USER_PASSWORD)) {
168 attr = encodeUserPassword(attr);
169 }
170 if (attr->getType() == PW_MESSAGE_AUTHENTICATOR) {
171 if (msg_auth_ptr != 0) {
172 isc_throw(BadValue, "2 Message-Authenticator attributes");
173 }
174 if ((attr->getValueType() != PW_TYPE_STRING) ||
175 (attr->getValueLen() != AUTH_VECTOR_LEN)) {
176 isc_throw(BadValue, "bad Message-Authenticator attribute");
177 }
178 msg_auth_ptr = buffer_.size();
179 }
180 vector<uint8_t> binary = attr->toBytes();
181 if (binary.empty()) {
182 continue;
183 }
184 if (buffer_.size() + binary.size() > PW_MAX_MSG_SIZE) {
185 isc_throw(BadValue, "message becomes too large");
186 }
187 buffer_.insert(buffer_.end(), binary.cbegin(), binary.cend());
188 }
189 }
190
191 // Finish.
192 length_ = static_cast<uint16_t>(buffer_.size());
193 buffer_[2] = static_cast<uint8_t>((length_ & 0xff00) >> 8);
194 buffer_[3] = static_cast<uint8_t>(length_ & 0xff);
195
196 // Computed before the Authenticator.
197 if (msg_auth_ptr != 0) {
198 signMessageAuthenticator(msg_auth_ptr);
199 }
200
201 // Compute the Authenticator when it is not a random value.
203 boost::scoped_ptr<Hash> md(CryptoLink::getCryptoLink().createHash(MD5));
204 md->update(&buffer_[0], buffer_.size());
205 md->update(&secret_[0], secret_.size());
206 md->final(&auth_[0], AUTH_VECTOR_LEN);
207 memmove(&buffer_[4], &auth_[0], auth_.size());
208 }
210 .arg(msgCodeToText(code_))
211 .arg(static_cast<unsigned>(code_))
212 .arg(static_cast<unsigned>(identifier_))
213 .arg(length_)
214 .arg(attributes_ ? attributes_->size() : 0);
215 return (buffer_);
216}
217
218void
220 if (secret_.empty()) {
221 isc_throw(InvalidOperation, "empty secret");
222 }
223
224 // Length.
225 if (buffer_.size() < AUTH_HDR_LEN) {
226 isc_throw(BadValue, "message is too short " << buffer_.size()
227 << " < " << AUTH_HDR_LEN);
228 }
229 code_ = buffer_[0];
230 identifier_ = buffer_[1];
231 length_ = static_cast<uint16_t>(buffer_[2]) << 8;
232 length_ |= static_cast<uint16_t>(buffer_[3]);
234 auth_.resize(AUTH_VECTOR_LEN);
235 memmove(&auth_[0], &buffer_[4], AUTH_VECTOR_LEN);
236 } else if (auth_.size() != AUTH_VECTOR_LEN) {
237 isc_throw(InvalidOperation, "bad authenticator");
238 }
239 // Note that now the auth_ is AUTH_VECTOR_LEN (16) octet long.
240 if (length_ > buffer_.size()) {
241 isc_throw(BadValue, "truncated " << msgCodeToText(code_)
242 << " length " << length_ << ", got " << buffer_.size());
243 }
244 if (length_ < AUTH_HDR_LEN) {
245 isc_throw(BadValue, "too short " << msgCodeToText(code_)
246 << " length " << length_ << " < " << AUTH_HDR_LEN);
247 }
248 if (length_ > PW_MAX_MSG_SIZE) {
249 isc_throw(BadValue, "too large " << msgCodeToText(code_)
250 << " length " << length_ << " > " << PW_MAX_MSG_SIZE);
251 }
252 if (length_ < buffer_.size()) {
253 buffer_.resize(length_);
254 }
255
256 // Verify authentication.
258 vector<uint8_t> work = buffer_;
259 memmove(&work[4], &auth_[0], auth_.size());
260 boost::scoped_ptr<Hash> md(CryptoLink::getCryptoLink().createHash(MD5));
261 md->update(&work[0], work.size());
262 md->update(&secret_[0], secret_.size());
263 vector<uint8_t> digest;
264 digest.resize(AUTH_VECTOR_LEN);
265 md->final(&digest[0], AUTH_VECTOR_LEN);
266 if (memcmp(&digest[0], &buffer_[4], AUTH_VECTOR_LEN) != 0) {
267 isc_throw(BadValue, "authentication for " << msgCodeToText(code_)
268 << " failed");
269 }
270 }
272 auth_.resize(AUTH_VECTOR_LEN);
273 memmove(&auth_[0], &buffer_[4], auth_.size());
274 }
275
276 // Get attributes.
277 attributes_.reset(new Attributes());
278 size_t ptr = AUTH_HDR_LEN;
279 size_t msg_auth_ptr = 0;
280 for (;;) {
281 if (ptr == length_) {
282 break;
283 }
284 if (ptr + 2 > length_) {
285 isc_throw(BadValue, "trailing octet");
286 }
287 const uint8_t type = buffer_[ptr];
288 const uint8_t len = buffer_[ptr + 1];
289 if (ptr + len > length_) {
290 isc_throw(BadValue, "trailing truncated "
291 << AttrDefs::instance().getName(type) << " ("
292 << static_cast<unsigned>(type) << "): length "
293 << static_cast<unsigned>(len) << ", space "
294 << (length_ - ptr));
295 }
296 if (len < 3) {
297 isc_throw(BadValue, "too small attribute length "
298 << static_cast<unsigned>(len) << " < 3");
299 }
300 vector<uint8_t> binary;
301 binary.resize(len);
302 memmove(&binary[0], &buffer_[ptr], binary.size());
304 if ((code_ == PW_ACCESS_REQUEST) && attr &&
305 (attr->getType() == PW_USER_PASSWORD)) {
306 attr = decodeUserPassword(attr);
307 }
308 if (attr->getType() == PW_MESSAGE_AUTHENTICATOR) {
309 if (msg_auth_ptr != 0) {
310 isc_throw(BadValue, "2 Message-Authenticator attributes");
311 }
312 msg_auth_ptr = ptr;
313 }
314 attributes_->add(attr);
315 ptr += len;
316 }
317 if (msg_auth_ptr != 0) {
318 verifyMessageAuthenticator(msg_auth_ptr);
319 }
320 if (attributes_->empty()) {
321 attributes_.reset();
322 }
323
325 .arg(msgCodeToText(code_))
326 .arg(static_cast<unsigned>(code_))
327 .arg(static_cast<unsigned>(identifier_))
328 .arg(length_)
329 .arg(attributes_ ? attributes_->size() : 0);
330}
331
334 if (!attr || (attr->getValueType() != PW_TYPE_STRING) ||
335 (attr->getValueLen() == 0) ||
336 (auth_.size() != AUTH_VECTOR_LEN) ||
337 secret_.empty()) {
338 isc_throw(Unexpected, "can't encode User-Password");
339 }
340
341 // Get padded password.
342 vector<uint8_t> password = attr->toBinary();
343 size_t len = password.size();
344 len = (len + AUTH_VECTOR_LEN - 1) & ~(AUTH_VECTOR_LEN - 1);
345 if (len > AUTH_PASS_LEN) {
346 len = AUTH_PASS_LEN;
347 }
348 password.resize(len);
349
350 // Hide password.
351 for (size_t i = 0; i < len; i += AUTH_VECTOR_LEN) {
352 boost::scoped_ptr<Hash> md(CryptoLink::getCryptoLink().createHash(MD5));
353 md->update(&secret_[0], secret_.size());
354
355 uint8_t* to_hash;
356 if (i == 0) {
357 to_hash = &auth_[0];
358 } else {
359 to_hash = &password[i - AUTH_VECTOR_LEN];
360 }
361 md->update(to_hash, AUTH_VECTOR_LEN);
362
363 vector<uint8_t> digest;
364 digest.resize(AUTH_VECTOR_LEN);
365 md->final(&digest[0], AUTH_VECTOR_LEN);
366 for (size_t j = 0; j < AUTH_VECTOR_LEN; j++) {
367 password[i + j] ^= digest[j];
368 }
369 memset(&digest[0], 0, AUTH_VECTOR_LEN);
370 }
371
372 // Return hidden attribute.
373 return (Attribute::fromBinary(PW_USER_PASSWORD, password));
374}
375
378 if (!attr || (attr->getValueType() != PW_TYPE_STRING) ||
379 (attr->getValueLen() == 0) ||
380 ((attr->getValueLen() % AUTH_VECTOR_LEN) != 0) ||
381 (auth_.size() != AUTH_VECTOR_LEN) ||
382 secret_.empty()) {
383 isc_throw(Unexpected, "can't decode User-Password");
384 }
385
386 // Get hidden password.
387 vector<uint8_t> password = attr->toBinary();
388 size_t len = password.size();
389 if (len > AUTH_PASS_LEN) {
390 len = AUTH_PASS_LEN;
391 password.resize(len);
392 }
393
394 // Get plain text password.
395 size_t i = len;
396 for (;;) {
397 if (i < AUTH_VECTOR_LEN) {
398 break;
399 }
400 i -= AUTH_VECTOR_LEN;
401 boost::scoped_ptr<Hash> md(CryptoLink::getCryptoLink().createHash(MD5));
402 md->update(&secret_[0], secret_.size());
403
404 uint8_t* to_hash;
405 if (i == 0) {
406 to_hash = &auth_[0];
407 } else {
408 to_hash = &password[i - AUTH_VECTOR_LEN];
409 }
410 md->update(to_hash, AUTH_VECTOR_LEN);
411
412 vector<uint8_t> digest;
413 digest.resize(AUTH_VECTOR_LEN);
414 md->final(&digest[0], AUTH_VECTOR_LEN);
415 for (size_t j = 0; j < AUTH_VECTOR_LEN; j++) {
416 password[i + j] ^= digest[j];
417 }
418 memset(&digest[0], 0, AUTH_VECTOR_LEN);
419 }
420
421 // Unpad password (requires no trailing nuls).
422 while (password.back() == 0) {
423 // Never leave an empty password.
424 if (password.size() == 1) {
425 break;
426 }
427 password.pop_back();
428 }
429
430 // Return plain text attribute.
431 return (Attribute::fromBinary(PW_USER_PASSWORD, password));
432}
433
434void
436 if ((ptr < AUTH_HDR_LEN) || (ptr > buffer_.size() - 2 - AUTH_VECTOR_LEN) ||
437 (buffer_[ptr + 1] != 2 + AUTH_VECTOR_LEN) ||
438 (auth_.size() != AUTH_VECTOR_LEN) ||
439 secret_.empty()) {
440 isc_throw(Unexpected, "can't sign Message-Authenticator");
441 }
442
443 boost::scoped_ptr<HMAC> hmac(
444 CryptoLink::getCryptoLink().createHMAC(&secret_[0], secret_.size(), MD5));
445
446 // Compute Message-Authenticator content.
447 std::vector<uint8_t> to_sign = buffer_;
448 memmove(&to_sign[4], &auth_[0], auth_.size());
449 memset(&to_sign[ptr + 2], 0, AUTH_VECTOR_LEN);
450 hmac->update(&to_sign[0], to_sign.size());
451 vector<uint8_t> sign = hmac->sign(AUTH_VECTOR_LEN);
452 memmove(&buffer_[ptr + 2], &sign[0], sign.size());
453}
454
455void
457 if ((ptr < AUTH_HDR_LEN) || (ptr > buffer_.size() - 2 - AUTH_VECTOR_LEN) ||
458 (buffer_[ptr + 1] != 2 + AUTH_VECTOR_LEN) ||
459 (auth_.size() != AUTH_VECTOR_LEN) ||
460 secret_.empty()) {
461 isc_throw(BadValue, "can't verify Message-Authenticator");
462 }
463
464 vector<uint8_t> sign;
465 sign.resize(AUTH_VECTOR_LEN);
466 memmove(&sign[0], &buffer_[ptr + 2], sign.size());
467
468 boost::scoped_ptr<HMAC> hmac(
469 CryptoLink::getCryptoLink().createHMAC(&secret_[0], secret_.size(), MD5));
470
471 // Build to_verify buffer.
472 std::vector<uint8_t> to_verify = buffer_;
473 memmove(&to_verify[4], &auth_[0], auth_.size());
474 memset(&to_verify[ptr + 2], 0, AUTH_VECTOR_LEN);
475
476 hmac->update(&to_verify[0], to_verify.size());
477 if (!hmac->verify(&sign[0], sign.size())) {
478 isc_throw(BadValue, "bad Message-Authenticator signature");
479 }
480}
481
482} // end of namespace isc::radius
483} // end of namespace isc
A generic exception that is thrown if a parameter given to a method is considered invalid in that con...
A generic exception that is thrown if a function is called in a prohibited way.
A generic exception that is thrown when an unexpected error condition occurs.
static AttrDefs & instance()
Returns a single instance.
static AttributePtr fromBytes(const std::vector< uint8_t > &bytes)
Generic factories.
static AttributePtr fromBinary(const uint8_t type, const std::vector< uint8_t > &value)
From binary with type.
Collection of attributes.
std::vector< uint8_t > auth_
Authenticator: header[4] (16 octets).
void setAuth(const std::vector< uint8_t > &auth)
Set authenticator.
ConstAttributePtr encodeUserPassword(const ConstAttributePtr &attr)
Encode User-Password in an Access-Request.
ConstAttributePtr decodeUserPassword(const ConstAttributePtr &attr)
Decode User-Password in an Access-Request.
void signMessageAuthenticator(size_t ptr)
Encode Message-Authenticator in an Status-Server.
Message(const uint8_t code, uint16_t length, const std::vector< uint8_t > &auth, const std::string &secret, const AttributesPtr &attributes)
Constructor.
uint8_t identifier_
Identifier (random): header[1].
void randomAuth()
Randomize authenticator.
std::vector< uint8_t > encode()
Encode a message.
std::vector< uint8_t > buffer_
Buffer (message content).
uint8_t code_
Code (useful values in MsgCode): header[0].
void decode()
Decode a message.
AttributesPtr attributes_
Attributes: header[20]...
uint16_t length_
Length: header[2] (16 bits, network order).
std::string secret_
Secret (not empty).
void zeroAuth()
Fill authenticator with 0.
virtual ~Message()
Destructor.
void verifyMessageAuthenticator(size_t ptr)
Decode Message-Authenticator in an Status-Server.
void setSecret(const std::string &secret)
Set secret.
void randomIdentifier()
Randomize identifier.
#define isc_throw(type, stream)
A shortcut macro to insert known values into exception arguments.
#define LOG_DEBUG(LOGGER, LEVEL, MESSAGE)
Macro to conveniently test debug output and log it.
Definition macros.h:14
@ PW_MESSAGE_AUTHENTICATOR
string.
boost::shared_ptr< Attributes > AttributesPtr
Shared pointers to attribute collection.
boost::shared_ptr< const Attribute > ConstAttributePtr
const isc::log::MessageID RADIUS_DECODE_MESSAGE
string msgCodeToText(const uint8_t code)
MsgCode value -> name function.
const int RADIUS_DBG_TRACE
Radius logging levels.
Definition radius_log.h:26
const isc::log::MessageID RADIUS_ENCODE_MESSAGE
isc::log::Logger radius_logger("radius-hooks")
Radius Logger.
Definition radius_log.h:35
Defines the logger used by the top-level component of kea-lfc.