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