From 3145b98bb31777e73201b13279f4e26115011836 Mon Sep 17 00:00:00 2001 From: Mirko Tietgen Date: Thu, 18 Jul 2013 23:10:11 +0200 Subject: [PATCH] Bug 8897 [ENH] GPG Mail encryption -- New version MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Koha should offer patrons the option to receive their emails encrypted. This patch adds a dialog to the user interface (OPAC side) for patrons to add or delete a GPG public key. The key is directly added to or deleted from gpg (gnupg needs to be installed, binary expected at /usr/bin/gpg). This feature does not require sysadmins/librarians to do anything as long as the gpg binary if found. So far, mails are encrypted if a key for the recipient is avaliable - when sending lists or carts (encryption of email text + attachment) - when mails are sent through the message queue - … Missing so far: - hide public key interface from OPAC/userdetails when no binary is found - de-hardcode path to gpg (syspref) - deal with BCC mails (send message explaining that an encrypted email has been send to the patron instead of a BCC) - … Maybe later: - library-side key management: secret key for signing - encryption of emails from library to vendors - … Test plan: -apply patch * Add/delete key: - in OPAC, log in as a user you got GPG kes for (primary email needs to match the key) - go to user details, email encryption - paste your public key into the form, save. when the page reloads, your key should be displayed in the form and a delete button at the bottom. in the terminal (as the koha-user!), do 'gpg --list-keys' and verify the key you just added is present - press the delete button under the form. on reload the form should be empty and your key gone. verify with (as the koha user) 'gpg --list-keys' in the terminal. - paste your key into the form again and save to do the following tests. * Encrypted cart emails - do a search, save a record in the the cart. open the cart, send it to the adress you added the key for. - check if the mail you receive has encrypted text and an encrypted attachment - decrypt both the text and the attachment, check if they are what you would expect * Encrypted list emails - if you do not have any lists, set up one with one record. - open the list from the opac, send it to you. enter the address you just added the key for - check that the mail you receive has encrypted text and an encrypted attachment. - decrypt both the text and the attachment, check if they are what you would expect * Encrypted message queue emails - check some items in and out for the patron you added the key for - wait for the message queue cronjob to run or run it manually - check if the email(s) you receive are encrypted and after decryption contain what you would expect Check all this again with another patron (without a gpg key) or use the same but delete the key before. Check that all mails and attachments are unencrypted and contain what you would expect (nothing is broken). If all that works for you, sign off the patch. Squashed a few minor changes. Bug 8897 Follow-up: plack fix --- C4/GnuPG.pm | 119 +++++++++++++++++++++ C4/Installer/PerlDependencies.pm | 5 + C4/Letters.pm | 16 +++- debian/control | 3 + debian/control.in | 1 + install_misc/debian.packages | 2 + install_misc/ubuntu.10.04.packages | 2 + install_misc/ubuntu.12.04.packages | 2 + install_misc/ubuntu.packages | 2 + koha-tmpl/opac-tmpl/prog/en/includes/usermenu.inc | 1 + koha-tmpl/opac-tmpl/prog/en/modules/opac-gnupg.tt | 59 ++++++++++ opac/opac-gnupg.pl | 81 ++++++++++++++ opac/opac-sendbasket.pl | 23 ++++- opac/opac-sendshelf.pl | 26 ++++- 14 files changed, 330 insertions(+), 12 deletions(-) create mode 100644 C4/GnuPG.pm create mode 100644 koha-tmpl/opac-tmpl/prog/en/modules/opac-gnupg.tt create mode 100755 opac/opac-gnupg.pl diff --git a/C4/GnuPG.pm b/C4/GnuPG.pm new file mode 100644 index 0000000..407013b --- /dev/null +++ b/C4/GnuPG.pm @@ -0,0 +1,119 @@ +package C4::GnuPG; + +# Copyright 2012 Mirko Tietgen +# +# This file is part of Koha. +# +# Koha is free software; you can redistribute it and/or modify it under the +# terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# Koha 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 Koha; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +use strict; +use warnings; +use C4::Context; +use Crypt::GPG; + +=head1 C4::GnuPG + +C4::GnuPG + +=head1 Description + +Provides methods to interact with GnuPG via Crypt::GPG for the purpose of encrypting emails to patrons + +=cut + +our $gpg = new Crypt::GPG; +$gpg->gpgbin('/usr/bin/gpg'); +$gpg->encryptsafe(0); # we have to allow untrusted keys + +=head2 encrypt + +Take email content and patron's email address, send back encrypted content. Call like + +$content = C4::GnuPG->encrypt($content, $patronemail); + +=cut + +sub encrypt { + my ($self, $content, $patronemail) = @_; + $content = $gpg->encrypt($content, $patronemail); + return $content; +} + +=head2 does_exist + +Check if there is a GnuPG key for the patron's email address. Call like + +if ( C4::GnuPG->does_exist($patronemail) = 1 ) { + # … encrypt … +} + +=cut + +sub does_exist { + # TODO: check if gpg binary is found before encrypting to avoid errors + my ($self, $patronemail) = @_; + if ( $gpg->keydb($patronemail) ) { + return 1; + } + else { + return 0; + } +} + +=head2 add_key + +Add or replace a patron's key. If there is already a key present, delete. Then add the new key. + +=cut + +sub add_key { + my ($self, $key, $patronemail) = @_; + # check if it is a public key before doing anything + if ( $key =~ /BEGIN PGP PUBLIC KEY BLOCK/ ) { + # if we already got a key, delete + if ( does_exist($patronemail) ) { + my @oldkey = $gpg->keydb($patronemail); + if ( $oldkey[0] ) { + $gpg->delkey($oldkey[0]); + } + } + # … add new key + $gpg->addkey($key); + } + else { + return 0; + } +} + +sub del_key { + my ($self, $patronemail) = @_; + my @key = $gpg->keydb($patronemail); + $gpg->delkey($key[0]); +} + +=head2 get_key + +=cut + +sub get_key { + # TODO: check security issue: can i get a secret key with this? + my ($self, $patronemail) = @_; + my @key = $gpg->keydb($patronemail); + my $keystring = $gpg->export($key[0]); + if ( $keystring =~ /BEGIN PGP PUBLIC KEY BLOCK/) { + return $keystring; + } +} + +1; diff --git a/C4/Installer/PerlDependencies.pm b/C4/Installer/PerlDependencies.pm index ca220aa..9530ac3 100644 --- a/C4/Installer/PerlDependencies.pm +++ b/C4/Installer/PerlDependencies.pm @@ -689,6 +689,11 @@ our $PERL_DEPS = { 'required' => '1', 'min_ver' => '0.22', }, + 'Crypt::GPG' => { + 'usage' => 'Messaging (encryption)', + 'required' => '1', + 'min_ver' => '1.52', + }, }; 1; diff --git a/C4/Letters.pm b/C4/Letters.pm index 93bc34d..7ab87b6d 100644 --- a/C4/Letters.pm +++ b/C4/Letters.pm @@ -29,6 +29,7 @@ use C4::Branch; use C4::Log; use C4::SMS; use C4::Debug; +use C4::GnuPG; use Koha::DateUtils; use Date::Calc qw( Add_Delta_Days ); use Encode; @@ -301,13 +302,18 @@ sub SendAlerts { }, want_librarian => 1, ) or return; - + my $subject = Encode::encode( "utf8", "" . $letter->{title} ); + my $content = Encode::encode( "utf8", "" . $letter->{content} ); + if ( C4::GnuPG->does_exist($email) ) { + $subject = 'A message from your library'; + $content = C4::GnuPG->encrypt($content, $email); + } # ... then send mail my %mail = ( To => $email, From => $branchdetails->{'branchemail'} || C4::Context->preference("KohaAdminEmailAddress"), - Subject => Encode::encode( "utf8", "" . $letter->{title} ), - Message => Encode::encode( "utf8", "" . $letter->{content} ), + Subject => $subject, + Message => $content, 'Content-Type' => 'text/plain; charset="utf8"', ); sendmail(%mail) or carp $Mail::Sendmail::error; @@ -945,6 +951,10 @@ sub _send_message_by_email { my $content = encode('utf8', $message->{'content'}); my $content_type = $message->{'content_type'} || 'text/plain; charset="UTF-8"'; my $is_html = $content_type =~ m/html/io; + if ( C4::GnuPG->does_exist($to_address) ) { + $subject = 'A message from your library'; + $content = C4::GnuPG->encrypt($content, $to_address); + } my %sendmail_params = ( To => $to_address, From => $message->{'from_address'} || C4::Context->preference('KohaAdminEmailAddress'), diff --git a/debian/control b/debian/control index 1d2c7e6..2724410 100644 --- a/debian/control +++ b/debian/control @@ -25,6 +25,7 @@ Build-Depends: libalgorithm-checkdigits-perl, libcgi-session-serialize-yaml-perl, libclass-accessor-perl, libclass-factory-util-perl, + libcrypt-gpg-perl, libdata-ical-perl, libdata-paginator-perl, libdate-calc-perl, @@ -174,6 +175,7 @@ Depends: ${misc:Depends}, at, daemon, debconf, + gnupg, idzebra-2.0, libjs-jquery, libjs-yui, @@ -211,6 +213,7 @@ Depends: libalgorithm-checkdigits-perl, libcgi-session-serialize-yaml-perl, libclass-accessor-perl, libclass-factory-util-perl, + libcrypt-gpg-perl, libdata-ical-perl, libdata-paginator-perl, libdate-calc-perl, diff --git a/debian/control.in b/debian/control.in index e2b8882..bcde7b3 100644 --- a/debian/control.in +++ b/debian/control.in @@ -66,6 +66,7 @@ Depends: ${misc:Depends}, at, daemon, debconf, + gnupg, idzebra-2.0, libjs-jquery, libjs-yui, diff --git a/install_misc/debian.packages b/install_misc/debian.packages index 531b056..c36f1dc 100644 --- a/install_misc/debian.packages +++ b/install_misc/debian.packages @@ -2,6 +2,7 @@ apache2 install at install cvs install daemon install +gnupg install gcc install gettext install idzebra-2.0 install @@ -23,6 +24,7 @@ libcgi-session-perl install libcgi-session-serialize-yaml-perl install libclass-accessor-perl install libclass-factory-util-perl install +libcypt-gpg-perl install libdata-ical-perl install libdata-paginator-perl install libdate-calc-perl install diff --git a/install_misc/ubuntu.10.04.packages b/install_misc/ubuntu.10.04.packages index 1d1470e..fd72a9d 100644 --- a/install_misc/ubuntu.10.04.packages +++ b/install_misc/ubuntu.10.04.packages @@ -35,6 +35,7 @@ libidzebra-2.0-modules install # crypto packages +gnupg install libgcrypt11 install libgcrypt11-dev install @@ -62,6 +63,7 @@ libcgi-session-perl install libcgi-session-serialize-yaml-perl install libclass-accessor-perl install libclass-factory-util-perl install +libcrypt-gpg-perl install libdata-ical-perl install libdata-paginator-perl install libdate-calc-perl install diff --git a/install_misc/ubuntu.12.04.packages b/install_misc/ubuntu.12.04.packages index df87256..4973a57 100644 --- a/install_misc/ubuntu.12.04.packages +++ b/install_misc/ubuntu.12.04.packages @@ -34,6 +34,7 @@ libidzebra-2.0-modules install # crypto packages +gnupg install libgcrypt11 install libgcrypt11-dev install @@ -64,6 +65,7 @@ libchi-driver-memcached-perl install libchi-perl install libclass-accessor-perl install libclass-factory-util-perl install +libcrypt-gpg-perl install libdata-ical-perl install libdata-paginator-perl install libdate-calc-perl install diff --git a/install_misc/ubuntu.packages b/install_misc/ubuntu.packages index 5356e54..93caf72 100644 --- a/install_misc/ubuntu.packages +++ b/install_misc/ubuntu.packages @@ -34,6 +34,7 @@ libidzebra-2.0-modules install # crypto packages +gnupg install libgcrypt11 install libgcrypt11-dev install @@ -64,6 +65,7 @@ libchi-driver-memcached-perl install libchi-perl install libclass-accessor-perl install libclass-factory-util-perl install +libcrypt-gpg-perl install libdata-ical-perl install libdata-paginator-perl install libdate-calc-perl install diff --git a/koha-tmpl/opac-tmpl/prog/en/includes/usermenu.inc b/koha-tmpl/opac-tmpl/prog/en/includes/usermenu.inc index 5a07d95..d0a2012 100644 --- a/koha-tmpl/opac-tmpl/prog/en/includes/usermenu.inc +++ b/koha-tmpl/opac-tmpl/prog/en/includes/usermenu.inc @@ -12,6 +12,7 @@ [% IF ( OpacPasswordChange ) %] [% IF ( passwdview ) %]
  • [% ELSE %]
  • [% END %]change my password
  • [% END %] + [% IF ( gnupgview ) %]
  • [% ELSE %]
  • [% END %]email encryption
  • [% IF ( ShowOpacRecentSearchLink ) %] [% IF ( searchhistoryview ) %]
  • [% ELSE %]
  • [% END %]my search history
  • [% END %] diff --git a/koha-tmpl/opac-tmpl/prog/en/modules/opac-gnupg.tt b/koha-tmpl/opac-tmpl/prog/en/modules/opac-gnupg.tt new file mode 100644 index 0000000..b1d9e26 --- /dev/null +++ b/koha-tmpl/opac-tmpl/prog/en/modules/opac-gnupg.tt @@ -0,0 +1,59 @@ +[% INCLUDE 'doc-head-open.inc' %][% IF ( LibraryNameTitle ) %][% LibraryNameTitle %][% ELSE %]Koha online[% END %] catalog › Add or change your GPG key +[% INCLUDE 'doc-head-close.inc' %] + + +
    +
    +[% INCLUDE 'masthead.inc' %] + +
    +
    +
    +

    [% firstname %] [% surname %]'s account ⇢ Add or change your GPG key

    +

    If you paste your GPG public key here, we will try to encrypt the emails we send you.
    + If you want to learn about email encryption, you will find some information at link

    + + [% IF ( Error_messages ) %] +

    There was a problem with your submission

    +

    + [% IF ( NoGnuPG ) %] + Could not find the path to GnuPG. This has to be set by the library for encryption to work. Please ask our staff about it. + [% END %] +

    + [% END %] + +

    Path to GPG [% IF (gpgbinary) %]found[% ELSE %]not found[% END %].

    + +
    +
    +

    +
    +
    +
    Cancel
    +
    + + [% IF ( does_exist ) %] +

    Delete the key from our server if you wish to receive unencrypted emails in the future.

    +
    + +
    +
    + [% END %] + +
    +
    +
    +
    +
    +
    +[% INCLUDE 'navigation.inc' IsPatronPage=1 %] +
    +
    +
    +[% INCLUDE 'opac-bottom.inc' %] diff --git a/opac/opac-gnupg.pl b/opac/opac-gnupg.pl new file mode 100755 index 0000000..0c4b12d --- /dev/null +++ b/opac/opac-gnupg.pl @@ -0,0 +1,81 @@ +#!/usr/bin/perl + +# Copyright 2013 Mirko Tietgen +# +# This file is part of Koha +# +# Koha is free software; you can redistribute it and/or modify it under the +# terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# Koha 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 Koha; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +use strict; +use warnings; + +use CGI; + +use C4::Auth; # checkauth, getborrowernumber. +use C4::Context; +use Digest::MD5 qw(md5_base64); +use C4::Circulation; +use C4::Members; +use C4::Output; +use C4::GnuPG; + +my $query = new CGI; +my $dbh = C4::Context->dbh; + +my ( $template, $borrowernumber, $cookie ) = get_template_and_user( + { + template_name => "opac-gnupg.tt", + query => $query, + type => "opac", + authnotrequired => 0, + flagsrequired => { borrow => 1 }, + debug => 1, + } +); + +# get borrower information .... +my ( $borr ) = GetMemberDetails( $borrowernumber ); +my $patronemail = $borr->{'email'}; +# save key if key received +if ( $query->param('PublicKey') ) { + C4::GnuPG->add_key($query->param('PublicKey'), $patronemail); +# $template->param( 'addedkey' => $query->param('PublicKey') ); +# $template->param( 'added' => '1' ); +} +elsif ( $query->param('DeleteKey') ) { + C4::GnuPG->del_key($patronemail); +} + if ( C4::GnuPG->does_exist($patronemail) ) { + # TODO: display public key if present + my $keythingy = C4::GnuPG->get_key($patronemail); + $template->param( 'does_exist' => '1' ); + $template->param( 'keythingy' => $keythingy ); + } + # ask for key + #$template->param( 'Ask_data' => '1' ); + #$template->param( 'Error_messages' => '1' ); + + my $gnupgbinary = '/usr/bin/gpg'; + if (-e $gnupgbinary) { + $template->param( 'gpgbinary' => '1' ); + } + +$template->param( + firstname => $borr->{'firstname'}, + surname => $borr->{'surname'}, + patronemail => $patronemail, + gnupgview => 1, +); + +output_html_with_http_headers $query, $cookie, $template->output; diff --git a/opac/opac-sendbasket.pl b/opac/opac-sendbasket.pl index da74745..2b2915e 100755 --- a/opac/opac-sendbasket.pl +++ b/opac/opac-sendbasket.pl @@ -33,6 +33,7 @@ use C4::Auth; use C4::Output; use C4::Biblio; use C4::Members; +use C4::GnuPG; my $query = new CGI; @@ -149,6 +150,21 @@ if ( $email_add ) { my $boundary = "====" . time() . "===="; $mail{'content-type'} = "multipart/mixed; boundary=\"$boundary\""; + + my $email_header_and_body = $email_header . $body; + + my $attachmentfilename; + # encrypt the attachment if we have a key + if ( C4::GnuPG->does_exist($email_add) ) { + $mail{'subject'} = "A message from your library"; + $email_header_and_body = C4::GnuPG->encrypt($email_header_and_body, $email_add); + $iso2709 = C4::GnuPG->encrypt($iso2709, $email_add); + $attachmentfilename = 'attachment.gpg'; + } + else { + $attachmentfilename = 'basket.iso2709'; + } + my $isofile = encode_base64(encode("UTF-8", $iso2709)); $boundary = '--' . $boundary; $mail{body} = <\n(.*)\n/s ) { $body = encode_qp($1); } + if ( $template_res =~ /\n(.*)\n/s ) { + $body = encode_qp($1); + } my $boundary = "====" . time() . "===="; # We set and put the multipart content $mail{'content-type'} = "multipart/mixed; boundary=\"$boundary\""; + my $email_header_and_body = $email_header . $body; + + my $attachmentfilename; + # encrypt the attachment if we have a key + if ( C4::GnuPG->does_exist($email) ) { + $mail{'subject'} = "A message from your library"; + $email_header_and_body = C4::GnuPG->encrypt($email_header_and_body, $email); + $iso2709 = C4::GnuPG->encrypt($iso2709, $email); + $attachmentfilename = 'attachment.gpg'; + } + else { + $attachmentfilename = 'shelf.iso2709'; + } my $isofile = encode_base64(encode("UTF-8", $iso2709)); + $boundary = '--' . $boundary; $mail{body} = <