diff options
-rw-r--r-- | include/Makefile.am | 1 | ||||
-rw-r--r-- | include/osmocom/gsm/oap_client.h | 82 | ||||
-rw-r--r-- | src/gsm/Makefile.am | 2 | ||||
-rw-r--r-- | src/gsm/libosmogsm.map | 5 | ||||
-rw-r--r-- | src/gsm/oap_client.c | 280 |
5 files changed, 369 insertions, 1 deletions
diff --git a/include/Makefile.am b/include/Makefile.am index 38ba14cd..ef8ec656 100644 --- a/include/Makefile.am +++ b/include/Makefile.am @@ -100,6 +100,7 @@ nobase_include_HEADERS = \ osmocom/gsm/prim.h \ osmocom/gsm/l1sap.h \ osmocom/gsm/oap.h \ + osmocom/gsm/oap_client.h \ osmocom/gsm/protocol/gsm_03_40.h \ osmocom/gsm/protocol/gsm_03_41.h \ osmocom/gsm/protocol/gsm_04_08.h \ diff --git a/include/osmocom/gsm/oap_client.h b/include/osmocom/gsm/oap_client.h new file mode 100644 index 00000000..80c86d5d --- /dev/null +++ b/include/osmocom/gsm/oap_client.h @@ -0,0 +1,82 @@ +/* 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/>. + * + */ + +#pragma once + +#include <stdint.h> + +struct msgb; +struct osmo_oap_message; + +/* This is the config part for vty. It is essentially copied in + * oap_client_state, where values are copied over once the config is + * considered valid. */ +struct oap_client_config { + uint16_t client_id; + int secret_k_present; + uint8_t secret_k[16]; + int secret_opc_present; + uint8_t secret_opc[16]; +}; + +/* The runtime state of the OAP client. client_id and the secrets are in fact + * duplicated from oap_client_config, so that a separate validation of the + * config data is possible, and so that only a struct oap_client_state* is + * passed around. */ +struct oap_client_state { + enum { + OAP_UNINITIALIZED = 0, /* just allocated. */ + OAP_DISABLED, /* disabled by config. */ + OAP_INITIALIZED, /* enabled, config is valid. */ + OAP_REQUESTED_CHALLENGE, + OAP_SENT_CHALLENGE_RESULT, + OAP_REGISTERED + } state; + uint16_t client_id; + uint8_t secret_k[16]; + uint8_t secret_opc[16]; + int registration_failures; +}; + +/* From config, initialize state. Return 0 on success. */ +int oap_client_init(struct oap_client_config *config, + struct oap_client_state *state); + +/* Construct an OAP registration message and return in *msg_tx. Use + * state->client_id and update state->state. + * Return 0 on success, or a negative value on error. + * If an error is returned, *msg_tx is guaranteed to be NULL. */ +int oap_client_register(struct oap_client_state *state, struct msgb **msg_tx); + +/* Decode and act on a received OAP message msg_rx. Update state->state. If a + * non-NULL pointer is returned in *msg_tx, that msgb should be sent to the OAP + * server (and freed) by the caller. The received msg_rx is not freed. + * Return 0 on success, or a negative value on error. + * If an error is returned, *msg_tx is guaranteed to be NULL. */ +int oap_client_handle(struct oap_client_state *state, + const struct msgb *msg_rx, struct msgb **msg_tx); + +/* Allocate a msgb and in it, return the encoded oap_client_msg. Return + * NULL on error. (Like oap_client_encode(), but also allocates a msgb.) + * About the name: the idea is do_something(oap_client_encoded(my_struct)) + */ +struct msgb *oap_client_encoded(const struct osmo_oap_message *oap_client_msg); diff --git a/src/gsm/Makefile.am b/src/gsm/Makefile.am index 900fcfa1..29299a64 100644 --- a/src/gsm/Makefile.am +++ b/src/gsm/Makefile.am @@ -30,7 +30,7 @@ libgsmint_la_SOURCES = a5.c rxlev_stat.c tlv_parser.c comp128.c comp128v23.c \ milenage/aes-internal.c milenage/aes-internal-enc.c \ milenage/milenage.c gan.c ipa.c gsm0341.c apn.c \ gsup.c gprs_gea.c gsm0503_conv.c oap.c gsm0808_utils.c \ - gsm23003.c mncc.c bts_features.c + gsm23003.c mncc.c bts_features.c oap_client.c libgsmint_la_LDFLAGS = -no-undefined libgsmint_la_LIBADD = $(top_builddir)/src/libosmocore.la diff --git a/src/gsm/libosmogsm.map b/src/gsm/libosmogsm.map index b44cfd25..6eb60cc2 100644 --- a/src/gsm/libosmogsm.map +++ b/src/gsm/libosmogsm.map @@ -493,5 +493,10 @@ osmo_mncc_stringify; osmo_mncc_names; _osmo_mncc_log; +oap_client_encoded; +oap_client_handle; +oap_client_init; +oap_client_register; + local: *; }; diff --git a/src/gsm/oap_client.c b/src/gsm/oap_client.c new file mode 100644 index 00000000..2227a3ce --- /dev/null +++ b/src/gsm/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/core/logging.h> +#include <osmocom/crypt/auth.h> +#include <osmocom/gsm/oap.h> + +#include <osmocom/gsm/oap_client.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; +} |