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