summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichael Raitza <spacefrogg-devel@meterriblecrew.net>2020-07-28 21:08:39 +0200
committerJohn Wiegley <johnw@newartisans.com>2020-10-30 10:08:00 -0700
commitf8e8cc160048cc7032254f1db7548c68571c3409 (patch)
treef9196d0624e1aa88924f4c0d33a767686026eba4
parenteaf095ec3b0017f824cdbfe4e9e4bc42331d421a (diff)
downloadfork-ledger-f8e8cc160048cc7032254f1db7548c68571c3409.tar.gz
fork-ledger-f8e8cc160048cc7032254f1db7548c68571c3409.tar.bz2
fork-ledger-f8e8cc160048cc7032254f1db7548c68571c3409.zip
Implement cryptographic access to files using GPGME
-rw-r--r--.travis.yml8
-rw-r--r--CMakeLists.txt14
-rw-r--r--default.nix5
-rw-r--r--src/CMakeLists.txt10
-rw-r--r--src/context.h8
-rw-r--r--src/error.cc17
-rw-r--r--src/gpgme.cc220
-rw-r--r--src/gpgme.h126
-rw-r--r--src/system.hh.in1
9 files changed, 401 insertions, 8 deletions
diff --git a/.travis.yml b/.travis.yml
index e56fcf30..e78076fd 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -30,7 +30,7 @@ addons:
project:
name: "ledger/ledger"
description: "Build submitted via Travis CI"
- build_command_prepend: "cmake . -DUSE_PYTHON=ON -DBUILD_DEBUG=ON -DCLANG_GCOV=ON -DPython_FIND_VERSION_MAJOR=${PY_MAJOR}"
+ build_command_prepend: "cmake . -DUSE_PYTHON=ON -DBUILD_DEBUG=ON -DUSE_GPGME=ON -DCLANG_GCOV=ON -DPython_FIND_VERSION_MAJOR=${PY_MAJOR}"
build_command: "make"
branch_pattern: coverity
apt:
@@ -47,6 +47,9 @@ addons:
- libboost-iostreams-dev
- libboost-filesystem-dev
- libboost-serialization-dev
+ - libgpgmepp-dev
+ - libgpg-error-dev
+ - libgpgme-dev
homebrew:
update: true
packages:
@@ -55,13 +58,14 @@ addons:
- boost-python3
- gmp
- mpfr
+ - gpgme
before_script:
# On macOS boost-python packaging is broken
- if [ "$TRAVIS_OS_NAME" = osx ]; then EXTRA_CMAKE_ARGS="-DBoost_NO_BOOST_CMAKE=ON"; fi
# Ensure cmake locates python 3.8. Brew changed boost-python3 to use 3.8 but it isn't in the path by default
- if [ "$TRAVIS_OS_NAME" = osx ]; then export PATH="/usr/local/opt/python@3.8/bin:$PATH"; fi
- - cmake . -DUSE_PYTHON=ON -DPython_FIND_VERSION_MAJOR=${PY_MAJOR} -DBUILD_DEBUG=ON $EXTRA_CMAKE_ARGS
+ - cmake . -DUSE_PYTHON=ON -DPython_FIND_VERSION_MAJOR=${PY_MAJOR} -DUSE_GPGME=ON -DBUILD_DEBUG=ON $EXTRA_CMAKE_ARGS
- make VERBOSE=1
script:
diff --git a/CMakeLists.txt b/CMakeLists.txt
index e06c31d9..15cb7ef9 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -33,6 +33,7 @@ option(DISABLE_ASSERTS "Build without any internal consistency checks" OFF)
option(BUILD_DEBUG "Build support for runtime debugging" OFF)
option(PRECOMPILE_SYSTEM_HH "Precompile system.hh" ON)
+option(USE_GPGME "Build with support for encrypted journals" OFF)
option(BUILD_LIBRARY "Build and install Ledger as a library" ON)
option(BUILD_DOCS "Build and install documentation" OFF)
option(BUILD_WEB_DOCS "Build version of documentation suitable for viewing online" OFF)
@@ -86,6 +87,16 @@ find_package(Boost 1.49.0
include_directories(SYSTEM ${Boost_INCLUDE_DIRS})
link_directories(${Boost_LIBRARY_DIRS})
+# Crypto
+if (USE_GPGME)
+ find_package(Gpgmepp REQUIRED)
+ set(HAVE_GPGME 1)
+ include_directories(SYSTEM ${Gpgmepp_INCLUDE_DIRS})
+ link_directories(${Gpgmepp_LIBRARY_DIRS})
+else()
+ set(HAVE_GPGME 0)
+endif()
+
########################################################################
include(CheckIncludeFiles)
@@ -256,6 +267,9 @@ macro(add_ledger_library_dependencies _target)
if (HAVE_GETTEXT)
target_link_libraries(${_target} ${INTL_LIB})
endif()
+ if (HAVE_GPGME)
+ target_link_libraries(${_target} Gpgmepp)
+ endif()
if (HAVE_BOOST_PYTHON)
if(CMAKE_SYSTEM_NAME STREQUAL Darwin)
# Don't link directly to a Python framework on macOS, to avoid segfaults
diff --git a/default.nix b/default.nix
index c19f356e..ef26b4b4 100644
--- a/default.nix
+++ b/default.nix
@@ -24,11 +24,12 @@ pkgs.stdenv.mkDerivation {
src = ./.;
- buildInputs = with pkgs; [ cmake boost gmp mpfr libedit python texinfo gnused ];
+ nativeBuildInputs = with pkgs; [ cmake ];
+ buildInputs = with pkgs; [ boost gmp mpfr libedit python texinfo gnused gpgme ];
enableParallelBuilding = true;
- cmakeFlags = [ "-DCMAKE_INSTALL_LIBDIR=lib" ];
+ cmakeFlags = [ "-DCMAKE_INSTALL_LIBDIR=lib" "-DUSE_GPGME=1" ];
buildPhase = "make -j$NIX_BUILD_CORES";
checkPhase = "ctest -j$NIX_BUILD_CORES";
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index b3d13a29..30d97cb9 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -53,6 +53,11 @@ set(LEDGER_SOURCES
utils.cc
wcwidth.cc)
+if (HAVE_GPGME)
+ list(APPEND LEDGER_SOURCES
+ gpgme.cc)
+endif()
+
if (HAVE_BOOST_PYTHON)
list(APPEND LEDGER_SOURCES
py_account.cc
@@ -94,6 +99,7 @@ set(LEDGER_INCLUDES
format.h
generate.h
global.h
+ gpgme.h
history.h
item.h
iterators.h
@@ -284,6 +290,10 @@ if (BUILD_LIBRARY)
add_executable(ledger main.cc global.cc)
target_link_libraries(ledger libledger)
+ if (HAVE_GPGME)
+ target_link_libraries(ledger Gpgmepp)
+ endif()
+
if (HAVE_BOOST_PYTHON)
target_link_libraries(ledger ${Python_LIBRARIES})
endif()
diff --git a/src/context.h b/src/context.h
index ca7af060..bf97feb5 100644
--- a/src/context.h
+++ b/src/context.h
@@ -45,6 +45,10 @@
#include "utils.h"
#include "times.h"
+#if HAVE_GPGME
+#include "gpgme.h"
+#endif
+
namespace ledger {
class journal_t;
@@ -119,7 +123,11 @@ inline parse_context_t open_for_reading(const path& pathname,
_f("Cannot read journal file %1%") % filename);
path parent(filename.parent_path());
+#if HAVE_GPGME
+ shared_ptr<std::istream> stream(decrypted_stream_t::open_stream(filename));
+#else
shared_ptr<std::istream> stream(new ifstream(filename));
+#endif
parse_context_t context(stream, parent);
context.pathname = filename;
return context;
diff --git a/src/error.cc b/src/error.cc
index 837d7499..ab7fab5b 100644
--- a/src/error.cc
+++ b/src/error.cc
@@ -33,6 +33,10 @@
#include "utils.h"
+#if HAVE_GPGME
+#include "gpgme.h"
+#endif
+
namespace ledger {
std::ostringstream _ctxt_buffer;
@@ -92,12 +96,16 @@ string source_context(const path& file,
std::ostringstream out;
- ifstream in(file);
- in.seekg(pos, std::ios::beg);
+#if HAVE_GPGME
+ std::istream* in(decrypted_stream_t::open_stream(file));
+#else
+ std::istream* in(new ifstream(file));
+#endif
+ in->seekg(pos, std::ios::beg);
scoped_array<char> buf(new char[static_cast<std::size_t>(len) + 1]);
- in.read(buf.get(), static_cast<std::streamsize>(len));
- assert(in.gcount() == static_cast<std::streamsize>(len));
+ in->read(buf.get(), static_cast<std::streamsize>(len));
+ assert(in->gcount() == static_cast<std::streamsize>(len));
buf[static_cast<std::ptrdiff_t>(len)] = '\0';
bool first = true;
@@ -111,6 +119,7 @@ string source_context(const path& file,
out << prefix << p;
}
+ delete(in);
return out.str();
}
diff --git a/src/gpgme.cc b/src/gpgme.cc
new file mode 100644
index 00000000..4f949c8c
--- /dev/null
+++ b/src/gpgme.cc
@@ -0,0 +1,220 @@
+/*
+ * Copyright (c) 2020, Michael Raitza. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <system.hh>
+
+#include "gpgme.h"
+#include "utils.h"
+
+#include <iostream>
+#include <sstream>
+#include <cerrno>
+
+#include <gpgme++/context.h>
+#include <gpgme++/decryptionresult.h>
+
+namespace ledger {
+
+using GpgME::Context;
+using GpgME::Data;
+using GpgME::Protocol;
+using namespace std;
+using std::shared_ptr;
+using std::unique_ptr;
+
+constexpr static unsigned int _bufsize = 4096;
+
+data_streambuffer_t::data_streambuffer_t(Data& _data) :
+ data(_data),
+ bufsize(_bufsize),
+ cbuf(unique_ptr<char[]>(new char[_bufsize])) {}
+
+streambuf::int_type data_streambuffer_t::underflow() {
+ if (this->gptr() == this->egptr()) {
+
+ auto buf = cbuf.get();
+ auto size = data.read(buf, bufsize);
+ if (size < 0)
+ throw_(runtime_error, _f("Reading decrypted data: GPGME: %1%") % strerror(errno));
+ else if (size == 0)
+ return char_traits<char>::eof();
+
+ this->setg(buf, buf, buf + size);
+ }
+ return this->gptr() == this->egptr()
+ ? char_traits<char>::eof()
+ : char_traits<char>::to_int_type(*this->gptr());
+}
+
+streambuf::pos_type data_streambuffer_t::seekpos(streambuf::pos_type sp, ios_base::openmode which = ios_base::in) {
+ return this->seekoff(static_cast<streambuf::off_type>(sp), ios::beg, which);
+}
+
+streambuf::pos_type data_streambuffer_t::seekoff(streambuf::off_type off,
+ ios_base::seekdir dir,
+ ios_base::openmode which = ios_base::in) {
+ streamoff pos = -1;
+ if (dir == ios::beg)
+ pos = static_cast<streambuf::pos_type>(data.seek(static_cast<streamoff>(off), SEEK_SET));
+ else if (dir == ios::end)
+ pos = static_cast<streambuf::pos_type>(data.seek(static_cast<streamoff>(off), SEEK_END));
+ else if (dir == ios::cur)
+ pos = static_cast<streambuf::pos_type>(data.seek(static_cast<streamoff>(off), SEEK_CUR));
+
+ if (pos == -1)
+ throw runtime_error("Unable to seek to position");
+
+ auto buf = cbuf.get();
+ // Trigger underflow on next read
+ this->setg(buf, buf+1, buf+1);
+ return pos;
+}
+
+FILE * decrypted_stream_t::open_file(const path& filename) {
+ FILE * f = fopen(filename.c_str(), "rb");
+ if (!f)
+ throw_(runtime_error, _f("Could not open file: %1%") % strerror(errno));
+ return f;
+}
+
+shared_ptr<Data> decrypted_stream_t::setup_cipher_buffer(FILE * f) {
+ auto enc_d = make_shared<Data>(f);
+ if (!enc_d)
+ throw runtime_error("Unable to create cipher text buffer");
+
+ if (enc_d->type() != Data::PGPEncrypted
+ && enc_d->type() != Data::CMSEncrypted
+ && enc_d->type() != Data::Unknown
+ && enc_d->type() != Data::Invalid)
+ throw_(runtime_error, _f("Unsupported encryption type: %1%") % enc_d->type());
+
+ return enc_d;
+}
+
+static bool is_encrypted(shared_ptr<Data> enc_d) {
+ if (enc_d->type() == Data::Unknown
+ || enc_d->type() == Data::Invalid)
+ return false;
+ else
+ return true;
+}
+
+shared_ptr<Data> decrypted_stream_t::decrypt(shared_ptr<Data> enc_d) {
+ unique_ptr<Context> ctx;
+ shared_ptr<Data> dec_d;
+
+ if (enc_d->type() == Data::Unknown
+ || enc_d->type() == Data::Invalid) {
+ ctx = nullptr;
+ dec_d = enc_d;
+ } else {
+#if GPGME_VERSION_NUMBER < 0x010d00
+ ctx = unique_ptr<Context>(Context::createForProtocol(enc_d->type() == Data::PGPEncrypted
+ ? Protocol::OpenPGP
+ : Protocol::CMS));
+#else
+ ctx = Context::create(enc_d->type() == Data::PGPEncrypted
+ ? Protocol::OpenPGP
+ : Protocol::CMS);
+#endif
+ if (!ctx)
+ throw runtime_error("Unable to establish decryption context");
+
+ ctx->setOffline(true);
+ dec_d = make_shared<Data>();
+ if (!dec_d)
+ throw runtime_error("Unable to create plain text buffer");
+
+ auto res = ctx->decrypt(*enc_d.get(), *dec_d.get());
+ if (res.error())
+ throw_(runtime_error, _f("Decryption error: %1%: %2%") % res.error().source() % res.error().asString());
+ }
+ return dec_d;
+}
+
+static inline void init_lib() {
+ auto err = GpgME::initializeLibrary(0);
+ if (err.code() != GPG_ERR_NO_ERROR)
+ throw_(runtime_error, _f("%1%: %2%") % err.source() % err.asString());
+}
+
+static inline void rewind(Data * d) {
+#if GPGME_VERSION_NUMBER < 0x010c00
+ d->seek(0, SEEK_SET);
+#else
+ d->rewind();
+#endif
+}
+
+istream* decrypted_stream_t::open_stream(const path& filename) {
+ init_lib();
+
+ unique_ptr<FILE, decltype(&fclose)> file(open_file(filename), &fclose);
+ auto enc_d = setup_cipher_buffer(file.get());
+ if (is_encrypted(enc_d)) {
+ auto dec_d = decrypt(enc_d);
+ rewind(dec_d.get());
+ return new decrypted_stream_t(dec_d);
+ }
+ return new ifstream(filename);
+}
+
+decrypted_stream_t::decrypted_stream_t(path& filename)
+: istream(new data_streambuffer_t(*new Data())) {
+ init_lib();
+
+ file = open_file(filename);
+ auto enc_d = setup_cipher_buffer(file);
+ dec_d = decrypt(enc_d);
+ rewind(dec_d.get());
+
+ if (is_encrypted(enc_d)) {
+ fclose(file);
+ file = nullptr;
+ }
+
+ set_rdbuf(new data_streambuffer_t(*dec_d.get()));
+ clear();
+}
+
+decrypted_stream_t::decrypted_stream_t(shared_ptr<Data> dec_d)
+ : istream(new data_streambuffer_t(*dec_d.get())),
+ dec_d(dec_d),
+ file(nullptr) {
+ clear();
+}
+
+decrypted_stream_t::~decrypted_stream_t() {
+ if (file)
+ fclose(file);
+}
+
+} // namespace ledger
diff --git a/src/gpgme.h b/src/gpgme.h
new file mode 100644
index 00000000..00824132
--- /dev/null
+++ b/src/gpgme.h
@@ -0,0 +1,126 @@
+/*
+ * Copyright (c) 2020, Michael Raitza. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <system.hh>
+
+#include "utils.h"
+
+#include <streambuf>
+#include <istream>
+
+#include <gpgme++/data.h>
+
+namespace ledger {
+
+ class data_streambuffer_t : public std::streambuf {
+ public:
+ GpgME::Data& data;
+
+ /* Size of cbuf */
+ const unsigned int bufsize;
+
+ /* Backing character buffer */
+ std::unique_ptr<char[]> cbuf;
+
+ explicit data_streambuffer_t(GpgME::Data& _data);
+
+ virtual int_type underflow();
+
+ protected:
+ virtual std::streambuf::pos_type seekpos(std::streambuf::pos_type sp, std::ios_base::openmode which);
+ virtual std::streambuf::pos_type seekoff(std::streambuf::off_type off,
+ std::ios_base::seekdir dir,
+ std::ios_base::openmode which);
+ };
+
+ class decrypted_stream_t : public std::istream {
+ public:
+ std::shared_ptr<GpgME::Data> dec_d;
+ std::FILE * file;
+
+ /* Establishes an istream decrypting a file pointed to by FILENAME.
+
+ Decryption is performed at object creation and only the decrypted Data
+ buffer is retained as the backing store for the stream.
+
+ Expects the input file to be unencrypted or encrypted in CMS or PGP
+ format (includes asymmetrically and symmetrically encrypted content).
+
+ Calls open_file(), setup_cipher_buffer() and decrypt() and throws
+ exceptions noted in there on error. */
+ decrypted_stream_t(path& filename);
+
+ /* Established an istream serving the decrypted content in DEC_D.
+
+ Make sure DEC_D is properly rewound. (Which it is not after decrypting.)
+
+ Expects DEC_D was created by actually decrypting input data (usually a
+ FILE object). Otherwise GpgME just hands over the reference from the
+ buffer holding the "encrypted" input to DEC_D. Then, you must keep the
+ original object around for the lifetime of this stream. */
+ decrypted_stream_t(std::shared_ptr<GpgME::Data> dec_d);
+
+ ~decrypted_stream_t();
+
+ /* Opens file pointed to by FILENAME.
+
+ Opens the file using fopen() in "rb" mode.
+
+ Throws a runtime error when the file cannot be opened for reading. */
+ static std::FILE * open_file(const path& filename);
+
+ /* Returns a Data buffer connected to an open FILE object.
+
+ Throws a runtime error when the content is neither PGPEncrypted,
+ CMSEncrypted or Unknown, or when the buffer cannot be established. */
+ static std::shared_ptr<GpgME::Data> setup_cipher_buffer(std::FILE * f);
+
+ /* Returns a Data buffer of the plain text. Decrypts cipher text by
+ establishing a proper decryption context, first. .
+
+ Returns the input Data buffer when the encryption type is Unknown, which
+ is considered unencrypted input.
+
+ Throws a runtime error when the decryption fails or when the cipher text
+ is neither PGPEncrypted nor CMSEncrypted. */
+ static std::shared_ptr<GpgME::Data> decrypt(std::shared_ptr<GpgME::Data> enc_d);
+
+ /* Returns an istream, which is either a decrypted_stream_t, given the file
+ is encrypted, or an ifstream object.
+
+ Use this to create the istream! The decrypted_stream_t is perfectly
+ capable reading unencrypted data, but the file size and data pointers no
+ longer match with a standard ifstream. */
+ static std::istream* open_stream(const path& filename);
+ };
+}
diff --git a/src/system.hh.in b/src/system.hh.in
index 2f0f6e79..97b2bead 100644
--- a/src/system.hh.in
+++ b/src/system.hh.in
@@ -68,6 +68,7 @@
#define HAVE_UNIX_PIPES @HAVE_UNIX_PIPES@
#define HAVE_BOOST_PYTHON @HAVE_BOOST_PYTHON@
+#define HAVE_GPGME @HAVE_GPGME@
#define HAVE_BOOST_REGEX_UNICODE @HAVE_BOOST_REGEX_UNICODE@
#define HAVE_BOOST_159_ISSUE_39 @HAVE_BOOST_159_ISSUE_39@