426 lines
20 KiB
HTML
426 lines
20 KiB
HTML
<!--startcut ==============================================-->
|
|
<!-- *** BEGIN HTML header *** -->
|
|
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2//EN">
|
|
<HTML><HEAD>
|
|
<title>Secure Printing with PGP LG #75</title>
|
|
</HEAD>
|
|
<BODY BGCOLOR="#FFFFFF" TEXT="#000000" LINK="#0000FF" VLINK="#0000AF"
|
|
ALINK="#FF0000">
|
|
<!-- *** END HTML header *** -->
|
|
|
|
<CENTER>
|
|
<A HREF="http://www.linuxgazette.com/">
|
|
<IMG ALT="LINUX GAZETTE" SRC="../gx/lglogo.png"
|
|
WIDTH="600" HEIGHT="124" border="0"></A>
|
|
<BR>
|
|
|
|
<!-- *** BEGIN navbar *** -->
|
|
<IMG ALT="" SRC="../gx/navbar/left.jpg" WIDTH="14" HEIGHT="45" BORDER="0" ALIGN="bottom"><A HREF="lg_bytes.html"><IMG ALT="[ Prev ]" SRC="../gx/navbar/prev.jpg" WIDTH="16" HEIGHT="45" BORDER="0" ALIGN="bottom"></A><A HREF="index.html"><IMG ALT="[ Table of Contents ]" SRC="../gx/navbar/toc.jpg" WIDTH="220" HEIGHT="45" BORDER="0" ALIGN="bottom" ></A><A HREF="../index.html"><IMG ALT="[ Front Page ]" SRC="../gx/navbar/frontpage.jpg" WIDTH="137" HEIGHT="45" BORDER="0" ALIGN="bottom"></A><A HREF="http://www.linuxgazette.com/cgi-bin/talkback/all.py?site=LG&article=http://www.linuxgazette.com/issue75/jenkins.html"><IMG ALT="[ Talkback ]" SRC="../gx/navbar/talkback.jpg" WIDTH="121" HEIGHT="45" BORDER="0" ALIGN="bottom" ></A><A HREF="../faq/index.html"><IMG ALT="[ FAQ ]" SRC="./../gx/navbar/faq.jpg"WIDTH="62" HEIGHT="45" BORDER="0" ALIGN="bottom"></A><A HREF="jones.html"><IMG ALT="[ Next ]" SRC="../gx/navbar/next.jpg" WIDTH="15" HEIGHT="45" BORDER="0" ALIGN="bottom" ></A><IMG ALT="" SRC="../gx/navbar/right.jpg" WIDTH="15" HEIGHT="45" ALIGN="bottom">
|
|
<!-- *** END navbar *** -->
|
|
<P>
|
|
</CENTER>
|
|
|
|
<!--endcut ============================================================-->
|
|
|
|
<H4 ALIGN="center">
|
|
"Linux Gazette...<I>making Linux just a little more fun!</I>"
|
|
</H4>
|
|
|
|
<P> <HR> <P>
|
|
<!--===================================================================-->
|
|
|
|
<center>
|
|
<H1><font color="maroon">Secure Printing with PGP</font></H1>
|
|
<H4>By <a href="mailto:grahjenk@au1.ibm.com">Graham Jenkins</a></H4>
|
|
</center>
|
|
<P> <HR> <P>
|
|
|
|
<!-- END header -->
|
|
|
|
|
|
|
|
|
|
<h2>The Brother Internet Print Protocol</h2>
|
|
|
|
<p>A recent article
|
|
<a href="../issue65/jenkins.html">
|
|
"Internet Printing - Another Way"</a> described a printing protocol which
|
|
can be used with some
|
|
<a href="http://www.brother.com">Brother</a> printers. It enables users
|
|
of Windows machines to send a multi-part base-64 encoded print file via
|
|
email directly to a Brother print server.</p>
|
|
|
|
<p>The article went on to show how the functionality of the Brother print
|
|
server can be implemented in simple Perl program which periodically polls
|
|
a POP3 server to check for jobs whose parts have all arrived. When such a
|
|
job is detected, its parts are downloaded in sequence and decoded for
|
|
printing.</p>
|
|
|
|
<p>A subsequent
|
|
article <a href="../issue68/jenkins.html">
|
|
"A Linux Client for the Brother Internet Print Protocol"</a> showed a
|
|
simple client program which can be used on Linux workstations for sending
|
|
print jobs to a Brother print server. That program was implemented as a
|
|
shell script which split an incoming stream into parts and placed them in
|
|
a scratch directory for subsequent encoding and transmission.</p>
|
|
|
|
<p>I have since developed a Perl client program which processes the incoming
|
|
stream on-the-fly and requires no temporary storage. This is, of course, a
|
|
much neater way to do things. The down-side is that there is no way of
|
|
ascertaining the total part-count until the last part is being processed. A
|
|
slight modification to the server program was therefore required to
|
|
accomodate an empty "total-parts" field on all except the final part.</p>
|
|
|
|
<h2>A Hole Big Enough to Drive a Truck Through</h2>
|
|
|
|
<p>The whole arrangement as outlined above has been in use at my place
|
|
for several months, and has saved us a whole lot of time and trouble.
|
|
However, as pointed out by one reviewer, what we really have here is a
|
|
security hole big enough to drive a truck through! Anybody in the
|
|
whole wide world can send celebrity pictures to your color printer,
|
|
and there's not a lot you can do about it.</p>
|
|
|
|
<p>Somebody else asked why we go to the trouble of splitting a large
|
|
job into parts without first trying to compress it. And indeed there
|
|
are a great number of jobs whose size can be significantly reduced through
|
|
compression.</p>
|
|
|
|
<p>Then there were the Windows (and other) users, who thought that
|
|
everything should be written in Perl for portability. And the Standards
|
|
Nazis, who thought that the job parts should be sent as 'message/partial'
|
|
entities in accordance with RFC 2046.</p>
|
|
|
|
<h2>Who's Printing Pamela Anderson Pictures?</h2>
|
|
|
|
<p>Of all the issues outlined above, the most serious is indubitably that of
|
|
client authentication. And the solution is blindingly obvious; why not
|
|
use one of the Public Key Encryption mechanisms now available?
|
|
What we need here is for the sender to digitally sign the entire message
|
|
using his private key. Upon receipt at the server, the message can then
|
|
be authenticated by application of the sender's public key. There's no
|
|
need for any secret key-entry rites at the server, so the whole server
|
|
operation can be automated.</p>
|
|
|
|
<p>A message signed in this fashion can be signed in 'clear' form;
|
|
the message itself is then sent as is, with a digital signature appended to
|
|
its end. If you elect not to use 'clear' signing, the message will
|
|
(if usual defaults are accepted) actually be compressed and the
|
|
signature will be incorporated therein. This comes pretty close to what
|
|
we need!</p>
|
|
|
|
<p>There is a set of Perl modules (Crypt::OpenPGP) which can perform the
|
|
necessary signature and verification procedures, so we can actually write
|
|
the entire client and server programs in a portable form. I had some
|
|
difficulty with installing these, since they require that a number
|
|
of other modules be installed, and they require the 'PARI-GP' mathematics
|
|
package. I elected instead to use
|
|
<a href="http://www.pgpi.org">pgp-2.6.3ia</a>;
|
|
<a href="http://www.gnupg.org">GnuPG-v1.0.6</a> will also
|
|
work with the programs in this article.</p>
|
|
|
|
<p>There are a couple of Perl modules (Crypt::PGPSimple and PGP::Sign) which
|
|
can be used to call pgp-2.6.3ia and its equivalent executables, but each
|
|
of them creates temporary files, and that's something I try to avoid where
|
|
possible.</p>
|
|
|
|
<h2>Appeasing the Standards Nazis</h2>
|
|
|
|
<p><a href="http://www.faqs.org/rfcs/rfc3156.html">RFC 3156</a>
|
|
("MIME Security with OpenPGP") describes how the OpenPGP
|
|
Message Format can be used to provide privacy and authentication using
|
|
MIME security content types. In particular, it decrees that after signing
|
|
our message by encrypting it with our private key, we should send it
|
|
as a 'multipart/encrypted' message. The first part should contain an
|
|
'application/pgp-encrypted' message showing a version number in plain-text
|
|
form; the second part should contain our actual PGP message.
|
|
|
|
<p>This is a
|
|
bit over-the-top, but the overhead is small, and the whole deal is
|
|
easily done using the Perl MIME::Lite module, as shown in the
|
|
'SEPclientPGP.pl' program hereunder.</p>
|
|
|
|
<p>So how do we send a long message which needs to be broken into parts
|
|
for passage through intermediate mail servers? RFC 3156 tells us we should
|
|
use the MIME message/partial mechanism
|
|
(<a href="http://www.faqs.org/rfcs/rfc2046.html">RFC 2046</a>) instead! I
|
|
think what they actually mean is "as well". So our output from
|
|
'SEPclientPGP.pl' is actually fed into the 'SplitSend.pl' program (also
|
|
hereunder) which extracts the message "To:" and "Subject:" lines and
|
|
replicates them into each sequentially numbered 'message/partial' component
|
|
that it generates.</p>
|
|
|
|
<h2>The Client Program</h2>
|
|
|
|
<p>Here's the
|
|
<a href="misc/jenkins/SEPclientPGP.pl.txt">client program</a>.
|
|
It's pretty much self-explanatory. A pipe
|
|
to the 'SplitSend.pl' program is opened for output. If
|
|
the passphrase is supplied on the command-line (dangerous, but sometimes
|
|
necessary!), it is planted in an environment variable.</p>
|
|
|
|
<p>The multipart MIME message as previously described is then constructed,
|
|
taking its second body part from a pipe fed by the PGP executable. If the
|
|
executable doesn't find a suitable passphase in the appropriate environment
|
|
variable, it requests it in a terminal window.</p>
|
|
|
|
<pre>
|
|
#!/usr/local/bin/perl -w
|
|
# @(#) SEPclientPGP.pl Secure Email Print client program. Ref: RFC 3156.
|
|
# Takes incoming stream and generates PGP-signed message
|
|
# which is piped to split-and-send program for email
|
|
# transmission to server. Requires 'pgp' program.
|
|
# Graham Jenkins, IBM GSA, Dec. 2001. [Rev'd 2001-12-30]
|
|
|
|
use strict;
|
|
use File::Basename;
|
|
use MIME::Lite;
|
|
use IO::File;
|
|
use Env qw(PGPPASS);
|
|
|
|
die "Usage: ".basename($0)." kb-per-part destination [passphrase]\n".
|
|
" e.g.: ".basename($0)." 16 lp3\@pserv.acme.com \"A secret\" < report.ps\n".
|
|
" Part-size must be >= 1\n"
|
|
if ( ($#ARGV < 1) or ($#ARGV > 2) or ($ARGV[0] < 1) );
|
|
|
|
my $fh = new IO::File "| /usr/local/bin/SplitSend.pl $ARGV[0]";
|
|
if( defined($ARGV[2]) ) {$PGPPASS=$ARGV[2]}
|
|
if( ! defined ($PGPPASS)) {$PGPPASS=""} # Plant passphrase in environment and
|
|
my $msg = MIME::Lite->new( # create signed message.
|
|
To => $ARGV[1],
|
|
Subject => 'Secure Email Print Job # '.time,
|
|
Type => 'multipart/encrypted');
|
|
$msg->attr ( "content-type.protocol" => "pgp-encrypted");
|
|
$msg->attach( Type => 'application/pgp-encrypted',
|
|
Encoding=> 'binary',
|
|
Data => "Version: 1\n");
|
|
$msg->attach( Type => 'application/octet-stream',
|
|
Encoding=> 'binary',
|
|
Path => "/usr/local/bin/pgp -fas - |");
|
|
$msg->print($fh); # Pipe the signed message into a
|
|
__END__ # split-and-send program.
|
|
</pre>
|
|
|
|
<h2>Split-and-Send</h2>
|
|
|
|
<p>Here's the
|
|
<a href="misc/jenkins/SplitSend.pl.txt">split-and-send program</a>.
|
|
The main loop at the end works
|
|
just as described above - extract the destination and subject fields,
|
|
accumulate lines until we are about to exceed the message-size limit
|
|
supplied as a parameter, then feed what we have to an output routine.</p>
|
|
|
|
<p>The output routine needs to re-insert the destination and subject
|
|
fields, and also insert a message-identifier, part-number and total-part-count.
|
|
The total-part-count is only required on the final part. All fairly
|
|
easy - except we don't know whether the current part is the final part
|
|
until we look for the next part. So we get around this by using a double-buffer
|
|
arrangement, where we don't actually output a buffer's contents until we
|
|
have the next buffer.</p>
|
|
|
|
<p>Using MIME::Simple in this program is really overkill; however, what
|
|
it does accomplish is that it tries to find an appropriate mailer
|
|
program on whatever platform it executes.</p>
|
|
|
|
<pre>
|
|
#!/usr/local/bin/perl -w
|
|
# @(#) SplitSend.pl Splits and sends an email message (Ref: RFC 1521, 2046).
|
|
# Graham Jenkins, IBM GSA, December 2001.
|
|
|
|
use strict;
|
|
use File::Basename;
|
|
use MIME::Lite;
|
|
use Net::Domain;
|
|
my ($Id,$j,$Dest,$Subj,$part,$InpBuf,$OutBuf,$Number,$Total);
|
|
|
|
die "Usage: ".basename($0)." kb-per-part\n".
|
|
" Part-size must be >= 1\n" if ( ($#ARGV != 0) or ($ARGV[0] < 1) );
|
|
|
|
$Id=(getlogin."\@".Net::Domain::hostfqdn().time) or $Id="unknown_user".time;
|
|
$Number = 0; $Total = ""; $OutBuf=""; $InpBuf=""; print STDERR "\n";
|
|
|
|
sub do_output { # Output subroutine.
|
|
die basename($0)." .. destination undefined!\n" if ! defined($Dest);
|
|
$Subj = "" if ! defined($Subj);
|
|
if ($OutBuf ne "") { # If output buffer contains data,
|
|
$Number++; # increment Number, and check whether
|
|
$Total=$Number if $InpBuf eq ""; # it is the last buffer.
|
|
print STDERR "Sending part: ", $Number,"/",$Total,"\n";
|
|
$part = MIME::Lite->new(
|
|
To => $Dest, # Construct a message containing the
|
|
Subject => $Subj, # output buffer contents.
|
|
Type => 'message/partial',
|
|
Encoding=> '7bit',
|
|
Data => $OutBuf);
|
|
$part->attr("content-type.id" => "$Id");
|
|
$part->attr("content-type.number" => "$Number");
|
|
$part->attr("content-type.total" => "$Total") if ($Number eq $Total);
|
|
$part->send; # Send the message.
|
|
}
|
|
$OutBuf = $InpBuf; # Move input buffer contents to
|
|
$InpBuf = "" # output buffer and exit.
|
|
}
|
|
|
|
while (<STDIN>) { # Main loop.
|
|
if ( (substr($_, 0, 3) eq "To:") && (! defined($Dest)) ) {
|
|
$Dest = substr($_, 4, length($_) - 4); chomp $Dest; next }
|
|
if ( (substr($_, 0, 8) eq "Subject:") && (! defined($Subj)) ) {
|
|
$Subj = substr($_, 9, length($_) - 9); chomp $Subj; next }
|
|
if ( (length($InpBuf . $_)) > ($ARGV[0] * 1024) ) {do_output}
|
|
$InpBuf = $InpBuf . $_
|
|
}
|
|
foreach $j (1,2) {do_output} # Flush both buffers and exit.
|
|
__END__
|
|
</pre>
|
|
|
|
<h2>The Art of Jigsaw Assembly</h2>
|
|
|
|
<p>There is no guarantee that the segments of our print-job will arrive
|
|
at the server in the same order as they left the client. We cannot be sure
|
|
that there will even be the same number of segments, since message-transfer
|
|
agents along the way are allowed to re-assemble message/partial entities as
|
|
they see fit. So what we have at the server end is a set of jigsaw
|
|
puzzles, with the pieces of each puzzle being related by a common
|
|
message-identifier, and their placement within that puzzle being
|
|
determined by their part-numbers.</p>
|
|
|
|
<p>For a full listing of the 'SEPserverPGP.pl', see the attached
|
|
<a href="misc/jenkins/SEPserverPGP.pl.txt">text version</a>. I haven't
|
|
bothered to replicate all of it hereunder, since much of it is the
|
|
same as the program shown in "Internet Printing - Another Way".</p>
|
|
|
|
<p>Basically, the program is intended for invocation via an entry in
|
|
'/etc/inittab', and loops continually thereafter, with half-minute
|
|
pauses between each loop. During each loop, it visits the mailboxes
|
|
of one or more printer-entities on a POP3 server, and deletes any
|
|
stale articles therein before tabulating the message-id's and
|
|
part-numbers of the remaining articles. When it finds a full set of
|
|
message/partial entities, it sucks each of them in part-number
|
|
sequence from the server, and throws their contents into a pipe.
|
|
The program-extract hereunder shows what happens then.</p>
|
|
|
|
<p>The relevant message content is deemed to begin at the
|
|
"-----BEGIN.." line in the first part. For subsequent parts, it begins
|
|
after the first blank line once an "id=.." line has been seen.</p>
|
|
|
|
<p>Once in the pipe, the composite message content passes to the PGP
|
|
executable for validation/decryption, and thence to an appropriate printer.
|
|
Validation output is passed to a scratch file, and then recovered from
|
|
there for logging. A validation failure results in no output to the
|
|
printer.</p>
|
|
|
|
<pre>
|
|
for ($k=1;$k<=$tp{$part[0]};$k++){ # Check if we have all parts.
|
|
goto I if ! defined($slot{$part[0]."=".$k});
|
|
}
|
|
$fh=new IO::File
|
|
"| /usr/local/bin/pgp -f 2>$tmp | lpr -P $user >/dev/null" or goto I;
|
|
for ($k=1;$k<=$tp{$part[0]};$k++){ # Assemble parts into pipe.
|
|
$message=$pop->get($slot{$part[0]."=".$k});
|
|
$l=0; $buffer=""; $print="N";
|
|
while ( defined(@$message[$l]) ) {
|
|
chomp @$message[$l]; # Part 1: start at "-----BEGIN",
|
|
if( $k == 1 ) { # stop before 2nd blank line.
|
|
if( @$message[$l]=~m/^-----BEGIN/ ) { $m=-2; $print="Y"}
|
|
if( $print eq "Y" ) {
|
|
if( @$message[$l] eq "" ) { $m++; if( $m >= 0) {last} }
|
|
$buffer=$buffer.@$message[$l]."\n"
|
|
}
|
|
} # Part 2,3,..: skip 1 blank line
|
|
else { # after "id=", then start; stop
|
|
if( $print eq "Y" ) { # before next blank line.
|
|
if( @$message[$l] eq "" ) {last}
|
|
$buffer=$buffer.@$message[$l]."\n"
|
|
}
|
|
if( @$message[$l]=~m/id=/ ) {$print="R"}
|
|
if((@$message[$l] eq "") && ($print eq "R")) {$print="Y"}
|
|
}
|
|
$l++;
|
|
}
|
|
print $fh $buffer or goto I;
|
|
}
|
|
$fh->close || goto I;
|
|
open $fh, $tmp;
|
|
while (<$fh>) { chomp; syslog('info', $_) }
|
|
close $fh;
|
|
for ($k=1;$k<=$tp{$part[0]};$k++){
|
|
$pop->delete($slot{$part[0]."=".$k})
|
|
}
|
|
goto I;
|
|
}
|
|
J: }
|
|
}
|
|
I:}
|
|
</pre>
|
|
|
|
<h2>Copycat Crime</h2>
|
|
|
|
<p>In the scheme outlined above, there is nothing to prevent a determined
|
|
trouble-maker replicating and replaying an entire authenticated message. To
|
|
cover this possibility, you need to retain each log entry for a week or
|
|
so, and
|
|
to reject any incoming message having a corresponding signature and
|
|
signature-date.</p>
|
|
|
|
<p>If, in addition, you wish to prevent someone from viewing the actual
|
|
data travelling to your printer as it traverses the Internet, you need
|
|
to change the PGP executable parameters at the client
|
|
end so that the data is encrypted with the server's public key as well
|
|
as signed; you will also need to feed a passphrase into the PGP executable
|
|
at the server end.</p>
|
|
|
|
<h2>GNU Privacy Guard</h2>
|
|
|
|
<p>I have a mental image of somebody reading this and saying: "How come he's
|
|
using pgp-2.6.3ia if he doesn't like un-necessary temporary files?" It's a
|
|
good question, because pgp-2.6.3ia creates temporary files both during
|
|
encryption and during decryption.</p>
|
|
|
|
<p>To get around this, or to comply with whatever laws are applicable in
|
|
your country, you may wish to use GnuPG-v1.0.6 (or later version
|
|
of the same) instead. In the client program, you will need to change the
|
|
parameters with which the executable is called. And you won't be able to
|
|
plant your passphrase in an environment variable.</p>
|
|
|
|
<p>I have attached for your interest a 'Lite'
|
|
<a href="misc/jenkins/SEPWincl.pl.txt">GPG client program</a> which will
|
|
execute on Windows machines with 'out-of-the-box' ActiveState Perl or
|
|
IndigoPerl, and requires no extra modules.</p>
|
|
|
|
<p>During decryption to a pipe, the 'gpg' executable actually outputs data
|
|
to the pipe until (and in some cases, after) it encounters a problem. So
|
|
you will need to send your output to a scratch file - then send that scratch
|
|
file to your printer if the decryption process completed satisfactorily.</p>
|
|
|
|
|
|
|
|
|
|
<!-- *** BEGIN bio *** -->
|
|
<SPACER TYPE="vertical" SIZE="30">
|
|
<P>
|
|
<H4><IMG ALIGN=BOTTOM ALT="" SRC="../gx/note.gif">Graham Jenkins</H4>
|
|
<EM>Graham is a Unix Specialist at IBM Global Services, Australia. He lives
|
|
in Melbourne and has
|
|
built and managed many flavors of proprietary and open systems on several
|
|
hardware platforms.</EM>
|
|
|
|
<!-- *** END bio *** -->
|
|
|
|
<!-- *** BEGIN copyright *** -->
|
|
<P> <hr> <!-- P -->
|
|
<H5 ALIGN=center>
|
|
|
|
Copyright © 2002, Graham Jenkins.<BR>
|
|
Copying license <A HREF="../copying.html">http://www.linuxgazette.com/copying.html</A><BR>
|
|
Published in Issue 75 of <i>Linux Gazette</i>, February 2002</H5>
|
|
<!-- *** END copyright *** -->
|
|
|
|
<!--startcut ==========================================================-->
|
|
<HR><P>
|
|
<CENTER>
|
|
<!-- *** BEGIN navbar *** -->
|
|
<IMG ALT="" SRC="../gx/navbar/left.jpg" WIDTH="14" HEIGHT="45" BORDER="0" ALIGN="bottom"><A HREF="lg_bytes.html"><IMG ALT="[ Prev ]" SRC="../gx/navbar/prev.jpg" WIDTH="16" HEIGHT="45" BORDER="0" ALIGN="bottom"></A><A HREF="index.html"><IMG ALT="[ Table of Contents ]" SRC="../gx/navbar/toc.jpg" WIDTH="220" HEIGHT="45" BORDER="0" ALIGN="bottom" ></A><A HREF="../index.html"><IMG ALT="[ Front Page ]" SRC="../gx/navbar/frontpage.jpg" WIDTH="137" HEIGHT="45" BORDER="0" ALIGN="bottom"></A><A HREF="http://www.linuxgazette.com/cgi-bin/talkback/all.py?site=LG&article=http://www.linuxgazette.com/issue75/jenkins.html"><IMG ALT="[ Talkback ]" SRC="../gx/navbar/talkback.jpg" WIDTH="121" HEIGHT="45" BORDER="0" ALIGN="bottom" ></A><A HREF="../faq/index.html"><IMG ALT="[ FAQ ]" SRC="./../gx/navbar/faq.jpg"WIDTH="62" HEIGHT="45" BORDER="0" ALIGN="bottom"></A><A HREF="jones.html"><IMG ALT="[ Next ]" SRC="../gx/navbar/next.jpg" WIDTH="15" HEIGHT="45" BORDER="0" ALIGN="bottom" ></A><IMG ALT="" SRC="../gx/navbar/right.jpg" WIDTH="15" HEIGHT="45" ALIGN="bottom">
|
|
<!-- *** END navbar *** -->
|
|
</CENTER>
|
|
</BODY></HTML>
|
|
<!--endcut ============================================================-->
|