Index: ms-biff.c =================================================================== RCS file: /cvs/gnome/gnumeric/plugins/excel/ms-biff.c,v retrieving revision 1.64 diff -u -r1.64 ms-biff.c --- ms-biff.c 25 Nov 2003 06:04:08 -0000 1.64 +++ ms-biff.c 16 Jan 2004 14:12:30 -0000 @@ -49,13 +49,74 @@ /* Read Side */ /*******************************************************************************/ +/** + * ms_biff_password_hash and ms_biff_crypt_seq + * based on pseudo-code in the OpenOffice.org XL documentation + **/ + +static guint16 +ms_biff_password_hash (char const *password) +{ + int tmp, index= 0, len= strlen(password); + guint16 chr, hash= 0; + + do { + chr = password[index]; + index++; + tmp = (chr << index); + hash ^= (tmp & 0x7fff) | (tmp >> 15); + } while (index < len); + hash = hash ^ len ^ 0xce4b; + + return hash; +} + +static void +ms_biff_crypt_seq (guint8 *seq, guint16 key, char const *password) +{ + guint8 low = key & 0xff, high = key >> 8; + guint8 preset[15] = {0xbb, 0xff, 0xff, 0xba, 0xff, 0xff, 0xb9, 0x80, + 0x00, 0xbe, 0x0f, 0x00, 0xbf, 0x0f, 0x00 }; + int k, len= strlen(password); + + strcpy(seq, password); + for (k= 0; len + k < 16; ++k) { + seq[len + k]= preset[k]; + } + for (k= 0; k < 16; k+=2) { + seq[k] ^= low; + seq[k+1] ^= high; + } + for (k= 0; k < 16; ++k) { + seq[k] = (seq[k] << 2) | (seq[k] >> 6); + } +} + static gboolean ms_biff_pre_biff8_query_set_decrypt (BiffQuery *q, char const *password) { - g_return_val_if_fail (q->length == sizeof_BIFF_2_7_FILEPASS, FALSE); + guint16 hash, key; + guint16 pw_hash = ms_biff_password_hash(password); + -#warning TODO OO has docs should be trivial to add - return FALSE; + if (q->length == 4) { + key = GSF_LE_GET_GUINT16(q->data + 0); + hash = GSF_LE_GET_GUINT16(q->data + 2); + } else if (q->length == 6) { + /* BIFF8 record with pre-biff8 crypto, these do exist */ + key = GSF_LE_GET_GUINT16(q->data + 2); + hash = GSF_LE_GET_GUINT16(q->data + 4); + } else { + return FALSE; + } + + if (hash != pw_hash) + return FALSE; + + ms_biff_crypt_seq(q->xor_key, key, password); + + q->encryption = MS_BIFF_CRYPTO_XOR; + return TRUE; } /* @@ -230,7 +291,7 @@ if (password == NULL) return FALSE; - if (version < MS_BIFF_V8) + if (version < MS_BIFF_V8 || q->data[0] == 0) return ms_biff_pre_biff8_query_set_decrypt (q, password); g_return_val_if_fail (q->length == sizeof_BIFF_8_FILEPASS, FALSE); @@ -239,7 +300,7 @@ q->data + 22, q->data + 38, &q->md5_ctxt)) return FALSE; - q->is_encrypted = TRUE; + q->encryption = MS_BIFF_CRYPTO_RC4; q->block = -1; /* For some reaons the 1st record after FILEPASS seems to be unencrypted */ @@ -266,7 +327,7 @@ q->data_malloced = q->non_decrypted_data_malloced = FALSE; q->data = q->non_decrypted_data = NULL; q->input = input; - q->is_encrypted = FALSE; + q->encryption = MS_BIFF_CRYPTO_NONE; #if BIFF_DEBUG > 0 ms_biff_query_dump (q); @@ -335,7 +396,7 @@ } else q->data = NULL; - if (q->is_encrypted) { + if (q->encryption == MS_BIFF_CRYPTO_RC4) { q->non_decrypted_data_malloced = q->data_malloced; q->non_decrypted_data = q->data; @@ -366,6 +427,21 @@ rc4 (data, len, &q->rc4_key); } + } else if (q->encryption == MS_BIFF_CRYPTO_XOR) { + unsigned int offset, k; + + q->non_decrypted_data_malloced = q->data_malloced; + q->non_decrypted_data = q->data; + q->data_malloced = TRUE; + q->data = g_new (guint8, q->length); + memcpy (q->data, q->non_decrypted_data, q->length); + + offset = (q->streamPos + q->length + 4) % 16; + for (k= 0; k < q->length; ++k) { + guint8 tmp = (q->data[k] << 3) | (q->data[k] >> 5); + q->data[k] = tmp ^ q->xor_key[offset]; + offset = (offset + 1) % 16; + } } else q->non_decrypted_data = q->data; Index: ms-biff.h =================================================================== RCS file: /cvs/gnome/gnumeric/plugins/excel/ms-biff.h,v retrieving revision 1.55 diff -u -r1.55 ms-biff.h --- ms-biff.h 25 Nov 2003 06:04:08 -0000 1.55 +++ ms-biff.h 16 Jan 2004 14:12:30 -0000 @@ -13,6 +13,11 @@ #include "rc4.h" #include "md5.h" +typedef enum { MS_BIFF_CRYPTO_NONE = 0, + MS_BIFF_CRYPTO_XOR, + MS_BIFF_CRYPTO_RC4, + MS_BIFF_CRYPTO_UNKNOWN } MsBiffCrypto ; + typedef enum { MS_BIFF_V2 = 2, MS_BIFF_V3 = 3, MS_BIFF_V4 = 4, @@ -41,7 +46,8 @@ guint32 streamPos; GsfInput *input; - gboolean is_encrypted; + MsBiffCrypto encryption; + guint8 xor_key[16]; RC4_KEY rc4_key; MD5_CTX md5_ctxt; int block; Index: ms-excel-read.c =================================================================== RCS file: /cvs/gnome/gnumeric/plugins/excel/ms-excel-read.c,v retrieving revision 1.564.2.1 diff -u -r1.564.2.1 ms-excel-read.c --- ms-excel-read.c 6 Jan 2004 03:34:49 -0000 1.564.2.1 +++ ms-excel-read.c 16 Jan 2004 14:12:32 -0000 @@ -4761,6 +4761,32 @@ g_ptr_array_add (container->v7.externsheet, sheet); } +/* FILEPASS, ask the user for a password if necessary + * return value is an error string, or NULL for success + */ + +static char * +excel_read_FILEPASS (BiffQuery *q, ExcelWorkbook *ewb) +{ + /* files with workbook protection are encrypted using a + * static password (why ?? ). + */ + if (ms_biff_query_set_decrypt(q, ewb->container.ver, "VelvetSweatshop")) + return NULL; + + do { + char const *filename = workbook_get_filename (ewb->gnum_wb); + char *passwd = gnm_cmd_context_get_password (GNM_CMD_CONTEXT (ewb->context), filename); + if (passwd == NULL) { + return _("No password supplied"); + } + if (ms_biff_query_set_decrypt (q, ewb->container.ver, passwd)) { + return NULL; + } + g_free (passwd); + } while (TRUE); +} + static gboolean excel_read_sheet (BiffQuery *q, ExcelWorkbook *ewb, WorkbookView *wb_view, ExcelReadSheet *esheet) @@ -5112,6 +5138,15 @@ case BIFF_FORMAT: excel_read_FORMAT (q, ewb); break; case BIFF_STYLE: break; case BIFF_1904: excel_read_1904 (q, ewb); break; + case BIFF_FILEPASS: + { + char *problem = excel_read_FILEPASS (q, ewb); + if (problem) { + gnm_cmd_context_error_import (GNM_CMD_CONTEXT (ewb->context), problem); + return FALSE; + } + } + break; default: excel_unexpected_biff (q, "Sheet", ms_excel_read_debug); @@ -5495,25 +5530,7 @@ break; case BIFF_FILEPASS: /* All records after this are encrypted */ - /* files with workbook protection are encrypted using a - * static password (why ?? ). - */ - if (ms_biff_query_set_decrypt (q, ewb->container.ver, "VelvetSweatshop")) - break; - do { - char const *filename = workbook_get_filename (ewb->gnum_wb); - char *passwd = gnm_cmd_context_get_password (GNM_CMD_CONTEXT (ewb->context), filename); - if (passwd == NULL) { - problem_loading = _("No password supplied"); - break; - } - if (!ms_biff_query_set_decrypt (q, ewb->container.ver, passwd)) - problem_loading = _("Invalid password"); - g_free (passwd); - if (problem_loading == NULL) - break; - problem_loading = NULL; - } while (TRUE); + problem_loading= excel_read_FILEPASS(q, ewb); break; case BIFF_STYLE: