提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
前言
近期在完成一个U盘升级功能,对于Arm嵌入式设备来说应该算是必备的了。
参考链接:
一、实现过程
1.检测U盘中的更新程序
结合netlink捕获USB的热拔插,我们需要去检测U盘的挂载点,通常情况下都是挂载在/media下。
bool checkConsoleAppExists()
{
DIR *dir = opendir("/run/media"); // 打开 /run/media 目录 具体的挂载点根据设备决定
if (dir)
{
struct dirent *entry;
while ((entry = readdir(dir)) != NULL)
{
if (strcmp(entry->d_name, ".") != 0 && strcmp(entry->d_name, "..") != 0)
{
devpath = "/run/media/" + std::string(entry->d_name);
path = "/run/media/" + std::string(entry->d_name) + "/updateFileName";
std::string signatureFilePath = devpath +"/sigFileName.sig";
std::string publicKeyPath = devpath + "/key.pub" ;
if (access(path.c_str(), F_OK) != -1)
{
//检测到更新程序,可以做一些校验,我这里的话是验证数字签名,根据具体情况决定
return checkUpdate(signatureFilePath,publicKeyPath,path);
}
}
}
closedir(dir);
}
return false;
}
2. 数字验证
/*检测是否需要更新,是否为正确的更新程序*/
bool checkUpdate(const std::string& signatureFile, const std::string& publicKeyFile, const std::string& dataFile) {
// 打开更新文件
// 这里是想要去检测版本号,去判断是否需要升级的,后面由于使用了数字签名就没有实现这部分。
FILE *updateFile = fopen(dataFile.c_str(), "rb");
if (!updateFile) {
perror("Error opening update file\n");
return false;
}
// 验证数字签名
return verifySignature(signatureFile,publicKeyFile,dataFile);
}
// 函数用于验证数字签名是否有效
bool verifySignature(const std::string& signatureFile, const std::string& publicKeyFile, const std::string& dataFile)
{
// 打开公钥文件以读取公钥信息
FILE* fp = fopen(publicKeyFile.c_str(), "r");
if (!fp) {
std::cerr << "Error opening public key file." << std::endl;
return false;
}
// 从公钥文件中读取 RSA 公钥信息
RSA* rsa = PEM_read_RSA_PUBKEY(fp, NULL, NULL, NULL);
fclose(fp);
// 检查是否成功读取公钥信息
if (!rsa) {
std::cerr << "Error reading public key." << std::endl;
return false;
}
// 创建一个用于存储 RSA 公钥的 EVP_PKEY 对象
EVP_PKEY* evpKey = EVP_PKEY_new();
// 将 RSA 公钥赋值给 EVP_PKEY 对象
if (!EVP_PKEY_assign_RSA(evpKey, rsa)) {
std::cerr << "Error assigning RSA key." << std::endl;
RSA_free(rsa);
return false;
}
// 创建一个用于消息摘要计算的 EVP_MD_CTX 对象
EVP_MD_CTX* ctx = EVP_MD_CTX_new();
if (!ctx) {
std::cerr << "Error creating context." << std::endl;
RSA_free(rsa);
return false;
}
// 打开数据文件以进行签名验证
std::ifstream fileStream(dataFile, std::ios::binary | std::ios::ate);
if (!fileStream.is_open()) {
std::cerr << "Error opening data file." << std::endl;
EVP_MD_CTX_free(ctx);
RSA_free(rsa);
return false;
}
// 读取数据文件的大小和内容
std::streamsize fileSize = fileStream.tellg();
fileStream.seekg(0, std::ios::beg);
std::vector<unsigned char> fileData(fileSize);
if (!fileStream.read(reinterpret_cast<char*>(fileData.data()), fileSize)) {
std::cerr << "Error reading data file." << std::endl;
fileStream.close();
EVP_MD_CTX_free(ctx);
RSA_free(rsa);
return false;
}
fileStream.close();
// 打开签名文件以进行签名验证
std::ifstream signatureFileStream(signatureFile, std::ios::binary);
if (!signatureFileStream.is_open()) {
std::cerr << "Error opening signature file." << std::endl;
EVP_MD_CTX_free(ctx);
RSA_free(rsa);
return false;
}
// 读取签名文件的大小和内容
signatureFileStream.seekg(0, std::ios::end);
size_t signatureFileSize = signatureFileStream.tellg();
signatureFileStream.seekg(0, std::ios::beg);
std::vector<unsigned char> signatureData(signatureFileSize);
if (!signatureFileStream.read(reinterpret_cast<char*>(signatureData.data()), signatureFileSize)) {
std::cerr << "Error reading signature file." << std::endl;
signatureFileStream.close();
EVP_MD_CTX_free(ctx);
RSA_free(rsa);
return false;
}
signatureFileStream.close();
// 更新消息摘要的内容,计算数据文件的哈希值
if (!EVP_VerifyUpdate(ctx, fileData.data(), fileSize)) {
std::cerr << "Error updating verification." << std::endl;
EVP_MD_CTX_free(ctx);
RSA_free(rsa);
return false;
}
// 验证签名的有效性
int result = EVP_VerifyFinal(ctx, signatureData.data(), signatureFileSize, evpKey);
EVP_MD_CTX_free(ctx);
RSA_free(rsa);
// 根据验证结果返回相应的布尔值
if (result != 1) {
std::cerr << "Signature verification failed." << std::endl;
return false;
}
std::cout << "Signature verification successful." << std::endl;
return true;
}
3.升级程序
升级程序的话,实际上就是一个拷贝的过程。将设备上的备份,U盘中的更新程序拷贝出来。
/* 更新程序 */
bool copyFile(const char *sourcePath, const char *destinationPath) {
//system("mv updateFileName updateFileName.back"); //这里是一个备份,后续可以进行一个升级失败,备份还原。
FILE *sourceFile = fopen(sourcePath, "rb");
FILE *destinationFile = fopen(destinationPath, "wb");
if (sourceFile == NULL) {
printf("Error opening sourceFile files.\n");
return false;
}
if(destinationFile == NULL){
printf("Error opening destinationFile files.\n");
return false;
}
char buffer[1024];
size_t bytesRead;
while ((bytesRead = fread(buffer, 1, sizeof(buffer), sourceFile)) > 0) {
printf("start copy file........................\n");
fwrite(buffer, 1, bytesRead, destinationFile);
}
/*更简单一点就是用system命令,直接拷贝
不过有一点需要注意,程序在运行的时候,有时候会拷贝失败。
可以先用system命令删除或者备份,然后再进行拷贝,再用system给个权限。
*/
fclose(sourceFile);
fclose(destinationFile);
return true;
}
4.主函数
int main(void)
{
struct sockaddr_nl client;
struct timeval tv;
int CppLive, rcvlen, ret;
fd_set fds;
int buffersize = 1024;
CppLive = socket(AF_NETLINK, SOCK_RAW, NETLINK_KOBJECT_UEVENT);
memset(&client, 0, sizeof(client));
client.nl_family = AF_NETLINK;
client.nl_pid = getpid();
client.nl_groups = 1; /* receive broadcast message*/
setsockopt(CppLive, SOL_SOCKET, SO_RCVBUF, &buffersize, sizeof(buffersize));
bind(CppLive, (struct sockaddr*)&client, sizeof(client));
while (1) {
char buf[UEVENT_BUFFER_SIZE] = { 0 };
FD_ZERO(&fds);
FD_SET(CppLive, &fds);
tv.tv_sec = 0;
tv.tv_usec = 100 * 1000;
ret = select(CppLive + 1, &fds, NULL, NULL, &tv);
if(ret < 0)
continue;
if(!(ret > 0 && FD_ISSET(CppLive, &fds)))
continue;
/* receive data */
rcvlen = recv(CppLive, &buf, sizeof(buf), 0);
if (rcvlen > 0) {
printf("%s\n", buf);
//检测更新程序
//升级程序
//重启....
}
}
close(CppLive);
return 0;
}
总结
以上就是今天要讲的内容,本文仅仅简单介绍了结合netlink的u盘升级,后期可以通过Qt实现U盘升级,实现一个程序,通过界面可以判断更新状态。