On Mon, Jan 10, 2011 at 02:18:31PM +0100, Daniel Stenberg wrote:
> On Mon, 27 Dec 2010, Quinn Slack wrote:
> 
> Do you have any more recent updates?

Yes, I've attached a revised version of the patch that includes tests. I 
realized I could use gnutls-serv, which comes with GnuTLS and can act as a 
simple TLS-SRP Web server.

Adds http+tls-srp <server> test option and new tests 320-324:

test320 simple TLS-SRP HTTPS GET, check user in response
test321 TLS-SRP with bad username and password
test322 TLS-SRP with bad password
test323 TLS-SRP to non-TLS-SRP server
test324 TLS-SRP with server cert checking

Tests 300-324 all pass under GnuTLS, including torture tests (with the other 
mem leak fix, thanks for applying!).

In the code itself, there aren't yet any #ifdefs for TLS-SRP support; if 
GnuTLS is used, then TLS-SRP support will be enabled. There's also no check 
to ensure that the GnuTLS version is new enough (>=2.3) to have the right 
values for the TLS-SRP cipher suite. I'll work on those now. How does 
everything else look, though?

-Quinn
diff --git a/configure.ac b/configure.ac
index 92adc50..6018d0f 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2802,6 +2802,9 @@ if test "x$USE_SSLEAY" = "x1" -o "x$USE_WINDOWS_SSPI" = "x1" \
     -o "x$GNUTLS_ENABLED" = "x1" -o "x$NSS_ENABLED" = "x1"; then
   SUPPORT_FEATURES="$SUPPORT_FEATURES NTLM"
 fi
+if test "x$GNUTLS_ENABLED" = "x1"; then
+  SUPPORT_FEATURES="$SUPPORT_FEATURES TLS-SRP"
+fi
 
 AC_SUBST(SUPPORT_FEATURES)
 
diff --git a/include/curl/curl.h b/include/curl/curl.h
index fbd0d9b..bf65420 100644
--- a/include/curl/curl.h
+++ b/include/curl/curl.h
@@ -502,6 +502,7 @@ typedef enum {
   CURLE_RTSP_SESSION_ERROR,      /* 86 - mismatch of RTSP Session Identifiers */
   CURLE_FTP_BAD_FILE_LIST,       /* 87 - unable to parse FTP file list */
   CURLE_CHUNK_FAILED,            /* 88 - chunk callback reported error */
+  CURLE_TLSAUTH_FAILED,          /* 89 - Failed TLS authentication */
 
   CURL_LAST /* never use! */
 } CURLcode;
@@ -1442,6 +1443,15 @@ typedef enum {
   /* send linked-list of name:port:address sets */
   CINIT(RESOLVE, OBJECTPOINT, 203),
 
+  /* Set a username for authenticated TLS */
+  CINIT(TLSAUTH_USERNAME, OBJECTPOINT, 204),
+
+  /* Set a password for authenticated TLS */
+  CINIT(TLSAUTH_PASSWORD, OBJECTPOINT, 205),
+
+  /* Set authentication type for authenticated TLS */
+  CINIT(TLSAUTH_TYPE, OBJECTPOINT, 206),
+
   CURLOPT_LASTENTRY /* the last unused */
 } CURLoption;
 
@@ -1538,6 +1548,12 @@ enum {
   CURL_SSLVERSION_LAST /* never use, keep last */
 };
 
+enum CURL_TLSAUTH {
+  CURL_TLSAUTH_NONE,
+  CURL_TLSAUTH_SRP,
+  CURL_TLSAUTH_LAST /* never use, keep last */
+};
+
 /* symbols to use with CURLOPT_POSTREDIR.
    CURL_REDIR_POST_301 and CURL_REDIR_POST_302 can be bitwise ORed so that
    CURL_REDIR_POST_301 | CURL_REDIR_POST_302 == CURL_REDIR_POST_ALL */
