openssl/crypto/pkcs7/pk7_mime.c
Richard Levitte 16a44ae7e9 Increase consistency of header data (some mail readers really do not
like spaces before the semicolon, and besides, other parts of this
file makes the values without those spaces), and move spacing of
continuation lines to support BIO's that break lines after each
write.
2001-01-30 13:38:59 +00:00

685 lines
18 KiB
C

/* pk7_mime.c */
/* Written by Dr Stephen N Henson (shenson@bigfoot.com) for the OpenSSL
* project 1999.
*/
/* ====================================================================
* Copyright (c) 1999 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
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. All advertising materials mentioning features or use of this
* software must display the following acknowledgment:
* "This product includes software developed by the OpenSSL Project
* for use in the OpenSSL Toolkit. (http://www.OpenSSL.org/)"
*
* 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to
* endorse or promote products derived from this software without
* prior written permission. For written permission, please contact
* licensing@OpenSSL.org.
*
* 5. Products derived from this software may not be called "OpenSSL"
* nor may "OpenSSL" appear in their names without prior written
* permission of the OpenSSL Project.
*
* 6. Redistributions of any form whatsoever must retain the following
* acknowledgment:
* "This product includes software developed by the OpenSSL Project
* for use in the OpenSSL Toolkit (http://www.OpenSSL.org/)"
*
* THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY
* EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OpenSSL PROJECT OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
* ====================================================================
*
* This product includes cryptographic software written by Eric Young
* (eay@cryptsoft.com). This product includes software written by Tim
* Hudson (tjh@cryptsoft.com).
*
*/
#include <stdio.h>
#include <ctype.h>
#include "cryptlib.h"
#include <openssl/rand.h>
#include <openssl/x509.h>
/* MIME and related routines */
/* MIME format structures
* Note that all are translated to lower case apart from
* parameter values. Quotes are stripped off
*/
typedef struct {
char *param_name; /* Param name e.g. "micalg" */
char *param_value; /* Param value e.g. "sha1" */
} MIME_PARAM;
DECLARE_STACK_OF(MIME_PARAM)
IMPLEMENT_STACK_OF(MIME_PARAM)
typedef struct {
char *name; /* Name of line e.g. "content-type" */
char *value; /* Value of line e.g. "text/plain" */
STACK_OF(MIME_PARAM) *params; /* Zero or more parameters */
} MIME_HEADER;
DECLARE_STACK_OF(MIME_HEADER)
IMPLEMENT_STACK_OF(MIME_HEADER)
static int B64_write_PKCS7(BIO *bio, PKCS7 *p7);
static PKCS7 *B64_read_PKCS7(BIO *bio);
static char * strip_ends(char *name);
static char * strip_start(char *name);
static char * strip_end(char *name);
static MIME_HEADER *mime_hdr_new(char *name, char *value);
static int mime_hdr_addparam(MIME_HEADER *mhdr, char *name, char *value);
static STACK_OF(MIME_HEADER) *mime_parse_hdr(BIO *bio);
static int mime_hdr_cmp(const MIME_HEADER * const *a,
const MIME_HEADER * const *b);
static int mime_param_cmp(const MIME_PARAM * const *a,
const MIME_PARAM * const *b);
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 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);
#define MAX_SMLEN 1024
#define mime_debug(x) /* x */
typedef void (*stkfree)();
/* Base 64 read and write of PKCS#7 structure */
static int B64_write_PKCS7(BIO *bio, PKCS7 *p7)
{
BIO *b64;
if(!(b64 = BIO_new(BIO_f_base64()))) {
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);
BIO_free(b64);
return 1;
}
static PKCS7 *B64_read_PKCS7(BIO *bio)
{
BIO *b64;
PKCS7 *p7;
if(!(b64 = BIO_new(BIO_f_base64()))) {
PKCS7err(PKCS7_F_B64_READ_PKCS7,ERR_R_MALLOC_FAILURE);
return 0;
}
bio = BIO_push(b64, bio);
if(!(p7 = d2i_PKCS7_bio(bio, NULL)))
PKCS7err(PKCS7_F_B64_READ_PKCS7,PKCS7_R_DECODE_ERROR);
BIO_flush(bio);
bio = BIO_pop(bio);
BIO_free(b64);
return p7;
}
/* SMIME sender */
int SMIME_write_PKCS7(BIO *bio, PKCS7 *p7, BIO *data, int flags)
{
char linebuf[MAX_SMLEN];
char bound[33], c;
int i;
if((flags & PKCS7_DETACHED) && data) {
/* We want multipart/signed */
/* Generate a random boundary */
RAND_pseudo_bytes((unsigned char *)bound, 32);
for(i = 0; i < 32; i++) {
c = bound[i] & 0xf;
if(c < 10) c += '0';
else c += 'A' - 10;
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");
/* 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);
/* 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);
return 1;
}
/* 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");
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
*/
PKCS7 *SMIME_read_PKCS7(BIO *bio, BIO **bcont)
{
BIO *p7in;
STACK_OF(MIME_HEADER) *headers = NULL;
STACK_OF(BIO) *parts = NULL;
MIME_HEADER *hdr;
MIME_PARAM *prm;
PKCS7 *p7;
int ret;
if(bcont) *bcont = NULL;
if (!(headers = mime_parse_hdr(bio))) {
PKCS7err(PKCS7_F_SMIME_READ_PKCS7,PKCS7_R_MIME_PARSE_ERROR);
return NULL;
}
if(!(hdr = mime_hdr_find(headers, "content-type")) || !hdr->value) {
sk_MIME_HEADER_pop_free(headers, mime_hdr_free);
PKCS7err(PKCS7_F_SMIME_READ_PKCS7, PKCS7_R_NO_CONTENT_TYPE);
return NULL;
}
/* Handle multipart/signed */
if(!strcmp(hdr->value, "multipart/signed")) {
/* Split into two parts */
prm = mime_param_find(hdr, "boundary");
if(!prm || !prm->param_value) {
sk_MIME_HEADER_pop_free(headers, mime_hdr_free);
PKCS7err(PKCS7_F_SMIME_READ_PKCS7, PKCS7_R_NO_MULTIPART_BOUNDARY);
return NULL;
}
ret = multi_split(bio, prm->param_value, &parts);
sk_MIME_HEADER_pop_free(headers, mime_hdr_free);
if(!ret || (sk_BIO_num(parts) != 2) ) {
PKCS7err(PKCS7_F_SMIME_READ_PKCS7, PKCS7_R_NO_MULTIPART_BODY_FAILURE);
sk_BIO_pop_free(parts, BIO_vfree);
return NULL;
}
/* Parse the signature piece */
p7in = sk_BIO_value(parts, 1);
if (!(headers = mime_parse_hdr(p7in))) {
PKCS7err(PKCS7_F_SMIME_READ_PKCS7,PKCS7_R_MIME_SIG_PARSE_ERROR);
sk_BIO_pop_free(parts, BIO_vfree);
return NULL;
}
/* Get content type */
if(!(hdr = mime_hdr_find(headers, "content-type")) ||
!hdr->value) {
sk_MIME_HEADER_pop_free(headers, mime_hdr_free);
PKCS7err(PKCS7_F_SMIME_READ_PKCS7, PKCS7_R_NO_SIG_CONTENT_TYPE);
return NULL;
}
if(strcmp(hdr->value, "application/x-pkcs7-signature") &&
strcmp(hdr->value, "application/pkcs7-signature")) {
sk_MIME_HEADER_pop_free(headers, mime_hdr_free);
PKCS7err(PKCS7_F_SMIME_READ_PKCS7,PKCS7_R_SIG_INVALID_MIME_TYPE);
ERR_add_error_data(2, "type: ", hdr->value);
sk_BIO_pop_free(parts, BIO_vfree);
return NULL;
}
sk_MIME_HEADER_pop_free(headers, mime_hdr_free);
/* Read in PKCS#7 */
if(!(p7 = B64_read_PKCS7(p7in))) {
PKCS7err(PKCS7_F_SMIME_READ_PKCS7,PKCS7_R_PKCS7_SIG_PARSE_ERROR);
sk_BIO_pop_free(parts, BIO_vfree);
return NULL;
}
if(bcont) {
*bcont = sk_BIO_value(parts, 0);
BIO_free(p7in);
sk_BIO_free(parts);
} else sk_BIO_pop_free(parts, BIO_vfree);
return p7;
}
/* OK, if not multipart/signed try opaque signature */
if (strcmp (hdr->value, "application/x-pkcs7-mime") &&
strcmp (hdr->value, "application/pkcs7-mime")) {
PKCS7err(PKCS7_F_SMIME_READ_PKCS7,PKCS7_R_INVALID_MIME_TYPE);
ERR_add_error_data(2, "type: ", hdr->value);
sk_MIME_HEADER_pop_free(headers, mime_hdr_free);
return NULL;
}
sk_MIME_HEADER_pop_free(headers, mime_hdr_free);
if(!(p7 = B64_read_PKCS7(bio))) {
PKCS7err(PKCS7_F_SMIME_READ_PKCS7, PKCS7_R_PKCS7_PARSE_ERROR);
return NULL;
}
return p7;
}
/* Copy text from one BIO to another making the output CRLF at EOL */
int SMIME_crlf_copy(BIO *in, BIO *out, int flags)
{
char eol;
int len;
char linebuf[MAX_SMLEN];
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);
}
return 1;
}
/* Strip off headers if they are text/plain */
int SMIME_text(BIO *in, BIO *out)
{
char iobuf[4096];
int len;
STACK_OF(MIME_HEADER) *headers;
MIME_HEADER *hdr;
if (!(headers = mime_parse_hdr(in))) {
PKCS7err(PKCS7_F_SMIME_TEXT,PKCS7_R_MIME_PARSE_ERROR);
return 0;
}
if(!(hdr = mime_hdr_find(headers, "content-type")) || !hdr->value) {
PKCS7err(PKCS7_F_SMIME_TEXT,PKCS7_R_MIME_NO_CONTENT_TYPE);
sk_MIME_HEADER_pop_free(headers, mime_hdr_free);
return 0;
}
if (strcmp (hdr->value, "text/plain")) {
PKCS7err(PKCS7_F_SMIME_TEXT,PKCS7_R_INVALID_MIME_TYPE);
ERR_add_error_data(2, "type: ", hdr->value);
sk_MIME_HEADER_pop_free(headers, mime_hdr_free);
return 0;
}
sk_MIME_HEADER_pop_free(headers, mime_hdr_free);
while ((len = BIO_read(in, iobuf, sizeof(iobuf))) > 0)
BIO_write(out, iobuf, len);
return 1;
}
/* Split a multipart/XXX message body into component parts: result is
* canonical parts in a STACK of bios
*/
static int multi_split(BIO *bio, char *bound, STACK_OF(BIO) **ret)
{
char linebuf[MAX_SMLEN];
int len, blen;
BIO *bpart = NULL;
STACK_OF(BIO) *parts;
char state, part, first;
blen = strlen(bound);
part = 0;
state = 0;
first = 1;
parts = sk_BIO_new_null();
*ret = parts;
while ((len = BIO_gets(bio, linebuf, MAX_SMLEN)) > 0) {
state = mime_bound_check(linebuf, len, bound, blen);
if(state == 1) {
first = 1;
part++;
} else if(state == 2) {
sk_BIO_push(parts, bpart);
return 1;
} else if(part) {
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);
}
}
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
#define MIME_START 1
#define MIME_TYPE 2
#define MIME_NAME 3
#define MIME_VALUE 4
#define MIME_QUOTE 5
#define MIME_COMMENT 6
static STACK_OF(MIME_HEADER) *mime_parse_hdr(BIO *bio)
{
char *p, *q, c;
char *ntmp;
char linebuf[MAX_SMLEN];
MIME_HEADER *mhdr = NULL;
STACK_OF(MIME_HEADER) *headers;
int len, state, save_state = 0;
headers = sk_MIME_HEADER_new(mime_hdr_cmp);
while ((len = BIO_gets(bio, linebuf, MAX_SMLEN)) > 0) {
/* If whitespace at line start then continuation line */
if(mhdr && isspace((unsigned char)linebuf[0])) state = MIME_NAME;
else state = MIME_START;
ntmp = NULL;
/* Go through all characters */
for(p = linebuf, q = linebuf; (c = *p) && (c!='\r') && (c!='\n'); p++) {
/* State machine to handle MIME headers
* if this looks horrible that's because it *is*
*/
switch(state) {
case MIME_START:
if(c == ':') {
state = MIME_TYPE;
*p = 0;
ntmp = strip_ends(q);
q = p + 1;
}
break;
case MIME_TYPE:
if(c == ';') {
mime_debug("Found End Value\n");
*p = 0;
mhdr = mime_hdr_new(ntmp, strip_ends(q));
sk_MIME_HEADER_push(headers, mhdr);
ntmp = NULL;
q = p + 1;
state = MIME_NAME;
} else if(c == '(') {
save_state = state;
state = MIME_COMMENT;
}
break;
case MIME_COMMENT:
if(c == ')') {
state = save_state;
}
break;
case MIME_NAME:
if(c == '=') {
state = MIME_VALUE;
*p = 0;
ntmp = strip_ends(q);
q = p + 1;
}
break ;
case MIME_VALUE:
if(c == ';') {
state = MIME_NAME;
*p = 0;
mime_hdr_addparam(mhdr, ntmp, strip_ends(q));
ntmp = NULL;
q = p + 1;
} else if (c == '"') {
mime_debug("Found Quote\n");
state = MIME_QUOTE;
} else if(c == '(') {
save_state = state;
state = MIME_COMMENT;
}
break;
case MIME_QUOTE:
if(c == '"') {
mime_debug("Found Match Quote\n");
state = MIME_VALUE;
}
break;
}
}
if(state == MIME_TYPE) {
mhdr = mime_hdr_new(ntmp, strip_ends(q));
sk_MIME_HEADER_push(headers, mhdr);
} else if(state == MIME_VALUE)
mime_hdr_addparam(mhdr, ntmp, strip_ends(q));
if(p == linebuf) break; /* Blank line means end of headers */
}
return headers;
}
static char *strip_ends(char *name)
{
return strip_end(strip_start(name));
}
/* Strip a parameter of whitespace from start of param */
static char *strip_start(char *name)
{
char *p, c;
/* Look for first non white space or quote */
for(p = name; (c = *p) ;p++) {
if(c == '"') {
/* Next char is start of string if non null */
if(p[1]) return p + 1;
/* Else null string */
return NULL;
}
if(!isspace((unsigned char)c)) return p;
}
return NULL;
}
/* As above but strip from end of string : maybe should handle brackets? */
static char *strip_end(char *name)
{
char *p, c;
if(!name) return NULL;
/* Look for first non white space or quote */
for(p = name + strlen(name) - 1; p >= name ;p--) {
c = *p;
if(c == '"') {
if(p - 1 == name) return NULL;
*p = 0;
return name;
}
if(isspace((unsigned char)c)) *p = 0;
else return name;
}
return NULL;
}
static MIME_HEADER *mime_hdr_new(char *name, char *value)
{
MIME_HEADER *mhdr;
char *tmpname, *tmpval, *p;
int c;
if(name) {
if(!(tmpname = BUF_strdup(name))) return NULL;
for(p = tmpname ; *p; p++) {
c = *p;
if(isupper(c)) {
c = tolower(c);
*p = c;
}
}
} else tmpname = NULL;
if(value) {
if(!(tmpval = BUF_strdup(value))) return NULL;
for(p = tmpval ; *p; p++) {
c = *p;
if(isupper(c)) {
c = tolower(c);
*p = c;
}
}
} else tmpval = NULL;
mhdr = (MIME_HEADER *) OPENSSL_malloc(sizeof(MIME_HEADER));
if(!mhdr) return NULL;
mhdr->name = tmpname;
mhdr->value = tmpval;
if(!(mhdr->params = sk_MIME_PARAM_new(mime_param_cmp))) return NULL;
return mhdr;
}
static int mime_hdr_addparam(MIME_HEADER *mhdr, char *name, char *value)
{
char *tmpname, *tmpval, *p;
int c;
MIME_PARAM *mparam;
if(name) {
tmpname = BUF_strdup(name);
if(!tmpname) return 0;
for(p = tmpname ; *p; p++) {
c = *p;
if(isupper(c)) {
c = tolower(c);
*p = c;
}
}
} else tmpname = NULL;
if(value) {
tmpval = BUF_strdup(value);
if(!tmpval) return 0;
} else tmpval = NULL;
/* Parameter values are case sensitive so leave as is */
mparam = (MIME_PARAM *) OPENSSL_malloc(sizeof(MIME_PARAM));
if(!mparam) return 0;
mparam->param_name = tmpname;
mparam->param_value = tmpval;
sk_MIME_PARAM_push(mhdr->params, mparam);
return 1;
}
static int mime_hdr_cmp(const MIME_HEADER * const *a,
const MIME_HEADER * const *b)
{
return(strcmp((*a)->name, (*b)->name));
}
static int mime_param_cmp(const MIME_PARAM * const *a,
const MIME_PARAM * const *b)
{
return(strcmp((*a)->param_name, (*b)->param_name));
}
/* Find a header with a given name (if possible) */
static MIME_HEADER *mime_hdr_find(STACK_OF(MIME_HEADER) *hdrs, char *name)
{
MIME_HEADER htmp;
int idx;
htmp.name = name;
idx = sk_MIME_HEADER_find(hdrs, &htmp);
if(idx < 0) return NULL;
return sk_MIME_HEADER_value(hdrs, idx);
}
static MIME_PARAM *mime_param_find(MIME_HEADER *hdr, char *name)
{
MIME_PARAM param;
int idx;
param.param_name = name;
idx = sk_MIME_PARAM_find(hdr->params, &param);
if(idx < 0) return NULL;
return sk_MIME_PARAM_value(hdr->params, idx);
}
static void mime_hdr_free(MIME_HEADER *hdr)
{
if(hdr->name) OPENSSL_free(hdr->name);
if(hdr->value) OPENSSL_free(hdr->value);
if(hdr->params) sk_MIME_PARAM_pop_free(hdr->params, mime_param_free);
OPENSSL_free(hdr);
}
static void mime_param_free(MIME_PARAM *param)
{
if(param->param_name) OPENSSL_free(param->param_name);
if(param->param_value) OPENSSL_free(param->param_value);
OPENSSL_free(param);
}
/* Check for a multipart boundary. Returns:
* 0 : no boundary
* 1 : part boundary
* 2 : final boundary
*/
static int mime_bound_check(char *line, int linelen, char *bound, int blen)
{
if(linelen == -1) linelen = strlen(line);
if(blen == -1) blen = strlen(bound);
/* Quickly eliminate if line length too short */
if(blen + 2 > linelen) return 0;
/* Check for part boundary */
if(!strncmp(line, "--", 2) && !strncmp(line + 2, bound, blen)) {
if(!strncmp(line + blen + 2, "--", 2)) return 2;
else return 1;
}
return 0;
}