[PATCH v5] wininet: Implement INTERNET_OPTION_SECURITY_CERTIFICATE flag for InternetQueryOption.

Daniel Lehman dlehman25 at gmail.com
Mon Sep 14 11:45:09 CDT 2020


Signed-off-by: Daniel Lehman <dlehman25 at gmail.com>

---
'null' isn't translated
v5: use skip instead of win_skip
v4: handle english:
    my spanish locale reports sys/usr lcid as 409/80a (spanish-honduras)
    the testbot japan reports sys/usr lcid as 411/409
    borrowing shlwapi/tests/string function to test for english
    calling SetUserDefaultUILanguage can force the string to be english on non-english locales
---

 dlls/wininet/http.c       | 103 ++++++++++++++++++++++++++
 dlls/wininet/resource.h   |  11 +++
 dlls/wininet/tests/http.c | 150 +++++++++++++++++++++++++++++++++++++-
 dlls/wininet/wininet.rc   |  11 +++
 4 files changed, 273 insertions(+), 2 deletions(-)

diff --git a/dlls/wininet/http.c b/dlls/wininet/http.c
index 220493718c3..d83c6fe3659 100644
--- a/dlls/wininet/http.c
+++ b/dlls/wininet/http.c
@@ -54,6 +54,7 @@
 
 #include "internet.h"
 #include "zlib.h"
+#include "resource.h"
 #include "wine/debug.h"
 #include "wine/exception.h"
 
@@ -2277,6 +2278,108 @@ static DWORD HTTPREQ_QueryOption(object_header_t *hdr, DWORD option, void *buffe
 
         return get_security_cert_struct(req, (INTERNET_CERTIFICATE_INFOA*)buffer);
     }
