+/*
+ Bacula® - The Network Backup Solution
+
+ Copyright (C) 2005-2007 Free Software Foundation Europe e.V.
+
+ The main author of Bacula is Kern Sibbald, with contributions from
+ many others, a complete list can be found in the file AUTHORS.
+ This program is Free Software; you can redistribute it and/or
+ modify it under the terms of version two of the GNU General Public
+ License as published by the Free Software Foundation plus additions
+ that are listed in the file LICENSE.
+
+ 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, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+
+ Bacula® is a registered trademark of John Walker.
+ The licensor of Bacula is the Free Software Foundation Europe
+ (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zürich,
+ Switzerland, email:ftf@fsfeurope.org.
+*/
/*
* crypto.c Encryption support functions
*
*
* Version $Id$
*
- * Copyright (C) 2005 Kern Sibbald
- *
* This file was contributed to the Bacula project by Landon Fuller.
*
* Landon Fuller has been granted a perpetual, worldwide, non-exclusive,
* If you wish to license these contributions under an alternate open source
* license please contact Landon Fuller <landonf@opendarwin.org>.
*/
-/*
- Copyright (C) 2005 Kern Sibbald
-
- This program is free software; you can redistribute it and/or
- modify it under the terms of the GNU General Public License
- version 2 as amended with additional clauses defined in the
- file LICENSE in the main source directory.
-
- 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
- the file LICENSE for additional details.
-
- */
#include "bacula.h"
ASN1_SEQUENCE(CryptoData) = {
ASN1_SIMPLE(CryptoData, version, ASN1_INTEGER),
+ ASN1_SIMPLE(CryptoData, contentEncryptionAlgorithm, ASN1_OBJECT),
ASN1_SIMPLE(CryptoData, iv, ASN1_OCTET_STRING),
ASN1_SET_OF(CryptoData, recipientInfo, RecipientInfo)
} ASN1_SEQUENCE_END(CryptoData);
/* 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 */
+ unsigned char *session_key; /* Private symmetric session key */
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;
goto err;
}
+ X509_free(cert);
return true;
err:
return (ctx->pem_callback(buf, size, ctx->pem_userdata));
}
+/*
+ * Check a PEM-encoded file
+ * for the existence of a private key.
+ * Returns: true if a private key is found
+ * false otherwise
+ */
+bool crypto_keypair_has_key (const char *file) {
+ BIO *bio;
+ char *name = NULL;
+ char *header = NULL;
+ unsigned char *data = NULL;
+ bool retval = false;
+ long len;
+
+ if (!(bio = BIO_new_file(file, "r"))) {
+ openssl_post_errors(M_ERROR, _("Unable to open private key file"));
+ return false;
+ }
+
+ while (PEM_read_bio(bio, &name, &header, &data, &len)) {
+ /* We don't care what the data is, just that it's there */
+ OPENSSL_free(header);
+ OPENSSL_free(data);
+
+ /*
+ * PEM Header Found, check for a private key
+ * Due to OpenSSL limitations, we must specifically
+ * list supported PEM private key encodings.
+ */
+ if (strcmp(name, PEM_STRING_RSA) == 0
+ || strcmp(name, PEM_STRING_DSA) == 0
+ || strcmp(name, PEM_STRING_PKCS8) == 0
+ || strcmp(name, PEM_STRING_PKCS8INF) == 0) {
+ retval = true;
+ OPENSSL_free(name);
+ break;
+ } else {
+ OPENSSL_free(name);
+ }
+ }
+
+ /* Free our bio */
+ BIO_free(bio);
+
+ /* Post PEM-decoding error messages, if any */
+ openssl_post_errors(M_ERROR, _("Unable to read private key from file"));
+ return retval;
+}
+
/*
* Load a PEM-encoded private key.
* Returns: true on success
DIGEST *digest;
const EVP_MD *md = NULL; /* Quell invalid uninitialized warnings */
- digest = (DIGEST *) malloc(sizeof(DIGEST));
+ digest = (DIGEST *)malloc(sizeof(DIGEST));
digest->type = type;
/* Initialize the OpenSSL message digest context */
* Returns: true on success
* false on failure
*/
-bool crypto_digest_update (DIGEST *digest, const void *data, size_t length) {
+bool crypto_digest_update(DIGEST *digest, const uint8_t *data, uint32_t length)
+{
if (EVP_DigestUpdate(&digest->ctx, data, length) == 0) {
return true;
} else {
* Returns: true on success
* false on failure
*/
-bool crypto_digest_finalize (DIGEST *digest, void *dest, size_t *length) {
- if (!EVP_DigestFinal(&digest->ctx, (unsigned char *) dest, (unsigned int *) length)) {
+bool crypto_digest_finalize (DIGEST *digest, uint8_t *dest, uint32_t *length)
+{
+ if (!EVP_DigestFinal(&digest->ctx, dest, (unsigned int *)length)) {
return false;
} else {
return true;
return CRYPTO_ERROR_BAD_SIGNATURE;
} else if (ok < 0) {
/* Shouldn't happen */
- openssl_post_errors(M_ERROR, _("OpenSSL error occured"));
+ openssl_post_errors(M_ERROR, _("OpenSSL error occurred"));
return CRYPTO_ERROR_INTERNAL;
}
}
* Returns: true on success, stores the encoded data in dest, and the size in length.
* false on failure.
*/
-int crypto_sign_encode(SIGNATURE *sig, void *dest, size_t *length)
+int crypto_sign_encode(SIGNATURE *sig, uint8_t *dest, uint32_t *length)
{
if (*length == 0) {
*length = i2d_SignatureData(sig->sigData, NULL);
return true;
}
- *length = i2d_SignatureData(sig->sigData, (unsigned char **) &dest);
+ *length = i2d_SignatureData(sig->sigData, (unsigned char **)&dest);
return true;
}
*/
-SIGNATURE *crypto_sign_decode(const void *sigData, size_t length)
+SIGNATURE *crypto_sign_decode(const uint8_t *sigData, uint32_t length)
{
SIGNATURE *sig;
#if (OPENSSL_VERSION_NUMBER >= 0x0090800FL)
const unsigned char *p = (const unsigned char *) sigData;
#else
- unsigned char *p = (unsigned char *) sigData;
+ unsigned char *p = (unsigned char *)sigData;
#endif
- sig = (SIGNATURE *) malloc(sizeof(SIGNATURE));
+ sig = (SIGNATURE *)malloc(sizeof(SIGNATURE));
if (!sig) {
return NULL;
}
if (!sig->sigData) {
/* Allocation / Decoding failed in OpenSSL */
openssl_post_errors(M_ERROR, _("Signature decoding failed"));
+ free(sig);
return NULL;
}
return NULL;
}
+ /* Initialize required fields */
+ cs->session_key = NULL;
+
+ /* Allocate a CryptoData structure */
cs->cryptoData = CryptoData_new();
if (!cs->cryptoData) {
/* Generate a symmetric session key */
cs->session_key_len = EVP_CIPHER_key_length(ec);
+ cs->session_key = (unsigned char *) malloc(cs->session_key_len);
if (RAND_bytes(cs->session_key, cs->session_key_len) <= 0) {
/* OpenSSL failure */
crypto_session_free(cs);
if (RAND_bytes(iv, iv_len) <= 0) {
/* OpenSSL failure */
crypto_session_free(cs);
+ free(iv);
return NULL;
}
if (!M_ASN1_OCTET_STRING_set(cs->cryptoData->iv, iv, iv_len)) {
/* Allocation failed in OpenSSL */
crypto_session_free(cs);
+ free(iv);
return NULL;
}
+ free(iv);
}
/*
* 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)
+bool crypto_session_encode(CRYPTO_SESSION *cs, uint8_t *dest, uint32_t *length)
{
if (*length == 0) {
*length = i2d_CryptoData(cs->cryptoData, NULL);
return true;
}
- *length = i2d_CryptoData(cs->cryptoData, (unsigned char **) &dest);
+ *length = i2d_CryptoData(cs->cryptoData, &dest);
return true;
}
*
* Returns: CRYPTO_SESSION instance on success.
* NULL on failure.
+ * Returns: CRYPTO_ERROR_NONE and a pointer to a newly allocated CRYPTO_SESSION structure in *session on success.
+ * A crypto_error_t value 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_error_t crypto_session_decode(const uint8_t *data, uint32_t length, alist *keypairs, CRYPTO_SESSION **session)
{
CRYPTO_SESSION *cs;
+ X509_KEYPAIR *keypair;
+ STACK_OF(RecipientInfo) *recipients;
+ crypto_error_t retval = CRYPTO_ERROR_NONE;
#if (OPENSSL_VERSION_NUMBER >= 0x0090800FL)
- const unsigned char *p = (const unsigned char *) data;
+ const unsigned char *p = (const unsigned char *)data;
#else
- unsigned char *p = (unsigned char *) data;
+ unsigned char *p = (unsigned char *)data;
#endif
+ /* bacula-fd.conf doesn't contains any key */
+ if (!keypairs) {
+ return CRYPTO_ERROR_NORECIPIENT;
+ }
+
cs = (CRYPTO_SESSION *) malloc(sizeof(CRYPTO_SESSION));
if (!cs) {
- return NULL;
+ return CRYPTO_ERROR_INTERNAL;
}
+ /* Initialize required fields */
+ cs->session_key = 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;
+ retval = CRYPTO_ERROR_INTERNAL;
+ goto err;
}
- return cs;
+ recipients = cs->cryptoData->recipientInfo;
+
+ /*
+ * Find a matching RecipientInfo structure for a supplied
+ * public key
+ */
+ foreach_alist(keypair, keypairs) {
+ RecipientInfo *ri;
+ int i;
+
+ /* Private key available? */
+ if (keypair->privkey == NULL) {
+ continue;
+ }
+
+ for (i = 0; i < sk_RecipientInfo_num(recipients); i++) {
+ ri = sk_RecipientInfo_value(recipients, i);
+
+ /* Match against the subjectKeyIdentifier */
+ if (M_ASN1_OCTET_STRING_cmp(keypair->keyid, ri->subjectKeyIdentifier) == 0) {
+ /* Match found, extract symmetric encryption session data */
+
+ /* RSA is required. */
+ assert(EVP_PKEY_type(keypair->privkey->type) == EVP_PKEY_RSA);
+
+ /* If we recieve a RecipientInfo structure that does not use
+ * RSA, return an error */
+ if (OBJ_obj2nid(ri->keyEncryptionAlgorithm) != NID_rsaEncryption) {
+ retval = CRYPTO_ERROR_INVALID_CRYPTO;
+ goto err;
+ }
+
+ /* Decrypt the session key */
+ /* Allocate sufficient space for the largest possible decrypted data */
+ cs->session_key = (unsigned char *) malloc(EVP_PKEY_size(keypair->privkey));
+ cs->session_key_len = EVP_PKEY_decrypt(cs->session_key, M_ASN1_STRING_data(ri->encryptedKey),
+ M_ASN1_STRING_length(ri->encryptedKey), keypair->privkey);
+
+ if (cs->session_key_len <= 0) {
+ openssl_post_errors(M_ERROR, _("Failure decrypting the session key"));
+ retval = CRYPTO_ERROR_DECRYPTION;
+ goto err;
+ }
+
+ /* Session key successfully extracted, return the CRYPTO_SESSION structure */
+ *session = cs;
+ return CRYPTO_ERROR_NONE;
+ }
+ }
+ }
+
+ /* No matching recipient found */
+ return CRYPTO_ERROR_NORECIPIENT;
+
+err:
+ crypto_session_free(cs);
+ return retval;
}
/*
*/
void crypto_session_free (CRYPTO_SESSION *cs)
{
- CryptoData_free(cs->cryptoData);
+ if (cs->cryptoData) {
+ CryptoData_free(cs->cryptoData);
+ }
+ if (cs->session_key){
+ free(cs->session_key);
+ }
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, uint32_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));
+ 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 uint8_t *data, uint32_t length, const uint8_t *dest, uint32_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, uint8_t *dest, uint32_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.
/* Load libssl and libcrypto human-readable error strings */
SSL_load_error_strings();
- /* Register OpenSSL ciphers */
+ /* Initialize OpenSSL SSL library */
SSL_library_init();
+ /* Register OpenSSL ciphers and digests */
+ OpenSSL_add_all_algorithms();
+
if (!openssl_seed_prng()) {
Emsg0(M_ERROR_TERM, 0, _("Failed to seed OpenSSL PRNG\n"));
}
/* Free libssl and libcrypto error strings */
ERR_free_strings();
+ /* Free all ciphers and digests */
+ EVP_cleanup();
+
/* Free memory used by PRNG */
RAND_cleanup();
{
DIGEST *digest;
- digest = (DIGEST *) malloc(sizeof(DIGEST));
+ digest = (DIGEST *)malloc(sizeof(DIGEST));
digest->type = type;
switch (type) {
return (digest);
}
-bool crypto_digest_update (DIGEST *digest, const void *data, size_t length) {
+bool crypto_digest_update(DIGEST *digest, const uint8_t *data, uint32_t length)
+{
switch (digest->type) {
case CRYPTO_DIGEST_MD5:
/* Doesn't return anything ... */
}
}
-bool crypto_digest_finalize (DIGEST *digest, void *dest, size_t *length) {
-
+bool crypto_digest_finalize(DIGEST *digest, uint8_t *dest, uint32_t *length)
+{
switch (digest->type) {
case CRYPTO_DIGEST_MD5:
/* Guard against programmer error by either the API client or
assert(*length >= CRYPTO_DIGEST_MD5_SIZE);
*length = CRYPTO_DIGEST_MD5_SIZE;
/* Doesn't return anything ... */
- MD5Final((unsigned char *) dest, &digest->md5);
+ MD5Final((unsigned char *)dest, &digest->md5);
return true;
case CRYPTO_DIGEST_SHA1:
/* Guard against programmer error by either the API client or
return false;
}
-void crypto_digest_free (DIGEST *digest)
+void crypto_digest_free(DIGEST *digest)
{
free (digest);
}
crypto_error_t crypto_sign_verify (SIGNATURE *sig, X509_KEYPAIR *keypair, DIGEST *digest) { return CRYPTO_ERROR_INTERNAL; }
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; }
+int crypto_sign_encode (SIGNATURE *sig, uint8_t *dest, uint32_t *length) { return false; }
-SIGNATURE *crypto_sign_decode (const void *sigData, size_t length) { return NULL; }
+SIGNATURE *crypto_sign_decode (const uint8_t *sigData, uint32_t length) { return NULL; }
void crypto_sign_free (SIGNATURE *sig) { }
X509_KEYPAIR *crypto_keypair_new (void) { return NULL; }
X509_KEYPAIR *crypto_keypair_dup (X509_KEYPAIR *keypair) { return NULL; }
int crypto_keypair_load_cert (X509_KEYPAIR *keypair, const char *file) { return false; }
+bool crypto_keypair_has_key (const char *file) { return false; }
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_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; }
+bool crypto_session_encode (CRYPTO_SESSION *cs, uint8_t *dest, uint32_t *length) { return false; }
+crypto_error_t crypto_session_decode(const uint8_t *data, uint32_t length, alist *keypairs, CRYPTO_SESSION **session) { return CRYPTO_ERROR_INTERNAL; }
+
+CIPHER_CONTEXT *crypto_cipher_new (CRYPTO_SESSION *cs, bool encrypt, uint32_t *blocksize) { return NULL; }
+bool crypto_cipher_update (CIPHER_CONTEXT *cipher_ctx, const uint8_t *data, uint32_t length, const uint8_t *dest, uint32_t *written) { return false; }
+bool crypto_cipher_finalize (CIPHER_CONTEXT *cipher_ctx, uint8_t *dest, uint32_t *written) { return false; }
+void crypto_cipher_free (CIPHER_CONTEXT *cipher_ctx) { }
#endif /* HAVE_CRYPTO */
return "No error";
case CRYPTO_ERROR_NOSIGNER:
return "Signer not found";
+ case CRYPTO_ERROR_NORECIPIENT:
+ return "Recipient not found";
case CRYPTO_ERROR_INVALID_DIGEST:
return "Unsupported digest algorithm";
+ case CRYPTO_ERROR_INVALID_CRYPTO:
+ return "Unsupported encryption algorithm";
case CRYPTO_ERROR_BAD_SIGNATURE:
return "Signature is invalid";
+ case CRYPTO_ERROR_DECRYPTION:
+ return "Decryption error";
case CRYPTO_ERROR_INTERNAL:
/* This shouldn't happen */
return "Internal error";