"La Gaceta de Linux...¡haciendo Linux un poco más divertido!"


Impresión Segura con PGP

Por Graham Jenkins

Traducción al español por Daniel Guerrero
el día 07 de Septiembre 2002, para La Gaceta de Linux


El Protocolo de Impresión en Internet Brother

Un artículo reciente "Impresión en Internet - Otra Manera" describió un protocolo de impresión que puede ser usado con algunas impresoras Brother. Habilita a usuarios de máquinas Windows enviar un archivo impresión multi-parte codificado base-64 vía email directamente al servidor de impresión Brother.

El artículo mostraba cómo la funcionalidad del servidor de impresión Brother puede ser implementado en un programa simple de perl que pregunta períodicamente a un servidor POP3 para verificar los trabajos cuyas todas sus partes hayan arrivado. Cuando cada trabajo es detectado, sus partes son descargadas en secuencia y decodificadas para impresión.

Un artículo subsecuente "Un Cliente Linux para el Protocolo de Impresión en Internet Brother" mostró un programa cliente simple que podía ser usado en estaciones de trabajo Linux para mandar trabajos de impresión al servidor de impresión Brother. Ese programa fue implementado como un script shell que dividía un flujo entrante en partes y las depositaba en un directorio vacío para su subsecuente codificación y transmisión.

Desde ahí desarrollé un programa cliente en Perl que procesa el fujo entrante al "vuelo" y no requiere almacenamiento temporal. Esto es, clar, una manera mucho más aseada de hacer las cosas. La contra parte es que no hay manera de comprobar el total de partes hasta que la última parte haya sido procesada. Una modificación ligera al programa servidor fue por lo tanto requerida para acomodar un campo "partes-totales" vacío en todos, excepto en la parte final.

Un agujero demasiado grande para manejar un camión a través de el

El diseño entero como lo he esbozado anteriormente lo he estado usando por varios meses, y nos ha ahorrado mucho tiempo y problemas. Sin embargo, como lo precisó un crítico, lo que realmente tenemos aquí ¡es un agujero de seguridad demasiado grande como para manejar un camión a través de el! Cualquiera en el mundo entero puede mandar fotos de celebridades a tu impresora de color y no hay mucho que puedas hacer,

Alguien más preguntó porqué tenemos el problema de dividir un trabajo grande en partes sin primero tratar de comprimirlo. Y en efecto hay un gran número de trabajos cuyo tamaño puede ser reducido significativamente por medio de la compresión.

Entonces hubo algunos usuarios Windows (y otros), que pensaron que todo puede ser escrito en Perl para la portabilidad. Y los Estándares Nazis, quienes pensaron que las partes del trabajo deben ser mandadas como entidades 'message/partial' (mensaje/parcial) de acuerdo con RFC 2046.

¿Quién está imprimiendo las Fotos de Pamela Anderson?

De todos las puntos dichos arriba, el más serio es indudablemente el del cliente de autentificación. Y la solución es deslumbrantemente obvia, ¿porqué no usar uno de los mecanismos de Encriptación de Llave Pública disponibles actualmente? Lo que necesitamos es que el emisor firme digitalmente el mensaje entero usando su llave privada. Una vez recibido en el servidor, el mensaje puede entonces ser autentificado aplicando la llave pública del emisor. Así no hay necesidad de ningun rito secreto de registro de llave en el servidor, así la operación del servidor entero puede ser automatizada.

Un mensaje firmado de esta manera puede ser firmado de la forma 'clara', el mensaje por sí mismo es enviado como es, con una firma digital agregada al final. Si eliges no usar la firma 'clara', el mensaje será (si las opciones por defecto usuales son aceptadas) comprimido y la firma será incorporada dentro. ¡Esto se acerca demasiado a lo que necesitamos!

Hay un conjunto de módulos de Perl (Crypt::OpenPGP) el cual puede realizar las firmas necesarias y los procedimientos de verificación, así que hemos escrito los programas enteros de cliente y el servidor en una forma portable. Tuve alguna dificultad instalándolos, debido a que requieren que un número de otros módulos sean instalados, y ellos requieren el paquete matemático 'PARI-GP'. Elegí en su lugar usar pgp-2.6.3ia; GnuPG-v1.0.6 también trabajará con los programas de este artículo.

Hay un par de módulos de Perl (Crypt::PGPSimple y PGP::Sign) los cuales pueden ser usados para llamar a pgp-2.6.3ia y sus ejecutables equivalentes, pero cada uno de ellos crean archivos temporales, y eso es algo que trato de evitar cuando sea posible.

Apaciguando los Estándares Nazis

