From: Ben Pfaff Date: Sat, 26 Sep 2015 23:42:13 +0000 (-0700) Subject: pspp-convert: Support decrypting encrypted SPSS syntax files. X-Git-Url: https://pintos-os.org/cgi-bin/gitweb.cgi?p=pspp;a=commitdiff_plain;h=2ea422dcad13f121dfb5a2f390c3e456f5bcec83 pspp-convert: Support decrypting encrypted SPSS syntax files. Thanks to charlesjohnsont@outlook.com for providing an example. Bug #45974. --- diff --git a/NEWS b/NEWS index 5da8bf74dd..b1031e7edb 100644 --- a/NEWS +++ b/NEWS @@ -11,6 +11,11 @@ Changes since 0.8.5: * The graphical user interface uses Gtk+ version 3 instead of version 2. Accordingly, it has a somewhat different look and feel. + * The pspp-convert utility can now decrypt encrypted syntax files. + The encrypted syntax file format is unacceptably insecure, so to + discourage its use PSPP and PSPPIRE do not directly read or write + this format. + * Bug fixes, including the following notable ones: - The correlation coefficient in the paired samples t-test diff --git a/doc/pspp-convert.texi b/doc/pspp-convert.texi index 838f81167c..3719de224c 100644 --- a/doc/pspp-convert.texi +++ b/doc/pspp-convert.texi @@ -4,7 +4,8 @@ @cindex @command{pspp-convert} @command{pspp-convert} is a command-line utility accompanying -@pspp{}. It reads an SPSS system or portable file @var{input} and +@pspp{}. It reads an SPSS or SPSS/PC+ system file or SPSS portable +file or encrypted SPSS syntax file @var{input} and writes a copy of it to another @var{output} in a different format. Synopsis: @@ -38,13 +39,19 @@ SPSS system file. @item por SPSS portable file. + +@item sps +SPSS syntax file. (Only encrypted syntax files may be converted to +this format.) @end table -As a special case of format conversion, @command{pspp-convert} can -decrypt an encrypted SPSS system file. Specify the encrypted file as -@var{input}. The output will be the equivalent plaintext SPSS system -file. You will be prompted for the password (or use @option{-p}, -documented below). +@command{pspp-convert} can convert most input formats to most output +formats. Encrypted system file and syntax files are exceptions: if +the input file is in an encrypted format, then the output file must be +the same format (decrypted). To decrypt such a file, specify the +encrypted file as @var{input}. The output will be the equivalent +plaintext file. You will be prompted for the password (or use +@option{-p}, documented below). Use @code{-O @var{extension}} to override the inferred format or to specify the format for unrecognized extensions. @@ -72,9 +79,10 @@ and SPSS/PC+ system files, do not self-identify their encoding. @item -p @var{password} @item --password=@var{password} -Specifies the password to use to decrypt an encrypted SPSS system -file. If this option is not specified, @command{pspp-convert} will -prompt interactively for the password as necessary. +Specifies the password to use to decrypt an encrypted SPSS system file +or syntax file. If this option is not specified, +@command{pspp-convert} will prompt interactively for the password as +necessary. Be aware that command-line options, including passwords, may be visible to other users on multiuser systems. diff --git a/src/data/automake.mk b/src/data/automake.mk index 8b26f525e4..8031de188a 100644 --- a/src/data/automake.mk +++ b/src/data/automake.mk @@ -58,6 +58,8 @@ src_data_libdata_la_SOURCES = \ src/data/dict-class.h \ src/data/dictionary.c \ src/data/dictionary.h \ + src/data/encrypted-file.c \ + src/data/encrypted-file.h \ src/data/file-handle-def.c \ src/data/file-handle-def.h \ src/data/file-name.c \ @@ -99,8 +101,6 @@ src_data_libdata_la_SOURCES = \ src/data/subcase.c \ src/data/subcase.h \ src/data/sys-file-encoding.c \ - src/data/sys-file-encryption.c \ - src/data/sys-file-encryption.h \ src/data/sys-file-private.c \ src/data/sys-file-private.h \ src/data/sys-file-reader.c \ diff --git a/src/data/encrypted-file.c b/src/data/encrypted-file.c new file mode 100644 index 0000000000..b90126ed26 --- /dev/null +++ b/src/data/encrypted-file.c @@ -0,0 +1,377 @@ +/* PSPP - a program for statistical analysis. + Copyright (C) 2013, 2015 Free Software Foundation, Inc. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#include + +#include "data/encrypted-file.h" + +#include +#include + +#include "data/file-name.h" +#include "libpspp/assertion.h" +#include "libpspp/cast.h" +#include "libpspp/cmac-aes256.h" +#include "libpspp/message.h" + +#include "gl/minmax.h" +#include "gl/rijndael-alg-fst.h" +#include "gl/xalloc.h" + +#include "gettext.h" +#define _(msgid) gettext (msgid) + +struct encrypted_file + { + FILE *file; + enum { SYSTEM, SYNTAX } type; + int error; + + uint8_t ciphertext[16]; + uint8_t plaintext[16]; + unsigned int ofs, n; + + uint32_t rk[4 * (RIJNDAEL_MAXNR + 1)]; + int Nr; + }; + +static bool try_password(struct encrypted_file *, const char *password); +static bool decode_password (const char *input, char output[11]); +static bool fill_buffer (struct encrypted_file *); + +/* If FILENAME names an encrypted SPSS file, returns 1 and initializes *FP + for further use by the caller. + + If FILENAME can be opened and read, but is not an encrypted SPSS file, + returns 0. + + If FILENAME cannot be open or read, returns a negative errno value. */ +int +encrypted_file_open (struct encrypted_file **fp, const char *filename) +{ + struct encrypted_file *f; + char header[36 + 16]; + int retval; + int n; + + f = xmalloc (sizeof *f); + f->error = 0; + f->file = fn_open (filename, "rb"); + if (f->file == NULL) + { + msg (ME, _("An error occurred while opening `%s': %s."), + filename, strerror (errno)); + retval = -errno; + goto error; + } + + n = fread (header, 1, sizeof header, f->file); + if (n != sizeof header) + { + int error = feof (f->file) ? 0 : errno; + if (error) + msg (ME, _("An error occurred while reading `%s': %s."), + filename, strerror (error)); + retval = -error; + goto error; + } + + if (!memcmp (header + 8, "ENCRYPTEDSAV", 12)) + f->type = SYSTEM; + else if (!memcmp (header + 8, "ENCRYPTEDSPS", 12)) + f->type = SYNTAX; + else + { + retval = 0; + goto error; + } + + memcpy (f->ciphertext, header + 36, 16); + f->n = 16; + f->ofs = 0; + *fp = f; + return 1; + +error: + if (f->file) + fn_close (filename, f->file); + free (f); + *fp = NULL; + + return retval; +} + +/* Attempts to use PASSWORD, which may be a plaintext or "encrypted" password, + to unlock F. Returns true if successful, otherwise false. */ +bool +encrypted_file_unlock (struct encrypted_file *f, const char *password) +{ + char decoded_password[11]; + + return (try_password (f, password) + || (decode_password (password, decoded_password) + && try_password (f, decoded_password))); +} + +/* Attempts to read N bytes of plaintext from F into BUF. Returns the number + of bytes successfully read. A return value less than N may indicate end of + file or an error; use encrypted_file_close() to distinguish. + + This function can only be used after encrypted_file_unlock() returns + true. */ +size_t +encrypted_file_read (struct encrypted_file *f, void *buf_, size_t n) +{ + uint8_t *buf = buf_; + size_t ofs = 0; + + if (f->error) + return 0; + + while (ofs < n) + { + unsigned int chunk = MIN (n - ofs, f->n - f->ofs); + if (chunk > 0) + { + memcpy (buf + ofs, &f->plaintext[f->ofs], chunk); + ofs += chunk; + f->ofs += chunk; + } + else + { + if (!fill_buffer (f)) + return ofs; + } + } + + return ofs; +} + +/* Closes F. Returns 0 if no read errors occurred, otherwise a positive errno + value. */ +int +encrypted_file_close (struct encrypted_file *f) +{ + int error = f->error; + if (fclose (f->file) == EOF && !error) + error = errno; + free (f); + + return error; +} + +/* Returns true if F is an encrypted system file, + false if it is an encrypted syntax file. */ +bool +encrypted_file_is_sav (const struct encrypted_file *f) +{ + return f->type == SYSTEM; +} + +#define b(x) (1 << (x)) + +static const uint16_t m0[4][2] = { + { b(2), b(2) | b(3) | b(6) | b(7) }, + { b(3), b(0) | b(1) | b(4) | b(5) }, + { b(4) | b(7), b(8) | b(9) | b(12) | b(14) }, + { b(5) | b(6), b(10) | b(11) | b(14) | b(15) }, +}; + +static const uint16_t m1[4][2] = { + { b(0) | b(3) | b(12) | b(15), b(0) | b(1) | b(4) | b(5) }, + { b(1) | b(2) | b(13) | b(14), b(2) | b(3) | b(6) | b(7) }, + { b(4) | b(7) | b(8) | b(11), b(8) | b(9) | b(12) | b(13) }, + { b(5) | b(6) | b(9) | b(10), b(10) | b(11) | b(14) | b(15) }, +}; + +static const uint16_t m2[4][2] = { + { b(2), b(1) | b(3) | b(9) | b(11) }, + { b(3), b(0) | b(2) | b(8) | b(10) }, + { b(4) | b(7), b(4) | b(6) | b(12) | b(14) }, + { b(5) | b(6), b(5) | b(7) | b(13) | b(15) }, +}; + +static const uint16_t m3[4][2] = { + { b(0) | b(3) | b(12) | b(15), b(0) | b(2) | b(8) | b(10) }, + { b(1) | b(2) | b(13) | b(14), b(1) | b(3) | b(9) | b(11) }, + { b(4) | b(7) | b(8) | b(11), b(4) | b(6) | b(12) | b(14) }, + { b(5) | b(6) | b(9) | b(10), b(5) | b(7) | b(13) | b(15) }, +}; + +static int +decode_nibble (const uint16_t table[4][2], int nibble) +{ + int i; + + for (i = 0; i < 4; i++) + if (table[i][0] & (1 << nibble)) + return table[i][1]; + + return 0; +} + +/* Returns true if X has exactly one 1-bit, false otherwise. */ +static bool +is_pow2 (int x) +{ + return x && (x & (x - 1)) == 0; +} + +/* If X has exactly one 1-bit, returns its index, where bit 0 is the LSB. + Otherwise, returns 0. */ +static int +find_1bit (uint16_t x) +{ + int i; + + if (!is_pow2 (x)) + return -1; + + for (i = 0; i < 16; i++) + if (x & (1u << i)) + return i; + + abort (); +} + +/* Attempts to decode a pair of encoded password characters A and B into a + single byte of the plaintext password. Returns 0 if A and B are not a valid + encoded password pair, otherwise a byte of the plaintext password. */ +static int +decode_password_2bytes (uint8_t a, uint8_t b) +{ + int x = find_1bit (decode_nibble (m0, a >> 4) & decode_nibble (m2, b >> 4)); + int y = find_1bit (decode_nibble (m1, a & 15) & decode_nibble (m3, b & 15)); + return x < 0 || y < 0 ? 0 : (x << 4) | y; +} + +/* Decodes an SPSS so-called "encrypted" password INPUT into OUTPUT. + + An encoded password is always an even number of bytes long and no longer + than 20 bytes. A decoded password is never longer than 10 bytes plus a null + terminator. + + Returns true if successful, otherwise false. */ +static bool +decode_password (const char *input, char output[11]) +{ + size_t len; + + len = strlen (input); + if (len > 20 || len % 2) + return false; + + for (; *input; input += 2) + { + int c = decode_password_2bytes (input[0], input[1]); + if (!c) + return false; + *output++ = c; + } + *output = '\0'; + + return true; +} + +/* If CIPHERTEXT is the first ciphertext block in an encrypted .sav file for + PASSWORD, initializes rk[] and returns an nonzero Nr value. + + Otherwise, returns zero. */ +static bool +try_password(struct encrypted_file *f, const char *password) +{ + /* NIST SP 800-108 fixed data. */ + static const uint8_t fixed[] = { + /* i */ + 0x00, 0x00, 0x00, 0x01, + + /* label */ + 0x35, 0x27, 0x13, 0xcc, 0x53, 0xa7, 0x78, 0x89, + 0x87, 0x53, 0x22, 0x11, 0xd6, 0x5b, 0x31, 0x58, + 0xdc, 0xfe, 0x2e, 0x7e, 0x94, 0xda, 0x2f, 0x00, + 0xcc, 0x15, 0x71, 0x80, 0x0a, 0x6c, 0x63, 0x53, + + /* delimiter */ + 0x00, + + /* context */ + 0x38, 0xc3, 0x38, 0xac, 0x22, 0xf3, 0x63, 0x62, + 0x0e, 0xce, 0x85, 0x3f, 0xb8, 0x07, 0x4c, 0x4e, + 0x2b, 0x77, 0xc7, 0x21, 0xf5, 0x1a, 0x80, 0x1d, + 0x67, 0xfb, 0xe1, 0xe1, 0x83, 0x07, 0xd8, 0x0d, + + /* L */ + 0x00, 0x00, 0x01, 0x00, + }; + + char padded_password[32]; + size_t password_len; + uint8_t cmac[16]; + uint8_t key[32]; + + /* Truncate password to at most 10 bytes. */ + password_len = strlen (password); + if (password_len > 10) + password_len = 10; + + /* padded_password = password padded with zeros to 32 bytes. */ + memset (padded_password, 0, sizeof padded_password); + memcpy (padded_password, password, password_len); + + /* cmac = CMAC(padded_password, fixed). */ + cmac_aes256 (CHAR_CAST (const uint8_t *, padded_password), + fixed, sizeof fixed, cmac); + + /* The key is the cmac repeated twice. */ + memcpy(key, cmac, 16); + memcpy(key + 16, cmac, 16); + + /* Use key to initialize AES. */ + assert (sizeof key == 32); + f->Nr = rijndaelKeySetupDec (f->rk, CHAR_CAST (const char *, key), 256); + + /* Check for magic number at beginning of plaintext. */ + rijndaelDecrypt (f->rk, f->Nr, + CHAR_CAST (const char *, f->ciphertext), + CHAR_CAST (char *, f->plaintext)); + return !memcmp (f->plaintext, f->type == SYSTEM ? "$FL" : "* E", 3); +} + +static bool +fill_buffer (struct encrypted_file *f) +{ + f->n = fread (f->ciphertext, 1, sizeof f->ciphertext, f->file); + f->ofs = 0; + if (f->n == sizeof f->ciphertext) + { + rijndaelDecrypt (f->rk, f->Nr, + CHAR_CAST (const char *, f->ciphertext), + CHAR_CAST (char *, f->plaintext)); + if (f->type == SYNTAX) + { + const char *eof = memchr (f->plaintext, '\04', sizeof f->plaintext); + if (eof) + f->n = CHAR_CAST (const uint8_t *, eof) - f->plaintext; + } + return true; + } + else + { + if (ferror (f->file)) + f->error = errno; + return false; + } +} diff --git a/src/data/encrypted-file.h b/src/data/encrypted-file.h new file mode 100644 index 0000000000..d089a046f0 --- /dev/null +++ b/src/data/encrypted-file.h @@ -0,0 +1,34 @@ +/* PSPP - a program for statistical analysis. + Copyright (C) 2013, 2015 Free Software Foundation, Inc. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifndef ENCRYPTED_FILE_H +#define ENCRYPTED_FILE_H 1 + +#include +#include + +/* Reading encrypted SPSS files. */ + +struct encrypted_file; + +int encrypted_file_open (struct encrypted_file **, const char *filename); +bool encrypted_file_unlock (struct encrypted_file *, const char *password); +size_t encrypted_file_read (struct encrypted_file *, void *, size_t); +int encrypted_file_close (struct encrypted_file *); + +bool encrypted_file_is_sav (const struct encrypted_file *); + +#endif /* encrypted-file.h */ diff --git a/src/data/sys-file-encryption.c b/src/data/sys-file-encryption.c deleted file mode 100644 index 51e253fbe3..0000000000 --- a/src/data/sys-file-encryption.c +++ /dev/null @@ -1,356 +0,0 @@ -/* PSPP - a program for statistical analysis. - Copyright (C) 2013 Free Software Foundation, Inc. - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . */ - -#include - -#include "data/sys-file-encryption.h" - -#include -#include - -#include "data/file-name.h" -#include "libpspp/assertion.h" -#include "libpspp/cast.h" -#include "libpspp/cmac-aes256.h" -#include "libpspp/message.h" - -#include "gl/minmax.h" -#include "gl/rijndael-alg-fst.h" -#include "gl/xalloc.h" - -#include "gettext.h" -#define _(msgid) gettext (msgid) - -struct encrypted_sys_file - { - FILE *file; - int error; - - uint8_t ciphertext[16]; - uint8_t plaintext[16]; - unsigned int n; - - uint32_t rk[4 * (RIJNDAEL_MAXNR + 1)]; - int Nr; - }; - -static bool try_password(struct encrypted_sys_file *, const char *password); -static bool decode_password (const char *input, char output[11]); -static bool fill_buffer (struct encrypted_sys_file *); - -/* If FILENAME names an encrypted system file, returns 1 and initializes *FP - for further use by the caller. - - If FILENAME can be opened and read, but is not an encrypted system file, - returns 0. - - If FILENAME cannot be open or read, returns a negative errno value. */ -int -encrypted_sys_file_open (struct encrypted_sys_file **fp, const char *filename) -{ - struct encrypted_sys_file *f; - char header[36 + 16]; - int retval; - int n; - - f = xmalloc (sizeof *f); - f->error = 0; - f->file = fn_open (filename, "rb"); - if (f->file == NULL) - { - msg (ME, _("An error occurred while opening `%s': %s."), - filename, strerror (errno)); - retval = -errno; - goto error; - } - - n = fread (header, 1, sizeof header, f->file); - if (n != sizeof header) - { - int error = feof (f->file) ? 0 : errno; - if (error) - msg (ME, _("An error occurred while reading `%s': %s."), - filename, strerror (error)); - retval = -error; - goto error; - } - - if (memcmp (header + 8, "ENCRYPTEDSAV", 12)) - { - retval = 0; - goto error; - } - - memcpy (f->ciphertext, header + 36, 16); - f->n = 16; - *fp = f; - return 1; - -error: - if (f->file) - fn_close (filename, f->file); - free (f); - *fp = NULL; - - return retval; -} - -/* Attempts to use PASSWORD, which may be a plaintext or "encrypted" password, - to unlock F. Returns true if successful, otherwise false. */ -bool -encrypted_sys_file_unlock (struct encrypted_sys_file *f, const char *password) -{ - char decoded_password[11]; - - return (try_password (f, password) - || (decode_password (password, decoded_password) - && try_password (f, decoded_password))); -} - -/* Attempts to read N bytes of plaintext from F into BUF. Returns the number - of bytes successfully read. A return value less than N may indicate end of - file or an error; use encrypted_sys_file_close() to distinguish. - - This function can only be used after encrypted_sys_file_unlock() returns - true. */ -size_t -encrypted_sys_file_read (struct encrypted_sys_file *f, void *buf_, size_t n) -{ - uint8_t *buf = buf_; - size_t ofs = 0; - - if (f->error) - return 0; - - while (ofs < n) - { - unsigned int chunk = MIN (n - ofs, f->n); - if (chunk > 0) - { - memcpy (buf + ofs, &f->plaintext[16 - f->n], chunk); - ofs += chunk; - f->n -= chunk; - } - else - { - if (!fill_buffer (f)) - return ofs; - } - } - - return ofs; -} - -/* Closes F. Returns 0 if no read errors occurred, otherwise a positive errno - value. */ -int -encrypted_sys_file_close (struct encrypted_sys_file *f) -{ - int error = f->error; - if (fclose (f->file) == EOF && !error) - error = errno; - free (f); - - return error; -} - -#define b(x) (1 << (x)) - -static const uint16_t m0[4][2] = { - { b(2), b(2) | b(3) | b(6) | b(7) }, - { b(3), b(0) | b(1) | b(4) | b(5) }, - { b(4) | b(7), b(8) | b(9) | b(12) | b(14) }, - { b(5) | b(6), b(10) | b(11) | b(14) | b(15) }, -}; - -static const uint16_t m1[4][2] = { - { b(0) | b(3) | b(12) | b(15), b(0) | b(1) | b(4) | b(5) }, - { b(1) | b(2) | b(13) | b(14), b(2) | b(3) | b(6) | b(7) }, - { b(4) | b(7) | b(8) | b(11), b(8) | b(9) | b(12) | b(13) }, - { b(5) | b(6) | b(9) | b(10), b(10) | b(11) | b(14) | b(15) }, -}; - -static const uint16_t m2[4][2] = { - { b(2), b(1) | b(3) | b(9) | b(11) }, - { b(3), b(0) | b(2) | b(8) | b(10) }, - { b(4) | b(7), b(4) | b(6) | b(12) | b(14) }, - { b(5) | b(6), b(5) | b(7) | b(13) | b(15) }, -}; - -static const uint16_t m3[4][2] = { - { b(0) | b(3) | b(12) | b(15), b(0) | b(2) | b(8) | b(10) }, - { b(1) | b(2) | b(13) | b(14), b(1) | b(3) | b(9) | b(11) }, - { b(4) | b(7) | b(8) | b(11), b(4) | b(6) | b(12) | b(14) }, - { b(5) | b(6) | b(9) | b(10), b(5) | b(7) | b(13) | b(15) }, -}; - -static int -decode_nibble (const uint16_t table[4][2], int nibble) -{ - int i; - - for (i = 0; i < 4; i++) - if (table[i][0] & (1 << nibble)) - return table[i][1]; - - return 0; -} - -/* Returns true if X has exactly one 1-bit, false otherwise. */ -static bool -is_pow2 (int x) -{ - return x && (x & (x - 1)) == 0; -} - -/* If X has exactly one 1-bit, returns its index, where bit 0 is the LSB. - Otherwise, returns 0. */ -static int -find_1bit (uint16_t x) -{ - int i; - - if (!is_pow2 (x)) - return -1; - - for (i = 0; i < 16; i++) - if (x & (1u << i)) - return i; - - abort (); -} - -/* Attempts to decode a pair of encoded password characters A and B into a - single byte of the plaintext password. Returns 0 if A and B are not a valid - encoded password pair, otherwise a byte of the plaintext password. */ -static int -decode_password_2bytes (uint8_t a, uint8_t b) -{ - int x = find_1bit (decode_nibble (m0, a >> 4) & decode_nibble (m2, b >> 4)); - int y = find_1bit (decode_nibble (m1, a & 15) & decode_nibble (m3, b & 15)); - return x < 0 || y < 0 ? 0 : (x << 4) | y; -} - -/* Decodes an SPSS so-called "encrypted" password INPUT into OUTPUT. - - An encoded password is always an even number of bytes long and no longer - than 20 bytes. A decoded password is never longer than 10 bytes plus a null - terminator. - - Returns true if successful, otherwise false. */ -static bool -decode_password (const char *input, char output[11]) -{ - size_t len; - - len = strlen (input); - if (len > 20 || len % 2) - return false; - - for (; *input; input += 2) - { - int c = decode_password_2bytes (input[0], input[1]); - if (!c) - return false; - *output++ = c; - } - *output = '\0'; - - return true; -} - -/* If CIPHERTEXT is the first ciphertext block in an encrypted .sav file for - PASSWORD, initializes rk[] and returns an nonzero Nr value. - - Otherwise, returns zero. */ -static bool -try_password(struct encrypted_sys_file *f, const char *password) -{ - /* NIST SP 800-108 fixed data. */ - static const uint8_t fixed[] = { - /* i */ - 0x00, 0x00, 0x00, 0x01, - - /* label */ - 0x35, 0x27, 0x13, 0xcc, 0x53, 0xa7, 0x78, 0x89, - 0x87, 0x53, 0x22, 0x11, 0xd6, 0x5b, 0x31, 0x58, - 0xdc, 0xfe, 0x2e, 0x7e, 0x94, 0xda, 0x2f, 0x00, - 0xcc, 0x15, 0x71, 0x80, 0x0a, 0x6c, 0x63, 0x53, - - /* delimiter */ - 0x00, - - /* context */ - 0x38, 0xc3, 0x38, 0xac, 0x22, 0xf3, 0x63, 0x62, - 0x0e, 0xce, 0x85, 0x3f, 0xb8, 0x07, 0x4c, 0x4e, - 0x2b, 0x77, 0xc7, 0x21, 0xf5, 0x1a, 0x80, 0x1d, - 0x67, 0xfb, 0xe1, 0xe1, 0x83, 0x07, 0xd8, 0x0d, - - /* L */ - 0x00, 0x00, 0x01, 0x00, - }; - - char padded_password[32]; - size_t password_len; - uint8_t cmac[16]; - uint8_t key[32]; - - /* Truncate password to at most 10 bytes. */ - password_len = strlen (password); - if (password_len > 10) - password_len = 10; - - /* padded_password = password padded with zeros to 32 bytes. */ - memset (padded_password, 0, sizeof padded_password); - memcpy (padded_password, password, password_len); - - /* cmac = CMAC(padded_password, fixed). */ - cmac_aes256 (CHAR_CAST (const uint8_t *, padded_password), - fixed, sizeof fixed, cmac); - - /* The key is the cmac repeated twice. */ - memcpy(key, cmac, 16); - memcpy(key + 16, cmac, 16); - - /* Use key to initialize AES. */ - assert (sizeof key == 32); - f->Nr = rijndaelKeySetupDec (f->rk, CHAR_CAST (const char *, key), 256); - - /* Check for magic number "$FL" always present in SPSS .sav file. */ - rijndaelDecrypt (f->rk, f->Nr, - CHAR_CAST (const char *, f->ciphertext), - CHAR_CAST (char *, f->plaintext)); - return !memcmp (f->plaintext, "$FL", 3); -} - -static bool -fill_buffer (struct encrypted_sys_file *f) -{ - f->n = fread (f->ciphertext, 1, sizeof f->ciphertext, f->file); - if (f->n == sizeof f->ciphertext) - { - rijndaelDecrypt (f->rk, f->Nr, - CHAR_CAST (const char *, f->ciphertext), - CHAR_CAST (char *, f->plaintext)); - return true; - } - else - { - if (ferror (f->file)) - f->error = errno; - return false; - } -} diff --git a/src/data/sys-file-encryption.h b/src/data/sys-file-encryption.h deleted file mode 100644 index b406cb2644..0000000000 --- a/src/data/sys-file-encryption.h +++ /dev/null @@ -1,34 +0,0 @@ -/* PSPP - a program for statistical analysis. - Copyright (C) 2013 Free Software Foundation, Inc. - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . */ - -#ifndef SYS_FILE_ENCRYPTION_H -#define SYS_FILE_ENCRYPTION_H 1 - -#include -#include - -/* Reading encrypted system files. */ - -struct encrypted_sys_file; - -int encrypted_sys_file_open (struct encrypted_sys_file **, - const char *filename); -bool encrypted_sys_file_unlock (struct encrypted_sys_file *, - const char *password); -size_t encrypted_sys_file_read (struct encrypted_sys_file *, void *, size_t); -int encrypted_sys_file_close (struct encrypted_sys_file *); - -#endif /* sys-file-encryption.h */ diff --git a/tests/automake.mk b/tests/automake.mk index 3b9a553503..188ffa3792 100644 --- a/tests/automake.mk +++ b/tests/automake.mk @@ -283,7 +283,7 @@ TESTSUITE_AT = \ tests/data/por-file.at \ tests/data/sys-file-reader.at \ tests/data/sys-file.at \ - tests/data/sys-file-encryption.at \ + tests/data/encrypted-file.at \ tests/language/command.at \ tests/language/control/do-if.at \ tests/language/control/do-repeat.at \ diff --git a/tests/data/encrypted-file.at b/tests/data/encrypted-file.at new file mode 100644 index 0000000000..241e4e59bb --- /dev/null +++ b/tests/data/encrypted-file.at @@ -0,0 +1,51 @@ +AT_BANNER([encrypted files]) + +AT_SETUP([decrypt an encrypted system file]) +AT_KEYWORDS([system file decrypt pspp-convert]) +AT_CHECK([pspp-convert $srcdir/data/hotel-encrypted.sav hotel.sav -p pspp]) +AT_CHECK([pspp-convert hotel.sav hotel.csv]) +AT_CHECK([cat hotel.csv], [0], [dnl +v1,v2,v3,v4,v5 +4,2,3,4,1 +1,1,3,1,1 +5,2,2,3,4 +3,1,3,1,2 +5,3,1,5,3 +1,2,5,4,2 +3,2,4,3,1 +1,4,5,2,1 +3,2,3,1,2 +2,5,4,2,1 +4,2,2,3,5 +2,1,4,1,1 +1,2,5,5,2 +2,3,3,3,1 +4,1,1,1,3 +1,1,5,1,2 +2,5,5,2,2 +]) +AT_CLEANUP + +AT_SETUP([decrypt an encrypted syntax file]) +AT_KEYWORDS([syntax file decrypt pspp-convert]) +AT_CHECK([pspp-convert $srcdir/data/test-encrypted.sps test.sps -p password]) + +# The sample file is not ideal: lines end in CRLF and its last line +# lacks a new-line, so "sed" and "echo" make it easier to work with. +AT_CHECK([sed 's/ //' test.sps; echo], [0], [dnl +* Encoding: windows-1252. +DATA LIST LIST /name (a25) quantity (f8). +BEGIN DATA. +widgets 10345 +oojars 2345 +dubreys 98 +thingumies 518 +END DATA. + @&t@ +LIST. + @&t@ +DESCRIPTIVES /quantity + /statistics ALL. +]) +AT_CLEANUP + diff --git a/tests/data/sys-file-encryption.at b/tests/data/sys-file-encryption.at deleted file mode 100644 index 337595d3ee..0000000000 --- a/tests/data/sys-file-encryption.at +++ /dev/null @@ -1,27 +0,0 @@ -AT_BANNER([system file encryption]) - -AT_SETUP([decrypt an encrypted system file]) -AT_KEYWORDS([system file decrypt pspp-convert]) -AT_CHECK([pspp-convert $srcdir/data/hotel-encrypted.sav hotel.sav -p pspp]) -AT_CHECK([pspp-convert hotel.sav hotel.csv]) -AT_CHECK([cat hotel.csv], [0], [dnl -v1,v2,v3,v4,v5 -4,2,3,4,1 -1,1,3,1,1 -5,2,2,3,4 -3,1,3,1,2 -5,3,1,5,3 -1,2,5,4,2 -3,2,4,3,1 -1,4,5,2,1 -3,2,3,1,2 -2,5,4,2,1 -4,2,2,3,5 -2,1,4,1,1 -1,2,5,5,2 -2,3,3,3,1 -4,1,1,1,3 -1,1,5,1,2 -2,5,5,2,2 -]) -AT_CLEANUP diff --git a/tests/data/test-encrypted.sps b/tests/data/test-encrypted.sps new file mode 100644 index 0000000000..58ed181f3a Binary files /dev/null and b/tests/data/test-encrypted.sps differ diff --git a/utilities/pspp-convert.1 b/utilities/pspp-convert.1 index 2dc608980e..bbbdd62a4c 100644 --- a/utilities/pspp-convert.1 +++ b/utilities/pspp-convert.1 @@ -7,7 +7,7 @@ .TH pspp\-convert 1 "October 2013" "PSPP" "PSPP Manual" . .SH NAME -pspp\-convert \- convert SPSS system and portable files to other formats +pspp\-convert \- convert SPSS files to other formats . .SH SYNOPSIS \fBpspp\-convert\fR [\fIoptions\fR] \fIinput\fR \fIoutput\fR @@ -18,7 +18,8 @@ pspp\-convert \- convert SPSS system and portable files to other formats . .SH DESCRIPTION The \fBpspp\-convert\fR program reads \fIinput\fR, which may be an -SPSS system file, an SPSS/PC+ system file, or an SPSS portable file, +SPSS system file, an SPSS/PC+ system file, an SPSS portable file, +or an encrypted SPSS syntax file, and writes it to \fIoutput\fR, performing format conversion as necessary. .PP @@ -44,14 +45,19 @@ SPSS system file. .IP \fBpor\fR SPSS portable file. . +.IP \fBsps\fR +SPSS syntax file. (Only encrypted syntax files may be converted to +this format.) +. .PP Use \fB\-O \fIextension\fR to override the inferred format or to specify the format for unrecognized extensions. . .PP \fBpspp\-convert\fR can convert most input formats to most output -formats, with one exception: if the input file is an encrypted system -file, then the output file must also be an (unencrypted) system file. +formats. Encrypted system file and syntax files are exceptions: if +the input file is in an encrypted format, then the output file must +be the same format (decrypted). . .SH "OPTIONS" . @@ -75,7 +81,8 @@ do not self-identify their encoding. . .IP "\fB\-p \fIpassword\fR" .IQ "\fB\-\-password=\fIpassword\fR" -Specifies the password to use to decrypt an encrypted SPSS system file +Specifies the password to use to decrypt encrypted SPSS system file +or syntax file \fIinput\fR. If this option is not specified, \fBpspp\-convert\fR prompts for the password. . diff --git a/utilities/pspp-convert.c b/utilities/pspp-convert.c index ebd340ec35..264ec7a705 100644 --- a/utilities/pspp-convert.c +++ b/utilities/pspp-convert.c @@ -1,5 +1,5 @@ /* PSPP - a program for statistical analysis. - Copyright (C) 2013, 2014 Free Software Foundation, Inc. + Copyright (C) 2013, 2014, 2015 Free Software Foundation, Inc. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -26,10 +26,10 @@ #include "data/casereader.h" #include "data/casewriter.h" #include "data/csv-file-writer.h" +#include "data/encrypted-file.h" #include "data/file-name.h" #include "data/por-file-writer.h" #include "data/settings.h" -#include "data/sys-file-encryption.h" #include "data/sys-file-writer.h" #include "data/file-handle-def.h" #include "libpspp/assertion.h" @@ -46,10 +46,10 @@ static void usage (void); -static void decrypt_sav_file (struct encrypted_sys_file *enc, - const char *input_filename, - const char *output_filename, - const char *password); +static void decrypt_file (struct encrypted_file *enc, + const char *input_filename, + const char *output_filename, + const char *password); int main (int argc, char *argv[]) @@ -62,7 +62,7 @@ main (int argc, char *argv[]) struct casereader *reader; struct file_handle *input_fh; const char *encoding = NULL; - struct encrypted_sys_file *enc; + struct encrypted_file *enc; const char *output_format = NULL; struct file_handle *output_fh; @@ -145,13 +145,22 @@ main (int argc, char *argv[]) output_format = dot + 1; } - if (encrypted_sys_file_open (&enc, input_filename) > 0) + if (encrypted_file_open (&enc, input_filename) > 0) { - if (strcmp (output_format, "sav") && strcmp (output_format, "sys")) - error (1, 0, _("can only convert encrypted data file to sav or sys " - "format")); + if (encrypted_file_is_sav (enc)) + { + if (strcmp (output_format, "sav") && strcmp (output_format, "sys")) + error (1, 0, _("can only convert encrypted data file to sav or " + "sys format")); + } + else + { + if (strcmp (output_format, "sps")) + error (1, 0, _("can only convert encrypted syntax file to sps " + "format")); + } - decrypt_sav_file (enc, input_filename, output_filename, password); + decrypt_file (enc, input_filename, output_filename, password); goto exit; } @@ -214,10 +223,10 @@ exit: } static void -decrypt_sav_file (struct encrypted_sys_file *enc, - const char *input_filename, - const char *output_filename, - const char *password) +decrypt_file (struct encrypted_file *enc, + const char *input_filename, + const char *output_filename, + const char *password) { FILE *out; int err; @@ -229,7 +238,7 @@ decrypt_sav_file (struct encrypted_sys_file *enc, exit (1); } - if (!encrypted_sys_file_unlock (enc, password)) + if (!encrypted_file_unlock (enc, password)) error (1, 0, _("sorry, wrong password")); out = fn_open (output_filename, "wb"); @@ -241,7 +250,7 @@ decrypt_sav_file (struct encrypted_sys_file *enc, uint8_t buffer[1024]; size_t n; - n = encrypted_sys_file_read (enc, buffer, sizeof buffer); + n = encrypted_file_read (enc, buffer, sizeof buffer); if (n == 0) break; @@ -249,7 +258,7 @@ decrypt_sav_file (struct encrypted_sys_file *enc, error (1, errno, ("%s: write error"), output_filename); } - err = encrypted_sys_file_close (enc); + err = encrypted_file_close (enc); if (err) error (1, err, ("%s: read error"), input_filename); @@ -264,20 +273,21 @@ usage (void) printf ("\ %s, a utility for converting SPSS data files to other formats.\n\ Usage: %s [OPTION]... INPUT OUTPUT\n\ -where INPUT is an SPSS system or portable file\n\ +where INPUT is an SPSS data file or encrypted syntax file\n\ and OUTPUT is the name of the desired output file.\n\ \n\ The desired format of OUTPUT is by default inferred from its extension:\n\ csv txt comma-separated value\n\ sav sys SPSS system file\n\ por SPSS portable file\n\ + sps SPSS syntax file (encrypted syntax input files only)\n\ \n\ Options:\n\ -O, --output-format=FORMAT set specific output format, where FORMAT\n\ is one of the extensions listed above\n\ -e, --encoding=CHARSET override encoding of input data file\n\ -c MAXCASES limit number of cases to copy (default is all cases)\n\ - -p PASSWORD password for encrypted .sav files\n\ + -p PASSWORD password for encrypted files\n\ --help display this help and exit\n\ --version output version information and exit\n", program_name, program_name);