diff options
author | chrisk%netscape.com <devnull@localhost> | 2000-06-23 16:40:31 +0000 |
---|---|---|
committer | chrisk%netscape.com <devnull@localhost> | 2000-06-23 16:40:31 +0000 |
commit | 4e2d9951e066ba32f4eb6b9e5125ea847a94bb8f (patch) | |
tree | 86ee9b47fc32e7474fb831c1144ae7a75eccafa7 | |
parent | 8f711f322bf761764c22707c08752dc005df7ddf (diff) | |
download | nss-hg-4e2d9951e066ba32f4eb6b9e5125ea847a94bb8f.tar.gz |
Preliminary checkin at a state where smime can parse messages generated
by itself.
-rw-r--r-- | security/nss/cmd/smimetools/cmsutil.c | 15 | ||||
-rwxr-xr-x | security/nss/cmd/smimetools/smime | 283 |
2 files changed, 274 insertions, 24 deletions
diff --git a/security/nss/cmd/smimetools/cmsutil.c b/security/nss/cmd/smimetools/cmsutil.c index f78076677..094a1d75c 100644 --- a/security/nss/cmd/smimetools/cmsutil.c +++ b/security/nss/cmd/smimetools/cmsutil.c @@ -325,9 +325,18 @@ decode(FILE *out, FILE *infile, char *progName, struct optionsStr options, struc } if (!decodeOptions.suppressContent) { - /* XXX only if we do not have detached content... */ - if ((item = NSS_CMSMessage_GetContent(cmsg)) != NULL) { - fwrite(item->data, item->len, 1, out); + if (decodeOptions.contentFile) { + char buffer[4096]; + size_t nbytes; + /* detached content: print content file */ + fseek(decodeOptions.contentFile, 0, SEEK_SET); + while ((nbytes = fread(buffer, 1, sizeof(buffer), decodeOptions.contentFile)) != 0) { + fwrite(buffer, nbytes, 1, out); + } + } else { + if ((item = NSS_CMSMessage_GetContent(cmsg)) != NULL) { + fwrite(item->data, item->len, 1, out); + } } } diff --git a/security/nss/cmd/smimetools/smime b/security/nss/cmd/smimetools/smime index 70179c8fe..91550b67d 100755 --- a/security/nss/cmd/smimetools/smime +++ b/security/nss/cmd/smimetools/smime @@ -32,7 +32,7 @@ # GPL. # -# smime.pl - frontend for S/MIME message generation +# smime.pl - frontend for S/MIME message generation and parsing # # $Id$ # @@ -45,7 +45,7 @@ use Getopt::Std; $cmsutilpath = "cmsutil"; # -# Thanks to Gisle Aas <gisle@aas.no> for the encode_base64 function +# 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($) @@ -65,6 +65,78 @@ sub encode_base64($) $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*\n\s+/ /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/; + @hdrvalues = split(/\s*;\s*/, $hdrvalue); + + # there is guaranteed to be at least one value + $hdrvalue = shift @hdrvalues; + if ($hdrvalue =~ /^\"(.*)\"$/) { + $hdrvalue = $1; + } + + $hdrhash{$hdrname}{MAIN} = $hdrvalue; + # print "XXX $hdrname = $hdrvalue\n"; + + # deal with additional name-value pairs + foreach $hdrvalue (@hdrvalues) { + ($subhdrname, $subhdrvalue) = $hdrvalue =~ m/^(\S+)\s*=\s*(.*)$/; + # ignore non-subheaders (or should we die?) + next unless (defined($subhdrname)); + $subhdrname =~ tr/A-Z/a-z/; + if ($subhdrvalue =~ /^\"(.*)\"$/) { + $subhdrvalue = $1; + } + $hdrhash{$hdrname}{$subhdrname} = $subhdrvalue; + } + + } + return %hdrhash; +} + # # encryptentity($entity, $options) - encrypt an S/MIME entity # @@ -193,10 +265,13 @@ sub usage { print STDERR " -S nick generate signed message, use certificate named \"nick\"\n"; print STDERR " -p passwd use \"passwd\" as security module password\n"; print STDERR " -E rec1[,rec2...] generate encrypted message for recipients\n"; + print STDERR " -D decode a S/MIME message\n"; print STDERR " -C pathname set pathname of \"cmsutil\"\n"; print STDERR "\nWith -S or -E, smime will take a regular RFC822 message or MIME entity\n"; - print STDERR "and generate a signed or encrypted S/MIME message with the same headers\n"; - print STDERR "and content from it. The output can be used as input to a MTA.\n"; + print STDERR "on stdin and generate a signed or encrypted S/MIME message with the same\n"; + 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 output\n"; + print STDERR "the \"inner\" message.\n"; } # @@ -211,8 +286,8 @@ unless (getopts('S:E:p:C:D')) { exit 1; } -unless (defined($opt_S) or defined($opt_E)) { - print STDERR "ERROR: -S and/or -E must be specified.\n"; +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; } @@ -243,51 +318,217 @@ if (defined($opt_C)) { # The RFC822 headers are preserved and stay on the outer layer of the message # $rfc822headers = ""; -$mimeentity = ""; +$mimeheaders = ""; +$mimebody = ""; +$skippedheaders = ""; while (<STDIN>) { last if (/^$/); if (/^content-\S+: /i) { - $mimeentity .= $_; + $lastref = \$mimeheaders; } elsif (/^mime-version: /i) { - ; # skip it + $lastref = \$skippedheaders; # skip it + } elsif (/^\s/) { + ; } else { - $rfc822headers .= $_; + $lastref = \$rfc822headers; } + $$lastref .= $_; } # # if there are no MIME entity headers, generate some default ones # -if ($mimeentity eq "") { - $mimeentity .= "Content-Type: text/plain; charset=us-ascii\n"; - $mimeentity .= "Content-Transfer-Encoding: 7bit\n"; +if ($mimeheaders eq "") { + $mimeheaders .= "Content-Type: text/plain; charset=us-ascii\n"; + $mimeheaders .= "Content-Transfer-Encoding: 7bit\n"; } # -# generate end of header-LF/LF pair -# -$mimeentity .= "\n"; - -# # slurp in the entity body # $saveRS = $/; $/ = undef; -$mimeentity .= <STDIN>; +$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 + + while (1) { + %hdrhash = parseheaders($mimeheaders); + unless (exists($hdrhash{"content-type"}{MAIN})) { + print STDERR "ERROR: no content type header found in MIME entity\n"; + 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! + # XXX + if ($hdrhash{"content-transfer-encoding"}{MAIN} eq "base64") { + $mimebody = decode_base64($mimebody); + } + + # 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 $mimebody; + unless (close(TMP)) { + print STDERR "ERROR: writing signature data to temporary file.\n"; + unlink($tmpsigfile); + exit 1; + } + + $mimeheaders = ""; + open(TMP, "$cmsutilpath -D -h 1 -i $tmpsigfile |") or die "ERROR: cannot open pipe to cmsutil"; + while (<TMP>) { + last if (/^\r?$/); + if (/^SMIME: /) { + $lastref = \$rfc822headers; + } elsif (/^\s/) { + ; + } else { + $lastref = \$mimeheaders; + } + $$lastref .= $_; + } + $olddelim = $/; + $/ = undef; + $mimebody = <TMP>; + $/ = $olddelim; + close(TMP); + unlink($tmpsigfile); + } else { + print STDERR "ERROR: unknown smime-type \"$smimetype\" in application/pkcs7-smime entity.\n"; + last; + } + } elsif ($contenttype eq "multipart/signed") { + # print STDERR "XXX multipart/signed\n"; + # + # 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--$boundary\n - 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(/\n--$boundary(?:--)?\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 -h 1 -c $tmpmsgfile -i $tmpsigfile |") or die "ERROR: cannot open pipe to cmsutil"; + while (<TMP>) { + last if (/^\r?$/); + if (/^SMIME: /) { + $lastref = \$rfc822headers; + } elsif (/^\s/) { + ; + } else { + $lastref = \$mimeheaders; + } + $$lastref .= $_; + } + $olddelim = $/; + $/ = undef; + $mimebody = <TMP>; + $/ = $olddelim; + 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. + # so dump em out and we're done. + print $rfc822headers; + print $mimeheaders . "\n" . $mimebody; } else { + # - # encode + # encode (much easier than decode) # + $mimeentity = $mimeheaders . "\n" . $mimebody; + # # canonicalize inner entity (rudimentary yet) # convert single LFs to CRLF |