[PATCH] wininet: Implement INTERNET_OPTION_SECURITY_CERTIFICATE flag for InternetQueryOption.
Daniel Lehman
dlehman25 at gmail.com
Sat Jul 25 20:21:06 CDT 2020
Signed-off-by: Daniel Lehman <dlehman25 at gmail.com>
---
dlls/wininet/http.c | 120 ++++++++++++++++++++++++++++++++++++++
dlls/wininet/resource.h | 3 +
dlls/wininet/tests/http.c | 112 ++++++++++++++++++++++++++++++++++-
dlls/wininet/wininet.rc | 12 ++++
4 files changed, 245 insertions(+), 2 deletions(-)
diff --git a/dlls/wininet/http.c b/dlls/wininet/http.c
index 56c995805b..379455fddb 100644
--- a/dlls/wininet/http.c
+++ b/dlls/wininet/http.c
@@ -35,6 +35,7 @@
#include <stdarg.h>
#include <stdio.h>
#include <time.h>
+#include <math.h>
#include <assert.h>
#include <errno.h>
#include <limits.h>
@@ -54,6 +55,7 @@
#include "internet.h"
#include "zlib.h"
+#include "resource.h"
#include "wine/debug.h"
#include "wine/exception.h"
@@ -2273,6 +2275,124 @@ static DWORD HTTPREQ_QueryOption(object_header_t *hdr, DWORD option, void *buffe
}
return ERROR_NOT_SUPPORTED;
}
+ case INTERNET_OPTION_SECURITY_CERTIFICATE: {
+ char fmt[256];
+ const char nullA[] = "(null)"; /* always appears in English */
+ CERT_CONTEXT *context;
+ char *subject = NULL;
+ char *issuer = NULL;
+ char *start_date = NULL;
+ char *start_time = NULL;
+ char *expiry_date = NULL;
+ char *expiry_time = NULL;
+ char strength[16];
+ int subject_len, issuer_len;
+ int start_date_len, start_time_len;
+ int expiry_date_len, expiry_time_len;
+ SYSTEMTIME start, expiry;
+ DWORD needed, keysize;
+
+ if (PRIMARYLANGID(GetUserDefaultLangID()) != LANG_ENGLISH)
+ FIXME("INTERNET_OPTION_SECURITY_CERTIFICATE currently English-only\n");
+
+ if(!req->netconn)
+ return ERROR_INTERNET_INVALID_OPERATION;
+
+ if(!size)
+ return ERROR_INVALID_PARAMETER;
+
+ if(!buffer) {
+ *size = 1;
+ return ERROR_INSUFFICIENT_BUFFER;
+ }
+
+ context = (CERT_CONTEXT *)NETCON_GetCert(req->netconn);
+ if(!context)
+ return ERROR_NOT_SUPPORTED;
+
+ needed = LoadStringA(WININET_hModule, IDS_CERT_FORMAT, fmt, sizeof(fmt));
+ needed += 1 - 20 * 2 + 9; /* include room for \0, subtract 20 format specifiers, add 9 \r */
+ if(needed > *size) goto error;
+
+ subject_len = CertNameToStrA(context->dwCertEncodingType, &context->pCertInfo->Subject,
+ CERT_SIMPLE_NAME_STR|CERT_NAME_STR_CRLF_FLAG, NULL, 0);
+ needed += subject_len - 1; /* minus \0 */
+ if(needed > *size) goto error;
+
+ issuer_len = CertNameToStrA(context->dwCertEncodingType, &context->pCertInfo->Issuer,
+ CERT_SIMPLE_NAME_STR|CERT_NAME_STR_CRLF_FLAG, NULL, 0);
+ needed += issuer_len - 1;
+ if(needed > *size) goto error;
+
+ FileTimeToSystemTime(&context->pCertInfo->NotBefore, &start);
+ start_date_len = GetDateFormatA(LOCALE_USER_DEFAULT, 0, &start, NULL, NULL, 0);
+ start_time_len = GetTimeFormatA(LOCALE_USER_DEFAULT, 0, &start, NULL, NULL, 0);
+ needed += start_date_len + start_time_len - 2;
+ if(needed > *size) goto error;
+
+ FileTimeToSystemTime(&context->pCertInfo->NotAfter, &expiry);
+ expiry_date_len = GetDateFormatA(LOCALE_USER_DEFAULT, 0, &expiry, NULL, NULL, 0);
+ expiry_time_len = GetTimeFormatA(LOCALE_USER_DEFAULT, 0, &expiry, NULL, NULL, 0);
+ needed += expiry_date_len + expiry_time_len - 2;
+ if(needed > *size) goto error;
+
+ needed += sizeof(nullA) * 3 - 3; /* protocol, signature type, encryption type */
+ if(needed > *size) goto error;
+
+ keysize = NETCON_GetCipherStrength(req->netconn);
+ needed += keysize ? floor(log10(keysize))+1 : 1;
+ needed += LoadStringA(WININET_hModule, keysize >= 128 ? IDS_CERT_HIGH : IDS_CERT_LOW,
+ strength, sizeof(strength));
+ if(needed > *size) goto error;
+
+ if(!(subject = heap_alloc(subject_len)) ||
+ !(issuer = heap_alloc(issuer_len)) ||
+ !(start_date = heap_alloc(start_date_len)) ||
+ !(start_time = heap_alloc(start_time_len)) ||
+ !(expiry_date = heap_alloc(expiry_date_len)) ||
+ !(expiry_time = heap_alloc(expiry_time_len)))
+ goto error;
+
+ CertNameToStrA(context->dwCertEncodingType, &context->pCertInfo->Subject,
+ CERT_SIMPLE_NAME_STR|CERT_NAME_STR_CRLF_FLAG, subject, subject_len);
+ CertNameToStrA(context->dwCertEncodingType, &context->pCertInfo->Issuer,
+ CERT_SIMPLE_NAME_STR|CERT_NAME_STR_CRLF_FLAG, issuer, issuer_len);
+ GetDateFormatA(LOCALE_USER_DEFAULT, 0, &start, NULL, start_date, start_date_len);
+ GetTimeFormatA(LOCALE_USER_DEFAULT, 0, &start, NULL, start_time, start_time_len);
+ GetDateFormatA(LOCALE_USER_DEFAULT, 0, &expiry, NULL, expiry_date, expiry_date_len);
+ GetTimeFormatA(LOCALE_USER_DEFAULT, 0, &expiry, NULL, expiry_time, expiry_time_len);
+
+ snprintf(buffer, *size, fmt,
+ '\r', subject, '\r',
+ '\r', issuer, '\r',
+ start_date, start_time, '\r',
+ expiry_date, expiry_time, '\r',
+ nullA, '\r',
+ nullA, '\r',
+ nullA, '\r',
+ strength, keysize);
+
+ heap_free(subject);
+ heap_free(issuer);
+ heap_free(start_date);
+ heap_free(start_time);
+ heap_free(expiry_date);
+ heap_free(expiry_time);
+ *size = needed - 1;
+ CertFreeCertificateContext(context);
+ return ERROR_SUCCESS;
+
+error:
+ heap_free(subject);
+ heap_free(issuer);
+ heap_free(start_date);
+ heap_free(start_time);
+ heap_free(expiry_date);
+ heap_free(expiry_time);
+ *size = 1;
+ CertFreeCertificateContext(context);
+ return ERROR_INSUFFICIENT_BUFFER;
+ }
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 256a374af0..190d3cc397 100644
--- a/dlls/wininet/resource.h
+++ b/dlls/wininet/resource.h
@@ -38,3 +38,6 @@
#define IDS_CERT_DATE_INVALID 0x502
#define IDS_CERT_CN_INVALID 0x503
#define IDS_CERT_ERRORS 0x504
+#define IDS_CERT_FORMAT 0x505
+#define IDS_CERT_HIGH 0x506
+#define IDS_CERT_LOW 0x507
diff --git a/dlls/wininet/tests/http.c b/dlls/wininet/tests/http.c
index c07d60d2a9..1eed1101c8 100644
--- a/dlls/wininet/tests/http.c
+++ b/dlls/wininet/tests/http.c
@@ -6112,9 +6112,26 @@ 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(HINTERNET req, const cert_struct_test_t *test)
{
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;
@@ -6138,6 +6155,33 @@ 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 (PRIMARYLANGID(GetUserDefaultLangID()) != LANG_ENGLISH)
+ {
+ skip("Non-English locale (test with hardcoded English)\n");
+ release_cert_info(&info);
+ return;
+ }
+
+ size = sizeof(actual);
+ memset(actual, 0xcc, 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), "cert = actual\n%s\n", actual);
+
release_cert_info(&info);
}
@@ -6217,7 +6261,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 +6422,26 @@ static void test_security_flags(void)
}
HeapFree(GetProcessHeap(), 0, cert);
+ res = InternetQueryOptionW(req, INTERNET_OPTION_SECURITY_CERTIFICATE, NULL, NULL);
+ ok(!res && GetLastError() == ERROR_INVALID_PARAMETER, "InternetQueryOption failed: %d\n", GetLastError());
+
+ size = 0;
+ 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;
+ 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);
+ res = InternetQueryOptionW(req, INTERNET_OPTION_SECURITY_CERTIFICATE, buf, &size);
+ ok(res && GetLastError() == ERROR_SUCCESS, "InternetQueryOption failed: %d\n", GetLastError());
+ ok(size && 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 +6626,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 +6694,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 +7595,35 @@ 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() );
+
+ SetLastError(0xdeadbeef);
+ size = sizeof(actual);
+ memset(actual, 0xcc, sizeof(actual));
+ res = InternetQueryOptionA(req, INTERNET_OPTION_SECURITY_CERTIFICATE, actual, &size);
+ ok(!res && GetLastError() == ERROR_INTERNET_INVALID_OPERATION,
+ "InternetQueryOption failed: %u\n", GetLastError());
+
+ InternetCloseHandle( req );
+ InternetCloseHandle( con );
+ InternetCloseHandle( ses );
+}
+
START_TEST(http)
{
HMODULE hdll;
@@ -7566,5 +7673,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 b6e35629ca..7e2830e7ff 100644
--- a/dlls/wininet/wininet.rc
+++ b/dlls/wininet/wininet.rc
@@ -29,6 +29,18 @@ 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."
+
+ /* each %c is a \r */
+ IDS_CERT_FORMAT "Subject:%c\n%s%c\n" \
+ "Issuer:%c\n%s%c\n" \
+ "Effective Date:\t%s %s%c\n" \
+ "Expiration Date:\t%s %s%c\n" \
+ "Security Protocol:\t%s%c\n" \
+ "Signature Type:\t%s%c\n" \
+ "Encryption Type:\t%s%c\n" \
+ "Privacy Strength:\t%s (%u bits)"
+ IDS_CERT_HIGH "High"
+ IDS_CERT_LOW "Low"
}
IDD_PROXYDLG DIALOG 36, 24, 220, 146
--
2.25.1
More information about the wine-devel
mailing list