Branch data Line data Source code
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*
57 : 3 : cut_pem_markers_off(char* b, int n, char* algo) {
58 : 3 : int algolen = strlen(algo);
59 [ - + ]: 3 : if (!b) return NULL;
60 : 3 : b[n-6-algolen-4-5] = '\0'; /* cut off `-----END PKCS7-----\n' */
61 : 3 : b+=5+6+algolen+6; /* skip `-----BEGIN PKCS7-----\n' */
62 : 3 : 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*
71 : 3 : wrap_in_pem_markers(const char* b, char* algo) {
72 : : char* bb;
73 : : int n;
74 : 3 : int algolen = strlen(algo);
75 : 3 : n = strlen(b);
76 [ - + ]: 3 : if (!(bb = (char*)OPENSSL_malloc(5+6+algolen+6+ n +5+4+algolen+6 +1 +1)))
77 : 0 : GOTO_ERR("no memory?");
78 : 3 : strcpy(bb, "-----BEGIN ");
79 : 3 : strcat(bb, algo);
80 : 3 : strcat(bb, "-----\n");
81 : 3 : strcat(bb, b);
82 [ + + + - ]: 3 : if (b[n-1] != '\012' && b[n-1] != '\015') /* supply \n if missing */
83 : 2 : strcat(bb, "\n");
84 : 3 : strcat(bb, "-----END ");
85 : 3 : strcat(bb, algo);
86 : 3 : strcat(bb, "-----\n");
87 : 3 : n = strlen(bb);
88 : 3 : return bb;
89 : 0 : err:
90 : 0 : 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*
98 : : concat(char* b, const char* s)
99 : 45 : {
100 [ - + ]: 45 : if (!(b = (char*)OPENSSL_realloc(b, strlen(b)+strlen(s)+1))) GOTO_ERR("no memory?");
101 : 45 : strcat(b,s);
102 : 45 : return b;
103 : 0 : err:
104 : 0 : return NULL;
105 : : }
106 : :
107 : : /* Called by: get_req_attr */
108 : : char*
109 : : concatmem(char* b, const char* s, int len)
110 : 0 : {
111 : 0 : int lb = strlen(b);
112 [ # # ]: 0 : if (!(b = (char*)OPENSSL_realloc(b, lb+len+1))) GOTO_ERR("no memory?");
113 : 0 : memcpy(b+lb, s, len);
114 : 0 : b[lb+len] = '\0';
115 : 0 : return b;
116 : 0 : err:
117 : 0 : return NULL;
118 : : }
119 : :
120 : : /* Arrange all headers for binary attachment */
121 : :
122 : : /* Called by: mime_mk_multipart x3 */
123 : : static char*
124 : : attach(char* b,
125 : : const char* data,
126 : : int len,
127 : : const char* type,
128 : : const char* name)
129 : 3 : {
130 : : int n;
131 : : char* b64;
132 : :
133 [ + + ]: 3 : if (!type) return b; /* type==NULL */
134 [ - + ]: 1 : if (!*type) return b; /* type=="" */
135 [ - + ]: 1 : if (!data) return b;
136 [ - + ]: 1 : if (!name) return b;
137 : :
138 : 1 : n = smime_base64(1, data, len, &b64);
139 [ - + ]: 1 : if (!b64) return b;
140 : :
141 [ + - ]: 1 : if (!(b = concat(b, CRLF "Content-type: "))) goto err;
142 [ + - ]: 1 : if (!(b = concat(b, type))) goto err;
143 [ + - ]: 1 : if (!(b = concat(b, "; name=\""))) goto err;
144 [ + - ]: 1 : if (!(b = concat(b, name))) goto err;
145 [ + - ]: 1 : if (!(b = concat(b, "\"" CRLF
146 : : "Content-transfer-encoding: base64" CRLF
147 : : "Content-disposition: inline; filename=\"")))
148 : 0 : goto err;
149 [ + - ]: 1 : if (!(b = concat(b, name))) goto err;
150 [ + - ]: 1 : if (!(b = concat(b, "\"" CRLF CRLF))) goto err;
151 [ + - ]: 1 : if (!(b = concat(b, b64))) goto err;
152 [ + - ]: 1 : if (!(b = concat(b, CRLF "--" SEP))) goto err;
153 : 1 : return b;
154 : 0 : err:
155 : 0 : 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*
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 : 1 : {
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 [ - + ]: 1 : 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 : 0 : CRLF))) GOTO_ERR("no memory?");
204 [ + - ]: 1 : if (!(b = concat(b, text))) goto err;
205 [ + - ]: 1 : if (!(b = concat(b, CRLF "--" SEP))) goto err;
206 : :
207 [ + - ]: 1 : if (!(b = attach(b, file1, len1, type1, name1))) goto err;
208 [ + - ]: 1 : if (!(b = attach(b, file2, len2, type2, name2))) goto err;
209 [ + - ]: 1 : if (!(b = attach(b, file3, len3, type3, name3))) goto err;
210 : :
211 [ + - ]: 1 : if (!(b = concat(b, "--" CRLF))) goto err;
212 : 1 : return b;
213 : 0 : err:
214 : 0 : return NULL;
215 : : }
216 : :
217 : : /* Called by: clear_sign */
218 : : char* /* returns smime encoded clear sig blob, or NULL if error */
219 : : smime_mk_multipart_signed(const char* mime_entity, const char* sig_entity)
220 : 0 : {
221 : : char* b;
222 : :
223 [ # # ]: 0 : if (!(b = strdup("Content-type: multipart/signed; protocol=\"application/x-pkcs7-signature\"; micalg=sha1; boundary=" SIG CRLF
224 : : CRLF
225 : 0 : "--" SIG CRLF))) GOTO_ERR("no memory?");
226 [ # # ]: 0 : if (!(b = concat(b, mime_entity))) goto err;
227 [ # # ]: 0 : 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 : 0 : goto err;
232 [ # # ]: 0 : if (!(b = concat(b, sig_entity))) goto err;
233 [ # # ]: 0 : if (!(b = concat(b, CRLF "--" SIG "--" CRLF))) goto err;
234 : 0 : return b;
235 : :
236 : 0 : err:
237 : 0 : 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 */
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 : 0 : {
258 : : char separator[256];
259 : 0 : int nparts = -1;
260 : : int sep_len, len;
261 : : char* p;
262 : : char* pp;
263 : : char* ppp;
264 : 0 : char* start = NULL; /* start of current sub entity */
265 : :
266 [ # # # # : 0 : if (!entity || (parts && !lengths)) GOTO_ERR("NULL arg(s)");
# # ]
267 : :
268 [ # # ]: 0 : if (!(p = strstr(entity, "Content-type: multipart/")))
269 : 0 : GOTO_ERR("16 No `Content-type: multipart/...' header found");
270 : :
271 [ # # ]: 0 : if (!(p = strstr(p, "boundary=")))
272 : 0 : GOTO_ERR("16 Badly formed multipart header. Didn't find `boundary='.");
273 : :
274 : 0 : p+=9; /* strlen("boundary=") */
275 : 0 : sep_len = strcspn(p, "\015\012 ;");
276 [ # # ]: 0 : if (sep_len <= 0) GOTO_ERR("16 No boundary separator?");
277 [ # # ]: 0 : if (sep_len >= (int)sizeof(separator))
278 : 0 : GOTO_ERR("16 Too long boundary separator. Only 255 chars allowed.");
279 : :
280 : 0 : separator[0] = separator[1] = '-';
281 : 0 : memcpy(separator+2, p, sep_len);
282 : 0 : sep_len+=2;
283 : 0 : separator[sep_len] = '\0';
284 : 0 : p+=sep_len;
285 : :
286 [ # # ]: 0 : while ((p = strstr(p, separator))) {
287 : :
288 : 0 : ppp = pp = p;
289 : 0 : p+=sep_len;
290 [ # # # # : 0 : if (*p != '\015' && *p != '\012' && *p != '-')
# # ]
291 : 0 : continue; /* False positive: separator appeared as line prefix */
292 : :
293 [ # # ]: 0 : if (pp[-1] == '\012') pp--; /* walk back and eat the CRLF */
294 [ # # ]: 0 : if (pp[-1] == '\015') pp--;
295 [ # # ]: 0 : if (pp == ppp)
296 : 0 : continue; /* False positive: separator in middle of line */
297 : :
298 [ # # # # : 0 : if (start && parts && nparts < max_parts) {
# # ]
299 : 0 : len = lengths[nparts] = pp-start;
300 [ # # ]: 0 : if (!(parts[nparts] = (char*)OPENSSL_malloc(len+1))) GOTO_ERR("no memory?");
301 : 0 : memcpy(parts[nparts], start, len);
302 : 0 : parts[nparts][len] = '\0'; /* Gratuitous nul termination */
303 : : }
304 : :
305 : 0 : nparts++;
306 [ # # ]: 0 : if (*p == '\015') p++; /* Skip CRLF */
307 [ # # ]: 0 : if (*p == '\012') p++; /* This is really mandatory */
308 : :
309 : 0 : start = p;
310 : : }
311 : 0 : return nparts;
312 : :
313 : : /* Called by: */
314 : 0 : err:
315 [ # # ]: 0 : if (parts) {
316 : : /* free what we have allocated so far */
317 [ # # ]: 0 : for (sep_len = 0; sep_len < nparts; sep_len++)
318 [ # # ]: 0 : if (parts[sep_len])
319 : 0 : OPENSSL_free(parts[sep_len]);
320 : : }
321 : 0 : return -1;
322 : : }
323 : :
324 : : /* Called by: main */
325 : : char*
326 : : mime_raw_entity(const char* text, const char* type)
327 : 7 : {
328 : : char* b;
329 [ - + ]: 7 : if (!(b = strdup("Content-type: "))) GOTO_ERR("no memory?");
330 [ + - ]: 7 : if (!(b = concat(b, type))) goto err;
331 [ + - ]: 7 : if (!(b = concat(b, CRLF CRLF))) goto err;
332 [ + - ]: 7 : if (!(b = concat(b, text))) goto err;
333 : 7 : return b;
334 : 0 : err:
335 : 0 : return NULL;
336 : : }
337 : :
338 : : /* Called by: main x3 */
339 : : char*
340 : : mime_base64_entity(const char* data, int len, const char* type)
341 : 3 : {
342 : : int n;
343 : : char* b64;
344 : : char* b;
345 [ - + ]: 3 : if (!(b = strdup("Content-type: "))) GOTO_ERR("no memory?");
346 [ + - ]: 3 : if (!(b = concat(b, type))) goto err;
347 [ + - ]: 3 : if (!(b = concat(b, CRLF CRLF))) goto err;
348 : :
349 : 3 : n = smime_base64(1, data, len, &b64);
350 [ - + ]: 3 : if (!b64) GOTO_ERR("no memory?");
351 [ + - ]: 3 : if (!(b = concat(b, b64))) goto err;
352 : 3 : return b;
353 : 0 : err:
354 : 0 : 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*
365 : : mime_canon(const char* s)
366 : 1 : {
367 : : char* d;
368 : : char* p;
369 : : int len;
370 : 1 : len = strlen(s);
371 : 1 : p = d = (char*)OPENSSL_malloc(len + len); /* Reserve spaces for CR's to be inserted. */
372 [ - + ]: 1 : if (!d) GOTO_ERR("no memory?");
373 : :
374 : : /* Scan and copy */
375 : :
376 [ + + ]: 571 : for (; *s; s++) {
377 [ + + + + ]: 1130 : if (s[0] != '\015' && s[0] != '\012')
378 : 560 : *(p++) = *s; /* pass thru */
379 : : else {
380 [ + + + - ]: 10 : if (s[0] == '\015' && s[1] == '\012') s++; /* already CRLF */
381 : 10 : *(p++) = '\015'; *(p++) = '\012';
382 : : }
383 : : }
384 : 1 : *(p++) = '\0';
385 : :
386 : : /* Shrink the buffer back to actual size (not very likely to fail) */
387 : :
388 : 1 : return (char*)OPENSSL_realloc(d, (int)p-(int)d);
389 : 0 : err:
390 : 0 : return NULL;
391 : : }
392 : :
393 : : /* EOF - smimemime.c */
|