inline int err_tpkp_to_gnutlse(tpkp_e err) noexcept
{
switch (err) {
- case TPKP_E_NONE: return GNUTLS_E_SUCCESS;
- case TPKP_E_MEMORY: return GNUTLS_E_MEMORY_ERROR;
- case TPKP_E_INVALID_URL: return GNUTLS_E_INVALID_SESSION;
- case TPKP_E_NO_URL_DATA: return GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE;
- case TPKP_E_PUBKEY_MISMATCH: return GNUTLS_E_CERTIFICATE_KEY_MISMATCH;
+ case TPKP_E_NONE: return GNUTLS_E_SUCCESS;
+ case TPKP_E_MEMORY: return GNUTLS_E_MEMORY_ERROR;
+ case TPKP_E_INVALID_URL: return GNUTLS_E_INVALID_SESSION;
+ case TPKP_E_NO_URL_DATA: return GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE;
+ case TPKP_E_PUBKEY_MISMATCH: return GNUTLS_E_CERTIFICATE_KEY_MISMATCH;
case TPKP_E_INVALID_CERT:
case TPKP_E_INVALID_PEER_CERT_CHAIN:
- case TPKP_E_FAILED_GET_PUBKEY_HASH: return GNUTLS_E_PK_SIG_VERIFY_FAILED;
+ case TPKP_E_FAILED_GET_PUBKEY_HASH: return GNUTLS_E_PK_SIG_VERIFY_FAILED;
+ case TPKP_E_CERT_VERIFICATION_FAILED: return GNUTLS_E_CERTIFICATE_ERROR;
case TPKP_E_STD_EXCEPTION:
case TPKP_E_INTERNAL:
- default: return GNUTLS_E_INTERNAL_ERROR;
+ default: return GNUTLS_E_INTERNAL_ERROR;
}
}
+using GnutlsX509Ptr = std::unique_ptr<gnutls_x509_crt_t, void(*)(gnutls_x509_crt_t *)>;
+inline GnutlsX509Ptr createGnutlsX509Ptr(void)
+{
+ return GnutlsX509Ptr(new gnutls_x509_crt_t, [](gnutls_x509_crt_t *ptr) {
+ if (!!ptr) gnutls_x509_crt_deinit(*ptr);
+ });
+}
+
TPKP::RawBuffer getPubkeyHash(gnutls_x509_crt_t cert, TPKP::HashAlgo algo)
{
std::unique_ptr<gnutls_pubkey_t, void(*)(gnutls_pubkey_t *)>
return out;
}
+GnutlsX509Ptr d2iCert(const gnutls_datum_t *datum)
+{
+ auto crtPtr = createGnutlsX509Ptr();
+
+ TPKP_CHECK_THROW_EXCEPTION(
+ gnutls_x509_crt_init(crtPtr.get()) == GNUTLS_E_SUCCESS,
+ TPKP_E_INTERNAL, "Failed to gnutls_x509_crt_init.");
+ TPKP_CHECK_THROW_EXCEPTION(
+ gnutls_x509_crt_import(*crtPtr, datum, GNUTLS_X509_FMT_DER) >= 0,
+ TPKP_E_INTERNAL, "Failed to import DER to gnutls crt");
+
+ return crtPtr;
+}
+
+/*
+ * Need not to gnutls_x509_crt_deinit for returned value unless GNUTLS_TL_GET_COPY
+ * flag is used.
+ * Refer API description of gnutls_certificate_get_issuer.
+ *
+ * gnutls_certificate_get_issuer will return the issuer of a given certificate.
+ * As with gnutls_x509_trust_list_get_issuer() this functions requires the
+ * GNUTLS_TL_GET_COPY flag in order to operate with PKCS11 trust list. In
+ * that case the issuer must be freed using gnutls_x509_crt_init().
+ */
+gnutls_x509_crt_t getIssuer(gnutls_session_t session, gnutls_x509_crt_t cert)
+{
+ gnutls_certificate_credentials_t cred;
+ TPKP_CHECK_THROW_EXCEPTION(
+ gnutls_credentials_get(session, GNUTLS_CRD_CERTIFICATE, (void **)&cred)
+ == GNUTLS_E_SUCCESS,
+ TPKP_E_INTERNAL, "Failed to get credential on session");
+
+ gnutls_x509_crt_t issuer;
+ TPKP_CHECK_THROW_EXCEPTION(
+ gnutls_x509_crt_init(&issuer) == GNUTLS_E_SUCCESS,
+ TPKP_E_INTERNAL, "Failed to gnutls_x509_crt_init");
+
+ TPKP_CHECK_THROW_EXCEPTION(
+ gnutls_certificate_get_issuer(cred, cert, &issuer, 0) == GNUTLS_E_SUCCESS,
+ TPKP_E_INTERNAL,
+ "Failed to get issuer! It's internal error because verify peer2 success already");
+
+ return issuer;
+}
+
}
EXPORT_API
tpkp_e res = TPKP::ExceptionSafe([&]{
gnutls_certificate_type_t type = gnutls_certificate_type_get(session);
if (type != GNUTLS_CRT_X509) {
- //
- // TODO: what should we do if it's not x509 type cert?
- // for now, just return 0 which means verification success
- //
+ /*
+ * TODO: what should we do if it's not x509 type cert?
+ * for now, just return 0 which means verification success
+ */
SLOGW("Certificate type of session isn't X509. skipt for now...");
return;
}
- unsigned int listSize = 0;
- const gnutls_datum_t *certChain = gnutls_certificate_get_peers(session, &listSize);
- TPKP_CHECK_THROW_EXCEPTION(certChain != nullptr && listSize != 0,
- TPKP_E_INVALID_PEER_CERT_CHAIN,
- "no certificate from peer!");
+ unsigned int status = 0;
+ int res = gnutls_certificate_verify_peers2(session, &status);
+ TPKP_CHECK_THROW_EXCEPTION(res == GNUTLS_E_SUCCESS,
+ TPKP_E_CERT_VERIFICATION_FAILED,
+ "Failed to certificate verify peers2.. res: " << gnutls_strerror(res));
+
+ TPKP_CHECK_THROW_EXCEPTION(status == 0,
+ TPKP_E_CERT_VERIFICATION_FAILED,
+ "Peer certificate verification failed!! status: " << status);
auto tid = TPKP::getThreadId();
std::string url;
return;
}
+ unsigned int listSize = 0;
+ const gnutls_datum_t *certChain = gnutls_certificate_get_peers(session, &listSize);
+ TPKP_CHECK_THROW_EXCEPTION(certChain != nullptr && listSize != 0,
+ TPKP_E_INVALID_PEER_CERT_CHAIN,
+ "no certificate from peer!");
+
for (unsigned int i = 0; i < listSize; i++) {
- std::unique_ptr<gnutls_x509_crt_t, void(*)(gnutls_x509_crt_t *)>
- crtPtr(new gnutls_x509_crt_t, [](gnutls_x509_crt_t *ptr)->void
- {
- if (ptr != nullptr)
- gnutls_x509_crt_deinit(*ptr);
- });
-
- TPKP_CHECK_THROW_EXCEPTION(
- gnutls_x509_crt_init(crtPtr.get()) == GNUTLS_E_SUCCESS,
- TPKP_E_INTERNAL,
- "Failed to gnutls_x509_crt_init.");
-
- TPKP_CHECK_THROW_EXCEPTION(
- gnutls_x509_crt_import(*crtPtr, certChain++, GNUTLS_X509_FMT_DER) >= 0,
- TPKP_E_INVALID_CERT,
- "Failed to import DER cert to gnutls crt");
+ auto crtPtr = d2iCert(certChain++);
ctx.addPubkeyHash(
TPKP::HashAlgo::SHA1,
getPubkeyHash(*crtPtr, TPKP::HashAlgo::SHA1));
+
+ /* add additional root CA cert for last one */
+ if (i == listSize - 1) {
+ auto issuer = getIssuer(session, *crtPtr);
+
+ ctx.addPubkeyHash(
+ TPKP::HashAlgo::SHA1,
+ getPubkeyHash(issuer, TPKP::HashAlgo::SHA1));
+ }
}
TPKP_CHECK_THROW_EXCEPTION(ctx.checkPubkeyPins(),
* @version 1.0
* @brief tpkp_gnutls unit test.
*/
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
+#include <iostream>
+#include <vector>
+#include <string>
+#include <thread>
+#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
+#include <netdb.h>
#include <netinet/in.h>
#include <arpa/inet.h>
-#include <iostream>
-
#include <gnutls/gnutls.h>
-
#include <tpkp_gnutls.h>
-
#include <boost/test/unit_test.hpp>
-#define SHA1_LENGTH 20
-
namespace {
-const std::string targetUrl = "https://WwW.GooGle.cO.Kr";
-
-const char certStr[] =
- "-----BEGIN CERTIFICATE-----\n"
- "MIIDfTCCAuagAwIBAgIDErvmMA0GCSqGSIb3DQEBBQUAME4xCzAJBgNVBAYTAlVT\n"
- "MRAwDgYDVQQKEwdFcXVpZmF4MS0wKwYDVQQLEyRFcXVpZmF4IFNlY3VyZSBDZXJ0\n"
- "aWZpY2F0ZSBBdXRob3JpdHkwHhcNMDIwNTIxMDQwMDAwWhcNMTgwODIxMDQwMDAw\n"
- "WjBCMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEbMBkGA1UE\n"
- "AxMSR2VvVHJ1c3QgR2xvYmFsIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB\n"
- "CgKCAQEA2swYYzD99BcjGlZ+W988bDjkcbd4kdS8odhM+KhDtgPpTSEHCIjaWC9m\n"
- "OSm9BXiLnTjoBbdqfnGk5sRgprDvgOSJKA+eJdbtg/OtppHHmMlCGDUUna2YRpIu\n"
- "T8rxh0PBFpVXLVDviS2Aelet8u5fa9IAjbkU+BQVNdnARqN7csiRv8lVK83Qlz6c\n"
- "JmTM386DGXHKTubU1XupGc1V3sjs0l44U+VcT4wt/lAjNvxm5suOpDkZALeVAjmR\n"
- "Cw7+OC7RHQWa9k0+bw8HHa8sHo9gOeL6NlMTOdReJivbPagUvTLrGAMoUgRx5asz\n"
- "PeE4uwc2hGKceeoWMPRfwCvocWvk+QIDAQABo4HwMIHtMB8GA1UdIwQYMBaAFEjm\n"
- "aPkr0rKV10fYIyAQTzOYkJ/UMB0GA1UdDgQWBBTAephojYn7qwVkDBF9qn1luMrM\n"
- "TjAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjA6BgNVHR8EMzAxMC+g\n"
- "LaArhilodHRwOi8vY3JsLmdlb3RydXN0LmNvbS9jcmxzL3NlY3VyZWNhLmNybDBO\n"
- "BgNVHSAERzBFMEMGBFUdIAAwOzA5BggrBgEFBQcCARYtaHR0cHM6Ly93d3cuZ2Vv\n"
- "dHJ1c3QuY29tL3Jlc291cmNlcy9yZXBvc2l0b3J5MA0GCSqGSIb3DQEBBQUAA4GB\n"
- "AHbhEm5OSxYShjAGsoEIz/AIx8dxfmbuwu3UOx//8PDITtZDOLC5MH0Y0FWDomrL\n"
- "NhGc6Ehmo21/uBPUR/6LWlxz/K7ZGzIZOKuXNBSqltLroxwUCEm2u+WR74M26x1W\n"
- "b8ravHNjkOR/ez4iyz0H7V84dJzjA1BOoa+Y7mHyhD8S\n"
- "-----END CERTIFICATE-----\n";
-
-const char sha1hash[] = // kSPKIHash_GeoTrustGlobal
- "\xc0\x7a\x98\x68\x8d\x89\xfb\xab\x05\x64"
- "\x0c\x11\x7d\xaa\x7d\x65\xb8\xca\xcc\x4e";
-
-int tcp_connect(const char *ip, int port)
-{
- int sock;
- struct sockaddr_in server_addr;
-
- BOOST_REQUIRE_MESSAGE(
- (sock = socket(PF_INET, SOCK_STREAM, 0)) >= 0,
- "Cannot create socket!");
-
- memset(&server_addr, 0x00, sizeof(server_addr));
- server_addr.sin_family = AF_INET;
- server_addr.sin_addr.s_addr = inet_addr(ip);
- server_addr.sin_port = htons(port);
- BOOST_REQUIRE_MESSAGE(
- (connect(sock, (struct sockaddr *)&server_addr, sizeof(server_addr))) >= 0,
- "Cannot connect to sock:" << sock);
+struct DataSet {
+ gnutls_session_t session;
+ gnutls_certificate_credentials_t cred;
+ int sockfd;
+};
- return sock;
-}
+static std::vector<std::string> s_urlList = {
+ "www.google.com",
+ "www.youtube.com",
+ "www.spideroak.com",
+ "www.facebook.com",
+ "www.dropbox.com",
+ "www.twitter.com",
+ "www.hackerrank.com", /* no pinned data exist */
+ "www.algospot.com" /* no pinned data exist */
+};
-void tcp_close(int sd)
+void connectWithUrl(const std::string &url, int &sockfd)
{
- close(sd);
-}
+ struct addrinfo *result;
+ struct addrinfo hints;
+ memset(&hints, 0x00, sizeof(struct addrinfo));
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_flags = AI_CANONNAME;
-typedef struct _GIOGnuTLSChannel GIOGnuTLSChannel;
+ int s = getaddrinfo(url.c_str(), "https", &hints, &result);
+ BOOST_REQUIRE_MESSAGE(s == 0, "getaddrinfo err code: " << s << " desc: " << gai_strerror(s));
-struct _GIOGnuTLSChannel {
- int fd;
-};
+ struct addrinfo *rp;
+ for (rp = result; rp != nullptr; rp = rp->ai_next) {
+ sockfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
+ if (sockfd == -1)
+ continue;
-gnutls_session_t makeDefaultSession(void)
-{
- int ret = gnutls_global_init();
- BOOST_REQUIRE_MESSAGE(
- ret == GNUTLS_E_SUCCESS,
- "Failed to gnutls global init: " << gnutls_strerror(ret));
+ if (connect(sockfd, rp->ai_addr, rp->ai_addrlen) != -1) {
+ char *ipaddr = inet_ntoa(*((struct in_addr *)rp->ai_addr));
+ break;
+ }
- gnutls_session_t session;
- ret = gnutls_init(&session, GNUTLS_CLIENT);
- BOOST_REQUIRE_MESSAGE(
- ret == GNUTLS_E_SUCCESS,
- "Failed to gnutls init session: " << gnutls_strerror(ret));
+ close(sockfd);
+ }
- ret = gnutls_set_default_priority(session);
- BOOST_REQUIRE_MESSAGE(
- ret == GNUTLS_E_SUCCESS,
- "Failed to set default priority on session: " << gnutls_strerror(ret));
+ BOOST_REQUIRE_MESSAGE(rp != nullptr, "Could not connect on url: " << url);
- return session;
+ std::cout << "url[" << url << "] canonname[" << result->ai_canonname << "] connected!" << std::endl;
+
+ freeaddrinfo(result);
}
inline gnutls_certificate_credentials_t makeDefaultCred(gnutls_certificate_verify_function *verify_callback)
BOOST_REQUIRE_MESSAGE(
ret > 0,
"Failed to gnutls_certificate_set_x509_trust_file ret: " << ret);
- std::cout << "x509 trust file loaded. cert num: " << ret << std::endl;
gnutls_certificate_set_verify_function(cred, verify_callback);
return cred;
}
-}
+DataSet makeDefaultSession(const std::string &url)
+{
+ DataSet data;
-BOOST_AUTO_TEST_SUITE(TPKP_GNUTLS_TEST)
+ data.cred = makeDefaultCred(&tpkp_gnutls_verify_callback);
-#define MAX_BUF 1024
-#define MSG "GET / HTTP/1.0\r\n\r\n"
-BOOST_AUTO_TEST_CASE(T00101_positive)
-{
- gnutls_certificate_credentials_t cred = makeDefaultCred(&tpkp_gnutls_verify_callback);
- gnutls_session_t session = makeDefaultSession();
+ int ret = gnutls_init(&data.session, GNUTLS_CLIENT);
+ BOOST_REQUIRE_MESSAGE(
+ ret == GNUTLS_E_SUCCESS,
+ "Failed to gnutls init session: " << gnutls_strerror(ret));
- int ret = gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, cred);
+ ret = gnutls_set_default_priority(data.session);
+ BOOST_REQUIRE_MESSAGE(
+ ret == GNUTLS_E_SUCCESS,
+ "Failed to set default priority on session: " << gnutls_strerror(ret));
+
+ ret = gnutls_credentials_set(data.session, GNUTLS_CRD_CERTIFICATE, data.cred);
BOOST_REQUIRE_MESSAGE(
ret == GNUTLS_E_SUCCESS,
"Failed to gnutls_credentials_set: " << gnutls_strerror(ret));
- int sd = tcp_connect("216.58.221.36", 443);
+ connectWithUrl(url, data.sockfd);
BOOST_REQUIRE_MESSAGE(
- tpkp_gnutls_set_url_data(targetUrl.c_str()) == TPKP_E_NONE,
+ tpkp_gnutls_set_url_data(url.c_str()) == TPKP_E_NONE,
"Failed to tpkp_gnutls_set_url_data.");
- gnutls_transport_set_int(session, sd);
- gnutls_handshake_set_timeout(session, GNUTLS_DEFAULT_HANDSHAKE_TIMEOUT);
+ gnutls_transport_set_int(data.session, data.sockfd);
+ gnutls_handshake_set_timeout(data.session, GNUTLS_DEFAULT_HANDSHAKE_TIMEOUT);
+
+ return data;
+}
+
+DataSet makeSessionWithoutPinning(const std::string &url)
+{
+ DataSet data;
+ int ret = gnutls_certificate_allocate_credentials(&data.cred);
+ BOOST_REQUIRE_MESSAGE(
+ ret == GNUTLS_E_SUCCESS,
+ "Failed to gnutls_certificate_allocate_credentials: " << gnutls_strerror(ret));
+
+ ret = gnutls_init(&data.session, GNUTLS_CLIENT);
+ BOOST_REQUIRE_MESSAGE(
+ ret == GNUTLS_E_SUCCESS,
+ "Failed to gnutls init session: " << gnutls_strerror(ret));
+
+ ret = gnutls_set_default_priority(data.session);
+ BOOST_REQUIRE_MESSAGE(
+ ret == GNUTLS_E_SUCCESS,
+ "Failed to set default priority on session: " << gnutls_strerror(ret));
+
+ ret = gnutls_credentials_set(data.session, GNUTLS_CRD_CERTIFICATE, data.cred);
+ BOOST_REQUIRE_MESSAGE(
+ ret == GNUTLS_E_SUCCESS,
+ "Failed to gnutls_credentials_set: " << gnutls_strerror(ret));
+
+ connectWithUrl(url, data.sockfd);
+
+ gnutls_transport_set_int(data.session, data.sockfd);
+ gnutls_handshake_set_timeout(data.session, GNUTLS_DEFAULT_HANDSHAKE_TIMEOUT);
+
+ return data;
+}
+
+DataSet makeDefaultSessionGlobal(const std::string &url)
+{
+ int ret = gnutls_global_init();
+ BOOST_REQUIRE_MESSAGE(
+ ret == GNUTLS_E_SUCCESS,
+ "Failed to gnutls global init: " << gnutls_strerror(ret));
+
+ return makeDefaultSession(url);
+}
+
+void performHandshake(DataSet &data)
+{
+ int ret;
do {
- ret = gnutls_handshake(session);
+ ret = gnutls_handshake(data.session);
} while (ret != GNUTLS_E_SUCCESS && gnutls_error_is_fatal(ret) == 0);
BOOST_REQUIRE_MESSAGE(
ret == GNUTLS_E_SUCCESS,
- "Handshake failed: " << gnutls_strerror(ret));
+ "Handshake failed! err code: " << ret << " desc: " << gnutls_strerror(ret));
+}
+
+void cleanup(DataSet &data)
+{
+ gnutls_bye(data.session, GNUTLS_SHUT_RDWR);
+ close(data.sockfd);
+ gnutls_certificate_free_credentials(data.cred);
+ gnutls_deinit(data.session);
- gnutls_bye(session, GNUTLS_SHUT_RDWR);
+ tpkp_gnutls_cleanup();
+}
+
+void cleanupGlobal(DataSet &data)
+{
+ cleanup(data);
+ gnutls_global_deinit();
+}
+
+void perform(const std::string &url)
+{
+ DataSet data = makeDefaultSession(url);
+ performHandshake(data);
+ cleanup(data);
+}
+
+void performWithoutPinning(const std::string &url)
+{
+ DataSet data = makeSessionWithoutPinning(url);
+ performHandshake(data);
+ cleanup(data);
+}
+
+}
+
+BOOST_AUTO_TEST_SUITE(TPKP_GNUTLS_TEST)
+
+BOOST_AUTO_TEST_CASE(T00101_positive_1)
+{
+ gnutls_global_init();
+
+ perform(s_urlList[0]);
+
+ gnutls_global_deinit();
+}
+
+BOOST_AUTO_TEST_CASE(T00102_positive_2)
+{
+ gnutls_global_init();
+
+ perform(s_urlList[1]);
+
+ gnutls_global_deinit();
+}
+
+BOOST_AUTO_TEST_CASE(T00103_positive_3)
+{
+ gnutls_global_init();
- tcp_close(sd);
+ perform(s_urlList[2]);
+
+ gnutls_global_deinit();
+}
+
+BOOST_AUTO_TEST_CASE(T00104_positive_4)
+{
+ gnutls_global_init();
+
+ perform(s_urlList[3]);
+
+ gnutls_global_deinit();
+}
+
+BOOST_AUTO_TEST_CASE(T00105_positive_5)
+{
+ gnutls_global_init();
+
+ perform(s_urlList[4]);
+
+ gnutls_global_deinit();
+}
+
+BOOST_AUTO_TEST_CASE(T00106_positive_6)
+{
+ gnutls_global_init();
+
+ perform(s_urlList[5]);
+
+ gnutls_global_deinit();
+}
+
+BOOST_AUTO_TEST_CASE(T00107_positive_7)
+{
+ gnutls_global_init();
+
+ perform(s_urlList[6]);
+
+ gnutls_global_deinit();
+}
+
+BOOST_AUTO_TEST_CASE(T00108_positive_8)
+{
+ gnutls_global_init();
+
+ perform(s_urlList[7]);
+
+ gnutls_global_deinit();
+}
+
+BOOST_AUTO_TEST_CASE(T00109_positive_all_single_thread)
+{
+ gnutls_global_init();
+
+ for (const auto &url : s_urlList)
+ perform(url);
+
+ gnutls_global_deinit();
+}
+
+BOOST_AUTO_TEST_CASE(T00110_positive_all_single_thread_without_pinning)
+{
+ gnutls_global_init();
- gnutls_certificate_free_credentials(cred);
+ for (const auto &url : s_urlList)
+ performWithoutPinning(url);
- gnutls_deinit(session);
- gnutls_global_deinit();
+ gnutls_global_deinit();
}
BOOST_AUTO_TEST_SUITE_END()