Skip to content

Instantly share code, notes, and snippets.

@rexim
Last active November 10, 2024 10:10
Show Gist options
  • Save rexim/ab45940928399bbda212185b4a6a6339 to your computer and use it in GitHub Desktop.
Save rexim/ab45940928399bbda212185b4a6a6339 to your computer and use it in GitHub Desktop.
Simple OpenSSL example that makes HTTPS request
// cc `pkg-config --cflags openssl` -o simple_openssl_client simple_openssl_client.c `pkg-config --libs openssl`
// Copyright 2021 Alexey Kutepov <[email protected]>
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#define _POSIX_C_SOURCE 200112L
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <stdbool.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#define HOST "tsoding.github.io"
#define PORT "443"
int main(int argc, char **argv)
{
struct addrinfo hints = {0};
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
struct addrinfo *addrs;
if (getaddrinfo(HOST, PORT, &hints, &addrs) < 0) {
fprintf(stderr, "Could not get address of `"HOST"`: %s\n",
strerror(errno));
exit(1);
}
int sd = 0;
for (struct addrinfo *addr = addrs; addr != NULL; addr = addr->ai_next) {
sd = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol);
if (sd == -1) break;
if (connect(sd, addr->ai_addr, addr->ai_addrlen) == 0) break;
close(sd);
sd = -1;
}
freeaddrinfo(addrs);
if (sd == -1) {
fprintf(stderr, "Could not connect to "HOST":"PORT": %s\n",
strerror(errno));
exit(1);
}
OpenSSL_add_all_algorithms();
SSL_load_error_strings();
SSL_CTX *ctx = SSL_CTX_new(TLS_client_method());
if (ctx == NULL) {
fprintf(stderr, "ERROR: could not initialize the SSL context: %s\n",
strerror(errno));
exit(1);
}
SSL *ssl = SSL_new(ctx);
SSL_set_fd(ssl, sd);
if (SSL_connect(ssl) < 0) {
fprintf(stderr, "ERROR: could not connect via SSL: %s\n",
strerror(errno));
exit(1);
}
const char *request =
"GET / HTTP/1.0\r\n"
"Host: tsoding.github.io\r\n"
"\r\n";
SSL_write(ssl, request, strlen(request));
char buffer[1024];
ssize_t n = SSL_read(ssl, buffer, sizeof(buffer));
while (n > 0) {
fwrite(buffer, 1, n, stdout);
n = SSL_read(ssl, buffer, sizeof(buffer));
}
SSL_set_shutdown(ssl, SSL_RECEIVED_SHUTDOWN | SSL_SENT_SHUTDOWN);
SSL_shutdown(ssl);
SSL_free(ssl);
SSL_CTX_free(ctx);
close(sd);
return 0;
}
// $ jai -version
// Version: beta 0.1.060, built on 8 April 2023.
// $ jai -plug Autorun simple_openssl_client.jai
// Copyright 2021 Alexey Kutepov <[email protected]>
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "Basic";
#import "Socket";
#import "POSIX";
libssl :: #system_library "libssl";
libcrypto :: #system_library "libcrypto";
OPENSSL_INIT_ADD_ALL_CIPHERS :: 0x00000004;
OPENSSL_INIT_ADD_ALL_DIGESTS :: 0x00000008;
OPENSSL_INIT_LOAD_CONFIG :: 0x00000040;
OPENSSL_INIT_LOAD_SSL_STRINGS :: 0x00200000;
OPENSSL_INIT_LOAD_CRYPTO_STRINGS :: 0x00000002;
OPENSSL_init_crypto :: (opts: u64, settings: *void) -> s32 #foreign libssl;
OPENSSL_init_ssl :: (opts: u64, settings: *void) -> s32 #foreign libssl;
SSL_METHOD :: void;
SSL_CTX :: void;
SSL :: void;
TLS_client_method :: () -> *SSL_METHOD #foreign libssl;
SSL_CTX_new :: (meth: *SSL_METHOD) -> *SSL_CTX #foreign libssl;
SSL_new :: (ctx: *SSL_CTX) -> *SSL #foreign libssl;
SSL_set_fd :: (ssl: *SSL, fd: s32) -> s32 #foreign libssl;
SSL_connect :: (ssl: *SSL) -> s32 #foreign libssl;
SSL_write :: (ssl: *SSL, buf: *void, num: s32) -> s32 #foreign libssl;
SSL_read :: (ssl: *SSL, buf: *void, num: s32) -> s32 #foreign libssl;
OpenSSL_add_all_algorithms :: inline () -> s32 {
return OPENSSL_init_crypto(OPENSSL_INIT_ADD_ALL_CIPHERS
| OPENSSL_INIT_ADD_ALL_DIGESTS
| OPENSSL_INIT_LOAD_CONFIG, null);
}
SSL_load_error_strings :: inline () -> s32 {
return OPENSSL_init_ssl(OPENSSL_INIT_LOAD_SSL_STRINGS
| OPENSSL_INIT_LOAD_CRYPTO_STRINGS, null);
}
HOST :: "tsoding.github.io";
PORT :: "443";
main :: () {
hints: addrinfo;
hints.ai_family = AF_INET;
hints.ai_socktype = .STREAM;
hints.ai_protocol = .TCP;
addrs: *addrinfo;
if getaddrinfo(HOST, PORT, *hints, *addrs) < 0 {
print("ERROR: Could not get adress of `%:%`", HOST, PORT);
exit(1);
}
addr := addrs;
sd: s32 = 0;
while addr != null {
sd = socket(addr.ai_family, addr.ai_socktype, addr.ai_protocol);
if sd == -1 break;
if connect(sd, addr.ai_addr, addr.ai_addrlen) == 0 break;
close(sd);
sd = -1;
addr = addr.ai_next;
}
freeaddrinfo(addrs);
if sd == -1 {
print("Could not connect to %:%", HOST, PORT);
exit(1);
}
ret := OpenSSL_add_all_algorithms();
assert(ret == 1);
ret = SSL_load_error_strings();
assert(ret == 1);
ctx := SSL_CTX_new(TLS_client_method());
if ctx == null {
print("ERROR: could not initialize SSL context\n");
exit(1);
}
ssl := SSL_new(ctx);
if ssl == null {
print("ERROR: could not create SSL structure\n");
exit(1);
}
ret = SSL_set_fd(ssl, sd);
assert(ret == 1);
if SSL_connect(ssl) < 0 {
print("ERROR: could not connect to %:% via SSL\n", HOST, PORT);
exit(1);
}
request :: "GET / HTTP/1.0\r\nHost: tsoding.github.io\r\n\r\n";
SSL_write(ssl, request.data, request.count);
buffer := NewArray(1024, u8);
n := SSL_read(ssl, buffer.data, xx buffer.count);
while n > 0 {
print("%", to_string(buffer.data, n));
n = SSL_read(ssl, buffer.data, xx buffer.count);
}
print("OK!\n");
}
@zhiayang
Copy link

zhiayang commented Mar 4, 2021

tsodeeeeeen you should clean up the SSL:

SSL_set_shutdown(ssl, SSL_RECEIVED_SHUTDOWN | SSL_SENT_SHUTDOWN);
SSL_shutdown(ssl);
SSL_free(ssl);
SSL_CTX_free(ctx);

Copy link

ghost commented Mar 4, 2021

You should use:
class and not struct
std::int32_t and not int
std::string and not const char*
std::array instead of C-style arrays

also I believe you should abstract it a bit inside of 1/2 classes like SSLConnecter and SSLReader so nobody gets lost

@rexim
Copy link
Author

rexim commented Mar 4, 2021

@zhiayang thank you! Adapted your patch! 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment