diff options
author | Yves-Alexis Perez <corsac@debian.org> | 2013-01-02 14:18:20 +0100 |
---|---|---|
committer | Yves-Alexis Perez <corsac@debian.org> | 2013-01-02 14:18:20 +0100 |
commit | c1343b3278cdf99533b7902744d15969f9d6fdc1 (patch) | |
tree | d5ed3dc5677a59260ec41cd39bb284d3e94c91b3 /src/libcharon/plugins/eap_aka_3gpp2 | |
parent | b34738ed08c2227300d554b139e2495ca5da97d6 (diff) | |
download | vyos-strongswan-c1343b3278cdf99533b7902744d15969f9d6fdc1.tar.gz vyos-strongswan-c1343b3278cdf99533b7902744d15969f9d6fdc1.zip |
Imported Upstream version 5.0.1
Diffstat (limited to 'src/libcharon/plugins/eap_aka_3gpp2')
5 files changed, 140 insertions, 66 deletions
diff --git a/src/libcharon/plugins/eap_aka_3gpp2/Makefile.in b/src/libcharon/plugins/eap_aka_3gpp2/Makefile.in index b0890fb39..4655d341b 100644 --- a/src/libcharon/plugins/eap_aka_3gpp2/Makefile.in +++ b/src/libcharon/plugins/eap_aka_3gpp2/Makefile.in @@ -50,6 +50,7 @@ am__aclocal_m4_deps = $(top_srcdir)/m4/config/libtool.m4 \ am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ $(ACLOCAL_M4) mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/config.h CONFIG_CLEAN_FILES = CONFIG_CLEAN_VPATH_FILES = am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; @@ -88,7 +89,7 @@ libstrongswan_eap_aka_3gpp2_la_LINK = $(LIBTOOL) --tag=CC \ @MONOLITHIC_FALSE@am_libstrongswan_eap_aka_3gpp2_la_rpath = -rpath \ @MONOLITHIC_FALSE@ $(plugindir) @MONOLITHIC_TRUE@am_libstrongswan_eap_aka_3gpp2_la_rpath = -DEFAULT_INCLUDES = -I.@am__isrc@ +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) depcomp = $(SHELL) $(top_srcdir)/depcomp am__depfiles_maybe = depfiles am__mv = mv -f @@ -114,6 +115,7 @@ AUTOCONF = @AUTOCONF@ AUTOHEADER = @AUTOHEADER@ AUTOMAKE = @AUTOMAKE@ AWK = @AWK@ +BFDLIB = @BFDLIB@ BTLIB = @BTLIB@ CC = @CC@ CCDEPMODE = @CCDEPMODE@ @@ -208,11 +210,14 @@ build_os = @build_os@ build_vendor = @build_vendor@ builddir = @builddir@ c_plugins = @c_plugins@ +charon_natt_port = @charon_natt_port@ +charon_plugins = @charon_plugins@ +charon_udp_port = @charon_udp_port@ clearsilver_LIBS = @clearsilver_LIBS@ datadir = @datadir@ datarootdir = @datarootdir@ dbusservicedir = @dbusservicedir@ -default_pkcs11 = @default_pkcs11@ +dev_headers = @dev_headers@ docdir = @docdir@ dvidir = @dvidir@ exec_prefix = @exec_prefix@ @@ -229,11 +234,12 @@ imcvdir = @imcvdir@ includedir = @includedir@ infodir = @infodir@ install_sh = @install_sh@ +ipsec_script = @ipsec_script@ +ipsec_script_upper = @ipsec_script_upper@ ipsecdir = @ipsecdir@ ipsecgroup = @ipsecgroup@ ipseclibdir = @ipseclibdir@ ipsecuser = @ipsecuser@ -libcharon_plugins = @libcharon_plugins@ libdir = @libdir@ libexecdir = @libexecdir@ linux_headers = @linux_headers@ @@ -249,6 +255,7 @@ mkdir_p = @mkdir_p@ nm_CFLAGS = @nm_CFLAGS@ nm_LIBS = @nm_LIBS@ nm_ca_dir = @nm_ca_dir@ +nm_plugins = @nm_plugins@ oldincludedir = @oldincludedir@ openac_plugins = @openac_plugins@ p_plugins = @p_plugins@ @@ -258,7 +265,6 @@ pdfdir = @pdfdir@ piddir = @piddir@ pki_plugins = @pki_plugins@ plugindir = @plugindir@ -pluto_plugins = @pluto_plugins@ pool_plugins = @pool_plugins@ prefix = @prefix@ program_transform_name = @program_transform_name@ diff --git a/src/libcharon/plugins/eap_aka_3gpp2/eap_aka_3gpp2_card.c b/src/libcharon/plugins/eap_aka_3gpp2/eap_aka_3gpp2_card.c index cec06fbd7..1bfc39e5a 100644 --- a/src/libcharon/plugins/eap_aka_3gpp2/eap_aka_3gpp2_card.c +++ b/src/libcharon/plugins/eap_aka_3gpp2/eap_aka_3gpp2_card.c @@ -74,13 +74,19 @@ METHOD(simaka_card_t, get_quintuplet, status_t, mac = autn + AKA_SQN_LEN + AKA_AMF_LEN; /* XOR anonymity key AK into SQN to decrypt it */ - this->f->f5(this->f, k, rand, ak); + if (!this->f->f5(this->f, k, rand, ak)) + { + return FAILED; + } DBG3(DBG_IKE, "using ak %b", ak, AKA_AK_LEN); memxor(sqn, ak, AKA_SQN_LEN); DBG3(DBG_IKE, "using sqn %b", sqn, AKA_SQN_LEN); /* calculate expected MAC and compare against received one */ - this->f->f1(this->f, k, rand, sqn, amf, xmac); + if (!this->f->f1(this->f, k, rand, sqn, amf, xmac)) + { + return FAILED; + } if (!memeq(mac, xmac, AKA_MAC_LEN)) { DBG1(DBG_IKE, "received MAC does not match XMAC"); @@ -98,11 +104,13 @@ METHOD(simaka_card_t, get_quintuplet, status_t, /* update stored SQN to the received one */ memcpy(this->sqn, sqn, AKA_SQN_LEN); - /* CK/IK */ - this->f->f3(this->f, k, rand, ck); - this->f->f4(this->f, k, rand, ik); - /* calculate RES */ - this->f->f2(this->f, k, rand, res); + /* CK/IK, calculate RES */ + if (!this->f->f3(this->f, k, rand, ck) || + !this->f->f4(this->f, k, rand, ik) || + !this->f->f2(this->f, k, rand, res)) + { + return FAILED; + } *res_len = AKA_RES_MAX; return SUCCESS; @@ -122,8 +130,11 @@ METHOD(simaka_card_t, resync, bool, /* AMF is set to zero in resync */ memset(amf, 0, AKA_AMF_LEN); - this->f->f5star(this->f, k, rand, aks); - this->f->f1star(this->f, k, rand, this->sqn, amf, macs); + if (!this->f->f5star(this->f, k, rand, aks) || + !this->f->f1star(this->f, k, rand, this->sqn, amf, macs)) + { + return FALSE; + } /* AUTS = SQN xor AKS | MACS */ memcpy(auts, this->sqn, AKA_SQN_LEN); memxor(auts, aks, AKA_AK_LEN); @@ -160,12 +171,13 @@ eap_aka_3gpp2_card_t *eap_aka_3gpp2_card_create(eap_aka_3gpp2_functions_t *f) }, .f = f, .seq_check = lib->settings->get_bool(lib->settings, - "charon.plugins.eap-aka-3gpp2.seq_check", + "%s.plugins.eap-aka-3gpp2.seq_check", #ifdef SEQ_CHECK /* handle legacy compile time configuration as default */ - TRUE), + TRUE, #else /* !SEQ_CHECK */ - FALSE), + FALSE, #endif /* SEQ_CHECK */ + charon->name), ); eap_aka_3gpp2_get_sqn(this->sqn, 0); diff --git a/src/libcharon/plugins/eap_aka_3gpp2/eap_aka_3gpp2_functions.c b/src/libcharon/plugins/eap_aka_3gpp2/eap_aka_3gpp2_functions.c index d000bebbb..93ea8d08c 100644 --- a/src/libcharon/plugins/eap_aka_3gpp2/eap_aka_3gpp2_functions.c +++ b/src/libcharon/plugins/eap_aka_3gpp2/eap_aka_3gpp2_functions.c @@ -170,12 +170,12 @@ static void mpz_mod_poly(mpz_t r, mpz_t a, mpz_t b) * Step 3 of the various fx() functions: * XOR the key into the SHA1 IV */ -static void step3(prf_t *prf, u_char k[AKA_K_LEN], +static bool step3(prf_t *prf, u_char k[AKA_K_LEN], u_char payload[AKA_PAYLOAD_LEN], u_int8_t h[HASH_SIZE_SHA1]) { /* use the keyed hasher to build the hash */ - prf->set_key(prf, chunk_create(k, AKA_K_LEN)); - prf->get_bytes(prf, chunk_create(payload, AKA_PAYLOAD_LEN), h); + return prf->set_key(prf, chunk_create(k, AKA_K_LEN)) && + prf->get_bytes(prf, chunk_create(payload, AKA_PAYLOAD_LEN), h); } /** @@ -211,7 +211,7 @@ static void step4(u_char x[HASH_SIZE_SHA1]) /** * Calculation function for f2(), f3(), f4() */ -static void fx(prf_t *prf, u_char f, u_char k[AKA_K_LEN], +static bool fx(prf_t *prf, u_char f, u_char k[AKA_K_LEN], u_char rand[AKA_RAND_LEN], u_char out[AKA_MAC_LEN]) { u_char payload[AKA_PAYLOAD_LEN]; @@ -230,16 +230,20 @@ static void fx(prf_t *prf, u_char f, u_char k[AKA_K_LEN], payload[35] ^= i; payload[51] ^= i; - step3(prf, k, payload, h); + if (!step3(prf, k, payload, h)) + { + return FALSE; + } step4(h); memcpy(out + i * 8, h, 8); } + return TRUE; } /** * Calculation function of f1() and f1star() */ -static void f1x(prf_t *prf, u_int8_t f, u_char k[AKA_K_LEN], +static bool f1x(prf_t *prf, u_int8_t f, u_char k[AKA_K_LEN], u_char rand[AKA_RAND_LEN], u_char sqn[AKA_SQN_LEN], u_char amf[AKA_AMF_LEN], u_char mac[AKA_MAC_LEN]) { @@ -257,15 +261,19 @@ static void f1x(prf_t *prf, u_int8_t f, u_char k[AKA_K_LEN], memxor(payload + 34, sqn, AKA_SQN_LEN); memxor(payload + 42, amf, AKA_AMF_LEN); - step3(prf, k, payload, h); + if (!step3(prf, k, payload, h)) + { + return FALSE; + } step4(h); memcpy(mac, h, AKA_MAC_LEN); + return TRUE; } /** * Calculation function of f5() and f5star() */ -static void f5x(prf_t *prf, u_char f, u_char k[AKA_K_LEN], +static bool f5x(prf_t *prf, u_char f, u_char k[AKA_K_LEN], u_char rand[AKA_RAND_LEN], u_char ak[AKA_AK_LEN]) { u_char payload[AKA_PAYLOAD_LEN]; @@ -276,88 +284,120 @@ static void f5x(prf_t *prf, u_char f, u_char k[AKA_K_LEN], memxor(payload + 12, fmk.ptr, fmk.len); memxor(payload + 16, rand, AKA_RAND_LEN); - step3(prf, k, payload, h); + if (!step3(prf, k, payload, h)) + { + return FALSE; + } step4(h); memcpy(ak, h, AKA_AK_LEN); + return TRUE; } /** * Calculate MAC from RAND, SQN, AMF using K */ -METHOD(eap_aka_3gpp2_functions_t, f1, void, +METHOD(eap_aka_3gpp2_functions_t, f1, bool, private_eap_aka_3gpp2_functions_t *this, u_char k[AKA_K_LEN], u_char rand[AKA_RAND_LEN], u_char sqn[AKA_SQN_LEN], u_char amf[AKA_AMF_LEN], u_char mac[AKA_MAC_LEN]) { - f1x(this->prf, F1, k, rand, sqn, amf, mac); - DBG3(DBG_IKE, "MAC %b", mac, AKA_MAC_LEN); + if (f1x(this->prf, F1, k, rand, sqn, amf, mac)) + { + DBG3(DBG_IKE, "MAC %b", mac, AKA_MAC_LEN); + return TRUE; + } + return FALSE; } /** * Calculate MACS from RAND, SQN, AMF using K */ -METHOD(eap_aka_3gpp2_functions_t, f1star, void, +METHOD(eap_aka_3gpp2_functions_t, f1star, bool, private_eap_aka_3gpp2_functions_t *this, u_char k[AKA_K_LEN], u_char rand[AKA_RAND_LEN], u_char sqn[AKA_SQN_LEN], u_char amf[AKA_AMF_LEN], u_char macs[AKA_MAC_LEN]) { - f1x(this->prf, F1STAR, k, rand, sqn, amf, macs); - DBG3(DBG_IKE, "MACS %b", macs, AKA_MAC_LEN); + if (f1x(this->prf, F1STAR, k, rand, sqn, amf, macs)) + { + DBG3(DBG_IKE, "MACS %b", macs, AKA_MAC_LEN); + return TRUE; + } + return FALSE; } /** * Calculate RES from RAND using K */ -METHOD(eap_aka_3gpp2_functions_t, f2, void, +METHOD(eap_aka_3gpp2_functions_t, f2, bool, private_eap_aka_3gpp2_functions_t *this, u_char k[AKA_K_LEN], u_char rand[AKA_RAND_LEN], u_char res[AKA_RES_MAX]) { - fx(this->prf, F2, k, rand, res); - DBG3(DBG_IKE, "RES %b", res, AKA_RES_MAX); + if (fx(this->prf, F2, k, rand, res)) + { + DBG3(DBG_IKE, "RES %b", res, AKA_RES_MAX); + return TRUE; + } + return FALSE; } /** * Calculate CK from RAND using K */ -METHOD(eap_aka_3gpp2_functions_t, f3, void, +METHOD(eap_aka_3gpp2_functions_t, f3, bool, private_eap_aka_3gpp2_functions_t *this, u_char k[AKA_K_LEN], u_char rand[AKA_RAND_LEN], u_char ck[AKA_CK_LEN]) { - fx(this->prf, F3, k, rand, ck); - DBG3(DBG_IKE, "CK %b", ck, AKA_CK_LEN); + if (fx(this->prf, F3, k, rand, ck)) + { + DBG3(DBG_IKE, "CK %b", ck, AKA_CK_LEN); + return TRUE; + } + return FALSE; } /** * Calculate IK from RAND using K */ -METHOD(eap_aka_3gpp2_functions_t, f4, void, +METHOD(eap_aka_3gpp2_functions_t, f4, bool, private_eap_aka_3gpp2_functions_t *this, u_char k[AKA_K_LEN], u_char rand[AKA_RAND_LEN], u_char ik[AKA_IK_LEN]) { - fx(this->prf, F4, k, rand, ik); - DBG3(DBG_IKE, "IK %b", ik, AKA_IK_LEN); + if (fx(this->prf, F4, k, rand, ik)) + { + DBG3(DBG_IKE, "IK %b", ik, AKA_IK_LEN); + return TRUE; + } + return FALSE; } /** * Calculate AK from a RAND using K */ -METHOD(eap_aka_3gpp2_functions_t, f5, void, +METHOD(eap_aka_3gpp2_functions_t, f5, bool, private_eap_aka_3gpp2_functions_t *this, u_char k[AKA_K_LEN], u_char rand[AKA_RAND_LEN], u_char ak[AKA_AK_LEN]) { - f5x(this->prf, F5, k, rand, ak); - DBG3(DBG_IKE, "AK %b", ak, AKA_AK_LEN); + if (f5x(this->prf, F5, k, rand, ak)) + { + DBG3(DBG_IKE, "AK %b", ak, AKA_AK_LEN); + return TRUE; + } + return FALSE; } /** * Calculate AKS from a RAND using K */ -METHOD(eap_aka_3gpp2_functions_t, f5star, void, +METHOD(eap_aka_3gpp2_functions_t, f5star, bool, private_eap_aka_3gpp2_functions_t *this, u_char k[AKA_K_LEN], u_char rand[AKA_RAND_LEN], u_char aks[AKA_AK_LEN]) { - f5x(this->prf, F5STAR, k, rand, aks); - DBG3(DBG_IKE, "AKS %b", aks, AKA_AK_LEN); + if (f5x(this->prf, F5STAR, k, rand, aks)) + { + DBG3(DBG_IKE, "AKS %b", aks, AKA_AK_LEN); + return TRUE; + } + return FALSE; } METHOD(eap_aka_3gpp2_functions_t, destroy, void, diff --git a/src/libcharon/plugins/eap_aka_3gpp2/eap_aka_3gpp2_functions.h b/src/libcharon/plugins/eap_aka_3gpp2/eap_aka_3gpp2_functions.h index 855efec3e..2706da349 100644 --- a/src/libcharon/plugins/eap_aka_3gpp2/eap_aka_3gpp2_functions.h +++ b/src/libcharon/plugins/eap_aka_3gpp2/eap_aka_3gpp2_functions.h @@ -45,8 +45,9 @@ struct eap_aka_3gpp2_functions_t { * @param sqn sequence number * @param amf authentication management field * @param mac buffer receiving mac MAC + * @return TRUE if calculations successful */ - void (*f1)(eap_aka_3gpp2_functions_t *this, u_char k[AKA_K_LEN], + bool (*f1)(eap_aka_3gpp2_functions_t *this, u_char k[AKA_K_LEN], u_char rand[AKA_RAND_LEN], u_char sqn[AKA_SQN_LEN], u_char amf[AKA_AMF_LEN], u_char mac[AKA_MAC_LEN]); @@ -58,8 +59,9 @@ struct eap_aka_3gpp2_functions_t { * @param sqn sequence number * @param amf authentication management field * @param macs buffer receiving resynchronization mac MACS + * @return TRUE if calculations successful */ - void (*f1star)(eap_aka_3gpp2_functions_t *this, u_char k[AKA_K_LEN], + bool (*f1star)(eap_aka_3gpp2_functions_t *this, u_char k[AKA_K_LEN], u_char rand[AKA_RAND_LEN], u_char sqn[AKA_SQN_LEN], u_char amf[AKA_AMF_LEN], u_char macs[AKA_MAC_LEN]); @@ -69,8 +71,9 @@ struct eap_aka_3gpp2_functions_t { * @param k secret key K * @param rand random value RAND * @param res buffer receiving result RES, uses full 128 bit + * @return TRUE if calculations successful */ - void (*f2)(eap_aka_3gpp2_functions_t *this, u_char k[AKA_K_LEN], + bool (*f2)(eap_aka_3gpp2_functions_t *this, u_char k[AKA_K_LEN], u_char rand[AKA_RAND_LEN], u_char res[AKA_RES_MAX]); /** * Calculate CK from RAND using K @@ -78,8 +81,9 @@ struct eap_aka_3gpp2_functions_t { * @param k secret key K * @param rand random value RAND * @param macs buffer receiving encryption key CK + * @return TRUE if calculations successful */ - void (*f3)(eap_aka_3gpp2_functions_t *this, u_char k[AKA_K_LEN], + bool (*f3)(eap_aka_3gpp2_functions_t *this, u_char k[AKA_K_LEN], u_char rand[AKA_RAND_LEN], u_char ck[AKA_CK_LEN]); /** * Calculate IK from RAND using K @@ -87,8 +91,9 @@ struct eap_aka_3gpp2_functions_t { * @param k secret key K * @param rand random value RAND * @param macs buffer receiving integrity key IK + * @return TRUE if calculations successful */ - void (*f4)(eap_aka_3gpp2_functions_t *this, u_char k[AKA_K_LEN], + bool (*f4)(eap_aka_3gpp2_functions_t *this, u_char k[AKA_K_LEN], u_char rand[AKA_RAND_LEN], u_char ik[AKA_IK_LEN]); /** * Calculate AK from a RAND using K @@ -96,8 +101,9 @@ struct eap_aka_3gpp2_functions_t { * @param k secret key K * @param rand random value RAND * @param macs buffer receiving anonymity key AK + * @return TRUE if calculations successful */ - void (*f5)(eap_aka_3gpp2_functions_t *this, u_char k[AKA_K_LEN], + bool (*f5)(eap_aka_3gpp2_functions_t *this, u_char k[AKA_K_LEN], u_char rand[AKA_RAND_LEN], u_char ak[AKA_AK_LEN]); /** * Calculate AKS from a RAND using K @@ -105,8 +111,9 @@ struct eap_aka_3gpp2_functions_t { * @param k secret key K * @param rand random value RAND * @param macs buffer receiving resynchronization anonymity key AKS + * @return TRUE if calculations successful */ - void (*f5star)(eap_aka_3gpp2_functions_t *this, u_char k[AKA_K_LEN], + bool (*f5star)(eap_aka_3gpp2_functions_t *this, u_char k[AKA_K_LEN], u_char rand[AKA_RAND_LEN], u_char aks[AKA_AK_LEN]); /** diff --git a/src/libcharon/plugins/eap_aka_3gpp2/eap_aka_3gpp2_provider.c b/src/libcharon/plugins/eap_aka_3gpp2/eap_aka_3gpp2_provider.c index b2b43da2a..0be122158 100644 --- a/src/libcharon/plugins/eap_aka_3gpp2/eap_aka_3gpp2_provider.c +++ b/src/libcharon/plugins/eap_aka_3gpp2/eap_aka_3gpp2_provider.c @@ -90,12 +90,12 @@ METHOD(simaka_provider_t, get_quintuplet, bool, /* generate RAND: we use a registered RNG, not f0() proposed in S.S0055 */ rng = lib->crypto->create_rng(lib->crypto, RNG_WEAK); - if (!rng) + if (!rng || !rng->get_bytes(rng, AKA_RAND_LEN, rand)) { DBG1(DBG_IKE, "generating RAND for AKA failed"); + DESTROY_IF(rng); return FALSE; } - rng->get_bytes(rng, AKA_RAND_LEN, rand); rng->destroy(rng); if (!eap_aka_3gpp2_get_k(id, k)) @@ -107,12 +107,13 @@ METHOD(simaka_provider_t, get_quintuplet, bool, DBG3(DBG_IKE, "generated rand %b", rand, AKA_RAND_LEN); DBG3(DBG_IKE, "using K %b", k, AKA_K_LEN); - /* MAC */ - this->f->f1(this->f, k, rand, this->sqn, amf, mac); - /* AK */ - this->f->f5(this->f, k, rand, ak); - /* XRES as expected from client */ - this->f->f2(this->f, k, rand, xres); + /* MAC, AK, XRES as expected from client */ + if (!this->f->f1(this->f, k, rand, this->sqn, amf, mac) || + !this->f->f5(this->f, k, rand, ak) || + !this->f->f2(this->f, k, rand, xres)) + { + return FALSE; + } *xres_len = AKA_RES_MAX; /* AUTN = (SQN xor AK) || AMF || MAC */ memcpy(autn, this->sqn, AKA_SQN_LEN); @@ -121,9 +122,11 @@ METHOD(simaka_provider_t, get_quintuplet, bool, memcpy(autn + AKA_SQN_LEN + AKA_AMF_LEN, mac, AKA_MAC_LEN); DBG3(DBG_IKE, "AUTN %b", autn, AKA_AUTN_LEN); /* CK/IK */ - this->f->f3(this->f, k, rand, ck); - this->f->f4(this->f, k, rand, ik); - + if (!this->f->f3(this->f, k, rand, ck) || + !this->f->f4(this->f, k, rand, ik)) + { + return FALSE; + } return TRUE; } @@ -143,12 +146,18 @@ METHOD(simaka_provider_t, resync, bool, /* AUTHS = (AK xor SQN) | MAC */ sqn = auts; macs = auts + AKA_SQN_LEN; - this->f->f5star(this->f, k, rand, aks); + if (!this->f->f5star(this->f, k, rand, aks)) + { + return FALSE; + } memxor(sqn, aks, AKA_AK_LEN); /* verify XMACS, AMF of zero is used in resynchronization */ memset(amf, 0, AKA_AMF_LEN); - this->f->f1star(this->f, k, rand, sqn, amf, xmacs); + if (!this->f->f1star(this->f, k, rand, sqn, amf, xmacs)) + { + return FALSE; + } if (!memeq(macs, xmacs, AKA_MAC_LEN)) { DBG1(DBG_IKE, "received MACS does not match XMACS"); |