在上一篇博文《介绍一个法国的时间戳服务器》中,介绍了获取时间戳响应的方法,分为两步:
1. 使用 OpenSSL 生成一个时间戳请求文件;
2. 使用 Linux 下的 curl 命令,发送时间戳请求、接收时间戳响应。
要想在 Windows 下执行第 2 步,有两种途径:
1) 下载为 Windows 平台实现的 curl 程序,可以到 http://curl.haxx.se/download.html 下载;
2)不用 curl 命令,自己编程实现向那个法国的时间戳服务器发送时间戳请求、接收时间戳响应。
为了更好地了解与时间戳服务器通信的过程细节,这里介绍一下第 2 种途径,即编程实现,先介绍 C 语言实现:
a) 为了读取时间戳请求文件,要实现获取文件大小的函数,实现过程如下:
文件1
get_small_file_size.h
#ifndef GET_SMALL_FILE_SIZE_H #define GET_SMALL_FILE_SIZE_H #ifdef __cplusplus extern "C" { #endif /************************************************** *函数名称:GetSmallFileSize *功能: 获取文件的大小,结果以字节为单位 *参数: file_name[in] 文件名 file_byte_size[out] 文件大小 *返回值: 0 成功 -1 失败 *备注: 该函数对实际文件大小有限制,文件大小不能超过 (2G-1) Bytes, 因为调用了 ftell() 函数,该函数的返回值是 long 类型,能表示的 整数最大不会超过 (2G-1) Bytes **************************************************/ int GetSmallFileSize(char *file_name, long *file_byte_size); #ifdef __cplusplus } #endif #endif /* end of GET_SMALL_FILE_SIZE_H */
文件2
get_small_file_size.c
#include "get_small_file_size.h" #include <stdio.h> int GetSmallFileSize(char *file_name, long *file_byte_size) { FILE * fp; if ( !(fp=fopen(file_name, "rb")) ) { printf("Open file %s failed!\n", file_name); return (-1); } fseek(fp, 0L, SEEK_END); *file_byte_size=ftell(fp); fclose(fp); return 0; }
b) 为了与时间戳服务器通信,要实现用 Windows socket 方式通信的函数,实现过程如下:
文件3
socket_communication.h
#ifndef SOCKET_COMMUNICATION_H #define SOCKET_COMMUNICATION_H #ifdef __cplusplus extern "C" { #endif /************************************************** *函数名称:socket_communication *功能: 以 socket 通信方式向指定 IP 地址和端口号的主机发送请求数据,接收响应数据 *参数: ip[in] 对方主机的 IP 地址 port[in] 对方主机接收数据时使用的端口号 request[in] 缓冲区首地址,该缓冲区用来存放要发送的请求数据 request_len[in] 要发送的请求数据的长度,以字节为单位 response[out] 缓冲区首地址,该缓冲区用来存放接收到的响应数据 response_buffer_len[in] 存放接收到的响应数据的缓冲区大小,以字节为单位 response_len[out] 实际接收到的响应数据的长度,以字节为单位 *返回值: 0 成功 -1 失败 **************************************************/ int socket_communication(unsigned char *ip, unsigned int port, unsigned char *request, unsigned int request_len, unsigned char *response, unsigned int response_buffer_len, unsigned int *response_len); #ifdef __cplusplus } #endif #endif /* end of SOCKET_COMMUNICATION_H */
文件4
socket_communication.c
#include "socket_communication.h" #include <stdio.h> #include <Winsock2.h> #pragma comment(lib,"WS2_32.lib") int socket_communication(unsigned char *ip, unsigned int port, unsigned char *request, unsigned int request_len, unsigned char *response, unsigned int response_buffer_len, unsigned int *response_len) { int result, received_data_len; char *p; WSADATA wsaData; SOCKET sockClient; SOCKADDR_IN addrSrv; result = WSAStartup(MAKEWORD(2, 2), &wsaData); if (result != NO_ERROR) { printf("WSAStartup function failed with error: %d at %s, line %d!\n", result, __FILE__, __LINE__); return (-1); } sockClient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if( sockClient == INVALID_SOCKET ) { printf("Create socket failed with error: %ld at %s, line %d!\n", WSAGetLastError(), __FILE__, __LINE__); WSACleanup(); return (-1); } addrSrv.sin_addr.s_addr = inet_addr((char *)(ip)); addrSrv.sin_family = AF_INET; addrSrv.sin_port = htons(port); result = connect(sockClient, (SOCKADDR*)(&addrSrv), sizeof(SOCKADDR)); if (result == SOCKET_ERROR) { printf("Invoke connect() function failed with error: %ld at %s, line %d!\n", WSAGetLastError(), __FILE__, __LINE__); closesocket(sockClient); WSACleanup( ); return (-1); } /* 发送时间戳请求 */ printf("Sending data..."); result = send(sockClient, (char *)request, (int)request_len, 0); if (result == -1) { printf("Send data failed at %s, line %d!\n", __FILE__, __LINE__); closesocket(sockClient); WSACleanup( ); return (-1); } printf("%d bytes have been sent.\n", result); /* 接收时间戳请求 */ printf("Receiving data..."); p = (char *)response; received_data_len = 0; while (1) { result = recv(sockClient, p, (response_buffer_len - received_data_len), 0); if (result == -1) { printf("Receive data failed at %s, line %d!\n", __FILE__, __LINE__); closesocket(sockClient); WSACleanup(); return (-1); } if (result == 0) break; p += result; received_data_len += result; } *response_len = received_data_len; printf("Receiving data complete.\n"); printf("%d bytes have been received.\n", received_data_len); result = closesocket(sockClient); if (result == SOCKET_ERROR) { printf("Invoke closesocket() function failed with error: %ld at %s, line %d!\n", WSAGetLastError(), __FILE__, __LINE__); WSACleanup(); return (-1); } WSACleanup(); return 0; }
c) 实现发送 HTTP POST 请求、接收时间戳服务器 HTTP 响应消息的函数,其功能包括:读取时间戳请求文件,生成一个 HTTP POST 请求,发给时间戳服务器,接收时间戳服务器返回的 HTTP 消息,并从 HTTP 消息中提取出 Content (这部分内容就是 ASN.1 编码格式的时间戳响应),输出或将其保存到文件中。实现过程如下:
文件5
TS_communication.h
/************************************************** * File name: TS_communication.h * Author: HAN Wei * Author's blog: http://blog.csdn.net/henter/ * Date: Aug 21st, 2014 **************************************************/ #ifndef TIME_STAMP_COMMUNICATION_H #define TIME_STAMP_COMMUNICATION_H #ifdef __cplusplus extern "C" { #endif /************************************************** *函数名称:TS_Communication *功能: 将时间戳请求文件发送到时间戳服务器,接收时间戳响应 *参数: TS_request_file_name[in] 时间戳请求文件名 TS_server_ip[in] 时间戳服务器的 IP 地址 TS_server_port[in] 时间戳服务器接收 HTTP POST 请求的端口号 save_file_flag[in] 表示是否要将时间戳服务器返回的时间戳响应保存到文件的标志变量 TS_response_file_name[in] 时间戳响应文件名 *返回值: 0 成功 -1 失败 *备注: 1. 与本函数通信的时间戳服务器的网址是 http://timestamping.edelweb.fr/ , 其 IP 地址是:92.103.215.77。 2. 对于输入参数 TS_request_file_name 指定的时间戳请求文件,其大小必须 小于 2GB,内容是时间戳请求的 ASN.1 编码,编码方式在 RFC 3161 中规定。 3. 输入参数 save_file_flag 用来表示是否要将时间戳服务器返回的时间戳响应 保存到文件,当其取值为 0 时,表示不需要保存到文件,此时输入参数 TS_response_file_name 将被忽略;当其取值为任意非零的无符号整数时, 表示要将时间戳响应保存到文件,文件名由 TS_response_file_name 指定。 **************************************************/ int TS_Communication(char *TS_request_file_name, unsigned char *TS_server_ip, unsigned int TS_server_port, unsigned int save_file_flag, char *TS_response_file_name); #ifdef __cplusplus } #endif #endif /* end of TIME_STAMP_COMMUNICATION_H */
文件6
TS_communication.c
/************************************************** * File name: TS_communication.c * Author: HAN Wei * Author's blog: http://blog.csdn.net/henter/ * Date: Aug 21st, 2014 **************************************************/ #include "get_small_file_size.h" #include "socket_communication.h" #include <stdio.h> #include <stdlib.h> #include <string.h> #define MAX_IP_PACKET_LEN (int)(65535) int TS_Communication(char *TS_request_file_name, unsigned char *TS_server_ip, unsigned int TS_server_port, unsigned int save_file_flag, char *TS_response_file_name) { int error_code, i; FILE *input_fp, *output_fp; long input_file_length; unsigned char *request; unsigned char response[MAX_IP_PACKET_LEN]; unsigned int request_len, response_buffer_len, response_len, message_len=0; unsigned char content_len[32], message[MAX_IP_PACKET_LEN]; unsigned char *p, *q, *content_start; if ( !(input_fp=fopen(TS_request_file_name, "rb")) ) { printf("Open file %s failed at %s, line %d!\n", TS_request_file_name, __FILE__, __LINE__); return (-1); } if ( error_code=GetSmallFileSize(TS_request_file_name, &input_file_length) ) { printf("Get file %s length failed at %s, line %d!\n", TS_request_file_name, __FILE__, __LINE__); return (-1); } request_len = (unsigned int)input_file_length; printf("Time stamp request ASN.1 encode length is %d bytes\n", request_len); if ( !(request=(unsigned char *)malloc(request_len)) ) { printf("Invoke malloc() function failed at %s, line %d!\n", __FILE__, __LINE__); fclose(input_fp); return (-1); } fread(request, request_len, 1, input_fp); fclose(input_fp); printf("Time stamp request:\n"); for (i=0; i<(int)(request_len); i++) printf("0x%x ", request[i]); printf("\n"); memset(content_len, 0, sizeof(content_len)); sprintf((char *)(content_len), "%d", request_len); memset(message, 0, sizeof(message)); strcat((char *)(message), "POST /service/tsp HTTP/1.1\n"); strcat((char *)(message), "Host: timestamping.edelweb.fr\n"); strcat((char *)(message), "Content-Type: application/timestamp-query\n"); strcat((char *)(message), "Content-Length: "); strcat((char *)(message), (char *)(content_len)); strcat((char *)(message), "\n\n"); p = message + strlen((char *)(message)); message_len = strlen((char *)(message)); memcpy(p, request, request_len); p = p + request_len; message_len += request_len; *p='\n'; message_len++; printf("HTTP POST message length is %d bytes.\n", message_len); printf("HTTP POST message:\n"); for (i=0; i<(int)(message_len); i++) printf("%c", message[i]); printf("\n"); free(request); response_buffer_len = sizeof(response); if ( error_code=socket_communication(TS_server_ip, TS_server_port, message, message_len, response, response_buffer_len, &response_len) ) { printf("Communicate with time stamp server failed at %s, line %d!\n", __FILE__, __LINE__); return (-1); } printf("HTTP Time stamp response is %d bytes.\n", response_len); printf("HTTP time stamp response:\n"); for (i=0; i<(int)(response_len); i++) printf("%c", response[i]); printf("\n"); q = (unsigned char *)strstr((char *)response, "Content-Type: application/timestamp-reply"); if (q!=NULL) { q += (strlen("Content-Type: application/timestamp-reply")+4); /* 注意在时间戳服务器返回的 HTTP 响应中, Content-Type: application/timestamp-reply 这一行之后有一个空行, 所以这一行内容之后的换行符总共有四个字符,即: 0x0d 0x0a 0x0d 0x0a。 将指针 q 移动到这四个字符之后,指针才指向 HTTP Content 的起始位置 */ content_start = q; } else { printf("Error at %s, line %d!\n", __FILE__, __LINE__); printf("Can't find string 'Content-Type: application/timestamp-reply' in HTTP response!\n"); return (-1); } i=0; printf("\nContent:\n"); while ( q < (response+response_len) ) { printf("0x%x ", *q); q++; i++; } printf("\n"); printf("Content length is %d bytes.\n", i); if (save_file_flag != 0) { if ( !(output_fp=fopen(TS_response_file_name, "wb")) ) { printf("Create file %s failed at %s, line %d!\n", TS_response_file_name, __FILE__, __LINE__); return (-1); } fwrite(content_start, i, 1, output_fp); fclose(output_fp); printf("Save time stamp response ASN.1 encode into file %s succeeded!\n", TS_response_file_name); } return 0; }
d) 调用已有的子函数,实现向法国时间戳服务器发送请求、接收响应。实现过程如下:
文件7
test.c
/************************************************** * File name: test.c * Author: HAN Wei * Author's blog: http://blog.csdn.net/henter/ * Date: Aug 21st, 2014 **************************************************/ #include "TS_communication.h" #include <stdio.h> #include <stdlib.h> #include <string.h> int main(int argc, char *argv[]) { int error_code; unsigned char ip[64]="92.103.215.77"; /* 法国时间戳服务器的 IP 地址 */ unsigned int port=80; unsigned int wrtie_file_flag=1; if (argc != 3) { printf("Invalid input!\n"); system("pause"); return (-1); } if ( error_code=TS_Communication(argv[1], ip, port, wrtie_file_flag, argv[2]) ) { printf("Send time stamp request or acquire time stamp response failed at %s, line %d!\n", __FILE__, __LINE__); return (-1); } printf("Test sending time stamp request and receiving time stamp response succeeded!\n"); system("pause"); return 0; }
一共有 7 个文件,假定编译后得到的 exe 文件名是 TS_communication.exe,可在 Windows 的命令提示符界面下执行命令:
TS_communication 时间戳请求文件名 时间戳响应文件名
例如:已经用 OpenSSL 生成了一个时间戳请求文件,名字是 TS_request.tsq,希望将从时间戳服务器返回的时间戳响应(ASN.1编码格式)保存到文件 TS_response.tsr 中,执行的命令是
TS_communication TS_request.tsq TS_response.tsr
以上介绍了 C 语言的实现过程。如果用 Python 语言实现,由于有一个名为 httplib2 的库,实现过程会简单得太多!
下面给出一个很粗糙的(未做异常检查和处理)示例程序:
import httplib2 in_file=open("TS_request.tsq", "rb") request=in_file.read() in_file.close() h=httplib2.Http(".cache") resp, content=h.request("http://timestamping.edelweb.fr/service/tsp", "POST", body=request, headers={'Content-Type':'application/timestamp-query'}) out_file=open("TS_response.tsr","wb") out_file.write(content) out_file.close()
要在 Python 中添加 httplib2 库,这里介绍两种方法:
1. 可以到 https://pypi.python.org/pypi/httplib2 下载,当前最新版本号是 0.9,下载文件 httplib2-0.9.zip 后解压缩,到解压缩后的目录中,执行命令
python setup.py install
2. 如果 Python 中已经安装了 pip 工具,执行命令
pip install httplib2
编程实现与时间戳服务器的通信