]> granicus.if.org Git - curl/commitdiff
schannel: add client certificate authentication
authorArchangel_SDY <Archangel.SDY@gmail.com>
Sat, 10 Mar 2018 15:40:00 +0000 (23:40 +0800)
committerDaniel Stenberg <daniel@haxx.se>
Mon, 16 Apr 2018 22:23:01 +0000 (00:23 +0200)
Users can now specify a client certificate in system certificates store
explicitly using expression like `--cert "CurrentUser\MY\<thumbprint>"`

Closes #2376

docs/libcurl/opts/CURLOPT_SSLCERT.3
lib/vtls/schannel.c

index 6e190dce157e714d0085f707cecf9554a883010a..3f40b73b946078f0721ae09721b28b348b323031 100644 (file)
@@ -38,6 +38,9 @@ you wish to authenticate with as it is named in the security database. If you
 want to use a file from the current directory, please precede it with "./"
 prefix, in order to avoid confusion with a nickname.
 
+With WinSSL, this can be expression like "CurrentUser\\MY\\<thumbprint>" to
+refer to a certificate in the system certificates store.
+
 When using a client certificate, you most likely also need to provide a
 private key with \fICURLOPT_SSLKEY(3)\fP.
 
index 11fc401f972a28cf3a6cfe2e68e92195f20ec3e9..cbcc9c532ceb39b3db37c869f37abc6c65941be1 100644 (file)
 #endif
 #endif
 
+#ifdef UNICODE
+#define CURL_CERT_STORE_PROV_SYSTEM CERT_STORE_PROV_SYSTEM_W
+#else
+#define CURL_CERT_STORE_PROV_SYSTEM CERT_STORE_PROV_SYSTEM_A
+#endif
+
 #ifndef SP_PROT_SSL2_CLIENT
 #define SP_PROT_SSL2_CLIENT             0x00000008
 #endif
 #define CURL_SCHANNEL_BUFFER_INIT_SIZE   4096
 #define CURL_SCHANNEL_BUFFER_FREE_SIZE   1024
 
+#define CERT_THUMBPRINT_STR_LEN 40
+#define CERT_THUMBPRINT_DATA_LEN 20
+
 /* Uncomment to force verbose output
  * #define infof(x, y, ...) printf(y, __VA_ARGS__)
  * #define failf(x, y, ...) printf(y, __VA_ARGS__)
@@ -227,6 +236,56 @@ set_ssl_version_min_max(SCHANNEL_CRED *schannel_cred, struct connectdata *conn)
   return CURLE_OK;
 }
 
+static CURLcode
+get_cert_location(TCHAR *path, DWORD *store_name, TCHAR **store_path,
+                  TCHAR **thumbprint)
+{
+  TCHAR *sep;
+  size_t store_name_len;
+
+  sep = _tcschr(path, TEXT('\\'));
+  if(sep == NULL)
+    return CURLE_SSL_CONNECT_ERROR;
+
+  store_name_len = sep - path;
+
+  if(_tcsnccmp(path, TEXT("CurrentUser"), store_name_len) == 0)
+    *store_name = CERT_SYSTEM_STORE_CURRENT_USER;
+  else if(_tcsnccmp(path, TEXT("LocalMachine"), store_name_len) == 0)
+    *store_name = CERT_SYSTEM_STORE_LOCAL_MACHINE;
+  else if(_tcsnccmp(path, TEXT("CurrentService"), store_name_len) == 0)
+    *store_name = CERT_SYSTEM_STORE_CURRENT_SERVICE;
+  else if(_tcsnccmp(path, TEXT("Services"), store_name_len) == 0)
+    *store_name = CERT_SYSTEM_STORE_SERVICES;
+  else if(_tcsnccmp(path, TEXT("Users"), store_name_len) == 0)
+    *store_name = CERT_SYSTEM_STORE_USERS;
+  else if(_tcsnccmp(path, TEXT("CurrentUserGroupPolicy"),
+                    store_name_len) == 0)
+    *store_name = CERT_SYSTEM_STORE_CURRENT_USER_GROUP_POLICY;
+  else if(_tcsnccmp(path, TEXT("LocalMachineGroupPolicy"),
+                    store_name_len) == 0)
+    *store_name = CERT_SYSTEM_STORE_LOCAL_MACHINE_GROUP_POLICY;
+  else if(_tcsnccmp(path, TEXT("LocalMachineEnterprise"),
+                    store_name_len) == 0)
+    *store_name = CERT_SYSTEM_STORE_LOCAL_MACHINE_ENTERPRISE;
+  else
+    return CURLE_SSL_CONNECT_ERROR;
+
+  *store_path = sep + 1;
+
+  sep = _tcschr(*store_path, TEXT('\\'));
+  if(sep == NULL)
+    return CURLE_SSL_CONNECT_ERROR;
+
+  *sep = 0;
+
+  *thumbprint = sep + 1;
+  if(_tcslen(*thumbprint) != CERT_THUMBPRINT_STR_LEN)
+    return CURLE_SSL_CONNECT_ERROR;
+
+  return CURLE_OK;
+}
+
 static CURLcode
 schannel_connect_step1(struct connectdata *conn, int sockindex)
 {
@@ -241,6 +300,7 @@ schannel_connect_step1(struct connectdata *conn, int sockindex)
   unsigned char alpn_buffer[128];
 #endif
   SCHANNEL_CRED schannel_cred;
+  PCCERT_CONTEXT client_certs[1] = { NULL };
   SECURITY_STATUS sspi_status = SEC_E_OK;
   struct curl_schannel_cred *old_cred = NULL;
   struct in_addr addr;
@@ -309,7 +369,7 @@ schannel_connect_step1(struct connectdata *conn, int sockindex)
       /* TODO s/data->set.ssl.no_revoke/SSL_SET_OPTION(no_revoke)/g */
       if(data->set.ssl.no_revoke)
         schannel_cred.dwFlags |= SCH_CRED_IGNORE_NO_REVOCATION_CHECK |
