aheinecke@404: /* Copyright (C) 2014 by Bundesamt für Sicherheit in der Informationstechnik
aheinecke@404:  * Software engineering by Intevation GmbH
aheinecke@404:  *
aheinecke@404:  * This file is Free Software under the GNU GPL (v>=2)
aheinecke@404:  * and comes with ABSOLUTELY NO WARRANTY!
aheinecke@404:  * See LICENSE.txt for details.
aheinecke@404:  */
aheinecke@4: #include "listutil.h"
aheinecke@4: 
aheinecke@4: #include <stdio.h>
aheinecke@4: #include <stdlib.h>
aheinecke@4: #include <errno.h>
aheinecke@4: #include <fcntl.h>
aheinecke@4: #include <unistd.h>
aheinecke@4: #include <sys/types.h>
aheinecke@4: #include <sys/stat.h>
aheinecke@7: #include <string.h>
aheinecke@4: 
aheinecke@286: #include "strhelp.h"
andre@630: #include "logging.h"
aheinecke@286: 
andre@359: #ifdef RELEASE_BUILD
aheinecke@28: #include "pubkey-release.h"
aheinecke@28: #else
aheinecke@28: #include "pubkey-test.h"
aheinecke@28: #endif
aheinecke@28: 
aheinecke@28: #pragma GCC diagnostic ignored "-Wconversion"
aheinecke@28: /* Polarssl mh.h contains a conversion which gcc warns about */
aheinecke@28: #include <polarssl/pk.h>
aheinecke@31: #include <polarssl/base64.h>
aheinecke@31: #include <polarssl/sha256.h>
aheinecke@28: #pragma GCC diagnostic pop
aheinecke@28: 
bernhard@178: #define MAX_FILESIZE (MAX_LINE_LENGTH * MAX_LINES)
aheinecke@4: 
aheinecke@31: #define READ_FILE_UNREADABLE -1
aheinecke@31: #define READ_FILE_TOO_LARGE -2
aheinecke@31: #define READ_FILE_NO_MEMORY -3
aheinecke@31: #define READ_FILE_READ_FAILED -4
aheinecke@86: #define READ_FILE_INVALID_CALL -5
andre@769: int
andre@769: read_file(const char *file_name, char **data, size_t *size,
andre@905:           const size_t max_size)
aheinecke@4: {
andre@905:   FILE *f;
andre@905:   long file_size;
aheinecke@86: 
andre@905:   if (!file_name || !data || !size || !max_size)
andre@905:     {
andre@905:       return READ_FILE_INVALID_CALL;
aheinecke@4:     }
aheinecke@4: 
andre@905:   f = fopen(file_name, "rb");
andre@905:   if (f == NULL)
andre@905:     return READ_FILE_UNREADABLE;
aheinecke@4: 
andre@905:   fseek(f, 0, SEEK_END);
andre@905:   file_size = ftell(f);
andre@905:   if (file_size <= 0)
andre@905:     {
andre@905:       fclose(f);
andre@905:       return READ_FILE_UNREADABLE;
aheinecke@38:     }
aheinecke@31: 
andre@905:   fseek(f, 0, SEEK_SET);
andre@905: 
andre@905:   if (file_size + 1 == 0)
andre@905:     {
andre@905:       fclose(f);
andre@905:       return READ_FILE_TOO_LARGE;
andre@905:     }
andre@905:   *size = (size_t) file_size;
andre@905: 
andre@905:   if (*size > max_size)
andre@905:     {
andre@905:       fclose(f);
andre@905:       return READ_FILE_TOO_LARGE;
aheinecke@4:     }
aheinecke@4: 
andre@905:   *data = (char *) malloc( *size + 1 );
andre@905:   if (*data == NULL)
andre@905:     {
andre@905:       fclose(f);
andre@905:       return READ_FILE_NO_MEMORY;
aheinecke@4:     }
aheinecke@4: 
andre@905:   if (fread(*data, 1, *size, f) != *size)
andre@905:     {
andre@905:       free(*data);
andre@905:       fclose(f);
andre@905:       return READ_FILE_READ_FAILED;
andre@905:     }
aheinecke@4: 
andre@905:   fclose(f);
aheinecke@4: 
andre@905:   (*data)[*size] = '\0';
andre@905: 
andre@905:   return 0;
aheinecke@4: }
aheinecke@4: 
aheinecke@68: int verify_list(const char *data, const size_t size)
aheinecke@4: {
andre@905:   int ret = -1;
andre@905:   pk_context pub_key_ctx;
andre@905:   char *p;
andre@770: 
andre@905:   /* Modulus / 8 are the necessary bytes. */
andre@770: #ifndef TRUSTBRIDGE_RSA_KEY_SIZE
andre@770: # error "Key size undefined"
andre@770: #endif
andre@905:   const size_t sig_b64_size = TRUSTBRIDGE_RSA_KEY_SIZE / 8 * 4 / 3;
andre@905:   size_t sig_size = TRUSTBRIDGE_RSA_KEY_SIZE / 8;
aheinecke@31: 
andre@905:   char signature_b64[sig_b64_size + 1];
andre@905:   unsigned char signature[sig_size];
andre@905:   /* Hash algroithm is sha256 */
andre@905:   unsigned char hash[32];
aheinecke@31: 
andre@905:   if (!data || !size)
andre@905:     {
andre@905:       return -1;
aheinecke@93:     }
aheinecke@93: 
andre@905:   /* Fetch the signature from the first line od data */
andre@905:   p = strchr(data, '\r');
andre@905:   if (p == 0 || (unsigned int)(p - (data + 2)) != sig_b64_size)
andre@905:     {
andre@905:       /*        printf("Invalid data. Signature might be too long.\n"); */
andre@905:       return -1;
aheinecke@31:     }
andre@905:   strncpy(signature_b64, data + 2, sig_b64_size);
andre@905:   signature_b64[sig_b64_size] = '\0';
aheinecke@31: 
andre@905:   ret = base64_decode(signature, &sig_size,
andre@905:                       (unsigned char *)signature_b64, sig_b64_size);
aheinecke@31: 
andre@905:   if (ret != 0 || sig_size != TRUSTBRIDGE_RSA_KEY_SIZE / 8)
andre@905:     {
andre@905:       /*        printf("failed to decode signature\n"); */
andre@905:       return -1;
aheinecke@31:     }
aheinecke@31: 
andre@905:   /* Hash is calculated over the data without the first line.
andre@905:    * linebreaks are \r\n so the first char of the new line is
andre@905:    * p+2 */
andre@905:   p += 2;
andre@905:   /* Size of the data to hash is the size - signature line
andre@905:    * signature line is sig_b64_size - "S:" and - "\r\n" so -4*/
andre@905:   sha256((unsigned char *)p, size - sig_b64_size - 4, hash, 0);
aheinecke@28: 
andre@905:   pk_init(&pub_key_ctx);
aheinecke@28: 
andre@905:   ret = pk_parse_public_key(&pub_key_ctx, public_key_pem,
andre@905:                             public_key_pem_size);
andre@905:   if (ret != 0)
andre@905:     {
andre@905:       ERRORPRINTF ("pk_parse_public_key failed with -0x%04x\n\n", -ret);
andre@905:       pk_free(&pub_key_ctx);
andre@905:       return ret;
aheinecke@28:     }
aheinecke@28: 
andre@905:   ret = pk_verify(&pub_key_ctx, POLARSSL_MD_SHA256, hash, 0,
andre@905:                   signature, sig_size);
aheinecke@31: 
andre@905:   if (ret != 0)
andre@905:     {
andre@905:       ERRORPRINTF ("pk_verify failed with -0x%04x\n\n", -ret);
aheinecke@31:     }
andre@905:   pk_free(&pub_key_ctx);
aheinecke@31: 
andre@905:   return ret;
aheinecke@28: }
aheinecke@28: 
aheinecke@28: list_status_t read_and_verify_list(const char *file_name, char **data,
aheinecke@28:                                    size_t *size)
aheinecke@28: {
andre@905:   list_status_t retval = UnknownError;
andre@905:   *data = NULL;
andre@905:   *size = 0;
andre@905:   int ret = 0;
aheinecke@4: 
andre@905:   ret = read_file(file_name, data, size, MAX_FILESIZE);
andre@905: 
andre@905:   /* printf ("Ret: %i \n", ret); */
andre@905:   if (ret != 0)
andre@905:     {
andre@905:       if (ret == READ_FILE_TOO_LARGE)
andre@905:         {
andre@905:           return TooLarge;
aheinecke@31:         }
andre@905:       if (ret == READ_FILE_UNREADABLE)
andre@905:         {
andre@905:           /* TODO: work with errno ? */
andre@905:           /* errsv = errno; */
andre@905:           /* perror("read_and_verify_list(), READ_FILE_UNREADABLE:");  */
andre@905:           return SeekFailed;
aheinecke@31:         }
andre@905:       if (ret == READ_FILE_READ_FAILED)
andre@905:         {
andre@905:           /* TODO: work with ferror() or feof() ? */
andre@905:           return ReadFailed;
aheinecke@31:         }
andre@905:       return UnknownError;
aheinecke@4:     }
aheinecke@4: 
andre@905:   if (!*data || !*size)
andre@905:     {
andre@905:       /* File is probably empty */
andre@905:       return UnknownError;
aheinecke@4:     }
aheinecke@4: 
andre@905:   if (**data != 'S')
andre@905:     {
andre@905:       retval = InvalidFormat;
andre@905:     }
andre@905:   else
andre@905:     {
andre@905:       ret = verify_list (*data, *size);
andre@905:       if (ret == 0)
andre@905:         {
andre@905:           /* Hooray */
andre@905:           return Valid;
aheinecke@31:         }
andre@905:       if (ret == -1)
andre@905:         {
andre@905:           /* our error */
andre@905:           retval = InvalidFormat;
andre@905:         }
andre@905:       else
andre@905:         {
andre@905:           retval = InvalidSignature;
aheinecke@31:         }
aheinecke@31:     }
aheinecke@9: 
andre@905:   if (retval != Valid && *data)
andre@905:     {
andre@905:       free(*data);
andre@905:       *data = NULL;
andre@905:       *size = 0;
aheinecke@4:     }
andre@905:   return retval;
aheinecke@4: }
aheinecke@4: 
aheinecke@286: char **
aheinecke@286: get_certs_from_list (char *data, const size_t size)
aheinecke@286: {
aheinecke@286:   char *cur = data;
aheinecke@286:   char **retval = NULL;
aheinecke@68: 
aheinecke@286:   if (!data || !size)
aheinecke@286:     {
andre@626:       ERRORPRINTF ("Invalid call to get_certs_to_remove \n");
aheinecke@286:       return NULL;
aheinecke@68:     }
aheinecke@286: 
aheinecke@286:   while (cur)
aheinecke@286:     {
aheinecke@286:       char *next = strchr(cur, '\n');
aheinecke@286:       if (strlen(cur) > 3 && (cur[0] == 'I' || cur[0] == 'R') &&
aheinecke@286:           next - cur > 4)
aheinecke@286:         {
aheinecke@292:           size_t len = (size_t) (next - cur - 3);
aheinecke@286:           /* Remove I: or R: at the beginning and \r\n at the end */
aheinecke@286:           strv_append(&retval, cur + 2, len);
aheinecke@286:         }
aheinecke@286:       cur = next ? (next + 1) : NULL;
aheinecke@286:     }
aheinecke@286:   return retval;
aheinecke@68: }
aheinecke@68: