asn_mime.c revision 269686
160786Sps/* asn_mime.c */ 260786Sps/* Written by Dr Stephen N Henson (steve@openssl.org) for the OpenSSL 360786Sps * project. 460786Sps */ 560786Sps/* ==================================================================== 660786Sps * Copyright (c) 1999-2008 The OpenSSL Project. All rights reserved. 760786Sps * 860786Sps * Redistribution and use in source and binary forms, with or without 960786Sps * modification, are permitted provided that the following conditions 1060786Sps * are met: 1160786Sps * 1260786Sps * 1. Redistributions of source code must retain the above copyright 1360786Sps * notice, this list of conditions and the following disclaimer. 1460786Sps * 1560786Sps * 2. Redistributions in binary form must reproduce the above copyright 1660786Sps * notice, this list of conditions and the following disclaimer in 1760786Sps * the documentation and/or other materials provided with the 1860786Sps * distribution. 1960786Sps * 2060786Sps * 3. All advertising materials mentioning features or use of this 2160786Sps * software must display the following acknowledgment: 2260786Sps * "This product includes software developed by the OpenSSL Project 2360786Sps * for use in the OpenSSL Toolkit. (http://www.OpenSSL.org/)" 2460786Sps * 2560786Sps * 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to 2660786Sps * endorse or promote products derived from this software without 2760786Sps * prior written permission. For written permission, please contact 2860786Sps * licensing@OpenSSL.org. 2960786Sps * 3060786Sps * 5. Products derived from this software may not be called "OpenSSL" 3160786Sps * nor may "OpenSSL" appear in their names without prior written 3260786Sps * permission of the OpenSSL Project. 3360786Sps * 3460786Sps * 6. Redistributions of any form whatsoever must retain the following 3560786Sps * acknowledgment: 3660786Sps * "This product includes software developed by the OpenSSL Project 3760786Sps * for use in the OpenSSL Toolkit (http://www.OpenSSL.org/)" 3860786Sps * 3960786Sps * THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY 4060786Sps * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 4160786Sps * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 4260786Sps * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OpenSSL PROJECT OR 4360786Sps * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 4460786Sps * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 4560786Sps * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 4660786Sps * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 4760786Sps * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 4860786Sps * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 4960786Sps * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 5060786Sps * OF THE POSSIBILITY OF SUCH DAMAGE. 5160786Sps * ==================================================================== 5260786Sps * 5360786Sps */ 5460786Sps 5560786Sps#include <stdio.h> 5660786Sps#include <ctype.h> 5760786Sps#include "cryptlib.h" 5860786Sps#include <openssl/rand.h> 5960786Sps#include <openssl/x509.h> 6060786Sps#include <openssl/asn1.h> 6160786Sps#include <openssl/asn1t.h> 6260786Sps#include "asn1_locl.h" 6360786Sps 6460786Sps/* Generalised MIME like utilities for streaming ASN1. Although many 6560786Sps * have a PKCS7/CMS like flavour others are more general purpose. 6660786Sps */ 6760786Sps 6860786Sps/* MIME format structures 6960786Sps * Note that all are translated to lower case apart from 7060786Sps * parameter values. Quotes are stripped off 7160786Sps */ 7260786Sps 7360786Spstypedef struct { 7460786Spschar *param_name; /* Param name e.g. "micalg" */ 7560786Spschar *param_value; /* Param value e.g. "sha1" */ 7660786Sps} MIME_PARAM; 7760786Sps 7860786SpsDECLARE_STACK_OF(MIME_PARAM) 7960786SpsIMPLEMENT_STACK_OF(MIME_PARAM) 8060786Sps 8160786Spstypedef struct { 8260786Spschar *name; /* Name of line e.g. "content-type" */ 8360786Spschar *value; /* Value of line e.g. "text/plain" */ 8460786SpsSTACK_OF(MIME_PARAM) *params; /* Zero or more parameters */ 8560786Sps} MIME_HEADER; 8660786Sps 8760786SpsDECLARE_STACK_OF(MIME_HEADER) 8860786SpsIMPLEMENT_STACK_OF(MIME_HEADER) 8960786Sps 9060786Spsstatic int asn1_output_data(BIO *out, BIO *data, ASN1_VALUE *val, int flags, 9160786Sps const ASN1_ITEM *it); 9260786Spsstatic char * strip_ends(char *name); 9360786Spsstatic char * strip_start(char *name); 9460786Spsstatic char * strip_end(char *name); 9560786Spsstatic MIME_HEADER *mime_hdr_new(char *name, char *value); 9660786Spsstatic int mime_hdr_addparam(MIME_HEADER *mhdr, char *name, char *value); 9760786Spsstatic STACK_OF(MIME_HEADER) *mime_parse_hdr(BIO *bio); 9860786Spsstatic int mime_hdr_cmp(const MIME_HEADER * const *a, 9960786Sps const MIME_HEADER * const *b); 10060786Spsstatic int mime_param_cmp(const MIME_PARAM * const *a, 10160786Sps const MIME_PARAM * const *b); 10260786Spsstatic void mime_param_free(MIME_PARAM *param); 10360786Spsstatic int mime_bound_check(char *line, int linelen, char *bound, int blen); 10460786Spsstatic int multi_split(BIO *bio, char *bound, STACK_OF(BIO) **ret); 10560786Spsstatic int strip_eol(char *linebuf, int *plen); 10660786Spsstatic MIME_HEADER *mime_hdr_find(STACK_OF(MIME_HEADER) *hdrs, char *name); 10760786Spsstatic MIME_PARAM *mime_param_find(MIME_HEADER *hdr, char *name); 10860786Spsstatic void mime_hdr_free(MIME_HEADER *hdr); 10960786Sps 11060786Sps#define MAX_SMLEN 1024 11160786Sps#define mime_debug(x) /* x */ 11260786Sps 11360786Sps/* Output an ASN1 structure in BER format streaming if necessary */ 11460786Sps 11560786Spsint i2d_ASN1_bio_stream(BIO *out, ASN1_VALUE *val, BIO *in, int flags, 11660786Sps const ASN1_ITEM *it) 11760786Sps { 11860786Sps /* If streaming create stream BIO and copy all content through it */ 11960786Sps if (flags & SMIME_STREAM) 12060786Sps { 12160786Sps BIO *bio, *tbio; 12260786Sps bio = BIO_new_NDEF(out, val, it); 12360786Sps if (!bio) 12460786Sps { 12560786Sps ASN1err(ASN1_F_I2D_ASN1_BIO_STREAM,ERR_R_MALLOC_FAILURE); 12660786Sps return 0; 12760786Sps } 12860786Sps SMIME_crlf_copy(in, bio, flags); 12960786Sps (void)BIO_flush(bio); 13060786Sps /* Free up successive BIOs until we hit the old output BIO */ 13160786Sps do 13260786Sps { 13360786Sps tbio = BIO_pop(bio); 13460786Sps BIO_free(bio); 13560786Sps bio = tbio; 13660786Sps } while (bio != out); 13760786Sps } 13860786Sps /* else just write out ASN1 structure which will have all content 13960786Sps * stored internally 14060786Sps */ 14160786Sps else 14260786Sps ASN1_item_i2d_bio(it, out, val); 14360786Sps return 1; 14460786Sps } 14560786Sps 14660786Sps/* Base 64 read and write of ASN1 structure */ 14760786Sps 14860786Spsstatic int B64_write_ASN1(BIO *out, ASN1_VALUE *val, BIO *in, int flags, 14960786Sps const ASN1_ITEM *it) 15060786Sps { 15160786Sps BIO *b64; 15260786Sps int r; 15360786Sps b64 = BIO_new(BIO_f_base64()); 15460786Sps if(!b64) 15560786Sps { 15660786Sps ASN1err(ASN1_F_B64_WRITE_ASN1,ERR_R_MALLOC_FAILURE); 15760786Sps return 0; 15860786Sps } 15960786Sps /* prepend the b64 BIO so all data is base64 encoded. 16060786Sps */ 16160786Sps out = BIO_push(b64, out); 16260786Sps r = i2d_ASN1_bio_stream(out, val, in, flags, it); 16360786Sps (void)BIO_flush(out); 16460786Sps BIO_pop(out); 16560786Sps BIO_free(b64); 16660786Sps return r; 16760786Sps } 16860786Sps 16960786Sps/* Streaming ASN1 PEM write */ 17060786Sps 17160786Spsint PEM_write_bio_ASN1_stream(BIO *out, ASN1_VALUE *val, BIO *in, int flags, 17260786Sps const char *hdr, 17360786Sps const ASN1_ITEM *it) 17460786Sps { 17560786Sps int r; 17660786Sps BIO_printf(out, "-----BEGIN %s-----\n", hdr); 17760786Sps r = B64_write_ASN1(out, val, in, flags, it); 17860786Sps BIO_printf(out, "-----END %s-----\n", hdr); 17960786Sps return r; 18060786Sps } 18160786Sps 18260786Spsstatic ASN1_VALUE *b64_read_asn1(BIO *bio, const ASN1_ITEM *it) 18360786Sps{ 18460786Sps BIO *b64; 18560786Sps ASN1_VALUE *val; 18660786Sps if(!(b64 = BIO_new(BIO_f_base64()))) { 18760786Sps ASN1err(ASN1_F_B64_READ_ASN1,ERR_R_MALLOC_FAILURE); 18860786Sps return 0; 18960786Sps } 19060786Sps bio = BIO_push(b64, bio); 19160786Sps val = ASN1_item_d2i_bio(it, bio, NULL); 19260786Sps if(!val) 19360786Sps ASN1err(ASN1_F_B64_READ_ASN1,ASN1_R_DECODE_ERROR); 19460786Sps (void)BIO_flush(bio); 19560786Sps bio = BIO_pop(bio); 19660786Sps BIO_free(b64); 19760786Sps return val; 19860786Sps} 19960786Sps 20060786Sps/* Generate the MIME "micalg" parameter from RFC3851, RFC4490 */ 20160786Sps 20260786Spsstatic int asn1_write_micalg(BIO *out, STACK_OF(X509_ALGOR) *mdalgs) 20360786Sps { 20460786Sps const EVP_MD *md; 20560786Sps int i, have_unknown = 0, write_comma, ret = 0, md_nid; 20660786Sps have_unknown = 0; 20760786Sps write_comma = 0; 20860786Sps for (i = 0; i < sk_X509_ALGOR_num(mdalgs); i++) 20960786Sps { 21060786Sps if (write_comma) 21160786Sps BIO_write(out, ",", 1); 21260786Sps write_comma = 1; 21360786Sps md_nid = OBJ_obj2nid(sk_X509_ALGOR_value(mdalgs, i)->algorithm); 21460786Sps md = EVP_get_digestbynid(md_nid); 21560786Sps if (md && md->md_ctrl) 21660786Sps { 21760786Sps int rv; 21860786Sps char *micstr; 21960786Sps rv = md->md_ctrl(NULL, EVP_MD_CTRL_MICALG, 0, &micstr); 22060786Sps if (rv > 0) 22160786Sps { 22260786Sps BIO_puts(out, micstr); 22360786Sps OPENSSL_free(micstr); 22460786Sps continue; 22560786Sps } 22660786Sps if (rv != -2) 22760786Sps goto err; 22860786Sps } 22960786Sps switch(md_nid) 23060786Sps { 23160786Sps case NID_sha1: 23260786Sps BIO_puts(out, "sha1"); 23360786Sps break; 23460786Sps 23560786Sps case NID_md5: 23660786Sps BIO_puts(out, "md5"); 23760786Sps break; 23860786Sps 23960786Sps case NID_sha256: 24060786Sps BIO_puts(out, "sha-256"); 24160786Sps break; 24260786Sps 24360786Sps case NID_sha384: 24460786Sps BIO_puts(out, "sha-384"); 24560786Sps break; 24660786Sps 24760786Sps case NID_sha512: 24860786Sps BIO_puts(out, "sha-512"); 24960786Sps break; 25060786Sps 25160786Sps case NID_id_GostR3411_94: 25260786Sps BIO_puts(out, "gostr3411-94"); 25360786Sps goto err; 25460786Sps break; 25560786Sps 25660786Sps default: 25760786Sps if (have_unknown) 25860786Sps write_comma = 0; 25960786Sps else 26060786Sps { 26160786Sps BIO_puts(out, "unknown"); 26260786Sps have_unknown = 1; 26360786Sps } 26460786Sps break; 26560786Sps 26660786Sps } 26760786Sps } 26860786Sps 26960786Sps ret = 1; 27060786Sps err: 27160786Sps 27260786Sps return ret; 27360786Sps 27460786Sps } 27560786Sps 27660786Sps/* SMIME sender */ 27760786Sps 27860786Spsint SMIME_write_ASN1(BIO *bio, ASN1_VALUE *val, BIO *data, int flags, 27960786Sps int ctype_nid, int econt_nid, 28060786Sps STACK_OF(X509_ALGOR) *mdalgs, 28160786Sps const ASN1_ITEM *it) 28260786Sps{ 28360786Sps char bound[33], c; 28460786Sps int i; 28560786Sps const char *mime_prefix, *mime_eol, *cname = "smime.p7m"; 28660786Sps const char *msg_type=NULL; 28760786Sps if (flags & SMIME_OLDMIME) 28860786Sps mime_prefix = "application/x-pkcs7-"; 28960786Sps else 29060786Sps mime_prefix = "application/pkcs7-"; 29160786Sps 29260786Sps if (flags & SMIME_CRLFEOL) 29360786Sps mime_eol = "\r\n"; 29460786Sps else 29560786Sps mime_eol = "\n"; 29660786Sps if((flags & SMIME_DETACHED) && data) { 29760786Sps /* We want multipart/signed */ 29860786Sps /* Generate a random boundary */ 29960786Sps RAND_pseudo_bytes((unsigned char *)bound, 32); 30060786Sps for(i = 0; i < 32; i++) { 30160786Sps c = bound[i] & 0xf; 30260786Sps if(c < 10) c += '0'; 30360786Sps else c += 'A' - 10; 30460786Sps bound[i] = c; 30560786Sps } 30660786Sps bound[32] = 0; 30760786Sps BIO_printf(bio, "MIME-Version: 1.0%s", mime_eol); 30860786Sps BIO_printf(bio, "Content-Type: multipart/signed;"); 30960786Sps BIO_printf(bio, " protocol=\"%ssignature\";", mime_prefix); 31060786Sps BIO_puts(bio, " micalg=\""); 31160786Sps asn1_write_micalg(bio, mdalgs); 31260786Sps BIO_printf(bio, "\"; boundary=\"----%s\"%s%s", 31360786Sps bound, mime_eol, mime_eol); 31460786Sps BIO_printf(bio, "This is an S/MIME signed message%s%s", 31560786Sps mime_eol, mime_eol); 31660786Sps /* Now write out the first part */ 31760786Sps BIO_printf(bio, "------%s%s", bound, mime_eol); 31860786Sps if (!asn1_output_data(bio, data, val, flags, it)) 31960786Sps return 0; 32060786Sps BIO_printf(bio, "%s------%s%s", mime_eol, bound, mime_eol); 32160786Sps 32260786Sps /* Headers for signature */ 32360786Sps 32460786Sps BIO_printf(bio, "Content-Type: %ssignature;", mime_prefix); 32560786Sps BIO_printf(bio, " name=\"smime.p7s\"%s", mime_eol); 32660786Sps BIO_printf(bio, "Content-Transfer-Encoding: base64%s", 32760786Sps mime_eol); 32860786Sps BIO_printf(bio, "Content-Disposition: attachment;"); 32960786Sps BIO_printf(bio, " filename=\"smime.p7s\"%s%s", 33060786Sps mime_eol, mime_eol); 33160786Sps B64_write_ASN1(bio, val, NULL, 0, it); 33260786Sps BIO_printf(bio,"%s------%s--%s%s", mime_eol, bound, 33360786Sps mime_eol, mime_eol); 33460786Sps return 1; 33560786Sps } 33660786Sps 33760786Sps /* Determine smime-type header */ 33860786Sps 33960786Sps if (ctype_nid == NID_pkcs7_enveloped) 34060786Sps msg_type = "enveloped-data"; 34160786Sps else if (ctype_nid == NID_pkcs7_signed) 34260786Sps { 34360786Sps if (econt_nid == NID_id_smime_ct_receipt) 34460786Sps msg_type = "signed-receipt"; 34560786Sps else if (sk_X509_ALGOR_num(mdalgs) >= 0) 34660786Sps msg_type = "signed-data"; 34760786Sps else 34860786Sps msg_type = "certs-only"; 34960786Sps } 35060786Sps else if (ctype_nid == NID_id_smime_ct_compressedData) 35160786Sps { 35260786Sps msg_type = "compressed-data"; 35360786Sps cname = "smime.p7z"; 35460786Sps } 35560786Sps /* MIME headers */ 35660786Sps BIO_printf(bio, "MIME-Version: 1.0%s", mime_eol); 35760786Sps BIO_printf(bio, "Content-Disposition: attachment;"); 35860786Sps BIO_printf(bio, " filename=\"%s\"%s", cname, mime_eol); 35960786Sps BIO_printf(bio, "Content-Type: %smime;", mime_prefix); 36060786Sps if (msg_type) 36160786Sps BIO_printf(bio, " smime-type=%s;", msg_type); 36260786Sps BIO_printf(bio, " name=\"%s\"%s", cname, mime_eol); 36360786Sps BIO_printf(bio, "Content-Transfer-Encoding: base64%s%s", 36460786Sps mime_eol, mime_eol); 36560786Sps if (!B64_write_ASN1(bio, val, data, flags, it)) 36660786Sps return 0; 36760786Sps BIO_printf(bio, "%s", mime_eol); 36860786Sps return 1; 36960786Sps} 37060786Sps 37160786Sps/* Handle output of ASN1 data */ 37260786Sps 37360786Sps 37460786Spsstatic int asn1_output_data(BIO *out, BIO *data, ASN1_VALUE *val, int flags, 37560786Sps const ASN1_ITEM *it) 37660786Sps { 37760786Sps BIO *tmpbio; 37860786Sps const ASN1_AUX *aux = it->funcs; 37960786Sps ASN1_STREAM_ARG sarg; 38060786Sps int rv = 1; 38160786Sps 38260786Sps /* If data is not deteched or resigning then the output BIO is 38360786Sps * already set up to finalise when it is written through. 38460786Sps */ 38560786Sps if (!(flags & SMIME_DETACHED) || (flags & PKCS7_REUSE_DIGEST)) 38660786Sps { 38760786Sps SMIME_crlf_copy(data, out, flags); 38860786Sps return 1; 38960786Sps } 39060786Sps 39160786Sps if (!aux || !aux->asn1_cb) 39260786Sps { 39360786Sps ASN1err(ASN1_F_ASN1_OUTPUT_DATA, 39460786Sps ASN1_R_STREAMING_NOT_SUPPORTED); 39560786Sps return 0; 39660786Sps } 39760786Sps 39860786Sps sarg.out = out; 39960786Sps sarg.ndef_bio = NULL; 40060786Sps sarg.boundary = NULL; 40160786Sps 40260786Sps /* Let ASN1 code prepend any needed BIOs */ 40360786Sps 40460786Sps if (aux->asn1_cb(ASN1_OP_DETACHED_PRE, &val, it, &sarg) <= 0) 40560786Sps return 0; 40660786Sps 40760786Sps /* Copy data across, passing through filter BIOs for processing */ 40860786Sps SMIME_crlf_copy(data, sarg.ndef_bio, flags); 40960786Sps 41060786Sps /* Finalize structure */ 41160786Sps if (aux->asn1_cb(ASN1_OP_DETACHED_POST, &val, it, &sarg) <= 0) 41260786Sps rv = 0; 41360786Sps 41460786Sps /* Now remove any digests prepended to the BIO */ 41560786Sps 41660786Sps while (sarg.ndef_bio != out) 41760786Sps { 41860786Sps tmpbio = BIO_pop(sarg.ndef_bio); 41960786Sps BIO_free(sarg.ndef_bio); 42060786Sps sarg.ndef_bio = tmpbio; 42160786Sps } 42260786Sps 42360786Sps return rv; 42460786Sps 42560786Sps } 42660786Sps 42760786Sps/* SMIME reader: handle multipart/signed and opaque signing. 42860786Sps * in multipart case the content is placed in a memory BIO 42960786Sps * pointed to by "bcont". In opaque this is set to NULL 43060786Sps */ 43160786Sps 43260786SpsASN1_VALUE *SMIME_read_ASN1(BIO *bio, BIO **bcont, const ASN1_ITEM *it) 43360786Sps{ 43460786Sps BIO *asnin; 43560786Sps STACK_OF(MIME_HEADER) *headers = NULL; 43660786Sps STACK_OF(BIO) *parts = NULL; 43760786Sps MIME_HEADER *hdr; 43860786Sps MIME_PARAM *prm; 43960786Sps ASN1_VALUE *val; 44060786Sps int ret; 44160786Sps 44260786Sps if(bcont) *bcont = NULL; 44360786Sps 44460786Sps if (!(headers = mime_parse_hdr(bio))) { 44560786Sps ASN1err(ASN1_F_SMIME_READ_ASN1,ASN1_R_MIME_PARSE_ERROR); 44660786Sps return NULL; 44760786Sps } 44860786Sps 44960786Sps if(!(hdr = mime_hdr_find(headers, "content-type")) || !hdr->value) { 45060786Sps sk_MIME_HEADER_pop_free(headers, mime_hdr_free); 45160786Sps ASN1err(ASN1_F_SMIME_READ_ASN1, ASN1_R_NO_CONTENT_TYPE); 45260786Sps return NULL; 45360786Sps } 45460786Sps 45560786Sps /* Handle multipart/signed */ 45660786Sps 45760786Sps if(!strcmp(hdr->value, "multipart/signed")) { 45860786Sps /* Split into two parts */ 45960786Sps prm = mime_param_find(hdr, "boundary"); 46060786Sps if(!prm || !prm->param_value) { 46160786Sps sk_MIME_HEADER_pop_free(headers, mime_hdr_free); 46260786Sps ASN1err(ASN1_F_SMIME_READ_ASN1, ASN1_R_NO_MULTIPART_BOUNDARY); 46360786Sps return NULL; 46460786Sps } 46560786Sps ret = multi_split(bio, prm->param_value, &parts); 46660786Sps sk_MIME_HEADER_pop_free(headers, mime_hdr_free); 46760786Sps if(!ret || (sk_BIO_num(parts) != 2) ) { 46860786Sps ASN1err(ASN1_F_SMIME_READ_ASN1, ASN1_R_NO_MULTIPART_BODY_FAILURE); 46960786Sps sk_BIO_pop_free(parts, BIO_vfree); 47060786Sps return NULL; 47160786Sps } 47260786Sps 47360786Sps /* Parse the signature piece */ 47460786Sps asnin = sk_BIO_value(parts, 1); 47560786Sps 47660786Sps if (!(headers = mime_parse_hdr(asnin))) { 47760786Sps ASN1err(ASN1_F_SMIME_READ_ASN1,ASN1_R_MIME_SIG_PARSE_ERROR); 47860786Sps sk_BIO_pop_free(parts, BIO_vfree); 47960786Sps return NULL; 48060786Sps } 48160786Sps 48260786Sps /* Get content type */ 48360786Sps 48460786Sps if(!(hdr = mime_hdr_find(headers, "content-type")) || 48560786Sps !hdr->value) { 48660786Sps sk_MIME_HEADER_pop_free(headers, mime_hdr_free); 48760786Sps ASN1err(ASN1_F_SMIME_READ_ASN1, ASN1_R_NO_SIG_CONTENT_TYPE); 48860786Sps return NULL; 48960786Sps } 49060786Sps 49160786Sps if(strcmp(hdr->value, "application/x-pkcs7-signature") && 49260786Sps strcmp(hdr->value, "application/pkcs7-signature")) { 49360786Sps ASN1err(ASN1_F_SMIME_READ_ASN1,ASN1_R_SIG_INVALID_MIME_TYPE); 49460786Sps ERR_add_error_data(2, "type: ", hdr->value); 49560786Sps sk_MIME_HEADER_pop_free(headers, mime_hdr_free); 49660786Sps sk_BIO_pop_free(parts, BIO_vfree); 49760786Sps return NULL; 49860786Sps } 49960786Sps sk_MIME_HEADER_pop_free(headers, mime_hdr_free); 50060786Sps /* Read in ASN1 */ 50160786Sps if(!(val = b64_read_asn1(asnin, it))) { 50260786Sps ASN1err(ASN1_F_SMIME_READ_ASN1,ASN1_R_ASN1_SIG_PARSE_ERROR); 50360786Sps sk_BIO_pop_free(parts, BIO_vfree); 50460786Sps return NULL; 50560786Sps } 50660786Sps 50760786Sps if(bcont) { 50860786Sps *bcont = sk_BIO_value(parts, 0); 50960786Sps BIO_free(asnin); 51060786Sps sk_BIO_free(parts); 51160786Sps } else sk_BIO_pop_free(parts, BIO_vfree); 51260786Sps return val; 51360786Sps } 51460786Sps 51560786Sps /* OK, if not multipart/signed try opaque signature */ 51660786Sps 51760786Sps if (strcmp (hdr->value, "application/x-pkcs7-mime") && 51860786Sps strcmp (hdr->value, "application/pkcs7-mime")) { 51960786Sps ASN1err(ASN1_F_SMIME_READ_ASN1,ASN1_R_INVALID_MIME_TYPE); 52060786Sps ERR_add_error_data(2, "type: ", hdr->value); 52160786Sps sk_MIME_HEADER_pop_free(headers, mime_hdr_free); 52260786Sps return NULL; 52360786Sps } 52460786Sps 52560786Sps sk_MIME_HEADER_pop_free(headers, mime_hdr_free); 52660786Sps 52760786Sps if(!(val = b64_read_asn1(bio, it))) { 52860786Sps ASN1err(ASN1_F_SMIME_READ_ASN1, ASN1_R_ASN1_PARSE_ERROR); 52960786Sps return NULL; 53060786Sps } 53160786Sps return val; 53260786Sps 53360786Sps} 53460786Sps 53560786Sps/* Copy text from one BIO to another making the output CRLF at EOL */ 53660786Spsint SMIME_crlf_copy(BIO *in, BIO *out, int flags) 53760786Sps{ 53860786Sps BIO *bf; 53960786Sps char eol; 54060786Sps int len; 54160786Sps char linebuf[MAX_SMLEN]; 54260786Sps /* Buffer output so we don't write one line at a time. This is 54360786Sps * useful when streaming as we don't end up with one OCTET STRING 54460786Sps * per line. 54560786Sps */ 54660786Sps bf = BIO_new(BIO_f_buffer()); 54760786Sps if (!bf) 54860786Sps return 0; 54960786Sps out = BIO_push(bf, out); 55060786Sps if(flags & SMIME_BINARY) 55160786Sps { 55260786Sps while((len = BIO_read(in, linebuf, MAX_SMLEN)) > 0) 55360786Sps BIO_write(out, linebuf, len); 55460786Sps } 55560786Sps else 55660786Sps { 55760786Sps if(flags & SMIME_TEXT) 55860786Sps BIO_printf(out, "Content-Type: text/plain\r\n\r\n"); 55960786Sps while ((len = BIO_gets(in, linebuf, MAX_SMLEN)) > 0) 56060786Sps { 56160786Sps eol = strip_eol(linebuf, &len); 56260786Sps if (len) 56360786Sps BIO_write(out, linebuf, len); 56460786Sps if(eol) BIO_write(out, "\r\n", 2); 56560786Sps } 56660786Sps } 56760786Sps (void)BIO_flush(out); 56860786Sps BIO_pop(out); 56960786Sps BIO_free(bf); 57060786Sps return 1; 57160786Sps} 57260786Sps 57360786Sps/* Strip off headers if they are text/plain */ 57460786Spsint SMIME_text(BIO *in, BIO *out) 57560786Sps{ 57660786Sps char iobuf[4096]; 57760786Sps int len; 57860786Sps STACK_OF(MIME_HEADER) *headers; 57960786Sps MIME_HEADER *hdr; 58060786Sps 58160786Sps if (!(headers = mime_parse_hdr(in))) { 58260786Sps ASN1err(ASN1_F_SMIME_TEXT,ASN1_R_MIME_PARSE_ERROR); 58360786Sps return 0; 58460786Sps } 58560786Sps if(!(hdr = mime_hdr_find(headers, "content-type")) || !hdr->value) { 58660786Sps ASN1err(ASN1_F_SMIME_TEXT,ASN1_R_MIME_NO_CONTENT_TYPE); 58760786Sps sk_MIME_HEADER_pop_free(headers, mime_hdr_free); 58860786Sps return 0; 58960786Sps } 59060786Sps if (strcmp (hdr->value, "text/plain")) { 59160786Sps ASN1err(ASN1_F_SMIME_TEXT,ASN1_R_INVALID_MIME_TYPE); 59260786Sps ERR_add_error_data(2, "type: ", hdr->value); 59360786Sps sk_MIME_HEADER_pop_free(headers, mime_hdr_free); 59460786Sps return 0; 59560786Sps } 59660786Sps sk_MIME_HEADER_pop_free(headers, mime_hdr_free); 59760786Sps while ((len = BIO_read(in, iobuf, sizeof(iobuf))) > 0) 59860786Sps BIO_write(out, iobuf, len); 59960786Sps if (len < 0) 60060786Sps return 0; 60160786Sps return 1; 60260786Sps} 60360786Sps 60460786Sps/* Split a multipart/XXX message body into component parts: result is 60560786Sps * canonical parts in a STACK of bios 60660786Sps */ 60760786Sps 60860786Spsstatic int multi_split(BIO *bio, char *bound, STACK_OF(BIO) **ret) 60960786Sps{ 61060786Sps char linebuf[MAX_SMLEN]; 61160786Sps int len, blen; 61260786Sps int eol = 0, next_eol = 0; 61360786Sps BIO *bpart = NULL; 61460786Sps STACK_OF(BIO) *parts; 61560786Sps char state, part, first; 61660786Sps 61760786Sps blen = strlen(bound); 61860786Sps part = 0; 61960786Sps state = 0; 62060786Sps first = 1; 62160786Sps parts = sk_BIO_new_null(); 62260786Sps *ret = parts; 62360786Sps while ((len = BIO_gets(bio, linebuf, MAX_SMLEN)) > 0) { 62460786Sps state = mime_bound_check(linebuf, len, bound, blen); 62560786Sps if(state == 1) { 62660786Sps first = 1; 62760786Sps part++; 62860786Sps } else if(state == 2) { 62960786Sps sk_BIO_push(parts, bpart); 63060786Sps return 1; 63160786Sps } else if(part) { 63260786Sps /* Strip CR+LF from linebuf */ 63360786Sps next_eol = strip_eol(linebuf, &len); 63460786Sps if(first) { 63560786Sps first = 0; 63660786Sps if(bpart) sk_BIO_push(parts, bpart); 63760786Sps bpart = BIO_new(BIO_s_mem()); 63860786Sps BIO_set_mem_eof_return(bpart, 0); 63960786Sps } else if (eol) 64060786Sps BIO_write(bpart, "\r\n", 2); 64160786Sps eol = next_eol; 64260786Sps if (len) 64360786Sps BIO_write(bpart, linebuf, len); 64460786Sps } 64560786Sps } 64660786Sps return 0; 64760786Sps} 64860786Sps 64960786Sps/* This is the big one: parse MIME header lines up to message body */ 65060786Sps 65160786Sps#define MIME_INVALID 0 65260786Sps#define MIME_START 1 65360786Sps#define MIME_TYPE 2 65460786Sps#define MIME_NAME 3 65560786Sps#define MIME_VALUE 4 65660786Sps#define MIME_QUOTE 5 65760786Sps#define MIME_COMMENT 6 65860786Sps 65960786Sps 66060786Spsstatic STACK_OF(MIME_HEADER) *mime_parse_hdr(BIO *bio) 66160786Sps{ 66260786Sps char *p, *q, c; 66360786Sps char *ntmp; 66460786Sps char linebuf[MAX_SMLEN]; 66560786Sps MIME_HEADER *mhdr = NULL; 66660786Sps STACK_OF(MIME_HEADER) *headers; 66760786Sps int len, state, save_state = 0; 66860786Sps 66960786Sps headers = sk_MIME_HEADER_new(mime_hdr_cmp); 67060786Sps if (!headers) 67160786Sps return NULL; 67260786Sps while ((len = BIO_gets(bio, linebuf, MAX_SMLEN)) > 0) { 67360786Sps /* If whitespace at line start then continuation line */ 67460786Sps if(mhdr && isspace((unsigned char)linebuf[0])) state = MIME_NAME; 67560786Sps else state = MIME_START; 67660786Sps ntmp = NULL; 67760786Sps /* Go through all characters */ 67860786Sps for(p = linebuf, q = linebuf; (c = *p) && (c!='\r') && (c!='\n'); p++) { 67960786Sps 68060786Sps /* State machine to handle MIME headers 68160786Sps * if this looks horrible that's because it *is* 68260786Sps */ 68360786Sps 68460786Sps switch(state) { 68560786Sps case MIME_START: 68660786Sps if(c == ':') { 68760786Sps state = MIME_TYPE; 68860786Sps *p = 0; 68960786Sps ntmp = strip_ends(q); 69060786Sps q = p + 1; 69160786Sps } 69260786Sps break; 69360786Sps 69460786Sps case MIME_TYPE: 69560786Sps if(c == ';') { 69660786Sps mime_debug("Found End Value\n"); 69760786Sps *p = 0; 69860786Sps mhdr = mime_hdr_new(ntmp, strip_ends(q)); 69960786Sps sk_MIME_HEADER_push(headers, mhdr); 70060786Sps ntmp = NULL; 70160786Sps q = p + 1; 70260786Sps state = MIME_NAME; 70360786Sps } else if(c == '(') { 70460786Sps save_state = state; 70560786Sps state = MIME_COMMENT; 70660786Sps } 70760786Sps break; 70860786Sps 70960786Sps case MIME_COMMENT: 71060786Sps if(c == ')') { 71160786Sps state = save_state; 71260786Sps } 71360786Sps break; 71460786Sps 71560786Sps case MIME_NAME: 71660786Sps if(c == '=') { 71760786Sps state = MIME_VALUE; 71860786Sps *p = 0; 71960786Sps ntmp = strip_ends(q); 72060786Sps q = p + 1; 72160786Sps } 72260786Sps break ; 72360786Sps 72460786Sps case MIME_VALUE: 72560786Sps if(c == ';') { 72660786Sps state = MIME_NAME; 72760786Sps *p = 0; 72860786Sps mime_hdr_addparam(mhdr, ntmp, strip_ends(q)); 72960786Sps ntmp = NULL; 73060786Sps q = p + 1; 73160786Sps } else if (c == '"') { 73260786Sps mime_debug("Found Quote\n"); 73360786Sps state = MIME_QUOTE; 73460786Sps } else if(c == '(') { 73560786Sps save_state = state; 73660786Sps state = MIME_COMMENT; 73760786Sps } 73860786Sps break; 73960786Sps 74060786Sps case MIME_QUOTE: 74160786Sps if(c == '"') { 74260786Sps mime_debug("Found Match Quote\n"); 74360786Sps state = MIME_VALUE; 74460786Sps } 74560786Sps break; 74660786Sps } 74760786Sps } 74860786Sps 74960786Sps if(state == MIME_TYPE) { 75060786Sps mhdr = mime_hdr_new(ntmp, strip_ends(q)); 75160786Sps sk_MIME_HEADER_push(headers, mhdr); 75260786Sps } else if(state == MIME_VALUE) 75360786Sps mime_hdr_addparam(mhdr, ntmp, strip_ends(q)); 75460786Sps if(p == linebuf) break; /* Blank line means end of headers */ 75560786Sps} 75660786Sps 75760786Spsreturn headers; 75860786Sps 75960786Sps} 76060786Sps 76160786Spsstatic char *strip_ends(char *name) 76260786Sps{ 76360786Sps return strip_end(strip_start(name)); 76460786Sps} 76560786Sps 76660786Sps/* Strip a parameter of whitespace from start of param */ 76760786Spsstatic char *strip_start(char *name) 76860786Sps{ 76960786Sps char *p, c; 77060786Sps /* Look for first non white space or quote */ 77160786Sps for(p = name; (c = *p) ;p++) { 77260786Sps if(c == '"') { 77360786Sps /* Next char is start of string if non null */ 77460786Sps if(p[1]) return p + 1; 77560786Sps /* Else null string */ 77660786Sps return NULL; 77760786Sps } 77860786Sps if(!isspace((unsigned char)c)) return p; 77960786Sps } 78060786Sps return NULL; 78160786Sps} 78260786Sps 78360786Sps/* As above but strip from end of string : maybe should handle brackets? */ 78460786Spsstatic char *strip_end(char *name) 78560786Sps{ 78660786Sps char *p, c; 78760786Sps if(!name) return NULL; 78860786Sps /* Look for first non white space or quote */ 78960786Sps for(p = name + strlen(name) - 1; p >= name ;p--) { 79060786Sps c = *p; 79160786Sps if(c == '"') { 79260786Sps if(p - 1 == name) return NULL; 79360786Sps *p = 0; 79460786Sps return name; 79560786Sps } 79660786Sps if(isspace((unsigned char)c)) *p = 0; 79760786Sps else return name; 79860786Sps } 79960786Sps return NULL; 80060786Sps} 80160786Sps 80260786Spsstatic MIME_HEADER *mime_hdr_new(char *name, char *value) 80360786Sps{ 80460786Sps MIME_HEADER *mhdr; 80560786Sps char *tmpname, *tmpval, *p; 80660786Sps int c; 80760786Sps if(name) { 80860786Sps if(!(tmpname = BUF_strdup(name))) return NULL; 80960786Sps for(p = tmpname ; *p; p++) { 81060786Sps c = (unsigned char)*p; 81160786Sps if(isupper(c)) { 81260786Sps c = tolower(c); 81360786Sps *p = c; 81460786Sps } 81560786Sps } 81660786Sps } else tmpname = NULL; 81760786Sps if(value) { 81860786Sps if(!(tmpval = BUF_strdup(value))) return NULL; 81960786Sps for(p = tmpval ; *p; p++) { 82060786Sps c = (unsigned char)*p; 82160786Sps if(isupper(c)) { 82260786Sps c = tolower(c); 82360786Sps *p = c; 82460786Sps } 82560786Sps } 82660786Sps } else tmpval = NULL; 82760786Sps mhdr = (MIME_HEADER *) OPENSSL_malloc(sizeof(MIME_HEADER)); 82860786Sps if(!mhdr) return NULL; 82960786Sps mhdr->name = tmpname; 83060786Sps mhdr->value = tmpval; 83160786Sps if(!(mhdr->params = sk_MIME_PARAM_new(mime_param_cmp))) return NULL; 83260786Sps return mhdr; 83360786Sps} 83460786Sps 83560786Spsstatic int mime_hdr_addparam(MIME_HEADER *mhdr, char *name, char *value) 83660786Sps{ 83760786Sps char *tmpname, *tmpval, *p; 83860786Sps int c; 83960786Sps MIME_PARAM *mparam; 84060786Sps if(name) { 84160786Sps tmpname = BUF_strdup(name); 84260786Sps if(!tmpname) return 0; 84360786Sps for(p = tmpname ; *p; p++) { 84460786Sps c = (unsigned char)*p; 84560786Sps if(isupper(c)) { 84660786Sps c = tolower(c); 84760786Sps *p = c; 84860786Sps } 84960786Sps } 85060786Sps } else tmpname = NULL; 85160786Sps if(value) { 85260786Sps tmpval = BUF_strdup(value); 85360786Sps if(!tmpval) return 0; 85460786Sps } else tmpval = NULL; 85560786Sps /* Parameter values are case sensitive so leave as is */ 85660786Sps mparam = (MIME_PARAM *) OPENSSL_malloc(sizeof(MIME_PARAM)); 85760786Sps if(!mparam) return 0; 85860786Sps mparam->param_name = tmpname; 85960786Sps mparam->param_value = tmpval; 86060786Sps sk_MIME_PARAM_push(mhdr->params, mparam); 86160786Sps return 1; 86260786Sps} 86360786Sps 86460786Spsstatic int mime_hdr_cmp(const MIME_HEADER * const *a, 86560786Sps const MIME_HEADER * const *b) 86660786Sps{ 86760786Sps if (!(*a)->name || !(*b)->name) 86860786Sps return !!(*a)->name - !!(*b)->name; 86960786Sps 87060786Sps return(strcmp((*a)->name, (*b)->name)); 87160786Sps} 87260786Sps 87360786Spsstatic int mime_param_cmp(const MIME_PARAM * const *a, 87460786Sps const MIME_PARAM * const *b) 87560786Sps{ 87660786Sps if (!(*a)->param_name || !(*b)->param_name) 87760786Sps return !!(*a)->param_name - !!(*b)->param_name; 87860786Sps return(strcmp((*a)->param_name, (*b)->param_name)); 87960786Sps} 88060786Sps 88160786Sps/* Find a header with a given name (if possible) */ 88260786Sps 88360786Spsstatic MIME_HEADER *mime_hdr_find(STACK_OF(MIME_HEADER) *hdrs, char *name) 88460786Sps{ 88560786Sps MIME_HEADER htmp; 88660786Sps int idx; 88760786Sps htmp.name = name; 88860786Sps idx = sk_MIME_HEADER_find(hdrs, &htmp); 88960786Sps if(idx < 0) return NULL; 89060786Sps return sk_MIME_HEADER_value(hdrs, idx); 89160786Sps} 89260786Sps 89360786Spsstatic MIME_PARAM *mime_param_find(MIME_HEADER *hdr, char *name) 89460786Sps{ 89560786Sps MIME_PARAM param; 89660786Sps int idx; 89760786Sps param.param_name = name; 89860786Sps idx = sk_MIME_PARAM_find(hdr->params, ¶m); 89960786Sps if(idx < 0) return NULL; 90060786Sps return sk_MIME_PARAM_value(hdr->params, idx); 90160786Sps} 90260786Sps 90360786Spsstatic void mime_hdr_free(MIME_HEADER *hdr) 90460786Sps{ 90560786Sps if(hdr->name) OPENSSL_free(hdr->name); 90660786Sps if(hdr->value) OPENSSL_free(hdr->value); 90760786Sps if(hdr->params) sk_MIME_PARAM_pop_free(hdr->params, mime_param_free); 90860786Sps OPENSSL_free(hdr); 90960786Sps} 91060786Sps 91160786Spsstatic void mime_param_free(MIME_PARAM *param) 91260786Sps{ 91360786Sps if(param->param_name) OPENSSL_free(param->param_name); 91460786Sps if(param->param_value) OPENSSL_free(param->param_value); 91560786Sps OPENSSL_free(param); 91660786Sps} 91760786Sps 91860786Sps/* Check for a multipart boundary. Returns: 91960786Sps * 0 : no boundary 92060786Sps * 1 : part boundary 92160786Sps * 2 : final boundary 92260786Sps */ 92360786Spsstatic int mime_bound_check(char *line, int linelen, char *bound, int blen) 92460786Sps{ 92560786Sps if(linelen == -1) linelen = strlen(line); 92660786Sps if(blen == -1) blen = strlen(bound); 92760786Sps /* Quickly eliminate if line length too short */ 92860786Sps if(blen + 2 > linelen) return 0; 92960786Sps /* Check for part boundary */ 93060786Sps if(!strncmp(line, "--", 2) && !strncmp(line + 2, bound, blen)) { 93160786Sps if(!strncmp(line + blen + 2, "--", 2)) return 2; 93260786Sps else return 1; 93360786Sps } 93460786Sps return 0; 93560786Sps} 93660786Sps 93760786Spsstatic int strip_eol(char *linebuf, int *plen) 93860786Sps { 93960786Sps int len = *plen; 94060786Sps char *p, c; 94160786Sps int is_eol = 0; 94260786Sps p = linebuf + len - 1; 94360786Sps for (p = linebuf + len - 1; len > 0; len--, p--) 94460786Sps { 94560786Sps c = *p; 94660786Sps if (c == '\n') 94760786Sps is_eol = 1; 94860786Sps else if (c != '\r') 94960786Sps break; 95060786Sps } 95160786Sps *plen = len; 95260786Sps return is_eol; 95360786Sps } 95460786Sps