RFC 3156 ("Seguridad MIME con OpenPGP") describe cómo el Formato de Mensajes OpenPGP puede ser usado para proveer privacidad y autentificación usando los tipos de contenidos de seguridad MIME. En particular, decreta que después de firmar nuestro mensaje encriptándolo con nuestra llave privada, debemos mandarlo como un mensaje 'multipart/encrypted' (multiparte/encriptado). La primera parte debe contener un mensaje 'application/pgp-encrypted' mostrando un número de versión de la forma texto plano, la segunda parte debe contener nuestro mensaje PGP actual.

Esto es un poco superficial, pero los gastos son pequeños, y el reparto entero es fácilmente hecho usando el módulo MIME:Lite, como se muestra en el programa 'SEPclientPGP.pl' abajo.

Así que ¿cómo enviamos un mensaje largo que necesita ser dividido en partes para pasar a través de servidores intermediarios de mail? RFC 3156 nos dice que ¡debemos usar el mecanismo MIME de message/partial (RFC 2046) en su lugar! Pienso que lo que ellos realmente quieren decir es "también". Así nuestra salida de 'SEPclientPGP.pl' es enviada al programa 'SplitSend.pl' (también abajo) que extrae del mensaje las líneas "To:" ("Para:") y "Subject:" ("Asunto:") y las replica en cada componente 'message/partial' enumerado secuencialmente que genera.

El programa Cliente

Aquí está el programa cliente. Es muy auto-explicatorio. Una tubería al programa 'SplitSend.pl' es abierta para la salida. Si passphrase es suplida en la línea de comando (peligroso, pero a veces necesario), es puesta en una variable de entorno.

El mensaje multiparte MIME como se describió anteriormente es entonces construido tomando la segunda parte del cuerpo de una tubería que envía el ejecutable PGP. Si el ejecutable no encuentra una passphrase apropiada en el entorno de variables apropiado, la pregunta en una terminal.

#!/usr/local/bin/perl -w
# @(#) SEPclientPGP.pl	Programa de Impresión Segura Email. Ref: RFC 3156.
#			Toma un flujo entrante y genera un mensaje firmado PGP
#			el cuál es direccionado a un programa de divide-y-envía para
#			la transmisíón email al servidor. Requiere el programa 'pgp'
#			Graham Jenkins, IBM GSA, Dic. 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=""}	# Pone passphrase en el entorno y 
my $msg = MIME::Lite->new(		# crea el mensaje firmado.
                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);			# Direcciona el mensaje firmado a un programa de 
__END__					# divide-y-envía.

Divide-y-Envía

Aquí está el programa de divide-y-envía. El ciclo principal al final trabaja justo como se describió anteriormente - extrae los campos del destinatario y el asunto, acumula líneas hasta que estamos cerca de exceder el límite del tamaño de mensaje provisto como parámetro, entonces envía lo que tenemos a una rutina de salida.

La rutina de salida necesita re-insertar los campos de destino y asunto, y también inserta un identificador de mensaje, número de parte y conteo de las partes totales. El conteo de las partes totales sólo es requerido en la parte final. Todos son bastantes fáciles - excepto que no sabemos si la parte actual es la parte final hasta que busquemos la siguiente parte. Así logramos esto, usando un arreglo de doble buffer, donde no mandamos los contenidos de un buffer hasta que tenemos el siguiente buffer.

Usando MIME::Simple en este programa es realmente sorprendente; sin embargo, lo que no puede hacer es que trata de encontrar un programa de mail apropiado en cualquier plataforma que se ejecute..

#!/usr/local/bin/perl -w
# @(#) SplitSend.pl	Divide y manda un mensaje de email  (Ref: RFC 1521, 2046).
#			Graham Jenkins, IBM GSA, Diciembre 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 {				# Rutina de salida
  die basename($0)." .. destination undefined!\n" if ! defined($Dest);
  $Subj = ""                                      if ! defined($Subj);
  if ($OutBuf ne "") {			# Si el buffer de salida contiene datos, 
    $Number++;				# incrementar Number, y checar si 
    $Total=$Number if $InpBuf eq "";	# es el último buffer
    print STDERR "Sending part: ", $Number,"/",$Total,"\n";
    $part = MIME::Lite->new(
              To      => $Dest,		# Construye un mensaje con el contenido 
              Subject => $Subj,		# del buffer de salida.
              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;			# manda el mensaje
  }
  $OutBuf = $InpBuf;			# Mueve los contenidos del buffer de entrada al
  $InpBuf = ""				# buffer de salida y termina.
}

while (<STDIN>) {			# Ciclo principal
  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}		# Libera ambos buffers y termina
__END__

El Arte de Ensamblar Rompecabezas

