Search code examples
clinuxopensslwinsock2wine

Why I am unable to verify the downloaded google's certificate in my C programm?


I downloaded the google's certificate in linux (I am using mingw for cross compilation and wine for execution):

mkdir -p ./release/certificates
echo | openssl s_client -servername google.com -connect google.com:443| sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > google.crt

And I attempt to verify it in my programm:

#define _WINSOCK_DEPRECATED_NO_WARNINGS
#define _CRT_SECURE_NO_WARNINGS

#include <winsock2.h>
#include <ws2tcpip.h>
#include <shlwapi.h>

#include <stdio.h>

#include <openssl/ssl.h>
#include <openssl/err.h>


// Need to link with Ws2_32.lib, Mswsock.lib, and Advapi32.lib
#pragma comment (lib, "Ws2_32.lib")
//#pragma comment (lib, "Mswsock.lib")
//#pragma comment (lib, "AdvApi32.lib")


#define DEFAULT_BUFLEN 512
#define FAIL    -1

#define WIN32_LEAN_AND_MEAN

SOCKET OpenConnection(char* hostname, char* port)
{
    WSADATA wsaData;
    SOCKET ConnectSocket = INVALID_SOCKET;
    struct addrinfo* result = NULL;
    struct addrinfo* ptr = NULL;
    struct addrinfo hints;
    char recvbuf[DEFAULT_BUFLEN];
    int iResult;
    int recvbuflen = DEFAULT_BUFLEN;

    printf("\nInitializing Winsock");
    // Initialize Winsock
    iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
    if (iResult != 0) {
        printf("WSAStartup failed with error: %d\n", iResult);
        return 1;
    }

    ZeroMemory(&hints, sizeof(hints));
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_protocol = IPPROTO_TCP;

    // Resolve the server address and port
    iResult = getaddrinfo(hostname, port, &hints, &result);
    if (iResult != 0) {
        printf("getaddrinfo failed with error: %d\n", iResult);
        WSACleanup();
        return 1;
    }

    for(ptr=result; ptr != NULL ;ptr=ptr->ai_next) {

        // Create a SOCKET for connecting to server
        ConnectSocket = socket(ptr->ai_family, ptr->ai_socktype, 
            ptr->ai_protocol);
        if (ConnectSocket == INVALID_SOCKET) {
            printf("socket failed with error: %ld\n", WSAGetLastError());
            WSACleanup();
            return 1;
        }

        // Connect to server.
        iResult = connect( ConnectSocket, ptr->ai_addr, (int)ptr->ai_addrlen);
        if (iResult == SOCKET_ERROR) {
            closesocket(ConnectSocket);
            ConnectSocket = INVALID_SOCKET;
            continue;
        }
        break;
    }

    return ConnectSocket;
}


SSL_CTX* InitCTX(void)
{
    OpenSSL_add_all_algorithms(); 
    SSL_load_error_strings();
    const SSL_METHOD* method = SSLv23_method();
    SSL_CTX* ctx = SSL_CTX_new(method);
    SSL_CTX_set_options(ctx, SSL_OP_ALL | SSL_OP_NO_TLSv1_1 | SSL_OP_NO_TLSv1);
    
    if (ctx == NULL)
    {
        ERR_print_errors_fp(stderr);
        abort();
    }
    
    char* path = malloc(MAX_PATH);
    memset(path, 0, MAX_PATH);
    GetModuleFileName(0, path, MAX_PATH);
    PathRemoveFileSpec(path);
    sprintf(path,"%s\\%s",path,"certificates");
    printf("Cert path %s\n",path);
    int value = SSL_CTX_load_verify_locations(ctx,"google.crt",path);
    if(value == 0) {
        printf("Certificate error\n");
        exit(1);
    }

    return ctx;
}


