]> www.infradead.org Git - users/dwmw2/openconnect.git/commitdiff
OpenSSL: Pass certinfo through load_certificate() functions
authorDavid Woodhouse <dwmw2@infradead.org>
Fri, 7 May 2021 14:44:37 +0000 (15:44 +0100)
committerDavid Woodhouse <dwmw2@infradead.org>
Sat, 8 May 2021 07:33:04 +0000 (08:33 +0100)
Much like in GnuTLS except in GnuTLS we already separated out the bit
which installs the key/certs into the https_cred while all these
OpenSSL functions are still just installing directly into the SSL_CTX
so we're going to want to fix that separately.

Signed-off-by: David Woodhouse <dwmw2@infradead.org>
openconnect-internal.h
openssl-pkcs11.c
openssl.c

index aa682c9fc0d21b9d3a9c1c4aaa8e58af1fa73592..79ceeb18f55e223c2a0ecbafd0b5cde8340477d2 100644 (file)
@@ -412,6 +412,7 @@ struct oc_tpm1_ctx;
 struct oc_tpm2_ctx;
 
 struct cert_info {
+       struct openconnect_info *vpninfo;
        char *cert;
        char *key;
        char *password;
@@ -1143,8 +1144,8 @@ int cancellable_send(struct openconnect_info *vpninfo, int fd,
 int cancellable_recv(struct openconnect_info *vpninfo, int fd,
                     char *buf, size_t len);
 /* openssl-pkcs11.c */
-int load_pkcs11_key(struct openconnect_info *vpninfo);
-int load_pkcs11_certificate(struct openconnect_info *vpninfo);
+int load_pkcs11_key(struct openconnect_info *vpninfo, struct cert_info *certinfo);
+int load_pkcs11_certificate(struct openconnect_info *vpninfo, struct cert_info *certinfo);
 
 /* esp.c */
 int verify_packet_seqno(struct openconnect_info *vpninfo,
index d0cd165f6053c379dfe0b51c1a8f03baa2e1b80f..fc97e4e7cd5d40e11a81fe33ef3783d4296049d6 100644 (file)
@@ -181,7 +181,8 @@ static int parse_pkcs11_uri(const char *uri, PKCS11_TOKEN **p_tok,
        return ret;
 }
 
-static int request_pin(struct openconnect_info *vpninfo, struct pin_cache *cache, int retrying)
+static int request_pin(struct openconnect_info *vpninfo, struct cert_info *certinfo,
+                      struct pin_cache *cache, int retrying)
 {
        struct oc_auth_form f;
        struct oc_form_opt o;
@@ -191,9 +192,9 @@ static int request_pin(struct openconnect_info *vpninfo, struct pin_cache *cache
        if (!vpninfo || !vpninfo->process_auth_form)
                return -EINVAL;
 
-       if (vpninfo->certinfo[0].password) {
-               cache->pin = vpninfo->certinfo[0].password;
-               vpninfo->certinfo[0].password = NULL;
+       if (certinfo->password) {
+               cache->pin = certinfo->password;
+               certinfo->password = NULL;
                return 0;
        }
        memset(&f, 0, sizeof(f));
@@ -218,7 +219,8 @@ static int request_pin(struct openconnect_info *vpninfo, struct pin_cache *cache
        return 0;
 }
 
-static int slot_login(struct openconnect_info *vpninfo, PKCS11_CTX *ctx, PKCS11_SLOT *slot)
+static int slot_login(struct openconnect_info *vpninfo, struct cert_info *certinfo,
+                     PKCS11_CTX *ctx, PKCS11_SLOT *slot)
 {
        PKCS11_TOKEN *token = slot->token;
        struct pin_cache *cache = vpninfo->pin_cache;
@@ -246,7 +248,7 @@ static int slot_login(struct openconnect_info *vpninfo, PKCS11_CTX *ctx, PKCS11_
                        vpninfo->pin_cache = cache;
                }
                if (!cache->pin) {
-                       ret = request_pin(vpninfo, cache, retrying);
+                       ret = request_pin(vpninfo, certinfo, cache, retrying);
                        if (ret)
                                return ret;
                }
@@ -324,7 +326,7 @@ static PKCS11_CERT *slot_find_cert(struct openconnect_info *vpninfo, PKCS11_CTX
        return NULL;
 }
 
-int load_pkcs11_certificate(struct openconnect_info *vpninfo)
+int load_pkcs11_certificate(struct openconnect_info *vpninfo, struct cert_info *certinfo)
 {
        PKCS11_CTX *ctx;
        PKCS11_TOKEN *match_tok = NULL;
@@ -340,11 +342,11 @@ int load_pkcs11_certificate(struct openconnect_info *vpninfo)
        if (!ctx)
                return -EIO;
 
-       if (parse_pkcs11_uri(vpninfo->certinfo[0].cert, &match_tok, &cert_id,
-                            &cert_id_len, &cert_label, &vpninfo->certinfo[0].password) < 0) {
+       if (parse_pkcs11_uri(certinfo->cert, &match_tok, &cert_id,
+                            &cert_id_len, &cert_label, &certinfo->password) < 0) {
                vpn_progress(vpninfo, PRG_ERR,
                             _("Failed to parse PKCS#11 URI '%s'\n"),
-                            vpninfo->certinfo[0].cert);
+                            certinfo->cert);
                return -EINVAL;
        }
 
@@ -386,7 +388,7 @@ int load_pkcs11_certificate(struct openconnect_info *vpninfo)
                vpn_progress(vpninfo, PRG_INFO,
                             _("Logging in to PKCS#11 slot '%s'\n"),
                             slot->description);
-               if (!slot_login(vpninfo, ctx, slot)) {
+               if (!slot_login(vpninfo, certinfo, ctx, slot)) {
                        cert = slot_find_cert(vpninfo, ctx, slot, cert_label, cert_id, cert_id_len);
                        if (cert)
                                goto got_cert;
@@ -395,7 +397,7 @@ int load_pkcs11_certificate(struct openconnect_info *vpninfo)
        ret = -EINVAL;
        vpn_progress(vpninfo, PRG_ERR,
                     _("Failed to find PKCS#11 cert '%s'\n"),
-                    vpninfo->certinfo[0].cert);
+                    certinfo->cert);
  got_cert:
        if (cert) {
                /* This happens if the cert is too large for the fixed buffer
@@ -408,7 +410,7 @@ int load_pkcs11_certificate(struct openconnect_info *vpninfo)
                }
 
                vpn_progress(vpninfo, PRG_DEBUG,
-                            _("Using PKCS#11 certificate %s\n"), vpninfo->certinfo[0].cert);
+                            _("Using PKCS#11 certificate %s\n"), certinfo->cert);
 
                vpninfo->cert_x509 = X509_dup(cert->x509);
                if (!SSL_CTX_use_certificate(vpninfo->https_ctx, vpninfo->cert_x509)) {
@@ -421,7 +423,7 @@ int load_pkcs11_certificate(struct openconnect_info *vpninfo)
                /* If the key is in PKCS#11 too (which is likely), then keep the slot around.
                   We might want to know which slot the certificate was found in, so we can
                   log into it to find the key. */
-               if (!strncmp(vpninfo->certinfo[0].key, "pkcs11:", 7)) {
+               if (!strncmp(certinfo->key, "pkcs11:", 7)) {
                        vpninfo->pkcs11_slot_list = slot_list;
                        vpninfo->pkcs11_slot_count = slot_count;
                        vpninfo->pkcs11_cert_slot = slot;
@@ -546,7 +548,7 @@ static int validate_ecdsa_key(struct openconnect_info *vpninfo, EC_KEY *priv_ec)
 }
 #endif
 
-int load_pkcs11_key(struct openconnect_info *vpninfo)
+int load_pkcs11_key(struct openconnect_info *vpninfo, struct cert_info *certinfo)
 {
        PKCS11_CTX *ctx;
        PKCS11_TOKEN *match_tok = NULL;
@@ -563,11 +565,11 @@ int load_pkcs11_key(struct openconnect_info *vpninfo)
        if (!ctx)
                return -EIO;
 
-       if (parse_pkcs11_uri(vpninfo->certinfo[0].key, &match_tok, &key_id,
-                            &key_id_len, &key_label, &vpninfo->certinfo[0].password) < 0) {
+       if (parse_pkcs11_uri(certinfo->key, &match_tok, &key_id,
+                            &key_id_len, &key_label, &certinfo->password) < 0) {
                vpn_progress(vpninfo, PRG_ERR,
                             _("Failed to parse PKCS#11 URI '%s'\n"),
-                            vpninfo->certinfo[0].key);
+                            certinfo->key);
                return -EINVAL;
        }
 
@@ -609,7 +611,7 @@ int load_pkcs11_key(struct openconnect_info *vpninfo)
           the cert was found in and the key wasn't separately specified, then
           try that slot. */
        if (matching_slots != 1 && vpninfo->pkcs11_cert_slot &&
-           vpninfo->certinfo[0].key == vpninfo->certinfo[0].cert) {
+           certinfo->key == certinfo->cert) {
                /* Use the slot the cert was found in, if one specifier was given for both */
                matching_slots = 1;
                login_slot = vpninfo->pkcs11_cert_slot;
@@ -620,7 +622,7 @@ int load_pkcs11_key(struct openconnect_info *vpninfo)
                vpn_progress(vpninfo, PRG_INFO,
                             _("Logging in to PKCS#11 slot '%s'\n"),
                             slot->description);
-               if (!slot_login(vpninfo, ctx, slot)) {
+               if (!slot_login(vpninfo, certinfo, ctx, slot)) {
                        key = slot_find_key(vpninfo, ctx, slot, key_label, key_id, key_id_len);
                        if (key)
                                goto got_key;
@@ -628,7 +630,7 @@ int load_pkcs11_key(struct openconnect_info *vpninfo)
                        /* We still haven't found it. If we weren't explicitly given a URI for
                           the key and we're inferring the location of the key from the cert,
                           then drop the label and try matching the CKA_ID of the cert. */
-                       if (vpninfo->certinfo[0].cert == vpninfo->certinfo[0].key && vpninfo->pkcs11_cert_id &&
+                       if (certinfo->cert == certinfo->key && vpninfo->pkcs11_cert_id &&
                            (key_label || !key_id)) {
                                key = slot_find_key(vpninfo, ctx, slot, NULL, vpninfo->pkcs11_cert_id,
                                                    vpninfo->pkcs11_cert_id_len);
@@ -640,12 +642,12 @@ int load_pkcs11_key(struct openconnect_info *vpninfo)
        ret = -EINVAL;
        vpn_progress(vpninfo, PRG_ERR,
                     _("Failed to find PKCS#11 key '%s'\n"),
-                    vpninfo->certinfo[0].key);
+                    certinfo->key);
 
  got_key:
        if (key) {
                vpn_progress(vpninfo, PRG_DEBUG,
-                            _("Using PKCS#11 key %s\n"), vpninfo->certinfo[0].key);
+                            _("Using PKCS#11 key %s\n"), certinfo->key);
 
                pkey = PKCS11_get_private_key(key);
                if (!pkey) {
@@ -708,13 +710,13 @@ int load_pkcs11_key(struct openconnect_info *vpninfo)
        return ret;
 }
 #else
-int load_pkcs11_key(struct openconnect_info *vpninfo)
+int load_pkcs11_key(struct openconnect_info *vpninfo, struct cert_info *certinfo)
 {
        vpn_progress(vpninfo, PRG_ERR,
                     _("This version of OpenConnect was built without PKCS#11 support\n"));
        return -EINVAL;
 }
-int load_pkcs11_certificate(struct openconnect_info *vpninfo)
+int load_pkcs11_certificate(struct openconnect_info *vpninfo, struct cert_info *certinfo)
 {
        vpn_progress(vpninfo, PRG_ERR,
                     _("This version of OpenConnect was built without PKCS#11 support\n"));
index 7c6afd16652967e3e245861b7845673f41d7bb53..25b2ac11d130972565713803229b4771e6d61963 100644 (file)
--- a/openssl.c
+++ b/openssl.c
@@ -498,15 +498,16 @@ static UI_METHOD *create_openssl_ui(void)
 }
 #endif
 
-static int pem_pw_cb(char *buf, int len, int w, void *v)
+static int pem_pw_cb(char *buf, int len, int w, void *ci)
 {
-       struct openconnect_info *vpninfo = v;
+       struct cert_info *certinfo = ci;
+       struct openconnect_info *vpninfo = certinfo->vpninfo;
        char *pass = NULL;
        int plen;
 
-       if (vpninfo->certinfo[0].password) {
-               pass = vpninfo->certinfo[0].password;
-               vpninfo->certinfo[0].password = NULL;
+       if (certinfo->password) {
+               pass = certinfo->password;
+               certinfo->password = NULL;
        } else if (request_passphrase(vpninfo, "openconnect_pem",
                                      &pass, _("Enter PEM pass phrase:")))
                return -1;
@@ -560,7 +561,8 @@ static int install_extra_certs(struct openconnect_info *vpninfo, const char *sou
        return 0;
 }
 
-static int load_pkcs12_certificate(struct openconnect_info *vpninfo, PKCS12 *p12)
+static int load_pkcs12_certificate(struct openconnect_info *vpninfo, struct cert_info *certinfo,
+                                  PKCS12 *p12)
 {
        EVP_PKEY *pkey = NULL;
        X509 *cert = NULL;
@@ -568,8 +570,8 @@ static int load_pkcs12_certificate(struct openconnect_info *vpninfo, PKCS12 *p12
        int ret = 0;
        char *pass;
 
-       pass = vpninfo->certinfo[0].password;
-       vpninfo->certinfo[0].password = NULL;
+       pass = certinfo->password;
+       certinfo->password = NULL;
  retrypass:
        /* We do this every time round the loop, to work around a bug in
           OpenSSL < 1.0.0-beta2 -- where the stack at *ca will be freed
@@ -640,7 +642,7 @@ static int load_pkcs12_certificate(struct openconnect_info *vpninfo, PKCS12 *p12
 
 #ifdef HAVE_ENGINE
 static int load_tpm_certificate(struct openconnect_info *vpninfo,
-                               const char *engine)
+                               struct cert_info *certinfo, const char *engine)
 {
        ENGINE *e;
        EVP_PKEY *key;
@@ -667,20 +669,20 @@ static int load_tpm_certificate(struct openconnect_info *vpninfo,
                return -EINVAL;
        }
 
-       if (vpninfo->certinfo[0].password) {
-               if (!ENGINE_ctrl_cmd(e, "PIN", strlen(vpninfo->certinfo[0].password),
-                                    vpninfo->certinfo[0].password, NULL, 0)) {
+       if (certinfo->password) {
+               if (!ENGINE_ctrl_cmd(e, "PIN", strlen(certinfo->password),
+                                    certinfo->password, NULL, 0)) {
                        vpn_progress(vpninfo, PRG_ERR,
                                     _("Failed to set TPM SRK password\n"));
                        openconnect_report_ssl_errors(vpninfo);
                }
-               free_pass(&vpninfo->certinfo[0].password);
+               free_pass(&certinfo->password);
        }
 
        /* Provide our own UI method to handle the PIN callback. */
        meth = create_openssl_ui();
 
-       key = ENGINE_load_private_key(e, vpninfo->certinfo[0].key, meth, vpninfo);
+       key = ENGINE_load_private_key(e, certinfo->key, meth, vpninfo);
        if (meth)
                UI_destroy_method(meth);
        if (!key) {
@@ -703,7 +705,7 @@ static int load_tpm_certificate(struct openconnect_info *vpninfo,
 }
 #else
 static int load_tpm_certificate(struct openconnect_info *vpninfo,
-                               const char *engine)
+                               struct cert_info *certinfo, const char *engine)
 {
        vpn_progress(vpninfo, PRG_ERR,
                     _("This version of OpenConnect was built without TPM support\n"));
@@ -733,17 +735,17 @@ static int load_tpm_certificate(struct openconnect_info *vpninfo,
  *   allows us to explicitly print the supporting certs that we're using,
  *   which may assist in diagnosing problems.
  */
-static int load_cert_chain_file(struct openconnect_info *vpninfo)
+static int load_cert_chain_file(struct openconnect_info *vpninfo, struct cert_info *certinfo)
 {
        BIO *b;
-       FILE *f = openconnect_fopen_utf8(vpninfo, vpninfo->certinfo[0].cert, "rb");
+       FILE *f = openconnect_fopen_utf8(vpninfo, certinfo->cert, "rb");
        STACK_OF(X509) *extra_certs = NULL;
        char buf[200];
 
        if (!f) {
                vpn_progress(vpninfo, PRG_ERR,
                             _("Failed to open certificate file %s: %s\n"),
-                            vpninfo->certinfo[0].cert, strerror(errno));
+                            certinfo->cert, strerror(errno));
                return -ENOENT;
        }
 
@@ -867,37 +869,39 @@ static int is_pem_password_error(struct openconnect_info *vpninfo)
        return 0;
 }
 
-static int load_certificate(struct openconnect_info *vpninfo)
+static int load_certificate(struct openconnect_info *vpninfo, struct cert_info *certinfo)
 {
        EVP_PKEY *key;
        FILE *f;
        char buf[256];
        int ret;
 
-       if (!strncmp(vpninfo->certinfo[0].cert, "pkcs11:", 7)) {
-               int ret = load_pkcs11_certificate(vpninfo);
+       certinfo->vpninfo = vpninfo;
+
+       if (!strncmp(certinfo->cert, "pkcs11:", 7)) {
+               int ret = load_pkcs11_certificate(vpninfo, certinfo);
                if (ret)
                        return ret;
                goto got_cert;
        }
 
        vpn_progress(vpninfo, PRG_DEBUG,
-                    _("Using certificate file %s\n"), vpninfo->certinfo[0].cert);
+                    _("Using certificate file %s\n"), certinfo->cert);
 
-       if (strncmp(vpninfo->certinfo[0].cert, "keystore:", 9)) {
+       if (strncmp(certinfo->cert, "keystore:", 9)) {
                PKCS12 *p12;
 
-               f = openconnect_fopen_utf8(vpninfo, vpninfo->certinfo[0].cert, "rb");
+               f = openconnect_fopen_utf8(vpninfo, certinfo->cert, "rb");
                if (!f) {
                        vpn_progress(vpninfo, PRG_ERR,
                                     _("Failed to open certificate file %s: %s\n"),
-                                    vpninfo->certinfo[0].cert, strerror(errno));
+                                    certinfo->cert, strerror(errno));
                        return -ENOENT;
                }
                p12 = d2i_PKCS12_fp(f, NULL);
                fclose(f);
                if (p12)
-                       return load_pkcs12_certificate(vpninfo, p12);
+                       return load_pkcs12_certificate(vpninfo, certinfo, p12);
 
                /* Not PKCS#12. Clear error and fall through to see if it's a PEM file... */
                ERR_clear_error();
@@ -905,11 +909,11 @@ static int load_certificate(struct openconnect_info *vpninfo)
 
        /* It's PEM or TPM now, and either way we need to load the plain cert: */
 #ifdef ANDROID_KEYSTORE
-       if (!strncmp(vpninfo->certinfo[0].cert, "keystore:", 9)) {
-               BIO *b = BIO_from_keystore(vpninfo, vpninfo->certinfo[0].cert);
+       if (!strncmp(certinfo->cert, "keystore:", 9)) {
+               BIO *b = BIO_from_keystore(vpninfo, certinfo->cert);
                if (!b)
                        return -EINVAL;
-               vpninfo->cert_x509 = PEM_read_bio_X509_AUX(b, NULL, pem_pw_cb, vpninfo);
+               vpninfo->cert_x509 = PEM_read_bio_X509_AUX(b, NULL, pem_pw_cb, certinfo);
                BIO_free(b);
                if (!vpninfo->cert_x509) {
                        vpn_progress(vpninfo, PRG_ERR,
@@ -928,21 +932,21 @@ static int load_certificate(struct openconnect_info *vpninfo)
        } else
 #endif /* ANDROID_KEYSTORE */
        {
-               int ret = load_cert_chain_file(vpninfo);
+               int ret = load_cert_chain_file(vpninfo, certinfo);
                if (ret)
                        return ret;
        }
 
  got_cert:
 #ifdef ANDROID_KEYSTORE
-       if (!strncmp(vpninfo->certinfo[0].key, "keystore:", 9)) {
+       if (!strncmp(certinfo->key, "keystore:", 9)) {
                BIO *b;
 
        again_android:
-               b = BIO_from_keystore(vpninfo, vpninfo->certinfo[0].key);
+               b = BIO_from_keystore(vpninfo, certinfo->key);
                if (!b)
                        return -EINVAL;
-               key = PEM_read_bio_PrivateKey(b, NULL, pem_pw_cb, vpninfo);
+               key = PEM_read_bio_PrivateKey(b, NULL, pem_pw_cb, certinfo);
                BIO_free(b);
                if (!key) {
                        if (is_pem_password_error(vpninfo))
@@ -961,14 +965,14 @@ static int load_certificate(struct openconnect_info *vpninfo)
                return 0;
        }
 #endif /* ANDROID_KEYSTORE */
-       if (!strncmp(vpninfo->certinfo[0].key, "pkcs11:", 7))
-               return load_pkcs11_key(vpninfo);
+       if (!strncmp(certinfo->key, "pkcs11:", 7))
+               return load_pkcs11_key(vpninfo, certinfo);
 
-       f = openconnect_fopen_utf8(vpninfo, vpninfo->certinfo[0].key, "rb");
+       f = openconnect_fopen_utf8(vpninfo, certinfo->key, "rb");
        if (!f) {
                vpn_progress(vpninfo, PRG_ERR,
                             _("Failed to open private key file %s: %s\n"),
-                            vpninfo->certinfo[0].key, strerror(errno));
+                            certinfo->key, strerror(errno));
                return -ENOENT;
        }
 
@@ -976,11 +980,11 @@ static int load_certificate(struct openconnect_info *vpninfo)
        while (fgets(buf, 255, f)) {
                if (!strcmp(buf, "-----BEGIN TSS KEY BLOB-----\n")) {
                        fclose(f);
-                       return load_tpm_certificate(vpninfo, "tpm");
+                       return load_tpm_certificate(vpninfo, certinfo, "tpm");
                } else if (!strcmp(buf, "-----BEGIN TSS2 KEY BLOB-----\n") ||
                           !strcmp(buf, "-----BEGIN TSS2 PRIVATE KEY-----\n")) {
                        fclose(f);
-                       return load_tpm_certificate(vpninfo, "tpm2");
+                       return load_tpm_certificate(vpninfo, certinfo, "tpm2");
                } else if (!strcmp(buf, "-----BEGIN RSA PRIVATE KEY-----\n") ||
                           !strcmp(buf, "-----BEGIN DSA PRIVATE KEY-----\n") ||
                           !strcmp(buf, "-----BEGIN EC PRIVATE KEY-----\n") ||
@@ -997,7 +1001,7 @@ static int load_certificate(struct openconnect_info *vpninfo)
                        }
                again:
                        fseek(f, 0, SEEK_SET);
-                       key = PEM_read_bio_PrivateKey(b, NULL, pem_pw_cb, vpninfo);
+                       key = PEM_read_bio_PrivateKey(b, NULL, pem_pw_cb, certinfo);
                        if (!key) {
                                if (is_pem_password_error(vpninfo))
                                        goto again;
@@ -1044,7 +1048,7 @@ static int load_certificate(struct openconnect_info *vpninfo)
 
                if (p8) {
                        PKCS8_PRIV_KEY_INFO *p8inf;
-                       char *pass = vpninfo->certinfo[0].password;
+                       char *pass = certinfo->password;
 
                        fclose(f);
 
@@ -1073,13 +1077,13 @@ static int load_certificate(struct openconnect_info *vpninfo)
                                }
 
                                free_pass(&pass);
-                               vpninfo->certinfo[0].password = NULL;
+                               certinfo->password = NULL;
 
                                X509_SIG_free(p8);
                                return -EINVAL;
                        }
                        free_pass(&pass);
-                       vpninfo->certinfo[0].password = NULL;
+                       certinfo->password = NULL;
 
                        key = EVP_PKCS82PKEY(p8inf);
 
@@ -1106,7 +1110,7 @@ static int load_certificate(struct openconnect_info *vpninfo)
        fclose(f);
        vpn_progress(vpninfo, PRG_ERR,
                     _("Failed to identify private key type in '%s'\n"),
-                    vpninfo->certinfo[0].key);
+                    certinfo->key);
        return -EINVAL;
 }
 
@@ -1815,7 +1819,7 @@ int openconnect_open_https(struct openconnect_info *vpninfo)
 #endif
 
                if (vpninfo->certinfo[0].cert) {
-                       err = load_certificate(vpninfo);
+                       err = load_certificate(vpninfo, &vpninfo->certinfo[0]);
                        if (!err && !SSL_CTX_check_private_key(vpninfo->https_ctx)) {
                                vpn_progress(vpninfo, PRG_ERR,
                                             _("SSL certificate and key do not match\n"));