From 3d6b9f4fe99c4351ba8937f6d7df6030986b8a92 Mon Sep 17 00:00:00 2001 From: Neels Hofmeyr Date: Thu, 8 Dec 2016 22:29:27 +0100 Subject: move gprs/oap.c to libcommon/oap_client.c This is in preparation for libvlr. Related: OS#1592 Change-Id: Ib526df6d9de55a1e59a379d5e2c8541ed0ef67e3 --- openbsc/src/gprs/Makefile.am | 3 +- openbsc/src/gprs/oap.c | 261 ------------------------------------- openbsc/src/libcommon/Makefile.am | 1 + openbsc/src/libcommon/oap_client.c | 261 +++++++++++++++++++++++++++++++++++++ openbsc/tests/oap/Makefile.am | 1 - openbsc/tests/sgsn/Makefile.am | 1 - 6 files changed, 263 insertions(+), 265 deletions(-) delete mode 100644 openbsc/src/gprs/oap.c create mode 100644 openbsc/src/libcommon/oap_client.c diff --git a/openbsc/src/gprs/Makefile.am b/openbsc/src/gprs/Makefile.am index ab2517eea..d059aeed0 100644 --- a/openbsc/src/gprs/Makefile.am +++ b/openbsc/src/gprs/Makefile.am @@ -92,7 +92,6 @@ osmo_sgsn_SOURCES = \ sgsn_cdr.c \ sgsn_ares.c \ slhc.c \ - oap.c \ gprs_llc_xid.c \ v42bis.c \ $(NULL) @@ -137,9 +136,9 @@ osmo_gtphub_LDADD = \ gsup_test_client_SOURCES = \ gprs_gsup_client.c \ gsup_test_client.c \ - oap.c \ $(NULL) gsup_test_client_LDADD = \ + $(top_builddir)/src/libcommon/libcommon.a \ $(LIBOSMOCORE_LIBS) \ $(LIBOSMOGSM_LIBS) \ $(LIBOSMOVTY_LIBS) \ diff --git a/openbsc/src/gprs/oap.c b/openbsc/src/gprs/oap.c deleted file mode 100644 index 92140c17d..000000000 --- a/openbsc/src/gprs/oap.c +++ /dev/null @@ -1,261 +0,0 @@ -/* Osmocom Authentication Protocol API */ - -/* (C) 2015 by Sysmocom s.f.m.c. GmbH - * All Rights Reserved - * - * Author: Neels Hofmeyr - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation; either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - * - */ - -#include - -#include -#include -#include - -#include -#include - -int oap_client_init(struct oap_client_config *config, - struct oap_client_state *state) -{ - OSMO_ASSERT(state->state == OAP_UNINITIALIZED); - - if (config->client_id == 0) - goto disable; - - if (config->secret_k_present == 0) { - LOGP(DGPRS, LOGL_NOTICE, "OAP: client ID set, but secret K missing.\n"); - goto disable; - } - - if (config->secret_opc_present == 0) { - LOGP(DGPRS, LOGL_NOTICE, "OAP: client ID set, but secret OPC missing.\n"); - goto disable; - } - - state->client_id = config->client_id; - memcpy(state->secret_k, config->secret_k, sizeof(state->secret_k)); - memcpy(state->secret_opc, config->secret_opc, sizeof(state->secret_opc)); - state->state = OAP_INITIALIZED; - return 0; - -disable: - state->state = OAP_DISABLED; - return 0; -} - -/* From the given state and received RAND and AUTN octets, validate the - * server's authenticity and formulate the matching milenage reply octets in - * *tx_xres. The state is not modified. - * On success, and if tx_res is not NULL, exactly 8 octets will be written to - * *tx_res. If not NULL, tx_res must point at allocated memory of at least 8 - * octets. The caller will want to send XRES back to the server in a challenge - * response message and update the state. - * Return 0 on success; -1 if OAP is disabled; -2 if rx_random and rx_autn fail - * the authentication check; -3 for any other errors. */ -static int oap_evaluate_challenge(const struct oap_client_state *state, - const uint8_t *rx_random, - const uint8_t *rx_autn, - uint8_t *tx_xres) -{ - struct osmo_auth_vector vec; - - struct osmo_sub_auth_data auth = { - .type = OSMO_AUTH_TYPE_UMTS, - .algo = OSMO_AUTH_ALG_MILENAGE, - }; - - osmo_static_assert(sizeof(((struct osmo_sub_auth_data*)0)->u.umts.k) - == sizeof(state->secret_k), _secret_k_size_match); - osmo_static_assert(sizeof(((struct osmo_sub_auth_data*)0)->u.umts.opc) - == sizeof(state->secret_opc), _secret_opc_size_match); - - switch (state->state) { - case OAP_UNINITIALIZED: - case OAP_DISABLED: - return -1; - default: - break; - } - - memcpy(auth.u.umts.k, state->secret_k, sizeof(auth.u.umts.k)); - memcpy(auth.u.umts.opc, state->secret_opc, sizeof(auth.u.umts.opc)); - memset(auth.u.umts.amf, '\0', sizeof(auth.u.umts.amf)); - auth.u.umts.sqn = 42; /* TODO use incrementing sequence nr */ - - memset(&vec, 0, sizeof(vec)); - osmo_auth_gen_vec(&vec, &auth, rx_random); - - if (vec.res_len != 8) { - LOGP(DGPRS, LOGL_ERROR, "OAP: Expected XRES to be 8 octets, got %d\n", - vec.res_len); - return -3; - } - - if (osmo_constant_time_cmp(vec.autn, rx_autn, sizeof(vec.autn)) != 0) { - LOGP(DGPRS, LOGL_ERROR, "OAP: AUTN mismatch!\n"); - LOGP(DGPRS, LOGL_INFO, "OAP: AUTN from server: %s\n", - osmo_hexdump_nospc(rx_autn, sizeof(vec.autn))); - LOGP(DGPRS, LOGL_INFO, "OAP: AUTN expected: %s\n", - osmo_hexdump_nospc(vec.autn, sizeof(vec.autn))); - return -2; - } - - if (tx_xres != NULL) - memcpy(tx_xres, vec.res, 8); - return 0; -} - -struct msgb *oap_client_encoded(const struct osmo_oap_message *oap_msg) -{ - struct msgb *msg = msgb_alloc_headroom(1000, 64, __func__); - OSMO_ASSERT(msg); - osmo_oap_encode(msg, oap_msg); - return msg; -} - -/* Create a new msgb containing an OAP registration message. - * On error, return NULL. */ -static struct msgb* oap_msg_register(uint16_t client_id) -{ - struct osmo_oap_message oap_msg = {0}; - - if (client_id < 1) { - LOGP(DGPRS, LOGL_ERROR, "OAP: Invalid client ID: %d\n", client_id); - return NULL; - } - - oap_msg.message_type = OAP_MSGT_REGISTER_REQUEST; - oap_msg.client_id = client_id; - return oap_client_encoded(&oap_msg); -} - -int oap_client_register(struct oap_client_state *state, struct msgb **msg_tx) -{ - *msg_tx = oap_msg_register(state->client_id); - if (!(*msg_tx)) - return -1; - - state->state = OAP_REQUESTED_CHALLENGE; - return 0; -} - -/* Create a new msgb containing an OAP challenge response message. - * xres must point at 8 octets to return as challenge response. - * On error, return NULL. */ -static struct msgb* oap_msg_challenge_response(uint8_t *xres) -{ - struct osmo_oap_message oap_reply = {0}; - - oap_reply.message_type = OAP_MSGT_CHALLENGE_RESULT; - memcpy(oap_reply.xres, xres, sizeof(oap_reply.xres)); - oap_reply.xres_present = 1; - return oap_client_encoded(&oap_reply); -} - -static int handle_challenge(struct oap_client_state *state, - struct osmo_oap_message *oap_rx, - struct msgb **msg_tx) -{ - int rc; - uint8_t xres[8]; - - if (!(oap_rx->rand_present && oap_rx->autn_present)) { - LOGP(DGPRS, LOGL_ERROR, - "OAP challenge incomplete (rand_present: %d, autn_present: %d)\n", - oap_rx->rand_present, oap_rx->autn_present); - rc = -2; - goto failure; - } - - rc = oap_evaluate_challenge(state, - oap_rx->rand, - oap_rx->autn, - xres); - if (rc < 0) - goto failure; - - *msg_tx = oap_msg_challenge_response(xres); - if ((*msg_tx) == NULL) { - rc = -1; - goto failure; - } - - state->state = OAP_SENT_CHALLENGE_RESULT; - return 0; - -failure: - OSMO_ASSERT(rc < 0); - state->state = OAP_INITIALIZED; - return rc; -} - -int oap_client_handle(struct oap_client_state *state, - const struct msgb *msg_rx, struct msgb **msg_tx) -{ - uint8_t *data = msgb_l2(msg_rx); - size_t data_len = msgb_l2len(msg_rx); - struct osmo_oap_message oap_msg = {0}; - int rc = 0; - - *msg_tx = NULL; - - OSMO_ASSERT(data); - - rc = osmo_oap_decode(&oap_msg, data, data_len); - if (rc < 0) { - LOGP(DGPRS, LOGL_ERROR, - "Decoding OAP message failed with error '%s' (%d)\n", - get_value_string(gsm48_gmm_cause_names, -rc), -rc); - return -10; - } - - switch (oap_msg.message_type) { - case OAP_MSGT_CHALLENGE_REQUEST: - return handle_challenge(state, &oap_msg, msg_tx); - - case OAP_MSGT_REGISTER_RESULT: - /* successfully registered */ - state->state = OAP_REGISTERED; - break; - - case OAP_MSGT_REGISTER_ERROR: - LOGP(DGPRS, LOGL_ERROR, - "OAP registration failed\n"); - state->state = OAP_INITIALIZED; - if (state->registration_failures < 3) { - state->registration_failures ++; - return oap_client_register(state, msg_tx); - } - return -11; - - case OAP_MSGT_REGISTER_REQUEST: - case OAP_MSGT_CHALLENGE_RESULT: - LOGP(DGPRS, LOGL_ERROR, - "Received invalid OAP message type for OAP client side: %d\n", - (int)oap_msg.message_type); - return -12; - - default: - LOGP(DGPRS, LOGL_ERROR, - "Unknown OAP message type: %d\n", - (int)oap_msg.message_type); - return -13; - } - - return 0; -} diff --git a/openbsc/src/libcommon/Makefile.am b/openbsc/src/libcommon/Makefile.am index 6cfebc2da..6582d1ea3 100644 --- a/openbsc/src/libcommon/Makefile.am +++ b/openbsc/src/libcommon/Makefile.am @@ -23,6 +23,7 @@ libcommon_a_SOURCES = \ debug.c \ gsm_data.c \ gsm_data_shared.c \ + oap_client.c \ socket.c \ talloc_ctx.c \ gsm_subscriber_base.c \ diff --git a/openbsc/src/libcommon/oap_client.c b/openbsc/src/libcommon/oap_client.c new file mode 100644 index 000000000..92140c17d --- /dev/null +++ b/openbsc/src/libcommon/oap_client.c @@ -0,0 +1,261 @@ +/* Osmocom Authentication Protocol API */ + +/* (C) 2015 by Sysmocom s.f.m.c. GmbH + * All Rights Reserved + * + * Author: Neels Hofmeyr + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include + +#include +#include +#include + +#include +#include + +int oap_client_init(struct oap_client_config *config, + struct oap_client_state *state) +{ + OSMO_ASSERT(state->state == OAP_UNINITIALIZED); + + if (config->client_id == 0) + goto disable; + + if (config->secret_k_present == 0) { + LOGP(DGPRS, LOGL_NOTICE, "OAP: client ID set, but secret K missing.\n"); + goto disable; + } + + if (config->secret_opc_present == 0) { + LOGP(DGPRS, LOGL_NOTICE, "OAP: client ID set, but secret OPC missing.\n"); + goto disable; + } + + state->client_id = config->client_id; + memcpy(state->secret_k, config->secret_k, sizeof(state->secret_k)); + memcpy(state->secret_opc, config->secret_opc, sizeof(state->secret_opc)); + state->state = OAP_INITIALIZED; + return 0; + +disable: + state->state = OAP_DISABLED; + return 0; +} + +/* From the given state and received RAND and AUTN octets, validate the + * server's authenticity and formulate the matching milenage reply octets in + * *tx_xres. The state is not modified. + * On success, and if tx_res is not NULL, exactly 8 octets will be written to + * *tx_res. If not NULL, tx_res must point at allocated memory of at least 8 + * octets. The caller will want to send XRES back to the server in a challenge + * response message and update the state. + * Return 0 on success; -1 if OAP is disabled; -2 if rx_random and rx_autn fail + * the authentication check; -3 for any other errors. */ +static int oap_evaluate_challenge(const struct oap_client_state *state, + const uint8_t *rx_random, + const uint8_t *rx_autn, + uint8_t *tx_xres) +{ + struct osmo_auth_vector vec; + + struct osmo_sub_auth_data auth = { + .type = OSMO_AUTH_TYPE_UMTS, + .algo = OSMO_AUTH_ALG_MILENAGE, + }; + + osmo_static_assert(sizeof(((struct osmo_sub_auth_data*)0)->u.umts.k) + == sizeof(state->secret_k), _secret_k_size_match); + osmo_static_assert(sizeof(((struct osmo_sub_auth_data*)0)->u.umts.opc) + == sizeof(state->secret_opc), _secret_opc_size_match); + + switch (state->state) { + case OAP_UNINITIALIZED: + case OAP_DISABLED: + return -1; + default: + break; + } + + memcpy(auth.u.umts.k, state->secret_k, sizeof(auth.u.umts.k)); + memcpy(auth.u.umts.opc, state->secret_opc, sizeof(auth.u.umts.opc)); + memset(auth.u.umts.amf, '\0', sizeof(auth.u.umts.amf)); + auth.u.umts.sqn = 42; /* TODO use incrementing sequence nr */ + + memset(&vec, 0, sizeof(vec)); + osmo_auth_gen_vec(&vec, &auth, rx_random); + + if (vec.res_len != 8) { + LOGP(DGPRS, LOGL_ERROR, "OAP: Expected XRES to be 8 octets, got %d\n", + vec.res_len); + return -3; + } + + if (osmo_constant_time_cmp(vec.autn, rx_autn, sizeof(vec.autn)) != 0) { + LOGP(DGPRS, LOGL_ERROR, "OAP: AUTN mismatch!\n"); + LOGP(DGPRS, LOGL_INFO, "OAP: AUTN from server: %s\n", + osmo_hexdump_nospc(rx_autn, sizeof(vec.autn))); + LOGP(DGPRS, LOGL_INFO, "OAP: AUTN expected: %s\n", + osmo_hexdump_nospc(vec.autn, sizeof(vec.autn))); + return -2; + } + + if (tx_xres != NULL) + memcpy(tx_xres, vec.res, 8); + return 0; +} + +struct msgb *oap_client_encoded(const struct osmo_oap_message *oap_msg) +{ + struct msgb *msg = msgb_alloc_headroom(1000, 64, __func__); + OSMO_ASSERT(msg); + osmo_oap_encode(msg, oap_msg); + return msg; +} + +/* Create a new msgb containing an OAP registration message. + * On error, return NULL. */ +static struct msgb* oap_msg_register(uint16_t client_id) +{ + struct osmo_oap_message oap_msg = {0}; + + if (client_id < 1) { + LOGP(DGPRS, LOGL_ERROR, "OAP: Invalid client ID: %d\n", client_id); + return NULL; + } + + oap_msg.message_type = OAP_MSGT_REGISTER_REQUEST; + oap_msg.client_id = client_id; + return oap_client_encoded(&oap_msg); +} + +int oap_client_register(struct oap_client_state *state, struct msgb **msg_tx) +{ + *msg_tx = oap_msg_register(state->client_id); + if (!(*msg_tx)) + return -1; + + state->state = OAP_REQUESTED_CHALLENGE; + return 0; +} + +/* Create a new msgb containing an OAP challenge response message. + * xres must point at 8 octets to return as challenge response. + * On error, return NULL. */ +static struct msgb* oap_msg_challenge_response(uint8_t *xres) +{ + struct osmo_oap_message oap_reply = {0}; + + oap_reply.message_type = OAP_MSGT_CHALLENGE_RESULT; + memcpy(oap_reply.xres, xres, sizeof(oap_reply.xres)); + oap_reply.xres_present = 1; + return oap_client_encoded(&oap_reply); +} + +static int handle_challenge(struct oap_client_state *state, + struct osmo_oap_message *oap_rx, + struct msgb **msg_tx) +{ + int rc; + uint8_t xres[8]; + + if (!(oap_rx->rand_present && oap_rx->autn_present)) { + LOGP(DGPRS, LOGL_ERROR, + "OAP challenge incomplete (rand_present: %d, autn_present: %d)\n", + oap_rx->rand_present, oap_rx->autn_present); + rc = -2; + goto failure; + } + + rc = oap_evaluate_challenge(state, + oap_rx->rand, + oap_rx->autn, + xres); + if (rc < 0) + goto failure; + + *msg_tx = oap_msg_challenge_response(xres); + if ((*msg_tx) == NULL) { + rc = -1; + goto failure; + } + + state->state = OAP_SENT_CHALLENGE_RESULT; + return 0; + +failure: + OSMO_ASSERT(rc < 0); + state->state = OAP_INITIALIZED; + return rc; +} + +int oap_client_handle(struct oap_client_state *state, + const struct msgb *msg_rx, struct msgb **msg_tx) +{ + uint8_t *data = msgb_l2(msg_rx); + size_t data_len = msgb_l2len(msg_rx); + struct osmo_oap_message oap_msg = {0}; + int rc = 0; + + *msg_tx = NULL; + + OSMO_ASSERT(data); + + rc = osmo_oap_decode(&oap_msg, data, data_len); + if (rc < 0) { + LOGP(DGPRS, LOGL_ERROR, + "Decoding OAP message failed with error '%s' (%d)\n", + get_value_string(gsm48_gmm_cause_names, -rc), -rc); + return -10; + } + + switch (oap_msg.message_type) { + case OAP_MSGT_CHALLENGE_REQUEST: + return handle_challenge(state, &oap_msg, msg_tx); + + case OAP_MSGT_REGISTER_RESULT: + /* successfully registered */ + state->state = OAP_REGISTERED; + break; + + case OAP_MSGT_REGISTER_ERROR: + LOGP(DGPRS, LOGL_ERROR, + "OAP registration failed\n"); + state->state = OAP_INITIALIZED; + if (state->registration_failures < 3) { + state->registration_failures ++; + return oap_client_register(state, msg_tx); + } + return -11; + + case OAP_MSGT_REGISTER_REQUEST: + case OAP_MSGT_CHALLENGE_RESULT: + LOGP(DGPRS, LOGL_ERROR, + "Received invalid OAP message type for OAP client side: %d\n", + (int)oap_msg.message_type); + return -12; + + default: + LOGP(DGPRS, LOGL_ERROR, + "Unknown OAP message type: %d\n", + (int)oap_msg.message_type); + return -13; + } + + return 0; +} diff --git a/openbsc/tests/oap/Makefile.am b/openbsc/tests/oap/Makefile.am index bb9ebb247..c17abb55f 100644 --- a/openbsc/tests/oap/Makefile.am +++ b/openbsc/tests/oap/Makefile.am @@ -27,7 +27,6 @@ oap_test_SOURCES = \ $(NULL) oap_test_LDADD = \ - $(top_builddir)/src/gprs/oap.o \ $(top_builddir)/src/gprs/gprs_utils.o \ $(top_builddir)/src/libcommon/libcommon.a \ $(LIBOSMOCORE_LIBS) \ diff --git a/openbsc/tests/sgsn/Makefile.am b/openbsc/tests/sgsn/Makefile.am index 9e1a5a8e3..00b80f637 100644 --- a/openbsc/tests/sgsn/Makefile.am +++ b/openbsc/tests/sgsn/Makefile.am @@ -54,7 +54,6 @@ sgsn_test_LDADD = \ $(top_builddir)/src/gprs/gprs_utils.o \ $(top_builddir)/src/gprs/gprs_subscriber.o \ $(top_builddir)/src/gprs/gprs_gb_parse.o \ - $(top_builddir)/src/gprs/oap.o \ $(top_builddir)/src/gprs/gprs_llc_xid.o \ $(top_builddir)/src/gprs/gprs_sndcp_xid.o \ $(top_builddir)/src/gprs/slhc.o \ -- cgit v1.2.3