smime
上传用户:lyxiangda
上传日期:2007-01-12
资源大小:3042k
文件大小:17k
- #!/usr/local/bin/perl
- # The contents of this file are subject to the Mozilla Public
- # License Version 1.1 (the "License"); you may not use this file
- # except in compliance with the License. You may obtain a copy of
- # the License at http://www.mozilla.org/MPL/
- #
- # Software distributed under the License is distributed on an "AS
- # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
- # implied. See the License for the specific language governing
- # rights and limitations under the License.
- #
- # The Original Code is the Netscape security libraries.
- #
- # The Initial Developer of the Original Code is Netscape
- # Communications Corporation. Portions created by Netscape are
- # Copyright (C) 1994-2000 Netscape Communications Corporation. All
- # Rights Reserved.
- #
- # Contributor(s):
- #
- # Alternatively, the contents of this file may be used under the
- # terms of the GNU General Public License Version 2 or later (the
- # "GPL"), in which case the provisions of the GPL are applicable
- # instead of those above. If you wish to allow use of your
- # version of this file only under the terms of the GPL and not to
- # allow others to use your version of this file under the MPL,
- # indicate your decision by deleting the provisions above and
- # replace them with the notice and other provisions required by
- # the GPL. If you do not delete the provisions above, a recipient
- # may use your version of this file under either the MPL or the
- # GPL.
- #
- # smime.pl - frontend for S/MIME message generation and parsing
- #
- # $Id: smime,v 1.6 2000/07/07 00:57:39 chrisk%netscape.com Exp $
- #
- use Getopt::Std;
- @boundarychars = ( "0" .. "9", "A" .. "F" );
- # path to cmsutil
- $cmsutilpath = "cmsutil";
- #
- # Thanks to Gisle Aas <gisle@aas.no> for the base64 functions
- # originally taken from MIME-Base64-2.11 at www.cpan.org
- #
- sub encode_base64($)
- {
- my $res = "";
- pos($_[0]) = 0; # ensure start at the beginning
- while ($_[0] =~ /(.{1,45})/gs) {
- $res .= substr(pack('u', $1), 1); # get rid of length byte after packing
- chop($res);
- }
- $res =~ tr|` -_|AA-Za-z0-9+/|;
- # fix padding at the end
- my $padding = (3 - length($_[0]) % 3) % 3;
- $res =~ s/.{$padding}$/'=' x $padding/e if $padding;
- # break encoded string into lines of no more than 76 characters each
- $res =~ s/(.{1,76})/$1n/g;
- $res;
- }
- sub decode_base64($)
- {
- local($^W) = 0; # unpack("u",...) gives bogus warning in 5.00[123]
- my $str = shift;
- my $res = "";
- $str =~ tr|A-Za-z0-9+=/||cd; # remove non-base64 chars
- if (length($str) % 4) {
- require Carp;
- Carp::carp("Length of base64 data not a multiple of 4")
- }
- $str =~ s/=+$//; # remove padding
- $str =~ tr|A-Za-z0-9+/| -_|; # convert to uuencoded format
- while ($str =~ /(.{1,60})/gs) {
- my $len = chr(32 + length($1)*3/4); # compute length byte
- $res .= unpack("u", $len . $1 ); # uudecode
- }
- $res;
- }
- #
- # parse headers into a hash
- #
- # %headers = parseheaders($headertext);
- #
- sub parseheaders($)
- {
- my ($headerdata) = @_;
- my $hdr;
- my %hdrhash;
- my $hdrname;
- my $hdrvalue;
- my @hdrvalues;
- my $subhdrname;
- my $subhdrvalue;
- # the expression in split() correctly handles continuation lines
- foreach $hdr (split(/n(?=S)/, $headerdata)) {
- $hdr =~ s/r*ns+/ /g; # collapse continuation lines
- ($hdrname, $hdrvalue) = $hdr =~ m/^(S+):s+(.*)$/;
- # ignore non-headers (or should we die horribly?)
- next unless (defined($hdrname));
- $hdrname =~ tr/A-Z/a-z/; # lowercase the header name
- @hdrvalues = split(/s*;s*/, $hdrvalue); # split header values (XXXX quoting)
- # there is guaranteed to be at least one value
- $hdrvalue = shift @hdrvalues;
- if ($hdrvalue =~ /^s*"(.*)"s*$/) { # strip quotes if there
- $hdrvalue = $1;
- }
- $hdrhash{$hdrname}{MAIN} = $hdrvalue;
- # print "XXX $hdrname = $hdrvaluen";
- # deal with additional name-value pairs
- foreach $hdrvalue (@hdrvalues) {
- ($subhdrname, $subhdrvalue) = $hdrvalue =~ m/^(S+)s*=s*(.*)$/;
- # ignore non-name-value pairs (or should we die?)
- next unless (defined($subhdrname));
- $subhdrname =~ tr/A-Z/a-z/;
- if ($subhdrvalue =~ /^s*"(.*)"s*$/) { # strip quotes if there
- $subhdrvalue = $1;
- }
- $hdrhash{$hdrname}{$subhdrname} = $subhdrvalue;
- }
- }
- return %hdrhash;
- }
- #
- # encryptentity($entity, $options) - encrypt an S/MIME entity,
- # creating a new application/pkcs7-smime entity
- #
- # entity - string containing entire S/MIME entity to encrypt
- # options - options for cmsutil
- #
- # this will generate and return a new application/pkcs7-smime entity containing
- # the enveloped input entity.
- #
- sub encryptentity($$)
- {
- my ($entity, $cmsutiloptions) = @_;
- my $out = "";
- my $boundary;
- $tmpencfile = "/tmp/encryptentity.$$";
- #
- # generate a random boundary string
- #
- $boundary = "------------ms" . join("", @boundarychars[map{rand @boundarychars }( 1 .. 24 )]);
- #
- # tell cmsutil to generate a enveloped CMS message using our data
- #
- open(CMS, "|$cmsutilpath -E $cmsutiloptions -o $tmpencfile") or die "ERROR: cannot pipe to cmsutil";
- print CMS $entity;
- unless (close(CMS)) {
- print STDERR "ERROR: encryption failed.n";
- unlink($tmpsigfile);
- exit 1;
- }
- $out = "Content-Type: application/pkcs7-mime; smime-type=enveloped-data; name=smime.p7mn";
- $out .= "Content-Transfer-Encoding: base64n";
- $out .= "Content-Disposition: attachment; filename=smime.p7mn";
- $out .= "n"; # end of entity header
- open (ENC, $tmpencfile) or die "ERROR: cannot find newly generated encrypted content";
- local($/) = undef; # slurp whole file
- $out .= encode_base64(<ENC>), "n"; # entity body is base64-encoded CMS message
- close(ENC);
- unlink($tmpencfile);
- $out;
- }
- #
- # signentity($entity, $options) - sign an S/MIME entity
- #
- # entity - string containing entire S/MIME entity to sign
- # options - options for cmsutil
- #
- # this will generate and return a new multipart/signed entity consisting
- # of the canonicalized original content, plus a signature block.
- #
- sub signentity($$)
- {
- my ($entity, $cmsutiloptions) = @_;
- my $out = "";
- my $boundary;
- $tmpsigfile = "/tmp/signentity.$$";
- #
- # generate a random boundary string
- #
- $boundary = "------------ms" . join("", @boundarychars[map{rand @boundarychars }( 1 .. 24 )]);
- #
- # tell cmsutil to generate a signed CMS message using the canonicalized data
- # The signedData has detached content (-T) and includes a signing time attribute (-G)
- #
- # if we do not provide a password on the command line, here's where we would be asked for it
- #
- open(CMS, "|$cmsutilpath -S -T -G $cmsutiloptions -o $tmpsigfile") or die "ERROR: cannot pipe to cmsutil";
- print CMS $entity;
- unless (close(CMS)) {
- print STDERR "ERROR: signature generation failed.n";
- unlink($tmpsigfile);
- exit 1;
- }
- open (SIG, $tmpsigfile) or die "ERROR: cannot find newly generated signature";
- #
- # construct a new multipart/signed MIME entity consisting of the original content and
- # the signature
- #
- # (we assume that cmsutil generates a SHA1 digest)
- $out .= "Content-Type: multipart/signed; protocol="application/pkcs7-signature"; micalg=sha1; boundary="${boundary}"n";
- $out .= "n"; # end of entity header
- $out .= "This is a cryptographically signed message in MIME format.n"; # explanatory comment
- $out .= "n--${boundary}n";
- $out .= $entity;
- $out .= "n--${boundary}n";
- $out .= "Content-Type: application/pkcs7-signature; name=smime.p7sn";
- $out .= "Content-Transfer-Encoding: base64n";
- $out .= "Content-Disposition: attachment; filename=smime.p7sn";
- $out .= "Content-Description: S/MIME Cryptographic Signaturen";
- $out .= "n"; # end of signature subentity header
- local($/) = undef; # slurp whole file
- $out .= encode_base64(<SIG>); # append base64-encoded signature
- $out .= "n--${boundary}--n";
- close(SIG);
- unlink($tmpsigfile);
- $out;
- }
- sub usage {
- print STDERR "usage: smime [options]n";
- print STDERR " options:n";
- print STDERR " -S nick generate signed message, use certificate named "nick"n";
- print STDERR " -p passwd use "passwd" as security module passwordn";
- print STDERR " -E rec1[,rec2...] generate encrypted message for recipientsn";
- print STDERR " -D decode a S/MIME messagen";
- print STDERR " -C pathname set pathname of "cmsutil"n";
- print STDERR " -d directory set directory containing certificate dbn";
- print STDERR " (default: ~/.netscape)n";
- print STDERR "nWith -S or -E, smime will take a regular RFC822 message or MIME entityn";
- print STDERR "on stdin and generate a signed or encrypted S/MIME message with the samen";
- print STDERR "headers and content from it. The output can be used as input to a MTA.n";
- print STDERR "-D causes smime to strip off all S/MIME layers if possible and outputn";
- print STDERR "the "inner" message.n";
- }
- #
- # start of main procedures
- #
- #
- # process command line options
- #
- unless (getopts('S:E:p:d:C:D')) {
- usage();
- exit 1;
- }
- unless (defined($opt_S) or defined($opt_E) or defined($opt_D)) {
- print STDERR "ERROR: -S and/or -E, or -D must be specified.n";
- usage();
- exit 1;
- }
- $signopts = "";
- $encryptopts = "";
- $decodeopts = "";
- # pass -d option along
- if (defined($opt_d)) {
- $signopts .= "-d "$opt_d" ";
- $encryptopts .= "-d "$opt_d" ";
- $decodeopts .= "-d "$opt_d" ";
- }
- if (defined($opt_S)) {
- $signopts .= "-N "$opt_S" ";
- }
- if (defined($opt_p)) {
- $signopts .= "-p "$opt_p" ";
- $decodeopts .= "-p "$opt_p" ";
- }
- if (defined($opt_E)) {
- @recipients = split(",", $opt_E);
- $encryptopts .= "-r ";
- $encryptopts .= join (" -r ", @recipients);
- }
- if (defined($opt_C)) {
- $cmsutilpath = $opt_C;
- }
- #
- # split headers into mime entity headers and RFC822 headers
- # The RFC822 headers are preserved and stay on the outer layer of the message
- #
- $rfc822headers = "";
- $mimeheaders = "";
- $mimebody = "";
- $skippedheaders = "";
- while (<STDIN>) {
- last if (/^$/);
- if (/^content-S+: /i) {
- $lastref = $mimeheaders;
- } elsif (/^mime-version: /i) {
- $lastref = $skippedheaders; # skip it
- } elsif (/^s/) {
- ;
- } else {
- $lastref = $rfc822headers;
- }
- $$lastref .= $_;
- }
- #
- # if there are no MIME entity headers, generate some default ones
- #
- if ($mimeheaders eq "") {
- $mimeheaders .= "Content-Type: text/plain; charset=us-asciin";
- $mimeheaders .= "Content-Transfer-Encoding: 7bitn";
- }
- #
- # slurp in the entity body
- #
- $saveRS = $/;
- $/ = undef;
- $mimebody = <STDIN>;
- $/ = $saveRS;
- if (defined $opt_D) {
- #
- # decode
- #
- # possible options would be:
- # - strip off only one layer
- # - strip off outer signature (if present)
- # - just print information about the structure of the message
- # - strip n layers, then dump DER of CMS message
- $layercounter = 1;
- while (1) {
- %hdrhash = parseheaders($mimeheaders);
- unless (exists($hdrhash{"content-type"}{MAIN})) {
- print STDERR "ERROR: no content type header found in MIME entityn";
- last; # no content-type - we're done
- }
- $contenttype = $hdrhash{"content-type"}{MAIN};
- if ($contenttype eq "application/pkcs7-mime") {
- #
- # opaque-signed or enveloped message
- #
- unless (exists($hdrhash{"content-type"}{"smime-type"})) {
- print STDERR "ERROR: no smime-type attribute in application/pkcs7-smime entity.n";
- last;
- }
- $smimetype = $hdrhash{"content-type"}{"smime-type"};
- if ($smimetype eq "signed-data" or $smimetype eq "enveloped-data") {
- # it's verification or decryption time!
- # can handle only base64 encoding for now
- # all other encodings are treated as binary (8bit)
- if ($hdrhash{"content-transfer-encoding"}{MAIN} eq "base64") {
- $mimebody = decode_base64($mimebody);
- }
- # if we need to dump the DER, we would do it right here
- # now write the DER
- $tmpderfile = "/tmp/der.$$";
- open(TMP, ">$tmpderfile") or die "ERROR: cannot write signature data to temporary file";
- print TMP $mimebody;
- unless (close(TMP)) {
- print STDERR "ERROR: writing signature data to temporary file.n";
- unlink($tmpderfile);
- exit 1;
- }
- $mimeheaders = "";
- open(TMP, "$cmsutilpath -D $decodeopts -h $layercounter -i $tmpderfile |") or die "ERROR: cannot open pipe to cmsutil";
- $layercounter++;
- while (<TMP>) {
- last if (/^r?$/); # empty lines mark end of header
- if (/^SMIME: /) { # add all SMIME info to the rfc822 hdrs
- $lastref = $rfc822headers;
- } elsif (/^s/) {
- ; # continuation lines go to the last dest
- } else {
- $lastref = $mimeheaders; # all other headers are mime headers
- }
- $$lastref .= $_;
- }
- # slurp in rest of the data to $mimebody
- $saveRS = $/; $/ = undef; $mimebody = <TMP>; $/ = $saveRS;
- close(TMP);
- unlink($tmpderfile);
- } else {
- print STDERR "ERROR: unknown smime-type "$smimetype" in application/pkcs7-smime entity.n";
- last;
- }
- } elsif ($contenttype eq "multipart/signed") {
- #
- # clear signed message
- #
- unless (exists($hdrhash{"content-type"}{"protocol"})) {
- print STDERR "ERROR: content type has no protocol attribute in multipart/signed entity.n";
- last;
- }
- if ($hdrhash{"content-type"}{"protocol"} ne "application/pkcs7-signature") {
- # we cannot handle this guy
- print STDERR "ERROR: unknown protocol "", $hdrhash{"content-type"}{"protocol"},
- "" in multipart/signed entity.n";
- last;
- }
- unless (exists($hdrhash{"content-type"}{"boundary"})) {
- print STDERR "ERROR: no boundary attribute in multipart/signed entity.n";
- last;
- }
- $boundary = $hdrhash{"content-type"}{"boundary"};
- # split $mimebody along n--$boundaryn - gets you four parts
- # first (0), any comments the sending agent might have put in
- # second (1), the message itself
- # third (2), the signature as a mime entity
- # fourth (3), trailing data (there shouldn't be any)
- @multiparts = split(/r?n--$boundary(?:--)?r?n/, $mimebody);
- #
- # parse the signature headers
- ($submimeheaders, $submimebody) = split(/^$/m, $multiparts[2]);
- %sighdrhash = parseheaders($submimeheaders);
- unless (exists($sighdrhash{"content-type"}{MAIN})) {
- print STDERR "ERROR: signature entity has no content type.n";
- last;
- }
- if ($sighdrhash{"content-type"}{MAIN} ne "application/pkcs7-signature") {
- # we cannot handle this guy
- print STDERR "ERROR: unknown content type "", $sighdrhash{"content-type"}{MAIN},
- "" in signature entity.n";
- last;
- }
- if ($sighdrhash{"content-transfer-encoding"}{MAIN} eq "base64") {
- $submimebody = decode_base64($submimebody);
- }
- # we would dump the DER at this point
- $tmpsigfile = "/tmp/sig.$$";
- open(TMP, ">$tmpsigfile") or die "ERROR: cannot write signature data to temporary file";
- print TMP $submimebody;
- unless (close(TMP)) {
- print STDERR "ERROR: writing signature data to temporary file.n";
- unlink($tmpsigfile);
- exit 1;
- }
- $tmpmsgfile = "/tmp/msg.$$";
- open(TMP, ">$tmpmsgfile") or die "ERROR: cannot write message data to temporary file";
- print TMP $multiparts[1];
- unless (close(TMP)) {
- print STDERR "ERROR: writing message data to temporary file.n";
- unlink($tmpsigfile);
- unlink($tmpmsgfile);
- exit 1;
- }
- $mimeheaders = "";
- open(TMP, "$cmsutilpath -D $decodeopts -h $layercounter -c $tmpmsgfile -i $tmpsigfile |") or die "ERROR: cannot open pipe to cmsutil";
- $layercounter++;
- while (<TMP>) {
- last if (/^r?$/);
- if (/^SMIME: /) {
- $lastref = $rfc822headers;
- } elsif (/^s/) {
- ;
- } else {
- $lastref = $mimeheaders;
- }
- $$lastref .= $_;
- }
- $saveRS = $/; $/ = undef; $mimebody = <TMP>; $/ = $saveRS;
- close(TMP);
- unlink($tmpsigfile);
- unlink($tmpmsgfile);
- } else {
- # not a content type we know - we're done
- last;
- }
- }
- # so now we have the S/MIME parsing information in rfc822headers
- # and the first mime entity we could not handle in mimeheaders and mimebody.
- # dump 'em out and we're done.
- print $rfc822headers;
- print $mimeheaders . "n" . $mimebody;
- } else {
- #
- # encode (which is much easier than decode)
- #
- $mimeentity = $mimeheaders . "n" . $mimebody;
- #
- # canonicalize inner entity (rudimentary yet)
- # convert single LFs to CRLF
- # if no Content-Transfer-Encoding header present:
- # if 8 bit chars present, use Content-Transfer-Encoding: quoted-printable
- # otherwise, use Content-Transfer-Encoding: 7bit
- #
- $mimeentity =~ s/r*n/rn/mg;
- #
- # now do the wrapping
- # we sign first, then encrypt because that's what Communicator needs
- #
- if (defined($opt_S)) {
- $mimeentity = signentity($mimeentity, $signopts);
- }
- if (defined($opt_E)) {
- $mimeentity = encryptentity($mimeentity, $encryptopts);
- }
- #
- # XXX sign again to do triple wrapping (RFC2634)
- #
- #
- # now write out the RFC822 headers
- # followed by the final $mimeentity
- #
- print $rfc822headers;
- print "MIME-Version: 1.0 (NSS SMIME - http://www.mozilla.org/projects/security)n"; # set up the flag
- print $mimeentity;
- }
- exit 0;