Linux c++ OCSP Client based on openssl API

By | March 21, 2023

It is functionally similar OCSP (Online Certificate Status Protocol) client as presented in previous post “Windows OCSP Client based on BouncyCastle.Crypto.dll” but oriented for Linux OS and written on C++ using openssl API. The code was tested on Ubuntu 22.04 and CentOS 8 only, but I hope it should be compatible with other Linux OS AS IS or with small changes. The opensll OCSP specific functions are defined in ocsp.h header file:
The code:

#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <netdb.h>
#include <dirent.h>
#include <fcntl.h>
#include <unistd.h>
#include <openssl/ssl.h>
#include <openssl/ocsp.h>
#include <string>
#include <string.h>
OCSP_RESPONSE* OCSP_sendreq_bio_custom(BIO* b, const char* host, const char* path, OCSP_REQUEST* req);
bool SetSocketMode(int Socket, bool bBlocking);
int main(int n, char** s)
{
    char buffer[512];
    char* host = 0, * path = 0, * port = 0;
    int usessl;
    STACK_OF(OPENSSL_STRING)* ocspList = NULL;
    BIO* bio = NULL;
    OCSP_REQUEST* reqOCSP = NULL;
    OCSP_RESPONSE* respOCSP = NULL;
    OCSP_CERTID* certID = NULL;
    X509* issuer;
    X509_STORE* store;
    STACK_OF(X509) * chain = NULL;
    FILE* p;
    X509_STORE_CTX* store_ctx;
    X509* serverCert;
    int ocspnum;
    char* ocspURL;
    struct in_addr ipaddr;
    struct sockaddr_in scktaddr;
    OCSP_BASICRESP* basicRespOCSP = NULL;
    int statusOCSP;
    ASN1_GENERALIZEDTIME* this_update = NULL, * next_update = NULL;
    if (n < 2) {
        printf("Argunmet is required. Example: https://ladydebug.com\n");
        return -1;
    }
    // Verify argument
    if (!OCSP_parse_url(s[1], &host, &port, &path, &usessl)) {
        printf("Unable to parse argument URL\n");
        return -1;
    }
    if (!usessl) {
        printf("Select HTTPS instead of HTTP\n");
        return -1;
    }
    unsigned int uiPort = atoi(port);
#if (OPENSSL_VERSION_NUMBER >= 0x10100000L)
    SSL_CTX* ctx = SSL_CTX_new(TLS_client_method());
#else
    SSL_CTX* ctx = SSL_CTX_new(SSLv23_client_method());
#endif
    SSL* ssl = SSL_new(ctx);
    if (!ssl) {
        printf("unable to create SSL object.\n");
        return -1;
    }
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    if (-1 == sock) {
        printf("Socket cannot be created, error code: %d\n", errno);
        goto end;
    }
    // set socket to non blocking state
    if (!SetSocketMode(sock, false)) {
        printf("Failed to set socket to non blocking state.\n");
        goto end;
    }
    if (!inet_aton(host, &ipaddr)) {
        struct hostent ho;
        char   szHBuf[8192] = { 0 };
        int    iRtn = 0;
        struct hostent* hostEnt = NULL;
        if (gethostbyname_r(host, &ho, szHBuf, sizeof(szHBuf), &hostEnt, &iRtn)) { //gethostbyname_r return 0 on success and nonzero on error
            hostEnt = NULL;
        }
        if ((hostEnt == (struct hostent*)NULL) || (0L == hostEnt->h_addr)) {

            printf("%s cannot be resolved\n", host);
         goto end;
        }
        memcpy(&ipaddr.s_addr, hostEnt->h_addr, hostEnt->h_length);
    }
    memset(&scktaddr, 0, sizeof(scktaddr));
    scktaddr.sin_family = AF_INET;
    scktaddr.sin_addr.s_addr = ipaddr.s_addr;
    scktaddr.sin_port = htons((short unsigned int)uiPort);
    if (::connect(sock, (struct sockaddr*)&scktaddr, (socklen_t)sizeof(scktaddr))) {
 //       printf("Socket failed to connect, but it is not always bad, error code: %d.\n", errno);
 //       perror("connect");
        // Expected result is that connection is in progress state, because it is non blocking socket.
        // If error is different exit.
        if (errno != EINPROGRESS)
        {
            goto end;
        }
    }
    // set socket to blocking state
    if (!SetSocketMode(sock, true)) {
        printf("Failed to set socket to blocking state\n");
        goto end;
    }
    // attach the normal socket to SSL object;
    if (1 != SSL_set_fd(ssl, sock)) {
        printf("Unable to attach security layer to socket.");
        goto end;
    }
    if (!SSL_set_tlsext_host_name(ssl, host)) {
        printf("Unable to set TLS servername extension for %s.\n", host);
        goto end;
    }
    // Start the TLS Hand shake.
    if (SSL_connect(ssl) != 1) {
        goto end;
    }
    // get server certificate
    serverCert = SSL_get_peer_certificate(ssl);
    close(sock);

    ocspList = X509_get1_ocsp(serverCert);
    ocspnum = sk_OPENSSL_STRING_num(ocspList);
    ocspURL = 0;
    for (int j = 0; j < ocspnum; j++)
    {
        ocspURL = sk_OPENSSL_STRING_value(ocspList, j);
        printf("%d. OCSP Url: %s\n", j+1, sk_OPENSSL_STRING_value(ocspList, j));
    }
    if (ocspnum < 1) {
        printf("Unable to get OCSP URL\n");
        goto end;
    }
    store = X509_STORE_new();
    chain = SSL_get0_verified_chain(ssl);
    if (chain == NULL)
    {
        printf("Unable to get certificate chain\n");
        goto end;
    }
    for(int i =0; i < sk_X509_num(chain); i++)
    {
        X509 * cert = sk_X509_value(chain, i);
        if (X509_cmp(serverCert, cert) != 0)
            X509_STORE_add_cert(store, cert);
    } 
    store_ctx = X509_STORE_CTX_new();
    if (store_ctx == NULL) {
        printf("Unable to create STORE_CTX*\n");
        goto end;
    }
    if (X509_STORE_CTX_init(store_ctx, store, NULL, NULL) == 0) {
        printf("Unable to init STORE_CTX*\n");
        goto end;
    }
    X509_STORE_CTX_get1_issuer(&issuer, store_ctx, serverCert);
    if (!issuer) {
        printf("Unable to get issuer certificate\n");
        goto end;
    }
    if (!OCSP_parse_url(ocspURL, &host, &port, &path, &usessl)) {
        printf("Unable to parse OCSP URL\n");
        goto end;
    }
    /* Setup BIO socket to OCSP request */
    bio = BIO_new_connect(host);
    BIO_set_conn_port(bio, port);
    BIO_do_connect(bio);
    reqOCSP = OCSP_REQUEST_new();
    certID = OCSP_cert_to_id(EVP_sha1(), serverCert, issuer);
    if (!certID) {
        printf("Unable to get certid\n");
        goto end;
    }
    if (!OCSP_request_add0_id(reqOCSP, certID)) {
        printf("Unable to add cetID to OCSP request\n");
        goto end;
    }
    respOCSP = OCSP_sendreq_bio_custom(bio, host, path, reqOCSP);
    OCSP_REQUEST_print(BIO_new_fp(stdout, BIO_NOCLOSE), reqOCSP, 0);
    if (!respOCSP) {
        printf("Unable to get OCSP response\n");
        goto end;
    }
    if (OCSP_response_status(respOCSP) != OCSP_RESPONSE_STATUS_SUCCESSFUL) {
        printf("Error. OCSP response status is not OK.");
        goto end;
    }
    basicRespOCSP = OCSP_response_get1_basic(respOCSP);
    if (!OCSP_resp_find_status(basicRespOCSP, certID, &statusOCSP, NULL, NULL, &this_update, &next_update)) {
        printf("OCSP_resp_find_status failed\n");
        goto end;
    }

    switch (statusOCSP)
    {
    case V_OCSP_CERTSTATUS_GOOD:
        printf("V_OCSP_CERTSTATUS_GOOD\n");
        break;
    case V_OCSP_CERTSTATUS_REVOKED:
        printf("V_OCSP_CERTSTATUS_REVOKED\n");
        break;
    case V_OCSP_CERTSTATUS_UNKNOWN:
    default:
        printf("OCSP status: UNKNOWN\n");
        break;
    }
end:
    SSL_shutdown(ssl);
    SSL_CTX_free(ctx);
    SSL_free(ssl);
    close(sock);
    if (ocspList)
        X509_email_free(ocspList);
    if (bio)
        BIO_free_all(bio);
    if (reqOCSP)
        OCSP_REQUEST_free(reqOCSP);
    if (respOCSP)
        OCSP_RESPONSE_free(respOCSP);
    return 0;
}
bool SetSocketMode(int Socket, bool bBlocking)
{
    int iStatus = 0;
    int iFflags = fcntl(Socket, F_GETFL);
    if (bBlocking)
    {
        iFflags &= ~O_NONBLOCK;
    }
    else {
        iFflags |= O_NONBLOCK;
    }
    iStatus = fcntl(Socket, F_SETFL, iFflags);

    if (-1 == iStatus) {
        return false;
    }
    return true;
}
OCSP_RESPONSE* OCSP_sendreq_bio_custom(BIO* b, const char* host, const char* path, OCSP_REQUEST* req) {
    OCSP_RESPONSE* resp = NULL;
    OCSP_REQ_CTX* ctx;
    int rv;
    ctx = OCSP_sendreq_new(b, path, NULL, -1);
    OCSP_REQ_CTX_add1_header(ctx, "Accept", "application/ocsp-response");
    OCSP_REQ_CTX_add1_header(ctx, "Host", host);
    OCSP_REQ_CTX_set1_req(ctx, req);
    if (ctx == NULL)
        return NULL;
    do {
        rv = OCSP_sendreq_nbio(&resp, ctx);
    } while ((rv == -1) && BIO_should_retry(b));
    OCSP_REQ_CTX_free(ctx);
    if (rv)
        return resp;
    return NULL;
}

