From: Landon Fuller Date: Tue, 17 Jan 2006 06:26:20 +0000 (+0000) Subject: Support for FD-side file encryption. X-Git-Tag: Release-7.0.0~8171 X-Git-Url: https://git.sur5r.net/?a=commitdiff_plain;h=a0452019ed5636a46042f0fafef7d30fd1f7d785;p=bacula%2Fbacula Support for FD-side file encryption. - Added MIN() and MAX() macros to baconfig.h - Added stream types for win32, file, and macos encrypted data (with and without gzip) - Added FO_ENCRYPT file flag. - Implement crypto_cipher_* encryption functions - Add encryption logic to save_file() Decryption support has not been committed (yet): If you use this code to encrypt your data, you will be unable to decrypt it! git-svn-id: https://bacula.svn.sourceforge.net/svnroot/bacula/trunk@2755 91ce42f0-d328-0410-95d8-f526ca767f89 --- diff --git a/bacula/src/baconfig.h b/bacula/src/baconfig.h index ca29db2897..0e1ce65627 100644 --- a/bacula/src/baconfig.h +++ b/bacula/src/baconfig.h @@ -30,6 +30,13 @@ #define TRUE 1 #define FALSE 0 +#ifndef MAX +#define MAX(a, b) ((a) > (b) ? (a) : (b)) +#endif +#ifndef MIN +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#endif + #ifdef HAVE_TLS #define have_tls 1 #else @@ -166,12 +173,15 @@ #define STREAM_UNIX_ATTRIBUTES_ACCESS_ACL 15 /* Standard ACL attributes on UNIX */ #define STREAM_UNIX_ATTRIBUTES_DEFAULT_ACL 16 /* Default ACL attributes on UNIX */ /*** FIXME ***/ -#define STREAM_SHA256_DIGEST 17 /* SHA-256 digest for the file */ -#define STREAM_SHA512_DIGEST 18 /* SHA-512 digest for the file */ -#define STREAM_SIGNED_DIGEST 19 /* Signed File Digest, ASN.1, DER Encoded */ -#define STREAM_ENCRYPTED_FILE_DATA 20 /* Encrypted, uncompressed data */ -#define STREAM_ENCRYPTED_WIN32_DATA 21 /* Encrypted, uncompressed Win32 BackupRead data */ -#define STREAM_ENCRYPTED_SESSION_DATA 22 /* Encrypted Session Data, ASN.1, DER Encoded */ +#define STREAM_SHA256_DIGEST 17 /* SHA-256 digest for the file */ +#define STREAM_SHA512_DIGEST 18 /* SHA-512 digest for the file */ +#define STREAM_SIGNED_DIGEST 19 /* Signed File Digest, ASN.1, DER Encoded */ +#define STREAM_ENCRYPTED_FILE_DATA 20 /* Encrypted, uncompressed data */ +#define STREAM_ENCRYPTED_WIN32_DATA 21 /* Encrypted, uncompressed Win32 BackupRead data */ +#define STREAM_ENCRYPTED_SESSION_DATA 22 /* Encrypted Session Data, ASN.1, DER Encoded */ +#define STREAM_ENCRYPTED_FILE_GZIP_DATA 23 /* Encrypted, compressed data */ +#define STREAM_ENCRYPTED_WIN32_GZIP_DATA 24 /* Encrypted, compressed Win32 BackupRead data */ +#define STREAM_ENCRYPTED_MACOS_FORK_DATA 25 /* Encrypted, uncompressed Mac resource fork */ /* diff --git a/bacula/src/filed/backup.c b/bacula/src/filed/backup.c index 6812d8cf74..7f277ba9da 100644 --- a/bacula/src/filed/backup.c +++ b/bacula/src/filed/backup.c @@ -110,6 +110,9 @@ bool blast_data_to_storage_daemon(JCR *jcr, char *addr) /* ... and store the encoded size */ jcr->pki_session_encoded_size = size; + + /* Allocate the encryption/decryption buffer */ + jcr->crypto_buf = get_memory(CRYPTO_CIPHER_MAX_BLOCK_SIZE); } Dmsg1(300, "set_find_options ff=%p\n", jcr->ff); @@ -141,6 +144,10 @@ bool blast_data_to_storage_daemon(JCR *jcr, char *addr) free_pool_memory(jcr->compress_buf); jcr->compress_buf = NULL; } + if (jcr->crypto_buf) { + free_pool_memory(jcr->crypto_buf); + jcr->crypto_buf = NULL; + } if (jcr->pki_session) { crypto_session_free(jcr->pki_session); @@ -308,7 +315,6 @@ static int save_file(FF_PKT *ff_pkt, void *vjcr, bool top_level) if (jcr->pki_sign) { signing_digest = crypto_digest_new(signing_algorithm); } - /* Full-stop if a failure occured initializing the signature digest */ if (jcr->pki_sign && signing_digest == NULL) { Jmsg(jcr, M_NOTSAVED, 0, _("%s signature digest initialization failed\n"), @@ -317,6 +323,11 @@ static int save_file(FF_PKT *ff_pkt, void *vjcr, bool top_level) return 1; } + /* Enable encryption */ + if (jcr->pki_encrypt) { + ff_pkt->flags |= FO_ENCRYPT; + } + /* Initialise the file descriptor we use for data and other streams. */ binit(&ff_pkt->bfd); if (ff_pkt->flags & FO_PORTABLE) { @@ -544,6 +555,11 @@ int send_data(JCR *jcr, int stream, FF_PKT *ff_pkt, DIGEST *digest, DIGEST *sign char *rbuf, *wbuf; int rsize = jcr->buf_size; /* read buffer size */ POOLMEM *msgsave; + CIPHER_CONTEXT *cipher_ctx = NULL; /* Quell bogus uninitialized warnings */ + const void *cipher_input; + size_t cipher_input_len; + size_t cipher_block_size; + size_t encrypted_len; #ifdef FD_NO_SEND_TEST return 1; #endif @@ -551,11 +567,11 @@ int send_data(JCR *jcr, int stream, FF_PKT *ff_pkt, DIGEST *digest, DIGEST *sign msgsave = sd->msg; rbuf = sd->msg; /* read buffer */ wbuf = sd->msg; /* write buffer */ + cipher_input = rbuf; /* encrypt uncompressed data */ Dmsg1(300, "Saving data, type=%d\n", ff_pkt->type); - #ifdef HAVE_LIBZ uLong compress_len, max_compress_len = 0; const Bytef *cbuf = NULL; @@ -569,9 +585,30 @@ int send_data(JCR *jcr, int stream, FF_PKT *ff_pkt, DIGEST *digest, DIGEST *sign max_compress_len = jcr->compress_buf_size; /* set max length */ } wbuf = jcr->compress_buf; /* compressed output here */ + cipher_input = jcr->compress_buf; /* encrypt compressed data */ } #endif + if (ff_pkt->flags & FO_ENCRYPT) { + /* Allocate the cipher context */ + if ((cipher_ctx = crypto_cipher_new(jcr->pki_session, true, &cipher_block_size)) == NULL) { + /* Shouldn't happen! */ + Jmsg0(jcr, M_FATAL, 0, _("Failed to initialize encryption context")); + goto err; + } + + /* + * Grow the crypto buffer, if necessary. + * crypto_cipher_update() will buffer up to (cipher_block_size - 1). + * We grow crypto_buf to the maximum number of blocks that + * could be returned for the given read buffer size. + * (Using the larger of either rsize or max_compress_len) + */ + jcr->crypto_buf = check_pool_memory_size(jcr->crypto_buf, (MAX((size_t) rsize, max_compress_len) + cipher_block_size - 1) / cipher_block_size * cipher_block_size); + + wbuf = jcr->crypto_buf; /* Encrypted, possibly compressed output here. */ + } + /* * Send Data header to Storage daemon * @@ -579,7 +616,7 @@ int send_data(JCR *jcr, int stream, FF_PKT *ff_pkt, DIGEST *digest, DIGEST *sign if (!bnet_fsend(sd, "%ld %d 0", jcr->JobFiles, stream)) { Jmsg1(jcr, M_FATAL, 0, _("Network send error to SD. ERR=%s\n"), bnet_strerror(sd)); - return 0; + goto err; } Dmsg1(300, ">stored: datahdr %s\n", sd->msg); @@ -628,6 +665,9 @@ int send_data(JCR *jcr, int stream, FF_PKT *ff_pkt, DIGEST *digest, DIGEST *sign jcr->ReadBytes += sd->msglen; /* count bytes read */ fileAddr += sd->msglen; + /* Uncompressed cipher input length */ + cipher_input_len = sd->msglen; + /* Update checksum if requested */ if (digest) { crypto_digest_update(digest, rbuf, sd->msglen); @@ -650,18 +690,34 @@ int send_data(JCR *jcr, int stream, FF_PKT *ff_pkt, DIGEST *digest, DIGEST *sign (const Bytef *)rbuf, (uLong)sd->msglen, ff_pkt->GZIP_level)) != Z_OK) { Jmsg(jcr, M_FATAL, 0, _("Compression error: %d\n"), zstat); - sd->msg = msgsave; - sd->msglen = 0; set_jcr_job_status(jcr, JS_ErrorTerminated); - return 0; + goto err; } Dmsg2(400, "compressed len=%d uncompressed len=%d\n", compress_len, sd->msglen); sd->msglen = compress_len; /* set compressed length */ + cipher_input_len = compress_len; } #endif + if (ff_pkt->flags & FO_ENCRYPT) { + /* Encrypt the input block */ + if (crypto_cipher_update(cipher_ctx, cipher_input, cipher_input_len, jcr->crypto_buf, &encrypted_len)) { + if (encrypted_len == 0) { + /* No full block of data available, read more data */ + continue; + } + Dmsg2(400, "encrypted len=%d unencrypted len=%d\n", + encrypted_len, sd->msglen); + sd->msglen = encrypted_len; /* set encrypted length */ + } else { + /* Encryption failed. Shouldn't happen. */ + Jmsg(jcr, M_FATAL, 0, _("Encryption error\n")); + goto err; + } + } + /* Send the buffer to the Storage daemon */ if (!sparseBlock) { if (ff_pkt->flags & FO_SPARSE) { @@ -671,18 +727,42 @@ int send_data(JCR *jcr, int stream, FF_PKT *ff_pkt, DIGEST *digest, DIGEST *sign if (!bnet_send(sd)) { Jmsg1(jcr, M_FATAL, 0, _("Network send error to SD. ERR=%s\n"), bnet_strerror(sd)); - sd->msg = msgsave; /* restore bnet buffer */ - sd->msglen = 0; - return 0; + goto err; } } Dmsg1(130, "Send data to SD len=%d\n", sd->msglen); /* #endif */ - jcr->JobBytes += sd->msglen; /* count bytes saved possibly compressed */ + jcr->JobBytes += sd->msglen; /* count bytes saved possibly compressed/encrypted */ sd->msg = msgsave; /* restore read buffer */ } /* end while read file data */ + /* Send any remaining encrypted data + padding */ + if (ff_pkt->flags & FO_ENCRYPT) { + if (!crypto_cipher_finalize(cipher_ctx, jcr->crypto_buf, &encrypted_len)) { + /* Padding failed. Shouldn't happen. */ + Jmsg(jcr, M_FATAL, 0, _("Encryption padding error\n")); + goto err; + } + + if (encrypted_len > 0) { + sd->msglen = encrypted_len; /* set encrypted length */ + + /* Send remaining encrypted data to the SD */ + if (ff_pkt->flags & FO_SPARSE) { + sd->msglen += SPARSE_FADDR_SIZE; /* include fileAddr in size */ + } + sd->msg = wbuf; /* set correct write buffer */ + if (!bnet_send(sd)) { + Jmsg1(jcr, M_FATAL, 0, _("Network send error to SD. ERR=%s\n"), + bnet_strerror(sd)); + goto err; + } + Dmsg1(130, "Send data to SD len=%d\n", sd->msglen); + jcr->JobBytes += sd->msglen; /* count bytes saved possibly compressed/encrypted */ + sd->msg = msgsave; /* restore bnet buffer */ + } + } if (sd->msglen < 0) { berrno be; @@ -697,10 +777,23 @@ int send_data(JCR *jcr, int stream, FF_PKT *ff_pkt, DIGEST *digest, DIGEST *sign if (!bnet_sig(sd, BNET_EOD)) { /* indicate end of file data */ Jmsg1(jcr, M_FATAL, 0, _("Network send error to SD. ERR=%s\n"), bnet_strerror(sd)); - return 0; + goto err; + } + + /* Free the cipher context */ + if (cipher_ctx) { + crypto_cipher_free(cipher_ctx); } return 1; + +err: + if (cipher_ctx) { + crypto_cipher_free(cipher_ctx); + } + sd->msg = msgsave; /* restore bnet buffer */ + sd->msglen = 0; + return 0; } /* @@ -770,7 +863,11 @@ static bool encode_and_send_attributes(JCR *jcr, FF_PKT *ff_pkt, int &data_strea #endif /* Find what data stream we will use, then encode the attributes */ - data_stream = select_data_stream(ff_pkt); + if ((data_stream = select_data_stream(ff_pkt)) == STREAM_NONE) { + /* This should not happen */ + Jmsg0(jcr, M_FATAL, 0, _("Invalid file flags, no supported data stream type.\n")); + return false; + } encode_stat(attribs, ff_pkt, data_stream); /* Now possibly extend the attributes */ diff --git a/bacula/src/filed/restore.c b/bacula/src/filed/restore.c index 7a9cb99d1f..c28bcf2dee 100644 --- a/bacula/src/filed/restore.c +++ b/bacula/src/filed/restore.c @@ -329,16 +329,23 @@ void do_restore(JCR *jcr) case STREAM_GZIP_DATA: case STREAM_SPARSE_GZIP_DATA: case STREAM_WIN32_GZIP_DATA: + case STREAM_ENCRYPTED_FILE_DATA: + case STREAM_ENCRYPTED_WIN32_DATA: + case STREAM_ENCRYPTED_FILE_GZIP_DATA: + case STREAM_ENCRYPTED_WIN32_GZIP_DATA: /* Force an expected, consistent stream type here */ if (extract && (prev_stream == stream || prev_stream == STREAM_UNIX_ATTRIBUTES || prev_stream == STREAM_UNIX_ATTRIBUTES_EX || prev_stream == STREAM_ENCRYPTED_SESSION_DATA)) { flags = 0; + if (stream == STREAM_SPARSE_DATA || stream == STREAM_SPARSE_GZIP_DATA) { flags |= FO_SPARSE; } + if (stream == STREAM_GZIP_DATA || stream == STREAM_SPARSE_GZIP_DATA - || stream == STREAM_WIN32_GZIP_DATA) { + || stream == STREAM_WIN32_GZIP_DATA || stream == STREAM_ENCRYPTED_FILE_GZIP_DATA + || stream == STREAM_ENCRYPTED_WIN32_GZIP_DATA) { flags |= FO_GZIP; } @@ -357,6 +364,7 @@ void do_restore(JCR *jcr) /* Resource fork stream - only recorded after a file to be restored */ /* Silently ignore if we cannot write - we already reported that */ + case STREAM_ENCRYPTED_MACOS_FORK_DATA: case STREAM_MACOS_FORK_DATA: #ifdef HAVE_DARWIN_OS if (extract) { diff --git a/bacula/src/findlib/attribs.c b/bacula/src/findlib/attribs.c index 678aa9c71a..3e4f7cd64c 100755 --- a/bacula/src/findlib/attribs.c +++ b/bacula/src/findlib/attribs.c @@ -56,6 +56,15 @@ int select_data_stream(FF_PKT *ff_pkt) { int stream; + /* + * Fix all incompatible options + */ + + /* No sparse option for encrypted data */ + if (ff_pkt->flags & FO_ENCRYPT) { + ff_pkt->flags &= ~FO_SPARSE; + } + /* Note, no sparse option for win32_data */ if (!is_portable_backup(&ff_pkt->bfd)) { stream = STREAM_WIN32_DATA; @@ -65,17 +74,65 @@ int select_data_stream(FF_PKT *ff_pkt) } else { stream = STREAM_FILE_DATA; } + + /* Encryption is only supported for file data */ + if (stream != STREAM_FILE_DATA && stream != STREAM_WIN32_DATA && + stream != STREAM_MACOS_FORK_DATA) { + ff_pkt->flags &= ~FO_ENCRYPT; + } + + /* Compression is not supported for Mac fork data */ + if (stream == STREAM_MACOS_FORK_DATA) { + ff_pkt->flags &= ~FO_GZIP; + } + + /* + * Handle compression and encryption options + */ #ifdef HAVE_LIBZ if (ff_pkt->flags & FO_GZIP) { - if (stream == STREAM_WIN32_DATA) { + switch (stream) { + case STREAM_WIN32_DATA: stream = STREAM_WIN32_GZIP_DATA; - } else if (stream == STREAM_FILE_DATA) { - stream = STREAM_GZIP_DATA; - } else { + break; + case STREAM_SPARSE_DATA: stream = STREAM_SPARSE_GZIP_DATA; + break; + case STREAM_FILE_DATA: + stream = STREAM_GZIP_DATA; + break; + default: + /* All stream types that do not support gzip should clear out + * FO_GZIP above, and this code block should be unreachable. */ + ASSERT(!ff_pkt->flags & FO_GZIP); + return STREAM_NONE; } } #endif +#ifdef HAVE_CRYPTO + if (ff_pkt->flags & FO_ENCRYPT) { + switch (stream) { + case STREAM_WIN32_DATA: + stream = STREAM_ENCRYPTED_WIN32_DATA; + break; + case STREAM_WIN32_GZIP_DATA: + stream = STREAM_ENCRYPTED_WIN32_GZIP_DATA; + break; + case STREAM_FILE_DATA: + stream = STREAM_ENCRYPTED_FILE_DATA; + break; + case STREAM_GZIP_DATA: + stream = STREAM_ENCRYPTED_FILE_GZIP_DATA; + break; + default: + /* All stream types that do not support encryption should clear out + * FO_ENCRYPT above, and this code block should be unreachable. */ + ASSERT(!ff_pkt->flags & FO_ENCRYPT); + return STREAM_NONE; + } + } +#endif + return stream; } diff --git a/bacula/src/findlib/bfile.c b/bacula/src/findlib/bfile.c index f9f66652d9..85b3ffe9e3 100644 --- a/bacula/src/findlib/bfile.c +++ b/bacula/src/findlib/bfile.c @@ -48,6 +48,8 @@ bool is_win32_stream(int stream) switch (stream) { case STREAM_WIN32_DATA: case STREAM_WIN32_GZIP_DATA: + case STREAM_ENCRYPTED_WIN32_DATA: + case STREAM_ENCRYPTED_WIN32_GZIP_DATA: return true; } return false; @@ -92,6 +94,16 @@ const char *stream_to_ascii(int stream) return _("SHA512 digest"); case STREAM_SIGNED_DIGEST: return _("Signed digest"); + case STREAM_ENCRYPTED_FILE_DATA: + return _("Encrypted File data"); + case STREAM_ENCRYPTED_FILE_GZIP_DATA: + return _("Encrypted GZIP data"); + case STREAM_ENCRYPTED_WIN32_DATA: + return _("Encrypted Win32 data"); + case STREAM_ENCRYPTED_WIN32_GZIP_DATA: + return _("Encrypted Win32 GZIP data"); + case STREAM_ENCRYPTED_MACOS_FORK_DATA: + return _("Encrypted HFS+ resource fork"); default: sprintf(buf, "%d", stream); return (const char *)buf; @@ -317,6 +329,7 @@ bool is_restore_stream_supported(int stream) #endif case STREAM_MACOS_FORK_DATA: case STREAM_HFSPLUS_ATTRIBUTES: + case STREAM_ENCRYPTED_MACOS_FORK_DATA: return false; /* Known streams */ @@ -340,6 +353,10 @@ bool is_restore_stream_supported(int stream) #endif #ifdef HAVE_CRYPTO case STREAM_SIGNED_DIGEST: + case STREAM_ENCRYPTED_FILE_DATA: + case STREAM_ENCRYPTED_FILE_GZIP_DATA: + case STREAM_ENCRYPTED_WIN32_DATA: + case STREAM_ENCRYPTED_WIN32_GZIP_DATA: #endif case 0: /* compatibility with old tapes */ return true; @@ -714,10 +731,20 @@ bool is_restore_stream_supported(int stream) case STREAM_SHA256_DIGEST: case STREAM_SHA512_DIGEST: #endif +#ifdef HAVE_CRYPTO + case STREAM_SIGNED_DIGEST: + case STREAM_ENCRYPTED_FILE_DATA: + case STREAM_ENCRYPTED_FILE_GZIP_DATA: + case STREAM_ENCRYPTED_WIN32_DATA: + case STREAM_ENCRYPTED_WIN32_GZIP_DATA: +#endif #ifdef HAVE_DARWIN_OS case STREAM_MACOS_FORK_DATA: case STREAM_HFSPLUS_ATTRIBUTES: -#endif +#ifdef HAVE_CRYPTO + case STREAM_ENCRYPTED_MACOS_FORK_DATA: +#endif /* HAVE_CRYPTO */ +#endif /* HAVE_DARWIN_OS */ case 0: /* compatibility with old tapes */ return true; diff --git a/bacula/src/findlib/find.h b/bacula/src/findlib/find.h index 752d009fe0..d5487aa539 100755 --- a/bacula/src/findlib/find.h +++ b/bacula/src/findlib/find.h @@ -88,6 +88,7 @@ enum { #define FO_WIN32DECOMP (1<<18) /* Use BackupRead decomposition */ #define FO_SHA256 (1<<19) /* Do SHA256 checksum */ #define FO_SHA512 (1<<20) /* Do SHA512 checksum */ +#define FO_ENCRYPT (1<<21) /* Encrypt data stream */ struct s_included_file { struct s_included_file *next; diff --git a/bacula/src/jcr.h b/bacula/src/jcr.h index 4134820639..869bde86a7 100644 --- a/bacula/src/jcr.h +++ b/bacula/src/jcr.h @@ -240,6 +240,7 @@ public: CRYPTO_SESSION *pki_session; /* PKE Public Keys + Symmetric Session Keys */ void *pki_session_encoded; /* Cached DER-encoded copy of pki_session */ size_t pki_session_encoded_size; /* Size of DER-encoded pki_session */ + POOLMEM *crypto_buf; /* Encryption/Decryption buffer */ DIRRES* director; /* Director resource */ #endif /* FILE_DAEMON */ diff --git a/bacula/src/lib/crypto.c b/bacula/src/lib/crypto.c index 55839e5008..f1639e71d6 100644 --- a/bacula/src/lib/crypto.c +++ b/bacula/src/lib/crypto.c @@ -276,6 +276,11 @@ struct Crypto_Session { size_t session_key_len; /* Symmetric session key length */ }; +/* Symmetric Cipher Context */ +struct Cipher_Context { + EVP_CIPHER_CTX ctx; +}; + /* PEM Password Dispatch Context */ typedef struct PEM_CB_Context { CRYPTO_PEM_PASSWD_CB *pem_callback; @@ -1214,6 +1219,115 @@ void crypto_session_free (CRYPTO_SESSION *cs) free(cs); } +/* + * Create a new crypto cipher context with the specified session object + * Returns: A pointer to a CIPHER_CONTEXT object on success. The cipher block size is returned in blocksize. + * NULL on failure. + */ +CIPHER_CONTEXT *crypto_cipher_new (CRYPTO_SESSION *cs, bool encrypt, size_t *blocksize) +{ + CIPHER_CONTEXT *cipher_ctx; + const EVP_CIPHER *ec; + + cipher_ctx = (CIPHER_CONTEXT *) malloc(sizeof(CIPHER_CONTEXT)); + if (!cipher_ctx) { + return NULL; + } + + /* + * Acquire a cipher instance for the given ASN.1 cipher NID + */ + if ((ec = EVP_get_cipherbyobj(cs->cryptoData->contentEncryptionAlgorithm)) == NULL) { + Emsg1(M_ERROR, 0, _("Unsupported contentEncryptionAlgorithm: %d\n"), OBJ_obj2nid(cs->cryptoData->contentEncryptionAlgorithm)); + crypto_cipher_free(cipher_ctx); + return NULL; + } + + /* Initialize the OpenSSL cipher context */ + EVP_CIPHER_CTX_init(&cipher_ctx->ctx); + if (encrypt) { + /* Initialize for encryption */ + if (!EVP_CipherInit_ex(&cipher_ctx->ctx, ec, NULL, NULL, NULL, 1)) { + openssl_post_errors(M_ERROR, _("OpenSSL cipher context initialization failed")); + goto err; + } + } else { + /* Initialize for decryption */ + if (!EVP_CipherInit_ex(&cipher_ctx->ctx, ec, NULL, NULL, NULL, 0)) { + openssl_post_errors(M_ERROR, _("OpenSSL cipher context initialization failed")); + goto err; + } + } + + /* Set the key size */ + if (!EVP_CIPHER_CTX_set_key_length(&cipher_ctx->ctx, cs->session_key_len)) { + openssl_post_errors(M_ERROR, _("Encryption session provided an invalid symmetric key")); + goto err; + } + + /* Validate the IV length */ + if (EVP_CIPHER_iv_length(ec) != M_ASN1_STRING_length(cs->cryptoData->iv)) { + openssl_post_errors(M_ERROR, _("Encryption session provided an invalid IV")); + goto err; + } + + /* Add the key and IV to the cipher context */ + if (!EVP_CipherInit_ex(&cipher_ctx->ctx, NULL, NULL, cs->session_key, M_ASN1_STRING_data(cs->cryptoData->iv), -1)) { + openssl_post_errors(M_ERROR, _("OpenSSL cipher context key/IV initialization failed")); + goto err; + } + + *blocksize = EVP_CIPHER_CTX_block_size(&cipher_ctx->ctx); + return cipher_ctx; + +err: + crypto_cipher_free(cipher_ctx); + return NULL; +} + + +/* + * Encrypt/Decrypt length bytes of data using the provided cipher context + * Returns: true on success, number of bytes output in written + * false on failure + */ +bool crypto_cipher_update (CIPHER_CONTEXT *cipher_ctx, const void *data, size_t length, const void *dest, size_t *written) { + if (!EVP_CipherUpdate(&cipher_ctx->ctx, (unsigned char *) dest, (int *) written, (const unsigned char *) data, length)) { + /* This really shouldn't fail */ + return false; + } else { + return true; + } +} + +/* + * Finalize the cipher context, writing any remaining data and necessary padding + * to dest, and the size in written. + * The result size will either be one block of data or zero. + * + * Returns: true on success + * false on failure + */ +bool crypto_cipher_finalize (CIPHER_CONTEXT *cipher_ctx, void *dest, size_t *written) { + if (!EVP_CipherFinal_ex(&cipher_ctx->ctx, (unsigned char *) dest, (int *) written)) { + /* This really shouldn't fail */ + return false; + } else { + return true; + } +} + + +/* + * Free memory associated with a cipher context. + */ +void crypto_cipher_free (CIPHER_CONTEXT *cipher_ctx) +{ + EVP_CIPHER_CTX_cleanup(&cipher_ctx->ctx); + free (cipher_ctx); +} + + /* * Perform global initialization of OpenSSL * This function is not thread safe. @@ -1403,8 +1517,13 @@ void crypto_keypair_free (X509_KEYPAIR *keypair) { } CRYPTO_SESSION *crypto_session_new (crypto_cipher_t cipher, alist *pubkeys) { return NULL; } void crypto_session_free (CRYPTO_SESSION *cs) { } -bool crypto_session_encode(CRYPTO_SESSION *cs, void *dest, size_t *length) { return false; } -crypto_error_t crypto_session_decode(const void *data, size_t length, alist *keypairs, CRYPTO_SESSION **session) { return CRYPTO_ERROR_INTERNAL; } +bool crypto_session_encode (CRYPTO_SESSION *cs, void *dest, size_t *length) { return false; } +crypto_error_t crypto_session_decode (const void *data, size_t length, alist *keypairs, CRYPTO_SESSION **session) { return CRYPTO_ERROR_INTERNAL; } + +CIPHER_CONTEXT *crypto_cipher_new (CRYPTO_SESSION *cs, bool encrypt, size_t *blocksize) { return NULL; } +bool crypto_cipher_update (CIPHER_CONTEXT *cipher_ctx, const void *data, size_t length, const void *dest, size_t *written) { return false; } +bool crypto_cipher_finalize (CIPHER_CONTEXT *cipher_ctx, void *dest, size_t *written) { return false; } +void crypto_cipher_free (CIPHER_CONTEXT *cipher_ctx) { } #endif /* HAVE_CRYPTO */ diff --git a/bacula/src/lib/crypto.h b/bacula/src/lib/crypto.h index f681f349c9..25c5260e25 100644 --- a/bacula/src/lib/crypto.h +++ b/bacula/src/lib/crypto.h @@ -48,6 +48,9 @@ typedef struct Signature SIGNATURE; /* Opaque PKI Symmetric Key Data Structure */ typedef struct Crypto_Session CRYPTO_SESSION; +/* Opaque Encryption/Decryption Context Structure */ +typedef struct Cipher_Context CIPHER_CONTEXT; + /* PEM Decryption Passphrase Callback */ typedef int (CRYPTO_PEM_PASSWD_CB) (char *buf, int size, const void *userdata); @@ -91,8 +94,9 @@ typedef enum { /* Maximum Message Digest Size */ #ifdef HAVE_OPENSSL -/* Let OpenSSL define it */ -#define CRYPTO_DIGEST_MAX_SIZE EVP_MAX_MD_SIZE +/* Let OpenSSL define a few things */ +#define CRYPTO_DIGEST_MAX_SIZE EVP_MAX_MD_SIZE +#define CRYPTO_CIPHER_MAX_BLOCK_SIZE EVP_MAX_BLOCK_LENGTH #else /* HAVE_OPENSSL */ diff --git a/bacula/src/lib/protos.h b/bacula/src/lib/protos.h index eb6d186345..4d54d50156 100644 --- a/bacula/src/lib/protos.h +++ b/bacula/src/lib/protos.h @@ -132,6 +132,10 @@ void crypto_session_free (CRYPTO_SESSION *cs); bool crypto_session_encode (CRYPTO_SESSION *cs, void *dest, size_t *length); crypto_error_t crypto_session_decode (const void *data, size_t length, alist *keypairs, CRYPTO_SESSION **session); CRYPTO_SESSION * crypto_session_decode (const void *data, size_t length); +CIPHER_CONTEXT * crypto_cipher_new (CRYPTO_SESSION *cs, bool encrypt, size_t *blocksize); +bool crypto_cipher_update (CIPHER_CONTEXT *cipher_ctx, const void *data, size_t length, const void *dest, size_t *written); +bool crypto_cipher_finalize (CIPHER_CONTEXT *cipher_ctx, void *dest, size_t *written); +void crypto_cipher_free (CIPHER_CONTEXT *cipher_ctx); X509_KEYPAIR * crypto_keypair_new (void); X509_KEYPAIR * crypto_keypair_dup (X509_KEYPAIR *keypair); int crypto_keypair_load_cert (X509_KEYPAIR *keypair, const char *file); diff --git a/bacula/src/stored/record.c b/bacula/src/stored/record.c index 05641be2b3..130a2884b7 100644 --- a/bacula/src/stored/record.c +++ b/bacula/src/stored/record.c @@ -117,6 +117,16 @@ const char *stream_to_ascii(char *buf, int stream, int fi) return "SIGNED-DIGEST"; case STREAM_ENCRYPTED_SESSION_DATA: return "ENCRYPTED-SESSION-DATA"; + case STREAM_ENCRYPTED_FILE_DATA: + return "ENCRYPTED-FILE"; + case STREAM_ENCRYPTED_FILE_GZIP_DATA: + return "ENCRYPTED-GZIP"; + case STREAM_ENCRYPTED_WIN32_DATA: + return "ENCRYPTED-WIN32-DATA"; + case STREAM_ENCRYPTED_WIN32_GZIP_DATA: + return "ENCRYPTED-WIN32-GZIP"; + case STREAM_ENCRYPTED_MACOS_FORK_DATA: + return "ENCRYPTED-MACOS-RSRC"; case -STREAM_UNIX_ATTRIBUTES: return "contUATTR"; case -STREAM_FILE_DATA: @@ -153,6 +163,16 @@ const char *stream_to_ascii(char *buf, int stream, int fi) return "contSIGNED-DIGEST"; case -STREAM_ENCRYPTED_SESSION_DATA: return "contENCRYPTED-SESSION-DATA"; + case -STREAM_ENCRYPTED_FILE_DATA: + return "contENCRYPTED-FILE"; + case -STREAM_ENCRYPTED_FILE_GZIP_DATA: + return "contENCRYPTED-GZIP"; + case -STREAM_ENCRYPTED_WIN32_DATA: + return "contENCRYPTED-WIN32-DATA"; + case -STREAM_ENCRYPTED_WIN32_GZIP_DATA: + return "contENCRYPTED-WIN32-GZIP"; + case -STREAM_ENCRYPTED_MACOS_FORK_DATA: + return "contENCRYPTED-MACOS-RSRC"; default: sprintf(buf, "%d", stream); return buf;