aboutsummaryrefslogtreecommitdiffstats
path: root/src/libcommon/oap_client.c
diff options
context:
space:
mode:
authorNeels Hofmeyr <neels@hofmeyr.de>2017-07-04 23:08:44 +0200
committerNeels Hofmeyr <neels@hofmeyr.de>2017-08-27 03:52:43 +0200
commit218e4b4aa0fc6de842ff820dec8e97d1f083268a (patch)
tree268a6e509270b1c80a36dd1a526da41a9b01a8e0 /src/libcommon/oap_client.c
parent5ea6bfce56d6ae7be6d85e05b5e4eaebc94d1005 (diff)
move openbsc/* to repos root
This is the first step in creating this repository from the legacy openbsc.git. Like all other Osmocom repositories, keep the autoconf and automake files in the repository root. openbsc.git has been the sole exception, which ends now. Change-Id: I9c6f2a448d9cb1cc088cf1cf6918b69d7e69b4e7
Diffstat (limited to 'src/libcommon/oap_client.c')
-rw-r--r--src/libcommon/oap_client.c280
1 files changed, 280 insertions, 0 deletions
diff --git a/src/libcommon/oap_client.c b/src/libcommon/oap_client.c
new file mode 100644
index 000000000..5128ac119
--- /dev/null
+++ b/src/libcommon/oap_client.c
@@ -0,0 +1,280 @@
+/* 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <string.h>
+#include <errno.h>
+
+#include <osmocom/core/utils.h>
+#include <osmocom/crypt/auth.h>
+#include <osmocom/gsm/oap.h>
+
+#include <openbsc/oap_client.h>
+#include <openbsc/debug.h>
+
+int oap_client_init(struct oap_client_config *config,
+ struct oap_client_state *state)
+{
+ OSMO_ASSERT(state->state == OAP_UNINITIALIZED);
+
+ if (!config)
+ goto disable;
+
+ if (config->client_id == 0)
+ goto disable;
+
+ if (config->secret_k_present == 0) {
+ LOGP(DLOAP, LOGL_NOTICE, "OAP: client ID set, but secret K missing.\n");
+ goto disable;
+ }
+
+ if (config->secret_opc_present == 0) {
+ LOGP(DLOAP, 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 = 41; /* TODO use incrementing sequence nr */
+
+ memset(&vec, 0, sizeof(vec));
+ osmo_auth_gen_vec(&vec, &auth, rx_random);
+
+ if (vec.res_len != 8) {
+ LOGP(DLOAP, 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(DLOAP, LOGL_ERROR, "OAP: AUTN mismatch!\n");
+ LOGP(DLOAP, LOGL_INFO, "OAP: AUTN from server: %s\n",
+ osmo_hexdump_nospc(rx_autn, sizeof(vec.autn)));
+ LOGP(DLOAP, 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(DLOAP, 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(DLOAP, 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(DLOAP, LOGL_ERROR,
+ "Decoding OAP message failed with error '%s' (%d)\n",
+ get_value_string(gsm48_gmm_cause_names, -rc), -rc);
+ return -10;
+ }
+
+ switch (state->state) {
+ case OAP_UNINITIALIZED:
+ LOGP(DLOAP, LOGL_ERROR,
+ "Received OAP message %d, but the OAP client is"
+ " not initialized\n", oap_msg.message_type);
+ return -ENOTCONN;
+ case OAP_DISABLED:
+ LOGP(DLOAP, LOGL_ERROR,
+ "Received OAP message %d, but the OAP client is"
+ " disabled\n", oap_msg.message_type);
+ return -ENOTCONN;
+ default:
+ break;
+ }
+
+ 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(DLOAP, 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(DLOAP, LOGL_ERROR,
+ "Received invalid OAP message type for OAP client side: %d\n",
+ (int)oap_msg.message_type);
+ return -12;
+
+ default:
+ LOGP(DLOAP, LOGL_ERROR,
+ "Unknown OAP message type: %d\n",
+ (int)oap_msg.message_type);
+ return -13;
+ }
+
+ return 0;
+}