From dd622621ff0407a871df1ebcce9e5991c684f5c3 Mon Sep 17 00:00:00 2001 From: Landon Fuller Date: Sat, 14 Jan 2006 22:23:22 +0000 Subject: [PATCH] - Rename "crypto_recipients" to "crypto_session" to more accurate describe the crypto session data (which includes the symmetric session keys). - Add the STREAM_ENCRYPTED_SESSION_DATA stream type. - Add support for encoding and decoding session data. - Send encoded session data to the storage daemon git-svn-id: https://bacula.svn.sourceforge.net/svnroot/bacula/trunk@2742 91ce42f0-d328-0410-95d8-f526ca767f89 --- bacula/src/baconfig.h | 3 +- bacula/src/filed/backup.c | 57 ++++++++++++++- bacula/src/filed/restore.c | 22 ++++-- bacula/src/filed/verify_vol.c | 1 + bacula/src/jcr.h | 4 +- bacula/src/lib/crypto.c | 134 ++++++++++++++++++++++++---------- bacula/src/lib/crypto.h | 2 +- bacula/src/lib/protos.h | 6 +- bacula/src/stored/bextract.c | 3 +- bacula/src/stored/bscan.c | 9 ++- bacula/src/stored/record.c | 4 + 11 files changed, 188 insertions(+), 57 deletions(-) diff --git a/bacula/src/baconfig.h b/bacula/src/baconfig.h index 749b7ac782..e2233fe562 100644 --- a/bacula/src/baconfig.h +++ b/bacula/src/baconfig.h @@ -166,9 +166,10 @@ /*** 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 Encoded */ +#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 */ /* diff --git a/bacula/src/filed/backup.c b/bacula/src/filed/backup.c index 2394f54426..34fed539da 100644 --- a/bacula/src/filed/backup.c +++ b/bacula/src/filed/backup.c @@ -81,9 +81,35 @@ bool blast_data_to_storage_daemon(JCR *jcr, char *addr) jcr->compress_buf_size = jcr->buf_size + ((jcr->buf_size+999) / 1000) + 30; jcr->compress_buf = get_memory(jcr->compress_buf_size); + /* Create encryption session data and a cached, DER-encoded session data + * structure. We use a single session key for each backup, so we'll encode + * the session data only once. */ if (jcr->pki_encrypt) { + size_t size = 0; + /* Create per-job session encryption context */ - jcr->pki_recipients = crypto_recipients_new(cipher, jcr->pki_readers); + jcr->pki_session = crypto_session_new(cipher, jcr->pki_readers); + + /* Get the session data size */ + if (crypto_session_encode(jcr->pki_session, NULL, &size) == false) { + Jmsg(jcr, M_FATAL, 0, _("An error occured while encrypting the stream.\n")); + return 0; + } + + /* Allocate buffer */ + jcr->pki_session_encoded = malloc(size); + if (!jcr->pki_session_encoded) { + return 0; + } + + /* Encode session data */ + if (crypto_session_encode(jcr->pki_session, jcr->pki_session_encoded, &size) == false) { + Jmsg(jcr, M_FATAL, 0, _("An error occured while encrypting the stream.\n")); + return 0; + } + + /* ... and store the encoded size */ + jcr->pki_session_encoded_size = size; } Dmsg1(300, "set_find_options ff=%p\n", jcr->ff); @@ -116,8 +142,11 @@ bool blast_data_to_storage_daemon(JCR *jcr, char *addr) jcr->compress_buf = NULL; } - if (jcr->pki_recipients) { - crypto_recipients_free(jcr->pki_recipients); + if (jcr->pki_session) { + crypto_session_free(jcr->pki_session); + } + if (jcr->pki_session_encoded) { + free(jcr->pki_session_encoded); } Dmsg1(100, "end blast_data ok=%d\n", ok); @@ -338,6 +367,26 @@ static int save_file(FF_PKT *ff_pkt, void *vjcr, bool top_level) stop_thread_timer(tid); tid = NULL; } + + /* Set up the encryption context, send the session data to the SD */ + if (jcr->pki_encrypt) { + /* Send our header */ + bnet_fsend(sd, "%ld %d 0", jcr->JobFiles, STREAM_ENCRYPTED_SESSION_DATA); + + /* Grow the bsock buffer to fit our message if necessary */ + if ((size_t) sizeof_pool_memory(sd->msg) < jcr->pki_session_encoded_size) { + sd->msg = realloc_pool_memory(sd->msg, jcr->pki_session_encoded_size); + } + + /* Copy our message over and send it */ + memcpy(sd->msg, jcr->pki_session_encoded, jcr->pki_session_encoded_size); + sd->msglen = jcr->pki_session_encoded_size; + jcr->JobBytes += sd->msglen; + + bnet_send(sd); + bnet_sig(sd, BNET_EOD); + } + stat = send_data(jcr, data_stream, ff_pkt, digest, signing_digest); bclose(&ff_pkt->bfd); if (!stat) { @@ -426,7 +475,7 @@ static int save_file(FF_PKT *ff_pkt, void *vjcr, bool top_level) /* Allocate signature data buffer */ buf = malloc(size); if (!buf) { - free(buf); + crypto_sign_free(sig); return 0; } diff --git a/bacula/src/filed/restore.c b/bacula/src/filed/restore.c index 4ca022dee0..3ebd3ab4f4 100644 --- a/bacula/src/filed/restore.c +++ b/bacula/src/filed/restore.c @@ -135,12 +135,13 @@ void do_restore(JCR *jcr) * 1. Stream record header * 2. Stream data * a. Attributes (Unix or Win32) - * or b. File data for the file - * or c. Alternate data stream (e.g. Resource Fork) - * or d. Finder info - * or e. ACLs - * or f. Possibly a cryptographic signature - * or g. Possibly MD5 or SHA1 record + * b. Possibly stream encryption session data (e.g., symmetric session key) + * or c. File data for the file + * or d. Alternate data stream (e.g. Resource Fork) + * or e. Finder info + * or f. ACLs + * or g. Possibly a cryptographic signature + * or h. Possibly MD5 or SHA1 record * 3. Repeat step 1 * * NOTE: We keep track of two bacula file descriptors: @@ -294,6 +295,12 @@ void do_restore(JCR *jcr) break; /* Data stream */ + case STREAM_ENCRYPTED_SESSION_DATA: + // TODO landonf: Implement + // sig = crypto_sign_decode(sd->msg, (size_t) sd->msglen); + Dmsg1(30, "Stream=Encrypted Session Data, size: %d\n", sd->msglen); + break; + case STREAM_FILE_DATA: case STREAM_SPARSE_DATA: case STREAM_WIN32_DATA: @@ -302,7 +309,8 @@ void do_restore(JCR *jcr) case STREAM_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_UNIX_ATTRIBUTES_EX + || prev_stream == STREAM_ENCRYPTED_SESSION_DATA)) { flags = 0; if (stream == STREAM_SPARSE_DATA || stream == STREAM_SPARSE_GZIP_DATA) { flags |= FO_SPARSE; diff --git a/bacula/src/filed/verify_vol.c b/bacula/src/filed/verify_vol.c index 119edf8d6b..abcf922500 100644 --- a/bacula/src/filed/verify_vol.c +++ b/bacula/src/filed/verify_vol.c @@ -204,6 +204,7 @@ void do_verify_volume(JCR *jcr) break; /* Data streams to ignore */ + case STREAM_ENCRYPTED_SESSION_DATA: case STREAM_FILE_DATA: case STREAM_SPARSE_DATA: case STREAM_WIN32_DATA: diff --git a/bacula/src/jcr.h b/bacula/src/jcr.h index fde1868eca..1eb61fc145 100644 --- a/bacula/src/jcr.h +++ b/bacula/src/jcr.h @@ -237,7 +237,9 @@ public: X509_KEYPAIR *pki_keypair; /* Encryption key pair */ alist *pki_signers; /* Trusted Signers */ alist *pki_readers; /* Trusted Readers */ - CRYPTO_RECIPIENTS *pki_recipients; /* PKE Public Keys + Symmetric Session Keys */ + 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 */ DIRRES* director; /* Director resource */ #endif /* FILE_DAEMON */ diff --git a/bacula/src/lib/crypto.c b/bacula/src/lib/crypto.c index 598dcf72aa..fb5efbaa0b 100644 --- a/bacula/src/lib/crypto.c +++ b/bacula/src/lib/crypto.c @@ -269,8 +269,8 @@ struct Signature { SignatureData *sigData; }; -/* Encryption Key Data */ -struct Crypto_Recipients { +/* Encryption Session Data */ +struct Crypto_Session { CryptoData *cryptoData; /* ASN.1 Structure */ EVP_CIPHER *openssl_cipher; /* OpenSSL Cipher Object */ unsigned char session_key[EVP_MAX_KEY_LENGTH]; /* Private symmetric session key */ @@ -875,34 +875,34 @@ void crypto_sign_free(SIGNATURE *sig) } /* - * Create a new encryption recipient. - * Returns: A pointer to a CRYPTO_RECIPIENTS object on success. + * Create a new encryption session. + * Returns: A pointer to a CRYPTO_SESSION object on success. * NULL on failure. */ -CRYPTO_RECIPIENTS *crypto_recipients_new (crypto_cipher_t cipher, alist *pubkeys) +CRYPTO_SESSION *crypto_session_new (crypto_cipher_t cipher, alist *pubkeys) { - CRYPTO_RECIPIENTS *cr; + CRYPTO_SESSION *cs; X509_KEYPAIR *keypair; const EVP_CIPHER *ec; unsigned char *iv; int iv_len; - /* Allocate our recipient description structures */ - cr = (CRYPTO_RECIPIENTS *) malloc(sizeof(CRYPTO_RECIPIENTS)); - if (!cr) { + /* Allocate our session description structures */ + cs = (CRYPTO_SESSION *) malloc(sizeof(CRYPTO_SESSION)); + if (!cs) { return NULL; } - cr->cryptoData = CryptoData_new(); + cs->cryptoData = CryptoData_new(); - if (!cr->cryptoData) { + if (!cs->cryptoData) { /* Allocation failed in OpenSSL */ - free(cr); + free(cs); return NULL; } /* Set the ASN.1 structure version number */ - ASN1_INTEGER_set(cr->cryptoData->version, BACULA_ASN1_VERSION); + ASN1_INTEGER_set(cs->cryptoData->version, BACULA_ASN1_VERSION); /* * Acquire a cipher instance and set the ASN.1 cipher NID @@ -910,35 +910,35 @@ CRYPTO_RECIPIENTS *crypto_recipients_new (crypto_cipher_t cipher, alist *pubkeys switch (cipher) { case CRYPTO_CIPHER_AES_128_CBC: /* AES 128 bit CBC */ - cr->cryptoData->contentEncryptionAlgorithm = OBJ_nid2obj(NID_aes_128_cbc); + cs->cryptoData->contentEncryptionAlgorithm = OBJ_nid2obj(NID_aes_128_cbc); ec = EVP_aes_128_cbc(); break; case CRYPTO_CIPHER_AES_192_CBC: /* AES 192 bit CBC */ - cr->cryptoData->contentEncryptionAlgorithm = OBJ_nid2obj(NID_aes_192_cbc); + cs->cryptoData->contentEncryptionAlgorithm = OBJ_nid2obj(NID_aes_192_cbc); ec = EVP_aes_192_cbc(); break; case CRYPTO_CIPHER_AES_256_CBC: /* AES 256 bit CBC */ - cr->cryptoData->contentEncryptionAlgorithm = OBJ_nid2obj(NID_aes_256_cbc); + cs->cryptoData->contentEncryptionAlgorithm = OBJ_nid2obj(NID_aes_256_cbc); ec = EVP_aes_256_cbc(); break; case CRYPTO_CIPHER_BLOWFISH_CBC: /* Blowfish CBC */ - cr->cryptoData->contentEncryptionAlgorithm = OBJ_nid2obj(NID_bf_cbc); + cs->cryptoData->contentEncryptionAlgorithm = OBJ_nid2obj(NID_bf_cbc); ec = EVP_bf_cbc(); break; default: Emsg0(M_ERROR, 0, _("Unsupported cipher type specified\n")); - crypto_recipients_free(cr); + crypto_session_free(cs); return NULL; } /* Generate a symmetric session key */ - cr->session_key_len = EVP_CIPHER_key_length(ec); - if (RAND_bytes(cr->session_key, cr->session_key_len) <= 0) { + cs->session_key_len = EVP_CIPHER_key_length(ec); + if (RAND_bytes(cs->session_key, cs->session_key_len) <= 0) { /* OpenSSL failure */ - crypto_recipients_free(cr); + crypto_session_free(cs); return NULL; } @@ -947,21 +947,21 @@ CRYPTO_RECIPIENTS *crypto_recipients_new (crypto_cipher_t cipher, alist *pubkeys iv = (unsigned char *) malloc(iv_len); if (!iv) { /* Malloc failure */ - crypto_recipients_free(cr); + crypto_session_free(cs); return NULL; } /* Generate random IV */ if (RAND_bytes(iv, iv_len) <= 0) { /* OpenSSL failure */ - crypto_recipients_free(cr); + crypto_session_free(cs); return NULL; } /* Store it in our ASN.1 structure */ - if (!M_ASN1_OCTET_STRING_set(cr->cryptoData->iv, iv, iv_len)) { + if (!M_ASN1_OCTET_STRING_set(cs->cryptoData->iv, iv, iv_len)) { /* Allocation failed in OpenSSL */ - crypto_recipients_free(cr); + crypto_session_free(cs); return NULL; } } @@ -978,7 +978,7 @@ CRYPTO_RECIPIENTS *crypto_recipients_new (crypto_cipher_t cipher, alist *pubkeys ri = RecipientInfo_new(); if (!ri) { /* Allocation failed in OpenSSL */ - crypto_recipients_free(cr); + crypto_session_free(cs); return NULL; } @@ -997,14 +997,14 @@ CRYPTO_RECIPIENTS *crypto_recipients_new (crypto_cipher_t cipher, alist *pubkeys ekey = (unsigned char *) malloc(EVP_PKEY_size(keypair->pubkey)); if (!ekey) { RecipientInfo_free(ri); - crypto_recipients_free(cr); + crypto_session_free(cs); return NULL; } - if ((ekey_len = EVP_PKEY_encrypt(ekey, cr->session_key, cr->session_key_len, keypair->pubkey)) <= 0) { + if ((ekey_len = EVP_PKEY_encrypt(ekey, cs->session_key, cs->session_key_len, keypair->pubkey)) <= 0) { /* OpenSSL failure */ RecipientInfo_free(ri); - crypto_recipients_free(cr); + crypto_session_free(cs); free(ekey); return NULL; } @@ -1013,7 +1013,7 @@ CRYPTO_RECIPIENTS *crypto_recipients_new (crypto_cipher_t cipher, alist *pubkeys if (!M_ASN1_OCTET_STRING_set(ri->encryptedKey, ekey, ekey_len)) { /* Allocation failed in OpenSSL */ RecipientInfo_free(ri); - crypto_recipients_free(cr); + crypto_session_free(cs); free(ekey); return NULL; } @@ -1022,19 +1022,73 @@ CRYPTO_RECIPIENTS *crypto_recipients_new (crypto_cipher_t cipher, alist *pubkeys free(ekey); /* Push the new RecipientInfo structure onto the stack */ - sk_RecipientInfo_push(cr->cryptoData->recipientInfo, ri); + sk_RecipientInfo_push(cs->cryptoData->recipientInfo, ri); } - return cr; + return cs; } /* - * Free memory associated with a crypto recipient object. + * Encodes the CryptoData structure. The length argument is used to specify the + * size of dest. A length of 0 will cause no data to be written to dest, and the + * required length to be written to length. The caller can then allocate sufficient + * space for the output. + * + * Returns: true on success, stores the encoded data in dest, and the size in length. + * false on failure. + */ +bool crypto_session_encode(CRYPTO_SESSION *cs, void *dest, size_t *length) +{ + if (*length == 0) { + *length = i2d_CryptoData(cs->cryptoData, NULL); + return true; + } + + *length = i2d_CryptoData(cs->cryptoData, (unsigned char **) &dest); + return true; +} + +/* + * Decodes the CryptoData structure. The length argument is + * used to specify the size of data. + * + * Returns: CRYPTO_SESSION instance on success. + * NULL on failure. + */ +// TODO landonf: Unimplemented, requires a private key to decrypt session key +CRYPTO_SESSION *crypto_session_decode(const void *data, size_t length) +{ + CRYPTO_SESSION *cs; +#if (OPENSSL_VERSION_NUMBER >= 0x0090800FL) + const unsigned char *p = (const unsigned char *) data; +#else + unsigned char *p = (unsigned char *) data; +#endif + + cs = (CRYPTO_SESSION *) malloc(sizeof(CRYPTO_SESSION)); + if (!cs) { + return NULL; + } + + /* d2i_CryptoData modifies the supplied pointer */ + cs->cryptoData = d2i_CryptoData(NULL, &p, length); + + if (!cs->cryptoData) { + /* Allocation / Decoding failed in OpenSSL */ + openssl_post_errors(M_ERROR, _("CryptoData decoding failed")); + return NULL; + } + + return cs; +} + +/* + * Free memory associated with a crypto session object. */ -void crypto_recipients_free (CRYPTO_RECIPIENTS *cr) +void crypto_session_free (CRYPTO_SESSION *cs) { - CryptoData_free(cr->cryptoData); - free(cr); + CryptoData_free(cs->cryptoData); + free(cs); } /* @@ -1213,7 +1267,7 @@ crypto_error_t crypto_sign_verify (SIGNATURE *sig, X509_KEYPAIR *keypair, DIGEST int crypto_sign_add_signer (SIGNATURE *sig, DIGEST *digest, X509_KEYPAIR *keypair) { return false; } int crypto_sign_encode (SIGNATURE *sig, void *dest, size_t *length) { return false; } -SIGNATURE *crypto_sign_decode (const void *sigData, size_t length) { return false; } +SIGNATURE *crypto_sign_decode (const void *sigData, size_t length) { return NULL; } void crypto_sign_free (SIGNATURE *sig) { } @@ -1223,8 +1277,10 @@ int crypto_keypair_load_cert (X509_KEYPAIR *keypair, const char *file) { return int crypto_keypair_load_key (X509_KEYPAIR *keypair, const char *file, CRYPTO_PEM_PASSWD_CB *pem_callback, const void *pem_userdata) { return false; } void crypto_keypair_free (X509_KEYPAIR *keypair) { } -CRYPTO_RECIPIENTS *crypto_recipients_new (crypto_cipher_t cipher, alist *pubkeys) { return NULL; } -void crypto_recipients_free (CRYPTO_RECIPIENTS *cr) { } +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_SESSION *crypto_session_decode(const void *data, size_t length) { return NULL; } #endif /* HAVE_CRYPTO */ diff --git a/bacula/src/lib/crypto.h b/bacula/src/lib/crypto.h index 17e546bd63..04b8f934e8 100644 --- a/bacula/src/lib/crypto.h +++ b/bacula/src/lib/crypto.h @@ -46,7 +46,7 @@ typedef struct Digest DIGEST; typedef struct Signature SIGNATURE; /* Opaque PKI Symmetric Key Data Structure */ -typedef struct Crypto_Recipients CRYPTO_RECIPIENTS; +typedef struct Crypto_Session CRYPTO_SESSION; /* PEM Decryption Passphrase Callback */ typedef int (CRYPTO_PEM_PASSWD_CB) (char *buf, int size, const void *userdata); diff --git a/bacula/src/lib/protos.h b/bacula/src/lib/protos.h index 3e8975cb75..138b8f181a 100644 --- a/bacula/src/lib/protos.h +++ b/bacula/src/lib/protos.h @@ -127,8 +127,10 @@ int crypto_sign_add_signer (SIGNATURE *sig, DIGEST *digest, int crypto_sign_encode (SIGNATURE *sig, void *dest, size_t *length); SIGNATURE * crypto_sign_decode (const void *sigData, size_t length); void crypto_sign_free (SIGNATURE *sig); -CRYPTO_RECIPIENTS *crypto_recipients_new (crypto_cipher_t cipher, alist *pubkeys); -void crypto_recipients_free (CRYPTO_RECIPIENTS *cr); +CRYPTO_SESSION * crypto_session_new (crypto_cipher_t cipher, alist *pubkeys); +void crypto_session_free (CRYPTO_SESSION *cs); +bool crypto_session_encode (CRYPTO_SESSION *cs, void *dest, size_t *length); +CRYPTO_SESSION * crypto_session_decode (const void *data, size_t length); 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/bextract.c b/bacula/src/stored/bextract.c index c7717a4b6c..167a7145b5 100644 --- a/bacula/src/stored/bextract.c +++ b/bacula/src/stored/bextract.c @@ -432,7 +432,8 @@ static bool record_cb(DCR *dcr, DEV_RECORD *rec) break; case STREAM_SIGNED_DIGEST: - // TODO landonf: Investigate signed digest support in the storage daemon + case STREAM_ENCRYPTED_SESSION_DATA: + // TODO landonf: Investigate crypto support in the storage daemon break; case STREAM_PROGRAM_NAMES: diff --git a/bacula/src/stored/bscan.c b/bacula/src/stored/bscan.c index 88a79c8760..099736b338 100644 --- a/bacula/src/stored/bscan.c +++ b/bacula/src/stored/bscan.c @@ -734,8 +734,15 @@ static bool record_cb(DCR *dcr, DEV_RECORD *rec) update_digest_record(db, digest, rec, CRYPTO_DIGEST_SHA512); break; + case STREAM_ENCRYPTED_SESSION_DATA: + // TODO landonf: Investigate crypto support in bscan + if (verbose > 1) { + Pmsg0(000, _("Got signed digest record\n")); + } + break; + case STREAM_SIGNED_DIGEST: - // TODO landonf: Investigate signed digest support in bscan + // TODO landonf: Investigate crypto support in bscan if (verbose > 1) { Pmsg0(000, _("Got signed digest record\n")); } diff --git a/bacula/src/stored/record.c b/bacula/src/stored/record.c index 522c50a49b..05641be2b3 100644 --- a/bacula/src/stored/record.c +++ b/bacula/src/stored/record.c @@ -115,6 +115,8 @@ const char *stream_to_ascii(char *buf, int stream, int fi) return "SHA512"; case STREAM_SIGNED_DIGEST: return "SIGNED-DIGEST"; + case STREAM_ENCRYPTED_SESSION_DATA: + return "ENCRYPTED-SESSION-DATA"; case -STREAM_UNIX_ATTRIBUTES: return "contUATTR"; case -STREAM_FILE_DATA: @@ -149,6 +151,8 @@ const char *stream_to_ascii(char *buf, int stream, int fi) return "contSHA512"; case -STREAM_SIGNED_DIGEST: return "contSIGNED-DIGEST"; + case -STREAM_ENCRYPTED_SESSION_DATA: + return "contENCRYPTED-SESSION-DATA"; default: sprintf(buf, "%d", stream); return buf; -- 2.39.5