+    case INTERNET_OPTION_SECURITY_CERTIFICATE: {
+        DWORD err;
+        int needed;
+        char subject[64];
+        char issuer[64];
+        char effective[64];
+        char expiration[64];
+        char protocol[64];
+        char signature[64];
+        char encryption[64];
+        char privacy[64];
+        char bits[16];
+        char strength[16];
+        char start_date[32];
+        char start_time[32];
+        char expiry_date[32];
+        char expiry_time[32];
+        SYSTEMTIME start, expiry;
+        INTERNET_CERTIFICATE_INFOA info;
+
+        if(!size)
+            return ERROR_INVALID_PARAMETER;
+
+        if(!req->netconn) {
+            *size = 0;
+            return ERROR_INTERNET_INVALID_OPERATION;
+        }
+
+        if(!buffer) {
+            *size = 1;
+            return ERROR_INSUFFICIENT_BUFFER;
+        }
+
+        if((err = get_security_cert_struct(req, &info)))
+            return err;
+
+        if (PRIMARYLANGID(GetUserDefaultUILanguage()) != LANG_ENGLISH)
+            FIXME("INTERNET_OPTION_SECURITY_CERTIFICATE currently English-only\n");
+
+        LoadStringA(WININET_hModule, IDS_CERT_SUBJECT, subject, sizeof(subject));
+        LoadStringA(WININET_hModule, IDS_CERT_ISSUER, issuer, sizeof(issuer));
+        LoadStringA(WININET_hModule, IDS_CERT_EFFECTIVE, effective, sizeof(effective));
+        LoadStringA(WININET_hModule, IDS_CERT_EXPIRATION, expiration, sizeof(expiration));
+        LoadStringA(WININET_hModule, IDS_CERT_PROTOCOL, protocol, sizeof(protocol));
+        LoadStringA(WININET_hModule, IDS_CERT_SIGNATURE, signature, sizeof(signature));
+        LoadStringA(WININET_hModule, IDS_CERT_ENCRYPTION, encryption, sizeof(encryption));
+        LoadStringA(WININET_hModule, IDS_CERT_PRIVACY, privacy, sizeof(privacy));
+        LoadStringA(WININET_hModule, info.dwKeySize >= 128 ? IDS_CERT_HIGH : IDS_CERT_LOW,
+                    strength, sizeof(strength));
+        LoadStringA(WININET_hModule, IDS_CERT_BITS, bits, sizeof(bits));
+
+        FileTimeToSystemTime(&info.ftStart, &start);
+        FileTimeToSystemTime(&info.ftExpiry, &expiry);
+        GetDateFormatA(LOCALE_USER_DEFAULT, 0, &start, NULL, start_date, sizeof(start_date));
+        GetTimeFormatA(LOCALE_USER_DEFAULT, 0, &start, NULL, start_time, sizeof(start_time));
+        GetDateFormatA(LOCALE_USER_DEFAULT, 0, &expiry, NULL, expiry_date, sizeof(expiry_date));
+        GetTimeFormatA(LOCALE_USER_DEFAULT, 0, &expiry, NULL, expiry_time, sizeof(expiry_time));
+
+        needed = _scprintf("%s:\r\n%s\r\n"
+                           "%s:\r\n%s\r\n"
+                           "%s:\t%s %s\r\n"
+                           "%s:\t%s %s\r\n"
+                           "%s:\t(null)\r\n"
+                           "%s:\t(null)\r\n"
+                           "%s:\t(null)\r\n"
+                           "%s:\t%s (%u %s)",
+                           subject, info.lpszSubjectInfo,
+                           issuer, info.lpszIssuerInfo,
+                           effective, start_date, start_time,
+                           expiration, expiry_date, expiry_time,
+                           protocol, signature, encryption,
+                           privacy, strength, info.dwKeySize, bits);
+
+        if(needed < *size) {
+            err = ERROR_SUCCESS;
+            *size = snprintf(buffer, *size,
+                           "%s:\r\n%s\r\n"
+                           "%s:\r\n%s\r\n"
+                           "%s:\t%s %s\r\n"
+                           "%s:\t%s %s\r\n"
+                           "%s:\t(null)\r\n"
+                           "%s:\t(null)\r\n"
+                           "%s:\t(null)\r\n"
+                           "%s:\t%s (%u %s)",
+                           subject, info.lpszSubjectInfo,
+                           issuer, info.lpszIssuerInfo,
+                           effective, start_date, start_time,
+                           expiration, expiry_date, expiry_time,
+                           protocol, signature, encryption,
+                           privacy, strength, info.dwKeySize, bits);
+        }else {
+            err = ERROR_INSUFFICIENT_BUFFER;
+            *size = 1;
+        }
+
+        LocalFree(info.lpszSubjectInfo);
+        LocalFree(info.lpszIssuerInfo);
+        LocalFree(info.lpszProtocolName);
+        LocalFree(info.lpszSignatureAlgName);
+        LocalFree(info.lpszEncryptionAlgName);
+        return err;
+    }
     case INTERNET_OPTION_CONNECT_TIMEOUT:
         if (*size < sizeof(DWORD))
             return ERROR_INSUFFICIENT_BUFFER;
diff --git a/dlls/wininet/resource.h b/dlls/wininet/resource.h
index 256a374af08..a9ccd19cf3d 100644
--- a/dlls/wininet/resource.h
+++ b/dlls/wininet/resource.h
@@ -38,3 +38,14 @@
 #define IDS_CERT_DATE_INVALID 0x502
 #define IDS_CERT_CN_INVALID   0x503
 #define IDS_CERT_ERRORS       0x504
