changeset 31:37fc66967517

Implement signature verification wiht polarssl
author Andre Heinecke <aheinecke@intevation.de>
date Thu, 13 Mar 2014 18:12:16 +0000
parents 381558ff6f26
children d8e93fa1fc93
files common/listutil.c common/listutil.h ui/certificatelist.cpp ui/mainwindow.cpp ui/tests/certlistparsertest.cpp
diffstat 5 files changed, 161 insertions(+), 104 deletions(-) [+]
line wrap: on
line diff
--- a/common/listutil.c	Thu Mar 13 17:26:33 2014 +0000
+++ b/common/listutil.c	Thu Mar 13 18:12:16 2014 +0000
@@ -18,90 +18,77 @@
 #pragma GCC diagnostic ignored "-Wconversion"
 /* Polarssl mh.h contains a conversion which gcc warns about */
 #include <polarssl/pk.h>
+#include <polarssl/base64.h>
+#include <polarssl/sha256.h>
 #pragma GCC diagnostic pop
 
-#define MAX_FILESIZE_KB 1024
+#define MAX_FILESIZE 1048576 /* 1024*1024 */
 
 void handle_errno()
 {
     printf("Error: %s \n", strerror(errno));
 }
 
-list_status_t read_list (const char *file_name, char **data, size_t *size)
+/**
+ *  @brief Read a file into memory.
+ *
+ * The caller needs to free data
+ *
+ * @param[in] fileName Name of the file.
+ * @param[out] data the file content
+ * @param[out] size size in bytes of the file content.
+ * @param[in] max_size the maximum amount of bytes to read.
+ *
+ * @return 0 on success an error code otherwise.
+ */
+#define READ_FILE_UNREADABLE -1
+#define READ_FILE_TOO_LARGE -2
+#define READ_FILE_NO_MEMORY -3
+#define READ_FILE_READ_FAILED -4
+static int read_file(const char *file_name, char **data, size_t *size,
+                     const size_t max_size)
 {
-    int fd = -1;
-    struct stat file_stat;
-    int rc = 0;
-    ssize_t bRead = 0;
-
-    memset(&file_stat, 0, sizeof(file_stat));
-
-    list_status_t retval = UnknownError;
+    FILE *f;
+    long file_size;
 
-    fd = open(file_name, O_RDONLY);
-    if (fd == -1) {
-        handle_errno();
-        retval = StatFailed;
-        goto cleanup;
-    }
+    f = fopen(file_name, "rb");
+    if (f == NULL)
+        return READ_FILE_UNREADABLE;
 
-    rc = fstat(fd, &file_stat);
-    if (rc < 0) {
-        printf ("Stat failed with rc: %i\n", rc);
-        retval = StatFailed;
-        goto cleanup;
-    }
-
-    // Check the size of the file
-    if (!file_stat.st_size) {
-        printf("Size zero\n");
-        retval = StatFailed;
-        goto cleanup;
+    fseek(f, 0, SEEK_END);
+    file_size = ftell(f);
+    if (file_size < 0){
+        fclose(f);
+        return READ_FILE_UNREADABLE;
     }
 
-    if (file_stat.st_size / 1024 > MAX_FILESIZE_KB &&
-            file_stat.st_size > 0) {
-        printf("File too large\n");
-        retval = TooLarge;
-        goto cleanup;
-    }
+    fseek(f, 0, SEEK_SET);
 
-    *size = (size_t) file_stat.st_size;
-
-    *data = (char*) malloc(*size);
+    if (file_size + 1 == 0) {
+        return READ_FILE_TOO_LARGE;
+    }
+    *size = (size_t) file_size;
 
-    if (*data == NULL) {
-        printf("Malloc failed\n");
-        retval = UnknownError;
-        goto cleanup;
+    if (*size > max_size)
+        return READ_FILE_TOO_LARGE;
+
+    *data = (char *) malloc( *size + 1 );
+    if (data == NULL) {
+        fclose(f);
+        return READ_FILE_NO_MEMORY;
     }
 
-    bRead = read(fd, *data, *size);
-
-    if (bRead < 0 || (size_t) bRead != *size) {
-        printf("Read failed\n");
-        if (bRead == -1) {
-            handle_errno();
-        }
-        retval = UnknownError;
-        *size = 0;
-        if (*data) {
-            free(*data);
-            printf("Nulling data\n");
-            *data = NULL;
-        }
-        goto cleanup;
+    if (fread(*data, 1, *size, f) != *size) {
+        fclose(f);
+        free(*data);
+        return READ_FILE_READ_FAILED;
     }
 
-    retval = UnknownValidity;
-cleanup:
+    fclose(f);
 
-    if (fd && fd != -1) {
-        close(fd);
-        fd = -1;
-    }
+    (*data)[*size] = '\0';
 
-    return retval;
+    return 0;
 }
 
 /** @brief verify the certificate list
@@ -116,57 +103,128 @@
  */
 int verify_list(char *data, size_t size)
 {
-//    char *sigstart = data;
     int ret = -1;
     pk_context pub_key_ctx;
-    size_t lenpem = strlen((const char*)publicKeyPEM);
+    char *p;
+    /* Fixed key size of 3072 implies the sizes*/
+    const size_t sig_b64_size = 512;
+    size_t sig_size = 384;
+
+    char signature_b64[sig_b64_size + 1];
+    unsigned char signature[sig_size];
+    /* Hash algroithm is sha256 */
+    unsigned char hash[32];
+
+    printf ("size: %lu", (unsigned long) size);
+
+    /* Fetch the signature from the first line od data */
+    p = strchr(data, '\r');
+    if (p == 0 || (unsigned int)(p - (data + 2)) != sig_b64_size) {
+        printf("Invalid data. Signature might be too long.\n");
+        return -1;
+    }
+    strncpy(signature_b64, data + 2, sig_b64_size);
+    signature_b64[sig_b64_size] = '\0';
+
+    ret = base64_decode(signature, &sig_size,
+                        (unsigned char *)signature_b64, sig_b64_size);
+
+    if (ret != 0 || sig_size != 384) {
+        printf("failed to decode signature\n");
+        return -1;
+    }
+
+    /* Hash is calculated over the data without the first line.
+     * linebreaks are \r\n so the first char of the new line is
+     * p+2 */
+    p += 2;
+    /* Size of the data to hash is the size - signature line
+     * signature line is sig_b64_size - "S:" and - "\r\n" so -4*/
+    sha256((unsigned char *)p, size - sig_b64_size - 4, hash, 0);
 
     pk_init(&pub_key_ctx);
+#if 0
+    {
+        int i;
+        FILE *foo = fopen("/tmp/testdump", "w");
+        FILE *foo2 = fopen("/tmp/rawdump", "w");
+        for (i=0; i< (int)(size - sig_b64_size - 2); i++)
+            fprintf (foo, "%c", p[i]);
+        for (i=0; i< (int)(size); i++)
+            fprintf (foo2, "%c", data[i]);
+        fclose(foo);
+        printf ("Hash: \n");
+        for (i=0; i<32; i++) {
+            printf ("%x", hash[i]);
+        }
+        printf("\n");
+    }
+#endif
 
-    ret = pk_parse_public_key(&pub_key_ctx, publicKeyPEM, lenpem);
-
+    ret = pk_parse_public_key(&pub_key_ctx, public_key_pem,
+                              public_key_pem_size);
     if (ret != 0) {
         printf("pk_parse_public_key failed with -0x%04x\n\n", -ret);
-        goto done;
+        pk_free(&pub_key_ctx);
+        return ret;
     }
 
-done:
+    ret = pk_verify(&pub_key_ctx, POLARSSL_MD_SHA256, hash, 0,
+                    signature, sig_size);
+
+    if (ret != 0) {
+        printf("pk_verify failed with -0x%04x\n\n", -ret);
+    }
     pk_free(&pub_key_ctx);
+
     return ret;
 }
 
 list_status_t read_and_verify_list(const char *file_name, char **data,
                                    size_t *size)
 {
-    char * signature = NULL;
-
     list_status_t retval = UnknownError;
     *data = NULL;
     *size = 0;
-
-    retval = read_list(file_name, data, size);
+    int ret = 0;
 
-    if (retval != UnknownValidity) {
-        printf("Readlist failed\n");
-        return retval;
-    }
+    ret = read_file(file_name, data, size, MAX_FILESIZE);
 
-    if (!data || !*size) {
-        // should not have happend if read_list works as specified
+    if (ret != 0) {
+        if (ret == READ_FILE_TOO_LARGE) {
+            return TooLarge;
+        }
+        if (ret == READ_FILE_UNREADABLE) {
+            return SeekFailed;
+        }
+        if (ret == READ_FILE_READ_FAILED) {
+            return ReadFailed;
+        }
         return UnknownError;
     }
 
-    signature = *data;
-
-    if (*signature != 'S') {
-        printf("Does not start with S\n");
-        retval = InvalidFormat;
-        goto cleanup;
+    if (!*data || !*size) {
+        return UnknownError;
     }
 
-    retval = verify_list (*data, *size);
+    if (**data != 'S') {
+        retval = InvalidFormat;
+    } else {
+        ret = verify_list (*data, *size);
+        if (ret == 0) {
+            /* Hooray */
+            return Valid;
+        }
+        if (ret == -1) {
+            /* our error */
+            retval = InvalidFormat;
+        } else if (ret == POLARSSL_ERR_RSA_VERIFY_FAILED) {
+            retval = InvalidSignature;
+        } else {
+            return UnknownError;
+        }
+    }
 
-cleanup:
     if (retval != Valid && *data) {
         free(*data);
         *data = NULL;
--- a/common/listutil.h	Thu Mar 13 17:26:33 2014 +0000
+++ b/common/listutil.h	Thu Mar 13 18:12:16 2014 +0000
@@ -16,15 +16,14 @@
  * @brief Status of the List Operations
  */
 typedef enum {
-    UnknownValidity = 0, // Not yet parsed
+    Valid = 100, // Could be read and signature matched
     UnknownError = 1, // The expected unexpected
     TooLarge = 2, // Failed because the file exeeds the limit
     InvalidFormat = 3, // File does not appear to be in list format
     InvalidSignature = 4, // Signature was invalid
-    StatFailed = 5, // Could not stat the file
-    ReadFailed = 6, // Could not read the file
-    IncompatibleVersion = 7, // The Format Version does not match
-    Valid = 8 // List is valid
+    SeekFailed = 5, // Could not seek in the file
+    ReadFailed = 6, // File exists but could not read the file
+    IncompatibleVersion = 7 // The Format Version does not match
 } list_status_t;
 
 /**
--- a/ui/certificatelist.cpp	Thu Mar 13 17:26:33 2014 +0000
+++ b/ui/certificatelist.cpp	Thu Mar 13 18:12:16 2014 +0000
@@ -9,7 +9,7 @@
     char *data = NULL;
     size_t size = 0;
 
-    mStatus = readAndVerifyList(fileName, &data, &size);
+    mStatus = read_and_verify_list(fileName, &data, &size);
 
     if (!isValid()) {
         return;
@@ -49,7 +49,7 @@
             mCertificatesInstall << Certificate(
                     QByteArray::fromBase64(curLine.remove(0,2).toLatin1()));
         } else if (curLine.startsWith("S:")) {
-            // Signature is verified in readAndVerifyList
+            // Signature is verified in read_and_verify_list
             continue;
         } else if (!curLine.isEmpty()){
             qDebug () << "Don't know how to handle: " << curLine;
--- a/ui/mainwindow.cpp	Thu Mar 13 17:26:33 2014 +0000
+++ b/ui/mainwindow.cpp	Thu Mar 13 18:12:16 2014 +0000
@@ -73,7 +73,7 @@
         char *data = NULL;
         size_t size;
 
-        if (readAndVerifyList(cFileName, &data, &size) != Valid) {
+        if (read_and_verify_list(cFileName, &data, &size) != Valid) {
             // Probably a bug when Qt fileName is encoded and cFileName
             // fails because of this. This needs a unit test!
             // Maybe check that the file is in our data directory
--- a/ui/tests/certlistparsertest.cpp	Thu Mar 13 17:26:33 2014 +0000
+++ b/ui/tests/certlistparsertest.cpp	Thu Mar 13 18:12:16 2014 +0000
@@ -3,6 +3,15 @@
 
 #include <QDebug>
 
+void CertListTest::testValidList()
+{
+    const char *fname = "list-valid-signed.txt";
+    CertificateList *certList = testWithFile(fname);
+    printf("Status :%i\n ",certList->getStatus());
+    QCOMPARE (certList->getStatus(), Valid);
+    QVERIFY (certList->isValid());
+    delete certList;
+}
 
 void CertListTest::testInvalidSig()
 {
@@ -44,15 +53,6 @@
     delete certList;
 }
 
-void CertListTest::testValidList()
-{
-    const char *fname = "list-valid-signed.txt";
-    CertificateList *certList = testWithFile(fname);
-    QCOMPARE (certList->getStatus(), Valid);
-    QVERIFY (certList->isValid());
-    delete certList;
-}
-
 void CertListTest::benchmarkValid()
 {
     const char *fname = "list-valid-signed.txt";

http://wald.intevation.org/projects/trustbridge/