void ShowCerts(SSL* ssl)
{
    X509* cert;
    char* line;
    cert = SSL_get_peer_certificate(ssl); /* get the server's certificate */
    if (cert != NULL)
    {
        printf("Server certificates:\n");
        line = X509_NAME_oneline(X509_get_subject_name(cert), 0, 0);
        printf("Subject: %s\n", line);
        //free(line);       /* free the malloc'ed string */
        line = X509_NAME_oneline(X509_get_issuer_name(cert), 0, 0);
        printf("Issuer: %s\n", line);
        //free(line);       /* free the malloc'ed string */
        X509_free(cert);     /* free the malloc'ed certificate copy */
    } else {
        printf("Info: No client certificates configured.\n");
    } 
}

void releaseSocket( SSL_CTX* ctx, int server)
{
    /* close socket */
    closesocket(server);   
    /* release context */
    SSL_CTX_free(ctx);        
    putchar('\n');
}

int main(int argc, char* argv[])
{
    printf("Initializing Connection");
    char buf[1024];
    
    SSL_library_init();
    char* hostname = "google.com";
    char* portnum = "443";

    SSL_CTX* ctx = InitCTX();
    int server = OpenConnection(hostname, portnum);
    SSL* ssl = SSL_new(ctx);      /* create new SSL connection state */
    SSL_set_fd(ssl, server);    /* attach the socket descriptor */


    if (SSL_connect(ssl) == FAIL) {
        ERR_print_errors_fp(stderr);
    } else {
        const char* cpRequestMessage = "GET / HTTP/1.1\r\nHost: www.google.com\r\nUser-Agent: curl/7.54.0\r\nConnection: close\r\nAccept: */*\r\n\r\n";

        printf("\n\nConnected with %s encryption\n", SSL_get_cipher(ssl));
       
        /* get any certs */
        ShowCerts(ssl);   
        /* encrypt & send message */
        printf("REQUEST:\n\n%s\n",cpRequestMessage);
        SSL_write(ssl, cpRequestMessage, strlen(cpRequestMessage));  

        /* get reply & decrypt */
        int bytes = SSL_read(ssl, buf, sizeof(buf));
        int error = SSL_get_error(ssl,bytes);
        switch (error)
        {
            case SSL_ERROR_SSL:
                puts("SSL ERROR SSL");
                releaseSocket(ctx,server);
                return 1;
            case SSL_ERROR_SYSCALL:
                puts("SSL ERROR SYSCALL");
                releaseSocket(ctx,server);
                return 1;
            case SSL_ERROR_WANT_ASYNC_JOB:
                puts("SSL ERROR WANT ASYNC_LOOKUP");
                releaseSocket(ctx,server);
                return 1;
            case SSL_ERROR_WANT_ASYNC:
                puts("SSL ERROR WANT X509_LOOKUP");
                releaseSocket(ctx,server);
                return 1;
            case SSL_ERROR_WANT_X509_LOOKUP:
                puts("SSL ERROR WANT X509_LOOKUP");
                releaseSocket(ctx,server);
                return 1;
            case SSL_ERROR_WANT_WRITE:
                puts("SSL ERROR WANT WRITE");
                releaseSocket(ctx,server);
                return 1;
            case SSL_ERROR_WANT_READ:
                puts("SSL ERROR WANT READ");
                releaseSocket(ctx,server);
                return 1;
            case SSL_ERROR_ZERO_RETURN:
                puts("SSL ERROR SSL_ERROR_ZERO_RETURN");
                releaseSocket(ctx,server);
                return 1;
            case SSL_ERROR_NONE:
            default:
                break;
        }
        puts("RESPONSE\n");
        for(int i=0;i<bytes;i++){
            putchar(buf[i]);
        }
        
        /* release connection state */
        SSL_free(ssl);       
    }

    releaseSocket(ctx,server);   
    putchar('\n');
    return 0;
}

