From 2f898265d0feb26363960de98929e4819dda546c Mon Sep 17 00:00:00 2001 From: Eric Date: Wed, 19 May 2021 18:57:50 +0200 Subject: [PATCH] add support for multiple encryption algorithms and a5/4 Change-Id: Ie6700c4e9d2df1eb5fde1b971e287b62668cc2de Related: SYS#5324 --- include/osmocom/sgsn/gprs_sgsn.h | 1 + include/osmocom/sgsn/sgsn.h | 2 +- src/sgsn/gprs_gmm.c | 48 +++++++++++++++++++--- src/sgsn/gprs_llc.c | 11 ++++- src/sgsn/gprs_sgsn.c | 3 -- src/sgsn/sgsn_vty.c | 69 ++++++++++++++++++++++++++++---- tests/osmo-sgsn_test-nodes.vty | 2 +- tests/sgsn/sgsn_test.c | 1 + 8 files changed, 118 insertions(+), 19 deletions(-) diff --git a/include/osmocom/sgsn/gprs_sgsn.h b/include/osmocom/sgsn/gprs_sgsn.h index 289e0c474..c176494b1 100644 --- a/include/osmocom/sgsn/gprs_sgsn.h +++ b/include/osmocom/sgsn/gprs_sgsn.h @@ -175,6 +175,7 @@ struct sgsn_mm_ctx { /* Iu: CK, IK, KSI */ /* CKSN */ enum gprs_ciph_algo ciph_algo; + uint8_t ue_cipher_mask; /* Auth & Ciphering Request reference from 3GPP TS 24.008 § 10.5.5.19: */ uint8_t ac_ref_nr_used; diff --git a/include/osmocom/sgsn/sgsn.h b/include/osmocom/sgsn/sgsn.h index b686c7cd5..5b29873b6 100644 --- a/include/osmocom/sgsn/sgsn.h +++ b/include/osmocom/sgsn/sgsn.h @@ -76,7 +76,7 @@ struct sgsn_config { struct gprs_ns2_inst *nsi; enum sgsn_auth_policy auth_policy; - enum gprs_ciph_algo cipher; + uint8_t cipher_support_mask; struct llist_head imsi_acl; struct sockaddr_in gsup_server_addr; diff --git a/src/sgsn/gprs_gmm.c b/src/sgsn/gprs_gmm.c index c5e927a87..1f68558ef 100644 --- a/src/sgsn/gprs_gmm.c +++ b/src/sgsn/gprs_gmm.c @@ -445,6 +445,17 @@ static bool mmctx_is_r99(const struct sgsn_mm_ctx *mm) return false; } +static enum gprs_ciph_algo gprs_ms_net_select_best_gea(uint8_t net_mask, uint8_t ms_mask) { + uint8_t common_mask = net_mask & ms_mask; + uint8_t r = 0; + + while (common_mask >>= 1) { + r++; + } + + return r; +} + /* 3GPP TS 24.008 § 9.4.9: Authentication and Ciphering Request */ int gsm48_tx_gmm_auth_ciph_req(struct sgsn_mm_ctx *mm, const struct osmo_auth_vector *vec, @@ -1147,6 +1158,21 @@ static void mmctx_handle_rat_change(struct sgsn_mm_ctx *mmctx, struct msgb *msg, } +static uint8_t gprs_ms_net_cap_gea_mask(const uint8_t *ms_net_cap, uint8_t cap_len) +{ + uint8_t mask = (1 << GPRS_ALGO_GEA0); + mask |= (0x80 & ms_net_cap[0]) ? (1 << GPRS_ALGO_GEA1) : 0; + + if (cap_len < 2) + return mask; + + /* extended GEA bits start from 2nd bit of the next byte */ + mask |= (0x40 & ms_net_cap[1]) ? (1 << GPRS_ALGO_GEA2) : 0; + mask |= (0x20 & ms_net_cap[1]) ? (1 << GPRS_ALGO_GEA3) : 0; + mask |= (0x10 & ms_net_cap[1]) ? (1 << GPRS_ALGO_GEA4) : 0; + return mask; +} + /* 3GPP TS 24.008 § 9.4.1 Attach request */ static int gsm48_rx_gmm_att_req(struct sgsn_mm_ctx *ctx, struct msgb *msg, struct gprs_llc_llme *llme) @@ -1290,15 +1316,27 @@ static int gsm48_rx_gmm_att_req(struct sgsn_mm_ctx *ctx, struct msgb *msg, ctx->ms_radio_access_capa.len); ctx->ms_network_capa.len = msnc_len; memcpy(ctx->ms_network_capa.buf, msnc, msnc_len); - if (!gprs_ms_net_cap_gea_supported(ctx->ms_network_capa.buf, msnc_len, - ctx->ciph_algo)) { + + ctx->ue_cipher_mask = gprs_ms_net_cap_gea_mask(ctx->ms_network_capa.buf, msnc_len); + + if (!(ctx->ue_cipher_mask & sgsn->cfg.cipher_support_mask)) { reject_cause = GMM_CAUSE_PROTO_ERR_UNSPEC; LOGMMCTXP(LOGL_NOTICE, ctx, "Rejecting ATTACH REQUEST with MI " - "%s because MS do not support required %s " - "encryption\n", mi_log_string, - get_value_string(gprs_cipher_names,ctx->ciph_algo)); + "%s because MS do not support required encryption, mask UE:0x%02x NW:0x%02x \n", + mi_log_string, ctx->ue_cipher_mask, sgsn->cfg.cipher_support_mask); goto rejected; } + + /* just assume that everythig is fine if the phone offers a5/4: + * it requires a valid umts security context which we can only have after + * 1) IDENTITY REQUEST to know what to ask the HLR for + * 2) and AUTHENTICATION AND CIPHERING REQUEST + * ... but 2) already requires selecting a cipher mode. + * So let's just assume we will have the auth data required to make it work. + */ + + ctx->ciph_algo = gprs_ms_net_select_best_gea(ctx->ue_cipher_mask, sgsn->cfg.cipher_support_mask); + #ifdef PTMSI_ALLOC /* Allocate a new P-TMSI (+ P-TMSI signature) and update TLLI */ ptmsi_update(ctx); diff --git a/src/sgsn/gprs_llc.c b/src/sgsn/gprs_llc.c index 4fbf211d4..eea1cecfa 100644 --- a/src/sgsn/gprs_llc.c +++ b/src/sgsn/gprs_llc.c @@ -42,6 +42,8 @@ #include #include +#include + const struct value_string gprs_llc_llme_state_names[] = { { GPRS_LLMS_UNASSIGNED, "UNASSIGNED" }, { GPRS_LLMS_ASSIGNED, "ASSIGNED" }, @@ -1042,8 +1044,13 @@ void gprs_llme_copy_key(struct sgsn_mm_ctx *mm, struct gprs_llc_llme *llme) llme->algo = mm->ciph_algo; if (llme->cksn != mm->auth_triplet.key_seq && mm->auth_triplet.key_seq != GSM_KEY_SEQ_INVAL) { - memcpy(llme->kc, mm->auth_triplet.vec.kc, - gprs_cipher_key_length(mm->ciph_algo)); + + /* gea4 needs kc128 */ + if (mm->ciph_algo == GPRS_ALGO_GEA4) + osmo_kdf_kc128(mm->auth_triplet.vec.ck, mm->auth_triplet.vec.ik, llme->kc); + else + memcpy(llme->kc, mm->auth_triplet.vec.kc, gprs_cipher_key_length(mm->ciph_algo)); + llme->cksn = mm->auth_triplet.key_seq; } } else diff --git a/src/sgsn/gprs_sgsn.c b/src/sgsn/gprs_sgsn.c index f74425711..304ecc516 100644 --- a/src/sgsn/gprs_sgsn.c +++ b/src/sgsn/gprs_sgsn.c @@ -293,11 +293,8 @@ struct sgsn_mm_ctx *sgsn_mm_ctx_alloc_gb(uint32_t tlli, memcpy(&ctx->ra, raid, sizeof(ctx->ra)); ctx->ran_type = MM_CTX_T_GERAN_Gb; ctx->gb.tlli = tlli; - ctx->ciph_algo = sgsn->cfg.cipher; osmo_fsm_inst_update_id_f(ctx->gb.mm_state_fsm, "%" PRIu32, tlli); - LOGMMCTXP(LOGL_DEBUG, ctx, "Allocated with %s cipher.\n", - get_value_string(gprs_cipher_names, ctx->ciph_algo)); return ctx; } diff --git a/src/sgsn/sgsn_vty.c b/src/sgsn/sgsn_vty.c index 3b2a04a85..30bd864ee 100644 --- a/src/sgsn/sgsn_vty.c +++ b/src/sgsn/sgsn_vty.c @@ -206,6 +206,7 @@ static int config_write_sgsn(struct vty *vty) struct apn_ctx *actx; struct ares_addr_node *server; struct sgsn_mme_ctx *mme; + int i; vty_out(vty, "sgsn%s", VTY_NEWLINE); @@ -236,10 +237,15 @@ static int config_write_sgsn(struct vty *vty) for (server = sgsn->ares_servers; server; server = server->next) vty_out(vty, " grx-dns-add %s%s", inet_ntoa(server->addr.addr4), VTY_NEWLINE); - if (g_cfg->cipher != GPRS_ALGO_GEA0) - vty_out(vty, " encryption %s%s", - get_value_string(gprs_cipher_names, g_cfg->cipher), - VTY_NEWLINE); + if (g_cfg->cipher_support_mask != 0) { + vty_out(vty, " encryption gea"); + + for (i = 0; i < _GPRS_ALGO_NUM; i++) + if (g_cfg->cipher_support_mask >> i & 1) + vty_out(vty, " %u", i); + + vty_out(vty, "%s", VTY_NEWLINE); + } if (g_cfg->sgsn_ipa_name) vty_out(vty, " gsup ipa-name %s%s", g_cfg->sgsn_ipa_name, VTY_NEWLINE); if (g_cfg->gsup_server_addr.sin_addr.s_addr) @@ -721,15 +727,19 @@ DEFUN(imsi_acl, cfg_imsi_acl_cmd, return CMD_SUCCESS; } -DEFUN(cfg_encrypt, cfg_encrypt_cmd, +DEFUN_DEPRECATED(cfg_encrypt, cfg_encrypt_cmd, "encryption (GEA0|GEA1|GEA2|GEA3|GEA4)", "Set encryption algorithm for SGSN\n" "Use GEA0 (no encryption)\n" "Use GEA1\nUse GEA2\nUse GEA3\nUse GEA4\n") { enum gprs_ciph_algo c = get_string_value(gprs_cipher_names, argv[0]); + + if (strcmp(argv[0], "gea") == 0) + return CMD_SUCCESS; + if (c != GPRS_ALGO_GEA0) { - if (!gprs_cipher_supported(c)) { + if (gprs_cipher_supported(c) <= 0) { vty_out(vty, "%% cipher %s is unsupported in current version%s", argv[0], VTY_NEWLINE); return CMD_WARNING; } @@ -741,7 +751,46 @@ DEFUN(cfg_encrypt, cfg_encrypt_cmd, } } - g_cfg->cipher = c; + g_cfg->cipher_support_mask |= (1 << c); + + return CMD_SUCCESS; +} + +DEFUN(cfg_encrypt2, cfg_encrypt2_cmd, + "encryption gea <0-4> [<0-4>] [<0-4>] [<0-4>] [<0-4>]", + "Set encryption algorithms for SGSN\n" + "GPRS Encryption Algorithm\n" + "GEAn Algorithm Number\n" + "GEAn Algorithm Number\n" + "GEAn Algorithm Number\n" + "GEAn Algorithm Number\n" + "GEAn Algorithm Number\n") +{ + int i = 0; + + g_cfg->cipher_support_mask = 0; + for (i = 0; i < argc; i++) + g_cfg->cipher_support_mask |= (1 << atoi(argv[i])); + + for (i = 0; i < _GPRS_ALGO_NUM; i++) { + if (g_cfg->cipher_support_mask >> i & 1) { + + if (i == GPRS_ALGO_GEA0) + continue; + + if (gprs_cipher_supported(i) <= 0) { + vty_out(vty, "%% cipher %d is unsupported in current version%s", i, VTY_NEWLINE); + return CMD_ERR_INCOMPLETE; + } + + if (!g_cfg->require_authentication) { + vty_out(vty, "%% unable to use encryption %s without authentication: please adjust auth-policy%s", + argv[i], VTY_NEWLINE); + return CMD_ERR_INCOMPLETE; + } + + } + } return CMD_SUCCESS; } @@ -1640,7 +1689,11 @@ int sgsn_vty_init(struct sgsn_config *cfg) install_element(SGSN_NODE, &cfg_imsi_acl_cmd); install_element(SGSN_NODE, &cfg_auth_policy_cmd); install_element(SGSN_NODE, &cfg_authentication_cmd); + + /* order matters here: ensure we attempt to parse our new command first! */ + install_element(SGSN_NODE, &cfg_encrypt2_cmd); install_element(SGSN_NODE, &cfg_encrypt_cmd); + install_element(SGSN_NODE, &cfg_gsup_ipa_name_cmd); install_element(SGSN_NODE, &cfg_gsup_remote_ip_cmd); install_element(SGSN_NODE, &cfg_gsup_remote_port_cmd); @@ -1691,6 +1744,8 @@ int sgsn_parse_config(const char *config_file) /* make sure sgsn_vty_init() was called before this */ OSMO_ASSERT(g_cfg); + g_cfg->cipher_support_mask = 0x1; /* support GEA0 by default unless specific encryption config exists */ + rc = vty_read_config_file(config_file, NULL); if (rc < 0) { fprintf(stderr, "Failed to parse the config file: '%s'\n", config_file); diff --git a/tests/osmo-sgsn_test-nodes.vty b/tests/osmo-sgsn_test-nodes.vty index f88954146..f2ed2dce7 100644 --- a/tests/osmo-sgsn_test-nodes.vty +++ b/tests/osmo-sgsn_test-nodes.vty @@ -35,7 +35,7 @@ OsmoSGSN(config-sgsn)# list imsi-acl (add|del) IMSI auth-policy (accept-all|closed|acl-only|remote) authentication (optional|required) - encryption (GEA0|GEA1|GEA2|GEA3|GEA4) + encryption gea <0-4> [<0-4>] [<0-4>] [<0-4>] [<0-4>] gsup ipa-name NAME gsup remote-ip A.B.C.D gsup remote-port <0-65535> diff --git a/tests/sgsn/sgsn_test.c b/tests/sgsn/sgsn_test.c index 63a7f3eed..562ae845d 100644 --- a/tests/sgsn/sgsn_test.c +++ b/tests/sgsn/sgsn_test.c @@ -48,6 +48,7 @@ static struct sgsn_instance sgsn_inst = { .cfg = { .gtp_statedir = "./", .auth_policy = SGSN_AUTH_POLICY_CLOSED, + .cipher_support_mask = 0x1, }, }; struct sgsn_instance *sgsn = &sgsn_inst;