smime
上传用户:lyxiangda
上传日期:2007-01-12
资源大小:3042k
文件大小:17k
源码类别:

CA认证

开发平台:

WINDOWS

  1. #!/usr/local/bin/perl
  2. # The contents of this file are subject to the Mozilla Public
  3. # License Version 1.1 (the "License"); you may not use this file
  4. # except in compliance with the License. You may obtain a copy of
  5. # the License at http://www.mozilla.org/MPL/
  6. # Software distributed under the License is distributed on an "AS
  7. # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
  8. # implied. See the License for the specific language governing
  9. # rights and limitations under the License.
  10. # The Original Code is the Netscape security libraries.
  11. # The Initial Developer of the Original Code is Netscape
  12. # Communications Corporation.  Portions created by Netscape are 
  13. # Copyright (C) 1994-2000 Netscape Communications Corporation.  All
  14. # Rights Reserved.
  15. # Contributor(s):
  16. # Alternatively, the contents of this file may be used under the
  17. # terms of the GNU General Public License Version 2 or later (the
  18. # "GPL"), in which case the provisions of the GPL are applicable 
  19. # instead of those above.  If you wish to allow use of your 
  20. # version of this file only under the terms of the GPL and not to
  21. # allow others to use your version of this file under the MPL,
  22. # indicate your decision by deleting the provisions above and
  23. # replace them with the notice and other provisions required by
  24. # the GPL.  If you do not delete the provisions above, a recipient
  25. # may use your version of this file under either the MPL or the
  26. # GPL.
  27. #
  28. # smime.pl - frontend for S/MIME message generation and parsing
  29. #
  30. # $Id: smime,v 1.6 2000/07/07 00:57:39 chrisk%netscape.com Exp $
  31. #
  32. use Getopt::Std;
  33. @boundarychars = ( "0" .. "9", "A" .. "F" );
  34. # path to cmsutil
  35. $cmsutilpath = "cmsutil";
  36. #
  37. # Thanks to Gisle Aas <gisle@aas.no> for the base64 functions
  38. # originally taken from MIME-Base64-2.11 at www.cpan.org
  39. #
  40. sub encode_base64($)
  41. {
  42.     my $res = "";
  43.     pos($_[0]) = 0;                          # ensure start at the beginning
  44.     while ($_[0] =~ /(.{1,45})/gs) {
  45. $res .= substr(pack('u', $1), 1);    # get rid of length byte after packing
  46. chop($res);
  47.     }
  48.     $res =~ tr|` -_|AA-Za-z0-9+/|;
  49.     # fix padding at the end
  50.     my $padding = (3 - length($_[0]) % 3) % 3;
  51.     $res =~ s/.{$padding}$/'=' x $padding/e if $padding;
  52.     # break encoded string into lines of no more than 76 characters each
  53.     $res =~ s/(.{1,76})/$1n/g;
  54.     $res;
  55. }
  56. sub decode_base64($)
  57. {
  58.     local($^W) = 0; # unpack("u",...) gives bogus warning in 5.00[123]
  59.     my $str = shift;
  60.     my $res = "";
  61.     $str =~ tr|A-Za-z0-9+=/||cd;            # remove non-base64 chars
  62.     if (length($str) % 4) {
  63. require Carp;
  64. Carp::carp("Length of base64 data not a multiple of 4")
  65.     }
  66.     $str =~ s/=+$//;                        # remove padding
  67.     $str =~ tr|A-Za-z0-9+/| -_|;            # convert to uuencoded format
  68.     while ($str =~ /(.{1,60})/gs) {
  69. my $len = chr(32 + length($1)*3/4); # compute length byte
  70. $res .= unpack("u", $len . $1 );    # uudecode
  71.     }
  72.     $res;
  73. }
  74. #
  75. # parse headers into a hash
  76. #
  77. # %headers = parseheaders($headertext);
  78. #
  79. sub parseheaders($)
  80. {
  81.     my ($headerdata) = @_;
  82.     my $hdr;
  83.     my %hdrhash;
  84.     my $hdrname;
  85.     my $hdrvalue;
  86.     my @hdrvalues;
  87.     my $subhdrname;
  88.     my $subhdrvalue;
  89.     # the expression in split() correctly handles continuation lines
  90.     foreach $hdr (split(/n(?=S)/, $headerdata)) {
  91. $hdr =~ s/r*ns+/ /g; # collapse continuation lines
  92. ($hdrname, $hdrvalue) = $hdr =~ m/^(S+):s+(.*)$/;
  93. # ignore non-headers (or should we die horribly?)
  94. next unless (defined($hdrname));
  95. $hdrname =~ tr/A-Z/a-z/; # lowercase the header name
  96. @hdrvalues = split(/s*;s*/, $hdrvalue); # split header values (XXXX quoting)
  97. # there is guaranteed to be at least one value
  98. $hdrvalue = shift @hdrvalues;
  99. if ($hdrvalue =~ /^s*"(.*)"s*$/) { # strip quotes if there
  100.     $hdrvalue = $1;
  101. }
  102. $hdrhash{$hdrname}{MAIN} = $hdrvalue;
  103. # print "XXX $hdrname = $hdrvaluen";
  104. # deal with additional name-value pairs
  105. foreach $hdrvalue (@hdrvalues) {
  106.     ($subhdrname, $subhdrvalue) = $hdrvalue =~ m/^(S+)s*=s*(.*)$/;
  107.     # ignore non-name-value pairs (or should we die?)
  108.     next unless (defined($subhdrname));
  109.     $subhdrname =~ tr/A-Z/a-z/;
  110.     if ($subhdrvalue =~ /^s*"(.*)"s*$/) { # strip quotes if there
  111. $subhdrvalue = $1;
  112.     }
  113.     $hdrhash{$hdrname}{$subhdrname} = $subhdrvalue;
  114. }
  115.     }
  116.     return %hdrhash;
  117. }
  118. #
  119. # encryptentity($entity, $options) - encrypt an S/MIME entity,
  120. #                                    creating a new application/pkcs7-smime entity
  121. #
  122. # entity  - string containing entire S/MIME entity to encrypt
  123. # options - options for cmsutil
  124. #
  125. # this will generate and return a new application/pkcs7-smime entity containing
  126. # the enveloped input entity.
  127. #
  128. sub encryptentity($$)
  129. {
  130.     my ($entity, $cmsutiloptions) = @_;
  131.     my $out = "";
  132.     my $boundary;
  133.     $tmpencfile = "/tmp/encryptentity.$$";
  134.     #
  135.     # generate a random boundary string
  136.     #
  137.     $boundary = "------------ms" . join("", @boundarychars[map{rand @boundarychars }( 1 .. 24 )]);
  138.     #
  139.     # tell cmsutil to generate a enveloped CMS message using our data
  140.     #
  141.     open(CMS, "|$cmsutilpath -E $cmsutiloptions -o $tmpencfile") or die "ERROR: cannot pipe to cmsutil";
  142.     print CMS $entity;
  143.     unless (close(CMS)) {
  144. print STDERR "ERROR: encryption failed.n";
  145. unlink($tmpsigfile);
  146. exit 1;
  147.     }
  148.     $out  = "Content-Type: application/pkcs7-mime; smime-type=enveloped-data; name=smime.p7mn";
  149.     $out .= "Content-Transfer-Encoding: base64n";
  150.     $out .= "Content-Disposition: attachment; filename=smime.p7mn";
  151.     $out .= "n"; # end of entity header
  152.     open (ENC, $tmpencfile) or die "ERROR: cannot find newly generated encrypted content";
  153.     local($/) = undef; # slurp whole file
  154.     $out .= encode_base64(<ENC>), "n"; # entity body is base64-encoded CMS message
  155.     close(ENC);
  156.     unlink($tmpencfile);
  157.     $out;
  158. }
  159. #
  160. # signentity($entity, $options) - sign an S/MIME entity
  161. #
  162. # entity  - string containing entire S/MIME entity to sign
  163. # options - options for cmsutil
  164. #
  165. # this will generate and return a new multipart/signed entity consisting
  166. # of the canonicalized original content, plus a signature block.
  167. #
  168. sub signentity($$)
  169. {
  170.     my ($entity, $cmsutiloptions) = @_;
  171.     my $out = "";
  172.     my $boundary;
  173.     $tmpsigfile = "/tmp/signentity.$$";
  174.     #
  175.     # generate a random boundary string
  176.     #
  177.     $boundary = "------------ms" . join("", @boundarychars[map{rand @boundarychars }( 1 .. 24 )]);
  178.     #
  179.     # tell cmsutil to generate a signed CMS message using the canonicalized data
  180.     # The signedData has detached content (-T) and includes a signing time attribute (-G)
  181.     #
  182.     # if we do not provide a password on the command line, here's where we would be asked for it
  183.     #
  184.     open(CMS, "|$cmsutilpath -S -T -G $cmsutiloptions -o $tmpsigfile") or die "ERROR: cannot pipe to cmsutil";
  185.     print CMS $entity;
  186.     unless (close(CMS)) {
  187. print STDERR "ERROR: signature generation failed.n";
  188. unlink($tmpsigfile);
  189. exit 1;
  190.     }
  191.     open (SIG, $tmpsigfile) or die "ERROR: cannot find newly generated signature";
  192.     #
  193.     # construct a new multipart/signed MIME entity consisting of the original content and
  194.     # the signature
  195.     #
  196.     # (we assume that cmsutil generates a SHA1 digest)
  197.     $out .= "Content-Type: multipart/signed; protocol="application/pkcs7-signature"; micalg=sha1; boundary="${boundary}"n";
  198.     $out .= "n"; # end of entity header
  199.     $out .= "This is a cryptographically signed message in MIME format.n"; # explanatory comment
  200.     $out .= "n--${boundary}n";
  201.     $out .= $entity;
  202.     $out .= "n--${boundary}n";
  203.     $out .= "Content-Type: application/pkcs7-signature; name=smime.p7sn";
  204.     $out .= "Content-Transfer-Encoding: base64n";
  205.     $out .= "Content-Disposition: attachment; filename=smime.p7sn";
  206.     $out .= "Content-Description: S/MIME Cryptographic Signaturen";
  207.     $out .= "n"; # end of signature subentity header
  208.     local($/) = undef; # slurp whole file
  209.     $out .= encode_base64(<SIG>); # append base64-encoded signature
  210.     $out .= "n--${boundary}--n";
  211.     close(SIG);
  212.     unlink($tmpsigfile);
  213.     $out;
  214. }
  215. sub usage {
  216.     print STDERR "usage: smime [options]n";
  217.     print STDERR " options:n";
  218.     print STDERR " -S nick             generate signed message, use certificate named "nick"n";
  219.     print STDERR "  -p passwd          use "passwd" as security module passwordn";
  220.     print STDERR " -E rec1[,rec2...]   generate encrypted message for recipientsn";
  221.     print STDERR " -D                  decode a S/MIME messagen";
  222.     print STDERR " -C pathname         set pathname of "cmsutil"n";
  223.     print STDERR " -d directory        set directory containing certificate dbn";
  224.     print STDERR "                     (default: ~/.netscape)n";
  225.     print STDERR "nWith -S or -E, smime will take a regular RFC822 message or MIME entityn";
  226.     print STDERR "on stdin and generate a signed or encrypted S/MIME message with the samen";
  227.     print STDERR "headers and content from it. The output can be used as input to a MTA.n";
  228.     print STDERR "-D causes smime to strip off all S/MIME layers if possible and outputn";
  229.     print STDERR "the "inner" message.n";
  230. }
  231. #
  232. # start of main procedures
  233. #
  234. #
  235. # process command line options
  236. #
  237. unless (getopts('S:E:p:d:C:D')) {
  238.     usage();
  239.     exit 1;
  240. }
  241. unless (defined($opt_S) or defined($opt_E) or defined($opt_D)) {
  242.     print STDERR "ERROR: -S and/or -E, or -D must be specified.n";
  243.     usage();
  244.     exit 1;
  245. }
  246. $signopts = "";
  247. $encryptopts = "";
  248. $decodeopts = "";
  249. # pass -d option along
  250. if (defined($opt_d)) {
  251.     $signopts .= "-d "$opt_d" ";
  252.     $encryptopts .= "-d "$opt_d" ";
  253.     $decodeopts .= "-d "$opt_d" ";
  254. }
  255. if (defined($opt_S)) {
  256.     $signopts .= "-N "$opt_S" ";
  257. }
  258. if (defined($opt_p)) {
  259.     $signopts .= "-p "$opt_p" ";
  260.     $decodeopts .= "-p "$opt_p" ";
  261. }
  262. if (defined($opt_E)) {
  263.     @recipients = split(",", $opt_E);
  264.     $encryptopts .= "-r ";
  265.     $encryptopts .= join (" -r ", @recipients);
  266. }
  267. if (defined($opt_C)) {
  268.     $cmsutilpath = $opt_C;
  269. }
  270. #
  271. # split headers into mime entity headers and RFC822 headers
  272. # The RFC822 headers are preserved and stay on the outer layer of the message
  273. #
  274. $rfc822headers = "";
  275. $mimeheaders = "";
  276. $mimebody = "";
  277. $skippedheaders = "";
  278. while (<STDIN>) {
  279.     last if (/^$/);
  280.     if (/^content-S+: /i) {
  281. $lastref = $mimeheaders;
  282.     } elsif (/^mime-version: /i) {
  283. $lastref = $skippedheaders; # skip it
  284.     } elsif (/^s/) {
  285. ;
  286.     } else {
  287. $lastref = $rfc822headers;
  288.     }
  289.     $$lastref .= $_;
  290. }
  291. #
  292. # if there are no MIME entity headers, generate some default ones
  293. #
  294. if ($mimeheaders eq "") {
  295.     $mimeheaders .= "Content-Type: text/plain; charset=us-asciin";
  296.     $mimeheaders .= "Content-Transfer-Encoding: 7bitn";
  297. }
  298. #
  299. # slurp in the entity body
  300. #
  301. $saveRS = $/;
  302. $/ = undef;
  303. $mimebody = <STDIN>;
  304. $/ = $saveRS;
  305. if (defined $opt_D) {
  306.     #
  307.     # decode
  308.     #
  309.     # possible options would be:
  310.     # - strip off only one layer
  311.     # - strip off outer signature (if present)
  312.     # - just print information about the structure of the message
  313.     # - strip n layers, then dump DER of CMS message
  314.     $layercounter = 1;
  315.     while (1) {
  316. %hdrhash = parseheaders($mimeheaders);
  317. unless (exists($hdrhash{"content-type"}{MAIN})) {
  318.     print STDERR "ERROR: no content type header found in MIME entityn";
  319.     last; # no content-type - we're done
  320. }
  321. $contenttype = $hdrhash{"content-type"}{MAIN};
  322. if ($contenttype eq "application/pkcs7-mime") {
  323.     #
  324.     # opaque-signed or enveloped message
  325.     #
  326.     unless (exists($hdrhash{"content-type"}{"smime-type"})) {
  327. print STDERR "ERROR: no smime-type attribute in application/pkcs7-smime entity.n";
  328. last;
  329.     }
  330.     $smimetype = $hdrhash{"content-type"}{"smime-type"};
  331.     if ($smimetype eq "signed-data" or $smimetype eq "enveloped-data") {
  332. # it's verification or decryption time!
  333. # can handle only base64 encoding for now
  334. # all other encodings are treated as binary (8bit)
  335. if ($hdrhash{"content-transfer-encoding"}{MAIN} eq "base64") {
  336.     $mimebody = decode_base64($mimebody);
  337. }
  338. # if we need to dump the DER, we would do it right here
  339. # now write the DER
  340. $tmpderfile = "/tmp/der.$$";
  341. open(TMP, ">$tmpderfile") or die "ERROR: cannot write signature data to temporary file";
  342. print TMP $mimebody;
  343. unless (close(TMP)) {
  344.     print STDERR "ERROR: writing signature data to temporary file.n";
  345.     unlink($tmpderfile);
  346.     exit 1;
  347. }
  348. $mimeheaders = "";
  349. open(TMP, "$cmsutilpath -D $decodeopts -h $layercounter -i $tmpderfile |") or die "ERROR: cannot open pipe to cmsutil";
  350. $layercounter++;
  351. while (<TMP>) {
  352.     last if (/^r?$/); # empty lines mark end of header
  353.     if (/^SMIME: /) { # add all SMIME info to the rfc822 hdrs
  354. $lastref = $rfc822headers;
  355.     } elsif (/^s/) {
  356. ; # continuation lines go to the last dest
  357.     } else {
  358. $lastref = $mimeheaders; # all other headers are mime headers
  359.     }
  360.     $$lastref .= $_;
  361. }
  362. # slurp in rest of the data to $mimebody
  363. $saveRS = $/; $/ = undef; $mimebody = <TMP>; $/ = $saveRS;
  364. close(TMP);
  365. unlink($tmpderfile);
  366.     } else {
  367. print STDERR "ERROR: unknown smime-type "$smimetype" in application/pkcs7-smime entity.n";
  368. last;
  369.     }
  370. } elsif ($contenttype eq "multipart/signed") {
  371.     #
  372.     # clear signed message
  373.     #
  374.     unless (exists($hdrhash{"content-type"}{"protocol"})) {
  375. print STDERR "ERROR: content type has no protocol attribute in multipart/signed entity.n";
  376. last;
  377.     }
  378.     if ($hdrhash{"content-type"}{"protocol"} ne "application/pkcs7-signature") {
  379. # we cannot handle this guy
  380. print STDERR "ERROR: unknown protocol "", $hdrhash{"content-type"}{"protocol"},
  381. "" in multipart/signed entity.n";
  382. last;
  383.     }
  384.     unless (exists($hdrhash{"content-type"}{"boundary"})) {
  385. print STDERR "ERROR: no boundary attribute in multipart/signed entity.n";
  386. last;
  387.     }
  388.     $boundary = $hdrhash{"content-type"}{"boundary"};
  389.     # split $mimebody along n--$boundaryn - gets you four parts
  390.     # first (0), any comments the sending agent might have put in
  391.     # second (1), the message itself
  392.     # third (2), the signature as a mime entity
  393.     # fourth (3), trailing data (there shouldn't be any)
  394.     @multiparts = split(/r?n--$boundary(?:--)?r?n/, $mimebody);
  395.     #
  396.     # parse the signature headers
  397.     ($submimeheaders, $submimebody) = split(/^$/m, $multiparts[2]);
  398.     %sighdrhash = parseheaders($submimeheaders);
  399.     unless (exists($sighdrhash{"content-type"}{MAIN})) {
  400. print STDERR "ERROR: signature entity has no content type.n";
  401. last;
  402.     }
  403.     if ($sighdrhash{"content-type"}{MAIN} ne "application/pkcs7-signature") {
  404. # we cannot handle this guy
  405. print STDERR "ERROR: unknown content type "", $sighdrhash{"content-type"}{MAIN},
  406. "" in signature entity.n";
  407. last;
  408.     }
  409.     if ($sighdrhash{"content-transfer-encoding"}{MAIN} eq "base64") {
  410. $submimebody = decode_base64($submimebody);
  411.     }
  412.     # we would dump the DER at this point
  413.     $tmpsigfile = "/tmp/sig.$$";
  414.     open(TMP, ">$tmpsigfile") or die "ERROR: cannot write signature data to temporary file";
  415.     print TMP $submimebody;
  416.     unless (close(TMP)) {
  417. print STDERR "ERROR: writing signature data to temporary file.n";
  418. unlink($tmpsigfile);
  419. exit 1;
  420.     }
  421.     $tmpmsgfile = "/tmp/msg.$$";
  422.     open(TMP, ">$tmpmsgfile") or die "ERROR: cannot write message data to temporary file";
  423.     print TMP $multiparts[1];
  424.     unless (close(TMP)) {
  425. print STDERR "ERROR: writing message data to temporary file.n";
  426. unlink($tmpsigfile);
  427. unlink($tmpmsgfile);
  428. exit 1;
  429.     }
  430.     $mimeheaders = "";
  431.     open(TMP, "$cmsutilpath -D $decodeopts -h $layercounter -c $tmpmsgfile -i $tmpsigfile |") or die "ERROR: cannot open pipe to cmsutil";
  432.     $layercounter++;
  433.     while (<TMP>) {
  434. last if (/^r?$/);
  435. if (/^SMIME: /) {
  436.     $lastref = $rfc822headers;
  437. } elsif (/^s/) {
  438.     ;
  439. } else {
  440.     $lastref = $mimeheaders;
  441. }
  442. $$lastref .= $_;
  443.     }
  444.     $saveRS = $/; $/ = undef; $mimebody = <TMP>; $/ = $saveRS;
  445.     close(TMP);
  446.     unlink($tmpsigfile);
  447.     unlink($tmpmsgfile);
  448. } else {
  449.     # not a content type we know - we're done
  450.     last;
  451. }
  452.     }
  453.     # so now we have the S/MIME parsing information in rfc822headers
  454.     # and the first mime entity we could not handle in mimeheaders and mimebody.
  455.     # dump 'em out and we're done.
  456.     print $rfc822headers;
  457.     print $mimeheaders . "n" . $mimebody;
  458. } else {
  459.     #
  460.     # encode (which is much easier than decode)
  461.     #
  462.     $mimeentity = $mimeheaders . "n" . $mimebody;
  463.     #
  464.     # canonicalize inner entity (rudimentary yet)
  465.     # convert single LFs to CRLF
  466.     # if no Content-Transfer-Encoding header present:
  467.     #  if 8 bit chars present, use Content-Transfer-Encoding: quoted-printable
  468.     #  otherwise, use Content-Transfer-Encoding: 7bit
  469.     #
  470.     $mimeentity =~ s/r*n/rn/mg;
  471.     #
  472.     # now do the wrapping
  473.     # we sign first, then encrypt because that's what Communicator needs
  474.     #
  475.     if (defined($opt_S)) {
  476. $mimeentity = signentity($mimeentity, $signopts);
  477.     }
  478.     if (defined($opt_E)) {
  479. $mimeentity = encryptentity($mimeentity, $encryptopts);
  480.     }
  481.     #
  482.     # XXX sign again to do triple wrapping (RFC2634)
  483.     #
  484.     #
  485.     # now write out the RFC822 headers
  486.     # followed by the final $mimeentity
  487.     #
  488.     print $rfc822headers;
  489.     print "MIME-Version: 1.0 (NSS SMIME - http://www.mozilla.org/projects/security)n"; # set up the flag
  490.     print $mimeentity;
  491. }
  492. exit 0;