Fix error code name.
[openssl.git] / crypto / pkcs7 / pk7_mime.c
index 994473c0bd3d174d748ef3eb8505def07efb8118..2d321a9e9153bfe4d7ba4a7b1706dde6cf0cf071 100644 (file)
@@ -1,9 +1,9 @@
 /* pk7_mime.c */
 /* Written by Dr Stephen N Henson (shenson@bigfoot.com) for the OpenSSL
- * project 1999.
+ * project.
  */
 /* ====================================================================
- * Copyright (c) 1999 The OpenSSL Project.  All rights reserved.
+ * Copyright (c) 1999-2005 The OpenSSL Project.  All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -86,7 +86,8 @@ STACK_OF(MIME_PARAM) *params;         /* Zero or more parameters */
 DECLARE_STACK_OF(MIME_HEADER)
 IMPLEMENT_STACK_OF(MIME_HEADER)
 
-static int B64_write_PKCS7(BIO *bio, PKCS7 *p7);
+static int pkcs7_output_data(BIO *bio, BIO *data, PKCS7 *p7, int flags);
+static int B64_write_PKCS7(BIO *bio, PKCS7 *p7, BIO *in, int flags);
 static PKCS7 *B64_read_PKCS7(BIO *bio);
 static char * strip_ends(char *name);
 static char * strip_start(char *name);
