]> git.ipfire.org Git - thirdparty/gnutls.git/commitdiff
* The RSA premaster secret version check can no longer be disabled.
authorNikos Mavrogiannopoulos <nmav@gnutls.org>
Thu, 20 Mar 2003 17:11:46 +0000 (17:11 +0000)
committerNikos Mavrogiannopoulos <nmav@gnutls.org>
Thu, 20 Mar 2003 17:11:46 +0000 (17:11 +0000)
* Implemented the counter measure discussed in the paper "Attacking
  RSA-based Sessions in SSL/TLS", against the attack discussed in the
  same paper.
* Added the functions: gnutls_handshake_get_last_in(),
  gnutls_handshake_get_last_out().

12 files changed:
NEWS
lib/auth_rsa.c
lib/gnutls.h.in.in
lib/gnutls_alert.c
lib/gnutls_global.c
lib/gnutls_handshake.c
lib/gnutls_int.h
lib/gnutls_pk.c
lib/gnutls_state.c
src/tests.c
src/tests.h
src/tls_test.c

diff --git a/NEWS b/NEWS
index 6d20ef544b5eecbbe47e7a59f123d68d4ceb66c8..9cc1b662778e72b41ac9d443bffd6e89c6e3461d 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -6,6 +6,12 @@ Version 0.9.3
   by allowing debugging messages just by increasing the level.
 - The diffie Hellman ciphersuites are now of higher priority than
   the plain RSA.
+- The RSA premaster secret version check can no longer be disabled.
+- Implemented the counter measure discussed in the paper "Attacking
+  RSA-based Sessions in SSL/TLS", against the attack discussed in the
+  same paper.
+- Added the functions: gnutls_handshake_get_last_in(), 
+  gnutls_handshake_get_last_out().
 
 Version 0.9.2 (15/03/2003)
 - Some corrections in the memory mapping code (file is unmapped after 
index 31f26f4e0cd31304fcc639c2340da7b0f08815a2..5c99916b6aa2c4d64315c4e4855e68e75b98d015 100644 (file)
@@ -1,7 +1,7 @@
 /*
- *      Copyright (C) 2000,2001,2002,2003 Nikos Mavroyanopoulos
+ *  Copyright (C) 2000,2001,2002,2003 Nikos Mavroyanopoulos
  *
- * This file is part of GNUTLS.
+ *  This file is part of GNUTLS.
  *
  *  The GNUTLS library is free software; you can redistribute it and/or
  *  modify it under the terms of the GNU Lesser General Public   
@@ -209,18 +209,22 @@ const gnutls_certificate_credentials cred;
 
 int _gnutls_proc_rsa_client_kx(gnutls_session session, opaque * data, size_t _data_size)
 {
-       gnutls_sdatum plaintext;
+       gnutls_sdatum plaintext = { NULL, 0 };
        gnutls_datum ciphertext;
        int ret, dsize;
        GNUTLS_MPI *params;
        int params_len;
+       int randomize_key = 0;
        ssize_t data_size = _data_size;
 
        if (gnutls_protocol_get_version(session) == GNUTLS_SSL3) {
-               /* SSL 3.0 */
+               /* SSL 3.0 
+                */
                ciphertext.data = data;
                ciphertext.size = data_size;
-       } else {                /* TLS 1 */
+       } else {
+               /* TLS 1.0
+                */
                DECR_LEN( data_size, 2);
                ciphertext.data = &data[2];
                dsize = _gnutls_read_uint16(data);
@@ -242,32 +246,52 @@ int _gnutls_proc_rsa_client_kx(gnutls_session session, opaque * data, size_t _da
                params_len, 2); /* btype==2 */
 
        if (ret < 0 || plaintext.size != TLS_MASTER_SIZE) {
-               /* in case decryption fails then don't inform
+               /* In case decryption fails then don't inform
                 * the peer. Just use a random key. (in order to avoid
                 * attack against pkcs-1 formating).
                 */
-               ret = 0;
                gnutls_assert();
-               _gnutls_x509_log("auth_rsa: Possible PKCS-1 format attack\n");
+               _gnutls_x509_log("auth_rsa: Possible PKCS #1 format attack\n");
+               randomize_key = 1;
+       } else {
+               /* If the secret was properly formatted, then
+                * check the version number.
+                */
+               if (_gnutls_get_adv_version_major(session) != plaintext.data[0]
+                   || _gnutls_get_adv_version_minor(session) != plaintext.data[1]) 
+               {
+                       /* No error is returned here, if the version number check
+                        * fails. We proceed normally.
+                        * That is to defend against the attack described in the paper
+                        * "Attacking RSA-based sessions in SSL/TLS" by Vlastimil Klima,
+                        * Ondej Pokorny and Tomas Rosa.
+                        */
+                       gnutls_assert();
+                       _gnutls_x509_log("auth_rsa: Possible PKCS #1 version check format attack\n");
+               }
+       }
+
+       if (randomize_key != 0) {
+               /* if the pkcs1 padding check failed, no need for
+                * that. That's why it has been initialized to zero.
+                */
+               _gnutls_free_datum( &plaintext);
 
                RANDOMIZE_KEY(session->key->key,
-                             gnutls_secure_malloc, GNUTLS_WEAK_RANDOM);
-       } else {
-               ret = 0;
-               if (session->internals.rsa_pms_check==0)
-                       if (_gnutls_get_adv_version_major(session) !=
-                           plaintext.data[0]
-                           || _gnutls_get_adv_version_minor(session) !=
-                           plaintext.data[1]) {
-                               gnutls_assert();
-                               ret = GNUTLS_E_DECRYPTION_FAILED;
-                       }
+                             gnutls_secure_malloc, GNUTLS_STRONG_RANDOM);
 
+       } else {
                session->key->key.data = plaintext.data;
                session->key->key.size = plaintext.size;
        }
 
