OpenSSL库的使用之C语言实现HTTPS的POST提交
代码含有详细注释, 不详解
0x01 测试页面的准备
首先编写一个测试页面, 我这里使用的PHP
如果有其它环境测试的话,可以直接从步骤2开始看
测试代码片段
<?php
if(isset($_SERVER['REQUEST_METHOD']) && strtoupper($_SERVER['REQUEST_METHOD'])=='POST'){
echo "POST Success re=1 \n";
$data = file_get_contents("php://input");
echo "data is : ==>> ";
print_r($data);
}else{
echo "Not is POST re=0 \n";
}
?>
上面代码片段的含义就是,当访问该网页是以POST方式提交的话,那么就将提交上来的结果返回给客户端。如果不是,则打印 Not is POST re=0
如果自己没有测试页面,也没有web后端开发能力,可以使用我的测试页面,但不保证永久提供,在可测试期间,如果遇到问题,欢迎留言,测试地址如下
https://www.wangsansan.com/mydir/test/HttpsPostTest.php
0x02 C语言程序
本程序依赖 openssl 如果系统没有相关库,请自行安装,此文不做阐述
1、https_post.h
/************************************************************************
> File Name: https_post.h
> Author: WangMinghang
> Mail: hackxiaowang@qq.com
> Blog: https://www.wangsansan.com
> Created Time: 2018年08月29日 星期三 16时42分21秒
***********************************************************************/
#ifndef __HTTPS_POST__
#define __HTTPS_POST__
/*
* @Name - HTTPS的POST提交
* @Parame *host - 主机地址, 即域名
* @Parame port - 端口号, 一般为443
* @Parame *url - url相对路径
* @Parame *data - 要提交的数据内容, 不包括Headers
* @Parame dsize - 需要发送的数据包大小, 由外部调用传入, 不包含头
* @Parame *buff - 数据缓存指针, 非空数组或提前malloc
* @Parame bsize - 需要读取的返回结果长度, 可以尽量给大, 直到读取结束
*
* @return - 返回结果长度, 如果读取失败, 则返回值 <0
* -1 : 为POST数据申请内存失败
* -2 : 建立TCP连接失败
* -3 : SSL初始化或绑定sockfd到SSL失败
* -4 : POST提交失败
* -5 : 等待响应失败
*/
int https_post(char *host, int port, char *url, const char *data, int dsize, char *buff, int bsize);
#endif
2、https_post.c
/************************************************************************
> File Name: https_post.c
> Author: WangMinghang
> Mail: hackxiaowang@qq.com
> Blog: https://www.wangsansan.com
> Created Time: 2018年08月29日 星期三 16时42分21秒
***********************************************************************/
#include <fcntl.h>
#include <netdb.h>
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <openssl/err.h>
#include <openssl/ssl.h>
#include <openssl/rand.h>
#include <openssl/crypto.h>
//#include "https_post.h"
#define HTTP_HEADERS_MAXLEN 512 // Headers 的最大长度
/*
* Headers 按需更改
*/
const char *HttpsPostHeaders = "User-Agent: Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0)\r\n"
"Cache-Control: no-cache\r\n"
"Accept: */*\r\n"
"Content-type: application/json\r\n";
/*
* @Name - 创建TCP连接, 并建立到连接
* @Parame *server - 字符串, 要连接的服务器地址, 可以为域名, 也可以为IP地址
* @Parame port - 端口
*
* @return - 返回对应sock操作句柄, 用于控制后续通信
*/
int client_connect_tcp(char *server,int port)
{
int sockfd;
struct hostent *host;
struct sockaddr_in cliaddr;
sockfd=socket(AF_INET,SOCK_STREAM,0);
if(sockfd < 0){
perror("create socket error");
return -1;
}
if(!(host=gethostbyname(server))){
printf("gethostbyname(%s) error!\n", server);
return -2;
}
bzero(&cliaddr,sizeof(struct sockaddr));
cliaddr.sin_family=AF_INET;
cliaddr.sin_port=htons(port);
cliaddr.sin_addr=*((struct in_addr *)host->h_addr);
if(connect(sockfd,(struct sockaddr *)&cliaddr,sizeof(struct sockaddr))<0){
perror("[-] error");
return -3;
}
return(sockfd);
}
/*
* @Name - 封装post数据包括headers
* @parame *host - 主机地址, 域名
* @parame port - 端口号
* @parame page - url相对路径
* @parame len - 数据内容的长度
* @parame content - 数据内容
* @parame data - 得到封装的数据结果
*
* @return int - 返回封装得到的数据长度
*/
int post_pack(const char *host, int port, const char *page, int len, const char *content, char *data)
{
int re_len = strlen(page) + strlen(host) + strlen(HttpsPostHeaders) + len + HTTP_HEADERS_MAXLEN;
char *post = NULL;
post = malloc(re_len);
if(post == NULL){
return -1;
}
sprintf(post, "POST %s HTTP/1.0\r\n", page);
sprintf(post, "%sHost: %s:%d\r\n",post, host, port);
sprintf(post, "%s%s", post, HttpsPostHeaders);
sprintf(post, "%sContent-Length: %d\r\n\r\n", post, len);
sprintf(post, "%s%s", post, content); // 此处需要修改, 当业务需要上传非字符串数据的时候, 会造成数据传输丢失或失败
re_len = strlen(post);
memset(data, 0, re_len+1);
memcpy(data, post, re_len);
free(post);
return re_len;
}
/*
* @Name - 初始化SSL, 并且绑定sockfd到SSL
* 此作用主要目的是通过SSL来操作sock
*
* @return - 返回已完成初始化并绑定对应sockfd的SSL指针
*/
SSL *ssl_init(int sockfd)
{
int re = 0;
SSL *ssl;
SSL_CTX *ctx;
SSL_library_init();
SSL_load_error_strings();
ctx = SSL_CTX_new(SSLv23_client_method());
if (ctx == NULL){
return NULL;
}
ssl = SSL_new(ctx);
if (ssl == NULL){
return NULL;
}
/* 把socket和SSL关联 */
re = SSL_set_fd(ssl, sockfd);
if (re == 0){
SSL_free(ssl);
return NULL;
}
/*
* 经查阅, WIN32的系统下, 不能很有效的产生随机数, 此处增加随机数种子
*/
RAND_poll();
while (RAND_status() == 0)
{
unsigned short rand_ret = rand() % 65536;
RAND_seed(&rand_ret, sizeof(rand_ret));
}
/*
* ctx使用完成, 进行释放
*/
SSL_CTX_free(ctx);
return ssl;
}
/*
* @Name - 通过SSL建立连接并发送数据
* @Parame *ssl - SSL指针, 已经完成初始化并绑定了对应sock句柄的SSL指针
* @Parame *data - 准备发送数据的指针地址
* @Parame size - 准备发送的数据长度
*
* @return - 返回发送完成的数据长度, 如果发送失败, 返回 -1
*/
int ssl_send(SSL *ssl, const char *data, int size)
{
int re = 0;
int count = 0;
re = SSL_connect(ssl);
if(re != 1){
return -1;
}
while(count < size)
{
re = SSL_write(ssl, data+count, size-count);
if(re == -1){
return -2;
}
count += re;
}
return count;
}
/*
* @Name - SSL接收数据, 需要已经建立连接
* @Parame *ssl - SSL指针, 已经完成初始化并绑定了对应sock句柄的SSL指针
* @Parame *buff - 接收数据的缓冲区, 非空指针
* @Parame size - 准备接收的数据长度
*
* @return - 返回接收到的数据长度, 如果接收失败, 返回值 <0
*/
int ssl_recv(SSL *ssl, char *buff, int size)
{
int i = 0; // 读取数据取换行数量, 即判断headers是否结束
int re;
int len = 0;
char headers[HTTP_HEADERS_MAXLEN];
if(ssl == NULL){
return -1;
}
// Headers以换行结束, 此处判断头是否传输完成
while((len = SSL_read(ssl, headers, 1)) == 1)
{
if(i < 4){
if(headers[0] == '\r' || headers[0] == '\n'){
i++;
if(i>=4){
break;
}
}else{
i = 0;
}
}
//printf("%c", headers[0]); // 打印Headers
}
len = SSL_read(ssl, buff, size);
return len;
}
int https_post(char *host, int port, char *url, const char *data, int dsize, char *buff, int bsize)
{
SSL *ssl;
int re = 0;
int sockfd;
int data_len = 0;
int ssize = dsize + HTTP_HEADERS_MAXLEN; // 欲发送的数据包大小
char *sdata = malloc(ssize);
if(sdata == NULL){
return -1;
}
// 1、建立TCP连接
sockfd = client_connect_tcp(host, port);
if(sockfd < 0){
free(sdata);
return -2;
}
// 2、SSL初始化, 关联Socket到SSL
ssl = ssl_init(sockfd);
if(ssl == NULL){
free(sdata);
close(sockfd);
return -3;
}
// 3、组合POST数据
data_len = post_pack(host, port, url, dsize, data, sdata);
// 4、通过SSL发送数据
re = ssl_send(ssl, sdata, data_len);
if(re < 0){
free(sdata);
close(sockfd);
SSL_shutdown(ssl);
return -4;
}
// 5、取回数据
int r_len = 0;
r_len = ssl_recv(ssl, buff, bsize);
if(r_len < 0){
free(sdata);
close(sockfd);
SSL_shutdown(ssl);
return -5;
}
// 6、关闭会话, 释放内存
free(sdata);
close(sockfd);
SSL_shutdown(ssl);
ERR_free_strings();
return r_len;
}
其中在 ssl_init() 函数中,包含一片段置随机数种子的代码,对此片段函数释义以下是查阅得到的结果
在win32 的环境中client程序运行时出错(SSL_connect返回-1)的一个主要机制便是与UNIX平台下的随机数生成机制不同(握手的时候用的到). 具体描述可见mod_ssl的FAQ.解决办法就是调用此函数,其中buf应该为一随机的字符串,作为"seed".
此文件内包含详细注释,就不过多阐述
其中,含有1个已知bug,在代码98行,当程序需要上传非字符串时,则不能使用sprintf()进行拼接,使用是按需修改
下面编写一个测试程序
3、example.c
/************************************************************************
> File Name: https_post_test.c
> Author: WangMinghang
> Mail: hackxiaowang@qq.com
> Blog: https://www.wangsansan.com
> Created Time: 2018年08月29日 星期三 17时36分06秒
***********************************************************************/
#include <stdio.h>
#include <string.h>
#include "https_post.h"
int Port = 443;
char *Host = "www.wangsansan.com";
char *Page = "/test/HttpsPostTest.php";
char *Data = "{\"A\":\"111\", \"B\":\"222\"}"; // 对应字符串 - {"A":"111", "B":"222"}
int main()
{
int read_len = 0;
char buff[512] = {0};
read_len = https_post(Host, Port, Page, Data, strlen(Data), buff, 512);
if(read_len < 0){
printf("Err = %d \n", read_len);
return read_len;
}
printf("==================== Recv [%d] ==================== \n", read_len);
printf("%s\n", buff);
return 1;
}
0x03 编译测试
依赖于ssl和crypto库,编译的时候使用-l链接
以下是编译过程以及测试结果
~$ gcc https_post.c example.c -o https_post -lssl -lcrypto
~$ ./https_post
==================== Recv [56] ====================
POST Success re=1
data is : ==>> {"A":"111", "B":"222"}
~$
CSDN:http://blog.csdn.net/byb123
Blog:https://www.wangsansan.com/
公众号:iamwangsansan (山中书)
欢迎关注
不定时更新
拜求
麻烦加我QQ,或者发一封邮件给我,我上次没有收到你的验证.我找了很久,都没有收到你加我的验证
前辈上次加我,没有看到你的验证信息,麻烦再加一次
前辈我又来了,你能不能加我微信,我已经在你的微信公众号留言了
前辈,我又又又又一次来了,能不能回家我,上次没有看到你的验证信息
前辈,我来了,国庆时,有空看到我的留言吗?麻烦再一次回加我QQ或者微信
前辈,我来也了,这是第七七七次留言,麻烦再一次回加我QQ或者微信
前辈,我来也了,这是第八八八次留言,麻烦再一次回加我QQ或者微信
你好,前段时间没上博客,已经加你了,你通过一下。