@@ -101,7 +102,7 @@ static int mime_param_cmp(const MIME_PARAM * const *a,
 static void mime_param_free(MIME_PARAM *param);
 static int mime_bound_check(char *line, int linelen, char *bound, int blen);
 static int multi_split(BIO *bio, char *bound, STACK_OF(BIO) **ret);
-static int iscrlf(char c);
+static int strip_eol(char *linebuf, int *plen);
 static MIME_HEADER *mime_hdr_find(STACK_OF(MIME_HEADER) *hdrs, char *name);
 static MIME_PARAM *mime_param_find(MIME_HEADER *hdr, char *name);
 static void mime_hdr_free(MIME_HEADER *hdr);
@@ -109,25 +110,59 @@ static void mime_hdr_free(MIME_HEADER *hdr);
 #define MAX_SMLEN 1024
 #define mime_debug(x) /* x */
 
-
-typedef void (*stkfree)();
+/* Output a PKCS#7 structure in BER format streaming if necessary */
+
+int i2d_PKCS7_bio_stream(BIO *out, PKCS7 *p7, BIO *in, int flags)
+       {
+       /* If streaming create stream BIO and copy all content through it */
+       if (flags & PKCS7_STREAM)
+               {
+               BIO *bio, *tbio;
+               bio = BIO_new_PKCS7(out, p7);
+               if (!bio)
+                       {
+                       PKCS7err(PKCS7_F_I2D_PKCS7_BIO_STREAM,ERR_R_MALLOC_FAILURE);
+                       return 0;
+                       }
+               SMIME_crlf_copy(in, bio, flags);
+               BIO_flush(bio);
+               /* Free up successive BIOs until we hit the old output BIO */
+               do
+                       {
+                       tbio = BIO_pop(bio);
+                       BIO_free(bio);
+                       bio = tbio;
+                       } while (bio != out);
+               }
+       /* else just write out PKCS7 structure which will have all content
+        * stored internally
+        */
+       else
+               i2d_PKCS7_bio(out, p7);
+       return 1;
+       }
 
 /* Base 64 read and write of PKCS#7 structure */
 
-static int B64_write_PKCS7(BIO *bio, PKCS7 *p7)
-{
+static int B64_write_PKCS7(BIO *out, PKCS7 *p7, BIO *in, int flags)
+       {
        BIO *b64;
-       if(!(b64 = BIO_new(BIO_f_base64()))) {
+       int r;
+       b64 = BIO_new(BIO_f_base64());
+       if(!b64)
+               {
                PKCS7err(PKCS7_F_B64_WRITE_PKCS7,ERR_R_MALLOC_FAILURE);
                return 0;
-       }
-       bio = BIO_push(b64, bio);
-       i2d_PKCS7_bio(bio, p7);
-       BIO_flush(bio);
-       bio = BIO_pop(bio);
+               }
+       /* prepend the b64 BIO so all data is base64 encoded.
+        */
+       out = BIO_push(b64, out);
+       r = i2d_PKCS7_bio_stream(out, p7, in, flags);
+       BIO_flush(out);
+       BIO_pop(out);
        BIO_free(b64);
-       return 1;
-}
+       return r;
+       }
 
 static PKCS7 *B64_read_PKCS7(BIO *bio)
 {
@@ -146,13 +181,114 @@ static PKCS7 *B64_read_PKCS7(BIO *bio)
        return p7;
 }
 
+/* Streaming PKCS#7 PEM write */
+
+int PEM_write_bio_PKCS7_stream(BIO *out, PKCS7 *p7, BIO *in, int flags)
+       {
+       int r;
+       BIO_puts(out, "-----BEGIN PKCS7-----\n");
+       r = B64_write_PKCS7(out, p7, in, flags);
+       BIO_puts(out, "-----END PKCS7-----\n");
+       return r;
+       }
+
+/* Generate the MIME "micalg" parameter from RFC3851, RFC4490 */
+
+static int pk7_write_micalg(BIO *out, PKCS7 *p7)
+       {
+       STACK_OF(X509_ALGOR) *mdalgs;
+       const EVP_MD *md;
+       int i, have_unknown = 0, write_comma, ret = 0, md_nid;
+       mdalgs = p7->d.sign->md_algs;
+       have_unknown = 0;
+       write_comma = 0;
+       for (i = 0; i < sk_X509_ALGOR_num(mdalgs); i++)
+               {
+               if (write_comma)
+                       BIO_write(out, ",", 1);
+               write_comma = 1;
+               md_nid = OBJ_obj2nid(sk_X509_ALGOR_value(mdalgs, i)->algorithm);
+               md = EVP_get_digestbynid(md_nid);
+               if (md && md->md_ctrl)
+                       {
+                       int rv;
+                       char *micstr;
+                       rv = md->md_ctrl(NULL, EVP_MD_CTRL_MICALG, 0, &micstr);
+                       if (rv > 0)
+                               {
+                               BIO_puts(out, micstr);
+                               OPENSSL_free(micstr);
+                               continue;
+                               }
+                       if (rv != -2)
+                               goto err;
+                       }
+               switch(md_nid)
+                       {
+                       case NID_sha1:
+                       BIO_puts(out, "sha1");
+                       break;
+
+                       case NID_md5:
+                       BIO_puts(out, "md5");
+                       break;
+
+                       case NID_sha256:
+                       BIO_puts(out, "sha-256");
+                       break;
+
+                       case NID_sha384:
+                       BIO_puts(out, "sha-384");
+                       break;
+
+                       case NID_sha512:
+                       BIO_puts(out, "sha-512");
+                       break;
+
+                       case NID_id_GostR3411_94:
+                       BIO_puts(out, "gostr3411-94");
+                               goto err;
+                       break;
+
+                       default:
+                       if (have_unknown)
+                               write_comma = 0;
+                       else
+                               {
+                               BIO_puts(out, "unknown");
+                               have_unknown = 1;
+                               }
+                       break;
+
+                       }
+               }
+
+       ret = 1;
+       err:
+
+       return ret;
+
+       }
+
+
+
+
 /* SMIME sender */
 
 int SMIME_write_PKCS7(BIO *bio, PKCS7 *p7, BIO *data, int flags)
 {
-       char linebuf[MAX_SMLEN];
        char bound[33], c;
        int i;
+       char *mime_prefix, *mime_eol, *msg_type=NULL;
+       if (flags & PKCS7_NOOLDMIMETYPE)
+               mime_prefix = "application/pkcs7-";
+       else
+               mime_prefix = "application/x-pkcs7-";
+
+       if (flags & PKCS7_CRLFEOL)
+               mime_eol = "\r\n";
+       else
+               mime_eol = "\n";
        if((flags & PKCS7_DETACHED) && data) {
        /* We want multipart/signed */
                /* Generate a random boundary */
@@ -164,37 +300,108 @@ int SMIME_write_PKCS7(BIO *bio, PKCS7 *p7, BIO *data, int flags)
                        bound[i] = c;
                }
                bound[32] = 0;
-               BIO_printf(bio, "MIME-Version: 1.0\n");
-               BIO_printf(bio, "Content-Type: multipart/signed ; ");
-               BIO_printf(bio, "protocol=\"application/x-pkcs7-signature\" ; ");
-               BIO_printf(bio, "micalg=sha1 ; boundary=\"----%s\"\n\n", bound);
-               BIO_printf(bio, "This is an S/MIME signed message\n\n");
+               BIO_printf(bio, "MIME-Version: 1.0%s", mime_eol);
+               BIO_printf(bio, "Content-Type: multipart/signed;");
+               BIO_printf(bio, " protocol=\"%ssignature\";", mime_prefix);
+               BIO_puts(bio, " micalg=\"");
+               pk7_write_micalg(bio, p7);
+               BIO_printf(bio, "\"; boundary=\"----%s\"%s%s",
+                                               bound, mime_eol, mime_eol);
+               BIO_printf(bio, "This is an S/MIME signed message%s%s",
+                                               mime_eol, mime_eol);
                /* Now write out the first part */
-               BIO_printf(bio, "------%s\n", bound);
-               if(flags & PKCS7_TEXT) BIO_printf(bio, "Content-Type: text/plain\n\n");
-               while((i = BIO_read(data, linebuf, MAX_SMLEN)) > 0) 
-                                               BIO_write(bio, linebuf, i);
-               BIO_printf(bio, "\n------%s\n", bound);
+               BIO_printf(bio, "------%s%s", bound, mime_eol);
+               pkcs7_output_data(bio, data, p7, flags);
+               BIO_printf(bio, "%s------%s%s", mime_eol, bound, mime_eol);
 
                /* Headers for signature */
 
-               BIO_printf(bio, "Content-Type: application/x-pkcs7-signature; name=\"smime.p7s\"\n");
-               BIO_printf(bio, "Content-Transfer-Encoding: base64\n");
-               BIO_printf(bio, "Content-Disposition: attachment; filename=\"smime.p7s\"\n\n");
-               B64_write_PKCS7(bio, p7);
-               BIO_printf(bio,"\n------%s--\n\n", bound);
+               BIO_printf(bio, "Content-Type: %ssignature;", mime_prefix); 
+               BIO_printf(bio, " name=\"smime.p7s\"%s", mime_eol);
+               BIO_printf(bio, "Content-Transfer-Encoding: base64%s",
+                                                               mime_eol);
+               BIO_printf(bio, "Content-Disposition: attachment;");
+               BIO_printf(bio, " filename=\"smime.p7s\"%s%s",
+                                                       mime_eol, mime_eol);
+               B64_write_PKCS7(bio, p7, NULL, 0);
+               BIO_printf(bio,"%s------%s--%s%s", mime_eol, bound,
+                                                       mime_eol, mime_eol);
                return 1;
        }
+
+       /* Determine smime-type header */
+
+       if (PKCS7_type_is_enveloped(p7))
+               msg_type = "enveloped-data";
+       else if (PKCS7_type_is_signed(p7))
+               {
+               /* If we have any signers it is signed-data otherwise 
+                * certs-only.
+                */
+               STACK_OF(PKCS7_SIGNER_INFO) *sinfos;
+               sinfos = PKCS7_get_signer_info(p7);
+               if (sk_PKCS7_SIGNER_INFO_num(sinfos) > 0)
+                       msg_type = "signed-data";
+               else
+                       msg_type = "certs-only";
+               }
+       else
+               flags &= ~PKCS7_STREAM;
        /* MIME headers */
-       BIO_printf(bio, "MIME-Version: 1.0\n");
-       BIO_printf(bio, "Content-Disposition: attachment; filename=\"smime.p7m\"\n");
-       BIO_printf(bio, "Content-Type: application/x-pkcs7-mime; name=\"smime.p7m\"\n");
-       BIO_printf(bio, "Content-Transfer-Encoding: base64\n\n");
-       B64_write_PKCS7(bio, p7);
-       BIO_printf(bio, "\n");
+       BIO_printf(bio, "MIME-Version: 1.0%s", mime_eol);
+       BIO_printf(bio, "Content-Disposition: attachment;");
+       BIO_printf(bio, " filename=\"smime.p7m\"%s", mime_eol);
+       BIO_printf(bio, "Content-Type: %smime;", mime_prefix);
+       if (msg_type)
+               BIO_printf(bio, " smime-type=%s;", msg_type);
+       BIO_printf(bio, " name=\"smime.p7m\"%s", mime_eol);
+       BIO_printf(bio, "Content-Transfer-Encoding: base64%s%s",
+                                               mime_eol, mime_eol);
+       B64_write_PKCS7(bio, p7, data, flags);
+       BIO_printf(bio, "%s", mime_eol);
        return 1;
 }
 
