C
main.c
/***************************************************************************
* _ _ ____ _
* Project ___| | | | _ \| |
* / __| | | | |_) | |
* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
* Copyright (C) 1998 - 2019, Daniel Stenberg, <daniel@haxx.se>, et al.
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
* are also available at https://curl.haxx.se/docs/copyright.html.
*
* You may opt to use, copy, modify, merge, publish, distribute and/or sell
* copies of the Software, and permit persons to whom the Software is
* furnished to do so, under the terms of the COPYING file.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
***************************************************************************/
/* <DESC>
* CA cert in memory with OpenSSL to get a HTTPS page.
* </DESC>
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <json-c/json.h>
#include <curl/curl.h>
struct MemoryStruct {
char *memory;
size_t size;
};
const char *pubkey_file = "pubkey.pem";
static size_t
WriteMemoryCallback(void *contents, size_t size, size_t nmemb, void *userp) {
size_t realsize = size * nmemb;
struct MemoryStruct *mem = (struct MemoryStruct *) userp;
char *ptr = realloc(mem->memory, mem->size + realsize + 1);
if (ptr == NULL) {
/* out of memory! */
printf("not enough memory (realloc returned NULL)\n");
return 0;
}
mem->memory = ptr;
memcpy(&(mem->memory[mem->size]), contents, realsize);
mem->size += realsize;
mem->memory[mem->size] = 0;
return realsize;
}
char worker(char api[], char domain[]) {
CURL *ch;
CURLcode rv;
// setup api response data holder
struct MemoryStruct chunk;
chunk.memory = malloc(1);
chunk.size = 0;
// setup libcurl
curl_global_init(CURL_GLOBAL_ALL);
ch = curl_easy_init();
curl_easy_setopt(ch, CURLOPT_VERBOSE, 0L);
curl_easy_setopt(ch, CURLOPT_HEADER, 0L);
curl_easy_setopt(ch, CURLOPT_NOPROGRESS, 1L);
curl_easy_setopt(ch, CURLOPT_NOSIGNAL, 1L);
curl_easy_setopt(ch, CURLOPT_WRITEDATA, stdout);
curl_easy_setopt(ch, CURLOPT_WRITEFUNCTION, WriteMemoryCallback);
curl_easy_setopt(ch, CURLOPT_WRITEDATA, (void *) &chunk);
curl_easy_setopt(ch, CURLOPT_SSLCERTTYPE, "PEM");
curl_easy_setopt(ch, CURLOPT_SSL_VERIFYPEER, 1L);
// call api.cert.ist to get the expected public keys
curl_easy_setopt(ch, CURLOPT_URL, api);
rv = curl_easy_perform(ch);
if (rv != CURLE_OK) {
printf("*** Failed to reach %s: %d ***\n", api, rv);
return 1;
}
// parse our the public key pem from cert.ist's api
json_object *jobj = json_tokener_parse(chunk.memory);
struct json_object *base = json_object_object_get(jobj, "openssl");
struct json_object *pubkey_obj = json_object_object_get(base, "pubkey");
struct json_object *pem_obj = json_object_object_get(pubkey_obj, "pem");
const char *pem = json_object_get_string(pem_obj);
// write the PEM public key to a file
FILE *file = fopen(pubkey_file, "w");
int results = fputs(pem, file);
if (results == EOF) {
printf("Error results: %d", results);
}
fclose(file);
/* use a fresh connection (optional)
* this option seriously impacts performance of multiple transfers but
* it is necessary order to demonstrate this example. recall that the
* ssl ctx callback is only called _before_ an SSL connection is
* established, therefore it will not affect existing verified SSL
* connections already in the connection cache associated with this
* handle. normally you would set the ssl ctx function before making
* any transfers, and not use this option.
*/
curl_easy_setopt(ch, CURLOPT_FRESH_CONNECT, 1L);
// tell curl to call the domain in question
curl_easy_setopt(ch, CURLOPT_URL, domain);
// tell curl to pin the public key we found from the API to the
// public key curl receives during the TLS hello handshake
curl_easy_setopt(ch, CURLOPT_PINNEDPUBLICKEY, pubkey_file);
rv = curl_easy_perform(ch);
if (rv == CURLE_OK) {
printf("(%s) certs matched :D\n", domain);
} else if (rv == CURLE_SSL_PINNEDPUBKEYNOTMATCH) {
printf("(%s) certs did not match\n", domain);
} else {
printf("Something else happened, we received error code: %d\n", rv);
}
curl_easy_cleanup(ch);
curl_global_cleanup();
return rv;
}
int main(void) {
char example = worker("https://api.cert.ist/example.com", "https://example.com");
char certist = worker("https://api.cert.ist/cert.ist", "https://cert.ist");
char uripio = worker("https://api.cert.ist/urip.io", "https://urip.io");
return example + certist + uripio;
}
CMakeLists.txt
cmake_minimum_required(VERSION 3.17)
project(ssl_pin_c VERSION 0.0.1 LANGUAGES C)
set(CMAKE_C_STANDARD 99)
find_package(OpenSSL REQUIRED)
find_package(CURL REQUIRED)
find_package(Json-C REQUIRED)
include_directories(/usr/local/include)
include_directories(/usr/local/opt/json-c/include)
link_directories(/usr/local/opt/json-c/lib)
include_directories(/usr/local/opt/openssl/include)
add_executable(ssl_pin_c main.c)
target_link_libraries(ssl_pin_c ${CURL_LIBRARIES})
target_link_libraries(ssl_pin_c OpenSSL::SSL OpenSSL::Crypto)
target_link_libraries(ssl_pin_c json-c)
Compile with cmake
$ cmake --build ./cmake-build-debug --target ssl_pin_c -lcurl -lssl -lcrypto
Output
$ ./cmake-build-debug/ssl_pin_c
(https://example.com) certs matched :D
(https://cert.ist) certs matched :D
(https://urip.io) certs matched :D
Github