This is an update of the patch made by Antti Tapio for 0.9.8a - ticket #1261
Index: apps/smime.c =================================================================== RCS file: /home/john/cvsroot/openssl/apps/smime.c,v retrieving revision 1.1.1.1 retrieving revision 1.1.1.1.2.1 diff -u -p -r1.1.1.1 -r1.1.1.1.2.1 --- apps/smime.c 14 Oct 2011 11:17:40 -0000 1.1.1.1 +++ apps/smime.c 20 Oct 2011 07:16:06 -0000 1.1.1.1.2.1 @@ -78,7 +78,7 @@ static int smime_cb(int ok, X509_STORE_C #define SMIME_ENCRYPT (1 | SMIME_OP) #define SMIME_DECRYPT (2 | SMIME_IP) #define SMIME_SIGN (3 | SMIME_OP | SMIME_SIGNERS) -#define SMIME_VERIFY (4 | SMIME_IP) +#define SMIME_VERIFY (4 | SMIME_IP | SMIME_OP) #define SMIME_PK7OUT (5 | SMIME_IP | SMIME_OP) #define SMIME_RESIGN (6 | SMIME_IP | SMIME_OP | SMIME_SIGNERS) @@ -365,6 +365,23 @@ int MAIN(int argc, char **argv) goto argerr; contfile = *++args; } + else if (!strcmp(*args, "-transenc") || !strcmp (*args, "-transferencoding")) + { + if (args[1]) + { + if (!strcmp(args[1], "binary")) + flags |= SMIME_TRANSFER_ENCODING_BINARY; + else if (!strcmp(args[1], "base64")) + ; + else { + BIO_printf(bio_err, "Supported transfer encodings are base64 and binary\n"); + badarg = 1; + } + args++; + } + else + badarg = 1; + } else if (args_verify(&args, NULL, &badarg, bio_err, &vpm)) continue; else if ((cipher = EVP_get_cipherbyname(*args + 1)) == NULL) @@ -488,6 +505,7 @@ int MAIN(int argc, char **argv) BIO_printf(bio_err, "-rand file%cfile%c...\n", LIST_SEPARATOR_CHAR, LIST_SEPARATOR_CHAR); BIO_printf(bio_err, " load the file (or the files in the directory) into\n"); BIO_printf(bio_err, " the random number generator\n"); + BIO_printf(bio_err, "-transenc enc transfer encoding to use (base64 or binary)\n"); BIO_printf (bio_err, "cert.pem recipient certificate(s) for encryption\n"); goto end; } Index: crypto/asn1/asn1.h =================================================================== RCS file: /home/john/cvsroot/openssl/crypto/asn1/asn1.h,v retrieving revision 1.1.1.1 retrieving revision 1.1.1.1.2.1 diff -u -p -r1.1.1.1 -r1.1.1.1.2.1 --- crypto/asn1/asn1.h 14 Oct 2011 11:17:40 -0000 1.1.1.1 +++ crypto/asn1/asn1.h 15 Oct 2011 09:36:51 -0000 1.1.1.1.2.1 @@ -161,6 +161,7 @@ extern "C" { #define SMIME_OLDMIME 0x400 #define SMIME_CRLFEOL 0x800 #define SMIME_STREAM 0x1000 +#define SMIME_TRANSFER_ENCODING_BINARY 0x2000 struct X509_algor_st; DECLARE_STACK_OF(X509_ALGOR) @@ -1222,6 +1223,8 @@ void ERR_load_ASN1_strings(void); #define ASN1_F_ASN1_VERIFY 137 #define ASN1_F_B64_READ_ASN1 209 #define ASN1_F_B64_WRITE_ASN1 210 +#define ASN1_F_BINARY_READ_ASN1 219 +#define ASN1_F_BINARY_WRITE_ASN1 220 #define ASN1_F_BIO_NEW_NDEF 208 #define ASN1_F_BITSTR_CB 180 #define ASN1_F_BN_TO_ASN1_ENUMERATED 138 @@ -1335,6 +1338,7 @@ void ERR_load_ASN1_strings(void); #define ASN1_R_INVALID_OBJECT_ENCODING 216 #define ASN1_R_INVALID_SEPARATOR 131 #define ASN1_R_INVALID_TIME_FORMAT 132 +#define ASN1_R_INVALID_TRANSFER_ENCODING 217 #define ASN1_R_INVALID_UNIVERSALSTRING_LENGTH 133 #define ASN1_R_INVALID_UTF8STRING 134 #define ASN1_R_IV_TOO_LARGE 135 Index: crypto/asn1/asn1_err.c =================================================================== RCS file: /home/john/cvsroot/openssl/crypto/asn1/asn1_err.c,v retrieving revision 1.1.1.1 retrieving revision 1.1.1.1.2.1 diff -u -p -r1.1.1.1 -r1.1.1.1.2.1 --- crypto/asn1/asn1_err.c 14 Oct 2011 11:17:40 -0000 1.1.1.1 +++ crypto/asn1/asn1_err.c 15 Oct 2011 09:36:51 -0000 1.1.1.1.2.1 @@ -135,6 +135,8 @@ static ERR_STRING_DATA ASN1_str_functs[] {ERR_FUNC(ASN1_F_ASN1_VERIFY), "ASN1_verify"}, {ERR_FUNC(ASN1_F_B64_READ_ASN1), "B64_READ_ASN1"}, {ERR_FUNC(ASN1_F_B64_WRITE_ASN1), "B64_WRITE_ASN1"}, +{ERR_FUNC(ASN1_F_BINARY_READ_ASN1), "BINARY_READ_ASN1"}, +{ERR_FUNC(ASN1_F_BINARY_WRITE_ASN1), "BINARY_WRITE_ASN1"}, {ERR_FUNC(ASN1_F_BIO_NEW_NDEF), "BIO_new_NDEF"}, {ERR_FUNC(ASN1_F_BITSTR_CB), "BITSTR_CB"}, {ERR_FUNC(ASN1_F_BN_TO_ASN1_ENUMERATED), "BN_to_ASN1_ENUMERATED"}, Index: crypto/asn1/asn_mime.c =================================================================== RCS file: /home/john/cvsroot/openssl/crypto/asn1/asn_mime.c,v retrieving revision 1.1.1.1 retrieving revision 1.1.1.1.2.2 diff -u -p -r1.1.1.1 -r1.1.1.1.2.2 --- crypto/asn1/asn_mime.c 14 Oct 2011 11:17:40 -0000 1.1.1.1 +++ crypto/asn1/asn_mime.c 20 Oct 2011 07:16:07 -0000 1.1.1.1.2.2 @@ -100,7 +100,6 @@ static int mime_hdr_cmp(const MIME_HEADE 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 strip_eol(char *linebuf, int *plen); static MIME_HEADER *mime_hdr_find(STACK_OF(MIME_HEADER) *hdrs, char *name); @@ -143,7 +142,7 @@ int i2d_ASN1_bio_stream(BIO *out, ASN1_V return 1; } -/* Base 64 read and write of ASN1 structure */ +/* Base 64 and binary read and write of ASN1 structure */ static int B64_write_ASN1(BIO *out, ASN1_VALUE *val, BIO *in, int flags, const ASN1_ITEM *it) @@ -166,6 +165,15 @@ static int B64_write_ASN1(BIO *out, ASN1 return r; } +static int BINARY_write_ASN1(BIO *out, ASN1_VALUE *val, BIO *in, int flags, + const ASN1_ITEM *it) + { + int r; + r = i2d_ASN1_bio_stream(out, val, in, flags, it); + (void)BIO_flush(out); + return r; + } + /* Streaming ASN1 PEM write */ int PEM_write_bio_ASN1_stream(BIO *out, ASN1_VALUE *val, BIO *in, int flags, @@ -197,6 +205,16 @@ static ASN1_VALUE *b64_read_asn1(BIO *bi return val; } +static ASN1_VALUE *binary_read_asn1(BIO *bio, const ASN1_ITEM *it) +{ + ASN1_VALUE *val; + val = ASN1_item_d2i_bio(it, bio, NULL); + if(!val) + ASN1err(ASN1_F_BINARY_READ_ASN1,ASN1_R_DECODE_ERROR); + (void)BIO_flush(bio); + return val; +} + /* Generate the MIME "micalg" parameter from RFC3851, RFC4490 */ static int asn1_write_micalg(BIO *out, STACK_OF(X509_ALGOR) *mdalgs) @@ -323,12 +341,20 @@ int SMIME_write_ASN1(BIO *bio, ASN1_VALU 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", + BIO_printf(bio, "Content-Transfer-Encoding: %s%s", + (flags&SMIME_TRANSFER_ENCODING_BINARY) ? "binary" : "base64", mime_eol); BIO_printf(bio, "Content-Disposition: attachment;"); BIO_printf(bio, " filename=\"smime.p7s\"%s%s", mime_eol, mime_eol); - B64_write_ASN1(bio, val, NULL, 0, it); + + if (flags & SMIME_TRANSFER_ENCODING_BINARY) { + BINARY_write_ASN1(bio, val, NULL, 0, it); + BIO_printf(bio, "%s", mime_eol); + } + else + B64_write_ASN1(bio, val, NULL, 0, it); + BIO_printf(bio,"%s------%s--%s%s", mime_eol, bound, mime_eol, mime_eol); return 1; @@ -360,10 +386,18 @@ int SMIME_write_ASN1(BIO *bio, ASN1_VALU if (msg_type) BIO_printf(bio, " smime-type=%s;", msg_type); BIO_printf(bio, " name=\"%s\"%s", cname, mime_eol); - BIO_printf(bio, "Content-Transfer-Encoding: base64%s%s", + BIO_printf(bio, "Content-Transfer-Encoding: %s%s%s", + (flags&SMIME_TRANSFER_ENCODING_BINARY) ? "binary" : "base64", mime_eol, mime_eol); - if (!B64_write_ASN1(bio, val, data, flags, it)) - return 0; + if (flags & SMIME_TRANSFER_ENCODING_BINARY) { + if (!BINARY_write_ASN1(bio, val, data, flags, it)) + return 0; + } + else { + if (!B64_write_ASN1(bio, val, data, flags, it)) + return 0; + } + BIO_printf(bio, "%s", mime_eol); return 1; } @@ -434,6 +468,7 @@ ASN1_VALUE *SMIME_read_ASN1(BIO *bio, BI MIME_PARAM *prm; ASN1_VALUE *val; int ret; + int base64; if(bcont) *bcont = NULL; @@ -492,9 +527,29 @@ ASN1_VALUE *SMIME_read_ASN1(BIO *bio, BI sk_BIO_pop_free(parts, BIO_vfree); return NULL; } + + /* Base64 or binary? Default to binary. This is + inconsistent with old code, but needed for AS2 */ + + base64 = 0; + if ((hdr = mime_hdr_find(headers, "content-transfer-encoding"))) { + if (hdr->value && strcmp (hdr->value, "base64") == 0) + base64 = 1; + else if (!hdr->value || strcmp (hdr->value, "binary") != 0) { + sk_MIME_HEADER_pop_free(headers, mime_hdr_free); + ASN1err(ASN1_F_SMIME_READ_ASN1,ASN1_R_INVALID_TRANSFER_ENCODING); + sk_BIO_pop_free (parts, BIO_vfree); + return NULL; + } + } + sk_MIME_HEADER_pop_free(headers, mime_hdr_free); /* Read in ASN1 */ - if(!(val = b64_read_asn1(asnin, it))) { + + if(!(val = base64 ? + b64_read_asn1(asnin, it) : + binary_read_asn1(asnin, it))) + { ASN1err(ASN1_F_SMIME_READ_ASN1,ASN1_R_ASN1_SIG_PARSE_ERROR); sk_BIO_pop_free(parts, BIO_vfree); return NULL; @@ -518,9 +573,25 @@ ASN1_VALUE *SMIME_read_ASN1(BIO *bio, BI return NULL; } + /* Base64 or binary? Default to binary. This is + inconsistent with old code, but needed for AS2 */ + + base64 = 0; + if ((hdr = mime_hdr_find(headers, "content-transfer-encoding"))) { + if (hdr->value && strcmp (hdr->value, "base64") == 0) + base64 = 1; + else if (!hdr->value || strcmp (hdr->value, "binary") != 0) { + sk_MIME_HEADER_pop_free(headers, mime_hdr_free); + ASN1err(ASN1_F_SMIME_READ_ASN1,ASN1_R_INVALID_TRANSFER_ENCODING); + return NULL; + } + } + sk_MIME_HEADER_pop_free(headers, mime_hdr_free); - if(!(val = b64_read_asn1(bio, it))) { + if(!(val = base64 ? b64_read_asn1(bio, it) : + binary_read_asn1(bio, it))) + { ASN1err(ASN1_F_SMIME_READ_ASN1, ASN1_R_ASN1_PARSE_ERROR); return NULL; } @@ -597,48 +668,163 @@ int SMIME_text(BIO *in, BIO *out) return 1; } -/* Split a multipart/XXX message body into component parts: result is - * canonical parts in a STACK of bios +/* + * Returns >0 if the given string starts with a linebreak. + * The return code is the index of the first character + * after the line break. + */ +static int starts_with_linebreak(const char *s) { + const char *p; + p = s; + if(*p == '\x0d') { + ++p; + if(*p == '\x0a') + ++p; + } else if(*p == '\x0a') { + ++p; + } + return (p - s); +} + +/* + * Finds a boundary in a buffer and returns the length of the boundary (this can + * vary depending on the kind of linebreaks), the starting index of the boundary + * and a flag telling if it is an ending boundary. + * Parameters: + * buf - buffer to search in + * buflen - length of buf + * bound - the boundary string to match + * blen - length of bound + * boundstart - returns the index of the start of the boundary + * ending - returns 1 if the found boundary is an ending boundary, + * i.e. if it ends with a "--" + * + * Returns: + * 0 - No boundary found + * >0 - Boundary found and the return value is the length of boundary, + * which can be skipped to proceed to the next MIME part. */ +static int find_boundary_start(const char *buf, int buflen, const char *bound, int blen, int *boundstart, int *ending) { + int e = -1; + int i, e2; + int is; + const char *p; -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; + if(buflen == -1) return 0; + if(blen == -1) blen = strlen(bound); + + + /* Quickly eliminate if line length too short */ + if((blen + 3) > buflen) + return 0; + + *ending = 0; + is = buflen - (blen + 3); + for(i = 0; i <= is; i++) { + *boundstart = i; + p = buf + i; + e = i; + if((e = starts_with_linebreak(p)) > 0) { + if(!strncmp(p + e, "--", 2)) { + e += 2; + if((e + blen) < buflen && !strncmp(p + e, bound, blen)) + e += blen; + else + e = 0; + } else + e = 0; + } + + if(e > 0) { + /* Look what is behind */ + if((e + 2) < buflen && !strncmp(p + e, "--", 2)) { + e += 2; + *ending = 1; + } + + /* Strip away the trailing linebreaks, for test issues */ + if(e > 0 && (e + 2) < buflen && (e2 = starts_with_linebreak(p + e)) > 0) { + e += e2; + } + break; + } + } + + return e; +} + +/* Split a multipart/XXX message body into component parts: result is + * parts in a STACK of bios. Is considered to be able to read also + * binary parts. + * Tries to split after the following rules (EBNF): + * { LineBreak }, LineBreak, Boundary, LineBreak, Data, + * ( LineBreak, Boundary, [ "--" ] | [ LineBreak | EOF ] ) | + * EOF + * LineBreak := ( "\x0d", "\x0a" ) | "\x0a" | "\x0d" + */ +static int multi_split(BIO *bio, char *bound, STACK_OF(BIO) **ret) { + int len, len2, boundlen; + char rbuf[MAX_SMLEN]; + char *buf = rbuf; BIO *bpart = NULL; STACK_OF(BIO) *parts; - char state, part, first; - - blen = strlen(bound); - part = 0; - state = 0; - first = 1; + int boundary_found, boundary_start, boundary_ending, maxboundlen; + + boundlen = strlen(bound); + maxboundlen = 2 + 2 + boundlen + 2 + 2; 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) { - /* Strip CR+LF from linebuf */ - next_eol = strip_eol(linebuf, &len); - if(first) { - first = 0; - if(bpart) sk_BIO_push(parts, bpart); + len = 0; + + while((len = (len > 0) ? len : BIO_read(bio, (buf = rbuf), MAX_SMLEN)) > 0) { + /* If there are not enough bytes in the buffer, to compare with the + * boundary, fill the buffer up + */ + if(len <= maxboundlen) { + memmove(rbuf, buf, len); buf = rbuf; + len2 = BIO_read(bio, buf + len, MAX_SMLEN - len); + if(len2 >= (maxboundlen - len)) + len += len2; + else { + /* Stream ended here, write the characters away and return */ + BIO_write(bpart, buf, (len + (len2 > 0) ? len2 : 0)); + if(bpart) + sk_BIO_push(parts, bpart); + break; + } + } + + if((boundary_found = find_boundary_start(buf, len, bound, boundlen, &boundary_start, &boundary_ending)) > 0) { + /* If there is something before the boundary, write it to the part */ + if(boundary_start > 0 && bpart) + BIO_write(bpart, buf, boundary_start); + buf += boundary_start; len -= boundary_start; + memmove(rbuf, buf, len); buf = rbuf; + len2 = BIO_read(bio, buf + len, MAX_SMLEN - len); + len += (len2) ? len2 : 0; + + boundary_found = find_boundary_start(buf, len, bound, boundlen, &boundary_start, &boundary_ending); + /* If boundary is not the last one, create a new part */ + if(!boundary_ending) { + if(bpart) + sk_BIO_push(parts, bpart); bpart = BIO_new(BIO_s_mem()); 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); + } else { + /* End is reached */ + if(bpart) + sk_BIO_push(parts, bpart); + return 1; + } + buf += (boundary_start + boundary_found); len -= (boundary_start + boundary_found); + } else { + if(bpart) + BIO_write(bpart, buf, len - maxboundlen); + buf += (len - maxboundlen); len = maxboundlen; + /* ELSE: Just forget it, no MIME part started */ } } + return 0; } @@ -904,25 +1090,6 @@ static void mime_param_free(MIME_PARAM * 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; -} - static int strip_eol(char *linebuf, int *plen) { int len = *plen;