0. 前提条件
关于SSL和OpenSSL的工作原理比较复杂,我只是了解一二,
所以这里并不会讲解SSL和OpenSSL到底是如何工作的,下面
仅给出一些相关参考资料:
- SSL的工作原理:RFC6101
- OpenSSL官网:http://www.openssl.org
- 使用OpenSSL API进行安全编程:http://www.ibm.com/developerworks/cn/linux/l-openssl.html
在继续往下看之前,请确保已经了解SSL/OpenSSL/Lighttpd如何工作。
1. 使用openssl命令生成证书和密钥
$openssl req -new -x509 -keyout lighttpd.pem -out lighttpd.pem -days 365 -nodes
$chmod 400 lighttpd.pem
$sudo chown root lighttpd.pem
$sudo mv lighttpd.pem /etc/lighttpd/lighttpd.pem
为了安全考虑,所以要确保对lighttpd.pem设置合适的权限。
另外由于lighttpd运行的时候具有root权限,所以不用担心lighttpd无法读取lighttpd.pem
2. 配置lighttpd
先确保lighttpd支持ssl:
输入lighttpd -v , 如果输出包含(ssl)就OK。
再来配置lighttpd.conf,加上下面的配置:
$SERVER["socket"] == ":443" {
ssl.engine = "enable"
ssl.pemfile = "/etc/lighttpd/lighttpd.pem"
}
然后我们运行lighttpd服务器:
$sudo lighttpd -f /etc/lighttpd/lighttpd.conf
最后我们用浏览器打开https://localhost来测试一下ssl能否工作,由于我们的证书是自己
生成的,浏览器不信任它,所以浏览器会警告我们,我们可以手动添加信任,这时候ssl
已经可以正常工作了。
3. 使用OpenSSL API与lighttpd建立连接:
关于API的说明请参考前面给出的链接,我这里就不说明了(其实我只知道怎么用)
下面给出两个版本的程序,一个使用非安全连接,另一个使用安全连接。
非安全连接:
#include <stdio.h>
#include <stdlib.h>
/* OpenSSL headers */
#include <openssl/bio.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
int main(int argc, char *argv[])
{
BIO *bio;
char host_port[100];
char buf[1024], req[1024];
int n;
if (argc != 3) {
fprintf(stderr, "usage: %s <host> <port>\n", argv[0]);
exit(EXIT_FAILURE);
}
snprintf(host_port, sizeof(host_port) - 1,
"%s:%s", argv[1], argv[2]);
/* Initializing OpenSSL */
SSL_load_error_strings();
ERR_load_BIO_strings();
OpenSSL_add_all_algorithms();
bio = BIO_new_connect(host_port);
if (bio == NULL) {
fprintf(stderr, "error: BIO_new_connect");
ERR_print_errors_fp(stderr);
exit(EXIT_FAILURE);
}
if (BIO_do_connect(bio) <= 0) {
fprintf(stderr, "error: BIO_do_connect");
ERR_print_errors_fp(stderr);
exit(EXIT_FAILURE);
}
printf("connected to %s\n\n", host_port);
snprintf(req, sizeof(req),
"GET / HTTP/1.1\r\n"
"Host: %s\r\n"
"\r\n", argv[1]);
BIO_write(bio, req, strlen(req));
while (1) {
n = BIO_read(bio, buf, sizeof(buf));
if (n > 0)
write(fileno(stdout), buf, n);
else if (n == 0)
break;
else {
if (!BIO_should_retry(bio))
break;
else
continue;
}
}
#if 0
/* To reuse the connection, use this line */
BIO_reset(bio);
#endif
/* To free it from memory, use this line */
BIO_free_all(bio);
return 0;
}
安全连接:
安全连接要复杂一点,其中SSL_CTX_load_verify_locations()需要加载证书库,
我们可以用刚才生成的lighttpd.pem(注意路径),由于lighttpd也是使用这个证书,所以我们
可以顺利连接。我们还可以另外生成一个新的证书,测试一下能否通过验证。
#include <stdio.h>
#include <stdlib.h>
/* OpenSSL headers */
#include <openssl/bio.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
int main(int argc, char *argv[])
{
BIO *bio;
SSL_CTX *ctx;
SSL *ssl;
char host_port[100];
char buf[1024], req[1024];
int n;
if (argc != 3) {
fprintf(stderr, "usage: %s <host> <port>\n", argv[0]);
exit(EXIT_FAILURE);
}
snprintf(host_port, sizeof(host_port) - 1,
"%s:%s", argv[1], argv[2]);
/* Initializing OpenSSL */
SSL_load_error_strings();
ERR_load_BIO_strings();
OpenSSL_add_all_algorithms();
/**
* I need to call this before SSL_CTX_new(),
* or it ctx will be null.
*
* May be a bug ?
**/
SSL_library_init();
ctx = SSL_CTX_new(SSLv23_client_method());
if (ctx == NULL) {
fprintf(stderr, "error: SSL_CTX_new\n");
ERR_print_errors_fp(stderr);
exit(EXIT_FAILURE);
}
if (!SSL_CTX_load_verify_locations(ctx, "lighttpd.pem", NULL)) {
fprintf(stderr, "error: SSL_CTX_load_verify_locations\n");
ERR_print_errors_fp(stderr);
exit(EXIT_FAILURE);
}
bio = BIO_new_ssl_connect(ctx);
if (bio == NULL) {
fprintf(stderr, "error: BIO_new_ssl_connect");
ERR_print_errors_fp(stderr);
exit(EXIT_FAILURE);
}
BIO_get_ssl(bio, &ssl);
SSL_set_mode(ssl, SSL_MODE_AUTO_RETRY);
/* Attempt to connect */
BIO_set_conn_hostname(bio, host_port);
/* Verify the connection opened and perform the handshake */
if (BIO_do_connect(bio) <= 0) {
fprintf(stderr, "error: BIO_do_connect\n");
ERR_print_errors_fp(stderr);
exit(EXIT_FAILURE);
}
if (SSL_get_verify_result(ssl) != X509_V_OK) {
fprintf(stderr, "error: SSL verify failed.\n");
ERR_print_errors_fp(stderr);
exit(EXIT_FAILURE);
}
printf("connected to %s\n\n", host_port);
snprintf(req, sizeof(req),
"GET / HTTP/1.1\r\n"
"Host: %s\r\n"
"\r\n", argv[1]);
BIO_write(bio, req, strlen(req));
while (1) {
n = BIO_read(bio, buf, sizeof(buf));
if (n > 0)
write(fileno(stdout), buf, n);
else if (n == 0)
break;
else {
if (!BIO_should_retry(bio))
break;
else
continue;
}
}
#if 0
/* To reuse the connection, use this line */
BIO_reset(bio);
#endif
/* To free it from memory, use this line */
BIO_free_all(bio);
SSL_CTX_free(ctx);
return 0;
}
Makefile:
LDFLAGS = -lssl -lcrypto
secure: openssl_test.c
gcc -o $@ $^ $(LDFLAGS)
nosecure: openssl_test_nosecure.c
gcc -o $@ $^ $(LDFLAGS)
最后测试一下:
$./nosecure localhost 80
$./secure localhost 443
注意:两个程序都是请求localhost的主页,所以要确保相关的设置都正确。