+/* Handle output of PKCS#7 data */
+
+
+static int pkcs7_output_data(BIO *out, BIO *data, PKCS7 *p7, int flags)
+       {
+       BIO *tmpbio, *p7bio;
+
+       if (!(flags & PKCS7_STREAM))
+               {
+               SMIME_crlf_copy(data, out, flags);
+               return 1;
+               }
+
+       /* Partial sign operation */
+
+       /* Initialize sign operation */
+       p7bio = PKCS7_dataInit(p7, out);
+
+       /* Copy data across, computing digests etc */
+       SMIME_crlf_copy(data, p7bio, flags);
+
+       /* Must be detached */
+       PKCS7_set_detached(p7, 1);
+
+       /* Finalize signatures */
+       PKCS7_dataFinal(p7, p7bio);
+
+       /* Now remove any digests prepended to the BIO */
+
+       while (p7bio != out)
+               {
+               tmpbio = BIO_pop(p7bio);
+               BIO_free(p7bio);
+               p7bio = tmpbio;
+               }
+
+       return 1;
+
+       }
+
 /* SMIME reader: handle multipart/signed and opaque signing.
  * in multipart case the content is placed in a memory BIO
  * pointed to by "bcont". In opaque this is set to NULL
@@ -306,24 +513,38 @@ PKCS7 *SMIME_read_PKCS7(BIO *bio, BIO **bcont)
 /* Copy text from one BIO to another making the output CRLF at EOL */
 int SMIME_crlf_copy(BIO *in, BIO *out, int flags)
 {
+       BIO *bf;
        char eol;
        int len;
        char linebuf[MAX_SMLEN];
-       if(flags & PKCS7_BINARY) {
+       /* Buffer output so we don't write one line at a time. This is
+        * useful when streaming as we don't end up with one OCTET STRING
+        * per line.
+        */
+       bf = BIO_new(BIO_f_buffer());
+       if (!bf)
+               return 0;
+       out = BIO_push(bf, out);
+       if(flags & PKCS7_BINARY)
+               {
                while((len = BIO_read(in, linebuf, MAX_SMLEN)) > 0)
                                                BIO_write(out, linebuf, len);
-               return 1;
-       }
-       if(flags & PKCS7_TEXT) BIO_printf(out, "Content-Type: text/plain\r\n\r\n");
-       while ((len = BIO_gets(in, linebuf, MAX_SMLEN)) > 0) {
-               eol = 0;
-               while(iscrlf(linebuf[len - 1])) {
-                       len--;
-                       eol = 1;
-               }       
-               BIO_write(out, linebuf, len);
-               if(eol) BIO_write(out, "\r\n", 2);
-       }
+               }
+       else
+               {
+               if(flags & PKCS7_TEXT)
+                       BIO_printf(out, "Content-Type: text/plain\r\n\r\n");
+               while ((len = BIO_gets(in, linebuf, MAX_SMLEN)) > 0)
+                       {
+                       eol = strip_eol(linebuf, &len);
+                       if (len)
+                               BIO_write(out, linebuf, len);
+                       if(eol) BIO_write(out, "\r\n", 2);
+                       }
+               }
+       BIO_flush(out);
+       BIO_pop(out);
+       BIO_free(bf);
        return 1;
 }
 
