]> git.ipfire.org Git - thirdparty/strongswan.git/blob - programs/pluto/pem.c
- import of strongswan-2.7.0
[thirdparty/strongswan.git] / programs / pluto / pem.c
1 /* Loading of PEM encoded files with optional encryption
2 * Copyright (C) 2001-2004 Andreas Steffen, Zuercher Hochschule Winterthur
3 *
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms of the GNU General Public License as published by the
6 * Free Software Foundation; either version 2 of the License, or (at your
7 * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
8 *
9 * This program is distributed in the hope that it will be useful, but
10 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
11 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12 * for more details.
13 *
14 * RCSID $Id: pem.c,v 1.4 2005/08/17 16:31:24 as Exp $
15 */
16
17 /* decrypt a PEM encoded data block using DES-EDE3-CBC
18 * see RFC 1423 PEM: Algorithms, Modes and Identifiers
19 */
20
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <unistd.h>
24 #include <errno.h>
25 #include <string.h>
26 #include <stddef.h>
27 #include <sys/types.h>
28
29 #include <freeswan.h>
30 #define HEADER_DES_LOCL_H /* stupid trick to force prototype decl in <des.h> */
31 #include <crypto/des.h>
32
33 #include "constants.h"
34 #include "defs.h"
35 #include "log.h"
36 #include "md5.h"
37 #include "whack.h"
38 #include "pem.h"
39
40 /*
41 * check the presence of a pattern in a character string
42 */
43 static bool
44 present(const char* pattern, chunk_t* ch)
45 {
46 u_int pattern_len = strlen(pattern);
47
48 if (ch->len >= pattern_len && strncmp(ch->ptr, pattern, pattern_len) == 0)
49 {
50 ch->ptr += pattern_len;
51 ch->len -= pattern_len;
52 return TRUE;
53 }
54 return FALSE;
55 }
56
57 /*
58 * compare string with chunk
59 */
60 static bool
61 match(const char *pattern, const chunk_t *ch)
62 {
63 return ch->len == strlen(pattern) &&
64 strncmp(pattern, ch->ptr, ch->len) == 0;
65 }
66
67 /*
68 * find a boundary of the form -----tag name-----
69 */
70 static bool
71 find_boundary(const char* tag, chunk_t *line)
72 {
73 chunk_t name = empty_chunk;
74
75 if (!present("-----", line))
76 return FALSE;
77 if (!present(tag, line))
78 return FALSE;
79 if (*line->ptr != ' ')
80 return FALSE;
81 line->ptr++; line->len--;
82
83 /* extract name */
84 name.ptr = line->ptr;
85 while (line->len > 0)
86 {
87 if (present("-----", line))
88 {
89 DBG(DBG_PARSING,
90 DBG_log(" -----%s %.*s-----",
91 tag, (int)name.len, name.ptr);
92 )
93 return TRUE;
94 }
95 line->ptr++; line->len--; name.len++;
96 }
97 return FALSE;
98 }
99
100 /*
101 * eat whitespace
102 */
103 static void
104 eat_whitespace(chunk_t *src)
105 {
106 while (src->len > 0 && (*src->ptr == ' ' || *src->ptr == '\t'))
107 {
108 src->ptr++; src->len--;
109 }
110 }
111
112 /*
113 * extracts a token ending with a given termination symbol
114 */
115 static bool
116 extract_token(chunk_t *token, char termination, chunk_t *src)
117 {
118 u_char *eot = memchr(src->ptr, termination, src->len);
119
120 /* initialize empty token */
121 *token = empty_chunk;
122
123 if (eot == NULL) /* termination symbol not found */
124 return FALSE;
125
126 /* extract token */
127 token->ptr = src->ptr;
128 token->len = (u_int)(eot - src->ptr);
129
130 /* advance src pointer after termination symbol */
131 src->ptr = eot + 1;
132 src->len -= (token->len + 1);
133
134 return TRUE;
135 }
136
137 /*
138 * extracts a name: value pair from the PEM header
139 */
140 static bool
141 extract_parameter(chunk_t *name, chunk_t *value, chunk_t *line)
142 {
143 DBG(DBG_PARSING,
144 DBG_log(" %.*s", (int)line->len, line->ptr);
145 )
146
147 /* extract name */
148 if (!extract_token(name,':', line))
149 return FALSE;
150
151 eat_whitespace(line);
152
153 /* extract value */
154 *value = *line;
155 return TRUE;
156 }
157
158 /*
159 * fetches a new line terminated by \n or \r\n
160 */
161 static bool
162 fetchline(chunk_t *src, chunk_t *line)
163 {
164 if (src->len == 0) /* end of src reached */
165 return FALSE;
166
167 if (extract_token(line, '\n', src))
168 {
169 if (line->len > 0 && *(line->ptr + line->len -1) == '\r')
170 line->len--; /* remove optional \r */
171 }
172 else /*last line ends without newline */
173 {
174 *line = *src;
175 src->ptr += src->len;
176 src->len = 0;
177 }
178 return TRUE;
179 }
180
181 /*
182 * decrypts a DES-EDE-CBC encrypted data block
183 */
184 static bool
185 pem_decrypt_3des(chunk_t *blob, chunk_t *iv, const char *passphrase)
186 {
187 MD5_CTX context;
188 u_char digest[MD5_DIGEST_SIZE];
189 u_char des_iv[DES_CBC_BLOCK_SIZE];
190 u_char key[24];
191 des_cblock *deskey = (des_cblock *)key;
192 des_key_schedule ks[3];
193 u_char padding, *last_padding_pos, *first_padding_pos;
194
195 /* Convert passphrase to 3des key */
196 MD5Init(&context);
197 MD5Update(&context, passphrase, strlen(passphrase));
198 MD5Update(&context, iv->ptr, iv->len);
199 MD5Final(digest, &context);
200
201 memcpy(key, digest, MD5_DIGEST_SIZE);
202
203 MD5Init(&context);
204 MD5Update(&context, digest, MD5_DIGEST_SIZE);
205 MD5Update(&context, passphrase, strlen(passphrase));
206 MD5Update(&context, iv->ptr, iv->len);
207 MD5Final(digest, &context);
208
209 memcpy(key + MD5_DIGEST_SIZE, digest, 24 - MD5_DIGEST_SIZE);
210
211 (void) des_set_key(&deskey[0], ks[0]);
212 (void) des_set_key(&deskey[1], ks[1]);
213 (void) des_set_key(&deskey[2], ks[2]);
214
215 /* decrypt data block */
216 memcpy(des_iv, iv->ptr, DES_CBC_BLOCK_SIZE);
217 des_ede3_cbc_encrypt((des_cblock *)blob->ptr, (des_cblock *)blob->ptr,
218 blob->len, ks[0], ks[1], ks[2], (des_cblock *)des_iv, FALSE);
219
220 /* determine amount of padding */
221 last_padding_pos = blob->ptr + blob->len - 1;
222 padding = *last_padding_pos;
223 first_padding_pos = (padding > blob->len)?
224 blob->ptr : last_padding_pos - padding;
225
226 /* check the padding pattern */
227 while (--last_padding_pos > first_padding_pos)
228 {
229 if (*last_padding_pos != padding)
230 return FALSE;
231 }
232
233 /* remove padding */
234 blob->len -= padding;
235 return TRUE;
236 }
237
238 /*
239 * optionally prompts for a passphrase before decryption
240 * currently we support DES-EDE3-CBC, only
241 */
242 static err_t
243 pem_decrypt(chunk_t *blob, chunk_t *iv, prompt_pass_t *pass, const char* label)
244 {
245 DBG(DBG_CRYPT,
246 DBG_log(" decrypting file using 'DES-EDE3-CBC'");
247 )
248 if (iv->len != DES_CBC_BLOCK_SIZE)
249 return "size of DES-EDE3-CBC IV is not 8 bytes";
250
251 if (pass == NULL)
252 return "no passphrase available";
253
254 /* do we prompt for the passphrase? */
255 if (pass->prompt && pass->fd != NULL_FD)
256 {
257 int i;
258 chunk_t blob_copy;
259 err_t ugh = "invalid passphrase, too many trials";
260
261 whack_log(RC_ENTERSECRET, "need passphrase for '%s'", label);
262
263 for (i = 0; i < MAX_PROMPT_PASS_TRIALS; i++)
264 {
265 int n;
266
267 if (i > 0)
268 whack_log(RC_ENTERSECRET, "invalid passphrase, please try again");
269
270 n = read(pass->fd, pass->secret, PROMPT_PASS_LEN);
271
272 if (n == -1)
273 {
274 err_t ugh = "read(whackfd) failed";
275
276 whack_log(RC_LOG_SERIOUS,ugh);
277 return ugh;
278 }
279
280 pass->secret[n-1] = '\0';
281
282 if (strlen(pass->secret) == 0)
283 {
284 err_t ugh = "no passphrase entered, aborted";
285
286 whack_log(RC_LOG_SERIOUS, ugh);
287 return ugh;
288 }
289
290 clonetochunk(blob_copy, blob->ptr, blob->len, "blob copy");
291
292 if (pem_decrypt_3des(blob, iv, pass->secret))
293 {
294 whack_log(RC_SUCCESS, "valid passphrase");
295 pfree(blob_copy.ptr);
296 return NULL;
297 }
298
299 /* blob is useless after wrong decryption, restore the original */
300 pfree(blob->ptr);
301 *blob = blob_copy;
302 }
303 whack_log(RC_LOG_SERIOUS, ugh);
304 return ugh;
305 }
306 else
307 {
308 if (pem_decrypt_3des(blob, iv, pass->secret))
309 return NULL;
310 else
311 return "invalid passphrase";
312 }
313 }
314
315 /* Converts a PEM encoded file into its binary form
316 *
317 * RFC 1421 Privacy Enhancement for Electronic Mail, February 1993
318 * RFC 934 Message Encapsulation, January 1985
319 */
320 err_t
321 pemtobin(chunk_t *blob, prompt_pass_t *pass, const char* label, bool *pgp)
322 {
323 typedef enum {
324 PEM_PRE = 0,
325 PEM_MSG = 1,
326 PEM_HEADER = 2,
327 PEM_BODY = 3,
328 PEM_POST = 4,
329 PEM_ABORT = 5
330 } state_t;
331
332 bool encrypted = FALSE;
333
334 state_t state = PEM_PRE;
335
336 chunk_t src = *blob;
337 chunk_t dst = *blob;
338 chunk_t line = empty_chunk;
339 chunk_t iv = empty_chunk;
340
341 u_char iv_buf[MAX_DIGEST_LEN];
342
343 /* zero size of converted blob */
344 dst.len = 0;
345
346 /* zero size of IV */
347 iv.ptr = iv_buf;
348 iv.len = 0;
349
350 while (fetchline(&src, &line))
351 {
352 if (state == PEM_PRE)
353 {
354 if (find_boundary("BEGIN", &line))
355 {
356 *pgp = FALSE;
357 state = PEM_MSG;
358 }
359 continue;
360 }
361 else
362 {
363 if (find_boundary("END", &line))
364 {
365 state = PEM_POST;
366 break;
367 }
368 if (state == PEM_MSG)
369 {
370 state = (memchr(line.ptr, ':', line.len) == NULL)?
371 PEM_BODY : PEM_HEADER;
372 }
373 if (state == PEM_HEADER)
374 {
375 chunk_t name = empty_chunk;
376 chunk_t value = empty_chunk;
377
378 /* an empty line separates HEADER and BODY */
379 if (line.len == 0)
380 {
381 state = PEM_BODY;
382 continue;
383 }
384
385 /* we are looking for a name: value pair */
386 if (!extract_parameter(&name, &value, &line))
387 continue;
388
389 if (match("Proc-Type", &name) && *value.ptr == '4')
390 encrypted = TRUE;
391 else if (match("DEK-Info", &name))
392 {
393 const char *ugh = NULL;
394 size_t len = 0;
395 chunk_t dek;
396
397 if (!extract_token(&dek, ',', &value))
398 dek = value;
399
400 /* we support DES-EDE3-CBC encrypted files, only */
401 if (!match("DES-EDE3-CBC", &dek))
402 return "we support DES-EDE3-CBC encrypted files, only";
403
404 eat_whitespace(&value);
405 ugh = ttodata(value.ptr, value.len, 16,
406 iv.ptr, MAX_DIGEST_LEN, &len);
407 if (ugh)
408 return "error in IV";
409
410 iv.len = len;
411 }
412 }
413 else /* state is PEM_BODY */
414 {
415 const char *ugh = NULL;
416 size_t len = 0;
417 chunk_t data;
418
419 /* remove any trailing whitespace */
420 if (!extract_token(&data ,' ', &line))
421 data = line;
422
423 /* check for PGP armor checksum */
424 if (*data.ptr == '=')
425 {
426 *pgp = TRUE;
427 data.ptr++;
428 data.len--;
429 DBG(DBG_PARSING,
430 DBG_log(" Armor checksum: %.*s", (int)data.len, data.ptr);
431 )
432 continue;
433 }
434
435 ugh = ttodata(data.ptr, data.len, 64,
436 dst.ptr, blob->len - dst.len, &len);
437 if (ugh)
438 {
439 DBG(DBG_PARSING,
440 DBG_log(" %s", ugh);
441 )
442 state = PEM_ABORT;
443 break;
444 }
445 else
446 {
447 dst.ptr += len;
448 dst.len += len;
449 }
450 }
451 }
452 }
453 /* set length to size of binary blob */
454 blob->len = dst.len;
455
456 if (state != PEM_POST)
457 return "file coded in unknown format, discarded";
458
459 if (encrypted)
460 return pem_decrypt(blob, &iv, pass, label);
461 else
462 return NULL;
463 }