old-www/LDP/LG/issue75/jenkins.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\" &lt; report.ps\n".
" Part-size must be &gt;= 1\n"
if ( ($#ARGV &lt; 1) or ($#ARGV &gt; 2) or ($ARGV[0] &lt; 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-&gt;new( # create signed message.
To =&gt; $ARGV[1],
Subject =&gt; 'Secure Email Print Job # '.time,
Type =&gt; 'multipart/encrypted');
$msg-&gt;attr ( "content-type.protocol" =&gt; "pgp-encrypted");
$msg-&gt;attach( Type =&gt; 'application/pgp-encrypted',
Encoding=&gt; 'binary',
Data =&gt; "Version: 1\n");
$msg-&gt;attach( Type =&gt; 'application/octet-stream',
Encoding=&gt; 'binary',
Path =&gt; "/usr/local/bin/pgp -fas - |");
$msg-&gt;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 &gt;= 1\n" if ( ($#ARGV != 0) or ($ARGV[0] &lt; 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-&gt;new(
To =&gt; $Dest, # Construct a message containing the
Subject =&gt; $Subj, # output buffer contents.
Type =&gt; 'message/partial',
Encoding=&gt; '7bit',
Data =&gt; $OutBuf);
$part-&gt;attr("content-type.id" =&gt; "$Id");
$part-&gt;attr("content-type.number" =&gt; "$Number");
$part-&gt;attr("content-type.total" =&gt; "$Total") if ($Number eq $Total);
$part-&gt;send; # Send the message.
}
$OutBuf = $InpBuf; # Move input buffer contents to
$InpBuf = "" # output buffer and exit.
}
while (&lt;STDIN&gt;) { # Main loop.
if ( (substr($_, 0, 3) eq "To:") &amp;&amp; (! defined($Dest)) ) {
$Dest = substr($_, 4, length($_) - 4); chomp $Dest; next }
if ( (substr($_, 0, 8) eq "Subject:") &amp;&amp; (! defined($Subj)) ) {
$Subj = substr($_, 9, length($_) - 9); chomp $Subj; next }
if ( (length($InpBuf . $_)) &gt; ($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&lt;=$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&gt;$tmp | lpr -P $user &gt;/dev/null" or goto I;
for ($k=1;$k&lt;=$tp{$part[0]};$k++){ # Assemble parts into pipe.
$message=$pop-&gt;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 &gt;= 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 "") &amp;&amp; ($print eq "R")) {$print="Y"}
}
$l++;
}
print $fh $buffer or goto I;
}
$fh-&gt;close || goto I;
open $fh, $tmp;
while (&lt;$fh&gt;) { chomp; syslog('info', $_) }
close $fh;
for ($k=1;$k&lt;=$tp{$part[0]};$k++){
$pop-&gt;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 &copy; 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 ============================================================-->