copensslopenai-apiclient-certificates

OpenSSL client authentication and macos


I'm trying to programmatically connect to Openai API to make some queries in c, but when i call try_connect() it fails with:

SSL connect error
SSL: error:0A000410:SSL routines::ssl/tls alert handshake failure

here's the code:

void printSSLErrors()
{
    char errstr[256] = {};

    unsigned long err = ERR_get_error();

    while (err != 0)
    {
        ERR_error_string_n(err, &errstr[0], sizeof(errstr));

        printf("SSL: %s", &errstr);

        err = ERR_get_error();
    }
}

union ipaddr
{
    uint8_t octect[4];  // e.g. address 192.168.4.5 has octect[0]=192 and octect[3]=5
    uint32_t octet_networkOrder;
} openai;

// fill the fields with the correct IP address
//openai.octet[0]=xx
//openai.octet[0]=xx
//openai.octet[0]=xx
//openai.octet[0]=xx

bool try_connect()
{
    //TCP socket creation
    int fd = ::socket(AF_INET, SOCK_STREAM, 0);

    if (fd == -1)
        return false;

    //SSL init

    ERR_clear_error();

    SSL_CTX* sslCtx = SSL_CTX_new(TLS_method());

    if (sslCtx == NULL)
    {
        //SSL context creation failed
        printSSLErrors();
        return false;
    }

    // CA file setup

    if (SSL_CTX_load_verify_file(sslCtx, "/etc/ssl/cert.pem") == 0)
    {
        //SSL load CA file failure
        printSSLErrors();
        return false;
    }

    // Certificate setup

    if (SSL_CTX_use_certificate_chain_file(sslCtx, "mycert.pem") != 1)
    {
        //SSL certificate file setup failed
        printSSLErrors();
        return false;
    }

    if (SSL_CTX_use_PrivateKey_file(sslCtx, "mykey.pem", SSL_FILETYPE_PEM) != 1)
    {
        //SSL private key file setup failed
        printSSLErrors();
        return false;
    }

    if (SSL_CTX_check_private_key(sslCtx) != 1)
    {
        //SSL private key check failed
        printSSLErrors();
        return false;
    }

    SSL_CTX_clear_mode(sslCtx, SSL_MODE_AUTO_RETRY);

    SSL* m_ssl = SSL_new(m_sslCtx);

    if (m_ssl == NULL)
    {
        printSSLErrors();
        return false;
    }

    if (SSL_set_fd(m_ssl, m_fd) != 1)
    {
        printSSLErrors();
        return false;
    }

    //connecting

    sockaddr_in addr{};
    addr.sin_family = AF_INET;
    addr.sin_port   = htons(443);

    addr.sin_addr.s_addr = openai.octet_networkOrder;

    int ret = ::connect(m_fd, (sockaddr *)&addr, sizeof(sockaddr_in));

    if (ret == -1)
        return false;


    if (SSL_connect(m_ssl) != 1)  // start SSL client handshaking
    {
        printf("SSL connect error");
        printSSLErrors();
        return false;
    }    

    return true;

}

The code has been tested with other servers that do not ask for client auth and it works fine. The api.openai.com asks for client auth so this code fails in try_connect(). mykey.pem and mycert.pem are files exported from the Keychain Access application starting from the com.apple.systemdefault certificate and private key. I could not try on another different system..

The interesting thing is curl works fine in the same system, using the provided curl example:

curl https://api.openai.com/v1/chat/completions \ 
-H "Content-Type: application/json" \ 
-H "Authorization: Bearer $OPENAI_API_KEY" \
-d '{
   "model": "gpt-3.5-turbo",
   "messages": [
     {
       "role": "system",
       "content": "You are a helpful assistant."
     },
     {
       "role": "user",
       "content": "Hello!"
     }
   ]
 }'

How can i fix this? Am i picking the wrong certificate or am i doing something wrong in the code?

UPDATE:

If i run

openssl s_client -connect api.openai.com:443 -servername api.openai.com -cert mycert.pem -key mykey.pem -CAfile /etc/ssl/cert.pem

It manages to connect

Connecting to 104.18.7.192
CONNECTED(00000006)
depth=2 C=US, O=Google Trust Services LLC, CN=GTS Root R1
verify return:1
depth=1 C=US, O=Google Trust Services LLC, CN=GTS CA 1P5
verify return:1
depth=0 CN=api.openai.com
verify return:1
---
Certificate chain
 0 s:CN=api.openai.com
   i:C=US, O=Google Trust Services LLC, CN=GTS CA 1P5
   a:PKEY: rsaEncryption, 2048 (bit); sigalg: RSA-SHA256
   v:NotBefore: Feb 25 00:03:27 2024 GMT; NotAfter: May 25 00:03:26 2024 GMT
 1 s:C=US, O=Google Trust Services LLC, CN=GTS CA 1P5
   i:C=US, O=Google Trust Services LLC, CN=GTS Root R1
   a:PKEY: rsaEncryption, 2048 (bit); sigalg: RSA-SHA256
   v:NotBefore: Aug 13 00:00:42 2020 GMT; NotAfter: Sep 30 00:00:42 2027 GMT
 2 s:C=US, O=Google Trust Services LLC, CN=GTS Root R1
   i:C=BE, O=GlobalSign nv-sa, OU=Root CA, CN=GlobalSign Root CA
   a:PKEY: rsaEncryption, 4096 (bit); sigalg: RSA-SHA256
   v:NotBefore: Jun 19 00:00:42 2020 GMT; NotAfter: Jan 28 00:00:42 2028 GMT
---
Server certificate
-----BEGIN CERTIFICATE-----
BLA BLA BLA
-----END CERTIFICATE-----
subject=CN=api.openai.com
issuer=C=US, O=Google Trust Services LLC, CN=GTS CA 1P5
---
No client certificate CA names sent
Peer signing digest: SHA256
Peer signature type: RSA-PSS
Server Temp Key: X25519, 253 bits
---
SSL handshake has read 4691 bytes and written 402 bytes
Verification: OK
---
New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384
Server public key is 2048 bit
This TLS version forbids renegotiation.
Compression: NONE
Expansion: NONE
No ALPN negotiated
Early data was not sent
Verify return code: 0 (ok)

so i suppose the cert and key i have are correct and accepted by the server


Solution

  • I've found the cause.

    SSL_set_tlsext_host_name() should be called before the SSL_connect() call to make SSL know the hostname of the server and complete the verification.

    I've added such a call and now it's working.

    I hope this answer will be useful to someone