+#define IDS_CERT_SUBJECT      0x505
+#define IDS_CERT_ISSUER       0x506
+#define IDS_CERT_EFFECTIVE    0x507
+#define IDS_CERT_EXPIRATION   0x508
+#define IDS_CERT_PROTOCOL     0x509
+#define IDS_CERT_SIGNATURE    0x50a
+#define IDS_CERT_ENCRYPTION   0x50b
+#define IDS_CERT_PRIVACY      0x50c
+#define IDS_CERT_HIGH         0x50d
+#define IDS_CERT_LOW          0x50e
+#define IDS_CERT_BITS         0x50f
diff --git a/dlls/wininet/tests/http.c b/dlls/wininet/tests/http.c
index c07d60d2a9d..fc984f6d33d 100644
--- a/dlls/wininet/tests/http.c
+++ b/dlls/wininet/tests/http.c
@@ -171,6 +171,26 @@ static INTERNET_STATUS_CALLBACK (WINAPI *pInternetSetStatusCallbackA)(HINTERNET
 static INTERNET_STATUS_CALLBACK (WINAPI *pInternetSetStatusCallbackW)(HINTERNET ,INTERNET_STATUS_CALLBACK);
 static BOOL (WINAPI *pInternetGetSecurityInfoByURLA)(LPSTR,PCCERT_CHAIN_CONTEXT*,DWORD*);
 
+static BOOL is_lang_english(void)
+{
+    static HMODULE hkernel32 = NULL;
+    static LANGID (WINAPI *pGetThreadUILanguage)(void) = NULL;
+    static LANGID (WINAPI *pGetUserDefaultUILanguage)(void) = NULL;
+
+    if (!hkernel32)
+    {
+        hkernel32 = GetModuleHandleA("kernel32.dll");
+        pGetThreadUILanguage = (void*)GetProcAddress(hkernel32, "GetThreadUILanguage");
+        pGetUserDefaultUILanguage = (void*)GetProcAddress(hkernel32, "GetUserDefaultUILanguage");
+    }
+    if (pGetThreadUILanguage)
+        return PRIMARYLANGID(pGetThreadUILanguage()) == LANG_ENGLISH;
+    if (pGetUserDefaultUILanguage)
+        return PRIMARYLANGID(pGetUserDefaultUILanguage()) == LANG_ENGLISH;
+
+    return PRIMARYLANGID(GetUserDefaultLangID()) == LANG_ENGLISH;
+}
+
 static int strcmp_wa(LPCWSTR strw, const char *stra)
 {
     WCHAR buf[512];
@@ -6112,6 +6132,58 @@ static const cert_struct_test_t test_winehq_com_cert = {
     "webmaster at winehq.org"
 };
 
+static const char *cert_string_fmt =
+    "Subject:\r\n%s\r\n"
+    "Issuer:\r\n%s\r\n"
+    "Effective Date:\t%s %s\r\n"
+    "Expiration Date:\t%s %s\r\n"
+    "Security Protocol:\t%s\r\n"
+    "Signature Type:\t%s\r\n"
+    "Encryption Type:\t%s\r\n"
+    "Privacy Strength:\t%s (%u bits)";
+
+static void test_cert_struct_string(HINTERNET req, const INTERNET_CERTIFICATE_INFOA *info)
+{
+    SYSTEMTIME start, expiry;
+    char expiry_date[32];
+    char expiry_time[32];
+    char start_date[32];
+    char start_time[32];
+    char expect[512];
+    char actual[512];
+    DWORD size;
+    BOOL res;
+
+    size = sizeof(actual);
+    SetLastError(0xdeadbeef);
+    memset(actual, 0x55, sizeof(actual));
+    res = InternetQueryOptionA(req, INTERNET_OPTION_SECURITY_CERTIFICATE, actual, &size);
+    ok(res, "InternetQueryOption failed: %u\n", GetLastError());
+
+    FileTimeToSystemTime(&info->ftStart, &start);
+    FileTimeToSystemTime(&info->ftExpiry, &expiry);
+
+    GetDateFormatA(LOCALE_USER_DEFAULT, 0, &start, NULL, start_date, sizeof(start_date));
+    GetTimeFormatA(LOCALE_USER_DEFAULT, 0, &start, NULL, start_time, sizeof(start_time));
+    GetDateFormatA(LOCALE_USER_DEFAULT, 0, &expiry, NULL, expiry_date, sizeof(expiry_date));
+    GetTimeFormatA(LOCALE_USER_DEFAULT, 0, &expiry, NULL, expiry_time, sizeof(expiry_time));
+
+    snprintf(expect, sizeof(expect), cert_string_fmt, info->lpszSubjectInfo, info->lpszIssuerInfo,
+             start_date, start_time, expiry_date, expiry_time,
+             info->lpszSignatureAlgName, info->lpszEncryptionAlgName, info->lpszProtocolName,
+             info->dwKeySize >= 128 ? "High" : "Low", info->dwKeySize);
+    ok(size == strlen(actual), "size = %u\n", size);
+    ok(!strcmp(actual, expect), "expected:\n%s\nactual:\n%s\n", expect, actual);
+
+    --size;
+    SetLastError(0xdeadbeef);
+    memset(actual, 0x55, sizeof(actual));
+    res = InternetQueryOptionA(req, INTERNET_OPTION_SECURITY_CERTIFICATE, actual, &size);
+    ok(!res && GetLastError() == ERROR_INSUFFICIENT_BUFFER, "InternetQueryOption failed: %d\n", GetLastError());
+    ok(size == 1, "unexpected size: %u\n", size);
+    ok(actual[0] == 0x55, "unexpected byte: %02x\n", actual[0]);
+}
+
 static void test_cert_struct(HINTERNET req, const cert_struct_test_t *test)
 {
     INTERNET_CERTIFICATE_INFOA info;
@@ -6138,6 +6210,10 @@ static void test_cert_struct(HINTERNET req, const cert_struct_test_t *test)
     ok(!info.lpszProtocolName, "lpszProtocolName = %s\n", info.lpszProtocolName);
     ok(info.dwKeySize >= 128 && info.dwKeySize <= 256, "dwKeySize = %u\n", info.dwKeySize);
 
+    if (is_lang_english())
+        test_cert_struct_string(req, &info);
+    else
+        skip("Skipping tests that are English-only\n");
     release_cert_info(&info);
 }
 
@@ -6217,7 +6293,7 @@ static void test_security_flags(void)
     INTERNET_CERTIFICATE_INFOA *cert;
     HINTERNET ses, conn, req;
     DWORD size, flags;
-    char buf[100];
+    char buf[512];
     BOOL res;
 
     if (!https_support)
@@ -6378,6 +6454,30 @@ static void test_security_flags(void)
     }
     HeapFree(GetProcessHeap(), 0, cert);
 
+    SetLastError(0xdeadbeef);
+    res = InternetQueryOptionW(req, INTERNET_OPTION_SECURITY_CERTIFICATE, NULL, NULL);
+    ok(!res && GetLastError() == ERROR_INVALID_PARAMETER, "InternetQueryOption failed: %d\n", GetLastError());
+
+    size = 0;
+    SetLastError(0xdeadbeef);
+    res = InternetQueryOptionW(req, INTERNET_OPTION_SECURITY_CERTIFICATE, NULL, &size);
+    ok(!res && GetLastError() == ERROR_INSUFFICIENT_BUFFER, "InternetQueryOption failed: %d\n", GetLastError());
+    ok(size == 1, "unexpected size: %u\n", size);
+
+    size = 42;
+    SetLastError(0xdeadbeef);
+    memset(buf, 0x55, sizeof(buf));
+    res = InternetQueryOptionW(req, INTERNET_OPTION_SECURITY_CERTIFICATE, buf, &size);
+    ok(!res && GetLastError() == ERROR_INSUFFICIENT_BUFFER, "InternetQueryOption failed: %d\n", GetLastError());
+    ok(size == 1, "unexpected size: %u\n", size);
+    ok(buf[0] == 0x55, "unexpected byte: %02x\n", buf[0]);
+
+    size = sizeof(buf);
+    SetLastError(0xdeadbeef);
+    res = InternetQueryOptionW(req, INTERNET_OPTION_SECURITY_CERTIFICATE, buf, &size);
+    ok(res && GetLastError() == ERROR_SUCCESS, "InternetQueryOption failed: %d\n", GetLastError());
+    ok(size < sizeof(buf), "unexpected size: %u\n", size);
+
     CHECK_NOTIFIED2(INTERNET_STATUS_CONNECTING_TO_SERVER, 2);
     CHECK_NOTIFIED2(INTERNET_STATUS_CONNECTED_TO_SERVER, 2);
     CHECK_NOTIFIED2(INTERNET_STATUS_CLOSING_CONNECTION, 2);
@@ -6562,9 +6662,10 @@ static void test_secure_connection(void)
     static const WCHAR get[] = {'G','E','T',0};
     static const WCHAR testpage[] = {'/','t','e','s','t','s','/','h','e','l','l','o','.','h','t','m','l',0};
     HINTERNET ses, con, req;
-    DWORD size, flags, err;
+    DWORD size, size2, flags, err;
     INTERNET_CERTIFICATE_INFOA *certificate_structA = NULL;
     INTERNET_CERTIFICATE_INFOW *certificate_structW = NULL;
+    char certstr1[512], certstr2[512];
     BOOL ret;
 
     ses = InternetOpenA("Gizmo5", INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0);
@@ -6629,6 +6730,19 @@ static void test_secure_connection(void)
     }
     HeapFree(GetProcessHeap(), 0, certificate_structW);
 
