Using digital signatures for data integrity checking in Linux
Introduction
One of the most important questions in today’s world is the question of confidence in received data. For example, user А sends data D to user B via email. How can user B be sure that the received data is the same data that was sent by user A? One possible way of resolving this issue is using a digital signature (DS). The following requirements apply to a DS:
-
The signature content should depend on the signed message;
-
The sender’s unique information should be used in a signature;
-
It should be easy to create a signature;
-
It should be impossible to falsify a signature computationally;
-
A signature should be small.
This article considers a DS implementation example for binary file integrity checking in Linux (64-bit ELF). We will use a direct DS when only a sender and a recipient are communicating (without a third party/an arbitrator). We will need a private encryption key and a public key (certificate) for this. The sender creates both keys. User A signs an executable file and passes the certificate to user B with the help of safe delivery means. After this, user A sends a signed file to user B. User B runs the received file; if the binary file is corrupted, user B will receive a message that DS verification has failed. To implement this solution, we will need a program for signing binary files and a code that verifies DSs.
DS implementation example
DS implementation includes the following steps:
-
MD5 source binary file generation;
-
The creation of two keys: private and public (certificate).
-
Binary file signing (ELF):
1 MD5 of the binary file is encrypted with the help of the private key;
3.2 The encrypted MD5 is written to a new .sig section of the binary file;
3.3 The certificate is saved to the ~/.ssh folder.
All this can be implemented with the help of the Linux utilities openssl, objcopy, and md5sum. Below you can find an example of a script sign_elf.sh that signs ELF binary files. (Note: source without line numbers is included at the end of this article.)
001 #!/bin/bash002 003 KEY_DIR="$HOME/.ssh"004 PRIVATE_KEY="$KEY_DIR/priv.key"005 CERTIFICATE="$KEY_DIR/pub.crt"006 SUBJECT="/C=RU/ST=Nizhni Novgorod/L=Nizhniy Novgorod/O=Auriga/OU=DEV/CN=www.auriga.com"007 008 if [ "$#" = "0" ]; then009 echo "Usage: sign_elfs.sh ... "010 exit 1;011 fi012 013 if [ ! -d "$KEY_DIR" ]; then014 # Control will enter here if $DIRECTORY does not exist.015 mkdir "$KEY_DIR"016 fi017 018 # Create private key and certificate019 openssl req -nodes -x509 -sha256 -newkey rsa:4096 -keyout "$PRIVATE_KEY" -out "$CERTIFICATE" -days 365 -subj "$SUBJECT"020 021 for ELF_BIN in $@; do022 ELF_BASE_NAME="${ELF_BIN##*/}"023 # ELF_BIN_OLD="$ELF_BIN.old"024 ELF_BIN_SIGNATURE="$ELF_BASE_NAME.sha256"025 ELF_BIN_MD5="$ELF_BASE_NAME.md5"026 027 if [ ! -f "$ELF_BIN" ] || [ "x$ELF_BIN" = "x" ];then028 echo "Error: no such file $ELF_BIN"029 exit 1030 fi031 032 # Remove .sig section033 objcopy --remove-section=.sig "$ELF_BIN"034 035 # Add 512-byte section filled with zeros036 rm -f dummy.txt037 touch dummy.txt038 truncate --size=512 dummy.txt039 objcopy --add-section .sig=dummy.txt --set-section-flags .sig=noload,readonly "$ELF_BIN"040 041 # Create MD5 hash042 md5sum "$ELF_BIN" | awk '{ print $1 }' > "$KEY_DIR/$ELF_BIN_MD5"043 044 # Encrypt MD5 hash using private key045 openssl dgst -sha256 -sign "$PRIVATE_KEY" -out "$KEY_DIR/$ELF_BIN_SIGNATURE" "$KEY_DIR/$ELF_BIN_MD5"046 047 # Validate encrypted MD5 hash using certificate048 openssl dgst -sha256 -verify <(openssl x509 -in "$CERTIFICATE" -pubkey -noout) -signature "$KEY_DIR/$ELF_BIN_SIGNATURE" "$KEY_DIR/$ELF_BIN_MD5"049 050 # Add encrypted MD5 hash into ELF binary into .sig section051 echo "Add .sig section"052 objcopy --update-section .sig="$KEY_DIR/$ELF_BIN_SIGNATURE" --set-section-flags .sig=noload,readonly "$ELF_BIN" "$ELF_BIN"053 054 # Print .sig section055 echo "Check .sig section"056 objdump -sj .sig "$ELF_BIN"057 done058 059 rm -f dummy.txt060 061 ls -ls ~/.ssh
Figure 1. The process of signing ELF binary. (Source: Auriga)
Let us explore the details of what this script does.
Line 19:
openssl req -nodes -x509 -sha256 -newkey rsa:4096 -keyout “$PRIVATE_KEY” -out “$CERTIFICATE” -days 365 -subj “$SUBJECT”
req — certificate creation request
-nodes — create a private plaintext key
-x509 — output — self-signed certificate
-sha256 — encryption algorithm
-newkey rsa:4096 — create a new certificate and RSA private key, number of bits — 4096
-keyout $PRIVATE_KEY — the path to the file where the private key is written to
-out $CERTIFICATE — the path to the file where the certificate is written to-days 365 — number of days for certificate acknowledgement
-subj $SUBJECT — new certificate subject (should have the format of /type0=value0/type1=value1/type2=…). In our case, this is /C=RU/ST=Nizhni Novgorod/L=Nizhniy Novgorod/O=Auriga/OU=DEV/CN=www.auriga.com, where
С — country
ST — state, region, province
L — location
O — organization
OU — organizational department/unit
CN — basic title/container name
The subject is described in detail in RFC-5280 (https://tools.ietf.org/html/rfc5280). After this command is run, a private key will be generated, ~/.ssh/priv.key and certificate ~/.ssh/pub.crt. The private key will be used to encrypt the data, and the certificate will be used for data decryption. Using one private key, it is possible to generate a number of unique certificates for decrypting data that was encrypted with this private key.
Line 21:
for ELF_BIN in $@; do
Start of loop for all binary files added to the sign_elf.sh script.
Line 33:
objcopy –remove-section=.sig “$ELF_BIN”
Remove the .sig section from our binary file. This needs to be conducted if the file was already signed with our script and we want to re-sign it.
Lines 36+:
rm -f dummy.txt
touch dummy.txt
truncate –size=512 dummy.txt
objcopy –add-section .sig=dummy.txt –set-section-flags .sig=noload,readonly “$ELF_BIN
Create a 512-byte text file and add it to our binary file not loaded on the runtime .sig section only for reading, which contains data from the dummy.txt file.
Line 42:
md5sum “$ELF_BIN” | awk '{ print $1 }' > “$KEY_DIR/$ELF_BIN_MD5”
Calculate MD5 of the binary file (with .sig section) and write the result to a text file, binary_name.md5.
Line 45:
openssl dgst -sha256 -sign “$PRIVATE_KEY” -out “$KEY_DIR/$ELF_BIN_SIGNATURE” “$KEY_DIR/$ELF_BIN_MD5”
This command encrypts the file with MD5 created by line 42 with a private key. Arguments:
dgst — this option indicates that we want to encrypt (sign) data;
-sha256 — encryption algorithm;
-sign $PRIVATE_KEY — encrypt the file with the help of private key $PRIVATE_KEY;
-out $KEY_DIR/$ELF_BIN_SIGNATURE — encrypted data is saved to file $KEY_DIR/$ELF_BIN_SIGNATURE;
$KEY_DIR/$ELF_BIN_MD5 — text file containing data to be encrypted.
Line 48:
openssl dgst -sha256 -verify <(openssl x509 -in "$CERTIFICATE" -pubkey -noout) -signature "$KEY_DIR/$ELF_BIN_SIGNATURE" "$KEY_DIR/$ELF_BIN_MD5"
Signed file verification. It can be understood by reference to this line that for DS verification we need encrypted data, a certificate that will help us perform verification and data verification. That is, if
x — encrypted data,
y — certificate,
z — verification data,
then
f(x,y) = z
Line 52:
objcopy –update-section .sig=”$KEY_DIR/$ELF_BIN_SIGNATURE” –set-section-flags .sig=noload,readonly “$ELF_BIN” “$ELF_BIN”
Remove the old .sig section and add a new one to file $ELF_BIN (binary_name). As data for the new .sig section, data from the signed file $KEY_DIR/$ELF_BIN_SIGNATURE (~/.ssh/binary_name.sha256) is used.
Let us review our DS verification method on runtime. We will use the libcrypto and libssl libraries for this and the following algorithm:
-
Retrieve the encrypted MD5 from the binary file;
-
Calculate the binary file MD5 with the .sig section filled out with nulls;
-
Verify that the encrypted MD5 equals the calculated MD5 with the help of a certificate.
Figure 2. Check integrity. (Source: Auriga)
The code for main.c is shown below.
001 #include <openssl/crypto.h>002 #include <openssl/err.h>003 #include <openssl/md5.h>004 #include <openssl/pem.h>005 #include <openssl/x509.h>006 #include 007 #include 008 009 #include 010 #include 011 #include 012 #include 013 #include 014 #include <sys/mman.h>015 #include <sys/stat.h>016 #include <sys/types.h>017 018 #define MD5_STR_SIZE (MD5_DIGEST_LENGTH * 2 + 1)019 #define SIGNATURE_SECTION_NAME ".sig"020 #define FILE_PIECE (1024)021 #define PWS_BUFFER (1024)022 #define PATH_TO_CERTIFICATE "/.ssh/pub.crt"023 024 void get_md5(char *m, size_t l, unsigned char *md5) {025 MD5_CTX mdContext = { 0 };026 MD5_Init(&mdContext);027 char *tmp = m;028 size_t bytes = l;029 030 // We cannot upload a big memory by one call of MD5_Update. Therefore, we031 // upload a whole file by pieces. The size of each piece is 1024 bytes,032 while (bytes > FILE_PIECE) {033 MD5_Update(&mdContext, tmp, FILE_PIECE);034 tmp = tmp + FILE_PIECE;035 bytes = bytes - FILE_PIECE;036 }037 038 // Upload last piece039 MD5_Update(&mdContext, tmp, bytes);040 041 // Calculate MD5042 MD5_Final(md5, &mdContext);043 }044 045 int calculate_md5(const char *const fname, unsigned char *md5) {046 struct stat st = {0};047 size_t size = 0;048 int fd = -1;049 int i = 0;050 int shnum = 0;051 const char *sh_strtab_p = NULL;052 char *p = NULL;053 char *m = NULL;054 Elf64_Ehdr *ehdr = NULL;055 Elf64_Shdr *shdr = NULL;056 Elf64_Shdr *sh_strtab = NULL;057 058 // Get size of binary059 if (stat(fname, &st) != 0) {060 perror("stat");061 return 1;062 }063 064 size = st.st_size;065 066 // Open binary file for reading067 fd = open(fname, O_RDONLY);068 if (fd < 0) {069 perror("open");070 return 1;071 }072 073 // Map binary file074 p = mmap(0, size, PROT_READ, MAP_PRIVATE, fd, 0);075 if (p == MAP_FAILED) {076 perror("mmap");077 return 1;078 }079 080 // Allocate memory to store mapped file081 m = (char *)calloc(size, sizeof(char));082 if (m == NULL) {083 perror("calloc");084 munmap(p, size);085 return 1;086 }087 088 // Copy mapped binary file to allocated memory089 memcpy(m, p, size);090 091 // Unmap mapped file092 munmap(p, size);093 094 // Get pointer to ELF header095 ehdr = (Elf64_Ehdr *)m;096 097 // Get pointer to section header table098 shdr = (Elf64_Shdr *)(m + ehdr->e_shoff);099 100 // Get number of section header table items101 shnum = ehdr->e_shnum;102 103 // Get pointer to section header string table104 sh_strtab = &shdr[ehdr->e_shstrndx];105 106 // Get base address of section header string table107 sh_strtab_p = m + sh_strtab->sh_offset;108 109 // For each section110 for (i = 0; i < shnum; ++i) {111 char *section_name = NULL;112 113 // Get section name114 section_name = (char *)(sh_strtab_p + shdr[i].sh_name);115 116 // If it is '.sig' section117 if (!strncmp(section_name, SIGNATURE_SECTION_NAME,118 strlen(SIGNATURE_SECTION_NAME))) {119 // Fill section content with zeros120 memset(m + shdr[i].sh_offset, 0, shdr[i].sh_size);121 }122 }123 124 // Calculate MD5 of memory125 get_md5(m, size, md5);126 127 // Free memory128 free(m);129 130 munmap(p, size);131 132 return 0;133 }134 135 int get_signature(const char *const fname, unsigned char *encrypted_md5) {136 Elf64_Ehdr *ehdr = NULL;137 Elf64_Shdr *shdr = NULL;138 Elf64_Shdr *sh_strtab = NULL;139 struct stat st = {0};140 size_t size = 0;141 int fd = -1;142 int i = 0;143 int shnum = 0;144 const char *sh_strtab_p = NULL;145 char *p = NULL;146 147 // Get size of binary148 if (stat(fname, &st) != 0) {149 perror("stat");150 return 1;151 }152 153 size = st.st_size;154 155 // Open binary file for reading156 fd = open(fname, O_RDONLY);157 if (fd < 0) {158 perror("open");159 return 1;160 }161 162 // Map binary file163 p = mmap(0, size, PROT_READ, MAP_PRIVATE, fd, 0);164 if (p == MAP_FAILED) {165 perror("mmap");166 return 1;167 }168 169 // Get pointer to ELF header170 ehdr = (Elf64_Ehdr *)p;171 172 // Get pointer to section header table173 shdr = (Elf64_Shdr *)(p + ehdr->e_shoff);174 175 // Get number of section header table items176 shnum = ehdr->e_shnum;177 178 // Get pointer to section header string table179 sh_strtab = &shdr[ehdr->e_shstrndx];180 181 // Get base address of section header string table182 sh_strtab_p = p + sh_strtab->sh_offset;183 184 // For each section185 for (i = 0; i < shnum; ++i) {186 char *section_name = NULL;187 188 // Get section name189 section_name = (char *)(sh_strtab_p + shdr[i].sh_name);190 191 // If it is '.sig' section192 if (!strncmp(section_name, SIGNATURE_SECTION_NAME,193 strlen(SIGNATURE_SECTION_NAME))) {194 int section_size = 0;195 int section_offset = 0;196 197 // Get '.sig' section size198 section_size = shdr[i].sh_size;199 200 // Get '.sig' section offset from start of ELF binary file201 section_offset = shdr[i].sh_offset;202 203 // Copy content of '.sig' section to array204 memcpy(encrypted_md5, (char *)(p + section_offset), section_size);205 }206 }207 208 munmap(p, size);209 return 0;210 }211 212 int check_integrity(const char * const binary) {213 EVP_MD_CTX *mctx = NULL;214 EVP_PKEY *sigkey = NULL;215 BIO *bio_cert = NULL;216 struct passwd *result = NULL;217 struct passwd pws = {0};218 char md5_string[MD5_STR_SIZE] = {0};219 char certificate[PATH_MAX] = {' '};220 unsigned char encrypted_md5[512] = {0};221 unsigned char md5[MD5_DIGEST_LENGTH] = {0};222 char buff[PWS_BUFFER] = {0};223 char *p = NULL;224 int siglen = 0;225 int ret = -1;226 227 if ((binary == NULL) || (binary[0] == ' ')) {228 fprintf(stderr, "Binary file is not validn");229 goto cleanup;230 }231 232 // Get home directory. We call getpwuid_r() instead of getpwuid() because233 // getpwuid_r() is thread-safe. Also we call geteuid() instead of getuid()234 // to get effective user ID: only user who is owner of executable file235 // has to be able to run this file236 if (getpwuid_r(geteuid(), &pws, buff, sizeof(buff), &result) != 0) {237 fprintf(stderr, "Failed to get home directoryn");238 goto cleanup;239 }240 241 // Create absolute path for certificate242 strncpy(certificate, pws.pw_dir, strlen(pws.pw_dir));243 strncat(certificate, PATH_TO_CERTIFICATE, strlen(PATH_TO_CERTIFICATE));244 245 // Add all digest algorithms to the table246 OpenSSL_add_all_algorithms();247 248 // allocates and initializes a X509 object249 X509 *cert = X509_new();250 if (cert == NULL) {251 fprintf(stderr, "X509_new() failedn");252 goto cleanup;253 }254 255 // Create BIO object associated with certificate256 bio_cert = BIO_new_file(certificate, "rb");257 if (bio_cert == NULL) {258 fprintf(stderr, "BIO_new_file() failedn");259 goto cleanup;260 }261 262 // Read certificate in PEM format from BIO263 if (PEM_read_bio_X509(bio_cert, &cert, NULL, NULL) == NULL) {264 fprintf(stderr, "PEM_read_bio_X509 failedn");265 goto cleanup;266 }267 268 // Get public key from certificate269 sigkey = X509_get_pubkey(cert);270 if (bio_cert == NULL) {271 fprintf(stderr, "X509_get_pubkey() failedn");272 goto cleanup;273 }274 275 // Create message digest context276 mctx = EVP_MD_CTX_create();277 if (mctx == NULL) {278 fprintf(stderr, "EVP_MD_CTX_create() failedn");279 goto cleanup;280 }281 282 // Set up verification context mctx using public key283 if (!EVP_DigestVerifyInit(mctx, NULL, EVP_sha256(), NULL, sigkey)) {284 fprintf(stderr, "EVP_DigestVerifyInit() failedn");285 goto cleanup;286 }287 288 // Get encrypted signature from ELF binary289 if (get_signature(binary, encrypted_md5)) {290 fprintf(stderr, "get_signature() failedn");291 goto cleanup;292 }293 294 // Get sigkey size295 siglen = EVP_PKEY_size(sigkey);296 if (siglen <= 0) {297 fprintf(stderr, "Error reading signature filen");298 goto cleanup;299 }300 301 // Get original MD5 from ELF302 if (calculate_md5(binary, md5)) {303 fprintf(stderr, "get_signature() failedn");304 goto cleanup;305 }306 307 // Convert MD5 digital to human readable string308 p = md5_string;309 for (int i = 0; i < MD5_DIGEST_LENGTH; i++) {310 snprintf(p, MD5_DIGEST_LENGTH, "%02x", md5[i]);311 // one step is two symbols312 p = p + 2;313 }314 // Last symbol is new line315 md5_string[MD5_STR_SIZE - 1] = 'n';316 317 // Add buffer (original MD5) to be compared to context318 EVP_DigestSignUpdate(mctx, md5_string, MD5_STR_SIZE);319 320 // Add encrypted buffer to context and perform verification321 ret = EVP_DigestVerifyFinal(mctx, encrypted_md5, (unsigned int)siglen);322 if (ret > 0) {323 fprintf(stderr, "Verified OKn");324 ret = 0;325 } else if (ret == 0) {326 fprintf(stderr, "Verification Failuren");327 ret = -1;328 } else {329 fprintf(stderr, "Error Verifying Datan");330 ret = -1;331 }332 333 cleanup:334 // Release objects335 EVP_MD_CTX_destroy(mctx);336 X509_free(cert);337 EVP_PKEY_free(sigkey);338 BIO_free(bio_cert);339 EVP_cleanup();340 341 return ret;342 }343 344 int main(int argc, char **argv) {345 if (check_integrity(argv[0]) < 0) {346 fprintf(stderr, "Signature check failedn");347 return 1;348 }349 350 /* Do something here */351 352 return 0;353 }
The main job will be performed by the сheck_integrity() function, which takes the path to the binary file as an argument and returns 0 if the signature verification is passed and (-1) otherwise. The function uses libcrypto library to verify DS. Let us examine the question of check_integrity() function operating in general:
Lines 236+:
if (getpwuid_r(geteuid(), &pws, buff, sizeof(buff), &result) != 0) {
fprintf(stderr, “Failed to get home directoryn”);
goto cleanup;
}
Create a path to the certificate file. The current implementation expects that the file will be kept in the $HOME/.ssh ($HOME/.ssh/pub.crt) folder.
Line 246:
OpenSSL_add_all_algorithms();
The OpenSSL_add_all_algorithms() function adds all algorithms to a special internal table that OpenSSL uses when calling different functions. Normally, this function is invoked at the beginning, and before exiting the application, EVP_cleanup() is invoked.
Line 249:
X509 *cert = X509_new();
Memory is allocated for the X509 structure, which is necessary for the X509 certificate introduction.
Line 256:
bio_cert = BIO_new_file(certificate, “rb”);
The BIO object is created in association with the certificate file. The BIO object can be viewed as an analogue of the file stream returned by the fopen() function.
Line 263:
if (PEM_read_bio_X509(bio_cert, &cert, NULL, NULL) == NULL) {
Read from the certificate file into the X509 structure.
Line 269:
sigkey = X509_get_pubkey(cert);
Retrieve the public key from the certificate. The result is saved in the EVP_PKEY structure pointer.
Line 276:
mctx = EVP_MD_CTX_create();
Create the verification context (the result is moved to the EVP_MD_CTX structure). This structure will be used to verify our certificate.
Line 283:
if (!EVP_DigestVerifyInit(mctx, NULL, EVP_sha256(), NULL, sigkey)) {
Initialize the mctx verification context. The third parameter contains the encryption algorithm and the fifth — public key. The second and the fourth parameters are not needed for our verification — set NULL.
Line 289:
if (get_signature(binary, encrypted_md5)) {
Retrieve the encrypted MD5 from the binary file. The operation of this function will be described below in more detail.
Line 295:
siglen = EVP_PKEY_size(sigkey);
Calculate the maximum signature size in bytes.
Line 302:
if (calculate_md5(binary, md5)) {
Calculate MD5 of the binary file. The operation of this function will be described below in more detail.
Lines 308+:
p = md5_string;
for (int i = 0; i < MD5_DIGEST_LENGTH; i++) {
snprintf(p, MD5_DIGEST_LENGTH, “%02x”, md5[i]);
// one step is two symbols
p = p + 2;
}
// Last symbol is new line
md5_string[MD5_STR_SIZE – 1] = 'n';
Convert MD5 returned by the calculate_md5() function into the line returned by the md5sum utility. We perform this conversion because the sign_elf.sh script encrypts MD5, which is output by the md5sum utility, and adds the encrypted MD5 into the .sig section.
Line 318:
EVP_DigestSignUpdate(mctx, md5_string, MD5_STR_SIZE);
Add the obtained MD5 as a line into the verification context.
Line 321:
ret = EVP_DigestVerifyFinal(mctx, encrypted_md5, (unsigned int)siglen);
This function performs the verification of the encrypted MD5 calculated on runtime MD5 (we remember that we have the public key and MD5 calculated on runtime in mctx). The EVP_DigestVerifyFinal() function returns a positive number if the verification is passed.
Now we move to the description of functions that work with the ELF header.
The Get_signature() function parses the header of the 64-bit ELF file (the path to the file is set by the first parameter) and retrieves the encrypted MD5 from the .sig section. The encrypted MD5 pointer is saved in the second parameter. Let us provide more insight into this function.
Lines 148+:
if (stat(fname, &st) != 0) {
perror(“stat”);
return 1;
}
size = st.st_size;
We obtain the file size in bytes.
Line 156:
fd = open(fname, O_RDONLY);
Open the binary file for reading.
Line 163:
p = mmap(0, size, PROT_READ, MAP_PRIVATE, fd, 0);
Map the file to memory equal to the file size. After that, we can work with the file as with the memory.
After this, we will search for our .sig section.
Line 170:
ehdr = (Elf64_Ehdr *)p;
We obtain the ELF header pointer — the header is at the beginning of the file.
Line 173:
shdr = (Elf64_Shdr *)(p + ehdr->e_shoff);
We obtain a pointer to the sections table (our .sig section is somewhere among them).
Line 176:
shnum = ehdr->e_shnum;
Count the total sections number.
Line 179:
sh_strtab = &shdr[ehdr->e_shstrndx];
Obtain a pointer to the lines table section header. Here is the ehdr field e_shstrndx — this is the lines table index in the sections table.
Line 182:
sh_strtab_p = p + sh_strtab->sh_offset;
Obtain the lines table address from the beginning of the file. Here is the sh_offset field — the section shift from the beginning of the file.
Lines 185+:
for (i = 0; i < shnum; ++i) {
char *section_name = NULL;
// Get section name
section_name = (char *)(sh_strtab_p + shdr[i].sh_name);
Here the loop starts for all sections (shnum). Each section header has the sh_name field — this is line index in the lines table. To retrieve the section nam,e we need to address the offset symbols table sh_name.
Lines 192+:
if (!strncmp(section_name, SIGNATURE_SECTION_NAME,
strlen(SIGNATURE_SECTION_NAME))) {
int section_size = 0;
int section_offset = 0;
// Get '.sig' section size
section_size = shdr[i].sh_size;
// Get '.sig' section offset from start of ELF binary file
section_offset = shdr[i].sh_offset;
If the section name is .sig , then we obtain its size (the field sh_size of the section header) and offset from the beginning of the file (field sh_offset).
Line 204:
memcpy(encrypted_md5, (char *)(p + section_offset), section_size);
With the section beginning pointer and its size, copy the section content and encrypted_md5[] array. Now the array has the encrypted MD5.
The Calculate_md5() function code is much like get_signature(). The Calculate_md5() function does the following:
-
Allocates memory equal to the signed binary file size;
-
Copies the mapped binary file to the allocated memory;
-
Searches for the .sig section;
-
Fills out the .sig section content with nulls;
-
Calculates MD5 for the retrieved memory content.
Section .sig is filled out with nulls to calculate MD5 for the binary file before it is signed. Here we need to be reminded of how we signed the binary file in the sign_elf.sh script:
touch dummy.txt truncate –size=512 dummy.txt objcopy –add-section .sig=dummy.txt –set-section-flags .sig=noload,readonly “$ELF_BIN”
Here we created a 512-byte file and filled it out with nulls. After that, we the added .sig section into the binary file that contained the dummy.txt file data.
The DS implementation method described above can be improved: Fill out the .sig section not with nulls but with some known numbers, such as with the first 10 bytes from the .data section. To do this, you will need to slightly modify the calculate_md5() function.
The source implementation code without line numbers is provided below.
Conclusion
This article reviewed one of the variants for DS implementation in Linux. A strong hash function encrypted with a sender’s private key lies at the root of the method we described. This implementation does not require writing a driver and is accomplished by standard Linux utilities. Another variant of DS support for Linux is DigSig (http://disec.sourceforge.net/), but, unfortunately, this project is no longer maintained. For more detailed information about DSs and asymmetric encryption, see the RFC pages: 5280 (https://tools.ietf.org/html/rfc5280), 7091 (https://tools.ietf.org/html/rfc7091), 3447 (https://tools.ietf.org/html/rfc3447), and 5959 (https://tools.ietf.org/html/rfc5959).
Kirill Brazhnikov is a software engineer at Auriga. His experience includes low-level software development (host-target development model) in RTOS LynxOS-178 and system programming in Linux.
Source code
main.c
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define MD5_STR_SIZE (MD5_DIGEST_LENGTH * 2 + 1)
#define SIGNATURE_SECTION_NAME “.sig”
#define FILE_PIECE (1024)
#define PWS_BUFFER (1024)
#define PATH_TO_CERTIFICATE “/.ssh/pub.crt”
void get_md5(char *m, size_t l, unsigned char *md5) {
MD5_CTX mdContext = { 0 };
MD5_Init(&mdContext);
char *tmp = m;
size_t bytes = l;
// We cannot upload a big memory by one call of MD5_Update. Therefore, we
// upload a whole file by pieces. The size of each piece is 1024 bytes,
while (bytes > FILE_PI