Building:


# g++ -o ocsp_is_revoked main1.cpp -lssl -lcrypto

Results:
Not revoked certificate:


# ./ocsp_is_revoked https://ladydebug.com
1. OCSP Url: http://ocsp.comodoca.com
OCSP Request Data:
  Version: 1 (0x0)
  Requestor List:
   Certificate ID:
    Hash Algorithm: sha1
    Issuer Name Hash: 93B9FA878A7AEE4BF3FD5A2D574A3451CE84CB7C
    Issuer Key Hash: 7E035A65416BA77E0AE1B89D08EA1D8E1D6AC765
    Serial Number: E1D3E7EE9D5DD17C1EF7958076CE1013
V_OCSP_CERTSTATUS_GOOD

Revoked certificate:


# ./ocsp_is_revoked https://revoked-rsa-dv.ssl.com
1. OCSP Url: http://ocsps.ssl.com
OCSP Request Data:
  Version: 1 (0x0)
  Requestor List:
   Certificate ID:
    Hash Algorithm: sha1
    Issuer Name Hash: D49294BE2B4A19852331FE698267BE94A9D8D4C5
    Issuer Key Hash: 26147EE0DCD7A6F7E2D40427DF61F1C2ECE732CA
    Serial Number: 6AAEFB5046E7425ACE83E2FA1E145105
