Kea 3.3.3
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
14
15#include <cstring>
16#include <sstream>
17
18#include <boost/scoped_ptr.hpp>
19
20#include <radius_log.h>
21
22using namespace isc;
23using namespace isc::asiolink;
24using namespace isc::cryptolink;
25using namespace isc::data;
26using namespace isc::util;
27using namespace std;
28
29namespace isc {
30namespace radius {
31
32string
33msgCodeToText(const uint8_t code) {
34 ostringstream result;
35 switch (code) {
37 return ("Access-Request");
39 return ("Access-Accept");
41 return ("Access-Reject");
43 return ("Accounting-Request");
45 return ("Accounting-Response");
47 return ("Accounting-Status");
49 return ("Password-Request");
50 case PW_PASSWORD_ACK:
51 return ("Password-Ack");
53 return ("Password-Reject");
55 return ("Accounting-Message");
57 return ("Access-Challenge");
59 return ("Status-Server");
61 return ("Status-Client");
62 default:
63 result << "Message-Code-" << static_cast<unsigned>(code);
64 return (result.str());
65 }
66}
67
68Message::Message(const uint8_t code, uint16_t length,
69 const vector<uint8_t>& auth, const string& secret,
70 const AttributesPtr& attributes)
71 : code_(code), identifier_(0), length_(length), auth_(auth),
72 secret_(secret), attributes_(attributes), buffer_() {
73}
74
76 : code_(other.code_),
78 length_(other.length_),
79 auth_(other.auth_),
80 secret_(other.secret_),
82 buffer_(other.buffer_) {
83 if (!other.attributes_) {
84 attributes_.reset();
85 } else {
86 for (auto const& attr : *other.attributes_) {
87 attributes_->add(attr);
88 }
89 }
90}
91
92Message::Message(const vector<uint8_t>& buffer,
93 const vector<uint8_t>& auth,
94 const string& secret)
95 : code_(0), identifier_(0), length_(0), auth_(auth), secret_(secret),
96 attributes_(), buffer_(buffer) {
97}
98
100 if (secret_.size() > 0) {
101 memset(&secret_[0], 0, secret_.size());
102 }
103 secret_.clear();
104}
105
106void
108 vector<uint8_t> r = cryptolink::random(1);
109 if (r.size() == 0) {
110 isc_throw(Unexpected, "random failed");
111 }
112 identifier_ = r[0];
113}
114
115void
116Message::setAuth(const vector<uint8_t>& auth) {
117 if (auth.size() != AUTH_VECTOR_LEN) {
118 isc_throw(BadValue, "authenticator must be 16 bytes long");
119 }
120 auth_ = auth;
121}
122
123void
125 auth_.clear();
126 auth_.resize(AUTH_VECTOR_LEN, 0);
127}
128
129void
131 auth_ = cryptolink::random(AUTH_VECTOR_LEN);
132 if (auth_.size() != AUTH_VECTOR_LEN) {
133 isc_throw(Unexpected, "random failed");
134 }
135}
136
137void
138Message::setSecret(const string& secret) {
139 if (secret.empty()) {
140 isc_throw(BadValue, "empty secret");
141 }
142 secret_ = secret;
143}
144
145vector<uint8_t>
147 if (secret_.empty()) {
148 isc_throw(InvalidOperation, "empty secret");
149 }
150 if (auth_.size() != AUTH_VECTOR_LEN) {
151 isc_throw(BadValue, "Bad auth");
152 }
153
154 // Header.
155 buffer_.resize(AUTH_HDR_LEN);
156 buffer_[0] = code_;
157 buffer_[1] = identifier_;
158 buffer_[2] = static_cast<uint8_t>((length_ & 0xff00) >> 8);
159 buffer_[3] = static_cast<uint8_t>(length_ & 0xff);
160 memmove(&buffer_[4], &auth_[0], auth_.size());
161
162 // Fill attributes.
163 size_t msg_auth_ptr = 0;
164 if (attributes_) {
165 for (auto attr : *attributes_) {
166 if (!attr) {
167 continue;
168 }
169 if ((code_ == PW_ACCESS_REQUEST) &&
170 (attr->getType() == PW_USER_PASSWORD)) {
171 attr = encodeUserPassword(attr);
172 }
173 if (attr->getType() == PW_MESSAGE_AUTHENTICATOR) {
174 if (msg_auth_ptr != 0) {
175 isc_throw(BadValue, "2 Message-Authenticator attributes");
176 }
177 if ((attr->getValueType() != PW_TYPE_STRING) ||
178 (attr->getValueLen() != AUTH_VECTOR_LEN)) {
179 isc_throw(BadValue, "bad Message-Authenticator attribute");
180 }
181 msg_auth_ptr = buffer_.size();
182 }
183 vector<uint8_t> binary = attr->toBytes();
184 if (binary.empty()) {
185 continue;
186 }
187 if (buffer_.size() + binary.size() > PW_MAX_MSG_SIZE) {
188 isc_throw(BadValue, "message becomes too large");
189 }
190 buffer_.insert(buffer_.end(), binary.cbegin(), binary.cend());
191 }
192 }
193
194 // Finish.
195 length_ = static_cast<uint16_t>(buffer_.size());
196 buffer_[2] = static_cast<uint8_t>((length_ & 0xff00) >> 8);
197 buffer_[3] = static_cast<uint8_t>(length_ & 0xff);
198
199 // Computed before the Authenticator.
200 if (msg_auth_ptr != 0) {
201 signMessageAuthenticator(msg_auth_ptr);
202 }
203
204 // Compute the Authenticator when it is not a random value.
206 boost::scoped_ptr<Hash> md(CryptoLink::getCryptoLink().createHash(MD5));
207 md->update(&buffer_[0], buffer_.size());
208 md->update(&secret_[0], secret_.size());
209 md->final(&auth_[0], AUTH_VECTOR_LEN);
210 memmove(&buffer_[4], &auth_[0], auth_.size());
211 }
213 .arg(msgCodeToText(code_))
214 .arg(static_cast<unsigned>(code_))
215 .arg(static_cast<unsigned>(identifier_))
216 .arg(length_)
217 .arg(attributes_ ? attributes_->size() : 0);
218 return (buffer_);
219}
220
221void
223 if (secret_.empty()) {
224 isc_throw(InvalidOperation, "empty secret");
225 }
226
227 // Length.
228 if (buffer_.size() < AUTH_HDR_LEN) {
229 isc_throw(BadValue, "message is too short " << buffer_.size()
230 << " < " << AUTH_HDR_LEN);
231 }
232 code_ = buffer_[0];
233 identifier_ = buffer_[1];
234 length_ = static_cast<uint16_t>(buffer_[2]) << 8;
235 length_ |= static_cast<uint16_t>(buffer_[3]);
237 auth_.resize(AUTH_VECTOR_LEN);
238 memmove(&auth_[0], &buffer_[4], AUTH_VECTOR_LEN);
239 } else if (auth_.size() != AUTH_VECTOR_LEN) {
240 isc_throw(InvalidOperation, "bad authenticator");
241 }
242 // Note that now the auth_ is AUTH_VECTOR_LEN (16) octet long.
243 if (length_ > buffer_.size()) {
244 isc_throw(BadValue, "truncated " << msgCodeToText(code_)
245 << " length " << length_ << ", got " << buffer_.size());
246 }
247 if (length_ < AUTH_HDR_LEN) {
248 isc_throw(BadValue, "too short " << msgCodeToText(code_)
249 << " length " << length_ << " < " << AUTH_HDR_LEN);
250 }
251 if (length_ > PW_MAX_MSG_SIZE) {
252 isc_throw(BadValue, "too large " << msgCodeToText(code_)
253 << " length " << length_ << " > " << PW_MAX_MSG_SIZE);
254 }
255 if (length_ < buffer_.size()) {
256 buffer_.resize(length_);
257 }
258
259 // Verify authentication.
261 vector<uint8_t> work = buffer_;
262 memmove(&work[4], &auth_[0], auth_.size());
263 boost::scoped_ptr<Hash> md(CryptoLink::getCryptoLink().createHash(MD5));
264 md->update(&work[0], work.size());
265 md->update(&secret_[0], secret_.size());
266 vector<uint8_t> digest;
267 digest.resize(AUTH_VECTOR_LEN);
268 md->final(&digest[0], AUTH_VECTOR_LEN);
269 if (memcmp(&digest[0], &buffer_[4], AUTH_VECTOR_LEN) != 0) {
270 isc_throw(BadValue, "authentication for " << msgCodeToText(code_)
271 << " failed");
272 }
273 }
275 auth_.resize(AUTH_VECTOR_LEN);
276 memmove(&auth_[0], &buffer_[4], auth_.size());
277 }
278
279 // Get attributes.
280 attributes_.reset(new Attributes());
281 size_t ptr = AUTH_HDR_LEN;
282 size_t msg_auth_ptr = 0;
283 for (;;) {
284 if (ptr == length_) {
285 break;
286 }
287 if (ptr + 2 > length_) {
288 isc_throw(BadValue, "trailing octet");
289 }
290 const uint8_t type = buffer_[ptr];
291 const uint8_t len = buffer_[ptr + 1];
292 if (ptr + len > length_) {
293 isc_throw(BadValue, "trailing truncated "
294 << AttrDefs::instance().getName(type) << " ("
295 << static_cast<unsigned>(type) << "): length "
296 << static_cast<unsigned>(len) << ", space "
297 << (length_ - ptr));
298 }
299 if (len < 3) {
300 isc_throw(BadValue, "too small attribute length "
301 << static_cast<unsigned>(len) << " < 3");
302 }
303 vector<uint8_t> binary;
304 binary.resize(len);
305 memmove(&binary[0], &buffer_[ptr], binary.size());
307 if ((code_ == PW_ACCESS_REQUEST) && attr &&
308 (attr->getType() == PW_USER_PASSWORD)) {
309 attr = decodeUserPassword(attr);
310 }
311 if (attr->getType() == PW_MESSAGE_AUTHENTICATOR) {
312 if (msg_auth_ptr != 0) {
313 isc_throw(BadValue, "2 Message-Authenticator attributes");
314 }
315 msg_auth_ptr = ptr;
316 }
317 attributes_->add(attr);
318 ptr += len;
319 }
320 if (msg_auth_ptr != 0) {
321 verifyMessageAuthenticator(msg_auth_ptr);
322 }
323 if (attributes_->empty()) {
324 attributes_.reset();
325 }
326
328 .arg(msgCodeToText(code_))
329 .arg(static_cast<unsigned>(code_))
330 .arg(static_cast<unsigned>(identifier_))
331 .arg(length_)
332 .arg(attributes_ ? attributes_->size() : 0);
333}
334
337 if (!attr || (attr->getValueType() != PW_TYPE_STRING) ||
338 (attr->getValueLen() == 0) ||
339 (auth_.size() != AUTH_VECTOR_LEN) ||
340 secret_.empty()) {
341 isc_throw(Unexpected, "can't encode User-Password");
342 }
343
344 // Get padded password.
345 vector<uint8_t> password = attr->toBinary();
346 size_t len = password.size();
347 len = (len + AUTH_VECTOR_LEN - 1) & ~(AUTH_VECTOR_LEN - 1);
348 if (len > AUTH_PASS_LEN) {
349 len = AUTH_PASS_LEN;
350 }
351 password.resize(len);
352
353 // Hide password.
354 for (size_t i = 0; i < len; i += AUTH_VECTOR_LEN) {
355 boost::scoped_ptr<Hash> md(CryptoLink::getCryptoLink().createHash(MD5));
356 md->update(&secret_[0], secret_.size());
357
358 uint8_t* to_hash;
359 if (i == 0) {
360 to_hash = &auth_[0];
361 } else {
362 to_hash = &password[i - AUTH_VECTOR_LEN];
363 }
364 md->update(to_hash, AUTH_VECTOR_LEN);
365
366 vector<uint8_t> digest;
367 digest.resize(AUTH_VECTOR_LEN);
368 md->final(&digest[0], AUTH_VECTOR_LEN);
369 for (size_t j = 0; j < AUTH_VECTOR_LEN; j++) {
370 password[i + j] ^= digest[j];
371 }
372 memset(&digest[0], 0, AUTH_VECTOR_LEN);
373 }
374
375 // Return hidden attribute.
376 return (Attribute::fromBinary(PW_USER_PASSWORD, password));
377}
378
381 if (!attr || (attr->getValueType() != PW_TYPE_STRING) ||
382 (attr->getValueLen() == 0) ||
383 ((attr->getValueLen() % AUTH_VECTOR_LEN) != 0) ||
384 (auth_.size() != AUTH_VECTOR_LEN) ||
385 secret_.empty()) {
386 isc_throw(Unexpected, "can't decode User-Password");
387 }
388
389 // Get hidden password.
390 vector<uint8_t> password = attr->toBinary();
391 size_t len = password.size();
392 if (len > AUTH_PASS_LEN) {
393 len = AUTH_PASS_LEN;
394 password.resize(len);
395 }
396
397 // Get plain text password.
398 size_t i = len;
399 for (;;) {
400 if (i < AUTH_VECTOR_LEN) {
401 break;
402 }
403 i -= AUTH_VECTOR_LEN;
404 boost::scoped_ptr<Hash> md(CryptoLink::getCryptoLink().createHash(MD5));
405 md->update(&secret_[0], secret_.size());
406
407 uint8_t* to_hash;
408 if (i == 0) {
409 to_hash = &auth_[0];
410 } else {
411 to_hash = &password[i - AUTH_VECTOR_LEN];
412 }
413 md->update(to_hash, AUTH_VECTOR_LEN);
414
415 vector<uint8_t> digest;
416 digest.resize(AUTH_VECTOR_LEN);
417 md->final(&digest[0], AUTH_VECTOR_LEN);
418 for (size_t j = 0; j < AUTH_VECTOR_LEN; j++) {
419 password[i + j] ^= digest[j];
420 }
421 memset(&digest[0], 0, AUTH_VECTOR_LEN);
422 }
423
424 // Unpad password (requires no trailing nuls).
425 while (password.back() == 0) {
426 // Never leave an empty password.
427 if (password.size() == 1) {
428 break;
429 }
430 password.pop_back();
431 }
432
433 // Return plain text attribute.
434 return (Attribute::fromBinary(PW_USER_PASSWORD, password));
435}
436
437void
439 if ((ptr < AUTH_HDR_LEN) || (ptr > buffer_.size() - 2 - AUTH_VECTOR_LEN) ||
440 (buffer_[ptr + 1] != 2 + AUTH_VECTOR_LEN) ||
441 (auth_.size() != AUTH_VECTOR_LEN) ||
442 secret_.empty()) {
443 isc_throw(Unexpected, "can't sign Message-Authenticator");
444 }
445
446 boost::scoped_ptr<HMAC> hmac(
447 CryptoLink::getCryptoLink().createHMAC(&secret_[0], secret_.size(), MD5));
448
449 // Compute Message-Authenticator content.
450 std::vector<uint8_t> to_sign = buffer_;
451 memmove(&to_sign[4], &auth_[0], auth_.size());
452 memset(&to_sign[ptr + 2], 0, AUTH_VECTOR_LEN);
453 hmac->update(&to_sign[0], to_sign.size());
454 vector<uint8_t> sign = hmac->sign(AUTH_VECTOR_LEN);
455 memmove(&buffer_[ptr + 2], &sign[0], sign.size());
456}
457
458void
460 if ((ptr < AUTH_HDR_LEN) || (ptr > buffer_.size() - 2 - AUTH_VECTOR_LEN) ||
461 (buffer_[ptr + 1] != 2 + AUTH_VECTOR_LEN) ||
462 (auth_.size() != AUTH_VECTOR_LEN) ||
463 secret_.empty()) {
464 isc_throw(BadValue, "can't verify Message-Authenticator");
465 }
466
467 vector<uint8_t> sign;
468 sign.resize(AUTH_VECTOR_LEN);
469 memmove(&sign[0], &buffer_[ptr + 2], sign.size());
470
471 boost::scoped_ptr<HMAC> hmac(
472 CryptoLink::getCryptoLink().createHMAC(&secret_[0], secret_.size(), MD5));
473
474 // Build to_verify buffer.
475 std::vector<uint8_t> to_verify = buffer_;
476 memmove(&to_verify[4], &auth_[0], auth_.size());
477 memset(&to_verify[ptr + 2], 0, AUTH_VECTOR_LEN);
478
479 hmac->update(&to_verify[0], to_verify.size());
480 if (!hmac->verify(&sign[0], sign.size())) {
481 isc_throw(BadValue, "bad Message-Authenticator signature");
482 }
483}
484
485} // end of namespace isc::radius
486} // 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.