No hay garantía de que los segmentos de nuestro trabajo de impresión arribarán al servidor en el mismo orden en que dejaron al cliente. No podemos estar seguros de que también habrá el mismo número de segmentos, debido a que el agente de transferencia de mensajes le es permitido en el camino re-ensamblar las entidades message/partial como parecen encajar. Así que lo que tenemos en el servidor es un conjunto de un rompecabezas, con las piezas de cada rompecabezas es relacionado por un identificador de mensaje común, y su lugar dentro del rompecabezas está determinado por los números de sus partes.

Para un listado completo de 'SEPserverPGP.pl', ve la versión de texto adjunta. No me incomoda replicar todo aquí abajo, debido a que muchas partes son las mismas que el programa mostrado en "Impresión en Internet - Otra Manera" .

Básicamente se prentende que el programa sea invocado por medio de una entrada en '/etc/inittab', y se cicle continuamente después de eso, con pausas de medio minuto entre cada ciclo. Durante cada ciclo, visita los buzones de correo de una o más entidades de impresoras en un servidor POP3, y borra cualquier artículo viejo que exista antes de tabular los id's de los mensajes y los números de partes de los artículos restantes. Cuando encuentra un conjunto completo de entidades message/partial, saca cada una de ellas en una secuencia número de partes del servidor, y manda su contenido a una tubería. El programa de extracción de aquí abajo, muestra lo que pasa entonces.

El contenido relevante del mensaje es considerado para empezar en la línea "-----BEGIN.." en la primera parte. Para partes subsecuentes, empieza después de la primera línea en blanco una vez que una línea "id=.." es vista.

Una vez en la tubería el contenido del mensaje compuesto es pasado al ejecutable PGP para la validación/decriptación, y entonces a una impresora apropiada. La salida de la validación es pasada a un archivo de trabajo, y entonces es recuperada de ahí para registrar. Un fallo de validación resulta en ninguna salida a la impresora.

          for ($k=1;$k<=$tp{$part[0]};$k++){	# Verifica si tenemos todas las partes
            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++){	# Ensambla las partes en una tubería
            $message=$pop->get($slot{$part[0]."=".$k});
            $l=0; $buffer=""; $print="N";
            while ( defined(@$message[$l]) ) {
              chomp @$message[$l]; 		# Parte 1: Empieza en "-----BEGIN",
              if( $k == 1 ) {			# se detiene antes de la segunda línea blanca.
                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"
                }
              }					# Parte 2,3 .... evita una línea blanca
              else {				# después de "id=", entonces inicia, se detiene
                if( $print eq "Y" ) {		# antes de la siguiente línea blanca.
                  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:}

El Crimen de la Imitación

En el esquema esbozado arriba, no hay nada para prevenir un determinado creador de problemas al replicar y reproducir un mensaje entero autenticado. Para cubrir esta posibilidad, necesitas retener cada registro por una semana, o algo así, y rechazar cualquier mensaje entrarnte que tenga la misma firma y fecha de firma.

Si además, deseas prevenir que alguien vea el flujo actual de datos a tu impresora así como a través de internet, necesitas cambiar los parámetros del ejecutable PGP al final del cliente así que los datos son encriptados con la llave pública del servidor así como la firma, tu también necesitas enviar una passphrase al ejecutable PGP al final del servidor.

GNU Privacy Guard (El Protector de Privacía GNU)

Tengo una imagen mental de alguien leyendo esto y diciendo: "¿Cómo llegó él a usar pgp-2.6.3ia si no le gustan los archivos temporales innecesarios?" Es una buena pregunta, porque pgp-2.6.3ia crea archivos temporales para ambos durante la encriptación y durante la desencriptación.

Para lograr esto, o que cumpla con cualquiera ley aplicable en tu país, debes desear usar GnuPG-v1.0.6 (o una versión posterior del mismo) en su lugar. En el programa cliente, necesitarás cambiar los parámetros con los cuáles el ejecutable es llamado. Y no serás capaz de poner tu passphrase en una variable de entorno.

He adjuntado para tu interés un 'Ligero' programa GPG cliente el cuál se ejecutará en máquinas Windows con ActiveState Perl instalado o IndigoPerl, y no requiere módulos extras.

Durante la desencriptación a una tubería, el ejecutable 'pgp' manda los datos a la tubería hasta (y en algunos casos, después) que encuentra un problema. Así que necesitarás mandar la salida a un archivo de trabajo - entonces mandar ese archivo de trabajo a tu impresora si el proceso de desencriptación se completó satisfactoriamente.

Graham Jenkins

Graham es un Especialista Unix en Servicios Globales IBM, Australia. Él vive en Melbourne y ha construido y manejado muchos tipos de sistemas propietarios y abiertos en varias plataformas de hardware.


Copyright © 2002, Graham Jenkins.
Licencia de Copiado http://www.linuxgazette.com/copying.html
Publicado en la Edición 75 de La Gaceta de Linux, Febrero 2002