安裝openssl
MAC
MAC上如果要使用library有點麻煩,需要先找到對應的路徑
1 2 3 4 5 6 7 8
| $ find /usr/local/Cellar/ -name "libssl.*" /usr/local/Cellar//openssl/1.0.2o_1/lib/pkgconfig/libssl.pc /usr/local/Cellar//openssl/1.0.2o_1/lib/libssl.dylib ... $ find /usr/local/Cellar/ -name "ssl.h" /usr/local/Cellar//node/8.4.0/include/node/openssl/ssl.h /usr/local/Cellar//openssl/1.0.2o_1/include/openssl/ssl.h ...
|
看起來路徑是在/usr/local/Cellar/openssl/1.0.2o_1/
我們先記起來,後面編譯時會用到。
創造憑證
openssl本身就有提供很多好用的工具,我們最常用到的大概就是用來產生憑證吧!
這邊介紹產生兩種常見憑證(RSA,ECC)的方法。
產生RSA憑證
1 2 3 4 5 6
| openssl genrsa -out server.key 2048
openssl req -new -sha384 -key server.key -out server.csr
openssl x509 -req -sha1 -days 3650 -signkey server.key -in server.csr -out server.crt
|
產生ECC憑證
1 2 3 4 5 6
| openssl ecparam -genkey -name secp384r1 -out ecc.key
openssl req -new -sha384 -key ecc.key -out ecc.csr
openssl x509 -req -sha1 -days 3650 -signkey ecc.key -in ecc.csr -out ecc.crt
|
使用openssl內建的連線工具
有時候我們只是想要測試ssl連線而已,還要自己寫程式有點麻煩,還好我們可以使用openssl提供的連線工具
client和server都有提供,非常方便的!
client
-msg
:看細節(hex格式)
-cipher
:決定要用哪種cipher連線
-showcerts
:把cert的chain也列出來
-curves
:指定要用的橢圓算法,client hello的extension中的elliptic_curves
-sigalgs
:指定交換key要用的簽名方式,client hello的extension中的signature_algorithms
-no_tls1 -no_ssl3
:加上後就可以只用tls1.2連線了
1 2 3 4 5 6 7 8
| openssl s_client -connect [IP]:[port]
openssl s_client -msg -connect [IP]:[port]
openssl s_client -connect sslanalyzer.comodoca.com:443 -cipher ECDHE-RSA-AES128-GCM-SHA256 -curves secp384r1 -sigals RSA+SHA512
openssl s_client -no_tls1 -no_ssl3 -connect [IP]:[port]
|
server
1 2
| openssl s_server -accept 5678 -key server.key -cert server.pem
|
其他
函式庫使用
我們來介紹openssl的函式庫最基本的使用方式。
基本範例 - client & server
這邊寫了兩個client和server的基本範例當作參考,大家可以基於這兩者來拓展自己的程式。
程式碼
ssl_client.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94
| #include <stdio.h> #include <unistd.h> #include <sys/socket.h> #include <arpa/inet.h> #include <openssl/ssl.h>
#define RECV_SIZE 256
SSL_CTX *create_sslcontext() { const SSL_METHOD *method; SSL_CTX *ctx; method = TLSv1_2_client_method(); ctx = SSL_CTX_new(method); if (!ctx) return NULL; return ctx; }
int create_socket(char *ip, int port) { int fd; struct sockaddr_in addr;
if ((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0 ) return -1; memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_port = htons(port); addr.sin_addr.s_addr = inet_addr(ip); if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) return -1;
return fd; }
int main(int argc, char *argv[]) { int fd; SSL_CTX *ctx; int port; int len; SSL *ssl; char buf[RECV_SIZE];
if (argc != 3) return -1; port = atoi(argv[2]); printf("Connect to %s:%d\n", argv[1], port);
OpenSSL_add_ssl_algorithms(); if ((ctx = create_sslcontext()) == NULL) return -1; if ((fd = create_socket(argv[1], port)) < 0) return -1; ssl = SSL_new(ctx); SSL_set_fd(ssl, fd); if (SSL_connect(ssl) <= 0) return -1; do { printf("Write data to server (q for quit): "); memset(buf, 0, sizeof(buf)); gets(buf); if (strcmp("q", buf) == 0) break; if (SSL_write(ssl, buf, strlen(buf)) < 0) break; memset(buf, 0, sizeof(buf)); len = SSL_read(ssl, buf, RECV_SIZE); if (len < 0) break; else printf("Recv %d bytes: %s\n", len, buf); } while(1); SSL_shutdown(ssl); SSL_free(ssl); close(fd); SSL_CTX_free(ctx); EVP_cleanup(); return 0; }
|
ssl_server.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109
| #include <stdio.h> #include <unistd.h> #include <sys/socket.h> #include <arpa/inet.h> #include <openssl/ssl.h>
#define SSL_CERT "server.crt" #define SSL_KEY "server.key"
#define BUF_LEN 256
SSL_CTX *create_sslcontext() { const SSL_METHOD *method; SSL_CTX *ctx; method = TLSv1_2_server_method(); ctx = SSL_CTX_new(method); if (!ctx) return NULL; return ctx; }
int configure_sslcertkey_file(SSL_CTX *ctx) { SSL_CTX_set_ecdh_auto(ctx, 1); if (SSL_CTX_use_certificate_file(ctx, SSL_CERT, SSL_FILETYPE_PEM) <= 0) return -1; if (SSL_CTX_use_PrivateKey_file(ctx, SSL_KEY, SSL_FILETYPE_PEM) <= 0 ) return -1; return 0; }
int create_socket(int port) { int fd; struct sockaddr_in addr;
addr.sin_family = AF_INET; addr.sin_port = htons(port); addr.sin_addr.s_addr = htonl(INADDR_ANY);
if ((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0 ) return -1; if (bind(fd, (struct sockaddr*)&addr, sizeof(addr)) < 0) return -1; if (listen(fd, 1) < 0) return -1;
return fd; }
int main(int argc, char *argv[]) { int server_fd, client_fd; SSL_CTX *ctx; SSL *ssl; struct sockaddr_in addr; uint len = sizeof(addr); int port; char buf[BUF_LEN];
if (argc != 2) return -1; port = atoi(argv[1]); printf("Listen port: %d\n", port);
OpenSSL_add_ssl_algorithms(); if ((ctx = create_sslcontext()) == NULL) return -1; if (configure_sslcertkey_file(ctx) < 0) return -1; if ((server_fd = create_socket(port)) < 0) return -1; if ((client_fd = accept(server_fd, (struct sockaddr*)&addr, &len)) < 0) return -1; ssl = SSL_new(ctx); SSL_set_fd(ssl, client_fd); if (SSL_accept(ssl) <= 0) return -1; while(1) { memset(buf, 0, sizeof(buf)); len = SSL_read(ssl, buf, BUF_LEN); if (len <= 0) break; else SSL_write(ssl, buf, strlen(buf)); } SSL_free(ssl); close(client_fd); close(server_fd); SSL_CTX_free(ctx); EVP_cleanup(); return 0; }
|
編譯與執行
接下來寫個簡單的Makefile,這時候就要用到前面所找到的路徑了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| SSL_PATH=/usr/local/Cellar/openssl/1.0.2o_1/ CFLAGS=-I$(SSL_PATH)include -L$(SSL_PATH)lib/ -lcrypto -lssl CC=gcc BIN=ssl_server ssl_client
all: $(BIN)
ssl_server: ssl_server.c $(CC) $^ -o $@.out $(CFLAGS)
ssl_client: ssl_client.c $(CC) $^ -o $@.out $(CFLAGS)
clean: -rm *.o -rm *.out
|
這邊要特別記住-lcrypto -lssl
要放最後面,不然有些平台會有error
然後就可以執行看看了
1 2 3
| $ make $ ./ssl_server 2222 Listen port: 2222
|
這時候另一邊再來執行client
1 2 3 4
| ./ssl_client.out 127.0.0.1 2222 Connect to 127.0.0.1:2222 Write data to server (q for quit): abcd Recv 4 bytes: abcd
|
可以順利收送資料了!
參考