-       return ret;
+       /* This is here to avoid the version check attack
+        * discussed above.
+        */
+       session->key->key.data[0] = _gnutls_get_adv_version_major(session);
+       session->key->key.data[1] = _gnutls_get_adv_version_minor(session);
+
+       return 0;
 }
 
 
@@ -294,8 +318,13 @@ int _gnutls_gen_rsa_client_kx(gnutls_session session, opaque ** data)
 
        ver = _gnutls_get_adv_version(session);
 
-       session->key->key.data[0] = _gnutls_version_get_major(ver);
-       session->key->key.data[1] = _gnutls_version_get_minor(ver);
+       if (session->internals.rsa_pms_version[0] == 0) {
+               session->key->key.data[0] = _gnutls_version_get_major(ver);
+               session->key->key.data[1] = _gnutls_version_get_minor(ver);
+       } else { /* use the version provided */
+               session->key->key.data[0] = session->internals.rsa_pms_version[0];
+               session->key->key.data[1] = session->internals.rsa_pms_version[1];
+       }
 
        /* move RSA parameters to key (session).
         */
index b1b521f5125c6b5b9b7d6d1182edaa9be1a03406..9b0f5416d18acd0f13d6759b93e9a47f90fcd13b 100644 (file)
@@ -84,8 +84,9 @@ typedef enum gnutls_connection_end { GNUTLS_SERVER=1, GNUTLS_CLIENT } gnutls_con
 
 typedef enum gnutls_alert_level { GNUTLS_AL_WARNING=1, GNUTLS_AL_FATAL } gnutls_alert_level;
 