V_OCSP_CERTSTATUS_REVOKED

OCSP HTTP Request/Response with tcpdump:


# tcpdump host ocsp.comodoca.com -vvv
tcpdump: listening on eth01, link-type EN10MB (Ethernet), snapshot length 262144 bytes
......
Smike.Ubuntu.51504 > 104.18.32.68.http: Flags [P.], cksum 0xc4aa (incorrect -> 0xc9af), seq 1:140, ack 1, win 502, options [nop,nop,TS val 712919980 ecr 808903555], length 139: HTTP, length: 139
   POST / HTTP/1.0
   Content-Type: application/ocsp-request
   Content-Length: 84
   Accept: application/ocsp-response
   Host: ocsp.comodoca.com
......
104.18.32.68.http > Smike.Ubuntu.51504: Flags [P.], cksum 0x939a (correct), seq 1:988, ack 224, win 8, options [nop,nop,TS val 808903573 ecr 712919982], length 987: HTTP, length: 987
   HTTP/1.1 200 OK
   Date: Tue, 21 Mar 2023 20:50:39 GMT
   Content-Type: application/ocsp-response
   Content-Length: 472
   Connection: close
   Last-Modified: Sat, 18 Mar 2023 14:59:00 GMT
   Expires: Sat, 25 Mar 2023 14:58:59 GMT
   Etag: "0d762da3bac5dad784f3be7ba298437a68ab7cd4"
   Cache-Control: max-age=340556,s-maxage=1800,public,no-transform,must-revalidate
   X-CCACDN-Proxy-ID: scdpinlb2
   X-Frame-Options: SAMEORIGIN
   CF-Cache-Status: REVALIDATED
   Accept-Ranges: bytes
   Server: cloudflare
   CF-RAY: 7ab911853c1d3b36-IAD

For earlier Linux versions replace SSL_get0_verified_chain with SSL_get_peer_cert_chain for earlier Linux versions.

One thought on “Linux c++ OCSP Client based on openssl API

  1. smike19 Post author

    Previously instead of SSL_get0_verified_chain I was parsing openssl command. I added this comment because I want to keep these code as well:

    FILE* p;
    const char opensslFormat[] = “openssl s_client -showcerts -connect %s:%s 2> /dev/null < /dev/null"; char opensslCommand[256]; const char beginCert[] = "BEGIN CERTIFICATE"; const char endCert[] = "END CERTIFICATE"; char szBuffer[256]; bool bCertBegin = false; bool bCertEnd = false; std::string certStr = "";

    sprintf(opensslCommand, opensslFormat, host, port);
    p = popen(opensslCommand, “r”);
    if (p != NULL) {
    while (!feof(p)) {
    if (fgets(szBuffer, sizeof(szBuffer), p) != NULL) {
    if (strstr(szBuffer, beginCert) != NULL) {
    bCertBegin = true;
    bCertEnd = false;
    }
    if (bCertBegin)
    certStr += szBuffer;
    if (strstr(szBuffer, endCert) != NULL) {
    bCertBegin = false;
    bCertEnd = true;
    }
    }
    if (bCertEnd) {
    BIO* bio_mem = BIO_new(BIO_s_mem());
    BIO_puts(bio_mem, certStr.c_str());
    X509* cert = PEM_read_bio_X509(bio_mem, NULL, NULL, NULL);
    if (X509_cmp(serverCert, cert) != 0)
    X509_STORE_add_cert(store, cert);
    BIO_free(bio_mem);
    bCertEnd = false;
    certStr.clear();
    }
    }
    pclose(p);
    }

    Reply

Leave a Reply to smike19 Cancel reply

Your email address will not be published. Required fields are marked *