#include <EAP8021X/EAPClientPlugin.h>
#include <EAP8021X/EAPClientProperties.h>
#include <SystemConfiguration/SCValidation.h>
#include <mach/boolean.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "EAPLog.h"
#include <CommonCrypto/CommonDigest.h>
#include <CommonCrypto/CommonCryptor.h>
#include <CommonCrypto/CommonHMAC.h>
#include <corecrypto/cc.h>
#include <Security/SecureTransport.h>
#include <Security/SecCertificate.h>
#include <sys/param.h>
#include <EAP8021X/EAPTLSUtil.h>
#include <EAP8021X/EAPCertificateUtil.h>
#include <EAP8021X/EAPKeychainUtil.h>
#include <EAP8021X/EAPSecurity.h>
#include <Security/SecureTransportPriv.h>
#include <Security/CipherSuite.h>
#include <EAP8021X/EAP.h>
#include <EAP8021X/EAPUtil.h>
#include <EAP8021X/EAPClientModule.h>
#include <EAP8021X/mschap.h>
#include <CoreFoundation/CFPreferences.h>
#include <TargetConditionals.h>
#include "myCFUtil.h"
#include "printdata.h"
#include "nbo.h"
#define STRLOG(__string, __level, __format, ...) \
{ if (__string != NULL) \
CFStringAppendFormat(__string, NULL, CFSTR(__format), \
## __VA_ARGS__); \
else EAPLOG(__level, __format, ## __VA_ARGS__); }
#define EAP_FAST_NAME "EAP-FAST"
#define EAP_FAST_NAME_LENGTH (sizeof(EAP_FAST_NAME) - 1)
enum {
kEAPFASTPacketFlagsVersionMask = 0x7,
kEAPFASTPacketFlagsFlagsMask = 0xf8,
kEAPFASTVersion1 = 1,
};
INLINE uint8_t
EAPFASTPacketFlagsFlags(uint8_t flags)
{
return (flags & kEAPFASTPacketFlagsFlagsMask);
}
INLINE uint8_t
EAPFASTPacketFlagsVersion(uint8_t flags)
{
return (flags & kEAPFASTPacketFlagsVersionMask);
}
INLINE void
EAPFASTPacketFlagsSetVersion(EAPTLSPacketRef eap_tls, uint8_t version)
{
eap_tls->flags &= ~kEAPFASTPacketFlagsVersionMask;
eap_tls->flags |= EAPFASTPacketFlagsVersion(version);
return;
}
typedef struct AuthorityIDData_s {
uint8_t auid_type[2];
uint8_t auid_length[2];
uint8_t auid_id[1];
} AuthorityIDData, * AuthorityIDDataRef;
INLINE uint16_t
AuthorityIDDataGetType(const AuthorityIDDataRef auid)
{
return (net_uint16_get(auid->auid_type));
}
INLINE uint16_t
AuthorityIDDataGetLength(const AuthorityIDDataRef auid)
{
return (net_uint16_get(auid->auid_length));
}
enum {
kAuthorityIDDataType = 4
};
enum {
kTLVTypeMask = 0x3fff,
kTLVTypeMandatoryBit = 0x8000,
kTLVTypeReservedBit = 0x4000,
kTLVTypeResult = 3,
kTLVTypeNAK = 4,
kTLVTypeError = 5,
kTLVTypeVendorSpecific = 7,
kTLVTypeEAPPayload = 9,
kTLVTypeIntermediateResult = 10,
kTLVTypePAC = 11,
kTLVTypeCryptoBinding = 12,
kTLVTypeServerTrustedRoot = 18,
kTLVTypeRequestAction = 19,
kTLVTypePKCS7 = 20,
};
typedef uint16_t TLVType;
typedef uint16_t TLVLength;
STATIC const char *
TLVTypeName(TLVType tlv_type)
{
const char * tlv_name = "Unknown";
switch (tlv_type) {
case kTLVTypeResult:
tlv_name = "Result";
break;
case kTLVTypeNAK:
tlv_name = "NAK";
break;
case kTLVTypeError:
tlv_name = "Error";
break;
case kTLVTypeVendorSpecific:
tlv_name = "VendorSpecific";
break;
case kTLVTypeEAPPayload:
tlv_name = "EAPPayload";
break;
case kTLVTypeIntermediateResult:
tlv_name = "IntermediateResult";
break;
case kTLVTypePAC:
tlv_name = "PAC";
break;
case kTLVTypeCryptoBinding:
tlv_name = "CryptoBinding";
break;
case kTLVTypeServerTrustedRoot:
tlv_name = "ServerTrustedRoot";
break;
case kTLVTypeRequestAction:
tlv_name = "RequestAction";
break;
case kTLVTypePKCS7:
tlv_name = "PKCS7";
break;
default:
break;
}
return (tlv_name);
}
typedef struct TLV_s {
uint8_t tlv_type[2];
uint8_t tlv_length[2];
uint8_t tlv_value[1];
} TLV, * TLVRef;
#define TLV_HEADER_LENGTH offsetof(TLV, tlv_value)
INLINE void
TLVSetLength(TLVRef tlv, TLVLength length)
{
net_uint16_set(tlv->tlv_length, length);
return;
}
INLINE TLVLength
TLVGetLength(const TLVRef tlv)
{
return (net_uint16_get(tlv->tlv_length));
}
INLINE void
TLVSetType(TLVRef tlv, TLVType type)
{
net_uint16_set(tlv->tlv_type, type);
return;
}
INLINE TLVType
TLVGetType(const TLVRef tlv)
{
return (net_uint16_get(tlv->tlv_type));
}
INLINE bool
TLVTypeIsMandatory(TLVType tlv_type)
{
return ((tlv_type & kTLVTypeMandatoryBit) != 0);
}
INLINE TLVType
TLVTypeGetType(TLVType tlv_type)
{
return (tlv_type & kTLVTypeMask);
}
typedef struct ResultTLV_s {
uint8_t res_type[2];
uint8_t res_length[2];
uint8_t res_status[2];
} ResultTLV, * ResultTLVRef;
#define RESULT_TLV_LENGTH 2
enum {
kTLVStatusSuccess = 1,
kTLVStatusFailure = 2,
};
typedef uint16_t TLVStatus;
INLINE void
ResultTLVSetStatus(ResultTLVRef tlv, TLVStatus status)
{
net_uint16_set(tlv->res_status, status);
return;
}
INLINE TLVStatus
ResultTLVGetStatus(const ResultTLVRef tlv)
{
return (net_uint16_get(tlv->res_status));
}
typedef struct NAKTLV_s {
uint8_t na_type[2];
uint8_t na_length[2];
uint8_t na_vendor_id[4];
uint8_t na_nak_type[2];
uint8_t na_tlvs[1];
} NAKTLV, * NAKTLVRef;
#define NAK_TLV_MIN_LENGTH 6
typedef uint32_t VendorId;
INLINE void
NAKTLVSetVendorId(NAKTLVRef tlv, VendorId vendor_id)
{
net_uint32_set(tlv->na_vendor_id, vendor_id);
return;
}
INLINE void
NAKTLVSetNAKType(NAKTLVRef tlv, TLVType nak_type)
{
net_uint16_set(tlv->na_nak_type, nak_type);
return;
}
typedef struct ErrorTLV_s {
uint8_t er_type[2];
uint8_t er_length[2];
uint8_t er_error_code[4];
} ErrorTLV, * ErrorTLVRef;
#define ERROR_TLV_LENGTH 4
enum {
kErrorTLVErrorCodeTunnelCompromise = 2001,
kErrorTLVErrorCodeUnexpectedTLVsExchanged = 2002
};
typedef uint32_t ErrorTLVErrorCode;
INLINE void
ErrorTLVSetErrorCode(ErrorTLVRef tlv, ErrorTLVErrorCode error_code)
{
net_uint32_set(tlv->er_error_code, error_code);
return;
}
INLINE ErrorTLVErrorCode
ErrorTLVGetErrorCode(const ErrorTLVRef tlv)
{
return ((ErrorTLVErrorCode)net_uint32_get(tlv->er_error_code));
}
typedef struct VendorSpecificTLV_s {
uint8_t vs_type[2];
uint8_t vs_length[2];
uint8_t vs_vendor_id[4];
uint8_t vs_tlvs[1];
} VendorSpecificTLV, * VendorSpecificTLVRef;
#define VENDOR_SPECIFIC_TLV_MIN_LENGTH 4
typedef struct EAPPayloadTLV_s {
uint8_t ep_type[2];
uint8_t ep_length[2];
uint8_t ep_eap_packet[1];
} EAPPayloadTLV, * EAPPayloadTLVRef;
typedef struct IntermediateResultTLV_s {
uint8_t ir_type[2];
uint8_t ir_length[2];
uint8_t ir_status[2];
uint8_t ir_tlvs[1];
} IntermediateResultTLV, * IntermediateResultTLVRef;
#define INTERMEDIATE_RESULT_TLV_MIN_LENGTH 2
INLINE void
IntermediateResultTLVSetStatus(IntermediateResultTLVRef tlv,
TLVStatus status)
{
net_uint16_set(tlv->ir_status, status);
return;
}
INLINE TLVStatus
IntermediateResultTLVGetStatus(const IntermediateResultTLVRef tlv)
{
return (net_uint16_get(tlv->ir_status));
}
typedef struct CryptoBindingTLV_s {
uint8_t cb_type[2];
uint8_t cb_length[2];
uint8_t cb_reserved;
uint8_t cb_version;
uint8_t cb_received_version;
uint8_t cb_sub_type;
uint8_t cb_nonce[32];
uint8_t cb_compound_mac[20];
} CryptoBindingTLV, * CryptoBindingTLVRef;
#define CRYPTO_BINDING_TLV_LENGTH (sizeof(CryptoBindingTLV) - TLV_HEADER_LENGTH)
enum {
kCryptoBindingSubTypeBindingRequest = 0,
kCryptoBindingSubTypeBindingResponse = 1
};
typedef struct RequestActionTLV_s {
uint8_t re_type[2];
uint8_t re_length[2];
uint8_t re_action[2];
} RequestActionTLV, * RequestActionTLVRef;
enum {
kRequestActionTLVActionProcessTLV = 1,
kRequestActionTLVActionNegotiateEAP = 2,
};
typedef uint16_t RequestActionTLVAction;
typedef struct PACTLV_s {
uint8_t pa_type[2];
uint8_t pa_length[2];
uint8_t pa_attributes[1];
} PACTLV, * PACTLVRef;
enum {
kPACTLVAttributeTypePAC_Key = 1,
kPACTLVAttributeTypePAC_Opaque = 2,
kPACTLVAttributeTypeCRED_LIFETIME = 3,
kPACTLVAttributeTypeA_ID = 4,
kPACTLVAttributeTypeI_ID = 5,
kPACTLVAttributeTypeReserved = 6,
kPACTLVAttributeTypeA_ID_Info = 7,
kPACTLVAttributeTypePAC_Acknowledgement = 8,
kPACTLVAttributeTypePAC_Info = 9,
kPACTLVAttributeTypePAC_Type = 10
};
typedef uint16_t PACTLVAttributeType;
STATIC const char *
PACTLVAttributeTypeName(PACTLVAttributeType pac_type)
{
const char * attr_name = "Unknown";
switch (pac_type) {
case kPACTLVAttributeTypePAC_Key:
attr_name = "PAC-Key";
break;
case kPACTLVAttributeTypePAC_Opaque:
attr_name = "PAC-Opaque";
break;
case kPACTLVAttributeTypeCRED_LIFETIME:
attr_name = "CRED_LIFETIME";
break;
case kPACTLVAttributeTypeA_ID:
attr_name = "A-ID";
break;
case kPACTLVAttributeTypeI_ID:
attr_name = "I-ID";
break;
case kPACTLVAttributeTypeReserved:
attr_name = "Reserved";
break;
case kPACTLVAttributeTypeA_ID_Info :
attr_name = "A-ID-Info ";
break;
case kPACTLVAttributeTypePAC_Acknowledgement:
attr_name = "PAC-Acknowledgement";
break;
case kPACTLVAttributeTypePAC_Info:
attr_name = "PAC-Info";
break;
case kPACTLVAttributeTypePAC_Type:
attr_name = "PAC-Type";
break;
default:
break;
}
return (attr_name);
}
enum {
kPACTypeTunnel = 1,
kPACTypeMachineAuthentication = 2,
kPACTypeUserAuthorization = 3
};
typedef uint16_t PACType;
typedef struct PACTypeTLV_s {
uint8_t pt_type[2];
uint8_t pt_length[2];
uint8_t pt_pac_type[2];
} PACTypeTLV, * PACTypeTLVRef;
#define PAC_TYPE_TLV_LENGTH 2
INLINE void
PACTypeTLVSetPACType(PACTypeTLVRef tlv, PACType pac_type)
{
net_uint16_set(tlv->pt_pac_type, pac_type);
return;
}
typedef struct PACTLVAttributeList_s {
TLVRef key;
TLVRef opaque;
TLVRef info;
PACTypeTLVRef type;
TLVRef a_id;
TLVRef a_id_info;
TLVRef i_id;
} PACTLVAttributeList, * PACTLVAttributeListRef;
typedef struct TLVList_s {
ResultTLVRef result;
TLVStatus result_status;
NAKTLVRef nak;
ErrorTLVRef error;
ErrorTLVErrorCode error_code;
EAPPayloadTLVRef eap;
IntermediateResultTLVRef intermediate;
CryptoBindingTLVRef crypto;
TLVRef mandatory;
PACTLVRef pac;
PACTLVAttributeList pac_tlvs;
} TLVList, * TLVListRef;
#define kTLSKeyExpansionLabel "key expansion"
#define kTLSKeyExpansionLabelLength (sizeof(kTLSKeyExpansionLabel) - 1)
#define kPACToMasterLabel "PAC to master secret label hash"
#define kPACToMasterLabelLength (sizeof(kPACToMasterLabel) - 1)
#define kIMCKLabel "Inner Methods Compound Keys"
#define kIMCKLabelLength (sizeof(kIMCKLabel) - 1)
#define kSessionKeyLabel "Session Key Generating Function"
#define kSessionKeyLabelLength (sizeof(kSessionKeyLabel) - 1)
#define kExtendedSessionKeyLabel "Extended Session Key Generating Function"
#define kExtendedSessionKeyLabelLength (sizeof(kExtendedSessionKeyLabel) - 1)
#define SESSION_KEY_SEED_LENGTH 40
#define IMCK_LENGTH 60
#define S_IMCK_LENGTH SESSION_KEY_SEED_LENGTH
#define CMK_LENGTH 20
#define MASTER_SECRET_LENGTH 48
#define MSK_LENGTH 32
#define MASTER_KEY_LENGTH 64
#define EXTENDED_MASTER_KEY_LENGTH 64
EAPClientPluginFuncIntrospect eapfast_introspect;
STATIC EAPClientPluginFuncVersion eapfast_version;
STATIC EAPClientPluginFuncEAPType eapfast_type;
STATIC EAPClientPluginFuncEAPName eapfast_name;
STATIC EAPClientPluginFuncInit eapfast_init;
STATIC EAPClientPluginFuncFree eapfast_free;
STATIC EAPClientPluginFuncProcess eapfast_process;
STATIC EAPClientPluginFuncFreePacket eapfast_free_packet;
STATIC EAPClientPluginFuncSessionKey eapfast_session_key;
STATIC EAPClientPluginFuncServerKey eapfast_server_key;
STATIC EAPClientPluginFuncMasterSessionKeyCopyBytes eapfast_msk_copy_bytes;
STATIC EAPClientPluginFuncRequireProperties eapfast_require_props;
STATIC EAPClientPluginFuncPublishProperties eapfast_publish_props_copy;
STATIC EAPClientPluginFuncCopyPacketDescription eapfast_copy_packet_description;
typedef enum {
kRequestTypeStart,
kRequestTypeAck,
kRequestTypeData,
} RequestType;
static int inner_auth_types[] = {
kEAPTypeMSCHAPv2,
kEAPTypeMD5Challenge,
kEAPTypeGenericTokenCard,
kEAPTypeTLS
};
static int inner_auth_types_count = sizeof(inner_auth_types) / sizeof(inner_auth_types[0]);
struct eap_client {
EAPClientModuleRef module;
EAPClientPluginData plugin_data;
CFArrayRef require_props;
CFDictionaryRef publish_props;
EAPType last_type;
const char * last_type_name;
EAPClientStatus last_status;
int last_error;
};
enum {
kEAPFASTInnerAuthStateUnknown = 0,
kEAPFASTInnerAuthStateSuccess = 1,
kEAPFASTInnerAuthStateFailure = 2,
};
typedef int EAPFASTInnerAuthState;
#define TLS_PEER_ID_DATA_LENGTH 16
typedef struct {
SSLContextRef ssl_context;
memoryBuffer read_buffer;
int last_read_size;
memoryBuffer write_buffer;
int last_write_size;
int previous_identifier;
memoryIO mem_io;
EAPClientState plugin_state;
bool cert_is_required;
CFArrayRef certs;
int mtu;
bool eapfast_version_set;
uint8_t eapfast_version;
uint8_t eapfast_received_version;
OSStatus last_ssl_error;
EAPClientStatus last_client_status;
bool handshake_complete;
EAPFASTInnerAuthState inner_auth_state;
struct eap_client eap;
char in_message_buf[32 * 1024];
size_t in_message_size;
TLVList in_message_tlvs;
int last_eap_type_index;
OSStatus trust_ssl_error;
EAPClientStatus trust_status;
bool trust_proceed;
bool master_key_valid;
uint8_t master_key[MASTER_KEY_LENGTH];
uint8_t extended_master_key[EXTENDED_MASTER_KEY_LENGTH];
bool server_auth_completed;
CFArrayRef server_certs;
bool resume_sessions;
bool use_pac;
bool provision_pac;
bool provision_pac_anonymously;
uint8_t * tls_peer_id;
uint8_t tls_peer_id_data[TLS_PEER_ID_DATA_LENGTH];
int tls_peer_id_length;
bool session_was_resumed;
CFDictionaryRef pac_dict;
bool pac_was_requested;
bool pac_was_used;
bool pac_was_provisioned;
bool must_use_mschapv2;
bool crypto_binding_done;
uint8_t last_s_imck[S_IMCK_LENGTH];
uint8_t mschap_server_challenge[MSCHAP2_CHALLENGE_SIZE];
uint8_t mschap_client_challenge[MSCHAP2_CHALLENGE_SIZE];
} EAPFASTPluginData, *EAPFASTPluginDataRef;
enum {
kEAPTLSAvoidDenialOfServiceSize = 128 * 1024
};
#define BAD_IDENTIFIER (-1)
typedef struct Buffer {
uint8_t * data;
int size;
int used;
} Buffer, * BufferRef;
INLINE void
BufferInit(BufferRef buf, uint8_t * data, int size)
{
buf->data = data;
buf->size = size;
buf->used = 0;
return;
}
INLINE uint8_t *
BufferGetPtr(BufferRef buf)
{
return (buf->data);
}
INLINE uint8_t *
BufferGetWritePtr(BufferRef buf)
{
return (buf->data + buf->used);
}
INLINE int
BufferGetSize(BufferRef buf)
{
return (buf->size);
}
INLINE int
BufferGetUsed(BufferRef buf)
{
return (buf->used);
}
INLINE int
BufferGetSpace(BufferRef buf)
{
return (BufferGetSize(buf) - BufferGetUsed(buf));
}
INLINE bool
BufferAdvanceWritePtr(BufferRef buf, int size)
{
if (size > BufferGetSpace(buf)) {
EAPLOG(LOG_NOTICE, "EAP-FAST: BufferAdvanceWritePtr failed: %d > %d",
size, BufferGetSpace(buf));
return (FALSE);
}
buf->used += size;
return (TRUE);
}
#define kEAPFASTApplicationID CFSTR("com.apple.network.eapclient.eapfast")
#define kPACList CFSTR("PACList")
#define kPACKey CFSTR("PACKey")
#define kPACKeyKeychainItemID CFSTR("PACKeyKeychainItemID")
#define kPACOpaque CFSTR("PACOpaque")
#define kPACInfo CFSTR("PACInfo")
#define kAuthorityID CFSTR("AuthorityID")
#define kAuthorityIDInfo CFSTR("AuthorityIDInfo")
#define kInitiatorID CFSTR("InitiatorID")
STATIC CFArrayRef
pac_list_copy(void)
{
CFArrayRef pac_list;
pac_list = CFPreferencesCopyValue(kPACList, kEAPFASTApplicationID,
kCFPreferencesCurrentUser,
kCFPreferencesAnyHost);
if (pac_list == NULL) {
return (NULL);
}
if (isA_CFArray(pac_list) == NULL) {
my_CFRelease(&pac_list);
}
return (pac_list);
}
STATIC int
pac_list_find_pac(CFArrayRef pac_list,
const void * auid, uint16_t auid_length,
const uint8_t * initiator, int initiator_length)
{
CFIndex count;
int i;
CFStringRef initiator_cf = NULL;
int ret = -1;
if (initiator != NULL) {
initiator_cf
= CFStringCreateWithBytes(NULL,
initiator, initiator_length,
kCFStringEncodingUTF8,
TRUE);
}
count = CFArrayGetCount(pac_list);
for (i = 0; i < count; i++) {
CFStringRef initiator;
CFDictionaryRef item;
CFDataRef this_auid;
item = CFArrayGetValueAtIndex(pac_list, i);
if (isA_CFDictionary(item) == NULL) {
continue;
}
this_auid = CFDictionaryGetValue(item, kAuthorityID);
if (isA_CFData(this_auid) == NULL) {
continue;
}
if (CFDataGetLength(this_auid) != auid_length
|| memcmp(auid, CFDataGetBytePtr(this_auid), auid_length) != 0) {
continue;
}
initiator = CFDictionaryGetValue(item, kInitiatorID);
if (initiator == NULL) {
ret = i;
if (initiator_cf == NULL) {
break;
}
continue;
}
if (initiator_cf == NULL) {
continue;
}
if (isA_CFString(initiator) == NULL) {
continue;
}
if (CFEqual(initiator_cf, initiator)) {
ret = i;
break;
}
}
my_CFRelease(&initiator_cf);
return (ret);
}
CF_RETURNS_RETAINED STATIC CFDictionaryRef
pac_dict_insert_key(CFDictionaryRef orig_pac_dict)
{
CFMutableDictionaryRef dict;
CFDictionaryRef pac_dict = NULL;
CFDataRef pac_key = NULL;
OSStatus status;
CFStringRef unique_id_str;
if (isA_CFData(CFDictionaryGetValue(orig_pac_dict, kPACOpaque)) == NULL) {
goto done;
}
if (isA_CFData(CFDictionaryGetValue(orig_pac_dict, kPACKey)) != NULL) {
pac_dict = CFRetain(orig_pac_dict);
goto done;
}
unique_id_str = CFDictionaryGetValue(orig_pac_dict, kPACKeyKeychainItemID);
if (isA_CFString(unique_id_str) == NULL) {
goto done;
}
status = EAPSecKeychainPasswordItemCopy(NULL, unique_id_str, &pac_key);
if (status != noErr) {
EAPLOG(LOG_NOTICE,
"EAP-FAST: EAPSecKeychainPasswordItemCopy failed, %s (%d)\n",
EAPSSLErrorString(status), (int)status);
goto done;
}
dict = CFDictionaryCreateMutableCopy(NULL, 0, orig_pac_dict);
CFDictionarySetValue(dict, kPACKey, pac_key);
pac_dict = CFDictionaryCreateCopy(NULL, dict);
CFRelease(dict);
done:
my_CFRelease(&pac_key);
return (pac_dict);
}
STATIC CFDictionaryRef
pac_dict_copy(const void * auid, uint16_t auid_length,
const uint8_t * initiator, int initiator_length)
{
int i;
CFDictionaryRef pac_dict = NULL;
CFArrayRef pac_list;
pac_list = pac_list_copy();
if (pac_list == NULL) {
goto done;
}
i = pac_list_find_pac(pac_list, auid, auid_length,
initiator, initiator_length);
if (i == -1) {
goto done;
}
pac_dict = CFArrayGetValueAtIndex(pac_list, i);
pac_dict = pac_dict_insert_key(pac_dict);
done:
my_CFRelease(&pac_list);
return (pac_dict);
}
STATIC void
dict_add_tlv_as_data(CFMutableDictionaryRef dict, CFStringRef key, TLVRef tlv,
bool header_too)
{
CFDataRef data;
int len = 0;
if (header_too) {
len = TLV_HEADER_LENGTH;
}
data = CFDataCreate(NULL, header_too ? (void *)tlv : tlv->tlv_value,
TLVGetLength(tlv) + len);
CFDictionarySetValue(dict, key, data);
my_CFRelease(&data);
return;
}
STATIC void
dict_add_tlv_as_string(CFMutableDictionaryRef dict, CFStringRef key, TLVRef tlv)
{
CFStringRef str;
str = CFStringCreateWithBytes(NULL, (const UInt8 *)tlv->tlv_value,
TLVGetLength(tlv), kCFStringEncodingUTF8,
TRUE);
CFDictionarySetValue(dict, key, str);
my_CFRelease(&str);
return;
}
#define PAC_KEY_LABEL "802.1X EAP-FAST"
#define PAC_KEY_LABEL_SIZE (sizeof(PAC_KEY_LABEL) - 1)
#define PAC_KEY_DESCR "PAC-Key"
#define PAC_KEY_DESCR_SIZE (sizeof(PAC_KEY_DESCR) - 1)
STATIC OSStatus
pac_keychain_init_items(bool system_mode,
const uint8_t * initiator, int initiator_length,
CFDataRef * initiator_p, SecAccessRef * access_p,
CFDataRef * descr_p, CFDataRef * label_p)
{
OSStatus status = noErr;
#if TARGET_OS_EMBEDDED
*access_p = NULL;
#else
if (system_mode) {
CFErrorRef error = NULL;
*access_p = SecAccessCreateWithOwnerAndACL(0, 0, kSecUseOnlyUID,
NULL, &error);
if (*access_p == NULL) {
status = errSecAllocate;
if (error != NULL) {
EAPLOG(LOG_NOTICE,
"EAP-FAST: mySecAccessCreateWithUid failed, %d",
(int)CFErrorGetCode(error));
CFRelease(error);
}
goto done;
}
}
else {
status = SecAccessCreate(CFSTR("802.1X EAP-FAST Plug-in"),
NULL, access_p);
if (status != noErr) {
EAPLOG(LOG_NOTICE, "EAP-FAST: SecAccessCreate failed, %s (%d)",
EAPSecurityErrorString(status), (int)status);
goto done;
}
}
#endif
*initiator_p = CFDataCreate(NULL, initiator, initiator_length);
*label_p = CFDataCreateWithBytesNoCopy(NULL,
(void *)PAC_KEY_LABEL,
PAC_KEY_LABEL_SIZE,
kCFAllocatorNull);
*descr_p = CFDataCreateWithBytesNoCopy(NULL,
(void *)PAC_KEY_DESCR,
PAC_KEY_DESCR_SIZE,
kCFAllocatorNull);
#if ! TARGET_OS_EMBEDDED
done:
#endif
return (status);
}
STATIC CFStringRef
pac_keychain_item_create(bool system_mode,
const uint8_t * password, int password_length,
const uint8_t * initiator, int initiator_length)
{
SecAccessRef access = NULL;
CFDataRef descr = NULL;
SecKeychainRef keychain = NULL;
CFDataRef label = NULL;
CFDataRef initiator_cf = NULL;
CFDataRef password_cf = NULL;
CFStringRef unique_id_str = NULL;
OSStatus status;
status = pac_keychain_init_items(system_mode,
initiator, initiator_length,
&initiator_cf, &access, &descr, &label);
if (status != noErr) {
goto done;
}
password_cf = CFDataCreate(NULL, password, password_length);
#if ! TARGET_OS_EMBEDDED
if (system_mode) {
status = SecKeychainCopyDomainDefault(kSecPreferencesDomainSystem,
&keychain);
if (status != noErr) {
goto done;
}
}
#endif
status
= EAPSecKeychainPasswordItemCreateUniqueWithAccess(keychain,
access,
label,
descr,
initiator_cf,
password_cf,
&unique_id_str);
if (status != noErr) {
EAPLOG(LOG_NOTICE,
"EAP-FAST: "
"EAPSecKeychainPasswordItemCreateUniqueWithAccess failed,"
"%s (%d)", EAPSecurityErrorString(status), (int)status);
}
done:
my_CFRelease(&keychain);
my_CFRelease(&access);
my_CFRelease(&descr);
my_CFRelease(&label);
my_CFRelease(&initiator_cf);
my_CFRelease(&password_cf);
return (unique_id_str);
}
STATIC OSStatus
pac_keychain_item_recreate(bool system_mode,
CFStringRef unique_id_str,
CFDataRef password_cf,
const uint8_t * initiator, int initiator_length)
{
SecAccessRef access = NULL;
CFDataRef descr = NULL;
SecKeychainRef keychain = NULL;
CFDataRef label = NULL;
CFDataRef initiator_cf = NULL;
OSStatus status;
status = pac_keychain_init_items(system_mode, initiator, initiator_length,
&initiator_cf, &access, &descr, &label);
if (status != noErr) {
goto done;
}
#if ! TARGET_OS_EMBEDDED
if (system_mode) {
status = SecKeychainCopyDomainDefault(kSecPreferencesDomainSystem,
&keychain);
if (status != noErr) {
goto done;
}
}
#endif
status = EAPSecKeychainPasswordItemCreateWithAccess(keychain,
access,
unique_id_str,
label,
descr,
initiator_cf,
password_cf);
if (status != noErr) {
EAPLOG(LOG_NOTICE,
"EAP-FAST: "
"EAPSecKeychainPasswordItemCreateWithAccess failed,"
"%s (%d)", EAPSecurityErrorString(status), (int)status);
}
done:
my_CFRelease(&keychain);
my_CFRelease(&access);
my_CFRelease(&descr);
my_CFRelease(&label);
my_CFRelease(&initiator_cf);
return (status);
}
STATIC OSStatus
pac_keychain_item_update(bool system_mode, CFStringRef unique_id_str,
const uint8_t * password, int password_length,
const uint8_t * initiator, int initiator_length)
{
CFDataRef password_cf;
OSStatus status;
password_cf = CFDataCreate(NULL, password, password_length);
status = EAPSecKeychainPasswordItemSet(NULL, unique_id_str, password_cf);
switch (status) {
case noErr:
break;
case errSecItemNotFound:
status = pac_keychain_item_recreate(system_mode,
unique_id_str, password_cf,
initiator, initiator_length);
break;
default:
EAPLOG(LOG_NOTICE,
"EAP-FAST: EAPSecKeychainPasswordItemSet failed, %s (%d)",
EAPSecurityErrorString(status), (int)status);
break;
}
my_CFRelease(&password_cf);
return (status);
}
STATIC void
remove_pac(CFDictionaryRef pac_dict,
const uint8_t * initiator, int initiator_length)
{
CFDataRef auid;
int i;
CFArrayRef pac_list;
CFMutableArrayRef new_pac_list;
OSStatus status;
CFStringRef unique_id_str;
unique_id_str = CFDictionaryGetValue(pac_dict, kPACKeyKeychainItemID);
auid = CFDictionaryGetValue(pac_dict, kAuthorityID);
pac_list = pac_list_copy();
if (pac_list == NULL) {
goto done;
}
i = pac_list_find_pac(pac_list,
CFDataGetBytePtr(auid), CFDataGetLength(auid),
initiator, initiator_length);
if (i == -1) {
goto done;
}
new_pac_list = CFArrayCreateMutableCopy(NULL, 0, pac_list);
CFArrayRemoveValueAtIndex(new_pac_list, i);
status = EAPSecKeychainPasswordItemRemove(NULL, unique_id_str);
if (status != noErr) {
EAPLOG(LOG_NOTICE,
"EAP-FAST: EAPSecKeychainPasswordItemRemove failed, %s (%d)",
EAPSecurityErrorString(status), (int)status);
}
CFPreferencesSetValue(kPACList, new_pac_list, kEAPFASTApplicationID,
kCFPreferencesCurrentUser,
kCFPreferencesAnyHost);
my_CFRelease(&new_pac_list);
(void)CFPreferencesSynchronize(kEAPFASTApplicationID,
kCFPreferencesCurrentUser,
kCFPreferencesAnyHost);
done:
my_CFRelease(&pac_list);
return;
}
STATIC bool
save_pac(bool system_mode, PACTLVAttributeListRef tlvlist_p)
{
int i = -1;
const uint8_t * initiator = NULL;
int initiator_length = 0;
CFStringRef key_id = NULL;
CFMutableArrayRef new_pac_list = NULL;
CFArrayRef old_pac_list = NULL;
CFMutableDictionaryRef pac_dict;
bool saved = FALSE;
pac_dict = CFDictionaryCreateMutable(NULL, 0,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
dict_add_tlv_as_data(pac_dict, kAuthorityID, tlvlist_p->a_id, FALSE);
if (tlvlist_p->a_id_info != NULL) {
dict_add_tlv_as_string(pac_dict, kAuthorityIDInfo,
tlvlist_p->a_id_info);
}
if (tlvlist_p->i_id != NULL) {
dict_add_tlv_as_string(pac_dict, kInitiatorID, tlvlist_p->i_id);
}
dict_add_tlv_as_data(pac_dict, kPACOpaque, tlvlist_p->opaque, TRUE);
dict_add_tlv_as_data(pac_dict, kPACInfo, tlvlist_p->info, FALSE);
if (tlvlist_p->i_id != NULL) {
initiator = tlvlist_p->i_id->tlv_value;
initiator_length = TLVGetLength(tlvlist_p->i_id);
}
old_pac_list = pac_list_copy();
if (old_pac_list == NULL) {
new_pac_list = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
}
else {
const void * auid;
int auid_length;
new_pac_list = CFArrayCreateMutableCopy(NULL, 0, old_pac_list);
auid = tlvlist_p->a_id->tlv_value;
auid_length = TLVGetLength(tlvlist_p->a_id);
i = pac_list_find_pac(new_pac_list, auid, auid_length,
initiator, initiator_length);
if (i != -1) {
CFDictionaryRef old_pac_dict;
old_pac_dict = CFArrayGetValueAtIndex(new_pac_list, i);
key_id = CFDictionaryGetValue(old_pac_dict, kPACKeyKeychainItemID);
}
}
if (key_id == NULL) {
key_id
= pac_keychain_item_create(system_mode,
tlvlist_p->key->tlv_value,
TLVGetLength(tlvlist_p->key),
initiator, initiator_length);
if (key_id == NULL) {
goto done;
}
CFDictionarySetValue(pac_dict, kPACKeyKeychainItemID, key_id);
my_CFRelease(&key_id);
}
else {
CFDictionarySetValue(pac_dict, kPACKeyKeychainItemID, key_id);
if (pac_keychain_item_update(system_mode, key_id,
tlvlist_p->key->tlv_value,
TLVGetLength(tlvlist_p->key),
initiator, initiator_length)
!= noErr) {
goto done;
}
}
if (i == -1) {
CFArrayAppendValue(new_pac_list, pac_dict);
}
else {
CFArraySetValueAtIndex(new_pac_list, i, pac_dict);
}
CFPreferencesSetValue(kPACList, new_pac_list, kEAPFASTApplicationID,
kCFPreferencesCurrentUser,
kCFPreferencesAnyHost);
saved = CFPreferencesSynchronize(kEAPFASTApplicationID,
kCFPreferencesCurrentUser,
kCFPreferencesAnyHost);
done:
my_CFRelease(&pac_dict);
my_CFRelease(&old_pac_list);
my_CFRelease(&new_pac_list);
return (saved);
}
STATIC void
T_PRF(const void * key, int key_length,
const void * label, int label_length,
const void * seed, int seed_length,
void * key_material, int key_material_length)
{
uint8_t * data;
uint16_t data_buf[256/sizeof(uint16_t)];
int data_length;
int i;
int left;
void * output;
uint8_t t_buf[CC_SHA1_DIGEST_LENGTH];
if (key_material_length == 0) {
EAPLOG_FL(LOG_NOTICE, "key_material_length is 0");
return;
}
data_length
= label_length
+ 1
+ seed_length
+ sizeof(t_buf)
+ sizeof(uint16_t)
+ 1;
if (data_length > sizeof(data_buf)) {
data = (uint8_t *)malloc(data_length);
}
else {
data = (uint8_t *)data_buf;
}
left = key_material_length;
output = key_material;
for (i = 0; ; i++) {
uint8_t * offset = data;
if (i != 0) {
memcpy(offset, t_buf, sizeof(t_buf));
offset += sizeof(t_buf);
}
memcpy(offset, label, label_length);
offset += label_length;
*offset++ = 0x00;
if (seed != NULL && seed_length != 0) {
memcpy(offset, seed, seed_length);
offset += seed_length;
}
net_uint16_set((void *)offset, key_material_length);
offset += sizeof(uint16_t);
*offset++ = i + 1;
CCHmac(kCCHmacAlgSHA1,
key, key_length, data, (offset - data), t_buf);
if (left <= sizeof(t_buf)) {
memcpy(output, t_buf, left);
break;
}
memcpy(output, t_buf, sizeof(t_buf));
output += sizeof(t_buf);
left -= sizeof(t_buf);
}
if ((void *)data != (void *)data_buf) {
free(data);
}
return;
}
STATIC void
eap_client_free(EAPClientPluginDataRef plugin)
{
EAPFASTPluginDataRef context = (EAPFASTPluginDataRef)plugin->private;
if (context->eap.module != NULL) {
EAPClientModulePluginFree(context->eap.module,
&context->eap.plugin_data);
context->eap.module = NULL;
my_CFRelease((CFDictionaryRef *)&context->eap.plugin_data.properties);
bzero(&context->eap.plugin_data, sizeof(context->eap.plugin_data));
}
my_CFRelease(&context->eap.require_props);
my_CFRelease(&context->eap.publish_props);
context->eap.last_type = kEAPTypeInvalid;
context->eap.last_type_name = NULL;
context->eap.last_status = kEAPClientStatusOK;
context->eap.last_error = 0;
return;
}
STATIC EAPType
eap_client_type(EAPFASTPluginDataRef context)
{
if (context->eap.module == NULL) {
return (kEAPTypeInvalid);
}
return (EAPClientModulePluginEAPType(context->eap.module));
}
INLINE void
S_set_uint32(const uint32_t * v_p, uint32_t value)
{
*((uint32_t *)v_p) = value;
return;
}
STATIC void
eap_client_set_properties(EAPClientPluginDataRef plugin)
{
EAPFASTPluginDataRef context = (EAPFASTPluginDataRef)plugin->private;
if (context->must_use_mschapv2) {
CFDataRef client_challenge;
CFMutableDictionaryRef dict;
CFDataRef server_challenge;
dict = CFDictionaryCreateMutableCopy(NULL, 0,
plugin->properties);
server_challenge
= CFDataCreateWithBytesNoCopy(NULL,
context->mschap_server_challenge,
sizeof(context->mschap_server_challenge),
kCFAllocatorNull);
CFDictionarySetValue(dict, kEAPClientPropEAPMSCHAPv2ServerChallenge,
server_challenge);
CFRelease(server_challenge);
client_challenge
= CFDataCreateWithBytesNoCopy(NULL,
context->mschap_client_challenge,
sizeof(context->mschap_client_challenge),
kCFAllocatorNull);
CFDictionarySetValue(dict, kEAPClientPropEAPMSCHAPv2ClientChallenge,
client_challenge);
CFRelease(client_challenge);
my_CFRelease((CFDictionaryRef *)&context->eap.plugin_data.properties);
*((CFDictionaryRef *)&context->eap.plugin_data.properties) = dict;
}
else {
my_CFRelease((CFDictionaryRef *)&context->eap.plugin_data.properties);
*((CFDictionaryRef *)&context->eap.plugin_data.properties)
= CFRetain(plugin->properties);
}
return;
}
STATIC bool
eap_client_init(EAPClientPluginDataRef plugin, EAPType type)
{
EAPFASTPluginDataRef context = (EAPFASTPluginDataRef)plugin->private;
EAPClientModule * module;
context->eap.last_type = kEAPTypeInvalid;
context->eap.last_type_name = NULL;
if (context->eap.module != NULL) {
EAPLOG_FL(LOG_NOTICE, "already initialized");
return (TRUE);
}
module = EAPClientModuleLookup(type);
if (module == NULL) {
return (FALSE);
}
my_CFRelease(&context->eap.require_props);
my_CFRelease(&context->eap.publish_props);
bzero(&context->eap.plugin_data, sizeof(context->eap.plugin_data));
S_set_uint32(&context->eap.plugin_data.mtu, plugin->mtu);
context->eap.plugin_data.username = plugin->username;
S_set_uint32(&context->eap.plugin_data.username_length,
plugin->username_length);
context->eap.plugin_data.password = plugin->password;
S_set_uint32(&context->eap.plugin_data.password_length,
plugin->password_length);
eap_client_set_properties(plugin);
context->eap.plugin_data.unique_id = plugin->unique_id;
S_set_uint32(&context->eap.plugin_data.unique_id_length,
plugin->unique_id_length);
context->eap.last_status =
EAPClientModulePluginInit(module, &context->eap.plugin_data,
&context->eap.require_props,
&context->eap.last_error);
context->eap.last_type_name = EAPClientModulePluginEAPName(module);
context->eap.last_type = type;
if (context->eap.last_status != kEAPClientStatusOK) {
return (FALSE);
}
context->eap.module = module;
return (TRUE);
}
STATIC CFArrayRef
eap_client_require_properties(EAPFASTPluginDataRef context)
{
return (EAPClientModulePluginRequireProperties(context->eap.module,
&context->eap.plugin_data));
}
STATIC CFDictionaryRef
eap_client_publish_properties(EAPFASTPluginDataRef context)
{
return (EAPClientModulePluginPublishProperties(context->eap.module,
&context->eap.plugin_data));
}
STATIC EAPClientState
eap_client_process(EAPClientPluginDataRef plugin, EAPPacketRef in_pkt_p,
EAPPacketRef * out_pkt_p)
{
EAPFASTPluginDataRef context = (EAPFASTPluginDataRef)plugin->private;
EAPClientState cstate;
context->eap.plugin_data.username = plugin->username;
S_set_uint32(&context->eap.plugin_data.username_length,
plugin->username_length);
context->eap.plugin_data.password = plugin->password;
S_set_uint32(&context->eap.plugin_data.password_length,
plugin->password_length);
S_set_uint32(&context->eap.plugin_data.generation,
plugin->generation);
eap_client_set_properties(plugin);
cstate = EAPClientModulePluginProcess(context->eap.module,
&context->eap.plugin_data,
in_pkt_p, out_pkt_p,
&context->eap.last_status,
&context->eap.last_error);
return (cstate);
}
STATIC void
eap_client_free_packet(EAPFASTPluginDataRef context, EAPPacketRef out_pkt_p)
{
EAPClientModulePluginFreePacket(context->eap.module,
&context->eap.plugin_data,
out_pkt_p);
}
STATIC void *
eap_client_session_key(EAPFASTPluginDataRef context, int * key_length)
{
return (EAPClientModulePluginSessionKey(context->eap.module,
&context->eap.plugin_data,
key_length));
}
STATIC OSStatus
ssl_get_server_client_random(SSLContextRef ssl_context,
void * random, int * random_size)
{
size_t offset;
size_t size;
OSStatus status;
size = *random_size;
status = SSLInternalServerRandom(ssl_context, random, &size);
if (status != noErr) {
EAPLOG(LOG_NOTICE,
"EAP-FAST: ssl_get_server_client_random:"
" SSLInternalServerRandom failed, %s (%d)\n",
EAPSSLErrorString(status), (int)status);
goto done;
}
if ((size + SSL_CLIENT_SRVR_RAND_SIZE) > *random_size) {
EAPLOG(LOG_NOTICE,
"EAP-FAST: ssl_get_server_client_random:"
" SSLInternalServerRandom %ld > %d\n",
size + SSL_CLIENT_SRVR_RAND_SIZE, *random_size);
status = errSSLBufferOverflow;
goto done;
}
offset = size;
size = *random_size - size;
status = SSLInternalClientRandom(ssl_context,
random + offset, &size);
if (status != noErr) {
EAPLOG(LOG_NOTICE,
"EAP-FAST: ssl_get_server_client_random:"
" SSLInternalClientRandom failed, %s\n",
EAPSSLErrorString(status));
goto done;
}
*random_size = (int)(offset + size);
done:
return (status);
}
STATIC OSStatus
eapfast_generate_key_material(EAPFASTPluginDataRef context,
uint8_t session_key_seed[SESSION_KEY_SEED_LENGTH],
uint8_t server_challenge[MSCHAP2_CHALLENGE_SIZE],
uint8_t client_challenge[MSCHAP2_CHALLENGE_SIZE])
{
size_t digest_size;
size_t iv_size;
uint8_t * key_block = NULL;
uint8_t key_block_data[256];
int key_block_length;
int key_block_offset;
char master_secret[SSL_MASTER_SECRET_SIZE];
size_t master_secret_length;
char random[SSL_CLIENT_SRVR_RAND_SIZE * 2];
int random_size;
OSStatus status;
size_t symmetric_key_size;
random_size = sizeof(random);
status = ssl_get_server_client_random(context->ssl_context,
random, &random_size);
if (status != noErr) {
goto done;
}
master_secret_length = sizeof(master_secret);
status = SSLInternalMasterSecret(context->ssl_context, master_secret,
&master_secret_length);
if (status != noErr) {
EAPLOG_FL(LOG_NOTICE,
"SSLInternalMasterSecret failed, %s (%d)",
EAPSSLErrorString(status), (int)status);
goto done;
}
status = SSLGetCipherSizes(context->ssl_context, &digest_size,
&symmetric_key_size, &iv_size);
if (status != noErr) {
EAPLOG_FL(LOG_NOTICE,
"SSLGetCipherSizes failed, %s (%d)",
EAPSSLErrorString(status), (int)status);
goto done;
}
key_block_offset = (int)(digest_size + symmetric_key_size + iv_size) * 2;
key_block_length = key_block_offset + SESSION_KEY_SEED_LENGTH
+ MSCHAP2_CHALLENGE_SIZE * 2;
if (key_block_length > sizeof(key_block_data)) {
key_block = (uint8_t *)malloc(key_block_length);
}
else {
key_block = key_block_data;
}
status = SSLInternal_PRF(context->ssl_context,
master_secret, master_secret_length,
kTLSKeyExpansionLabel,
kTLSKeyExpansionLabelLength,
random, random_size,
key_block,
key_block_length);
if (status != noErr) {
EAPLOG_FL(LOG_NOTICE,
"SSLInternal_PRF failed, %s (%d)\n",
EAPSSLErrorString(status), (int)status);
goto done;
}
memcpy(session_key_seed, key_block + key_block_offset,
SESSION_KEY_SEED_LENGTH);
key_block_offset += SESSION_KEY_SEED_LENGTH;
memcpy(server_challenge, key_block + key_block_offset,
MSCHAP2_CHALLENGE_SIZE);
key_block_offset += MSCHAP2_CHALLENGE_SIZE;
memcpy(client_challenge, key_block + key_block_offset,
MSCHAP2_CHALLENGE_SIZE);
status = noErr;
done:
if (key_block != NULL && key_block != key_block_data) {
free(key_block);
}
return (status);
}
STATIC void
eapfast_compute_session_key(EAPFASTPluginDataRef context)
{
T_PRF(context->last_s_imck, sizeof(context->last_s_imck),
kSessionKeyLabel, kSessionKeyLabelLength,
NULL, 0,
context->master_key, sizeof(context->master_key));
T_PRF(context->last_s_imck, sizeof(context->last_s_imck),
kExtendedSessionKeyLabel, kExtendedSessionKeyLabelLength,
NULL, 0,
context->extended_master_key, sizeof(context->extended_master_key));
context->master_key_valid = TRUE;
return;
}
STATIC void
eapfast_free_context(EAPClientPluginDataRef plugin)
{
EAPFASTPluginDataRef context = (EAPFASTPluginDataRef)plugin->private;
if (context == NULL) {
return;
}
eap_client_free(plugin);
if (context->ssl_context != NULL) {
CFRelease(context->ssl_context);
context->ssl_context = NULL;
}
if (context->tls_peer_id != NULL
&& context->tls_peer_id != context->tls_peer_id_data) {
free(context->tls_peer_id);
}
my_CFRelease(&context->certs);
my_CFRelease(&context->server_certs);
memoryIOClearBuffers(&context->mem_io);
my_CFRelease(&context->pac_dict);
free(context);
return;
}
STATIC const void *
GetAuthorityID(const void * tls_data, int tls_data_length,
uint16_t *ret_length)
{
const void * auid = NULL;
AuthorityIDDataRef auid_data;
uint16_t auid_length;
*ret_length = 0;
auid_data = (AuthorityIDDataRef)tls_data;
if (tls_data_length < sizeof(AuthorityIDData)
|| (AuthorityIDDataGetType(auid_data) != kAuthorityIDDataType)) {
goto done;
}
auid_length = AuthorityIDDataGetLength(auid_data);
if (auid_length == 0) {
goto done;
}
if (auid_length > (tls_data_length - offsetof(AuthorityIDData, auid_id))) {
EAPLOG(LOG_NOTICE, "EAP-FAST: GetAuthorityID %d > %ld, ignoring",
auid_length,
tls_data_length - offsetof(AuthorityIDData, auid_id));
goto done;
}
auid = auid_data->auid_id;
*ret_length = auid_length;
done:
return (auid);
}
STATIC void
eapfast_context_clear(EAPFASTPluginDataRef context)
{
context->plugin_state = kEAPClientStateAuthenticating;
context->previous_identifier = BAD_IDENTIFIER;
context->last_ssl_error = noErr;
context->last_client_status = kEAPClientStatusOK;
context->handshake_complete = FALSE;
context->trust_proceed = FALSE;
context->server_auth_completed = FALSE;
context->in_message_size = 0;
context->inner_auth_state = kEAPFASTInnerAuthStateUnknown;
context->master_key_valid = FALSE;
context->last_write_size = 0;
context->last_read_size = 0;
context->eapfast_version_set = FALSE;
context->session_was_resumed = FALSE;
context->pac_was_requested = FALSE;
context->pac_was_used = FALSE;
context->crypto_binding_done = FALSE;
context->must_use_mschapv2 = FALSE;
context->pac_was_provisioned = FALSE;
return;
}
STATIC void
eapfast_compute_master_secret(SSLContextRef ctx, const void * arg,
void * secret, size_t *secret_length)
{
EAPFASTPluginDataRef context = (EAPFASTPluginDataRef)arg;
CFDataRef pac_key;
char random[SSL_CLIENT_SRVR_RAND_SIZE * 2];
int random_size;
OSStatus status;
if (context->pac_dict == NULL) {
EAPLOG_FL(LOG_NOTICE, "pac_dict is NULL");
goto failed;
}
pac_key = CFDictionaryGetValue(context->pac_dict, kPACKey);
if (pac_key == NULL) {
EAPLOG_FL(LOG_NOTICE, "pac_key is NULL");
goto failed;
}
if (*secret_length < MASTER_SECRET_LENGTH) {
EAPLOG_FL(LOG_NOTICE, "%lu < %d",
*secret_length, MASTER_SECRET_LENGTH);
goto failed;
}
random_size = sizeof(random);
status = ssl_get_server_client_random(context->ssl_context,
random, &random_size);
if (status != noErr) {
goto failed;
}
T_PRF(CFDataGetBytePtr(pac_key), (int)CFDataGetLength(pac_key),
kPACToMasterLabel, kPACToMasterLabelLength,
random, random_size,
secret, MASTER_SECRET_LENGTH);
*secret_length = MASTER_SECRET_LENGTH;
context->pac_was_used = TRUE;
return;
failed:
*secret_length = 0;
return;
}
STATIC OSStatus
eapfast_start(EAPClientPluginDataRef plugin,
const void * in_data_ptr, int in_data_length)
{
const void * auid;
uint16_t auid_length;
EAPFASTPluginDataRef context = (EAPFASTPluginDataRef)plugin->private;
SSLContextRef ssl_context = NULL;
OSStatus status = noErr;
auid = GetAuthorityID(in_data_ptr, in_data_length, &auid_length);
context->last_eap_type_index = 0;
if (context->ssl_context != NULL) {
CFRelease(context->ssl_context);
context->ssl_context = NULL;
}
my_CFRelease(&context->server_certs);
my_CFRelease(&context->pac_dict);
memoryIOClearBuffers(&context->mem_io);
ssl_context = EAPTLSMemIOContextCreate(FALSE, &context->mem_io, NULL,
&status);
if (ssl_context == NULL) {
EAPLOG_FL(LOG_NOTICE, "EAPTLSMemIOContextCreate failed, %s (%ld)",
EAPSSLErrorString(status), (long)status);
goto failed;
}
if (context->cert_is_required) {
if (context->certs == NULL) {
status = EAPTLSCopyIdentityTrustChain(plugin->sec_identity,
plugin->properties,
&context->certs);
if (status != noErr) {
EAPLOG_FL(LOG_NOTICE,
"failed to find client cert/identity, %s (%ld)",
EAPSSLErrorString(status), (long)status);
goto failed;
}
}
status = SSLSetCertificate(ssl_context, context->certs);
if (status != noErr) {
EAPLOG_FL(LOG_NOTICE,
"SSLSetCertificate failed, %s (%ld)",
EAPSSLErrorString(status), (long)status);
goto failed;
}
}
if (context->use_pac && auid != NULL) {
CFDictionaryRef pac_dict;
pac_dict = pac_dict_copy(auid, auid_length,
plugin->username, plugin->username_length);
if (pac_dict != NULL) {
CFDataRef pac_opaque;
pac_opaque = CFDictionaryGetValue(pac_dict, kPACOpaque);
status
= SSLInternalSetSessionTicket(ssl_context,
CFDataGetBytePtr(pac_opaque),
CFDataGetLength(pac_opaque));
if (status != noErr) {
my_CFRelease(&pac_dict);
EAPLOG_FL(LOG_NOTICE,
"SSLInternalSetSessionTicket failed, %s (%ld)",
EAPSSLErrorString(status), (long)status);
goto failed;
}
status
= SSLInternalSetMasterSecretFunction(ssl_context,
eapfast_compute_master_secret,
context);
if (status != noErr) {
my_CFRelease(&pac_dict);
EAPLOG_FL(LOG_NOTICE,
"SSLInternalSetMasterSecretFunction failed, %s (%ld)",
EAPSSLErrorString(status), (long)status);
goto failed;
}
context->pac_dict = pac_dict;
}
else if (context->provision_pac_anonymously) {
SSLCipherSuite cipher = TLS_DH_anon_WITH_AES_128_CBC_SHA;
status = SSLSetEnabledCiphers(ssl_context, &cipher, 1);
if (status != noErr) {
EAPLOG_FL(LOG_NOTICE,
"SSLSetEnabledCiphers failed, %s (%ld)",
EAPSSLErrorString(status), (long)status);
goto failed;
}
}
}
if (context->pac_dict == NULL
&& context->resume_sessions && context->tls_peer_id != NULL) {
status = SSLSetPeerID(ssl_context, context->tls_peer_id,
context->tls_peer_id_length);
if (status != noErr) {
EAPLOG_FL(LOG_NOTICE,
"SSLSetPeerID failed, %s (%ld)",
EAPSSLErrorString(status), (long)status);
goto failed;
}
}
context->ssl_context = ssl_context;
eapfast_context_clear(context);
return (status);
failed:
if (ssl_context != NULL) {
CFRelease(ssl_context);
}
return (status);
}
STATIC EAPClientStatus
eapfast_init(EAPClientPluginDataRef plugin, CFArrayRef * require_props,
EAPClientDomainSpecificError * error)
{
EAPFASTPluginDataRef context = NULL;
*error = 0;
context = malloc(sizeof(*context));
bzero(context, sizeof(*context));
context->cert_is_required
= my_CFDictionaryGetBooleanValue(plugin->properties,
kEAPClientPropTLSCertificateIsRequired,
FALSE);
context->mtu = plugin->mtu;
context->resume_sessions
= my_CFDictionaryGetBooleanValue(plugin->properties,
kEAPClientPropTLSEnableSessionResumption,
TRUE);
if (context->resume_sessions) {
int len;
len = EAP_FAST_NAME_LENGTH;
if (plugin->unique_id != NULL) {
len += plugin->unique_id_length;
}
if (len > sizeof(context->tls_peer_id_data)) {
context->tls_peer_id = malloc(len);
}
else {
context->tls_peer_id = context->tls_peer_id_data;
}
memcpy(context->tls_peer_id, EAP_FAST_NAME, EAP_FAST_NAME_LENGTH);
if (plugin->unique_id != NULL) {
memcpy(context->tls_peer_id + EAP_FAST_NAME_LENGTH,
plugin->unique_id, plugin->unique_id_length);
}
context->tls_peer_id_length = len;
}
context->use_pac
= my_CFDictionaryGetBooleanValue(plugin->properties,
kEAPClientPropEAPFASTUsePAC,
FALSE);
if (context->use_pac) {
context->provision_pac
= my_CFDictionaryGetBooleanValue(plugin->properties,
kEAPClientPropEAPFASTProvisionPAC,
FALSE);
context->provision_pac_anonymously
= my_CFDictionaryGetBooleanValue(plugin->properties,
kEAPClientPropEAPFASTProvisionPACAnonymously,
FALSE);
}
memoryIOInit(&context->mem_io, &context->read_buffer,
&context->write_buffer);
plugin->private = context;
eapfast_context_clear(context);
return (kEAPClientStatusOK);
}
STATIC void
eapfast_free(EAPClientPluginDataRef plugin)
{
eapfast_free_context(plugin);
plugin->private = NULL;
return;
}
STATIC void
eapfast_free_packet(EAPClientPluginDataRef plugin, EAPPacketRef arg)
{
if (arg != NULL) {
free(arg);
}
return;
}
STATIC EAPPacketRef
EAPFASTPacketCreateAck(int identifier)
{
return (EAPTLSPacketCreate(kEAPCodeResponse, kEAPTypeEAPFAST,
identifier, 0, NULL, NULL));
}
STATIC bool
is_supported_type(EAPFASTPluginDataRef context, EAPType type)
{
int i;
if (context->must_use_mschapv2) {
return (type == kEAPTypeMSCHAPv2);
}
for (i = 0; i < inner_auth_types_count; i++) {
if (inner_auth_types[i] == type) {
return (TRUE);
}
}
return (FALSE);
}
STATIC EAPType
next_eap_type(EAPFASTPluginDataRef context)
{
if (context->must_use_mschapv2) {
if (context->last_eap_type_index == 0) {
context->last_eap_type_index++;
return (kEAPTypeMSCHAPv2);
}
return (kEAPTypeInvalid);
}
if (context->last_eap_type_index >= inner_auth_types_count) {
return (kEAPTypeInvalid);
}
return (inner_auth_types[context->last_eap_type_index++]);
}
STATIC EAPResponsePacketRef
eapfast_eap_process(EAPClientPluginDataRef plugin, EAPRequestPacketRef in_pkt_p,
char * out_buf, int * out_buf_size,
EAPClientStatus * client_status,
bool * call_module_free_packet)
{
EAPFASTPluginDataRef context = (EAPFASTPluginDataRef)plugin->private;
uint8_t desired_type;
EAPResponsePacketRef out_pkt_p = NULL;
EAPClientState state;
*call_module_free_packet = FALSE;
switch (in_pkt_p->code) {
case kEAPCodeRequest:
if (in_pkt_p->type == kEAPTypeInvalid) {
goto done;
}
if (in_pkt_p->type != eap_client_type(context)) {
if (is_supported_type(context, in_pkt_p->type) == FALSE) {
EAPType eap_type = next_eap_type(context);
if (eap_type == kEAPTypeInvalid) {
*client_status = kEAPClientStatusProtocolNotSupported;
context->plugin_state = kEAPClientStateFailure;
goto done;
}
desired_type = eap_type;
out_pkt_p = (EAPResponsePacketRef)
EAPPacketCreate(out_buf, *out_buf_size,
kEAPCodeResponse,
in_pkt_p->identifier,
kEAPTypeNak, &desired_type,
1,
out_buf_size);
goto done;
}
eap_client_free(plugin);
if (eap_client_init(plugin, in_pkt_p->type) == FALSE) {
if (context->eap.last_status
!= kEAPClientStatusUserInputRequired) {
EAPLOG_FL(LOG_NOTICE,
"eap_client_init type %d failed",
in_pkt_p->type);
*client_status = context->eap.last_status;
context->plugin_state = kEAPClientStateFailure;
goto done;
}
*client_status = context->eap.last_status;
goto done;
}
}
break;
case kEAPCodeResponse:
if (in_pkt_p->type != eap_client_type(context)) {
goto done;
}
break;
case kEAPCodeFailure:
break;
case kEAPCodeSuccess:
break;
default:
break;
}
if (context->eap.module == NULL) {
goto done;
}
my_CFRelease(&context->eap.require_props);
my_CFRelease(&context->eap.publish_props);
state = eap_client_process(plugin, (EAPPacketRef)in_pkt_p,
(EAPPacketRef *)&out_pkt_p);
if (out_pkt_p != NULL) {
*call_module_free_packet = TRUE;
*out_buf_size = EAPPacketGetLength((EAPPacketRef)out_pkt_p);
}
context->eap.publish_props = eap_client_publish_properties(context);
switch (state) {
case kEAPClientStateAuthenticating:
if (context->eap.last_status == kEAPClientStatusUserInputRequired) {
context->eap.require_props
= eap_client_require_properties(context);
*client_status = context->last_client_status =
context->eap.last_status;
}
break;
case kEAPClientStateSuccess:
context->inner_auth_state = kEAPFASTInnerAuthStateSuccess;
break;
case kEAPClientStateFailure:
context->inner_auth_state = kEAPFASTInnerAuthStateFailure;
*client_status = context->eap.last_status;
break;
}
done:
return (out_pkt_p);
}
STATIC bool
PACTLVAttributeListParse(PACTLVAttributeListRef tlvlist_p,
const void * buf, int buf_size,
CFMutableStringRef str)
{
int left;
bool ret = FALSE;
TLVRef scan;
scan = (const TLVRef)buf;
left = buf_size;
while (1) {
TLVType tlv_type;
TLVLength tlv_length;
if (left == 0) {
break;
}
if (left < TLV_HEADER_LENGTH) {
STRLOG(str, LOG_NOTICE,
"EAP-FAST: TLV attribute is too short (%d < %d)",
left, (int)TLV_HEADER_LENGTH);
goto done;
}
tlv_type = TLVGetType(scan);
tlv_length = TLVGetLength(scan);
if (left < (TLV_HEADER_LENGTH + tlv_length)) {
STRLOG(str, LOG_NOTICE,
"EAP-FAST: TLV attribute is too short (%d < %d)",
left, (int)TLV_HEADER_LENGTH);
goto done;
}
if (str != NULL) {
TLVType t = TLVTypeGetType(tlv_type);
STRING_APPEND(str,
"%s PACTLV Attribute (type=%d) Length=%d%s\n",
PACTLVAttributeTypeName(t), t, tlv_length,
TLVTypeIsMandatory(tlv_type) ? " [mandatory]" : "");
print_data_cfstr(str, scan->tlv_value, tlv_length);
}
switch (TLVTypeGetType(tlv_type)) {
case kPACTLVAttributeTypePAC_Key:
if (tlv_length > 0
&& tlvlist_p != NULL && tlvlist_p->key == NULL) {
tlvlist_p->key = scan;
}
break;
case kPACTLVAttributeTypePAC_Opaque:
if (tlv_length > 0
&& tlvlist_p != NULL && tlvlist_p->opaque == NULL) {
tlvlist_p->opaque = scan;
}
break;
case kPACTLVAttributeTypePAC_Info:
if (tlvlist_p == NULL || tlvlist_p->info == NULL) {
if (PACTLVAttributeListParse(tlvlist_p,
scan->tlv_value, tlv_length,
str)
&& tlv_length > 0 && tlvlist_p != NULL) {
tlvlist_p->info = scan;
}
}
break;
case kPACTLVAttributeTypePAC_Type:
if (tlv_length < PAC_TYPE_TLV_LENGTH) {
goto done;
}
if (tlvlist_p != NULL && tlvlist_p->type == NULL) {
tlvlist_p->type = (PACTypeTLVRef)scan;
}
break;
case kPACTLVAttributeTypeA_ID:
if (tlv_length > 0
&& tlvlist_p != NULL && tlvlist_p->a_id == NULL) {
tlvlist_p->a_id = scan;
}
break;
case kPACTLVAttributeTypeI_ID:
if (tlv_length > 0
&& tlvlist_p != NULL && tlvlist_p->i_id == NULL) {
tlvlist_p->i_id = scan;
}
break;
case kPACTLVAttributeTypeA_ID_Info:
if (tlv_length > 0
&& tlvlist_p != NULL && tlvlist_p->a_id_info == NULL) {
tlvlist_p->a_id_info = scan;
}
break;
case kPACTLVAttributeTypeCRED_LIFETIME:
break;
case kPACTLVAttributeTypeReserved:
break;
case kPACTLVAttributeTypePAC_Acknowledgement:
break;
default:
break;
}
left -= (TLV_HEADER_LENGTH + tlv_length);
scan = (TLVRef)(((void *)scan) + TLV_HEADER_LENGTH + tlv_length);
}
ret = TRUE;
done:
return (ret);
}
STATIC bool
TLVListParse(TLVListRef tlvlist_p,
const void * buf, int buf_size,
CFMutableStringRef str)
{
int left;
PACTLVAttributeListRef pac_tlvs = NULL;
TLVStatus result_status;
bool ret = FALSE;
TLVRef scan;
if (tlvlist_p != NULL) {
bzero(tlvlist_p, sizeof(*tlvlist_p));
}
scan = (const TLVRef)buf;
left = buf_size;
while (1) {
TLVType tlv_type;
TLVLength tlv_length;
if (left == 0) {
break;
}
if (left < TLV_HEADER_LENGTH) {
STRLOG(str, LOG_NOTICE,
"EAP-FAST: TLV is too short (%d < %d)",
left, (int)TLV_HEADER_LENGTH);
goto done;
}
tlv_type = TLVGetType(scan);
tlv_length = TLVGetLength(scan);
if (left < (TLV_HEADER_LENGTH + tlv_length)) {
STRLOG(str, LOG_NOTICE,
"EAP-FAST: TLV is too short (%d < %d)",
left, (int)TLV_HEADER_LENGTH);
goto done;
}
if (str != NULL) {
TLVType t = TLVTypeGetType(tlv_type);
STRING_APPEND(str, "%s TLV (type=%d) Length=%d%s\n",
TLVTypeName(t), t, tlv_length,
TLVTypeIsMandatory(tlv_type) ? " [mandatory]" : "");
print_data_cfstr(str, scan->tlv_value, tlv_length);
}
switch (TLVTypeGetType(tlv_type)) {
case kTLVTypeResult:
if (tlv_length < RESULT_TLV_LENGTH) {
STRLOG(str, LOG_NOTICE,
"EAP-FAST: Result TLV is too short (%d < %d)",
tlv_length, RESULT_TLV_LENGTH);
goto done;
}
result_status = ResultTLVGetStatus((const ResultTLVRef)scan);
switch (result_status) {
case kTLVStatusSuccess:
if (str != NULL) {
STRING_APPEND(str, "Success\n");
}
break;
case kTLVStatusFailure:
if (str != NULL) {
STRING_APPEND(str, "Failure\n");
}
break;
default:
STRLOG(str, LOG_NOTICE,
"EAP-FAST: Result TLV unrecognized status = %d",
result_status);
goto done;
}
if (tlvlist_p != NULL) {
if (tlvlist_p->result != NULL) {
STRLOG(str, LOG_NOTICE,
"EAP-FAST: multiple Result TLV's defined");
goto done;
}
tlvlist_p->result = (ResultTLVRef)scan;
tlvlist_p->result_status = result_status;
}
break;
case kTLVTypeNAK:
if (tlv_length < NAK_TLV_MIN_LENGTH) {
STRLOG(str, LOG_NOTICE,
"EAP-FAST: NAK TLV is too short (%d < %d)",
tlv_length, NAK_TLV_MIN_LENGTH);
goto done;
}
if (tlvlist_p != NULL && tlvlist_p->nak == NULL) {
tlvlist_p->nak = (NAKTLVRef)scan;
}
break;
case kTLVTypeError:
if (tlv_length < ERROR_TLV_LENGTH) {
STRLOG(str, LOG_NOTICE,
"EAP-FAST: Error TLV is too short (%d < %d)",
tlv_length, ERROR_TLV_LENGTH);
goto done;
}
if (str) {
STRING_APPEND(str, "ErrorCode = %d\n",
ErrorTLVGetErrorCode((ErrorTLVRef)scan));
}
if (tlvlist_p != NULL && tlvlist_p->error == NULL) {
tlvlist_p->error = (ErrorTLVRef)scan;
tlvlist_p->error_code
= ErrorTLVGetErrorCode((ErrorTLVRef)scan);
}
break;
case kTLVTypeEAPPayload:
if (tlvlist_p != NULL) {
if (tlvlist_p->eap != NULL) {
STRLOG(str, LOG_NOTICE,
"EAP-FAST: EAP Payload TLV appears multiple times");
goto done;
}
tlvlist_p->eap = (EAPPayloadTLVRef)scan;
}
if (EAPPacketIsValid((EAPPacketRef)scan->tlv_value, tlv_length,
NULL) == FALSE) {
STRLOG(str, LOG_NOTICE,
"EAP-FAST: EAP Payload TLV invalid");
if (str != NULL) {
(void)EAPPacketIsValid((EAPPacketRef)scan->tlv_value,
tlv_length, str);
}
goto done;
}
break;
case kTLVTypeIntermediateResult:
if (tlv_length < INTERMEDIATE_RESULT_TLV_MIN_LENGTH) {
STRLOG(str, LOG_NOTICE,
"EAP-FAST: Intermediate Result TLV too short (%d < %d)",
tlv_length, INTERMEDIATE_RESULT_TLV_MIN_LENGTH);
goto done;
}
if (tlvlist_p != NULL) {
if (tlvlist_p->intermediate != NULL) {
STRLOG(str, LOG_NOTICE,
"EAP-FAST: multiple Intermediate Result TLV's");
goto done;
}
tlvlist_p->intermediate = (IntermediateResultTLVRef)scan;
}
break;
case kTLVTypeCryptoBinding:
if (tlv_length < CRYPTO_BINDING_TLV_LENGTH) {
STRLOG(str, LOG_NOTICE,
"EAP-FAST: Crypto Binding TLV too short (%d < %ld)",
tlv_length, CRYPTO_BINDING_TLV_LENGTH);
goto done;
}
if (tlvlist_p != NULL) {
if (tlvlist_p->crypto != NULL) {
STRLOG(str, LOG_NOTICE,
"EAP-FAST: multiple Crypto Binding TLV's defined");
goto done;
}
tlvlist_p->crypto = (CryptoBindingTLVRef)scan;
}
break;
case kTLVTypePAC:
if (tlvlist_p != NULL && tlvlist_p->pac == NULL) {
pac_tlvs = &tlvlist_p->pac_tlvs;
}
if (PACTLVAttributeListParse(pac_tlvs,
scan->tlv_value, tlv_length,
str) == FALSE) {
STRLOG(str, LOG_NOTICE, "EAP-FAST: PAC TLV parse failed");
goto done;
}
if (pac_tlvs != NULL) {
tlvlist_p->pac = (PACTLVRef)scan;
}
break;
case kTLVTypeVendorSpecific:
if (tlv_length < VENDOR_SPECIFIC_TLV_MIN_LENGTH) {
STRLOG(str, LOG_NOTICE,
"EAP-FAST: Vendor Specific TLV too short (%d < %d)",
tlv_length, VENDOR_SPECIFIC_TLV_MIN_LENGTH);
goto done;
}
default:
if (tlvlist_p != NULL) {
if (TLVTypeIsMandatory(tlv_type)
&& tlvlist_p->mandatory == NULL) {
tlvlist_p->mandatory = scan;
}
}
break;
}
left -= (TLV_HEADER_LENGTH + tlv_length);
scan = (TLVRef)(((void *)scan) + TLV_HEADER_LENGTH + tlv_length);
}
ret = TRUE;
done:
return (ret);
}
STATIC bool
make_result(BufferRef buf, TLVStatus result_status)
{
ResultTLVRef res;
res = (ResultTLVRef)BufferGetWritePtr(buf);
if (BufferAdvanceWritePtr(buf, TLV_HEADER_LENGTH + RESULT_TLV_LENGTH)
== FALSE) {
EAPLOG(LOG_NOTICE, "EAP-FAST: make_result(): buffer too small");
return (FALSE);
}
TLVSetLength((TLVRef)res, RESULT_TLV_LENGTH);
TLVSetType((TLVRef)res, kTLVTypeMandatoryBit | kTLVTypeResult);
ResultTLVSetStatus(res, result_status);
return (TRUE);
}
STATIC bool
make_intermediate_result(BufferRef buf, TLVStatus result_status)
{
IntermediateResultTLVRef ires;
ires = (IntermediateResultTLVRef)BufferGetWritePtr(buf);
if (BufferAdvanceWritePtr(buf,
(TLV_HEADER_LENGTH
+ INTERMEDIATE_RESULT_TLV_MIN_LENGTH))
== FALSE) {
EAPLOG(LOG_NOTICE,
"EAP-FAST: make_intermediate_result(): buffer too small");
return (FALSE);
}
TLVSetLength((TLVRef)ires, INTERMEDIATE_RESULT_TLV_MIN_LENGTH);
TLVSetType((TLVRef)ires, kTLVTypeMandatoryBit | kTLVTypeIntermediateResult);
IntermediateResultTLVSetStatus(ires, result_status);
return (TRUE);
}
STATIC bool
make_error(BufferRef buf, ErrorTLVErrorCode code)
{
ErrorTLVRef err;
err = (ErrorTLVRef)BufferGetWritePtr(buf);
if (BufferAdvanceWritePtr(buf, TLV_HEADER_LENGTH + ERROR_TLV_LENGTH)
== FALSE) {
EAPLOG(LOG_NOTICE, "EAP-FAST: make_error(): buffer too small");
return (FALSE);
}
TLVSetLength((TLVRef)err, ERROR_TLV_LENGTH);
TLVSetType((TLVRef)err, kTLVTypeMandatoryBit | kTLVTypeError);
ErrorTLVSetErrorCode(err, code);
return (TRUE);
}
STATIC bool
make_nak(BufferRef buf, const TLVRef tlv)
{
NAKTLVRef nak;
nak = (NAKTLVRef)BufferGetWritePtr(buf);
if (BufferAdvanceWritePtr(buf, TLV_HEADER_LENGTH + NAK_TLV_MIN_LENGTH)
== FALSE) {
EAPLOG(LOG_NOTICE, "EAP-FAST: make_nak(): buffer too small");
return (FALSE);
}
TLVSetLength((TLVRef)nak, NAK_TLV_MIN_LENGTH);
TLVSetType((TLVRef)nak, kTLVTypeMandatoryBit | kTLVTypeNAK);
NAKTLVSetNAKType(nak, TLVTypeGetType(TLVGetType(tlv)));
NAKTLVSetVendorId(nak, 0);
return (TRUE);
}
STATIC bool
make_eap(BufferRef buf, const EAPPacketRef out_pkt_p, int out_pkt_size)
{
EAPPayloadTLVRef eap;
eap = (EAPPayloadTLVRef)BufferGetWritePtr(buf);
if (BufferAdvanceWritePtr(buf, out_pkt_size + TLV_HEADER_LENGTH) == FALSE) {
EAPLOG(LOG_NOTICE, "EAP-FAST: make_eap(): buffer too small");
return (FALSE);
}
TLVSetLength((TLVRef)eap, out_pkt_size);
TLVSetType((TLVRef)eap, kTLVTypeMandatoryBit | kTLVTypeEAPPayload);
memcpy(eap->ep_eap_packet, out_pkt_p, out_pkt_size);
return (TRUE);
}
STATIC bool
make_pac_request(BufferRef buf)
{
PACTLVRef pac;
PACTypeTLVRef pac_type;
pac = (PACTLVRef)BufferGetWritePtr(buf);
if (BufferAdvanceWritePtr(buf, TLV_HEADER_LENGTH * 2 + PAC_TYPE_TLV_LENGTH)
== FALSE) {
EAPLOG(LOG_NOTICE, "EAP-FAST: make_pac_request(): buffer too small");
return (FALSE);
}
TLVSetLength((TLVRef)pac, TLV_HEADER_LENGTH + PAC_TYPE_TLV_LENGTH);
TLVSetType((TLVRef)pac, kTLVTypePAC);
pac_type = (PACTypeTLVRef)pac->pa_attributes;
TLVSetLength((TLVRef)pac_type, PAC_TYPE_TLV_LENGTH);
TLVSetType((TLVRef)pac_type, kPACTLVAttributeTypePAC_Type);
PACTypeTLVSetPACType(pac_type, kPACTypeTunnel);
return (TRUE);
}
STATIC bool
make_pac_ack(BufferRef buf, TLVStatus result_status)
{
PACTLVRef pac;
ResultTLVRef pac_ack;
pac = (PACTLVRef)BufferGetWritePtr(buf);
if (BufferAdvanceWritePtr(buf, TLV_HEADER_LENGTH * 2 + RESULT_TLV_LENGTH)
== FALSE) {
EAPLOG(LOG_NOTICE, "EAP-FAST: make_pac_ack(): buffer too small");
return (FALSE);
}
TLVSetLength((TLVRef)pac, TLV_HEADER_LENGTH + PAC_TYPE_TLV_LENGTH);
TLVSetType((TLVRef)pac, kTLVTypePAC);
pac_ack = (ResultTLVRef)pac->pa_attributes;
TLVSetLength((TLVRef)pac_ack, RESULT_TLV_LENGTH);
TLVSetType((TLVRef)pac_ack, kPACTLVAttributeTypePAC_Acknowledgement);
ResultTLVSetStatus(pac_ack, result_status);
return (TRUE);
}
STATIC bool
process_crypto_binding(EAPFASTPluginDataRef context,
const CryptoBindingTLVRef crypto, BufferRef buf)
{
CryptoBindingTLVRef cb_p;
uint8_t compound_mac[20];
void * inner_auth_key;
int inner_auth_key_length;
uint8_t imck[IMCK_LENGTH];
uint8_t msk[MSK_LENGTH];
bool ret = FALSE;
if (BufferGetSpace(buf) < (CRYPTO_BINDING_TLV_LENGTH + TLV_HEADER_LENGTH)) {
EAPLOG(LOG_NOTICE,
"EAP-FAST: process_crypto_binding: buffer too small %d < %ld",
BufferGetSpace(buf),
CRYPTO_BINDING_TLV_LENGTH + TLV_HEADER_LENGTH);
goto done;
}
if (crypto->cb_version != kEAPFASTVersion1) {
EAPLOG(LOG_NOTICE,
"EAP-FAST: process_crypto_binding version is %d != %d",
crypto->cb_version, kEAPFASTVersion1);
goto done;
}
if (crypto->cb_received_version != context->eapfast_version) {
EAPLOG(LOG_NOTICE,
"EAP-FAST: process_crypto_binding received_version is %d != %d",
crypto->cb_received_version, context->eapfast_version);
goto done;
}
if (crypto->cb_sub_type != kCryptoBindingSubTypeBindingRequest) {
EAPLOG(LOG_NOTICE,
"EAP-FAST: process_crypto_binding sub_type %d != %d",
crypto->cb_sub_type, kCryptoBindingSubTypeBindingRequest);
goto done;
}
inner_auth_key = eap_client_session_key(context, &inner_auth_key_length);
if (inner_auth_key != NULL) {
if (inner_auth_key_length > sizeof(msk)) {
inner_auth_key_length = sizeof(msk);
}
memcpy(msk, inner_auth_key, inner_auth_key_length);
if (inner_auth_key_length < sizeof(msk)) {
memset(msk + inner_auth_key_length, 0,
sizeof(msk) - inner_auth_key_length);
}
}
else {
if (context->must_use_mschapv2) {
EAPLOG(LOG_NOTICE, "EAP-FAST: anonymous PAC provisioning "
"requires MSCHAPv2 - possible malicious server");
goto done;
}
memset(msk, 0, sizeof(msk));
}
T_PRF(context->last_s_imck, S_IMCK_LENGTH,
kIMCKLabel, kIMCKLabelLength,
msk, MSK_LENGTH, imck, IMCK_LENGTH);
cb_p = (CryptoBindingTLVRef)BufferGetWritePtr(buf);
*cb_p = *crypto;
memset(cb_p->cb_compound_mac, 0, sizeof(cb_p->cb_compound_mac));
CCHmac(kCCHmacAlgSHA1,
imck + S_IMCK_LENGTH, CMK_LENGTH,
(unsigned char *)cb_p, sizeof(*cb_p), compound_mac);
if (cc_cmp_safe(sizeof(compound_mac), crypto->cb_compound_mac,
compound_mac) != 0) {
EAPLOG(LOG_NOTICE,
"EAP-FAST: process_crypto_binding Compound MAC is incorrect");
goto done;
}
TLVSetLength((TLVRef)cb_p, CRYPTO_BINDING_TLV_LENGTH);
TLVSetType((TLVRef)cb_p, kTLVTypeMandatoryBit | kTLVTypeCryptoBinding);
cb_p->cb_received_version = context->eapfast_received_version;
cb_p->cb_nonce[sizeof(cb_p->cb_nonce) - 1] |= 0x01;
cb_p->cb_sub_type = kCryptoBindingSubTypeBindingResponse;
CCHmac(kCCHmacAlgSHA1,
imck + S_IMCK_LENGTH, CMK_LENGTH,
(unsigned char *)cb_p, sizeof(*cb_p), compound_mac);
memcpy(cb_p->cb_compound_mac, compound_mac, sizeof(compound_mac));
BufferAdvanceWritePtr(buf, CRYPTO_BINDING_TLV_LENGTH + TLV_HEADER_LENGTH);
memcpy(context->last_s_imck, imck, sizeof(context->last_s_imck));
ret = TRUE;
done:
return (ret);
}
STATIC bool
eapfast_eap(EAPClientPluginDataRef plugin, EAPTLSPacketRef eaptls_in,
EAPClientStatus * client_status)
{
EAPFASTPluginDataRef context = (EAPFASTPluginDataRef)plugin->private;
bool do_log = FALSE;
uint8_t out_tlvs[16 * 1024];
Buffer out_tlvs_buf;
Buffer out_tlvs_buf_saved = { 0, 0, 0 };
size_t out_tlvs_size;
bool result_success = FALSE;
bool ret = FALSE;
OSStatus status;
TLVListRef tlvlist_p = &context->in_message_tlvs;
BufferInit(&out_tlvs_buf, out_tlvs, sizeof(out_tlvs));
if (context->in_message_size == 0) {
bool parse_ok;
do_log = TRUE;
status = SSLRead(context->ssl_context, context->in_message_buf,
sizeof(context->in_message_buf),
&context->in_message_size);
if (status == errSSLWouldBlock) {
ret = TRUE;
goto done;
}
if (status != noErr) {
EAPLOG_FL(LOG_NOTICE, "SSLRead failed, %s (%d)",
EAPSSLErrorString(status), (int)status);
context->plugin_state = kEAPClientStateFailure;
context->last_ssl_error = status;
goto done;
}
if (context->in_message_size == 0) {
EAPLOG_FL(LOG_NOTICE, "zero-length TLV");
context->plugin_state = kEAPClientStateFailure;
make_result(&out_tlvs_buf, kTLVStatusFailure);
make_error(&out_tlvs_buf,
kErrorTLVErrorCodeUnexpectedTLVsExchanged);
goto send_message;
}
{
CFMutableStringRef str = NULL;
if (plugin->log_enabled) {
str = CFStringCreateMutable(NULL, 0);
}
parse_ok = TLVListParse(tlvlist_p,
context->in_message_buf,
(int)context->in_message_size,
str);
if (str != NULL) {
EAPLOG(-LOG_DEBUG, "-------- Receive TLVs: ----------\n%@",
str);
CFRelease(str);
}
}
if (parse_ok == FALSE) {
context->plugin_state = kEAPClientStateFailure;
make_result(&out_tlvs_buf, kTLVStatusFailure);
make_error(&out_tlvs_buf,
kErrorTLVErrorCodeUnexpectedTLVsExchanged);
goto send_message;
}
if (tlvlist_p->mandatory != NULL) {
if (tlvlist_p->result != NULL) {
make_result(&out_tlvs_buf, kTLVStatusFailure);
make_error(&out_tlvs_buf,
kErrorTLVErrorCodeUnexpectedTLVsExchanged);
}
else {
make_nak(&out_tlvs_buf, tlvlist_p->mandatory);
}
goto send_message;
}
if (tlvlist_p->intermediate != NULL) {
TLVStatus result_status;
if (eap_client_type(context) == kEAPTypeInvalid) {
EAPLOG_FL(LOG_NOTICE,
"Intermediate Result TLV supplied"
" but no inner EAP method negotiated");
context->plugin_state = kEAPClientStateFailure;
make_result(&out_tlvs_buf, kTLVStatusFailure);
make_error(&out_tlvs_buf,
kErrorTLVErrorCodeUnexpectedTLVsExchanged);
goto send_message;
}
result_status =
IntermediateResultTLVGetStatus(tlvlist_p->intermediate);
switch (result_status) {
case kTLVStatusSuccess:
if (tlvlist_p->crypto != NULL) {
make_intermediate_result(&out_tlvs_buf,
kTLVStatusSuccess);
break;
}
EAPLOG_FL(LOG_NOTICE, "Crypto Binding TLV is missing");
context->plugin_state = kEAPClientStateFailure;
make_result(&out_tlvs_buf, kTLVStatusFailure);
make_error(&out_tlvs_buf,
kErrorTLVErrorCodeUnexpectedTLVsExchanged);
goto send_message;
break;
default:
case kTLVStatusFailure:
if (result_status == kTLVStatusFailure) {
EAPLOG_FL(LOG_NOTICE, "Intermediate Result TLV Failure");
}
else {
EAPLOG_FL(LOG_NOTICE, "Intermediate Result TLV:"
" unrecognized status = %d",
result_status);
}
context->plugin_state = kEAPClientStateFailure;
make_result(&out_tlvs_buf, kTLVStatusFailure);
make_error(&out_tlvs_buf,
kErrorTLVErrorCodeUnexpectedTLVsExchanged);
goto send_message;
break;
}
}
if (tlvlist_p->result != NULL) {
switch (tlvlist_p->result_status) {
default:
case kTLVStatusFailure:
context->inner_auth_state = kEAPFASTInnerAuthStateFailure;
if (tlvlist_p->error != NULL) {
EAPLOG_FL(LOG_NOTICE,
"Result TLV Failure, Error %d",
tlvlist_p->error_code);
}
else {
EAPLOG_FL(LOG_NOTICE,
"Result TLV Failure");
}
break;
case kTLVStatusSuccess:
result_success = TRUE;
break;
}
out_tlvs_buf_saved = out_tlvs_buf;
make_result(&out_tlvs_buf, tlvlist_p->result_status);
}
if (tlvlist_p->crypto != NULL) {
if (process_crypto_binding(context, tlvlist_p->crypto,
&out_tlvs_buf) == FALSE) {
EAPLOG_FL(LOG_NOTICE, "Crypto Binding TLV validation failed");
context->plugin_state = kEAPClientStateFailure;
if (tlvlist_p->result != NULL) {
out_tlvs_buf = out_tlvs_buf_saved;
}
make_result(&out_tlvs_buf, kTLVStatusFailure);
make_error(&out_tlvs_buf,
kErrorTLVErrorCodeTunnelCompromise);
goto send_message;
}
context->crypto_binding_done = TRUE;
}
if (tlvlist_p->pac != NULL) {
if (context->crypto_binding_done
&& context->use_pac
&& tlvlist_p->pac_tlvs.key != NULL
&& tlvlist_p->pac_tlvs.opaque != NULL
&& tlvlist_p->pac_tlvs.a_id != NULL
&& save_pac(plugin->system_mode, &tlvlist_p->pac_tlvs)) {
make_pac_ack(&out_tlvs_buf, kTLVStatusSuccess);
context->pac_was_provisioned = TRUE;
EAPLOG(LOG_NOTICE, "EAP-FAST: PAC was provisioned");
}
else {
make_pac_ack(&out_tlvs_buf, kTLVStatusFailure);
}
}
if (result_success) {
context->inner_auth_state = kEAPFASTInnerAuthStateSuccess;
eapfast_compute_session_key(context);
if (context->provision_pac
&& tlvlist_p->pac == NULL
&& context->pac_dict == NULL
&& context->pac_was_requested == FALSE) {
context->pac_was_requested = TRUE;
make_pac_request(&out_tlvs_buf);
}
}
if (tlvlist_p->result != NULL) {
goto send_message;
}
}
if (tlvlist_p->eap != NULL) {
bool call_module_free_packet = FALSE;
EAPRequestPacketRef in_pkt_p = NULL;
char out_pkt_buf[2048];
EAPResponsePacketRef out_pkt_p = NULL;
int out_pkt_size;
in_pkt_p = (EAPRequestPacketRef)tlvlist_p->eap->ep_eap_packet;
if (plugin->log_enabled && do_log) {
CFMutableStringRef log_msg;
log_msg = CFStringCreateMutable(NULL, 0);
EAPPacketIsValid((const EAPPacketRef)in_pkt_p,
EAPPacketGetLength((const EAPPacketRef)in_pkt_p),
log_msg);
EAPLOG(-LOG_DEBUG, "EAP-FAST Receive EAP Payload:\n%@", log_msg);
CFRelease(log_msg);
}
switch (in_pkt_p->code) {
case kEAPCodeRequest:
switch (in_pkt_p->type) {
case kEAPTypeIdentity:
out_pkt_p = (EAPResponsePacketRef)
EAPPacketCreate(out_pkt_buf, sizeof(out_pkt_buf),
kEAPCodeResponse, in_pkt_p->identifier,
kEAPTypeIdentity, plugin->username,
plugin->username_length,
&out_pkt_size);
break;
case kEAPTypeNotification:
out_pkt_p = (EAPResponsePacketRef)
EAPPacketCreate(out_pkt_buf, sizeof(out_pkt_buf),
kEAPCodeResponse, in_pkt_p->identifier,
kEAPTypeNotification, NULL, 0,
&out_pkt_size);
break;
default:
out_pkt_size = sizeof(out_pkt_buf);
out_pkt_p = eapfast_eap_process(plugin, in_pkt_p,
out_pkt_buf, &out_pkt_size,
client_status,
&call_module_free_packet);
break;
}
break;
case kEAPCodeResponse:
case kEAPCodeSuccess:
case kEAPCodeFailure:
out_pkt_p = eapfast_eap_process(plugin, in_pkt_p,
out_pkt_buf, &out_pkt_size,
client_status,
&call_module_free_packet);
break;
}
if (out_pkt_p == NULL) {
goto done;
}
if (plugin->log_enabled) {
CFMutableStringRef log_msg;
log_msg = CFStringCreateMutable(NULL, 0);
EAPPacketIsValid((const EAPPacketRef)out_pkt_p,
EAPPacketGetLength((const EAPPacketRef)out_pkt_p),
log_msg);
EAPLOG(-LOG_DEBUG, "EAP-FAST Send EAP Payload:\n%@", log_msg);
CFRelease(log_msg);
}
if (make_eap(&out_tlvs_buf, (void *)out_pkt_p, out_pkt_size)
== FALSE) {
EAPLOG_FL(LOG_NOTICE, "failed to insert EAP Payload TLV");
context->plugin_state = kEAPClientStateFailure;
goto done;
}
if ((char *)out_pkt_p != out_pkt_buf) {
if (call_module_free_packet) {
eap_client_free_packet(context, (EAPPacketRef)out_pkt_p);
}
else {
free(out_pkt_p);
}
}
}
send_message:
out_tlvs_size = BufferGetUsed(&out_tlvs_buf);
if (out_tlvs_size == 0) {
EAPLOG_FL(LOG_NOTICE, "nothing to send?");
goto done;
}
context->in_message_size = 0;
if (plugin->log_enabled) {
CFMutableStringRef str;
str = CFStringCreateMutable(NULL, 0);
STRING_APPEND(str, "======== Send TLVs: ========\n");
(void)TLVListParse(NULL, BufferGetPtr(&out_tlvs_buf),
(int)out_tlvs_size, str);
EAPLOG(-LOG_DEBUG, "%@", str);
my_CFRelease(&str);
}
status = SSLWrite(context->ssl_context, BufferGetPtr(&out_tlvs_buf),
out_tlvs_size, &out_tlvs_size);
if (status != noErr) {
EAPLOG_FL(LOG_NOTICE,
"SSLWrite failed, %s (%d)",
EAPSSLErrorString(status), (int)status);
}
else {
ret = TRUE;
}
if (context->plugin_state == kEAPClientStateFailure) {
SSLClose(context->ssl_context);
}
done:
return (ret);
}
STATIC EAPPacketRef
eapfast_verify_server(EAPClientPluginDataRef plugin,
int identifier, EAPClientStatus * client_status)
{
EAPFASTPluginDataRef context = (EAPFASTPluginDataRef)plugin->private;
EAPPacketRef pkt = NULL;
memoryBufferRef write_buf = &context->write_buffer;
if (context->pac_was_used == TRUE) {
context->trust_proceed = TRUE;
return (NULL);
}
if (context->pac_dict == NULL
&& context->provision_pac_anonymously) {
SSLCipherSuite cipher;
if (SSLGetNegotiatedCipher(context->ssl_context, &cipher) == noErr
&& cipher == TLS_DH_anon_WITH_AES_128_CBC_SHA) {
context->trust_proceed = TRUE;
context->must_use_mschapv2 = TRUE;
return (NULL);
}
}
context->trust_status
= EAPTLSVerifyServerCertificateChain(plugin->properties,
context->server_certs,
&context->trust_ssl_error);
if (context->trust_status != kEAPClientStatusOK) {
EAPLOG_FL(LOG_NOTICE, "server certificate not trusted, status %d %d",
context->trust_status,
(int)context->trust_ssl_error);
}
switch (context->trust_status) {
case kEAPClientStatusOK:
context->trust_proceed = TRUE;
break;
case kEAPClientStatusUserInputRequired:
*client_status = context->last_client_status
= kEAPClientStatusUserInputRequired;
break;
default:
*client_status = context->last_client_status = context->trust_status;
context->last_ssl_error = context->trust_ssl_error;
context->plugin_state = kEAPClientStateFailure;
SSLClose(context->ssl_context);
pkt = EAPTLSPacketCreate(kEAPCodeResponse,
kEAPTypeEAPFAST,
identifier,
context->mtu,
write_buf,
&context->last_write_size);
break;
}
return (pkt);
}
STATIC EAPPacketRef
eapfast_tunnel(EAPClientPluginDataRef plugin, EAPTLSPacketRef eaptls_in,
EAPClientStatus * client_status)
{
EAPFASTPluginDataRef context = (EAPFASTPluginDataRef)plugin->private;
EAPPacketRef pkt = NULL;
memoryBufferRef write_buf = &context->write_buffer;
if (eapfast_eap(plugin, eaptls_in, client_status)) {
pkt = EAPTLSPacketCreate2(kEAPCodeResponse,
kEAPTypeEAPFAST,
eaptls_in->identifier,
context->mtu,
write_buf,
&context->last_write_size,
FALSE);
}
return (pkt);
}
STATIC void
eapfast_set_session_was_resumed(EAPFASTPluginDataRef context)
{
char buf[MAX_SESSION_ID_LENGTH];
size_t buf_len = sizeof(buf);
Boolean resumed = FALSE;
OSStatus status;
status = SSLGetResumableSessionInfo(context->ssl_context,
&resumed, buf, &buf_len);
if (status == noErr) {
context->session_was_resumed = resumed;
}
return;
}
STATIC EAPPacketRef
eapfast_handshake(EAPClientPluginDataRef plugin, EAPTLSPacketRef eaptls_in,
EAPClientStatus * client_status)
{
EAPFASTPluginDataRef context = (EAPFASTPluginDataRef)plugin->private;
EAPPacketRef eaptls_out = NULL;
OSStatus status = noErr;
memoryBufferRef write_buf = &context->write_buffer;
if (context->server_auth_completed && context->trust_proceed == FALSE) {
eaptls_out = eapfast_verify_server(plugin, eaptls_in->identifier,
client_status);
if (context->trust_proceed == FALSE) {
goto done;
}
}
status = SSLHandshake(context->ssl_context);
if (status == errSSLServerAuthCompleted) {
if (context->server_auth_completed) {
EAPLOG_FL(LOG_NOTICE, "AuthCompleted again?");
goto done;
}
context->server_auth_completed = TRUE;
my_CFRelease(&context->server_certs);
(void)EAPSSLCopyPeerCertificates(context->ssl_context,
&context->server_certs);
eaptls_out = eapfast_verify_server(plugin, eaptls_in->identifier,
client_status);
if (context->trust_proceed == FALSE) {
goto done;
}
status = SSLHandshake(context->ssl_context);
}
switch (status) {
case noErr:
if (context->trust_proceed == FALSE) {
my_CFRelease(&context->server_certs);
(void)EAPSSLCopyPeerCertificates(context->ssl_context,
&context->server_certs);
eaptls_out = eapfast_verify_server(plugin, eaptls_in->identifier,
client_status);
if (context->trust_proceed == FALSE) {
EAPLOG_FL(LOG_NOTICE, "trust_proceed is FALSE?");
break;
}
}
status
= eapfast_generate_key_material(context,
context->last_s_imck,
context->mschap_server_challenge,
context->mschap_client_challenge);
if (status != noErr) {
goto close_up_shop;
}
context->handshake_complete = TRUE;
eapfast_set_session_was_resumed(context);
if (eapfast_eap(plugin, eaptls_in, client_status)) {
eaptls_out = EAPTLSPacketCreate2(kEAPCodeResponse,
kEAPTypeEAPFAST,
eaptls_in->identifier,
context->mtu,
write_buf,
&context->last_write_size,
FALSE);
}
break;
default:
EAPLOG_FL(LOG_NOTICE, "SSLHandshake failed, %s (%d)",
EAPSSLErrorString(status), (int)status);
close_up_shop:
context->last_ssl_error = status;
my_CFRelease(&context->server_certs);
(void) EAPSSLCopyPeerCertificates(context->ssl_context,
&context->server_certs);
context->plugin_state = kEAPClientStateFailure;
SSLClose(context->ssl_context);
if (status == errSSLPeerBadCert && context->pac_dict) {
remove_pac(context->pac_dict,
plugin->username, plugin->username_length);
}
case errSSLWouldBlock:
if (write_buf->data == NULL) {
eaptls_out = EAPFASTPacketCreateAck(eaptls_in->identifier);
}
else {
eaptls_out = EAPTLSPacketCreate(kEAPCodeResponse,
kEAPTypeEAPFAST,
eaptls_in->identifier,
context->mtu,
write_buf,
&context->last_write_size);
}
break;
}
done:
return (eaptls_out);
}
STATIC EAPPacketRef
eapfast_request(EAPClientPluginDataRef plugin,
const EAPPacketRef in_pkt, EAPClientStatus * client_status)
{
EAPFASTPluginDataRef context = (EAPFASTPluginDataRef)plugin->private;
EAPTLSPacketRef eaptls_in = (EAPTLSPacketRef)in_pkt;
EAPTLSLengthIncludedPacketRef eaptls_in_l;
EAPPacketRef eaptls_out = NULL;
int in_data_length;
void * in_data_ptr = NULL;
u_int16_t in_length = EAPPacketGetLength(in_pkt);
memoryBufferRef read_buf = &context->read_buffer;
SSLSessionState ssl_state = kSSLIdle;
OSStatus status = noErr;
u_int32_t tls_message_length = 0;
RequestType type;
memoryBufferRef write_buf = &context->write_buffer;
eaptls_in_l = (EAPTLSLengthIncludedPacketRef)(void *)in_pkt;
if (in_length < sizeof(*eaptls_in)) {
EAPLOG_FL(LOG_NOTICE, "length %d < %ld", in_length, sizeof(*eaptls_in));
goto done;
}
if (context->ssl_context != NULL) {
status = SSLGetSessionState(context->ssl_context, &ssl_state);
if (status != noErr) {
EAPLOG_FL(LOG_NOTICE, "SSLGetSessionState failed, %s (%ld)",
EAPSSLErrorString(status), (long)status);
context->plugin_state = kEAPClientStateFailure;
context->last_ssl_error = status;
goto done;
}
}
in_data_ptr = eaptls_in->tls_data;
tls_message_length = in_data_length = in_length - sizeof(EAPTLSPacket);
type = kRequestTypeData;
if ((eaptls_in->flags & kEAPTLSPacketFlagsStart) != 0) {
type = kRequestTypeStart;
if (ssl_state != kSSLHandshake
|| write_buf->data == NULL
|| in_pkt->identifier != context->previous_identifier) {
ssl_state = kSSLIdle;
}
}
else if (in_length == sizeof(*eaptls_in)) {
type = kRequestTypeAck;
}
else if ((eaptls_in->flags & kEAPTLSPacketFlagsLengthIncluded) != 0) {
if (in_length < sizeof(EAPTLSLengthIncludedPacket)) {
EAPLOG_FL(LOG_NOTICE,
"packet too short %d < %ld",
in_length, sizeof(EAPTLSLengthIncludedPacket));
goto done;
}
tls_message_length
= EAPTLSLengthIncludedPacketGetMessageLength(eaptls_in_l);
if (tls_message_length > kEAPTLSAvoidDenialOfServiceSize) {
EAPLOG_FL(LOG_NOTICE,
"received message too large, %d > %d",
tls_message_length, (int)kEAPTLSAvoidDenialOfServiceSize);
goto done;
}
in_data_ptr = eaptls_in_l->tls_data;
in_data_length = in_length - sizeof(EAPTLSLengthIncludedPacket);
if (tls_message_length == 0) {
type = kRequestTypeAck;
}
}
switch (ssl_state) {
case kSSLClosed:
case kSSLAborted:
break;
case kSSLIdle:
if (type != kRequestTypeStart) {
EAPLOG_FL(LOG_NOTICE, "ignoring non EAP-FAST start frame");
goto done;
}
status = eapfast_start(plugin, in_data_ptr, in_data_length);
if (status != noErr) {
context->last_ssl_error = status;
context->plugin_state = kEAPClientStateFailure;
goto done;
}
status = SSLHandshake(context->ssl_context);
if (status != errSSLWouldBlock) {
EAPLOG_FL(LOG_NOTICE, "SSLHandshake failed, %s (%d)",
EAPSSLErrorString(status), (int)status);
context->last_ssl_error = status;
context->plugin_state = kEAPClientStateFailure;
goto done;
}
eaptls_out = EAPTLSPacketCreate(kEAPCodeResponse,
kEAPTypeEAPFAST,
eaptls_in->identifier,
context->mtu,
write_buf,
&context->last_write_size);
break;
case kSSLHandshake:
case kSSLConnected:
if (write_buf->data != NULL) {
if (in_pkt->identifier == context->previous_identifier) {
eaptls_out = EAPTLSPacketCreate(kEAPCodeResponse,
kEAPTypeEAPFAST,
in_pkt->identifier,
context->mtu,
write_buf,
&context->last_write_size);
break;
}
if ((write_buf->offset + context->last_write_size)
< write_buf->length) {
write_buf->offset += context->last_write_size;
eaptls_out = EAPTLSPacketCreate(kEAPCodeResponse,
kEAPTypeEAPFAST,
in_pkt->identifier,
context->mtu,
write_buf,
&context->last_write_size);
break;
}
memoryBufferClear(write_buf);
context->last_write_size = 0;
}
if (type != kRequestTypeData) {
EAPLOG_FL(LOG_NOTICE, "unexpected %s frame",
type == kRequestTypeAck ? "Ack" : "Start");
goto done;
}
if (in_pkt->identifier == context->previous_identifier) {
if ((eaptls_in->flags & kEAPTLSPacketFlagsMoreFragments) != 0) {
eaptls_out = EAPFASTPacketCreateAck(eaptls_in->identifier);
break;
}
}
else {
if (read_buf->data == NULL) {
memoryBufferAllocate(read_buf, tls_message_length);
}
if (memoryBufferAddData(read_buf, in_data_ptr, in_data_length)
== FALSE) {
EAPLOG_FL(LOG_NOTICE,
"fragment too large %d",
in_data_length);
goto done;
}
if (memoryBufferIsComplete(read_buf) == FALSE) {
if ((eaptls_in->flags & kEAPTLSPacketFlagsMoreFragments) == 0) {
EAPLOG_FL(LOG_NOTICE,
"expecting more data but "
"more fragments bit is not set, ignoring");
}
eaptls_out = EAPFASTPacketCreateAck(eaptls_in->identifier);
break;
}
}
if (context->handshake_complete) {
eaptls_out = eapfast_tunnel(plugin, eaptls_in,
client_status);
}
else {
eaptls_out = eapfast_handshake(plugin, eaptls_in,
client_status);
}
break;
default:
break;
}
context->previous_identifier = in_pkt->identifier;
if (context->eapfast_version_set == FALSE) {
uint8_t eapfast_version;
context->eapfast_received_version =
eapfast_version = EAPFASTPacketFlagsVersion(eaptls_in_l->flags);
if (eapfast_version != kEAPFASTVersion1) {
eapfast_version = kEAPFASTVersion1;
}
context->eapfast_version = eapfast_version;
}
if (eaptls_out != NULL) {
EAPFASTPacketFlagsSetVersion((EAPTLSPacketRef)eaptls_out,
context->eapfast_version);
}
done:
return (eaptls_out);
}
STATIC EAPClientState
eapfast_process(EAPClientPluginDataRef plugin,
const EAPPacketRef in_pkt,
EAPPacketRef * out_pkt_p,
EAPClientStatus * client_status,
EAPClientDomainSpecificError * error)
{
EAPFASTPluginDataRef context = (EAPFASTPluginDataRef)plugin->private;
*client_status = kEAPClientStatusOK;
*error = 0;
*out_pkt_p = NULL;
switch (in_pkt->code) {
case kEAPCodeRequest:
*out_pkt_p = eapfast_request(plugin, in_pkt, client_status);
break;
case kEAPCodeSuccess:
if (context->inner_auth_state == kEAPFASTInnerAuthStateSuccess) {
context->plugin_state = kEAPClientStateSuccess;
}
break;
case kEAPCodeFailure:
if (context->inner_auth_state == kEAPFASTInnerAuthStateFailure
|| context->pac_was_provisioned == FALSE) {
context->plugin_state = kEAPClientStateFailure;
}
break;
case kEAPCodeResponse:
default:
break;
}
if (context->plugin_state == kEAPClientStateFailure) {
if (context->last_ssl_error == noErr) {
switch (context->last_client_status) {
case kEAPClientStatusOK:
case kEAPClientStatusUserInputRequired:
*client_status = kEAPClientStatusFailed;
break;
default:
*client_status = context->last_client_status;
break;
}
}
else {
*error = context->last_ssl_error;
*client_status = kEAPClientStatusSecurityError;
}
}
return (context->plugin_state);
}
STATIC const char *
eapfast_failure_string(EAPClientPluginDataRef plugin)
{
return (NULL);
}
STATIC void *
eapfast_session_key(EAPClientPluginDataRef plugin, int * key_length)
{
EAPFASTPluginDataRef context = (EAPFASTPluginDataRef)plugin->private;
*key_length = 0;
if (context->master_key_valid == FALSE) {
return (NULL);
}
*key_length = MASTER_KEY_LENGTH / 2;
return (context->master_key);
}
STATIC void *
eapfast_server_key(EAPClientPluginDataRef plugin, int * key_length)
{
EAPFASTPluginDataRef context = (EAPFASTPluginDataRef)plugin->private;
*key_length = 0;
if (context->master_key_valid == FALSE) {
return (NULL);
}
*key_length = MASTER_KEY_LENGTH / 2;
return (context->master_key + (MASTER_KEY_LENGTH / 2));
}
STATIC int
eapfast_msk_copy_bytes(EAPClientPluginDataRef plugin,
void * msk, int msk_size)
{
EAPFASTPluginDataRef context = (EAPFASTPluginDataRef)plugin->private;
int ret_msk_size;
if (msk_size < MASTER_KEY_LENGTH
|| context->master_key_valid == FALSE) {
ret_msk_size = 0;
}
else {
ret_msk_size = MASTER_KEY_LENGTH;
bcopy(context->master_key, msk, ret_msk_size);
}
return (ret_msk_size);
}
STATIC void
dictInsertEAPTypeInfo(CFMutableDictionaryRef dict, EAPType type,
const char * type_name)
{
CFNumberRef eap_type_cf;
int eap_type = type;
if (type == kEAPTypeInvalid) {
return;
}
if (type_name != NULL) {
CFStringRef eap_type_name_cf;
eap_type_name_cf
= CFStringCreateWithCString(NULL, type_name,
kCFStringEncodingASCII);
CFDictionarySetValue(dict, kEAPClientInnerEAPTypeName,
eap_type_name_cf);
my_CFRelease(&eap_type_name_cf);
}
eap_type_cf = CFNumberCreate(NULL, kCFNumberIntType, &eap_type);
CFDictionarySetValue(dict, kEAPClientInnerEAPType, eap_type_cf);
my_CFRelease(&eap_type_cf);
return;
}
STATIC CFDictionaryRef
eapfast_publish_props_copy(EAPClientPluginDataRef plugin)
{
CFArrayRef cert_list = NULL;
SSLCipherSuite cipher = SSL_NULL_WITH_NULL_NULL;
EAPFASTPluginDataRef context = (EAPFASTPluginDataRef)plugin->private;
CFMutableDictionaryRef dict;
if (context->server_certs != NULL) {
cert_list
= EAPSecCertificateArrayCreateCFDataArray(context->server_certs);
if (cert_list == NULL) {
return (NULL);
}
}
if (context->handshake_complete && context->eap.publish_props != NULL) {
dict = CFDictionaryCreateMutableCopy(NULL, 0,
context->eap.publish_props);
}
else {
dict = CFDictionaryCreateMutable(NULL, 0,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
}
if (cert_list != NULL) {
CFDictionarySetValue(dict, kEAPClientPropTLSServerCertificateChain,
cert_list);
CFRelease(cert_list);
}
if (context->ssl_context != NULL) {
(void)SSLGetNegotiatedCipher(context->ssl_context, &cipher);
if (cipher != SSL_NULL_WITH_NULL_NULL) {
CFNumberRef c;
int tmp = cipher;
c = CFNumberCreate(NULL, kCFNumberIntType, &tmp);
CFDictionarySetValue(dict, kEAPClientPropTLSNegotiatedCipher, c);
CFRelease(c);
}
}
if (context->pac_was_provisioned) {
CFDictionarySetValue(dict, kEAPClientPropEAPFASTPACWasProvisioned,
kCFBooleanTrue);
}
CFDictionarySetValue(dict, kEAPClientPropTLSSessionWasResumed,
context->session_was_resumed
? kCFBooleanTrue
: kCFBooleanFalse);
if (context->eap.module != NULL) {
dictInsertEAPTypeInfo(dict, context->eap.last_type,
context->eap.last_type_name);
}
if (context->last_client_status == kEAPClientStatusUserInputRequired
&& context->trust_proceed == FALSE) {
CFNumberRef num;
num = CFNumberCreate(NULL, kCFNumberSInt32Type,
&context->trust_status);
CFDictionarySetValue(dict, kEAPClientPropTLSTrustClientStatus, num);
CFRelease(num);
}
return (dict);
}
STATIC CFArrayRef
eapfast_require_props(EAPClientPluginDataRef plugin)
{
CFArrayRef array = NULL;
EAPFASTPluginDataRef context = (EAPFASTPluginDataRef)plugin->private;
if (context->last_client_status != kEAPClientStatusUserInputRequired) {
goto done;
}
if (context->trust_proceed == FALSE) {
CFStringRef str = kEAPClientPropTLSUserTrustProceedCertificateChain;
array = CFArrayCreate(NULL, (const void **)&str,
1, &kCFTypeArrayCallBacks);
}
else if (context->handshake_complete) {
if (context->eap.require_props != NULL) {
array = CFRetain(context->eap.require_props);
}
}
done:
return (array);
}
STATIC CFStringRef
eapfast_copy_packet_description(const EAPPacketRef pkt, bool * packet_is_valid)
{
EAPTLSPacketRef eaptls_pkt = (EAPTLSPacketRef)pkt;
EAPTLSLengthIncludedPacketRef eaptls_pkt_l;
int data_length;
void * data_ptr = NULL;
u_int16_t length = EAPPacketGetLength(pkt);
CFMutableStringRef str;
u_int32_t tls_message_length = 0;
*packet_is_valid = FALSE;
switch (pkt->code) {
case kEAPCodeRequest:
case kEAPCodeResponse:
break;
default:
return (NULL);
}
str = CFStringCreateMutable(NULL, 0);
if (length < sizeof(*eaptls_pkt)) {
STRING_APPEND(str, "EAPTLSPacket header truncated %d < %d\n",
length, (int)sizeof(*eaptls_pkt));
goto done;
}
STRING_APPEND(str, "EAP-FAST Version %d %s: "
"Identifier %d Length %d Flags 0x%x%s",
EAPFASTPacketFlagsVersion(eaptls_pkt->flags),
pkt->code == kEAPCodeRequest ? "Request" : "Response",
pkt->identifier, length, eaptls_pkt->flags,
(EAPFASTPacketFlagsFlags(eaptls_pkt->flags) != 0)
? " [" : "");
eaptls_pkt_l = (EAPTLSLengthIncludedPacketRef)(void *)pkt;
data_ptr = eaptls_pkt->tls_data;
tls_message_length = data_length = length - sizeof(EAPTLSPacket);
if ((eaptls_pkt->flags & kEAPTLSPacketFlagsStart) != 0) {
STRING_APPEND(str, " start");
}
if ((eaptls_pkt->flags & kEAPTLSPacketFlagsLengthIncluded) != 0) {
if (length >= sizeof(EAPTLSLengthIncludedPacket)) {
data_ptr = eaptls_pkt_l->tls_data;
data_length = length - sizeof(EAPTLSLengthIncludedPacket);
tls_message_length
= EAPTLSLengthIncludedPacketGetMessageLength(eaptls_pkt_l);
STRING_APPEND(str, " length=%u", tls_message_length);
}
}
if ((eaptls_pkt->flags & kEAPTLSPacketFlagsMoreFragments) != 0) {
STRING_APPEND(str, " more");
}
STRING_APPEND(str, "%s Data Length %d\n",
EAPFASTPacketFlagsFlags(eaptls_pkt->flags) != 0 ? " ]" : "",
data_length);
if (tls_message_length > kEAPTLSAvoidDenialOfServiceSize) {
STRING_APPEND(str, "potential DOS attack %u > %d\n",
tls_message_length, kEAPTLSAvoidDenialOfServiceSize);
STRING_APPEND(str, "bogus EAP Packet:\n");
print_data_cfstr(str, (void *)pkt, length);
goto done;
}
if ((eaptls_pkt->flags & kEAPTLSPacketFlagsStart) != 0
&& data_length >= sizeof(AuthorityIDData)
&& (AuthorityIDDataGetType((AuthorityIDDataRef)data_ptr)
== kAuthorityIDDataType)) {
AuthorityIDDataRef auid = (AuthorityIDDataRef)data_ptr;
uint16_t auid_length;
auid_length = AuthorityIDDataGetLength(auid);
STRING_APPEND(str, "Authority ID Data Length %d ID ", auid_length);
if (auid_length > (data_length - offsetof(AuthorityIDData, auid_id))) {
auid_length = data_length - offsetof(AuthorityIDData, auid_id);
STRING_APPEND(str, "> available %d! ", auid_length);
}
print_bytes_cfstr(str, auid->auid_id, auid_length);
STRING_APPEND(str, "\n");
}
else {
print_data_cfstr(str, data_ptr, data_length);
}
*packet_is_valid = TRUE;
done:
return (str);
}
STATIC EAPType
eapfast_type()
{
return (kEAPTypeEAPFAST);
}
STATIC const char *
eapfast_name()
{
return (EAP_FAST_NAME);
}
STATIC EAPClientPluginVersion
eapfast_version()
{
return (kEAPClientPluginVersion);
}
STATIC struct func_table_ent {
const char * name;
void * func;
} func_table[] = {
#if 0
{ kEAPClientPluginFuncNameIntrospect, eapfast_introspect },
#endif
{ kEAPClientPluginFuncNameVersion, eapfast_version },
{ kEAPClientPluginFuncNameEAPType, eapfast_type },
{ kEAPClientPluginFuncNameEAPName, eapfast_name },
{ kEAPClientPluginFuncNameInit, eapfast_init },
{ kEAPClientPluginFuncNameFree, eapfast_free },
{ kEAPClientPluginFuncNameProcess, eapfast_process },
{ kEAPClientPluginFuncNameFreePacket, eapfast_free_packet },
{ kEAPClientPluginFuncNameFailureString, eapfast_failure_string },
{ kEAPClientPluginFuncNameSessionKey, eapfast_session_key },
{ kEAPClientPluginFuncNameServerKey, eapfast_server_key },
{ kEAPClientPluginFuncNameMasterSessionKeyCopyBytes,
eapfast_msk_copy_bytes },
{ kEAPClientPluginFuncNameRequireProperties, eapfast_require_props },
{ kEAPClientPluginFuncNamePublishProperties, eapfast_publish_props_copy },
{ kEAPClientPluginFuncNameCopyPacketDescription,
eapfast_copy_packet_description },
{ NULL, NULL},
};
EAPClientPluginFuncRef
eapfast_introspect(EAPClientPluginFuncName name)
{
struct func_table_ent * scan;
for (scan = func_table; scan->name != NULL; scan++) {
if (strcmp(name, scan->name) == 0) {
return (scan->func);
}
}
return (NULL);
}