Kea 3.1.5
client_message.cc
Go to the documentation of this file.
1// Copyright (C) 2023-2025 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>
14#include <boost/scoped_ptr.hpp>
15#include <sstream>
16
17using namespace isc;
18using namespace isc::asiolink;
19using namespace isc::cryptolink;
20using namespace isc::data;
21using namespace isc::util;
22using namespace std;
23
24namespace isc {
25namespace radius {
26
27string
28msgCodeToText(const uint8_t code) {
29 ostringstream result;
30 switch (code) {
32 return ("Access-Request");
34 return ("Access-Accept");
36 return ("Access-Reject");
38 return ("Accounting-Request");
40 return ("Accounting-Response");
42 return ("Accounting-Status");
44 return ("Password-Request");
45 case PW_PASSWORD_ACK:
46 return ("Password-Ack");
48 return ("Password-Reject");
50 return ("Accounting-Message");
52 return ("Access-Challenge");
54 return ("Status-Server");
56 return ("Status-Client");
57 default:
58 result << "Message-Code-" << static_cast<unsigned>(code);
59 return (result.str());
60 }
61}
62
63Message::Message(const uint8_t code, uint16_t length,
64 const vector<uint8_t>& auth, const string& secret,
65 const AttributesPtr& attributes)
66 : code_(code), identifier_(0), length_(length), auth_(auth),
67 secret_(secret), attributes_(attributes), buffer_() {
68}
69
71 : code_(other.code_),
73 length_(other.length_),
74 auth_(other.auth_),
75 secret_(other.secret_),
77 buffer_(other.buffer_) {
78 if (!other.attributes_) {
79 attributes_.reset();
80 } else {
81 for (auto const& attr : *other.attributes_) {
82 attributes_->add(attr);
83 }
84 }
85}
86
87Message::Message(const vector<uint8_t>& buffer,
88 const vector<uint8_t>& auth,
89 const string& secret)
90 : code_(0), identifier_(0), length_(0), auth_(auth), secret_(secret),
91 attributes_(), buffer_(buffer) {
92}
93
95 if (secret_.size() > 0) {
96 memset(&secret_[0], 0, secret_.size());
97 }
98 secret_.clear();
99}
100
101void
103 vector<uint8_t> r = cryptolink::random(1);
104 if (r.size() == 0) {
105 isc_throw(Unexpected, "random failed");
106 }
107 identifier_ = r[0];
108}
109
110void
111Message::setAuth(const vector<uint8_t>& auth) {
112 if (auth.size() != AUTH_VECTOR_LEN) {
113 isc_throw(BadValue, "authenticator must be 16 bytes long");
114 }
115 auth_ = auth;
116}
117
118void
120 auth_.clear();
121 auth_.resize(AUTH_VECTOR_LEN, 0);
122}
123
124void
126 auth_ = cryptolink::random(AUTH_VECTOR_LEN);
127 if (auth_.size() != AUTH_VECTOR_LEN) {
128 isc_throw(Unexpected, "random failed");
129 }
130}
131
132void
133Message::setSecret(const string& secret) {
134 if (secret.empty()) {
135 isc_throw(BadValue, "empty secret");
136 }
137 secret_ = secret;
138}
139
140vector<uint8_t>
142 if (secret_.empty()) {
143 isc_throw(InvalidOperation, "empty secret");
144 }
145
146 // Header.
147 buffer_.resize(AUTH_HDR_LEN);
148 buffer_[0] = code_;
149 buffer_[1] = identifier_;
150 buffer_[2] = static_cast<uint8_t>((length_ & 0xff00) >> 8);
151 buffer_[3] = static_cast<uint8_t>(length_ & 0xff);
152 memmove(&buffer_[4], &auth_[0], auth_.size());
153
154 // Fill attributes.
155 if (attributes_) {
156 for (auto attr : *attributes_) {
157 if (!attr) {
158 continue;
159 }
160 if ((code_ == PW_ACCESS_REQUEST) &&
161 (attr->getType() == PW_USER_PASSWORD)) {
162 attr = encodeUserPassword(attr);
163 }
164 vector<uint8_t> binary = attr->toBytes();
165 if (binary.empty()) {
166 continue;
167 }
168 if (buffer_.size() + binary.size() > PW_MAX_MSG_SIZE) {
169 isc_throw(BadValue, "message becomes too large");
170 }
171 buffer_.insert(buffer_.end(), binary.cbegin(), binary.cend());
172 }
173 }
174
175 // Finish.
176 length_ = static_cast<uint16_t>(buffer_.size());
177 buffer_[2] = static_cast<uint8_t>((length_ & 0xff00) >> 8);
178 buffer_[3] = static_cast<uint8_t>(length_ & 0xff);
179
180 if (code_ != PW_ACCESS_REQUEST) {
181 boost::scoped_ptr<Hash> md(CryptoLink::getCryptoLink().createHash(MD5));
182 md->update(&buffer_[0], buffer_.size());
183 md->update(&secret_[0], secret_.size());
184 md->final(&auth_[0], AUTH_VECTOR_LEN);
185 memmove(&buffer_[4], &auth_[0], auth_.size());
186 }
187
189 .arg(msgCodeToText(code_))
190 .arg(static_cast<unsigned>(code_))
191 .arg(static_cast<unsigned>(identifier_))
192 .arg(length_)
193 .arg(attributes_ ? attributes_->size() : 0);
194 return (buffer_);
195}
196
197void
199 if (secret_.empty()) {
200 isc_throw(InvalidOperation, "empty secret");
201 }
202
203 // Length.
204 if (buffer_.size() < AUTH_HDR_LEN) {
205 isc_throw(BadValue, "message is too short " << buffer_.size()
206 << " < " << AUTH_HDR_LEN);
207 }
208 code_ = buffer_[0];
209 identifier_ = buffer_[1];
210 length_ = static_cast<uint16_t>(buffer_[2]) << 8;
211 length_ |= static_cast<uint16_t>(buffer_[3]);
212 if (code_ == PW_ACCESS_REQUEST) {
213 auth_.resize(AUTH_VECTOR_LEN);
214 memmove(&auth_[0], &buffer_[4], AUTH_VECTOR_LEN);
215 } else if (auth_.size() != AUTH_VECTOR_LEN) {
216 isc_throw(InvalidOperation, "bad authenticator");
217 }
218 if (length_ > buffer_.size()) {
219 isc_throw(BadValue, "truncated " << msgCodeToText(code_)
220 << " length " << length_ << ", got " << buffer_.size());
221 }
222 if (length_ < AUTH_HDR_LEN) {
223 isc_throw(BadValue, "too short " << msgCodeToText(code_)
224 << " length " << length_ << " < " << AUTH_HDR_LEN);
225 }
226 if (length_ > PW_MAX_MSG_SIZE) {
227 isc_throw(BadValue, "too large " << msgCodeToText(code_)
228 << " length " << length_ << " > " << PW_MAX_MSG_SIZE);
229 }
230 if (length_ < buffer_.size()) {
231 buffer_.resize(length_);
232 }
233
234 // Verify authentication.
235 if (code_ != PW_ACCESS_REQUEST) {
236 vector<uint8_t> work = buffer_;
237 memmove(&work[4], &auth_[0], auth_.size());
238 boost::scoped_ptr<Hash> md(CryptoLink::getCryptoLink().createHash(MD5));
239 md->update(&work[0], work.size());
240 md->update(&secret_[0], secret_.size());
241 vector<uint8_t> digest;
242 digest.resize(AUTH_VECTOR_LEN);
243 md->final(&digest[0], AUTH_VECTOR_LEN);
244 if (memcmp(&digest[0], &buffer_[4], AUTH_VECTOR_LEN) != 0) {
245 isc_throw(BadValue, "authentication for " << msgCodeToText(code_)
246 << " failed");
247 }
248 }
249 auth_.resize(AUTH_VECTOR_LEN);
250 memmove(&auth_[0], &buffer_[4], auth_.size());
251
252 // Get attributes.
253 attributes_.reset(new Attributes());
254 size_t ptr = AUTH_HDR_LEN;
255 for (;;) {
256 if (ptr == length_) {
257 break;
258 }
259 if (ptr + 2 > length_) {
260 isc_throw(BadValue, "trailing octet");
261 }
262 const uint8_t type = buffer_[ptr];
263 const uint8_t len = buffer_[ptr + 1];
264 if (ptr + len > length_) {
265 isc_throw(BadValue, "trailing truncated "
266 << AttrDefs::instance().getName(type) << " ("
267 << static_cast<unsigned>(type) << "): length "
268 << static_cast<unsigned>(len) << ", space "
269 << (length_ - ptr));
270 }
271 if (len < 3) {
272 isc_throw(BadValue, "too small attribute length "
273 << static_cast<unsigned>(len) << " < 3");
274 }
275 vector<uint8_t> binary;
276 binary.resize(len);
277 memmove(&binary[0], &buffer_[ptr], binary.size());
279 if ((code_ == PW_ACCESS_REQUEST) && attr &&
280 (attr->getType() == PW_USER_PASSWORD)) {
281 attr = decodeUserPassword(attr);
282 }
283 attributes_->add(attr);
284 ptr += len;
285 }
286 if (attributes_->empty()) {
287 attributes_.reset();
288 }
289
291 .arg(msgCodeToText(code_))
292 .arg(static_cast<unsigned>(code_))
293 .arg(static_cast<unsigned>(identifier_))
294 .arg(length_)
295 .arg(attributes_ ? attributes_->size() : 0);
296}
297
300 if (!attr || (attr->getValueType() != PW_TYPE_STRING) ||
301 (attr->getValueLen() == 0) ||
302 (auth_.size() != AUTH_VECTOR_LEN)) {
303 isc_throw(Unexpected, "Can't encode User-Password");
304 }
305
306 // Get padded password.
307 vector<uint8_t> password = attr->toBinary();
308 size_t len = password.size();
309 len = (len + AUTH_VECTOR_LEN - 1) & ~(AUTH_VECTOR_LEN - 1);
310 if (len > AUTH_PASS_LEN) {
311 len = AUTH_PASS_LEN;
312 }
313 password.resize(len);
314
315 // Hide password.
316 for (size_t i = 0; i < len; i += AUTH_VECTOR_LEN) {
317 boost::scoped_ptr<Hash> md(CryptoLink::getCryptoLink().createHash(MD5));
318 md->update(&secret_[0], secret_.size());
319
320 uint8_t* to_hash;
321 if (i == 0) {
322 to_hash = &auth_[0];
323 } else {
324 to_hash = &password[i - AUTH_VECTOR_LEN];
325 }
326 md->update(to_hash, AUTH_VECTOR_LEN);
327
328 vector<uint8_t> digest;
329 digest.resize(AUTH_VECTOR_LEN);
330 md->final(&digest[0], AUTH_VECTOR_LEN);
331 for (size_t j = 0; j < AUTH_VECTOR_LEN; j++) {
332 password[i + j] ^= digest[j];
333 }
334 memset(&digest[0], 0, AUTH_VECTOR_LEN);
335 }
336
337 // Return hidden attribute.
338 return (Attribute::fromBinary(PW_USER_PASSWORD, password));
339}
340
343 if (!attr || (attr->getValueType() != PW_TYPE_STRING) ||
344 (attr->getValueLen() == 0) ||
345 ((attr->getValueLen() % AUTH_VECTOR_LEN) != 0) ||
346 (auth_.size() != AUTH_VECTOR_LEN)) {
347 isc_throw(Unexpected, "can't decode User-Password");
348 }
349
350 // Get hidden password.
351 vector<uint8_t> password = attr->toBinary();
352 size_t len = password.size();
353 if (len > AUTH_PASS_LEN) {
354 len = AUTH_PASS_LEN;
355 password.resize(len);
356 }
357
358 // Get plain text password.
359 size_t i = len;
360 for (;;) {
361 if (i < AUTH_VECTOR_LEN) {
362 break;
363 }
364 i -= AUTH_VECTOR_LEN;
365 boost::scoped_ptr<Hash> md(CryptoLink::getCryptoLink().createHash(MD5));
366 md->update(&secret_[0], secret_.size());
367
368 uint8_t* to_hash;
369 if (i == 0) {
370 to_hash = &auth_[0];
371 } else {
372 to_hash = &password[i - AUTH_VECTOR_LEN];
373 }
374 md->update(to_hash, AUTH_VECTOR_LEN);
375
376 vector<uint8_t> digest;
377 digest.resize(AUTH_VECTOR_LEN);
378 md->final(&digest[0], AUTH_VECTOR_LEN);
379 for (size_t j = 0; j < AUTH_VECTOR_LEN; j++) {
380 password[i + j] ^= digest[j];
381 }
382 memset(&digest[0], 0, AUTH_VECTOR_LEN);
383 }
384
385 // Unpad password (requires no trailing nuls).
386 while (password.back() == 0) {
387 // Never leave an empty password.
388 if (password.size() == 1) {
389 break;
390 }
391 password.pop_back();
392 }
393
394 // Return plain text attribute.
395 return (Attribute::fromBinary(PW_USER_PASSWORD, password));
396}
397
398} // end of namespace isc::radius
399} // 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.
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 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
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.