1 /* smimemime.c  -  Utility functions for performing MIME assembly and parsing
2  *
3  * Copyright (c) 1999 Sampo Kellomaki <sampo@iki.fi>, All Rights Reserved.
4  * License: This software may be distributed under the same license
5  *          terms as openssl (i.e. free, but mandatory attribution).
6  *          See file LICENSE for details.
7  *
8  * 11.9.1999, Created. --Sampo
9  * 13.9.1999, 0.1 released. Now adding verify. --Sampo
10  * 1.10.1999, improved error handling, fixed decrypt --Sampo
11  * 6.10.1999, separated from smimeutil.c --Sampo
12  * 9.10.1999, reviewed for double frees --Sampo
13  *
14  * This module has been developed to support a Lingo XTRA that is supposed
15  * to provide crypto functionality. It may, however, be useful for other
16  * purposes as well.
17  *
18  * This is a very simple S/MIME library. For example the multipart
19  * boundary separators are hard coded and no effort is made to verify
20  * that mime entities are in their canonical form before signing (the
21  * caller should make sure they are, canonical form means using CRLF
22  * as line termination, among other things). Also the multipart functionality
23  * only understands up to 3 attachments. For many tasks this is enough,
24  * but if its not, feel free to write more generic utilities.
25  *
26  * Memory management: most routines malloc the results. Freeing them is
27  * application's responsibility. I use libc malloc, but if in doubt
28  * it might be safer to just leak the memory (i.e. don't ever free it).
29  * This library works entirely in memory, so maximum memory consumption
30  * might be more than twice the total size of all files to be encrypted.
31  */
32 
33 #include "platform.h"
34 
35 #include <stdio.h>
36 #include <string.h>
37 #include <time.h>
38 
39 #if defined(macintosh) || defined(__MWERKS__)
40 #include "macglue.h"
41 #endif
42 
43 #include <openssl/crypto.h>
44 #include <openssl/buffer.h>
45 #include <openssl/bio.h>
46 #include <openssl/x509.h>
47 #include <openssl/pem.h>
48 #include <openssl/err.h>
49 
50 #define SMIME_INTERNALS  /* we want also our internal helper functions */
51 #include "smimeutil.h"
52 
53 #include "logprint.h"
54 
55 /* Called by:  clear_sign, encrypt1, sign */
56 char*
cut_pem_markers_off(char * b,int n,char * algo)57 cut_pem_markers_off(char* b, int n, char* algo) {
58   int algolen = strlen(algo);
59   if (!b) return NULL;
60   b[n-6-algolen-4-5] = '\0'; /* cut off `-----END PKCS7-----\n' */
61   b+=5+6+algolen+6;          /* skip `-----BEGIN PKCS7-----\n' */
62   return b;  /* WARNING: returned value can not be freed */
63 }
64 
65 /* As an additional goodie, this inserts possibly missing newline
66  * to the end of pem entity.
67  */
68 
69 /* Called by:  get_pkcs7_from_pem */
70 char*
wrap_in_pem_markers(const char * b,char * algo)71 wrap_in_pem_markers(const char* b, char* algo) {
72   char* bb;
73   int n;
74   int algolen = strlen(algo);
75   n = strlen(b);
76   if (!(bb = (char*)OPENSSL_malloc(5+6+algolen+6+ n +5+4+algolen+6 +1 +1)))
77     GOTO_ERR("no memory?");
78   strcpy(bb, "-----BEGIN ");
79   strcat(bb, algo);
80   strcat(bb, "-----\n");
81   strcat(bb, b);
82   if (b[n-1] != '\012' && b[n-1] != '\015')  /* supply \n if missing */
83     strcat(bb, "\n");
84   strcat(bb, "-----END ");
85   strcat(bb, algo);
86   strcat(bb, "-----\n");
87   n = strlen(bb);
88   return bb;
89 err:
90   return NULL;
91 }
92 
93 /* reallocing every time is not exactly the most efficient way, but it
94  * is simple and it works */
95 
96 /* Called by:  attach x9, encrypt1, get_req_attr x3, mime_base64_entity x3, mime_mk_multipart x3, mime_raw_entity x3, sign, smime_mk_multipart_signed x4 */
97 char*
concat(char * b,const char * s)98 concat(char* b, const char* s)
99 {
100   if (!(b = (char*)OPENSSL_realloc(b, strlen(b)+strlen(s)+1))) GOTO_ERR("no memory?");
101   strcat(b,s);
102   return b;
103 err:
104   return NULL;
105 }
106 
107 /* Called by:  get_req_attr */
108 char*
concatmem(char * b,const char * s,int len)109 concatmem(char* b, const char* s, int len)
110 {
111   int lb = strlen(b);
112   if (!(b = (char*)OPENSSL_realloc(b, lb+len+1))) GOTO_ERR("no memory?");
113   memcpy(b+lb, s, len);
114   b[lb+len] = '\0';
115   return b;
116 err:
117   return NULL;
118 }
119 
120 /* Arrange all headers for binary attachment */
121 
122 /* Called by:  mime_mk_multipart x3 */
123 static char*
attach(char * b,const char * data,int len,const char * type,const char * name)124 attach(char* b,
125        const char* data,
126        int len,
127        const char* type,
128        const char* name)
129 {
130   /*int n;*/
131   char* b64;
132 
133   if (!type) return b;  /* type==NULL */
134   if (!*type) return b; /* type=="" */
135   if (!data) return b;
136   if (!name) return b;
137 
138   /*n =*/ smime_base64(1, data, len, &b64);
139   if (!b64) return b;
140 
141   if (!(b = concat(b, CRLF "Content-type: "))) goto err;
142   if (!(b = concat(b, type))) goto err;
143   if (!(b = concat(b, "; name=\""))) goto err;
144   if (!(b = concat(b, name))) goto err;
145   if (!(b = concat(b, "\"" CRLF
146 		   "Content-transfer-encoding: base64" CRLF
147 		   "Content-disposition: inline; filename=\"")))
148     goto err;
149   if (!(b = concat(b, name))) goto err;
150   if (!(b = concat(b, "\"" CRLF CRLF))) goto err;
151   if (!(b = concat(b, b64))) goto err;
152   if (!(b = concat(b, CRLF "--" SEP))) goto err;
153   return b;
154 err:
155   return NULL;
156 }
157 
158 /* ======= M I M E   M U L T I P A R T   M A N I P U L A T I O N ======= */
159 
160 /* Create MIME multipart/mixed entity containing some text and
161  * then up to 3 attachmets. All attachments are base64 encoded so they
162  * can be binary, if needed. Text itself is assumed 8bit.
163  *
164  * Note: In MIME multiparts the CRLF before --separator is considered
165  * part of the separator. If data ends in CRLF, an empty line will
166  * appear.
167  */
168 
169 /*
170 Content-type: multipart/mixed; boundary=separator_42
171 
172 --separator_42
173 Content-type: text/plain
174 Content-transfer-encoding: 8bit
175 
176 First part is text.
177 --separator_42
178 Content-type: image/gif; name="foo.gif"
179 Content-transfer-encoding: base64
180 Content-disposition: attachment; filename="foo.gif"
181 
182 AQW232ASA232NFKDJFD==
183 --separator_42--
184  */
185 
186 /* Called by:  mk_multipart */
187 char*
mime_mk_multipart(const char * text,const char * file1,int len1,const char * type1,const char * name1,const char * file2,int len2,const char * type2,const char * name2,const char * file3,int len3,const char * type3,const char * name3)188 mime_mk_multipart(const char* text,
189 	  const char* file1, int len1, const char* type1, const char* name1,
190 	  const char* file2, int len2, const char* type2, const char* name2,
191 	  const char* file3, int len3, const char* type3, const char* name3)
192 {
193   char* b;
194 
195   /* Concatenate all components into one message. This type of stuff
196    * is sooo ugly in C. I'm missing perl. */
197 
198   if (!(b = strdup("Content-type: multipart/mixed; boundary=" SEP CRLF
199 		   CRLF
200 		   "--" SEP CRLF
201 		   "Content-type: text/plain" CRLF
202 		   "Content-transfer-encoding: 8bit" CRLF
203 		   CRLF))) GOTO_ERR("no memory?");
204   if (!(b = concat(b, text))) goto err;
205   if (!(b = concat(b, CRLF "--" SEP))) goto err;
206 
207   if (!(b = attach(b, file1, len1, type1, name1))) goto err;
208   if (!(b = attach(b, file2, len2, type2, name2))) goto err;
209   if (!(b = attach(b, file3, len3, type3, name3))) goto err;
210 
211   if (!(b = concat(b, "--" CRLF))) goto err;
212   return b;
213 err:
214   return NULL;
215 }
216 
217 /* Called by:  clear_sign */
218 char*  /* returns smime encoded clear sig blob, or NULL if error */
smime_mk_multipart_signed(const char * mime_entity,const char * sig_entity)219 smime_mk_multipart_signed(const char* mime_entity, const char* sig_entity)
220 {
221   char* b;
222 
223   if (!(b = strdup("Content-type: multipart/signed; protocol=\"application/x-pkcs7-signature\"; micalg=sha1; boundary=" SIG CRLF
224 		   CRLF
225 		   "--" SIG CRLF))) GOTO_ERR("no memory?");
226   if (!(b = concat(b, mime_entity))) goto err;
227   if (!(b = concat(b, CRLF "--" SIG CRLF
228    "Content-Type: application/x-pkcs7-signature; name=\"smime.p7s\"" CRLF
229    "Content-Transfer-Encoding: base64" CRLF
230    "Content-Disposition: attachment; filename=\"smime.p7s\"" CRLF CRLF)))
231     goto err;
232   if (!(b = concat(b, sig_entity))) goto err;
233   if (!(b = concat(b, CRLF "--" SIG "--" CRLF))) goto err;
234   return b;
235 
236 err:
237   return NULL;
238 }
239 
240 /* Finds boundary marker from `Content-type: multipart/...; boundary=sep'
241  * header and splits the multiparts into array parts. Lengths of each
242  * part go to lengths array. If parts is NULL, just returns the number
243  * of parts found. Memory for each part is obtained from OPENSSL_malloc. Only
244  * one level of multipart encoding is interpretted, i.e. if one of the
245  * contained parts happens to be a multipart, it needs to be separately
246  * interpretted on a second pass.
247  *
248  * *** WARNING: this is not totally robust and mime compliant, improvements
249  *              welcome. --Sampo
250  */
251 
252 int  /* returns number of parts, -1 on error */
mime_split_multipart(const char * entity,int max_parts,char * parts[],int lengths[])253 mime_split_multipart(const char* entity,
254 		     int max_parts,  /* how big following arrays are */
255 		     char* parts[],  /* NULL --> just count parts */
256 		     int lengths[])
257 {
258   char separator[256];
259   int nparts = -1;
260   int sep_len, len;
261   char* p;
262   char* pp;
263   char* ppp;
264   char* start = NULL;  /* start of current sub entity */
265 
266   if (!entity || (parts && !lengths)) GOTO_ERR("NULL arg(s)");
267 
268   if (!(p = strstr(entity, "Content-type: multipart/")))
269     GOTO_ERR("16 No `Content-type: multipart/...' header found");
270 
271   if (!(p = strstr(p, "boundary=")))
272     GOTO_ERR("16 Badly formed multipart header. Didn't find `boundary='.");
273 
274   p+=9; /* strlen("boundary=") */
275   sep_len = strcspn(p, "\015\012 ;");
276   if (sep_len <= 0) GOTO_ERR("16 No boundary separator?");
277   if (sep_len >= (int)sizeof(separator))
278     GOTO_ERR("16 Too long boundary separator. Only 255 chars allowed.");
279 
280   separator[0] = separator[1] = '-';
281   memcpy(separator+2, p, sep_len);
282   sep_len+=2;
283   separator[sep_len] = '\0';
284   p+=sep_len;
285 
286   while ((p = strstr(p, separator))) {
287 
288     ppp = pp = p;
289     p+=sep_len;
290     if (*p != '\015' && *p != '\012' && *p != '-')
291       continue;   /* False positive: separator appeared as line prefix */
292 
293     if (pp[-1] == '\012') pp--;  /* walk back and eat the CRLF */
294     if (pp[-1] == '\015') pp--;
295     if (pp == ppp)
296       continue;  /* False positive: separator in middle of line */
297 
298     if (start && parts && nparts < max_parts) {
299       len = lengths[nparts] = pp-start;
300       if (!(parts[nparts] = (char*)OPENSSL_malloc(len+1))) GOTO_ERR("no memory?");
301       memcpy(parts[nparts], start, len);
302       parts[nparts][len] = '\0';  /* Gratuitous nul termination */
303     }
304 
305     nparts++;
306     if (*p == '\015') p++;  /* Skip CRLF */
307     if (*p == '\012') p++;  /* This is really mandatory */
308 
309     start = p;
310   }
311   return nparts;
312 
313 /* Called by: */
314 err:
315   if (parts) {
316     /* free what we have allocated so far */
317     for (sep_len = 0; sep_len < nparts; sep_len++)
318       if (parts[sep_len])
319 	OPENSSL_free(parts[sep_len]);
320   }
321   return -1;
322 }
323 
324 /* Called by:  main */
325 char*
mime_raw_entity(const char * text,const char * type)326 mime_raw_entity(const char* text, const char* type)
327 {
328   char* b;
329   if (!(b = strdup("Content-type: "))) GOTO_ERR("no memory?");
330   if (!(b = concat(b, type))) goto err;
331   if (!(b = concat(b, CRLF CRLF))) goto err;
332   if (!(b = concat(b, text))) goto err;
333   return b;
334 err:
335   return NULL;
336 }
337 
338 /* Called by:  main x3 */
339 char*
mime_base64_entity(const char * data,int len,const char * type)340 mime_base64_entity(const char* data, int len, const char* type)
341 {
342   /*int n;*/
343   char* b64;
344   char* b;
345   if (!(b = strdup("Content-type: "))) GOTO_ERR("no memory?");
346   if (!(b = concat(b, type))) goto err;
347   if (!(b = concat(b, CRLF CRLF))) goto err;
348 
349   /*n =*/ smime_base64(1, data, len, &b64);
350   if (!b64) GOTO_ERR("no memory?");
351   if (!(b = concat(b, b64))) goto err;
352   return b;
353 err:
354   return NULL;
355 }
356 
357 /* Canonicalization involves converting LF->CRLF (Unix) and CR->CRLF (Mac).
358  * Canonicalization is of prime importance when signing data because
359  * verification assumes canonicalized form. This canonicalization is really
360  * not mime specific at all so you can use it for fixing PEM blobs
361  * on Mac (becaue OpenSSL does not understand lone CR as line termination). */
362 
363 /* Called by:  clear_sign, extract_certificate, extract_request, main, open_private_key, sign */
364 char*
mime_canon(const char * s)365 mime_canon(const char* s)
366 {
367   char* d;
368   char* p;
369   int len;
370   len = strlen(s);
371   p = d = (char*)OPENSSL_malloc(len + len);  /* Reserve spaces for CR's to be inserted. */
372   if (!d) GOTO_ERR("no memory?");
373 
374   /* Scan and copy */
375 
376   for (; *s; s++) {
377     if (s[0] != '\015' && s[0] != '\012')
378       *(p++) = *s; /* pass thru */
379     else {
380       if (s[0] == '\015' && s[1] == '\012') s++;  /* already CRLF */
381       *(p++) = '\015'; *(p++) = '\012';
382     }
383   }
384   *(p++) = '\0';
385 
386   /* Shrink the buffer back to actual size (not very likely to fail) */
387 
388   return (char*)OPENSSL_realloc(d, (p-d));
389 err:
390   return NULL;
391 }
392 
393 /* EOF  -  smimemime.c */
394