| /* |
| <: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(); |
| } |
| |