X-Git-Url: https://pintos-os.org/cgi-bin/gitweb.cgi?a=blobdiff_plain;f=src%2Fdata%2Fencrypted-file.c;h=c0124cbee9de81493289ad1b8aae9b9c9f39de23;hb=8e36488dd7866df169de48322bdfb4f8b853f8e0;hp=f1074b4c1d159d4ad805ea9b887f40400114e20f;hpb=d6cbbc8d634fa91f050661355139a4e4697e99ab;p=pspp diff --git a/src/data/encrypted-file.c b/src/data/encrypted-file.c index f1074b4c1d..c0124cbee9 100644 --- a/src/data/encrypted-file.c +++ b/src/data/encrypted-file.c @@ -27,6 +27,7 @@ #include "libpspp/cast.h" #include "libpspp/cmac-aes256.h" #include "libpspp/message.h" +#include "libpspp/str.h" #include "gl/minmax.h" #include "gl/rijndael-alg-fst.h" @@ -37,21 +38,20 @@ struct encrypted_file { + const struct file_handle *fh; FILE *file; - enum { SYSTEM, SYNTAX } type; int error; - uint8_t ciphertext[16]; - uint8_t plaintext[16]; - unsigned int ofs, n; + uint8_t ciphertext[256]; + uint8_t plaintext[256]; + unsigned int ofs, n, readable; 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 *); +static void fill_buffer (struct encrypted_file *); /* If FILENAME names an encrypted SPSS file, returns 1 and initializes *FP for further use by the caller. @@ -64,12 +64,14 @@ int encrypted_file_open (struct encrypted_file **fp, const struct file_handle *fh) { struct encrypted_file *f; - char header[36 + 16]; + enum { HEADER_SIZE = 36 }; + char data[HEADER_SIZE + sizeof f->ciphertext]; int retval; int n; f = xmalloc (sizeof *f); f->error = 0; + f->fh = fh; f->file = fn_open (fh, "rb"); if (f->file == NULL) { @@ -79,8 +81,8 @@ encrypted_file_open (struct encrypted_file **fp, const struct file_handle *fh) goto error; } - n = fread (header, 1, sizeof header, f->file); - if (n != sizeof header) + n = fread (data, 1, sizeof data, f->file); + if (n < HEADER_SIZE + 2 * 16) { int error = feof (f->file) ? 0 : errno; if (error) @@ -90,19 +92,16 @@ encrypted_file_open (struct encrypted_file **fp, const struct file_handle *fh) goto error; } - if (!memcmp (header + 8, "ENCRYPTEDSAV", 12)) - f->type = SYSTEM; - else if (!memcmp (header + 8, "ENCRYPTEDSPS", 12)) - f->type = SYNTAX; - else + if (memcmp (data + 8, "ENCRYPTED", 9)) { retval = 0; goto error; } - memcpy (f->ciphertext, header + 36, 16); - f->n = 16; + f->n = n - HEADER_SIZE; + memcpy (f->ciphertext, data + HEADER_SIZE, f->n); f->ofs = 0; + f->readable = 0; *fp = f; return 1; @@ -122,9 +121,9 @@ encrypted_file_unlock (struct encrypted_file *f, const char *password) { char decoded_password[11]; - return (try_password (f, password) + return (encrypted_file_unlock__ (f, password) || (decode_password (password, decoded_password) - && try_password (f, decoded_password))); + && encrypted_file_unlock__ (f, decoded_password))); } /* Attempts to read N bytes of plaintext from F into BUF. Returns the number @@ -139,12 +138,9 @@ 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); + unsigned int chunk = MIN (n - ofs, f->readable - f->ofs); if (chunk > 0) { memcpy (buf + ofs, &f->plaintext[f->ofs], chunk); @@ -153,8 +149,9 @@ encrypted_file_read (struct encrypted_file *f, void *buf_, size_t n) } else { - if (!fill_buffer (f)) - return ofs; + fill_buffer (f); + if (!f->readable) + break; } } @@ -166,21 +163,13 @@ encrypted_file_read (struct encrypted_file *f, void *buf_, size_t n) int encrypted_file_close (struct encrypted_file *f) { - int error = f->error; + int error = f->error > 0 ? f->error : 0; 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)) @@ -287,12 +276,30 @@ decode_password (const char *input, char output[11]) 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. */ +/* Check for magic number at beginning of plaintext decrypted from F. */ static bool -try_password(struct encrypted_file *f, const char *password) +is_good_magic (const struct encrypted_file *f) +{ + char plaintext[16]; + rijndaelDecrypt (f->rk, f->Nr, CHAR_CAST (const char *, f->ciphertext), + plaintext); + + const struct substring magic[] = { + ss_cstr ("$FL2@(#)"), + ss_cstr ("$FL3@(#)"), + ss_cstr ("* Encoding"), + ss_buffer ("PK\3\4\x14\0\x8", 7) + }; + for (size_t i = 0; i < sizeof magic / sizeof *magic; i++) + if (ss_equals (ss_buffer (plaintext, magic[i].length), magic[i])) + return true; + return false; +} + +/* Attempts to use plaintext password PASSWORD to unlock F. Returns true if + successful, otherwise false. */ +bool +encrypted_file_unlock__ (struct encrypted_file *f, const char *password) { /* NIST SP 800-108 fixed data. */ static const uint8_t fixed[] = { @@ -344,35 +351,107 @@ try_password(struct encrypted_file *f, const char *password) 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); + if (!is_good_magic (f)) + return false; + + fill_buffer (f); + return true; } -static bool +/* Checks the 16 bytes of PLAINTEXT for PKCS#7 padding bytes. Returns the + number of padding bytes (between 1 and 16, inclusive), if well formed, + otherwise 0. */ +static int +check_padding (const uint8_t *plaintext) +{ + uint8_t pad = plaintext[15]; + if (pad < 1 || pad > 16) + return 0; + + for (size_t i = 1; i < pad; i++) + if (plaintext[15 - i] != pad) + return 0; + + return pad; +} + +static void fill_buffer (struct encrypted_file *f) { - f->n = fread (f->ciphertext, 1, sizeof f->ciphertext, f->file); + /* Move bytes between f->ciphertext[f->readable] and f->ciphertext[f->n] to + the beginning of f->ciphertext. + + The first time this is called for a given file, it does nothing because + f->readable is initially 0. After that, in steady state f->readable is 16 + less than f->n, so the final 16 bytes of ciphertext become the first 16 + bytes. This is necessary because we don't know until we hit end-of-file + whether padding in the last 16 bytes will require us to discard up to 16 + bytes of data. */ + memmove (f->ciphertext, f->ciphertext + f->readable, f->n - f->readable); + f->n -= f->readable; + f->readable = 0; f->ofs = 0; - if (f->n == sizeof f->ciphertext) + + if (f->error) /* or assert(!f->error)? */ + return; + + /* Read new ciphernext, extending f->n, until we've filled up f->ciphertext + or until we reach end-of-file or encounter an error. + + Afterward, f->error indicates what happened. */ + while (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) + size_t retval = fread (f->ciphertext + f->n, 1, + sizeof f->ciphertext - f->n, f->file); + if (!retval) { - const char *eof = memchr (f->plaintext, '\04', sizeof f->plaintext); - if (eof) - f->n = CHAR_CAST (const uint8_t *, eof) - f->plaintext; + f->error = ferror (f->file) ? errno : EOF; + break; } - return true; + f->n += retval; + } + + /* Calculate the number of readable bytes. If we're at the end of the file, + then we can read everything, otherwise we hold back the last 16 bytes + because they might be padding or not. */ + if (!f->error) + { + assert (f->n == sizeof f->ciphertext); + f->readable = f->n - 16; } else + f->readable = f->n; + + /* If we have an incomplete block then trim it off and complain. */ + unsigned int overhang = f->readable % 16; + if (overhang) + { + assert (f->error); + msg (ME, _("%s: encrypted file corrupted (ends in incomplete %u-byte " + "ciphertext block)"), + fh_get_file_name (f->fh), overhang); + f->error = EIO; + f->readable -= overhang; + } + + /* Decrypt all the blocks we have. */ + for (size_t ofs = 0; ofs < f->readable; ofs += 16) + rijndaelDecrypt (f->rk, f->Nr, + CHAR_CAST (const char *, f->ciphertext + ofs), + CHAR_CAST (char *, f->plaintext + ofs)); + + /* If we're at end of file then check the padding and trim it off. */ + if (f->error == EOF) { - if (ferror (f->file)) - f->error = errno; - return false; + unsigned int pad = check_padding (&f->plaintext[f->n - 16]); + if (!pad) + { + msg (ME, _("%s: encrypted file corrupted (ends with bad padding)"), + fh_get_file_name (f->fh)); + f->error = EIO; + return; + } + + f->readable -= pad; } }