+    SetLastError(0xdeadbeef);
+    size = sizeof(certstr1);
+    ret = InternetQueryOptionW(req, INTERNET_OPTION_SECURITY_CERTIFICATE, certstr1, &size);
+    ok(ret && GetLastError() == ERROR_SUCCESS, "InternetQueryOption failed: %d\n", GetLastError());
+
+    SetLastError(0xdeadbeef);
+    size2 = sizeof(certstr2);
+    ret = InternetQueryOptionA(req, INTERNET_OPTION_SECURITY_CERTIFICATE, certstr2, &size2);
+    ok(ret && GetLastError() == ERROR_SUCCESS, "InternetQueryOption failed: %d\n", GetLastError());
+
+    ok(size == size2, "expected same size\n");
+    ok(!strcmp(certstr1, certstr2), "expected same string\n");
+
     InternetCloseHandle(req);
     InternetCloseHandle(con);
     InternetCloseHandle(ses);
@@ -7517,6 +7631,37 @@ static void test_concurrent_header_access(void)
     CloseHandle( wait );
 }
 
+static void test_cert_string(void)
+{
+    HINTERNET ses, con, req;
+    char actual[512];
+    DWORD size;
+    BOOL res;
+
+    ses = InternetOpenA( "winetest", 0, NULL, NULL, 0 );
+    ok( ses != NULL, "InternetOpenA failed\n" );
+
+    con = InternetConnectA( ses, "test.winehq.org", INTERNET_DEFAULT_HTTP_PORT, NULL, NULL,
+                            INTERNET_SERVICE_HTTP, 0, 0 );
+    ok( con != NULL, "InternetConnectA failed %u\n", GetLastError() );
+
+    req = HttpOpenRequestA( con, NULL, "/", NULL, NULL, NULL, 0, 0 );
+    ok( req != NULL, "HttpOpenRequestA failed %u\n", GetLastError() );
+
+    size = sizeof(actual);
+    SetLastError( 0xdeadbeef );
+    memset( actual, 0x55, sizeof(actual) );
+    res = InternetQueryOptionA( req, INTERNET_OPTION_SECURITY_CERTIFICATE, actual, &size );
+    ok( !res && GetLastError() == ERROR_INTERNET_INVALID_OPERATION,
+        "InternetQueryOption failed: %u\n", GetLastError() );
+    ok( size == 0, "unexpected size: %u\n", size );
+    ok( actual[0] == 0x55, "unexpected byte: %02x\n", actual[0] );
+
+    InternetCloseHandle( req );
+    InternetCloseHandle( con );
+    InternetCloseHandle( ses );
+}
+
 START_TEST(http)
 {
     HMODULE hdll;
@@ -7566,5 +7711,6 @@ START_TEST(http)
     test_connection_failure();
     test_default_service_port();
     test_concurrent_header_access();
+    test_cert_string();
     free_events();
 }