For clarity in the code above I verify the certificate in InitCTX function like this:

  char* path = malloc(MAX_PATH);
    memset(path, 0, MAX_PATH);
    GetModuleFileName(0, path, MAX_PATH);
    PathRemoveFileSpec(path);
    sprintf(path,"%s\\%s",path,"certificates");
    printf("Cert path %s\n",path);
    int value = SSL_CTX_load_verify_locations(ctx,"google.crt",path);
    if(value == 0) {
        printf("Certificate error\n");
        exit(1);
    }

But the certificate seems not to be able to verify:

Initializing ConnectionCert path Z:\home\pcmagas\job\Kwdikas\my_win_app\release\certificates
Certificate error

As a means of diagnosis using wine's derived cmd:

wine cmd
> cd release\certificates
> dir
0009:fixme:ntdll:server_ioctl_file Unsupported ioctl 24000 (device=2 access=1 func=0 method=0)
Volume in drive Z has no label.
Volume Serial Number is 0000-0000

Directory of Z:\home\pcmagas\job\Kwdikas\my_win_app\release\certificates

 15/7/2022  10:01 μμ  <DIR>         .
 15/7/2022  10:37 μμ  <DIR>         ..
 15/7/2022  10:01 μμ         4,860  google.crt
       1 file                     4,860 bytes
       2 directories     24,553,922,560 bytes free

> type google.crt
-----BEGIN CERTIFICATE-----
MIIN2DCCDMCgAwIBAgIQYM9tSNNu7FgSBQMJka23FDANBgkqhkiG9w0BAQsFADBG
MQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExM
QzETMBEGA1UEAxMKR1RTIENBIDFDMzAeFw0yMjA2MjcwODE3MzlaFw0yMjA5MTkw
ODE3MzhaMBcxFTATBgNVBAMMDCouZ29vZ2xlLmNvbTBZMBMGByqGSM49AgEGCCqG
SM49AwEHA0IABIc+IN08t8+aCMkZ8A7h8IkNd8eTW9nEZaCfoUBLrlIPq5gPL2OM
BMbh+x/FqStjWaXLhlf6n0srtnqOLcAq3m6jggu6MIILtjAOBgNVHQ8BAf8EBAMC
B4AwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQU
NRZdk0ysjIh2U0LY2vwqY2/nVD8wHwYDVR0jBBgwFoAUinR/r4XN7pXNPZzQ4kYU
83E1HScwagYIKwYBBQUHAQEEXjBcMCcGCCsGAQUFBzABhhtodHRwOi8vb2NzcC5w
a2kuZ29vZy9ndHMxYzMwMQYIKwYBBQUHMAKGJWh0dHA6Ly9wa2kuZ29vZy9yZXBv
L2NlcnRzL2d0czFjMy5kZXIwggloBgNVHREEgglfMIIJW4IMKi5nb29nbGUuY29t
ghYqLmFwcGVuZ2luZS5nb29nbGUuY29tggkqLmJkbi5kZXaCEiouY2xvdWQuZ29v
Z2xlLmNvbYIYKi5jcm93ZHNvdXJjZS5nb29nbGUuY29tghgqLmRhdGFjb21wdXRl
Lmdvb2dsZS5jb22CCyouZ29vZ2xlLmNhggsqLmdvb2dsZS5jbIIOKi5nb29nbGUu
Y28uaW6CDiouZ29vZ2xlLmNvLmpwgg4qLmdvb2dsZS5jby51a4IPKi5nb29nbGUu
Y29tLmFygg8qLmdvb2dsZS5jb20uYXWCDyouZ29vZ2xlLmNvbS5icoIPKi5nb29n
bGUuY29tLmNvgg8qLmdvb2dsZS5jb20ubXiCDyouZ29vZ2xlLmNvbS50coIPKi5n
b29nbGUuY29tLnZuggsqLmdvb2dsZS5kZYILKi5nb29nbGUuZXOCCyouZ29vZ2xl
LmZyggsqLmdvb2dsZS5odYILKi5nb29nbGUuaXSCCyouZ29vZ2xlLm5sggsqLmdv
b2dsZS5wbIILKi5nb29nbGUucHSCEiouZ29vZ2xlYWRhcGlzLmNvbYIPKi5nb29n
bGVhcGlzLmNughEqLmdvb2dsZXZpZGVvLmNvbYIMKi5nc3RhdGljLmNughAqLmdz
dGF0aWMtY24uY29tgg9nb29nbGVjbmFwcHMuY26CESouZ29vZ2xlY25hcHBzLmNu
ghFnb29nbGVhcHBzLWNuLmNvbYITKi5nb29nbGVhcHBzLWNuLmNvbYIMZ2tlY25h
cHBzLmNugg4qLmdrZWNuYXBwcy5jboISZ29vZ2xlZG93bmxvYWRzLmNughQqLmdv
b2dsZWRvd25sb2Fkcy5jboIQcmVjYXB0Y2hhLm5ldC5jboISKi5yZWNhcHRjaGEu
bmV0LmNughByZWNhcHRjaGEtY24ubmV0ghIqLnJlY2FwdGNoYS1jbi5uZXSCC3dp
ZGV2aW5lLmNugg0qLndpZGV2aW5lLmNughFhbXBwcm9qZWN0Lm9yZy5jboITKi5h
bXBwcm9qZWN0Lm9yZy5jboIRYW1wcHJvamVjdC5uZXQuY26CEyouYW1wcHJvamVj
dC5uZXQuY26CF2dvb2dsZS1hbmFseXRpY3MtY24uY29tghkqLmdvb2dsZS1hbmFs
eXRpY3MtY24uY29tghdnb29nbGVhZHNlcnZpY2VzLWNuLmNvbYIZKi5nb29nbGVh
ZHNlcnZpY2VzLWNuLmNvbYIRZ29vZ2xldmFkcy1jbi5jb22CEyouZ29vZ2xldmFk
cy1jbi5jb22CEWdvb2dsZWFwaXMtY24uY29tghMqLmdvb2dsZWFwaXMtY24uY29t
ghVnb29nbGVvcHRpbWl6ZS1jbi5jb22CFyouZ29vZ2xlb3B0aW1pemUtY24uY29t
ghJkb3VibGVjbGljay1jbi5uZXSCFCouZG91YmxlY2xpY2stY24ubmV0ghgqLmZs
cy5kb3VibGVjbGljay1jbi5uZXSCFiouZy5kb3VibGVjbGljay1jbi5uZXSCDmRv
dWJsZWNsaWNrLmNughAqLmRvdWJsZWNsaWNrLmNughQqLmZscy5kb3VibGVjbGlj
ay5jboISKi5nLmRvdWJsZWNsaWNrLmNughFkYXJ0c2VhcmNoLWNuLm5ldIITKi5k
YXJ0c2VhcmNoLWNuLm5ldIIdZ29vZ2xldHJhdmVsYWRzZXJ2aWNlcy1jbi5jb22C
HyouZ29vZ2xldHJhdmVsYWRzZXJ2aWNlcy1jbi5jb22CGGdvb2dsZXRhZ3NlcnZp
Y2VzLWNuLmNvbYIaKi5nb29nbGV0YWdzZXJ2aWNlcy1jbi5jb22CF2dvb2dsZXRh
Z21hbmFnZXItY24uY29tghkqLmdvb2dsZXRhZ21hbmFnZXItY24uY29tghhnb29n
bGVzeW5kaWNhdGlvbi1jbi5jb22CGiouZ29vZ2xlc3luZGljYXRpb24tY24uY29t
giQqLnNhZmVmcmFtZS5nb29nbGVzeW5kaWNhdGlvbi1jbi5jb22CFmFwcC1tZWFz
dXJlbWVudC1jbi5jb22CGCouYXBwLW1lYXN1cmVtZW50LWNuLmNvbYILZ3Z0MS1j
bi5jb22CDSouZ3Z0MS1jbi5jb22CC2d2dDItY24uY29tgg0qLmd2dDItY24uY29t
ggsybWRuLWNuLm5ldIINKi4ybWRuLWNuLm5ldIIUZ29vZ2xlZmxpZ2h0cy1jbi5u
ZXSCFiouZ29vZ2xlZmxpZ2h0cy1jbi5uZXSCDGFkbW9iLWNuLmNvbYIOKi5hZG1v
Yi1jbi5jb22CDSouZ3N0YXRpYy5jb22CFCoubWV0cmljLmdzdGF0aWMuY29tggoq
Lmd2dDEuY29tghEqLmdjcGNkbi5ndnQxLmNvbYIKKi5ndnQyLmNvbYIOKi5nY3Au
Z3Z0Mi5jb22CECoudXJsLmdvb2dsZS5jb22CFioueW91dHViZS1ub2Nvb2tpZS5j
b22CCyoueXRpbWcuY29tggthbmRyb2lkLmNvbYINKi5hbmRyb2lkLmNvbYITKi5m
bGFzaC5hbmRyb2lkLmNvbYIEZy5jboIGKi5nLmNuggRnLmNvggYqLmcuY2+CBmdv
by5nbIIKd3d3Lmdvby5nbIIUZ29vZ2xlLWFuYWx5dGljcy5jb22CFiouZ29vZ2xl
LWFuYWx5dGljcy5jb22CCmdvb2dsZS5jb22CEmdvb2dsZWNvbW1lcmNlLmNvbYIU
Ki5nb29nbGVjb21tZXJjZS5jb22CCGdncGh0LmNuggoqLmdncGh0LmNuggp1cmNo
aW4uY29tggwqLnVyY2hpbi5jb22CCHlvdXR1LmJlggt5b3V0dWJlLmNvbYINKi55
b3V0dWJlLmNvbYIUeW91dHViZWVkdWNhdGlvbi5jb22CFioueW91dHViZWVkdWNh
dGlvbi5jb22CD3lvdXR1YmVraWRzLmNvbYIRKi55b3V0dWJla2lkcy5jb22CBXl0
LmJlggcqLnl0LmJlghphbmRyb2lkLmNsaWVudHMuZ29vZ2xlLmNvbYIbZGV2ZWxv
cGVyLmFuZHJvaWQuZ29vZ2xlLmNughxkZXZlbG9wZXJzLmFuZHJvaWQuZ29vZ2xl
LmNughhzb3VyY2UuYW5kcm9pZC5nb29nbGUuY24wIQYDVR0gBBowGDAIBgZngQwB
AgEwDAYKKwYBBAHWeQIFAzA8BgNVHR8ENTAzMDGgL6AthitodHRwOi8vY3Jscy5w
a2kuZ29vZy9ndHMxYzMvemRBVHQwRXhfRmsuY3JsMIIBBgYKKwYBBAHWeQIEAgSB
9wSB9ADyAHcABZwB0yDgB4QTlYBJjRF8kDJmr69yULWvO0akPhGEDUoAAAGBpHPd
IQAABAMASDBGAiEA2WQJomYavFJzDXZDxr00uVqZD5BECioqTZoKpyAtQb8CIQDq
tTwx9sKrTe7AnT9LmXJLQkqNOOgOXC1lzWraQnTZVgB3AEalVet1+pEgMLWiiWn0
830RLEF0vv1JuIWr8vxw/m1HAAABgaRz3SEAAAQDAEgwRgIhAOrP+HKPZQGYDY10
L1U5qfMeNus2Ag7I8Ad8GFKXu4N/AiEAjZ+Hi1zQXLnod3/dT0RuIRIkPQvR/bBQ
lrFq1I1JJHEwDQYJKoZIhvcNAQELBQADggEBAD68OnygUK82iD2YG3fbgIfwVgcp
5VRkED36vM91B6RQVkDIBftWfWMrkRo52eCFJ9G8UT6qj4XrYwnC0KwYOUA5s3t3
haw+y7qYyaILaBKTr/MNo/Q9SuGA1kRAeQc9MAZE6OD8f817OfF6exnYoEw0YMzV
nSGCh5Sudy+edxjETU/mtS0eNG9ve/ydgxGq6DoIrjGb/7Vv1NvVzawtAhijFZkz
6CprociZvhLgi/oMO5RLg5zIGFW0Tfmnr0y63PyyyTvLK3bbHucnDJ9QCTbQM52k
BBABc9qm3J9EjQfYjvrZFzLVFG88x5bVJDC3bSyMsI4iwpfJAPaLO1ZNaHM=
-----END CERTIFICATE-----