@@ -364,6 +585,7 @@ static int multi_split(BIO *bio, char *bound, STACK_OF(BIO) **ret)
 {
        char linebuf[MAX_SMLEN];
        int len, blen;
+       int eol = 0, next_eol = 0;
        BIO *bpart = NULL;
        STACK_OF(BIO) *parts;
        char state, part, first;
@@ -383,26 +605,23 @@ static int multi_split(BIO *bio, char *bound, STACK_OF(BIO) **ret)
                        sk_BIO_push(parts, bpart);
                        return 1;
                } else if(part) {
+                       /* Strip CR+LF from linebuf */
+                       next_eol = strip_eol(linebuf, &len);
                        if(first) {
                                first = 0;
                                if(bpart) sk_BIO_push(parts, bpart);
                                bpart = BIO_new(BIO_s_mem());
-                               
-                       } else BIO_write(bpart, "\r\n", 2);
-                       /* Strip CR+LF from linebuf */
-                       while(iscrlf(linebuf[len - 1])) len--;
-                       BIO_write(bpart, linebuf, len);
+                               BIO_set_mem_eof_return(bpart, 0);
+                       } else if (eol)
+                               BIO_write(bpart, "\r\n", 2);
+                       eol = next_eol;
+                       if (len)
+                               BIO_write(bpart, linebuf, len);
                }
        }
        return 0;
 }
 
-static int iscrlf(char c)
-{
-       if(c == '\r' || c == '\n') return 1;
-       return 0;
-}
-
 /* This is the big one: parse MIME header lines up to message body */
 
 #define MIME_INVALID   0
@@ -683,3 +902,21 @@ static int mime_bound_check(char *line, int linelen, char *bound, int blen)
        }
        return 0;
 }
+
+static int strip_eol(char *linebuf, int *plen)
+       {
+       int len = *plen;
+       char *p, c;
+       int is_eol = 0;
+       p = linebuf + len - 1;
+       for (p = linebuf + len - 1; len > 0; len--, p--)
+               {
+               c = *p;
+               if (c == '\n')
+                       is_eol = 1;
+               else if (c != '\r')
+                       break;
+               }
+       *plen = len;
+       return is_eol;
+       }