diff --git a/dlls/wininet/wininet.rc b/dlls/wininet/wininet.rc
index b6e35629ca2..485c80793e0 100644
--- a/dlls/wininet/wininet.rc
+++ b/dlls/wininet/wininet.rc
@@ -29,6 +29,17 @@ STRINGTABLE
   IDS_CERT_DATE_INVALID "The date on the certificate is invalid."
   IDS_CERT_CN_INVALID   "The name on the certificate does not match the site."
   IDS_CERT_ERRORS       "There is at least one unspecified security problem with this certificate."
+  IDS_CERT_SUBJECT      "Subject"
+  IDS_CERT_ISSUER       "Issuer"
+  IDS_CERT_EFFECTIVE    "Effective Date"
+  IDS_CERT_EXPIRATION   "Expiration Date"
+  IDS_CERT_PROTOCOL     "Security Protocol"
+  IDS_CERT_SIGNATURE    "Signature Type"
+  IDS_CERT_ENCRYPTION   "Encryption Type"
+  IDS_CERT_PRIVACY      "Privacy Strength"
+  IDS_CERT_HIGH         "High"
+  IDS_CERT_LOW          "Low"
+  IDS_CERT_BITS         "bits"
 }
 
 IDD_PROXYDLG DIALOG 36, 24, 220, 146
-- 
2.17.1




More information about the wine-devel mailing list