As far as I can see certificate is in the appropriate position. Thereofre I have no idea why I cannot verify the certificate I downloaded from google.


Solution

  • The problematic line is:

    int value = SSL_CTX_load_verify_locations(ctx,"google.crt",path);
    

    If CAfile is not NULL, it points to a file of CA certificates in PEM format.

    see https://www.openssl.org/docs/manmaster/man3/SSL_CTX_load_verify_locations.html

    An absolute path to this file is expected there.

    You specify only the file name as the second parameter. The associated directory is specified as the third parameter. However, the third parameter is for something else. It provides an alternative CA lookup mechanism for peer certificate verification.

    Since you provide the data already in the CA file google.crt, the third parameter can simply be set to NULL.

    So the line should rather look like:

    int value = SSL_CTX_load_verify_locations(ctx,"/absolute/path/to/google.crt", NULL);
    

    Additional Notes

    The certificate chain would look in this case like this:

    certificate chain

    SSL_CTX_load_verify_locations only specifies locations where trusted certificates are located, it does not perform any chain verification.

    Often only the root certificates are stored there, which are trusted and not a server certificate as in your example.

    A check of a server certificate is often made in such a way whether the cryptographic signatures of the certificate chain lead into one of the trusted root certificates.

    This means that it is not necessary to make adjustments to the client program every time the server certificate is changed. However, in the case of high security requirements, even server certificates are pinned. There are various procedures for this.

    Verify against root certificate

    Now, if you want to trust the GlobalSign Root CA root certificate (see the above graphic) and verify the chain, you would need to make at least the following changes to your code as follows:

    • Download the certificate with serial number 04:00:00:00:00:01:15:4b:5a:c3:94 in base 64 from here https://support.globalsign.com/ca-certificates/root-certificates/globalsign-root-certificates and save it e.g. under the file name GlobalSign Root CA.pem.

    • In the code you would then trust this root certificate: SSL_CTX_load_verify_locations(ctx,"/path/to/GlobalSign Root CA.pem",NULL);

    • in your InitCTX function directly after SSL_CTX_set_options add the line: SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL);

    SSL_CTX_set_verify() sets the verification flags for ctx to be mode and specifies the verify_callback function to be used. If no callback function shall be specified, the NULL pointer can be used for verify_callback.

    The option SSL_VERIFY_PEER means:

    Client mode: the server certificate is verified. If the verification process fails, the TLS/SSL handshake is immediately terminated with an alert message containing the reason for the verification failure. If no server certificate is sent, because an anonymous cipher is used, SSL_VERIFY_PEER is ignored.

    see https://www.openssl.org/docs/manmaster/man3/SSL_CTX_set_verify.html

    • in main after calling SSL_set_fd and before SSL_connect add:
        int res = SSL_set_tlsext_host_name(ssl, hostname);
        if(res == 0) {
            ERR_print_errors_fp(stderr);
        }
    

    SSL_set_tlsext_host_name() sets the server name indication ClientHello extension to contain the value name,

    see https://www.openssl.org/docs/manmaster/man3/SSL_set_tlsext_host_name.html