summaryrefslogtreecommitdiff
path: root/Master/tlpkg/tlperl0/lib/Convert/PEM.pm
diff options
context:
space:
mode:
Diffstat (limited to 'Master/tlpkg/tlperl0/lib/Convert/PEM.pm')
-rwxr-xr-xMaster/tlpkg/tlperl0/lib/Convert/PEM.pm472
1 files changed, 472 insertions, 0 deletions
diff --git a/Master/tlpkg/tlperl0/lib/Convert/PEM.pm b/Master/tlpkg/tlperl0/lib/Convert/PEM.pm
new file mode 100755
index 00000000000..f6668bf3d87
--- /dev/null
+++ b/Master/tlpkg/tlperl0/lib/Convert/PEM.pm
@@ -0,0 +1,472 @@
+# $Id: PEM.pm 1829 2005-05-25 21:51:40Z btrott $
+
+package Convert::PEM;
+use strict;
+use base qw( Class::ErrorHandler );
+
+use MIME::Base64;
+use Digest::MD5 qw( md5 );
+use Convert::ASN1;
+use Carp qw( croak );
+use Convert::PEM::CBC;
+
+use vars qw( $VERSION );
+$VERSION = '0.07';
+
+sub new {
+ my $class = shift;
+ my $pem = bless { }, $class;
+ $pem->init(@_);
+}
+
+sub init {
+ my $pem = shift;
+ my %param = @_;
+ unless (exists $param{ASN} && exists $param{Name}) {
+ return (ref $pem)->error("init: Name and ASN are required");
+ }
+ else {
+ $pem->{ASN} = $param{ASN};
+ $pem->{Name} = $param{Name};
+ }
+ $pem->{Macro} = $param{Macro};
+ my $asn = $pem->{_asn} = Convert::ASN1->new;
+ $asn->prepare( $pem->{ASN} ) or
+ return (ref $pem)->error("ASN prepare failed: $asn->{error}");
+ $pem;
+}
+
+sub asn { $_[0]->{_asn} }
+sub ASN { $_[0]->{ASN} }
+sub name { $_[0]->{Name} }
+
+sub read {
+ my $pem = shift;
+ my %param = @_;
+
+ my $blob;
+ local *FH;
+ my $fname = delete $param{Filename};
+ open FH, $fname or
+ return $pem->error("Can't open $fname: $!");
+ $blob = do { local $/; <FH> };
+ close FH;
+
+ $param{Content} = $blob;
+ $pem->decode(%param);
+}
+
+sub write {
+ my $pem = shift;
+ my %param = @_;
+
+ my $fname = delete $param{Filename} or
+ return $pem->error("write: Filename is required");
+ my $blob = $pem->encode(%param);
+
+ local *FH;
+ open FH, ">$fname" or
+ return $pem->error("Can't open $fname: $!");
+ print FH $blob;
+ close FH;
+ $blob;
+}
+
+sub decode {
+ my $pem = shift;
+ my %param = @_;
+ my $blob = $param{Content} or
+ return $pem->error("'Content' is required");
+ chomp $blob;
+
+ my $dec = $pem->explode($blob) or return;
+ my $name = $param{Name} || $pem->name;
+ return $pem->error("Object $dec->{Object} does not match " . $name)
+ unless $dec->{Object} eq $name;
+
+ my $head = $dec->{Headers};
+ my $buf = $dec->{Content};
+ my %headers = map { $_->[0] => $_->[1] } @$head;
+ if (%headers && $headers{'Proc-Type'} eq '4,ENCRYPTED') {
+ $buf = $pem->decrypt( Ciphertext => $buf,
+ Info => $headers{'DEK-Info'},
+ Password => $param{Password} )
+ or return;
+ }
+
+ my $asn = $pem->asn;
+ if (my $macro = ($param{Macro} || $pem->{Macro})) {
+ $asn = $asn->find($macro) or
+ return $pem->error("Can't find Macro $macro");
+ }
+ my $obj = $asn->decode($buf) or
+ return $pem->error("ASN decode failed: $asn->{error}");
+
+ $obj;
+}
+
+sub encode {
+ my $pem = shift;
+ my %param = @_;
+
+ my $asn = $pem->asn;
+ if (my $macro = ($param{Macro} || $pem->{Macro})) {
+ $asn = $asn->find($macro) or
+ return $pem->error("Can't find Macro $macro");
+ }
+ my $buf = $asn->encode( $param{Content} ) or
+ return $pem->error("ASN encode failed: $asn->{error}");
+
+ my(@headers);
+ if ($param{Password}) {
+ my($info);
+ ($buf, $info) = $pem->encrypt( Plaintext => $buf,
+ Password => $param{Password} )
+ or return;
+ push @headers, [ 'Proc-Type' => '4,ENCRYPTED' ];
+ push @headers, [ 'DEK-Info' => $info ];
+ }
+
+ $pem->implode( Object => $param{Name} || $pem->name,
+ Headers => \@headers,
+ Content => $buf );
+}
+
+sub explode {
+ my $pem = shift;
+ my($message) = @_;
+ my($head, $object, $headers, $content, $tail) = $message =~
+ m:(-----BEGIN ([^\n\-]+)-----)\n(.*?\n\n)?(.+)(-----END .*?-----)$:s;
+ my $buf = decode_base64($content);
+
+ my @headers;
+ if ($headers) {
+ for my $h ( split /\n/, $headers ) {
+ my($k, $v) = split /:\s*/, $h, 2;
+ push @headers, [ $k => $v ] if $k;
+ }
+ }
+
+ { Content => $buf,
+ Object => $object,
+ Headers => \@headers }
+}
+
+sub implode {
+ my $pem = shift;
+ my %param = @_;
+ my $head = "-----BEGIN $param{Object}-----";
+ my $tail = "-----END $param{Object}-----";
+ my $content = encode_base64( $param{Content}, '' );
+ $content =~ s!(.{1,64})!$1\n!g;
+ my $headers = join '',
+ map { "$_->[0]: $_->[1]\n" }
+ @{ $param{Headers} };
+ $headers .= "\n" if $headers;
+ "$head\n$headers$content$tail\n";
+}
+
+use vars qw( %CTYPES );
+%CTYPES = ('DES-EDE3-CBC' => 'Crypt::DES_EDE3');
+
+sub decrypt {
+ my $pem = shift;
+ my %param = @_;
+ my $passphrase = $param{Password} || "";
+ my($ctype, $iv) = split /,/, $param{Info};
+ my $cmod = $CTYPES{$ctype} or
+ return $pem->error("Unrecognized cipher: '$ctype'");
+ $iv = pack "H*", $iv;
+ my $cbc = Convert::PEM::CBC->new(
+ Passphrase => $passphrase,
+ Cipher => $cmod,
+ IV => $iv );
+ my $buf = $cbc->decrypt($param{Ciphertext}) or
+ return $pem->error("Decryption failed: " . $cbc->errstr);
+ $buf;
+}
+
+sub encrypt {
+ my $pem = shift;
+ my %param = @_;
+ $param{Password} or return $param{Plaintext};
+ my $ctype = $param{Cipher} || 'DES-EDE3-CBC';
+ my $cmod = $CTYPES{$ctype} or
+ return $pem->error("Unrecognized cipher: '$ctype'");
+ my $cbc = Convert::PEM::CBC->new(
+ Passphrase => $param{Password},
+ Cipher => $cmod );
+ my $iv = uc join '', unpack "H*", $cbc->iv;
+ my $buf = $cbc->encrypt($param{Plaintext}) or
+ return $pem->error("Encryption failed: " . $cbc->errstr);
+ ($buf, "$ctype,$iv");
+}
+
+1;
+__END__
+
+=head1 NAME
+
+Convert::PEM - Read/write encrypted ASN.1 PEM files
+
+=head1 SYNOPSIS
+
+ use Convert::PEM;
+ my $pem = Convert::PEM->new(
+ Name => "DSA PRIVATE KEY",
+ ASN => qq(
+ DSAPrivateKey SEQUENCE {
+ version INTEGER,
+ p INTEGER,
+ q INTEGER,
+ g INTEGER,
+ pub_key INTEGER,
+ priv_key INTEGER
+ }
+ ));
+
+ my $pkey = $pem->read(
+ Filename => $keyfile,
+ Password => $pwd
+ );
+
+ $pem->write(
+ Content => $pkey,
+ Password => $pwd,
+ Filename => $keyfile
+ );
+
+=head1 DESCRIPTION
+
+I<Convert::PEM> reads and writes PEM files containing ASN.1-encoded
+objects. The files can optionally be encrypted using a symmetric
+cipher algorithm, such as 3DES. An unencrypted PEM file might look
+something like this:
+
+ -----BEGIN DH PARAMETERS-----
+ MB4CGQDUoLoCULb9LsYm5+/WN992xxbiLQlEuIsCAQM=
+ -----END DH PARAMETERS-----
+
+The string beginning C<MB4C...> is the Base64-encoded, ASN.1-encoded
+"object."
+
+An encrypted file would have headers describing the type of
+encryption used, and the initialization vector:
+
+ -----BEGIN DH PARAMETERS-----
+ Proc-Type: 4,ENCRYPTED
+ DEK-Info: DES-EDE3-CBC,C814158661DC1449
+
+ AFAZFbnQNrGjZJ/ZemdVSoZa3HWujxZuvBHzHNoesxeyqqidFvnydA==
+ -----END DH PARAMETERS-----
+
+The two headers (C<Proc-Type> and C<DEK-Info>) indicate information
+about the type of encryption used, and the string starting with
+C<AFAZ...> is the Base64-encoded, encrypted, ASN.1-encoded
+contents of this "object."
+
+The initialization vector (C<C814158661DC1449>) is chosen randomly.
+
+=head1 USAGE
+
+=head2 $pem = Convert::PEM->new( %arg )
+
+Constructs a new I<Convert::PEM> object designed to read/write an
+object of a specific type (given in I<%arg>, see below). Returns the
+new object on success, C<undef> on failure (see I<ERROR HANDLING> for
+details).
+
+I<%arg> can contain:
+
+=over 4
+
+=item * Name
+
+The name of the object; when decoding a PEM-encoded stream, the name
+in the encoding will be checked against the value of I<Name>.
+Similarly, when encoding an object, the value of I<Name> will be used
+as the name of the object in the PEM-encoded content. For example, given
+the string C<FOO BAR>, the output from I<encode> will start with a
+header like:
+
+ -----BEGIN FOO BAR-----
+
+I<Name> is a required argument.
+
+=item * ASN
+
+An ASN.1 description of the content to be either encoded or decoded.
+
+I<ASN> is a required argument.
+
+=item * Macro
+
+If your ASN.1 description (in the I<ASN> parameter) includes more than
+one ASN.1 macro definition, you will want to use the I<Macro> parameter
+to specify which definition to use when encoding/decoding objects.
+For example, if your ASN.1 description looks like this:
+
+ Foo ::= SEQUENCE {
+ x INTEGER,
+ bar Bar
+ }
+
+ Bar ::= INTEGER
+
+If you want to encode/decode a C<Foo> object, you will need to tell
+I<Convert::PEM> to use the C<Foo> macro definition by using the I<Macro>
+parameter and setting the value to C<Foo>.
+
+I<Macro> is an optional argument.
+
+=back
+
+=head2 $obj = $pem->decode(%args)
+
+Decodes, and, optionally, decrypts a PEM file, returning the
+object as decoded by I<Convert::ASN1>. The difference between this
+method and I<read> is that I<read> reads the contents of a PEM file
+on disk; this method expects you to pass the PEM contents as an
+argument.
+
+If an error occurs while reading the file or decrypting/decoding
+the contents, the function returns I<undef>, and you should check
+the error message using the I<errstr> method (below).
+
+I<%args> can contain:
+
+=over 4
+
+=item * Content
+
+The PEM contents.
+
+=item * Password
+
+The password with which the file contents were encrypted.
+
+If the file is encrypted, this is a mandatory argument (well, it's
+not strictly mandatory, but decryption isn't going to work without
+it). Otherwise it's not necessary.
+
+=back
+
+=head2 $blob = $pem->encode(%args)
+
+Constructs the contents for the PEM file from an object: ASN.1-encodes
+the object, optionally encrypts those contents.
+
+Returns I<undef> on failure (encryption failure, file-writing failure,
+etc.); in this case you should check the error message using the
+I<errstr> method (below). On success returns the constructed PEM string.
+
+I<%args> can contain:
+
+=over 4
+
+=item * Content
+
+A hash reference that will be passed to I<Convert::ASN1::encode>,
+and which should correspond to the ASN.1 description you gave to the
+I<new> method. The hash reference should have the exact same format
+as that returned from the I<read> method.
+
+This argument is mandatory.
+
+=item * Password
+
+A password used to encrypt the contents of the PEM file. This is an
+optional argument; if not provided the contents will be unencrypted.
+
+=back
+
+=head2 $obj = $pem->read(%args)
+
+Reads, decodes, and, optionally, decrypts a PEM file, returning
+the object as decoded by I<Convert::ASN1>. This is implemented
+as a wrapper around I<decode>, with the bonus of reading the PEM
+file from disk for you.
+
+If an error occurs while reading the file or decrypting/decoding
+the contents, the function returns I<undef>, and you should check
+the error message using the I<errstr> method (below).
+
+In addition to the arguments that can be passed to the I<decode>
+method (minus the I<Content> method), I<%args> can contain:
+
+=over 4
+
+=item * Filename
+
+The location of the PEM file that you wish to read.
+
+=back
+
+=head2 $pem->write(%args)
+
+Constructs the contents for the PEM file from an object: ASN.1-encodes
+the object, optionally encrypts those contents; then writes the file
+to disk. This is implemented as a wrapper around I<encode>, with the
+bonus of writing the file to disk for you.
+
+Returns I<undef> on failure (encryption failure, file-writing failure,
+etc.); in this case you should check the error message using the
+I<errstr> method (below). On success returns the constructed PEM string.
+
+In addition to the arguments for I<encode>, I<%args> can contain:
+
+=over 4
+
+=item * Filename
+
+The location on disk where you'd like the PEM file written.
+
+=back
+
+=head2 $pem->errstr
+
+Returns the value of the last error that occurred. This should only
+be considered meaningful when you've received I<undef> from one of
+the functions above; in all other cases its relevance is undefined.
+
+=head2 $pem->asn
+
+Returns the I<Convert::ASN1> object used internally to decode and
+encode ASN.1 representations. This is useful when you wish to
+interact directly with that object; for example, if you need to
+call I<configure> on that object to set the type of big-integer
+class to be used when decoding/encoding big integers:
+
+ $pem->asn->configure( decode => { bigint => 'Math::Pari' },
+ encode => { bigint => 'Math::Pari' } );
+
+=head1 ERROR HANDLING
+
+If an error occurs in any of the above methods, the method will return
+C<undef>. You should then call the method I<errstr> to determine the
+source of the error:
+
+ $pem->errstr
+
+In the case that you do not yet have a I<Convert::PEM> object (that is,
+if an error occurs while creating a I<Convert::PEM> object), the error
+can be obtained as a class method:
+
+ Convert::PEM->errstr
+
+For example, if you try to decode an encrypted object, and you do not
+give a passphrase to decrypt the object:
+
+ my $obj = $pem->read( Filename => "encrypted.pem" )
+ or die "Decryption failed: ", $pem->errstr;
+
+=head1 AUTHOR & COPYRIGHTS
+
+Benjamin Trott, ben@rhumba.pair.com
+
+Except where otherwise noted, Convert::PEM is Copyright 2001
+Benjamin Trott. All rights reserved. Convert::PEM is free
+software; you may redistribute it and/or modify it under
+the same terms as Perl itself.
+
+=cut