blob: 22ea80a4bc996cd6ae6ac766e19e0937436a2e2f [file] [log] [blame]
/*
<:copyright-BRCM:2016:DUAL/GPL:standard
Broadcom Proprietary and Confidential.(c) 2016 Broadcom
All Rights Reserved
Unless you and Broadcom execute a separate written software license
agreement governing use of this software, this software is licensed
to you under the terms of the GNU General Public License version 2
(the "GPL"), available at http://www.broadcom.com/licenses/GPLv2.php,
with the following added to such license:
As a special exception, the copyright holders of this software give
you permission to link this software with independent modules, and
to copy and distribute the resulting executable under terms of your
choice, provided that you also meet, for each linked independent
module, the terms and conditions of the license of that module.
An independent module is a module which is not derived from this
software. The special exception does not apply to any modifications
of the software.
Not withstanding the above, under no circumstances may you combine
this software in any way with any other Broadcom software provided
under a license other than the GPL, without Broadcom's express prior
written consent.
:>
*/
#include "dpoe_eap_tls.h"
#include "bcmolt_buf.h"
#include "dpoe_sec_fsm.h"
/* The destination multicast MAC address for EAPOL packets. */
static const bcmos_mac_address eapol_dst_mac = { { 0x01, 0x80, 0xc2, 0x00, 0x00, 0x03 } };
/* The length of the RSA key used in the server_key_exchange TLS message sent to the ONU during DPoE bi-directional
authentication. */
#define DPOE_BI_RSA_KEY_SIZE 2048
/* 2 1200 byte certs + some overhead.... */
#define EAPOL_PKT_SIZE 5000
typedef enum
{
EAP_CODE_REQUEST = 1,
EAP_CODE_RESPONSE,
EAP_CODE_SUCCESS,
EAP_CODE_FAILURE
} eap_code;
typedef struct
{
uint8_t eap_code;
uint8_t id;
uint16_t length;
} eap_frame;
#define EAP_FRAME_SIZE 4
typedef enum
{
EAP_TYPE_IDENTITY = 1,
EAP_TYPE_NOTIFICATION,
EAP_TYPE_NAK,
EAP_TYPE_MD5,
EAP_TYPE_OTP,
EAP_TYPE_GENERIC_TOKEN_CARD,
EAP_TYPE_TLS = 13
} eap_type;
#define TLS_MAJOR_VERSION 3
#define TLS_MINOR_VERSION 2
/* First the EAP structures.... */
typedef struct
{
uint8_t auth_sub_type;
uint8_t eap_tls_flags;
} eap_tls_hdr;
#define EAP_TLS_HDR_SIZE 2
#define EAP_HDR_SIZE (EAP_FRAME_SIZE + EAP_TLS_HDR_SIZE)
typedef enum
{
TLS_CHANGE_CIPHER_SPEC_ID = 20,
TLS_ALERT_ID = 21,
TLS_HANDSHAKE_ID = 22,
TLS_APPLICATION_DATA_ID = 23,
} tls_content_type;
typedef struct
{
uint8_t content_type;
tls_protocol_version tls_version;
uint16_t tls_record_length;
} tls_record_hdr;
#define TLS_RECORD_HDR_SIZE (3 + PROTOCOL_VERSION_SIZE)
#define EAP_TLS_FLAG_LENGTH_OFFSET 7
#define EAP_TLS_FLAG_MORE_OFFSET 6
#define EAP_TLS_FLAG_START_OFFSET 5
#define EAP_TLS_FLAG_LENGTH_BIT (1U << EAP_TLS_FLAG_LENGTH_OFFSET)
#define EAP_TLS_FLAG_MORE_BIT (1U << EAP_TLS_FLAG_MORE_OFFSET)
#define EAP_TLS_FLAG_START_BIT (1U << EAP_TLS_FLAG_START_OFFSET)
typedef struct
{
eap_frame hdr;
eap_tls_hdr tls;
} eap_msg_buf;
typedef struct
{
eapol_msg_hdr hdr;
eap_msg_buf eap_msg;
} eapol_msg;
/* Now the TLS records. */
typedef struct tls_session_id
{
uint8_t length;
uint8_t session_id[SIZE_OF_TLS_SESSION_ID];
} tls_session_id;
typedef enum
{
cm_null = 0
} compression_method;
typedef struct
{
uint8_t length;
uint8_t compression_method;
} compression_methods;
typedef enum
{
RSA_SIGN = 1,
DSS_SIGN = 2,
RSA_FIXED_DH = 3,
DSS_FIXED_DH = 4,
RSA_EPHEMERAL_DH_RESERVED = 5,
DSS_EPHEMERAL_DH_RESERVED = 6,
FORTEZZA_DMS_RESERVED = 20,
} client_certificate_type;
/* For 2048 bit RSA key */
#define SIZE_OF_RSA_MODULUS 256
#define SIZE_OF_RSA_EXPONENT 3
typedef enum
{
HS_HELLO_REQUEST = 0,
HS_CLIENT_HELLO = 1,
HS_SERVER_HELLO = 2,
HS_CERTIFICATE = 11,
HS_SERVER_KEY_EXCHANGE = 12,
HS_CERTIFICATE_REQUEST = 13,
HS_SERVER_HELLO_DONE = 14,
HS_CERTIFICATE_VERIFY = 15,
HS_CLIENT_KEY_EXCHANGE = 16,
HS_FINISHED = 20,
} handshake_type;
typedef struct
{
uint8_t msg_id;
uint32_t msg_length; /* actually U24 */
} tls_handshake;
#define TLS_HANDSHAKE_SIZE 4
typedef struct
{
uint8_t *eapol;
uint8_t *eap;
uint8_t *eap_tls;
uint8_t *tls_rec;
} eap_length_fields;
static f_dpoe_sec_cert_trust_cb _cert_trust;
static f_dpoe_sec_auth_cb _auth_complete;
static void _buffer_hash_data(dpoe_sec_link_rec *link_rec, const uint8_t *buf, uint32_t len)
{
/* Parameter checks. */
BUG_ON(link_rec == NULL);
BUG_ON(buf == NULL);
dpoe_sec_sha1_update(&link_rec->auth_ctrl.trans_data.sha1_hash, buf, len);
dpoe_sec_md5_update(&link_rec->auth_ctrl.trans_data.md5_hash, buf, len);
}
static void _dpoe_eap_tls_compute_master_session_key(auth_trans_data *trans_data)
{
uint8_t seed[(2* COUNT_OF_RANDOM_BYTES)];
uint32_t seed_len = 0;
/* Parameter checks. */
BUG_ON(trans_data == NULL);
/* The seed to the PRF is the client random value concatenated with the server random value. */
memcpy(&seed[seed_len], trans_data->client_random, COUNT_OF_RANDOM_BYTES);
seed_len += COUNT_OF_RANDOM_BYTES;
memcpy(&seed[seed_len], trans_data->server_random, COUNT_OF_RANDOM_BYTES);
seed_len += COUNT_OF_RANDOM_BYTES;
dpoe_sec_prf_expand_4346(
trans_data->pre_master_secret,
sizeof(trans_data->pre_master_secret),
(const uint8_t *)"master secret",
strlen("master secret"),
seed,
seed_len,
trans_data->master_secret,
sizeof(trans_data->master_secret));
dpoe_sec_prf_expand_4346(
trans_data->master_secret,
sizeof(trans_data->master_secret),
(const uint8_t *)"client EAP encryption",
strlen("client EAP encryption"),
seed,
seed_len,
trans_data->key_material,
sizeof(trans_data->key_material));
memcpy(trans_data->master_session_key, trans_data->key_material, sizeof(trans_data->master_session_key));
}
static bcmos_bool _dpoe_eap_tls_prepare_security_data(dpoe_sec_link_rec *link_rec)
{
/* Parameter checks. */
BUG_ON(link_rec == NULL);
/* If bidirectional, compute the master session key and generate the RSA public/private key pair */
if (link_rec->cfg.enc_mode == BCMOLT_EPON_OAM_DPOE_ENCRYPTION_MODE_TEN_BI)
{
_dpoe_eap_tls_compute_master_session_key(&link_rec->auth_ctrl.trans_data);
}
return BCMOS_TRUE;
}
static bcmos_bool _dpoe_eap_tls_skip_length(bcmolt_buf *buf, uint32_t skip, uint8_t** len)
{
*len = bcmolt_buf_snap_get(buf);
return bcmolt_buf_skip(buf, skip);
}
static bcmos_bool _dpoe_eap_tls_init_eap_hdr(
dpoe_sec_link_rec *link_rec,
bcmolt_buf *buf,
eap_code code,
eap_length_fields *lf)
{
/* Parameter checks. */
BUG_ON(link_rec == NULL);
BUG_ON(buf == NULL);
return
bcmolt_buf_write_mac_address(buf, eapol_dst_mac) &&
bcmolt_buf_write_mac_address(buf, link_rec->ni_mac) &&
bcmolt_buf_write_u16_be(buf, ETHERTYPE_EAPOL) &&
bcmolt_buf_write_u8(buf, EAPOL_PROTOCOL_VERSION_DPOE) &&
bcmolt_buf_write_u8(buf, EAPOL_TYPE_PACKET) &&
/* Eapol Length - we re-compute this after building the packet */
_dpoe_eap_tls_skip_length(buf, sizeof(uint16_t), &lf->eapol) &&
/* EAP layer headers */
bcmolt_buf_write_u8(buf, code) &&
bcmolt_buf_write_u8(buf, link_rec->auth_ctrl.current_packet_id++) &&
/* EAP length - again, computed when the packet is packed! */
_dpoe_eap_tls_skip_length(buf, sizeof(uint16_t), &lf->eap);
}
static bcmos_bool _dpoe_eap_tls_init_tls_hdr(bcmolt_buf *buf, uint8_t eapTlsFlags, eap_length_fields *lf)
{
bcmos_bool rc = BCMOS_FALSE;
/* Parameter checks. */
BUG_ON(buf == NULL);
if (bcmolt_buf_write_u8(buf, EAP_TYPE_TLS) &&
bcmolt_buf_write_u8(buf, eapTlsFlags))
{
if ((eapTlsFlags & EAP_TLS_FLAG_LENGTH_BIT) == EAP_TLS_FLAG_LENGTH_BIT)
{
/* EAP-TLS length - computed at pack completion only allocate room for this if EapTlsFlagLengthBit is set
But if the EapTlsFlagLengthBit is set, we also write the TLS Record header. */
if (_dpoe_eap_tls_skip_length(buf, sizeof(uint32_t), &lf->eap_tls) &&
bcmolt_buf_write_u8(buf, TLS_HANDSHAKE_ID) &&
bcmolt_buf_write_u8(buf, TLS_MAJOR_VERSION) &&
bcmolt_buf_write_u8(buf, TLS_MINOR_VERSION) &&
/* Another length field, the TLS record length */
_dpoe_eap_tls_skip_length(buf, sizeof(uint16_t), &lf->tls_rec))
{
rc = BCMOS_TRUE;
}
}
else
{
rc = BCMOS_TRUE;
}
}
return rc;
}
static void _dpoe_eap_tls_pack_handshake_length(
dpoe_sec_link_rec *link_rec,
bcmolt_buf *buf,
uint8_t *start,
uint8_t *len)
{
uint32_t rec_len;
uint8_t *end;
/* Now go back and fill in the length field. This is the length of the TlsHandshake record */
end = bcmolt_buf_snap_get(buf);
rec_len = end - start;
bcmolt_buf_snap_restore(buf, len);
bcmolt_buf_write_u24(buf, uint32_to_24(rec_len - TLS_HANDSHAKE_SIZE));
bcmolt_buf_snap_restore(buf, end);
_buffer_hash_data(link_rec, start, rec_len);
}
static bcmos_bool _dpoe_eap_tls_pack_server_hello_hs(dpoe_sec_link_rec *link_rec, bcmolt_buf *buf)
{
uint8_t *start;
uint8_t *len;
/* Parameter checks. */
BUG_ON(link_rec == NULL);
BUG_ON(buf == NULL);
start = bcmolt_buf_snap_get(buf);
if (bcmolt_buf_write_u8(buf, HS_SERVER_HELLO) &&
(len = bcmolt_buf_snap_get(buf), bcmolt_buf_skip(buf, sizeof(uint24_t))) &&
bcmolt_buf_write_u8(buf, link_rec->auth_ctrl.version.major) &&
bcmolt_buf_write_u8(buf, link_rec->auth_ctrl.version.minor) &&
bcmolt_buf_write(buf, link_rec->auth_ctrl.trans_data.server_random, COUNT_OF_RANDOM_BYTES) &&
bcmolt_buf_write_u8(buf, COUNT_OF_RANDOM_BYTES) &&
bcmolt_buf_write(buf, link_rec->auth_ctrl.trans_data.server_random, COUNT_OF_RANDOM_BYTES) &&
/* Cipher Suites - we write 0 as the length of the Cipher Suites
Short circuit if using pre-release authentication */
((link_rec->auth_ctrl.version.minor == 3) || bcmolt_buf_write_u16_be(buf, 0)) &&
/* CompressionMethods also write 0 as the length */
bcmolt_buf_write_u8(buf, 0))
{
_dpoe_eap_tls_pack_handshake_length(link_rec, buf, start, len);
return BCMOS_TRUE;
}
return BCMOS_FALSE;
}
static bcmos_bool _dpoe_eap_tls_pack_server_key_exchange_hs(dpoe_sec_link_rec *link_rec, bcmolt_buf *buf)
{
uint8_t *start;
uint8_t *len;
uint8_t rsa_modulus[SIZE_OF_RSA_MODULUS] = {};
uint8_t rsa_exponent[SIZE_OF_RSA_EXPONENT] = {};
/* Parameter checks. */
BUG_ON(link_rec == NULL);
BUG_ON(buf == NULL);
/* If not bidirectional, just return BCMOS_TRUE */
if (link_rec->cfg.enc_mode != BCMOLT_EPON_OAM_DPOE_ENCRYPTION_MODE_TEN_BI)
{
return BCMOS_TRUE;
}
/* Verify that the length of the public key (modulus) of the private key matches the expected length. Also, verify
that the modulus is successfully read from the RSA key. */
start = bcmolt_buf_snap_get(buf);
if ((dpoe_sec_rsa_public_get(link_rec->auth_ctrl.trans_data.rsa, rsa_modulus, rsa_exponent)) &&
bcmolt_buf_write_u8(buf, HS_SERVER_KEY_EXCHANGE) &&
(len = bcmolt_buf_snap_get(buf), bcmolt_buf_skip(buf, sizeof(uint24_t))) &&
bcmolt_buf_write(buf, rsa_modulus, sizeof(rsa_modulus)) &&
bcmolt_buf_write(buf, rsa_exponent, sizeof(rsa_exponent)))
{
_dpoe_eap_tls_pack_handshake_length(link_rec, buf, start, len);
return BCMOS_TRUE;
}
return BCMOS_FALSE;
}
static bcmos_bool _dpoe_eap_tls_pack_cert_req_hs(dpoe_sec_link_rec *link_rec, bcmolt_buf *buf)
{
uint8_t *start;
uint8_t *len;
/* Parameter checks. */
BUG_ON(link_rec == NULL);
BUG_ON(buf == NULL);
start = bcmolt_buf_snap_get(buf);
if (bcmolt_buf_write_u8(buf, HS_CERTIFICATE_REQUEST) &&
(len = bcmolt_buf_snap_get(buf), bcmolt_buf_skip(buf, sizeof(uint24_t))) &&
bcmolt_buf_write_u8(buf, 1) && /* length of 'Certificate Type' */
bcmolt_buf_write_u8(buf, RSA_SIGN) && /* Certificate Type */
bcmolt_buf_write_u16_be(buf, 0)) /* zero length for cert authorities */
{
_dpoe_eap_tls_pack_handshake_length(link_rec, buf, start, len);
return BCMOS_TRUE;
}
return BCMOS_FALSE;
}
static bcmos_bool _dpoe_eap_tls_pack_server_hello_done_hs(dpoe_sec_link_rec *link_rec, bcmolt_buf *buf)
{
uint8_t *start;
uint8_t *len;
/* Parameter checks. */
BUG_ON(link_rec == NULL);
BUG_ON(buf == NULL);
start = bcmolt_buf_snap_get(buf);
if (bcmolt_buf_write_u8(buf, HS_SERVER_HELLO_DONE) &&
(len = bcmolt_buf_snap_get(buf), bcmolt_buf_skip(buf, sizeof(uint24_t))))
{
_dpoe_eap_tls_pack_handshake_length(link_rec, buf, start, len);
return BCMOS_TRUE;
}
return BCMOS_FALSE;
}
static bcmos_bool _dpoe_eap_tls_pack_finished_hs(dpoe_sec_link_rec *link_rec, bcmolt_buf *buf)
{
uint8_t *start;
uint8_t *len;
start = bcmolt_buf_snap_get(buf);
if (bcmolt_buf_write_u8(buf, HS_FINISHED) &&
(len = bcmolt_buf_snap_get(buf), bcmolt_buf_skip(buf, sizeof(uint24_t))))
{
_dpoe_eap_tls_pack_handshake_length(link_rec, buf, start, len);
return BCMOS_TRUE;
}
return BCMOS_FALSE;
}
static bcmos_bool _dpoe_eap_tls_pack_msg_lengths(bcmolt_buf *buf, uint8_t eap_tls_flags, eap_length_fields *lf)
{
uint16_t eapol_length;
uint32_t eap_tls_length;
uint8_t *snap = bcmolt_buf_snap_get(buf);
/* Parameter checks. */
BUG_ON(buf == NULL);
BUG_ON(lf == NULL);
eapol_length = bcmolt_buf_get_used(buf) - EAPOL_MSG_HDR_SIZE;
/* Seems odd that these two would be the same. But eapolLength is defined as the length of the body fields of the
Eapol buffer, which does not include the Eapol header. EAP is different, the EAP length is define to include the
entire EAP packet, including the header. */
bcmolt_buf_snap_restore(buf, lf->eapol);
if (!bcmolt_buf_write_u16_be(buf, eapol_length))
{
bcmolt_buf_snap_restore(buf, snap);
return BCMOS_FALSE;
}
bcmolt_buf_snap_restore(buf, lf->eap);
if (!bcmolt_buf_write_u16_be(buf, eapol_length))
{
bcmolt_buf_snap_restore(buf, snap);
return BCMOS_FALSE;
}
if ((eap_tls_flags & EAP_TLS_FLAG_LENGTH_BIT) == EAP_TLS_FLAG_LENGTH_BIT)
{
/* The TLS message length - take away the EapTlsHdr, AND the EapMsgHdr which, surprisingly was not removed in
the previous step. */
eap_tls_length = eapol_length - (EAP_FRAME_SIZE + EAP_TLS_HDR_SIZE + sizeof(uint32_t));
bcmolt_buf_snap_restore(buf, lf->eap_tls);
if (!bcmolt_buf_write_u32_be(buf, eap_tls_length))
{
bcmolt_buf_snap_restore(buf, snap);
return BCMOS_FALSE;
}
bcmolt_buf_snap_restore(buf, lf->tls_rec);
if (!bcmolt_buf_write_u16_be(buf, (uint16_t)eap_tls_length - TLS_RECORD_HDR_SIZE))
{
bcmolt_buf_snap_restore(buf, snap);
return BCMOS_FALSE;
}
}
bcmolt_buf_snap_restore(buf, snap);
return BCMOS_TRUE;
}
static bcmos_errno _dpoe_eap_tls_pack_start(dpoe_sec_link_rec *link_rec, bcmolt_buf *buf)
{
bcmos_errno rc = BCM_ERR_NOMEM;
uint8_t flags = EAP_TLS_FLAG_START_BIT;
eap_length_fields lf;
/* Parameter checks. */
BUG_ON(link_rec == NULL);
BUG_ON(buf == NULL);
if (_dpoe_eap_tls_init_eap_hdr(link_rec, buf, EAP_CODE_REQUEST, &lf) &&
_dpoe_eap_tls_init_tls_hdr(buf, flags, &lf) &&
_dpoe_eap_tls_pack_msg_lengths(buf, flags, &lf))
{
rc = BCM_ERR_OK;
}
return rc;
}
static bcmos_bool _dpoe_eap_tls_send_cert_request(dpoe_sec_link_rec *link_rec)
{
bcmos_bool rc = BCMOS_FALSE;
bcmos_errno err;
bcmolt_buf buf;
uint8_t *msg = NULL;
eap_length_fields lf;
/* Parameter checks. */
BUG_ON(link_rec == NULL);
msg = bcmos_calloc(EAPOL_PKT_SIZE);
if (msg == NULL)
{
return BCMOS_FALSE;
}
/* Initialize the maximum supported length EAP-TLS message buffer. The various TLS records contained in an EAP-TLS
message are variable size. However, this can only be a combined maximum length. */
bcmolt_buf_init(&buf, EAPOL_PKT_SIZE, msg, BCMOLT_BUF_ENDIAN_FIXED);
if (_dpoe_eap_tls_init_eap_hdr(link_rec, &buf, EAP_CODE_REQUEST, &lf) &&
_dpoe_eap_tls_init_tls_hdr(&buf, EAP_TLS_FLAG_LENGTH_BIT, &lf))
{
/* The Cert request contains 3 or 4 TLS records (3 for DS-only and 4 for bi-directional). The TLS records are
ordered as:
- Server Hello
- Server Key Exchange (if bi-directional)
- Certificate Request
- Server Hello Done */
if (_dpoe_eap_tls_pack_server_hello_hs(link_rec, &buf) &&
_dpoe_eap_tls_pack_server_key_exchange_hs(link_rec, &buf) &&
_dpoe_eap_tls_pack_cert_req_hs(link_rec, &buf) &&
_dpoe_eap_tls_pack_server_hello_done_hs(link_rec, &buf))
{
_dpoe_eap_tls_pack_msg_lengths(&buf, EAP_TLS_FLAG_LENGTH_BIT, &lf);
/* Send the message */
err = dpoe_sec_send_eapol(link_rec->device, &link_rec->key, msg, bcmolt_buf_get_used(&buf));
if (err != BCM_ERR_OK)
{
bcmos_free(msg);
return BCMOS_FALSE;
}
/* Authentication has begun. */
link_rec->auth_ctrl.onu_auth_state = DPOE_ONU_AUTH_STATE_EAP_START;
rc = BCMOS_TRUE;
}
}
/* Free the message. */
bcmos_free(msg);
return rc;
}
static bcmos_errno _dpoe_eap_tls_send_finished_hs(dpoe_sec_link_rec *link_rec)
{
bcmos_errno rc = BCM_ERR_OVERFLOW;
bcmolt_buf buf;
uint8_t *msg = NULL;
eap_length_fields lf;
/* Parameter checks. */
BUG_ON(link_rec == NULL);
msg = bcmos_calloc(EAPOL_PKT_SIZE);
if (msg == NULL)
{
return BCM_ERR_NOMEM;
}
/* Initialize the EAP-TLS message buffer. */
bcmolt_buf_init(&buf, EAPOL_PKT_SIZE, msg, BCMOLT_BUF_ENDIAN_FIXED);
if (_dpoe_eap_tls_init_eap_hdr(link_rec, &buf, EAP_CODE_REQUEST, &lf) &&
_dpoe_eap_tls_init_tls_hdr(&buf, EAP_TLS_FLAG_LENGTH_BIT, &lf) &&
_dpoe_eap_tls_pack_finished_hs(link_rec, &buf))
{
_dpoe_eap_tls_pack_msg_lengths(&buf, EAP_TLS_FLAG_LENGTH_BIT, &lf);
/* No error detection here. We have already authenticated. this message is just for standards compliance */
rc = dpoe_sec_send_eapol(link_rec->device, &link_rec->key, msg, bcmolt_buf_get_used(&buf));
}
/* Free the message. */
bcmos_free(msg);
return rc;
}
static bcmos_errno _dpoe_eap_tls_send_result(dpoe_sec_link_rec *link_rec, eap_code result)
{
uint8_t *msg;
bcmolt_buf buf;
bcmos_errno rc = BCM_ERR_INTERNAL;
eap_length_fields lf;
/* Parameter checks */
BUG_ON(link_rec == NULL);
msg = bcmos_calloc(EAPOL_PKT_SIZE);
if (msg == NULL)
{
return BCM_ERR_NOMEM;
}
/* Initialize the EAP-TLS message buffer. */
bcmolt_buf_init(&buf, EAPOL_PKT_SIZE, msg, BCMOLT_BUF_ENDIAN_FIXED);
/* Encode the EAP-Success message to the buffer. */
if (_dpoe_eap_tls_init_eap_hdr(link_rec, &buf, result, &lf) &&
_dpoe_eap_tls_pack_msg_lengths(&buf, 0, &lf))
{
rc = dpoe_sec_send_eapol(link_rec->device, &link_rec->key, msg, bcmolt_buf_get_used(&buf));
}
bcmos_free(msg);
return rc;
}
static bcmos_bool _dpoe_eap_tls_parse_certificate(dpoe_sec_link_rec *link_rec, bcmolt_buf *buf)
{
bcmos_bool rc = BCMOS_FALSE;
uint24_t length;
/* Parameter checks. */
BUG_ON(link_rec == NULL);
BUG_ON(buf == NULL);
link_rec->auth_ctrl.certificate = bcmos_calloc(EAPOL_PKT_SIZE);
if (link_rec->auth_ctrl.certificate == NULL)
{
return BCMOS_FALSE;
}
if (bcmolt_buf_read_u24(buf, &length) &&
((link_rec->auth_ctrl.certLen = (uint16_t)uint24_to_32(length)), (link_rec->auth_ctrl.certLen <= EAPOL_PKT_SIZE)) &&
bcmolt_buf_read(buf, link_rec->auth_ctrl.certificate, link_rec->auth_ctrl.certLen))
{
rc = BCMOS_TRUE;
}
return rc;
}
static bcmos_bool _dpoe_eap_tls_parse_client_key_exchange(dpoe_sec_link_rec *link_rec, bcmolt_buf *buf)
{
bcmos_bool rc = BCMOS_FALSE;
uint16_t length;
uint8_t encrypted_buf[SIZE_OF_RSA_ENCRYPTED_BLOCK] = {};
/* Parameter checks. */
BUG_ON(link_rec == NULL);
BUG_ON(buf == NULL);
if (bcmolt_buf_read_u16_be(buf, &length) &&
(length == (SIZE_OF_RSA_ENCRYPTED_BLOCK)) &&
bcmolt_buf_read(buf, encrypted_buf, sizeof(encrypted_buf)))
{
int val;
val = dpoe_sec_rsa_private_decrypt(
sizeof(encrypted_buf),
encrypted_buf,
link_rec->auth_ctrl.trans_data.pre_master_secret,
link_rec->auth_ctrl.trans_data.rsa);
if (val == SIZE_OF_PRE_MASTER_SECRET)
{
/* We now need the security data - we have the "client random", "server random", and "pre master secret". */
_dpoe_eap_tls_prepare_security_data(link_rec);
rc = BCMOS_TRUE;
}
else
{
DPOE_SEC_LINK_LOG(ERROR, link_rec, "RSA decrypt pre-master secret failed: %d\n", val);
}
}
else
{
DPOE_SEC_LINK_LOG(ERROR, link_rec, "Failed to parse client key exchange: %u\n", length);
}
return rc;
}
static bcmos_bool _dpoe_eap_tls_parse_cert_verify(dpoe_sec_link_rec *link_rec, bcmolt_buf *buf)
{
uint16_t length;
bcmos_bool ret = BCMOS_FALSE;
if (bcmolt_buf_read_u16_be(buf, &length) &&
(length <= (2 * SIZE_OF_RSA_ENCRYPTED_BLOCK)))
{
bcmolt_buf cert_buf;
uint24_t temp;
const uint8_t *cert;
uint32_t cert_len = 0;
uint8_t cert_verify[sizeof(dpoe_sec_sha1_digest) + sizeof(dpoe_sec_md5_digest)];
uint8_t *enc_buf;
dpoe_sec_rsa_key *rsa;
bcmolt_buf_init(&cert_buf, link_rec->auth_ctrl.certLen, link_rec->auth_ctrl.certificate, BCMOLT_BUF_ENDIAN_FIXED);
/* Get the pointer to the ONU cert and its length */
bcmolt_buf_read_u24(&cert_buf, &temp);
cert_len = uint24_to_32(temp);
cert = bcmolt_buf_snap_get(&cert_buf);
enc_buf = bcmolt_buf_snap_get(buf);
bcmolt_buf_skip(buf, length);
/* Get the public key from the ONU cert. */
rsa = dpoe_sec_x509_pub_key_get(cert, cert_len);
/* decrypt the remainder of the buf with the ONU cert. */
if (rsa != NULL)
{
if (dpoe_sec_rsa_public_decrypt(length, enc_buf, cert_verify, rsa) >= 0)
{
/* RSA_size() returns the size of the modulus in bytes. Convert to bits. */
link_rec->auth_ctrl.onu_cert_key_size = dpoe_sec_rsa_size(rsa) * 8;
/* Compare the output (should be 36 bytes) to sha1/md5 messages digests If they match, we are good */
if ((memcmp(&cert_verify[0],
link_rec->auth_ctrl.trans_data.md5_digest,
sizeof(dpoe_sec_md5_digest)) == 0) &&
(memcmp(&cert_verify[sizeof(dpoe_sec_md5_digest)],
link_rec->auth_ctrl.trans_data.sha1_digest,
sizeof(dpoe_sec_sha1_digest)) == 0))
{
bcmolt_buf_init(&cert_buf, link_rec->auth_ctrl.certLen, link_rec->auth_ctrl.certificate, BCMOLT_BUF_ENDIAN_FIXED);
/* Get the pointer to the ONU certificate. */
bcmolt_buf_read_u24(&cert_buf, &temp);
link_rec->auth_ctrl.onu_cert_len = uint24_to_32(temp);
link_rec->auth_ctrl.onu_cert = bcmolt_buf_snap_get(&cert_buf);
bcmolt_buf_skip(&cert_buf, link_rec->auth_ctrl.onu_cert_len);
/* Get the pointer to the Manufacturer CA certificate. */
bcmolt_buf_read_u24(&cert_buf, &temp);
link_rec->auth_ctrl.mfg_cert_len = uint24_to_32(temp);
link_rec->auth_ctrl.mfg_cert = bcmolt_buf_snap_get(&cert_buf);
ret = BCMOS_TRUE;
}
else
{
DPOE_SEC_LINK_LOG(ERROR, link_rec, "Cert Verify failed\n");
}
}
else
{
DPOE_SEC_LINK_LOG(ERROR, link_rec, "RSA public decrypt/verify failed\n");
}
}
else
{
DPOE_SEC_LINK_LOG(ERROR, link_rec, "Failed to retrieve RSA key\n");
}
dpoe_sec_rsa_key_free(rsa);
}
return ret;
}
static bcmos_bool _dpoe_eap_tls_parse_verify_data(dpoe_sec_link_rec *link_rec)
{
bcmos_bool rc = BCMOS_FALSE;
/* Parameter checks. */
BUG_ON(link_rec == NULL);
if (link_rec->auth_ctrl.version.minor == TLS_MINOR_VERSION)
{
/* This version of authentication does not parse verify data */
rc = BCMOS_TRUE;
}
return rc;
}
static bcmos_bool _dpoe_eap_tls_parse_client_hello(dpoe_sec_link_rec *link_rec, bcmolt_buf *buf)
{
tls_protocol_version version;
tls_session_id session_id;
/* Parameter checks. */
BUG_ON(link_rec == NULL);
BUG_ON(buf == NULL);
if (bcmolt_buf_read_u8(buf, &version.major) &&
(version.major == TLS_MAJOR_VERSION) &&
bcmolt_buf_read_u8(buf, &version.minor) &&
(version.minor >= TLS_MINOR_VERSION) &&
bcmolt_buf_read(buf, link_rec->auth_ctrl.trans_data.client_random, COUNT_OF_RANDOM_BYTES) &&
bcmolt_buf_read_u8(buf, &session_id.length) &&
(session_id.length <= sizeof(session_id.session_id)) &&
bcmolt_buf_read(buf, session_id.session_id, session_id.length))
{
compression_methods comp_methods;
uint16_t tlvLen;
uint8_t cipher_suites[2];
uint8_t *cm_snap = bcmolt_buf_snap_get(buf);
memset(&comp_methods, 0, sizeof(comp_methods));
/* Cipher Suites may be present -if so parse over it and it will be ignored. Still check that comp methods is
NULL */
if (bcmolt_buf_read_u16_be(buf, &tlvLen) && /* Cipher Suites */
(tlvLen <= sizeof(uint16_t)) &&
bcmolt_buf_read(buf, cipher_suites, tlvLen) &&
bcmolt_buf_read_u8(buf, &comp_methods.length) && /* Compression Methods */
(comp_methods.length == sizeof(comp_methods.compression_method)) &&
bcmolt_buf_read(buf, &comp_methods.compression_method, comp_methods.length) &&
(comp_methods.compression_method == cm_null) )
{
link_rec->auth_ctrl.version.major = version.major;
link_rec->auth_ctrl.version.minor = version.minor;
return BCMOS_TRUE;
}
/* No cipher suites, check that comp method is null */
memset(&comp_methods, 0, sizeof(comp_methods));
bcmolt_buf_snap_restore(buf, cm_snap);
if (bcmolt_buf_read_u8(buf, &comp_methods.length) && /* Compression Methods */
(comp_methods.length == sizeof(comp_methods.compression_method)) &&
bcmolt_buf_read(buf, &comp_methods.compression_method, comp_methods.length) &&
(comp_methods.compression_method == cm_null) )
{
link_rec->auth_ctrl.version.major = version.major;
link_rec->auth_ctrl.version.minor = version.minor;
return BCMOS_TRUE;
}
/* Ok we failed to decode what was sent as cipher suites and comp methods. Do we care? Absolutely Not! */
link_rec->auth_ctrl.version.major = version.major;
link_rec->auth_ctrl.version.minor = version.minor;
return BCMOS_TRUE;
}
return BCMOS_FALSE;
}
static bcmos_bool _dpoe_eap_tls_handle_client_hello_hs(dpoe_sec_link_rec *link_rec, bcmolt_buf *buf)
{
/* Parameter checks. */
BUG_ON(link_rec == NULL);
BUG_ON(buf == NULL);
if (_dpoe_eap_tls_parse_client_hello(link_rec, buf))
{
/* We now have the data, build the EAP session ID (see RFC 5216) */
link_rec->auth_ctrl.trans_data.session_id[0] = 0xd;
memcpy(
&link_rec->auth_ctrl.trans_data.session_id[1],
link_rec->auth_ctrl.trans_data.client_random,
COUNT_OF_RANDOM_BYTES);
memcpy(
&link_rec->auth_ctrl.trans_data.session_id[COUNT_OF_RANDOM_BYTES + 1],
link_rec->auth_ctrl.trans_data.server_random,
COUNT_OF_RANDOM_BYTES);
if (_dpoe_eap_tls_send_cert_request(link_rec))
{
link_rec->auth_ctrl.onu_auth_state = DPOE_ONU_AUTH_STATE_CERT_REQUEST;
return BCMOS_TRUE;
}
}
return BCMOS_FALSE;
}
static bcmos_bool _dpoe_eap_tls_handle_certificate_hs(dpoe_sec_link_rec *link_rec, bcmolt_buf *buf)
{
/* Parameter checks. */
BUG_ON(link_rec == NULL);
BUG_ON(buf == NULL);
if (_dpoe_eap_tls_parse_certificate(link_rec, buf))
{
if (link_rec->auth_ctrl.version.minor == TLS_MINOR_VERSION)
{
link_rec->auth_ctrl.onu_auth_state = DPOE_ONU_AUTH_STATE_CERT_RECEIVED;
return BCMOS_TRUE;
}
}
return BCMOS_FALSE;
}
/******************************************************************************/
static bcmos_bool _dpoe_eap_tls_handle_client_key_exchange_hs(dpoe_sec_link_rec *link_rec, bcmolt_buf *buf)
{
/* Parameter checks. */
BUG_ON(link_rec == NULL);
BUG_ON(buf == NULL);
if (_dpoe_eap_tls_parse_client_key_exchange(link_rec, buf))
{
if (link_rec->auth_ctrl.version.minor == TLS_MINOR_VERSION)
{
link_rec->auth_ctrl.onu_auth_state = DPOE_ONU_AUTH_STATE_CLIENT_KEY_RECEIVED;
return BCMOS_TRUE;
}
else
{
DPOE_SEC_LINK_LOG(ERROR, link_rec, "Wrong TLS version in client key exchange: %u\n",
link_rec->auth_ctrl.version.minor);
}
}
else
{
DPOE_SEC_LINK_LOG(ERROR, link_rec, "Failed to parse client key exchange\n");
}
return BCMOS_FALSE;
}
static bcmos_bool _dpoe_eap_tls_handle_cert_verify_hs(dpoe_sec_link_rec *link_rec, bcmolt_buf *buf)
{
bcmos_bool success;
/* Parameter checks. */
BUG_ON(link_rec == NULL);
BUG_ON(buf == NULL);
success = _dpoe_eap_tls_parse_cert_verify(link_rec, buf);
if (success)
{
link_rec->auth_ctrl.onu_auth_state = DPOE_ONU_AUTH_STATE_CERT_VALIDATED;
}
else
{
link_rec->auth_ctrl.onu_auth_state = DPOE_ONU_AUTH_STATE_FAILED;
if (_auth_complete != NULL)
{
_auth_complete(link_rec, BCM_ERR_ONU_ERR_RESP);
}
}
return success;
}
static bcmos_bool _dpoe_eap_tls_handle_finished_hs(dpoe_sec_link_rec *link_rec)
{
bcmos_bool success;
/* Parameter checks. */
BUG_ON(link_rec == NULL);
success = _dpoe_eap_tls_parse_verify_data(link_rec);
/* And send the final Finished Hs to the ONU */
if (link_rec->auth_ctrl.version.minor == TLS_MINOR_VERSION)
{
_dpoe_eap_tls_send_finished_hs(link_rec);
}
if (success)
{
if ((_cert_trust == NULL) || (_cert_trust(link_rec)))
{
link_rec->auth_ctrl.onu_auth_state = DPOE_ONU_AUTH_STATE_AUTHENTICATED;
_dpoe_eap_tls_send_result(link_rec, EAP_CODE_SUCCESS);
if (_auth_complete != NULL)
{
_auth_complete(link_rec, BCM_ERR_OK);
}
}
else
{
link_rec->auth_ctrl.onu_auth_state = DPOE_ONU_AUTH_STATE_FAILED;
_dpoe_eap_tls_send_result(link_rec, EAP_CODE_FAILURE);
success = BCMOS_FALSE;
if (_auth_complete != NULL)
{
_auth_complete(link_rec, BCM_ERR_INTERNAL);
}
}
}
else
{
/* Reset ALL the state variables! */
memset(&link_rec->auth_ctrl, 0, sizeof(link_rec->auth_ctrl));
}
return success;
}
static bcmos_bool _dpoe_eap_tls_alloc_tls_frag_buffer(onu_auth_control *auth_ctrl)
{
/* Parameter checks. */
BUG_ON(auth_ctrl == NULL);
if (auth_ctrl->tls_frag_buffer == NULL)
{
auth_ctrl->tls_frag_buffer = bcmos_calloc(EAPOL_PKT_SIZE);
auth_ctrl->tls_frag_length = 0;
auth_ctrl->tls_total_length = 0;
}
if (auth_ctrl->tls_frag_buffer == NULL)
{
return BCMOS_FALSE;
}
return BCMOS_TRUE;
}
static void _dpoe_eap_tls_free_tls_frag_buffer(onu_auth_control *auth_ctrl)
{
/* Parameter checks. */
BUG_ON(auth_ctrl == NULL);
if (auth_ctrl->tls_frag_buffer != NULL)
{
bcmos_free(auth_ctrl->tls_frag_buffer);
auth_ctrl->tls_frag_buffer = NULL;
auth_ctrl->tls_frag_length = 0;
auth_ctrl->tls_total_length = 0;
}
}
static bcmos_errno _dpoe_eap_tls_send_tls_frag_ack_to_onu(dpoe_sec_link_rec *link_rec)
{
bcmos_errno rc;
bcmolt_buf buf;
uint8_t *msg;
eap_length_fields lf;
/* Parameter checks. */
BUG_ON(link_rec == NULL);
/* Allocate a message buffer */
msg = bcmos_calloc(EAPOL_PKT_SIZE);
if (NULL == msg)
{
return BCM_ERR_NOMEM;
}
/* Initialize the EAP-TLS buffer. */
bcmolt_buf_init(&buf, EAPOL_PKT_SIZE, msg, BCMOLT_BUF_ENDIAN_FIXED);
if (_dpoe_eap_tls_init_eap_hdr(link_rec, &buf, EAP_CODE_REQUEST, &lf) &&
_dpoe_eap_tls_init_tls_hdr(&buf, 0, &lf) &&
_dpoe_eap_tls_pack_msg_lengths(&buf, 0, &lf))
{
/* Send the message */
rc = dpoe_sec_send_eapol(link_rec->device, &link_rec->key, msg, bcmolt_buf_get_used(&buf));
if (rc != BCM_ERR_OK)
{
bcmos_free(msg);
return rc;
}
}
else
{
rc = BCM_ERR_OVERFLOW;
}
bcmos_free(msg);
return rc;
}
static bcmos_bool _dpoe_eap_tls_unpack_tls_handshake(bcmolt_buf *buf, tls_handshake* hs)
{
uint24_t temp;
bcmos_bool rc = BCMOS_FALSE;
if (bcmolt_buf_read_u8(buf, &hs->msg_id) &&
bcmolt_buf_read_u24(buf, &temp))
{
hs->msg_length = uint24_to_32(temp);
rc = BCMOS_TRUE;
}
return rc;
}
static bcmos_errno _dpoe_eap_tls_run_auth_state_machine(dpoe_sec_link_rec *link_rec, bcmolt_buf *buf)
{
bcmos_errno rc = BCM_ERR_OK;
uint8_t *hs_snap;
tls_handshake handshake;
/* Parameter checks. */
BUG_ON(link_rec == NULL);
while (hs_snap = bcmolt_buf_snap_get(buf), _dpoe_eap_tls_unpack_tls_handshake(buf, &handshake))
{
bcmos_bool success = BCMOS_TRUE;
if (handshake.msg_length > EAPOL_PKT_SIZE)
{
DPOE_SEC_LINK_LOG(DEBUG, link_rec, "Handshake length is insane: msg %u, len %u\n",
handshake.msg_id, handshake.msg_length);
return BCM_ERR_PARSE;
}
if ((handshake.msg_id == HS_CLIENT_HELLO) &&
(link_rec->auth_ctrl.onu_auth_state == DPOE_ONU_AUTH_STATE_EAP_START))
{
DPOE_SEC_LINK_LOG(DEBUG, link_rec, "EAP TLS RX msg %u in state %u\n",
handshake.msg_id, link_rec->auth_ctrl.onu_auth_state);
_buffer_hash_data(link_rec, hs_snap, handshake.msg_length + TLS_HANDSHAKE_SIZE);
success = _dpoe_eap_tls_handle_client_hello_hs(link_rec, buf);
DPOE_SEC_LINK_LOG(DEBUG, link_rec, "New auth state %u\n", link_rec->auth_ctrl.onu_auth_state);
}
else if ((handshake.msg_id == HS_CERTIFICATE) &&
(link_rec->auth_ctrl.onu_auth_state == DPOE_ONU_AUTH_STATE_CERT_REQUEST))
{
DPOE_SEC_LINK_LOG(DEBUG, link_rec, "EAP TLS RX msg %u in state %u\n",
handshake.msg_id, link_rec->auth_ctrl.onu_auth_state);
_buffer_hash_data(link_rec, hs_snap, handshake.msg_length + TLS_HANDSHAKE_SIZE);
success = _dpoe_eap_tls_handle_certificate_hs(link_rec, buf);
DPOE_SEC_LINK_LOG(DEBUG, link_rec, "New auth state %u\n", link_rec->auth_ctrl.onu_auth_state);
}
else if ((handshake.msg_id == HS_CLIENT_KEY_EXCHANGE) &&
(link_rec->auth_ctrl.onu_auth_state == DPOE_ONU_AUTH_STATE_CERT_RECEIVED))
{
DPOE_SEC_LINK_LOG(DEBUG, link_rec, "EAP TLS RX msg %u in state %u\n",
handshake.msg_id, link_rec->auth_ctrl.onu_auth_state);
_buffer_hash_data(link_rec, hs_snap, handshake.msg_length + TLS_HANDSHAKE_SIZE);
success = _dpoe_eap_tls_handle_client_key_exchange_hs(link_rec, buf);
DPOE_SEC_LINK_LOG(DEBUG, link_rec, "New auth state %u\n", link_rec->auth_ctrl.onu_auth_state);
}
else if ((handshake.msg_id == HS_CERTIFICATE_VERIFY) &&
((link_rec->auth_ctrl.onu_auth_state == DPOE_ONU_AUTH_STATE_CERT_RECEIVED) ||
(link_rec->auth_ctrl.onu_auth_state == DPOE_ONU_AUTH_STATE_CLIENT_KEY_RECEIVED)))
{
DPOE_SEC_LINK_LOG(DEBUG, link_rec, "EAP TLS RX msg %u in state %u\n",
handshake.msg_id, link_rec->auth_ctrl.onu_auth_state);
dpoe_sec_sha1_final(link_rec->auth_ctrl.trans_data.sha1_digest, &link_rec->auth_ctrl.trans_data.sha1_hash);
dpoe_sec_md5_final(link_rec->auth_ctrl.trans_data.md5_digest, &link_rec->auth_ctrl.trans_data.md5_hash);
success = _dpoe_eap_tls_handle_cert_verify_hs(link_rec, buf);
DPOE_SEC_LINK_LOG(DEBUG, link_rec, "New auth state %u\n", link_rec->auth_ctrl.onu_auth_state);
}
else if ((handshake.msg_id == HS_FINISHED) &&
(link_rec->auth_ctrl.onu_auth_state == DPOE_ONU_AUTH_STATE_CERT_VALIDATED))
{
DPOE_SEC_LINK_LOG(DEBUG, link_rec, "EAP TLS RX msg %u in state %u\n",
handshake.msg_id, link_rec->auth_ctrl.onu_auth_state);
success = _dpoe_eap_tls_handle_finished_hs(link_rec);
DPOE_SEC_LINK_LOG(DEBUG, link_rec, "New auth state %u\n", link_rec->auth_ctrl.onu_auth_state);
}
else
{
DPOE_SEC_LINK_LOG(DEBUG, link_rec, "EAP TLS RX msg %u in state %u\n",
handshake.msg_id, link_rec->auth_ctrl.onu_auth_state);
success = BCMOS_FALSE;
}
if (!success)
{
/* Something went wrong, just abort... */
DPOE_SEC_LINK_LOG(DEBUG, link_rec, "EAP TLS handshake processing failed: %u\n", handshake.msg_id);
rc = BCM_ERR_PARSE;
}
}
return rc;
}
static bcmos_bool _dpoe_eap_tls_unpack_tls_record_hdr(bcmolt_buf *buf, tls_record_hdr *msg)
{
return bcmolt_buf_read_u8(buf, &msg->content_type) &&
bcmolt_buf_read_u8(buf, &msg->tls_version.major) &&
bcmolt_buf_read_u8(buf, &msg->tls_version.minor) &&
bcmolt_buf_read_u16_be(buf, &msg->tls_record_length);
}
static bcmos_errno _dpoe_eap_tls_process_tls_records(dpoe_sec_link_rec *link_rec)
{
bcmos_errno rc = BCM_ERR_OK;
bcmolt_buf buf;
tls_record_hdr tls_hdr;
/* Parameter checks. */
BUG_ON(link_rec == NULL);
bcmolt_buf_init(&buf, link_rec->auth_ctrl.tls_frag_length, link_rec->auth_ctrl.tls_frag_buffer, BCMOLT_BUF_ENDIAN_FIXED);
while ((rc == BCM_ERR_OK) && _dpoe_eap_tls_unpack_tls_record_hdr(&buf, &tls_hdr))
{
rc = _dpoe_eap_tls_run_auth_state_machine(link_rec, &buf);
}
return rc;
}
static bcmos_bool _dpoe_eap_tls_check_tls_version(const tls_record_hdr *tls_rec_hdr)
{
/* Parameter checks. */
BUG_ON(tls_rec_hdr == NULL);
if ((tls_rec_hdr->content_type != TLS_HANDSHAKE_ID) ||
(tls_rec_hdr->tls_version.major != TLS_MAJOR_VERSION) ||
(tls_rec_hdr->tls_version.minor < TLS_MINOR_VERSION))
{
return BCMOS_TRUE;
}
return BCMOS_FALSE;
}
static void _dpoe_eap_tls_buffer_tls_data(dpoe_sec_link_rec *link_rec, const void *data, uint32_t length)
{
/* Parameter checks. */
BUG_ON(link_rec == NULL);
BUG_ON(data == NULL);
/* do not overrun the buffer, no matter how much data received */
if ((link_rec->auth_ctrl.tls_frag_length + length) > EAPOL_PKT_SIZE)
{
length = EAPOL_PKT_SIZE - link_rec->auth_ctrl.tls_frag_length;
}
memcpy(&link_rec->auth_ctrl.tls_frag_buffer[link_rec->auth_ctrl.tls_frag_length], data, length);
link_rec->auth_ctrl.tls_frag_length += length;
}
static bcmos_bool _dpoe_eap_tls_unpack_msg(bcmolt_buf *buf, eapol_msg *msg)
{
return dpoe_sec_eapol_unpack(buf, &msg->hdr) &&
bcmolt_buf_read_u8(buf, &msg->eap_msg.hdr.eap_code) &&
bcmolt_buf_read_u8(buf, &msg->eap_msg.hdr.id) &&
bcmolt_buf_read_u16_be(buf, &msg->eap_msg.hdr.length) &&
bcmolt_buf_read_u8(buf, &msg->eap_msg.tls.auth_sub_type) &&
bcmolt_buf_read_u8(buf, &msg->eap_msg.tls.eap_tls_flags);
}
static bcmos_errno _dpoe_eap_tls_gen_key(auth_trans_data *trans_data)
{
bcmos_errno rc = BCM_ERR_OK;
dpoe_sec_rsa_key *rsa = NULL;
/* Parameter checks */
BUG_ON(trans_data == NULL);
/* Generate the RSA key to be used in the ServerKeyExchange and ClientKeyExchange */
rsa = dpoe_sec_rsa_generate_key(DPOE_BI_RSA_KEY_SIZE);
if (rsa == NULL)
{
rc = BCM_ERR_INTERNAL;
}
else
{
/* Store the pointer to the key. */
trans_data->rsa = rsa;
}
return rc;
}
bcmos_errno dpoe_eap_tls_send_start(dpoe_sec_link_rec *link_rec)
{
bcmos_errno rc = BCM_ERR_OK;
bcmolt_buf buf;
uint8_t *msg;
time_t unix_time;
/* Parameter checks */
BUG_ON(link_rec == NULL);
/* Initialize the authentication control data. */
memset(&link_rec->auth_ctrl, 0, sizeof(link_rec->auth_ctrl));
dpoe_sec_sha1_init(&link_rec->auth_ctrl.trans_data.sha1_hash);
dpoe_sec_md5_init(&link_rec->auth_ctrl.trans_data.md5_hash);
/* Per standard, the first four bytes of the server random value should be "unix time" */
unix_time = time(NULL);
memcpy(link_rec->auth_ctrl.trans_data.server_random, &unix_time, sizeof(unix_time));
dpoe_sec_generate_n_byte_random_number(&link_rec->auth_ctrl.trans_data.server_random[sizeof(unix_time)], COUNT_OF_RANDOM_BYTES - sizeof(unix_time));
/* If bidirectional, generate the RSA public/private key pair for the ServerKeyExchange and ClientKeyExchange. */
if (link_rec->cfg.enc_mode == BCMOLT_EPON_OAM_DPOE_ENCRYPTION_MODE_TEN_BI)
{
rc = _dpoe_eap_tls_gen_key(&link_rec->auth_ctrl.trans_data);
BCMOS_CHECK_RETURN_ERROR(BCM_ERR_OK != rc, rc);
}
/* Allocate a message buffer */
msg = bcmos_calloc(EAPOL_PKT_SIZE);
if (NULL == msg)
{
return BCM_ERR_NOMEM;
}
/* Initialize the EAP-TLS buffer. */
bcmolt_buf_init(&buf, EAPOL_PKT_SIZE, msg, BCMOLT_BUF_ENDIAN_FIXED);
/* Encode the EAP-TLS Start message to the buffer. */
_dpoe_eap_tls_pack_start(link_rec, &buf);
/* Send the message */
rc = dpoe_sec_send_eapol(link_rec->device, &link_rec->key, msg, bcmolt_buf_get_used(&buf));
bcmos_free(msg);
BCMOS_CHECK_RETURN_ERROR(BCM_ERR_OK != rc, rc);
/* Authentication has begun. */
link_rec->auth_ctrl.onu_auth_state = DPOE_ONU_AUTH_STATE_EAP_START;
return rc;
}
bcmos_errno dpoe_eap_tls_process_eapol_pkt(dpoe_sec_link_rec *link_rec, uint8_t *msg, uint32_t msg_len)
{
bcmos_errno rc = BCM_ERR_INTERNAL;
uint32_t eap_tls_length;
tls_record_hdr tls_rec_hdr;
eapol_msg unpacked_msg;
bcmolt_buf buf;
/* Parameter checks */
BUG_ON(link_rec == NULL);
BUG_ON(msg == NULL);
bcmolt_buf_init(&buf, msg_len, msg, BCMOLT_BUF_ENDIAN_FIXED);
_dpoe_eap_tls_unpack_msg(&buf, &unpacked_msg);
if (unpacked_msg.eap_msg.tls.auth_sub_type == EAP_TYPE_NAK)
{
link_rec->auth_ctrl.onu_auth_state = DPOE_ONU_AUTH_STATE_FAILED;
return BCM_ERR_ONU_ERR_RESP;
}
if (unpacked_msg.hdr.eapol_length != unpacked_msg.eap_msg.hdr.length)
{
return BCM_ERR_PARSE;
}
/* We should only read the length if the length bit is set. */
if ((unpacked_msg.eap_msg.tls.eap_tls_flags & EAP_TLS_FLAG_LENGTH_BIT) != 0)
{
bcmolt_buf_read_u32_be(&buf, &eap_tls_length);
uint8_t *tls_data = bcmolt_buf_snap_get(&buf);
_dpoe_eap_tls_unpack_tls_record_hdr(&buf, &tls_rec_hdr);
if (_dpoe_eap_tls_check_tls_version(&tls_rec_hdr))
{
/* Invalid TLS record, discard... */
return BCM_ERR_PARSE;
}
/* Allocate the fragmentation buffer. Multiple calls to the frag alloc function is allowed since it allocates a
new buffer only if there is no frag buffer currently allocated. The frag buffer is also freed if the EAP-TLS
session goes down for some reason. */
if (!_dpoe_eap_tls_alloc_tls_frag_buffer(&link_rec->auth_ctrl))
{
return BCM_ERR_NOMEM;
}
/* If the length bit is set, and the more bit is not, this is likely a single fragment packet. In that case
the TlsMsg length must be present, and match the size of the buffer. It is also possible that this is a
subsequent fragment and the client always adds the length (which per standard is the length of the set of
fragments), in which case we cannot validate this now. The best we can do is save this size, and validate
when all fragments have been received. */
if (link_rec->auth_ctrl.tls_total_length == 0)
{
link_rec->auth_ctrl.tls_total_length = eap_tls_length;
}
if (link_rec->auth_ctrl.tls_total_length != eap_tls_length)
{
return BCM_ERR_PARSE;
}
_dpoe_eap_tls_buffer_tls_data(
link_rec,
tls_data,
unpacked_msg.hdr.eapol_length - (EAP_FRAME_SIZE + EAP_TLS_HDR_SIZE + sizeof(uint32_t)));
if ((unpacked_msg.eap_msg.tls.eap_tls_flags & EAP_TLS_FLAG_MORE_BIT) != 0)
{
/* Need to acknowledge the fragment */
_dpoe_eap_tls_send_tls_frag_ack_to_onu(link_rec);
rc = BCM_ERR_IN_PROGRESS;
}
else
{
/* Either a single fragment packet, or the last fragment of a multi-fragment packet. If it is the last
fragment of a multi-fragment, then all we can check is that eapTlsLength is one TlsRecordHdr bigger than
the buffered (authCtrl->tlsFragLen) data stream. The same logic works on a single frag packet. */
if ((eap_tls_length != link_rec->auth_ctrl.tls_total_length) ||
(eap_tls_length != link_rec->auth_ctrl.tls_frag_length))
{
return BCM_ERR_PARSE;
}
rc = _dpoe_eap_tls_process_tls_records(link_rec);
_dpoe_eap_tls_free_tls_frag_buffer(&link_rec->auth_ctrl);
}
}
else
{
uint8_t *tls_data = bcmolt_buf_snap_get(&buf);
/* No length field - must be fragmented, and not first */
if (link_rec->auth_ctrl.tls_frag_buffer == NULL)
{
return BCM_ERR_OUT_OF_SYNC;
}
_dpoe_eap_tls_buffer_tls_data(
link_rec,
tls_data,
unpacked_msg.hdr.eapol_length - (EAP_FRAME_SIZE + EAP_TLS_HDR_SIZE));
/* If the more bit is set, wait for the next fragment. Otherwise dump what we have into the state machine. */
if ((unpacked_msg.eap_msg.tls.eap_tls_flags & EAP_TLS_FLAG_MORE_BIT) != 0)
{
/* Need to acknowledge the fragment? The standard does not define a message set that will get us here. */
_dpoe_eap_tls_send_tls_frag_ack_to_onu(link_rec);
rc = BCM_ERR_IN_PROGRESS;
}
else
{
if (link_rec->auth_ctrl.tls_total_length != link_rec->auth_ctrl.tls_frag_length)
{
return BCM_ERR_PARSE;
}
rc = _dpoe_eap_tls_process_tls_records(link_rec);
_dpoe_eap_tls_free_tls_frag_buffer(&link_rec->auth_ctrl);
}
}
return rc;
}
void dpoe_eap_tls_cleanup(dpoe_sec_link_rec *link_rec)
{
/* Parameter checks */
BUG_ON(link_rec == NULL);
// We are done with this.
if (link_rec->auth_ctrl.certificate != NULL)
{
bcmos_free(link_rec->auth_ctrl.certificate);
}
if (link_rec->auth_ctrl.trans_data.rsa != NULL)
{
dpoe_sec_rsa_key_free(link_rec->auth_ctrl.trans_data.rsa);
link_rec->auth_ctrl.trans_data.rsa = NULL;
}
link_rec->auth_ctrl.certLen = 0;
_dpoe_eap_tls_free_tls_frag_buffer(&link_rec->auth_ctrl);
memset(&link_rec->auth_ctrl, 0, sizeof(link_rec->auth_ctrl));
}
bcmos_errno dpoe_eap_tls_init(f_dpoe_sec_auth_cb auth_cb, f_dpoe_sec_cert_trust_cb cert_trust_cb)
{
_auth_complete = auth_cb;
_cert_trust = cert_trust_cb;
return dpoe_sec_rng_seed();
}