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:

  1. MD5 source binary file generation;

  2. The creation of two keys: private and public (certificate).

  3. 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:

  1. Retrieve the encrypted MD5 from the binary file;

  2. Calculate the binary file MD5 with the .sig section filled out with nulls;

  3. 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:

  1. Allocates memory equal to the signed binary file size;

  2. Copies the mapped binary file to the allocated memory;

  3. Searches for the .sig section;

  4. Fills out the .sig section content with nulls;

  5. 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

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.