-typedef enum gnutls_alert_description { GNUTLS_A_CLOSE_NOTIFY, GNUTLS_A_UNEXPECTED_MESSAGE=10, GNUTLS_A_BAD_RECORD_MAC=20,
-       GNUTLS_A_DECRYPTION_FAILED, GNUTLS_A_RECORD_OVERFLOW,  GNUTLS_A_DECOMPRESSION_FAILURE=30,
+typedef enum gnutls_alert_description { GNUTLS_A_CLOSE_NOTIFY, 
+       GNUTLS_A_UNEXPECTED_MESSAGE=10, GNUTLS_A_BAD_RECORD_MAC=20,
+       GNUTLS_A_DECRYPTION_FAILED, GNUTLS_A_RECORD_OVERFLOW, GNUTLS_A_DECOMPRESSION_FAILURE=30,
        GNUTLS_A_HANDSHAKE_FAILURE=40, GNUTLS_A_SSL3_NO_CERTIFICATE=41,
        GNUTLS_A_BAD_CERTIFICATE=42, GNUTLS_A_UNSUPPORTED_CERTIFICATE,
        GNUTLS_A_CERTIFICATE_REVOKED, GNUTLS_A_CERTIFICATE_EXPIRED, GNUTLS_A_CERTIFICATE_UNKNOWN,
@@ -96,6 +97,14 @@ typedef enum gnutls_alert_description { GNUTLS_A_CLOSE_NOTIFY, GNUTLS_A_UNEXPECT
        GNUTLS_A_CERTIFICATE_UNOBTAINABLE=111, GNUTLS_A_UNRECOGNIZED_NAME=112
 } gnutls_alert_description;
 
+typedef enum gnutls_handshake_description { GNUTLS_HANDSHAKE_HELLO_REQUEST, 
+       GNUTLS_HANDSHAKE_CLIENT_HELLO, GNUTLS_HANDSHAKE_SERVER_HELLO,
+       GNUTLS_HANDSHAKE_CERTIFICATE_PKT=11, GNUTLS_HANDSHAKE_SERVER_KEY_EXCHANGE,
+       GNUTLS_HANDSHAKE_CERTIFICATE_REQUEST, GNUTLS_HANDSHAKE_SERVER_HELLO_DONE,
+       GNUTLS_HANDSHAKE_CERTIFICATE_VERIFY, GNUTLS_HANDSHAKE_CLIENT_KEY_EXCHANGE,
+       GNUTLS_HANDSHAKE_FINISHED=20 
+} gnutls_handshake_description;
+
 typedef enum gnutls_certificate_status { 
        GNUTLS_CERT_NOT_TRUSTED=2, 
        GNUTLS_CERT_INVALID=4, 
@@ -184,8 +193,11 @@ const char* gnutls_strerror( int error);
 /* Semi-internal functions.
  */
 void gnutls_handshake_set_private_extensions(gnutls_session session, int allow);
-void gnutls_record_set_cbc_protection(gnutls_session session, int prot);
 void gnutls_handshake_set_rsa_pms_check(gnutls_session session, int check);
+gnutls_handshake_description gnutls_handshake_get_last_out( gnutls_session session);
+gnutls_handshake_description gnutls_handshake_get_last_in( gnutls_session session);
+
+void gnutls_record_set_cbc_protection(gnutls_session session, int prot);
 
 /* Record layer functions.
  */
index 704024c23fc1858fec85054431f1f68afd5973a1..68ec163551790eae9ed4b67b71fb34c6afe4b7d3 100644 (file)
@@ -1,7 +1,7 @@
 /*
  *  Copyright (C) 2000,2001,2002,2003 Nikos Mavroyanopoulos
  *
- * This file is part of GNUTLS.
+ *  This file is part of GNUTLS.
  *
  *  The GNUTLS library is free software; you can redistribute it and/or
  *  modify it under the terms of the GNU Lesser General Public   
index 237d1b0df56f5d55f9a86976f21bd6c78474d2ff..f92b33ecd22ca3281aba7aa5c2c607159ba6ddb7 100644 (file)
@@ -32,7 +32,7 @@ extern const ASN1_ARRAY_TYPE gnutls_asn1_tab[];
 extern const ASN1_ARRAY_TYPE pkix_asn1_tab[];
 
 LOG_FUNC _gnutls_log_func;
-int _gnutls_log_level = 2; /* default log level */
+int _gnutls_log_level = 1; /* default log level */
 
 static ASN1_TYPE PKIX1_ASN;
 static ASN1_TYPE GNUTLS_ASN;
index 244fc1ca2f3a9f99b6cf267600490bcce418fdbb..0f8a95497d08adf3ae08cd4e111abb2f53a0b28a 100644 (file)
@@ -1,7 +1,7 @@
 /*
- * Copyright (C) 2000,2001,2002,2003 Nikos Mavroyanopoulos
+ *  Copyright (C) 2000,2001,2002,2003 Nikos Mavroyanopoulos
  *
- * This file is part of GNUTLS.
+ *  This file is part of GNUTLS.
  *
  *  The GNUTLS library is free software; you can redistribute it and/or
  *  modify it under the terms of the GNU Lesser General Public   
@@ -766,6 +766,8 @@ int _gnutls_send_handshake(gnutls_session session, void *i_data,
                                return ret;
                }
 
+       session->internals.last_handshake_out = type;
+
        ret =
            _gnutls_handshake_io_send_int(session, GNUTLS_HANDSHAKE, type,
                                       data, datasize);
@@ -979,6 +981,7 @@ int _gnutls_recv_handshake(gnutls_session session, uint8 ** data,
                return ret;
        }
 
+       session->internals.last_handshake_in = recv_type;
 
        length32 = ret;
 
@@ -2251,18 +2254,17 @@ int _gnutls_handshake_common(gnutls_session session)
 int _gnutls_generate_session_id(char *session_id, uint8 * len)
 {
        char buf[64];
-       opaque rand[TLS_RANDOM_SIZE];
 
-       if (_gnutls_get_random(rand, TLS_RANDOM_SIZE, GNUTLS_WEAK_RANDOM) <
+       *len = TLS_RANDOM_SIZE;
+
+       if (_gnutls_get_random(session_id, *len, GNUTLS_WEAK_RANDOM) <
            0) {
                gnutls_assert();
                return GNUTLS_E_MEMORY_ERROR;
        }
-       memcpy(session_id, rand, TLS_RANDOM_SIZE);
-       *len = TLS_RANDOM_SIZE;
 
        _gnutls_handshake_log("HSK: Generated SessionID: %s\n",
-                   _gnutls_bin2hex(session_id, TLS_RANDOM_SIZE, buf, sizeof(buf)));
+                   _gnutls_bin2hex(session_id, *len, buf, sizeof(buf)));
 
        return 0;
 }
@@ -2444,3 +2446,34 @@ gnutls_protocol_version _gnutls_get_adv_version( gnutls_session session) {
                _gnutls_get_adv_version_minor( session));
 }
 
+/**
+  * gnutls_handshake_get_last_in - Returns the last handshake message received.
+  * @session: is a &gnutls_session structure.
+  *
+  * Returns the last handshake message received. This function is only useful
+  * to check where the last performed handshake failed. If the previous handshake
+  * succeed or was not performed at all then no meaningful value will be returned.
+  *
+  * Check gnutls.h for the available handshake descriptions.
+  **/
+gnutls_handshake_description gnutls_handshake_get_last_in( gnutls_session session) 
+{
+       return session->internals.last_handshake_in;
+}
+
+/**
+  * gnutls_handshake_get_last_out - Returns the last handshake message sent.
+  * @session: is a &gnutls_session structure.
+  *
+  * Returns the last handshake message sent. This function is only useful
+  * to check where the last performed handshake failed. If the previous handshake
+  * succeed or was not performed at all then no meaningful value will be returned.
+  *
+  * Check gnutls.h for the available handshake descriptions.
+  *
+  **/
+gnutls_handshake_description gnutls_handshake_get_last_out( gnutls_session session) 
+{
+       return session->internals.last_handshake_out;
+}
+
index 95ceadeac7171ac28c10b2cc9fef5e6116457c2e..e7edf79d76424fec7b88e01c05aadb403d3b6c7b 100644 (file)
@@ -132,6 +132,8 @@ typedef enum HandshakeType { GNUTLS_HELLO_REQUEST, GNUTLS_CLIENT_HELLO, GNUTLS_S
                     GNUTLS_CERTIFICATE_VERIFY, GNUTLS_CLIENT_KEY_EXCHANGE,
                     GNUTLS_FINISHED=20 } HandshakeType;
 
+typedef HandshakeType gnutls_handshake_description;
+
 typedef struct {
        opaque * data;
        unsigned int size;
@@ -441,6 +443,12 @@ typedef struct {
        int                             may_write;
 
        int                             last_alert; /* last alert received */
+
+       /* The last handshake messages sent or received.
+        */
+       int                             last_handshake_in;
+       int                             last_handshake_out;
+
        /* this is the compression method we are going to use */
        gnutls_compression_method               compression_method;
        /* priorities */
@@ -575,8 +583,6 @@ typedef struct {
 
        int                     cbc_protection_hack;
 
-       int                     rsa_pms_check; /* 0 means enabled */
-
        void*                   user_ptr;
 
        int                     enable_private;/* non zero to
@@ -599,6 +605,12 @@ typedef struct {
         */
        int                     ignore_rdn_sequence;
 
+       /* This is used to set an arbitary version in the RSA
+        * PMS secret. Can be used by clients to test whether the
+        * server checks that version.
+        */
+       opaque                  rsa_pms_version[2];
+
        /* If you add anything here, check _gnutls_handshake_internal_state_clear().
         */
 } GNUTLS_INTERNALS;
index ac8eab67dc7d78c5caf9d8d30d14e52c17e5b364..b4d4258b9c643a44d4509d0ca9574cb03f2674a6 100644 (file)
@@ -30,6 +30,7 @@
 #include <gnutls_random.h>
 #include <gnutls_datum.h>
 #include <gnutls_global.h>
+#include <gnutls_num.h>
 #include "debug.h"
 
 static int _gnutls_pk_encrypt(int algo, GNUTLS_MPI * resarr, GNUTLS_MPI data, GNUTLS_MPI * pkey, int pkey_len);
@@ -85,13 +86,35 @@ int _gnutls_pkcs1_rsa_encrypt(gnutls_datum * ciphertext,
                        return GNUTLS_E_INTERNAL_ERROR;
                }
                
-               if ( (ret=_gnutls_get_random(ps, psize, GNUTLS_WEAK_RANDOM)) < 0) {
+               if ( (ret=_gnutls_get_random(ps, psize, GNUTLS_STRONG_RANDOM)) < 0) {
                        gnutls_assert();
                        return ret;
                }
                for (i = 0; i < psize; i++) {
-                       if (ps[i] == 0)
-                               ps[i] = 0xff;
+                       opaque rnd[3];
+
+                       /* Read three random bytes that will be
+                        * used to replace the zeros.
+                        */
+                       if ( (ret=_gnutls_get_random( rnd, 3, GNUTLS_STRONG_RANDOM)) < 0) {
+                               gnutls_assert();
+                               return ret;
+                       }
+                       /* use non zero values for 
+                        * the first two.
+                        */
+                       if (rnd[0]==0) rnd[0] = 0xaf;
+                       if (rnd[1]==0) rnd[1] = 0xae;
+
+                       if (ps[i] == 0) {
+                               /* If the first one is zero then set it to rnd[0].
+                                * If the second one is zero then set it to rnd[1].
+                                * Otherwise add (mod 256) the two previous ones plus rnd[3], or use
+                                * rnd[1] if the value == 0.
+                                */
+                               if (i<2) ps[i] = rnd[i];
+                               else ps[i] = GMAX( rnd[3] + ps[i-1] + ps[i-2], rnd[1]);
+                       }
                }
                break;
        case 1:
index e10da9b33562be12fd4127e25a5b0f359fcbd092..624bdde8a025a6ec98e7adcce9345532c9d9fed5 100644 (file)
@@ -135,6 +135,12 @@ void _gnutls_handshake_internal_state_clear( gnutls_session session) {
        session->internals.adv_version_minor = 0;
        session->internals.direction = 0;
 
+       /* use out of band data for the last
+        * handshake messages received.
+        */
+       session->internals.last_handshake_in = -1;
+       session->internals.last_handshake_out = -1;
+
        session->internals.resumable = RESUME_TRUE;
 
 }
@@ -535,28 +541,6 @@ void gnutls_handshake_set_private_extensions(gnutls_session session, int allow)
        session->internals.enable_private = allow;
 }
 
-/**
-  * gnutls_handshake_set_rsa_pms_check - Used to disable the RSA PMS check
-  * @session: is a &gnutls_session structure.
-  * @prot: is an integer (0 or 1)
-  *
-  * The TLS 1.0 handshake protocol includes a check in the in the RSA
-  * encrypted data (only in the case of RSA key exchange), which allows
-  * to detect version roll back attacks. 
-  *
-  * However it seems that some broken TLS clients exist which do not
-  * use this check properly. The only solution is to disable this
-  * check completely.
-  *
-  * if check == 0 then the check is enabled (default), otherwise it
-  * is disabled.
-  *
-  **/
-void gnutls_handshake_set_rsa_pms_check(gnutls_session session, int check)
-{
-       session->internals.rsa_pms_check = check;
-}
-
 inline
 static void _gnutls_cal_PRF_A( gnutls_mac_algorithm algorithm, const void *secret, int secret_size, const void *seed, int seed_size, void* result)
 {
@@ -783,3 +767,20 @@ int gnutls_record_get_direction(gnutls_session session) {
        return session->internals.direction;
 }
 
+/*-
+  * _gnutls_rsa_pms_set_version - Sets a version to be used at the RSA PMS
+  * @session: is a &gnutls_session structure.
+  * @major: is the major version to use
+  * @minor: is the minor version to use
+  *
+  * This function will set the given version number to be used at the
+  * RSA PMS secret. This is only useful to clients, which want to
+  * test server's capabilities.
+  *
+  -*/
+void _gnutls_rsa_pms_set_version(gnutls_session session, unsigned char major,
+       unsigned char minor)
+{
+       session->internals.rsa_pms_version[0] = major;
+       session->internals.rsa_pms_version[1] = minor;
+}
index e05dbcc80be77f2d0c2ffd7303b637819e36b84c..ae3ba1f54e8a2f812476a57b41a5bde9a3ab7ac9 100644 (file)
@@ -44,6 +44,7 @@ static char *session_data = NULL;
 static char session_id[32];
 static int session_data_size=0, session_id_size=0;
 static int sfree=0;
+static int handshake_output = 0;
 
 int do_handshake( gnutls_session session) {
 int ret, alert;
@@ -52,6 +53,8 @@ int ret, alert;
                        ret = gnutls_handshake(session);
                } while (ret == GNUTLS_E_INTERRUPTED
                         || ret == GNUTLS_E_AGAIN);
+       
+               handshake_output = ret;
 
                if (ret < 0 && more_info != 0) {
                        printf("\n");
@@ -496,7 +499,8 @@ int ret;
         * and we connect using a 3.1 client hello version,
         * and a 3.0 record version. Some implementations
         * are buggy (and vulnerable to man in the middle
-        * attacks) and this connection will fail.
+        * attacks which allow a version downgrade) and this 
+        * connection will fail.
         */
        ADD_ALL_CIPHERS(session);
        ADD_ALL_COMP(session);
@@ -516,6 +520,31 @@ int ret;
        return SUCCEED;
 }
 
+void _gnutls_rsa_pms_set_version(gnutls_session session, unsigned char major,
+        unsigned char minor);
+
+int test_rsa_pms_version_check( gnutls_session session) 
+{
+int ret;
+       /* here we use an arbitary version in the RSA PMS
+        * to see whether to server will check this version.
+        *
+        * A normal server would abort this handshake.
+        */
+       ADD_ALL_CIPHERS(session);
+       ADD_ALL_COMP(session);
+       ADD_ALL_CERTTYPES(session);
+       ADD_ALL_PROTOCOLS(session);
+       ADD_ALL_MACS(session);
+       ADD_ALL_KX(session);
+       gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, xcred);
+       _gnutls_rsa_pms_set_version( session, 5, 5); /* use SSL 5.5 version */
+
+       ret = do_handshake( session);
+       return ret;
+
+}
+
 
 int test_anonymous( gnutls_session session) {
 int ret;
index 7ae0b24ea799225cfc9563abb0504f07823582d6..52eed9e1dc8c86df8bc8e47d5eee7de8c44fe6b8 100644 (file)
@@ -27,6 +27,7 @@ int test_bye( gnutls_session state);
 int test_certificate( gnutls_session state);
 int test_server_cas( gnutls_session state);
 int test_session_resume2( gnutls_session state);
+int test_rsa_pms_version_check( gnutls_session session);
 
 #define GERR(ret) fprintf(stderr, "*** Error: %s\n", gnutls_strerror(ret))
 
index f13c3dda226fe15449558bbb8c3c472d5ef9bc14..ac54d61012fa068c36620d5d801ec4254ed38d6d 100644 (file)
@@ -86,6 +86,7 @@ static const TLS_TEST tls_tests[] = {
         * buggy */
        { "whether we need to disable TLS 1.0", test_tls1_2, "no", "yes", "dunno" },
 
+       { "whether the server ignores the RSA PMS version", test_rsa_pms_version_check, "yes", "no", "dunno"},
        { "whether the server can accept Hello Extensions", test_hello_extension, "yes", "no", "dunno"},
        { "whether the server can accept cipher suites not in SSL 3.0 spec", test_unknown_ciphersuites, "yes", "no", "dunno"},
        { "for certificate information", test_certificate, "", "", "" },