@@ -2043,6 +2059,7 @@ typedef struct {
 #define CURL_VERSION_SSPI      (1<<11) /* SSPI is supported */
 #define CURL_VERSION_CONV      (1<<12) /* character conversions supported */
 #define CURL_VERSION_CURLDEBUG (1<<13) /* debug memory tracking supported */
+#define CURL_VERSION_TLSAUTH_SRP (1<<14) /* TLS-SRP auth is supported */
 
 /*
  * NAME curl_version_info()
diff --git a/lib/gtls.c b/lib/gtls.c
index 9a87c39..163a681 100644
--- a/lib/gtls.c
+++ b/lib/gtls.c
@@ -346,6 +346,27 @@ gtls_connect_step1(struct connectdata *conn,
     return CURLE_SSL_CONNECT_ERROR;
   }
 
+  if(data->set.ssl.authtype == CURL_TLSAUTH_SRP) {
+    infof(data, "Using TLS-SRP username: %s\n", data->set.ssl.username);
+
+    rc = gnutls_srp_allocate_client_credentials(
+           &conn->ssl[sockindex].srp_client_cred);
+    if(rc != GNUTLS_E_SUCCESS) {
+      failf(data, "gnutls_srp_allocate_client_cred() failed: %s",
+            gnutls_strerror(rc));
+      return CURLE_TLSAUTH_FAILED;
+    }
+
+    rc = gnutls_srp_set_client_credentials(conn->ssl[sockindex].srp_client_cred,
+                                           data->set.ssl.username,
+                                           data->set.ssl.password);
+    if(rc != GNUTLS_E_SUCCESS) {
+      failf(data, "gnutls_srp_set_client_cred() failed: %s",
+            gnutls_strerror(rc));
+      return CURLE_TLSAUTH_FAILED;
+    }
+  }
+
   if(data->set.ssl.CAfile) {
     /* set the trusted CA cert bundle file */
     gnutls_certificate_set_verify_flags(conn->ssl[sockindex].cred,
@@ -432,8 +453,16 @@ gtls_connect_step1(struct connectdata *conn,
   }
 
   /* put the credentials to the current session */
-  rc = gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE,
-                              conn->ssl[sockindex].cred);
+  if(data->set.ssl.authtype == CURL_TLSAUTH_SRP) {
+    rc = gnutls_credentials_set(session, GNUTLS_CRD_SRP,
+                                conn->ssl[sockindex].srp_client_cred);
+    if (rc != GNUTLS_E_SUCCESS) {
+      failf(data, "gnutls_credentials_set() failed: %s", gnutls_strerror(rc));
+    }
+  } else {
+    rc = gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE,
+                                conn->ssl[sockindex].cred);
+  }
 
   /* set the connection handle (file descriptor for the socket) */
   gnutls_transport_set_ptr(session,
@@ -496,8 +525,18 @@ gtls_connect_step3(struct connectdata *conn,
     if(data->set.ssl.verifypeer ||
        data->set.ssl.verifyhost ||
        data->set.ssl.issuercert) {
-      failf(data, "failed to get server cert");
-      return CURLE_PEER_FAILED_VERIFICATION;
+
+      if(data->set.ssl.authtype == CURL_TLSAUTH_SRP
+         && data->set.ssl.username != NULL
+         && !data->set.ssl.verifypeer
+         && gnutls_cipher_get(session)) {
+        /* no peer cert, but auth is ok if we have SRP user and cipher and no
+           peer verify */
+      }
+      else {
+        failf(data, "failed to get server cert");
+        return CURLE_PEER_FAILED_VERIFICATION;
+      }
     }
     infof(data, "\t common name: WARNING couldn't obtain\n");
   }
@@ -530,8 +569,10 @@ gtls_connect_step3(struct connectdata *conn,
     else
       infof(data, "\t server certificate verification OK\n");
   }
-  else
+  else {
     infof(data, "\t server certificate verification SKIPPED\n");
+    goto after_server_cert_verification;
+  }
 
   /* initialize an X.509 certificate structure. */
   gnutls_x509_crt_init(&x509_cert);
@@ -661,6 +702,8 @@ gtls_connect_step3(struct connectdata *conn,
 
   gnutls_x509_crt_deinit(x509_cert);
 
+after_server_cert_verification:
+
   /* compression algorithm (if any) */
   ptr = gnutls_compression_get_name(gnutls_compression_get(session));
   /* the *_get_name() says "NULL" if GNUTLS_COMP_NULL is returned */
@@ -820,6 +863,10 @@ static void close_one(struct connectdata *conn,
     gnutls_certificate_free_credentials(conn->ssl[idx].cred);
     conn->ssl[idx].cred = NULL;
   }
+  if (conn->ssl[idx].srp_client_cred) {
+    gnutls_srp_free_client_credentials(conn->ssl[idx].srp_client_cred);
+    conn->ssl[idx].srp_client_cred = NULL;
+  }
 }
 
 void Curl_gtls_close(struct connectdata *conn, int sockindex)
@@ -888,6 +935,9 @@ int Curl_gtls_shutdown(struct connectdata *conn, int sockindex)
     gnutls_deinit(conn->ssl[sockindex].session);
   }
   gnutls_certificate_free_credentials(conn->ssl[sockindex].cred);
+  if(data->set.ssl.authtype == CURL_TLSAUTH_SRP
+     && data->set.ssl.username != NULL)
+    gnutls_srp_free_client_credentials(conn->ssl[sockindex].srp_client_cred);
 
   conn->ssl[sockindex].cred = NULL;
   conn->ssl[sockindex].session = NULL;
diff --git a/lib/url.c b/lib/url.c
index f8bd07a..dba7280 100644
--- a/lib/url.c
+++ b/lib/url.c
@@ -751,6 +751,7 @@ CURLcode Curl_init_userdefined(struct UserDefined *set)
    */
   set->ssl.verifypeer = TRUE;
   set->ssl.verifyhost = 2;
+  set->ssl.authtype = CURL_TLSAUTH_NONE;
   set->ssh_auth_types = CURLSSH_AUTH_DEFAULT; /* defaults to any auth
                                                       type */
   set->ssl.sessionid = TRUE; /* session ID caching enabled by default */
