Komunikasi TCP plain-text dapat diintip dan dimodifikasi siapa pun di sepanjang jalur jaringan: ISP, hotspot Wi-Fi publik, atau router yang disusupi. TLS (Transport Layer Security) menyelesaikan masalah ini dengan tiga jaminan: confidentiality (data dienkripsi), integrity (data tidak dimodifikasi tanpa terdeteksi), dan authentication (identitas server diverifikasi via sertifikat). Bab ini memperkenalkan API OpenSSL yang menjadi standar de facto untuk TLS di C, dengan urutan pemanggilan yang konsisten: inisialisasi context, TCP connect biasa, bungkus dengan SSL, handshake, lalu SSL_read/SSL_write seperti halnya recv/send.
Tiga Jaminan TLS #
| Jaminan | Cara Kerja | Tanpa TLS |
|---|---|---|
| Confidentiality | Enkripsi simetris (AES, ChaCha20) | Paket terbaca lewat sniffing |
| Integrity | HMAC per record | Paket bisa diamankan dimodifikasi |
| Authentication | Sertifikat X.509 ditandatangani CA | Server palsu bisa menyamar |
TLS handshake terjadi setelah TCP three-way handshake. Klien mengirim ClientHello berisi daftar cipher suite yang didukung. Server membalas dengan ServerHello yang berisi cipher yang dipilih, sertifikat X.509, dan (opsional) permintaan sertifikat klien. Klien memverifikasi sertifikat terhadap daftar CA yang dipercaya, lalu mengirim key material yang dienkripsi dengan public key server. Setelah handshake selesai, kedua sisi punya session key simetris untuk enkripsi semua data berikutnya.
Pola API OpenSSL #
OpenSSL menyediakan lapisan abstraksi di atas socket TCP biasa. Alurnya selalu sama:
OPENSSL_init_ssl()— inisialisasi library (modern, menggantikanSSL_library_init()).SSL_CTX_new(TLS_client_method())— buat SSL Context dengan metode TLS generik.SSL_CTX_set_default_verify_paths()— muat CA sistem untuk verifikasi sertifikat.SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL)— aktifkan verifikasi peer.socket()+connect()— sambungkan TCP seperti biasa.SSL_new(ctx)— buat SSL object dari context.SSL_set_fd(ssl, fd)— kaitkan SSL dengan file descriptor TCP.SSL_set_tlsext_host_name(ssl, hostname)— SNI (Server Name Indication).SSL_connect(ssl)— jalankan handshake TLS.SSL_write()/SSL_read()— kirim/terima data terenkripsi.SSL_shutdown()— graceful shutdown TLS.SSL_free()/SSL_CTX_free()— bersihkan resource.
TLS Client Lengkap dengan OpenSSL #
Kode berikut adalah klien HTTPS minimal yang melakukan TLS handshake ke www.example.com:443 dan mengirim HTTP GET request lewat SSL_write. Kode ini teruji dan menghasilkan respons nyata.
/* File: tls_client.c - Pola TLS client dengan OpenSSL */
#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netdb.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
int main(void) {
const char *hostname = "www.example.com";
const char *port = "443";
/* 1) Inisialisasi OpenSSL (modern: OPENSSL_init_ssl) */
OPENSSL_init_ssl(OPENSSL_INIT_LOAD_SSL_STRINGS, NULL);
SSL_CTX *ctx = SSL_CTX_new(TLS_client_method());
if (ctx == NULL) {
fprintf(stderr, "SSL_CTX_new gagal\n");
ERR_print_errors_fp(stderr);
return 1;
}
printf("[TLS] SSL_CTX dibuat dengan TLS_client_method()\n");
/* 2) Aktifkan verifikasi sertifikat (default ON di OpenSSL 1.1+) */
SSL_CTX_set_default_verify_paths(ctx);
SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL);
printf("[TLS] Verifikasi sertifikat peer: ON\n");
/* 3) Buat TCP socket biasa dan connect */
struct addrinfo hints = {0}, *res;
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
int rc = getaddrinfo(hostname, port, &hints, &res);
if (rc != 0) {
fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rc));
SSL_CTX_free(ctx);
return 1;
}
int fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
if (fd < 0) { perror("socket"); freeaddrinfo(res); SSL_CTX_free(ctx); return 1; }
if (connect(fd, res->ai_addr, res->ai_addrlen) != 0) {
perror("connect");
close(fd);
freeaddrinfo(res);
SSL_CTX_free(ctx);
return 1;
}
freeaddrinfo(res);
printf("[TLS] TCP connect sukses ke %s:%s\n", hostname, port);
/* 4) Bungkus socket TCP dengan SSL */
SSL *ssl = SSL_new(ctx);
SSL_set_fd(ssl, fd);
SSL_set_tlsext_host_name(ssl, hostname); /* SNI */
/* 5) SSL_connect: handshake TLS */
if (SSL_connect(ssl) != 1) {
fprintf(stderr, "[TLS] SSL_connect gagal\n");
ERR_print_errors_fp(stderr);
SSL_free(ssl);
close(fd);
SSL_CTX_free(ctx);
return 1;
}
printf("[TLS] Handshake sukses, cipher: %s\n", SSL_get_cipher(ssl));
/* 6) SSL_write: kirim HTTP request */
const char *req = "GET / HTTP/1.0\r\nHost: www.example.com\r\n\r\n";
SSL_write(ssl, req, (int) strlen(req));
/* 7) SSL_read: baca respons */
char buf[4096];
int n = SSL_read(ssl, buf, sizeof(buf) - 1);
if (n > 0) {
buf[n] = '\0';
printf("[TLS] Menerima %d byte pertama:\n%.200s...\n", n, buf);
}
/* 8) Bersih-bersih */
SSL_shutdown(ssl);
SSL_free(ssl);
close(fd);
SSL_CTX_free(ctx);
return 0;
}Compile dan jalankan:
gcc -std=c11 -Wall -Wextra -o tls_client tls_client.c -lssl -lcrypto
./tls_clientOutput yang Dihasilkan:
[TLS] SSL_CTX dibuat dengan TLS_client_method()
[TLS] Verifikasi sertifikat peer: ON
[TLS] TCP connect sukses ke www.example.com:443
[TLS] Handshake sukses, cipher: TLS_AES_256_GCM_SHA384
[TLS] Menerima 891 byte pertama:
HTTP/1.1 200 OK
Date: Tue, 13 Oct 2026 08:59:45 GMT
Content-Type: text/html; charset=utf-8
Connection: close
Server: cloudflare...Mari kita telaah baris-baris kode di atas:
- Baris 18 (
OPENSSL_init_ssl(OPENSSL_INIT_LOAD_SSL_STRINGS, ...)): Inisialisasi OpenSSL modern, menggantikanSSL_library_init()yang obsolete. FlagOPENSSL_INIT_LOAD_SSL_STRINGSmemuat string error dan tabel algoritma. - Baris 19 (
SSL_CTX_new(TLS_client_method())): Membuat SSL Context untuk klien.TLS_client_method()memilih versi TLS tertinggi yang didukung (TLS 1.2 atau 1.3) secara otomatis—tidak ada hardcoding versi. - Baris 28 (
SSL_CTX_set_default_verify_paths(ctx)): Memuat file CA bundle default sistem (biasanya/etc/ssl/certs/ca-certificates.crtdi Debian/Ubuntu). Tanpa ini, verifikasi sertifikat tidak punya anchor trust. - Baris 29 (
SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL)): Mengaktifkan verifikasi sertifikat server.SSL_VERIFY_PEERberarti OpenSSL akan memanggil callback verifikasi default yang menolak sertifikat yang tidak valid (kedaluwarsa, hostname tidak cocok, CA tidak dikenal). - Baris 49-50 (
SSL_new(ctx); SSL_set_fd(ssl, fd)): Membuat SSL object baru dari context. Satu context bisa dipakai untuk banyak koneksi—biasanya kita buat context sekali di awal program. - Baris 51 (
SSL_set_tlsext_host_name(ssl, hostname)): Mengatur SNI (Server Name Indication). Tanpa SNI, server dengan multiple virtual host (mis. cloudflare) tidak tahu sertifikat mana yang harus dipresentasikan—bisa menyebabkan handshake gagal atau sertifikat salah. - Baris 54 (
SSL_connect(ssl)): Menjalankan TLS handshake. Fungsi ini memblokir hingga handshake selesai (atau gagal). Return1= sukses,0atau negatif = gagal. Bila gagal,ERR_print_errors_fp(stderr)mencetak stack error OpenSSL. - Baris 64 (
SSL_write(ssl, req, strlen(req))): Mengirim data terenkripsi. API-nya identik dengansend()—return jumlah byte yang tertulis. Perbedaan: data dienkripsi otomatis sebelum dikirim ke socket. - Baris 68 (
SSL_read(ssl, buf, sizeof(buf) - 1)): Membaca data terdekripsi. OpenSSL menerima record TLS dari socket, mendekripsi, lalu mengembalikan ke aplikasi. Aplikasi tidak melihat overhead TLS—API terasa seperti socket biasa. - Baris 76 (
SSL_shutdown(ssl)): Mengirim close_notify TLS ke peer—sinyal aplikatif bahwa kita selesai. Berbeda dariclose(fd)yang langsung memutus TCP,SSL_shutdownmemberi kesempatan peer untuk menyelesaikan sesi TLS dengan bersih.
SSL Context: Satu untuk Banyak Koneksi #
SSL_CTX didesain untuk dipakai ulang. Pada server dengan banyak koneksi, kita buat satu context di awal program, lalu membuat SSL * baru untuk setiap koneksi dari context yang sama. Pendekatan ini efisien karena context menyimpan konfigurasi sertifikat dan CA yang mahal untuk dimuat berulang-ulang.
/* Pola server: satu context untuk banyak klien */
SSL_CTX *ctx = SSL_CTX_new(TLS_server_method());
SSL_CTX_use_certificate_file(ctx, "server.crt", SSL_FILETYPE_PEM);
SSL_CTX_use_PrivateKey_file(ctx, "server.key", SSL_FILETYPE_PEM);
while (1) {
int cli = accept(srv, NULL, NULL);
SSL *ssl = SSL_new(ctx); /* SSL_new murah; ctx mahal */
SSL_set_fd(ssl, cli);
SSL_accept(ssl); /* handshake server-side */
/* ... SSL_read/SSL_write ... */
SSL_free(ssl);
close(cli);
}Verifikasi Sertifikat #
Verifikasi sertifikat adalah inti dari TLS. Tanpa verifikasi, klien tidak tahu apakah ia berbicara dengan server asli atau penyerang yang menyamar (man-in-the-middle). OpenSSL secara default memverifikasi:
- Sertifikat ditandatangani oleh CA yang dikenal.
- Sertifikat belum kedaluwarsa (
NotBefore< now <NotAfter). - CN (Common Name) atau SAN (Subject Alternative Name) cocok dengan hostname yang diminta klien (di OpenSSL 1.1+, otomatis dengan SNI).
Bila salah satu cek gagal, SSL_connect mengembalikan error dan ERR_print_errors_fp mencetak alasan kegagalan. Untuk debugging sertifikat dari command line, gunakan openssl s_client:
openssl s_client -connect www.example.com:443 -servername www.example.com </dev/nullOutput yang Dihasilkan (dipotong):
Connecting to 172.66.147.243
depth=3 C=US, O=SSL Corporation, CN=SSL.com TLS ECC Root CA 2022
verify return:1
depth=2 C=US, O=SSL Corporation, CN=SSL.com TLS Transit ECC CA R2
verify return:1
depth=1 C=US, O=SSL Corporation, CN=Cloudflare TLS Issuing ECC CA 3
verify return:1
depth=0 CN=example.com
verify return:1
CONNECTED(00000003)
---
Certificate chain
0 s:CN=example.com
i:C=US, O=SSL Corporation, CN=Cloudflare TLS Issuing ECC CA 3Output di atas menampilkan certificate chain dari leaf (CN=example.com) ke root CA (SSL.com TLS ECC Root CA 2022). Setiap depth=N menandakan level dalam rantai. verify return:1 di setiap level berarti verifikasi sukses.