libosmogsm: Add support for TUAK authentication algorithm

The TUAK algorithm is specified in 3GPP TS 35.231, 232 and 233 and
intended as an alternative to MILENAGE.  It's based around the
cryptographic function of KeccakP1600, which is part of SHA-3.

This patch adds support for TUAK to the libosmogsm authentication
core API via 'struct osmo_auth_impl'.

Unit tests covering the test cases from the 3GPP specification are added
(and are all passing).

Change-Id: Ib905b8d8bdf248e8299bf50666ee1bca8298433d
This commit is contained in:
Harald Welte 2023-05-28 09:36:50 +02:00
parent 9d5d355720
commit 8bd9d5d7a3
13 changed files with 1186 additions and 1 deletions

1
.checkpatch.conf Normal file
View File

@ -0,0 +1 @@
--exclude ^src/gsm/tuak/KeccakP-1600-3gpp\.(c|h)$

View File

@ -16,3 +16,4 @@ libosmocoding ADD new gsm0503_tch_hr_decode2() public API, previous API
libosmogsm ADD new osmo_sub_auth_data2 / osmo_auth_gen_vec2 / osmo_auth_gen_vec_auts2
libosmogsm MODIFY osmo_auth_impl callback function signature change. No known external users
libosmogsm ADD osmo_auth_c2
libosmogsm ADD OSMO_AUTH_ALG_TUAK

View File