@@ -2526,6 +2527,24 @@ CURLcode Curl_setopt(struct SessionHandle *data, CURLoption option,
   case CURLOPT_FNMATCH_DATA:
     data->set.fnmatch_data = va_arg(param, void *);
     break;
+  case CURLOPT_TLSAUTH_USERNAME:
+    result = setstropt(&data->set.str[STRING_TLSAUTH_USERNAME],
+                       va_arg(param, char *));
+    if (data->set.str[STRING_TLSAUTH_USERNAME] && !data->set.ssl.authtype)
+      data->set.ssl.authtype = CURL_TLSAUTH_SRP; /* default to SRP */
+    break;
+  case CURLOPT_TLSAUTH_PASSWORD:
+    result = setstropt(&data->set.str[STRING_TLSAUTH_PASSWORD],
+                       va_arg(param, char *));
+    if (data->set.str[STRING_TLSAUTH_USERNAME] && !data->set.ssl.authtype)
+      data->set.ssl.authtype = CURL_TLSAUTH_SRP; /* default to SRP */
+    break;
+  case CURLOPT_TLSAUTH_TYPE:
+    if (strncmp((char *)va_arg(param, char *), "SRP", strlen("SRP")) == 0)
+      data->set.ssl.authtype = CURL_TLSAUTH_SRP;
+    else
+      data->set.ssl.authtype = CURL_TLSAUTH_NONE;
+    break;
   default:
     /* unknown tag and its companion, just ignore: */
     result = CURLE_FAILED_INIT; /* correct this */
@@ -4929,6 +4948,8 @@ static CURLcode create_conn(struct SessionHandle *data,
   data->set.ssl.random_file = data->set.str[STRING_SSL_RANDOM_FILE];
   data->set.ssl.egdsocket = data->set.str[STRING_SSL_EGDSOCKET];
   data->set.ssl.cipher_list = data->set.str[STRING_SSL_CIPHER_LIST];
+  data->set.ssl.username = data->set.str[STRING_TLSAUTH_USERNAME];
+  data->set.ssl.password = data->set.str[STRING_TLSAUTH_PASSWORD];
 
   if(!Curl_clone_ssl_config(&data->set.ssl, &conn->ssl_config))
     return CURLE_OUT_OF_MEMORY;
diff --git a/lib/urldata.h b/lib/urldata.h
index 55167fb..5788696 100644
--- a/lib/urldata.h
+++ b/lib/urldata.h
@@ -251,6 +251,7 @@ struct ssl_connect_data {
 #ifdef USE_GNUTLS
   gnutls_session session;
   gnutls_certificate_credentials cred;
+  gnutls_srp_client_credentials srp_client_cred;
   ssl_connect_state connecting_state;
 #endif /* USE_GNUTLS */
 #ifdef USE_POLARSSL
@@ -300,6 +301,10 @@ struct ssl_config_data {
   void *fsslctxp;        /* parameter for call back */
   bool sessionid;        /* cache session IDs or not */
   bool certinfo;         /* gather lots of certificate info */
+
+  char *username; /* TLS username (for, e.g., SRP) */
+  char *password; /* TLS password (for, e.g., SRP) */
+  enum CURL_TLSAUTH authtype; /* TLS authentication type (default SRP) */
 };
 
 /* information stored about one single SSL session */
@@ -1315,6 +1320,9 @@ enum dupstring {
 #endif
   STRING_MAIL_FROM,
 
+  STRING_TLSAUTH_USERNAME,     /* TLS auth <username> */
+  STRING_TLSAUTH_PASSWORD,     /* TLS auth <password> */
+
   /* -- end of strings -- */
   STRING_LAST /* not used, just an end-of-list marker */
 };
diff --git a/lib/version.c b/lib/version.c
index 5931e71..947b2eb 100644
--- a/lib/version.c
+++ b/lib/version.c
@@ -265,6 +265,9 @@ static curl_version_info_data version_info = {
 #if defined(CURL_DOES_CONVERSIONS)
   | CURL_VERSION_CONV
 #endif
+#if defined(USE_GNUTLS)
+  | CURL_VERSION_TLSAUTH_SRP
+#endif
   ,
   NULL, /* ssl_version */
   0,    /* ssl_version_num, this is kept at zero */
diff --git a/src/main.c b/src/main.c
index 7e2e34e..8d2cfc7 100644
--- a/src/main.c
+++ b/src/main.c
@@ -503,6 +503,9 @@ struct Configurable {
   long low_speed_time;
   bool showerror;
   char *userpwd;
+  char *tls_username;
+  char *tls_password;
+  char *tls_authtype;
   char *proxyuserpwd;
   char *proxy;
   int proxyver;     /* set to CURLPROXY_HTTP* define */
@@ -903,6 +906,9 @@ static void help(void)
     "    --url <URL>     Set URL to work with",
     " -B/--use-ascii     Use ASCII/text transfer",
     " -u/--user <user[:password]> Set server user and password",
+    "    --tlsuser     <user> Set TLS username",
+    "    --tlspassword <string> Set TLS password",
+    "    --tlsauthtype <string> Set TLS authentication type (default SRP)",
     " -A/--user-agent <string> User-Agent to send to server (H)",
     " -v/--verbose       Make the operation more talkative",
     " -V/--version       Show version number and quit",
@@ -1916,6 +1922,9 @@ static ParameterError getparameter(char *flag, /* f or -long-flag */
     {"Eh","pubkey",      TRUE},
     {"Ei", "hostpubmd5", TRUE},
     {"Ej","crlfile",     TRUE},
+    {"Ek","tlsuser",     TRUE},
+    {"El","tlspassword", TRUE},
+    {"Em","tlsauthtype", TRUE},
     {"f", "fail",        FALSE},
     {"F", "form",        TRUE},
     {"Fs","form-string", TRUE},
@@ -2744,6 +2753,26 @@ static ParameterError getparameter(char *flag, /* f or -long-flag */
         /* CRL file */
         GetStr(&config->crlfile, nextarg);
         break;
+      case 'k': /* TLS username */
+        if(curlinfo->features & CURL_VERSION_TLSAUTH_SRP) {
+           GetStr(&config->tls_username, nextarg);
+        } else
+          return PARAM_LIBCURL_DOESNT_SUPPORT;
+	break;
+      case 'l': /* TLS password */
+        if(curlinfo->features & CURL_VERSION_TLSAUTH_SRP) {
+           GetStr(&config->tls_password, nextarg);
+        } else
+          return PARAM_LIBCURL_DOESNT_SUPPORT;
+	break;
+      case 'm': /* TLS authentication type */
+        if(curlinfo->features & CURL_VERSION_TLSAUTH_SRP) {
+          GetStr(&config->tls_authtype, nextarg);
+          if (strncmp(config->tls_authtype, "SRP", strlen("SRP")) != 0)
+            return PARAM_LIBCURL_DOESNT_SUPPORT; /* only support TLS-SRP */
+        } else
+          return PARAM_LIBCURL_DOESNT_SUPPORT;
+	break;
       default: /* certificate file */
       {
         char *ptr = strchr(nextarg, ':');
@@ -3122,7 +3151,8 @@ static ParameterError getparameter(char *flag, /* f or -long-flag */
           {"SSPI",  CURL_VERSION_SSPI},
           {"krb4", CURL_VERSION_KERBEROS4},
           {"libz", CURL_VERSION_LIBZ},
-          {"CharConv", CURL_VERSION_CONV}
+          {"CharConv", CURL_VERSION_CONV},
+          {"TLS-SRP", CURL_VERSION_TLSAUTH_SRP}
         };
         printf("Features: ");
         for(i=0; i<sizeof(feats)/sizeof(feats[0]); i++) {
@@ -4048,6 +4078,12 @@ static void free_config_fields(struct Configurable *config)
     free(config->hostpubmd5);
   if(config->mail_from)
     free(config->mail_from);
+  if(config->tls_authtype)
+    free(config->tls_authtype);
+  if(config->tls_username)
+    free(config->tls_username);
+  if(config->tls_password)
+    free(config->tls_password);
 #if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI)
   if(config->socks5_gssapi_service)
     free(config->socks5_gssapi_service);
@@ -5462,6 +5498,10 @@ operate(struct Configurable *config, int argc, argv_item_t argv[])
           /* new in 7.21.3 */
           my_setopt(curl, CURLOPT_RESOLVE, config->resolve);
 
+        /* TODO: new in ### */
+        curl_easy_setopt(curl, CURLOPT_TLSAUTH_USERNAME, config->tls_username);
+        curl_easy_setopt(curl, CURLOPT_TLSAUTH_PASSWORD, config->tls_password);
+
         retry_numretries = config->req_retry;
 
         retrystart = cutil_tvnow();
diff --git a/tests/FILEFORMAT b/tests/FILEFORMAT
index f4565d3..05d622f 100644
--- a/tests/FILEFORMAT
+++ b/tests/FILEFORMAT
@@ -158,6 +158,7 @@ rtsp-ipv6
 imap
 pop3
 smtp
+http+tls-srp
 
 Give only one per line.  This subsection is mandatory.
 </server>
diff --git a/tests/certs/srp-verifier-conf b/tests/certs/srp-verifier-conf
new file mode 100644
index 0000000..67825ce
--- /dev/null
+++ b/tests/certs/srp-verifier-conf
@@ -0,0 +1,3 @@
+1:Ewl2hcjiutMd3Fu2lgFnUXWSc67TVyy2vwYCKoS9MLsrdJVT9RgWTCuEqWJrfB6uE3LsE9GkOlaZabS7M29sj5TnzUqOLJMjiwEzArfiLr9WbMRANlF68N5AVLcPWvNx6Zjl3m5Scp0BzJBz9TkgfhzKJZ.WtP3Mv/67I/0wmRZ:2
+2:dUyyhxav9tgnyIg65wHxkzkb7VIPh4o0lkwfOKiPp4rVJrzLRYVBtb76gKlaO7ef5LYGEw3G.4E0jbMxcYBetDy2YdpiP/3GWJInoBbvYHIRO9uBuxgsFKTKWu7RnR7yTau/IrFTdQ4LY/q.AvoCzMxV0PKvD9Odso/LFIItn8PbTov3VMn/ZEH2SqhtpBUkWtmcIkEflhX/YY/fkBKfBbe27/zUaKUUZEUYZ2H2nlCL60.JIPeZJSzsu/xHDVcx:2
+3:2iQzj1CagQc/5ctbuJYLWlhtAsPHc7xWVyCPAKFRLWKADpASkqe9djWPFWTNTdeJtL8nAhImCn3Sr/IAdQ1FrGw0WvQUstPx3FO9KNcXOwisOQ1VlL.gheAHYfbYyBaxXL.NcJx9TUwgWDT0hRzFzqSrdGGTN3FgSTA1v4QnHtEygNj3eZ.u0MThqWUaDiP87nqha7XnT66bkTCkQ8.7T8L4KZjIImrNrUftedTTBi.WCi.zlrBxDuOM0da0JbUkQlXqvp0yvJAPpC11nxmmZOAbQOywZGmu9nhZNuwTlxjfIro0FOdthaDTuZRL9VL7MRPUDo/DQEyW.d4H.UIlzp:2
diff --git a/tests/certs/srp-verifier-db b/tests/certs/srp-verifier-db
new file mode 100644
index 0000000..2f851a3
--- /dev/null
+++ b/tests/certs/srp-verifier-db
@@ -0,0 +1,2 @@
+jsmith:34fPk7u.w3R/M1k2sQ9F.04GZqLKAsqDn44CHGu7ML0M8VWwu1p79OLxi6jRhSNdSM46Kx9GRVyJLXz7eok53..A6X5p3NdnMSYX8WwYrDmuseHDr.eua7gjd04S4EoY4ZuKix2.WGAsMTwk86AmTvcqyzqsH7GDhGOHEhjP5zs:lTjBBoK04K9vTKiL10rI/:1
+alice:3IIP1g1HDTN6VEUr8DUkMleocoC1cpuFZnmunDaGhMyIsw8LAwCc7ZapWaC66gZSyis4ezSuCqvhsJdwdc.0es2UrH6PBkBQflcQDuC.dEpjhWgAcH2Dw.2qU.E0ApQzLkcKOjXMQ2R6jMBL14kEUPjjHS3aa16yB.Afj3bNPdf:1JxU4GkweUEii6.b0grkzU:1
diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am
index 701b324..6f4e77a 100644
--- a/tests/data/Makefile.am
+++ b/tests/data/Makefile.am
@@ -69,7 +69,7 @@ EXTRA_DIST = test1 test108 test117 test127 test20 test27 test34 test46	   \
  test569 test570 test571 test572 test804 test805 test806 test807 test573   \
  test313 test1115 test578 test579 test1116 test1200 test1201 test1202	   \
  test1203 test1117 test1118 test1119 test1120 test1300 test1301 test1302 \
- test1303
+ test1303 test320 test321 test322 test323 test324
 
 filecheck:
 	@mkdir test-place; \
diff --git a/tests/data/test320 b/tests/data/test320
new file mode 100644
index 0000000..450116f
--- /dev/null
+++ b/tests/data/test320
@@ -0,0 +1,95 @@
+<testcase>
+<info>
+<keywords>
+HTTPS
+HTTP GET
+TLS-SRP
+</keywords>
+</info>
+
+# Server-side
+<reply>
+<data nocheck="yes">
+HTTP/1.0 200 OK
+Content-type: text/html
+
+
+<HTML><BODY>
+<CENTER><H1>This is <a href="http://www.gnu.org/software/gnutls";>GNUTLS</a></H1></CENTER>
+
+
+<p>Session ID: <i>003030000100000001000000000000000030330001000000B062410001000000</i></p>
+<h5>If your browser supports session resuming, then you should see the same session ID, when you press the <b>reload</b> button.</h5>
+<p>Connected as user 'jsmith'.</p>
+<P>
+<TABLE border=1><TR><TD>Protocol version:</TD><TD>TLS1.2</TD></TR>
+<TR><TD>Key Exchange:</TD><TD>SRP</TD></TR>
+<TR><TD>Compression</TD><TD>NULL</TD></TR>
+<TR><TD>Cipher</TD><TD>AES-128-CBC</TD></TR>
+<TR><TD>MAC</TD><TD>SHA1</TD></TR>
+<TR><TD>Ciphersuite</TD><TD>SRP_SHA_AES_128_CBC_SHA1</TD></TR></p></TABLE>
+<hr><P>Your HTTP header was:<PRE>User-Agent: curl/7.21.4-DEV (x86_64-apple-darwin10.5.0) libcurl/7.21.4-DEV GnuTLS/2.10.4 zlib/1.2.5 libidn/1.19
+Host: localhost:9011
+Accept: */*
+
+</PRE></P>
+</BODY></HTML>
+
+</data>
+</reply>
+
+# Client-side
+<client>
+<server>
+http+tls-srp
+</server>
+<features>
+GnuTLS
+</features>
+ <name>
+simple TLS-SRP HTTPS GET, check user in response
+ </name>
+<command>
+--insecure --tlsauthtype SRP --tlsuser jsmith --tlspassword abc https://%HOSTIP:%HTTPTLSSRPPORT
+</command>
+</client>
+
+# Verify data after the test has been "shot"
+<verify>
+<protocol>
+</protocol>
+<file name="log/curl320.out" mode="text">
+HTTP/1.0 200 OK
+Content-type: text/html
+
+
+<HTML><BODY>
+<CENTER><H1>This is <a href="http://www.gnu.org/software/gnutls";>GNUTLS</a></H1></CENTER>
+
+
+
+<h5>If your browser supports session resuming, then you should see the same session ID, when you press the <b>reload</b> button.</h5>
+<p>Connected as user 'jsmith'.</p>
+<P>
+<TABLE border=1><TR><TD>
+<TR><TD>Key Exchange:</TD><TD>SRP</TD></TR>
+<TR><TD>Compression</TD><TD>NULL</TD></TR>
+<TR><TD>Cipher</TD><TD>AES-128-CBC</TD></TR>
+<TR><TD>MAC</TD><TD>SHA1</TD></TR>
+<TR><TD>Ciphersuite</TD><TD>SRP_SHA_AES_128_CBC_SHA1</TD></TR></p></TABLE>
+
+Host: %HOSTIP:%HTTPTLSSRPPORT
+Accept: */*
+
+</PRE></P>
+</BODY></HTML>
+
+</file>
+<stripfile>
+s/^<p>Session ID:.*//
+s/^<hr><P>Your HTTP header was:.*//
+s/Protocol version:.*//
+</stripfile>
+</verify>
+
+</testcase>
diff --git a/tests/data/test321 b/tests/data/test321
new file mode 100644
index 0000000..0089520
--- /dev/null
+++ b/tests/data/test321
@@ -0,0 +1,33 @@
+<testcase>
+<info>
+<keywords>
+HTTPS
+TLS-SRP
+FAILURE
+</keywords>
+</info>
+
+# Client-side
+<client>
+<server>
+http+tls-srp
+</server>
+<features>
+GnuTLS
+</features>
+ <name>
+TLS-SRP with bad username and password
+ </name>
+<command>
+--insecure --tlsauthtype SRP --tlsuser baduser --tlspassword badpass https://%HOSTIP:%HTTPTLSSRPPORT
+</command>
+</client>
+
+# Verify data after the test has been "shot"
+<verify>
+<errorcode>
+35
+</errorcode>
+</verify>
+
+</testcase>
diff --git a/tests/data/test322 b/tests/data/test322
new file mode 100644
index 0000000..d6cac82
--- /dev/null
+++ b/tests/data/test322
@@ -0,0 +1,33 @@
+<testcase>
+<info>
+<keywords>
+HTTPS
+TLS-SRP
+FAILURE
+</keywords>
+</info>
+
+# Client-side
+<client>
+<server>
+http+tls-srp
+</server>
+<features>
+GnuTLS
+</features>
+ <name>
+TLS-SRP with bad password
+ </name>
+<command>
+--insecure --tlsauthtype SRP --tlsuser jsmith --tlspassword badpass https://%HOSTIP:%HTTPTLSSRPPORT
+</command>
+</client>
+
+# Verify data after the test has been "shot"
+<verify>
+<errorcode>
+35
+</errorcode>
+</verify>
+
+</testcase>
diff --git a/tests/data/test323 b/tests/data/test323
new file mode 100644
index 0000000..bb99fef
--- /dev/null
+++ b/tests/data/test323
@@ -0,0 +1,33 @@
+<testcase>
+<info>
+<keywords>
+HTTPS
+TLS-SRP
+FAILURE
+</keywords>
+</info>
+
+# Client-side
+<client>
+<server>
+https
+</server>
+<features>
+GnuTLS
+</features>
+ <name>
+TLS-SRP to non-TLS-SRP server
+ </name>
+<command>
+--insecure --tlsauthtype SRP --tlsuser jsmith --tlspassword badpass https://%HOSTIP:%HTTPSPORT/want/323
+</command>
+</client>
+
+# Verify data after the test has been "shot"
+<verify>
+<errorcode>
+35
+</errorcode>
+</verify>
+
+</testcase>
diff --git a/tests/data/test324 b/tests/data/test324
new file mode 100644
index 0000000..5696d29
--- /dev/null
+++ b/tests/data/test324
@@ -0,0 +1,33 @@
+<testcase>
+<info>
+<keywords>
+HTTPS
+TLS-SRP
+FAILURE
+</keywords>
+</info>
+
+# Client-side
+<client>
+<server>
+http+tls-srp
+</server>
+<features>
+GnuTLS
+</features>
+ <name>
+TLS-SRP with server cert checking
+ </name>
+<command> # no --insecure
+--tlsauthtype SRP --tlsuser jsmith --tlspassword abc https://%HOSTIP:%HTTPTLSSRPPORT/want/323
+</command>
+</client>
+
+# Verify data after the test has been "shot"
+<verify>
+<errorcode>
+51
+</errorcode>
+</verify>
+
+</testcase>
diff --git a/tests/runtests.pl b/tests/runtests.pl
index 9f44306..66fe115 100755
--- a/tests/runtests.pl
+++ b/tests/runtests.pl
@@ -135,6 +135,7 @@ my $RTSPPORT; # RTSP
 my $RTSP6PORT; # RTSP IPv6 server port
 my $GOPHERPORT; # Gopher
 my $GOPHER6PORT; # Gopher IPv6 server port
+my $HTTPTLSSRPPORT; # TLS-SRP HTTP port
 
 my $srcdir = $ENV{'srcdir'} || '.';
 my $CURL="../src/curl".exe_ext(); # what curl executable to run on the tests
@@ -334,7 +335,7 @@ sub init_serverpidfile_hash {
       }
     }
   }
-  for my $proto (('tftp', 'sftp', 'socks', 'ssh', 'rtsp', 'gopher')) {
+  for my $proto (('tftp', 'sftp', 'socks', 'ssh', 'rtsp', 'gopher', 'http+tls-srp')) {
     for my $ipvnum ((4, 6)) {
       for my $idnum ((1, 2)) {
         my $serv = servername_id($proto, $ipvnum, $idnum);
@@ -957,6 +958,81 @@ sub verifysftp {
     return $verified;
 }
 
+#######################################################################
+# Verify that the TLS-SRP HTTP server that runs on $ip, $port is our server.
+# This also implies that we can speak with it, as there might be occasions when
+# the server runs fine but we cannot talk to it ("Failed to connect to ::1:
+# Can't assign requested address" #
+
+sub verifyhttptlssrp {
+    my ($proto, $ipvnum, $idnum, $ip, $port) = @_;
+    my $server = servername_id($proto, $ipvnum, $idnum);
+    my $pidfile = server_pidfilename($proto, $ipvnum, $idnum);
+    my $pid = 0;
+    my $bonus="";
+
+    my $verifyout = "$LOGDIR/".
+        servername_canon($proto, $ipvnum, $idnum) .'_verify.out';
+    unlink($verifyout) if(-f $verifyout);
+
+    my $verifylog = "$LOGDIR/".
+        servername_canon($proto, $ipvnum, $idnum) .'_verify.log';
+    unlink($verifylog) if(-f $verifylog);
+
+    my $flags = "--max-time $server_response_maxtime ";
+    $flags .= "--output $verifyout ";
+    $flags .= "--verbose ";
+    $flags .= "--globoff ";
+    $flags .= "--insecure ";
+    $flags .= "--tlsauthtype SRP --tlsuser jsmith --tlspassword abc ";
+    $flags .= "\"https://$ip:$port/verifiedserver\"";;
+
+    my $cmd = "$VCURL $flags 2>$verifylog";
+
+    # verify if our/any server is running on this port
+    logmsg "RUN: $cmd\n" if($verbose);
+    my $res = runclient($cmd);
+
+    $res >>= 8; # rotate the result
+    if($res & 128) {
+        logmsg "RUN: curl command died with a coredump\n";
+        return -1;
+    }
+
+    if($res && $verbose) {
+        logmsg "RUN: curl command returned $res\n";
+        if(open(FILE, "<$verifylog")) {
+            while(my $string = <FILE>) {
+                logmsg "RUN: $string" if($string !~ /^([ \t]*)$/);
+            }
+            close(FILE);
+        }
+    }
+
+    my $data;
+    if(open(FILE, "<$verifyout")) {
+        while(my $string = <FILE>) {
+            $data .= $string;
+        }
+        close(FILE);
+    }
+
+    if($data && ($data =~ /GNUTLS/) && open(FILE, "<$pidfile")) {
+        $pid=0+<FILE>;
+        close(FILE);
+        return $pid;
+    }
+    elsif($res == 6) {
+        # curl: (6) Couldn't resolve host '::1'
+        logmsg "RUN: failed to resolve host (https://$ip:$port/verifiedserver)\n";
+        return -1;
+    }
+    elsif($data || ($res && ($res != 7))) {
+        logmsg "RUN: Unknown server on our $server port: $port ($res)\n";
+        return -1;
+    }
+    return $pid;
+}
 
 #######################################################################
 # STUB for verifying socks
@@ -1001,7 +1077,8 @@ my %protofunc = ('http' => \&verifyhttp,
                  'tftp' => \&verifyftp,
                  'ssh' => \&verifyssh,
                  'socks' => \&verifysocks,
-                 'gopher' => \&verifyhttp);
+                 'gopher' => \&verifyhttp,
+                 'http+tls-srp' => \&verifyhttptlssrp);
 
 sub verifyserver {
     my ($proto, $ipvnum, $idnum, $ip, $port) = @_;
@@ -1192,6 +1269,81 @@ sub runhttpsserver {
 }
 
 #######################################################################
+# start the TLS-SRP HTTP server
+#
+sub runhttptlssrpserver {
+    my ($verbose) = @_;
+    my $proto = "http+tls-srp";
+    my $ip = $HOSTIP;
+    my $port = $HTTPTLSSRPPORT;
+    my $ipvnum = 4;
+    my $idnum = 1;
+    my $server;
+    my $srvrname;
+    my $pidfile;
+    my $logfile;
+    my $flags = "";
+
+    $server = servername_id($proto, $ipvnum, $idnum);
+
+    $pidfile = $serverpidfile{$server};
+
+    # don't retry if the server doesn't work
+    if ($doesntrun{$pidfile}) {
+        return (0,0);
+    }
+
+    my $pid = processexists($pidfile);
+    if($pid > 0) {
+        stopserver($server, "$pid");
+    }
+    unlink($pidfile) if(-f $pidfile);
+
+    $srvrname = servername_str($proto, $ipvnum, $idnum);
+
+    $logfile = server_logfilename($LOGDIR, $proto, $ipvnum, $idnum);
+
+    $flags .= "--fork " if($forkserver);
+    $flags .= "--http ";
+    $flags .= "-d 1 " if($debugprotocol);
+    $flags .= "--port $port ";
+    $flags .= "--srppasswd certs/srp-verifier-db --srppasswdconf certs/srp-verifier-conf ";
+
+    # TODO(sqs): make find_gnutls_serv
+    my $cmd = "gnutls-serv $flags";
+    my ($httptlssrppid, $pid2) = startnew($cmd, $pidfile, 1, 1);
+
+    if($httptlssrppid <= 0 || !kill(0, $httptlssrppid)) {
+        # it is NOT alive
+        logmsg "RUN: failed to start the $srvrname server\n";
+        stopserver($server, "$pid2");
+        displaylogs($testnumcheck);
+        $doesntrun{$pidfile} = 1;
+        return (0,0);
+    }
+
+    # Server is up. Verify that we can speak to it.
+    my $pid3 = verifyserver($proto, $ipvnum, $idnum, $ip, $port);
+    if(!$pid3) {
+        logmsg "RUN: $srvrname server failed verification\n";
+        # failed to talk to it properly. Kill the server and return failure
+        stopserver($server, "$httptlssrppid $pid2");
+        displaylogs($testnumcheck);
+        $doesntrun{$pidfile} = 1;
+        return (0,0);
+    }
+    $pid2 = $pid3;
+
+    if($verbose) {
+        logmsg "RUN: $srvrname server is now running PID $httptlssrppid\n";
+    }
+
+    sleep(1);
+
+    return ($httptlssrppid, $pid2);
+}
+
+#######################################################################
 # start the pingpong server (FTP, POP3, IMAP, SMTP)
 #
 sub runpingpongserver {
@@ -1967,6 +2119,13 @@ sub checksystem {
             # compiled in because the <features> test will fail.
             push @protocols, map($_ . "-ipv6", @protocols);
 
+            # Hack - we need a different, non-stunnel server to test HTTP
+            # TLS-SRP, but we don't want to add HTTP+TLS-SRP as a protocol
+            # throughout curl
+            if ($has_gnutls) {
+                push @protocols, ('http+tls-srp');
+            }
+
             # 'none' is used in test cases to mean no server
             push @protocols, ('none');
         }
@@ -2171,6 +2330,7 @@ sub subVariables {
   $$thing =~ s/%RTSP6PORT/$RTSP6PORT/g;
   $$thing =~ s/%GOPHERPORT/$GOPHERPORT/g;
   $$thing =~ s/%GOPHER6PORT/$GOPHER6PORT/g;
+  $$thing =~ s/%HTTPTLSSRPPORT/$HTTPTLSSRPPORT/g;
 
   # The purpose of FTPTIME2 and FTPTIME3 is to provide times that can be
   # used for time-out tests and that whould work on most hosts as these
@@ -3257,7 +3417,7 @@ sub startservers {
     for(@what) {
         my (@whatlist) = split(/\s+/,$_);
         my $what = lc($whatlist[0]);
-        $what =~ s/[^a-z0-9-]//g;
+        $what =~ s/[^a-z0-9-+]//g;
 
         my $certfile;
         if($what =~ /^(ftp|http|imap|pop3|smtp)s(.*)$/) {
@@ -3432,6 +3592,20 @@ sub startservers {
                 $run{'https'}="$pid $pid2";
             }
         }
+        elsif($what eq "http+tls-srp") {
+            if(!$has_gnutls) {
+                return "no GnuTLS";
+            }
+            if(!$run{'http+tls-srp'}) {
+                ($pid, $pid2) = runhttptlssrpserver($verbose);
+                if($pid <= 0) {
+                    return "failed starting HTTP+TLS-SRP server (gnutls-serv)";
+                }
+                logmsg sprintf("* pid http+tls-srp => %d %d\n", $pid, $pid2)
+                    if($verbose);
+                $run{'http+tls-srp'}="$pid $pid2";
+            }
+        }
         elsif($what eq "tftp") {
             if(!$run{'tftp'}) {
                 ($pid, $pid2) = runtftpserver("", $verbose);
@@ -3522,7 +3696,7 @@ sub serverfortest {
         my $proto = lc($_);
         chomp $proto;
         $proto =~ s/\s.*//g;  # take first word
-        if (! grep /^$proto$/, @protocols) {
+        if (! grep /^\Q$proto\E$/, @protocols) {
             if (substr($proto,0,5) ne "socks") {
                 return "curl lacks $proto support";
             }
@@ -3905,6 +4079,7 @@ $RTSPPORT =  $base++;
 $RTSP6PORT = $base++;
 $GOPHERPORT =$base++;
 $GOPHER6PORT=$base++;
+$HTTPTLSSRPPORT=$base++;
 
 #######################################################################
 # clear and create logging directory:
diff --git a/tests/serverhelp.pm b/tests/serverhelp.pm
index 34b155d..faaeebf 100644
--- a/tests/serverhelp.pm
+++ b/tests/serverhelp.pm
@@ -96,7 +96,7 @@ sub servername_str {
 
     $proto = uc($proto) if($proto);
     die "unsupported protocol: $proto" unless($proto &&
-        ($proto =~ /^(((FTP|HTTP|IMAP|POP3|SMTP)S?)|(TFTP|SFTP|SOCKS|SSH|RTSP|GOPHER))$/));
+        ($proto =~ /^(((FTP|HTTP|IMAP|POP3|SMTP)S?)|(TFTP|SFTP|SOCKS|SSH|RTSP|GOPHER|HTTP\+TLS-SRP))$/));
 
     $ipver = (not $ipver) ? 'ipv4' : lc($ipver);
     die "unsupported IP version: $ipver" unless($ipver &&
-------------------------------------------------------------------
List admin: http://cool.haxx.se/list/listinfo/curl-library
Etiquette:  http://curl.haxx.se/mail/etiquette.html

Reply via email to