summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorchrisk%netscape.com <devnull@localhost>2000-06-23 16:40:31 +0000
committerchrisk%netscape.com <devnull@localhost>2000-06-23 16:40:31 +0000
commit4e2d9951e066ba32f4eb6b9e5125ea847a94bb8f (patch)
tree86ee9b47fc32e7474fb831c1144ae7a75eccafa7
parent8f711f322bf761764c22707c08752dc005df7ddf (diff)
downloadnss-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.c15
-rwxr-xr-xsecurity/nss/cmd/smimetools/smime283
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