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