-                                 SCH_CRED_IGNORE_REVOCATION_OFFLINE;
+          SCH_CRED_IGNORE_REVOCATION_OFFLINE;
       else
         schannel_cred.dwFlags |= SCH_CRED_REVOCATION_CHECK_CHAIN;
 #endif
@@ -361,11 +421,67 @@ schannel_connect_step1(struct connectdata *conn, int sockindex)
       return CURLE_SSL_CONNECT_ERROR;
     }
 
+    /* client certificate */
+    if(data->set.ssl.cert) {
+      DWORD cert_store_name;
+      TCHAR *cert_store_path;
+      TCHAR *cert_thumbprint_str;
+      CRYPT_HASH_BLOB cert_thumbprint;
+      BYTE cert_thumbprint_data[CERT_THUMBPRINT_DATA_LEN];
+      HCERTSTORE cert_store;
+
+      TCHAR *cert_path = Curl_convert_UTF8_to_tchar(data->set.ssl.cert);
+      if(!cert_path)
+        return CURLE_OUT_OF_MEMORY;
+
+      result = get_cert_location(cert_path, &cert_store_name,
+                                 &cert_store_path, &cert_thumbprint_str);
+      if(result != CURLE_OK) {
+        Curl_unicodefree(cert_path);
+        return result;
+      }
+
+      cert_store = CertOpenStore(CURL_CERT_STORE_PROV_SYSTEM, 0, NULL,
+                                 cert_store_name, cert_store_path);
+      if(!cert_store) {
+        Curl_unicodefree(cert_path);
+        return CURLE_SSL_CONNECT_ERROR;
+      }
+
+      cert_thumbprint.pbData = cert_thumbprint_data;
+      cert_thumbprint.cbData = CERT_THUMBPRINT_DATA_LEN;
+
+      if(!CryptStringToBinary(cert_thumbprint_str, CERT_THUMBPRINT_STR_LEN,
+                              CRYPT_STRING_HEXRAW,
+                              cert_thumbprint_data, &cert_thumbprint.cbData,
+                              NULL, NULL)) {
+        Curl_unicodefree(cert_path);
+        return CURLE_SSL_CONNECT_ERROR;
+      }
+
+      client_certs[0] = CertFindCertificateInStore(
+        cert_store, X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, 0,
+        CERT_FIND_HASH, &cert_thumbprint, NULL);
+
+      Curl_unicodefree(cert_path);
+
+      if(client_certs[0]) {
+        schannel_cred.cCreds = 1;
+        schannel_cred.paCred = client_certs;
+      }
+
+      CertCloseStore(cert_store, 0);
+    }
+
     /* allocate memory for the re-usable credential handle */
     BACKEND->cred = (struct curl_schannel_cred *)
       calloc(1, sizeof(struct curl_schannel_cred));
     if(!BACKEND->cred) {
       failf(data, "schannel: unable to allocate memory");
+
+      if(client_certs[0])
+        CertFreeCertificateContext(client_certs[0]);
+
       return CURLE_OUT_OF_MEMORY;
     }
     BACKEND->cred->refcount = 1;
@@ -379,6 +495,9 @@ schannel_connect_step1(struct connectdata *conn, int sockindex)
                                          &BACKEND->cred->cred_handle,
                                          &BACKEND->cred->time_stamp);
 
+    if(client_certs[0])
+      CertFreeCertificateContext(client_certs[0]);
+
     if(sspi_status != SEC_E_OK) {
       if(sspi_status == SEC_E_WRONG_PRINCIPAL)
         failf(data, "schannel: SNI or certificate check failed: %s",