xref: /freebsd/crypto/krb5/src/lib/krad/packet.c (revision b670c9bafc0e31c7609969bf374b2e80bdc00211)
1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /* lib/krad/packet.c - Packet functions for libkrad */
3 /*
4  * Copyright 2013 Red Hat, Inc.  All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions are met:
8  *
9  *    1. Redistributions of source code must retain the above copyright
10  *       notice, this list of conditions and the following disclaimer.
11  *
12  *    2. Redistributions in binary form must reproduce the above copyright
13  *       notice, this list of conditions and the following disclaimer in
14  *       the documentation and/or other materials provided with the
15  *       distribution.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
18  * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
19  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
20  * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
21  * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
22  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
23  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
24  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
25  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
26  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28  */
29 
30 #include "internal.h"
31 
32 #include <string.h>
33 
34 #include <arpa/inet.h>
35 
36 typedef unsigned char uchar;
37 
38 /* RFC 2865 */
39 #define OFFSET_CODE 0
40 #define OFFSET_ID 1
41 #define OFFSET_LENGTH 2
42 #define OFFSET_AUTH 4
43 #define OFFSET_ATTR 20
44 #define AUTH_FIELD_SIZE (OFFSET_ATTR - OFFSET_AUTH)
45 
46 #define offset(d, o) (&(d)->data[o])
47 #define pkt_code_get(p) (*(krad_code *)offset(&(p)->pkt, OFFSET_CODE))
48 #define pkt_code_set(p, v) (*(krad_code *)offset(&(p)->pkt, OFFSET_CODE)) = v
49 #define pkt_id_get(p) (*(uchar *)offset(&(p)->pkt, OFFSET_ID))
50 #define pkt_id_set(p, v) (*(uchar *)offset(&(p)->pkt, OFFSET_ID)) = v
51 #define pkt_len_get(p)  load_16_be(offset(&(p)->pkt, OFFSET_LENGTH))
52 #define pkt_len_set(p, v)  store_16_be(v, offset(&(p)->pkt, OFFSET_LENGTH))
53 #define pkt_auth(p) ((uchar *)offset(&(p)->pkt, OFFSET_AUTH))
54 #define pkt_attr(p) ((unsigned char *)offset(&(p)->pkt, OFFSET_ATTR))
55 
56 struct krad_packet_st {
57     char buffer[KRAD_PACKET_SIZE_MAX];
58     krad_attrset *attrset;
59     krb5_data pkt;
60 };
61 
62 typedef struct {
63     uchar x[(UCHAR_MAX + 1) / 8];
64 } idmap;
65 
66 /* Ensure the map is empty. */
67 static inline void
68 idmap_init(idmap *map)
69 {
70     memset(map, 0, sizeof(*map));
71 }
72 
73 /* Set an id as already allocated. */
74 static inline void
75 idmap_set(idmap *map, uchar id)
76 {
77     map->x[id / 8] |= 1 << (id % 8);
78 }
79 
80 /* Determine whether or not an id is used. */
81 static inline krb5_boolean
82 idmap_isset(const idmap *map, uchar id)
83 {
84     return (map->x[id / 8] & (1 << (id % 8))) != 0;
85 }
86 
87 /* Find an unused id starting the search at the value specified in id.
88  * NOTE: For optimal security, the initial value of id should be random. */
89 static inline krb5_error_code
90 idmap_find(const idmap *map, uchar *id)
91 {
92     krb5_int16 i;
93 
94     for (i = *id; i >= 0 && i <= UCHAR_MAX; (*id % 2 == 0) ? i++ : i--) {
95         if (!idmap_isset(map, i))
96             goto success;
97     }
98 
99     for (i = *id; i >= 0 && i <= UCHAR_MAX; (*id % 2 == 1) ? i++ : i--) {
100         if (!idmap_isset(map, i))
101             goto success;
102     }
103 
104     return ERANGE;
105 
106 success:
107     *id = i;
108     return 0;
109 }
110 
111 /* Generate size bytes of random data into the buffer. */
112 static inline krb5_error_code
113 randomize(krb5_context ctx, void *buffer, unsigned int size)
114 {
115     krb5_data rdata = make_data(buffer, size);
116     return krb5_c_random_make_octets(ctx, &rdata);
117 }
118 
119 /* Generate a radius packet id. */
120 static krb5_error_code
121 id_generate(krb5_context ctx, krad_packet_iter_cb cb, void *data, uchar *id)
122 {
123     krb5_error_code retval;
124     const krad_packet *tmp;
125     idmap used;
126     uchar i;
127 
128     retval = randomize(ctx, &i, sizeof(i));
129     if (retval != 0) {
130         if (cb != NULL)
131             (*cb)(data, TRUE);
132         return retval;
133     }
134 
135     if (cb != NULL) {
136         idmap_init(&used);
137         for (tmp = (*cb)(data, FALSE); tmp != NULL; tmp = (*cb)(data, FALSE))
138             idmap_set(&used, tmp->pkt.data[1]);
139 
140         retval = idmap_find(&used, &i);
141         if (retval != 0)
142             return retval;
143     }
144 
145     *id = i;
146     return 0;
147 }
148 
149 /* Generate a random authenticator field. */
150 static krb5_error_code
151 auth_generate_random(krb5_context ctx, uchar *rauth)
152 {
153     krb5_ui_4 trunctime;
154     time_t currtime;
155 
156     /* Get the least-significant four bytes of the current time. */
157     currtime = time(NULL);
158     if (currtime == (time_t)-1)
159         return errno;
160     trunctime = (krb5_ui_4)currtime;
161     memcpy(rauth, &trunctime, sizeof(trunctime));
162 
163     /* Randomize the rest of the buffer. */
164     return randomize(ctx, rauth + sizeof(trunctime),
165                      AUTH_FIELD_SIZE - sizeof(trunctime));
166 }
167 
168 /* Generate a response authenticator field. */
169 static krb5_error_code
170 auth_generate_response(krb5_context ctx, const char *secret,
171                        const krad_packet *response, const uchar *auth,
172                        uchar *rauth)
173 {
174     krb5_error_code retval;
175     krb5_checksum hash;
176     krb5_data data;
177 
178     /* Allocate the temporary buffer. */
179     retval = alloc_data(&data, response->pkt.length + strlen(secret));
180     if (retval != 0)
181         return retval;
182 
183     /* Encoded RADIUS packet with the request's
184      * authenticator and the secret at the end. */
185     memcpy(data.data, response->pkt.data, response->pkt.length);
186     memcpy(data.data + OFFSET_AUTH, auth, AUTH_FIELD_SIZE);
187     memcpy(data.data + response->pkt.length, secret, strlen(secret));
188 
189     /* Hash it. */
190     retval = krb5_c_make_checksum(ctx, CKSUMTYPE_RSA_MD5, NULL, 0, &data,
191                                   &hash);
192     free(data.data);
193     if (retval != 0)
194         return retval;
195 
196     memcpy(rauth, hash.contents, AUTH_FIELD_SIZE);
197     krb5_free_checksum_contents(ctx, &hash);
198     return 0;
199 }
200 
201 /* Create a new packet. */
202 static krad_packet *
203 packet_new()
204 {
205     krad_packet *pkt;
206 
207     pkt = calloc(1, sizeof(krad_packet));
208     if (pkt == NULL)
209         return NULL;
210     pkt->pkt = make_data(pkt->buffer, sizeof(pkt->buffer));
211 
212     return pkt;
213 }
214 
215 /* Set the attrset object by decoding the packet. */
216 static krb5_error_code
217 packet_set_attrset(krb5_context ctx, const char *secret, krad_packet *pkt)
218 {
219     krb5_data tmp;
220 
221     tmp = make_data(pkt_attr(pkt), pkt->pkt.length - OFFSET_ATTR);
222     return kr_attrset_decode(ctx, &tmp, secret, pkt_auth(pkt), &pkt->attrset);
223 }
224 
225 ssize_t
226 krad_packet_bytes_needed(const krb5_data *buffer)
227 {
228     size_t len;
229 
230     if (buffer->length < OFFSET_AUTH)
231         return OFFSET_AUTH - buffer->length;
232 
233     len = load_16_be(offset(buffer, OFFSET_LENGTH));
234     if (len > KRAD_PACKET_SIZE_MAX)
235         return -1;
236 
237     return (buffer->length > len) ? 0 : len - buffer->length;
238 }
239 
240 void
241 krad_packet_free(krad_packet *pkt)
242 {
243     if (pkt)
244         krad_attrset_free(pkt->attrset);
245     free(pkt);
246 }
247 
248 /* Create a new request packet. */
249 krb5_error_code
250 krad_packet_new_request(krb5_context ctx, const char *secret, krad_code code,
251                         const krad_attrset *set, krad_packet_iter_cb cb,
252                         void *data, krad_packet **request)
253 {
254     krb5_error_code retval;
255     krad_packet *pkt;
256     uchar id;
257     size_t attrset_len;
258 
259     pkt = packet_new();
260     if (pkt == NULL) {
261         if (cb != NULL)
262             (*cb)(data, TRUE);
263         return ENOMEM;
264     }
265 
266     /* Generate the ID. */
267     retval = id_generate(ctx, cb, data, &id);
268     if (retval != 0)
269         goto error;
270     pkt_id_set(pkt, id);
271 
272     /* Generate the authenticator. */
273     retval = auth_generate_random(ctx, pkt_auth(pkt));
274     if (retval != 0)
275         goto error;
276 
277     /* Encode the attributes. */
278     retval = kr_attrset_encode(set, secret, pkt_auth(pkt), pkt_attr(pkt),
279                                &attrset_len);
280     if (retval != 0)
281         goto error;
282 
283     /* Set the code, ID and length. */
284     pkt->pkt.length = attrset_len + OFFSET_ATTR;
285     pkt_code_set(pkt, code);
286     pkt_len_set(pkt, pkt->pkt.length);
287 
288     /* Copy the attrset for future use. */
289     retval = packet_set_attrset(ctx, secret, pkt);
290     if (retval != 0)
291         goto error;
292 
293     *request = pkt;
294     return 0;
295 
296 error:
297     free(pkt);
298     return retval;
299 }
300 
301 /* Create a new request packet. */
302 krb5_error_code
303 krad_packet_new_response(krb5_context ctx, const char *secret, krad_code code,
304                          const krad_attrset *set, const krad_packet *request,
305                          krad_packet **response)
306 {
307     krb5_error_code retval;
308     krad_packet *pkt;
309     size_t attrset_len;
310 
311     pkt = packet_new();
312     if (pkt == NULL)
313         return ENOMEM;
314 
315     /* Encode the attributes. */
316     retval = kr_attrset_encode(set, secret, pkt_auth(request), pkt_attr(pkt),
317                                &attrset_len);
318     if (retval != 0)
319         goto error;
320 
321     /* Set the code, ID and length. */
322     pkt->pkt.length = attrset_len + OFFSET_ATTR;
323     pkt_code_set(pkt, code);
324     pkt_id_set(pkt, pkt_id_get(request));
325     pkt_len_set(pkt, pkt->pkt.length);
326 
327     /* Generate the authenticator. */
328     retval = auth_generate_response(ctx, secret, pkt, pkt_auth(request),
329                                     pkt_auth(pkt));
330     if (retval != 0)
331         goto error;
332 
333     /* Copy the attrset for future use. */
334     retval = packet_set_attrset(ctx, secret, pkt);
335     if (retval != 0)
336         goto error;
337 
338     *response = pkt;
339     return 0;
340 
341 error:
342     free(pkt);
343     return retval;
344 }
345 
346 /* Decode a packet. */
347 static krb5_error_code
348 decode_packet(krb5_context ctx, const char *secret, const krb5_data *buffer,
349               krad_packet **pkt)
350 {
351     krb5_error_code retval;
352     krad_packet *tmp;
353     krb5_ui_2 len;
354 
355     tmp = packet_new();
356     if (tmp == NULL) {
357         retval = ENOMEM;
358         goto error;
359     }
360 
361     /* Ensure a proper message length. */
362     retval = (buffer->length < OFFSET_ATTR) ? EMSGSIZE : 0;
363     if (retval != 0)
364         goto error;
365     len = load_16_be(offset(buffer, OFFSET_LENGTH));
366     retval = (len < OFFSET_ATTR) ? EBADMSG : 0;
367     if (retval != 0)
368         goto error;
369     retval = (len > buffer->length || len > tmp->pkt.length) ? EBADMSG : 0;
370     if (retval != 0)
371         goto error;
372 
373     /* Copy over the buffer. */
374     tmp->pkt.length = len;
375     memcpy(tmp->pkt.data, buffer->data, len);
376 
377     /* Parse the packet to ensure it is well-formed. */
378     retval = packet_set_attrset(ctx, secret, tmp);
379     if (retval != 0)
380         goto error;
381 
382     *pkt = tmp;
383     return 0;
384 
385 error:
386     krad_packet_free(tmp);
387     return retval;
388 }
389 
390 krb5_error_code
391 krad_packet_decode_request(krb5_context ctx, const char *secret,
392                            const krb5_data *buffer, krad_packet_iter_cb cb,
393                            void *data, const krad_packet **duppkt,
394                            krad_packet **reqpkt)
395 {
396     const krad_packet *tmp = NULL;
397     krb5_error_code retval;
398 
399     retval = decode_packet(ctx, secret, buffer, reqpkt);
400     if (cb != NULL && retval == 0) {
401         for (tmp = (*cb)(data, FALSE); tmp != NULL; tmp = (*cb)(data, FALSE)) {
402             if (pkt_id_get(*reqpkt) == pkt_id_get(tmp))
403                 break;
404         }
405     }
406 
407     if (cb != NULL && (retval != 0 || tmp != NULL))
408         (*cb)(data, TRUE);
409 
410     *duppkt = tmp;
411     return retval;
412 }
413 
414 krb5_error_code
415 krad_packet_decode_response(krb5_context ctx, const char *secret,
416                             const krb5_data *buffer, krad_packet_iter_cb cb,
417                             void *data, const krad_packet **reqpkt,
418                             krad_packet **rsppkt)
419 {
420     uchar auth[AUTH_FIELD_SIZE];
421     const krad_packet *tmp = NULL;
422     krb5_error_code retval;
423 
424     retval = decode_packet(ctx, secret, buffer, rsppkt);
425     if (cb != NULL && retval == 0) {
426         for (tmp = (*cb)(data, FALSE); tmp != NULL; tmp = (*cb)(data, FALSE)) {
427             if (pkt_id_get(*rsppkt) != pkt_id_get(tmp))
428                 continue;
429 
430             /* Response */
431             retval = auth_generate_response(ctx, secret, *rsppkt,
432                                             pkt_auth(tmp), auth);
433             if (retval != 0) {
434                 krad_packet_free(*rsppkt);
435                 break;
436             }
437 
438             /* If the authenticator matches, then the response is valid. */
439             if (memcmp(pkt_auth(*rsppkt), auth, sizeof(auth)) == 0)
440                 break;
441         }
442     }
443 
444     if (cb != NULL && (retval != 0 || tmp != NULL))
445         (*cb)(data, TRUE);
446 
447     *reqpkt = tmp;
448     return retval;
449 }
450 
451 const krb5_data *
452 krad_packet_encode(const krad_packet *pkt)
453 {
454     return &pkt->pkt;
455 }
456 
457 krad_code
458 krad_packet_get_code(const krad_packet *pkt)
459 {
460     if (pkt == NULL)
461         return 0;
462 
463     return pkt_code_get(pkt);
464 }
465 
466 const krb5_data *
467 krad_packet_get_attr(const krad_packet *pkt, krad_attr type, size_t indx)
468 {
469     return krad_attrset_get(pkt->attrset, type, indx);
470 }
471