2 * Copyright (C) 2009 Martin Willi
3 * Hochschule fuer Technik Rapperswil
5 * This program is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License as published by the
7 * Free Software Foundation; either version 2 of the License, or (at your
8 * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
10 * This program is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
16 #include "simaka_message.h"
18 #include "simaka_manager.h"
21 #include <utils/linked_list.h>
23 typedef struct private_simaka_message_t private_simaka_message_t
;
24 typedef struct hdr_t hdr_t
;
25 typedef struct attr_hdr_t attr_hdr_t
;
26 typedef struct attr_t attr_t
;
29 * packed EAP-SIM/AKA header struct
32 /** EAP code (REQUEST/RESPONSE) */
34 /** unique message identifier */
36 /** length of whole message */
38 /** EAP type => EAP_SIM/EAP_AKA */
44 } __attribute__((__packed__
));
47 * packed EAP-SIM/AKA attribute header struct
52 /** attibute length */
54 } __attribute__((__packed__
));
57 * SIM/AKA attribute, parsed
60 /** type of attribute */
61 simaka_attribute_t type
;
64 /** start of data, variable length */
68 ENUM_BEGIN(simaka_subtype_names
, AKA_CHALLENGE
, AKA_IDENTITY
,
70 "AKA_AUTHENTICATION_REJECT",
72 "AKA_SYNCHRONIZATION_FAILURE",
74 ENUM_NEXT(simaka_subtype_names
, SIM_START
, AKA_CLIENT_ERROR
, AKA_IDENTITY
,
77 "SIM/AKA_NOTIFICATION",
78 "SIM/AKA_REAUTHENTICATION",
79 "SIM/AKA_CLIENT_ERROR");
80 ENUM_END(simaka_subtype_names
, AKA_CLIENT_ERROR
);
83 ENUM_BEGIN(simaka_attribute_names
, AT_RAND
, AT_CLIENT_ERROR_CODE
,
93 "AT_PERMANENT_ID_REQ",
99 "AT_SELECTED_VERSION",
100 "AT_FULLAUTH_ID_REQ",
103 "AT_COUNTER_TOO_SMALL",
105 "AT_CLIENT_ERROR_CODE");
106 ENUM_NEXT(simaka_attribute_names
, AT_IV
, AT_RESULT_IND
, AT_CLIENT_ERROR_CODE
,
114 ENUM_END(simaka_attribute_names
, AT_RESULT_IND
);
117 ENUM_BEGIN(simaka_notification_names
, SIM_GENERAL_FAILURE_AA
, SIM_GENERAL_FAILURE_AA
,
118 "General failure after authentication");
119 ENUM_NEXT(simaka_notification_names
, SIM_TEMP_DENIED
, SIM_TEMP_DENIED
, SIM_GENERAL_FAILURE_AA
,
120 "User has been temporarily denied access");
121 ENUM_NEXT(simaka_notification_names
, SIM_NOT_SUBSCRIBED
, SIM_NOT_SUBSCRIBED
, SIM_TEMP_DENIED
,
122 "User has not subscribed to the requested service");
123 ENUM_NEXT(simaka_notification_names
, SIM_GENERAL_FAILURE
, SIM_GENERAL_FAILURE
, SIM_NOT_SUBSCRIBED
,
125 ENUM_NEXT(simaka_notification_names
, SIM_SUCCESS
, SIM_SUCCESS
, SIM_GENERAL_FAILURE
,
126 "User has been successfully authenticated");
127 ENUM_END(simaka_notification_names
, SIM_SUCCESS
);
130 ENUM(simaka_client_error_names
, SIM_UNABLE_TO_PROCESS
, SIM_RANDS_NOT_FRESH
,
131 "unable to process packet",
132 "unsupported version",
133 "insufficient number of challenges",
134 "RANDs are not fresh",
138 * Check if an EAP-SIM/AKA attribute is skippable
140 bool simaka_attribute_skippable(simaka_attribute_t attribute
)
142 bool skippable
= !((int)attribute
>= 0 && attribute
<= 127);
144 DBG1(DBG_LIB
, "%sskippable EAP-SIM/AKA attribute %N",
145 skippable ?
"ignoring " : "found non-",
146 simaka_attribute_names
, attribute
);
151 * Private data of an simaka_message_t object.
153 struct private_simaka_message_t
{
156 * Public simaka_message_t interface.
158 simaka_message_t
public;
161 * EAP message, starting with EAP header
166 * List of parsed attributes, attr_t
168 linked_list_t
*attributes
;
171 * Currently parsing AT_ENCR_DATA wrapped attributes?
178 simaka_crypto_t
*crypto
;
181 * Phase a NOTIFICATION is sent within
186 * MAC value, pointing into message
191 * ENCR_DATA value, pointing into message
196 * IV value, pointing into message
201 METHOD(simaka_message_t
, is_request
, bool,
202 private_simaka_message_t
*this)
204 return this->hdr
->code
== EAP_REQUEST
;
207 METHOD(simaka_message_t
, get_identifier
, u_int8_t
,
208 private_simaka_message_t
*this)
210 return this->hdr
->identifier
;
213 METHOD(simaka_message_t
, get_subtype
, simaka_subtype_t
,
214 private_simaka_message_t
*this)
216 return this->hdr
->subtype
;
219 METHOD(simaka_message_t
, get_type
, eap_type_t
,
220 private_simaka_message_t
*this)
222 return this->hdr
->type
;
226 * convert attr_t to type and data enumeration
228 static bool attr_enum_filter(void *null
, attr_t
**in
, simaka_attribute_t
*type
,
229 void *dummy
, chunk_t
*data
)
234 *data
= chunk_create(attr
->data
, attr
->len
);
238 METHOD(simaka_message_t
, create_attribute_enumerator
, enumerator_t
*,
239 private_simaka_message_t
*this)
241 return enumerator_create_filter(
242 this->attributes
->create_enumerator(this->attributes
),
243 (void*)attr_enum_filter
, NULL
, NULL
);
246 METHOD(simaka_message_t
, add_attribute
, void,
247 private_simaka_message_t
*this, simaka_attribute_t type
, chunk_t data
)
251 attr
= malloc(sizeof(attr_t
) + data
.len
);
252 attr
->len
= data
.len
;
254 memcpy(attr
->data
, data
.ptr
, data
.len
);
256 this->attributes
->insert_last(this->attributes
, attr
);
260 * Error handling for unencrypted attributes
262 static bool not_encrypted(simaka_attribute_t type
)
264 DBG1(DBG_LIB
, "received unencrypted %N", simaka_attribute_names
, type
);
269 * Error handling for invalid length
271 static bool invalid_length(simaka_attribute_t type
)
273 DBG1(DBG_LIB
, "invalid length of %N", simaka_attribute_names
, type
);
278 * Call SIM/AKA message hooks
280 static void call_hook(private_simaka_message_t
*this,
281 bool inbound
, bool decrypted
)
283 simaka_manager_t
*mgr
;
285 switch (this->hdr
->type
)
288 mgr
= lib
->get(lib
, "sim-manager");
291 mgr
= lib
->get(lib
, "aka-manager");
296 mgr
->message_hook(mgr
, &this->public, inbound
, decrypted
);
300 * Parse attributes from a chunk of data
302 static bool parse_attributes(private_simaka_message_t
*this, chunk_t in
)
309 if (in
.len
< sizeof(attr_hdr_t
))
311 DBG1(DBG_LIB
, "found short %N attribute header",
312 eap_type_names
, this->hdr
->type
);
315 hdr
= (attr_hdr_t
*)in
.ptr
;
319 /* attributes without data */
320 case AT_COUNTER_TOO_SMALL
:
321 if (!this->encrypted
)
323 return not_encrypted(hdr
->type
);
327 case AT_PERMANENT_ID_REQ
:
328 case AT_FULLAUTH_ID_REQ
:
330 if (hdr
->length
!= 1 || in
.len
< 4)
332 return invalid_length(hdr
->type
);
335 in
= chunk_skip(in
, 4);
338 /* attributes with two bytes data */
340 if (!this->encrypted
)
342 return not_encrypted(hdr
->type
);
345 case AT_CLIENT_ERROR_CODE
:
346 case AT_SELECTED_VERSION
:
347 case AT_NOTIFICATION
:
349 if (hdr
->length
!= 1 || in
.len
< 4)
351 return invalid_length(hdr
->type
);
353 data
= chunk_create(in
.ptr
+ 2, 2);
354 in
= chunk_skip(in
, 4);
357 /* attributes with an additional actual-length in bits or bytes */
358 case AT_NEXT_PSEUDONYM
:
359 case AT_NEXT_REAUTH_ID
:
360 if (!this->encrypted
)
362 return not_encrypted(hdr
->type
);
367 case AT_VERSION_LIST
:
371 if (hdr
->length
< 1 || in
.len
< 4)
373 return invalid_length(hdr
->type
);
375 memcpy(&len
, in
.ptr
+ 2, 2);
377 if (hdr
->type
== AT_RES
)
378 { /* AT_RES uses length encoding in bits */
381 if (len
> hdr
->length
* 4 || len
> in
.len
)
383 return invalid_length(hdr
->type
);
385 data
= chunk_create(in
.ptr
+ 4, len
);
386 in
= chunk_skip(in
, hdr
->length
* 4);
389 /* attributes with two reserved bytes, 16 bytes length */
391 if (!this->encrypted
)
393 return not_encrypted(hdr
->type
);
401 if (hdr
->length
!= 5 || in
.len
< 20)
403 return invalid_length(hdr
->type
);
405 data
= chunk_create(in
.ptr
+ 4, 16);
406 in
= chunk_skip(in
, 20);
409 /* attributes with two reserved bytes, variable length */
413 if (hdr
->length
* 4 > in
.len
|| in
.len
< 4)
415 return invalid_length(hdr
->type
);
417 data
= chunk_create(in
.ptr
+ 4, hdr
->length
* 4 - 4);
418 in
= chunk_skip(in
, hdr
->length
* 4);
421 /* attributes with no reserved bytes, 14 bytes length */
424 if (hdr
->length
!= 4 || in
.len
< 16)
426 return invalid_length(hdr
->type
);
428 data
= chunk_create(in
.ptr
+ 2, 14);
429 in
= chunk_skip(in
, 16);
432 /* other attributes (with 4n + 2 length) */
436 if (hdr
->length
* 4 > in
.len
|| in
.len
< 4)
438 return invalid_length(hdr
->type
);
440 data
= chunk_create(in
.ptr
+ 2, hdr
->length
* 4 - 2);
441 in
= chunk_skip(in
, hdr
->length
* 4);
446 /* handle special attributes */
460 case AT_NOTIFICATION
:
462 { /* remember P bit for MAC verification */
463 this->p_bit
= !!(data
.ptr
[0] & 0x40);
465 else if (!this->encrypted
)
467 DBG1(DBG_LIB
, "found P-bit 0 notify in unencrypted message");
472 add_attribute(this, hdr
->type
, data
);
477 call_hook(this, TRUE
, this->encrypted
);
483 * Decrypt a message and parse the decrypted attributes
485 static bool decrypt(private_simaka_message_t
*this)
491 crypter
= this->crypto
->get_crypter(this->crypto
);
492 if (!crypter
|| !this->iv
.len
|| !this->encr
.len
|| this->encrypted
)
496 if (this->encr
.len
% crypter
->get_block_size(crypter
))
498 DBG1(DBG_LIB
, "%N ENCR_DATA not a multiple of block size",
499 eap_type_names
, this->hdr
->type
);
503 crypter
->decrypt(crypter
, this->encr
, this->iv
, &plain
);
505 this->encrypted
= TRUE
;
506 success
= parse_attributes(this, plain
);
507 this->encrypted
= FALSE
;
512 METHOD(simaka_message_t
, parse
, bool,
513 private_simaka_message_t
*this)
517 if (this->attributes
->get_count(this->attributes
))
518 { /* Already parsed. Try to decrypt and parse AT_ENCR_DATA. */
519 return decrypt(this);
522 in
= chunk_create((char*)this->hdr
, ntohs(this->hdr
->length
));
523 if (!parse_attributes(this, chunk_skip(in
, sizeof(hdr_t
))))
527 /* try to decrypt if we already have keys */
528 return decrypt(this);
531 METHOD(simaka_message_t
, verify
, bool,
532 private_simaka_message_t
*this, chunk_t sigdata
)
534 chunk_t data
, backup
;
537 signer
= this->crypto
->get_signer(this->crypto
);
539 switch (this->hdr
->subtype
)
542 case SIM_CLIENT_ERROR
:
543 /* AKA_CLIENT_ERROR: */
544 case AKA_AUTHENTICATION_REJECT
:
545 case AKA_SYNCHRONIZATION_FAILURE
:
547 /* skip MAC if available */
551 case SIM_REAUTHENTICATION
:
552 /* AKA_REAUTHENTICATION: */
554 if (!this->mac
.ptr
|| !signer
)
555 { /* require MAC, but not found */
556 DBG1(DBG_LIB
, "%N message requires a MAC, but none found",
557 simaka_subtype_names
, this->hdr
->subtype
);
562 case SIM_NOTIFICATION
:
563 /* AKA_NOTIFICATION: */
566 { /* MAC not verified if in Phase 1 */
569 if (!this->mac
.ptr
|| !signer
)
571 DBG1(DBG_LIB
, "%N message has a phase 0 notify, but "
572 "no MAC found", simaka_subtype_names
, this->hdr
->subtype
);
578 /* unknown message? */
579 DBG1(DBG_LIB
, "signature rule for %N messages missing",
580 simaka_subtype_names
, this->hdr
->subtype
);
584 /* zero MAC for verification */
585 backup
= chunk_clonea(this->mac
);
586 memset(this->mac
.ptr
, 0, this->mac
.len
);
588 data
= chunk_create((char*)this->hdr
, ntohs(this->hdr
->length
));
591 data
= chunk_cata("cc", data
, sigdata
);
593 if (!signer
->verify_signature(signer
, data
, backup
))
595 DBG1(DBG_LIB
, "%N MAC verification failed",
596 eap_type_names
, this->hdr
->type
);
602 METHOD(simaka_message_t
, generate
, chunk_t
,
603 private_simaka_message_t
*this, chunk_t sigdata
)
605 /* buffers large enough for messages we generate */
606 char out_buf
[1024], encr_buf
[512];
607 enumerator_t
*enumerator
;
608 chunk_t out
, encr
, data
, *target
, mac
= chunk_empty
;
609 simaka_attribute_t type
;
614 call_hook(this, FALSE
, TRUE
);
616 out
= chunk_create(out_buf
, sizeof(out_buf
));
617 encr
= chunk_create(encr_buf
, sizeof(encr_buf
));
620 memcpy(out
.ptr
, this->hdr
, sizeof(hdr_t
));
621 out
= chunk_skip(out
, sizeof(hdr_t
));
623 /* encode attributes */
624 enumerator
= create_attribute_enumerator(this);
625 while (enumerator
->enumerate(enumerator
, &type
, &data
))
627 /* encrypt this attribute? */
631 case AT_NEXT_PSEUDONYM
:
632 case AT_NEXT_REAUTH_ID
:
634 case AT_COUNTER_TOO_SMALL
:
637 case AT_NOTIFICATION
:
638 /* P bit not set, encrypt */
639 if (!(data
.ptr
[0] & 0x40))
650 hdr
= (attr_hdr_t
*)target
->ptr
;
653 /* encode type specific */
656 /* attributes without data */
657 case AT_COUNTER_TOO_SMALL
:
659 case AT_PERMANENT_ID_REQ
:
660 case AT_FULLAUTH_ID_REQ
:
663 memset(target
->ptr
+ 2, 0, 2);
664 *target
= chunk_skip(*target
, 4);
667 /* attributes with two bytes data */
669 case AT_CLIENT_ERROR_CODE
:
670 case AT_SELECTED_VERSION
:
671 case AT_NOTIFICATION
:
674 memcpy(target
->ptr
+ 2, data
.ptr
, 2);
675 *target
= chunk_skip(*target
, 4);
678 /* attributes with an additional actual-length in bits or bytes */
679 case AT_NEXT_PSEUDONYM
:
680 case AT_NEXT_REAUTH_ID
:
682 case AT_VERSION_LIST
:
685 u_int16_t len
, padding
;
687 len
= htons(data
.len
);
689 { /* AT_RES uses length encoding in bits */
692 memcpy(target
->ptr
+ 2, &len
, sizeof(len
));
693 memcpy(target
->ptr
+ 4, data
.ptr
, data
.len
);
694 hdr
->length
= data
.len
/ 4 + 1;
695 padding
= (4 - (data
.len
% 4)) % 4;
699 memset(target
->ptr
+ 4 + data
.len
, 0, padding
);
701 *target
= chunk_skip(*target
, hdr
->length
* 4);
704 /* attributes with two reserved bytes, 16 bytes length */
710 memset(target
->ptr
+ 2, 0, 2);
711 memcpy(target
->ptr
+ 4, data
.ptr
, data
.len
);
712 *target
= chunk_skip(*target
, 20);
715 /* attributes with two reserved bytes, variable length */
718 hdr
->length
= 1 + data
.len
/ 4;
719 memset(target
->ptr
+ 2, 0, 2);
720 memcpy(target
->ptr
+ 4, data
.ptr
, data
.len
);
721 *target
= chunk_skip(*target
, data
.len
+ 4);
724 /* attributes with no reserved bytes, 14 bytes length */
728 memcpy(target
->ptr
+ 2, data
.ptr
, data
.len
);
729 *target
= chunk_skip(*target
, 16);
734 DBG1(DBG_LIB
, "no rule to encode %N, skipped",
735 simaka_attribute_names
, type
);
740 enumerator
->destroy(enumerator
);
742 /* encrypt attributes, if any */
743 if (encr
.len
< sizeof(encr_buf
))
750 crypter
= this->crypto
->get_crypter(this->crypto
);
751 bs
= crypter
->get_block_size(crypter
);
752 iv
.len
= crypter
->get_iv_size(crypter
);
754 /* add AT_PADDING attribute */
755 padding
= bs
- ((sizeof(encr_buf
) - encr
.len
) % bs
);
758 hdr
= (attr_hdr_t
*)encr
.ptr
;
759 hdr
->type
= AT_PADDING
;
760 hdr
->length
= padding
/ 4;
761 memset(encr
.ptr
+ 2, 0, padding
- 2);
762 encr
= chunk_skip(encr
, padding
);
764 encr
= chunk_create(encr_buf
, sizeof(encr_buf
) - encr
.len
);
766 /* add IV attribute */
767 hdr
= (attr_hdr_t
*)out
.ptr
;
769 hdr
->length
= iv
.len
/ 4 + 1;
770 memset(out
.ptr
+ 2, 0, 2);
771 out
= chunk_skip(out
, 4);
773 rng
= this->crypto
->get_rng(this->crypto
);
774 rng
->get_bytes(rng
, iv
.len
, out
.ptr
);
776 iv
= chunk_clonea(chunk_create(out
.ptr
, iv
.len
));
777 out
= chunk_skip(out
, iv
.len
);
779 /* inline encryption */
780 crypter
->encrypt(crypter
, encr
, iv
, NULL
);
782 /* add ENCR_DATA attribute */
783 hdr
= (attr_hdr_t
*)out
.ptr
;
784 hdr
->type
= AT_ENCR_DATA
;
785 hdr
->length
= encr
.len
/ 4 + 1;
786 memset(out
.ptr
+ 2, 0, 2);
787 memcpy(out
.ptr
+ 4, encr
.ptr
, encr
.len
);
788 out
= chunk_skip(out
, encr
.len
+ 4);
792 signer
= this->crypto
->get_signer(this->crypto
);
793 switch (this->hdr
->subtype
)
797 case SIM_REAUTHENTICATION
:
798 /* AKA_REAUTHENTICATION: */
799 /* TODO: Notifications without P bit */
803 bs
= signer
->get_block_size(signer
);
804 hdr
= (attr_hdr_t
*)out
.ptr
;
806 hdr
->length
= bs
/ 4 + 1;
807 memset(out
.ptr
+ 2, 0, 2 + bs
);
808 mac
= chunk_create(out
.ptr
+ 4, bs
);
809 out
= chunk_skip(out
, bs
+ 4);
816 /* calculate message length */
817 out
= chunk_create(out_buf
, sizeof(out_buf
) - out
.len
);
818 len
= htons(out
.len
);
819 memcpy(out
.ptr
+ 2, &len
, sizeof(len
));
824 data
= chunk_cata("cc", out
, sigdata
);
825 signer
->get_signature(signer
, data
, mac
.ptr
);
828 call_hook(this, FALSE
, FALSE
);
830 return chunk_clone(out
);
833 METHOD(simaka_message_t
, destroy
, void,
834 private_simaka_message_t
*this)
836 this->attributes
->destroy_function(this->attributes
, free
);
842 * Generic constructor.
844 static simaka_message_t
*simaka_message_create_data(chunk_t data
,
845 simaka_crypto_t
*crypto
)
847 private_simaka_message_t
*this;
848 hdr_t
*hdr
= (hdr_t
*)data
.ptr
;
850 if (data
.len
< sizeof(hdr_t
) || hdr
->length
!= htons(data
.len
))
852 DBG1(DBG_LIB
, "EAP-SIM/AKA header has invalid length");
855 if (hdr
->code
!= EAP_REQUEST
&& hdr
->code
!= EAP_RESPONSE
)
857 DBG1(DBG_LIB
, "invalid EAP code in EAP-SIM/AKA message",
858 eap_type_names
, hdr
->type
);
861 if (hdr
->type
!= EAP_SIM
&& hdr
->type
!= EAP_AKA
)
863 DBG1(DBG_LIB
, "invalid EAP type in EAP-SIM/AKA message",
864 eap_type_names
, hdr
->type
);
870 .is_request
= _is_request
,
871 .get_identifier
= _get_identifier
,
872 .get_type
= _get_type
,
873 .get_subtype
= _get_subtype
,
874 .create_attribute_enumerator
= _create_attribute_enumerator
,
875 .add_attribute
= _add_attribute
,
878 .generate
= _generate
,
881 .attributes
= linked_list_create(),
884 .hdr
= malloc(data
.len
),
886 memcpy(this->hdr
, hdr
, data
.len
);
888 return &this->public;
894 simaka_message_t
*simaka_message_create_from_payload(chunk_t data
,
895 simaka_crypto_t
*crypto
)
897 return simaka_message_create_data(data
, crypto
);
903 simaka_message_t
*simaka_message_create(bool request
, u_int8_t identifier
,
904 eap_type_t type
, simaka_subtype_t subtype
,
905 simaka_crypto_t
*crypto
)
908 .code
= request ? EAP_REQUEST
: EAP_RESPONSE
,
909 .identifier
= identifier
,
910 .length
= htons(sizeof(hdr_t
)),
914 return simaka_message_create_data(chunk_create((char*)&hdr
, sizeof(hdr
)),