libosmocore/src/gsm/auth_milenage.c

196 lines
6.3 KiB
C
Raw Normal View History

/*! \file auth_milenage.c
* GSM/GPRS/3G authentication core infrastructure */
/*
* (C) 2011 by Harald Welte <laforge@gnumonks.org>
*
* All Rights Reserved
*
* SPDX-License-Identifier: GPL-2.0+
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 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 General Public License for more details.
*
*/
#include <errno.h>
#include <osmocom/crypt/auth.h>
#include <osmocom/core/bits.h>
#include "milenage/common.h"
#include "milenage/milenage.h"
/*! \addtogroup auth
* @{
*/
static const uint8_t *gen_opc_if_needed(const struct osmo_sub_auth_data2 *aud, uint8_t *gen_opc)
{
int rc;
/* Check if we only know OP and compute OPC if required */
if (aud->type == OSMO_AUTH_TYPE_UMTS && aud->u.umts.opc_is_op) {
rc = milenage_opc_gen(gen_opc, aud->u.umts.k, aud->u.umts.opc);
if (rc < 0)
return NULL;
return gen_opc;
} else
return aud->u.umts.opc;
}
static int milenage_gen_vec(struct osmo_auth_vector *vec,
struct osmo_sub_auth_data2 *aud,
const uint8_t *_rand)
{
size_t res_len = sizeof(vec->res);
osmo_auth_gen_vec: UMTS auth: store last used SQN, not next Prepare for the implementation of splitting SQN increments in SEQ and an IND part; particularly to clearly show where the changes in auth/milenage_test's expectations originate. Rationale: the source of UMTS auth vectors, for us usually OsmoHLR, typically stores the last used SQN, not the next one to be used. Particularly with the upcoming fix of the SQN scheme, this change is important: the next SQN will depend on which entity asks for it, because each auth consumer may have a particular slot in the IND part of SQN. It does not make sense to store the next SQN, because we will not know which consumer that will be for. The milenage_test has always calculated a tuple for SQN == 34. To account for the increment now happening before calculating a tuple, lower the test_aud->sqn by one to 0x21 == 33, so that it is still calculating for SQN == 34. Because we are no longer incrementing SQN after the tuple is generated, milenage_test's expected output after doing an AUTS resync to 31 changes to the next SQN = 32, the SQN used for the generated tuple. (BTW, a subsequent patch will illustrate AUTS in detail.) osmo-auc-gen now needs to pass the user requested SQN less one, because the SQN will be incremented befor generating the auth vector. Also the SQN remains the same after generating, so SQN output needs less decrementing. Note that the expected output for osmo-auc-gen_test remains unchanged, hence the same input arguments (particularly -s <sqn> and -A <auts>) still produce the same results. Note: osmo-hlr regression tests will require adjustments when this patch is merged, because it must now pass desired_sqn - 1 instead of just desired_sqn. See osmo-hlr change-id I4ec5a578537acb1d9e1ebfe00a72417fc3ca5894 . Related: OS#1968 Change-Id: Iadf43f21e0605e9e85f7e8026c40985f7ceff1a3
2017-03-13 16:36:17 +00:00
uint64_t next_sqn;
uint8_t gen_opc[16];
const uint8_t *opc;
uint8_t sqn[6];
uint64_t ind_mask;
uint64_t seq_1;
OSMO_ASSERT(aud->algo == OSMO_AUTH_ALG_MILENAGE);
if (aud->u.umts.k_len != 16)
return -EINVAL;
if (aud->u.umts.opc_len != 16)
return -EINVAL;
if (vec->res_len != 4 && vec->res_len != 8)
return -EINVAL;
opc = gen_opc_if_needed(aud, gen_opc);
if (!opc)
return -1;
/* Determine next SQN, according to 3GPP TS 33.102:
* SQN consists of SEQ and a lower significant part of IND bits:
*
* |----------SEQ------------|
* |------------------------SQN-----------|
* |-----IND----|
*
* The IND part is used as "slots": e.g. a given HLR client will always
* get the same IND part, called ind here, with incrementing SEQ. In
* the USIM, each IND slot enforces that its SEQ are used in ascending
* order -- as long as that constraint is satisfied, the SQN may jump
* forwards and backwards. For example, for ind_bitlen == 5, asking the
* USIM for SQN = 32, 64, 33 is allowed, because 32 and 64 are
* SEQ || (ind == 0), and though 33 is below 64, it is ind == 1 and
* allowed. Not allowed would be 32, 96, 64, because 64 would go
* backwards after 96, both being ind == 0.
*
* From the last used SQN, we want to increment SEQ + 1, and then pick
* the matching IND part.
*
* IND size is suggested in TS 33.102 as 5 bits. SQN is 48 bits long.
* If ind_bitlen is passed too large here, the algorithms will break
* down. But at which point should we return an error? A sane limit
* seems to be ind_bitlen == 10, but to protect against failure,
* limiting ind_bitlen to 28 is enough, 28 being the number of bits
* suggested for the delta in 33.102, which is discussed to still
* require 2^15 > 32000 authentications to wrap the SQN back to the
* start.
*
* Note that if a caller with ind == 1 generates N vectors, the SQN
* stored after this will reflect SEQ + N. If then another caller with
* ind == 2 generates another N vectors, this will then use SEQ + N
* onwards and end up with SEQ + N + N. In other words, most of each
* SEQ's IND slots will remain unused. When looking at SQN being 48
* bits wide, after dropping ind_bitlen (say 5) from it, we will still
* have a sequence range of 2^43 = 8.8e12, eight trillion sequences,
* which is large enough to not bother further. With the maximum
* ind_bitlen of 28 enforced below, we still get more than 1 million
* sequences, which is also sufficiently large.
*
* An ind_bitlen of zero may be passed from legacy callers that are not
* aware of the IND extension. For these, below algorithm works out as
* before, simply incrementing SQN by 1.
*
* This is also a mechanism for tools like the osmo-auc-gen to directly
* request a given SQN to be used. With ind_bitlen == 0 the caller can
* be sure that this code will increment SQN by exactly one before
* generating a tuple, thus a caller would simply pass
* { .ind_bitlen = 0, .ind = 0, .sqn = (desired_sqn - 1) }
*/
if (aud->u.umts.ind_bitlen > OSMO_MILENAGE_IND_BITLEN_MAX)
return -2;
seq_1 = 1LL << aud->u.umts.ind_bitlen;
ind_mask = ~(seq_1 - 1);
/* the ind index must not affect the SEQ part */
if (aud->u.umts.ind >= seq_1)
return -3;
osmo_auth_gen_vec: UMTS auth: store last used SQN, not next Prepare for the implementation of splitting SQN increments in SEQ and an IND part; particularly to clearly show where the changes in auth/milenage_test's expectations originate. Rationale: the source of UMTS auth vectors, for us usually OsmoHLR, typically stores the last used SQN, not the next one to be used. Particularly with the upcoming fix of the SQN scheme, this change is important: the next SQN will depend on which entity asks for it, because each auth consumer may have a particular slot in the IND part of SQN. It does not make sense to store the next SQN, because we will not know which consumer that will be for. The milenage_test has always calculated a tuple for SQN == 34. To account for the increment now happening before calculating a tuple, lower the test_aud->sqn by one to 0x21 == 33, so that it is still calculating for SQN == 34. Because we are no longer incrementing SQN after the tuple is generated, milenage_test's expected output after doing an AUTS resync to 31 changes to the next SQN = 32, the SQN used for the generated tuple. (BTW, a subsequent patch will illustrate AUTS in detail.) osmo-auc-gen now needs to pass the user requested SQN less one, because the SQN will be incremented befor generating the auth vector. Also the SQN remains the same after generating, so SQN output needs less decrementing. Note that the expected output for osmo-auc-gen_test remains unchanged, hence the same input arguments (particularly -s <sqn> and -A <auts>) still produce the same results. Note: osmo-hlr regression tests will require adjustments when this patch is merged, because it must now pass desired_sqn - 1 instead of just desired_sqn. See osmo-hlr change-id I4ec5a578537acb1d9e1ebfe00a72417fc3ca5894 . Related: OS#1968 Change-Id: Iadf43f21e0605e9e85f7e8026c40985f7ceff1a3
2017-03-13 16:36:17 +00:00
/* keep the incremented SQN local until gsm_milenage() succeeded. */
next_sqn = ((aud->u.umts.sqn + seq_1) & ind_mask) + aud->u.umts.ind;
osmo_auth_gen_vec: UMTS auth: store last used SQN, not next Prepare for the implementation of splitting SQN increments in SEQ and an IND part; particularly to clearly show where the changes in auth/milenage_test's expectations originate. Rationale: the source of UMTS auth vectors, for us usually OsmoHLR, typically stores the last used SQN, not the next one to be used. Particularly with the upcoming fix of the SQN scheme, this change is important: the next SQN will depend on which entity asks for it, because each auth consumer may have a particular slot in the IND part of SQN. It does not make sense to store the next SQN, because we will not know which consumer that will be for. The milenage_test has always calculated a tuple for SQN == 34. To account for the increment now happening before calculating a tuple, lower the test_aud->sqn by one to 0x21 == 33, so that it is still calculating for SQN == 34. Because we are no longer incrementing SQN after the tuple is generated, milenage_test's expected output after doing an AUTS resync to 31 changes to the next SQN = 32, the SQN used for the generated tuple. (BTW, a subsequent patch will illustrate AUTS in detail.) osmo-auc-gen now needs to pass the user requested SQN less one, because the SQN will be incremented befor generating the auth vector. Also the SQN remains the same after generating, so SQN output needs less decrementing. Note that the expected output for osmo-auc-gen_test remains unchanged, hence the same input arguments (particularly -s <sqn> and -A <auts>) still produce the same results. Note: osmo-hlr regression tests will require adjustments when this patch is merged, because it must now pass desired_sqn - 1 instead of just desired_sqn. See osmo-hlr change-id I4ec5a578537acb1d9e1ebfe00a72417fc3ca5894 . Related: OS#1968 Change-Id: Iadf43f21e0605e9e85f7e8026c40985f7ceff1a3
2017-03-13 16:36:17 +00:00
osmo_store64be_ext(next_sqn, sqn, 6);
milenage_generate(opc, aud->u.umts.amf, aud->u.umts.k,
sqn, _rand,
vec->autn, vec->ik, vec->ck, vec->res, &res_len);
osmo_auth_c3(vec->kc, vec->ck, vec->ik);
osmo_auth_c2(vec->sres, vec->res, vec->res_len, 1);
vec->auth_types = OSMO_AUTH_TYPE_UMTS | OSMO_AUTH_TYPE_GSM;
osmo_auth_gen_vec: UMTS auth: store last used SQN, not next Prepare for the implementation of splitting SQN increments in SEQ and an IND part; particularly to clearly show where the changes in auth/milenage_test's expectations originate. Rationale: the source of UMTS auth vectors, for us usually OsmoHLR, typically stores the last used SQN, not the next one to be used. Particularly with the upcoming fix of the SQN scheme, this change is important: the next SQN will depend on which entity asks for it, because each auth consumer may have a particular slot in the IND part of SQN. It does not make sense to store the next SQN, because we will not know which consumer that will be for. The milenage_test has always calculated a tuple for SQN == 34. To account for the increment now happening before calculating a tuple, lower the test_aud->sqn by one to 0x21 == 33, so that it is still calculating for SQN == 34. Because we are no longer incrementing SQN after the tuple is generated, milenage_test's expected output after doing an AUTS resync to 31 changes to the next SQN = 32, the SQN used for the generated tuple. (BTW, a subsequent patch will illustrate AUTS in detail.) osmo-auc-gen now needs to pass the user requested SQN less one, because the SQN will be incremented befor generating the auth vector. Also the SQN remains the same after generating, so SQN output needs less decrementing. Note that the expected output for osmo-auc-gen_test remains unchanged, hence the same input arguments (particularly -s <sqn> and -A <auts>) still produce the same results. Note: osmo-hlr regression tests will require adjustments when this patch is merged, because it must now pass desired_sqn - 1 instead of just desired_sqn. See osmo-hlr change-id I4ec5a578537acb1d9e1ebfe00a72417fc3ca5894 . Related: OS#1968 Change-Id: Iadf43f21e0605e9e85f7e8026c40985f7ceff1a3
2017-03-13 16:36:17 +00:00
/* for storage in the caller's AUC database */
aud->u.umts.sqn = next_sqn;
return 0;
}
static int milenage_gen_vec_auts(struct osmo_auth_vector *vec,
struct osmo_sub_auth_data2 *aud,
const uint8_t *auts, const uint8_t *rand_auts,
const uint8_t *_rand)
{
uint8_t sqn_out[6];
uint8_t gen_opc[16];
const uint8_t *opc;
int rc;
OSMO_ASSERT(aud->algo == OSMO_AUTH_ALG_MILENAGE);
if (aud->u.umts.k_len != 16)
return -EINVAL;
if (aud->u.umts.opc_len != 16)
return -EINVAL;
opc = gen_opc_if_needed(aud, gen_opc);
rc = milenage_auts(opc, aud->u.umts.k, rand_auts, auts, sqn_out);
if (rc < 0)
return rc;
aud->u.umts.sqn_ms = osmo_load64be_ext(sqn_out, 6) >> 16;
/* Update our "largest used SQN" from the USIM -- milenage_gen_vec()
* below will increment SQN. */
aud->u.umts.sqn = aud->u.umts.sqn_ms;
return milenage_gen_vec(vec, aud, _rand);
}
static struct osmo_auth_impl milenage_alg = {
.algo = OSMO_AUTH_ALG_MILENAGE,
.name = "MILENAGE (libosmogsm built-in)",
.priority = 1000,
.gen_vec = &milenage_gen_vec,
.gen_vec_auts = &milenage_gen_vec_auts,
};
static __attribute__((constructor)) void on_dso_load_milenage(void)
{
osmo_auth_register(&milenage_alg);
}
/*! @} */