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.
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 = "";
SSL_library_init();
SSL_load_error_strings();
OpenSSL_add_all_digests();
May be required before
#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