@ -33,6 +33,7 @@ enum osmo_auth_algo {
OSMO_AUTH_ALG_XOR_3G,
OSMO_AUTH_ALG_MILENAGE,
OSMO_AUTH_ALG_XOR_2G,
OSMO_AUTH_ALG_TUAK,
_OSMO_AUTH_ALG_NUM,
};
/* Backwards-compatibility. We used to call XOR-3G just "XOR" which became ambiguous when

View File

@ -15,6 +15,8 @@ noinst_HEADERS = milenage/aes.h milenage/aes_i.h milenage/aes_wrap.h \
milenage/common.h milenage/crypto.h milenage/includes.h \
milenage/milenage.h
noinst_HEADERS += tuak/KeccakP-1600-3gpp.h tuak/tuak.h
noinst_LTLIBRARIES = libgsmint.la
lib_LTLIBRARIES = libosmogsm.la
@ -30,6 +32,7 @@ libgsmint_la_SOURCES = a5.c rxlev_stat.c tlv_parser.c comp128.c comp128v23.c \
auth_milenage.c milenage/aes-encblock.c gea.c \
milenage/aes-internal.c milenage/aes-internal-enc.c \
milenage/milenage.c gan.c ipa.c gsm0341.c apn.c \
tuak/KeccakP-1600-3gpp.c tuak/tuak.c auth_tuak.c \
gsup.c gsup_sms.c gprs_gea.c gsm0503_conv.c oap.c gsm0808_utils.c \
gsm23003.c gsm23236.c mncc.c bts_features.c oap_client.c \
gsm29118.c gsm48_rest_octets.c cbsp.c gsm48049.c \

View File

@ -328,6 +328,7 @@ static const struct value_string auth_alg_vals[] = {
{ OSMO_AUTH_ALG_XOR_3G, "XOR-3G" },
{ OSMO_AUTH_ALG_MILENAGE, "MILENAGE" },
{ OSMO_AUTH_ALG_XOR_2G, "XOR-2G" },
{ OSMO_AUTH_ALG_TUAK, "TUAK" },
{ 0, NULL }
};

207
src/gsm/auth_tuak.c Normal file
View File

@ -0,0 +1,207 @@
/*! \file auth_tuak.c
* GSM/GPRS/3G authentication core infrastructure */
/*
* (C) 2023 by Harald Welte <laforge@osmocom.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.
*
*/
/* NOTE: TUAK offers a lot of size variability in terms of size of length of MAC_A, MAC_S,
* but this is not used within 3GPP. The different sizes of Kc and RES are handled via
* osmo_sub_auth_data2. */
#include <errno.h>
#include <osmocom/crypt/auth.h>
#include <osmocom/core/bits.h>
#include "tuak/tuak.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 = tuak_opc_gen(gen_opc, aud->u.umts.k, aud->u.umts.k_len, aud->u.umts.opc);
if (rc < 0)
return NULL;
return gen_opc;
}
return aud->u.umts.opc;
}
static int tuak_gen_vec(struct osmo_auth_vector *vec,
struct osmo_sub_auth_data2 *aud,
const uint8_t *_rand)
{
size_t res_len = vec->res_len;
uint64_t next_sqn;
uint8_t gen_opc[32];
const uint8_t *opc;
uint8_t sqn[6];
uint64_t ind_mask;
uint64_t seq_1;
switch (vec->res_len) {
case 4:
case 8:
case 16:
break;
default:
return -EINVAL;
}
OSMO_ASSERT(aud->algo == OSMO_AUTH_ALG_TUAK);
if (aud->u.umts.k_len != 16 && aud->u.umts.k_len != 32)
return -EINVAL;
if (aud->u.umts.opc_len != 32)
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;
/* keep the incremented SQN local until gsm_milenage() succeeded. */
next_sqn = ((aud->u.umts.sqn + seq_1) & ind_mask) + aud->u.umts.ind;
osmo_store64be_ext(next_sqn, sqn, 6);
tuak_generate(opc, aud->u.umts.amf, aud->u.umts.k, aud->u.umts.k_len,
sqn, _rand, vec->autn, vec->ik, vec->ck, vec->res, &res_len);
/* generate the GSM Kc + SRES values using C2 + C3 functions */
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;
/* for storage in the caller's AUC database */
aud->u.umts.sqn = next_sqn;
return 0;
}
static int tuak_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[32];
const uint8_t *opc;
int rc;
OSMO_ASSERT(aud->algo == OSMO_AUTH_ALG_TUAK);
if (aud->u.umts.k_len != 16 && aud->u.umts.k_len != 32)
return -EINVAL;
if (aud->u.umts.opc_len != 32)
return -EINVAL;
opc = gen_opc_if_needed(aud, gen_opc);
rc = tuak_auts(opc, aud->u.umts.k, sizeof(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 tuak_gen_vec(vec, aud, _rand);
}
static struct osmo_auth_impl tuak_alg = {
.algo = OSMO_AUTH_ALG_TUAK,
.name = "TUAK (libosmogsm built-in)",
.priority = 1000,
.gen_vec = &tuak_gen_vec,
.gen_vec_auts = &tuak_gen_vec_auts,
};
static __attribute__((constructor)) void on_dso_load_tuak(void)
{
osmo_auth_register(&tuak_alg);
}
/*! @} */

View File

@ -0,0 +1,176 @@
/* -----------------------------------------------------------------------
* code extracted from 3GPP TS 35.231, annex E for Keccak core functions
* https://portal.3gpp.org/desktopmodules/Specifications/SpecificationDetails.aspx?specificationId=2402
*-----------------------------------------------------------------------*/
/* This code may be freely used or adapted.
*/
#include "KeccakP-1600-3gpp.h"
const uint8_t Rho[25] = {0,1,62,28,27,36,44,6,55,20,3,10,43,25,39,41,45,
15,21,8,18,2,61,56,14};
const uint8_t Pi[25] = {0,6,12,18,24,3,9,10,16,22,1,7,13,19,20,4,5,11,17,
23,2,8,14,15,21};
const uint8_t Iota[24] = {1,146,218,112,155,33,241,89,138,136,57,42,187,203,
217,83,82,192,26,106,241,208,33,120};
#define ROTATE64(value, n) \
((((uint64_t)(value))<<(n)) | (((uint64_t)(value))>>(64-(n))))
/* ---------------------------------------------------------------------
64-bit version of Keccak_f(1600)
---------------------------------------------------------------------
*/
void Keccak_f_64(uint64_t s[25])
{ uint64_t t[5];
uint8_t i, j, round;
for(round=0; round<24; ++round)
{ /* Theta function */
for(i=0; i<5; ++i)
t[i] = s[i] ^ s[5+i] ^ s[10+i] ^ s[15+i] ^ s[20+i];
for(i=0; i<5; ++i, s+=5)
{ s[0] ^= t[4] ^ ROTATE64(t[1], 1);
s[1] ^= t[0] ^ ROTATE64(t[2], 1);
s[2] ^= t[1] ^ ROTATE64(t[3], 1);
s[3] ^= t[2] ^ ROTATE64(t[4], 1);
s[4] ^= t[3] ^ ROTATE64(t[0], 1);
}
s -= 25;
/* Rho function */
for(i=1; i<25; ++i)
s[i] = ROTATE64(s[i], Rho[i]);
/* Pi function */
for(t[1] = s[i=1]; (j=Pi[i]) > 1; s[i]=s[j], i=j);
s[i] = t[1];
/* Chi function */
for(i=0; i<5; ++i, s += 5)
{ t[0] = (~s[1]) & s[2];
t[1] = (~s[2]) & s[3];
t[2] = (~s[3]) & s[4];
t[3] = (~s[4]) & s[0];
t[4] = (~s[0]) & s[1];
for(j=0; j<5; ++j) s[j] ^= t[j];
}
s -= 25;
/* Iota function */
t[0] = Iota[round];
*s ^= (t[0] | (t[0]<<11) | (t[0]<<26) | (t[0]<<57))
& 0x800000008000808BULL; /* set & mask bits 0,1,3,7,15,31,63 */
}
}
/* ---------------------------------------------------------------------
8-bit version of Keccak_f(1600)
---------------------------------------------------------------------
*/
void Keccak_f_8(uint8_t s[200])
{ uint8_t t[40], i, j, k, round;
for(round=0; round<24; ++round)
{ /* Theta function */
for(i=0; i<40; ++i)
t[i]=s[i]^s[40+i]^s[80+i]^s[120+i]^s[160+i];
for(i=0; i<200; i+=8)
for(j = (i+32)%40, k=0; k<8; ++k)
s[i+k] ^= t[j+k];
for(i=0; i<40; t[i] = (t[i]<<1)|j, i+=8)
for(j = t[i+7]>>7, k=7; k; --k)
t[i+k] = (t[i+k]<<1)|(t[i+k-1]>>7);
for(i=0; i<200; i+=8)
for(j = (i+8)%40, k=0; k<8; ++k)
s[i+k] ^= t[j+k];
/* Rho function */
for(i=8; i<200; i+=8)
{ for(j = Rho[i>>3]>>3, k=0; k<8; ++k) /* j:=bytes to shift, s->t */
t[(k+j)&7] = s[i+k];
for(j = Rho[i>>3]&7, k=7; k; --k) /* j:=bits to shift, t->s */
s[i+k] = (t[k]<<j) | (t[k-1]>>(8-j));
s[i] = (t[0]<<j) | (t[7]>>(8-j));
}
/* Pi function */
for(k=8; k<16; ++k) t[k] = s[k]; /* =memcpy(t+8, s+8, 8) */
for(i=1; (j=Pi[i])>1; i=j)
for(k=0; k<8; ++k) /* =memcpy(s+(i<<3), s+(j<<3), 8) */
s[(i<<3)|k] = s[(j<<3)|k];
for(k=0; k<8; ++k) /* =memcpy(s+(i<<3), t+8, 8) */
s[(i<<3)|k] = t[k+8];
/* Chi function */
for(i=0; i<200; i+=40)
{ for(j=0; j<40; ++j)
t[j]=(~s[i+(j+8)%40]) & s[i+(j+16)%40];
for(j=0; j<40; ++j) s[i+j]^=t[j];
}
/* Iota function */
k = Iota[round];
s[0] ^= k & 0x8B; /* bits 0, 1, 3, 7 */
s[1] ^= (k<<3)&0x80; /* bit 15 */
s[3] ^= (k<<2)&0x80; /* bit 31 */
s[7] ^= (k<<1)&0x80; /* bit 63 */
}
}
/* ---------------------------------------------------------------------
32-bit version of Keccak_f(1600)
---------------------------------------------------------------------
*/
void Keccak_f_32(uint32_t s[50])
{ uint32_t t[10];
uint8_t i, j, round, k;
for(round=0; round<24; ++round)
{ /* Theta function */
for(i=0; i<10; ++i)
t[i] = s[i] ^ s[10+i] ^ s[20+i] ^ s[30+i] ^ s[40+i];
for(i=0; i<5; ++i)
for(j=8, k=2; ; j%=10, k=(k+2)%10)
{ *s++ ^= t[j++] ^ ((t[k]<<1)|(t[k+1]>>31));
*s++ ^= t[j++] ^ ((t[k+1]<<1)|(t[k]>>31));
if(j==8) break;
}
s -= 50;
/* Rho function */
for(i=2; i<50; i+=2)
{ k = Rho[i>>1] & 0x1f;
t[0] = (s[i+1] << k) | (s[i] >> (32-k));
t[1] = (s[i] << k) | (s[i+1] >> (32-k));
k = Rho[i>>1] >> 5;
s[i] = t[1-k], s[i+1] = t[k];
}
/* Pi function */
for(i=2, t[0]=s[2], t[1]=s[3]; (j=(Pi[i>>1]<<1))>2; i=j)
s[i]=s[j], s[i+1]=s[j+1];
s[i]=t[0], s[i+1]=t[1];
/* Chi function */
for(i=0; i<5; ++i, s+=10)
{ for(j=0; j<10; ++j)
t[j] = (~s[(j+2)%10]) & s[(j+4)%10];
for(j=0; j<10; ++j)
s[j] ^= t[j];
}
s -= 50;
/* Iota function */
t[0] = Iota[round];
s[0] ^= (t[0] | (t[0]<<11) | (t[0]<<26)) & 0x8000808B;
s[1] ^= (t[0]<<25) & 0x80000000;
}
}

View File

@ -0,0 +1,25 @@
/* -----------------------------------------------------------------------
* code extracted from 3GPP TS 35.231, annex E for Keccak core functions
* https://portal.3gpp.org/desktopmodules/Specifications/SpecificationDetails.aspx?specificationId=2402
*-----------------------------------------------------------------------*/
/* this is the trick to make the code cross-platform
* at least, Win32 / Linux */
#if defined(_WIN32) || defined(__WIN32__)
# include <windows.h>
# define EXPORTIT __declspec(dllexport)
#else
# define EXPORTIT
#endif
#include <stdint.h>
/*------------------------------------------------------------------------
* KeccakP-1600-3gpp.h
*------------------------------------------------------------------------*/
EXPORTIT void Keccak_f_8 (uint8_t s[200]);
EXPORTIT void Keccak_f_32(uint32_t s[50]);
EXPORTIT void Keccak_f_64(uint64_t s[25]);

372
src/gsm/tuak/tuak.c Normal file
View File

@ -0,0 +1,372 @@
/* (C) 2023 by Harald Welte <laforge@osmocom.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 <stdint.h>
#include <string.h>
#include <string.h>
#include <errno.h>
#include <osmocom/core/utils.h>
#include "KeccakP-1600-3gpp.h"
/* TUAK authentication algorithm
* as proposed by 3GPP as an alternative to Milenage
* algorithm based on SHA-3 (more exactly its KeccakP-1600 permutation)
* see 3GPP TS 35.231, 232 and 233 */
static unsigned int g_keccak_iterations = 1;
static const char algoname[] = "TUAK1.0";
const uint8_t zero16[16] = { 0, };
void tuak_set_keccak_iterations(unsigned int i)
{
g_keccak_iterations = i;
}
/* append data from 'input' to 'buf' at 'idx', reversing byte order */
#define PUSH_DATA(buf, idx, input, nbytes) \
for (int i = nbytes-1; i >= 0; i--) { \
buf[idx++] = input[i]; \
}
/* like memcpy(), but reversing they order of bytes */
void memcpy_reverse(uint8_t *dst, const uint8_t *src, size_t len)
{
for (size_t i = 0; i < len; i++)
dst[i] = src[len-i-1];
}
static void tuak_core(uint8_t buf[200], const uint8_t *opc, uint8_t instance, const uint8_t *_rand,
const uint8_t *amf, const uint8_t *sqn, const uint8_t *k, uint8_t k_len_bytes,
unsigned int keccac_iterations)
{
unsigned int idx = 0;
PUSH_DATA(buf, idx, opc, 32);
buf[idx++] = instance;
PUSH_DATA(buf, idx, algoname, strlen(algoname)); /* without trailing NUL */
PUSH_DATA(buf, idx, _rand, 16);
PUSH_DATA(buf, idx, amf, 2);
PUSH_DATA(buf, idx, sqn, 6);
PUSH_DATA(buf, idx, k, k_len_bytes);
memset(buf+idx, 0, 32-k_len_bytes); idx += 32-k_len_bytes;
buf[idx++] = 0x1f;
memset(buf+idx, 0, 38); idx += 38;
buf[idx++] = 0x80;
memset(buf+idx, 0, 64); idx += 64;
OSMO_ASSERT(idx == 200);
for (unsigned int i = 0; i < keccac_iterations; i++)
Keccak_f_64((uint64_t *) buf);
}
/**
* tuak_f1 - TUAK f1 algorithm
* @opc: OPc = 256-bit value derived from OP and K
* @k: K = 128-bit or 256-bit subscriber key
* @_rand: RAND = 128-bit random challenge
* @sqn: SQN = 48-bit sequence number
* @amf: AMF = 16-bit authentication management field
* @mac_a: Buffer for MAC-A = 64/128/256-bit network authentication code
* Returns: 0 on success, -1 on failure
*/
int tuak_f1(const uint8_t *opc, const uint8_t *k, uint8_t k_len_bytes, const uint8_t *_rand,
const uint8_t *sqn, const uint8_t *amf, uint8_t *mac_a, uint8_t mac_a_len_bytes,
unsigned int keccac_iterations)
{
uint8_t buf[200];
uint8_t instance = 0x00;
switch (mac_a_len_bytes) {
case 8:
instance |= 0x08;
break;
case 16:
instance |= 0x10;
break;
case 32:
instance |= 0x20;
break;
default:
return -EINVAL;
}
switch (k_len_bytes) {
case 16:
break;
case 32:
instance |= 0x01;
break;
default:
return -EINVAL;
}
tuak_core(buf, opc, instance, _rand, amf, sqn, k, k_len_bytes, keccac_iterations);
memcpy_reverse(mac_a, buf, mac_a_len_bytes);
return 0;
}
/**
* tuak_f1star - TUAK f1* algorithm
* @opc: OPc = 256-bit value derived from OP and K
* @k: K = 128-bit or 256-bit subscriber key
* @_rand: RAND = 128-bit random challenge
* @sqn: SQN = 48-bit sequence number
* @amf: AMF = 16-bit authentication management field
* @mac_s: Buffer for MAC-S = 64/128/256-bit resync authentication code
* Returns: 0 on success, -1 on failure
*/
int tuak_f1star(const uint8_t *opc, const uint8_t *k, uint8_t k_len_bytes, const uint8_t *_rand,
const uint8_t *sqn, const uint8_t *amf, uint8_t *mac_s, uint8_t mac_s_len_bytes,
unsigned int keccac_iterations)
{
uint8_t buf[200];
uint8_t instance = 0x80;
switch (mac_s_len_bytes) {
case 8:
instance |= 0x08;
break;
case 16:
instance |= 0x10;
break;
case 32:
instance |= 0x20;
break;
default:
return -EINVAL;
}
switch (k_len_bytes) {
case 16:
break;
case 32:
instance |= 0x01;
break;
default:
return -EINVAL;
}
tuak_core(buf, opc, instance, _rand, amf, sqn, k, k_len_bytes, keccac_iterations);
memcpy_reverse(mac_s, buf, mac_s_len_bytes);
return 0;
}
/**
* tuak_f2345 - TUAK f2, f3, f4, f5, algorithms
* @opc: OPc = 256-bit value derived from OP and K
* @k: K = 128/256-bit subscriber key
* @_rand: RAND = 128-bit random challenge
* @res: Buffer for RES = 32/64/128/256-bit signed response (f2), or %NULL
* @ck: Buffer for CK = 128/256-bit confidentiality key (f3), or %NULL
* @ik: Buffer for IK = 128/256-bit integrity key (f4), or %NULL
* @ak: Buffer for AK = 48-bit anonymity key (f5), or %NULL
* Returns: 0 on success, -1 on failure
*/
int tuak_f2345(const uint8_t *opc, const uint8_t *k, uint8_t k_len_bytes,
const uint8_t *_rand, uint8_t *res, uint8_t res_len_bytes,
uint8_t *ck, uint8_t ck_len_bytes,
uint8_t *ik, uint8_t ik_len_bytes, uint8_t *ak, unsigned int keccac_iterations)
{
uint8_t buf[200];
uint8_t instance = 0x40;
switch (res_len_bytes) {
case 4:
break;
case 8:
instance |= 0x08;
break;
case 16:
instance |= 0x10;
break;
case 32:
instance |= 0x20;
break;
default:
return -EINVAL;
}
switch (ck_len_bytes) {
case 16:
break;
case 32:
instance |= 0x04;
break;
default:
return -EINVAL;
}
switch (ik_len_bytes) {
case 16:
break;
case 32:
instance |= 0x02;
break;
default:
return -EINVAL;
}
switch (k_len_bytes) {
case 16:
break;
case 32:
instance |= 0x01;
break;
default:
return -EINVAL;
}
tuak_core(buf, opc, instance, _rand, zero16, zero16, k, k_len_bytes, keccac_iterations);
if (res)
memcpy_reverse(res, buf, res_len_bytes);
if (ck)
memcpy_reverse(ck, buf + 32, ck_len_bytes);
if (ik)
memcpy_reverse(ik, buf + 64, ik_len_bytes);
if (ak)
memcpy_reverse(ak, buf + 96, 6);
return 0;
}
/**
* tuak_f5star - TUAK f5* algorithm
* @opc: OPc = 256-bit value derived from OP and K
* @k: K = 128/256-bit subscriber key
* @_rand: RAND = 128-bit random challenge
* @ak: Buffer for AK = 48-bit anonymity key (f5)
* Returns: 0 on success, -1 on failure
*/
int tuak_f5star(const uint8_t *opc, const uint8_t *k, uint8_t k_len_bytes,
const uint8_t *_rand, uint8_t *ak, unsigned int keccac_iterations)
{
uint8_t buf[200];
uint8_t instance = 0xc0;
switch (k_len_bytes) {
case 16:
break;
case 32:
instance += 1;
break;
default:
return -EINVAL;
}
tuak_core(buf, opc, instance, _rand, zero16, zero16, k, k_len_bytes, keccac_iterations);
memcpy_reverse(ak, buf + 96, 6);
return 0;
}
/**
* tuak_generate - Generate AKA AUTN,IK,CK,RES
* @opc: OPc = 256-bit operator variant algorithm configuration field (encr.)
* @amf: AMF = 16-bit authentication management field
* @k: K = 128/256-bit subscriber key
* @sqn: SQN = 48-bit sequence number
* @_rand: RAND = 128-bit random challenge
* @autn: Buffer for AUTN = 128-bit authentication token
* @ik: Buffer for IK = 128/256-bit integrity key (f4), or %NULL
* @ck: Buffer for CK = 128/256-bit confidentiality key (f3), or %NULL
* @res: Buffer for RES = 32/64/128-bit signed response (f2), or %NULL
* @res_len: Max length for res; set to used length or 0 on failure
*/
void tuak_generate(const uint8_t *opc, const uint8_t *amf, const uint8_t *k, uint8_t k_len_bytes,
const uint8_t *sqn, const uint8_t *_rand, uint8_t *autn, uint8_t *ik,
uint8_t *ck, uint8_t *res, size_t *res_len)
{
int i;
uint8_t mac_a[8], ak[6];
if (*res_len < 4) {
*res_len = 0;
return;
}
if (tuak_f1(opc, k, k_len_bytes, _rand, sqn, amf, mac_a, sizeof(mac_a), g_keccak_iterations) ||
tuak_f2345(opc, k, k_len_bytes, _rand, res, *res_len, ck, 16, ik, 16, ak, g_keccak_iterations)) {
*res_len = 0;
return;
}
/* AUTN = (SQN ^ AK) || AMF || MAC */
for (i = 0; i < 6; i++)
autn[i] = sqn[i] ^ ak[i];
memcpy(autn + 6, amf, 2);
memcpy(autn + 8, mac_a, 8);
}
/**
* tuak_auts - Milenage AUTS validation
* @opc: OPc = 256-bit operator variant algorithm configuration field (encr.)
* @k: K = 128/256-bit subscriber key
* @_rand: RAND = 128-bit random challenge
* @auts: AUTS = 112-bit authentication token from client
* @sqn: Buffer for SQN = 48-bit sequence number
* Returns: 0 = success (sqn filled), -1 on failure
*/
int tuak_auts(const uint8_t *opc, const uint8_t *k, uint8_t k_len_bytes,
const uint8_t *_rand, const uint8_t *auts, uint8_t *sqn)
{
uint8_t amf[2] = { 0x00, 0x00 }; /* TS 33.102 v7.0.0, 6.3.3 */
uint8_t ak[6], mac_s[8];
int i;
if (tuak_f5star(opc, k, k_len_bytes, _rand, ak, g_keccak_iterations))
return -1;
for (i = 0; i < 6; i++)
sqn[i] = auts[i] ^ ak[i];
if (tuak_f1star(opc, k, k_len_bytes, _rand, sqn, amf, mac_s, 8, g_keccak_iterations) ||
memcmp(mac_s, auts + 6, 8) != 0)
return -1;
return 0;
}
int tuak_opc_gen(uint8_t *opc, const uint8_t *k, uint8_t k_len_bytes, const uint8_t *op)
{
uint8_t buf[200];
uint8_t instance;
switch (k_len_bytes) {
case 16:
instance = 0x00;
break;
case 32:
instance = 0x01;
break;
default:
return -EINVAL;
}
tuak_core(buf, op, instance, zero16, zero16, zero16, k, k_len_bytes, g_keccak_iterations);
memcpy_reverse(opc, buf, 32);
return 0;
}

33
src/gsm/tuak/tuak.h Normal file
View File

@ -0,0 +1,33 @@
#pragma once
#include <stdint.h>
/* low-level functions */
int tuak_f1(const uint8_t *opc, const uint8_t *k, uint8_t k_len_bytes, const uint8_t *_rand,
const uint8_t *sqn, const uint8_t *amf, uint8_t *mac_a, uint8_t mac_a_len_bytes,
unsigned int keccac_iterations);
int tuak_f1star(const uint8_t *opc, const uint8_t *k, uint8_t k_len_bytes, const uint8_t *_rand,
const uint8_t *sqn, const uint8_t *amf, uint8_t *mac_s, uint8_t mac_s_len_bytes,
unsigned int keccac_iterations);
int tuak_f2345(const uint8_t *opc, const uint8_t *k, uint8_t k_len_bytes,
const uint8_t *_rand, uint8_t *res, uint8_t res_len_bytes,
uint8_t *ck, uint8_t ck_len_bytes,
uint8_t *ik, uint8_t ik_len_bytes, uint8_t *ak, unsigned int keccac_iterations);
int tuak_f5star(const uint8_t *opc, const uint8_t *k, uint8_t k_len_bytes,
const uint8_t *_rand, uint8_t *ak, unsigned int keccac_iterations);
/* high-level API */
void tuak_set_keccak_iterations(unsigned int i);
void tuak_generate(const uint8_t *opc, const uint8_t *amf, const uint8_t *k, uint8_t k_len_bytes,
const uint8_t *sqn, const uint8_t *_rand, uint8_t *autn, uint8_t *ik,
uint8_t *ck, uint8_t *res, size_t *res_len);
int tuak_auts(const uint8_t *opc, const uint8_t *k, uint8_t k_len_bytes,
const uint8_t *_rand, const uint8_t *auts, uint8_t *sqn);
int tuak_opc_gen(uint8_t *opc, const uint8_t *k, uint8_t k_len_bytes, const uint8_t *op);

View File

@ -10,7 +10,8 @@ endif
check_PROGRAMS = timer/timer_test sms/sms_test ussd/ussd_test \
bits/bitrev_test a5/a5_test \
conv/conv_test auth/milenage_test lapd/lapd_test \
conv/conv_test auth/milenage_test auth/tuak_test \
lapd/lapd_test \
gsm0808/gsm0808_test gsm0408/gsm0408_test \
gprs/gprs_test kasumi/kasumi_test gea/gea_test \
logging/logging_test codec/codec_test \
@ -115,6 +116,10 @@ comp128_comp128_test_LDADD = $(top_builddir)/src/gsm/libosmogsm.la $(LDADD)
auth_milenage_test_SOURCES = auth/milenage_test.c
auth_milenage_test_LDADD = $(top_builddir)/src/gsm/libosmogsm.la $(LDADD)
auth_tuak_test_SOURCES = auth/tuak_test.c
auth_tuak_test_LDADD = $(top_builddir)/src/gsm/libgsmint.la $(LDADD)
auth_tuak_test_CPPFLAGS = $(AM_CPPFLAGS) -I$(top_srcdir)/src
auth_xor2g_test_SOURCES = auth/xor2g_test.c
auth_xor2g_test_LDADD = $(top_builddir)/src/gsm/libosmogsm.la $(LDADD)
@ -393,6 +398,7 @@ EXTRA_DIST = testsuite.at $(srcdir)/package.m4 $(TESTSUITE) \
timer/timer_test.ok sms/sms_test.ok ussd/ussd_test.ok \
bits/bitrev_test.ok a5/a5_test.ok \
conv/conv_test.ok auth/milenage_test.ok ctrl/ctrl_test.ok \
auth/tuak_test.ok \
auth/xor2g_test.ok \
lapd/lapd_test.ok \
gsm0408/gsm0408_test.ok gsm0408/gsm0408_test.err \
@ -535,6 +541,8 @@ endif
>$(srcdir)/ussd/ussd_test.ok
auth/milenage_test \
>$(srcdir)/auth/milenage_test.ok
auth/tuak_test \
>$(srcdir)/auth/tuak_test.ok
comp128/comp128_test \
>$(srcdir)/comp128/comp128_test.ok
lapd/lapd_test \

309
tests/auth/tuak_test.c Normal file
View File

@ -0,0 +1,309 @@
#include <stdint.h>
#include <osmocom/core/utils.h>
#include "gsm/tuak/tuak.h"
/* user-friendly test specification, uses hex-strings for all parameters for
* copy+pasting from the spec. */
struct tuak_testspec {
const char *name;
struct {
const char *k;
const char *rand;
const char *sqn;
const char *amf;
const char *top;
unsigned int keccak_iterations;
} in;
struct {
const char *topc;
const char *f1;
const char *f1star;
const char *f2;
const char *f3;
const char *f4;
const char *f5;
const char *f5star;
} out;
};
static const struct tuak_testspec testspecs[] = {
{
.name = "TS 35.233 Section 6.3 Test Set 1",
.in = {
.k = "abababababababababababababababab",
.rand = "42424242424242424242424242424242",
.sqn = "111111111111",
.amf = "ffff",
.top = "5555555555555555555555555555555555555555555555555555555555555555",
.keccak_iterations = 1,
},
.out = {
.topc = "bd04d9530e87513c5d837ac2ad954623a8e2330c115305a73eb45d1f40cccbff",
.f1 = "f9a54e6aeaa8618d",
.f1star = "e94b4dc6c7297df3",
.f2 = "657acd64",
.f3 = "d71a1e5c6caffe986a26f783e5c78be1",
.f4 = "be849fa2564f869aecee6f62d4337e72",
.f5 = "719f1e9b9054",
.f5star = "e7af6b3d0e38",
},
}, {
.name = "TS 35.233 Section 6.4 Test Set 2",
.in = {
.k = "fffefdfcfbfaf9f8f7f6f5f4f3f2f1f0efeeedecebeae9e8e7e6e5e4e3e2e1e0",
.rand = "0123456789abcdef0123456789abcdef",
.sqn = "0123456789ab",
.amf = "abcd",
.top = "808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f",
.keccak_iterations = 1,
},
.out = {
.topc = "305425427e18c503c8a4b294ea72c95d0c36c6c6b29d0c65de5974d5977f8524",
.f1 = "c0b8c2d4148ec7aa5f1d78a97e4d1d58",
.f1star = "ef81af7290f7842c6ceafa537fa0745b",
.f2 = "e9d749dc4eea0035",
.f3 = "a4cb6f6529ab17f8337f27baa8234d47",
.f4 = "2274155ccf4199d5e2abcbf621907f90",
.f5 = "480a9345cc1e",
.f5star = "f84eb338848c",
},
}, {
.name = "TS 35.233 Section 6.5 Test Set 3",
.in = {
.k = "fffefdfcfbfaf9f8f7f6f5f4f3f2f1f0efeeedecebeae9e8e7e6e5e4e3e2e1e0",
.rand = "0123456789abcdef0123456789abcdef",
.sqn = "0123456789ab",
.amf = "abcd",
.top = "808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f",
.keccak_iterations = 1,
},
.out = {
.topc = "305425427e18c503c8a4b294ea72c95d0c36c6c6b29d0c65de5974d5977f8524",
.f1 = "d97b75a1776065271b1e212bc3b1bf173f438b21e6c64a55a96c372e085e5cc5",
.f1star = "427bbf07c6e3a86c54f8c5216499f3909a6fd4a164c9fe235b1550258111b821",
.f2 = "07021c73e7635c7d",
.f3 = "4d59ac796834eb85d11fa148a5058c3c",
.f4 = "126d47500136fdc5ddfd14f19ebf16749ce4b6435323fbb5715a3a796a6082bd",
.f5 = "1d6622c4e59a",
.f5star = "f84eb338848c",
},
}, {
.name = "TS 35.233 Section 6.6 Test Set 4",
.in = {
.k = "b8da837a50652d6ac7c97da14f6acc61",
.rand = "6887e55425a966bd86c9661a5fa72be8",
.sqn = "0dea2ee2c5af",
.amf = "df1e",
.top = "0952be13556c32ebc58195d9dd930493e12a9003669988ffde5fa1f0fe35cc01",
.keccak_iterations = 1,
},
.out = {
.topc = "2bc16eb657a68e1f446f08f57c0efb1d493527a2e652ce281eb6ca0e4487760a",
.f1 = "749214087958dd8f58bfcdf869d8ae3f",
.f1star = "619e865afe80e382aee13063f9dfb56d",
.f2 = "4041ce438e3e38e8aa96562eed83ac43",
.f3 = "3e3bc01bea0cd914c4c2c83ce2d92757",
.f4 = "666a8e6f577b1aa77b7fd53cebb8a3d6",
.f5 = "1f880d005119",
.f5star = "45e617d77fe5",
},
}, {
.name = "TS 35.233 Section 6.7 Test Set 5",
.in = {
.k = "1574ca56881d05c189c82880f789c9cd4244955f4426aa2b69c29f15770e5aa5",
.rand = "c570aac68cde651fb1e3088322498bef",
.sqn = "c89bb71f3a41",
.amf = "297d",
.top = "e59f6eb10ea406813f4991b0b9e02f181edf4c7e17b480f66d34da35ee88c95e",
.keccak_iterations = 1,
},
.out = {
.topc = "3c6052e41532a28a47aa3cbb89f223e8f3aaa976aecd48bc3e7d6165a55eff62",
.f1 = "d7340dad02b4cb01",
.f1star = "c6021e2e66accb15",
.f2 = "84d89b41db1867ffd4c7ba1d82163f4d526a20fbae5418fbb526940b1eeb905c",
.f3 = "d419676afe5ab58c1d8bee0d43523a4d2f52ef0b31a4676a0c334427a988fe65",
.f4 = "205533e505661b61d05cc0eac87818f4",
.f5 = "d7b3d2d4980a",
.f5star = "ca9655264986",
},
}, {
.name = "TS 35.233 Section 6.8 Test Set 6",
.in = {
.k = "1574ca56881d05c189c82880f789c9cd4244955f4426aa2b69c29f15770e5aa5",
.rand = "c570aac68cde651fb1e3088322498bef",
.sqn = "c89bb71f3a41",
.amf = "297d",
.top = "e59f6eb10ea406813f4991b0b9e02f181edf4c7e17b480f66d34da35ee88c95e",
.keccak_iterations = 2,
},
.out = {
.topc = "b04a66f26c62fcd6c82de22a179ab65506ecf47f56245cd149966cfa9cec7a51",
.f1 = "90d2289ed1ca1c3dbc2247bb480d431ac71d2e4a7677f6e997cfddb0cbad88b7",
.f1star = "427355dbac30e825063aba61b556e87583abac638e3ab01c4c884ad9d458dc2f",
.f2 = "d67e6e64590d22eecba7324afa4af4460c93f01b24506d6e12047d789a94c867",
.f3 = "ede57edfc57cdffe1aae75066a1b7479bbc3837438e88d37a801cccc9f972b89",
.f4 = "48ed9299126e5057402fe01f9201cf25249f9c5c0ed2afcf084755daff1d3999",
.f5 = "6aae8d18c448",
.f5star = "8c5f33b61f4e",
},
},
};
struct tuak_testset {
const char *name;
struct {
uint8_t k[32];
uint8_t k_len_bytes;
uint8_t rand[16];
uint8_t sqn[6];
uint8_t amf[2];
uint8_t top[32];
unsigned int keccak_iterations;
} in;
struct {
uint8_t topc[32];
uint8_t mac_a[32];
uint8_t mac_s[32];
uint8_t mac_len_bytes;
uint8_t res[32];
uint8_t res_len_bytes;
uint8_t ck[32];
uint8_t ck_len_bytes;
uint8_t ik[32];
uint8_t ik_len_bytes;
uint8_t ak[6];
uint8_t f5star[6];
} out;
};
static void expect_equal(const char *name, const uint8_t *actual, const uint8_t *expected, size_t len)
{
if (!memcmp(actual, expected, len)) {
printf("\t%s: %s\r\n", name, osmo_hexdump_nospc(actual, len));
} else {
char buf[len*2+1];
printf("\t%s: %s != %s\r\n", name, osmo_hexdump_nospc(actual, len),
osmo_hexdump_buf(buf, sizeof(buf), expected, len, "", true));
}
}
static void execute_testset(const struct tuak_testset *tset)
{
uint8_t topc[32];
printf("==> %s\n", tset->name);
tuak_set_keccak_iterations(tset->in.keccak_iterations);
tuak_opc_gen(topc, tset->in.k, tset->in.k_len_bytes, tset->in.top);
expect_equal("TOPc", topc, tset->out.topc, sizeof(topc));
if (tset->out.mac_len_bytes) {
uint8_t mac_a[32];
uint8_t mac_s[32];
tuak_f1(topc, tset->in.k, tset->in.k_len_bytes, tset->in.rand, tset->in.sqn, tset->in.amf,
mac_a, tset->out.mac_len_bytes, tset->in.keccak_iterations);
expect_equal("MAC_A", mac_a, tset->out.mac_a, tset->out.mac_len_bytes);
tuak_f1star(topc, tset->in.k, tset->in.k_len_bytes, tset->in.rand, tset->in.sqn, tset->in.amf,
mac_s, tset->out.mac_len_bytes, tset->in.keccak_iterations);
expect_equal("MAC_S", mac_s, tset->out.mac_s, tset->out.mac_len_bytes);
}
if (tset->out.ck_len_bytes || tset->out.ik_len_bytes || tset->out.res_len_bytes) {
uint8_t res[32];
uint8_t ck[32];
uint8_t ik[32];
uint8_t ak[6];
tuak_f2345(topc, tset->in.k, tset->in.k_len_bytes, tset->in.rand,
tset->out.res_len_bytes ? res : NULL, tset->out.res_len_bytes,
tset->out.ck_len_bytes ? ck : NULL, tset->out.ck_len_bytes,
tset->out.ik_len_bytes ? ik : NULL, tset->out.ik_len_bytes,
ak, tset->in.keccak_iterations);
if (tset->out.res_len_bytes)
expect_equal("RES", res, tset->out.res, tset->out.res_len_bytes);
if (tset->out.ck_len_bytes)
expect_equal("CK", ck, tset->out.ck, tset->out.ck_len_bytes);
if (tset->out.ik_len_bytes)
expect_equal("IK", ik, tset->out.ik, tset->out.ik_len_bytes);
expect_equal("AK", ak, tset->out.ak, 6);
}
}
/* convert string-testspec to binary-testset and execute it */
static void execute_testspec(const struct tuak_testspec *tcase)
{
struct tuak_testset _tset, *tset = &_tset;
tset->name = tcase->name;
tset->in.keccak_iterations = tcase->in.keccak_iterations;
osmo_hexparse(tcase->in.k, tset->in.k, sizeof(tset->in.k));
tset->in.k_len_bytes = strlen(tcase->in.k)/2;
OSMO_ASSERT(tset->in.k_len_bytes == 16 || tset->in.k_len_bytes == 32);
osmo_hexparse(tcase->in.rand, tset->in.rand, sizeof(tset->in.rand));
OSMO_ASSERT(strlen(tcase->in.rand)/2 == 16);
osmo_hexparse(tcase->in.sqn, tset->in.sqn, sizeof(tset->in.sqn));
OSMO_ASSERT(strlen(tcase->in.sqn)/2 == 6);
osmo_hexparse(tcase->in.amf, tset->in.amf, sizeof(tset->in.amf));
OSMO_ASSERT(strlen(tcase->in.amf)/2 == 2);
osmo_hexparse(tcase->in.top, tset->in.top, sizeof(tset->in.top));
OSMO_ASSERT(strlen(tcase->in.top)/2 == 32);
osmo_hexparse(tcase->out.topc, tset->out.topc, sizeof(tset->out.topc));
OSMO_ASSERT(strlen(tcase->out.topc)/2 == 32);
osmo_hexparse(tcase->out.f1, tset->out.mac_a, sizeof(tset->out.mac_a));
osmo_hexparse(tcase->out.f1star, tset->out.mac_s, sizeof(tset->out.mac_s));
OSMO_ASSERT(strlen(tcase->out.f1) == strlen(tcase->out.f1star));
tset->out.mac_len_bytes = strlen(tcase->out.f1)/2;
OSMO_ASSERT(tset->out.mac_len_bytes == 8 || tset->out.mac_len_bytes == 16 ||
tset->out.mac_len_bytes == 32);
osmo_hexparse(tcase->out.f2, tset->out.res, sizeof(tset->out.res));
tset->out.res_len_bytes = strlen(tcase->out.f2)/2;
OSMO_ASSERT(tset->out.res_len_bytes == 4 || tset->out.res_len_bytes == 8 ||
tset->out.res_len_bytes == 16 || tset->out.res_len_bytes == 32);
osmo_hexparse(tcase->out.f3, tset->out.ck, sizeof(tset->out.ck));
tset->out.ck_len_bytes = strlen(tcase->out.f3)/2;
OSMO_ASSERT(tset->out.ck_len_bytes == 16 || tset->out.ck_len_bytes == 32);
osmo_hexparse(tcase->out.f4, tset->out.ik, sizeof(tset->out.ik));
tset->out.ik_len_bytes = strlen(tcase->out.f4)/2;
OSMO_ASSERT(tset->out.ik_len_bytes == 16 || tset->out.ik_len_bytes == 32);
osmo_hexparse(tcase->out.f5, tset->out.ak, sizeof(tset->out.ak));
OSMO_ASSERT(strlen(tcase->out.f5)/2 == 6);
osmo_hexparse(tcase->out.f5star, tset->out.f5star, sizeof(tset->out.f5star));
OSMO_ASSERT(strlen(tcase->out.f5star)/2 == 6);
execute_testset(tset);
}
int main(int argc, char **argv)
{
#if 0
for (unsigned int i = 0; i < ARRAY_SIZE(testsets); i++)
execute_testset(&testsets[i]);
#endif
for (unsigned int i = 0; i < ARRAY_SIZE(testspecs); i++)
execute_testspec(&testspecs[i]);
}

48
tests/auth/tuak_test.ok Normal file
View File

@ -0,0 +1,48 @@
==> TS 35.233 Section 6.3 Test Set 1
TOPc: bd04d9530e87513c5d837ac2ad954623a8e2330c115305a73eb45d1f40cccbff
MAC_A: f9a54e6aeaa8618d
MAC_S: e94b4dc6c7297df3
RES: 657acd64
CK: d71a1e5c6caffe986a26f783e5c78be1
IK: be849fa2564f869aecee6f62d4337e72
AK: 719f1e9b9054
==> TS 35.233 Section 6.4 Test Set 2
TOPc: 305425427e18c503c8a4b294ea72c95d0c36c6c6b29d0c65de5974d5977f8524
MAC_A: c0b8c2d4148ec7aa5f1d78a97e4d1d58
MAC_S: ef81af7290f7842c6ceafa537fa0745b
RES: e9d749dc4eea0035
CK: a4cb6f6529ab17f8337f27baa8234d47
IK: 2274155ccf4199d5e2abcbf621907f90
AK: 480a9345cc1e
==> TS 35.233 Section 6.5 Test Set 3
TOPc: 305425427e18c503c8a4b294ea72c95d0c36c6c6b29d0c65de5974d5977f8524
MAC_A: d97b75a1776065271b1e212bc3b1bf173f438b21e6c64a55a96c372e085e5cc5
MAC_S: 427bbf07c6e3a86c54f8c5216499f3909a6fd4a164c9fe235b1550258111b821
RES: 07021c73e7635c7d
CK: 4d59ac796834eb85d11fa148a5058c3c
IK: 126d47500136fdc5ddfd14f19ebf16749ce4b6435323fbb5715a3a796a6082bd
AK: 1d6622c4e59a
==> TS 35.233 Section 6.6 Test Set 4
TOPc: 2bc16eb657a68e1f446f08f57c0efb1d493527a2e652ce281eb6ca0e4487760a
MAC_A: 749214087958dd8f58bfcdf869d8ae3f
MAC_S: 619e865afe80e382aee13063f9dfb56d
RES: 4041ce438e3e38e8aa96562eed83ac43
CK: 3e3bc01bea0cd914c4c2c83ce2d92757
IK: 666a8e6f577b1aa77b7fd53cebb8a3d6
AK: 1f880d005119
==> TS 35.233 Section 6.7 Test Set 5
TOPc: 3c6052e41532a28a47aa3cbb89f223e8f3aaa976aecd48bc3e7d6165a55eff62
MAC_A: d7340dad02b4cb01
MAC_S: c6021e2e66accb15
RES: 84d89b41db1867ffd4c7ba1d82163f4d526a20fbae5418fbb526940b1eeb905c
CK: d419676afe5ab58c1d8bee0d43523a4d2f52ef0b31a4676a0c334427a988fe65
IK: 205533e505661b61d05cc0eac87818f4
AK: d7b3d2d4980a
==> TS 35.233 Section 6.8 Test Set 6
TOPc: b04a66f26c62fcd6c82de22a179ab65506ecf47f56245cd149966cfa9cec7a51
MAC_A: 90d2289ed1ca1c3dbc2247bb480d431ac71d2e4a7677f6e997cfddb0cbad88b7
MAC_S: 427355dbac30e825063aba61b556e87583abac638e3ab01c4c884ad9d458dc2f
RES: d67e6e64590d22eecba7324afa4af4460c93f01b24506d6e12047d789a94c867
CK: ede57edfc57cdffe1aae75066a1b7479bbc3837438e88d37a801cccc9f972b89
IK: 48ed9299126e5057402fe01f9201cf25249f9c5c0ed2afcf084755daff1d3999
AK: 6aae8d18c448