]> git.wh0rd.org - dump.git/blobdiff - common/transformation_ssl.c
Encryption (and compression as plugins) support.
[dump.git] / common / transformation_ssl.c
diff --git a/common/transformation_ssl.c b/common/transformation_ssl.c
new file mode 100644 (file)
index 0000000..b97c150
--- /dev/null
@@ -0,0 +1,519 @@
+#include <stdio.h>
+#include <config.h>
+#include <sys/mman.h>
+
+#ifdef HAVE_OPENSSL
+#include <openssl/err.h>
+#include <openssl/evp.h>
+#include <openssl/rand.h>
+#include <openssl/pkcs12.h>
+#endif
+
+#ifdef HAVE_ZLIB
+#include <zlib.h>
+#endif /* HAVE_ZLIB */
+
+#include "transformation.h"
+
+/*
+ * IMPORTANT: this is an important piece of the puzzle for native
+ * support of encrypted backups but Key management is much more
+ * important and much harder. The best encryption algorithms in the
+ * world won't help you if you have to reveal your one key to everyone.
+ *
+ * IMPORTANT: always comply with local laws! Do not modify the source
+ * to give you stronger encryption than permitted by local law.
+ *
+ * -----------------------
+ * 30-second cryptanalysis
+ * -----------------------
+ *
+ * This is a known-plaintext problem since many of the encrypted blocks
+ * will start with a BSD inode structure. (It will be compressed binary
+ * data but still (mostly) known.) This means that it is very important
+ * to choose a strong IV.
+ *
+ * The dump tape/file is written by multiple slave processes so we don't
+ * have ready access to an intrinsic value such as tape position. That
+ * leaves random salts that are included in the dump buffer. The salt
+ * doesn't need to be cryptographically strong since it will be published.
+ *
+ * The IV is created by calculating the hash of the salt, a function of
+ * the salt and session key, and the salt again.
+ *
+ * The greatest vulnerability is predictable keys (and weak ciphers
+ * required by local laws). The key should also be protected in memory
+ * while dumping and restoring files but it's not as critical since
+ * anyone able to read the key probably has the ability to read the 
+ * filesystem directly.
+ */
+
+/*
+ * Initialize.
+ *
+ * 1. load policy file, select ciphers based on it.
+ * 2. PKCS12 keystore contains certificate (public keys) and
+ *    private keys.
+ * 3. private keys can be password-protected individually, and
+ *    you can also password-protect the entire keystore.
+ * 4. PKCS12 keystores can be manipulated by openssl, java,
+ *    windows apps, more.
+ *
+ * questions:
+ * 1. how to select which keystore? (command line option passed
+ *    in as function argument?
+ * 2. how to select which key, if doing restore?
+ * 3. how to pass keystore password(s), if doing restore?
+ */
+static int
+ssl_initialize(Transformation *xform, int enc)
+{
+#ifdef HAVE_OPENSSL
+       int keylen;
+
+       OpenSSL_add_all_algorithms(); // see below */
+       OpenSSL_add_all_ciphers(); // see below */
+       OpenSSL_add_all_digests(); // see below */
+       ERR_load_crypto_strings();
+       ERR_load_RAND_strings();
+       ERR_load_ERR_strings();
+
+       // read policy file and call those two methods to load acceptable
+       // ciphers and digests. We always want CBC mode, not ECB mode.
+
+/*
+       If we are encrypting we need to
+           1. create a random session key (good random numbers are critical!)
+           2. read the X509 certificates for everyone who needs to be able to read the dump file -
+               extract the public keys. These certificates may be in a PKCS12 keystore which many
+               applications use. OpenSSL also provides a large number of programs for it.
+           3. encrypt the session key with the public keys and put it into another PKCS12 keystore.
+               This keystore will be written to the tape in startnewtape().
+           4. set up the cipher context.
+
+       If we are decrypting we need to
+           1. open our keystore(PKCS12) containing our private keys. The keystore itself may be
+               password protected, the individual keys definitely should be.
+           2. read the PKCS12 keystore from the tape header.
+           3. look for matching key ids. If we can't find a match tell the user.
+           4. we still need to be able to open the private key so get the password somewhere.
+           5. set up the cipher context.
+*/
+
+       // RAND_egd(path);
+
+#endif /* HAVE_OPENSSL */
+
+       return 0;
+}
+
+/*
+ * Shut down.
+ *
+ * The OpenSSL library may be using a physical crypto device so
+ * we need to make sure we properly release it.
+ */
+static int
+ssl_shutdown(Transformation *xform)
+{
+#ifdef HAVE_OPENSSL
+       OPENSSL_cleanse(xform->state.ssl.key, sizeof xform->state.ssl.key);
+
+       munlock(xform, sizeof(Transformation));
+       free(xform);
+
+       /* is this the right method? */
+       EVP_cleanup();
+       RAND_cleanup();
+       free(xform);
+#endif /* HAVE_OPENSSL */
+       return 0;
+}
+
+/*
+ * We need to write new crypto header containing the encrypted
+ * session key.
+ */
+static int
+ssl_startNewTape(Transformation *xform, struct tapebuf *tpbin,
+       unsigned long *destlen)
+{
+#ifdef HAVE_OPENSSL
+       /* write new TS containing PKCS12 containing encrypted session key. */
+#endif /* HAVE_OPENSSL */
+
+       return 0;
+}
+/*
+ * Start slave process. We need to reinitialize the encryption
+ * engine.
+ */
+static int
+ssl_startDiskIOProcess(Transformation *xform)
+{
+#ifdef HAVE_OPENSSL
+       mlock(xform, sizeof(Transformation));
+
+       OpenSSL_add_all_algorithms(); // see below */
+       OpenSSL_add_all_ciphers(); // see below */
+       OpenSSL_add_all_digests(); // see below */
+       ERR_load_crypto_strings();
+       ERR_load_RAND_strings();
+       ERR_load_ERR_strings();
+
+       // Initialize key information with values we obtained from parent
+       // thread's startNewTape().
+
+       xform->state.ssl.dataCtx = EVP_CIPHER_CTX_new();
+       xform->state.ssl.ivCtx = EVP_CIPHER_CTX_new();
+#endif /* HAVE_OPENSSL */
+
+       return 0;
+}
+
+/*
+ * End of slave process. Clear encryption keys, etc.
+ */
+static int
+ssl_endDiskIOProcess(Transformation *xform)
+{
+#ifdef HAVE_OPENSSL
+       EVP_CIPHER_CTX_cleanup(xform->state.ssl.dataCtx);
+       EVP_CIPHER_CTX_cleanup(xform->state.ssl.ivCtx);
+       EVP_CIPHER_CTX_free(xform->state.ssl.dataCtx);
+       EVP_CIPHER_CTX_free(xform->state.ssl.ivCtx);
+
+       OPENSSL_cleanse(xform->state.ssl.key, sizeof xform->state.ssl.key);
+
+       munlock(xform, sizeof(Transformation));
+       free(xform);
+
+       /* is this the right method? */
+       EVP_cleanup();
+       RAND_cleanup();
+#endif /* HAVE_OPENSSL */
+       return 0;
+}
+
+/*
+ * Method to generate 'random' salt and IV.
+ *
+ * The generated salt is 16 bytes long. It is duplicated to get a 256-bit value (to support
+ * 256-bit block ciphers.)
+ *
+ */
+#ifdef HAVE_OPENSSL
+static int
+generateIV(Transformation *xform, unsigned char *salt, unsigned int *saltlen,
+       unsigned char *iv, unsigned int *ivlen)
+{
+       unsigned char ivbuffer[64];
+       unsigned int buflen, y;
+
+       /* we can use pseudorandom bytes since they're going */
+       /* to be exposed to any attacker anyway. */
+       *saltlen = 16;
+       if (xform->enc == 1) {
+               RAND_pseudo_bytes(salt, *saltlen);
+       }
+       memcpy(ivbuffer, salt, 16);
+
+       /* -decrypt- salt value */
+       memset(ivbuffer, 0, sizeof(ivbuffer));
+       EVP_CipherInit_ex(xform->state.ssl.ivCtx, xform->state.ssl.cipher, xform->state.ssl.engine, NULL, NULL, 0);
+       //EVP_CIPHER_CTX_set_key_length(&ctx, 8);
+       EVP_CIPHER_CTX_set_padding(xform->state.ssl.ivCtx, 0);  // -nopad
+       EVP_CipherInit_ex(xform->state.ssl.ivCtx, NULL, NULL, xform->state.ssl.key, ivbuffer, 0);
+       buflen = 32;
+       if (!EVP_CipherUpdate(xform->state.ssl.ivCtx, ivbuffer + 16, &buflen, salt, 16)) {
+               y = 32 - buflen;
+               if (!EVP_CipherFinal(xform->state.ssl.ivCtx, ivbuffer + 16 + buflen, &y)) {
+                       buflen += y;
+               } else {
+                       memset(ivbuffer + 16, 0, 32);
+               }
+       } else {
+               memset(ivbuffer + 16, 0, 32);
+       }
+       memcpy(ivbuffer + 48, salt, 16);
+
+       /* now digest it. */
+       EVP_Digest(ivbuffer, 64, iv, ivlen, xform->state.ssl.digest, NULL);
+
+       return 0;
+}
+#endif
+
+
+/*
+ * Encrypt a single chunk of blocks. Each chunk is encrypted
+ * independently so it's critically important to choose a good
+ * initial vector (iv).
+ *
+ * The ciphertext format is:
+ *  - 20 bytes of random salt
+ *  - encrypted (plaintext . digest(plaintext))
+ */
+static int
+ssl_compress(Transformation *xform, struct tapebuf *tpbin,
+       unsigned long *destlen, const char *src, int srclen)
+{
+#ifdef HAVE_OPENSSL
+       unsigned char salt[16], iv[EVP_MAX_MD_SIZE];
+       unsigned int saltlen = sizeof(salt);
+       unsigned int ivlen = sizeof(iv);
+       unsigned char digest[EVP_MAX_MD_SIZE];
+       unsigned int digestlen = 0;
+       char *buf;
+       unsigned int len, len2;
+
+       len = srclen + 1000;
+       buf = malloc(len);
+
+       digestlen = sizeof(digest);
+
+       /* generate salt, put it in header */
+       generateIV(xform, salt, &saltlen, iv, &ivlen);
+       memcpy(tpbin->buf, salt, saltlen);
+
+       /* compress the buffer first - increase the entropy */
+       int compresult;
+       compresult = compress2(buf, destlen, src, srclen, xform->state.ssl.complvl);
+       if (compresult != Z_OK) {
+               printf("unable to compress...\n");
+               return 0;
+       }
+
+       EVP_EncryptInit_ex(xform->state.ssl.dataCtx, xform->state.ssl.cipher,  xform->state.ssl.engine, NULL, NULL);
+       //EVP_CIPHER_CTX_set_key_length(&ctx, 8);
+       EVP_EncryptInit_ex(xform->state.ssl.dataCtx, NULL, NULL, xform->state.ssl.key, iv);
+
+       // encrypt content.
+       if (!EVP_EncryptUpdate(xform->state.ssl.dataCtx, tpbin->buf + saltlen, &len, buf, *destlen)) {
+               EVP_CIPHER_CTX_cleanup(xform->state.ssl.dataCtx);
+               ERR_print_errors_fp(stdout);
+               free(buf);
+               return 0;
+       }
+
+       // calculate digest, add it.
+       EVP_Digest(src, srclen, digest, &digestlen, xform->state.ssl.digest,  xform->state.ssl.engine);
+
+       len2 = *destlen - len - saltlen;
+       if (!EVP_EncryptUpdate(xform->state.ssl.dataCtx, tpbin->buf + saltlen + len, &len2, digest, digestlen)) {
+               EVP_CIPHER_CTX_cleanup(xform->state.ssl.dataCtx);
+               ERR_print_errors_fp(stdout);
+               free(buf);
+               return 0;
+       }
+
+       len += len2;
+
+       // finish up
+       len2 = *destlen - len - saltlen;
+       if (!EVP_EncryptFinal(xform->state.ssl.dataCtx, tpbin->buf + saltlen + len, &len2)) {
+               EVP_CIPHER_CTX_cleanup(xform->state.ssl.dataCtx);
+               ERR_print_errors_fp(stdout);
+               free(buf);
+               return 0;
+       }
+
+       *destlen = len + len2 + saltlen;
+       free(buf);
+
+       OPENSSL_cleanse(iv, sizeof iv);
+#endif /* HAVE_OPENSSL */
+
+       return 1;
+}
+
+/*
+ *
+ */
+static int
+ssl_decompress(Transformation *xform, struct tapebuf *tpbin,
+       unsigned long *destlen, const char *src, int srclen, char **reason)
+{
+#ifdef HAVE_OPENSSL
+       unsigned char salt[16], iv[EVP_MAX_MD_SIZE];
+       unsigned int saltlen = sizeof(salt);
+       unsigned int ivlen = sizeof(iv);
+       unsigned char digest[EVP_MAX_MD_SIZE];
+       unsigned int digestlen;
+       char *buf;
+       unsigned int len, len2;
+
+       digestlen = EVP_MD_size(xform->state.ssl.digest);
+
+       len = *destlen + 1000;
+       buf = malloc(len);
+
+       // how to know salt length?
+       memcpy(salt, src, saltlen);
+       generateIV(xform, salt, &saltlen, iv, &ivlen);
+
+       EVP_DecryptInit_ex(xform->state.ssl.dataCtx, xform->state.ssl.cipher,  xform->state.ssl.engine, NULL, NULL);
+       //EVP_CIPHER_CTX_set_key_length(&ctx, 8);
+       EVP_DecryptInit_ex(xform->state.ssl.dataCtx, NULL, NULL, xform->state.ssl.key, iv);
+
+       if (!EVP_DecryptUpdate(xform->state.ssl.dataCtx, buf, &len, src+saltlen, srclen-saltlen)) {
+               EVP_CIPHER_CTX_cleanup(xform->state.ssl.dataCtx);
+               free(buf);
+               ERR_print_errors_fp(stdout);
+               printf("error 1\n");
+               return 0;
+       }
+
+       len2 = *destlen + 1000 - len;
+       if (!EVP_DecryptFinal(xform->state.ssl.dataCtx, buf + len, &len2)) {
+               EVP_CIPHER_CTX_cleanup(xform->state.ssl.dataCtx);
+               free(buf);
+               ERR_print_errors_fp(stdout);
+               printf("error 2\n");
+               return 0;
+       }
+       len += len2;
+       len -= digestlen;
+
+       OPENSSL_cleanse(iv, sizeof iv);
+
+       int cresult;
+       cresult = uncompress(tpbin->buf, destlen, buf, len);
+       switch (cresult) {
+               case Z_OK:
+                       *reason = "";
+                       break;
+               case Z_MEM_ERROR:
+                       *reason = "not enough memory";
+                       break;
+               case Z_BUF_ERROR:
+                       *reason = "buffer too small";
+                       break;
+               case Z_DATA_ERROR:
+                       *reason = "data error";
+                       break;
+               default:
+                       *reason = "unknown";
+       }
+       if (cresult != Z_OK) {
+               printf("compression failed: %s\n", *reason);
+               free(buf);
+               return 0;
+       }
+
+       /* verify digest */
+       EVP_Digest(tpbin->buf, *destlen, digest, &digestlen, xform->state.ssl.digest,  xform->state.ssl.engine);
+
+       if (memcmp(buf + len, digest, digestlen)) {
+               *reason = "digests did not match";
+               return 0;
+       }
+
+       free(buf);
+
+#endif /* HAVE_OPENSSL */
+
+       return 1;
+}
+
+/*
+ *
+ */
+#if 0
+static int
+ssl_compress_ts_addr(char *state, struct tapebuf *comp_buf,
+       unsigned int *worklen, char *data, int writesize, int compressed)
+{
+#ifdef HAVE_OPENSSL
+       SSLState *s = (SSLState *) state;
+       unsigned char iv[32];
+       unsigned int len;
+
+       EVP_CIPHER_CTX *ctx = (EVP_CIPHER_CTX *) malloc(sizeof(EVP_CIPHER_CTX));
+
+       //EVP_BytesToKey(cipher, EVP_md5(), NULL, buf, strlen(buf), 1, key, iv);
+
+       //EVP_CIPHER_CTX_init(ctx);
+       //EVP_CipherInit_ex(ctx, cipher, NULL, key, iv, do_encrypt);
+       //blocksize = EVP_CIPHER_CTX_block_size(ctx);
+
+       s->digest = EVP_md_null;
+       s->cipher = EVP_enc_null;
+       EVP_CIPHER_CTX_init(s->ctx);
+
+       /* calculate 'random' IV. */
+       EVP_MD_CTX_init(&md);
+       EVP_DigestInit_ex(s->md, s->md, NULL);
+       /* EVP_DigestUpdate(s->md, x, len);  <-- use logical record number */
+       EVP_DigestUpdate(s->md, s->salt, s->saltlen);
+       len = sizeof(iv);
+       EVP_DigestFinal(s->md, iv, &len);
+
+       // mlock on state info...
+
+       /* do the actual encryption */
+       EVP_CipherInit(s->ctx, s->cipher, s->key, iv);
+       EVP_CipherUpdate(s->ctx, out, &n, buf, buflen);
+       EVP_CipherFinal(s->ctx, out + n, outlen - y);
+       return 1;
+#endif /* HAVE_OPENSSL */
+
+       return 1;
+}
+#endif
+
+
+/*
+ * Factory. The cipher and digestnames should be read from a localized
+ * policy file.
+ *
+ * TODO: indicate error if unknown cipher or digest.
+ */
+Transformation
+*transformation_ssl_factory(int enc, int complvl, const char *ciphername, const char *digestname)
+{
+       int keylen;
+       Transformation *t;
+
+       t = (Transformation *) malloc(sizeof (Transformation));
+       mlock(t, sizeof(Transformation));
+
+       OpenSSL_add_all_ciphers();
+       OpenSSL_add_all_digests();
+
+       t->enc = enc;
+       t->state.ssl.complvl = complvl;
+       t->state.ssl.cipher = EVP_get_cipherbyname(ciphername);
+       t->state.ssl.digest = EVP_get_digestbyname(digestname);
+       t->state.ssl.engine = NULL;
+
+       t->name = "ssl";
+       t->mandatory = 1;
+       t->initialize = &ssl_initialize;
+       t->shutdown = &ssl_shutdown;
+       t->startNewTape = &ssl_startNewTape;
+       t->startDiskIOProcess = &ssl_startDiskIOProcess;
+       t->endDiskIOProcess = &ssl_endDiskIOProcess;
+       t->compress = &ssl_compress;
+       t->decompress = &ssl_decompress;
+
+       // we could use this to generate a key from a passphrase.
+       // using this as the actual encryption key has the problems
+       // discussed elsewhere.
+       //EVP_BytesToKey(cipher, EVP_md5(), NULL, buf, strlen(buf), 1, key, iv);
+
+       if (enc) {
+               /* generate random session key */
+               //EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
+               //EVP_CIPHER_CTX_init(ctx);
+               //EVP_CIPHER_CTX_rand_key(ctx, t->state.ssl.key);
+               //EVP_CIPHER_CTX_cleanup(ctx);
+               //EVP_CIPHER_CTX_free(ctx);
+               RAND_bytes(t->state.ssl.key, t->state.ssl.cipher->key_len);
+       } else {
+               // how do we get keys?
+       }
+
+       return t;
+}