Signed-off-by: Daniel Lehman dlehman25@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@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